/* ============================================================================
   Ballstrikers Golf Club — landing page
   Implements design/design-system.md. Used by index.html and LandingPage.jsx.

   GENERATED from the preview template. Iterate there, then re-run sync_css.py,
   so the reviewed page and the shipped page cannot drift apart.

   Breakpoints, and only these three:
     48rem =  768px  tablet
     64rem = 1024px  desktop
     75rem = 1200px  wide
   ========================================================================= */
:root {
  --deep:   #12241A;
  --deeper: #0B1710;
  --gold:   #C8A356;
  --gold-lt:#DBBE7E;
  --gold-dk:#8A6620;
  --cream:  #F7F5F0;
  --cream-2:#EEEAE0;
  --ink:    #12241A;
  --mute:   #5F6E64;

  --display: system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', sans-serif;
  --body: system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;

  /* Spacing scale — 8 / 16 / 24 / 32 / 48 / 64 / 80.
     --s3x and --s4x were added 2026-08-06. The scale doubled from 24 to 48 to
     80 with nothing between, so anything that wanted 32 or 64 was rounded to
     a neighbour and the page picked up a coarse, mechanical rhythm. The gap
     was in the scale, not in the layout. */
  --s1: .5rem;    /*  8 */
  --s2: 1rem;     /* 16 */
  --s3: 1.5rem;   /* 24 */
  --s3x: 2rem;    /* 32 */
  --s4: 3rem;     /* 48 */
  --s4x: 4rem;    /* 64 */
  --s5: 5rem;     /* 80 */

  /* Section padding-block. Applied top AND bottom, so it doubles at every join
     between two sections: this is 64px mobile / 104px desktop of actual gap.
     Tightened 2026-08-06 from clamp(2.5rem, 6vw, 4.5rem), which was 144px a
     seam. Across eleven seams that alone was 1,600px of the page's height, and
     the page read as though every section had been scaled up. */
  --sy: clamp(2.25rem, 5vw, 3.75rem);

  /* Widened 2026-08-06 from 75rem. At 1200px on a 1920px screen the page sat
     in the middle third with 360px of empty ground down each side, and every
     section header was capped narrower still. 1312px keeps the grid clearly
     contained without the content looking stranded in the centre. */
  --maxw: 82rem;                          /* 1312px content grid */
  --gutter: clamp(1.25rem, 4vw, 2rem);    /* 20px → 32px */
  --gap: clamp(2rem, 4vw, 4rem);
  /* --measure stays put. 656px is ~65 characters at this size, which is the
     readable range; widening it would make the prose worse, not fuller.
     --measure-wide governs headings, which can carry more per line. */
  --measure: 41rem;                       /* 656px reading measure */
  --measure-wide: 52rem;                  /* 832px, headings only */
  --headh: 4.5rem;

  /* images — repoint these eight at a CDN and nothing else changes.
     The two guide covers have no file yet: the elements carry
     .is-placeholder and render as gold boxes until the files arrive. */
  --img-hero:      url("./img/hero-tall.jpg");
  --img-links:     url("./img/s3-band.jpg");
  --img-portrait:  url("./img/s7-portrait.jpg");
  --img-closer:    url("./img/s10-closer.jpg");
  --img-statement: url("./img/statement.jpg");
  --img-coaching:  url("./img/s9-coaching.jpg");
  --img-drills:    url("./img/guide-drills.jpg");
  --img-slice:     url("./img/guide-slice.jpg");

  /* Two radii, and only two. Containers and photographs take --radius;
     buttons and small chips take --radius-sm. Pills and circles were the
     consumer-friendly register and are gone. */
  --radius: 8px;
  --radius-sm: 4px;
  --lift: 0 8px 24px rgba(11,23,16,.06);  /* barely visible, separates layers */
  --lift-2: 0 14px 36px rgba(11,23,16,.1);
}

/* breakpoints, only these three: 48rem 768 · 64rem 1024 · 75rem 1200 */

* { box-sizing: border-box; }
body {
  margin: 0; background: var(--cream); color: var(--ink);
  font-family: var(--body); font-size: 17px; line-height: 1.65;
  -webkit-font-smoothing: antialiased;
}
@media (min-width: 48rem) { body { font-size: 18px; } }

.wrap { max-width: var(--maxw); margin: 0 auto; padding-inline: var(--gutter); }

/* ── type ─────────────────────────────────────────── */
h1, h2, h3 { margin: 0; font-family: var(--display); text-wrap: balance; }
h1 {
  font-size: clamp(2.5rem, 5.4vw, 4.75rem);
  font-weight: 800; line-height: .97;
  letter-spacing: -.038em; text-transform: uppercase;
}
/* Sentence case, and half the size it was.
   Every heading on this page used to be uppercase at up to 50px, which made
   the largest type only 1.5x the section headings. That is not a hierarchy,
   it is a volume setting, and when everything shouts the eye cannot rank
   anything. Uppercase also strips the ascenders and descenders the eye uses
   to read shape, so it flattens further.
   Uppercase now belongs to the H1, the eyebrows, and small UI only. Section
   headings speak. The hero is 76px against a 34px heading: a real range. */
h2 {
  font-size: clamp(1.6rem, 2.6vw, 2.15rem);
  font-weight: 800; line-height: 1.1;
  letter-spacing: -.022em;
}
h3 { font-size: 1.1rem; font-weight: 700; letter-spacing: -.01em; line-height: 1.3; }
p { margin: 0; max-width: var(--measure); }

/* Muted, not gold. Gold now marks the primary button and the stat numbers and
   nothing else, so that at any point on the page the only gold object in view
   is the thing you are meant to click. An accent used nine ways is decoration;
   used once it is an instruction. */
.eyebrow {
  font-size: .68rem; font-weight: 800; letter-spacing: .16em;
  text-transform: uppercase; color: var(--mute);
}
/* Enlarged variant, on the client's "bigger font" note of 2026-08-29. Used on
   the About and 1-on-1 headers only, where the eyebrow introduces a person
   rather than labelling a section. */
.eyebrow.lg { font-size: .82rem; letter-spacing: .14em; }
.lede { color: var(--mute); font-size: 1.05rem; max-width: var(--measure); }

/* ── grounds ──────────────────────────────────────── */
section { padding-block: var(--sy); }
/* Three registers, not one.
   .tight   informational — what you get, how it works, guides, questions
   default  argument — proof, the gap, inside the academy
   .breathe personal — about
   A page where every section breathes identically feels mechanical no matter
   how well the value is chosen. */
.tight   { padding-block: calc(var(--sy) * .78); }
.breathe { padding-block: calc(var(--sy) * 1.3); }
.on-cream-2 { background: var(--cream-2); }
.on-deep { background: var(--deep); color: var(--cream); }
.on-deep .lede, .on-deep .mute { color: rgba(247,245,240,.7); }
.on-deep .eyebrow { color: rgba(247,245,240,.55); }
.on-deep .price { color: rgba(247,245,240,.6); }
.on-deep h2, .on-deep h3 { color: #fff; }

/* The strongest sentence on the page, so it gets to be the largest thing in
   its section rather than a footnote under the three quotes above it. The gold
   rule is the only one on the page: it marks the argument the whole offer
   rests on. */
.pull {
  margin-top: var(--s4); padding-top: var(--s3);
  border-top: 2px solid var(--gold);
  color: #fff; font-family: var(--display); font-weight: 800;
  font-size: clamp(1.45rem, 3.1vw, 2.05rem);
  letter-spacing: -.03em; line-height: 1.15; max-width: 24rem;
  text-wrap: balance;
}

.stack { display: flex; flex-direction: column; gap: var(--s3); }
.stack-sm { display: flex; flex-direction: column; gap: var(--s1); }
.head {
  display: flex; flex-direction: column; align-items: flex-start;
  margin-bottom: clamp(1.75rem, 3.5vw, 2.75rem);
  max-width: var(--measure-wide);
}
.head .eyebrow { margin-bottom: var(--s1); }
.head .lede { margin-top: var(--s2); }
.head.mid { align-items: center; text-align: center; max-width: 40rem; margin-inline: auto; }
.head.mid .lede { margin-inline: auto; }

/* alternating two-column rows. .flip puts the image first on desktop. */
.duo { display: grid; gap: var(--gap); align-items: center; }
@media (min-width: 64rem) {
  .duo { grid-template-columns: minmax(0,1fr) minmax(0,1fr); }
  .duo.flip > *:first-child { order: 2; }
}

/* ── buttons ──────────────────────────────────────── */
.cta, .cta-2 {
  display: inline-flex; align-items: center; justify-content: center;
  min-height: 62px; padding-inline: 2.25rem;   /* one-handed phone use */
  font-family: var(--display); font-size: .92rem; font-weight: 800;
  letter-spacing: .1em; text-transform: uppercase; text-align: center;
  text-decoration: none; border-radius: var(--radius-sm); cursor: pointer;
  transition: background .18s ease, color .18s ease, border-color .18s ease;
}
@media (min-width: 48rem) { .cta, .cta-2 { min-height: 56px; } }

.cta {
  width: 100%; max-width: 23rem;
  background: var(--gold); color: var(--deep); border: 0;
}
.cta:hover { background: var(--gold-lt); }
.cta:active { background: var(--gold); }

.cta-2 { color: var(--ink); background: none; border: 1.5px solid var(--ink); }
.cta-2:hover { background: var(--ink); color: var(--cream); }

/* Doubled on the client's annotation of 2026-09-06, from .95rem.
   ⚠ This is the tertiary CTA and it is now larger than the secondary "Buy now"
   buttons, which is the opposite of the brief's offer hierarchy. It stays a
   text link with no gold and no box, which is the only thing still ranking it
   below the guides. Do not also give it a border or a background. */
.cta-3 {
  color: var(--ink); text-decoration: none;
  border-bottom: 2px solid rgba(18,36,26,.3);
  font-weight: 650;
  font-size: clamp(1.35rem, 2.6vw, 1.9rem);
  line-height: 1.3;
  white-space: nowrap;
}
.price { font-size: .82rem; color: var(--mute); letter-spacing: .01em; }
a:focus-visible, summary:focus-visible, button:focus-visible { outline: 2px solid var(--gold); outline-offset: 3px; }

/* ── site header ──────────────────────────────────── */
/* Solid from the top, not a fade-in. A translucent gradient over the hero
   photograph ended on a visible horizontal edge partway down, which read as
   a rendering artefact rather than an effect. A plain bar is the calmer
   answer and it means the wordmark is legible at every scroll position.
   `.past` still gates the CTA — that is a separate decision. */
.site {
  position: sticky; top: 0; z-index: 60;
  margin-bottom: calc(var(--headh) * -1 - 1px);
  background: rgba(11,23,16,.97);
  border-bottom: 1px solid rgba(247,245,240,.14);
}
.site-in { display: flex; align-items: center; gap: var(--s4); min-height: var(--headh); }
.site .mark { color: var(--cream); text-decoration: none; margin-right: auto; }
.site-nav { display: none; }
.site .cta { display: none; }
@media (min-width: 64rem) {
  .site-nav { display: flex; align-items: center; gap: var(--s4); }
  .site.past .cta {
    display: inline-flex; width: auto; box-shadow: none;
    min-height: 44px; font-size: .72rem; padding-inline: 1.4rem;
  }
}
.site-nav a {
  color: rgba(247,245,240,.82); text-decoration: none;
  font-family: var(--display); font-size: .76rem; font-weight: 800;
  letter-spacing: .13em; text-transform: uppercase; padding-block: .5rem;
}
.site-nav a:hover { color: var(--gold); }
[id] { scroll-margin-top: calc(var(--headh) + var(--s2)); }

/* ── sticky CTA (below 64rem) ─────────────────────── */
.sticky {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 50;
  background: rgba(11,23,16,.97); color: var(--cream);
  border-top: 1px solid rgba(247,245,240,.14);
  transform: translateY(110%); transition: transform .28s ease;
}
.sticky.on { transform: none; }
.sticky-in { display: flex; align-items: center; padding-block: .6rem; }
@media (max-width: 63.99rem) { body { padding-bottom: 5rem; } }
@media (min-width: 64rem) { .sticky { display: none; } }

/* ── motion: interactions, not animation ──────────────
   Visible by default; hidden only once the script has confirmed it is running
   and can bring them back. The .js class is added by the first line of that
   script.

   This was previously the other way round: .rise started at opacity 0 and the
   script added .in. On ClickFunnels the footer script had not been pasted, so
   the entire stats band rendered invisible with no clue why.
   Content that disappears when a script is missing is a defect, not an
   animation. The animation is now the enhancement it was always meant to be. */
.rise { opacity: 1; transform: none; }
.js .rise { opacity: 0; transform: translateY(18px); transition: opacity .6s ease, transform .6s ease; }
.js .rise.in { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) {
  * { transition-duration: .01ms !important; animation-duration: .01ms !important; }
  .rise, .js .rise { opacity: 1; transform: none; }
}

/* ── hero ─────────────────────────────────────────────
   Text left, portrait right at ~48% of the grid, the photograph fading into
   the ground rather than sitting in a box. Mobile reorders to image →
   headline → button → paragraph: almost nobody reads a paragraph first. */
.hero { position: relative; background: var(--deeper); color: var(--cream); overflow: hidden; }
.hero-grid { display: grid; align-items: center; }
@media (min-width: 64rem) {
  /* 45 / 55 in favour of the photograph. Golf imagery sells; the copy above
     the fold only has to earn the click. */
  .hero-grid { grid-template-columns: minmax(0,.9fr) minmax(0,1.1fr); gap: var(--s4); }
  /* stretch, not center: the wrap must fill the hero so the photograph
     reaches its bottom edge. Centring it left a strip of bare ground under
     the image and a hard horizontal cut where the photo stopped. */
  .hero { min-height: min(78vh, 42rem); display: grid; align-items: stretch; }
}

.hero-in {
  display: flex; flex-direction: column; align-items: flex-start;
  padding-block: clamp(2rem, 5vw, 4rem);
  order: 2;
}
@media (min-width: 64rem) {
  /* The header is sticky and overlaps the hero by design, so the copy has to
     clear it itself. Without this the eyebrow sits behind the wordmark once
     the text block is tall enough to fill the hero. The photograph is not
     padded — it should still run to the top edge, under the header. */
  .hero-in {
    order: 0;
    padding-top: calc(var(--headh) + clamp(1.5rem, 4vw, 3.5rem));
    padding-bottom: clamp(3rem, 7vw, 6rem);
  }
}
/* Not gold. This label sits directly above the gold button, so two gold
   objects were competing in the one place on the page where the button should
   be unmistakably the only thing to act on. */
.hero-in .eyebrow { color: rgba(247,245,240,.6); margin-bottom: var(--s2); }
/* 14ch lands the headline on three lines and breaks it on sense:
   "Build a swing / that holds up / when it matters." */
.hero h1 { color: #fff; max-width: 14ch; font-size: clamp(2.5rem, 5vw, 4.25rem); }
.hero-in .cta { margin-top: var(--s4); }
.hero-in .price { color: rgba(247,245,240,.62); margin-top: var(--s1); }
/* on mobile the paragraph follows the button */
.hero-in .lede { color: rgba(247,245,240,.78); order: 9; margin-top: var(--s4); }
@media (min-width: 64rem) {
  .hero-in .eyebrow { order: 1; }
  .hero-in h1       { order: 2; }
  .hero-in .lede    { order: 3; margin-top: var(--s3); }
  .hero-in .cta     { order: 4; margin-top: var(--s3x); }
  .hero-in .price   { order: 5; }
}

.hero-shot {
  position: relative; order: 1;
  min-height: 22rem; aspect-ratio: 4 / 5;
  background-image: var(--img-hero);
  background-size: cover; background-position: center 30%;
  margin-inline: calc(var(--gutter) * -1);
}
@media (min-width: 64rem) {
  /* Bleeds to the right edge by being positioned against .hero, which is the
     true document width. The obvious version — a negative margin of
     calc((100vw - maxw) / -2 - gutter) — overshoots by the scrollbar width,
     because 100vw includes the scrollbar and the layout box does not. The
     overhang then gets clipped by .hero's overflow, cutting the photograph. */
  .hero-shot {
    position: absolute; inset: 0 0 0 auto; width: 55%;
    order: 0; margin: 0; aspect-ratio: auto; min-height: 0;
    /* Source is 1080×1920; this container shows only a ~41% band of it. At
       25% his head landed 42% down the frame, which read as sitting low.
       35% moves the window down the source and lifts him to ~28%. */
    background-position: center 35%;
  }
  /* the copy column stays above the photograph */
  .hero-in { position: relative; z-index: 1; }
}
/* the fade: photograph dissolving into the ground, not a hard edge */
/* The bottom stop must reach full opacity. Anything less leaves a visible
   horizontal line where the photograph ends. */
.hero-shot::after {
  content: ''; position: absolute; inset: 0;
  background: linear-gradient(180deg, rgba(11,23,16,.7) 0%, rgba(11,23,16,0) 42%, rgba(11,23,16,1) 100%);
}
@media (min-width: 64rem) {
  /* left side ~10% darker than before, so the white headline separates hard */
  .hero-shot::after {
    background:
      linear-gradient(90deg, rgba(11,23,16,1) 0%, rgba(11,23,16,.86) 16%, rgba(11,23,16,.62) 34%, rgba(11,23,16,0) 70%),
      linear-gradient(180deg, rgba(11,23,16,.55) 0%, rgba(11,23,16,0) 22%, rgba(11,23,16,0) 74%, rgba(11,23,16,1) 100%);
  }
}

/* ── stats ────────────────────────────────────────────
   Large enough to be read from across a room. Numbers build trust; a small
   stat band asks to be skipped. */
/* same ground as the hero, so the photograph dissolves into the stats rather
   than crossing a colour step. Hero and stats read as one dark block. */
.statband { background: var(--deeper); }
/* Asymmetric on purpose. The hero shares this ground and its lower half
   already supplies the air above, so a symmetric 64px top left the numbers
   stranded in a void. The generous bottom is the real separation, against
   the cream section below. */
.stats {
  display: grid; gap: var(--s4);
  padding-block: clamp(1rem, 2vw, 1.5rem) clamp(3rem, 6vw, 4.5rem);
}
@media (min-width: 48rem) { .stats { grid-template-columns: repeat(3,1fr); gap: var(--gap); } }
.stat { display: flex; flex-direction: column; gap: var(--s1); }
.stat + .stat { border-top: 1px solid rgba(247,245,240,.14); padding-top: var(--s4); }
@media (min-width: 48rem) {
  .stat + .stat { border-top: 0; border-left: 1px solid rgba(247,245,240,.14); padding-top: 0; padding-left: var(--gap); }
}
.stat .n {
  font-family: var(--display); font-size: clamp(3.4rem, 8vw, 6rem);
  font-weight: 800; letter-spacing: -.05em; line-height: .88;
  color: var(--gold); font-variant-numeric: tabular-nums;
}
.stat .l {
  font-size: .76rem; font-weight: 700; letter-spacing: .16em;
  text-transform: uppercase; color: rgba(247,245,240,.62);
}
.stat .n.empty { color: rgba(200,163,86,.4); }

/* ── photo blocks ─────────────────────────────────── */
.shot {
  position: relative; border-radius: var(--radius); overflow: hidden;
  aspect-ratio: 4 / 5;
  background-size: cover; background-position: center;
  box-shadow: var(--lift-2);
}
/* min-height, not aspect-ratio alone. This was the only image block on the
   page whose height came purely from aspect-ratio: the hero shot is absolutely
   positioned, the About portrait and the statement band both carry their own
   min-height, and the closer takes height from its content. If aspect-ratio
   resolves to zero for any reason the div collapses silently and shows
   nothing, while every other photograph on the page carries on regardless.
   A floor costs nothing when aspect-ratio works and saves the section when it
   does not. */
.duo .shot { min-height: 20rem; }
@media (min-width: 64rem) { .duo .shot { aspect-ratio: 5 / 6; min-height: 26rem; } }
/* 38%, not center. s3-band is a 2.33:1 landscape shown in a 5/6 portrait box,
   so cover scales it by height and crops ~64% of the width away. The figure
   sits at ~42% across the source, which left him at ~26% across the visible
   slice: noticeably left of centre. 38% brings him to the middle.
   The vertical value is inert here. The crop is height-driven, so the full
   height of the source always shows and there is nothing to move within. */
.s3 { background-image: var(--img-links); background-position: 38% 40%; }
.s7 { background-image: var(--img-portrait); background-position: center 22%; }
/* 55%, not center. P6 is a 2:3 portrait shown in a 5/6 box, so cover scales it
   by width and crops ~20% of the height away. The club head sits at ~14% down
   the source and his back foot at ~86%, which is a 72% span that only just
   fits the 80% window: at `center` the feet clear the bottom edge by 2%, and
   at 40% the club head is cut. 55% centres the slack, leaving ~3.5% at both
   ends. Do not change this without re-checking both ends of the swing. */
.s9 { background-image: var(--img-coaching); background-position: center 55%; }

/* A missing image shows as a gold block naming what belongs there, not as a
   stand-in image and not as an empty grey box that could be mistaken for a
   loading state. Hard rule 8: an unsupplied asset must be impossible to ship
   by accident. Add .is-placeholder to any .shot or .book-cover whose source
   has not arrived, and remove it when the file lands.

   Both selectors are deliberately two-class (0,2,0) so they beat the image
   classes regardless of source order. A bare .is-placeholder would tie with
   .b-drills and .s9 and depend on where it happens to sit in the file. */
.shot.is-placeholder,
.book-cover.is-placeholder {
  background-image: none;
  background-color: var(--gold);
  box-shadow: none;
  display: grid; place-items: center;
}
.shot.is-placeholder span,
.book-cover.is-placeholder span {
  font-family: var(--display); font-weight: 800; font-size: 1rem;
  letter-spacing: .1em; text-transform: uppercase; color: var(--ink);
}

/* ── benefits: one featured, four supporting ──────────
   Equal cards give a reader no way in. Size is the fastest hierarchy signal
   there is, so the thing they are actually buying gets the large box. */
.feature {
  background: var(--deep); color: var(--cream); border-radius: var(--radius);
  padding: clamp(1.75rem, 4vw, 3rem);
  display: grid; gap: var(--s3); align-items: center;
  /* No shadow. A dark filled panel on cream separates itself; lifting it as
     well was the card treatment arriving where it was not needed. Shadows are
     now reserved for photographs, where depth is describing something real. */
  margin-bottom: var(--s3x);
}
.feature:hover { transform: translateY(-4px); box-shadow: 0 20px 44px rgba(11,23,16,.16); }
@media (min-width: 64rem) { .feature { grid-template-columns: minmax(0,1fr) minmax(0,1fr); gap: var(--gap); } }
/* Doubled and centred on the client's annotation of 2026-09-06. This is now the
   second-largest type on the page after the hero H1, which is a deliberate
   promotion: the feature panel is the page's single statement of what the
   product IS, and at 2.25rem it read as a card heading rather than a claim. */
.feature h3 {
  font-size: clamp(2.75rem, 5.5vw, 4.5rem);
  letter-spacing: -.03em; line-height: 1.05; color: #fff;
  text-align: center;
}
.feature p { color: rgba(247,245,240,.72); }
.feature .eyebrow { color: rgba(247,245,240,.55); display: block; margin-bottom: var(--s2); }

.cards { display: grid; gap: var(--s3); }
@media (min-width: 48rem) { .cards { grid-template-columns: repeat(2,1fr); } }
@media (min-width: 75rem) { .cards { grid-template-columns: repeat(4,1fr); } }
/* No border, no shadow, no radius. Four bordered boxes with drop shadows are
   the most template-like object in web design, and they were making a list of
   four short facts look like a pricing table. A hairline above each does the
   same separating work with none of the packaging.
   .slot keeps its dashed frame — that one is meant to look unfinished. */
.card {
  display: flex; flex-direction: column; gap: var(--s1);
  padding-top: var(--s3);
  border-top: 1px solid rgba(18,36,26,.16);
}
.card p { color: var(--mute); font-size: .93rem; }
/* The dashed pending card keeps its frame and its padding back. */
.card.slot { padding: var(--s3); border-top: 0; }

/* ── proof ────────────────────────────────────────── */
.quotes { display: grid; gap: var(--s3); }
@media (min-width: 48rem) { .quotes { grid-template-columns: repeat(2,1fr); } }

/* Six short quotes under the two before/after cards. Transcribed into the
   page's own type rather than shown as WhatsApp and Skool screenshots: phone
   chrome is unreadable on a phone, it fights the palette, and a screenshot
   carries the sender's profile photo, which nobody consented to publish.
   First names only, for the same reason.
   Exactly six, which divides evenly at 3-up, 2-up and 1-up. Adding a seventh
   leaves a dead cell at every breakpoint above mobile.

   ⚠ NAMED .said, NOT .note. A .note class already exists further down this
   stylesheet as a small muted footnote with a gold left border. It is declared
   AFTER this block, so it won the cascade and rendered these six testimonials
   as footnotes: shrunken grey text with a gold bar down the side. Renamed
   rather than reordered, because two unrelated things sharing one class name
   will collide again the next time either of them moves. */
/* --gap (up to 4rem) is the column gutter for full-bleed two-column rows and
   is far too wide between small cards; it left the three columns looking
   unrelated. A single even gap at the card scale instead. */
.saids { display: grid; gap: var(--s3); margin-top: var(--s4); }
/* 3-up from 62rem (992px), not 75rem (1200px). Six short quotes at 992px are
   roughly 300px wide each, which is comfortable for two or three lines. The
   old 1200px threshold meant an ordinary laptop window, or any browser zoomed
   past 100%, fell back to 2-up and the section read as a long vertical list
   instead of the intended 3 x 2 block. */
@media (min-width: 48rem) { .saids { grid-template-columns: repeat(2,1fr); } }
@media (min-width: 62rem) { .saids { grid-template-columns: repeat(3,1fr); } }

/* Same card as the two before/after testimonials above them: white on cream,
   hairline border, one radius. The section then reads as a single body of
   proof at two scales rather than two unrelated components, and the six short
   quotes stop looking like a footnote list bolted underneath.

   This is not the "four bordered boxes" the design pass rejected. That
   objection was about a set of FEATURES dressed as a pricing table. These are
   quotes from named people, and a card is what separates one person's words
   from the next. */
.said {
  /* margin:0 is load-bearing, for the same reason as on .said blockquote.
     A <figure> carries a browser-default margin of 1em 40px, so without this
     every card sits 80px narrower than its grid cell and the 24px gutter
     renders as ~104px. It reads as "the boxes are floating and the gaps are
     enormous", not as a margin bug. .quote resets this; .said did not. */
  margin: 0;
  display: flex; flex-direction: column;
  background: #fff;
  border: 1px solid rgba(18,36,26,.12);
  border-radius: var(--radius);
  padding: clamp(1.25rem, 2.4vw, 1.6rem);
}
/* margin:0 is load-bearing. A <blockquote> carries a default 40px left margin,
   so without it the quote sits indented while its attribution stays flush
   left, and the column reads as broken alignment rather than as a quote. */
.said blockquote {
  margin: 0;
  font-size: 1.02rem; line-height: 1.55; color: var(--ink);
}
/* margin-top:auto pins every attribution to the bottom of its card, so the
   names line up across a row even though the quotes are different lengths.
   The rule above it separates the person from their words at a consistent
   height, which is what makes the row scan as a set. */
.said figcaption {
  margin-top: auto; padding-top: var(--s3);
  border-top: 1px solid rgba(18,36,26,.09);
  font-size: .8rem; color: var(--mute);
}
.said figcaption b {
  display: block; font-family: var(--display); font-weight: 800;
  font-size: .74rem; letter-spacing: .12em; text-transform: uppercase;
  color: var(--ink); margin-bottom: 2px;
}

/* Real testimonials, supplied 2026-08-03. The metric leads, because the copy
   deck's selection rule is specific-over-glowing and the number is the part a
   stranger can check themselves. Left-aligned inside a centred section header:
   a quotation is read, not scanned. */
.quote {
  display: flex; flex-direction: column; gap: var(--s2); margin: 0;
  background: #fff; border: 1px solid rgba(18,36,26,.12);
  border-radius: var(--radius); padding: clamp(1.5rem, 3vw, 2rem);
  text-align: left;
}
.quote .gain {
  font-family: var(--display); font-size: clamp(1.6rem, 3.4vw, 2.2rem);
  font-weight: 800; letter-spacing: -.03em; line-height: 1;
  color: var(--ink); font-variant-numeric: tabular-nums;
}
/* The before/after pair, above the quote. A quote is a claim; a quote with
   two frames above it is evidence, and this audience reads swing positions
   for a living.

   Displayed large on purpose. These are two-frame comparisons: shrink them and
   the comparison stops being legible, which is the only reason they are here.

   3/4 normalises three sources that are 0.70, 0.72 and 0.77 — all close enough
   that cover trims only sky and grass. It keeps the cards the same height so
   the quotes start on the same line. */
/* max-width, not full card width. At two columns in a 1200px grid a card is
   ~570px, and a 3:4 image filling it renders 570x760 — which made the whole
   results section over 1100px tall and read as a photo gallery with captions
   rather than testimonials with evidence.

   17rem lands each pair at 272x363. Each of the two frames inside is then
   ~136px wide, which is still enough to read the shapes that matter: shoulder
   tilt, club position, where the hands are. Any smaller and the comparison
   stops working, which is the only reason these are on the page.

   aspect-ratio with cover keeps both cards identical in height despite sources
   of 0.70 and 0.77, so the quotes below them start on the same line. */
.quote .ba {
  /* flex: none is load-bearing. .quote is a flex column, so this image is a
     flex item with the default flex-shrink: 1, and the grid stretches both
     cards to the taller one's content height. The browser then negotiates the
     image's height to make the column fit, and aspect-ratio loses that
     argument. The two pairs rendered ~140px different in height because of it. */
  flex: none;
  /* An explicit height rather than aspect-ratio, so there is nothing left to
     negotiate: both cards are identical whatever the sources or the copy
     lengths do. 272x368 sits between the two natural ratios (0.70 and 0.77),
     so cover trims a few percent of sky from one and a few percent of width
     from the other, and nothing that matters from either. */
  width: 100%; max-width: 17rem; height: 23rem;
  object-fit: cover;
  /* Centred, while the copy below stays left-aligned. Capped at 17rem inside a
     ~34rem card, a left-aligned image leaves a broad band of empty white down
     one side that reads as a layout fault rather than intent. The type is read
     and stays on the left margin; the photograph is looked at and sits in the
     middle of its own frame. */
  display: block; margin-inline: auto;
  border-radius: var(--radius); background: var(--deep);
}
.quote blockquote { margin: 0; font-size: 1.02rem; line-height: 1.55; }
.quote figcaption {
  display: flex; flex-direction: column; gap: 2px; margin-top: auto;
  padding-top: var(--s2); border-top: 1px solid rgba(18,36,26,.09);
}
.quote figcaption b {
  font-family: var(--display); font-weight: 800; font-size: .78rem;
  letter-spacing: .14em; text-transform: uppercase;
}
.quote figcaption span { font-size: .76rem; color: var(--mute); }

/* ── timeline ─────────────────────────────────────────
   A process is a sequence, so it gets a rail rather than four more boxes.
   Horizontal on desktop, vertical on a phone. */
.rail { display: grid; gap: var(--s4); position: relative; }
/* Three columns, because there are three steps. This was left at repeat(4,1fr)
   when the rail went from four steps to three, so the nodes packed into the
   first three quarters of the grid and the last quarter sat empty — the rail
   stopped a third of the way from the right edge with nothing to explain why. */
@media (min-width: 64rem) { .rail { grid-template-columns: repeat(3,1fr); gap: var(--gap); } }
.node { position: relative; display: flex; flex-direction: column; gap: var(--s2); padding-left: 3.25rem; }
@media (min-width: 64rem) { .node { padding-left: 0; padding-top: 3.25rem; } }
.node::before {
  content: attr(data-n);
  position: absolute; left: 0; top: 0;
  width: 2.25rem; height: 2.25rem; border-radius: var(--radius-sm);
  display: grid; place-items: center;
  background: none; color: var(--ink); border: 1.5px solid rgba(18,36,26,.3);
  font-family: var(--display); font-weight: 800; font-size: .95rem;
  z-index: 1;
}
/* the connecting rail */
.node::after {
  content: ''; position: absolute;
  left: 1.09rem; top: 2.25rem; bottom: calc(var(--s4) * -1);
  width: 2px; background: rgba(18,36,26,.14);
}
.node:last-child::after { display: none; }
@media (min-width: 64rem) {
  .node::after { left: 2.25rem; top: 1.09rem; bottom: auto; right: calc(var(--gap) * -1); width: auto; height: 2px; }
}
.node p { color: var(--mute); font-size: .95rem; }

/* ── academy screenshot ── */
/* .gallery and .shotslot removed 2026-08-06. The section is one real
   screenshot now, so the three-column grid and the dashed placeholder boxes
   had no markup left to style. */

/* A real screenshot, captioned. Per the copy deck: a screenshot without a
   caption is decoration; with a caption it is proof. */
/* Capped and centred. Spanning the full 1200px row rendered it 1200x790, which
   dominated everything around it. 56rem keeps the lesson titles and the notes
   legible — the whole point of showing it — without the screenshot becoming
   the largest object on the page. */
/* Full grid width, sharing the heading's left edge.
   It was capped at 56rem and centred, under a heading that is left-aligned, so
   the two had no edge in common and the image floated in the middle of its own
   section. Spanning the grid fixes the alignment and lets the screenshot be
   the focal point the section is built around, which it should be: it is the
   only look inside the product on the whole page. */
.ss { margin: 0; }
.ss img {
  width: 100%; height: auto; display: block;
  border-radius: var(--radius); box-shadow: var(--lift-2);
}
.ss figcaption { margin-top: var(--s2); font-size: .9rem; color: var(--mute); }
/* The section moved to deep green, where --mute is far too dark to read. */
.on-deep .ss figcaption { color: rgba(247,245,240,.62); }

/* ── statement band ───────────────────────────────────
   A deliberate pause. No heading, no CTA, nothing to do. Sits between the
   product and the person so the page stops arguing for a moment. Centred,
   because this is the one place on the page that is purely emotional. */
.statement {
  /* 82vh. This is the page's one cinematic beat and it earns the room: a
     full-bleed photograph, one line, nothing to click.

     It was cut to 19rem on a scrolling complaint, which was the right
     engineering answer and the wrong creative one — at 304px it became a strip,
     and a strip cannot stop anyone. The height it costs is bought back out of
     the repetitive middle sections, where the same pixels were spent on
     eyebrows and card padding nobody remembers.

     Uniform length is what makes a page feel long, not length itself. */
  position: relative; min-height: min(82vh, 46rem);
  display: grid; place-items: center; text-align: center;
  background-image: var(--img-statement);
  background-size: cover; background-position: center 42%;
  color: var(--cream); padding-block: var(--s5);
}
.statement::before {
  content: ''; position: absolute; inset: 0;
  background: linear-gradient(180deg, rgba(11,23,16,.74), rgba(11,23,16,.5) 50%, rgba(11,23,16,.74));
}
.statement > .wrap { position: relative; }
.statement .eyebrow { color: rgba(247,245,240,.6); margin-bottom: var(--s3); }
.statement .line {
  max-width: 16ch; margin-inline: auto; color: #fff;
  font-family: var(--display); font-weight: 800;
  font-size: clamp(2.3rem, 6vw, 5rem); line-height: 1.02;
  letter-spacing: -.038em;
}

/* ── about ────────────────────────────────────────────
   Portrait dominates. People buy the coach before they buy the curriculum. */
/* uppercase is reserved for the hero, section titles, nav and buttons. A
   person's name set in caps reads as a headline, not as an introduction. */
.about h2 { text-transform: none; letter-spacing: -.03em; }
/* "Bigger font" on the client's annotation of 2026-08-29. Sits between the H1
   and the standard H2, so the two headings that are a person's name read as
   names rather than as section labels. Still clearly under the hero, which is
   what keeps the page's type hierarchy intact. */
.about-name {
  font-size: clamp(2.1rem, 3.6vw, 3rem);
  letter-spacing: -.03em; text-transform: none; line-height: 1.05;
}
.about { display: grid; gap: var(--gap); align-items: start; }
.about .shot { aspect-ratio: 4 / 5; }
/* Desktop: the portrait takes its height from the row rather than from a fixed
   4/5 ratio. Until 2026-08-29 the two columns happened to be about the same
   height. Adding the coaching philosophy made the text column ~250px taller,
   and a fixed-ratio photo stopped well short of it, leaving a void in the left
   column that reads as a layout bug rather than as whitespace.
   The crop absorbs the change: the source is a 2:3 portrait in a box that is
   now slightly narrower than that, so `cover` trims a little from the sides and
   `background-position: center 22%` keeps his face exactly where it was.
   Mobile is untouched. At one column there is no second column to match, and
   the fixed 4/5 is what keeps the photo from running the whole screen. */
@media (min-width: 64rem) {
  .about { grid-template-columns: minmax(0,1.35fr) minmax(0,.85fr); align-items: stretch; }
  .about .shot { aspect-ratio: auto; height: 100%; min-height: 40rem; }
}
.creds { display: flex; flex-direction: column; gap: var(--s2); margin-top: var(--s3); }
.cred { display: flex; gap: var(--s2); align-items: baseline; padding-block: var(--s2); border-top: 1px solid rgba(18,36,26,.14); }
.cred b { font-family: var(--display); font-weight: 800; color: var(--ink); font-size: .8rem; letter-spacing: .1em; text-transform: uppercase; white-space: nowrap; }
.cred span { color: var(--mute); font-size: .95rem; }
/* A member's line about how he coaches, on the light About ground. It cannot
   reuse .pull (white text, built for the dark sections) and it deliberately
   does not go in the proof grid: it is repeat(2,1fr) and a third child
   would sit in a dead half-row. No gold here either. Gold is the CTA and the
   stat numbers, and spending it on a quote would flatten the CTA. */
.vouch {
  /* margin shorthand, not margin-top: a <figure> defaults to 1em 40px, so
     setting only the top leaves 40px of side margin indenting the whole
     quote away from the credentials above it. */
  margin: var(--s3) 0 0;
  padding: var(--s3) var(--s3) var(--s3) var(--s3x);
  border-left: 2px solid rgba(18,36,26,.18);
  background: rgba(18,36,26,.035);
}
.vouch blockquote { font-size: 1.05rem; line-height: 1.5; color: var(--ink); }
.vouch figcaption { margin-top: var(--s2); font-size: .9rem; color: var(--mute); }
.sig {
  font-family: var(--display); font-size: 1.5rem; font-weight: 800;
  letter-spacing: -.02em; margin-top: var(--s3); color: var(--ink);
}

/* ── slots ────────────────────────────────────────── */
.slot {
  border: 1.5px dashed var(--gold-dk); border-radius: var(--radius);
  padding: var(--s3); background: rgba(200,163,86,.09);
}
.on-deep .slot { border-color: var(--gold); background: rgba(200,163,86,.1); }
.slot .eyebrow { display: block; margin-bottom: var(--s1); }
.slot p { color: var(--mute); font-size: .9rem; }
.on-deep .slot p { color: rgba(247,245,240,.65); }

/* ── guides band ──────────────────────────────────── */
/* A composed offer block, not a promo strip. This used to be one line of text
   with the button floated to the far right edge of the card, which is the
   layout of a banner ad and read as an afterthought. The button now sits under
   the copy, on the copy's own axis.

   Centred, which is a deliberate exception to the page's left-aligned default.
   A bounded card is its own frame, so centring reads as composed here where it
   would read as drifting in a full-width band. It also stops the card sitting
   as a narrow left-aligned block against a wide empty right side.

   Hierarchy is carried by case as much as size. Academy sections are uppercase
   h2 up to 50px; this is a sentence-case h3 at 31px; coaching below has no
   heading at all. The button stays cta-2 and no price appears here, so the
   section can gain presence without gaining rank. */
.offer {
  display: flex; flex-direction: column; align-items: center; text-align: center;
  border: 1px solid rgba(18,36,26,.12); border-radius: var(--radius);
  padding: clamp(1.75rem, 4vw, 3rem);
  background: #fff;
}
.offer .eyebrow { margin-bottom: var(--s1); }
.offer h3 {
  font-size: clamp(1.5rem, 2.6vw, 1.95rem);
  line-height: 1.1; letter-spacing: -.022em; text-transform: none;
  max-width: var(--measure-wide);
}
.offer .lede { margin-top: var(--s2); margin-inline: auto; }
.offer .cta-2 { margin-top: var(--s3); }

/* ── guide covers ─────────────────────────────────────
   Two covers below the card, not inside it. The card makes the argument; the
   covers are the product. Each cover already carries its own title, promise
   and byline, so the card under it adds one line of outcome and a button and
   nothing else. Repeating the title as a heading would say it twice.

   Capped at 46rem and centred. Left full width, two covers on a 1312px grid
   render at ~620px each, which is larger than the hero portrait and makes the
   secondary offer the biggest thing on the page.

   No price on either, deliberately: a visible $50 beside $99/month invites a
   comparison this page does not want. See copy/landing-page.md section 8. */
.books {
  display: grid; gap: var(--s4);
  grid-template-columns: 1fr;
  max-width: 46rem; margin: var(--s4x) auto 0;
}
@media (min-width: 48rem) { .books { grid-template-columns: repeat(2, 1fr); } }
.book {
  display: flex; flex-direction: column; align-items: center;
  text-align: center; gap: var(--s3);
}
/* 1000/1590 is the covers' own ratio. Fixed here rather than left to the image
   so the two sit level even while both are still placeholders. */
/* The gold background-COLOUR is the missing-file state, and the cover's
   background-IMAGE paints over it. So an absent file shows a gold block rather
   than a broken-image icon, and dropping the real file into img/ needs no code
   change at all: save, reload, done. */
.book-cover {
  width: 100%; aspect-ratio: 1000 / 1590;
  border-radius: var(--radius-sm); box-shadow: var(--lift-2);
  background-color: var(--gold);
  background-size: cover; background-position: center;
}
.b-drills { background-image: var(--img-drills); }
.b-slice  { background-image: var(--img-slice); }
.book p { margin: 0; color: var(--mute); font-size: .98rem; }
/* Outline, never solid. Solid gold is the membership's alone. */
.book .cta-2 { width: 100%; max-width: 18rem; }

/* Tertiary. A footnote under the guides card, on the card's own centre line,
   so the eye reads card-then-aside instead of hitting an unattached sentence.
   No heading, muted colour, text link: it cannot outrank anything. */
.aside {
  display: flex; flex-direction: column; align-items: center; gap: var(--s1);
  margin-top: var(--s3); text-align: center; color: var(--mute); font-size: .92rem;
}
@media (min-width: 48rem) {
  .aside { flex-direction: row; justify-content: center; align-items: baseline; gap: var(--s2); }
}

/* ── 1-on-1 coaching ──────────────────────────────────
   The waitlist line, set larger than the .aside footnote it replaced, on the
   client's "this info in bigger font" note of 2026-08-29. It is still prose
   with a text link under it and no gold anywhere, which is the only thing now
   holding coaching below the membership and the guides. If this ever grows a
   button, the brief's CTA hierarchy is broken. */
.coach-note { margin-top: var(--s3); font-size: 1.15rem; }
.coach-note + .cta-3 { display: inline-flex; margin-top: var(--s2); }

/* ── faq ──────────────────────────────────────────── */
/* Heading above the list, both centred on the page's axis.
   This was a .split: heading in a 0.7fr column, accordion in a 1.3fr column.
   With five items the list runs ~800px tall while the heading is three lines,
   so the left column sat empty for almost the whole section — the single
   largest void on the page. A short heading beside a long list is a layout
   that only works when the two are close in height.
   48rem keeps the questions at a readable width rather than letting a
   one-line question stretch across 1200px. */
.faq {
  display: flex; flex-direction: column; gap: var(--s2);
  max-width: 48rem; margin-inline: auto;
}
details {
  border: 1px solid rgba(18,36,26,.12); border-radius: var(--radius);
  background: #fff;
  transition: border-color .18s ease;
}
details:hover { border-color: rgba(18,36,26,.28); }
details[open] { border-color: rgba(18,36,26,.3); }
details.pending { border-style: dashed; border-color: var(--gold-dk); background: rgba(200,163,86,.09); box-shadow: none; }
details.pending > summary { color: var(--gold-dk); }
summary {
  cursor: pointer; list-style: none;
  padding: var(--s3); min-height: 56px;
  font-family: var(--display); font-weight: 700; font-size: 1.02rem;
  letter-spacing: -.01em; color: var(--ink);
  display: flex; justify-content: space-between; gap: var(--s3); align-items: center;
}
summary::-webkit-details-marker { display: none; }
details[open] > summary { padding-bottom: var(--s2); }
summary::after {
  content: ''; flex: none; width: .5rem; height: .5rem; margin-right: .2rem;
  border-right: 2px solid rgba(18,36,26,.45); border-bottom: 2px solid rgba(18,36,26,.45);
  transform: translateY(-25%) rotate(45deg);
  transition: transform .18s ease;
}
details[open] > summary::after { transform: translateY(10%) rotate(-135deg); }
details p { padding: 0 var(--s3) var(--s3); color: var(--mute); font-size: .95rem; }

/* ── closing footer ───────────────────────────────────
   The closing CTA and the footer are one block. Two separate ones put two
   identical gold buttons within a screen of each other, which reads as
   crowding rather than emphasis. The ask happens once, on the photograph,
   and the links sit quietly underneath it. */
.closer { position: relative; color: var(--cream); background: var(--deeper); font-size: .82rem; }
/* Only the ask sits on the photograph. The links and the legal line sit on
   solid ground below it: chrome over a photograph reads as decoration, and
   stretching the image behind all three rows is what made the footer look
   like a second hero. */
.closer-photo {
  position: relative;
  background-image: var(--img-closer);
  /* 50%, and the crop does the aiming. A full-bleed band 280px tall is 7:1 at
     desktop width and can only ever show ~200px of the source, so wherever the
     figure sits in the crop is what this band shows. Steering with
     background-position instead means the framing changes with viewport width
     and breaks at one end or the other. See make-crops.ps1 FocalY 0.55. */
  background-size: cover; background-position: center 50%;
}
/* lighter than before: at .9 the photograph was only texture. The last stop
   is opaque --deeper so the band dissolves into the footer with no seam. */
.closer-photo::before {
  content: ''; position: absolute; inset: 0;
  background: linear-gradient(180deg, rgba(11,23,16,.82) 0%, rgba(11,23,16,.58) 40%, rgba(11,23,16,.9) 88%, #0B1710 100%);
}
.closer-photo > .wrap { position: relative; }
.closer a { color: rgba(247,245,240,.75); text-decoration: none; }
.closer a:hover { color: var(--gold); }

/* Was clamp(4.5rem, 10vw, 8rem) = 128px at width, against a --sy of 72px.
   Nearly double the standard section rhythm, wrapped around two lines of
   text, which is what read as "the footer is enormous". 80px gives the last
   ask a little more weight than a normal section without leaving a hole. */
.closer .inner {
  display: grid; gap: var(--gap); align-items: center;
  padding-block: clamp(2.5rem, 5vw, 4rem);
}
@media (min-width: 64rem) { .closer .inner { grid-template-columns: minmax(0,1fr) 23rem; } }
/* The closer is a closing statement, not a section header, so it keeps the
   larger setting the section headings gave up. */
.closer h2 { color: #fff; font-size: clamp(1.9rem, 3.6vw, 2.9rem); }
.closer .act { display: flex; flex-direction: column; gap: var(--s2); align-items: flex-start; }
.closer .price { color: rgba(247,245,240,.7); }

/* Legal links left, social right, on one line. The border moved from
   .foot-links to .foot-row so the rule spans the full width, rather than
   underlining only the left half once the icons sit beside it. */
.foot-row {
  display: flex; flex-wrap: wrap; align-items: center;
  justify-content: space-between; gap: var(--s3) var(--s4);
  padding-block: var(--s3);
  border-top: 1px solid rgba(247,245,240,.16);
}
.foot-links { display: flex; flex-wrap: wrap; gap: var(--s2) var(--s4); }

/* Inline SVG rather than an icon font or a CDN sprite: one less network
   request, and nothing that fails silently if a third party goes down.
   44px tap target per the design system's accessibility rule, even though the
   glyph inside is 20px. */
.foot-social { display: flex; align-items: center; gap: var(--s1); }
.foot-social a {
  display: grid; place-items: center;
  width: 44px; height: 44px;
  border-radius: 50%;
  color: var(--gold);
  transition: color .18s ease, background .18s ease;
}
.foot-social a:hover { color: var(--gold-lt); background: rgba(200,163,86,.12); }
.foot-social a:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
.foot-social svg { width: 20px; height: 20px; display: block; fill: currentColor; }
.foot-bottom {
  display: flex; flex-direction: column; gap: var(--s2);
  padding-block: var(--s3);
  border-top: 1px solid rgba(247,245,240,.1);
}
@media (min-width: 48rem) { .foot-bottom { flex-direction: row; justify-content: space-between; align-items: baseline; } }
.foot-note { color: rgba(247,245,240,.5); font-size: .78rem; max-width: var(--measure); }
.mark {
  font-family: var(--display); font-weight: 800; letter-spacing: .06em;
  text-transform: uppercase; color: var(--cream); font-size: 1rem;
}

.note { font-size: .82rem; color: var(--mute); border-left: 3px solid var(--gold); padding-left: var(--s2); max-width: var(--measure); }


/* ── modal ────────────────────────────────────────── */
dialog {
  border: 0; border-radius: var(--radius); background: var(--cream); color: var(--ink);
  padding: clamp(1.75rem, 4vw, 2.5rem);
  width: min(27rem, calc(100vw - 2rem));
  box-shadow: 0 30px 70px rgba(0,0,0,.45);
}
dialog::backdrop { background: rgba(9,18,13,.82); }
dialog form { display: flex; flex-direction: column; gap: 1.1rem; }
dialog h3 { font-family: var(--display); font-size: 1.75rem; font-weight: 800; text-transform: uppercase; letter-spacing: -.03em; }
.field { display: flex; flex-direction: column; gap: .35rem; }
.field label { font-size: .7rem; font-weight: 800; letter-spacing: .14em; text-transform: uppercase; color: var(--mute); }
.field input {
  font: inherit; font-size: 1rem; padding: .8rem .9rem;
  border-radius: var(--radius-sm); border: 1.5px solid rgba(18,36,26,.22);
  background: #fff; color: var(--ink);
}
.field input:focus-visible { outline: 2px solid var(--gold); outline-offset: 1px; border-color: var(--gold); }
/* Added 2026-08-03. index.html and LandingPage.jsx both validate in-modal and
   write into .err, but styles.css is generated from this file, so a class that
   only ever existed downstream had no rule at all and errors rendered as plain
   body text. Anything added to the production markup has to be added here too.
   Colour is not the only signal: the element carries role="alert" and the
   message itself says what to fix. */
.err { font-size: .82rem; font-weight: 650; color: #9B2C2C; display: none; }
.err:not(:empty) { display: block; }
.handover { font-size: .85rem; color: var(--mute); }
.handover strong { color: var(--ink); }
.modal-close {
  position: absolute; top: .5rem; right: .6rem;
  background: none; border: 0; cursor: pointer;
  color: var(--mute); font-size: 1.6rem; line-height: 1; width: 44px; height: 44px;
}
.handoff-list { margin: 0; padding-left: 1.2rem; display: flex; flex-direction: column; gap: .5rem; font-size: .9rem; color: var(--mute); }
.handoff-list strong { color: var(--ink); }
