/* ==========================================================================
   GRANITE

   Two tones, and one accent. Black and white for everything — and a single
   green that belongs to the coach's correction alone.

   THE RULE: the corrected value — the coach changing something — is the ONE
   thing on the site permitted to carry colour. It lands in the accent green.
   Nothing else is green: not a button, not a hover, not a heading. If the
   accent is used for decoration it stops meaning anything, and the meaning is
   the point. (The Invert button still flips the two tones; the accent is fixed.)

   Every neutral is derived from --fg with alpha, so the whole site still flips
   with two swaps; the accent stays put.
   ========================================================================== */

/* Self-hosted so the site makes no third-party request — the privacy promise
   has to hold for the marketing page too, not just the app. Both are variable
   fonts: one file each carries the whole weight axis. OFL 1.1, see fonts/OFL.txt. */
/* Instrument Serif (SIL OFL) — a high-contrast display serif, self-hosted.
   The serif was a system stack before this, which on a page where type carries
   this much of the argument is a compromise you feel without being able to name
   it. Latin subset only; 30KB for both cuts, and neither is on the critical
   path for reading. */
@font-face {
  font-family: "Instrument Serif";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("fonts/instrument-serif.woff2") format("woff2");
}

@font-face {
  font-family: "Instrument Serif";
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url("fonts/instrument-serif-italic.woff2") format("woff2");
}

@font-face {
  font-family: "Geist";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("fonts/geist.woff2") format("woff2");
}

@font-face {
  font-family: "Geist Mono";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("fonts/geist-mono.woff2") format("woff2");
}

:root {
  --bg: #000000;
  --fg: #FFFFFF;

  color-scheme: dark;

  /* Mix percentages differ per theme: sRGB is not perceptually linear, so the
     same ratio that clears 4.5:1 on black falls to 2:1 on white. These values
     are the lowest that hold AA for the 10px mono labels in each direction. */
  --dim:    color-mix(in srgb, var(--fg) 62%, var(--bg));
  --faint:  color-mix(in srgb, var(--fg) 48%, var(--bg));
  /* The rupture inverts to the opposite ground whichever theme is active, so
     its two ends are absolute rather than theme-relative. Muted tones are
     picked for ~7.5:1 against their own ground. */
  --rup-light-bg: #FFFFFF; --rup-light-fg: #000000; --rup-light-mut: #565656;
  --rup-dark-bg:  #000000; --rup-dark-fg:  #FFFFFF; --rup-dark-mut:  #9E9E9E;

  /* --data-warning glows on black and washes out on white, exactly as the
     accent does. Text takes the ink; fills keep the neon. */
  --warn-text: #FAB354;
  --danger-text: #FF7075;

  --line:   color-mix(in srgb, var(--fg) 15%, var(--bg));
  --line-2: color-mix(in srgb, var(--fg) 32%, var(--bg));

  /* ── The app's colour system — imported exactly from Granite/Views/Theme.swift.
     Two tones stay the base. Hue is permitted only in the two roles the app
     permits, and never as chrome:
       DATA — success / warning / danger, where the hue *is* the datum: the
              coach's correction, a fatigue caution, an active pain score.
              Rendered as a filled chip with fixed dark ink, always beside a
              word, so colour is never the only cue.
       TAGS — the five neon accents, as the small category / phase DOTS that
              colour-code a session at a glance. A dot beside a mono label,
              never a fill that would compete with the correction.
     All fixed across both inversions: a datum must read the same whichever way
     the page is flipped, so it sits outside the invertible palette. Light-theme
     visibility is handled per-token below (the neons are tuned to glow on
     black; on white they need deepening). Values are the exact sRGB the app
     ships. */
  --data-success: #7FF3CB;   /* Color(0.498,0.953,0.796) — the app's mint success green */
  --data-warning: #FAB354;   /* Color(0.98,0.70,0.33) — fatigue / back-off     */
  --data-danger:  #FF7075;   /* Color(1.00,0.44,0.46) — active pain            */
  --on-data:      #04140b;   /* fixed near-black ink for text on any data fill */

  /* The one green the product uses — a cool, fresh MINT, deliberately never
     anywhere near Spotify's saturated mid-green. The correction chip (the signature
     the eye lands on) and the positive data hue share it, dark ink on top. */
  --correction: #7FF3CB;
  --correction-text: #7FF3CB;  /* Light: this var is overridden per theme. Dark mode: bright for text on dark bg. Light mode: dark for text on light bg. */

  --cat-main:  #38EDD1;  /* main lift    · accumulation   (neon teal)   */
  --cat-acc:   #B887FF;  /* accessory    · deload         (neon purple) */
  --cat-cond:  #FFDB59;  /* conditioning · intensification (neon yellow)*/
  --cat-rehab: #FF70B8;  /* rehab / injury                (neon pink)   */
  --cat-warm:  #5CB8FF;  /* warm-up      · maintenance    (neon blue)   */

  --sans: "Geist", "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --mono: "Geist Mono", ui-monospace, "SF Mono", Menlo, monospace;

  /* The second voice — reserved for the handful of lines that carry the whole
     page's feeling in one sentence: a hero thesis, a headline, a campaign
     close. Geist does everything structural; this does the two or three
     moments that need to sound felt rather than built. System-only and
     unhinted on purpose: no font file to fetch keeps the self-hosted,
     no-third-party-request promise intact even for the second face. */
  --serif: "Instrument Serif", ui-serif, "New York", "Iowan Old Style", Georgia, serif;

  --measure: 36rem;
  --gutter:  clamp(1.25rem, 5vw, 4.5rem);
  --ease:    cubic-bezier(0.16, 1, 0.3, 1);
}

[data-theme="light"] {
  --bg: #FFFFFF;
  --fg: #000000;

  color-scheme: light;

  --dim:   color-mix(in srgb, var(--fg) 66%, var(--bg));
  --faint: color-mix(in srgb, var(--fg) 54%, var(--bg));

  --correction: #38EDD1;
  /* Was #0a9b8f, which measures 3.4:1 on white — under AA for small text.
     Deepened to clear 4.5:1 with margin while holding the same hue. */
  --correction-text: #0a7a71;   /* 5.2:1 on white */
  --warn-text: #7A4E00;         /* 5.6:1 on white */
  --danger-text: #B3261E;       /* 5.9:1 on white */

  /* The neons are tuned to glow on black; a solid dot of them washes out on
     white. Deepen each ~38% toward black for the light inversion — the hue
     survives, the dot stays legible. Data fills keep their ink contrast on
     their own colour, so they need no per-theme change. */
  --cat-main:  color-mix(in srgb, #38EDD1 62%, #000);
  --cat-acc:   color-mix(in srgb, #B887FF 62%, #000);
  --cat-cond:  color-mix(in srgb, #FFDB59 62%, #000);
  --cat-rehab: color-mix(in srgb, #FF70B8 62%, #000);
  --cat-warm:  color-mix(in srgb, #5CB8FF 62%, #000);
}

*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font-family: var(--sans);
  font-size: clamp(1rem, 0.95rem + 0.28vw, 1.125rem);
  line-height: 1.6;
  letter-spacing: -0.011em;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  accent-color: var(--fg);
  text-rendering: optimizeLegibility;
  /* Hang quotes and list marks into the margin so text blocks read optically
     flush — the same principle as the wordmark's hung sidebearing. */
  hanging-punctuation: first allow-end last;
}

::selection { background: var(--fg); color: var(--bg); }
a { color: inherit; }

/* Film grain: a faint monochrome noise lifts the flat black off the screen and
   gives the page an analog, printed tactility. Fixed and non-interactive, so it
   sits over everything without ever catching a click. */
body::after {
  content: "";
  position: fixed; inset: 0; z-index: 300; pointer-events: none;
  opacity: 0.06;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.82' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23g)'/%3E%3C/svg%3E");
}
[data-theme="light"] body::after { opacity: 0.035; }

/* A designed focus ring, not a browser default: a crisp 2px rule in the ink
   colour, offset so it reads as intentional on either theme. */
:focus-visible { outline: 2px solid var(--fg); outline-offset: 3px; }

/* Page navigations use view transitions too, so the four pages read as one
   surface: the wordmark, the toggle and the footer persist and morph between
   pages instead of hard-reloading. Enabled only where motion is welcome. */
@media (prefers-reduced-motion: no-preference) {
  @view-transition { navigation: auto; }
}

.wordmark  { view-transition-name: granite-wordmark; }
.invert    { view-transition-name: granite-invert; }
footer     { view-transition-name: granite-footer; }
.doc__back { view-transition-name: granite-docback; }

/* The invert toggle is the site's one interaction, so it gets treated as a
   gesture, not a fade: the new tone wipes across on a hard edge from the control
   in the top corner — a single decisive sweep, the way the coach flips the sheet.
   Scoped to its own transition type so it never bleeds onto a navigation (those
   get the quiet default crossfade plus the morphs above). During the wipe the
   shared names are dropped, so the whole page sweeps as one piece rather than
   leaving the wordmark and footer to crossfade on their own. */
:root:active-view-transition-type(invert) .wordmark,
:root:active-view-transition-type(invert) .invert,
:root:active-view-transition-type(invert) footer,
:root:active-view-transition-type(invert) .doc__back {
  view-transition-name: none;
}

:root:active-view-transition-type(invert)::view-transition-old(root) { animation: none; }

:root:active-view-transition-type(invert)::view-transition-new(root) {
  animation: granite-wipe 560ms var(--ease);
  z-index: 1;
}

@keyframes granite-wipe {
  from { clip-path: inset(0 0 0 100%); }
  to   { clip-path: inset(0 0 0 0); }
}

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

.wrap { max-width: 66rem; margin: 0 auto; padding-inline: var(--gutter); }
/* Was up to 10rem top and bottom — 320px of air between every section, which
   across sixteen bands is a fifth of the page height spent on nothing. */
.band { padding-block: clamp(3rem, 5.5vw, 5.5rem); border-top: 1px solid var(--line); }
.band--flush { border-top: 0; }
.band--tight { padding-block: clamp(2rem, 4vw, 3.25rem); }
.prose { max-width: var(--measure); }

/* Direction A — the marginal document grid. Content bands are indexed and offset
   right, so the page reads like a training log with numbered sections hanging in
   the margin. Wide screens only; below 64rem it collapses to the plain column. */
main { counter-reset: chapter; }
.band--doc { counter-increment: chapter; }

@media (min-width: 64rem) {
  .band--doc > .wrap {
    display: grid;
    grid-template-columns: 8rem minmax(0, 1fr);
    column-gap: clamp(1.5rem, 3vw, 3rem);
  }

  /* All real content — including the sheets — sits in the right column, clear of
     the rail so the sticky marker never overlaps it. */
  .band--doc > .wrap > * { grid-column: 2; }

  /* The running-index marker: the section number over its label, and it STICKS
     in the margin as you scroll its section — a live table of contents, the way
     a book runs its chapter header down the page. */
  .band--doc > .wrap::before {
    content: counter(chapter, decimal-leading-zero) "\A" attr(data-label);
    white-space: pre-line;
    grid-column: 1;
    grid-row: 1;
    align-self: start;
    position: sticky;
    top: clamp(1.5rem, 5vw, 2.75rem);
    font-family: var(--mono);
    font-size: 0.625rem;
    letter-spacing: 0.12em;
    line-height: 1.7;
    text-transform: uppercase;
    color: var(--faint);
  }
}

/* --- the skim path: the whole mechanic in one glance -------------------- */

.skim {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.5rem 0.9rem;
  font-family: var(--mono);
  font-size: clamp(0.8125rem, 0.74rem + 0.4vw, 1rem);
  letter-spacing: -0.01em;
}

.skim__step { color: var(--fg); }
.skim__arrow { color: var(--faint); }

/* On a narrow screen the steps stack and the arrows turn to point down. */
@media (max-width: 34rem) {
  .skim { flex-direction: column; align-items: flex-start; gap: 0.65rem; }
  .skim__arrow { transform: rotate(90deg); transform-origin: left center; margin-left: 0.2rem; }
}

/* Visible to assistive tech and search, off-screen for sighted users. Lets the
   wordmark stay the visual h1 while the h1 still carries a descriptive line. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
}

.skip {
  position: absolute;
  left: -9999px;
  top: 0;
}

.skip:focus {
  left: var(--gutter);
  top: 1rem;
  z-index: 20;
  font-family: var(--mono);
  font-size: 0.625rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  text-decoration: none;
  background: var(--bg);
  color: var(--fg);
  border: 1px solid var(--fg);
  padding: 0.75rem 1rem;
}

/* --- theme toggle ------------------------------------------------------- */

.invert {
  position: fixed;
  top: clamp(1rem, 2.5vw, 1.75rem);
  right: clamp(1rem, 2.5vw, 1.75rem);
  z-index: 10;
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  font-family: var(--mono);
  font-size: 0.625rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--dim);
  background: var(--bg);
  border: 1px solid var(--line-2);
  padding: 0.5rem 0.75rem;
  cursor: pointer;
  transition: color 200ms var(--ease), border-color 200ms var(--ease);
}

/* The visible chip stays tight; the hit area extends to the 44px Apple/WCAG
   minimum via an invisible overlay, so touch targets don't force the look. */
.invert::after {
  content: "";
  position: absolute;
  inset: -7px -3px;
}

.invert:hover { color: var(--fg); border-color: var(--fg); }

.invert i {
  width: 8px;
  height: 8px;
  background: var(--fg);
  font-style: normal;
  border: 1px solid var(--fg);
}

[data-theme="light"] .invert i { background: var(--bg); }

/* --- product masthead --------------------------------------------------- */
/* A slim top menu surfacing the three angles. The base stays black-and-white;
   each product just carries a different accent — mint (athletes), purple
   (coaches), clinical teal (clinic) — drawn from the app's own palette. The
   accent is the correction chip + the lit menu item on that product's pages. */
[data-product="coaches"] { --correction: #B887FF; }
[data-product="clinic"]  { --correction: #38EDD1; }

.masthead { padding-top: clamp(1rem, 2.5vw, 1.75rem); }
.masthead__nav {
  display: flex;
  flex-wrap: wrap;
  gap: 0.55rem 1.6rem;
  padding-right: 6.5rem;              /* clear the fixed invert button */
  font-family: var(--mono);
  font-size: 0.6875rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}
.mh {
  display: inline-flex;
  align-items: center;
  gap: 0.55em;
  white-space: nowrap;
  color: var(--faint);
  text-decoration: none;
  transition: color 180ms var(--ease);
}
/* Dots stay muted until an item is the current product (or hovered) — with
   only two items live, mint and teal sit close enough on the wheel that
   showing both at once reads as "almost the same colour" rather than two
   distinct products. One lit dot at a time removes the comparison entirely. */
.mh { --dot: var(--faint); }
.mh[data-p="athlete"] { --dot: #7FF3CB; }
.mh[data-p="coaches"] { --dot: #B887FF; }
.mh[data-p="clinic"]  { --dot: #38EDD1; }
.mh i { width: 6px; height: 6px; flex: none; background: var(--faint); transition: background 180ms var(--ease); }
.mh:hover i { background: var(--dot); }
.mh:hover { color: var(--fg); }
/* current product is lit; athletes is the default when no product is set */
.mh[data-p="athlete"] { color: var(--fg); }
.mh[data-p="athlete"] i { background: var(--dot); }
[data-product="coaches"] .mh[data-p="athlete"],
[data-product="clinic"]  .mh[data-p="athlete"] { color: var(--faint); }
[data-product="coaches"] .mh[data-p="athlete"] i,
[data-product="clinic"]  .mh[data-p="athlete"] i { background: var(--faint); }
[data-product="coaches"] .mh[data-p="coaches"],
[data-product="clinic"]  .mh[data-p="clinic"] { color: var(--fg); }
[data-product="coaches"] .mh[data-p="coaches"] i,
[data-product="clinic"]  .mh[data-p="clinic"] i { background: var(--dot); }

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

.hero {
  padding-block: clamp(6rem, 14vw, 11rem) clamp(4rem, 8vw, 7rem);
  border-bottom: 1px solid var(--line);
}

/* The negative inline start hangs the G's sidebearing into the gutter, so the
   stem aligns optically with the text below it rather than mathematically. */
.wordmark {
  font-size: clamp(4rem, 17.6vw, 15rem);
  font-weight: 700;
  line-height: 0.86;
  /* Open, not pinched: -0.02em lets the letters breathe — the T and E in
     particular need the air — while keeping the mark denser than default. */
  letter-spacing: -0.02em;
  text-transform: uppercase;
  margin: 0 0 0 -0.035em;
}

/* Optical kern for the I–T pair: two verticals with no sidebearing relief read
   tight even at even tracking, so the "TE" gets a hair of extra space before it.
   Wraps only "te" in the wordmark, so it never adds a line-break opportunity. */
.wm-kern { margin-left: 0.03em; }

/* Variable-axis intro. Geist carries its whole weight axis in one file, so the
   mark can arrive light and set to its full weight once, like stone settling.
   A single beat, not a loop. No-JS and reduced motion get the final weight with
   no animation — font-weight:700 above is the resting state either way. */
@keyframes wordmark-set {
  from { opacity: 0; font-variation-settings: "wght" 240; }
  to   { opacity: 1; font-variation-settings: "wght" 700; }
}

.js .wordmark.reveal { opacity: 0; transform: none; }

.js .wordmark.reveal.in {
  transition: none;
  animation: wordmark-set 1100ms var(--ease) both;
  animation-delay: var(--d, 120ms);
}

.kicker {
  font-family: var(--mono);
  font-size: 0.6875rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--faint);
  margin: 0 0 clamp(1.5rem, 3vw, 2.25rem);
}

.thesis {
  font-family: var(--serif);
  font-style: italic;
  font-size: clamp(1.25rem, 1rem + 1.2vw, 2rem);
  font-weight: 400;
  line-height: 1.32;
  letter-spacing: -0.008em;
  color: var(--dim);
  max-width: 24ch;
  margin: clamp(1.75rem, 4vw, 3rem) 0 0;
  text-wrap: balance;
}

/* The one word that has to land precisely breaks back to the structural sans,
   upright and bold, out of the felt italic line around it — the same range
   move DixonBaxi's serif/sans pairing runs, in a single sentence. */
.thesis strong {
  font-family: var(--sans);
  font-style: normal;
  color: var(--fg);
  font-weight: 600;
  letter-spacing: -0.02em;
}

.hero__sub {
  max-width: 34rem;
  color: var(--dim);
  margin: 1.5rem 0 0;
}

.hero__cta {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 1rem 1.5rem;
  margin-top: clamp(2rem, 4vw, 2.75rem);
}

.hero__cta-note {
  font-family: var(--mono);
  font-size: 0.6875rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--faint);
}

.hero__sheet {
  margin-top: clamp(3rem, 7vw, 5.5rem);
  max-width: 42rem;
}

/* The interactive week: plain outline toggles. Turning one on is the athlete's
   input; the coach's answer is the inverted chip that lands in the sheet — so
   inversion still means "the coach changed something", now triggered by you. */
.week {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.55rem;
  margin-bottom: 1.25rem;
}

.week__label {
  font-family: var(--mono);
  font-size: 0.625rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--faint);
  margin-right: 0.35rem;
}

.week__toggle {
  display: inline-flex;
  align-items: center;
  font-family: var(--mono);
  font-size: 0.6875rem;
  letter-spacing: 0.04em;
  color: var(--dim);
  background: var(--bg);
  border: 1px solid var(--line-2);
  padding: 0.5rem 0.8rem;
  cursor: pointer;
  transition: color 180ms var(--ease), border-color 180ms var(--ease);
}

/* The dot colour-codes the input the way the app tags a captured week: an
   activity is conditioning (yellow), a flagged joint is rehab (pink). The label
   stays mono — the dot carries the hue, never the text. */
.week__toggle::before {
  content: "";
  width: 6px;
  height: 6px;
  margin-right: 0.5rem;
  border: 1px solid var(--dot, currentColor);
  background: transparent;
  transition: background-color 180ms var(--ease), border-color 180ms var(--ease);
}

.week__toggle[data-cat="cond"]  { --dot: var(--cat-cond); }
.week__toggle[data-cat="rehab"] { --dot: var(--cat-rehab); }

.week__toggle:hover { color: var(--fg); border-color: var(--line); }
.week__toggle:hover::before { border-color: var(--dot, var(--fg)); }

.week__toggle[aria-pressed="true"] { color: var(--fg); border-color: var(--fg); }
.week__toggle[aria-pressed="true"]::before { background: var(--dot, var(--fg)); border-color: var(--dot, var(--fg)); }

/* Two-column hero on wide screens: the pitch on the left, the correction sheet
   beside it on the right, so the product's one demo lands in the first screen
   instead of below the fold. Below 64rem it stacks — lead, then sheet. The grid
   supplies the space after the wordmark, so the thesis drops its own top margin. */
.hero__grid { margin-top: clamp(1.75rem, 4vw, 3rem); }
.hero__grid .thesis { margin-top: 0; }

@media (min-width: 64rem) {
  .hero__grid {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.06fr);
    gap: clamp(2.5rem, 5vw, 5rem);
    align-items: start;
    margin-top: clamp(2.5rem, 5vw, 4rem);
  }
  .hero__sheet { margin-top: 0; max-width: none; }
}

/* --- type --------------------------------------------------------------- */

h1, h2, h3 { font-weight: 600; margin: 0; letter-spacing: -0.035em; text-wrap: balance; }
p { margin: 0 0 1.15em; text-wrap: pretty; }
p:last-child { margin-bottom: 0; }

.section-title {
  font-size: clamp(1.75rem, 1.15rem + 2.1vw, 3.125rem);
  line-height: 1.02;
  margin: 0 0 1.25rem;
  max-width: 18ch;
  text-transform: uppercase;
  letter-spacing: -0.05em;
}

.eyebrow {
  font-family: var(--mono);
  font-size: 0.6875rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--faint);
  margin: 0 0 1.75rem;
}

.lede {
  font-size: clamp(1.125rem, 1rem + 0.7vw, 1.625rem);
  line-height: 1.42;
  letter-spacing: -0.024em;
}

.muted { color: var(--dim); }

/* --- the sheet: the signature ------------------------------------------- */

.sheet {
  border: 1px solid var(--line-2);
  font-family: var(--mono);
  font-size: clamp(0.75rem, 0.68rem + 0.32vw, 0.875rem);
  line-height: 1.5;
  letter-spacing: 0;
  /* Real coaching-sheet data typography: figures line up in columns and the
     zero is slashed so it can't be misread as an O in a load or a set. */
  font-variant-numeric: tabular-nums slashed-zero;
}

.sheet__head {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
  font-size: 0.625rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--faint);
  padding: 1.1rem clamp(1.1rem, 3vw, 1.75rem);
  border-bottom: 1px solid var(--line);
}

.rows { padding-inline: clamp(1.1rem, 3vw, 1.75rem); }

.row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 0.35rem 1.5rem;
  align-items: center;
  padding-block: 0.95rem;
}

.row + .row { border-top: 1px solid var(--line); }
.row__name { color: var(--dim); }
.row__val { text-align: right; }

/* Per-row reasoning in the coach's review queue: full width beneath the row,
   in the same faint register as the coach's note. The override mark is a
   strong in --fg — emphasis, not inversion. */
.row__why {
  grid-column: 1 / -1;
  margin: 0.5rem 0 0.1rem;
  color: var(--faint);
  line-height: 1.5;
}

.row__why strong { color: var(--fg); font-weight: 500; }

/* The correction is marked up as <del>/<ins> — the semantics of "this was
   changed to that" — so assistive tech announces the old value as removed and
   the new as inserted. The visual is drawn by hand (the animated ::after strike
   and the chip), so the browser's default line-through / underline are cleared. */
.was, .now { text-decoration: none; }

/* the correction — struck, then inverted */
.was {
  position: relative;
  color: var(--faint);
  margin-right: 0.6rem;
  white-space: nowrap;
}

.was::after {
  content: "";
  position: absolute;
  left: -0.14em;
  right: -0.14em;
  top: 52%;
  height: 1px;
  background: var(--fg);
  transform-origin: left center;
}

.now {
  display: inline-block;
  background: var(--correction);
  color: var(--on-data);
  padding: 0.22em 0.5em;
  white-space: nowrap;
}

/* Without JS the correction is simply already made: struck and chipped. The
   animated start state only exists once we know we can finish it. */
.js .was::after { transform: scaleX(0); }
.js .now { clip-path: inset(0 100% 0 0); }
.js .note { opacity: 0; }

.sheet.play .was::after {
  animation: strike 360ms var(--ease) forwards;
  animation-delay: var(--t, 0ms);
}

/* The chip wipes in left to right, the way a coach writes it, rather than
   fading up like a notification. */
.sheet.play .now {
  animation: chip 440ms var(--ease) forwards;
  animation-delay: calc(var(--t, 0ms) + 300ms);
}

.note {
  margin: 0;
  padding: 1.6rem clamp(1.1rem, 3vw, 1.75rem) 1.85rem;
  border-top: 1px solid var(--line);
  font-family: var(--sans);
  font-size: clamp(0.9375rem, 0.9rem + 0.25vw, 1.0625rem);
  line-height: 1.55;
  letter-spacing: -0.011em;
}

.sheet.play .note {
  animation: settle 640ms var(--ease) forwards;
  animation-delay: calc(var(--t, 0ms) + 700ms);
}

.note__by {
  display: block;
  margin-top: 1rem;
  font-family: var(--mono);
  font-size: 0.625rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--faint);
}

/* ── App colour system, on the site ──────────────────────────────────────────
   Everything below borrows the app's exact rule: a hue rides a small dot or a
   filled data chip, always next to a word, and the coach's correction stays the
   only thing that ever inverts. */

/* Category dot — prefixes an exercise name, the way each row is tagged in the
   app. `--dot` is set per category; the name text stays in its mono grey. */
.row__name[data-cat] {
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
}
.row__name[data-cat]::before {
  content: "";
  flex: none;
  width: 7px;
  height: 7px;
  background: var(--dot, var(--fg));
}
.row__name[data-cat="main"]  { --dot: var(--cat-main); }
.row__name[data-cat="acc"]   { --dot: var(--cat-acc); }
.row__name[data-cat="cond"]  { --dot: var(--cat-cond); }
.row__name[data-cat="rehab"] { --dot: var(--cat-rehab); }
.row__name[data-cat="warm"]  { --dot: var(--cat-warm); }

/* Phase tag — a dotted marker inside a sheet head, tinted by periodisation
   phase exactly as the app tints its phase tag + timeline. */
.tag {
  display: inline-flex;
  align-items: center;
  gap: 0.4em;
}
.tag::before {
  content: "";
  width: 6px;
  height: 6px;
  background: var(--dot, currentColor);
}
.tag[data-phase="accumulation"]    { --dot: var(--cat-main); }
.tag[data-phase="intensification"] { --dot: var(--cat-cond); }
.tag[data-phase="deload"]          { --dot: var(--cat-acc); }
.tag[data-phase="maintenance"]     { --dot: var(--cat-warm); }

/* Data flags — the inputs that drove the week's changes, shown the app's way:
   a filled chip with fixed dark ink where the hue *is* the reading. Pain is
   danger, accumulated fatigue is warning, an off-platform session is the
   conditioning tag. Each carries a word, so the colour is never the only cue. */
.flags {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  padding: 0.9rem clamp(1.1rem, 3vw, 1.75rem);
  border-bottom: 1px solid var(--line);
}
.flag {
  display: inline-flex;
  align-items: center;
  gap: 0.4em;
  font-family: var(--mono);
  font-size: 0.625rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 0.28em 0.55em;
  background: var(--flag, var(--data-danger));
  color: var(--on-data);
}
.flag--pain { --flag: var(--data-danger); }
.flag--warn { --flag: var(--data-warning); }
/* An off-platform activity isn't a data reading but a category, so it reads as
   the conditioning tag: a dot, not a fill. */
.flag--cond {
  background: transparent;
  color: var(--dim);
  padding-inline: 0;
}
.flag--cond::before {
  content: "";
  width: 6px;
  height: 6px;
  background: var(--cat-cond);
}

/* Block timeline — a phase dot above each week, and a key beneath the strip,
   so the twelve weeks colour-code by phase the way the app's timeline does. */
.block__n {
  display: inline-flex;
  align-items: center;
  gap: 0.4em;
}
.block__wk[data-phase]::before { display: none; }
.block__n .dot {
  width: 6px;
  height: 6px;
  flex: none;
}
.block__wk[data-phase="accumulation"]    .dot { background: var(--cat-main); }
.block__wk[data-phase="intensification"] .dot { background: var(--cat-cond); }
.block__wk[data-phase="deload"]          .dot { background: var(--cat-acc); }
.block__wk[data-phase="maintenance"]     .dot { background: var(--cat-warm); }

.block__key {
  list-style: none;
  margin: 1.1rem 0 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem 1.4rem;
  font-family: var(--mono);
  font-size: 0.625rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--faint);
}
.block__key li {
  display: inline-flex;
  align-items: center;
  gap: 0.5em;
}
.block__key .dot { width: 7px; height: 7px; flex: none; }
.block__key [data-phase="accumulation"]    { background: var(--cat-main); }
.block__key [data-phase="intensification"] { background: var(--cat-cond); }
.block__key [data-phase="deload"]          { background: var(--cat-acc); }
.block__key [data-phase="maintenance"]     { background: var(--cat-warm); }

@keyframes strike { to { transform: scaleX(1); } }
@keyframes chip   { to { clip-path: inset(0 0 0 0); } }
@keyframes settle { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } }

/* --- reveal ------------------------------------------------------------- */

.js .reveal { opacity: 0; transform: translateY(14px); }

/* The hero is always above the fold, so it must never depend on an observer
   firing to become visible — that failure mode has cost this page its headline
   twice. It runs as an unconditional animation with a both-fill, which resolves
   to visible on its own whatever happens to the JavaScript below it. */
@media (prefers-reduced-motion: no-preference) {
  .js .hero .reveal { animation: reveal-rise 700ms var(--ease) var(--d, 0ms) both; }
  @keyframes reveal-rise { to { opacity: 1; transform: none; } }
}

.js .reveal.in {
  opacity: 1;
  transform: none;
  transition: opacity 750ms var(--ease), transform 750ms var(--ease);
  transition-delay: var(--d, 0ms);
}

/* The problem grid fills its 1px grid-gap with the container's own background
   (the hairline-divider trick) — fading a cell's opacity exposes that fill as
   a flat block behind it. Keep these cells opaque throughout; slide only. */
.js .probs .reveal { opacity: 1; }
.js .probs .reveal.in { transition: transform 750ms var(--ease); transition-delay: var(--d, 0ms); }

/* --- claims ------------------------------------------------------------- */

.claims { display: grid; border: 1px solid var(--line-2); }

@media (min-width: 54rem) {
  .claims { grid-template-columns: repeat(3, 1fr); }

  /* Scoped with .claims so it outranks the `.claim + .claim` top border
     declared later in this file. Unscoped, that rule wins on specificity tie
     and every card past the first draws a stray line along the container edge. */
  .claims .claim + .claim { border-top: 0; border-left: 1px solid var(--line); }

  /* Four recognitions sit 2×2. Three-up would strand the fourth card in a
     row of its own with the wrong two borders on it. */
  .claims--four { grid-template-columns: repeat(2, 1fr); }
  .claims--four .claim:nth-child(odd) { border-left: 0; }
  .claims--four .claim:nth-child(even) { border-left: 1px solid var(--line); }
  .claims--four .claim:nth-child(n + 3) { border-top: 1px solid var(--line); }

  /* Two claims in the default 3-track grid leaves a third, empty column-width
     of dead space inside the border — the default only fits sets of three. */
  .claims--two { grid-template-columns: repeat(2, 1fr); }
}

.claim { padding: clamp(1.5rem, 3.5vw, 2.5rem); }
.claim + .claim { border-top: 1px solid var(--line); }

.claim h3 {
  font-family: var(--mono);
  font-size: 0.625rem;
  font-weight: 400;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--faint);
  margin-bottom: 1.15rem;
}

.claim p { font-size: 0.9375rem; line-height: 1.6; color: var(--dim); }
.claim strong { color: var(--fg); font-weight: 500; }

/* --- spec: the feature list as a table, not an icon grid ---------------- */

.spec {
  border: 1px solid var(--line-2);
  margin-top: 2.75rem;
  counter-reset: spec;
}

.spec__row {
  display: grid;
  grid-template-columns: minmax(0, 13rem) minmax(0, 1fr);
  gap: 0.6rem 2.5rem;
  padding: clamp(1.35rem, 2.5vw, 1.85rem) clamp(1.1rem, 3vw, 1.75rem);
  counter-increment: spec;
}

/* 01–05, the coach's decisions indexed like a real sheet. */
.spec dt::before {
  content: counter(spec, decimal-leading-zero);
  display: block;
  margin-bottom: 0.55rem;
  color: var(--faint);
}

.spec__row + .spec__row { border-top: 1px solid var(--line); }

@media (max-width: 44rem) {
  .spec__row { grid-template-columns: 1fr; }
}

.spec dt {
  font-family: var(--mono);
  font-size: 0.625rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--faint);
  padding-top: 0.28em;
}

.spec dd {
  margin: 0;
  font-size: 0.9375rem;
  line-height: 1.6;
  color: var(--dim);
}

.spec dd strong { color: var(--fg); font-weight: 500; }

/* --- the block: twelve weeks as one training-log object ----------------- */

.block__strip {
  list-style: none;
  margin: 2.75rem 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(6, minmax(0, 1fr));
  border-top: 1px solid var(--line-2);
  border-left: 1px solid var(--line-2);
  font-family: var(--mono);
}

.block__wk {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  padding: clamp(0.9rem, 2vw, 1.35rem);
  border-right: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}

.block__n {
  font-size: 0.625rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--faint);
}

.block__load {
  font-size: clamp(0.8125rem, 0.72rem + 0.4vw, 1rem);
  color: var(--dim);
  font-variant-numeric: tabular-nums;
}

@media (max-width: 44rem) { .block__strip { grid-template-columns: repeat(3, 1fr); } }
@media (max-width: 22rem) { .block__strip { grid-template-columns: repeat(2, 1fr); } }

/* The two deloads are the coach's corrections, so they land as chips when the
   block scrolls into view. */
.js .block__strip.in .was::after { animation: strike 360ms var(--ease) 220ms forwards; }
.js .block__strip.in .now { animation: chip 440ms var(--ease) 540ms forwards; }

/* --- price -------------------------------------------------------------- */

.price { display: grid; gap: clamp(2rem, 5vw, 4rem); align-items: center; }

@media (min-width: 54rem) { .price { grid-template-columns: 1fr 1fr; } }

.tier { border-top: 1px solid var(--line); padding-top: 1.35rem; }
.tier + .tier { margin-top: 2.25rem; }

.tier__amount {
  font-size: clamp(2rem, 1.4rem + 2.6vw, 3.5rem);
  font-weight: 700;
  letter-spacing: -0.05em;
  line-height: 1;
  text-transform: uppercase;
  font-variant-numeric: tabular-nums;
}

.tier__label {
  display: block;
  font-family: var(--mono);
  font-size: 0.625rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--faint);
  margin-top: 0.8rem;
}

/* Two clean tiers. The market rate is an anchor in a faint sub-line, not a
   headline the reader has to decode. */
.tier__per {
  font-size: 0.34em;
  font-weight: 500;
  letter-spacing: -0.02em;
  color: var(--faint);
  text-transform: none;
}

.tier__sub {
  margin: 1rem 0 0;
  max-width: 24rem;
  font-size: 0.875rem;
  line-height: 1.55;
  color: var(--dim);
}

.price__close {
  font-size: clamp(1.125rem, 1rem + 0.5vw, 1.375rem);
  letter-spacing: -0.02em;
  color: var(--fg);
}

/* A single carried line — bigger than prose, quieter than a section title, no
   uppercase. Used to land the sentence a campaign lane turns on. */
.statement {
  font-family: var(--serif);
  font-style: italic;
  margin: 0;
  max-width: 22ch;
  font-size: clamp(1.5rem, 1.1rem + 2vw, 2.75rem);
  line-height: 1.12;
  letter-spacing: -0.008em;
  color: var(--fg);
  text-wrap: balance;
}

/* Founding-member banner — a promotion, kept in the site's register: a hairline
   band, mono, two tones. No colour, no urgency theatre; the offer and the count
   carry it. Reused above the homepage price and at the foot of a campaign page. */
.founding {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.75rem 1.5rem;
  border: 1px solid var(--line-2);
  padding: 1.15rem clamp(1.1rem, 3vw, 1.6rem);
  margin-bottom: clamp(2.5rem, 5vw, 4rem);
}
.founding__text {
  margin: 0;
  max-width: 46rem;
  font-family: var(--mono);
  font-size: clamp(0.75rem, 0.7rem + 0.3vw, 0.8125rem);
  line-height: 1.55;
  color: var(--dim);
}
.founding__eyebrow {
  display: block;
  margin-bottom: 0.4rem;
  font-size: 0.625rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--faint);
}
.founding__text strong { color: var(--fg); font-weight: 500; }
.founding__count {
  margin: 0;
  flex: none;
  font-family: var(--mono);
  font-size: 0.625rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--faint);
  white-space: nowrap;
}
.founding__count b { color: var(--fg); font-weight: 500; font-variant-numeric: tabular-nums; }

.cta {
  display: inline-block;
  font-family: var(--mono);
  font-size: 0.75rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  text-decoration: none;
  background: var(--bg);
  color: var(--fg);
  border: 1px solid var(--fg);
  padding: 1rem 1.9rem;
  transition: background-color 180ms var(--ease), color 180ms var(--ease);
}

/* the button fills, it does not invert — inversion belongs to the coach */
.cta:hover { background: var(--fg); color: var(--bg); }

/* --- form: one field, one button ---------------------------------------- */

.form { margin-top: clamp(2.25rem, 4vw, 3rem); }

.form__label {
  display: block;
  font-family: var(--mono);
  font-size: 0.625rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--faint);
  margin-bottom: 0.9rem;
}

.form__group { display: flex; flex-wrap: wrap; gap: 0.75rem; }

.form__input {
  flex: 1 1 15rem;
  min-width: 0;
  font-family: var(--mono);
  font-size: 0.8125rem;
  letter-spacing: 0.02em;
  color: var(--fg);
  background: var(--bg);
  border: 1px solid var(--line-2);
  border-radius: 0;
  padding: 1rem 1.1rem;
  transition: border-color 180ms var(--ease);
}

.form__input::placeholder { color: var(--faint); }
.form__input:hover { border-color: var(--fg); }
.form__input:focus-visible { outline: 1px solid var(--fg); outline-offset: 2px; }

/* the button fills, it does not invert — inversion belongs to the coach */
.form__submit { cursor: pointer; border-radius: 0; }

.form__ack {
  margin: 1.25rem 0 0;
  font-family: var(--mono);
  font-size: 0.625rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--fg);
}

.form__ack[hidden] { display: none; }

/* --- app mockups -------------------------------------------------------- */
/* Real screenshots from the shipping iOS app, in a minimal phone frame — a
   hairline bezel, rounded like the device, nothing skeuomorphic. The row
   scrolls on a phone and centres on a wide screen. */
.mockups__row {
  display: flex;
  gap: clamp(1rem, 3vw, 2.25rem);
  margin-top: clamp(2.5rem, 5vw, 3.5rem);
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  padding-bottom: 1.25rem;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}
.mockups__row::-webkit-scrollbar { display: none; }
@media (min-width: 60rem) { .mockups__row { justify-content: center; flex-wrap: wrap; } }

.mock { flex: none; scroll-snap-align: center; text-align: center; }
.phone {
  width: clamp(208px, 62vw, 256px);
  aspect-ratio: 620 / 1348;
  border: 1px solid var(--line-2);
  border-radius: 2.2rem;
  background: var(--bg);
  overflow: hidden;
  padding: 3px;
}
.phone img { width: 100%; height: 100%; object-fit: cover; border-radius: 2rem; display: block; }
.mock__cap {
  margin: 1rem 0 0;
  font-family: var(--mono);
  font-size: 0.625rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--faint);
}

/* --- clinic ------------------------------------------------------------- */
/* Granite Clinic leads with a headline, not the wordmark, and reads a touch
   warmer than the athlete pages — more air, the one clinical-teal accent. */
.brandline {
  margin: clamp(1.75rem, 4vw, 3rem) 0 0;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.35rem 0.9rem;
  font-family: var(--mono);
  font-size: 0.75rem;
  letter-spacing: 0.06em;
}
.brandline__name { color: var(--fg); text-transform: uppercase; letter-spacing: 0.12em; }
.brandline__on { color: var(--correction-text); }
.brandline__on::before { content: "· "; color: var(--faint); }

.hero__title {
  font-family: var(--serif);
  margin: 1rem 0 0;
  font-size: clamp(2.2rem, 1.3rem + 3.5vw, 4.25rem);
  font-weight: 400;
  line-height: 0.98;
  letter-spacing: -0.005em;
  text-wrap: balance;
}
.hero__cta-link { color: var(--correction-text); transition: color 180ms var(--ease); }
.hero__cta-link:hover { color: var(--fg); }

/* Text-headline heroes (Clinic, the design system) are top-anchored: the giant
   GRANITE wordmark hero earns its generous space, a headline shouldn't inherit
   it. This scopes only to heroes that lead with .hero__title — the wordmark
   heroes (home, coaches) are untouched. */
.hero:has(.hero__title) { padding-block: clamp(1.75rem, 3vw, 2.5rem) clamp(3rem, 6vw, 5rem); }
.hero:has(.hero__title) .kicker { margin-bottom: clamp(0.85rem, 1.8vw, 1.3rem); }
.hero:has(.hero__title) .brandline { margin-top: 0; }
.hero:has(.hero__title) .hero__grid { margin-top: clamp(0.85rem, 1.6vw, 1.25rem); }
@media (min-width: 64rem) {
  .hero:has(.hero__title) .hero__grid { margin-top: clamp(0.85rem, 1.6vw, 1.25rem); }
}

/* Problem grid: three numbered blocks, generous, hairline-separated. */
.probs {
  list-style: none; padding: 0;
  display: grid;
  gap: 1px;
  margin-top: clamp(2.5rem, 5vw, 3.5rem);
  background: var(--line);
  border: 1px solid var(--line);
}
@media (min-width: 52rem) { .probs { grid-template-columns: repeat(3, 1fr); } }
.prob { background: var(--bg); padding: clamp(1.75rem, 3.5vw, 2.75rem); }
.prob__n {
  margin: 0 0 1.4rem;
  font-family: var(--mono);
  font-size: 0.6875rem;
  letter-spacing: 0.14em;
  color: var(--faint);
}
.prob__n::before {
  content: ""; display: inline-block; width: 18px; height: 2px;
  background: var(--correction); vertical-align: 0.25em; margin-right: 0.75rem;
}
.prob h3 { font-size: clamp(1.125rem, 1rem + 0.5vw, 1.375rem); letter-spacing: -0.02em; line-height: 1.15; margin: 0 0 0.7rem; }
.prob p { color: var(--dim); font-size: 0.9375rem; line-height: 1.55; margin: 0; }

/* ── Clinic art direction: line-icon system + supporting components ────────── */
/* The proposition, up front: three gains, each proven by a real readout drawn in
   the sheet's own instrument language — data as the argument, not decoration. */
.benefits-band { padding-block: clamp(2.5rem, 5vw, 4rem); }
.benefits-band__lede { max-width: 34rem; margin: 0 0 clamp(2.5rem, 5vw, 3.5rem); }
.gains { list-style: none; margin: 0; padding: 0; display: grid; gap: clamp(3rem, 6vw, 4.25rem); }
@media (min-width: 58rem) {
  /* minmax(0, 1fr), not bare 1fr — a bare 1fr track still yields to a child's
     intrinsic min-content width, so a long unwrapped label (a status pill, a
     "target ≥90%" head) can widen one column and steal space from the other
     two, breaking the equal thirds. The 0 floor forces genuinely equal columns. */
  .gains { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: clamp(1.5rem, 2.5vw, 2.25rem); }
}
/* Each gain is its own object, with air around it and its own hairline — not
   three panes sharing dividers. Welded together there was nowhere for a hover
   to go: lifting a card only shifted it against the borders it was fused to. */
.gain {
  position: relative; margin: 0; display: flex; flex-direction: column;
  border: 1px solid var(--line); background: var(--bg);
  padding: clamp(2rem, 3.5vw, 3rem) clamp(1.6rem, 2.5vw, 2.25rem);
}

/* Hover: the accent rule draws across the card's own top edge — the same wipe
   the correction chip uses elsewhere, and the page's one signature gesture.
   No lift: the readouts are tabular, and shifting them a couple of pixels
   makes aligned figures look loose. */
.gain::before {
  content: ""; position: absolute; top: -1px; left: -1px; right: -1px; height: 2px;
  background: var(--correction); transform: scaleX(0); transform-origin: 0 50%;
  transition: transform .3s var(--ease);
}
@media (hover: hover) {
  .gain { transition: border-color .25s var(--ease); }
  .gain:hover { border-color: var(--line-2); }
  .gain:hover::before { transform: scaleX(1); }
}
.gain__idx { margin: 0 0 1.1rem; font-family: var(--mono); font-size: 0.625rem; letter-spacing: 0.14em; text-transform: uppercase; color: var(--correction-text); }
.gain__title { margin: 0 0 0.7rem; font-size: clamp(1.375rem, 1.1rem + 1vw, 1.75rem); font-weight: 600; letter-spacing: -0.025em; line-height: 1.05; text-wrap: balance; }
.gain__body { margin: 0; color: var(--dim); font-size: 0.9375rem; line-height: 1.55; }

/* The readout: a compact instrument that proves the gain, in the sheet's grammar
   — mono, tabular, hairline-ruled, with the correction chip as the live moment. */
.readout { margin: auto 0 0; padding-top: clamp(1.4rem, 3vw, 1.85rem); border: 0; font-family: var(--mono); font-variant-numeric: tabular-nums slashed-zero; }
.readout__head { display: flex; justify-content: space-between; gap: 1rem; font-size: 0.5625rem; letter-spacing: 0.14em; text-transform: uppercase; color: var(--faint); padding-bottom: 0.7rem; border-bottom: 1px solid var(--line-2); }
.readout__rows { }
.rr { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 0.5rem 1rem; align-items: center; padding: 0.7rem 0; font-size: 0.75rem; }
.rr + .rr { border-top: 1px solid var(--line); }
.rr__n { color: var(--dim); }
.rr__v { text-align: right; color: var(--fg); }
.rr__v .old { font-size: 0.9em; }
.rr__v .chip { font-size: 0.9em; }
.readout__foot { margin: 0; padding-top: 0.85rem; border-top: 1px solid var(--line-2); font-size: 0.625rem; letter-spacing: 0.04em; color: var(--faint); }

/* Example UI — a small standalone instrument in the readout's grammar, used
   where a section needs to show the shape of a screen rather than reproduce
   one. Deliberately a fragment: enough to recognise, too little to read as a
   claim about what the product looks like in full. */
.exui {
  margin: clamp(2.5rem, 5vw, 3.5rem) 0 0; padding: clamp(1.4rem, 2.5vw, 1.9rem);
  max-width: 34rem; border: 1px solid var(--line);
  font-family: var(--mono); font-variant-numeric: tabular-nums slashed-zero;
}
.exui__head {
  display: flex; justify-content: space-between; gap: 1rem;
  font-size: 0.5625rem; letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--faint); padding-bottom: 0.75rem; border-bottom: 1px solid var(--line-2);
}
.exui__foot {
  margin: 0; padding-top: 0.85rem; border-top: 1px solid var(--line-2);
  font-size: 0.625rem; letter-spacing: 0.04em; color: var(--faint); line-height: 1.5;
}
.exui .rr__v { white-space: nowrap; }
/* a citation reads as a line of its own, not a right-aligned value */
.exui .rr--cite { grid-template-columns: minmax(0, 1fr); }
.exui .rr--cite .rr__n { color: var(--fg); }
.exui .rr__sub { display: block; margin-top: 0.3rem; font-size: 0.6875rem; color: var(--faint); line-height: 1.45; white-space: normal; }
/* proportion bar: the length carries the comparison, the figure confirms it */
.exui__bar { display: inline-block; width: 74px; height: 5px; background: var(--line-2); margin-right: 0.75rem; vertical-align: middle; }
.exui__bar i { display: block; height: 100%; background: var(--correction); }
.exui__n { display: inline-block; min-width: 3ch; text-align: right; }
@media (max-width: 46rem) { .exui { padding: 1.15rem; } .exui__bar { width: 52px; } }

/* ── The threshold block ────────────────────────────────────────────────────
   The page's one operable moment. Everything here is sized so that nothing
   reflows as values change — the figures are tabular and every cell that can
   change has a reserved width, so dragging the slider costs no layout. */
.thresh {
  margin-top: clamp(2.5rem, 5vw, 3.5rem);
  display: grid; gap: clamp(1.5rem, 3vw, 2.25rem);
  font-variant-numeric: tabular-nums slashed-zero;
}
@media (min-width: 62rem) { .thresh { grid-template-columns: minmax(0, 1fr) minmax(0, 0.85fr); grid-template-areas: "control control" "panel cal"; } 
  .thresh__control { grid-area: control; } .thresh__panel { grid-area: panel; } .thresh__cal { grid-area: cal; } }

.thresh__control { border: 1px solid var(--line); padding: clamp(1.25rem, 2.5vw, 1.75rem); }
.thresh__label { display: block; font-family: var(--mono); font-size: 0.625rem; letter-spacing: 0.14em; text-transform: uppercase; color: var(--faint); margin-bottom: 1rem; }
.thresh__slider { display: flex; align-items: center; gap: 1.25rem; }
.thresh__out {
  font-family: var(--mono); font-size: clamp(1.25rem, 1rem + 1vw, 1.75rem); color: var(--correction-text);
  min-width: 5ch; text-align: right; flex: none;
}
.thresh__scale { display: flex; justify-content: space-between; margin: 0.6rem 0 0; font-family: var(--mono); font-size: 0.5625rem; letter-spacing: 0.1em; color: var(--faint); }

/* The control itself: a 44px-tall hit area with a visible track through it, so
   the touch target is honest without the track looking heavy. */
.thresh__slider input[type="range"] {
  flex: 1 1 auto; appearance: none; -webkit-appearance: none;
  height: 44px; background: transparent; cursor: pointer; margin: 0;
}
.thresh__slider input[type="range"]:focus-visible { outline: 2px solid var(--correction); outline-offset: 4px; }
.thresh__slider input[type="range"]::-webkit-slider-runnable-track { height: 2px; background: var(--line-2); }
.thresh__slider input[type="range"]::-moz-range-track { height: 2px; background: var(--line-2); }
.thresh__slider input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none; width: 18px; height: 18px; margin-top: -8px;
  background: var(--correction); border: 0; border-radius: 50%;
}
.thresh__slider input[type="range"]::-moz-range-thumb {
  width: 18px; height: 18px; background: var(--correction); border: 0; border-radius: 50%;
}

.thresh__panel, .thresh__cal { border: 1px solid var(--line); padding: clamp(1.25rem, 2.5vw, 1.75rem); }
.thresh__head { display: flex; justify-content: space-between; gap: 1rem; font-family: var(--mono); font-size: 0.5625rem; letter-spacing: 0.14em; text-transform: uppercase; color: var(--faint); padding-bottom: 0.9rem; border-bottom: 1px solid var(--line-2); }

.thresh__verdict {
  margin: 1rem 0; font-family: var(--mono); font-size: 0.6875rem; letter-spacing: 0.12em;
  text-transform: uppercase; display: inline-block; padding: 0.3rem 0.6rem;
}
.thresh__verdict--ok { background: var(--correction); color: var(--on-data); }
.thresh__verdict--held { background: var(--data-warning); color: var(--on-data); }

.thresh__rows { display: grid; }
.thresh__row { display: grid; grid-template-columns: minmax(0, 1fr) 4.5ch 6ch 5ch; gap: 0.75rem; align-items: center; padding: 0.7rem 0; font-family: var(--mono); font-size: 0.75rem; }
.thresh__row + .thresh__row { border-top: 1px solid var(--line); }
.thresh__n { color: var(--dim); }
.thresh__v { text-align: right; color: var(--fg); }
.thresh__t { text-align: right; color: var(--faint); }
.thresh__s { text-align: right; font-size: 0.625rem; letter-spacing: 0.08em; text-transform: uppercase; }
.thresh__s--met { color: var(--correction-text); }
.thresh__s--short { color: var(--data-warning); }

/* The cleared sentence names every criterion; the held one names only what
   failed, so it is shorter. Reserve the taller of the two or the panel jumps
   height every time the decision flips. */
.thresh__why { margin: 1rem 0 0; padding-top: 0.9rem; border-top: 1px solid var(--line-2); font-family: var(--mono); font-size: 0.6875rem; line-height: 1.6; color: var(--dim); min-height: calc(3 * 1.6em + 0.9rem); }
@media (max-width: 62rem) { .thresh__why { min-height: calc(5 * 1.6em + 0.9rem); } }
@media (max-width: 30rem) { .thresh__why { min-height: calc(7 * 1.6em + 0.9rem); } }
.thresh__why-k { color: var(--fg); }

.thresh__cal-head { margin: 0 0 1rem; font-family: var(--mono); font-size: 0.5625rem; letter-spacing: 0.14em; text-transform: uppercase; color: var(--faint); }
.thresh__band { display: grid; grid-template-columns: 7ch minmax(0, 1fr) 4ch; gap: 0.75rem; align-items: center; padding: 0.55rem 0.5rem; margin: 0 -0.5rem; font-family: var(--mono); font-size: 0.6875rem; color: var(--faint); }
.thresh__band-b { display: block; height: 5px; background: var(--line-2); }
.thresh__band-b i { display: block; height: 100%; background: var(--line-2); }
.thresh__band-v { text-align: right; }
/* the band the threshold currently sits in is the only one that carries colour */
.thresh__band.is-here { color: var(--fg); background: color-mix(in srgb, var(--correction) 8%, transparent); }
.thresh__band.is-here .thresh__band-b i { background: var(--data-danger); }
.thresh__cal-foot { margin: 1rem 0 0; padding-top: 0.9rem; border-top: 1px solid var(--line-2); font-family: var(--mono); font-size: 0.6875rem; line-height: 1.6; color: var(--dim); }
.thresh__cal-foot b { color: var(--fg); font-weight: 500; }

.thresh__note { margin: clamp(1.5rem, 3vw, 2rem) 0 0; font-family: var(--mono); font-size: 0.625rem; letter-spacing: 0.1em; text-transform: uppercase; color: var(--faint); }

@media (prefers-reduced-motion: no-preference) {
  .thresh__verdict, .thresh__band, .thresh__s { transition: background-color .18s var(--ease), color .18s var(--ease); }
}

/* ── The one scale rupture ──────────────────────────────────────────────────
   Exactly one on the page. A second would halve the first. Sized in vw with a
   hard ceiling so it stays a statement on a laptop and does not become absurd
   on a 5K display, and capped low enough that the longest word clears 320px. */
.rupture { padding-block: clamp(3rem, 7vw, 6rem); }
.rupture__lead {
  margin: 0 0 clamp(1rem, 2vw, 1.5rem); font-family: var(--mono);
  font-size: 0.6875rem; letter-spacing: 0.16em; text-transform: uppercase; color: var(--faint);
}
/* The serif is the human voice against the mono's machine voice, and it earns
   exactly three appearances: the hero, where the page states what it is; this
   line, where it states what it will not do; and a closing thesis. Anywhere
   else it reads as decoration. */
.rupture__line {
  margin: 0; font-family: var(--serif); font-size: clamp(3.25rem, 12.5vw, 12.5rem);
  line-height: 0.88; letter-spacing: -0.015em; font-weight: 400; text-wrap: balance;
}
.rupture__foot {
  margin: clamp(1.75rem, 3vw, 2.5rem) 0 0; max-width: 32rem;
  color: var(--dim); font-size: 0.9375rem; line-height: 1.55;
}

/* Inversion as the signature moment. The brand law reserves inversion for
   "the coach changed something", so it is spent here — on the one section that
   states what Granite will not do — rather than left on a utility toggle.

   Scoped to this band rather than flipping the document. A full-page inversion
   mid-scroll is disorienting on a phone and would put every component's
   contrast at risk at once; a single inverted band carries the same rupture
   and can actually be verified. Colour only — nothing moves, so a reader's
   place on the page is never lost. */
.rupture {
  --rup-bg: var(--bg);
  --rup-fg: var(--fg);
  --rup-mut: var(--dim);
  background: var(--rup-bg);
  color: var(--rup-fg);
}
.rupture.is-inverted {
  --rup-bg: var(--rup-light-bg);
  --rup-fg: var(--rup-light-fg);
  --rup-mut: var(--rup-light-mut);
}
[data-theme="light"] .rupture.is-inverted {
  --rup-bg: var(--rup-dark-bg);
  --rup-fg: var(--rup-dark-fg);
  --rup-mut: var(--rup-dark-mut);
}
.rupture .rupture__line { color: var(--rup-fg); }
/* The muted tone is set per state rather than mixed from the two ends: a
   color-mix whose arguments are themselves custom properties resolved to
   transparent here, which reads as invisible text rather than a failed rule. */
.rupture .rupture__lead,
.rupture .rupture__foot { color: var(--rup-mut); }
@media (prefers-reduced-motion: no-preference) {
  .rupture { transition: background-color 380ms var(--ease), color 380ms var(--ease); }
  .rupture .rupture__lead,
  .rupture .rupture__foot,
  .rupture .rupture__line { transition: color 380ms var(--ease); }
}

/* ── the spine ──────────────────────────────────────────────────────────────
   The page's one persistent spatial idea, and it is the product's own logic
   turned on the reader: a vertical rule down the left margin with a node at
   every section, filling as you pass each one. The page progresses on criteria
   met, exactly as a patient does.

   It reuses the wrap's existing data-label rather than inventing a second
   navigation — the labels were already there, sitting in the margin doing
   nothing but decorating. This connects them.

   Hidden below the width where the margin exists at all, because a spine
   crammed against the text is worse than no spine. */
.spine {
  position: fixed; left: clamp(1rem, 2.4vw, 2.25rem); top: 50%; translate: 0 -50%;
  z-index: 40; display: none; pointer-events: none;
}
@media (min-width: 78rem) { .spine { display: block; } }
@media (prefers-reduced-motion: reduce) { .spine__fill { transition: none; } }

.spine__track {
  position: relative; width: 1px; height: min(52vh, 30rem);
  background: var(--line-2);
}
.spine__fill {
  position: absolute; inset: 0 0 auto 0; width: 1px; height: 0;
  background: var(--correction); transition: height .45s var(--ease);
}
.spine__node {
  position: absolute; left: -3px; width: 7px; height: 7px; translate: 0 -50%;
  border: 1px solid var(--line-2); border-radius: 50%; background: var(--bg);
  transition: background .3s var(--ease), border-color .3s var(--ease), scale .3s var(--ease);
}
.spine__node.is-past { border-color: var(--correction); background: var(--correction); }
.spine__node.is-here { border-color: var(--correction); background: var(--bg); scale: 1.75; }
.spine__label {
  position: absolute; left: 1rem; translate: 0 -50%; white-space: nowrap;
  font-family: var(--mono); font-size: 0.5rem; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--faint);
  opacity: 0; transition: opacity .3s var(--ease), color .3s var(--ease);
}
.spine__node.is-here + .spine__label { opacity: 1; color: var(--fg); }

/* The entry. The spine draws down before anything settles, so the page's
   structure arrives before its content — the same order the product asks a
   clinician to work in. One sequence, on load, never repeated. */
@media (prefers-reduced-motion: no-preference) {
  .js .spine__track { animation: spine-draw 900ms var(--ease) 240ms both; transform-origin: 50% 0; }
  @keyframes spine-draw { from { scale: 1 0; opacity: 0; } to { scale: 1 1; opacity: 1; } }
  .js .spine__node { animation: spine-node 400ms var(--ease) both; }
  @keyframes spine-node { from { opacity: 0; } to { opacity: 1; } }
}

/* A delivered photograph keeps the placeholder's frame discipline: same border,
   same aspect reservation, so swapping one in moves nothing. Portraits are
   held to a column rather than stretched across the text measure. */
.photo__frame img { width: 100%; height: auto; display: block; }
.photo[data-shot="waiting"] .photo__frame,
.photo[data-shot="effort"] .photo__frame { border: 1px solid var(--line); overflow: hidden; }
/* 24rem, not 30: the source frames are 768px wide, so a 384px column uses them
   at exactly 2x and they stay sharp on a retina screen. Sizing the frame to the
   file is the honest fix — the alternative is displaying a soft image and
   hoping nobody looks closely. */
.photo[data-shot="waiting"], .photo[data-shot="effort"],
.photo[data-shot="patient-portrait"], .photo[data-shot="clinician-portrait"],
.photo[data-shot="athome"] { max-width: 24rem; }
/* the frame must reserve the shot's own ratio, or the column collapses the
   image to whatever height it can get before the file lands */
.photo[data-shot="waiting"] .photo__frame,
.photo[data-shot="effort"] .photo__frame,
.photo[data-shot="patient-portrait"] .photo__frame,
.photo[data-shot="clinician-portrait"] .photo__frame,
.photo[data-shot="athome"] .photo__frame { aspect-ratio: 2 / 3; }

/* The patient and clinician portraits only make their argument together: the
   identical light, distance and crop are the point, and either one alone is
   just a headshot. Two up, never wrapped apart. */
.photo-row--pair { grid-template-columns: repeat(2, minmax(0, 1fr)); max-width: 34rem; }
.photo-row--pair .photo { max-width: none; }

/* ── the portrait series ────────────────────────────────────────────────────
   The art direction is people, shot identically. Patients and clinicians get
   the same light, the same distance and the same crop, so the page never
   arranges them as expert-and-subject — which is the product's argument made
   in an editing decision rather than a sentence.

   Each portrait carries a data line: who, what week, what they are waiting on.
   That is the pairing the whole brand rests on — a person and their number —
   and it is the reason this cannot be replaced with stock. */
.photo-row {
  display: grid; gap: clamp(1rem, 2vw, 1.75rem);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr));
  margin: clamp(2rem, 4vw, 3rem) 0;
}
.photo-row .photo { margin: 0; max-width: none; }
.photo-row .photo__frame { aspect-ratio: 2 / 3; border: 1px solid var(--line); }
.photo__cap b {
  display: block; margin-bottom: 0.25rem; color: var(--fg); font-weight: 400;
  letter-spacing: 0.1em; text-transform: uppercase; font-size: 0.625rem;
}
.photo__brief strong { color: var(--fg); font-weight: 500; }

/* A portrait is taller than it is wide, so the brief has to sit tighter in it. */
.photo--ph .photo__brief { padding: clamp(1rem, 2.5vw, 1.75rem); }

/* ── photography placeholders ───────────────────────────────────────────────
   Standing in for shots not taken yet. Each carries its own brief so the frame
   is not a grey box but a request: what is in it, how it is lit, why it is
   here. Swap the placeholder for an <img> and the layout does not move — the
   aspect ratio is declared, so nothing reflows when the real frame lands.

   Treatment for all of them: available light, muted, high contrast, no gloss,
   no eye contact with camera, nobody smiling at a clipboard. The materials of
   the work rather than a stock idea of care. */
/* A figure between a heading and its argument needs to be an object, not a
   line in the flow — without this the caption butts straight into the first
   paragraph and reads as part of it. */
.photo { margin: clamp(2rem, 4vw, 3rem) 0 clamp(2.25rem, 4.5vw, 3.25rem); }
.photo__cap { margin-top: 1rem; }

.photo--ph .photo__frame {
  position: relative; display: grid; place-items: center;
  background: color-mix(in srgb, var(--fg) 4%, var(--bg));
  border: 1px dashed var(--line-2);
  aspect-ratio: var(--ar, 16 / 9);
}
.photo__brief {
  max-width: 30rem; padding: clamp(1.5rem, 4vw, 2.5rem); text-align: left;
  font-family: var(--mono); color: var(--dim);
}
.photo__brief b {
  display: block; margin-bottom: 0.75rem; font-size: 0.5625rem; letter-spacing: 0.16em;
  text-transform: uppercase; color: var(--correction-text); font-weight: 400;
}
.photo__brief p { margin: 0 0 0.6rem; font-size: 0.75rem; line-height: 1.65; }
.photo__brief p:last-child { margin-bottom: 0; }
.photo__brief .spec { color: var(--dim); font-size: 0.6875rem; }

/* full-bleed variant — the page's breathing room, and one of the breaks in
   the band rhythm */
.photoband--bleed { padding-block: 0; }
.photoband--bleed .wrap { max-width: 84rem; }
.photoband--bleed .photo__cap {
  max-width: 68rem; margin-inline: auto;
  padding-inline: clamp(1.25rem, 5vw, 4rem); padding-top: 1rem;
}

/* ── instrument hierarchy ───────────────────────────────────────────────────
   Every instrument on this page was the same object: hairline box, mono head,
   rows. That leaves a reader no way to tell a passing proof from the thing
   they are meant to touch. Three tiers instead:

   inline   — sits in the text column, no box, a rule above it. A footnote you
              can read. Used where the evidence supports a sentence.
   section  — the default .exui. A discrete object beside its argument.
   primary  — reserved for the threshold block, the page's one operable
              moment. More air, larger type, and it owns its band. */

.exui--inline {
  margin: 1.5rem 0 0; padding: 1rem 0 0; max-width: 34rem;
  border: 0; border-top: 1px solid var(--line-2);
}
.exui--inline .exui__head { border-bottom: 0; padding-bottom: 0.5rem; }

/* primary: the interactive block reads as the main event rather than another card */
.thresh { margin-top: clamp(3rem, 6vw, 4.5rem); gap: clamp(2rem, 4vw, 3rem); }
.thresh__control { padding: clamp(1.75rem, 3.5vw, 2.5rem); border-color: var(--line-2); }
.thresh__label { font-size: 0.6875rem; color: var(--dim); }
.thresh__out { font-size: clamp(1.75rem, 1.2rem + 2vw, 2.75rem); }
.thresh__panel, .thresh__cal { padding: clamp(1.75rem, 3.5vw, 2.5rem); border-color: var(--line-2); }
.thresh__verdict { font-size: 0.8125rem; padding: 0.45rem 0.9rem; }
.thresh__row { font-size: 0.8125rem; padding: 0.85rem 0; }

/* Icons are thin line-work; stroke follows currentColor so they invert cleanly
   and take the teal accent wherever colour is set on the parent. */
.ic {
  width: 1.25em; height: 1.25em; flex: none;
  fill: none; stroke: currentColor; stroke-width: 1.6;
  stroke-linecap: round; stroke-linejoin: round;
}

/* Problem cards lead with a teal icon; four sit in a 2×2 grid, the numeral
   drops back to a faint index. */
@media (min-width: 46rem) { .probs--4 { grid-template-columns: repeat(2, 1fr); } }

/* Capability strip — the answer to the guidance gap, three iconned lines. */
.caps { margin-top: clamp(2.5rem, 5vw, 3.5rem); }
.caps__lead { margin: 0 0 1.6rem; font-size: clamp(1.05rem, 0.98rem + 0.4vw, 1.375rem); letter-spacing: -0.015em; }
.caps__list { list-style: none; margin: 0; padding: 0; display: grid; gap: 1px; background: var(--line); border: 1px solid var(--line); }
/* The list carries no top margin because inside .caps it follows .caps__lead,
   which already sets the gap below itself. Where it follows a prose block
   instead, nothing separates the two and the cards butt into the paragraph. */
.prose + .caps__list { margin-top: clamp(2rem, 4vw, 3rem); }
@media (min-width: 52rem) { .caps__list { grid-template-columns: repeat(3, 1fr); } }
.cap { background: var(--bg); padding: clamp(1.5rem, 3vw, 2rem); }
.cap__n { display: block; margin-bottom: 1rem; font-family: var(--mono); font-size: 0.6875rem; letter-spacing: 0.14em; text-transform: uppercase; color: var(--correction-text); }
.cap h4 { margin: 0 0 0.4rem; font-size: 1.0625rem; letter-spacing: -0.01em; }
.cap p { margin: 0; color: var(--dim); font-size: 0.9rem; line-height: 1.5; }

/* Loop nodes: an editorial number in a teal-ringed marker — no icon, the
   numeral carries it. */
.flow__n { width: clamp(2.5rem, 5.5vw, 3rem); height: clamp(2.5rem, 5.5vw, 3rem); font-size: clamp(1rem, 0.9rem + 0.4vw, 1.1875rem); font-weight: 500; }
.flow__step:not(:last-child)::before {
  left: calc((clamp(2.5rem, 5.5vw, 3rem) - 1px) / 2);
  top: clamp(2.5rem, 5.5vw, 3rem);
}

/* The loop closes — a dashed return marker with the return glyph in teal. */
.flow__close {
  display: flex; gap: 1rem; align-items: center; margin: 0.25rem 0 0;
  padding: 1.2rem clamp(1.1rem, 3vw, 1.6rem);
  border: 1px dashed var(--line-2); color: var(--dim);
  font-size: 0.9375rem; line-height: 1.5; max-width: 47rem;
}
.flow__return { flex: none; font-size: 1.5rem; line-height: 1; color: var(--correction); }

/* Patient-phone action rows — the two taps that make the plan 'guidance'. */
.pv-actions { display: flex; flex-wrap: wrap; gap: 0.5rem; padding: 0.9rem clamp(1.1rem, 3vw, 1.75rem) 1.2rem; border-top: 1px solid var(--line); }
.pv-action { display: inline-flex; align-items: center; gap: 0.5em; font-family: var(--mono); font-size: 0.625rem; letter-spacing: 0.06em; text-transform: uppercase; color: var(--dim); border: 1px solid var(--line-2); padding: 0.42em 0.7em; }
.pv-action .ic { width: 15px; height: 15px; color: var(--correction); }

/* Real app screenshots — reuses the shared .mockups__row / .phone frame. */
.appshots { margin-top: clamp(2.75rem, 5.5vw, 4.25rem); }
.appshots__lead { margin: 0; max-width: 46rem; color: var(--dim); font-size: 0.9375rem; line-height: 1.55; }
.appshots__tag { display: inline-block; margin-right: 0.6rem; font-family: var(--mono); font-size: 0.5625rem; letter-spacing: 0.14em; text-transform: uppercase; color: var(--on-data); background: var(--correction); padding: 0.28em 0.5em; }

/* The dashboard, in the flat, un-skeuomorphic frame a desktop screen earns —
   a hairline border, sharp corners, no device chrome to fake. Two shots,
   full width, stacked; the screenshots carry their own real UI chrome so
   nothing else needs to dress them up. */
.dashshots { margin-top: clamp(3rem, 6vw, 4.5rem); }
.dash__row { display: flex; flex-direction: column; gap: clamp(2rem, 4vw, 3rem); margin-top: clamp(2.5rem, 5vw, 3.5rem); }
.dashfig { margin: 0; }
.dash__frame { border: 1px solid var(--line-2); overflow: hidden; background: var(--bg); }
.dash__frame img { width: 100%; height: auto; display: block; }
.dash__cap { margin: 1rem 0 0; font-family: var(--mono); font-size: 0.625rem; letter-spacing: 0.14em; text-transform: uppercase; color: var(--faint); }

/* Reasoning trace — the deterministic 'why', drawn as a rule chain. */
.trace { margin: clamp(2.5rem, 5vw, 3.5rem) 0 0; border: 1px solid var(--line-2); max-width: 46rem; }
.trace__head { display: flex; align-items: center; gap: 0.6rem; margin: 0; padding: 1rem clamp(1.1rem, 3vw, 1.6rem); border-bottom: 1px solid var(--line); font-family: var(--mono); font-size: 0.625rem; letter-spacing: 0.12em; text-transform: uppercase; color: var(--faint); }
.trace__head .ic { width: 18px; height: 18px; color: var(--correction); }
.trace__chain { padding: clamp(1.2rem, 3vw, 1.6rem); font-family: var(--mono); font-size: clamp(0.8rem, 0.74rem + 0.3vw, 0.9rem); }
.trace__out { margin: 0 0 0.9rem; display: flex; flex-wrap: wrap; align-items: center; gap: 0.7rem; }
.trace__out span { color: var(--dim); }
.trace__step { margin: 0.5rem 0 0; color: var(--dim); position: relative; padding-left: 1.5rem; }
.trace__step::before { content: "\2191"; position: absolute; left: 0.15rem; color: var(--faint); }
.trace__lbl { display: inline-block; min-width: 4.5rem; color: var(--faint); text-transform: uppercase; letter-spacing: 0.08em; }
.trace__foot { margin: 0; padding: 1.1rem clamp(1.1rem, 3vw, 1.6rem) 1.3rem; border-top: 1px solid var(--line); font-family: var(--sans); font-size: 0.9rem; line-height: 1.5; color: var(--dim); }
/* A standalone teal chip (not the animated correction chip, which needs a .sheet). */
.chip { display: inline-block; background: var(--correction); color: var(--on-data); padding: 0.22em 0.5em; }

/* Boundary headings gain an icon: teal shield for 'does', faint lock for 'never'. */
.boundary__col h3 { display: flex; align-items: center; gap: 0.55rem; }
.boundary__col h3 .ic { width: 18px; height: 18px; color: var(--correction); }
.boundary__col:last-child h3 .ic { color: var(--faint); }

/* The loop: five numbered steps, joined by a hairline that runs through the
   teal markers — the fifth step feeds back, so its marker gets a ring. */
.flow { list-style: none; margin: clamp(2.5rem, 5vw, 3.75rem) 0 0; padding: 0; }
.flow__step { display: flex; gap: clamp(1.1rem, 2.5vw, 1.75rem); padding-bottom: clamp(2rem, 4vw, 2.75rem); position: relative; }
.flow__step:not(:last-child)::before {
  content: ""; position: absolute; left: calc((clamp(2.2rem, 5vw, 2.6rem) - 1px) / 2);
  top: clamp(2.2rem, 5vw, 2.6rem); bottom: 0; width: 1px; background: var(--line-2);
}
.flow__n {
  flex: none;
  width: clamp(2.2rem, 5vw, 2.6rem); height: clamp(2.2rem, 5vw, 2.6rem);
  display: grid; place-items: center;
  font-family: var(--mono); font-size: 0.9375rem; font-variant-numeric: tabular-nums;
  color: var(--correction);
  border: 1px solid var(--correction);
  border-radius: 50%;
  background: var(--bg);
}
.flow__step--loop .flow__n { box-shadow: 0 0 0 4px color-mix(in srgb, var(--correction) 22%, transparent); }
.flow__body { padding-top: 0.35rem; }
.flow__body h3 { font-size: clamp(1.125rem, 1rem + 0.4vw, 1.375rem); letter-spacing: -0.02em; margin: 0 0 0.55rem; }
.flow__body p { color: var(--dim); line-height: 1.6; max-width: 46rem; margin: 0; }

/* The evidence: three figures that carry the market case. The number leads in
   teal; the claim sits under it; the sources are a quiet mono footnote. */
.stats {
  display: grid; gap: 1px;
  background: var(--line);
  border: 1px solid var(--line);
  margin: clamp(2.5rem, 5vw, 3.75rem) 0 0;
}
@media (min-width: 52rem) { .stats { grid-template-columns: repeat(3, 1fr); } }
.stat { background: var(--bg); padding: clamp(1.75rem, 4vw, 2.5rem); border-top: 2px solid var(--correction); }
.stat__fig {
  margin: 0 0 0.9rem;
  font-size: clamp(2.25rem, 1.6rem + 2.6vw, 3.25rem);
  font-weight: 600; letter-spacing: -0.03em; line-height: 1;
  font-variant-numeric: tabular-nums;
  color: var(--fg);
}
.stat__claim { margin: 0; color: var(--dim); font-size: 0.9375rem; line-height: 1.55; }
.stats__src {
  margin: 1.5rem 0 0; max-width: 52rem;
  font-family: var(--mono); font-size: 0.6875rem; line-height: 1.7;
  letter-spacing: 0.01em; color: var(--faint);
}

/* ── The product, both sides of the loop ─────────────────────────────────────
   A patient's phone (what they carry and log) beside the clinician's board
   (the between-visits view). Both are drawn in the site's own sheet language —
   no screenshots, because the pilot is still being built. */
.loopview {
  margin: clamp(2.5rem, 5vw, 3.75rem) 0 0;
  display: grid; gap: clamp(2.5rem, 5vw, 3.5rem);
}
.loopview__side { margin: 0; }
.loopview__side figcaption {
  margin-top: 1.1rem; color: var(--dim);
  font-size: 0.9375rem; line-height: 1.5; max-width: 44rem;
}
/* Patient side: the phone sits next to its caption like an annotated screen;
   the board side runs full width so its five columns have room to breathe. */
@media (min-width: 44rem) {
  .loopview__side--phone {
    display: grid; grid-template-columns: minmax(0, 300px) minmax(0, 1fr);
    gap: 2.5rem; align-items: center;
  }
  .loopview__side--phone figcaption { margin-top: 0; }
}

/* A restrained phone frame: rounded shell, a speaker pill, the sheet inside with
   its own border removed so the frame is the only edge. */
.phoneframe {
  position: relative;
  max-width: 340px;
  padding: 1.6rem 0.55rem 0.9rem;
  border: 1px solid var(--line-2);
  border-radius: 2rem;
  background: var(--bg);
}
.phoneframe::before {
  content: ""; position: absolute; top: 0.75rem; left: 50%; transform: translateX(-50%);
  width: 34px; height: 4px; border-radius: 2px; background: var(--line-2);
}
.phoneframe .sheet { border: none; }

/* The caseload board: a five-column register that collapses to a stacked card
   on narrow screens. Status is a word first, a colour second. */
.board__labels, .board__row {
  display: grid;
  grid-template-columns: minmax(0, 1.6fr) minmax(0, 1.1fr) auto auto minmax(0, 0.9fr);
  gap: 0.55rem 1rem; align-items: center;
  padding: 0.85rem clamp(1.1rem, 3vw, 1.75rem);
}
.board__labels {
  font-size: 0.5625rem; letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--faint); border-bottom: 1px solid var(--line);
}
.board__row { color: var(--dim); }
.board__row + .board__row { border-top: 1px solid var(--line); }
.pt { display: flex; flex-direction: column; gap: 0.15rem; min-width: 0; }
.pt b { color: var(--fg); font-weight: 500; }
.pt i { font-style: normal; color: var(--faint); font-size: 0.6875rem; }
.pt__proto { color: var(--dim); }
.pt__num { text-align: right; font-variant-numeric: tabular-nums slashed-zero; }

/* Criteria meter — three cells, filled teal as each threshold is met; outlined
   while the phase is still protective and criteria aren't loaded yet. */
.meter { display: inline-flex; gap: 3px; align-items: center; }
.meter i { width: 16px; height: 6px; background: var(--line-2); flex: none; }
.meter i.on { background: var(--correction); }
.meter i.hold { background: transparent; box-shadow: inset 0 0 0 1px var(--line-2); }

/* Status semaphore: a dot for steady states, a filled amber chip for the one
   that needs the clinician now. */
.st {
  display: inline-flex; align-items: center; gap: 0.5em;
  font-family: var(--mono); font-size: 0.625rem; letter-spacing: 0.06em;
  text-transform: uppercase; white-space: nowrap; color: var(--dim);
}
.st::before { content: ""; width: 8px; height: 8px; border-radius: 50%; background: var(--st, var(--dim)); flex: none; }
.st--ok { --st: var(--correction); }
.st--watch { --st: var(--data-warning); }
.st--stall {
  color: var(--on-data); background: var(--data-warning);
  padding: 0.3em 0.55em; letter-spacing: 0.05em;
}
.st--stall::before { display: none; }

@media (max-width: 48rem) {
  .board__labels { display: none; }
  .board__row {
    grid-template-columns: minmax(0, 1fr) auto;
    gap: 0.45rem 1rem; padding-block: 1rem;
  }
  .pt        { grid-column: 1; grid-row: 1; }
  .st        { grid-column: 2; grid-row: 1; justify-self: end; }
  .pt__proto { grid-column: 1; grid-row: 2; font-size: 0.6875rem; color: var(--faint); }
  .meter     { grid-column: 1; grid-row: 3; }
  .pt__num   { grid-column: 2; grid-row: 3; justify-self: end; }
}

/* Does / never-does boundary: the decision-support line, made scannable. */
.boundary {
  margin: clamp(2.25rem, 4vw, 3rem) 0 0;
  display: grid; gap: 1px; background: var(--line); border: 1px solid var(--line);
}
@media (min-width: 46rem) { .boundary { grid-template-columns: 1fr 1fr; } }
.boundary__col { background: var(--bg); padding: clamp(1.5rem, 3.5vw, 2.25rem); }
.boundary__col h3 {
  margin: 0 0 1rem; font-size: 0.75rem; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--faint); font-family: var(--mono); font-weight: 400;
}
.ticks { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.7rem; }
.ticks li {
  position: relative; padding-left: 1.6rem;
  color: var(--dim); font-size: 0.9375rem; line-height: 1.5;
}
.ticks li::before { position: absolute; left: 0; top: -0.05em; font-size: 1rem; }
.ticks--do   li::before { content: "\2713"; color: var(--correction); }
.ticks--dont li::before { content: "\00d7"; color: var(--faint); }

.credential {
  margin: 0;
  max-width: 52rem;
  font-family: var(--mono);
  font-size: 0.6875rem;
  line-height: 1.7;
  letter-spacing: 0.02em;
  color: var(--faint);
}
.credential a { color: var(--dim); }
.credential a:hover { color: var(--fg); }

/* ── Brand / design-language system ──────────────────────────────────────── */
code { font-family: var(--mono); font-size: 0.9em; color: var(--fg); background: color-mix(in srgb, var(--fg) 8%, var(--bg)); padding: 0.1em 0.35em; }

/* The engine mark: a core that holds a value and rewrites it — the correction,
   abstracted. Chip takes the product accent, except the neutral family core. */
.engine { width: 3rem; height: 3rem; color: var(--fg); }
.engine--lg { width: clamp(3.5rem, 8vw, 4.5rem); height: clamp(3.5rem, 8vw, 4.5rem); }
.engine__chip { fill: var(--correction); stroke: none; }

/* One engine, three products. */
.family { margin-top: clamp(2.5rem, 5vw, 3.5rem); display: grid; gap: clamp(2rem, 4vw, 3rem); align-items: start; }
@media (min-width: 54rem) { .family { grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.15fr); gap: clamp(2.5rem, 5vw, 4rem); } }
.family__core .engine__chip { fill: var(--fg); }
.family__core h3 { margin: 1.1rem 0 0.5rem; font-size: 1.125rem; letter-spacing: -0.01em; }
.family__core p { margin: 0; color: var(--dim); font-size: 0.9375rem; line-height: 1.55; max-width: 26rem; }
.family__products { list-style: none; margin: 0; padding: 0; display: grid; gap: 1px; background: var(--line); border: 1px solid var(--line); }
.fam a { display: grid; gap: 0.3rem 1rem; grid-template-columns: 1fr auto; align-items: baseline; background: var(--bg); padding: clamp(1.1rem, 2.5vw, 1.5rem) clamp(1.1rem, 3vw, 1.6rem); text-decoration: none; color: inherit; transition: background 0.15s; }
.fam a:hover { background: color-mix(in srgb, var(--correction) 8%, var(--bg)); }
.fam__mark { display: inline-flex; align-items: center; gap: 0.6rem; font-weight: 500; letter-spacing: -0.01em; }
.fam__dot { width: 10px; height: 10px; background: var(--correction); flex: none; }
.fam__line { grid-column: 1 / -1; color: var(--dim); font-size: 0.875rem; line-height: 1.5; }
.fam__hex { font-family: var(--mono); font-size: 0.625rem; letter-spacing: 0.06em; color: var(--faint); }

/* The five correction states, one grammar. */
.states { list-style: none; margin: clamp(2.5rem, 5vw, 3.5rem) 0 0; padding: 0; display: grid; gap: 1px; background: var(--line); border: 1px solid var(--line); }
.state { background: var(--bg); display: grid; gap: 0.4rem 1.5rem; grid-template-columns: minmax(0, auto) minmax(0, 1fr) auto; align-items: center; padding: clamp(1rem, 2.5vw, 1.35rem) clamp(1.1rem, 3vw, 1.6rem); }
.state__reason { display: inline-flex; align-items: center; gap: 0.5rem; font-family: var(--mono); font-size: 0.625rem; letter-spacing: 0.08em; text-transform: uppercase; color: var(--faint); white-space: nowrap; }
.state__reason .ic { width: 16px; height: 16px; color: var(--correction); }
.state__move { font-family: var(--mono); font-size: clamp(0.8rem, 0.74rem + 0.3vw, 0.9375rem); }
.state__name { font-family: var(--mono); font-size: 0.625rem; letter-spacing: 0.1em; text-transform: uppercase; color: var(--dim); text-align: right; }
.old { text-decoration: line-through; color: var(--faint); margin-right: 0.4rem; }
.chip { text-decoration: none; }
@media (max-width: 40rem) {
  .state { grid-template-columns: 1fr auto; }
  .state__reason { grid-column: 1; grid-row: 1; }
  .state__name { grid-column: 2; grid-row: 1; }
  .state__move { grid-column: 1 / -1; grid-row: 2; }
}

/* Accent spectrum. */
.swatches { margin: clamp(2.5rem, 5vw, 3.5rem) 0 0; display: grid; gap: 1px; background: var(--line); border: 1px solid var(--line); }
@media (min-width: 46rem) { .swatches { grid-template-columns: repeat(3, 1fr); } }
.swatch { background: var(--bg); padding: clamp(1.4rem, 3vw, 2rem); display: grid; gap: 0.35rem; }
.swatch__chip { width: 100%; height: 3.5rem; background: var(--correction); }
.swatch b { margin-top: 0.7rem; letter-spacing: -0.01em; }
.swatch__hex { font-family: var(--mono); font-size: 0.75rem; color: var(--dim); }
.swatch__note { font-family: var(--mono); font-size: 0.625rem; letter-spacing: 0.1em; text-transform: uppercase; color: var(--faint); }
.tones { margin-top: 1.25rem; display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: center; }
.tone { font-family: var(--mono); font-size: 0.6875rem; letter-spacing: 0.06em; padding: 0.5em 0.8em; }
.tone--faint { color: var(--faint); }

/* Type specimen. */
.type { margin-top: clamp(2.5rem, 5vw, 3.5rem); display: grid; gap: 1.25rem; }
.type__display { margin: 0; font-size: clamp(2.75rem, 1.5rem + 5vw, 5.5rem); font-weight: 600; letter-spacing: -0.035em; line-height: 0.95; }
.type__serif { margin: 0; font-family: var(--serif); font-style: italic; font-size: clamp(1.5rem, 1.1rem + 1.6vw, 2.25rem); font-weight: 400; letter-spacing: -0.008em; color: var(--dim); max-width: 22ch; }
.type__h { margin: 0; font-size: clamp(1.5rem, 1.1rem + 1.6vw, 2.25rem); font-weight: 500; letter-spacing: -0.02em; }
.type__body { margin: 0; max-width: 42rem; color: var(--dim); font-size: 1.0625rem; line-height: 1.6; }
.type__mono { margin: 0.5rem 0 0; font-family: var(--mono); font-size: 0.8125rem; letter-spacing: 0.04em; color: var(--dim); font-variant-numeric: tabular-nums slashed-zero; }

/* Icon wall. */
.iconwall { list-style: none; margin: clamp(2.5rem, 5vw, 3.5rem) 0 0; padding: 0; display: grid; grid-template-columns: repeat(auto-fill, minmax(6.25rem, 1fr)); gap: 1px; background: var(--line); border: 1px solid var(--line); }
.iconwall li { background: var(--bg); display: flex; flex-direction: column; align-items: center; gap: 0.7rem; padding: 1.5rem 0.75rem; }
.iconwall .ic { width: 26px; height: 26px; color: var(--correction); }
.iconwall span { font-family: var(--mono); font-size: 0.5625rem; letter-spacing: 0.08em; text-transform: uppercase; color: var(--faint); }

/* The criteria, on the body: a schematic knee wired to the sheet's figures —
   custom technical line-work, blueprint chrome, the one teal accent on the marks. */
.anatomy__lede { margin: clamp(1.5rem, 3vw, 2.25rem) 0 0; max-width: 40rem; font-size: clamp(1.15rem, 1rem + 0.6vw, 1.5rem); line-height: 1.35; letter-spacing: -0.02em; text-wrap: balance; }
.anatomy { margin-top: clamp(2rem, 4vw, 3rem); display: grid; gap: clamp(2rem, 4vw, 3rem); align-items: center; }
@media (min-width: 58rem) { .anatomy { grid-template-columns: minmax(0, 0.75fr) minmax(0, 1.25fr); gap: clamp(2.5rem, 5vw, 4.5rem); } }
.anat { margin: 0; }
.anat svg { width: 100%; max-width: 460px; height: auto; display: block; }

/* The one moment on the page allowed to break the rhythm: this is Granite
   Clinic's single most ownable, hand-drawn device, so it gets weight nothing
   else on the page gets — a heading well past the standard section-title
   scale, and a diagram let all the way out to its column instead of held to
   a supporting-graphic size. Every other section stays exactly as it was. */
#criteria .section-title { font-size: clamp(2.6rem, 1.5rem + 4.6vw, 5.75rem); max-width: 21ch; }
@media (min-width: 58rem) {
  #criteria .anatomy { grid-template-columns: minmax(0, 1.15fr) minmax(0, 0.85fr); }
  #criteria .anat svg { max-width: 620px; }
}
.a-grid { stroke: var(--line); stroke-width: 1; fill: none; }
.a-grid .a-ground { stroke-dasharray: 2 5; }
.a-bone { fill: var(--bg); stroke: var(--dim); stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.a-bone.a-thin { stroke-width: 1.5; fill: none; }
.a-patella { fill: none; stroke: var(--faint); stroke-width: 1.5; stroke-dasharray: 3 3; }
.a-soft { fill: none; stroke: var(--faint); stroke-width: 1.5; }
.a-lig { stroke: var(--faint); stroke-width: 1.5; }
.a-node circle { fill: var(--bg); stroke: var(--correction); stroke-width: 2; }
.a-node--ok circle { fill: var(--correction); }
.a-node text { fill: var(--correction); font-family: var(--mono); font-size: 12px; text-anchor: middle; font-weight: 500; }
.a-node--ok text { fill: var(--on-data); }
.anat__cap { margin-top: 1.1rem; font-family: var(--mono); font-size: 0.625rem; letter-spacing: 0.1em; text-transform: uppercase; color: var(--faint); }

.anat__criteria { list-style: none; margin: 0 0 1.75rem; padding: 0; display: grid; gap: 1px; background: var(--line); border: 1px solid var(--line); }
.ac { background: var(--bg); display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 1rem; align-items: center; padding: clamp(1rem, 2.5vw, 1.35rem) clamp(1.1rem, 2.5vw, 1.5rem); }
.ac__n { width: 27px; height: 27px; flex: none; display: grid; place-items: center; border-radius: 50%; border: 1px solid var(--correction-text); color: var(--correction-text); font-family: var(--mono); font-size: 0.75rem; }
.ac__n--ok { background: var(--correction); color: var(--on-data); }
.ac__lbl { margin: 0 0 0.25rem; font-family: var(--mono); font-size: 0.6875rem; letter-spacing: 0.1em; text-transform: uppercase; color: var(--faint); }
.ac__v { margin: 0; display: flex; align-items: center; gap: 0.6rem; flex-wrap: wrap; font-family: var(--mono); font-size: 0.9375rem; color: var(--fg); font-variant-numeric: tabular-nums slashed-zero; }
.ac__v span { color: var(--dim); font-size: 0.8125rem; }
.anatomy__body { margin: 0; color: var(--dim); line-height: 1.6; }

/* Interactive link — hover a criteria row or its joint node, both light up. */
.a-halo { fill: none; stroke: var(--correction); stroke-width: 0; opacity: 0; transition: stroke-width .25s var(--ease), opacity .25s var(--ease); }
.a-node.is-active .a-halo { stroke-width: 7; opacity: 0.32; }
.a-node.is-active text { font-weight: 600; }
.ac { transition: background .2s var(--ease); }
.ac.is-active { background: color-mix(in srgb, var(--correction) 9%, var(--bg)); }
.ac.is-active .ac__n { box-shadow: 0 0 0 4px color-mix(in srgb, var(--correction) 24%, transparent); }

/* The knee draws itself in the first time it scrolls into view. */
@media (prefers-reduced-motion: no-preference) {
  .anat.will-draw .a-node { opacity: 0; transform: scale(0.4); transform-box: fill-box; transform-origin: center; }
  .anat.will-draw.drawn .a-node { opacity: 1; transform: scale(1); transition: opacity .45s var(--ease), transform .55s var(--ease); }
  .anat.will-draw.drawn .a-node:nth-of-type(1) { transition-delay: .95s; }
  .anat.will-draw.drawn .a-node:nth-of-type(2) { transition-delay: 1.1s; }
  .anat.will-draw.drawn .a-node:nth-of-type(3) { transition-delay: 1.25s; }
}

/* The loop draws down: the connector line grows and the nodes pop in sequence
   as the section reveals. Gated on .js + motion so no-JS/reduced show it whole. */
@media (prefers-reduced-motion: no-preference) {
  .js .flow .flow__n { opacity: 0; transform: scale(0.55); transition: opacity .45s var(--ease), transform .5s var(--ease); }
  .js .flow .flow__step::before { transform: scaleY(0); transform-origin: top center; transition: transform .6s var(--ease); }
  .js .flow.in .flow__n { opacity: 1; transform: scale(1); }
  .js .flow.in .flow__step::before { transform: scaleY(1); }
  .js .flow .flow__step:nth-child(1) .flow__n { transition-delay: .05s; }
  .js .flow .flow__step:nth-child(2) .flow__n { transition-delay: .20s; }
  .js .flow .flow__step:nth-child(3) .flow__n { transition-delay: .35s; }
  .js .flow .flow__step:nth-child(4) .flow__n { transition-delay: .50s; }
  .js .flow .flow__step:nth-child(5) .flow__n { transition-delay: .65s; }
  .js .flow .flow__step:nth-child(1)::before { transition-delay: .12s; }
  .js .flow .flow__step:nth-child(2)::before { transition-delay: .27s; }
  .js .flow .flow__step:nth-child(3)::before { transition-delay: .42s; }
  .js .flow .flow__step:nth-child(4)::before { transition-delay: .57s; }
}

/* The loop drawn as a loop: a radial ring of five nodes, the fifth feeding the
   first — a layout the page hasn't used (not a vertical timeline). */
.loopwheel { margin-top: clamp(2.5rem, 5vw, 3.5rem); display: grid; gap: clamp(2.5rem, 5vw, 3.5rem); align-items: center; }
@media (min-width: 62rem) {
  .loopwheel { grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr); gap: clamp(3rem, 6vw, 5rem); align-items: start; }
  /* Pin the ring so it holds the eye while you read the five steps, instead of
     floating in the middle of its column with dead space around it. */
  .wheel { position: sticky; top: clamp(5rem, 15vh, 9rem); }
}
.wheel { margin: 0; }
.wheel svg { width: 100%; max-width: 360px; height: auto; display: block; margin-inline: auto; }
/* The rotor carries the ring, the closing arc and every node; it turns to
   bring the active step to the top as you scroll the list. Each node then
   counter-rotates by the same amount so its number stays upright rather than
   swinging around the ring with the rest of the assembly. */
.wheel__rotor { transform: rotate(var(--rot, 0deg)); transform-origin: 200px 200px; }
.wheel__ring { fill: none; stroke: var(--line-2); stroke-width: 1.5; }
.wheel__close { fill: none; stroke: var(--correction); stroke-width: 2.5; stroke-linecap: round; }
/* The ring fills as you move through the steps. A circle path starts at three
   o'clock, so it is turned back a quarter to begin at node one; the rotation
   composes with the rotor's, keeping the fill anchored to the nodes it
   measures. pathLength normalises the circumference to 1, so --prog is read
   directly as a fraction. */
.wheel__prog {
  fill: none; stroke: var(--correction); stroke-width: 2.5; stroke-linecap: round;
  stroke-dasharray: 1; stroke-dashoffset: calc(1 - var(--prog, 0));
  transform: rotate(-90deg); transform-origin: 200px 200px;
}
#wah path { fill: var(--correction); }
.wheel__c1 { fill: var(--faint); font-family: var(--mono); font-size: 13px; letter-spacing: 3px; text-anchor: middle; }
.wheel__c2 { fill: var(--correction); font-family: var(--mono); font-size: 15px; letter-spacing: 2px; text-anchor: middle; }
.wheel__n { transform: scale(var(--s, 1)) rotate(calc(-1 * var(--rot, 0deg))); transform-box: fill-box; transform-origin: center; }
.wheel__n circle { fill: var(--bg); stroke: var(--correction); stroke-width: 2; }
.wheel__n text { fill: var(--correction-text); font-family: var(--mono); font-size: 15px; font-weight: 500; text-anchor: middle; }
.wheel__n.is-active circle { fill: var(--correction); }
.wheel__n.is-active text { fill: var(--on-data); }

.loopsteps { list-style: none; margin: 0; padding: 0; display: grid; gap: 1px; background: var(--line); border: 1px solid var(--line); }
.lstep { background: var(--bg); display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 0.3rem 1.1rem; align-items: start; padding: clamp(1.1rem, 2.5vw, 1.5rem) clamp(1.2rem, 2.5vw, 1.6rem); transition: background .2s var(--ease); }
.lstep.is-active { background: var(--bg); }
.lstep__n { width: 27px; height: 27px; flex: none; display: grid; place-items: center; border-radius: 50%; border: 1px solid var(--correction-text); color: var(--correction-text); font-family: var(--mono); font-size: 0.75rem; margin-top: 0.1rem; grid-row: span 2; align-self: start; transition: box-shadow .2s var(--ease); }
.lstep.is-active .lstep__n { box-shadow: 0 0 0 4px color-mix(in srgb, var(--correction) 22%, transparent); }
.lstep h3 { margin: 0 0 0.4rem; font-size: 1.0625rem; letter-spacing: -0.015em; }
.lstep p { margin: 0; color: var(--dim); font-size: 0.9rem; line-height: 1.55; }
.lstep p strong { color: var(--fg); font-weight: 500; }

/* Reveal: nodes settle in around the ring, then the feedback arc draws 5 → 1.
   The arc runs as a plain CSS @keyframes animation rather than a transition
   gated behind the scroll-reveal IntersectionObserver's .in class — that
   class is a second, independent trigger from the one now driving the
   rotor, and if the two ever fall out of step the old transition-based
   draw-in could stay permanently at its hidden starting value. An
   unconditional animation always resolves to fully drawn on its own. Nodes
   stay transition-based: --s composes into the same transform as the live
   rotation, so a keyframe animation there would fight the rotor once
   scrolling starts. Once settled, the rotor keeps turning in response to
   scroll — same easing, so the ongoing rotation reads as a continuation of
   the draw-in. */
@media (prefers-reduced-motion: no-preference) {
  .wheel__rotor { transition: transform 640ms var(--ease); }
  .wheel__prog { transition: stroke-dashoffset 640ms var(--ease); }
  .wheel__n { transition: opacity .4s var(--ease), transform .55s var(--ease); }
  .js .wheel__n { --s: 0.5; opacity: 0; }
  .js .loopwheel.in .wheel__n { --s: 1; opacity: 1; }
  .js .wheel__close { stroke-dasharray: 1; stroke-dashoffset: 1; animation: wheel-close-in .9s var(--ease) 1.15s both; }
  @keyframes wheel-close-in { to { stroke-dashoffset: 0; } }
  .js .loopwheel .wheel__n[data-step="1"] { transition-delay: .10s; }
  .js .loopwheel .wheel__n[data-step="2"] { transition-delay: .28s; }
  .js .loopwheel .wheel__n[data-step="3"] { transition-delay: .46s; }
  .js .loopwheel .wheel__n[data-step="4"] { transition-delay: .64s; }
  .js .loopwheel .wheel__n[data-step="5"] { transition-delay: .82s; }
}

/* Block-load bar chart: the climb and the two forced deloads, drawn. Neutral
   bars for the build; mint where the coach pulled the load down, with a dotted
   ghost at the height the block had planned — the correction motif, charted. */
.chart { margin: clamp(2.5rem, 5vw, 3.5rem) 0 0; }
.chart__plot { display: flex; align-items: flex-end; gap: clamp(4px, 1.2vw, 12px); height: clamp(200px, 32vw, 280px); border-bottom: 1px solid var(--line-2); }

/* The one moment on this page allowed to break the rhythm: the twelve-week
   block is Granite's own data telling the correction story, so it's let run
   far bigger than a supporting chart, with a heading to match. Every other
   section on the page holds the standard scale. */
#block .section-title { font-size: clamp(2.6rem, 1.5rem + 4.6vw, 5.75rem); max-width: 18ch; }
#block .chart__plot { height: clamp(260px, 46vw, 460px); }
#block .bar__col, #block .bar__ghost { max-width: 34px; }
.bar { flex: 1; min-width: 0; height: 100%; display: flex; flex-direction: column; justify-content: flex-end; align-items: center; gap: 0.55rem; position: relative; }
.bar__v { font-family: var(--mono); font-size: clamp(0.5rem, 1.35vw, 0.75rem); color: var(--dim); font-variant-numeric: tabular-nums slashed-zero; }
.bar__col { width: 64%; max-width: 22px; height: var(--h); background: var(--line-2); }
.bar__ghost { position: absolute; bottom: 0; left: 50%; transform: translateX(-50%); width: 64%; max-width: 22px; height: var(--ghost); border: 1px dashed var(--faint); border-bottom: 0; }
.bar--cut .bar__v { color: var(--correction); }
.bar--cut .bar__col { background: var(--correction); }
.chart__weeks { list-style: none; margin: 0.7rem 0 0; padding: 0; display: flex; gap: clamp(4px, 1.2vw, 12px); }
.chart__weeks li { flex: 1; min-width: 0; text-align: center; font-family: var(--mono); font-size: clamp(0.5rem, 1.2vw, 0.625rem); color: var(--faint); font-variant-numeric: tabular-nums; }
.chart__cap { margin: 1.5rem 0 0; max-width: 44rem; color: var(--dim); font-size: 0.9375rem; line-height: 1.55; }
.chart__cap span { color: var(--faint); }

@media (prefers-reduced-motion: no-preference) {
  .js .chart .bar__col { height: 0; transition: height .7s var(--ease); transition-delay: calc(var(--i, 0) * 45ms); }
  .js .chart .bar__ghost { opacity: 0; transition: opacity .4s var(--ease); transition-delay: calc(var(--i, 0) * 45ms + .35s); }
  .js .chart.in .bar__col { height: var(--h); }
  .js .chart.in .bar__ghost { opacity: 1; }
}

/* Top announcement bar with a live 'piloting' status dot — a thin status line
   above the masthead, mono, with the one teal accent. */
.topbar { border-bottom: 1px solid var(--line); font-family: var(--mono); font-size: 0.6875rem; letter-spacing: 0.08em; }
.topbar .wrap { display: flex; align-items: center; justify-content: center; gap: 0.75rem 1.5rem; padding: 0.6rem 1.5rem; flex-wrap: wrap; }
.topbar__txt { display: inline-flex; align-items: center; gap: 0.6rem; color: var(--dim); text-transform: uppercase; }
.topbar__dot { width: 6px; height: 6px; border-radius: 50%; background: var(--correction); flex: none; }
.topbar__cta { color: var(--correction-text); text-transform: uppercase; letter-spacing: 0.08em; text-decoration: none; transition: color .18s var(--ease); }
.topbar__cta:hover { color: var(--fg); }
@media (prefers-reduced-motion: no-preference) {
  .topbar__dot { animation: pilot-pulse 2.4s ease-in-out infinite; }
  @keyframes pilot-pulse { 0%, 100% { opacity: 1; box-shadow: 0 0 0 0 color-mix(in srgb, var(--correction) 45%, transparent); } 50% { opacity: 0.55; box-shadow: 0 0 0 4px color-mix(in srgb, var(--correction) 0%, transparent); } }
}

/* Protocol ticker — a slow clinical-monitor readout of what the engine covers.
   Two identical groups; the track slides one group's width and repeats. */
.ticker { overflow: hidden; border-block: 1px solid var(--line); }
.ticker__track { display: flex; width: max-content; }
.ticker__group { display: flex; align-items: center; white-space: nowrap; padding: 0.95rem 0; font-family: var(--mono); font-size: clamp(0.6875rem, 0.62rem + 0.3vw, 0.8125rem); letter-spacing: 0.08em; text-transform: uppercase; color: var(--faint); }
.ticker__group i { color: var(--correction-text); font-style: normal; margin: 0 1.4rem; }
@media (prefers-reduced-motion: no-preference) {
  .ticker__track { animation: ticker-scroll 42s linear infinite; }
  .ticker:hover .ticker__track { animation-play-state: paused; }
  @keyframes ticker-scroll { to { transform: translateX(-50%); } }
}

/* Row highlight on the hero sheet — the one motion move on this element,
   pointing at a real correction row rather than tracking the cursor for its
   own sake. */
#proof-sheet { position: relative; overflow: hidden; }
#proof-sheet .row { position: relative; transition: background .15s var(--ease); }
/* No hover on the proof sheet: it is a figure, not a control, and lighting
   rows on pointer-over promises an interaction that does not exist. */

/* The duotone was built for a single stock-ish hero image and applied to every
   .photo by default. It cannot carry a portrait series: mapping every face to
   the same two tones flattens exactly the human variety the series exists to
   show, and it does the most damage across different skin tones. It now only
   applies where it is asked for, and the portraits run clean — they were shot
   muted and high-contrast already, which is what the brief was for. */
.photo { margin: 0; }
.photo__frame { position: relative; aspect-ratio: 16 / 7; overflow: hidden; border: 1px solid var(--line-2); }
.photo__frame img { display: block; width: 100%; height: 100%; object-fit: cover; }
.photo--duotone .photo__frame img { filter: url(#duotone) contrast(1.05); }
@supports not (filter: url(#duotone)) {
  .photo--duotone .photo__frame img { filter: grayscale(1) contrast(1.05) sepia(0.2) hue-rotate(120deg); }
}

/* A caption is not body copy. It was set at reading size, so it read as the
   first line of the paragraph under it rather than as a label on the frame. */
.photo__cap {
  margin: 1.1rem 0 0; max-width: 46rem;
  font-family: var(--mono); font-size: 0.6875rem; line-height: 1.6;
  letter-spacing: 0.04em; color: var(--faint);
}
.photo__note {
  display: block; margin-top: 0.7rem;
  font-family: var(--mono); font-size: 0.625rem; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--faint);
}

/* Claims card takes a faint teal wash on hover — shared site-wide convention,
   unrelated to Clinic's own motion cleanup below. */
.claim { transition: background .2s var(--ease); }
.claim:hover {
  background: color-mix(in srgb, var(--correction) 4%, var(--bg));
}

/* Motion push — all of it additive: content is fully visible by default and
   these only move things that are already on screen. No observer can hide
   anything.

   Everything decorative that didn't carry real meaning — a scroll-progress
   hairline, parallax on the one photograph, a cursor scan-line — has been
   cut. The correction strike/chip is the one authored gesture on this page;
   what's left below is data, not chrome: a status genuinely live. */
@media (prefers-reduced-motion: no-preference) {
  .st--stall { animation: stall-pulse 2.4s ease-in-out infinite; }
  @keyframes stall-pulse { 50% { opacity: 0.68; } }
}

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

footer {
  border-top: 1px solid var(--line);
  padding-block: 2.5rem 3.5rem;
  font-family: var(--mono);
  font-size: 0.625rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  /* --dim, not --faint: at 10px the fainter grey lands at 4.29:1 on white,
     just under AA. The footer stays quiet either way, and now clears 4.5:1. */
  color: var(--dim);
}

footer .wrap { display: flex; justify-content: space-between; gap: 1rem 2rem; flex-wrap: wrap; }
footer a { color: var(--dim); text-decoration: none; transition: color 180ms var(--ease); }
footer a:hover { color: var(--fg); }
/* Wraps: five or six links + the stitch don't fit one row on a phone, and an
   un-wrapped nav was pushing the page ~16px wide. Row-gap clears the links'
   negative hit-area margins so wrapped rows never overlap. */
footer nav { display: flex; flex-wrap: wrap; gap: 1.25rem 1.75rem; }

/* The Frame stitch — a quiet full-width line above the credit row. Two live
   products, one sentence: not uppercase like the rest of the footer, because it
   reads as a note, not a label. */
.footer__stitch {
  flex-basis: 100%;
  margin: 0 0 1.6rem;
  max-width: 54rem;
  text-transform: none;
  letter-spacing: 0.01em;
  font-size: 0.6875rem;
  line-height: 1.6;
  color: var(--dim);
}
.footer__stitch a { color: var(--fg); }
.footer__stitch a:hover { color: var(--dim); }

/* Extend the hit area to clear the 24px minimum target without changing the
   footer's visual rhythm — the negative margin gives the padding back to layout. */
footer nav a { padding-block: 0.6rem; margin-block: -0.6rem; }

/* --- document pages ----------------------------------------------------- */

.doc { padding-block: clamp(3rem, 6vw, 5.5rem) 5rem; }

.doc__back {
  font-family: var(--mono);
  font-size: 0.625rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--faint);
  display: inline-block;
  margin-bottom: 3.5rem;
  padding-block: 0.4rem;
  transition: color 180ms var(--ease);
}

.doc__back:hover { color: var(--fg); }

.doc h1 {
  font-size: clamp(2.25rem, 1.5rem + 3vw, 4rem);
  font-weight: 700;
  line-height: 0.95;
  letter-spacing: -0.055em;
  text-transform: uppercase;
  margin-bottom: 0.75rem;
}

.doc__stamp {
  font-family: var(--mono);
  font-size: 0.625rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--faint);
  margin-bottom: 3rem;
}

.doc h2 {
  font-family: var(--mono);
  font-size: 0.625rem;
  font-weight: 400;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--faint);
  margin: 3rem 0 0.9rem;
  padding-bottom: 0.8rem;
  border-bottom: 1px solid var(--line);
}

.doc p { color: var(--dim); }
.doc .lede { color: var(--fg); }

/* Prose lists — the browser's default disc was the one un-designed element on the site.
   An en-dash marker in the faint grey: the same voice as the doc__stamp, no new shapes. */
.doc ul, .prose ul {
  list-style: none;
  padding-inline-start: 0;
  margin-block: 1rem;
}
.doc ul li, .prose ul li {
  position: relative;
  padding-inline-start: 1.4em;
  color: var(--dim);
  margin-block-end: .45em;
}
.doc ul li::before, .prose ul li::before {
  content: "\2013";           /* en dash */
  position: absolute;
  inset-inline-start: 0;
  color: var(--faint);
}

/* Prose tables — hairline rules only, both themes, inverts for free. */
.doc table, .prose table {
  width: 100%;
  border-collapse: collapse;
  margin-block: 1.5rem;
  font-size: .95rem;
}
.doc th, .prose th {
  font-weight: 600;
  color: var(--fg);
  text-align: left;
  padding: .7rem .75rem .7rem 0;
  border-bottom: 1px solid var(--line);
}
.doc td, .prose td {
  color: var(--dim);
  text-align: left;
  padding: .7rem .75rem .7rem 0;
  border-bottom: 1px solid var(--line-2);
}

/* Inline links carry a resting hairline; on hover or focus a solid rule draws
   across it left to right — the same drawing gesture as the strike, in miniature. */
.doc a {
  color: var(--fg);
  text-decoration: none;
  border-bottom: 1px solid var(--line-2);
  position: relative;
}

.doc a::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -1px;
  height: 1px;
  background: var(--fg);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 260ms var(--ease);
}

.doc a:hover::after,
.doc a:focus-visible::after { transform: scaleX(1); }

/* --- higher-contrast preference ----------------------------------------- */

/* Readers who ask their OS for more contrast get the muted tones pushed toward
   the ink — still two tones, just louder. The chip and rules stay as they are. */
@media (prefers-contrast: more) {
  :root {
    --dim:    color-mix(in srgb, var(--fg) 82%, var(--bg));
    --faint:  color-mix(in srgb, var(--fg) 70%, var(--bg));
    --line:   color-mix(in srgb, var(--fg) 34%, var(--bg));
    --line-2: color-mix(in srgb, var(--fg) 55%, var(--bg));
  }
}

/* --- print -------------------------------------------------------------- */

/* Printed, the site is a coaching document on paper: black ink, the chips still
   inverted so a correction still reads, the toggle gone, and everything the
   scroll animations would have hidden forced visible. */
@media print {
  :root,
  [data-theme="light"],
  [data-theme="dark"] { --bg: #FFFFFF; --fg: #000000; }

  html { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
  .invert, .skip { display: none !important; }

  .js .reveal { opacity: 1 !important; transform: none !important; }
  .js .wordmark.reveal { opacity: 1 !important; }
  .js .was::after { transform: scaleX(1) !important; }
  .js .now { clip-path: none !important; }
  .js .note { opacity: 1 !important; }
}

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

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .js .was::after { transform: scaleX(1); }
  .js .now { clip-path: none; }
  .js .note { opacity: 1; }
  .js .reveal { opacity: 1; transform: none; }

  ::view-transition-old(root),
  ::view-transition-new(root) { animation: none !important; }

  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}
