/* Duffers Anonymous — marketing site.
 *
 * One stylesheet for every page. The previous site styled each of its five
 * pages with its own inline <style> block, which meant a change to the palette
 * was five edits kept in sync by hand — untenable once the SEO pages multiply
 * one template across many locations.
 *
 * The site is dark-only and paints itself with the --web-* palette, NOT the
 * app's surface tokens. The app is a light-first iOS product; the marketing
 * site has always been a dark green ground, and mixing the two would make both
 * look like a mistake.
 */

/* ---------- Reset ---------- */

*,*::before,*::after{box-sizing:border-box}

/* [hidden] must actually hide. The UA rule is `[hidden]{display:none}` at UA
   origin, which ANY author display declaration outranks — and .btn sets
   display:inline-flex. So `button.hidden = true` on a successful signup set the
   attribute, changed nothing, and left a disabled button reading "Adding you…"
   on screen as the terminal state of the only conversion on this site. The
   field beside it vanished correctly, because .waitlist__field never declares
   display. Worst of both: the input disappears and the dead button remains, so
   success looks exactly like a hang. */
[hidden]{display:none !important}

html{
  color-scheme:dark;
  scroll-behavior:smooth;
  /* Anchor links land below the fixed nav instead of under it. */
  scroll-padding-top:80px;
}

body{
  margin:0;
  background:var(--web-bg);
  color:var(--web-text);
  font:var(--type-body);
  -webkit-font-smoothing:antialiased;
}

/* height:auto is load-bearing, not boilerplate.
   Every img on this site carries width/height attributes so the browser can
   reserve space and avoid layout shift. Those attributes are PRESENTATIONAL
   HINTS: they map to the CSS width and height properties at zero specificity.
   So the moment a rule sets width — .shot__detail{width:min(360px,86%)},
   .device img{width:100%} — the width hint is overridden but the HEIGHT hint is
   NOT, and the image renders at the new width with its ORIGINAL height. A phone
   screenshot measured 150x521 on a phone instead of 150x326: squeezed to 65% of
   its width. (That example was .stage__screen, which the #how rebuild removed;
   the rule it demonstrates is unchanged and still load-bearing.)
   It was invisible on desktop only because the widths there happened to equal
   the attributes. Setting height:auto lets the aspect-ratio the browser derives
   from the same attributes do its job.
   Rules that genuinely want a fixed height (.media img, .hero__bg img) set it
   explicitly and still win on specificity. */
img{max-width:100%;height:auto}

a{color:var(--web-accent-bright);text-decoration:none}
a:hover{opacity:.8}

/* Skip link. Positioned off the top of the page rather than display:none —
   hidden content cannot take focus, and a skip link that cannot be focused is
   the one control whose entire job is being focusable. No transition: this
   needs to appear the instant it is reached, and an animated arrival is one
   more thing to exempt from prefers-reduced-motion. */
.skip{
  position:fixed;top:-120px;left:var(--space-md);z-index:60;
  padding:13px 18px;border-radius:var(--radius-xl);
  background:var(--web-accent);color:#fff;font-weight:700;font-size:14px;
}
.skip:focus{top:12px}
/* The skip target takes focus programmatically, so it must not draw a ring
   around the entire page body when it does. */
main:focus{outline:none}

/* Authored focus ring. The UA default survives on this site (nothing sets
   outline:none), but on a near-black ground its colour is whatever the engine
   picks and it differs per browser. Bright green reads against every surface
   here — page, card, green band, and the white CTA button — at one offset.
   :focus-visible, so a mouse click on a button doesn't leave a ring behind. */
:focus-visible{
  outline:2px solid var(--web-accent-bright);
  outline-offset:3px;
  border-radius:2px;
}

/* ---------- Layout ---------- */

.wrap{max-width:1120px;margin:0 auto;padding-left:var(--space-xl);padding-right:var(--space-xl)}
.wrap-narrow{max-width:760px;margin:0 auto;padding-left:var(--space-xl);padding-right:var(--space-xl)}

.section{padding-top:64px;padding-bottom:64px}

/* Two-column split that collapses to one on narrow screens without a media
   query — flex-wrap does it, and the min-widths decide when. */
.split{display:flex;gap:56px;align-items:center;flex-wrap:wrap}
.split__text{flex:1 1 400px;min-width:300px}
.split__media{flex:0 1 320px;margin:0 auto}

.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:var(--space-lg)}

/* ---------- Nav ---------- */

.nav{
  position:fixed;top:0;left:0;right:0;z-index:50;
  display:flex;align-items:center;gap:var(--space-xl);
  padding:var(--space-sm) var(--space-xl);
  background:color-mix(in srgb,var(--web-bg) 72%,transparent);
  backdrop-filter:blur(16px);
  border-bottom:1px solid var(--web-hairline);
}
.nav__brand{display:flex;align-items:center;gap:10px;color:var(--web-text);font-weight:700;font-size:16px;min-height:44px}
.nav__word{white-space:nowrap}

/* Two link sets, one in the box tree at a time — see the note in layout.html
   for why forcing a <details> open on desktop is not viable across engines.
   Crucially, `display` is never declared on the shared .nav__links class: an
   author display on the drawer panel would beat the UA rule that hides closed
   <details> content in Firefox and Safari, and the drawer would never close. */
.nav__links{gap:var(--space-lg);align-items:center;font-size:14px;flex-wrap:wrap}
.nav__links--bar{display:flex;margin-left:auto}
.nav__menu{display:none}

/* 14px text with no padding is a ~20px hit area — under half the 44px minimum,
   in the one bar a thumb reaches for most. The min-height buys the target
   without moving the text, since the flex centring keeps the baseline put.
   :not(.btn) is load-bearing. Without it this rule (0-1-1) outranks
   .btn--primary's own colour (0-1-0) and repaints the CTA's white label in link
   grey: 3.21:1 on the green, the only contrast failure on the site, on the one
   control present in every viewport of all 14 pages. */
.nav__links a:not(.btn){color:var(--web-link);display:flex;align-items:center;min-height:44px}

/* .btn declares font and padding LATER in this file at equal specificity, so a
   bare .nav__cta loses to it — the 14px and 13/18 here were dead for as long as
   they have existed, and the button rendered 17px with 14/24 padding. The .nav
   prefix puts them back on top.
   white-space keeps "Get the App" on one line: at 390px it broke into two and
   made the fixed bar 99px tall, 11.7% of the viewport, permanently. */
.nav .nav__cta{font-size:14px;padding:13px 18px;white-space:nowrap}

@media (max-width:699px){
  /* Four text links plus a CTA wrap to three rows inside a FIXED bar on a
     phone. They move into a drawer rather than out of reach — hiding them
     outright left /compare, the page written to answer "is this the app I
     want?", reachable only from the footer, and a footer is not navigation
     until you have already decided to leave. */
  .nav{gap:var(--space-md)}
  /* The wordmark cannot fit beside a toggle and a CTA at 390px — it wrapped to
     "Duffers / Anonymous" and became the tallest thing in a fixed bar the
     moment the CTA stopped wrapping. The mark is the app icon and still links
     home; the name is two lines further down in the hero anyway. */
  .nav__word{display:none}
  .nav__links--bar{display:none}
  .nav__menu{display:block;margin-left:auto}
  .nav__toggle{
    display:flex;align-items:center;gap:8px;
    min-height:44px;padding:0 2px;cursor:pointer;list-style:none;
    color:var(--web-link);font-size:14px;font-weight:600;
  }
  .nav__toggle::-webkit-details-marker{display:none}
  /* The chevron the FAQ already uses, at 8px instead of 10 — drawn in CSS,
     which is how the other disclosure on this site is built. */
  .nav__toggle::after{
    content:"";width:8px;height:8px;flex:none;
    border-right:2px solid var(--web-muted);border-bottom:2px solid var(--web-muted);
    transform:rotate(45deg) translateY(-25%);transition:transform .2s ease;
  }
  .nav__menu[open] .nav__toggle::after{transform:rotate(-135deg) translateY(-25%)}

  /* Absolute, not in flow: an open drawer must not make the fixed bar taller,
     or opening the menu would shove the content it is sitting over.
     Solid ground rather than the bar's 72% tint — this panel covers arbitrary
     page content, and a translucent one puts body copy behind link text. */
  .nav__menu[open] > .nav__links{
    display:flex;flex-direction:column;align-items:stretch;gap:0;
    position:absolute;top:100%;left:0;right:0;
    background:var(--web-bg);
    border-bottom:1px solid var(--web-hairline);
    padding:var(--space-xs) var(--space-xl) var(--space-md);
  }
  .nav__menu[open] > .nav__links a{min-height:48px}
  .nav__menu[open] > .nav__links a + a{border-top:1px solid var(--web-hairline)}
}

@media (prefers-reduced-motion:reduce){
  .nav__toggle::after{transition:none}
}

/* ---------- Buttons ---------- */

/* Always 16px radius — never pills, never square. That rule is from the app
   and it is the one shape decision that shows up on every surface. */
.btn{
  display:inline-flex;align-items:center;justify-content:center;gap:10px;
  padding:14px var(--space-xl);
  border-radius:var(--radius-xl);
  font:var(--type-headline);font-weight:700;
  border:none;cursor:pointer;
  transition:opacity .2s ease;
}
.btn:hover{opacity:.85;text-decoration:none}
.btn--primary{background:var(--web-accent);color:#fff}
/* 1.5px, not 1px. The app uses 1.5 for component outlines everywhere and the
   difference is visible next to a 1px divider. */
.btn--secondary{background:transparent;color:var(--web-text);border:1.5px solid var(--web-hairline)}
.btn--light{background:#fff;color:var(--web-bg)}
.btn[disabled]{opacity:.55;cursor:not-allowed}

/* ---------- AI commentary card ---------- */

/* The DOM twin of the ai_bubble overlay in scripts/video/overlays.sh, down to
   the hex, the 1.5px green edge, the 18px radius and the drawn sparkle — so the
   card in the video and the card on the page are the same object rather than
   two designs of the same idea.
   It exists because "Talk trash" is a third of the h1 and its only evidence was
   two seconds of a 460 KB video: preload="none", and under reduced motion the
   element never loads at all, with a poster carrying no product content. So
   reduced-motion, no-JS and everyone who did not scroll far enough saw the
   claim and none of the proof.
   No shadow, deliberately. The edge is doing the work; pairing a hairline with
   a wide blur is the pattern the detector flags on invite.html. */
.aicard{
  max-width:520px;margin:0 0 var(--space-xl);
  background:#131c17;
  border:1.5px solid var(--web-accent-bright);
  border-radius:18px;
  padding:20px 26px 22px;
}
.aicard__head{display:flex;align-items:center;gap:10px;margin-bottom:8px}
.aicard__spark{width:19px;height:19px;fill:var(--web-accent-bright);flex:none}
.aicard__label{
  font:var(--type-footnote);font-weight:700;
  letter-spacing:.08em;text-transform:uppercase;
  color:var(--web-accent-bright);
}
.aicard__line{margin:0;font:var(--type-title3);font-weight:400;color:var(--web-text)}

/* ---------- Score ladder ---------- */

/* The site's own visual grammar, stated once in its own chrome.
   The product's most recognisable rule is that a score is coloured against the
   hole's par — and until now that rule appeared on this site exclusively INSIDE
   screenshots of the app, at about four pixels tall. The prose described the
   convention and then showed a picture of it.
   This is the convention itself, in the rounded numerals the token file
   reserves for golf numbers, in the app's own hues. It is a key, not a
   duplicate of the screenshot beside it: the screenshot proves the app does
   this, and this makes every score on every other screenshot legible. */
.ladder{margin:0 0 var(--space-xl);padding:0}
.ladder__row{
  display:flex;gap:var(--space-xl);flex-wrap:wrap;
  list-style:none;margin:0;padding:0;
}
.ladder__step{display:flex;flex-direction:column;align-items:center;gap:2px}
.ladder__n{
  font:var(--type-score-medium);
  font-variant-numeric:tabular-nums;
  line-height:1.1;
}
.ladder__label{font:var(--type-footnote);font-weight:400;color:var(--web-muted)}
.ladder__n--eagle{color:var(--web-score-eagle)}
.ladder__n--birdie{color:var(--web-score-birdie)}
.ladder__n--par{color:var(--web-score-par)}
.ladder__n--bogey{color:var(--web-score-bogey)}
.ladder__n--double{color:var(--web-score-double)}

@media (max-width:560px){
  .ladder__row{gap:var(--space-lg)}
}

/* ---------- Standings ---------- */

/* The other half of the grammar. --rank-first/second/third were declared with
   the score ladder and equally unused; a points race is where they mean
   something, and this page's copy asserts that everybody sees the same
   standings while showing a picture of them.
   The money column is here, and the reason is worth recording because I got
   this wrong once. A review flagged both teams showing "up" money as impossible
   in a two-team match; I believed it and omitted the column. It is not a
   head-to-head wager. scripts/seed-demo-trip.cjs sets
   `prizePool: { prizes: [180, 90, 50], basis: 'team' }` against
   `antePerPlayer: 40` and eight players — a $320 pot paid out 180/90/50, which
   balances to the cent. Both teams are up because both finished in the money.
   The figure was always right; the inference about it was not. */
.standings{margin:0 0 var(--space-xl);padding:0;max-width:420px}
.standings__rows{list-style:none;margin:0;padding:0}
.standings__row{
  display:flex;align-items:baseline;gap:var(--space-md);
  padding:10px 0;
}
.standings__row + .standings__row{border-top:1px solid var(--web-hairline)}
.standings__rank{
  font:var(--type-score-small);font-variant-numeric:tabular-nums;
  min-width:1.2em;
}
.standings__rank--first{color:var(--web-rank-first)}
.standings__rank--second{color:var(--web-rank-second)}
.standings__team{flex:1;color:var(--web-text);font:var(--type-callout)}
.standings__money{
  font:var(--type-score-small);font-variant-numeric:tabular-nums;
  color:var(--web-accent-bright);min-width:4.2em;text-align:right;
}
.standings__pts{
  font:var(--type-score-medium);font-variant-numeric:tabular-nums;
  color:var(--web-text);
}
.standings__unit{font:var(--type-footnote);font-weight:400;color:var(--web-muted)}

/* ---------- Waitlist form ---------- */

/* The App Store build is in review, so the page collects an email instead of
   linking to a listing that doesn't exist yet. Retire this section along with
   the form when the app is approved. */

/* Android list, under the download in the closing card.

   The separation is carried by the SPACING and the drop to fine-print size —
   measured, the hairline renders 42 against a 31 ground and is close to
   invisible, which is what --web-hairline is for elsewhere on this site too.
   It marks the boundary; it does not make it. What matters is that the second
   email field never reads as part of the first offer, or the card appears to
   ask for an address in order to download a free app. */
.cta__android{margin-top:var(--space-xl);padding-top:var(--space-lg);border-top:1px solid var(--web-hairline)}
.cta__android .waitlist__input{font-size:15px}
.waitlist{display:flex;gap:var(--space-sm);flex-wrap:wrap;align-items:flex-start;margin:0}
.waitlist__field{flex:1 1 260px;min-width:0}

.waitlist__input{
  width:100%;
  /* 17px via --type-headline, at normal weight. NOT the 14px the nav uses:
     iOS Safari force-zooms a focused input below 16px, which would jerk the
     whole page on the single interaction this site is asking for. */
  font:var(--type-headline);font-weight:400;
  padding:14px var(--space-lg);
  min-height:52px;                       /* matches .btn, so the row aligns */
  color:var(--web-text);
  background:var(--web-card);
  border:1.5px solid var(--web-field-border);
  border-radius:var(--radius-xl);
}
/* opacity:1 because Firefox dims placeholders by default, which would undo the
   contrast this colour was chosen for. */
.waitlist__input::placeholder{color:var(--web-muted);opacity:1}
.waitlist__input:disabled{opacity:.55;cursor:not-allowed}

/* Honeypot. Off-screen rather than display:none — some bots skip hidden fields
   but fill everything else, and this needs to look fillable to them. Kept out
   of the tab order and the accessibility tree for everyone real. */
.waitlist__trap{
  position:absolute;left:-9999px;width:1px;height:1px;
  overflow:hidden;opacity:0;pointer-events:none;
}

/* Full-width row under the field, so a message never squeezes the input. */
/* --type-headline, not subheadline. This line IS the success state now that the
   button is gone, and it was 13px — the smallest type on the page carrying the
   most important sentence in the funnel. */
.waitlist__msg{flex:1 0 100%;margin:12px 0 0;font:var(--type-headline);font-weight:400}
/* Out of flow while empty, NOT display:none. A role="status" region has to be
   in the accessibility tree BEFORE its content changes or the change is not
   reliably announced — display:none means it is absent right up until the
   instant it matters, which is the classic way a live region silently fails.
   Absolute with no offsets sits at its static position at zero size, so this
   costs nothing visually and the flex row never appears. */
.waitlist__msg:empty{position:absolute;margin:0}
.waitlist__msg--ok{color:var(--web-accent-bright)}
.waitlist__msg--error{color:var(--web-error)}

/* On the white CTA card the field sits on --web-card like everywhere else, so
   only the button flips. Nothing to override. */

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

/* ---------- Cards ---------- */

.card{
  display:block;
  background:var(--web-card);
  border:1px solid var(--web-hairline);
  border-radius:var(--radius-card);
  padding:var(--space-xl);
  color:var(--web-text);
}
/* border-color only: no rule on this site ever sets a transform on .card, so
   transitioning one was a declaration with nothing on the other end. */
a.card{transition:border-color .2s ease}
a.card:hover{border-color:color-mix(in srgb,var(--web-accent-bright) 40%,transparent);text-decoration:none}

.card__label{font:var(--type-footnote);font-weight:700;letter-spacing:.08em;color:var(--web-accent-bright);margin-bottom:10px;text-transform:uppercase}
.card__label--soon{color:var(--color-accent)}
/* Shipped is deliberately the QUIET label: on a roadmap the accent belongs to
   the work still coming, not to the work already in the store. */
.card__label--shipped{color:var(--web-muted)}
/* These four carry heading tags. They were <div>s and <span>s that looked
   exactly like sub-headings and behaved like nothing: 118 of them against 55
   real headings, so the heading map of /compare — a 4,554px decision page —
   was six entries and its whole argument was unreachable by heading
   navigation. The classes already carried every style; only the margins
   needed zeroing, because a heading brings its own. */
.card__title{font:var(--type-title2);font-weight:700;margin:0 0 6px}
.card__body{color:var(--web-muted);font:var(--type-subheadline);margin:0}

/* ---------- Type ---------- */

.eyebrow{
  display:inline-flex;align-items:center;gap:var(--space-xs);
  border:1.5px solid color-mix(in srgb,var(--web-accent-bright) 30%,transparent);
  color:var(--web-accent-bright);
  font:var(--type-footnote);font-weight:700;letter-spacing:.08em;
  padding:6px var(--space-sm);border-radius:var(--radius-full);
  margin-bottom:var(--space-lg);text-transform:uppercase;
}

.h1{font-size:clamp(40px,6vw,64px);line-height:1.05;margin:0 0 var(--space-lg);font-weight:800}
/* h2 clamps for the same reason h1 does. It was a hard 34px against an h1 that
   floors at 40, so the page's two largest sizes finished 6px apart on a phone —
   a 1.18 step, which is not a hierarchy, it is a rounding error. Now 1.43. */
.h2{font-size:clamp(28px,4vw,34px);margin:0 0 var(--space-xs);font-weight:800}
.lede{color:var(--web-muted);font-size:19px;max-width:440px;margin:0 0 var(--space-xxl)}
.muted{color:var(--web-muted)}
.accent{color:var(--web-accent-bright)}
/* max-width, because this line had none and ran 103 characters at 13px on four
   pages — the smallest type on the site carrying the disclosure that makes the
   whole waitlist make sense. `ch` overstates: it is the advance of "0" and
   lowercase runs about three quarters of that, so the number here is the
   measured one: 64ch still gave 82 characters, 56ch gives 72. */
.fine{max-width:56ch;color:var(--web-dim);font:var(--type-subheadline)}

/* Section rule: a label and a hairline that fills the remaining width. */
.rule{display:flex;align-items:baseline;gap:14px;margin:44px 0 18px}
.rule__label{margin:0;font:var(--type-footnote);font-weight:700;letter-spacing:.1em;color:var(--web-dim);text-transform:uppercase}
.rule__line{flex:1;height:1px;background:var(--web-hairline)}

/* ---------- Device frame ---------- */

.device{border-radius:52px;border:10px solid #000;box-shadow:0 30px 80px rgba(0,0,0,.55);overflow:hidden;background:#000}
.device img{display:block;width:100%}
.device--sm{border-radius:48px;border-width:9px;box-shadow:0 24px 64px rgba(0,0,0,.5)}

/* The hero handset. Was `style="width:320px"` inline on seven pages, which is
   wider than a 320px viewport has room for once .wrap takes its 24px each side
   — the box overflowed and the page scrolled sideways. As a class it can be
   fluid, and it can be overridden, which an inline style could not be. */
.device--hero{width:100%;max-width:320px;margin:0 auto}

/* ---------- Footer ---------- */

.footer{border-top:1px solid var(--web-hairline);margin-top:40px}
.footer__cols{display:flex;gap:40px;flex-wrap:wrap;font-size:14px;padding-top:40px;padding-bottom:40px}
.footer__col{display:flex;flex-direction:column}
/* Same 44px rule as the nav, and it matters more here: thirteen 14px links at
   an 8px pitch is a mis-tap generator, and until the remaining pages ship a
   mis-tap lands on a 404. The min-height supplies the separation, so the old
   gap is gone rather than stacked on top of it. */
.footer__col a{color:var(--web-link);display:flex;align-items:center;min-height:44px}
.footer__heading{color:var(--web-dim);font-weight:700;font:var(--type-footnote);letter-spacing:.08em;text-transform:uppercase;margin-bottom:var(--space-xs)}
/* Was #5c6b62 at 11px — 3.29:1, the only text on the site that failed AA.
   --web-dim clears it at 5.55:1, and 13px is a size a human can actually read. */
.footer__legal{color:var(--web-dim);font:var(--type-subheadline);padding-bottom:var(--space-xxl)}

/* ---------- Long-form (privacy, terms, support) ---------- */

/* The legal pages are dense text, so they get a narrower measure and a bigger
   line-height than the marketing pages. */
.prose{max-width:760px;margin:0 auto;padding:120px var(--space-xl) 64px;line-height:1.7}
.prose h1{font-size:clamp(32px,5vw,44px);font-weight:800;margin:0 0 var(--space-xs)}
.prose h2{font-size:22px;font-weight:700;margin:var(--space-xxl) 0 var(--space-sm);color:var(--web-text)}
.prose h3{font-size:17px;font-weight:700;margin:var(--space-xl) 0 var(--space-xs)}
.prose p,.prose li{color:var(--web-muted)}
.prose li{margin-bottom:var(--space-xs)}
.prose strong{color:var(--web-text)}
.prose ul{padding-left:var(--space-lg)}
.prose__meta{color:var(--web-dim);font:var(--type-subheadline);margin:0 0 var(--space-xxl)}

/* A complete hairline box, NOT a coloured 3px bar down one edge. The legacy
   pages used a left-border accent — the "side tab" the detector flags and the
   design floor bans as a costume. A full border says the same thing. */
.callout{
  background:color-mix(in srgb,var(--web-accent-bright) 8%,transparent);
  border:1px solid color-mix(in srgb,var(--web-accent-bright) 28%,transparent);
  border-radius:var(--radius-card);
  padding:var(--space-lg) var(--space-xl);
  margin:var(--space-lg) 0;
}
.callout>:first-child{margin-top:0}
.callout>:last-child{margin-bottom:0}
/* Gold, for the two that are warnings rather than summaries. */
.callout--warn{
  background:color-mix(in srgb,var(--color-accent) 9%,transparent);
  border-color:color-mix(in srgb,var(--color-accent) 32%,transparent);
}

/* Tables scroll inside their own container. A wide table must never be the
   reason the whole page scrolls sideways on a phone. */
.prose__scroll{overflow-x:auto;margin:var(--space-md) 0}
.prose table{border-collapse:collapse;width:100%;min-width:520px;font:var(--type-subheadline)}
.prose th,.prose td{text-align:left;padding:10px 12px;border-bottom:1px solid var(--web-hairline);vertical-align:top;color:var(--web-muted)}
.prose th{color:var(--web-text);font-weight:600}

.prose__footer{margin-top:var(--space-xxl);padding-top:var(--space-lg);border-top:1px solid var(--web-hairline);color:var(--web-dim);font:var(--type-subheadline)}

/* Standalone rows of links inside prose get the nav and footer's 44px target.
   WCAG exempts a link sitting INSIDE a sentence, which is why the addresses and
   cross-references in these pages are left alone — but a row that is only links
   is navigation, and at a 16-20px hit area it was the smallest target on the
   site by some way. */
.prose__links a,.prose__footer a{display:inline-flex;align-items:center;min-height:44px}
.prose__updated{color:var(--web-dim);font:var(--type-subheadline);margin:0 0 var(--space-xxl)}

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

/* Elements start hidden ONLY when JS is running — without this guard a reader
   with JS disabled gets a permanently blank page, which is the classic way a
   reveal animation becomes an accessibility bug.

   Exponential ease-out, NOT the overshoot curve this used to share with the
   hero. Twenty elements arriving with the same little spring is not character,
   it is the same entrance twenty times; the bounce is now spent once, on the
   phone, where it reads as deliberate. Real objects decelerate — they do not
   sail past where they were going and come back. */
.js [data-reveal]{opacity:0;transform:translateY(28px)}
.js [data-reveal].is-visible{
  opacity:1;transform:none;
  transition:opacity .55s ease,transform .7s cubic-bezier(.22,1,.36,1);
}

@media (prefers-reduced-motion:reduce){
  html{scroll-behavior:auto}
  /* .is-visible on BOTH selectors, and that is the entire fix. The animating
     rule is `.js [data-reveal].is-visible` at 0-3-0; this reset was 0-2-0 and
     a media query adds no specificity, so it lost every time. Nineteen
     elements on the homepage kept fading and sliding for a reader who had
     asked for none of it — the reset was declared, matched, and outranked. */
  .js [data-reveal],
  .js [data-reveal].is-visible{opacity:1;transform:none;transition:none}
  /* .hero-phone is a SEPARATE class from .hero-in, not a variant of it, and it
     was escaping this block — a 60px translate plus a 2deg rotation on the
     largest element above the fold, playing for readers who asked for less
     motion. It was also the single most motion-heavy thing on the page. */
  .hero-in,.hero-phone{animation:none !important}
}

/* ---------- Hero background ---------- */

/* Full-bleed photograph behind the hero, under a flat scrim — the same rule the
   app uses for text over satellite imagery, and the reason .media__scrim exists.
   62% is MEASURED, not guessed: over this photograph the brightest pixel in the
   headline region still clears 5.4:1 against --web-text and the mean is 7.0:1.
   At 50% the worst case drops to 3.7:1 and fails AA, so this is a floor, not a
   preference — re-measure before changing it or swapping the image. */
/* Deliberately NOT overflow:hidden. The background can't overflow — it's
   inset:0 with object-fit:cover, so the crop happens inside the box. But
   .hero-phone animates in from translateY(60px), and clipping the section
   would cut that entrance off mid-flight. */
.hero{position:relative}
.hero__bg{position:absolute;inset:0;z-index:0;overflow:hidden}
/* Centred deliberately, and checked at both ends. A phone crops this 16:9
   frame to a ~24% vertical slice; centred, that slice holds the player at
   address with fairway beside him. An earlier photograph had both golfers
   jammed against the right edge and needed a 72% bias to survive the crop —
   this one does not, so the override is gone rather than left behind. */
.hero__bg img{width:100%;height:100%;object-fit:cover;object-position:center}

/* The scrim ramps across the frame instead of washing it flatly.
   A flat 62% was measured against COMPUTED style, which cannot resolve a
   photograph — so three review passes reported zero contrast failures while
   the hero lede was running at 2.56:1 against the bright fairway behind it.
   Sampling the actual pixels under each line and solving for the alpha each
   one needs gives 0.815 at 1440 and 0.705 at 390. Those are the numbers below,
   with headroom.
   Ramping rather than flooding keeps the photograph a photograph: the text
   column sits on a deep ground, and the right of the frame — trees, the second
   player, the light down the fairway — stays open.
   The hold is .89 rather than .86 because of a crop flip: past a viewport
   aspect of about 1.78 object-fit:cover stops scaling to height and starts
   scaling to width, so a different and much brighter band of the photograph
   arrives behind the text. Measured at 1920 the fine print sat at 4.39:1 with
   .86. Swept at eight widths from 390 to 1920 rather than the two the first
   pass checked. */
.hero__bg::after{
  content:"";position:absolute;inset:0;
  background:linear-gradient(90deg,
    rgba(8,12,10,.89) 0%,
    rgba(8,12,10,.89) 54%,
    rgba(8,12,10,.64) 76%,
    rgba(8,12,10,.46) 100%);
}
.hero__content{position:relative;z-index:1}

/* Caps the hero copy at a readable measure. The bullets and fine print were
   running the full 676px of the column — .fine measured 103 characters per
   line at 13px, the smallest type on the page on the widest measure on it.
   This also pulls every line inside the dark end of the ramp above, so the two
   problems had one fix. */
.hero__content .split__text{max-width:560px}

@media (max-width:879px){
  /* The photograph stops trying to back the whole hero.
     The comment above is right about the crop being centred on the player — but
     it assumed a box roughly the shape of a screen. On a phone the hero is 390
     wide and 1690 TALL, because the headline, lede, three bullets, form, button
     and fine print all stack. object-fit:cover scales to the greater dimension,
     so the image is sized to that 1690px height, its width balloons to ~3000px,
     and a 390px window onto it shows 13% of the frame. Worse, the full vertical
     extent is spread over twice the viewport, which puts the player himself
     around y=1127 — below the fold. Everything a phone actually showed above it
     was the sky over his head.
     Capping the height fixes the geometry rather than the framing: the box
     becomes ~390x540, the visible slice widens from 13% to about 39%, and the
     player lands on screen. The gradient carries the photograph into the page
     ground so the cap is a fade rather than a horizon line. */
  .hero__bg{height:min(64vh,600px);bottom:auto}
  /* Vertical here, not horizontal: on a phone the copy spans the full width, so
     there is no quiet side to ramp toward. Measured requirement is 0.705 — the
     eyebrow was the failure at 3.38:1, being 12px of bright green over sky. */
  .hero__bg::after{
    background:linear-gradient(180deg,
      rgba(8,12,10,.78) 0%,
      rgba(8,12,10,.82) 45%,
      color-mix(in srgb,var(--web-bg) 90%,transparent) 84%,
      var(--web-bg) 100%);
  }
}

/* ---------- Hero entrance ---------- */

@keyframes heroUp{from{opacity:0;transform:translateY(32px)}to{opacity:1;transform:none}}
@keyframes phoneUp{from{opacity:0;transform:translateY(60px) rotate(2deg)}to{opacity:1;transform:none}}
/* The seven hero text elements settle; only the phone overshoots. One authored
   moment, on the page's signature element — the whole point of a flourish is
   that it is the exception. */
.hero-in{animation:heroUp .7s cubic-bezier(.22,1,.36,1) both}
.hero-phone{animation:phoneUp .9s cubic-bezier(.34,1.56,.64,1) both .3s}

/* ============================================================
 * BANDS
 * ------------------------------------------------------------
 * The site is dark throughout, so rhythm comes from tone rather than from
 * flipping to light: page ground → raised panel → brand green. A long scroll
 * on one flat colour reads as one endless section, which is the thing the
 * banding is there to prevent.
 * ============================================================ */

.band{padding-top:72px;padding-bottom:72px}
.band--raised{background:var(--web-card)}
.band--warm{background:linear-gradient(180deg,#1E1B18,#141312)}
.band--flush{padding-top:0;padding-bottom:0}

/* ============================================================
 * MEDIA SLOTS
 * ------------------------------------------------------------
 * Every place a photograph or video will eventually live is a `.media`, and it
 * renders app UI until then. Swapping in real media is a one-element change
 * inside the slot — no layout, no CSS, no page edits. See
 * web/assets/media/README.md for the shot list and exact sizes.
 *
 * Screenshots prove the product works. People on a course explain why anyone
 * would want it, and that is the job of the hero and the closing band.
 * ============================================================ */

.media{position:relative;overflow:hidden;border-radius:var(--radius-hero);margin:0;background:#0E0F10}
.media img,.media video{display:block;width:100%;height:100%;object-fit:cover}
.media--wide{aspect-ratio:16/9}
.media--square{aspect-ratio:1}
.media--tall{aspect-ratio:4/5}

/* A slot still waiting on real media. Deliberately quiet rather than a loud
   "PLACEHOLDER" bar — it should look like a considered surface if it ships,
   not like a bug. */
.media--pending{background:linear-gradient(140deg,#1C1A17,#131211);border:1px solid var(--web-hairline)}
.media__pending{
  position:absolute;inset:0;display:flex;flex-direction:column;
  align-items:center;justify-content:center;gap:var(--space-xs);
  text-align:center;padding:var(--space-xl);color:var(--web-dim);
}
.media__pending b{color:var(--web-muted);font:var(--type-headline);font-weight:700}
.media__pending span{font:var(--type-subheadline)}

/* Text over media needs a flat scrim, never a gradient fade — the app's rule
   for the same problem on satellite imagery, and it holds here. */
.media__scrim{position:absolute;inset:0;background:rgba(8,12,10,.62)}
.media__over{position:absolute;inset:0;display:flex;align-items:center}

/* ============================================================
 * 404 — THE LOST BALL
 * ============================================================ */

/* The .steps / .step / .step__n component lived here: a vertical rail of
   captions that dimmed to 35% until the scroll observer lit one up. It only
   ever had one caller, "See Duffers in action", and that section no longer
   sequences — .step is now defined with the rest of #how, further down. */

/* Clips at the viewport edge and stops a 115vw translate from adding a
   horizontal scrollbar. */
.scene{position:relative;overflow:hidden}

.lostball{position:absolute;left:9%;bottom:16%;width:88px;height:60px;pointer-events:none}
@media (max-width:699px){ .lostball{left:6%;bottom:10%} }

/* Grass, drawn as grass. The turf keeps the retired brand greens because it
   is depicting a lawn, not carrying an identity — only the BALL follows the
   accent, matching the highlighted word in the heading above it. */
.lostball__grass{position:absolute;left:0;bottom:0}
.lostball__turf{fill:var(--web-turf)}
.lostball__blades{stroke:var(--web-turf-blade)}

.lostball__tee{position:absolute;left:37px;bottom:10px;fill:var(--web-dim)}
.lostball__dimples{fill:var(--web-bg)}

/* ONE iteration, not a loop. This page exists to get somebody unstuck, and
   motion repeating every few seconds competes with the two buttons that do
   that. Played once it is a moment; then the page is quiet and the empty tee
   is left behind, which is the actual joke. */
/* inset:0 is load-bearing, not tidiness. This element is transformed, and a
   transformed element becomes the containing block for its absolutely
   positioned descendants — so without it the ball resolved against a
   zero-height box and floated off the tee the moment the animation applied. */
.lostball__flight{position:absolute;inset:0;animation:lostball-drift 2.8s cubic-bezier(.42,0,.58,1) .9s both}
.lostball__ball{
  position:absolute;left:31px;bottom:29px;   /* centred on the tee, resting on its cup */
  fill:var(--web-accent-bright);           /* the same ember as "ball" in the heading */
  animation:lostball-climb 2.8s cubic-bezier(.3,.45,.6,1) .9s both;
}

/* Traced from a drawn trajectory: steeply up out of the tee, bending right as
   it flattens, off the TOP of the screen at roughly the horizontal centre.
   Still climbing when it goes — no apex, no descent.
   Y decelerates (ease-out) while X is ease-in-out, so early on the ball is
   nearly vertical and only picks up sideways travel as it flattens. That
   mismatch IS the curve; matching the two would draw a straight diagonal.
   The distances are set by where the two curves MEET, solved rather than
   guessed: 63vh of climb puts the ball at the top 74% of the way through, and
   44vw of drift has it at x=53% at exactly that moment. Changing one without
   the other moves the exit point, and raising the climb pushes the exit far
   earlier — at 70vh it leaves at t=0.45, barely on screen. */
@keyframes lostball-drift{
  from{transform:translateX(0)}
  to  {transform:translateX(44vw)}
}
@keyframes lostball-climb{
  from{transform:translateY(0) scale(1) rotate(0deg)}
  to  {transform:translateY(-63vh) scale(.14) rotate(700deg)}
}

@media (prefers-reduced-motion:reduce){
  /* The ball simply stays teed up. The joke is in the heading; the flight is
     a bonus, not the message. */
  .lostball__flight,.lostball__ball{animation:none}
}

/* ---------- Screen + blown-up detail ---------- */

/* A phone with one true row of it enlarged alongside. This is the site's
   workhorse proof device: a 240px photograph of a handset shows that a screen
   exists, and the crop next to it shows what the screen SAYS. The homepage
   Clubhouse block was the first use; the feature pages are built out of it, so
   it carries a general name and .clubhouse rides along.
   padding-bottom reserves room for the detail's overhang, so the absolute crop
   can overlap the phone without escaping the section. */
.clubhouse,.shot{position:relative;flex:1 1 380px;min-width:300px;padding-bottom:64px}
.clubhouse__phone,.shot__phone{width:240px;margin-left:auto}
.clubhouse__detail,.shot__detail{
  position:absolute;left:0;bottom:0;width:min(360px,86%);
  border-radius:var(--radius-card);
  border:1px solid var(--web-hairline);
  box-shadow:0 22px 48px rgba(0,0,0,.55);
}

/* Mirrored, for sections that alternate. Without this every screen on a page
   sits on the same side and the scroll reads as one long column. */
.shot--flip .shot__phone{margin-left:0;margin-right:auto}
.shot--flip .shot__detail{left:auto;right:0}

/* The crop on its own, no handset behind it.
   For the one section per page whose phone is the SAME screenshot the hero
   already showed. The handset in a .shot is context for the crop; when the hero
   established it one scroll earlier that context is already paid for, and a
   second copy is just the same picture twice — which is what it looked like on
   a phone, where the two stack instead of sitting side by side.
   Wider than the overlapping version at 360px, because it no longer has to
   leave the handset visible and legibility is the whole job of this crop. 440
   is the ceiling: the 720w asset is still 1.6x dense there, and there is no
   larger export. */
.shot--solo{padding-bottom:0;display:flex;align-items:center;justify-content:center}
.shot--solo .shot__detail{position:static;width:100%;max-width:440px;margin:0 auto}

@media (max-width:879px){
  /* Overlapping a 240px phone on a narrow screen leaves neither readable, so
     the crop drops below it in flow. */
  .clubhouse,.shot{padding-bottom:0}
  .clubhouse__phone,.shot__phone{margin:0 auto}
  .shot--flip .shot__phone{margin:0 auto}

  /* Was capped at 240px to stop the crop dwarfing the handset above it. That
     diagnosis was wrong: the crop was not too wide, it was SQUEEZED — its
     height attribute survived the width override, so it rendered at the wrong
     aspect and read as distorted rather than large. See the img rule at the top
     of this file. With the ratio correct, a crop at the container's width is
     right: it is the thing that has to be legible, and 240px made it small
     without making it any less wrong. */
  .clubhouse__detail,.shot__detail{
    position:static;
    width:100%;max-width:360px;
    margin:var(--space-lg) auto 0;
  }

  /* The 240px cap above exists to stop a crop dwarfing the handset stacked
     beneath it. A solo crop has no handset to dwarf, and shrinking it to 240
     would throw away the legibility it exists for. */
  .shot--solo .shot__detail{width:100%;max-width:100%;margin:0}
}

/* ---------- Feature pages ---------- */

/* Deep pages open on type, not photography. The hero image is the homepage's
   signature and reusing it on nine subpages would spend it — these earn
   attention with the claim and then prove it with the product. */
.subhero{padding-top:136px;padding-bottom:var(--space-xxl)}

/* Breadcrumb above the headline. Cold search traffic lands here without ever
   having seen the homepage, so the page has to say where it sits. */
.crumbs{display:flex;align-items:center;gap:10px;font:var(--type-subheadline);color:var(--web-dim);margin-bottom:var(--space-lg)}
.crumbs a{color:var(--web-link)}

/* Same reasoning as .prose__links below: a breadcrumb row is only a link, so it
   is navigation, and at 119.6x18.2 it sat under even WCAG 2.5.8's 24x24 floor
   on all eight breadcrumbed pages. The negative block margin keeps the row's
   optical position — the 44px target grows around the text instead of pushing
   the headline down 26px on every one of them. */
.crumbs a{display:inline-flex;align-items:center;min-height:44px;margin-block:-13px}

/* A claim with its supporting line, used in rows of two or three. Lighter than
   a .card — these are statements, not links, and boxing every one of them
   turns a page into a container yard. */
.claims{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:var(--space-xl) var(--space-xxl)}
.claim__title{font:var(--type-headline);font-weight:700;margin:0 0 var(--space-xs)}
.claim__body{color:var(--web-muted);font:var(--type-subheadline);margin:0}

/* Cross-links to the sibling feature pages, so a reader who landed on one of
   them can find the rest without going back to the homepage. */
.more{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:var(--space-md)}

/* ---------- How it works: the whole arc, in one glance ---------- */

/* This was a sticky stage driven by a scroll observer — a 1444px column of
   three captions on the left, one phone pinned on the right, frames swapping
   as each caption crossed the middle of the viewport. It measured 1702px at
   1440 and 1764px at 390: the tallest section on the page, delivering 47 words
   and one screenshot at a time. Each caption sat in a 468px box around 47px of
   ink — 10% fill, 45px of scroll per word.
   None of that height was spacing. It was runway: the observer needs the
   captions to be tall enough that they cross the trigger band one at a time,
   and the stage needs the column to outrun its own sticky height or it never
   pins. Cut min-height and the device strobes; keep it and the reader pays two
   screens. There was no tuning that fixed both, so the device went.
   Three columns instead. Before, during and after a trip is a COMPARISON — the
   trip screen has to still be in your eye when the money lands, and serially it
   is not. Now the whole arc arrives in one glance, which is also what the hero
   button promises when it sends a reader to #how. */

/* 48px under the h2, which is what the green band spends on the same gap. The
   standfirst used to hold this space open; without it the .h2's own 4px bottom
   margin was the only thing between a 34px heading and the first caption, and
   the two read as one block. */
.how{
  list-style:none;margin:48px 0 0;padding:0;
  display:grid;grid-template-columns:repeat(3,1fr);grid-template-rows:auto auto;
  gap:var(--space-xl) var(--space-xxl);
}

/* subgrid, not align-items:start. The three sentences are near enough the same
   length to wrap identically today, but a font swap or a copy edit that pushes
   one to three lines would stagger the screenshots by a line-height and read as
   a mistake. Borrowing the parent's rows pins them to a shared line whatever
   the text does. */
.step{display:grid;grid-row:span 2;grid-template-rows:subgrid}

/* balance, because every one of these wraps to two lines in a 336px column and
   the first one broke to a two-word widow — "…plan the trip and send / a code".
   Three lines is the balancer's practical ceiling and these are two. */
.step__line{margin:0;font-size:20px;font-weight:600;line-height:1.4;color:var(--web-text);text-wrap:balance}

/* 440px is this site's ceiling for a standalone crop: the 720w export is still
   1.6x dense there, and there is no larger asset. It only binds in the stacked
   layout — the three-up columns land near 336px. */
.step__shot{
  display:block;align-self:start;
  width:100%;max-width:440px;height:auto;
  border-radius:var(--radius-card);
  border:1px solid var(--web-hairline);
  /* Shallower than the 0 22px 48px the stage used. That shadow was cast by a
     600px pinned figure; under a 140px crop it pooled darker than the object
     was tall. */
  box-shadow:0 16px 36px rgba(0,0,0,.5);
}

/* Three-up stops at 1024, not the site's usual 880. Below that the columns fall
   under ~300px and the crop's own type — a $401.31, a 3.5 — shrinks past the
   point where the screenshot is evidence rather than texture. */
@media (max-width:1023px){
  /* Capped and centred, not left-aligned across the band. The crop stops at
     440px whatever the viewport does, so on a tablet a left-aligned stack put
     440px of content against 535px of nothing, under a centred heading. The
     cap belongs to the column, not just the image. Below ~490px the wrap is
     narrower than 440 and this stops binding, so a phone is unaffected. */
  .how{grid-template-columns:1fr;grid-template-rows:none;gap:var(--space-xxl);max-width:440px;margin-inline:auto}
  .step{grid-row:auto;grid-template-rows:auto auto;row-gap:var(--space-md)}
}

@media (max-width:720px){
  .step__line{font-size:19px}
}

/* ============================================================
 * FEATURE ROW
 * ============================================================ */

.features{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:var(--space-xxl)}
.feature__icon{width:36px;height:36px;color:var(--web-accent-bright);margin-bottom:var(--space-sm)}
.feature__title{font:var(--type-title3);font-weight:700;margin:0 0 var(--space-xs)}
.feature__body{color:var(--web-muted);font:var(--type-subheadline);margin:0}

/* The one typographic rule this product actually has. tokens/typography.css has
   said since the day it was written that "a rounded numeral means 'this is a
   golf number'" — and nothing on this site had ever fired it. --font-rounded
   and all five --type-score-* tokens had zero references anywhere: the
   marketing site set every number in the same face as its body copy, while the
   app it sells sets scores, money and distances in SF Pro Rounded. Site and
   product did not look related in the one place the product is most itself.

   Three numbers, one per feature, each measuring exactly what its card claims —
   a score, a settlement, a distance, which is precisely the set the rule
   covers. Every one is real: each already renders inside a screenshot on this
   site, four pixels tall. This is the same figure at a size somebody can read.

   Deliberately NOT applied to the 01 / 02 / 03 step counters. Those are
   ordinals. Spending the rule on section numbering would say "this is a golf
   number" about a list index and blunt it everywhere it means something.

   tabular-nums AFTER the font shorthand, which resets font-variant. */
.spec{
  display:flex;align-items:baseline;gap:10px;flex-wrap:wrap;
  margin:var(--space-md) 0 0;padding-top:var(--space-md);
  border-top:1px solid color-mix(in srgb,var(--web-accent-bright) 24%,transparent);
}
.spec__n{
  font:var(--type-score-medium);
  font-variant-numeric:tabular-nums;
  color:var(--web-text);
}
.spec__label{font:var(--type-footnote);font-weight:400;color:var(--web-muted)}

/* ============================================================
 * FAQ
 * ------------------------------------------------------------
 * <details>, not JavaScript. It is open-able without JS, keyboard accessible
 * for free, and — the reason it exists — the answer text is in the DOM on first
 * paint, which is what makes it eligible for rich results and AI answers.
 * ============================================================ */

.faq{border-top:1px solid var(--web-hairline)}
.faq__item{border-bottom:1px solid var(--web-hairline)}
.faq__q{
  display:flex;align-items:center;justify-content:space-between;gap:var(--space-md);
  padding:var(--space-lg) 0;cursor:pointer;list-style:none;
  font:var(--type-title3);font-weight:600;color:var(--web-text);
}
.faq__q::-webkit-details-marker{display:none}
.faq__q::after{content:"";width:10px;height:10px;border-right:2px solid var(--web-muted);border-bottom:2px solid var(--web-muted);transform:rotate(45deg) translateY(-25%);transition:transform .2s ease;flex:none}
.faq__item[open] .faq__q::after{transform:rotate(-135deg) translateY(-25%)}

@media (prefers-reduced-motion:reduce){
  /* The chevron still flips to show state — it just stops sweeping there. */
  .faq__q::after{transition:none}
}
/* 54ch, not 70. `ch` is the advance of "0", and average lowercase Latin runs
   about three quarters of that — so 70ch measured 94 characters per line at
   1440, well past the 75 the eye wants. 54ch still measured 79. 50ch lands it
   inside the range; the number here is the measured one, not the tidy one. */
.faq__a{color:var(--web-muted);padding-bottom:var(--space-lg);margin:0;max-width:50ch}

/* ============================================================
 * CTA BAND
 * ============================================================ */

/* Padding moved off the card and onto the text column so the photograph can
   reach the card's own edges. A 16:9 figure floating inside a padded box left
   gaps on three sides and read as an afterthought; filling the half commits. */
.cta{position:relative;overflow:hidden;border-radius:var(--radius-hero);background:var(--web-card);border:1px solid var(--web-hairline)}
.cta__inner{position:relative;display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);align-items:stretch}
.cta__text{padding:56px var(--space-xxl)}

/* Absolute fill, so the media column takes its height from the text column
   rather than from the image's own aspect ratio. */
.cta__media{position:relative;min-height:100%}
.cta__media img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;display:block}

@media (max-width:879px){
  .cta__inner{grid-template-columns:1fr}
  .cta__text{padding:var(--space-xxl) var(--space-xl)}
  .cta__media{min-height:240px}
}

@media (max-width:720px){
  .band{padding-top:48px;padding-bottom:48px}
  .cta{padding:var(--space-xxl) var(--space-lg)}
}


/* ---------- Reduced motion, last word ---------- */

/* .btn and a.card were the last two animated selectors outside the
   prefers-reduced-motion blocks. Opacity and colour only, no positional motion,
   but the promise is "no motion" and two uncovered out of ten reads as an
   oversight.
   This lives at the END of the file on purpose. The first attempt put it in the
   nav block near the top, where it lost to `.btn{transition:opacity .2s}`
   declared later at equal specificity — the same way the original
   [data-reveal] reset lost, and a reminder that a media query buys no
   precedence whatsoever. */
@media (prefers-reduced-motion:reduce){
  .btn,a.card{transition:none}
}

/* ---------- Watch page hero video ---------- */

/* The screen inside the drawn case is a real capture of the watch app. The
   still is the same capture's first frame, shown instead of the loop for
   anyone who asked the OS for less motion — an 8-second autoplaying cycle is
   exactly the thing that setting exists to switch off. */
.watchhero__still{display:none}
@media (prefers-reduced-motion: reduce){
  .watchhero__video{display:none}
  .watchhero__still{display:block}
}
