/* ──────────────────────────────────────────────── the typefaces, SELF-HOSTED
   Barlow Condensed and Nunito — the same two their current site uses, served
   from this domain instead of from Google.

   WHY, measured: on Lighthouse's throttled mobile profile the page was small
   (7KB of HTML, 19KB of CSS) and still took 3.3-3.8s to its largest paint.
   Almost none of that was bytes. It was ROUND TRIPS: fonts.googleapis.com and
   fonts.gstatic.com are two more origins, each needing DNS, TCP and TLS before
   anything is asked for, and the font files cannot even START until the CSS
   from the first one has arrived. On a 150ms link that is most of a second
   spent before a byte of typeface moves.

   Nunito is the VARIABLE font — one file covering 400 through 800, instead of
   four static weights. That alone took the set from 363KB to 144KB, and an
   English page only fetches the three `latin` files, about 80KB.

   `font-display: swap` is kept from Google's own CSS: text paints immediately
   in the fallback and reflows when the real face lands. The metric-matched
   fallbacks below keep that reflow small.

   It also removes a third party. The privacy notice says nothing about the
   visitor's details leaving this site, and Google Fonts does log the IP of
   everyone who loads a page. Now it is true without an asterisk. */

@font-face {
  font-family: 'Barlow Condensed';
  font-style: normal;
  font-weight: 800;
  font-display: swap;
  src: url(/fonts/barlow-condensed-800-latin-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Barlow Condensed';
  font-style: normal;
  font-weight: 800;
  font-display: swap;
  src: url(/fonts/barlow-condensed-800-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Barlow Condensed';
  font-style: normal;
  font-weight: 900;
  font-display: swap;
  src: url(/fonts/barlow-condensed-900-latin-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Barlow Condensed';
  font-style: normal;
  font-weight: 900;
  font-display: swap;
  src: url(/fonts/barlow-condensed-900-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Nunito';
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url(/fonts/nunito-400-800-latin-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Nunito';
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url(/fonts/nunito-400-800-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* R13 · THE TEXT MUST NOT JUMP WHEN THE TYPEFACE ARRIVES.
   With `swap`, a first visit paints in the fallback and re-wraps when the real
   face lands. On an iPhone the heading fallback was plain Helvetica/SF (iOS has
   no "Arial Narrow" or "Helvetica Neue Condensed" family), which is ~43% wider
   than Barlow Condensed — every headline re-broke its lines and the page below
   it jumped (measured CLS 0.17-0.25 on the 404, order and shop pages).
   These two faces are Arial (or its metric twin Liberation Sans) RESIZED to
   Barlow's and Nunito's own widths and line metrics, measured in Chromium over
   the site's real headings and paragraphs: Barlow 900 caps run 70.2% of Arial
   Bold, Nunito 400 is 100.8% of Arial. size-adjust scales the overrides too,
   hence 100/70.2 for Barlow's 1.00em ascent. A device without Arial (Android)
   fails the local() and falls through to the stack exactly as before. */
@font-face {
  font-family: 'Barlow Condensed Fallback';
  font-weight: 700 900;
  src: local('Arial Bold'), local('Arial-BoldMT'), local('Liberation Sans Bold'), local('LiberationSans-Bold');
  size-adjust: 70.2%;
  ascent-override: 142.5%; descent-override: 28.5%; line-gap-override: 0%;
}
@font-face {
  font-family: 'Nunito Fallback';
  font-weight: 400 599;
  src: local('Arial'), local('ArialMT'), local('Liberation Sans'), local('LiberationSans');
  size-adjust: 100.8%;
  ascent-override: 100.2%; descent-override: 34.7%; line-gap-override: 0%;
}
@font-face {
  font-family: 'Nunito Fallback';
  font-weight: 600 900;
  src: local('Arial Bold'), local('Arial-BoldMT'), local('Liberation Sans Bold'), local('LiberationSans-Bold');
  size-adjust: 99%;
  ascent-override: 102%; descent-override: 35.4%; line-gap-override: 0%;
}

/* SmashIT — LIGHT, built on the blue in their own logo.
   James, 11 October 2026: "LIGHT background, SmashIT BLUE as the main colour
   (from their logo). Redo the dark theme."

   ── where the blue comes from ───────────────────────────────────────────────
   Measured off their logo file rather than chosen: every saturated, bright pixel
   in public/img/smashit-mark.jpg clusters at hue 185-187 degrees, around #14DCF5,
   with a deeper companion blue at hue 213 degrees. So --brand is THEIR cyan, not
   a designer's idea of it, and --navy is the darker blue already in the mark.

   ── the trap a light theme sets, and how this avoids it ─────────────────────
   #14DCF5 on white is a contrast ratio of 1.57:1. It is a beautiful colour and it
   is ILLEGIBLE as text — on the dark build it carried headings and links, and
   moving those straight across would have made the site unreadable in sunlight,
   which is where padel is played. So the brand cyan is used for FILLS, the ball
   and highlights, and never for text on a light ground. Links and small type use
   --brand-deep, a darkened version of the same hue.

   Every ratio below is computed, not estimated, against --bg #F6F8FA:
     --ink       16.25:1   body text
     --ink-2      7.23:1   secondary text
     --mute       4.74:1   small print, on its TIGHTEST ground (--surface-2)
     --brand-deep 5.19:1   links and small type, same ground   (AA needs 4.5)
     --navy      11.25:1   solid buttons
     --brand      1.57:1   FILLS ONLY — never text
   And for buttons: dark label on brand cyan is 10.37:1, white on navy 11.98:1. */

:root {
  /* ground */
  --bg: #F6F8FA;
  --surface: #FFFFFF;
  --surface-2: #EEF3F7;
  --line: #DCE5EC;
  --line-strong: #C3D1DC;

  /* type */
  --ink: #0A1C2B;
  --ink-2: #3D5668;
  /* #5E7384 measured 4.41:1 on --surface-2, which is where the footer and the
     bands put it — under the 4.5 AA floor. Darkened one step until it passes on
     the TIGHTEST ground it is used on, not just on the page background. */
  --mute: #5A6E7E;           /* 4.74 on surface-2 · 4.97 on bg · 5.29 on white */

  /* THEIR blue */
  --brand: #14DCF5;          /* measured from the logo. Fills, never text. */
  --brand-deep: #0B6E8C;     /* the same hue, darkened until it is legible */
  --navy: #123A52;           /* the deeper blue already in their mark */
  --lime: #B7D92F;           /* the ball in their logo, darkened for a light ground */

  /* tints, all derived from the one brand token so a change propagates */
  --brand-05: color-mix(in srgb, var(--brand) 6%, transparent);
  --brand-10: color-mix(in srgb, var(--brand) 12%, transparent);
  --brand-20: color-mix(in srgb, var(--brand) 24%, transparent);
  --brand-40: color-mix(in srgb, var(--brand) 45%, transparent);

  --danger: #B3261E;
  --danger-bg: #FDECEA;

  --gut: 20px;              /* the phone side gutter, used everywhere */

  /* R12 · ONE SPACING SCALE (James, 17 Oct: "inconsistent spacing between
     sections"). Every section on every page is padded top and bottom by
     --sec: 56px on a phone, rising with the screen to 96px on a laptop.
     --sec-half is the only other step, used where a block CONTINUES the one
     above it (a page head and its content, a hero and its gallery strip).
     Do not type a section padding anywhere else — change it here. */
  --sec: clamp(56px, 7vw, 96px);
  --sec-half: calc(var(--sec) / 2);
  --rad: 16px;
  --max: 1180px;

  /* Shadows on a light ground are a different craft: near-black at low alpha
     reads as dirt. These are the brand navy, barely there, which keeps the
     whole surface feeling blue rather than grey. */
  --shadow: 0 18px 44px rgba(10, 28, 43, .10), 0 2px 6px rgba(10, 28, 43, .05);
  --shadow-lift: 0 22px 50px rgba(10, 28, 43, .14), 0 3px 8px rgba(10, 28, 43, .06);
  --glow: 0 6px 22px rgba(20, 220, 245, .40);
  --ease: cubic-bezier(.22, .61, .36, 1);
}

* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

/* R6 §4: the page's colour moved from <body> to <html> so the ball trail can
   sit BEHIND everything.
   A fixed element at z-index -1 paints behind in-flow content but still above
   the ROOT background — so with the colour on <body>, a negative-z line was
   hidden completely, and the only way to see it was the positive z-index it
   had, which put it OVER the text. Colour on <html>, body transparent, line at
   -1: behind every card, photo and word, and still visible. */
html { background: var(--bg); }
/* R7 §8A: THE PAGE MUST NEVER SLIDE SIDEWAYS.
   `overflow-x: hidden` on <body> alone does not stop it — the root can still
   scroll, which is exactly the drag Thomas hit on the Vanguard site. It goes on
   BOTH, `clip` where it is supported (it cannot be turned into a scroll port by
   a descendant, which `hidden` can), with `hidden` left underneath it for older
   Safari. `overscroll-behavior-x: none` kills the rubber-band drag as well.
   This is the net, not the fix: every element wider than the screen is contained
   at its own source, and scrollWidth === innerWidth is checked top, middle and
   bottom at 360, 375 and 390. */
html, body {
  overflow-x: hidden;
  overflow-x: clip;
  overscroll-behavior-x: none;
}
body {
  margin: 0;
  background: transparent;
  color: var(--ink);
  font: 400 16px/1.6 Nunito, 'Nunito Fallback', -apple-system, system-ui, "Segoe UI", sans-serif;
}

img { max-width: 100%; display: block; }
a { color: inherit; }

h1, h2, h3, .display {
  /* The fallback must be CONDENSED, not Nunito.
     Barlow Condensed arrives from Google with display=swap, so the first paint uses
     whatever is next in this list. Nunito was next, and it is far wider — "THAT
     MEANS" measured 306px of the 335px available in Barlow Condensed, but overflowed
     the phone entirely in Nunito, so the headline was clipped until the webfont
     landed. (body{overflow-x:hidden} hid the damage instead of preventing it.)
     Arial Narrow ships on macOS/iOS and Windows and narrows the gap — but it is NOT
     on its own enough (it measured 345px against 335px available), which is why the
     headline is also kept to short lines. Both defences, because either alone fails.
     See the same class of bug in [web-font-cls-fallback]: a fallback that is not
     metric-close to the real face is a layout bug waiting for a slow connection. */
  font-family: "Barlow Condensed", 'Barlow Condensed Fallback', "Arial Narrow", "Helvetica Neue Condensed", "Roboto Condensed", sans-serif-condensed, sans-serif;
  font-weight: 900;
  text-transform: uppercase;
  letter-spacing: .01em;
  line-height: .95;
  margin: 0;
}

.wrap { width: 100%; max-width: var(--max); margin-inline: auto; padding-inline: var(--gut); }

/* Focus that is actually visible on a dark background — never removed. */
:where(a, button, input, select, textarea, [tabindex]):focus-visible {
  outline: 3px solid var(--brand-deep);
  outline-offset: 3px;
  border-radius: 6px;
}

/* ---------------------------------------------------------------- the banner
   A preview carries a standing notice that its products are examples. It is the
   first thing on the page on purpose: nobody should scroll a price here and think
   it is a quote. */
.notice {
  background: var(--navy);
  color: #fff;
  font-weight: 800;
  font-size: 13px;
  line-height: 1.45;
  padding: 10px var(--gut);
  text-align: center;
}
.notice strong { text-transform: uppercase; letter-spacing: .04em; }

/* ------------------------------------------------------------------- the bar */
.bar {
  position: sticky; top: 0; z-index: 60;
  display: flex; align-items: center; gap: 12px;
  padding: 10px var(--gut);
  background: color-mix(in srgb, var(--bg) 78%, transparent);
  /* R9 §4: no backdrop blur — it smeared the page scrolling underneath. */
  border-bottom: 1px solid transparent;
  transition: border-color .3s var(--ease), background .3s var(--ease);
}
.bar.stuck { border-bottom-color: var(--line); background: color-mix(in srgb, var(--bg) 94%, transparent); box-shadow: 0 1px 0 var(--line), 0 10px 26px rgba(10, 28, 43, .06); }
.bar__logo { height: 34px; width: auto; border-radius: 7px; }
.bar__spacer { flex: 1; }
.bar nav { display: none; gap: 22px; font-weight: 800; font-size: 14px; letter-spacing: .04em; text-transform: uppercase; }
.bar nav a { text-decoration: none; color: var(--mute); transition: color .2s; }
@media (min-width: 860px) { .bar nav { display: flex; } }

.cartbtn {
  position: relative;
  display: inline-flex; align-items: center; gap: 8px;
  background: var(--brand); color: var(--ink);
  border: 0; border-radius: 999px;
  padding: 10px 16px;
  font: 900 13px/1 "Barlow Condensed", 'Barlow Condensed Fallback', sans-serif;
  text-transform: uppercase; letter-spacing: .06em;
  cursor: pointer;
  /* 44px is the smallest thing a thumb reliably hits. */
  min-height: 44px;
  transition: transform .18s var(--ease), box-shadow .18s var(--ease);
}
.cartbtn:active { transform: scale(.95); }
.cartbtn[data-count="0"] .cartbtn__n { display: none; }
.cartbtn__n {
  background: var(--ink); color: var(--brand);
  border-radius: 999px; min-width: 20px; height: 20px;
  display: inline-grid; place-items: center; font-size: 12px; padding-inline: 5px;
}

/* --------------------------------------------------------------- the hero
   An OBJECT THAT TURNS AS YOU SCROLL — George's reference, 11 October. The
   racket is an inline SVG; `--s` is written from scroll position by the single
   handler in app.js and every transform below is a function of it. One variable,
   one handler, so the racket, the ball and the wash all move as one gesture
   rather than three animations competing.

   Why not CSS scroll-driven animations (`animation-timeline: scroll()`): they
   are the right tool and they are not safe here yet — the phones these players
   carry include iOS versions without it, and the fallback for a hero is a
   stationary object, which is the whole feature missing. The rAF handler is a
   dozen lines and works everywhere. Revisit when iOS support is boring. */
.hero {
  position: relative;
  min-height: 88svh;              /* svh, so the iOS toolbar cannot crop it */
  display: grid; align-items: center;
  overflow: hidden;
  isolation: isolate;
  padding-block: clamp(24px, 6vh, 60px) clamp(40px, 9vh, 90px);
}

/* The ground behind it: their blue, barely there, so a light page still feels
   like their brand rather than like white paper. */
.hero__wash {
  position: absolute; inset: 0; z-index: -2;
  /* NO solid --bg here. It used to end `…, var(--bg)`, which is opaque, and sat
     at z-index -2 directly over the photo sequence at -3 — the hero rendered
     with no footage in it at all and looked, convincingly, like a design
     decision. The body already carries --bg; this layer is tint only. */
  background:
    radial-gradient(60% 55% at 78% 30%, var(--brand-20), transparent 68%),
    radial-gradient(70% 60% at 12% 85%, color-mix(in srgb, var(--navy) 10%, transparent), transparent 70%);
}
/* A faint court, drawn in the wash. It reads as texture, not as decoration. */
.hero__wash::after {
  content: ""; position: absolute; inset: 0;
  background-image:
    linear-gradient(to right, var(--brand-10) 1px, transparent 1px),
    linear-gradient(to bottom, var(--brand-10) 1px, transparent 1px);
  background-size: 64px 64px;
  mask-image: radial-gradient(70% 70% at 50% 40%, #000, transparent 75%);
  -webkit-mask-image: radial-gradient(70% 70% at 50% 40%, #000, transparent 75%);
  opacity: .5;
}

/* ON A PHONE THE OBJECT IS NOT BELOW THE COPY.
   Stacked, the headline and the two buttons fill the viewport and the racket
   begins below the fold — so the one thing the hero exists to show is the one
   thing you cannot see until you scroll past it. Instead it is placed to the
   right and allowed to BLEED off the edge, sitting behind the type: in frame
   from the first paint, turning as soon as you move, and never fighting the
   headline for width. At 900px it becomes a proper two-column hero. */
.hero__grid { display: grid; gap: clamp(18px, 4vh, 40px); align-items: center; position: relative; }
.hero__copy { position: relative; z-index: 2; }

.hero__object {
  position: absolute; z-index: 1;
  top: 58%; right: calc(var(--gut) * -2.2);
  translate: 0 -50%;
  width: 56%; min-height: 0;
  opacity: .92;
  pointer-events: none;
}

@media (min-width: 900px) {
  .hero__grid { grid-template-columns: 1fr 0.9fr; gap: 40px; }
  .hero__lede { max-width: 34ch; }
  .hero__object {
    /* relative, NOT static: the ball is absolute inside this, and `static`
       sent it to .hero__grid — across the hero and over the headline. */
    position: relative; inset: auto; translate: none;
    width: auto; opacity: 1;
    display: grid; place-items: center; min-height: 62svh;
  }
}

.hero h1 {
  font-size: clamp(54px, 15vw, 118px);
  margin-bottom: 16px;
}
@media (min-width: 900px) { .hero h1 { font-size: clamp(72px, 7.4vw, 124px); } }
.hero h1 em { font-style: normal; color: var(--brand-deep); }
.hero__lede {
  margin: 0 0 26px; max-width: 19ch;
  color: var(--ink-2); font-size: 17.5px; font-weight: 600;
}
.hero__cta { display: flex; flex-wrap: wrap; gap: 12px; }
@media (max-width: 560px) {
  /* A hero button that wraps to two lines inside a narrow copy column reads as
     broken. On a phone they are full width, which is where a thumb wants them. */
  .hero__cta .btn { width: 100%; }
}

/* ── the object ───────────────────────────────────────────────────────────
   `--s` runs 0 → 1 across the first viewport of scroll. The racket turns about
   52 degrees over that, rises, and drifts; the ball falls past it at a
   different rate, which is what stops the two reading as one flat picture. */
.racket {
  width: 100%; height: auto;
  filter: drop-shadow(0 30px 50px rgba(10, 28, 43, .22));
  transform:
    rotate(calc(-16deg + var(--s, 0) * 52deg))
    translateY(calc(var(--s, 0) * -7vh))
    translateX(calc(var(--s, 0) * 2vw))
    scale(calc(1 + var(--s, 0) * 0.06));
  transform-origin: 52% 78%;      /* the grip, so it swings rather than spins */
  will-change: transform;
}
@media (min-width: 900px) { .racket { width: min(76%, 340px); } }

.ball {
  position: absolute;
  top: 2%; left: -2%;
  width: clamp(26px, 6vw, 44px); aspect-ratio: 1;
  border-radius: 50%;
  background: radial-gradient(circle at 34% 30%, #E8FF8E, var(--lime) 58%, #8FA827);
  box-shadow: 0 10px 24px rgba(10, 28, 43, .20);
  transform: translate3d(calc(var(--s, 0) * 26vw), calc(var(--s, 0) * 46vh), 0) rotate(calc(var(--s, 0) * 300deg));
  will-change: transform;
}
/* The seam, so it is a padel ball and not a dot. */
.ball::after {
  content: ""; position: absolute; inset: 14% 8%;
  border-radius: 50%;
  border: 2px solid rgba(255, 255, 255, .5);
  border-left-color: transparent; border-right-color: transparent;
}

@media (prefers-reduced-motion: reduce) {
  .racket, .ball { transform: none; }
}

/* The hint, which is now a line that fills rather than a bouncing dot: a page
   that tells you to scroll should not itself be jumping. */
.scrollhint {
  position: absolute; left: 50%; bottom: 14px; translate: -50% 0;
  width: 2px; height: 46px; border-radius: 2px;
  background: var(--line-strong); overflow: hidden;
}
.scrollhint span {
  position: absolute; inset: 0;
  background: var(--brand-deep);
  transform: scaleY(var(--s, 0)); transform-origin: top;
}
@media (prefers-reduced-motion: reduce) { .scrollhint span { transform: scaleY(1); } }

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 9px;
  min-height: 52px; padding: 0 24px;
  border-radius: 999px; border: 0; cursor: pointer; text-decoration: none;
  font: 900 15px/1 "Barlow Condensed", 'Barlow Condensed Fallback', sans-serif;
  text-transform: uppercase; letter-spacing: .07em;
  transition: transform .18s var(--ease), box-shadow .22s var(--ease), background .2s;
}
.btn:active { transform: scale(.96); }
/* The signature button: THEIR cyan with a dark navy label. 10.37:1, which is
   well past AA, and it is the one place the logo colour gets to be loud. */
.btn--primary { background: var(--brand); color: var(--ink); box-shadow: var(--glow); }
.btn--primary:hover { background: color-mix(in srgb, var(--brand) 86%, var(--navy)); box-shadow: var(--shadow-lift); }

/* The serious one, for sponsorship and anything that commits somebody. */
.btn--navy { background: var(--navy); color: #fff; }
.btn--navy:hover { background: color-mix(in srgb, var(--navy) 88%, #000); box-shadow: var(--shadow-lift); }

.btn--lime { background: var(--lime); color: var(--ink); }
.btn--ghost { background: var(--surface); color: var(--ink); border: 1px solid var(--line-strong); }
/* NOT `color: var(--brand)` on hover — the brand cyan is 1.57:1 on a light
   ground and would make the label vanish on the one interaction that confirms
   the button works. The border carries the brand instead, the text goes deeper. */
.btn--ghost:hover { border-color: var(--brand); color: var(--brand-deep); box-shadow: var(--shadow); }
.btn[disabled] { opacity: .45; cursor: not-allowed; transform: none; }
.btn--full { width: 100%; }

/* (The old bouncing-ball scroll hint lived here. It was replaced by the filling
   line in the hero block above, and left behind a second lime ball at the foot
   of the hero because it came later in the file and won. Deleted 11 Oct.) */

/* ------------------------------------------------------------------ sections */
/* R12: ONE spacing scale — see --sec in :root. Every section on every page. */
section { padding: var(--sec) 0; position: relative; }
.eyebrow {
  font: 900 12px/1 "Barlow Condensed", 'Barlow Condensed Fallback', sans-serif;
  text-transform: uppercase; letter-spacing: .22em;
  color: var(--brand-deep); margin: 0 0 14px;
  display: flex; align-items: center; gap: 10px;
}
.eyebrow::before { content: ""; width: 26px; height: 2px; background: var(--brand); }
h2 { font-size: clamp(36px, 9vw, 64px); margin-bottom: 18px; }
.lede { color: var(--mute); max-width: 62ch; font-size: 16.5px; margin: 0 0 30px; }

/* One continuous rise, staggered by order rather than each element doing its own
   thing. Baseline is IntersectionObserver in app.js, so it works on every iOS
   Safari rather than only where scroll-driven animations have landed. */
/* R12: VISIBLE BY DEFAULT. The hidden start state only applies to .rise--wait,
   which app.js puts on an element itself, once it is running the reveals and
   has already shown everything on screen — so no JavaScript, slow JavaScript or a failed
   app.js can never leave a section blank (James: "empty pages"). */
.rise { transition: opacity .4s var(--ease), transform .4s var(--ease); }
.rise--wait:not(.in) { opacity: 0; transform: translateY(18px); }
.rise.rise--now { transition: none; }
@media (prefers-reduced-motion: reduce) { .rise, .rise--wait:not(.in) { opacity: 1; transform: none; transition: none; } }

/* --------------------------------------------------------------------- shop */
.filters { display: flex; gap: 8px; overflow-x: auto; padding-bottom: 12px; margin-bottom: 22px; scrollbar-width: none; }
.filters::-webkit-scrollbar { display: none; }
.chip {
  flex: 0 0 auto; min-height: 40px; padding: 0 16px;
  border-radius: 999px; border: 1px solid var(--line); background: transparent;
  color: var(--mute); font: 800 13px/1 Nunito, 'Nunito Fallback', sans-serif; cursor: pointer;
  display: inline-flex; align-items: center;
  transition: .2s var(--ease);
}
.chip[aria-pressed="true"] { background: var(--brand); border-color: var(--brand); color: var(--ink); }

.grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px; }
/* The `:empty` reservations below are now a FALLBACK, not the main defence.
   Since 12 October build.mjs renders the real 57 cards and the real chips into
   the HTML, so the grid has its true height before a single byte of JavaScript
   runs — which is exactly right at every breakpoint, where a guessed 70vh was
   not (it left 0.101 of shift on a desktop shop, against a 0.1 pass mark).
   These stay for any grid that is still filled from JS — /product's "Other
   gear" — and cost nothing when the grid is already full. */
/* RESERVED SPACE, until the cards arrive.
   The grid and the filter chips are both empty in the HTML and filled by
   app.js once /api/catalogue answers. Until 12 October that meant everything
   below them — the "How buying works" band — rendered high up the page and
   then jumped 0.32 of a viewport downwards the moment the products landed.
   Lighthouse scored the shop's layout shift at 0.322, where anything over 0.1
   is a fail, and on a phone it is the kind of jump that makes you tap the
   wrong thing. The cards themselves never shifted — .card__fig has
   aspect-ratio: 1 — so this is purely about holding the space open.
   `:empty` stops applying the instant the first card is appended. */
.grid:empty { min-height: 70vh; }
.filters:empty { min-height: 42px; }
@media (min-width: 680px) { .grid { grid-template-columns: repeat(3, 1fr); gap: 18px; } }
@media (min-width: 980px) { .grid { grid-template-columns: repeat(4, 1fr); } }

.card {
  background: var(--surface);
  border: 1px solid var(--line); border-radius: var(--rad);
  overflow: hidden; display: flex; flex-direction: column;
  box-shadow: var(--shadow);
  transition: transform .25s var(--ease), border-color .25s var(--ease), box-shadow .25s var(--ease);
}
.card:hover { transform: translateY(-4px); border-color: var(--brand); box-shadow: var(--shadow-lift); }
.card__fig {
  aspect-ratio: 1; position: relative;
  background: repeating-linear-gradient(135deg, var(--surface-2) 0 12px, var(--surface) 12px 24px);
  display: grid; place-items: center;
}
.card__fig img { width: 100%; height: 100%; object-fit: cover; }
.card__ph { color: var(--mute); font: 900 11px/1.4 "Barlow Condensed", 'Barlow Condensed Fallback', sans-serif; letter-spacing: .14em; text-align: center; padding: 10px; }
.card__body { padding: 12px; display: flex; flex-direction: column; gap: 6px; flex: 1; }
.card__name { font-weight: 800; font-size: 14px; line-height: 1.3; }
.card__by { font-size: 11px; color: var(--mute); }
.card__by b { color: var(--brand-deep); font-weight: 800; }
.card__price { font: 900 20px/1 "Barlow Condensed", 'Barlow Condensed Fallback', sans-serif; color: var(--ink); margin-top: auto; }
.card__add {
  min-height: 42px; border: 0; border-radius: 10px; cursor: pointer;
  background: var(--brand-10); color: var(--brand-deep);
  font: 900 13px/1 "Barlow Condensed", 'Barlow Condensed Fallback', sans-serif; text-transform: uppercase; letter-spacing: .07em;
  transition: .18s var(--ease);
}
.card__add:hover { background: var(--brand); color: var(--ink); }
.card__add:active { transform: scale(.96); }
.tag {
  position: absolute; top: 8px; left: 8px; z-index: 2;
  background: var(--lime); color: var(--ink);
  font: 900 9px/1 "Barlow Condensed", 'Barlow Condensed Fallback', sans-serif; letter-spacing: .1em;
  padding: 5px 7px; border-radius: 5px; text-transform: uppercase;
}

/* ------------------------------------------------------------------- events */
.ev { display: grid; gap: 14px; }
@media (min-width: 760px) { .ev { grid-template-columns: repeat(2, 1fr); } }
.ev__item {
  display: grid; grid-template-columns: 92px 1fr; gap: 14px;
  background: var(--surface); border: 1px solid var(--line); box-shadow: var(--shadow);
  border-radius: var(--rad); overflow: hidden;
}
.ev__item img { width: 92px; height: 100%; object-fit: cover; }
.ev__txt { padding: 14px 14px 14px 0; display: flex; flex-direction: column; gap: 5px; }
.ev__when { font: 900 11px/1 "Barlow Condensed", 'Barlow Condensed Fallback', sans-serif; letter-spacing: .12em; color: var(--brand-deep); text-transform: uppercase; }
.ev__name { font-weight: 800; font-size: 15px; }
.ev__meta { font-size: 12.5px; color: var(--mute); }
.ev__done { font: 900 10px/1 "Barlow Condensed", 'Barlow Condensed Fallback', sans-serif; letter-spacing: .1em; color: var(--mute); text-transform: uppercase; }
.ev__done a { color: var(--brand-deep); text-decoration: underline; }

.empty {
  border: 1px dashed var(--line); border-radius: var(--rad);
  padding: 30px 22px; text-align: center; background: var(--surface);
}
.empty h3 { font-size: 24px; margin-bottom: 8px; }
.empty p { color: var(--mute); margin: 0 0 18px; }

/* ----------------------------------------------------------------- sponsors
   A wall, not a ticker. Deliberately static: a moving strip of logos was asked
   for once and taken down again. */
.wall { display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; }
@media (min-width: 680px) { .wall { grid-template-columns: repeat(5, 1fr); gap: 14px; } }
.wall a, .wall span {
  display: grid; place-items: center; gap: 8px;
  min-height: 104px; padding: 16px 10px;
  background: var(--surface); border: 1px solid var(--line); border-radius: 14px;
  text-decoration: none; text-align: center;
  font: 900 13px/1.25 "Barlow Condensed", 'Barlow Condensed Fallback', sans-serif; letter-spacing: .06em;
  text-transform: uppercase; color: var(--ink-2);
  transition: .24s var(--ease);
}
.wall a:hover { border-color: var(--brand); color: var(--brand-deep); transform: translateY(-3px); box-shadow: var(--shadow-lift); }
.wall img { max-height: 42px; width: auto; object-fit: contain; }

/* ---------------------------------------------------------------- community */
.join { display: grid; gap: 12px; }
@media (min-width: 680px) { .join { grid-template-columns: repeat(2, 1fr); } }
.join a {
  display: flex; align-items: center; gap: 14px;
  padding: 20px; border-radius: var(--rad);
  background: var(--surface); border: 1px solid var(--line);
  text-decoration: none; transition: .24s var(--ease);
}
.join a:hover { border-color: var(--brand); transform: translateY(-3px); }
.join b { display: block; font-size: 16px; font-weight: 800; }
.join small { color: var(--mute); font-size: 13px; }
.join .ic { width: 42px; height: 42px; flex: 0 0 42px; display: grid; place-items: center; border-radius: 12px; background: var(--brand-10); }

/* ------------------------------------------------------------------- footer */
/* R8 §A2: 13.5px was the whole problem. Thomas could not read the email
   address or the page links, so the footer now sets 16px — the same size as the
   page body — in the site's full-strength navy rather than a grey. */
footer { border-top: 1px solid var(--line); padding: var(--sec) 0 calc(var(--sec) + env(safe-area-inset-bottom)); color: var(--ink); font-size: 16px; background: var(--surface-2); }
footer a { color: var(--brand-deep); }
.footer__grid { display: grid; gap: 22px; }
@media (min-width: 680px) { .footer__grid { grid-template-columns: 1.4fr 1fr 1fr; } }

/* ------------------------------------------------------- the cart, as a sheet
   A bottom sheet on a phone because that is where the thumb is, and a panel on a
   desktop. One element, two shapes. */
.scrim {
  position: fixed; inset: 0; z-index: 90;
  background: rgba(10, 28, 43, .55);
  opacity: 0; pointer-events: none; transition: opacity .3s var(--ease);
}
.scrim.open { opacity: 1; pointer-events: auto; }

.sheet {
  position: fixed; z-index: 100; left: 0; right: 0; bottom: 0;
  max-height: 92svh; display: flex; flex-direction: column;
  background: var(--surface); border-top: 1px solid var(--line);
  border-radius: 22px 22px 0 0;
  transform: translateY(101%);
  transition: transform .36s var(--ease);
  box-shadow: var(--shadow);
}
.sheet.open { transform: none; }
@media (min-width: 760px) {
  .sheet {
    top: 0; left: auto; width: 440px; max-height: 100svh;
    border-radius: 0; border-left: 1px solid var(--line); border-top: 0;
    transform: translateX(101%);
  }
  .sheet.open { transform: none; }
}
.sheet__head {
  display: flex; align-items: center; gap: 12px;
  padding: 16px var(--gut); border-bottom: 1px solid var(--line); flex: 0 0 auto;
}
.sheet__head h3 { font-size: 22px; flex: 1; }
.x {
  width: 40px; height: 40px; border-radius: 50%; border: 1px solid var(--line);
  background: transparent; color: var(--mute); font-size: 20px; cursor: pointer; line-height: 1;
}
.x:hover { color: var(--ink); border-color: var(--brand); }
.sheet__body { overflow-y: auto; padding: var(--gut); flex: 1; -webkit-overflow-scrolling: touch; }
.sheet__foot {
  padding: 14px var(--gut) calc(14px + env(safe-area-inset-bottom));
  border-top: 1px solid var(--line); background: var(--surface-2); flex: 0 0 auto;
}

.row { display: grid; grid-template-columns: 1fr auto; gap: 10px; padding: 12px 0; border-bottom: 1px solid var(--line); }
.row:last-of-type { border-bottom: 0; }
.row__n { font-weight: 800; font-size: 14px; }
.row__by { font-size: 11.5px; color: var(--mute); }
.row__r { text-align: right; display: flex; flex-direction: column; align-items: flex-end; gap: 6px; }
.row__p { font: 900 16px/1 "Barlow Condensed", 'Barlow Condensed Fallback', sans-serif; color: var(--ink); }
.qty { display: inline-flex; align-items: center; border: 1px solid var(--line); border-radius: 999px; overflow: hidden; }
.qty button { width: 32px; height: 32px; background: transparent; border: 0; color: var(--brand-deep); font-size: 17px; cursor: pointer; line-height: 1; }
.qty button:hover { background: var(--brand-10); }
.qty span { min-width: 26px; text-align: center; font-weight: 800; font-size: 13px; }

.seg { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin: 16px 0; }
.seg button {
  min-height: 56px; border-radius: 12px; cursor: pointer;
  border: 1px solid var(--line); background: transparent; color: var(--mute);
  font: 800 13px/1.3 Nunito, 'Nunito Fallback', sans-serif; padding: 8px; transition: .2s var(--ease);
}
.seg button[aria-pressed="true"] { border-color: var(--brand); background: var(--brand-10); color: var(--ink); }
.seg small { display: block; font-weight: 600; font-size: 11px; opacity: .75; margin-top: 3px; }

.fields { display: grid; gap: 10px; margin-top: 4px; }
.fields label { font-size: 12px; font-weight: 800; color: var(--mute); display: block; margin-bottom: 5px; }
.fields input, .fields textarea {
  width: 100%; min-height: 50px; padding: 13px 14px;
  background: var(--surface); border: 1px solid var(--line-strong); border-radius: 11px;
  color: var(--ink); font: 400 16px/1.45 Nunito, 'Nunito Fallback', sans-serif;  /* 16px stops iOS zooming on focus */
  resize: vertical;
}
.fields input:focus, .fields textarea:focus { border-color: var(--brand-deep); outline: none; box-shadow: 0 0 0 3px var(--brand-20); }
.fields input::placeholder, .fields textarea::placeholder { color: var(--mute); }

.totals { display: grid; gap: 7px; font-size: 14px; margin-bottom: 12px; }
.totals div { display: flex; justify-content: space-between; color: var(--mute); }
.totals .big { color: var(--ink); font: 900 24px/1 "Barlow Condensed", 'Barlow Condensed Fallback', sans-serif; padding-top: 9px; border-top: 1px solid var(--line); margin-top: 3px; }
.totals .big span:last-child { color: var(--ink); }

.pay__note { font-size: 11.5px; color: var(--mute); text-align: center; margin: 10px 0 0; line-height: 1.5; }
.wallets { display: flex; align-items: center; justify-content: center; gap: 8px; margin-top: 9px; opacity: .85; }
.wallets span {
  font: 800 10px/1 Nunito, 'Nunito Fallback', sans-serif; letter-spacing: .05em; color: var(--mute);
  border: 1px solid var(--line); border-radius: 5px; padding: 5px 7px;
}

.err {
  background: var(--danger-bg); border: 1px solid var(--danger);
  color: var(--danger); border-radius: 11px; padding: 11px 13px; font-size: 13.5px; margin-bottom: 12px;
}
.err ul { margin: 6px 0 0; padding-left: 17px; }

.hide { display: none !important; }
.spin { display: inline-block; width: 15px; height: 15px; border: 2px solid color-mix(in srgb, var(--ink) 25%, transparent); border-top-color: var(--ink); border-radius: 50%; animation: sp .7s linear infinite; }
@keyframes sp { to { transform: rotate(360deg); } }

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

/* ===========================================================================
   PAGES — added 11 October 2026, when the build went from one page to nine.
   Everything above this line is the 8 October single page and is unchanged.
   Phone first, same as the rest: the only media queries widen things.
   =========================================================================== */

/* ------------------------------------------------------- cross-page motion
   The native View Transitions API. On a multi-page site this is the single
   biggest "premium" win available and it costs nothing: the browser cross-fades
   the old and new document itself, so tapping Shop no longer flashes white.
   Browsers without it simply navigate as they always did — there is no polyfill
   and nothing to load, which is exactly why it is worth using. */
@view-transition { navigation: auto; }

/* The bar and the footer are the SAME elements either side of a navigation, so
   naming them keeps them still while the content crosses over. Without this the
   header fades out and back in on every tap, which reads as a page reload. */
.bar { view-transition-name: bar; }
footer { view-transition-name: foot; }

::view-transition-old(root) { animation-duration: .22s; }
::view-transition-new(root) { animation-duration: .28s; }

@media (prefers-reduced-motion: reduce) {
  @view-transition { navigation: none; }
}

/* ---------------------------------------------------------------- skip link */
.skip {
  position: absolute; left: 50%; translate: -50% -140%;
  z-index: 200; padding: 12px 18px;
  background: var(--lime); color: var(--ink);
  font: 900 13px/1 "Barlow Condensed", 'Barlow Condensed Fallback', sans-serif; text-transform: uppercase;
  letter-spacing: .08em; text-decoration: none; border-radius: 0 0 10px 10px;
  transition: translate .2s var(--ease);
}
.skip:focus-visible { translate: -50% 0; }

/* ------------------------------------------------------------- the bar, v2 */
.bar__home { display: inline-flex; border-radius: 7px; }
.bar__nav { display: none; gap: 22px; font-weight: 800; font-size: 14px; letter-spacing: .04em; text-transform: uppercase; }
.bar__nav a {
  position: relative; text-decoration: none; color: var(--mute);
  padding: 4px 0; transition: color .2s;
}
.bar__nav a:hover { color: var(--brand-deep); }
/* The current page is drawn from aria-current, so the underline and what a
   screen reader announces can never disagree. */
.bar__nav a[aria-current="page"] { color: var(--ink); }
.bar__nav a[aria-current="page"]::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: -2px;
  height: 2px; background: var(--brand); box-shadow: var(--glow);
}
@media (min-width: 900px) { .bar__nav { display: flex; } }

/* The phone menu: two bars that become a cross. */
.burger {
  width: 44px; height: 44px; flex: 0 0 44px;
  display: grid; place-content: center; gap: 6px;
  background: transparent; border: 1px solid var(--line); border-radius: 12px;
  cursor: pointer;
}
.burger span {
  display: block; width: 18px; height: 2px; background: var(--ink);
  transition: transform .26s var(--ease), opacity .2s;
}
.burger[aria-expanded="true"] span:first-child { transform: translateY(4px) rotate(45deg); }
.burger[aria-expanded="true"] span:last-child { transform: translateY(-4px) rotate(-45deg); }
@media (min-width: 900px) { .burger { display: none; } }

.menu {
  position: sticky; top: 54px; z-index: 55;
  background: color-mix(in srgb, var(--bg) 97%, transparent);
  border-bottom: 1px solid var(--line);
}
.menu[hidden] { display: none; }
.menu nav { display: grid; padding: 6px var(--gut) 14px; }
.menu nav a {
  padding: 15px 0; border-bottom: 1px solid var(--line);
  text-decoration: none; color: var(--ink);
  font: 900 20px/1 "Barlow Condensed", 'Barlow Condensed Fallback', sans-serif; text-transform: uppercase; letter-spacing: .04em;
}
.menu nav a:last-child { border-bottom: 0; }
.menu nav a[aria-current="page"] { color: var(--brand-deep); }
@media (min-width: 900px) { .menu { display: none; } }

/* ------------------------------------------------------------ page sections
   `.top` is the first section of an inner page. It carries the nav clearance and
   a soft cyan wash so an inner page has a head of its own rather than starting
   abruptly under the bar. */
.top { padding: var(--sec) 0 0; position: relative; }
.top::before {
  content: ""; position: absolute; inset: 0 0 auto; height: 320px; z-index: -1;
  background: radial-gradient(70% 100% at 18% 0%, var(--brand-10), transparent 70%);
  pointer-events: none;
}
.top h1 { font-size: clamp(46px, 13vw, 104px); margin-bottom: 18px; }
.top .lede { margin-bottom: 0; }
/* a section that continues the page head straight above it */
.top + .tight, main > .tight:first-child { padding-top: var(--sec-half); }
.narrow { max-width: 760px; }
.center { text-align: center; }
.center .eyebrow { justify-content: center; }
.center .eyebrow::before { display: none; }

/* A band sets a section apart without a new colour: on a light ground it goes
   DOWN a step into the tinted surface, not up, or it disappears against --bg. */
.band {
  background:
    linear-gradient(180deg, var(--brand-05), transparent 55%),
    var(--surface-2);
  border-block: 1px solid var(--line);
}

.muted { color: var(--mute); }
.small { font-size: 13px; }

/* A statement of fact we want read, not skipped — a placeholder warning, a
   policy that is not written yet. Deliberately not styled as an error. */
.flag {
  border-left: 3px solid var(--brand);
  background: var(--brand-05);
  border-radius: 0 12px 12px 0;
  padding: 13px 16px; margin: 18px 0;
  font-size: 14px; line-height: 1.6; color: var(--ink);
}
.flag strong { color: var(--navy); }

/* A link that goes somewhere, shaped so a thumb finds it. */
.arrow {
  display: inline-flex; align-items: center; gap: 9px;
  margin-top: 26px; min-height: 44px;
  font: 900 14px/1 "Barlow Condensed", 'Barlow Condensed Fallback', sans-serif;
  text-transform: uppercase; letter-spacing: .09em;
  color: var(--brand-deep); text-decoration: none;
}
.arrow::after {
  content: "→"; transition: transform .22s var(--ease);
  font-family: Nunito, 'Nunito Fallback', sans-serif; font-size: 16px;
}
.arrow:hover::after { transform: translateX(5px); }

.btn--lg { min-height: 58px; padding: 0 30px; font-size: 16px; }

/* ----------------------------------------------------------- about, split */
.about__split { display: grid; gap: 10px; }
@media (min-width: 860px) {
  .about__split { grid-template-columns: 1fr 1fr; gap: 54px; align-items: start; }
}
.story { font-size: 17.5px; line-height: 1.75; color: var(--ink-2); margin: 0 0 22px; }
.story:first-of-type {
  font-size: 21px; color: var(--ink); font-weight: 600;
}

/* -------------------------------------------------------- the court motif
   The one decorative element, taken from their logo's lime ball and the court
   itself. The lines stroke themselves in when the divider is reached, then the
   ball settles. Pure SVG and CSS: sharp at any width, no payload, no credits.
   `--d` is the dash length; it only has to exceed each path's own length. */
/* `color` here feeds `stroke="currentColor"` on the SVG; it is a line, not type. */
.court { color: var(--brand); padding: 18px 0 26px; overflow: hidden; }
.court svg { width: min(420px, 78%); height: auto; margin-inline: auto; display: block; }
.court__g * {
  --d: 1100;
  stroke-dasharray: var(--d); stroke-dashoffset: 0;
  transition: stroke-dashoffset 1.25s var(--ease);
}
/* R12: undrawn only while app.js is holding it for the reveal (see .rise) */
.court.rise--wait:not(.in) .court__g * { stroke-dashoffset: var(--d); }
.court__g rect { transition-delay: 0s; }
.court__g line:nth-of-type(1) { transition-delay: .18s; }
.court__g line:nth-of-type(2) { transition-delay: .3s; }
.court__g line:nth-of-type(3) { transition-delay: .42s; }
.court__g line:nth-of-type(4) { transition-delay: .5s; }
.court__ball { opacity: 1; transform-origin: 200px 100px; transition: opacity .5s .9s var(--ease); }
.court.rise--wait:not(.in) .court__ball { opacity: 0; }
.court.in .court__ball { animation: pulse 2.6s 1.4s var(--ease) infinite; }
@keyframes pulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.22); } }
@media (prefers-reduced-motion: reduce) {
  .court__g * { stroke-dashoffset: 0; transition: none; }
  .court__ball { opacity: 1; animation: none; transition: none; }
}

/* ------------------------------------------------------------------- steps */
.steps { display: grid; gap: 14px; margin: 26px 0; text-align: left; }
@media (min-width: 760px) { .steps { grid-template-columns: repeat(3, 1fr); gap: 18px; } }
.step {
  background: var(--surface); box-shadow: var(--shadow); border: 1px solid var(--line); border-radius: var(--rad);
  padding: 22px 20px; transition: border-color .25s var(--ease), transform .25s var(--ease);
}
.step:hover { border-color: var(--brand-40); transform: translateY(-3px); }
.step__n {
  display: grid; place-items: center;
  width: 34px; height: 34px; border-radius: 50%; margin-bottom: 14px;
  background: var(--brand-10); color: var(--brand-deep);
  font: 900 15px/1 "Barlow Condensed", 'Barlow Condensed Fallback', sans-serif;
}
.step h3 { font-size: 21px; margin-bottom: 8px; }
.step p { margin: 0; color: var(--mute); font-size: 14.5px; }

/* -------------------------------------------------------------- breadcrumb */
.crumbs {
  padding: 16px 0 0;
  font: 800 12px/1 Nunito, 'Nunito Fallback', sans-serif; color: var(--mute);
  letter-spacing: .02em;
}
.crumbs a { color: var(--brand-deep); text-decoration: none; }
.crumbs a:hover { text-decoration: underline; }
.crumbs span[aria-hidden] { padding: 0 6px; opacity: .5; }

/* ---------------------------------------------------------- product detail */
.product { display: grid; gap: 24px; }
@media (min-width: 900px) {
  .product { grid-template-columns: 1fr 1fr; gap: 50px; align-items: start; }
  /* The picture stays while the detail scrolls — the one place stickiness is
     worth it, because the thing being bought should not leave the screen. */
  .product__fig { position: sticky; top: 86px; }
}
.product__fig {
  position: relative; border-radius: var(--rad); overflow: hidden;
  border: 1px solid var(--line);
  background: repeating-linear-gradient(135deg, var(--surface-2) 0 12px, var(--surface) 12px 24px);
  aspect-ratio: 1; display: grid; place-items: center;
}
.product__fig img { width: 100%; height: 100%; object-fit: cover; }
.product__body h1 {
  font-size: clamp(32px, 8vw, 56px); margin-bottom: 10px;
  /* Product names are the CLIENT'S words and are never shortened to fit —
     "Racket Bag Multigame 3.4 (Black/Orange)" has a 14-character unbreakable
     run in it. A heading that may contain anything has to be allowed to break;
     the alternative is clipping a product name on a phone. */
  overflow-wrap: anywhere;
  hyphens: auto;
}
.product__price {
  font: 900 clamp(32px, 8vw, 46px)/1 "Barlow Condensed", 'Barlow Condensed Fallback', sans-serif;
  color: var(--ink); margin: 0 0 18px;
}

/* A spec list: label left, value right, one line each on a phone too. */
.spec { margin: 26px 0 0; display: grid; gap: 0; }
.spec__row {
  display: grid; grid-template-columns: 1fr; gap: 3px;
  padding: 14px 0; border-bottom: 1px solid var(--line);
}
.spec__row:first-child { border-top: 1px solid var(--line); }
.spec dt {
  font: 900 11px/1.2 "Barlow Condensed", 'Barlow Condensed Fallback', sans-serif;
  text-transform: uppercase; letter-spacing: .14em; color: var(--mute);
}
.spec dd { margin: 0; font-size: 15px; font-weight: 600; }
.spec dd a { color: var(--brand-deep); }
@media (min-width: 560px) {
  .spec__row { grid-template-columns: 170px 1fr; gap: 16px; align-items: baseline; }
}

/* ------------------------------------------------------- tournament detail */
.event { display: grid; gap: 22px; }
.event__head h1 { font-size: clamp(38px, 10vw, 76px); margin-bottom: 10px; }
.event__sub { color: var(--brand-deep); font-weight: 700; font-size: 16px; margin: 0; }
.event__sub:empty { display: none; }
.event__fig { border-radius: var(--rad); overflow: hidden; border: 1px solid var(--line); }
.event__fig:empty { display: none; }
.event__fig img { width: 100%; height: auto; aspect-ratio: 3/2; object-fit: cover; }
.event__entry { margin: 26px 0 0; display: grid; gap: 10px; justify-items: start; }
@media (min-width: 900px) {
  .event { grid-template-columns: 1.1fr 1fr; gap: 46px; align-items: start; }
  .event__head { grid-column: 1 / -1; }
  .event__fig { position: sticky; top: 86px; }
}

/* A state pill: open is cyan and alive, closed is grey and quiet. */
.pill {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 6px 12px; border-radius: 999px;
  font: 900 11px/1 "Barlow Condensed", 'Barlow Condensed Fallback', sans-serif;
  text-transform: uppercase; letter-spacing: .12em;
}
.pill::before { content: ""; width: 7px; height: 7px; border-radius: 50%; background: currentColor; }
.pill.is-open { background: var(--brand-10); color: var(--brand-deep); }
.pill.is-open::before { animation: blink 2s var(--ease) infinite; }
.pill.is-closed { background: var(--surface-2); color: var(--mute); }
@keyframes blink { 0%, 100% { opacity: 1; } 50% { opacity: .3; } }
@media (prefers-reduced-motion: reduce) { .pill.is-open::before { animation: none; } }

/* --------------------------------------------------------- sponsor blocks */
/* ONE row per sponsor, full width — `grid-template-columns` is set explicitly
   because an earlier rule on this same class laid the old cards out two
   across, and a rule that only sets `display: grid` does not clear it. */
.sponsors { display: grid; grid-template-columns: 1fr; gap: 14px; }
/* R8 §B1: one row per sponsor at every width — the old two-up grid belonged to the small cards these replaced. */
@media (min-width: 760px) { .sponsors { gap: 18px; } }
.sp {
  background: var(--surface); box-shadow: var(--shadow); border: 1px solid var(--line); border-radius: var(--rad);
  padding: 24px 22px; display: flex; flex-direction: column; gap: 10px;
  transition: border-color .25s var(--ease), transform .25s var(--ease);
}
.sp:hover { border-color: var(--brand-40); transform: translateY(-3px); }
.sp__mark {
  min-height: 60px; display: flex; align-items: center;
  font: 900 26px/1 "Barlow Condensed", 'Barlow Condensed Fallback', sans-serif;
  text-transform: uppercase; letter-spacing: .03em; color: var(--ink);
}
.sp__mark img { max-height: 54px; width: auto; object-fit: contain; }
.sp__holds { font-size: 14px; color: var(--mute); margin: 0; }
.sp__holds b { color: var(--brand-deep); font-weight: 800; }
.sp__note {
  font-size: 12.5px; color: var(--mute); margin: 0;
  padding-top: 10px; border-top: 1px solid var(--line);
}
.sp__links { display: flex; flex-wrap: wrap; gap: 10px; margin-top: auto; padding-top: 4px; }

/* ------------------------------------------------------------------ venues */
.venues { display: grid; gap: 12px; }
@media (min-width: 680px) { .venues { grid-template-columns: repeat(2, 1fr); } }
.venue {
  background: var(--surface); box-shadow: var(--shadow); border: 1px solid var(--line); border-radius: var(--rad);
  padding: 20px; display: grid; gap: 5px;
}
.venue h3 { font-size: 22px; }
.venue p { margin: 0; color: var(--mute); font-size: 14px; }

/* -------------------------------------------------------------- policies */
.jump { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 30px; }
.jump a {
  min-height: 40px; display: inline-flex; align-items: center; padding: 0 15px;
  border: 1px solid var(--line); border-radius: 999px;
  font: 800 13px/1 Nunito, 'Nunito Fallback', sans-serif; color: var(--mute); text-decoration: none;
  transition: .2s var(--ease);
}
.jump a:hover { border-color: var(--brand); color: var(--brand-deep); }

.policy { margin-bottom: 46px; }   /* R12: anchor offset is html scroll-padding-top now */
.policy h2 {
  font-size: clamp(28px, 7vw, 40px); margin-bottom: 16px;
  padding-bottom: 12px; border-bottom: 2px solid var(--brand);
}
.policy h3 {
  font-size: 19px; margin: 26px 0 8px; color: var(--ink);
  /* Not condensed-uppercase here: a legal page reads better in the text face,
     and three levels of uppercase display type on one page is a wall. */
  font-family: Nunito, 'Nunito Fallback', sans-serif; font-weight: 900; text-transform: none; letter-spacing: 0;
}
.policy p, .policy li { color: var(--ink-2); font-size: 15.5px; line-height: 1.7; }
.policy p { margin: 0 0 12px; }
.policy ul { margin: 0 0 14px; padding-left: 20px; }
.policy li { margin-bottom: 7px; }

/* -------------------------------------------------------------- order page */
.receipt { min-height: 72svh; display: grid; align-content: center; }
.receipt__tick { margin: 0 auto 20px; width: 56px; height: 56px; }
.receipt__path {
  stroke-dasharray: 40; stroke-dashoffset: 40;
  animation: draw .7s .25s var(--ease) forwards;
}
@keyframes draw { to { stroke-dashoffset: 0; } }
@media (prefers-reduced-motion: reduce) { .receipt__path { animation: none; stroke-dashoffset: 0; } }
.receipt__ref {
  font: 900 15px/1 "Barlow Condensed", 'Barlow Condensed Fallback', sans-serif; letter-spacing: .12em;
  color: var(--brand-deep); margin: 22px 0;
}
.receipt__ref:empty { display: none; }

/* ------------------------------------------------------------------ footer */
.footer__logo { border-radius: 9px; margin-bottom: 12px; }
.footer__who { margin: 0; line-height: 1.7; }
.footer__h {
  margin: 0 0 10px; color: var(--ink); font-weight: 900;
  font-size: 13px; letter-spacing: .12em; text-transform: uppercase;
}
.footer__links { margin: 0; line-height: 2.1; }
.footer__links a { text-decoration: none; }
.footer__links a:hover { text-decoration: underline; }
.footer__base {
  margin-top: 30px; padding-top: 20px;
  border-top: 1px solid var(--line); font-size: 14px;
}
.footer__base p { margin: 0; }

/* ------------------------------------------------------------ consent bar
   POPIA: nothing is measured before this is answered, so the bar has to be
   reachable and legible rather than a strip of 10px grey at the bottom. It sits
   above the iOS home indicator and never covers the cart button. */
.consent {
  position: fixed; z-index: 95;
  left: var(--gut); right: var(--gut);
  bottom: calc(var(--gut) + env(safe-area-inset-bottom));
  background: var(--surface-2); border: 1px solid var(--line); border-radius: var(--rad);
  box-shadow: var(--shadow);
  padding: 18px; display: grid; gap: 14px;
  animation: consentIn .45s .6s var(--ease) both;
}
.consent[hidden] { display: none; }
.consent p { margin: 0; font-size: 13.5px; line-height: 1.6; color: var(--ink-2); }
.consent__btns { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.consent .btn { min-height: 46px; font-size: 13px; }
@keyframes consentIn { from { opacity: 0; transform: translateY(14px); } }
@media (min-width: 680px) {
  .consent { left: auto; right: var(--gut); max-width: 420px; }
}
@media (prefers-reduced-motion: reduce) { .consent { animation: none; } }

/* ---------------------------------------------------------- the hero film
   When a film exists it replaces the still and is SCRUBBED from scroll — see
   app.js. Both elements are stacked in the same place so swapping one for the
   other cannot move the headline. */
.hero__film {
  /* -3, NOT -2, and this is the whole trick. The scrim that makes dark type
     readable over a photograph is `.hero__seq::after`, a child of the sequence
     at -3. A film at -2 sits OVER that scrim: the hero looks richer and the
     headline drops to about 2:1 against it. At -3, prepended before .hero__seq
     in the DOM, the film paints under the same scrim the frames measured with,
     so adding motion cannot cost contrast. */
  position: absolute; inset: 0; z-index: -3;
  width: 100%; height: 100%; object-fit: cover;
  object-position: 50% 50%;
  filter: saturate(1.05) contrast(1.06);
}
.hero.has-film .hero__img { opacity: 0; }
/* The frames give way to the film but the sequence element STAYS — it carries
   the scrim, and it is what a visitor sees if the film 404s or is still
   arriving. */
/* `display`, not `opacity`. The obvious rule here was `opacity: 0`, and it did
   nothing: `.hero.has-seq .hero__seq img:first-child` is one class more
   specific, and the scroll handler writes `--o: 1` into it at the top of the
   page. So frame one sat fully opaque on top of the film, the hero showed
   their net photograph instead of the camera move, and every number in this
   file about the film was describing something nobody could see. `display`
   is not in that fight at all — nothing else sets it. */
.hero.has-film .hero__seq img { display: none; }

/* THE SCRIM, REOPENED FOR THE FILM — measured, not adjusted by eye.
   The frame-sequence scrim holds the left of the hero at 94–97% of the page
   colour. Over six stills that was right. Over the film it was erasure: the
   first screenshot of the finished hero was a white page with a ghost in it,
   and 340 credits of camera move were invisible.

   So it was measured instead. The darkest pixel anywhere behind the type,
   across all 15 seconds, is effectively black (the net mesh in the closing
   shot). Compositing --bg over black in linear light, the weakest type in the
   hero — the lede at --ink-2 — reaches 4.5:1 at a 61% scrim. Below 61% the
   lede fails WCAG AA; above it there is nothing to gain but fog.

   68% is used, a deliberate margin over the 61% floor for JPEG noise and the
   browser's own compositing, which still measures 5.0:1 on the lede and
   11.2:1 on the headline. Where there is no type — the right of a desktop
   hero — it opens to 38% and the film is simply seen.

   Scoped to .has-film on purpose: the sequence scrim above was measured
   against their photographs, which are a different set of pixels, and is not
   reopened on the strength of this measurement. */
.hero.has-film .hero__seq::after {
  background: linear-gradient(100deg,
    color-mix(in srgb, var(--bg) 72%, transparent) 0%,
    color-mix(in srgb, var(--bg) 68%, transparent) 38%,
    color-mix(in srgb, var(--bg) 50%, transparent) 62%,
    color-mix(in srgb, var(--bg) 38%, transparent) 100%);
}
@media (max-width: 899px) {
  /* On a phone the type crosses the whole frame, so there is no low-scrim side
     to open up: it is flat at 70%, which measures 5.2:1 on the lede. */
  .hero.has-film .hero__seq::after {
    background: linear-gradient(180deg,
      color-mix(in srgb, var(--bg) 68%, transparent) 0%,
      color-mix(in srgb, var(--bg) 70%, transparent) 55%,
      color-mix(in srgb, var(--bg) 74%, transparent) 100%);
  }
}

/* ------------------------------------------------- a looping band backdrop
   The sponsor wall's ground: a slow 5-second loop, held well back so the logos
   on top of it are the subject. Same discipline as the hero — the film is under
   the scrim, never over it. */
.band--film { position: relative; isolation: isolate; overflow: hidden; }
.band__film {
  position: absolute; inset: 0; z-index: -2;
  width: 100%; height: 100%; object-fit: cover;
}
/* One mechanism, not two. The first version also held the film at opacity .5
   AND put a scrim over it, which fogged it twice and read as a mistake. The
   scrim alone sets the contrast; the film is simply the ground beneath it. */
.band--film::after {
  content: ""; position: absolute; inset: 0; z-index: -1;
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--bg) 90%, transparent) 0%,
    color-mix(in srgb, var(--bg) 82%, transparent) 50%,
    color-mix(in srgb, var(--bg) 92%, transparent) 100%);
}

/* --------------------------------------------------------------- the grid
   One more breakpoint than the 8 October version: at 1180px the four-column
   grid was giving cards a 270px photograph, which is wider than any product
   shot SmashIT are likely to send. Five columns keeps them a sane size. */
@media (min-width: 1180px) { .grid { grid-template-columns: repeat(5, 1fr); } }

/* A card is a link now, not a div with a button in it: the whole card goes to
   the product page, and Add to cart stops the click from following it. */
.card__link { text-decoration: none; color: inherit; display: flex; flex-direction: column; flex: 1; }
.card__link:hover .card__name { color: var(--brand-deep); }
.card__name { transition: color .2s; }

/* The join card that is NOT a link yet.
   SmashIT have not sent their WhatsApp invite, so that card must not be an <a>
   going nowhere — but as a bare <div> it rendered with no card at all beside a
   styled Instagram one, which reads as a broken page rather than as something
   waiting. It gets the same shape, dimmed, with no hover: visibly the same kind
   of thing, visibly not yet active. */
.join__wait {
  display: flex; align-items: center; gap: 14px;
  padding: 20px; border-radius: var(--rad);
  background: var(--surface); border: 1px dashed var(--line);
  /* NO `opacity: .72` here any more. It was doing an honest job — this is the
     "WhatsApp link is still coming" card and it should read as not-yet-active
     — but opacity fades the TEXT as well as the box, and it took --mute from
     a passing 4.74:1 down to 3.02:1. The dashed border already says waiting;
     it does not need to cost the words their legibility. */
}
.join__wait b { display: block; font-size: 16px; font-weight: 800; }
.join__wait small { color: var(--mute); font-size: 13px; }
/* The icon can still be faded — it carries no text and is aria-hidden. */
.join__wait .ic { opacity: .6; width: 42px; height: 42px; flex: 0 0 42px; display: grid; place-items: center; border-radius: 12px; background: var(--brand-10); }

/* ───────────────────────────────────────────────────────── their real logo
   Their transparent PNG, trimmed of its canvas margin and resized. The navy
   tile that used to sit behind it is gone: it existed only because all we had
   was a dark JPEG of the mark on its own black square, which read as a box
   somebody forgot to cut out. */
.bar__home { display: inline-flex; align-items: center; }
.bar__logo { height: 38px; width: auto; display: block; }
@media (min-width: 900px) { .bar__logo { height: 44px; } }

.footer__logo { height: 52px; width: auto; margin-bottom: 14px; display: block; }

/* ───────────────────────────────────────────── waiting-on-content slots
   A slot is a thing the client owes us. It is drawn as an obvious dashed well
   rather than a grey rectangle, because a grey rectangle looks like a broken
   image and gets reported as a bug; this says what it is waiting for. */
.slot {
  border: 2px dashed var(--line-strong);
  border-radius: var(--rad);
  background: var(--surface-2);
  display: grid; place-items: center; text-align: center;
  padding: 26px 20px; gap: 6px;
  color: var(--mute);
}
.slot b {
  display: block;
  font: 900 12px/1.3 "Barlow Condensed", 'Barlow Condensed Fallback', sans-serif;
  text-transform: uppercase; letter-spacing: .14em;
  color: var(--brand-deep);
}
.slot span { font-size: 13px; line-height: 1.5; max-width: 32ch; }

/* ──────────────────────────────────────────── the product page, close up
   George's first reference (@design_visionxl, 11 Oct): a modern product detail
   page, smooth, clean, built around the user rather than the catalogue. These
   are the three things that actually make one work on a phone. */

/* 1 · buy as one row: quantity next to the button, not stacked above it */
.buy { display: flex; gap: 10px; align-items: stretch; margin-top: 4px; }
.btn--grow { flex: 1; }
.qty--lg { border-radius: 999px; border: 1px solid var(--line-strong); background: var(--surface); }
.qty--lg button { width: 46px; height: auto; font-size: 20px; }
.qty--lg span { min-width: 32px; font-size: 15px; }

/* 2 · the three questions somebody asks before paying a stranger */
.assure { list-style: none; margin: 18px 0 0; padding: 0; display: grid; gap: 9px; }
.assure li {
  position: relative; padding-left: 26px;
  font-size: 14px; color: var(--ink-2); line-height: 1.5;
}
.assure li::before {
  content: ""; position: absolute; left: 0; top: 3px;
  width: 17px; height: 17px; border-radius: 50%;
  background: var(--brand-20);
  box-shadow: inset 0 0 0 2px var(--brand);
}

/* 3 · the sticky buy bar.
   On a phone the Add button scrolls away under the spec list, and a page you
   have to scroll back up to buy from is a page people leave. On a desktop the
   photograph is already sticky and the button never leaves, so this is phone
   only. Shown by app.js once the real button is out of view. */
.buybar {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 70;
  background: color-mix(in srgb, var(--surface) 94%, transparent);
  /* R9 §4: no backdrop blur — it smeared the page scrolling underneath. */
  border-top: 1px solid var(--line);
  padding: 10px var(--gut) calc(10px + env(safe-area-inset-bottom));
  box-shadow: 0 -8px 28px rgba(10, 28, 43, .10);
  animation: buybarIn .3s var(--ease) both;
}
.buybar[hidden] { display: none; }
.buybar__in { display: flex; align-items: center; gap: 14px; max-width: var(--max); margin-inline: auto; }
.buybar__in > div { flex: 1; min-width: 0; }
.buybar__n {
  margin: 0; font-size: 13px; font-weight: 800; color: var(--ink);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.buybar__p { margin: 0; font: 900 18px/1.1 "Barlow Condensed", 'Barlow Condensed Fallback', sans-serif; color: var(--ink); }
.buybar .btn { min-height: 46px; }
@keyframes buybarIn { from { transform: translateY(100%); } }
@media (prefers-reduced-motion: reduce) { .buybar { animation: none; } }
@media (min-width: 900px) { .buybar { display: none !important; } }

/* The recommended retail price, struck through beside what they pay.
   Their own READ ME asks for both, "so customers can see the saving". It is set
   only when it is genuinely higher — the importer drops it otherwise, because a
   saving that is not a saving is the manufactured discount the rules forbid. */
.card__was, .product__was {
  color: var(--mute); font-weight: 700;
  text-decoration: line-through; text-decoration-thickness: 2px;
}
.card__price { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
.card__was { font: 700 13px/1 Nunito, 'Nunito Fallback', sans-serif; }
.product__was { font: 700 20px/1 Nunito, 'Nunito Fallback', sans-serif; margin-left: 10px; }
.card__by { text-transform: none; }

/* "Delivered in 2-5 business days", and the free-over-R2000 line. Said where a
   shopper meets it, not only at checkout. */
.ship {
  display: flex; flex-wrap: wrap; gap: 8px 16px; align-items: center;
  margin: 16px 0 0; padding: 12px 14px;
  background: var(--brand-05); border: 1px solid var(--line); border-radius: 12px;
  font-size: 13.5px; color: var(--ink-2);
}
.ship b { color: var(--ink); }

/* The product thumbnail strip. Their catalogue carries up to ten photographs a
   product; four is a gallery and ten is a scroll, so the build takes four. */
.thumbs { display: flex; gap: 8px; padding: 10px; overflow-x: auto; scrollbar-width: none; }
.thumbs::-webkit-scrollbar { display: none; }
.thumb {
  flex: 0 0 auto; width: 62px; height: 62px; padding: 0;
  border: 2px solid var(--line); border-radius: 10px; overflow: hidden;
  background: var(--surface); cursor: pointer;
  transition: border-color .2s var(--ease);
}
.thumb.is-on { border-color: var(--brand); }
.thumb img { width: 100%; height: 100%; object-fit: contain; }

/* The figure holds the big image and the strip, so the strip sits inside the
   frame rather than under it. */
.product__fig { display: block; aspect-ratio: auto; }
.product__fig > img { width: 100%; height: auto; aspect-ratio: 1; object-fit: contain; background: var(--surface); }

/* ───────────────────────────────────────── the scroll sequence
   Six real frames from SmashIT's own footage, cross-faded by scroll position
   and each scaled slowly while it is on screen. `--s` is the same 0→1 the
   racket turns on, so the whole hero is one gesture.

   The maths, so the next person does not have to work it out: with six frames,
   frame N owns the window [(N-1)/5, N/5] of the scroll. Each image's opacity is
   driven by a CSS variable the handler writes, rather than by a class, so the
   fade is continuous rather than stepped — a stepped one reads as a slideshow,
   which is the thing this is not. */
.hero__seq { position: absolute; inset: 0; z-index: -3; overflow: hidden; }
.hero__seq img {
  position: absolute; inset: 0;
  width: 100%; height: 100%; object-fit: cover;
  opacity: var(--o, 0);
  transform: scale(var(--z, 1.06));
  will-change: opacity, transform;
}
/* The first frame is visible before any script runs, so the hero is never an
   empty box on a slow connection or with JavaScript off. */
.hero__seq img:first-child { opacity: 1; }
.hero.has-seq .hero__seq img:first-child { opacity: var(--o, 0); }

/* On a light theme the photograph has to be held back hard, or dark type on it
   is unreadable. This is a scrim over the sequence, under the wash. */
.hero__seq::after {
  content: ""; position: absolute; inset: 0;
  /* Dark type on a photograph needs a guaranteed ground, not a tint. The left
     two-fifths — where the headline, the lede and both buttons sit — is held at
     94% of the page colour, which measures the same contrast as type on the
     plain background. It opens up to 45% on the right so the photograph is
     genuinely visible rather than fogged. */
  background: linear-gradient(100deg,
    color-mix(in srgb, var(--bg) 96%, transparent) 0%,
    color-mix(in srgb, var(--bg) 94%, transparent) 34%,
    color-mix(in srgb, var(--bg) 70%, transparent) 58%,
    color-mix(in srgb, var(--bg) 45%, transparent) 100%);
}
@media (max-width: 899px) {
  /* On a phone the type sits over the middle of the frame, so the scrim is
     flat and stronger rather than angled. */
  .hero__seq::after {
    /* On a phone the type crosses the whole frame, so the scrim is flat and
       strong rather than angled — the photograph is atmosphere there, not
       subject. */
    background: linear-gradient(180deg,
      color-mix(in srgb, var(--bg) 88%, transparent) 0%,
      color-mix(in srgb, var(--bg) 92%, transparent) 55%,
      color-mix(in srgb, var(--bg) 97%, transparent) 100%);
  }
}

@media (prefers-reduced-motion: reduce) {
  .hero__seq img { transform: none; }
}

/* ------------------------------------------------------------ sold out
   Set from data/stock.js. The product keeps its card, its page and its price;
   only the way to buy it goes. Amber rather than red: it is not an error, it
   is a thing coming back. */
.tag--out {
  background: #FDF1E7;
  color: #8A3E1E;
  border: 1px solid #F0CDB4;
}
.card__out {
  margin: 0; padding: 11px 0; text-align: center;
  font: 700 13px/1.2 'Barlow Condensed', 'Barlow Condensed Fallback', system-ui, sans-serif;
  letter-spacing: .08em; text-transform: uppercase;
  color: #8A3E1E; background: #FDF1E7;
  border: 1px solid #F0CDB4; border-radius: 999px;
}
.soldout {
  margin: 18px 0 6px; padding: 16px 18px;
  border: 1px solid #F0CDB4; border-left: 4px solid #B4532B; border-radius: 12px;
  background: #FDF1E7; color: #6B3317;
  font: 15px/1.55 Nunito, 'Nunito Fallback', system-ui, sans-serif;
}
.soldout strong { color: #8A3E1E; }

/* ------------------------------------------- a link inside a paragraph
   WCAG 1.4.1: colour alone may not be the only way of telling something
   apart. Lighthouse measured the links in our body copy at 1.09:1 against
   the text around them — --brand-deep (#0B6E8C) beside --mute (#5A6E7E) —
   which is to say they were, to most eyes, simply not links.

   Underlining ONLY links sitting inside running text. A nav item, a button
   or a card that is entirely a link is told apart by its position and its
   shape, and underlining those makes a page look like 1997. */
.lede a,
.policy a,
.muted a,
.footer__links a,
p:not([class]) a,
.ev__meta a,
.crumbs a,
.small a {
  color: var(--brand-deep);
  text-decoration: underline;
  text-underline-offset: 2px;
  text-decoration-thickness: 1px;
}
.lede a:hover,
.policy a:hover,
.muted a:hover,
.footer__links a:hover,
p:not([class]) a:hover,
.crumbs a:hover,
.small a:hover { text-decoration-thickness: 2px; }

/* The two "join" cards are filled from /api/catalogue, because whether the
   WhatsApp link exists is a runtime fact. Empty they had no height, so the
   page below them moved when they arrived — 0.103 of layout shift, just over
   the 0.1 pass mark. Two cards at a known height, held open until they land. */
.join:empty { min-height: 168px; }

/* ══════════════════════════════════════════════════ V2 · HOME ONLY (14 Oct)
   Everything below is scoped to the home page — either by a class that exists
   only there (.intro, .story, .orbitsec) or by [data-page="home"]. Thomas is
   going page by page, so nothing here may change another page by accident.

   Colours are SmashIT's own, already in :root — --brand #14DCF5 and --navy
   #123A52 from their logo — plus one new token for the ball, which is the
   yellow of a real padel ball and is used for nothing else. */
:root {
  --ball: #D7E84B;
  --ball-deep: #AFC032;
  --ink-deep: #04101B;   /* the intro ground: darker than --bg, so the ball glows */
}

/* ── the intro ─────────────────────────────────────────────────────────── */
.intro {
  position: fixed; inset: 0; z-index: 90;
  background: radial-gradient(120% 90% at 50% 46%, #0B2236 0%, var(--ink-deep) 62%, #020A12 100%);
  display: grid; place-items: center;
  /* The lift at the end. Transform+opacity only: a fixed overlay that animates
     anything else would repaint the whole page on the way out. */
  transition: opacity .52s cubic-bezier(.4,0,.2,1), transform .52s cubic-bezier(.4,0,.2,1);
}
.intro[hidden] { display: none; }
.intro.is-gone { opacity: 0; transform: scale(1.04); pointer-events: none; }

.intro__stage {
  position: relative;
  width: min(78vw, 460px); height: min(78vw, 460px);
  display: grid; place-items: center;
}
.intro__ball {
  position: absolute; left: 50%; top: 50%;
  width: 54px; height: 54px; margin: -27px 0 0 -27px;
  /* the ball's own light on the court, not a CSS glow for its own sake */
  filter: drop-shadow(0 6px 14px rgba(215,232,75,.26));
  will-change: transform;
}
.intro__shadow {
  position: absolute; left: 50%; top: 50%;
  width: 60px; height: 13px; margin-left: -30px;
  border-radius: 50%;
  background: radial-gradient(50% 50%, rgba(0,0,0,.52), rgba(0,0,0,0) 70%);
  will-change: transform, opacity;
}
/* 240px, which is the INTRINSIC width of the only clean logo file that exists.
   Displaying it larger upscales a 240px raster, and on a 3x phone that is
   already a third of the pixels the screen can show. Capped at native so it is
   never worse than the asset allows.
   THE REAL FIX IS A VECTOR LOGO FROM SMASHIT — every raster in brand/ is either
   this 240px cutout or a 1536px mockup baked onto a grey background with a glow
   that cannot be keyed out (the halo and the mark's own navy overlap in both
   chroma and luminance, measured 14 Oct). An SVG would let this run full-bleed. */
.intro__logo {
  position: absolute; width: min(62vw, 240px); height: auto;
  opacity: 0; will-change: opacity, transform;
  filter: drop-shadow(0 10px 30px rgba(0,0,0,.5));
}
.intro__skip {
  position: absolute; right: 18px; bottom: calc(18px + env(safe-area-inset-bottom));
  font: 600 13px/1 Inter, system-ui, sans-serif; letter-spacing: .08em; text-transform: uppercase;
  color: rgba(255,255,255,.55); background: none; border: 0; padding: 12px 14px; cursor: pointer;
  min-height: 44px;   /* a real tap target, not a decoration */
}
.intro__skip:hover, .intro__skip:focus-visible { color: #fff; }
/* While the intro is up the page must not scroll underneath it. */
body.intro-locked { overflow: hidden; }

/* ── the story ─────────────────────────────────────────────────────────── */
section.story { position: relative; padding: var(--sec) 0; overflow: hidden; }
/* The path runs down the RIGHT of the column on a phone, not through the middle
   of it. Centred, it crossed the headline and read as a stray mark rather than
   a flight — the line has to be clearly behind and beside the type, never
   through a word. On desktop there is room for it to sweep the full width. */
.story__path {
  position: absolute; right: -8%; top: 0; width: 56%; height: 100%;
  pointer-events: none; opacity: .75;
}
@media (min-width: 760px) {
  .story__path { right: auto; left: 50%; width: min(92vw, 1100px); transform: translateX(-50%); opacity: .55; }
}
.story__head { max-width: 22ch; margin-bottom: clamp(38px, 7vw, 68px); }
/* --brand-deep, NOT --brand. The cyan in their logo measures 1.57:1 on this
   page's background — it is a fill colour, and scripts/check-pages.mjs fails
   the build if it is ever used as type on a light ground. --brand-deep is the
   same hue darkened until it reads, at 5.19:1. */
.story__head h2 em { font-style: normal; color: var(--brand-deep); }

.story__moments { list-style: none; margin: 0; padding: 0; display: grid; gap: clamp(26px, 6vw, 56px); }
.moment {
  position: relative; background: var(--surface); border: 1px solid var(--line);
  border-radius: 20px; overflow: hidden; box-shadow: var(--shadow);
}
.moment__media { position: relative; aspect-ratio: 3 / 2; background: var(--surface-2); }
.moment__media img { width: 100%; height: 100%; object-fit: cover; display: block; }
.moment__nomedia { width: 100%; height: 100%; background: linear-gradient(135deg, var(--surface-2), var(--line)); }
.moment__state {
  position: absolute; left: 14px; top: 14px;
  font: 800 11px/1 Inter, system-ui, sans-serif; letter-spacing: .14em; text-transform: uppercase;
  padding: 8px 12px; border-radius: 999px;
}
/* "Played" is deliberately quiet and "Entries open" is deliberately loud. The
   page must never make a finished tournament look like one you can enter. */
.moment__state.is-played { background: rgba(10,28,43,.72); color: #CBD9E4; }
.moment__state.is-open   { background: var(--brand); color: var(--ink); }

.moment__body { padding: clamp(20px, 5vw, 30px); }
.moment__n {
  font: 900 13px/1 'Barlow Condensed', 'Barlow Condensed Fallback', sans-serif; letter-spacing: .2em;
  color: var(--brand-deep); margin: 0 0 8px;
}
.moment__body h3 { font-size: clamp(22px, 5.6vw, 30px); margin: 0 0 16px; }
.moment__facts { display: grid; gap: 10px; margin: 0 0 16px; }
.moment__facts > div { display: grid; grid-template-columns: 74px 1fr; gap: 12px; align-items: baseline; }
.moment__facts dt {
  font: 800 11px/1.4 Inter, system-ui, sans-serif; letter-spacing: .12em;
  text-transform: uppercase; color: var(--mute); margin: 0;
}
.moment__facts dd { margin: 0; font-size: 15px; color: var(--ink-2); font-weight: 600; }
.moment__p { font-size: 15px; color: var(--ink-2); margin: 0 0 18px; }
.moment__link { font-weight: 800; color: var(--brand-deep); text-decoration: none; font-size: 15px; }
.moment__link span { display: inline-block; margin-left: 6px; transition: transform .22s var(--ease); }
.moment__link:hover span, .moment__link:focus-visible span { transform: translateX(4px); }

.story__next { margin-top: clamp(34px, 7vw, 60px); display: grid; gap: 16px; justify-items: start; }
.story__next__p { max-width: 46ch; color: var(--mute); margin: 0; font-size: 15px; }

/* ── the sponsors, circling ────────────────────────────────────────────── */
.orbitsec { overflow: hidden; }

/* The sponsors circle on their OWN dark stage, inside an otherwise light
   section. Two reasons, and the second is the real one:
     · Depth needs contrast. A card that recedes has to get darker and dimmer
       than its background, and there is nowhere to recede to on near-white.
       The first version put white cards on the washed film and they vanished.
     · It makes the bottom of the page land. The eye arrives at one dark block
       with five names turning in it, which is the note the page should end on.
   The navy is their own --navy, deepened; nothing new is introduced. */
.orbit {
  position: relative;
  margin: clamp(28px, 6vw, 48px) 0 clamp(26px, 5vw, 40px);
  background: radial-gradient(120% 120% at 50% 40%, #0E2E44 0%, #08192A 70%, #050F1B 100%);
  border-radius: 24px;
  overflow: hidden;
}
.orbit::after {
  /* the court's own light, bleeding in at the edges rather than a CSS glow */
  content: ''; position: absolute; inset: 0; pointer-events: none;
  background: radial-gradient(70% 60% at 50% 50%, rgba(20,220,245,.10), transparent 70%);
}
/* Before home.js touches it this is a plain readable row — see the note on the
   section in src/pages/01-home.html for why that matters. */
.orbit .wall {
  display: flex; flex-wrap: wrap; gap: 12px; justify-content: center;
  padding: 0 20px;
}
.orbit .wall { padding: 34px 20px; }
/* wallBuild() nests a <span> inside each card, and the V1 rule `.wall a, .wall
   span` styles THAT as a card too — a white block with its own border and a
   104px min-height, inside the card. On the light V1 wall it was invisible;
   on the dark stage it painted every card solid white and swallowed the
   depth. Scoped off here rather than changed at source, because the V1 wall
   on /sponsors still wants it. */
.orbit .wall > * > * {
  background: none; border: 0; min-height: 0; padding: 0;
  color: inherit; display: block;
}
.orbit .wall > * {
  display: grid; place-items: center; min-width: 136px; min-height: 72px;
  padding: 16px 20px; border-radius: 14px;
  background: rgba(255,255,255,.07); border: 1px solid rgba(255,255,255,.16);
  color: #EAF3F9; text-decoration: none; text-align: center;
  letter-spacing: .06em; text-transform: uppercase;
}
/* Once arranged, the row becomes a ring: cards are absolutely placed and the
   depth comes from scale + blur + opacity, computed per frame in home.js. */
.orbit.is-on { height: 230px; }
.orbit.is-on .wall { display: block; padding: 0; height: 100%; position: relative; }
.orbit.is-on .wall > * {
  position: absolute; left: 50%; top: 50%; margin: 0;
  width: 150px; min-width: 0; height: 78px; padding: 0 14px;
  will-change: transform, opacity, filter;
  transition: background .2s var(--ease), border-color .2s var(--ease);
}
.orbit.is-on .wall > *:hover { background: rgba(20,220,245,.14); border-color: rgba(20,220,245,.5); }

@media (min-width: 760px) {
  .story__moments { grid-template-columns: 1fr 1fr; gap: 34px; }
  .orbit.is-on { height: 280px; }
  .orbit.is-on .wall > * { width: 180px; height: 92px; }
}

/* ── the one rule that outranks all of the above ───────────────────────── */
@media (prefers-reduced-motion: reduce) {
  .intro { display: none !important; }
  .story__path { display: none; }
  .orbit.is-on { height: auto; }
  .orbit.is-on .wall { display: flex; }
  .orbit.is-on .wall > * {
    position: static; transform: none !important; filter: none !important;
    opacity: 1 !important; width: auto; min-width: 136px; height: auto; padding: 16px 20px;
  }
}

/* ═════════════════════════════════════════ V2 ROUND 2 · HOME ONLY (14 Oct)
   Thomas's feedback on round 1. Everything he said to keep is untouched: the
   light ground, their colours, the layout, the yellow line, the tournaments,
   the nav and button interactions. */

/* ── the ball line, through the whole page ─────────────────────────────── */
.pageline {
  /* R13: absolute and as tall as the page (home.js sets the height), so it
     scrolls with the page instead of being moved by script every frame. */
  position: absolute; top: 0; left: 0; width: 100%; height: 0;
  z-index: -1;            /* R6 §4: behind all content, never over text */
  pointer-events: none;
}
/* R7 §6: 3px read as a hairline on a 1440 screen. The width is set here rather
   than on the <path> attribute so it can differ by screen without touching the
   drawing code — CSS stroke-width beats the presentation attribute. */
#pagelinePath { stroke-width: 4; }
@media (min-width: 760px) { #pagelinePath { stroke-width: 5.5; } }
@media (prefers-reduced-motion: reduce) { .pageline { display: none; } }

/* ── the scroll hero ───────────────────────────────────────────────────── */
/* NO background photograph — that was the note. The ground is the page's own
   light and the photographs are objects moving in it. */
.shero { position: relative; height: 260vh; }     /* the room the scene plays in */
.shero__pin {
  position: sticky; top: 0; height: 100svh; min-height: 540px;
  display: grid; place-items: center; overflow: hidden;
  background: linear-gradient(180deg, var(--bg) 0%, color-mix(in srgb, var(--brand) 5%, var(--bg)) 100%);
}
.shero__plates { position: absolute; inset: 0; pointer-events: none; }
.shero__plate {
  position: absolute; margin: 0; overflow: hidden;
  border-radius: 18px; box-shadow: var(--shadow-lift);
  will-change: transform, opacity;
  /* --q is how far this plate has arrived (0 → 1), written by home.js.
     --plate-max is how visible a plate may BE, which is a design decision and
     differs by screen — so it lives here and not in the script. */
  --q: 0; --plate-max: 1;
  opacity: calc(var(--q) * var(--plate-max));
}
.shero__plate img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* Positions are deliberately off the centre column so the type never sits on a
   photograph — the thing that made round 1 read as a washed-out template. */
.shero__plate--a { width: 54vw; max-width: 460px; aspect-ratio: 16/9;  left: -8vw;  top: 11%; }
.shero__plate--b { width: 46vw; max-width: 390px; aspect-ratio: 16/9;  right: -6vw; top: 30%; }
.shero__plate--c { width: 34vw; max-width: 280px; aspect-ratio: 4/5;   right: 6vw;  bottom: 6%; }

.shero__copy { position: relative; z-index: 2; text-align: left; }
.shero__h1 { display: grid; gap: 0; margin: 0 0 18px; }
.shero__w { display: block; will-change: transform, opacity; }
.shero__w--em { color: var(--brand-deep); }
.shero__lede { color: var(--ink-2); font-size: 17px; max-width: 34ch; margin: 0 0 26px; }

@media (max-width: 759px) {
  /* On a phone the plates sit BEHIND the type at low opacity rather than
     beside it — there is no room for both, and a photograph crowding a
     headline is worse than one sitting quietly behind it. */
  .shero { height: 230vh; }
  .shero__plate--a { width: 72vw; left: -14vw; top: 7%; }
  .shero__plate--b { width: 62vw; right: -12vw; top: 33%; }
  .shero__plate--c { width: 50vw; right: -6vw; bottom: 4%; }
  /* On a phone the photographs sit quietly BEHIND the type. There is no room
     for both, and a photograph crowding a headline is what round 1 was told
     to remove. */
  .shero__plate { --plate-max: .26; box-shadow: none; }
}

/* ── sponsors, at the top, big and bright ──────────────────────────────── */
.orbitsec--top .orbit { height: 300px; }
@media (min-width: 760px) { .orbitsec--top .orbit { height: 360px; } }
/* Bigger cards, brighter, and a real tap target. The round-1 cards were
   150x78 and moving, which Thomas could not reliably hit. */
/* BRIGHT was the word Thomas used, and the first attempt was not: dim glass
   on navy reads as "unavailable" rather than "premium". The card now carries
   real light, the name is white rather than off-white, and the whole thing has
   a lift under it so it sits ON the stage instead of in it. */
.orbitsec--top .orbit.is-on .wall > * {
  width: 164px; height: 96px;
  font-size: 14px; color: #fff;
  background: linear-gradient(180deg, rgba(255,255,255,.22), rgba(255,255,255,.12));
  border: 1px solid rgba(255,255,255,.46);
  box-shadow: 0 18px 44px -16px rgba(0,0,0,.75), inset 0 1px 0 rgba(255,255,255,.4);
  text-shadow: 0 1px 10px rgba(0,0,0,.35);
}
@media (min-width: 760px) {
  .orbitsec--top .orbit.is-on .wall > * { width: 226px; height: 122px; font-size: 17px; }
}
.orbitsec--top .orbit.is-on .wall > *:hover,
.orbitsec--top .orbit.is-on .wall > *:focus-visible {
  background: rgba(20,220,245,.22); border-color: var(--brand);
}
.orbit { touch-action: pan-y; }     /* swipe the ring without blocking page scroll */
.orbit.is-drag { cursor: grabbing; }

/* ── a taste of the shop ───────────────────────────────────────────────── */
.mixsec { padding: var(--sec) 0; }
.mix__head { max-width: 24ch; margin-bottom: clamp(24px, 5vw, 40px); }
.mix__head h2 em { font-style: normal; color: var(--brand-deep); }
/* A rail on a phone, a grid on a desktop. A horizontal rail is how a phone
   wants to browse products and it keeps the section short. */
.mix__rail {
  display: grid; grid-auto-flow: column; grid-auto-columns: 62vw;
  gap: 14px; overflow-x: auto; scroll-snap-type: x mandatory;
  padding: 4px 24px 18px; margin-bottom: 26px;
  scrollbar-width: none; -webkit-overflow-scrolling: touch;
}
.mix__rail::-webkit-scrollbar { display: none; }
.mix__card {
  scroll-snap-align: start; text-decoration: none; color: inherit;
  background: var(--surface); border: 1px solid var(--line); border-radius: 16px;
  overflow: hidden; display: grid; grid-template-rows: auto 1fr;
  transition: transform .24s var(--ease), box-shadow .24s var(--ease), border-color .24s var(--ease);
}
.mix__card:hover, .mix__card:focus-visible {
  transform: translateY(-4px); box-shadow: var(--shadow-lift); border-color: var(--brand);
}
.mix__shot { display: block; aspect-ratio: 1; background: var(--surface-2); }
.mix__shot img { width: 100%; height: 100%; object-fit: contain; padding: 10px; }
.mix__meta { display: grid; gap: 4px; padding: 14px 16px 18px; align-content: start; }
.mix__brand { font: 800 10px/1 Inter, system-ui, sans-serif; letter-spacing: .16em; text-transform: uppercase; color: var(--mute); }
.mix__name { font: 900 15px/1.25 'Barlow Condensed', 'Barlow Condensed Fallback', sans-serif; letter-spacing: .02em; text-transform: uppercase; color: var(--ink); }
.mix__price { font-size: 15px; font-weight: 700; color: var(--ink-2); }
.mix__price s { color: var(--mute); font-weight: 500; margin-left: 5px; }
@media (min-width: 760px) {
  .mix__rail {
    grid-auto-flow: row; grid-template-columns: repeat(4, 1fr);
    overflow: visible; padding-inline: 24px; max-width: 1180px; margin-inline: auto;
  }
}

/* ── the rally ─────────────────────────────────────────────────────────── */
.court--rally .rally__players .pl { will-change: transform; }
.court--rally .court__ball { will-change: transform; }
@media (prefers-reduced-motion: reduce) {
  /* the rally never starts; the court sits there with the players in place */
  .court--rally .court__ball { transform: none; }
}

/* ── the intro, bigger ─────────────────────────────────────────────────── */
.intro__burst {
  position: absolute; left: 50%; top: 50%; width: 170%; height: 170%;
  transform: translate(-50%, -50%); opacity: 0; pointer-events: none;
}
.intro__burst .ring { opacity: 0; }
.intro__burst .lines { opacity: 0; }

/* ═══════════════════════════════════════ V2 ROUND 4 · THE HERO AND THE INTRO
   Built to the ChatGPT mock-up: one dark dramatic moment, then the rest of the
   site stays light. */

/* R6 §3: the block sits in the UPPER middle, not the centre. After the intro
   lands, a centred headline leaves the top third of the frame empty and the
   hero reads as unfinished. Moved up on both sizes; the ball stays clear. */
.hero4 {
  position: relative; isolation: isolate;
  min-height: 100svh; display: grid; align-items: start;
  background: #06121F;             /* the ground under the photo, never white */
  overflow: hidden;
}
.hero4__pic { position: absolute; inset: 0; z-index: -2; }
.hero4__pic img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* The wash: strong where the words are, CLEAR by the ball. The ball is where
   the intro's ball comes to rest, so a scrim over it would break the one shot
   the whole opening is built around. */
.hero4__wash {
  position: absolute; inset: 0; z-index: -1;
  background:
    linear-gradient(90deg, rgba(6,18,31,.95) 0%, rgba(6,18,31,.88) 26%, rgba(6,18,31,.55) 48%, rgba(6,18,31,.10) 64%, rgba(6,18,31,0) 76%),
    linear-gradient(180deg, rgba(6,18,31,.55) 0%, rgba(6,18,31,0) 34%);
}
/* R7 §1: the headline starts about 20% down the viewport, not 40%.
   Measured rather than reasoned, because three paddings stack here and only a
   browser knows the total: the sticky bar ends at 65px, .hero4 adds its own
   99px, and this used to add 180 on top — putting "PADEL" at 344px of a 900px
   screen, 38% down, which is the empty patch in Thomas's photograph. With this
   the h1 lands at 184px = 20.4% on a 1440x900 laptop. */
.hero4__copy { padding-block: clamp(6px, 2.2vh, 30px) 0; }
.hero4__h1 {
  color: #fff; margin: 0 0 20px;
  font-size: clamp(46px, 7.6vw, 104px); line-height: .92; letter-spacing: -.02em;
}
/* MORE in their cyan, which the mock-up specifies — and which is only legible
   because this hero is dark. Measured against the hero ground #06121F: 11.30:1,
   against the 1.57:1 the same colour scores on the light page. The declaration
   is what tells scripts/check-pages.mjs this one is on a dark ancestor rather
   than a mistake. */
.hero4__h1 em { font-style: normal; color: var(--brand); --on-dark: 11.30; }
.hero4__lede { color: #C9DAE6; font-size: clamp(16px, 1.3vw, 19px); max-width: 38ch; margin: 0 0 30px; }

@media (max-width: 759px) {
  /* On the phone crop the ball is lower-CENTRE, so the wash comes up from the
     bottom instead of in from the left — same rule, different edge. */
  /* THE COPY SITS ABOVE THE BALL, not over it. The first version put the
     buttons across the lower third — which is exactly where the photographed
     ball is (74.7% down) and exactly where the intro's ball comes to rest. The
     whole opening is built so those two land on each other; a button over that
     spot throws the payoff away. So the words take the top two-thirds and the
     bottom third is left to the court. */
  .hero4 { align-items: start; }
  .hero4__wash {
    background:
      linear-gradient(180deg, rgba(6,18,31,.92) 0%, rgba(6,18,31,.78) 34%, rgba(6,18,31,.35) 56%, rgba(6,18,31,0) 72%);
  }
  /* Phone: right under the nav, as asked. 11vh put it at 251px of 844 (30%
     down) on top of the same stacked paddings. */
  .hero4__copy { padding-block: 0 0; max-height: 74svh; }
  .hero4__h1 { font-size: clamp(42px, 13vw, 60px); }
}

/* ── the intro, one continuous shot ────────────────────────────────────── */
/* The navy SKY is a separate layer from the overlay, because it is the thing
   that fades to reveal the hero photograph while the ball is still falling.
   The overlay itself never lifts — that is what makes it one shot. */
.intro { position: fixed; inset: 0; z-index: 90; background: none; }
.intro[hidden] { display: none; }
.intro__sky {
  position: absolute; inset: 0;
  background: radial-gradient(120% 90% at 50% 42%, #0B2236 0%, #04101B 62%, #020A12 100%);
  will-change: opacity;
}
.intro__logowrap {
  position: absolute; left: 50%; top: 38%;
  transform: translate(-50%, -50%);
  display: grid; place-items: center;
}
.intro__logo { width: min(62vw, 240px); height: auto; opacity: 0; will-change: opacity, transform; }
.intro__burst { position: absolute; width: 420px; height: 420px; opacity: 0; pointer-events: none; }
.intro__burst .ring { opacity: 0; }
.intro__ball {
  position: absolute; left: 0; top: 0; width: 56px; height: 56px;
  margin: -28px 0 0 -28px;        /* so left/top are its CENTRE */
  filter: drop-shadow(0 8px 18px rgba(0,0,0,.45));
  will-change: transform;
}
.intro__shadow {
  position: absolute; left: 0; top: 0; width: 64px; height: 14px; margin-left: -32px;
  border-radius: 50%; opacity: 0;
  background: radial-gradient(50% 50%, rgba(0,0,0,.55), rgba(0,0,0,0) 70%);
  will-change: transform, opacity;
}
.intro__skip {
  position: absolute; right: 18px; bottom: calc(18px + env(safe-area-inset-bottom));
  font: 600 13px/1 Inter, system-ui, sans-serif; letter-spacing: .08em; text-transform: uppercase;
  color: rgba(255,255,255,.6); background: none; border: 0; padding: 12px 14px; cursor: pointer;
  min-height: 44px; z-index: 2;
}
.intro__skip:hover, .intro__skip:focus-visible { color: #fff; }
body.intro-locked { overflow: hidden; }
/* the hero copy waits for the ball to land */
.hero4__copy.is-waiting { opacity: 0; }
.hero4__copy { transition: opacity .7s cubic-bezier(.2,.7,.3,1); }

@media (prefers-reduced-motion: reduce) {
  .intro { display: none !important; }
  .hero4__copy.is-waiting { opacity: 1; }
}

/* ════════════════════════════════════════ V2 ROUND 5 · THOMAS'S EXACT SPEC */

/* ── §1 the intro film ─────────────────────────────────────────────────── */
/* R7 §8B: `inset: 0` alone is the LARGE viewport on iOS, which leaves the
   film short of the bottom the moment Safari's bar slides away. min-height is
   the small viewport and height is the dynamic one, so it is never shorter
   than the screen in either state. */
.introv {
  position: fixed; inset: 0; z-index: 95; background: #0B2130;
  min-height: 100svh; height: 100dvh;
}
.introv[hidden] { display: none; }
.introv.is-gone { opacity: 0; pointer-events: none; transition: opacity .4s linear; }
/* `cover` so the film fills the screen exactly as the hero photo does — the
   last frame of one has to sit on the first frame of the other. */
.introv__v { width: 100%; height: 100%; object-fit: cover; display: block; }
/* R7 §8B: the phone cut is a real 1080x1920 frame now, so it covers. The film
   inside it is a 1350-wide crop of the master scaled down to 1080 — never up —
   with the logo and the ball centred and the bars above and below taking the
   colour sampled off the film's own edges. A phone at 390x844 crops about 9%
   off each side of a 9:16 video under `cover`, and 9% of a crop that wide is
   empty court on both sides, so nothing of the logo is lost. */
.introv__v { object-position: center; }
.introv__skip {
  position: absolute; right: 18px; bottom: calc(18px + env(safe-area-inset-bottom));
  font: 600 13px/1 Inter, system-ui, sans-serif; letter-spacing: .08em; text-transform: uppercase;
  color: rgba(255,255,255,.65); background: rgba(0,0,0,.28); border: 0;
  padding: 13px 18px; border-radius: 999px; cursor: pointer; min-height: 44px;
}
.introv__skip:hover, .introv__skip:focus-visible { color: #fff; background: rgba(0,0,0,.45); }
/* R13: the logo entrance when the film cannot autoplay (home.js fallback). */
.introv__logo { display: none; }
.introv--logo .introv__logo {
  display: block; position: absolute; left: 50%; top: 50%; width: min(70vw, 420px); height: auto;
  transform: translate(-50%, -50%); animation: introLogo 1.2s cubic-bezier(.2,.9,.3,1.2) both;
}
@keyframes introLogo {
  0%   { opacity: 0; transform: translate(-50%, -50%) scale(.6) rotate(-6deg); }
  60%  { opacity: 1; transform: translate(-50%, -50%) scale(1.06) rotate(1deg); }
  100% { opacity: 1; transform: translate(-50%, -50%) scale(1) rotate(0); }
}
@media (prefers-reduced-motion: reduce) { .introv { display: none !important; } }

/* ── §3 the line only after you start scrolling ────────────────────────── */
.pageline { opacity: 0; transition: opacity .5s linear; }
.pageline.is-on { opacity: 1; }

/* ── R7 §2 sponsors: the marks on a turning 3D ring ────────────────────── */
.sponsors5 { padding: var(--sec) 0; }
.sponsors5 h2 em { font-style: normal; color: var(--brand-deep); }
/* the mock-up's split head: name left, the line and the button right */
.sponsors5__head { display: grid; gap: clamp(20px, 4vw, 40px); align-items: end; }
@media (min-width: 900px) { .sponsors5__head { grid-template-columns: 1.15fr .85fr; } }
.sponsors5__aside p { color: var(--mute); max-width: 42ch; margin: 0 0 18px; }

/* THE STAGE. `perspective` here and `preserve-3d` on the track is the whole
   trick — without the perspective the rotateY collapses to a flat squash and
   it reads as a ticker again. overflow:clip because the ring's radius is wider
   than a phone on purpose: the side marks are meant to run off the edge, and
   §8A says the page must never be draggable sideways. */
.ring {
  position: relative; height: 210px; margin-top: clamp(22px, 4vw, 38px);
  /* The perspective is what sets how hard the marks foreshorten. 1100 on a
     phone magnified the front mark to 88% of the screen; 1200 with a narrower
     slot puts it at 57%, which is the ~60% asked for. */
  perspective: 1200px; perspective-origin: 50% 50%;
  overflow: clip; touch-action: pan-y; cursor: grab;
}
@media (min-width: 760px) { .ring { height: 250px; perspective: 1800px; } }
.ring.is-drag { cursor: grabbing; }
.ring__track {
  position: relative; height: 100%; width: 100%;
  transform-style: preserve-3d; will-change: transform;
}
/* No card, no box, no border, no shadow — the mark is the whole thing. */
.ring__track > * {
  position: absolute; left: 50%; top: 50%; margin: 0;
  display: grid; place-items: center;
  width: 230px; height: 110px; padding: 6px;
  text-decoration: none; color: var(--ink);
  font: 900 24px/1.1 'Barlow Condensed', 'Barlow Condensed Fallback', sans-serif; letter-spacing: .04em; text-transform: uppercase;
  /* the back half is simply not drawn, so no mirrored mark ever shows */
  backface-visibility: hidden; -webkit-backface-visibility: hidden;
  transition: filter .25s var(--ease);
}
@media (min-width: 760px) { .ring__track > * { width: 280px; height: 130px; } }
/* Sized by visual weight rather than by a single rule: Verbe and Rooibru are
   long wordmarks, Diadem is a stacked mark with a crown over it, so capping
   only the height would leave the wordmarks twice the optical size. */
.ring__track img { max-width: 100%; max-height: 58px; width: auto; height: auto; object-fit: contain; display: block; }
.ring__track [data-sp="diadem"] img { max-height: 84px; }
.ring__track [data-sp="verbe"] img { max-height: 46px; }
@media (min-width: 760px) {
  .ring__track img { max-height: 64px; }
  .ring__track [data-sp="diadem"] img { max-height: 94px; }
  .ring__track [data-sp="verbe"] img { max-height: 52px; }
}
/* Phone: the front mark is about 60% of the screen, as asked. */
@media (max-width: 759px) {
  /* 46vw, not 62: the front slot is magnified by the perspective, and at 62vw
     it came out wider than the phone. 46vw * 1.27 = about 58% of the screen. */
  .ring__track > * { width: min(46vw, 200px); height: 96px; }
  .ring__track img { max-height: 46px; }
  .ring__track [data-sp="diadem"] img { max-height: 70px; }
  .ring__track [data-sp="verbe"] img { max-height: 38px; }
}
/* hover: that mark comes back to full colour (the ring pause is in home.js) */
.ring__track > *:hover, .ring__track > *:focus-visible { filter: none !important; color: var(--brand-deep); }

@media (prefers-reduced-motion: reduce) {
  /* a still, evenly spaced row — no perspective, no rotation, no animation */
  .ring { perspective: none; height: auto; padding: 10px 0; }
  .ring__track {
    transform: none !important; transform-style: flat; height: auto;
    display: flex; flex-wrap: wrap; gap: 28px 44px; align-items: center; justify-content: center;
  }
  .ring__track > * {
    position: static; transform: none !important; opacity: 1 !important; filter: none !important;
    width: auto; height: auto;
  }
  /* R13: the ring's second copy of each mark exists only so a turning ring has
     no gap. In a still row it printed every sponsor twice. */
  .ring__track > [aria-hidden="true"] { display: none; }
  /* R13: in this row the slot is as big as its logo, and a logo still loading
     was 0px tall, so the row grew twice as they landed (CLS 0.22). A height
     the max-height always clamps makes each logo exactly its max-height (what
     it rendered at anyway — every file is taller than its cap) and its width
     comes from the width/height wallBuild() writes, before the file is in. */
  .ring__track img { height: 1000px; }
}

/* ── §6 their film, and the photo trail ────────────────────────────────── */
.trailsec__film { position: relative; min-height: 62svh; display: grid; align-items: end; isolation: isolate; overflow: hidden; }
.trailsec__v { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; z-index: -2; }
.trailsec__scrim {
  position: absolute; inset: 0; z-index: -1;
  background: linear-gradient(180deg, rgba(10,28,43,.35) 0%, rgba(10,28,43,.1) 40%, rgba(10,28,43,.88) 100%);
}
.trailsec__over { padding-block: clamp(50px, 9vw, 90px); }
/* On the film's foot, where the scrim is 88% navy — measured 9.81:1 there,
   against the 1.57:1 the same cyan scores on the light page. The declaration
   is what distinguishes this from the mistake the check exists to catch. */
.trailsec__over .eyebrow { color: var(--brand); --on-dark: 9.81; }
.trailsec__over h2 { color: #fff; }
.trailsec__over h2 em { font-style: normal; color: var(--brand); --on-dark: 8.9; }

/* the trail's ground is clean and light, as the reference is */
.trail {
  position: relative; overflow: hidden;
  min-height: 78svh; display: grid; place-items: center;
  /* transparent, so the trail shows through from <html> and the ball line
     keeps running behind this section rather than disappearing for a screen */
  background: transparent;
}
.trail__mid { position: relative; z-index: 2; text-align: center; pointer-events: none; padding: 0 24px; }
.trail__mid img { width: min(62vw, 420px); height: auto; opacity: .92; }
/* R12 · the slogan is James's vector now, inlined rather than a 640x433 raster.
   It stays sharp however big the screen is, because there is no pixel to run out
   of any more.

   WHY 37vw/252px AND NOT 62vw/420px. The old PNG was 640x433 with the lockup
   small inside it — 384x118 of actual ink, 60% of the file, the rest empty
   padding. So at the old `min(62vw, 420px)` the slogan ON SCREEN measured 252px.
   James's vector is cropped tight (98% ink), so the same CSS width would have
   drawn it 411px — more than half as big again. That is a design change nobody
   asked for, on a day when the brief is to swap the file and touch nothing else,
   so the width is set to the size it has always appeared. It is a one-line
   change if James wants it bigger. */
.trail__mid .slogan { width: min(37vw, 252px); height: auto; opacity: .92; display: block; margin-inline: auto; }
.trail__hint { margin-top: 14px; color: var(--mute); font-size: 14px; }
.trail__stage { position: absolute; inset: 0; z-index: 1; pointer-events: none; }
/* each photograph the trail drops */
.trail__ph {
  position: absolute; width: 150px; height: 200px; margin: -100px 0 0 -75px;
  border-radius: 10px; overflow: hidden; box-shadow: 0 18px 40px -16px rgba(10,28,43,.45);
  will-change: transform, opacity; opacity: 0;
}
@media (min-width: 760px) { .trail__ph { width: 164px; height: 219px; margin: -110px 0 0 -82px; } }
.trail__ph img { width: 100%; height: 100%; object-fit: cover; display: block; }

@media (prefers-reduced-motion: reduce) {
  /* a static collage instead, exactly as the spec asks */
  .trail__stage { position: static; display: grid; grid-template-columns: repeat(auto-fill, minmax(120px,1fr)); gap: 10px; padding: 24px; }
  .trail__ph { position: static; margin: 0; opacity: 1 !important; transform: none !important; width: auto; height: 150px; }
  .trail__mid { display: none; }
}

/* ── §8 recently played ────────────────────────────────────────────────── */
.played { padding: var(--sec) 0; }

/* ── R7 §5 · THE FOOTER: LIGHT, AND NOT THE COMMUNITY BAND AGAIN ────────
   Round 6 put a tournament photograph behind this with a 93-96% navy scrim
   over it. Thomas could not read the email address or the page links through
   it, which is the only job a footer has. The photograph is gone.

   It is now the lightest surface on the site — paler than the page itself, so
   it separates from "Join the community" directly above it by VALUE rather
   than by another band of colour. Three layers, all in CSS, no images:
     · a pale ice-cyan ground
     · thin white court lines (one inline SVG, a padel court seen flat)
     · a huge outlined SMASHIT behind the columns, barely there
   Text is the site's navy at 13.9:1 on that ground and links are --brand-deep
   at 5.1:1, both measured; scripts/check-pages.mjs holds them. */
.footer--shot {
  position: relative; isolation: isolate; overflow: clip;
  background: #EAF9FC;
  border-top: 1px solid #CFEDF4;
  color: var(--ink);
}
/* R9 §4: both grounds are real vector elements in the markup now — see the
   comment there. No background-image, no text-stroke, no filter anywhere. */
.footer__court {
  position: absolute; inset: -1px; z-index: -2; width: calc(100% + 2px); height: calc(100% + 2px);
  opacity: .5; pointer-events: none;
}
.footer__word {
  position: absolute; left: 0; right: 0; bottom: -2%; z-index: -1;
  width: 100%; height: auto; pointer-events: none; user-select: none;
}
.footer--shot .footer__h { color: var(--ink); }
/* Full-strength navy on the ice ground: 15.6:1, against the 7.2:1 the lighter
   ink scored and the 4.6:1 of the grey before it. */
.footer--shot .footer__who,
.footer--shot .footer__links,
.footer--shot .footer__base { color: var(--ink); --on-dark: 15.6; }
.footer--shot .footer__links a { color: var(--brand-deep); text-decoration-thickness: 1px; }
.footer--shot .footer__links a:hover,
.footer--shot .footer__links a:focus-visible { text-decoration-thickness: 2.5px; color: #084E63; }
.footer--shot .footer__base { border-top-color: #CFEDF4; }

/* ════════════════════════════════════ V2 ROUND 8 §A1 · THE ARENA ══════════ */
/*
 * The one dark block on a light page, so the tournaments stop reading as
 * another white card. Thomas's word for the round 7 version was "boring".
 *
 * THE DARK IS PAINTED BY ::before AT z-index -2, not by the section's own
 * background, and the section must never take `isolation: isolate`. The ball
 * line is a fixed svg at z-index -1 in the root stacking context; a background
 * on the section itself would cover it, and an isolated section would too. At
 * -2 the order is court, then line, then content — so the line crosses the dark
 * instead of vanishing under it, which is the whole point of the section.
 */
.arena { position: relative; padding: var(--sec) 0; color: #EAF3F8; }
.arena::before {
  content: ""; position: absolute; inset: 0; z-index: -2;
  background: linear-gradient(180deg, #06121F 0%, #0A1C2B 48%, #06121F 100%);
}
/* faint neon court, drawn once and stretched: the ground, not a picture.
   R17: drawn as a real padel court — net across the middle, four service
   boxes, the centre line stopping at the service lines (see court.svg). */
.arena__court {
  position: absolute; inset: 0; z-index: -2; pointer-events: none; opacity: .5;
  background:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 400 260' preserveAspectRatio='none'%3E%3Cg fill='none' stroke='%2314DCF5' stroke-width='1' opacity='.42'%3E%3Crect x='24' y='20' width='352' height='220'/%3E%3Cline x1='200' y1='20' x2='200' y2='240'/%3E%3Cline x1='77.7' y1='20' x2='77.7' y2='240'/%3E%3Cline x1='322.3' y1='20' x2='322.3' y2='240'/%3E%3Cline x1='77.7' y1='130' x2='322.3' y2='130'/%3E%3C/g%3E%3C/svg%3E")
    center/100% 100% no-repeat;
  mask-image: radial-gradient(85% 70% at 50% 45%, #000 20%, transparent 78%);
  -webkit-mask-image: radial-gradient(85% 70% at 50% 45%, #000 20%, transparent 78%);
}
.arena__head { display: grid; gap: 18px; align-items: end; margin-bottom: clamp(26px, 4vw, 44px); }
@media (min-width: 900px) { .arena__head { grid-template-columns: 1.1fr .9fr; } }
.arena__h {
  margin: 0; color: #fff; --on-dark: 15.9;
  font-size: clamp(42px, 8.6vw, 96px); line-height: .92; letter-spacing: -.01em;
}
.arena .eyebrow { color: var(--brand); --on-dark: 9.9; }
.arena__headside p { margin: 0 0 14px; color: #BDD3E0; --on-dark: 9.4; max-width: 44ch; }
.arrow--light { color: var(--brand); --on-dark: 9.9; }

.arena__grid { display: grid; gap: clamp(20px, 3vw, 32px); align-items: stretch; }
@media (min-width: 960px) { .arena__grid { grid-template-columns: 1.04fr .96fr; } }
.arena__film { margin: 0; border-radius: var(--rad); overflow: hidden; background: #04101B; min-height: 240px; }
.arena__film video { width: 100%; height: 100%; min-height: 240px; object-fit: cover; display: block; }
/* R13: the film's own 16:9, reserved up front. Without it the box took the
   4:5 POSTER's shape (438px tall on a phone) until the film's metadata
   arrived, then snapped to 16:9 (240px) just as it scrolled into view — the
   page under it jumped up 198px and the ball line redrew mid-scroll. On a
   laptop the grid row's stretch still sets the height, as before. */
.arena__film video { aspect-ratio: 16 / 9; }

/* ── the Next up panel, on the dark ───────────────────────────────────── */
.nextup {
  display: grid; align-content: start; gap: 14px; height: 100%;
  padding: clamp(24px, 4.4vw, 40px);
  background: rgba(255,255,255,.055); border: 1px solid rgba(255,255,255,.16);
  border-radius: var(--rad);
}
.nextup__tag {
  margin: 0; justify-self: start; padding: 7px 13px; border-radius: 999px;
  background: var(--brand); color: var(--ink);
  font: 900 12px/1 "Barlow Condensed", 'Barlow Condensed Fallback', sans-serif; letter-spacing: .14em; text-transform: uppercase;
}
.nextup__tag--soon { background: var(--lime); }
.nextup h3 { margin: 0; color: #fff; --on-dark: 15.9; font-size: clamp(30px, 5.6vw, 54px); line-height: .98; }
.nextup__sub { margin: 0; color: #BDD3E0; --on-dark: 9.4; }
.nextup__chips { display: flex; flex-wrap: wrap; gap: 8px; }
.nextup__chips span {
  padding: 7px 13px; border-radius: 999px; background: rgba(255,255,255,.08);
  border: 1px solid rgba(255,255,255,.18); font-size: 13.5px; color: #DCE9F1; --on-dark: 11.6;
}
.nextup .btn { justify-self: start; margin-top: 4px; }
.nextup p.muted { color: #BDD3E0; --on-dark: 9.4; }

/* the three steps, when nothing is open */
/* A row on a laptop, stacked on a phone. Left to auto-flow they came out
   three-across at every width and "Join the WhatsApp group" broke over three
   lines inside a 150px column. */
.howto { list-style: none; margin: 2px 0 4px; padding: 0; display: grid; gap: 14px; grid-template-columns: 1fr; }
@media (min-width: 1100px) { .howto { grid-template-columns: repeat(3, 1fr); gap: 18px; } }
.howto li { display: grid; grid-template-columns: 34px 1fr; gap: 12px; align-items: center; }
.howto b {
  display: grid; place-items: center; width: 34px; height: 34px; border-radius: 50%;
  background: rgba(20,220,245,.14); border: 1px solid rgba(20,220,245,.42);
  color: var(--brand); --on-dark: 9.9;
  font: 900 15px/1 "Barlow Condensed", 'Barlow Condensed Fallback', sans-serif;
}
.howto span { color: #DCE9F1; --on-dark: 11.6; font-size: 15.5px; }

/* ── the countdown ────────────────────────────────────────────────────── */
.count { display: flex; gap: 10px; flex-wrap: wrap; margin: 4px 0 2px; }
.count > div {
  min-width: 70px; padding: 11px 6px; border-radius: 12px; text-align: center;
  background: rgba(255,255,255,.07); border: 1px solid rgba(255,255,255,.16); color: #fff;
}
.count b {
  display: block; font: 900 27px/1 "Barlow Condensed", 'Barlow Condensed Fallback', sans-serif;
  font-variant-numeric: tabular-nums; color: #fff; --on-dark: 15.9;
}
.count span {
  display: block; margin-top: 3px; font-size: 10.5px; letter-spacing: .12em;
  text-transform: uppercase; color: #AFC9D8; --on-dark: 8.1;
}
.count__now {
  padding: 11px 18px; border-radius: 999px; background: var(--lime); color: var(--ink);
  font: 900 14px/1 "Barlow Condensed", 'Barlow Condensed Fallback', sans-serif; letter-spacing: .12em; text-transform: uppercase;
}

/* ── played, as poster cards on a rail ────────────────────────────────── */
.arena__playedhead {
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
  margin: clamp(44px, 7vw, 76px) 0 clamp(14px, 2vw, 20px);
}
.railbtns { display: flex; gap: 8px; }
.railbtn {
  width: 46px; height: 46px; border-radius: 50%; cursor: pointer;
  background: rgba(255,255,255,.07); border: 1px solid rgba(255,255,255,.2); color: #fff;
  font-size: 17px; line-height: 1; transition: background .2s var(--ease), transform .2s var(--ease);
}
.railbtn:hover, .railbtn:focus-visible { background: rgba(20,220,245,.2); transform: translateY(-2px); }
.railbtn[disabled] { opacity: .3; cursor: default; transform: none; }

.posters { list-style: none; margin: 0; padding: 0; display: grid; gap: 18px; }
/* The rail is horizontal at EVERY width, laptop included — a row of posters
   that runs off the edge reads as "there are more", which a 3-up grid does not.
   Its overflow is its own, so the page itself still cannot be dragged sideways. */
.posters--rail {
  grid-auto-flow: column; grid-auto-columns: 78%;
  overflow-x: auto; overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory; scroll-behavior: smooth;
  margin-inline: calc(var(--gut) * -1); padding-inline: var(--gut);
  scrollbar-width: none;
}
.posters--rail::-webkit-scrollbar { display: none; }
@media (min-width: 760px) { .posters--rail { grid-auto-columns: 32%; gap: 24px; } }
@media (min-width: 1200px) { .posters--rail { grid-auto-columns: 25%; } }
.poster { margin: 0; scroll-snap-align: start; }
.poster a {
  position: relative; display: block; aspect-ratio: 3 / 4; overflow: hidden;
  border-radius: var(--rad); text-decoration: none; color: #fff;
  background: #04101B; box-shadow: 0 24px 60px -28px rgba(0,0,0,.8);
  transition: transform .4s var(--ease), box-shadow .4s var(--ease);
}
.poster a:hover, .poster a:focus-visible { transform: translateY(-6px); box-shadow: 0 34px 70px -26px rgba(0,0,0,.9); }
.poster__img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; transition: transform 1.1s var(--ease), opacity .5s linear; }
.poster a:hover .poster__img, .poster a:focus-visible .poster__img { transform: scale(1.07); }
/* the hover flick: extra real photographs from that same tournament, crossfaded */
.poster__img--alt { opacity: 0; }
.poster__img--alt.is-on { opacity: 1; }
.poster__img--none { background: linear-gradient(140deg, var(--navy), #0A1C2B); }
.poster a::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(180deg, rgba(4,16,27,0) 34%, rgba(4,16,27,.55) 58%, rgba(4,16,27,.94) 100%);
}
.poster__stamp {
  position: absolute; top: 14px; left: 14px; z-index: 2;
  padding: 6px 12px; border-radius: 999px; background: #04101B;
  border: 1px solid rgba(255,255,255,.3); color: #fff; --on-dark: 15.1;
  font: 900 11px/1 "Barlow Condensed", 'Barlow Condensed Fallback', sans-serif; letter-spacing: .16em; text-transform: uppercase;
}
.poster__foot { position: absolute; left: 0; right: 0; bottom: 0; z-index: 2; display: grid; gap: 8px; padding: clamp(16px, 4vw, 24px); }
.poster__chips { display: flex; flex-wrap: wrap; gap: 6px; }
.poster__chips span {
  padding: 4px 10px; border-radius: 999px; background: rgba(255,255,255,.14);
  border: 1px solid rgba(255,255,255,.22); font-size: 11.5px; color: #EAF3F8; --on-dark: 10.4;
}
.poster__title {
  display: block; font: 900 clamp(24px, 4.4vw, 34px)/0.98 "Barlow Condensed", 'Barlow Condensed Fallback', sans-serif;
  text-transform: uppercase; color: #fff; --on-dark: 13.1;
}
.poster__won { font-size: 13px; color: #CFE3EE; --on-dark: 9.6; }
.poster__go { font-size: 13px; color: var(--brand); --on-dark: 7.9; font-weight: 700; }

@media (prefers-reduced-motion: reduce) {
  .poster a, .poster__img, .railbtn { transition: none; }
  .poster a:hover { transform: none; }
  .poster a:hover .poster__img { transform: none; }
  .posters--rail { scroll-behavior: auto; }
}

/* ═══════════════════════════════ V2 ROUND 8 §B1 · A PAGE PER SPONSOR ═══════ */
/*
 * One template, four pages, each wearing that brand's own colour. `--ink` is
 * set inline from data/catalogue.js, where it was MEASURED off the company's
 * own logo file rather than picked — Diadem's #008996 is the mean of the
 * opaque pixels in the mark they publish.
 *
 * The brand colour is used as an accent on SmashIT's own light ground, never as
 * a full repaint: a page that turns Decathlon blue top to bottom stops being
 * SmashIT's site, and these are SmashIT's sponsor pages.
 */
.sphero {
  position: relative; padding: var(--sec) 0 var(--sec-half);
  overflow: clip;
}
/* the brand's colour, as a wash rather than a fill */
.sphero__wash {
  position: absolute; inset: 0; z-index: -2; pointer-events: none;
  background:
    radial-gradient(70% 90% at 18% 0%, color-mix(in srgb, var(--ink) 14%, transparent), transparent 70%),
    radial-gradient(50% 60% at 92% 20%, color-mix(in srgb, var(--ink) 9%, transparent), transparent 72%);
}
.crumbs { display: inline-block; margin-bottom: clamp(22px, 4vw, 38px); font-size: 14px; }
.sphero__mark { margin-bottom: clamp(20px, 3.4vw, 34px); }
/* R9 §8: capped by the SMALLEST mark we hold rather than by taste. Verbe's
   file is 426x79 and was drawn 420 wide — 1.01x, which on a 2x screen is as
   soft as an image gets. 200px is 2x of 426. A bigger set from each sponsor
   lifts this cap; see docs/screenshots/r9/IMAGE-AUDIT.md. */
.sphero__mark img { max-height: 62px; max-width: min(56vw, 200px); width: auto; height: auto; object-fit: contain; }
@media (min-width: 760px) { .sphero__mark img { max-height: 74px; max-width: 240px; } }
.sphero__word {
  font: 900 clamp(40px, 8vw, 86px)/0.95 "Barlow Condensed", 'Barlow Condensed Fallback', sans-serif;
  text-transform: uppercase; color: var(--ink);
}
.sphero__h {
  margin: 0 0 14px; font-size: clamp(38px, 7vw, 78px); line-height: .96;
  /* the brand's colour on the brand's name, and nowhere else in the heading */
  color: color-mix(in srgb, var(--ink) 72%, var(--ink-2));
}
.sphero .lede { max-width: 54ch; }
.sphero .btn--primary { background: var(--ink); color: #fff; }
.sphero .btn--primary:hover { background: color-mix(in srgb, var(--ink) 84%, #000); }

/* ── what they make, or the brands they carry ─────────────────────────── */
.spcarries { padding: var(--sec) 0; }
.spcarries__list {
  list-style: none; margin: clamp(20px, 3vw, 30px) 0 18px; padding: 0;
  display: flex; flex-wrap: wrap; gap: 10px;
}
.spcarries__list li {
  padding: 12px 20px; border-radius: 999px;
  background: var(--surface); border: 1px solid var(--line);
  font: 900 15px/1 "Barlow Condensed", 'Barlow Condensed Fallback', sans-serif; letter-spacing: .07em; text-transform: uppercase;
  color: var(--ink);
  transition: transform .25s var(--ease), border-color .25s var(--ease), color .25s var(--ease);
}
.spcarries__list li:hover { transform: translateY(-3px); border-color: var(--ink); color: var(--ink); }
@media (min-width: 760px) { .spcarries__list li { font-size: 17px; padding: 14px 24px; } }

/* ── how they back SmashIT ───────────────────────────────────────────── */
.spback { padding: var(--sec) 0; background: var(--surface-2); }
.spback .lede { max-width: 56ch; }
.spshop { padding: var(--sec) 0; }

@media (prefers-reduced-motion: reduce) {
  .spcarries__list li { transition: none; }
  .spcarries__list li:hover { transform: none; }
}

/* ── R8 §B1 · the sponsors index: one row per brand ───────────────────── */
/* ONE row per sponsor, full width — `grid-template-columns` is set explicitly
   because an earlier rule on this same class laid the old cards out two
   across, and a rule that only sets `display: grid` does not clear it. */
.sponsors { display: grid; grid-template-columns: 1fr; gap: 14px; }
.sprow {
  display: grid; grid-template-columns: 1fr auto; gap: 16px 20px; align-items: center;
  padding: clamp(20px, 3.4vw, 30px); border-radius: var(--rad);
  background: var(--surface); border: 1px solid var(--line); text-decoration: none; color: var(--ink);
  transition: transform .3s var(--ease), border-color .3s var(--ease), box-shadow .3s var(--ease);
}
@media (min-width: 760px) { .sprow { grid-template-columns: 220px 1fr auto; gap: 30px; } }
.sprow:hover, .sprow:focus-visible {
  transform: translateY(-4px); border-color: color-mix(in srgb, var(--ink) 45%, var(--line));
  box-shadow: var(--shadow-lift);
}
.sprow__mark { display: grid; place-items: center; min-height: 64px; }
.sprow__mark img { max-height: 54px; max-width: 150px; width: auto; object-fit: contain; }
@media (min-width: 760px) { .sprow__mark { place-items: center start; } .sprow__mark img { max-height: 62px; max-width: 190px; } }
.sprow__body { display: grid; gap: 8px; }
.sprow__body strong { font: 900 clamp(22px, 3.4vw, 30px)/1 "Barlow Condensed", 'Barlow Condensed Fallback', sans-serif; text-transform: uppercase; }
.sprow__what { color: var(--mute); font-size: 15px; max-width: 54ch; }
.sprow__chips { display: flex; flex-wrap: wrap; gap: 6px; }
.sprow__chips span {
  padding: 4px 10px; border-radius: 999px; background: var(--surface-2);
  border: 1px solid var(--line); font-size: 12px; color: var(--ink-2);
}
.sprow__holds { font-size: 13px; color: var(--brand-deep); font-weight: 700; }
.sprow__go {
  display: grid; place-items: center; width: 46px; height: 46px; border-radius: 50%;
  background: var(--surface-2); border: 1px solid var(--line); color: var(--ink);
  font-size: 18px; transition: background .3s var(--ease), transform .3s var(--ease);
}
.sprow:hover .sprow__go { background: color-mix(in srgb, var(--ink) 12%, var(--surface-2)); transform: translateX(3px); }
@media (prefers-reduced-motion: reduce) {
  .sprow, .sprow__go { transition: none; }
  .sprow:hover { transform: none; }
  .sprow:hover .sprow__go { transform: none; }
}

/* ═══════════════════════════ V2 ROUND 8 §B2 · THE SHOP'S TWO FILTERS ══════ */
.shopbar {
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  margin-bottom: clamp(16px, 2.6vw, 24px);
}
.shopbar__open { min-height: 48px; display: inline-flex; align-items: center; gap: 9px; }
.shopbar__badge {
  display: inline-grid; place-items: center; min-width: 22px; height: 22px; padding: 0 6px;
  border-radius: 999px; background: var(--brand); color: var(--ink);
  font: 900 12px/1 "Barlow Condensed", 'Barlow Condensed Fallback', sans-serif;
}
.shopbar__count { margin: 0; color: var(--mute); font-size: 14.5px; }
.shopbar__clear {
  margin-left: auto; background: none; border: 0; cursor: pointer; padding: 10px 2px; min-height: 44px;
  color: var(--brand-deep); font: 600 14px/1 inherit; text-decoration: underline; text-underline-offset: 3px;
}

/* ── the chips ─────────────────────────────────────────────────────────── */
.filters { display: flex; flex-wrap: wrap; gap: 8px; }
.chip {
  display: inline-flex; align-items: center; gap: 7px; min-height: 44px;
  padding: 10px 16px; border-radius: 999px; cursor: pointer;
  background: var(--surface); border: 1px solid var(--line); color: var(--ink);
  font: 600 14.5px/1 inherit;
  transition: background .2s var(--ease), border-color .2s var(--ease), color .2s var(--ease), transform .2s var(--ease);
}
.chip:hover:not([disabled]) { border-color: var(--brand-deep); transform: translateY(-2px); }
.chip[aria-pressed="true"] { background: var(--navy); border-color: var(--navy); color: #fff; }
.chip[aria-pressed="true"] .chip__n { background: rgba(255,255,255,.2); color: #fff; }
.chip[disabled] { opacity: .4; cursor: default; }
.chip__n {
  display: inline-grid; place-items: center; min-width: 20px; height: 20px; padding: 0 5px;
  border-radius: 999px; background: var(--surface-2); color: var(--mute);
  font: 700 11.5px/1 inherit;
}

/* ── the sheet. A panel on a laptop, a bottom sheet on a phone. ────────── */
.filtersheet { display: grid; gap: clamp(20px, 3vw, 28px); margin-bottom: clamp(20px, 3vw, 30px); }
.filtersheet__head, .filtersheet__foot { display: none; }
.filterset { display: grid; gap: 10px; }
.filterset__h {
  margin: 0; font: 900 12px/1 "Barlow Condensed", 'Barlow Condensed Fallback', sans-serif;
  letter-spacing: .14em; text-transform: uppercase; color: var(--mute);
}
@media (min-width: 760px) { .shopbar__open { display: none; } }

@media (max-width: 759px) {
  .filtersheet {
    position: fixed; z-index: 100; left: 0; right: 0; bottom: 0;
    max-height: 82svh; grid-template-rows: auto 1fr auto; gap: 0; margin: 0;
    background: var(--surface); border-top: 1px solid var(--line);
    border-radius: 22px 22px 0 0; box-shadow: var(--shadow);
    transform: translateY(101%); transition: transform .34s var(--ease);
    padding-bottom: env(safe-area-inset-bottom);
  }
  .filtersheet.open { transform: none; }
  .filtersheet__head {
    display: flex; align-items: center; justify-content: space-between;
    padding: 18px 20px 10px;
  }
  .filtersheet__head h2 { margin: 0; font-size: 24px; }
  .filtersheet__body { overflow-y: auto; padding: 4px 20px 18px; display: grid; gap: 22px; }
  .filtersheet__foot { display: block; padding: 14px 20px 20px; border-top: 1px solid var(--line); }
  .filtersheet__foot .btn { width: 100%; }
  body.noscroll { overflow: hidden; }
}

/* the grid swap: opacity only, so the change is felt without anything moving */
.grid { transition: opacity .14s linear; }
.grid.is-swapping { opacity: 0; }
.linkish {
  background: none; border: 0; padding: 0; cursor: pointer; color: var(--brand-deep);
  font: inherit; text-decoration: underline; text-underline-offset: 3px;
}
@media (prefers-reduced-motion: reduce) {
  .chip, .grid, .filtersheet { transition: none; }
  .chip:hover:not([disabled]) { transform: none; }
}

/* ═════════════════════════ V2 ROUND 8 §B3 · TOURNAMENTS ══════════════════ */
/* their own footage as the page's head */
.tophero { position: relative; isolation: isolate; display: grid; align-items: end; min-height: 56svh; overflow: clip; }
@media (min-width: 760px) { .tophero { min-height: 64svh; } }
.tophero__v { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; z-index: -2; }
.tophero__scrim {
  position: absolute; inset: 0; z-index: -1;
  background: linear-gradient(180deg, rgba(6,18,31,.52) 0%, rgba(6,18,31,.22) 38%, rgba(6,18,31,.9) 100%);
}
.tophero__copy { padding-block: var(--sec); }
.tophero__copy .eyebrow { color: var(--brand); --on-dark: 7.6; }
/* 36px floor, not 44: "Tournaments" is one word of eleven characters and
   cannot be broken, and at 44px it needs 333px of the 280 the narrowest phone
   this site supports has. */
.tophero__copy h1 { color: #fff; --on-dark: 14.8; font-size: clamp(36px, 9vw, 110px); line-height: .92; margin: 0 0 14px; }
.tophero__copy .lede { color: #DCE9F1; --on-dark: 10.9; max-width: 50ch; }

/* upcoming / played */
.tabs { display: flex; gap: 10px; margin-bottom: clamp(22px, 3.4vw, 34px); }
/* R9 §5: big crisp pills, not thin lines. No blur, no translucency — a solid
   fill is what makes the type on a selected tab read as sharp. */
.tab {
  min-height: 56px; padding: 15px 28px; border-radius: 999px; cursor: pointer;
  background: var(--surface); border: 2px solid var(--line); color: var(--ink);
  font: 900 15px/1 "Barlow Condensed", 'Barlow Condensed Fallback', sans-serif; letter-spacing: .1em; text-transform: uppercase;
  transition: background .22s var(--ease), border-color .22s var(--ease), color .22s var(--ease), transform .22s var(--ease);
}
@media (min-width: 760px) { .tab { min-height: 62px; padding: 18px 36px; font-size: 18px; } }
.tab[aria-selected="true"] { transform: translateY(-1px); }
.tab[aria-selected="true"] { background: var(--navy); border-color: var(--navy); color: #fff; }
.tab:hover:not([aria-selected="true"]) { border-color: var(--brand-deep); }
/* the played posters, as a grid here rather than a rail */
.posters--grid { grid-auto-flow: row; }
@media (min-width: 760px) { .posters--grid { grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 22px; } }
@media (prefers-reduced-motion: reduce) { .tab { transition: none; } }

/* ── R8 §B2 · the product page's accordions ───────────────────────────── */
.acc { border-top: 1px solid var(--line); }
.acc:last-of-type { border-bottom: 1px solid var(--line); }
.acc summary {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 18px 2px; cursor: pointer; min-height: 56px; list-style: none;
  font: 900 13px/1 "Barlow Condensed", 'Barlow Condensed Fallback', sans-serif; letter-spacing: .13em; text-transform: uppercase;
  color: var(--ink);
}
.acc summary::-webkit-details-marker { display: none; }
.acc summary::after {
  content: "+"; font: 400 22px/1 inherit; color: var(--brand-deep);
  transition: transform .25s var(--ease);
}
.acc[open] summary::after { content: "−"; }
.acc summary:hover { color: var(--brand-deep); }
.acc .spec { margin: 0 0 18px; }
@media (prefers-reduced-motion: reduce) { .acc summary::after { transition: none; } }

/* ── R8 §B4 · the photo wall ──────────────────────────────────────────── */
.wallsec { padding: var(--sec) 0; }
.wallsec__head { display: flex; align-items: end; justify-content: space-between; gap: 18px; flex-wrap: wrap; margin-bottom: clamp(20px, 3vw, 30px); }
.photowall {
  list-style: none; margin: 0; padding: 0; display: grid; gap: 8px;
  grid-template-columns: repeat(2, 1fr);
}
@media (min-width: 560px) { .photowall { grid-template-columns: repeat(3, 1fr); gap: 10px; } }
@media (min-width: 900px) { .photowall { grid-template-columns: repeat(5, 1fr); gap: 12px; } }
.photowall__t { margin: 0; }
.photowall__t a { display: block; aspect-ratio: 3 / 4; overflow: hidden; border-radius: 12px; background: var(--surface-2); }
.photowall__t img { width: 100%; height: 100%; object-fit: cover; display: block; transition: transform .7s var(--ease); }
.photowall__t a:hover img, .photowall__t a:focus-visible img { transform: scale(1.07); }
@media (prefers-reduced-motion: reduce) {
  .photowall__t img { transition: none; }
  .photowall__t a:hover img { transform: none; }
}

/* ── R8 §B5 · the timeline and the photograph band ───────────────────── */
section.story { padding: var(--sec) 0; }   /* R12: section.story — p.story (the About paragraphs) shares the name and was being padded like a section */
.tline { list-style: none; margin: clamp(22px, 3.4vw, 34px) 0 0; padding: 0; display: grid; gap: 0; }
.tline__i {
  display: grid; gap: 4px; padding: 22px 0 22px 26px; position: relative;
  border-top: 1px solid var(--line);
}
.tline__i:last-child { border-bottom: 1px solid var(--line); }
@media (min-width: 760px) { .tline__i { grid-template-columns: 220px 1fr; gap: 6px 28px; align-items: baseline; padding-left: 30px; } }
/* the line and its dot */
.tline__i::before {
  content: ""; position: absolute; left: 7px; top: 0; bottom: 0; width: 2px; background: var(--line);
}
.tline__i:first-child::before { top: 28px; }
.tline__i:last-child::before { bottom: calc(100% - 28px); }
.tline__i::after {
  content: ""; position: absolute; left: 2px; top: 24px; width: 12px; height: 12px; border-radius: 50%;
  background: var(--brand); border: 2px solid var(--surface); box-shadow: 0 0 0 2px var(--brand-deep);
}
.tline__when { font: 900 13px/1 "Barlow Condensed", 'Barlow Condensed Fallback', sans-serif; letter-spacing: .13em; text-transform: uppercase; color: var(--brand-deep); }
.tline__what { font: 900 clamp(20px, 3.2vw, 27px)/1.1 "Barlow Condensed", 'Barlow Condensed Fallback', sans-serif; text-transform: uppercase; color: var(--ink); }
.tline__what a { text-decoration: none; }
.tline__what a:hover { color: var(--brand-deep); }
.tline__note { color: var(--mute); font-size: 15px; }
@media (min-width: 760px) { .tline__note { grid-column: 2; } }

.stripsec { overflow: clip; padding-bottom: clamp(20px, 4vw, 40px); }
.strip {
  list-style: none; margin: 0; padding: 0 var(--gut); display: grid; gap: 10px;
  grid-auto-flow: column; grid-auto-columns: 54%;
  overflow-x: auto; overscroll-behavior-x: contain; scroll-snap-type: x mandatory; scrollbar-width: none;
}
.strip::-webkit-scrollbar { display: none; }
/* R9 §8: their photographs are 420x560. At 24% of 1440 they were drawn 336
   wide — 1.25x on a 2x screen, which is soft. 15% draws them at 210, which is
   2x of the pixels that exist. The row shows more of them instead. */
@media (min-width: 760px) { .strip { grid-auto-columns: 15%; gap: 12px; } }
.strip li { margin: 0; scroll-snap-align: start; }
.strip img { width: 100%; aspect-ratio: 3/4; object-fit: cover; border-radius: 12px; display: block; }
.ring--small { height: 170px; }
@media (min-width: 760px) { .ring--small { height: 200px; } }

/* ═══════════════ V2 ROUND 8 §C2 · WHAT THE MOBILE PASS FOUND ══════════════ */
/*
 * 126 page × device checks across 360, 375, 390, 414, 430, 390-landscape and
 * 768. Nothing was wider than the screen on any page at any width. What it did
 * find was tap targets under the 44px Thomas set — some of them on every single
 * page, which is why they are fixed here in one place rather than per page.
 *
 * Each one below is a REAL finding with its measured size, not a precaution.
 */

/* the skip link: 162x37 on every page */
.skip { min-height: 44px; display: inline-flex; align-items: center; }

/* the logo in the bar: 56x38 on every page — the whole thing is the home link */
.bar__home { min-height: 44px; }

/* the cart's close button, and the filter sheet's: both 40x40 */
.x { min-width: 44px; min-height: 44px; display: inline-grid; place-items: center; }

/* the breadcrumb on the product and tournament pages: 105x30 */
.crumbs { min-height: 44px; display: inline-flex; align-items: center; }
.crumbs a { display: inline-flex; align-items: center; min-height: 44px; }

/* "Add to cart" on a product card: 128x42, two short of the bar */
.card__add { min-height: 44px; }

/* the quantity stepper: 46x22, which is less than half the minimum */
.qty button { width: 44px; height: 44px; font-size: 18px; }

/* Links sitting on their own — a footer column, a nav row — rather than inside
 * a sentence. 19px tall is a line of text, not something a thumb can hit. */
.footer__links a, .join a, .crumbs a, .arrow { min-height: 44px; display: inline-flex; align-items: center; }
.footer__links { line-height: 1.5; }

/* Nothing a reader has to READ is under 14px. The 11-13px that remain are
 * uppercase tracked LABELS — eyebrows, column headings, a card's brand line —
 * which is what that size is for. */
.muted.small, .small { font-size: 14.5px; }
.buybar__n { font-size: 14px; }

/* Pass 2 of the mobile run: the three jump links at the top of the policies
   page measured 115x40, 77x40 and 80x40 — four short of the bar, at every
   portrait width. */
.jump a { min-height: 44px; display: inline-flex; align-items: center; }

/* ═══════════════════ V2 ROUND 9 §6 · THREE FILTERS, AS CONTROLS ══════════ */
/* Not underline tabs, not thin lines: three buttons that open a panel of big
   pill tiles. Every one is a 44px target, and the button shows what is chosen
   without the panel being open. */
.shopbar__groups { display: flex; flex-wrap: wrap; gap: 10px; }
.fbtn {
  display: inline-flex; align-items: center; gap: 9px; min-height: 52px;
  padding: 13px 20px; border-radius: 14px; cursor: pointer;
  background: var(--surface); border: 2px solid var(--line); color: var(--ink);
  font: 900 13px/1 "Barlow Condensed", 'Barlow Condensed Fallback', sans-serif; letter-spacing: .12em; text-transform: uppercase;
  transition: border-color .2s var(--ease), background .2s var(--ease), transform .2s var(--ease);
}
.fbtn:hover { border-color: var(--brand-deep); transform: translateY(-1px); }
.fbtn.is-on { border-color: var(--navy); background: var(--navy); color: #fff; }
/* on the navy fill of a chosen filter, not on the light page — measured */
.fbtn.is-on .fbtn__v { color: var(--brand); --on-dark: 7.18; }
.fbtn.is-open { border-color: var(--brand-deep); }
.fbtn__v { font: 700 14px/1 Nunito, 'Nunito Fallback', sans-serif; letter-spacing: 0; text-transform: none; color: var(--mute); }
.fbtn__c { font-size: 11px; opacity: .7; transition: transform .2s var(--ease); }
.fbtn.is-open .fbtn__c { transform: rotate(180deg); }
.filterset__none { margin: 0; color: var(--mute); font-size: 14.5px; max-width: 52ch; }

/* Laptop: the three panels are closed until their button is pressed. */
@media (min-width: 760px) {
  .filtersheet { gap: 0; }
  .filterset { display: none; padding: 20px 0 4px; }
  .filterset.is-open { display: grid; }
  .filterset__h { display: none; }
  .shopbar { align-items: center; }
}
/* Phone: one button, one sheet, all three groups as big tiles. */
@media (max-width: 759px) {
  .shopbar__groups { display: none; }
  .filterset { display: grid; }
  .filtersheet .chip { min-height: 52px; padding: 14px 18px; font-size: 15.5px; }
}
@media (prefers-reduced-motion: reduce) {
  .fbtn, .fbtn__c { transition: none; }
  .fbtn:hover { transform: none; }
}

/* ── R9 §1 · the range, as branded cards ─────────────────────────────── */
.rangegrid {
  list-style: none; margin: clamp(22px, 3.4vw, 34px) 0 18px; padding: 0;
  display: grid; gap: 12px; grid-template-columns: repeat(2, 1fr);
}
@media (min-width: 620px) { .rangegrid { grid-template-columns: repeat(3, 1fr); gap: 14px; } }
@media (min-width: 1000px) { .rangegrid { grid-template-columns: repeat(4, 1fr); } }
.rangecard {
  margin: 0; display: grid; gap: 14px; align-content: space-between;
  min-height: 150px; padding: 18px; border-radius: var(--rad);
  background: color-mix(in srgb, var(--ink) 5%, var(--surface));
  border: 1px solid color-mix(in srgb, var(--ink) 16%, var(--line));
  transition: transform .28s var(--ease), border-color .28s var(--ease), background .28s var(--ease);
}
.rangecard:hover {
  transform: translateY(-4px);
  background: color-mix(in srgb, var(--ink) 9%, var(--surface));
  border-color: color-mix(in srgb, var(--ink) 38%, var(--line));
}
.rangecard__mark { display: block; min-height: 26px; }
.rangecard__mark img { max-height: 24px; max-width: 72%; width: auto; object-fit: contain; opacity: .85; }
.rangecard__n {
  font: 900 clamp(17px, 2.4vw, 21px)/1.05 "Barlow Condensed", 'Barlow Condensed Fallback', sans-serif;
  letter-spacing: .03em; text-transform: uppercase; color: var(--ink);
}
@media (prefers-reduced-motion: reduce) {
  .rangecard { transition: none; }
  .rangecard:hover { transform: none; }
}

/* A sponsor's name is the longest string on its page and is not ours to
   shorten — "Diadem Sports" is thirteen characters, which needs 308px at the
   section size against the 280 of the narrowest phone. This heading gets a
   lower floor rather than the name being abbreviated. */
.spname { font-size: clamp(26px, 6vw, 56px); }

/* ── R9 §3 · three reasons, on their own photographs ─────────────────── */
.reasons {
  list-style: none; margin: clamp(26px, 4vw, 40px) 0 0; padding: 0;
  display: grid; gap: 14px;
}
@media (min-width: 760px) { .reasons { grid-template-columns: repeat(3, 1fr); gap: 20px; } }
.reason {
  position: relative; margin: 0; overflow: hidden; border-radius: var(--rad);
  aspect-ratio: 4 / 5; isolation: isolate; background: #04101B;
  transition: transform .4s var(--ease), box-shadow .4s var(--ease);
}
@media (min-width: 760px) { .reason { aspect-ratio: 3 / 4; } }
.reason:hover { transform: translateY(-5px); box-shadow: var(--shadow-lift); }
.reason img {
  position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
  z-index: -2; transition: transform 1.2s var(--ease);
}
.reason:hover img { transform: scale(1.06); }
.reason::after {
  content: ""; position: absolute; inset: 0; z-index: -1;
  background: linear-gradient(180deg, rgba(4,16,27,.12) 0%, rgba(4,16,27,.5) 48%, rgba(4,16,27,.93) 100%);
}
.reason__body {
  position: absolute; left: 0; right: 0; bottom: 0; display: grid; gap: 8px;
  padding: clamp(18px, 3.4vw, 26px);
}
.reason__body strong {
  font: 900 clamp(21px, 3vw, 27px)/1 "Barlow Condensed", 'Barlow Condensed Fallback', sans-serif;
  letter-spacing: .04em; text-transform: uppercase; color: #fff; --on-dark: 14.6;
}
.reason__body span { color: #D7E6EF; --on-dark: 11.1; font-size: 14.5px; line-height: 1.55; }
@media (prefers-reduced-motion: reduce) {
  .reason, .reason img { transition: none; }
  .reason:hover { transform: none; }
  .reason:hover img { transform: none; }
}

/* ── R9 §7 · their own footage, as a reel row ─────────────────────────── */
.reels { padding: var(--sec) 0; }
.courtsec { padding: var(--sec) 0; }
.reelrow {
  list-style: none; margin: clamp(20px, 3vw, 30px) 0 0; padding: 0 var(--gut);
  margin-inline: calc(var(--gut) * -1);
  display: grid; grid-auto-flow: column; grid-auto-columns: 62%; gap: 12px;
  overflow-x: auto; overscroll-behavior-x: contain; scroll-snap-type: x mandatory; scrollbar-width: none;
}
.reelrow::-webkit-scrollbar { display: none; }
@media (min-width: 760px) { .reelrow { grid-auto-columns: 25%; gap: 16px; } }
.reelrow li {
  position: relative; margin: 0; scroll-snap-align: start;
  border-radius: var(--rad); overflow: hidden; background: #04101B;
}
.reelrow video { width: 100%; aspect-ratio: 9 / 16; object-fit: cover; display: block; }
.reelrow__sound {
  position: absolute; right: 10px; bottom: 10px; min-height: 44px; padding: 11px 16px;
  border-radius: 999px; cursor: pointer; border: 1px solid rgba(255,255,255,.3);
  background: #04101B; color: #fff; --on-dark: 15.1;
  font: 900 11px/1 "Barlow Condensed", 'Barlow Condensed Fallback', sans-serif; letter-spacing: .14em; text-transform: uppercase;
}
.reelrow__sound:hover { background: var(--brand); color: var(--ink); border-color: var(--brand); }

/* ── R10 §1 · a sponsor's range, with their own photographs ───────────── */
/* the sponsor hero's gallery strip continues the hero, so it takes no top space of its own */
.spgal { padding: 0; }
.spgal__row {
  list-style: none; margin: 0; padding: 0 var(--gut); margin-inline: calc(var(--gut) * -1);
  display: grid; grid-auto-flow: column; grid-auto-columns: 46%; gap: 10px;
  overflow-x: auto; scroll-snap-type: x mandatory; scrollbar-width: none; overscroll-behavior-x: contain;
}
.spgal__row::-webkit-scrollbar { display: none; }
@media (min-width: 760px) { .spgal__row { grid-auto-columns: 1fr; gap: 14px; overflow: visible; } }
.spgal__row li {
  margin: 0; scroll-snap-align: start; border-radius: var(--rad); overflow: hidden;
  background: color-mix(in srgb, var(--ink, #0A1C2B) 5%, #fff); aspect-ratio: 1;
}
.spgal__row img { width: 100%; height: 100%; object-fit: cover; display: block; }

.rgroup { margin-top: clamp(26px, 4vw, 42px); }
.rgroup__h {
  display: flex; align-items: baseline; gap: 10px; margin: 0 0 14px;
  font: 900 clamp(17px, 2.2vw, 22px)/1 "Barlow Condensed", 'Barlow Condensed Fallback', sans-serif;
  letter-spacing: .05em; text-transform: uppercase;
}
.rgroup__h span {
  font-size: 12px; letter-spacing: .1em; padding: 4px 9px; border-radius: 999px;
  background: color-mix(in srgb, var(--ink, #0A1C2B) 10%, transparent);
  color: color-mix(in srgb, var(--ink, #0A1C2B) 82%, #000);
}
.rangegrid {
  list-style: none; margin: 0; padding: 0;
  display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px;
}
@media (min-width: 620px) { .rangegrid { grid-template-columns: repeat(3, 1fr); gap: 14px; } }
@media (min-width: 1040px) { .rangegrid { grid-template-columns: repeat(4, 1fr); } }
.rcard { margin: 0; }
.rcard > a {
  display: grid; grid-template-rows: auto 1fr; height: 100%; text-decoration: none; color: inherit;
  border: 1px solid var(--line); border-radius: var(--rad); overflow: hidden; background: #fff;
  transition: transform .34s var(--ease), box-shadow .34s var(--ease), border-color .34s var(--ease);
}
.rcard > a:hover { transform: translateY(-4px); box-shadow: var(--shadow-lift); border-color: var(--ink, #0A1C2B); }
.rcard__shot {
  display: block; aspect-ratio: 1; overflow: hidden;
  background: color-mix(in srgb, var(--ink, #0A1C2B) 4%, #fff);
}
.rcard__shot img { width: 100%; height: 100%; object-fit: contain; display: block; transition: transform .6s var(--ease); }
.rcard > a:hover .rcard__shot img { transform: scale(1.05); }
.rcard__body { display: grid; gap: 8px; align-content: start; padding: 12px 13px 14px; }
.rcard__body strong { font-size: 14px; line-height: 1.32; font-weight: 650; }
.rcard__foot { display: grid; gap: 5px; }
.rcard__p { font: 900 16px/1 "Barlow Condensed", 'Barlow Condensed Fallback', sans-serif; letter-spacing: .02em; }
.rcard__p--none { font-size: 13px; font-weight: 600; font-family: inherit; color: var(--muted); }
.rcard__cur { font-size: 10px; letter-spacing: .12em; opacity: .62; }
.rcard__go {
  font: 900 10px/1 "Barlow Condensed", 'Barlow Condensed Fallback', sans-serif; letter-spacing: .13em; text-transform: uppercase;
  color: color-mix(in srgb, var(--ink, #0A1C2B) 88%, #000);
}
@media (prefers-reduced-motion: reduce) {
  .rcard > a, .rcard__shot img { transition: none; }
  .rcard > a:hover { transform: none; }
  .rcard > a:hover .rcard__shot img { transform: none; }
}

/* ── R10 §2 · each sponsor row, with their own products beside it ─────── */
.spblock {
  border: 1px solid var(--line); border-radius: var(--rad); overflow: hidden;
  background: #fff; transition: border-color .34s var(--ease), box-shadow .34s var(--ease);
}
.spblock + .spblock { margin-top: 14px; }
.spblock:hover { border-color: var(--ink, #0A1C2B); box-shadow: var(--shadow-lift); }
.spblock .sprow { border: 0; border-radius: 0; box-shadow: none; }
.spblock .sprow:hover { transform: none; box-shadow: none; }
.spstrip {
  list-style: none; margin: 0; padding: 0 14px 14px;
  display: grid; grid-auto-flow: column; grid-auto-columns: 30%; gap: 8px;
  overflow-x: auto; scroll-snap-type: x mandatory; scrollbar-width: none; overscroll-behavior-x: contain;
  border-top: 1px solid var(--line);
  padding-top: 14px;
}
.spstrip::-webkit-scrollbar { display: none; }
@media (min-width: 760px) { .spstrip { grid-auto-columns: 1fr; gap: 10px; overflow: visible; } }
.spstrip li { margin: 0; scroll-snap-align: start; }
.spstrip a {
  display: block; aspect-ratio: 1; border-radius: 12px; overflow: hidden;
  background: color-mix(in srgb, var(--ink, #0A1C2B) 5%, #fff);
}
.spstrip img { width: 100%; height: 100%; object-fit: contain; display: block; transition: transform .5s var(--ease); }
.spstrip a:hover img { transform: scale(1.07); }
.spstrip__more a {
  display: grid; place-content: center; text-align: center; text-decoration: none;
  background: var(--ink, #0A1C2B); color: #fff; --on-dark: 12.4;
  font: 900 12px/1.35 "Barlow Condensed", 'Barlow Condensed Fallback', sans-serif; letter-spacing: .1em; text-transform: uppercase;
}
@media (prefers-reduced-motion: reduce) { .spstrip img { transition: none; } .spstrip a:hover img { transform: none; } }

/* ── R10 §4 · a sponsor's own range, inside the shop grid ─────────────── */
.gridnote {
  grid-column: 1 / -1; margin: 18px 0 2px; padding-top: 18px; border-top: 1px solid var(--line);
  font-size: 14px; color: var(--muted); line-height: 1.55;
}
.card--theirs > a { display: grid; grid-template-rows: auto 1fr; height: 100%; text-decoration: none; color: inherit; }
.card--theirs .card__shot { display: block; aspect-ratio: 1; overflow: hidden; background: var(--wash, #F4F7F9); }
.card--theirs .card__shot img { width: 100%; height: 100%; object-fit: contain; display: block; }
.card--theirs .card__b { display: grid; gap: 7px; align-content: start; padding: 13px 14px 16px; }
.card--theirs h3 { font-size: 14.5px; line-height: 1.3; margin: 0; font-weight: 650; }
.card--theirs .card__p { margin: 0; font: 900 17px/1 "Barlow Condensed", 'Barlow Condensed Fallback', sans-serif; }
.card--theirs .card__from {
  margin: 0; font: 900 10px/1 "Barlow Condensed", 'Barlow Condensed Fallback', sans-serif;
  letter-spacing: .13em; text-transform: uppercase; color: var(--brand-ink, #0A1C2B);
}

/* ── R10 §3 · the tournaments page ────────────────────────────────────── */
.courtsec--dark {
  background: var(--deep, #04101B); color: #fff; --on-dark: 15.2;
  margin-block: clamp(34px, 5vw, 60px);
}
.courtsec--dark .lede { color: #C7DCE8; --on-dark: 10.4; }
.courtsec--dark .eyebrow { color: var(--brand); }

.tshots { padding: var(--sec) 0; }
.tshots__row {
  list-style: none; margin: clamp(20px, 3vw, 30px) 0 0;
  padding: 0 var(--gut); margin-inline: calc(var(--gut) * -1);
  display: grid; grid-auto-flow: column; grid-auto-columns: 78%; gap: 12px;
  overflow-x: auto; scroll-snap-type: x mandatory; scrollbar-width: none; overscroll-behavior-x: contain;
}
.tshots__row::-webkit-scrollbar { display: none; }
@media (min-width: 900px) { .tshots__row { grid-auto-columns: 1fr; gap: 16px; overflow: visible; } }
.tshots__row li {
  position: relative; margin: 0; scroll-snap-align: start; isolation: isolate;
  border-radius: var(--rad); overflow: hidden; aspect-ratio: 4 / 3; background: #04101B;
}
.tshots__row img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; z-index: -2; }
.tshots__row li::after {
  content: ""; position: absolute; inset: 0; z-index: -1;
  background: linear-gradient(180deg, rgba(4,16,27,0) 42%, rgba(4,16,27,.88) 100%);
}
.tshots__row span {
  position: absolute; left: 0; right: 0; bottom: 0; padding: 14px 16px;
  color: #fff; --on-dark: 14.8; font-size: 13.5px; line-height: 1.4;
}

.steps--n { list-style: none; counter-reset: none; }
.steps--n .step { margin: 0; }

.faq { padding: var(--sec) 0; }
.faq__list { margin-top: clamp(18px, 3vw, 28px); border-top: 1px solid var(--line); }
.faq__i { border-bottom: 1px solid var(--line); }
.faq__i summary {
  cursor: pointer; list-style: none; padding: 18px 40px 18px 0; position: relative;
  font: 650 clamp(15px, 1.9vw, 17.5px)/1.4 inherit; min-height: 44px;
}
.faq__i summary::-webkit-details-marker { display: none; }
.faq__i summary::after {
  content: "+"; position: absolute; right: 6px; top: 50%; transform: translateY(-50%);
  font: 300 26px/1 inherit; color: var(--brand-ink, #0A1C2B); transition: transform .3s var(--ease);
}
.faq__i[open] summary::after { content: "−"; }
.faq__i summary:hover { color: var(--brand-ink, #0A1C2B); }
.faq__i p { margin: 0 0 20px; color: var(--muted); line-height: 1.62; max-width: 62ch; }
@media (prefers-reduced-motion: reduce) { .faq__i summary::after { transition: none; } }

/* ── R10 §5 · About, as an editorial spread ───────────────────────────── */
.edit { padding: var(--sec) 0; }
.edit figure { margin: 0; }
.edit img { width: 100%; height: 100%; object-fit: cover; display: block; border-radius: var(--rad); }
.edit figcaption {
  margin-top: 12px; font-size: 14px; line-height: 1.58; color: var(--muted); max-width: 54ch;
}
.edit figcaption strong { color: var(--ink); font-weight: 650; }
.edit__hero { margin-top: clamp(24px, 4vw, 40px); }
.edit__hero img { aspect-ratio: 16 / 9; }
@media (min-width: 900px) { .edit__hero img { aspect-ratio: 21 / 9; } }
.edit__hero figcaption { font-size: 15px; max-width: 64ch; }
.edit__pair {
  display: grid; gap: clamp(16px, 2.6vw, 30px);
  margin-top: clamp(20px, 3.4vw, 38px);
}
@media (min-width: 760px) {
  .edit__pair { grid-template-columns: 1fr 1fr; }
  .edit__pair--wide { grid-template-columns: 1.35fr 1fr; }
}
.edit__pair img { aspect-ratio: 4 / 3; }
.edit__pair figure { transition: transform .45s var(--ease); }
.edit__pair figure:hover { transform: translateY(-4px); }
@media (prefers-reduced-motion: reduce) {
  .edit__pair figure { transition: none; }
  .edit__pair figure:hover { transform: none; }
}

/* R10 · the editorial hero is their biggest frame at 1920x1080. A full 1140px
   column on a 2x laptop wants 2280 and there is no such file, so it is drawn at
   the width its own pixels actually cover rather than enlarged to fill. */
.edit__hero { max-width: 940px; margin-inline: auto; }
.edit__hero figcaption { margin-inline: auto; }

/* R11 · the preview line, for SmashIT's own test run. Quiet on purpose: it is
   a note to two people, not a banner across the site. */
.footer__preview {
  /* 14px, not 12.5 — the mobile check refused it, and it is right: this is body
     text and body text under 14px is a squint on a phone. */
  margin: 8px 0 0; font-size: 14px; line-height: 1.55;
  color: color-mix(in srgb, currentColor 72%, transparent);
}
.footer__preview strong {
  /* NOT the brand cyan: it is a highlight colour, not a type colour, and the
     contrast check refused it as unreadable. It inherits the footer's own
     text colour and earns its emphasis from weight and letter-spacing. */
  color: inherit; letter-spacing: .1em; text-transform: uppercase;
  font: 900 11px/1 "Barlow Condensed", 'Barlow Condensed Fallback', sans-serif; margin-right: 2px;
}

/* ═══════════════════════════════════════════ ROUND 12 · JAMES, 17 OCTOBER ══
   Four of his points land in this block: nothing hidden under the sticky bar,
   the shop's sections as tabs, an "Arriving soon" note instead of an empty
   grid, and the "What we have run" section rebuilt so no writing sits on a
   photograph and the photographs are always on screen. The fifth — one
   spacing scale — is --sec in :root, applied in place to every section rule. */

/* ── nothing slides under the sticky bar ─────────────────────────────────
   An anchor jump (/sponsors#become, a policy link, "skip to content") used to
   land the heading BEHIND the bar, which is 64px tall. Every jump now stops
   below it, with a little air. */
html { scroll-padding-top: 84px; }

/* ── the shop's sections, as tabs across the top ─────────────────────── */
.cattabs { margin: 0 0 clamp(16px, 2.4vw, 22px); }
.cattabs__row {
  display: flex; gap: 8px;
  /* A phone: one row that scrolls INSIDE ITSELF, edge to edge, so the page
     never moves sideways. The fade on the right says there is more. */
  overflow-x: auto; scroll-snap-type: x proximity; scroll-padding-inline: var(--gut); scrollbar-width: none;
  margin-inline: calc(var(--gut) * -1); padding: 2px var(--gut) 8px;
  -webkit-mask-image: linear-gradient(90deg, #000 calc(100% - 36px), transparent);
          mask-image: linear-gradient(90deg, #000 calc(100% - 36px), transparent);
}
.cattabs__row::-webkit-scrollbar { display: none; }
@media (min-width: 760px) {
  /* A laptop has the room: every section in view, no scrolling, no fade. */
  .cattabs__row { flex-wrap: wrap; overflow: visible; margin-inline: 0; padding-inline: 0;
    -webkit-mask-image: none; mask-image: none; }
  /* the tabs ARE the category filter here, so its dropdown button would be a second copy */
  .shopbar__groups .fbtn[data-group="cat"] { display: none; }
}
.cattab {
  flex: 0 0 auto; scroll-snap-align: start;
  display: inline-flex; align-items: center; gap: 8px; min-height: 46px;
  padding: 0 18px; border-radius: 999px; cursor: pointer;
  background: var(--surface); border: 2px solid var(--line); color: var(--ink);
  font: 900 14px/1 "Barlow Condensed", 'Barlow Condensed Fallback', sans-serif; letter-spacing: .1em; text-transform: uppercase;
  transition: background .2s var(--ease), border-color .2s var(--ease), color .2s var(--ease);
}
.cattab:hover:not([disabled]) { border-color: var(--brand-deep); }
.cattab[aria-pressed="true"] { background: var(--navy); border-color: var(--navy); color: #fff; }
.cattab[aria-pressed="true"] .chip__n { background: rgba(255,255,255,.2); color: #fff; }
.cattab[disabled] { opacity: .4; cursor: default; }
.cattab .chip__n { letter-spacing: 0; font-family: Nunito, 'Nunito Fallback', sans-serif; }
/* a section that is coming: dashed, not greyed — it is a promise, not a dead end */
.cattab--soon, .chip--soon { border-style: dashed; }
.cattab--soon .chip__n, .chip--soon .chip__n { background: var(--brand-10); color: var(--brand-deep); padding: 0 7px; }
@media (prefers-reduced-motion: reduce) { .cattab { transition: none; } }

/* ── "Arriving soon", for that tab only ──────────────────────────────── */
.soon {
  grid-column: 1 / -1; text-align: center;
  padding: clamp(36px, 6vw, 64px) 20px; border-radius: var(--rad);
  background: var(--surface); border: 1px dashed var(--line-strong);
}
.soon .eyebrow { justify-content: center; }
.soon__h { font-size: clamp(30px, 6vw, 44px); margin: 0 0 10px; }
.soon__p { color: var(--mute); max-width: 44ch; margin: 0 auto 22px; }

/* ── home · "Why sponsor SmashIT" now carries the sponsor button ─────── */
.why__cta { margin-top: 8px; }

/* ── home · "What we have run": words off the photograph ────────────────
   The heading sits on the page's own ground above the film (no scrim, no
   white-on-photo), the film carries no text at all, and the photographs are
   a real grid that is there before, without and regardless of JavaScript. */
.trailsec__head h2 em { font-style: normal; color: var(--brand-deep); }
.trailsec__film { min-height: 0; aspect-ratio: 16 / 9; max-height: 72svh; width: 100%; display: block; }
@media (max-width: 759px) {
  .trailsec__film { aspect-ratio: 4 / 3; }
  /* James, 18 Oct: "the pic of us … our heads are cut off" on a phone. The tall
     poster and the film are cropped into 4:3, so keep the TOP of the frame, where
     the faces are, instead of the middle. */
  .trailsec__v { object-position: 50% 12%; }
}
.pics {
  list-style: none; margin: var(--sec-half) 0 0; padding: 0;
  display: grid; gap: 10px; grid-template-columns: repeat(2, 1fr);
}
@media (min-width: 680px) { .pics { grid-template-columns: repeat(3, 1fr); gap: 14px; } }
@media (min-width: 1100px) { .pics { grid-template-columns: repeat(6, 1fr); } }
.pics__i { margin: 0; border-radius: 12px; overflow: hidden; background: var(--surface-2); aspect-ratio: 3 / 4; }
.pics__i img, .pics__i picture { width: 100%; height: 100%; object-fit: cover; display: block; }
/* The trail: only for a mouse on a laptop (home.js adds .is-live). Anywhere
   else this is just the slogan, with no tall empty canvas under it. */
.trail { min-height: 0; padding: var(--sec-half) 0 0; }
.trail:not(.is-live) .trail__hint { display: none; }
.trail.is-live { min-height: 46svh; margin-top: var(--sec-half); padding: 0; }
@media (prefers-reduced-motion: reduce) {
  /* the old reduced-motion collage was an empty box (no photos were ever put
     in it); the grid above is the collage now */
  .trail__stage { display: none; }
  .trail__mid { display: block; }
}

/* ── the sticky bar is SOLID ─────────────────────────────────────────────
   James: headings "sliding under the sticky header". At 78-94% opacity the
   bar let a heading scrolling beneath it show through as a ghost over the
   logo and the Cart button. It is the page's own ground, fully opaque, now —
   what goes under it is hidden cleanly, never half-readable. */
.bar, .bar.stuck { background: var(--bg); }

/* ── Tournaments page head: words off the film on a phone ────────────────
   James: "photos over writing". On a phone the headline and the paragraph sat
   across the middle of the film, where the scrim was only ~30% dark and two
   players' heads were behind the words. On a phone the film is now a band at
   the top and the words sit BELOW it on the page's navy. On a laptop the
   words stay on the film, with a proper dark wash in from the left where
   they are. */
@media (max-width: 759px) {
  .tophero { display: block; min-height: 0; padding: 0; background: #06121F; }
  .tophero__v { position: relative; inset: auto; z-index: 0; display: block; height: auto; aspect-ratio: 16 / 10; }
  .tophero__scrim { display: none; }
  .tophero__copy { padding-block: var(--sec-half) var(--sec); }
}
@media (min-width: 760px) {
  .tophero__scrim {
    background:
      linear-gradient(90deg, rgba(6,18,31,.88) 0%, rgba(6,18,31,.66) 38%, rgba(6,18,31,.12) 72%, rgba(6,18,31,0) 100%),
      linear-gradient(180deg, rgba(6,18,31,.35) 0%, rgba(6,18,31,0) 40%, rgba(6,18,31,.7) 100%);
  }
}

/* ── Sponsors page, "how they back the game" cards: words UNDER the photo ─
   James: "photos over writing". The three cards set their heading and line
   across the bottom of the photograph — over the fence, the crowd and the
   sky. The photo is now the top of the card and the words sit below it on
   the card's own navy, at every size. Nothing to read over a busy picture. */
.reason { aspect-ratio: auto; display: grid; grid-template-rows: auto 1fr; }
@media (min-width: 760px) { .reason { aspect-ratio: auto; } }
.reason img { position: relative; inset: auto; z-index: 0; aspect-ratio: 16 / 10; height: auto; }
.reason::after { display: none; }
.reason__body { position: relative; left: auto; right: auto; bottom: auto; background: #04101B; align-content: start; }

/* ── Tournament posters: words under the photo ───────────────────────────
   The date, venue and title chips sat over the lower half of the photograph,
   which on "Men's SmashIT Tournament" is bright sky and a white building. The
   photo is now the top of the poster (every image stacked in the same cell,
   so the hover flick still crossfades), and the words sit below it on navy. */
.poster a { aspect-ratio: auto; display: grid; grid-template-rows: auto 1fr; }
.poster__img { position: relative; inset: auto; grid-area: 1 / 1; aspect-ratio: 4 / 3; height: auto; }
.poster a::after { display: none; }
.poster__foot { position: relative; left: auto; right: auto; bottom: auto; grid-area: 2 / 1; align-content: start; }

/* ── "What a day looks like": the caption under the photo, not on it ──── */
.tshots__row li { aspect-ratio: auto; }
.tshots__row img { position: relative; inset: auto; z-index: 0; aspect-ratio: 4 / 3; height: auto; display: block; }
.tshots__row li::after { display: none; }
.tshots__row span { position: relative; display: block; left: auto; right: auto; bottom: auto; }

/* the film sits in the page column on a laptop rather than edge to edge:
   their footage is 1280 wide, and stretched to 1440+ it went soft */
@media (min-width: 760px) {
  .trailsec__film { width: calc(100% - 2 * var(--gut)); max-width: calc(var(--max) - 2 * var(--gut)); margin-inline: auto; border-radius: var(--rad); max-height: 62svh; }
}

/* ── product page, phone: the sticky buy bar never covers the footer ─────
   The bar is fixed to the bottom once the real button has scrolled away, and
   at the foot of the page it sat over the last lines of the footer. The page
   now ends with room for it. */
@media (max-width: 899px) {
  body:has(#buybar) footer { padding-bottom: calc(var(--sec) + 72px + env(safe-area-inset-bottom)); }
}

/* the laptop nav links: a 44px target like everything else (they were 30px) */
.bar nav a { display: inline-flex; align-items: center; min-height: 44px; }

/* "All tournaments →", "Read their story →": measured 43.99px tall on some
   screens, a hair under the 44px tap floor. A clear 46 now. */
.arrow { min-height: 46px; }

/* ── R14 · Verbe's products: sizes, and photographs shown whole ───────────
   A shoe packshot is a shoe on white, wider than it is tall. Cropped to a
   square with `cover` it loses its toe and heel, so Verbe's photographs are
   CONTAINED on white instead — the whole shoe, nothing cut off. */
.card__fig img[src^="/img/products/verbe/"],
.product__fig img[src^="/img/products/verbe/"] { object-fit: contain; background: #fff; }

/* The card's "Choose your size": a link styled as the Add button it replaces. */
a.card__size { display: flex; align-items: center; justify-content: center; text-decoration: none; min-height: 44px; }

/* The size picker: every size visible, every one a 44px target, none chosen
   for you. A checked size is filled, not just outlined, so it reads at a
   glance on a phone in sunlight. */
.sizepick { border: 0; margin: 14px 0 12px; padding: 0; min-width: 0; }
.sizepick legend {
  padding: 0; margin: 0 0 9px;
  font: 800 13px/1.3 Nunito, 'Nunito Fallback', system-ui, sans-serif; color: var(--ink);
}
.sizepick__hint { font-weight: 600; color: var(--mute); }
.sizepick__hint::before { content: "· "; }
.sizepick__grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(58px, 1fr)); gap: 8px; }
.sizepick__s { position: relative; display: block; }
.sizepick__s input { position: absolute; inset: 0; opacity: 0; margin: 0; cursor: pointer; }
.sizepick__s span {
  display: flex; align-items: center; justify-content: center; min-height: 46px;
  border: 1px solid var(--line-strong); border-radius: 10px; background: var(--surface);
  font: 800 15px/1 Nunito, 'Nunito Fallback', system-ui, sans-serif; color: var(--ink);
  transition: background .15s var(--ease), border-color .15s var(--ease);
}
.sizepick__s input:hover + span { border-color: var(--brand-deep); }
.sizepick__s input:focus-visible + span { outline: 3px solid var(--brand-deep); outline-offset: 2px; }
.sizepick__s input:checked + span { background: var(--ink); border-color: var(--ink); color: #fff; }
.sizepick.is-missing .sizepick__s span { border-color: #B4532B; }
.sizepick__err { margin: 8px 0 0; min-height: 0; color: #8A3E1E; font: 700 14px/1.4 Nunito, 'Nunito Fallback', system-ui, sans-serif; }
.sizepick__err:empty { display: none; }

/* /order-received: what was ordered, sizes included (this tab's own order). */
.receipt__lines { list-style: none; margin: 4px auto 18px; padding: 0; max-width: 40ch; text-align: left; display: grid; gap: 8px; }
.receipt__lines li { padding: 10px 14px; border: 1px solid var(--line); border-radius: 12px; background: var(--surface); font-weight: 800; font-size: 15px; }
.receipt__lines small { display: block; font-weight: 600; color: var(--mute); font-size: 13px; margin-top: 2px; }
.receipt__q { color: var(--mute); }

/* ═══════════════════════════ ROUND 15 · THOMAS'S iPHONE (17 OCTOBER) ═════
   Thomas opened the preview on his own iPhone (Safari, 440pt wide) and sent
   seven screenshots: "The mobile version is horrible." Every rule below is
   tagged R15 and says WHY Safari differed from the Chromium emulation all the
   earlier rounds were checked in — because the checks passed while the phone
   looked bad, and the reason matters more than the patch.

   The house rules for anything below 760px from here on:
     · explicit boxes for images (percentage padding + absolute fill), never
       a percentage height that depends on aspect-ratio resolving first;
     · no preserve-3d / perspective on a phone;
     · svh/dvh with a fallback, never bare 100vh;
     · min-width: 0 on grid and flex children that hold media. */

/* ── R15 A · the intro is one screen, not a band on a band ──────────────
   The phone film was a 720x576 picture with flat bars baked into it above and
   below, and the bars were a lighter navy than the picture's vignetted edges —
   so even a perfect `cover` showed three bands. On top of that iOS Safari
   draws a <video>'s POSTER letterboxed (contained) whatever object-fit says,
   so before the first frame there was a fourth colour: the box itself.
   R16 (Thomas: "it literally looks like it's landscape"): the phone film is
   now a TRUE 9:16 film made from the 16:9 master by scripts/make-phone-intro.py
   — the logo fills the screen, then a push-in lands on exactly the crop of
   hero-mobile.jpg, so the last frame hands over to the hero. (The R15 file
   also carried a broken pixel-aspect flag, SAR 80:3, which Safari obeys and
   stretched sideways; the new file is SAR 1:1.) And the poster is no longer the
   <video>'s poster on a phone (home.js removes it): it is this layer, which
   is a CSS background and therefore obeys `cover` in every browser. It is
   the film's own first frame, so the hand-over to the playing film is
   invisible. The box colour is that frame's edge colour, sampled. */
@media (max-width: 767px) {
  .introv { background: #041422; }
  .introv::before {
    content: ""; position: absolute; inset: 0; z-index: 0;
    background: #041422 url("/img/hero/intro-phone-poster-916.jpg") center / cover no-repeat;
    transition: opacity .35s linear;
  }
  /* the logo-entrance fallback (home.js) runs on the plain navy, so the
     poster's own logo leaves with the film instead of doubling the new one */
  .introv--logo::before { opacity: 0; }
  .introv__v { position: relative; }
}

/* ── R15 B · sponsors on a phone: a still, even grid ────────────────────
   The 3D ring broke in Safari: the logos stacked on top of each other in a
   flat line (Verbe, Decathlon and Rooibru overlapping). WebKit FLATTENS a
   preserve-3d subtree as soon as an ancestor or the element itself has
   overflow other than visible, a filter, or opacity < 1 — the stage clips
   (`overflow: clip`) and every mark carries a grayscale filter and an opacity
   from home.js, so on iOS the translateZ that spreads the marks round the
   cylinder collapses to nothing and they all land in the middle. Chromium
   keeps the 3D context, which is why every check passed. On a phone the ring
   is now the same still layout reduced motion already used, as a 2x2 grid:
   four real marks, each in a cell of the same size, nothing overlapping, and
   home.js does not run the ring at all below 760px. The ring's fixed
   210px stage height is gone with it, which was most of the empty band
   under the logos. Laptop: unchanged.
   R17: this still grid is now what a phone shows under REDUCED MOTION only;
   with motion allowed, the phone gets the 2D marquee in R17 A. */
@media (max-width: 759px) and (prefers-reduced-motion: reduce) {
  .ring {
    perspective: none; height: auto; overflow: visible; cursor: auto;
    margin-top: 28px; padding: 0;
  }
  .ring__track {
    transform: none !important; transform-style: flat; height: auto;
    display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px;
  }
  .ring__track > * {
    position: static; transform: none !important; opacity: 1 !important;
    filter: none !important; pointer-events: auto !important;
    width: auto; height: 84px; min-width: 0; padding: 10px 12px;
    backface-visibility: visible; -webkit-backface-visibility: visible;
  }
  /* the second copies exist only so a turning ring has no gap */
  .ring__track > [aria-hidden="true"] { display: none; }
  /* Sized before the file arrives (the R13 trick): a height the max-height
     always clamps, so each logo is exactly its cap and its width comes from
     the width/height attributes — no jump as they load. */
  .ring__track img { height: 1000px; width: auto; max-width: 100%; max-height: 38px; }
  .ring__track [data-sp="diadem"] img { max-height: 58px; }
  .ring__track [data-sp="verbe"] img { max-height: 32px; }
}

/* ── R15 C · the ball line is a laptop thing ────────────────────────────
   On a 390px column there is no gutter for it to run in, so it crossed the
   headings and the body copy whatever the weave did. Off below 760px (home.js
   does not build it there either). Laptop: unchanged. */
/* R17: back on, on phones too (Thomas: "the line animation is gone as well").
   The rule that hid it is gone; home.js builds it at every width. */

/* ── R15 D · "What a day looks like": room between heading and photos ───
   The row sat 20px under the heading (the h2's 18px margin collapsed through
   .wrap into the row's 20px), and the italic "LOOKS LIKE" sits low in its line
   box, so on the phone the photographs met the heading. flow-root stops the
   collapse and the row starts a clear 32px below. The photo boxes get an
   explicit height on a phone (78% of the column at 4:3) instead of trusting
   aspect-ratio on an <img> that is a grid item's child. */
.tshots .wrap { display: flow-root; }
.tshots__row { margin-top: 14px; }
@media (max-width: 899px) {
  .tshots__row img { height: calc((100vw - 2 * var(--gut)) * .78 * .75); width: 100%; object-fit: cover; }
  .tshots__row li { min-width: 0; }
}

/* ── R15 E · "How it all works": the cards were pushed right ────────────
   The steps are an <ol>, and nothing ever reset its padding: every browser's
   own stylesheet gives a list 40px of padding-inline-start, so each card had a
   60px left gutter against a 20px right one. Chromium did it too (it is in the
   R12 screenshots); it was simply never measured. A sweep of every list on
   every page found no other card list with the default padding left in. */
.steps { padding: 0; list-style: none; }

/* ── R15 F · the footer on a phone: no stray lines, no cut-off word ─────
   The court drawing is stretched to the footer's box with
   preserveAspectRatio="none"; on a phone the footer is ~900px tall and 390
   wide, so the "court" became a few faint lines straight through the columns
   — they read as a broken grid, not a court. The outline SMASHIT is anchored
   below the bottom edge (bottom: -2%) and the footer clips, so on a phone its
   lower third was always cut off — worse in Safari, where the bottom padding
   includes the home-indicator inset. Both are laptop decoration now. And the
   footer's own top padding is halved on a phone: with the section above
   ending in its own 56px, there were ~115px of nothing before the logo. */
@media (max-width: 759px) {
  .footer__court, .footer__word { display: none; }
  footer, .footer--shot { padding-top: var(--sec-half); }
}

/* ── R15 G · every product image in an identical, explicit box ──────────
   The "huge racket" was the Adipower Multiweight CTRL 3.4: its photo is 807 x
   1146, not square. The box was `aspect-ratio: 1` and the image inside it
   `height: 100%`. iOS Safari does not treat a height that only exists because
   of aspect-ratio as definite for a percentage child when the box is a grid
   or flex item, so the 100% fell back to auto and the image took its own
   portrait shape at full width: taller than the box, over the name, and the
   box's "photo to come" stripes showing above it. Chromium resolves it, so it
   only ever broke on the iPhone.
   The box is now the oldest trick there is, which every browser has done the
   same way for twenty years: height 0, padding-bottom 100% of its own width,
   and the image absolutely filling it, `contain`ed on white so a tall racket
   and a wide bag are both whole and the same size box. The stripes belong to
   the "photo to come" placeholder only. The same box for the home page shop
   rail, the sponsor ranges, the sponsor product strips and the sponsor
   galleries, which used the same pattern. */
.card { min-width: 0; }
.card__fig,
.mix__shot,
.card--theirs .card__shot,
.rcard__shot,
.spstrip li:not(.spstrip__more) > a,
.spgal__row li {
  position: relative; display: block; overflow: hidden;
  aspect-ratio: auto; height: 0; padding: 0 0 100%; min-width: 0;
}
.card__fig { background: #fff; }
.card__fig > img,
.mix__shot > img,
.card--theirs .card__shot > img,
.rcard__shot > img,
.spstrip li:not(.spstrip__more) > a > img,
.spgal__row li > img {
  position: absolute; top: 0; left: 0; width: 100%; height: 100%; max-width: none;
}
.card__fig > img { object-fit: contain; padding: 6px; }
.card__ph {
  position: absolute; inset: 0; margin: 0; display: grid; place-content: center;
  background: repeating-linear-gradient(135deg, var(--surface-2) 0 12px, var(--surface) 12px 24px);
}
/* The name may run to two lines and the price sits under it; the button row
   is outside the link, so the link takes all the spare height and every
   button in a row lines up along the bottom. */
.card__name { overflow-wrap: anywhere; }
/* The product page's frame kept the same "photo to come" stripes behind a
   real photograph, and they showed round the thumbnail strip. A frame with a
   picture in it is plain. */
.product__fig:has(> img) { background: var(--surface); }

/* ── R15 H · their own footage, sharp on a phone ────────────────────────
   The films are their own landscape clips at 480p-720p (854x480, 1280x720,
   960x540 — the largest copies there are). A 9:16 tile shows a 270px-wide
   slice of an 854px frame, and an iPhone draws that tile 660 to 760 device
   pixels wide: 2.5-2.8x enlarged, which is the blur. On a phone the tiles are
   now the films' own 16:9 shape, so a frame is drawn at about 1:1 on a 3x
   screen. The posters are new: each film's sharpest frame at its full size
   (the old community still was its blurriest, a soft close-up at 800x450),
   and the two films that open on a soft frame start at the sharp one
   (#t= in the src). 16:9 tile = 16:9 poster, so it does not matter that
   Safari letterboxes posters instead of covering. Laptop: unchanged. */
@media (max-width: 759px) {
  .reelrow { grid-auto-columns: 86%; }
  .reelrow li { min-width: 0; }
  .reelrow video { aspect-ratio: auto; height: calc((100vw - 2 * var(--gut)) * .86 * .5625); }
}

/* ══════════════════════════════════════ ROUND 17 · THOMAS'S iPHONE, AGAIN ══
   Thomas checked R15/R16 on his own iPhone (Safari, 390pt): "you fixed the
   spacing … but keep the cool animation", "the line animation is gone as
   well", "padel is a 4 block court", "the footer and the thing just above it
   is too similar", "the drop down looks shit", "fix the filter thing here",
   and a photo instead of the empty court on About.
   Thomas also: "make sure the desktop version doesn't change dramatically".
   So EVERY rule here that is not the footer, the court geometry or the About
   photo sits inside `max-width: 759px`. At 760px and up those three are the
   only differences (compared screen by screen at 1280 and 1440 before
   commit — see docs/ROUND-12.md, Round 17). The R15 house rules hold: explicit
   boxes, no preserve-3d on a phone, svh/dvh with fallbacks, min-width: 0. */

/* ── R17 A · sponsors on a phone: a 2D marquee, not a 3D ring ───────────
   R15 B stopped the overlap by making the ring a still 2x2 grid; Thomas wants
   the motion back. The ring itself cannot come back on a phone: Safari
   flattens preserve-3d under overflow/filter/opacity (R15 B). So this is
   motion WITHOUT 3D: the eight marks build.mjs already writes (four sponsors,
   then the same four again, aria-hidden and out of the tab order) sit in one
   flat row of identical 150x84 boxes, 16px apart, and the row slides left by
   exactly half its own width, then starts again — the second four are where
   the first four were, so the loop has no seam and no gap. One CSS keyframe
   on one element, translate3d only: the compositor runs it, no JS, nothing
   per frame on the main thread (home.js's ring does not run below 760px).
   Every box is the same width, so nothing can ever overlap, and the strip is
   84px tall with no stage under it, so there is no empty band. The edges
   fade with a mask (prefixed for Safari) so a logo slides in rather than
   being cut by the screen edge. A finger on it or keyboard focus in it holds
   it still. Reduced motion: the still R15 grid instead. */
@media (max-width: 759px) and (prefers-reduced-motion: no-preference) {
  .ring {
    perspective: none; height: auto; cursor: auto; touch-action: pan-y;
    margin-top: 28px; padding: 6px 0;
    overflow: hidden;   /* `clip` is iOS 16+; hidden clips the same here */
    -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 11%, #000 89%, transparent 100%);
            mask-image: linear-gradient(90deg, transparent 0, #000 11%, #000 89%, transparent 100%);
  }
  .ring__track {
    display: flex; width: max-content; height: auto;
    transform-style: flat;
    animation: r17-marquee 26s linear infinite;
    will-change: transform;
  }
  .ring__track > * {
    position: static; flex: 0 0 auto;
    width: 150px; height: 84px; min-width: 0; margin: 0 16px 0 0; padding: 10px 12px;
    /* whatever home.js may have written on a wider screen earlier */
    transform: none !important; opacity: 1 !important; filter: none !important;
    pointer-events: auto !important;
    backface-visibility: visible; -webkit-backface-visibility: visible;
  }
  /* sized before the files land (the R13/R15 trick): no width jump on load */
  .ring__track img { height: 1000px; width: auto; max-width: 100%; max-height: 38px; }
  .ring__track [data-sp="diadem"] img { max-height: 58px; }
  .ring__track [data-sp="verbe"] img { max-height: 32px; }
  .ring:hover .ring__track,
  .ring:focus-within .ring__track,
  .ring:active .ring__track { animation-play-state: paused; }
}
/* -50% = exactly one set of four (4 x (150 + 16)px), because every box and
   every gap is identical — that is what makes the loop seamless */
@keyframes r17-marquee {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(-50%, 0, 0); }
}

/* ── R17 B · the photo trail on a phone, over the grid ──────────────────
   home.js (phoneTrail) adds .pics-live to the grid's wrapper and a stage
   inside it. The stage takes no room (absolute, inset 0), cannot be touched
   (pointer-events none, so links and scrolling are untouched) and clips its
   photographs to the grid's own box, so a photo near the edge can never push
   the page sideways. The six grid photos stay where they are underneath: at
   rest the section is full, the trail only ever adds to it. */
@media (max-width: 759px) {
  .pics-live { position: relative; }
  .pics__stage {
    position: absolute; inset: 0; z-index: 2; pointer-events: none;
    overflow: hidden; contain: layout paint;
  }
  /* over photographs rather than white, so each one is a print: a white
     edge and a deeper shadow make it read as a card dropped on the grid */
  .pics__ph {
    left: 0; top: 0; width: 112px; height: 150px; margin: -75px 0 0 -56px;
    border: 3px solid #fff; box-shadow: 0 16px 34px -10px rgba(10, 28, 43, .6);
  }
}

/* ── R17 C · the court is a padel court ─────────────────────────────────
   court.svg is now 20m x 10m to scale with the net across the middle and
   four service boxes (see the comment there). The CSS stagger only ever
   addressed lines by position, and there are three lines now. The rally's
   ball is moved by home.js; the old idle "pulse" scaled it about a fixed
   point (200,100), which on a moving ball read as a jump, so the rally ball
   does not pulse. */
.court--rally .court__ball { animation: none; }
.court__net { stroke-linecap: round; }

/* ── R17 D · the footer: dark navy, clearly not the band above it ───────
   Thomas: "the footer and the thing just above it is too similar: make the
   footer the darker blue colour, but then make the text available to see,
   and their logo". R7 made it the palest surface on the site, which next to
   the light-blue "Join the community" band read as one block. It is now
   their own --navy (#123A52, the deeper blue in their mark, already the
   PREVIEW notice and the chosen-filter fill), at every width.
   Every colour measured on #123A52 (WCAG relative luminance):
     body text  #E6F0F6  10.36:1      headings #FFFFFF  11.98:1
     links      #8FEAF7   8.72:1      small print / preview #C7DCE8  8.46:1
   Links stay underlined (they are links, not decoration); hover and focus
   go white with a heavier underline, and keyboard focus gets the ball-yellow
   ring (#D7E84B, 8.86:1 against the navy) so it is seen on dark.
   The logo is their full-colour mark, which is drawn for a dark ground:
   white lettering with a dark keyline and the cyan swoosh, so it now reads
   better than it did on the ice ground. No plate, no new artwork. The laptop
   court lines and the outline word are kept but redrawn for dark: the court
   lines at 11% white and the word in cyan at 9%, ground, not picture. */
.footer--shot {
  background: var(--navy);
  border-top: 1px solid #0B2C40;
  color: #E6F0F6;
}
.footer--shot .footer__h { color: #FFFFFF; --on-dark: 11.98; }
.footer--shot .footer__who,
.footer--shot .footer__links,
.footer--shot .footer__base { color: #E6F0F6; --on-dark: 10.36; }
.footer--shot .footer__links a,
.footer--shot .footer__base a {
  color: #8FEAF7; --on-dark: 8.72;
  text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 3px;
}
.footer--shot .footer__links a:hover,
.footer--shot .footer__links a:focus-visible,
.footer--shot .footer__base a:hover,
.footer--shot .footer__base a:focus-visible { color: #FFFFFF; text-decoration-thickness: 2.5px; }
.footer--shot a:focus-visible { outline: 3px solid var(--ball); outline-offset: 3px; border-radius: 3px; }
.footer--shot .footer__base { border-top-color: rgba(255, 255, 255, .16); }
.footer--shot .footer__preview { color: #C7DCE8; --on-dark: 8.46; }
.footer--shot .footer__preview strong { color: #FFFFFF; }
.footer--shot .footer__court { opacity: .11; }
.footer--shot .footer__word text { stroke: #14DCF5; opacity: .09; }
/* the logo file is cut to the artwork's edges (the old one was half empty
   margin, so at 52px tall the mark itself was about 25px) */
.footer--shot .footer__logo { height: 64px; width: auto; border-radius: 0; margin-bottom: 16px; }

/* ── R17 E · About: a photograph where the empty court was ──────────────
   The box is explicit (R15 house rule): the <picture> is a height-0 padding
   box, 16:9 on a phone (the photo's own shape, so nothing is cropped) and
   21:9 on a laptop, and the image fills it absolutely. The crop keeps his
   face and the handshake in frame at 21:9. */
.aboutpic { margin: clamp(30px, 5vw, 48px) auto 0; max-width: 940px; }
.aboutpic picture {
  position: relative; display: block; height: 0; padding-bottom: 56.25%;
  border-radius: var(--rad); overflow: hidden; background: var(--surface-2);
}
@media (min-width: 900px) { .aboutpic picture { padding-bottom: 42.857%; } }
.aboutpic img {
  position: absolute; top: 0; left: 0; width: 100%; height: 100%; max-width: none;
  object-fit: cover; object-position: 50% 32%; display: block;
}

/* ── R17 F · the phone menu: a full screen of solid navy ────────────────
   Thomas: "the drop down looks shit". It was 97% of the page colour (so the
   page showed through on Safari, which also ignores an unprefixed
   backdrop-filter), sticky, and only as tall as its links, so the page went
   on below it. Below 760px it is now fixed under the bar to the bottom of the
   screen (100dvh, 100vh first for older Safari, plus the home-indicator
   inset), opaque --navy, white 30px links on 60px rows with dividers that
   read on navy, the current page in the brand cyan (7.18:1 on navy), and the
   footer's two ways to get in touch underneath. It fades and drops in by
   10px over .22s (transform and opacity). The bar stays on top, so CART and
   the X stay where they were and as readable. app.js pins the page behind it
   (body fixed at -scrollY; Safari ignores overflow:hidden for touch). The
   760-899px panel is unchanged. */
.menu__more { display: none; }
@media (max-width: 759px) {
  body.menu-open { position: fixed; left: 0; right: 0; width: 100%; overflow: hidden; }
  body.menu-open .bar { position: fixed; top: 0; left: 0; right: 0; background: var(--bg); border-bottom-color: var(--line); }
  .menu {
    position: fixed; z-index: 59; left: 0; right: 0; bottom: 0;
    top: var(--bar-h, 64px);
    height: calc(100vh - var(--bar-h, 64px));
    height: calc(100dvh - var(--bar-h, 64px));
    background: var(--navy); border: 0;
    overflow-y: auto; overscroll-behavior: contain; -webkit-overflow-scrolling: touch;
    padding-bottom: calc(28px + env(safe-area-inset-bottom));
    opacity: 0; transform: translate3d(0, -10px, 0);
    transition: opacity .22s var(--ease), transform .22s var(--ease);
  }
  .menu.is-open { opacity: 1; transform: none; }
  .menu nav { display: grid; padding: 6px var(--gut) 0; }
  .menu nav a {
    display: flex; align-items: center; min-height: 60px;
    padding: 4px 0;
    color: #FFFFFF; --on-dark: 11.98;
    font-size: 30px; letter-spacing: .03em;
    border-bottom: 1px solid rgba(255, 255, 255, .14);
  }
  .menu nav a:last-child { border-bottom: 1px solid rgba(255, 255, 255, .14); }
  .menu nav a[aria-current="page"] { color: var(--brand); --on-dark: 7.18; }
  /* outside the row, into the gutter, so it never touches the word */
  .menu nav a:focus-visible { outline: 3px solid var(--ball); outline-offset: 4px; border-radius: 6px; }
  .menu__more { display: block; padding: 28px var(--gut) 0; }
  .menu__h {
    margin: 0 0 6px; color: #C7DCE8; --on-dark: 8.46;
    font: 900 13px/1 "Barlow Condensed", 'Barlow Condensed Fallback', sans-serif; letter-spacing: .14em; text-transform: uppercase;
  }
  .menu__links { margin: 0; font-size: 17px; line-height: 2.3; }
  .menu__links a { color: #8FEAF7; --on-dark: 8.72; text-decoration: underline; text-underline-offset: 3px; }
  .menu__links a:focus-visible { outline: 3px solid var(--ball); outline-offset: 2px; border-radius: 3px; }
}
@media (max-width: 759px) and (prefers-reduced-motion: reduce) {
  .menu { transition: none; transform: none; }
}

/* ── R17 G · the shop's sections and filter on a phone ──────────────────
   Thomas: "fix the filter thing here". At 390 the tabs were 46px pills with
   18px of padding and a 2px border, so two and a half fitted and the third
   ("Activewear  Soon", dashed) was cut by the edge; under them a 52px FILTER
   button wore a "0" — app.js does hide the badge when nothing is chosen, but
   `.shopbar__badge { display: inline-grid }` beat the `hidden` attribute.
   Now: one row of compact tabs (44px tap height, 12px padding, 14px label,
   counts as plain small numbers) that snaps and fades at the right edge to
   say there is more; a "Soon" section is quiet (muted, solid hairline, a
   small "Soon"), not dashed; then ONE line with the count on the left and a
   compact Filter button on the right, the badge only when a filter is on. */
@media (max-width: 759px) {
  .cattabs { margin: 0 0 4px; }
  .cattabs__row {
    gap: 6px; padding: 2px var(--gut) 8px; scroll-snap-type: x mandatory;
    -webkit-mask-image: linear-gradient(90deg, #000 calc(100% - 44px), transparent);
            mask-image: linear-gradient(90deg, #000 calc(100% - 44px), transparent);
  }
  .cattab {
    min-height: 44px; padding: 0 13px; gap: 6px; border-width: 1px;
    font-size: 14px; letter-spacing: .06em;
  }
  .cattab .chip__n {
    min-width: 0; height: auto; padding: 0; background: none; color: var(--mute);
    font: 700 12px/1 Nunito, 'Nunito Fallback', sans-serif;
  }
  .cattab[aria-pressed="true"] .chip__n { background: none; color: #CFE3EE; }
  .cattab--soon { border-style: solid; color: var(--mute); }
  .cattab--soon .chip__n {
    background: none; padding: 0; color: var(--mute);
    font: 800 10.5px/1 Nunito, 'Nunito Fallback', sans-serif; letter-spacing: .06em; text-transform: uppercase;
  }
  .shopbar { flex-wrap: nowrap; gap: 10px; margin-bottom: 14px; }
  .shopbar__count { order: 1; flex: 1 1 auto; min-width: 0; font-size: 14px; }
  .shopbar__clear { order: 2; margin-left: 0; flex: 0 0 auto; }
  .shopbar__open {
    order: 3; flex: 0 0 auto; min-height: 44px; padding: 0 16px; gap: 7px; font-size: 13px;
  }
  .shopbar__badge[hidden] { display: none; }
}
/* and the products start higher: on the shop only, on a phone, the heading
   block's top padding is halved and the gap to the tabs tightened, so the
   first two products are on the first screen without scrolling */
@media (max-width: 759px) {
  body[data-page="shop"] .top { padding-top: var(--sec-half); }
  body[data-page="shop"] .top + .tight { padding-top: 20px; }
}

/* ═══════════════════════ ROUND 18 · CODES AND VERBE'S STOCK (17 OCTOBER) ═════
   R18 A · The cart's discount code box: one quiet line until it is used.
   16px text (iOS does not zoom on focus), 48px tall field and button, the
   button never narrower than its word. Same tokens as the rest of the form. */
.codebox { margin: 0 0 14px; }
.codebox label { font-size: 12px; font-weight: 800; color: var(--mute); display: block; margin-bottom: 5px; }
.codebox__row { display: flex; gap: 8px; }
.codebox__row input {
  flex: 1 1 auto; min-width: 0; min-height: 48px; padding: 11px 13px;
  background: var(--surface); border: 1px solid var(--line-strong); border-radius: 11px;
  color: var(--ink); font: 400 16px/1.3 Nunito, 'Nunito Fallback', sans-serif;
  text-transform: uppercase; letter-spacing: .04em;
}
.codebox__row input::placeholder { text-transform: none; letter-spacing: 0; color: var(--mute); }
.codebox__row input:focus { border-color: var(--brand-deep); outline: none; box-shadow: 0 0 0 3px var(--brand-20); }
.codebox__row input[aria-invalid="true"] { border-color: var(--danger); }
.codebox__row .btn { flex: 0 0 auto; min-height: 48px; padding-inline: 18px; }
.codebox__on { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 10px; margin: 0; font-size: 13.5px; color: var(--ink); }
.codebox__on .linkish { margin-left: auto; min-height: 44px; font-size: 13.5px; }
.codebox__tag {
  font: 800 13px/1 Nunito, 'Nunito Fallback', sans-serif; letter-spacing: .06em;
  padding: 7px 10px; border-radius: 8px; background: var(--brand-10); border: 1px dashed var(--brand-deep); color: var(--ink);
}
.codebox__msg { margin: 6px 0 0; font-size: 13px; line-height: 1.4; }
.codebox__msg:empty { display: none; }
.codebox__msg.is-bad { color: var(--danger); font-weight: 700; }
.codebox__msg.is-ok { color: var(--brand-deep); font-weight: 700; }
.totals .totals__off { color: var(--brand-deep); font-weight: 700; }
.totals .totals__off small { font-weight: 800; letter-spacing: .04em; }

/* R18 B · A size Verbe have sold out of: crossed out, greyed, not choosable.
   The line is drawn as well as the text struck, so it reads at a glance on a
   phone in sunlight; the "sold out" words are in the label for screen readers. */
.sizepick__s.is-out input { cursor: not-allowed; }
.sizepick__s.is-out span {
  position: relative; overflow: hidden;
  color: var(--mute); background: var(--surface-2, #f3f4f6); border-style: dashed;
  text-decoration: line-through; text-decoration-thickness: 2px;
}
.sizepick__s.is-out span::after {
  content: ""; position: absolute; left: -10%; right: -10%; top: 50%; height: 1.5px;
  background: color-mix(in srgb, var(--mute) 70%, transparent); transform: rotate(-24deg);
}
.sizepick__s.is-out input:hover + span { border-color: var(--line-strong); }

/* R18 C · /order-received: the code, as one more line under what was ordered. */
.receipt__lines .receipt__code { border-style: dashed; color: var(--brand-deep); }

/* About · Our courts (James, 18 Oct) */
.courts { clear: both; padding-top: clamp(40px, 7vw, 72px); }
.courts .eyebrow { position: static; margin: 0; }
.courts__list { list-style: none; margin: 12px 0 0; padding: 0; display: grid; gap: 10px; grid-template-columns: 1fr; }
@media (min-width: 680px) { .courts__list { grid-template-columns: repeat(4, 1fr); } }
.courts__list li { background: var(--surface); border: 1px solid var(--line); border-radius: var(--rad, 14px); padding: 16px 18px; display: flex; flex-direction: column; gap: 4px; }
.courts__list strong { font-family: var(--display, inherit); text-transform: uppercase; letter-spacing: .02em; font-size: 1.15rem; color: var(--ink); }
.courts__list span { color: var(--mute); font-size: .92rem; }

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

/* George, 18 Oct: the hero menu (his old site), in SmashIT's own display type.
   Each word slides in from the left out of a soft blur, one after another, once
   the intro ball lands. Hover: the word shifts right, a cyan bar draws in front
   of it and its number lights up. */
.heromenu { display: flex; flex-direction: column; align-items: flex-start; gap: clamp(0px, .6vh, 8px); counter-reset: hm; }
.heromenu a {
  position: relative; display: inline-flex; align-items: baseline; gap: .45em;
  color: #fff; text-decoration: none;
  font-family: "Barlow Condensed", 'Barlow Condensed Fallback', "Arial Narrow", "Helvetica Neue Condensed", "Roboto Condensed", sans-serif-condensed, sans-serif;
  font-weight: 900; text-transform: uppercase; letter-spacing: .01em; line-height: .98;
  font-size: clamp(38px, 5.4vw, 84px); text-shadow: 0 2px 30px rgba(0,0,0,.45);
  opacity: 0; transform: translateX(-46px); filter: blur(8px);
  transition: opacity .7s ease, transform .8s cubic-bezier(.16,.84,.3,1), filter .7s ease, color .25s, padding .35s cubic-bezier(.2,.7,.3,1);
  transition-delay: calc(.15s + var(--i) * 110ms), calc(.15s + var(--i) * 110ms), calc(.15s + var(--i) * 110ms), 0s, 0s;
}
.heromenu a::before {
  counter-increment: hm; content: "0" counter(hm);
  font-size: .26em; font-weight: 700; letter-spacing: .12em; color: var(--brand); --on-dark: 11.30;
  opacity: .75; transform: translateY(-1.6em); transition: opacity .25s;
}
.heromenu a::after {
  content: ""; position: absolute; left: 0; bottom: .08em; height: .07em; width: 100%;
  background: var(--brand); transform: scaleX(0); transform-origin: left;
  transition: transform .45s cubic-bezier(.2,.7,.3,1);
}
.hero4__copy:not(.is-waiting) .heromenu a { opacity: 1; transform: none; filter: none; }
.heromenu a:hover, .heromenu a:focus-visible { color: var(--brand); --on-dark: 11.30; padding-left: .25em; }
.heromenu a:hover::before, .heromenu a:focus-visible::before { opacity: 1; }
.heromenu a:hover::after, .heromenu a:focus-visible::after { transform: scaleX(1); }
.heromenu:hover a:not(:hover) { color: rgba(255,255,255,.55); }
@media (max-width: 759px) {
  .hero4__copy { text-align: center; }
  .heromenu { align-items: center; margin-top: 16vh; gap: 2px; }
  .heromenu a { font-size: clamp(34px, 11vw, 52px); transform: translateY(22px); }
  .heromenu a::before { display: none; }
  .heromenu a::after { transform-origin: center; }
}
@media (prefers-reduced-motion: reduce) { .heromenu a { opacity: 1; transform: none; filter: none; transition: color .2s; } }

/* Thomas, 18 Oct: About photos must fit and look nice. Every frame is a fixed,
   even box (no stretching to the row height, which pushed captions under the next
   row), cropped from the top third where the faces are, with room for its caption. */
.edit__hero, .edit__pair figure { display: flex; flex-direction: column; }
.edit img { height: auto; }
.edit__hero img { aspect-ratio: 16 / 9; object-position: 50% 30%; }
@media (min-width: 900px) { .edit__hero img { aspect-ratio: 2 / 1; } }
.edit__pair { align-items: start; row-gap: clamp(26px, 4vw, 44px); }
@media (min-width: 760px) { .edit__pair, .edit__pair--wide { grid-template-columns: 1fr 1fr; } }
.edit__pair img { aspect-ratio: 4 / 3; object-position: 50% 28%; }
.edit figcaption { margin-top: 12px; }
@media (min-width: 980px) { .venues { grid-template-columns: repeat(4, 1fr); } }
.edit__hero img { object-position: 50% 92%; }
/* Thomas, 18 Oct: the wall always ends on a full row, never a lone photo. 19 photos:
   18 on phones (2 and 3 across), 15 on laptops (5 across). */
.photowall__t:nth-child(n+19) { display: none; }
@media (min-width: 900px) { .photowall__t:nth-child(n+16) { display: none; } }
.photowall__t img { object-position: 50% 30%; }

/* the closed cart can never peek or be tabbed into */
.sheet:not(.open) { visibility: hidden; transition: transform .36s var(--ease), visibility 0s linear .36s; }
.sheet.open { visibility: visible; transition: transform .36s var(--ease); }
/* "Be first to know" is white type: on the Tournaments page it sat on the light
   page and vanished. Its own dark navy ground, so it reads wherever it is placed. */
.nextup { background: #0A1C2B; border-color: rgba(255,255,255,.12); }
/* the phone filter sheet, closed, can never peek either */
@media (max-width: 759px) {
  .filtersheet:not(.open) { visibility: hidden; transition: transform .34s var(--ease), visibility 0s linear .34s; }
  .filtersheet.open { visibility: visible; }
}

/* QA 18 Oct · the About hero spans the same column as the pairs under it
   (it was capped at 940px and sat off to the left on a laptop). */
.edit__hero { max-width: none; margin-inline: 0; }
.edit__hero figcaption { margin-inline: 0; }

/* QA 18 Oct · "the writing is blurry". The header logo was a 240x164 file
   whose mark filled only 170x78 of it, so at 44px the logo was ~21px tall and
   2-3x enlarged on a phone: unreadable. It is now SmashIT's own original
   (brand/SmashIT logo no back.png) cropped to the mark, so the height here IS
   the logo's height. The intro's fallback logo gets the same, at 960px. */
.bar__home picture { display: block; }
.bar__logo { height: 34px; width: auto; display: block; border-radius: 0; }
@media (min-width: 900px) { .bar__logo { height: 40px; } }
.introv--logo .introv__logo { width: min(64vw, 380px); }

/* QA 18 Oct · home shop rail on white, as on the shop. The pale-blue frame
   with 10px of padding made every white-ground product photo a box in a box. */
.mix__shot { background: #fff; }
.mix__shot > img { padding: 6px; }

/* QA 18 Oct · spacing. Two sections on the same ground each brought a full
   --sec of padding, so the gap between them read 200-240px on a laptop and
   130-160px on a phone (measured) where a change of ground reads right. The
   second of each measured pair now opens with less. */
body[data-page="home"] .sponsors5 + .mixsec,
body[data-page="home"] .trailsec + section:not([class]),
body[data-page="about"] section:not([class]) + .courtsec:not(.courtsec--dark),
body[data-page="about"] .courtsec:not(.courtsec--dark) + .story,
body[data-page="about"] .story + .edit,
body[data-page="community"] section:not([class]) + .wallsec,
body[data-page="sponsors"] .sponsors5 + .tight { padding-top: calc(var(--sec) * .35); }

/* QA 18 Oct · Tournaments top: the poster was an 819x1024 PORTRAIT photo
   covering a 2.4:1 banner, enlarged ~3.5x on a laptop and cropped to two cap
   brims. It is now a sharp 1280x720 frame of the film itself, and the film is
   framed from 30% down so heads stay in the banner. */
.tophero__v { object-position: 50% 20%; }

/* George, 18 Oct 14:16-14:23 · the hero menu, small and simple. His old site:
   their white logo, then a short plain list. No numbers, no big condensed
   capitals, no blur. Laptop: on the left, over the dark side of the photo
   (the ball on the right stays clear). Phone: centred, as in his picture. */
.hero4__copy { display: flex; flex-direction: column; align-items: flex-start; padding-top: clamp(40px, 12vh, 130px); }
.herologo { display: block; width: clamp(200px, 22vw, 320px); margin: 0 0 clamp(26px, 4vh, 40px); }
.herologo img { display: block; width: 100%; height: auto; }
.heromenu { gap: clamp(10px, 1.6vh, 16px); margin-top: 0; }
.heromenu a {
  font-family: Nunito, 'Nunito Fallback', sans-serif; font-weight: 600;
  text-transform: none; letter-spacing: .01em; line-height: 1.2;
  font-size: clamp(18px, 1.5vw, 22px); text-shadow: 0 1px 12px rgba(0,0,0,.5);
  gap: 0; transform: translateY(10px); filter: none;
  transition: opacity .6s ease, transform .7s cubic-bezier(.16,.84,.3,1), filter 0s, color .2s, padding 0s;
}
.heromenu a::before { display: none; }
.heromenu a::after { bottom: -.12em; height: 2px; }
.heromenu a:hover, .heromenu a:focus-visible { padding-left: 0; }
.herologo { opacity: 0; transform: translateY(10px); transition: opacity .7s ease .05s, transform .8s cubic-bezier(.16,.84,.3,1) .05s; }
.hero4__copy:not(.is-waiting) .herologo { opacity: 1; transform: none; }
@media (max-width: 759px) {
  .hero4__copy { align-items: center; padding-top: clamp(48px, 13vh, 120px); }
  .herologo { width: min(66vw, 280px); }
  .heromenu { margin-top: 0; gap: 14px; }
  .heromenu a { font-size: 20px; transform: translateY(10px); }
}
@media (prefers-reduced-motion: reduce) { .herologo, .heromenu a { opacity: 1; transform: none; transition: color .2s; } }

/* QA 18 Oct · on the home page the "Be first to know" card is half width, and
   three columns squeezed "Join the WhatsApp group" onto three lines. One step
   per line there; the full-width card on /tournaments keeps three columns. */
@media (min-width: 1100px) { .arena .howto { grid-template-columns: 1fr; gap: 12px; } }

/* QA 18 Oct · the About lead photo is now theirs/under-lights.webp (the old one was
   fence and sky): a wide laptop crop, held on the player. */
@media (min-width: 900px) { .edit__hero img { aspect-ratio: 5 / 2; object-position: 50% 45%; } }
.edit__hero img { object-position: 50% 45%; }

/* QA 18 Oct · the phone wall ended on its two weakest frames (a dark
   silhouette and a reflection in the glass). 16 is eight full rows of two;
   the laptop already shows 15 (three rows of five). */
.photowall__t:nth-child(n+17) { display: none; }

/* George, 18 Oct 14:52 (laptop only; the phone is approved as it is): "remove
   SmashIT logo and make the tabs bigger but keep the same style, so it takes up
   more space". Same plain words, larger, on the dark left of the photo. */
@media (min-width: 760px) {
  .herologo { display: none; }
  .hero4__copy { justify-content: center; min-height: calc(100svh - 90px); padding-top: 0; }
  .heromenu { gap: clamp(6px, 1.4vh, 14px); }
  .heromenu a { font-size: clamp(34px, 3.6vw, 56px); font-weight: 600; letter-spacing: 0; line-height: 1.12; }
  .heromenu a::after { height: 3px; bottom: -.02em; }
}

/* Thomas, 18 Oct: "on desktop space it out more" — more air between the words. */
@media (min-width: 760px) { .heromenu { gap: clamp(18px, 3.2vh, 34px); } }
