/* ============================================================
   Melbourne Punters Cricket Club
   Design system: "Form Guide"
   The club is called Punters, so the visual language is borrowed
   from the bookmaker's form guide and the suburban manual
   scoreboard: slat-textured boards, saddlecloth numbers, tabular
   mono numerals, and a six-box form line that encodes real
   recent-performance data.
   ============================================================ */

/* ---------- 1. Tokens ---------- */
:root {
  /* Prahran CC's own "True Blues" navy, taken from their site rather
     than lifted off it. */
  --pitch:      #002E5F;
  --pitch-2:    #073C74;
  --pitch-3:    #0F4E90;
  --pitch-line: #1C5C9C;
  /* Deep navy for text sitting on a light or amber fill. */
  --pitch-deep: #001E40;

  /* Paper: the cool light grey Prahran pairs with the navy. */
  --paper:     #F2F4F7;
  --paper-2:   #E8EAEC;
  --paper-line:#CFD6DF;

  /* Two tokens the theme switch turns on. --pitch and --chalk each did
     double duty — navy was both a section background and the body text
     colour, chalk both a card surface and the text on navy — so neither
     could be flipped without taking the other meaning with it.
     --ink is text on paper; --card is a raised surface on paper. */
  --ink:       #002E5F;
  --card:      #F7F9FC;

  /* Text on navy. Navy sections look the same in both themes, so this
     one does not move. */
  --chalk:     #F7F9FC;
  --ash:       #A3BBD4;
  /* Checked against --paper-2 (#E4E8ED), not just --paper: the
     scorebook-grid sections are the darker of the two, and a value
     tuned only to --paper left every table header at 4.29:1. */
  --ash-deep:  #4C6580;

  /* Accents taken from the club crest: crimson, orange and cyan.
     --claret/--amber keep their names so nothing downstream renames;
     the values are now the crest's own red and orange, deepened just
     enough to hold 4.5:1 against the paper and pitch backgrounds. */
  --claret:    #C41E32;
  /* Claret as TEXT on paper. The fill above keeps its value in both
     themes so white-on-claret buttons hold 5.5:1. */
  --claret-ink:#C41E32;
  --claret-lo: #A0182A;
  /* Crest orange and cyan, each lifted just enough to clear 4.5:1 on
     the lighter navy — at their raw crest values they measured 4.36:1
     and 4.24:1 and would have failed. */
  --amber:     #FF9130;
  --amber-lo:  #E0741B;
  --cyan:      #22BFD6;
  --cyan-lo:   #0F9DB4;
  /* Cards sit on --pitch-2, a lighter navy than the section behind
     them, so small amber text on a card needs lifting again: --amber
     measures 4.12:1 there against 5.03:1 on the section. */
  --amber-raised: #FFA751;

  /* Two families, not three. Archivo carries the headlines; Inter does
     everything else including figures — its tabular numerals align as
     well as a monospace without the technical noise. --mono is kept as
     a token name so label rules downstream don't all need renaming. */
  /* Prahran sets headings in Stag and everything else in Proxima Nova.
     Both are licensed Typekit faces we cannot embed, so these are the
     closest freely-licensed equivalents: Roboto Slab for the slab
     headings, Mulish for the humanist-geometric text face. */
  --display: "Roboto Slab", Rockwell, Georgia, serif;
  --body:    "Mulish", system-ui, -apple-system, sans-serif;
  --mono:    "Mulish", system-ui, -apple-system, sans-serif;

  --sp-section: clamp(4.25rem, 7.5vw, 8.5rem);
  /* The gutter scales with the viewport rather than sitting at a fixed
     28px, so the margin grows in proportion on a big screen instead of
     the content running hard to the edge. The max-width is set high
     enough that it only engages on very large displays — it stops the
     page becoming a 2500px-wide slab without ever making it a narrow
     centred column. */
  --gutter: clamp(14px, 2.2vw, 38px);
  --maxw: 2040px;
  /* Prose still needs a readable measure even when the page is wide. */
  --measure: 62ch;
  --r: 4px;

  --ease: cubic-bezier(.22, .61, .36, 1);

  /* ---- Elevation ----------------------------------------------------
     One scale, used everywhere, instead of the four hand-rolled shadows
     this file had grown. Two layers each: a tight contact shadow that
     sits the element on the page, and a wide ambient one that gives it
     height. A single blur reads as a smudge; the pair reads as depth.

     Tinted with the club navy rather than black — a black shadow over
     a blue-grey page goes muddy, and the tint is what makes it look
     considered rather than default. */
  --e1: 0 1px 2px rgba(0,30,64,.05),  0 1px 3px rgba(0,30,64,.07);
  --e2: 0 2px 4px rgba(0,30,64,.05),  0 5px 12px rgba(0,30,64,.09);
  --e3: 0 4px 8px rgba(0,30,64,.06),  0 14px 28px rgba(0,30,64,.11);
  --e4: 0 8px 16px rgba(0,30,64,.08), 0 28px 56px rgba(0,30,64,.16);
  /* The hairline that separates a raised surface from its own shadow. */
  --lip: inset 0 1px 0 rgba(255,255,255,.75);
}

/* ---------- 1b. Dark theme ----------
   Only the paper surfaces turn over. The navy bands — hero, page
   heads, on-pitch sections, footer — are the club's colour and stay
   exactly as they are, which keeps the site recognisably itself and
   means every contrast figure already measured against navy still
   holds. What changes is the light half of the page.

   The accents are re-tuned rather than reused: claret measures 3.1:1
   on the dark paper and would fail on every eyebrow. */
[data-theme="dark"] {
  --paper:      #061523;
  --paper-2:    #0B1F31;
  --paper-line: #17364F;
  --card:       #0E2438;
  --ink:        #E7EEF6;
  --ash-deep:   #9FB6CE;
  --claret-ink: #F2647A;
  --claret-lo:  #F2647A;
  --amber-lo:   #FFA751;
  --cyan-lo:    #4FD4E8;
  /* A navy-tinted shadow disappears on a dark surface. Dark mode goes
     back to black, and deeper, because there is less tonal room. */
  --e1: 0 1px 2px rgba(0,0,0,.30), 0 1px 3px rgba(0,0,0,.24);
  --e2: 0 2px 4px rgba(0,0,0,.30), 0 5px 12px rgba(0,0,0,.30);
  --e3: 0 4px 8px rgba(0,0,0,.32), 0 14px 28px rgba(0,0,0,.36);
  --e4: 0 8px 16px rgba(0,0,0,.36), 0 28px 56px rgba(0,0,0,.46);
  --lip: inset 0 1px 0 rgba(255,255,255,.06);
}
/* The switch itself should not animate the whole page. */
@media (prefers-reduced-motion: no-preference) {
  body, .section, .card, .foot { transition: background-color .3s var(--ease), color .3s var(--ease); }
}

/* Every figure on the site is tabular, so columns of numbers line up
   the way a monospace would without importing a third family. */
.tbl, .num, .stat__v, .plate__v, .tally__v, .fixture__day,
.ranked__v, .bline__key b, .player__cap, .board__foot b {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

/* ---------- 2. Base ---------- */
*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--body);
  font-size: clamp(1rem, .96rem + .2vw, 1.075rem);
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

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

::selection { background: var(--amber); color: var(--pitch-deep); }

:focus-visible {
  outline: 3px solid var(--amber);
  outline-offset: 3px;
  border-radius: 2px;
}

.skip {
  position: absolute; left: -9999px; top: 0; z-index: 200;
  background: var(--amber); color: var(--pitch-deep);
  padding: .75rem 1.25rem; font-weight: 700;
}
.skip:focus { left: 1rem; top: 1rem; }

/* ---------- 3. Typography ---------- */
.d1, .d2, .d3, .d4 {
  font-family: var(--display);
  font-weight: 800;
  font-stretch: 105%;          /* Archivo is variable-width; a touch expanded reads confident */
  line-height: .95;
  letter-spacing: -.008em;     /* a slab needs far less negative tracking than Archivo did */
  text-transform: uppercase;
  margin: 0;
}
.d1 { font-size: clamp(2.9rem, 8.6vw, 7.6rem); }
.d2 { font-size: clamp(2.05rem, 5vw, 4rem); }
.d3 { font-size: clamp(1.6rem, 3vw, 2.5rem); }
.d4 { font-size: clamp(1.05rem, 1.6vw, 1.35rem); line-height: 1.1; letter-spacing: -.015em; }
/* For headings with a long unbreakable word — "ACKNOWLEDGEMENT" is 15
   characters and overruns a split column at full .d2 size. */
.d2--long { font-size: clamp(1.5rem, 3.1vw, 2.5rem); }

.eyebrow {
  font-family: var(--mono);
  font-size: .7rem;
  font-weight: 600;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--claret-ink);
  margin: 0 0 1.1rem;
  display: flex;
  align-items: flex-start;
  gap: .7rem;
}
.eyebrow::before {
  content: "";
  width: 26px; height: 2px;
  background: var(--claret);
  flex: none;
  /* Sits on the first line's optical centre, so a wrapped label
     doesn't leave the rule floating between two lines. */
  margin-top: .52em;
}
.on-pitch .eyebrow { color: var(--amber); }
.on-pitch .eyebrow::before { background: var(--amber); }
.on-pitch .card .eyebrow,
.on-pitch .partner__tier { color: var(--amber-raised); }
.on-pitch .card .eyebrow::before { background: var(--amber-raised); }

.lede {
  font-size: clamp(1.1rem, 1.6vw, 1.3rem);
  line-height: 1.5;
  max-width: var(--measure);
  color: var(--ash-deep);
}
/* Long-form body copy keeps its measure on a wide page. */
.prose p { max-width: var(--measure); }
.on-pitch .lede { color: var(--ash); }
/* The hero is .hero, not .section.on-pitch, so it never picked up the
   light override above and was rendering the dark-background grey on a
   dark background — 2.82:1. */
.hero .lede { color: #C2D5EA; }
/* This one line of small type was capping how much photograph the
   whole left side could show: amber needs a near-solid scrim to hold
   4.5:1, and every lift that made the photo visible broke it first.
   Chalk holds 6.6:1 at a scrim light enough to show the picture. The
   claret rule before it still marks it as an eyebrow. */
.hero .eyebrow { color: var(--chalk); }

.num { font-family: var(--mono); font-variant-numeric: tabular-nums; font-weight: 700; }

p { margin: 0 0 1.1em; }
p:last-child { margin-bottom: 0; }

/* ---------- 4. Layout ---------- */
.wrap {
  width: 100%;
  max-width: var(--maxw);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.section { padding-block: var(--sp-section); position: relative; }
.section.is-tight { padding-block: clamp(3rem, 5vw, 4.5rem); }

.section.on-pitch {
  /* The navy fields were each one flat colour, which is what made the
     blue read as plain across every page. They now deepen top to
     bottom between two navies already in the palette. The gradient
     only ever gets darker than --pitch, so every contrast figure
     measured against flat --pitch still holds. */
  background: linear-gradient(180deg, var(--pitch) 0%, var(--pitch-deep) 100%);
  color: var(--chalk);
}
/* Slat texture — the horizontal timber of a manual scoreboard */
.section.on-pitch::before {
  content: "";
  position: absolute; inset: 0;
  background: repeating-linear-gradient(
    to bottom,
    rgba(255,255,255,.028) 0 1px,
    transparent 1px 46px
  );
  pointer-events: none;
}
.section.on-pitch > * { position: relative; z-index: 1; }

/* Scorebook grid for paper sections */
.section.on-book {
  background-color: var(--paper-2);
  background-image:
    linear-gradient(to right, rgba(0,46,95,.055) 1px, transparent 1px),
    linear-gradient(to bottom, rgba(0,46,95,.055) 1px, transparent 1px);
  background-size: 30px 30px;
}

.grid { display: grid; gap: clamp(1.25rem, 2vw, 2.25rem); }
/* minmax(0,…) rather than 1fr so wide children — tables above all —
   can shrink instead of forcing the row past the viewport. */
.g-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.g-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.g-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
/* Cards size themselves, so a wide page adds columns rather than
   stretching each card. */
.g-auto { grid-template-columns: repeat(auto-fill, minmax(252px, 1fr)); }
.g-auto-lg { grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); }
.split {
  display: grid;
  gap: clamp(2rem, 5vw, 4.5rem);
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr);
  align-items: start;
}

.stack-lg > * + * { margin-top: clamp(1.5rem, 3vw, 2.5rem); }

/* ---------- 5. Buttons ---------- */
.btn {
  display: inline-flex;
  align-items: center;
  gap: .6rem;
  font-family: var(--mono);
  font-size: .78rem;
  font-weight: 700;
  letter-spacing: .085em;
  text-transform: uppercase;
  text-decoration: none;
  padding: .95rem 1.5rem;
  border: 2px solid transparent;
  border-radius: var(--r);
  cursor: pointer;
  transition: transform .18s var(--ease), background-color .18s, color .18s, border-color .18s;
}
.btn:hover { transform: translateY(-2px); }
.btn:active { transform: translateY(0); }

.btn--primary { background: var(--claret); color: var(--chalk); }
.btn--primary:hover { background: var(--claret-lo); }

.btn--amber { background: var(--amber); color: var(--pitch-deep); }
.btn--amber:hover { background: var(--amber-lo); }

.btn--ghost { border-color: currentColor; color: inherit; background: transparent; }
.btn--ghost:hover { background: var(--amber); border-color: var(--amber); color: var(--pitch-deep); }

.btn-row { display: flex; flex-wrap: wrap; gap: .75rem; }

/* ---------- 6. Masthead ---------- */
.masthead {
  position: sticky; top: 0; z-index: 100;
  background: linear-gradient(180deg, var(--pitch) 0%, var(--pitch-deep) 100%);
  color: var(--chalk);
  border-bottom: 1px solid var(--pitch-line);
}
.masthead__bar {
  max-width: var(--maxw);
  margin-inline: auto;
  padding: .8rem var(--gutter);
  display: flex;
  align-items: center;
  gap: 1.5rem;
}

.mark {
  display: flex; align-items: center; gap: .7rem;
  text-decoration: none; color: inherit;
  margin-right: auto;
}
/* Crest slot. The crest is drawn for a white ground, so it sits on a
   white chip — that keeps every colour in the mark true and gives the
   darker teal lettering something to read against.
   The ball-and-seam below is a fallback for when no crest file exists;
   `img + .mark__seam` hides it whenever the image is present, and the
   image removes itself on error, which brings the fallback back. */
.mark__badge {
  position: relative;
  /* 52px, not 40: the crest carries banners and fine lettering, and
     below about 50px it collapses into a coloured smudge. */
  width: 52px; height: 52px; flex: none;
  /* No chip — the crest sits directly on the navy. Its own reds,
     oranges, cyan and whites all carry against that ground. */
  background: transparent;
}
.mark__badge img {
  position: relative; z-index: 1;
  width: 100%; height: 100%;
  object-fit: contain;
}
.mark__badge img + .mark__seam { display: none; }
.mark__seam {
  position: absolute; inset: 5px;
  border-radius: 50%;
  background: var(--claret);
  box-shadow: inset 0 -4px 8px rgba(0,0,0,.35);
}
/* The seam: two stitched arcs of a cricket ball */
.mark__seam::before,
.mark__seam::after {
  content: "";
  position: absolute; inset: 4px 0;
  border-left: 1.5px dashed var(--paper-2);
  left: 50%;
  transform: translateX(-50%);
}
.mark__seam::after { inset: 8px 0; opacity: .5; }

.mark__text { line-height: 1; }
.mark__name {
  font-family: var(--display);
  font-weight: 800;
  font-size: 1.05rem;
  letter-spacing: -.01em;
  text-transform: uppercase;
  display: block;
}
.mark__sub {
  font-family: var(--mono);
  font-size: .58rem;
  letter-spacing: .07em;
  color: var(--ash);
  text-transform: uppercase;
}

.nav { display: flex; align-items: center; gap: .15rem; }
/* :not(.btn) so the Join CTA keeps its own button styling — plain
   `.nav a` (0,1,1) outranks `.btn--amber` (0,1,0) and would otherwise
   force white text on amber, a teal hover, and amber-on-amber text
   when aria-current lands on it. */
.nav a:not(.btn) {
  font-family: var(--mono);
  font-size: .72rem;
  letter-spacing: .07em;
  text-transform: uppercase;
  text-decoration: none;
  padding: .55rem .62rem;
  border-radius: var(--r);
  color: var(--chalk);
  opacity: .78;
  transition: opacity .15s, background-color .15s;
  white-space: nowrap;
}
.nav a:not(.btn):hover { opacity: 1; background: var(--pitch-3); }
.nav a:not(.btn)[aria-current="page"] {
  opacity: 1;
  color: var(--amber);
  box-shadow: inset 0 -2px 0 var(--amber);
}

/* ---------- press feedback ----------
   Every clickable surface answers the pointer within its own frame: a
   small lift on hover, and a settle back down on press. Kept to
   transform and shadow so nothing reflows, and scoped to devices that
   actually hover — a phone would otherwise leave the lifted state
   stuck after a tap. */
@media (hover: hover) {
  .btn { transition: transform .18s var(--ease), box-shadow .18s var(--ease),
                     background-color .22s var(--ease), color .22s var(--ease); }
  .btn:hover { transform: translateY(-2px); box-shadow: var(--e2); }
}
.btn:active { transform: translateY(0) scale(.985); box-shadow: var(--e1); }

@media (prefers-reduced-motion: reduce) {
  .player, .btn { transition: none; }
  .player:hover, .player:focus-within, .btn:hover, .btn:active { transform: none; }
}

/* ---------- theme toggle ---------- */
.theme-toggle {
  flex: none;
  width: 38px; height: 38px;
  display: grid; place-items: center;
  margin-left: .35rem;
  border: 1px solid var(--pitch-line);
  border-radius: 50%;
  background: transparent;
  color: var(--chalk);
  cursor: pointer;
  transition: background .25s var(--ease), border-color .25s var(--ease);
}
.theme-toggle:hover { background: var(--pitch-3); border-color: var(--amber); }
.theme-toggle__icon { width: 17px; height: 17px; }
/* One icon at a time: the sun offers the light theme, so it shows
   while the dark one is active, and vice versa. */
.theme-toggle__icon--sun { display: none; }
[data-theme="dark"] .theme-toggle__icon--sun  { display: block; }
[data-theme="dark"] .theme-toggle__icon--moon { display: none; }

.nav-toggle {
  display: none;
  background: transparent;
  border: 2px solid var(--pitch-line);
  color: var(--chalk);
  border-radius: var(--r);
  padding: .5rem .8rem;
  font-family: var(--mono);
  font-size: .72rem;
  letter-spacing: .085em;
  text-transform: uppercase;
  cursor: pointer;
}

/* ---------- 7. Scoreboard (hero signature) ---------- */
.hero {
  background: linear-gradient(180deg, var(--pitch) 0%, var(--pitch-deep) 100%);
  color: var(--chalk);
  position: relative;
  overflow: hidden;
  /* Cut right back. The controls no longer live in a bottom strip, so
     the band it needed goes with them. */
  padding-block: clamp(2rem, 3.6vw, 3.25rem);
}
/* The wordmark carried the old three-word slogan at slogan size. As a
   club name it wants to read as a wordmark, not a billboard — and it
   is what was making the section tall. */
.hero__title { font-size: clamp(2.1rem, 5.2vw, 4.4rem); }
/* Hero slideshow. The club's own photographs crossfade behind the
   headline; the scrim below keeps the type legible over all of them. */
.hero__stage {
  position: absolute; inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
}
.hero__slide {
  position: absolute; inset: 0;
  background-size: cover;
  /* Per-photo focal point, set by the script from the measured
     position of the players. See HERO_IMAGES in data.js. */
  background-position: center var(--posy, 42%);
  opacity: 0;
  transition: opacity 1s var(--ease);
}
.hero__slide.is-on { opacity: .84; }

@media (prefers-reduced-motion: reduce) {
  .hero__slide { transition: none; }
}
.hero::before {
  content: "";
  position: absolute; inset: 0;
  z-index: 1;
  /* Falls away past the text column, which ends at ~54% of the hero.
     Wound back as far as it goes: measured against the brightest pixel
     of all nine slides under the real glyph boxes, and at each photo's
     own focal crop, the lede lands at 4.85:1 against the 4.5 it needs.
     One step lighter puts it at 4.21:1. That one line of body copy is
     the whole constraint — everything else has room. */
  background:
    linear-gradient(96deg, rgba(0,46,95,.78) 0%, rgba(0,46,95,.75) 56%, rgba(0,46,95,.40) 78%, rgba(0,46,95,.26) 100%),
    radial-gradient(120% 90% at 82% 8%, rgba(245,130,31,.08), transparent 55%),
    repeating-linear-gradient(to bottom, rgba(255,255,255,.03) 0 1px, transparent 1px 46px);
  pointer-events: none;
}
/* Hero layers, back to front: slides (0) → scrim (1) → content (2) →
   controls (3). This names the content wrapper rather than matching
   every child: an earlier `.hero > *` also caught the backdrop and,
   on equal specificity, overrode its `position: absolute` — which
   collapsed the stage to 0px and blanked the slideshow entirely. */
.hero > .wrap { position: relative; z-index: 2; }

/* ---------- hero slide controls ---------- */
.hero__nav {
  position: absolute;
  bottom: clamp(1.25rem, 2.5vw, 2rem);
  z-index: 3;
  width: clamp(44px, 3.4vw, 52px);
  aspect-ratio: 1;
  display: grid;
  place-items: center;
  border: 1px solid rgba(247,249,252,.32);
  border-radius: 50%;
  background: rgba(0,30,64,.55);
  color: var(--chalk);
  cursor: pointer;
  transition: background .3s var(--ease), border-color .3s var(--ease),
              transform .3s var(--ease);
}
.hero__nav--prev { left: var(--gutter); }
.hero__nav--next { right: var(--gutter); }

/* On a wide screen they move to the middle of each edge, the way
   Prahran's do. The hero content takes a wider inline padding to
   match — the type is left-aligned to the gutter, so without it the
   left arrow would land on top of the wordmark. */
@media (min-width: 901px) {
  .hero__nav {
    top: 50%; bottom: auto;
    translate: 0 -50%;
  }
  .hero__nav--prev { left: clamp(10px, 1.1vw, 18px); }
  .hero__nav--next { right: clamp(10px, 1.1vw, 18px); }
  .hero > .wrap { padding-inline: calc(var(--gutter) + 2.6rem); }
}
.hero__nav:hover {
  background: rgba(0,30,64,.82);
  border-color: var(--amber);
  transform: translateY(-2px);
}
.hero__nav:focus-visible { outline: 2px solid var(--amber); outline-offset: 3px; }
.hero__nav svg { width: 40%; fill: none; stroke: currentColor; stroke-width: 2.2; }
/* Hidden until the script wires them up — they do nothing without it,
   and `display: grid` would otherwise beat the UA rule for [hidden]. */
.hero__nav[hidden] { display: none; }
@media (prefers-reduced-motion: reduce) {
  .hero__nav { transition: none; }
  .hero__nav:hover { transform: none; }
}

@media (max-width: 900px) {
  /* The controls stay in the bottom corners here — a phone has no clear
     column for them to sit beside — so the hero keeps the strip they
     need. Desktop, where the height mattered, does not carry it. */
  .hero { padding-bottom: calc(clamp(2rem, 3.6vw, 3.25rem) + 3.75rem); }
  /* A portrait viewport crops a landscape photo hard to its centre,
     so the slides pull back and sit further behind the type. */
  .hero__slide { background-position: center 38%; }
  .hero__slide.is-on { opacity: .86; }
  /* There is no open column on a phone — the type runs the full width
     — so the scrim stays near-solid down through the lede (ending at
     41%) and only opens up over the button row, where the one piece of
     unbacked type gets its own plate below. The scoreboard is opaque,
     so nothing shows behind it either way. */
  .hero::before {
    background:
      linear-gradient(to bottom, rgba(0,46,95,.78) 0%, rgba(0,46,95,.78) 41%, rgba(0,46,95,.46) 47%, rgba(0,46,95,.40) 62%, rgba(0,46,95,.56) 100%),
      repeating-linear-gradient(to bottom, rgba(255,255,255,.03) 0 1px, transparent 1px 46px);
  }
  /* The outline button is the only hero text with nothing behind it
     once the scrim opens up; a photo can put sky right under it. */
  .hero .btn--ghost { background: rgba(0,30,64,.62); }
}

.hero__grid {
  display: grid;
  gap: clamp(2.5rem, 5vw, 4rem);
  grid-template-columns: minmax(0, 1.15fr) minmax(0, .85fr);
  align-items: end;
}

.hero__title { margin-bottom: 1.4rem; }
.hero__title .line { display: block; overflow: hidden; }
.hero__title .line > span { display: block; }
.hero__title em {
  font-style: normal;
  color: var(--amber);
}

.board {
  background: var(--pitch-2);
  border: 1px solid var(--pitch-line);
  border-radius: var(--r);
  padding: 1.4rem;
  box-shadow: var(--e4);
}
.board__head {
  display: flex; justify-content: space-between; align-items: baseline;
  font-family: var(--mono);
  font-size: .64rem;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ash);
  border-bottom: 1px solid var(--pitch-line);
  padding-bottom: .8rem;
  margin-bottom: 1.1rem;
  gap: 1rem;
}
.board__plates {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: .5rem;
}
.plate {
  background: var(--pitch);
  border: 1px solid var(--pitch-line);
  border-radius: 3px;
  padding: .85rem .4rem .6rem;
  text-align: center;
  box-shadow: inset 0 2px 0 rgba(0,0,0,.4);
}
.plate__v {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  font-size: clamp(1.2rem, 2.1vw, 1.6rem);
  color: var(--amber);
  line-height: 1;
  display: block;
}
.plate__k {
  font-family: var(--mono);
  font-size: .55rem;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ash);
  margin-top: .45rem;
  display: block;
}
.board__foot {
  margin-top: 1.1rem;
  padding-top: .9rem;
  border-top: 1px dashed var(--pitch-line);
  font-family: var(--mono);
  font-size: .68rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ash);
  display: flex; justify-content: space-between; gap: 1rem; flex-wrap: wrap;
}
.board__foot b { color: var(--chalk); font-weight: 700; }

/* ---------- 8. Boundary line (signature device) ----------
   A single bar split by the player's real fours-to-sixes ratio.
   It encodes something true and specific about this club: a
   hard-tennis-ball T10 side where several players hit more sixes
   than fours. Claret = fours, amber = sixes. */
.bline { margin-top: .2rem; }
.bline__bar {
  display: flex;
  height: 8px;
  border-radius: 999px;
  overflow: hidden;
  background: var(--paper-2);
}
.on-pitch .bline__bar { background: var(--pitch-3); }
.bline__4 { background: var(--claret); }
.bline__6 { background: var(--amber); }
.bline__4, .bline__6 {
  transform-origin: left;
  transition: flex-grow .5s var(--ease);
}
.bline__key {
  display: flex;
  gap: 1rem;
  margin-top: .55rem;
  font-family: var(--mono);
  font-size: .58rem;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ash-deep);
}
.on-pitch .bline__key { color: var(--ash); }
.bline__key span { display: flex; align-items: center; gap: .38rem; }
.bline__key i {
  width: 8px; height: 8px; border-radius: 2px; flex: none;
  background: var(--claret);
}
.bline__key span + span i { background: var(--amber); }
.bline__key b {
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}
.on-pitch .bline__key b { color: var(--chalk); }

/* ---------- 9. Cards ---------- */
.card {
  /* A flat fill is what made these read as boxes. The gradient runs
     between two colours already in the palette, so nothing new is
     introduced — it just stops the surface being one dead tone. */
  background: linear-gradient(180deg, var(--card) 0%, var(--paper-2) 100%);
  border: 1px solid var(--paper-line);
  border-radius: var(--r);
  padding: clamp(1.25rem, 2.2vw, 1.75rem);
  box-shadow: var(--e1), var(--lip);
}
.on-pitch .card {
  background: linear-gradient(180deg, var(--pitch-2) 0%, var(--pitch) 100%);
  border-color: var(--pitch-line);
  box-shadow: var(--e2);
}

/* Player card — photo-led, with the cap number worn on the image */
.player {
  position: relative;
  display: flex;
  flex-direction: column;
  text-decoration: none;
  color: inherit;
  background: linear-gradient(180deg, var(--card) 0%, var(--paper-2) 100%);
  border: 1px solid var(--paper-line);
  border-radius: var(--r);
  overflow: hidden;
  box-shadow: var(--e1), var(--lip);
  /* 220ms sits in the 150-300ms band for a micro-interaction, and only
     transform and box-shadow move — nothing that triggers layout. */
  transition: box-shadow .22s var(--ease), border-color .22s var(--ease),
              transform .22s var(--ease);
}
.on-pitch .player {
  background: linear-gradient(180deg, var(--pitch-2) 0%, var(--pitch) 100%);
  border-color: var(--pitch-line);
}

.player:hover, .player:focus-within {
  transform: translateY(-3px);
  box-shadow: var(--e3), var(--lip);
  border-color: var(--pitch-line);
}

/* ---- photo ---- */
.player__shot {
  position: relative;
  /* Portrait ratio, but capped: without the cap the panel grows with
     the card, so a wide card produced a 500px+ image and a very tall
     card. The cap also means every card lands at the same height
     whatever the column width. */
  /* Square, matching the square source crops, so the framing chosen
     per photo is the framing that shows. No max-height: capping it
     turned the panel landscape between two and four columns (337x300
     at 768px) and re-cropped 11% off the top of every head. The
     column width already keeps this in range. */
  aspect-ratio: 1 / 1;
  background: var(--pitch);
  overflow: hidden;
}
.player__shot img {
  /* Positioned so it paints ABOVE the monogram. The monogram is
     absolutely positioned and comes first in the DOM, so a static img
     was painting underneath it — the photo only appeared on hover,
     when the scale transform promoted it into its own stacking
     context. */
  position: relative;
  z-index: 1;
  width: 100%; height: 100%;
  object-fit: cover;
  object-position: center;
  transition: transform .6s var(--ease);
}
.player:hover .player__shot img { transform: scale(1.04); }

/* Monogram stands in until a real photo is dropped in. It is a
   designed state, not a broken-image placeholder. */
.player__mono {
  position: absolute; inset: 0;
  display: grid; place-items: center;
  background:
    radial-gradient(120% 90% at 30% 15%, var(--pitch-3), var(--pitch) 70%);
  font-family: var(--display);
  font-weight: 800;
  font-size: clamp(2rem, 3.2vw, 2.9rem);
  letter-spacing: .02em;
  color: rgba(245,247,242,.16);
  user-select: none;
}
/* Photo panel layers: monogram (0) -> photo (1) -> scrim (2) -> name (3).
   The scrim had no z-index while the photo has one, so the photo was
   painting straight over it and the name sat on bare picture — white
   type on a white studio background on half the squad. */
.player__shot::after {
  content: "";
  position: absolute; inset: 0;
  z-index: 2;
  /* No wash over the picture itself — see .player__id, which carries
     its own scrim sized to the text it holds. */
  background: none;
  pointer-events: none;
}

/* The scrim rides with the text rather than being a fixed slice of the
   panel. A one-line name sits 26% up and a wrapped one 32%, so any
   fixed ramp is either too weak for the long names or drowns the
   player for the short ones. Anchored to the text block it is always
   exactly as tall as it needs to be, and the fade happens inside the
   top padding, so the type always lands on the strong part. */
/* The band exists to carry the name, nothing more. It used to run 2.75rem
   of padding above the text with a slow fade, which covered about 38% of
   the panel — invisible against the old photo backgrounds, a hard block
   once every player sat on white. Shorter now, and the ramp falls away
   faster, so it reads as a caption rather than a shadow over the picture.

   Opacity can only come down so far. The role is amber, and amber on
   this navy over a white photo hits 4.51:1 at 0.90 and fails below it —
   so 0.90 is the floor, and the way to make the band feel lighter is to
   cover less rather than to fade more. Hence the tighter padding and a
   ramp that is transparent by 88%: about 20% of the panel now, from 38%.
   Chalk reads 9.6:1 and the amber role 4.6:1 where each is painted. */
.player__id {
  position: absolute; left: 0; right: 0; bottom: 0;
  z-index: 3;
  padding: .95rem 1rem .8rem;
  /* With padding this tight the name sits about 85% up the band, so the
     fade has to happen above it rather than through it — hold the plate
     to 0.90 until 80%, then drop. A slower ramp put the name at 4.1:1,
     which is why this is not the gentler curve it looks like it wants
     to be. */
  background: linear-gradient(to top,
    rgba(0,46,95,.92) 0%, rgba(0,46,95,.90) 80%, rgba(0,46,95,.45) 94%, transparent 100%);
}
.player__name {
  font-family: var(--display);
  font-weight: 800;
  font-size: clamp(1.02rem, 1.2vw, 1.24rem);
  line-height: .92;
  text-transform: uppercase;
  color: var(--chalk);
  margin: 0 0 .3rem;
}
.player__role {
  font-family: var(--mono);
  font-size: .58rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--amber);
}

/* Cap number, worn top-left like a shirt number */
.player__cap {
  position: absolute; top: .8rem; left: .8rem; z-index: 1;
  min-width: 38px; height: 38px;
  display: grid; place-items: center;
  padding: 0 .5rem;
  border-radius: 3px;
  background: rgba(0,46,95,.72);
  backdrop-filter: blur(6px);
  border: 1px solid rgba(245,247,242,.18);
  color: var(--chalk);
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  font-size: .82rem;
}
.player__cap.is-skipper { background: var(--claret); border-color: var(--claret); }

/* ---- body ---- */
.player__body {
  padding: 1.05rem 1.1rem 1.15rem;
  display: flex;
  flex-direction: column;
  gap: .8rem;
  flex: 1;
}
.player__stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(52px, 1fr));
  gap: .45rem .4rem;
  padding-bottom: .8rem;
  border-bottom: 1px solid var(--paper-line);
}
.on-pitch .player__stats { border-bottom-color: var(--pitch-line); }
.player__note {
  font-size: .85rem;
  line-height: 1.5;
  color: var(--ash-deep);
  margin: 0;
  margin-top: auto;
}
.on-pitch .player__note { color: var(--ash); }

.player__link {
  font-family: var(--mono);
  font-size: .58rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--claret-ink);
  text-decoration: none;
  border-bottom: 1px solid currentColor;
  align-self: flex-start;
}
.on-pitch .player__link { color: var(--amber); }
.player__link:hover { opacity: .75; }

.player__none {
  font-family: var(--mono);
  font-size: .6rem;
  letter-spacing: .085em;
  text-transform: uppercase;
  color: var(--ash-deep);
  padding: .5rem 0;
}
.stat__v {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  font-size: 1.05rem;
  display: block;
  line-height: 1.1;
}
.stat__k {
  font-family: var(--mono);
  font-size: .53rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ash-deep);
  display: block;
  margin-top: .2rem;
}

.badge {
  position: absolute; top: .8rem; right: .8rem; z-index: 1;
  border-radius: 3px;
  background: var(--amber);
  color: var(--pitch-deep);
  font-family: var(--mono);
  font-size: .55rem;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  padding: .3rem .6rem;
}

/* ---------- 10. Big stat blocks ---------- */
.tally { border-top: 2px solid currentColor; padding-top: 1rem; }
.tally__v {
  font-family: var(--display);
  font-weight: 800;
  font-size: clamp(2.1rem, 3.8vw, 3.1rem);
  line-height: .85;
  display: block;
}
.on-pitch .tally__v { color: var(--amber); }
.tally__k {
  font-family: var(--mono);
  font-size: .62rem;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ash-deep);
  margin-top: .6rem;
  display: block;
}
.on-pitch .tally__k { color: var(--ash); }

/* ---------- 11. Tables ---------- */
.table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }

.tbl {
  width: 100%;
  border-collapse: collapse;
  min-width: 640px;
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-size: .8rem;
}
.tbl th {
  text-align: left;
  font-size: .6rem;
  font-weight: 600;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ash-deep);
  padding: .7rem .8rem;
  border-bottom: 2px solid var(--pitch);
  white-space: nowrap;
}
.tbl td {
  padding: .85rem .8rem;
  border-bottom: 1px solid var(--paper-line);
  white-space: nowrap;
}
.tbl td:first-child, .tbl th:first-child { padding-left: 0; }
.tbl tbody tr:hover td { background: rgba(206,27,51,.06); }
.tbl .is-name { font-family: var(--body); font-weight: 600; white-space: normal; }
.tbl .is-lead { color: var(--claret-ink); font-weight: 700; }

.on-pitch .tbl th { color: var(--ash); border-bottom-color: var(--amber); }
.on-pitch .tbl td { border-bottom-color: var(--pitch-line); }
.on-pitch .tbl tbody tr:hover td { background: rgba(255,145,48,.08); }
/* Highlighted figures are claret, which is a light-background colour —
   on a dark section it measured 1.93:1. Amber on the same ground is
   5.03:1. */
.on-pitch .tbl .is-lead { color: var(--amber); }

.tbl__cap {
  caption-side: bottom;
  text-align: left;
  font-family: var(--mono);
  font-size: .6rem;
  letter-spacing: .06em;
  color: var(--ash-deep);
  padding-top: .9rem;
  white-space: normal;
}
.on-pitch .tbl__cap { color: var(--ash); }

/* ---------- 11b. Ranked bars ---------- */
.ranked {
  display: grid;
  grid-template-columns: minmax(0, 170px) minmax(0, 1fr) 40px;
  align-items: center;
  gap: 1rem;
  padding: .6rem 0;
  border-bottom: 1px solid var(--paper-line);
}
.on-pitch .ranked { border-bottom-color: var(--pitch-line); }
.ranked__name { font-weight: 600; font-size: .92rem; }
.ranked__track {
  height: 10px;
  border-radius: 999px;
  background: var(--paper-2);
  overflow: hidden;
}
.on-pitch .ranked__track { background: var(--pitch-3); }
.ranked__fill {
  display: block; height: 100%;
  border-radius: 999px;
  background: linear-gradient(90deg, var(--cyan), var(--amber));
}
.ranked__v { text-align: right; font-size: .92rem; }

.pill {
  display: inline-block;
  font-family: var(--mono);
  font-size: .58rem;
  font-weight: 700;
  letter-spacing: .07em;
  text-transform: uppercase;
  padding: .22rem .5rem;
  border-radius: 2px;
}
.pill--w { background: var(--amber); color: var(--pitch-deep); }
.pill--l { background: var(--paper-2); color: var(--ash-deep); }
.on-pitch .pill--l { background: var(--pitch-deep); color: var(--ash); }
.pill--t { background: var(--claret); color: var(--chalk); }

/* ---------- 12. Fixture rows ---------- */
.fixture {
  display: grid;
  grid-template-columns: 86px minmax(0,1fr) auto;
  gap: 1.25rem;
  align-items: center;
  padding: 1.15rem 0;
  border-bottom: 1px solid var(--paper-line);
}
.on-pitch .fixture { border-bottom-color: var(--pitch-line); }
.fixture:first-child { border-top: 1px solid var(--paper-line); }
.on-pitch .fixture:first-child { border-top-color: var(--pitch-line); }

.fixture__date {
  font-family: var(--mono);
  text-align: center;
  border-right: 1px solid var(--paper-line);
  padding-right: 1rem;
  line-height: 1.1;
}
.on-pitch .fixture__date { border-right-color: var(--pitch-line); }
.fixture__day { display: block; font-size: 1.35rem; font-weight: 700; font-variant-numeric: tabular-nums; }
.fixture__mon { display: block; font-size: .58rem; letter-spacing: .085em; text-transform: uppercase; color: var(--ash-deep); }
.on-pitch .fixture__mon { color: var(--ash); }

.fixture__opp { font-family: var(--display); font-weight: 700; font-size: 1.18rem; line-height: 1.15; letter-spacing: -.015em; text-transform: uppercase; }
.fixture__meta { font-family: var(--mono); font-size: .62rem; letter-spacing: .06em; text-transform: uppercase; color: var(--ash-deep); margin-top: .4rem; }
.on-pitch .fixture__meta { color: var(--ash); }

.fixture.is-next { background: rgba(245,165,36,.09); padding-inline: 1rem; margin-inline: -1rem; }

/* ---------- 13. Page header ---------- */
.pagehead {
  background: linear-gradient(180deg, var(--pitch) 0%, var(--pitch-deep) 100%);
  color: var(--chalk);
  padding-block: clamp(3rem, 7vw, 5.5rem) clamp(2.5rem, 5vw, 4rem);
  position: relative;
  overflow: hidden;
}
.pagehead::before {
  content: "";
  position: absolute; inset: 0;
  background: repeating-linear-gradient(to bottom, rgba(255,255,255,.03) 0 1px, transparent 1px 46px);
}
/* Names the wrapper rather than matching every child, so the artwork
   layer below keeps its own positioning. */
.pagehead > .wrap { position: relative; z-index: 2; }
/* Both lifted off --ash: there is artwork behind them now, and --ash
   was tuned for flat blue. The crumb links were already the tightest
   type on the site at 4.2:1 — small, dim, and dimmed again by an .8
   opacity on the anchor. */
.pagehead .lede { color: #C2D5EA; margin-top: 1.4rem; }
.pagehead .crumb { color: #C2D5EA; }
.pagehead .crumb a { opacity: .92; }

/* ---------- 13b. Page-head artwork ----------
   Eleven inner pages shared one flat blue band. Each now carries its
   own motif taken from the game, picked by the page key already on
   <body>. Every shape is styled at its finished state and the
   keyframes run *from* an off-state, so killing the animation under
   reduced motion leaves the drawing intact rather than an empty box.

   Entrances only ever touch `transform`; ambient loops only ever
   touch `translate`. Keeping them on separate properties means the
   two can run on one element without fighting over a shared value. */
.ph-art {
  position: absolute; inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
  --ink: #A7C4E2;
}
.ph-art svg { display: block; width: 100%; height: 100%; }

@keyframes ph-riser  { from { transform: scaleY(0); opacity: 0; } }
@keyframes ph-pop    { from { transform: scale(0); opacity: 0; } }
@keyframes ph-wipe   { from { transform: scaleX(0); opacity: 0; } }
@keyframes ph-lift   { from { transform: translateY(26px) scaleY(.2); opacity: 0; } }
@keyframes ph-draw   { from { stroke-dashoffset: 1; } }
@keyframes ph-float  { to   { translate: 0 -9px; } }
@keyframes ph-run    { to   { translate: -1040px 0; } }
@keyframes ph-travel { from { offset-distance: 0%; } to { offset-distance: 100%; } }

/* Every stroked motif is normalised with pathLength="1", so one
   dash rule draws a line, an arc and a bezier alike. */
.ph-art [data-draw] {
  fill: none;
  stroke-dasharray: 1;
  stroke-dashoffset: 0;
  animation: ph-draw 1.1s var(--ease) backwards;
  animation-delay: calc(var(--i, 0) * 90ms);
}

/* about — eleven risers, for eleven on the park */
/* ---- The Club: wagon wheel ----
   The arc lays the ground down first, then the eleven shots fire out of
   the striker's end one after another. Sixes carry the amber; everything
   else is the same ink as the rest of the artwork, so the three that are
   picked out are the only thing the eye is asked to notice. */
/* All scoped under .ph-art: the shared `.ph-art [data-draw]` rule that
   normalises every stroked motif is (0,2,0) and would otherwise beat a
   bare class here, taking the timings with it. */
.ph-art .a-wheel {
  fill: none; stroke: var(--ink); stroke-width: 2; opacity: .16;
  animation: ph-draw 1.5s cubic-bezier(.22,.61,.36,1) both;
}
.ph-art .a-shot {
  stroke: var(--ink); stroke-width: 2; stroke-linecap: round; opacity: .17;
  animation: ph-draw .62s cubic-bezier(.22,.61,.36,1) both;
  animation-delay: calc(.42s + var(--i) * .085s);
}
.ph-art .a-shot.is-six { stroke: var(--amber); opacity: .3; stroke-width: 2.4; }
.ph-art .a-strike {
  fill: var(--amber); opacity: .38;
  animation: ph-pop .5s cubic-bezier(.34,1.56,.64,1) both;
}

/* ---- Competitions: knockout bracket ----
   Each tie draws its two arms and its feed to the next round, in order,
   so the ladder builds towards the final rather than appearing at once.
   The final node lands last and holds the amber. */
/* Weight carries this, not opacity. The bracket sits behind the heading
   and the lede, and the lede is the dimmer of the two — it drops to
   4.50:1 at 0.32 opacity and fails past that, so 0.30 is the ceiling.
   Thickness costs nothing at a given pixel, so the lines get it instead:
   double the stroke, and the joins rounded so the ladder reads as one
   continuous path rather than separate segments meeting at corners. */
.ph-art .a-tie, .ph-art .a-feed {
  fill: none; stroke: var(--ink); stroke-width: 4;
  stroke-linecap: round; stroke-linejoin: round;
  opacity: .28;
  animation: ph-draw .5s cubic-bezier(.22,.61,.36,1) both;
  animation-delay: calc(var(--i) * .075s);
}
.ph-art .a-feed { stroke-width: 4.5; opacity: .3; }
.ph-art .a-final {
  fill: var(--amber); opacity: .46;
  animation: ph-pop .55s cubic-bezier(.34,1.56,.64,1) both;
  animation-delay: .95s;
}

.a-riser {
  fill: var(--ink); opacity: .2;
  transform-box: fill-box; transform-origin: 50% 100%;
  transform: scaleY(var(--h));
  animation: ph-riser .9s var(--ease) backwards;
  animation-delay: calc(var(--i) * 70ms);
}
/* squad — the slip cordon */
.a-dot {
  fill: var(--ink); opacity: .22;
  transform-box: fill-box; transform-origin: 50% 50%;
  animation: ph-pop .7s var(--ease) backwards,
             ph-float 7s ease-in-out infinite alternate;
  animation-delay: calc(var(--i) * 80ms), calc(var(--i) * 240ms);
}
.a-dot.is-key { opacity: .26; }
.a-cordon { stroke: var(--ink); stroke-width: 2; opacity: .14; }
/* fixtures — the draw */
.a-bar {
  fill: var(--ink); opacity: .18;
  transform-box: fill-box;
  animation: ph-wipe .8s var(--ease) backwards;
  animation-delay: calc(var(--i) * 90ms);
}
.a-bar.from-l { transform-origin: 0 50%; }
.a-bar.from-r { transform-origin: 100% 50%; }
/* results — over by over, wickets marked */
.a-over {
  fill: var(--ink); opacity: .2;
  transform-box: fill-box; transform-origin: 50% 100%;
  transform: scaleY(var(--h));
  animation: ph-riser .8s var(--ease) backwards;
  animation-delay: calc(var(--i) * 45ms);
}
.a-wk {
  fill: var(--ink); opacity: .26;
  transform-box: fill-box; transform-origin: 50% 50%;
  animation: ph-pop .6s var(--ease) backwards;
  animation-delay: calc(var(--i) * 45ms + 320ms);
}
/* stats — the wagon wheel */
.a-ray { stroke: var(--ink); stroke-width: 2; opacity: .22; }
.a-hub { fill: var(--ink); opacity: .26; }
/* news — the ticker */
.a-tick {
  fill: var(--ink); opacity: .16;
  animation: ph-run var(--dur) linear infinite;
}
/* gallery — a contact sheet with a player in every frame */
.a-cell {
  transform-box: fill-box; transform-origin: 50% 50%;
  animation: ph-pop .7s var(--ease) backwards,
             ph-float 6s ease-in-out infinite alternate;
  animation-delay: calc(var(--i) * 35ms), calc(var(--i) * 80ms);
}
.a-frame { fill: none; stroke: var(--ink); stroke-width: 2; opacity: .22; }
/* The player inside each frame. */
.a-player { opacity: .26; }
.a-player circle,
.a-player rect { fill: var(--ink); }
.a-player path,
.a-player line {
  fill: none;
  stroke: var(--ink);
  stroke-width: 5;
  stroke-linecap: round;
  stroke-linejoin: round;
}
/* Pivot at the hands and at the shoulder, not at the middle of the
   limb. Durations run off --i so two dozen figures do not swing as
   one metronome. */
.a-mini-bat {
  transform-box: fill-box; transform-origin: 50% 0%;
  rotate: -30deg;
  animation: ph-minibat calc(2.8s + var(--i) * 90ms) var(--ease) infinite;
}
.a-mini-arm {
  transform-box: fill-box; transform-origin: 0% 100%;
  rotate: -140deg;
  animation: ph-miniarm calc(3.1s + var(--i) * 90ms) var(--ease) infinite;
}
@keyframes ph-minibat {
  0%, 55% { rotate: -30deg; }
  72%     { rotate: 26deg; }
  100%    { rotate: -30deg; }
}
@keyframes ph-miniarm {
  0%, 40% { rotate: -140deg; }
  62%     { rotate: 12deg; }
  100%    { rotate: -140deg; }
}
/* contact — the crease, and a ball bowled down it */
.a-line  { stroke: var(--ink); stroke-width: 3; opacity: .2; }
.a-stump { stroke: var(--ink); stroke-width: 6; opacity: .26; stroke-linecap: round; }
/* The line the ball comes down, up to the point of contact. It stops
   there deliberately — where the shot goes is the ball's business.
   No dash pattern here: `[data-draw]` sets stroke-dasharray to drive
   the draw-on, at higher specificity, so one would be dead code. */
.a-track { stroke: var(--ink); stroke-width: 2; opacity: .12; }
/* The two players. Silhouettes rather than detail — at this size and
   this opacity anything finer turns to mud. */
.a-figure { opacity: .26; }
.a-figure circle { fill: var(--ink); }
.a-figure path {
  fill: none;
  stroke: var(--ink);
  stroke-width: 7;
  stroke-linecap: round;
}
/* The bowling arm, pivoting at the shoulder rather than its middle. */
.a-arm {
  fill: none;
  stroke: var(--ink);
  stroke-width: 7;
  stroke-linecap: round;
  opacity: .26;
  transform-box: fill-box;
  transform-origin: 0% 100%;
  animation: ph-arm 4s var(--ease) infinite;
}
/* Releases at 0% with the ball, follows through, waits out the shot,
   then swings back over the top to deliver again. */
@keyframes ph-arm {
  0%   { rotate: 0deg; }
  20%  { rotate: 62deg; }
  56%  { rotate: 62deg; }
  82%  { rotate: -150deg; }
  100% { rotate: 0deg; }
}
.a-bat {
  fill: var(--ink); opacity: .24;
  transform-box: fill-box;
  transform-origin: 50% 4%;          /* the hands, not the middle of the blade */
  rotate: -44deg;
  animation: ph-bat 4s var(--ease) infinite;
}
.a-cherry {
  fill: var(--ink); opacity: .26;
  animation: ph-bowl 4s linear infinite;
}
/* Keyframed in viewBox units rather than along an offset-path. On an
   SVG child `offset-path: path()` does not resolve against the viewBox
   the way it reads: the ball came out at (725,98) for a path point of
   (545,170), a long way past the bat. `translate` on an SVG element is
   plain user units and lands where the numbers say.

   Down the pitch, pitching at 42%, met by the bat at 56%, then away —
   note the spacing between keyframes widens after contact, because a
   struck ball does not travel at bowling speed. */
@keyframes ph-bowl {
  0%        { translate: 440px 66px; }    /* out of the bowler's hand */
  14%       { translate: 478px 118px; }
  27%       { translate: 520px 165px; }
  38%       { translate: 556px 196px; }
  44%       { translate: 578px 205px; }   /* pitches */
  50%       { translate: 622px 191px; }
  56%       { translate: 672px 176px; }   /* met by the middle of the blade */
  62%       { translate: 760px 138px; }
  70%       { translate: 900px 82px; }
  80%       { translate: 1090px 12px; }
  88%, 100% { translate: 1290px -30px; }  /* held off-canvas until it restarts */
}
/* Held back, through the line at 56% to meet the ball, then reset
   while the ball is out of shot so the recovery is never seen. */
@keyframes ph-bat {
  0%, 40% { rotate: -44deg; }
  56%     { rotate: 12deg; }
  64%     { rotate: 32deg; }
  88%     { rotate: 32deg; }
  100%    { rotate: -44deg; }
}
/* join — the delivery */
.a-arc  { stroke: var(--ink); stroke-width: 3; opacity: .24; }
.a-ball {
  fill: var(--ink); opacity: .26;
  offset-path: path("M -20 96 C 300 40, 520 250, 760 236 S 1080 150, 1240 84");
  offset-rotate: 0deg;
  /* Linear, not eased: a ball in flight holds its pace, and the club's
     easing curve made it arrive and then loiter. */
  animation: ph-travel 9s linear infinite;
}
/* sponsors — the boundary boards */
.a-board {
  fill: var(--ink); opacity: .18;
  transform-box: fill-box; transform-origin: 50% 100%;
  animation: ph-lift .8s var(--ease) backwards;
  animation-delay: calc(var(--i) * 85ms);
}
/* policies — the ledger */
.a-rule { stroke: var(--ink); stroke-width: 2; opacity: .18; }

@media (prefers-reduced-motion: reduce) {
  /* The finished state is the base state, so this stops the motion
     without taking the artwork away. */
  .ph-art * { animation: none !important; }
  /* The ball's base position is off the left edge, which would just
     read as a missing element. Park it at the moment of contact. */
  .a-cherry { translate: 672px 176px; }
}

.crumb {
  font-family: var(--mono);
  font-size: .62rem;
  letter-spacing: .085em;
  text-transform: uppercase;
  color: var(--ash);
  margin-bottom: 1.4rem;
}
.crumb a { text-decoration: none; opacity: .8; }
.crumb a:hover { opacity: 1; color: var(--amber); }

/* ---------- 14. Timeline / values ---------- */
.rule-list { list-style: none; margin: 0; padding: 0; }
.rule-list li {
  padding: 1.15rem 0;
  border-bottom: 1px solid var(--paper-line);
  display: grid;
  grid-template-columns: minmax(0, 200px) minmax(0, 1fr);
  gap: 1.5rem;
  align-items: baseline;
}
.on-pitch .rule-list li { border-bottom-color: var(--pitch-line); }
.rule-list li:first-child { border-top: 1px solid var(--paper-line); }
.on-pitch .rule-list li:first-child { border-top-color: var(--pitch-line); }
.rule-list dt, .rule-list .k {
  font-family: var(--display);
  font-weight: 700;
  font-size: 1.05rem;
  text-transform: uppercase;
  line-height: 1.15;
}
.rule-list .k-mono {
  font-family: var(--mono);
  font-size: .7rem;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--claret-ink);
  font-weight: 700;
}
.on-pitch .rule-list .k-mono { color: var(--amber); }

/* ---------- 15. Gallery ---------- */
/* Every tile identical: same span, same ratio. Mixed spans and mixed
   ratios were what left the rows ragged before — uniform tiles mean a
   row can only ever close exactly. */
.gallery {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: clamp(.6rem, 1vw, 1rem);
}
.shot {
  position: relative;
  aspect-ratio: 4 / 3;
  border-radius: var(--r);
  overflow: hidden;
  background: var(--pitch-2);
  display: grid;
  place-items: center;
  border: 1px solid var(--pitch-line);
}
.shot img {
  width: 100%; height: 100%; object-fit: cover;
  transition: transform .5s cubic-bezier(.22,.61,.36,1), filter .35s ease;
}
/* The tile is a crop, so the whole frame is never visible in the grid.
   The lift on hover is the hint that there is more to see; the caption
   rides up with it rather than sitting there permanently and covering
   the photograph. */
.shot.is-openable { cursor: zoom-in; }
.shot__open {
  position: absolute; inset: 0;
  border: 0; padding: 0; margin: 0;
  background: none;
  cursor: inherit;
  z-index: 3;
}
.shot__open:focus-visible { outline: 3px solid var(--amber); outline-offset: -4px; }
/* The caption already lays a navy gradient over the foot of the tile, so
   a second scrim on hover would only muddy it — and it would have
   painted over the caption, which carries no z-index of its own. The
   scale alone is the hint. */
@media (hover: hover) {
  .shot.is-openable:hover img,
  .shot.is-openable:focus-within img { transform: scale(1.045); }
}

/* ---------- lightbox ----------
   A <dialog>, so focus trapping, page inertness and Escape are the
   browser's job rather than hand-rolled here. */
.lb {
  border: 0; padding: 0;
  width: min(96vw, 1500px);
  max-width: none;
  max-height: 94dvh;
  background: transparent;
  color: var(--chalk);
  overflow: visible;
}
.lb::backdrop { background: rgba(0,20,42,.88); backdrop-filter: blur(3px); }
.lb__frame {
  margin: 0;
  display: grid;
  justify-items: center;
  gap: .9rem;
}
.lb__frame img {
  max-width: 100%;
  max-height: 78dvh;
  width: auto; height: auto;
  border-radius: var(--r);
  box-shadow: 0 30px 80px rgba(0,0,0,.55);
}
.lb__frame figcaption {
  font-size: .95rem;
  color: var(--chalk);
  text-align: center;
  max-width: 60ch;
}
.lb__x, .lb__nav {
  position: absolute;
  z-index: 2;
  display: grid; place-items: center;
  border: 1px solid rgba(255,255,255,.28);
  background: rgba(0,30,64,.72);
  color: var(--chalk);
  border-radius: 50%;
  cursor: pointer;
  transition: background .2s ease, transform .2s ease, border-color .2s ease;
}
.lb__x { top: -14px; right: -6px; width: 44px; height: 44px; font-size: 1.5rem; line-height: 1; }
.lb__nav { top: 50%; transform: translateY(-50%); width: 52px; height: 52px; font-size: 1.9rem; line-height: 1; }
.lb__nav--prev { left: -10px; }
.lb__nav--next { right: -10px; }
.lb__x:hover, .lb__nav:hover { background: var(--claret); border-color: var(--claret); }
.lb__nav:hover { transform: translateY(-50%) scale(1.06); }
.lb__count {
  margin: .85rem 0 0;
  text-align: center;
  font-family: var(--mono);
  font-size: .7rem;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--ash);
}
@media (prefers-reduced-motion: no-preference) {
  .lb[open] { animation: lb-in .28s cubic-bezier(.22,.61,.36,1); }
  @keyframes lb-in { from { opacity: 0; transform: scale(.965); } to { opacity: 1; transform: none; } }
}
@media (max-width: 620px) {
  .lb__nav { width: 44px; height: 44px; font-size: 1.5rem; }
  .lb__nav--prev { left: -4px; }
  .lb__nav--next { right: -4px; }
  .lb__x { top: -10px; right: -2px; }
}

/* Lazy images decode at unpredictable moments; without this they snap
   in and the grid reads as flickering while it fills. */
.shot img.is-loading,
.media__frame img.is-loading,
.join-shot img.is-loading,
.player__shot img.is-loading { opacity: 0; }
.shot img.is-loaded,
.media__frame img.is-loaded,
.join-shot img.is-loaded,
.player__shot img.is-loaded { animation: img-in .45s ease both; }
@keyframes img-in { from { opacity: 0; } to { opacity: 1; } }

/* ---------- squad filter ---------- */
.filters {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .6rem;
  margin-bottom: clamp(1.5rem, 3vw, 2.25rem);
}
.filter {
  display: inline-flex;
  align-items: center;
  gap: .5rem;
  padding: .55rem 1rem;
  border: 1px solid var(--paper-line);
  border-radius: 999px;
  background: var(--card);
  color: var(--ink);
  font-family: var(--mono);
  font-size: .72rem;
  letter-spacing: .09em;
  text-transform: uppercase;
  cursor: pointer;
  transition: background .2s ease, border-color .2s ease, color .2s ease, transform .18s ease;
}
.filter:hover { border-color: var(--pitch-3); transform: translateY(-1px); }
.filter__n {
  font-size: .68rem;
  padding: .1rem .42rem;
  border-radius: 999px;
  background: var(--paper-2);
  color: var(--ash-deep);
}
.filter[aria-pressed="true"] {
  background: var(--pitch);
  border-color: var(--pitch);
  color: var(--chalk);
}
.filter[aria-pressed="true"] .filter__n { background: rgba(255,255,255,.16); color: var(--chalk); }
.filters__live {
  margin: 0 0 0 auto;
  font-family: var(--mono);
  font-size: .68rem;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--ash-deep);
}
/* `.player` sets `display: flex`, and a class selector beats the user
   agent's `[hidden] { display: none }` — so setting the attribute alone
   left every card on screen while the counts changed. Same trap as the
   hero controls further up. */
.player[hidden] { display: none !important; }

/* Cards are hidden outright rather than faded to nothing, so a filtered
   player is gone from the tab order too, not just invisible. */
@media (prefers-reduced-motion: no-preference) {
  .player:not(.is-filtered) { animation: card-in .32s cubic-bezier(.22,.61,.36,1) both; }
  @keyframes card-in { from { opacity: 0; transform: scale(.97); } to { opacity: 1; transform: none; } }
}

/* ---------- copy to clipboard ---------- */
.copy {
  position: relative;
  margin-left: .55rem;
  padding: .12rem .5rem;
  border: 1px solid var(--paper-line);
  border-radius: 999px;
  background: transparent;
  color: var(--ash-deep);
  font-family: var(--mono);
  font-size: .6rem;
  letter-spacing: .09em;
  text-transform: uppercase;
  cursor: pointer;
  vertical-align: middle;
  transition: color .2s ease, border-color .2s ease, background .2s ease;
}
/* The pill renders at roughly 47x18, which is under any sane minimum for
   a touch target — and it sits directly beside a mailto: link, so a
   near-miss on a phone opened the mail app instead of copying. This
   grows the hit area to about 63x44 without changing how it looks.
   Sits behind the label so it never covers the text it belongs to. */
.copy::after {
  content: "";
  position: absolute;
  inset: -13px -8px;
  z-index: -1;
}
.copy:hover { color: var(--ink); border-color: var(--pitch-3); }
.copy.is-done { color: var(--chalk); background: var(--pitch); border-color: var(--pitch); }
.copy:focus-visible { outline: 3px solid var(--amber); outline-offset: 2px; }

/* ---------- cross-page transitions ----------
   Every page here is a separate document, so moving between them used
   to be a white flash. The browser can cross-fade one document into the
   next on its own; this is the whole opt-in. Anything that does not
   support it navigates exactly as before, so there is nothing to fall
   back to.

   The masthead and the footer are named, which pins them: they stay put
   while the page body underneath them changes, instead of the whole
   window fading as one sheet. Only offered when motion is welcome. */
@media (prefers-reduced-motion: no-preference) {
  @view-transition { navigation: auto; }
  .masthead { view-transition-name: masthead; }
  .foot     { view-transition-name: foot; }
  /* Quick enough not to feel like waiting. The default 250ms cross-fade
     reads as sluggish on a static site that loads in under that. */
  ::view-transition-group(root) { animation-duration: .18s; }
}

/* Read progress: a hairline along the foot of the masthead. Scales a
   transform rather than a width, so it never triggers layout. */
.progress {
  position: absolute; left: 0; right: 0; bottom: -1px;
  height: 2px;
  background: var(--amber);
  transform: scaleX(var(--p, 0));
  transform-origin: 0 50%;
  will-change: transform;
}
.shot__ph {
  font-family: var(--mono);
  font-size: .6rem;
  letter-spacing: .085em;
  text-transform: uppercase;
  color: var(--ash);
  text-align: center;
  padding: 1rem;
}
.shot__cap {
  position: absolute; left: 0; right: 0; bottom: 0;
  padding: .7rem .85rem;
  background: linear-gradient(to top, rgba(0,46,95,.92), transparent);
  font-family: var(--mono);
  font-size: .6rem;
  letter-spacing: .085em;
  text-transform: uppercase;
  color: var(--chalk);
}

/* ---------- 16. Forms ---------- */
.field { display: block; margin-bottom: 1.1rem; }
.field > span {
  display: block;
  font-family: var(--mono);
  font-size: .62rem;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ash-deep);
  margin-bottom: .45rem;
}
.on-pitch .field > span { color: var(--ash); }
.field input, .field select, .field textarea {
  width: 100%;
  font: inherit;
  font-size: .95rem;
  padding: .8rem .9rem;
  border: 1.5px solid var(--paper-line);
  border-radius: var(--r);
  background: var(--card);
  color: var(--ink);
}
.field textarea { min-height: 130px; resize: vertical; }
.field input:focus, .field select:focus, .field textarea:focus {
  border-color: var(--claret);
  outline: none;
  box-shadow: 0 0 0 3px rgba(0,46,95,.16);
}
.form-note {
  font-family: var(--mono);
  font-size: .62rem;
  letter-spacing: .06em;
  color: var(--ash-deep);
  line-height: 1.6;
}

/* ---------- 17. Footer ---------- */
.foot {
  background: linear-gradient(180deg, var(--pitch) 0%, var(--pitch-deep) 100%);
  color: var(--chalk);
  padding-block: clamp(3rem, 6vw, 4.5rem) 1.75rem;
  border-top: 4px solid var(--claret);
}
.foot__grid {
  display: grid;
  grid-template-columns: minmax(0, 1.4fr) repeat(3, minmax(0, 1fr));
  gap: 2.5rem;
  padding-bottom: 2.5rem;
  border-bottom: 1px solid var(--pitch-line);
}
.foot h4 {
  font-family: var(--mono);
  font-size: .62rem;
  font-weight: 600;
  letter-spacing: .085em;
  text-transform: uppercase;
  color: var(--amber);
  margin: 0 0 1rem;
}
.foot ul { list-style: none; margin: 0; padding: 0; }
.foot li { margin-bottom: .55rem; }
.foot a { text-decoration: none; color: var(--ash); font-size: .9rem; transition: color .15s; }
.foot a:hover { color: var(--chalk); }
.foot__bar {
  padding-top: 1.5rem;
  display: flex; flex-wrap: wrap; gap: 1rem; justify-content: space-between;
  font-family: var(--mono);
  font-size: .6rem;
  letter-spacing: .085em;
  text-transform: uppercase;
  /* --ash-deep is the grey for light backgrounds; the footer is navy,
     where it measured 2.2:1. */
  color: var(--ash);
}
/* Remove this chip once the placeholder data is replaced. */
.demo-chip {
  border: 1px solid var(--pitch-line);
  border-radius: 2px;
  padding: .25rem .5rem;
  color: var(--ash);
}

/* ---------- 18. Motion ----------
   Every hidden state is scoped to .js so the page is fully
   readable with JavaScript off, blocked, or still loading. */
.js [data-anim] {
  opacity: 0;
  transform: translateY(18px);
  transition: opacity .7s var(--ease), transform .7s var(--ease);
  transition-delay: var(--d, 0ms);
}
.js [data-anim].is-in { opacity: 1; transform: none; }

/* Board plates drop in like slotted number tiles */
.js .plate { opacity: 0; transform: translateY(-12px); }
.is-boarded .plate {
  animation: slot .5s var(--ease) forwards;
  animation-delay: calc(var(--i, 0) * 90ms + 260ms);
}
@keyframes slot {
  from { opacity: 0; transform: translateY(-14px) scaleY(.86); }
  to   { opacity: 1; transform: none; }
}

.js .hero__title .line > span {
  transform: translateY(105%);
  animation: rise .9s var(--ease) forwards;
  animation-delay: calc(var(--i, 0) * 110ms);
}
@keyframes rise { to { transform: none; } }

/* The boundary bar fills from the left when it scrolls into view */
.js .bline__bar > * { transform: scaleX(0); }
.js .is-in .bline__bar > *,
.js .bline.is-in .bline__bar > * {
  animation: sweep .6s var(--ease) forwards;
  animation-delay: 120ms;
}
@keyframes sweep { to { transform: none; } }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-delay: 0ms !important;
    transition-duration: .001ms !important;
  }
  .js [data-anim] { opacity: 1; transform: none; }
  .js .plate { opacity: 1; transform: none; }
  .js .bline__bar > * { transform: none; }
  .js .hero__title .line > span { transform: none; }
  .player:hover .player__shot img { transform: none; }
}

/* ---------- 19. Responsive ---------- */
@media (max-width: 1320px) {
  .nav a:not(.btn) { padding: .5rem .4rem; font-size: .69rem; letter-spacing: .05em; }
  .masthead__bar { gap: 1rem; }
}

@media (max-width: 1100px) {
  .nav {
    display: none;
    position: absolute; left: 0; right: 0; top: 100%;
    flex-direction: column;
    align-items: stretch;
    background: var(--pitch-2);
    border-bottom: 1px solid var(--pitch-line);
    padding: .75rem var(--gutter) 1.25rem;
    gap: 0;
  }
  .nav.is-open { display: flex; }
  .nav a:not(.btn) { padding: .85rem .25rem; font-size: .78rem; border-bottom: 1px solid var(--pitch-line); }
  .nav a:not(.btn)[aria-current="page"] { box-shadow: none; }
  .nav-toggle { display: block; }
  /* The masthead stays sticky here, as it is on desktop. The drawer is
     absolutely positioned against .masthead__bar, not the masthead, so
     pinning the header does not move it. */
  .masthead__bar { position: relative; }
  /* A drawer taller than the screen would otherwise be unreachable once
     the header is pinned, so it scrolls within whatever is left below
     the bar. Ten links clear this comfortably; it is here so adding an
     eleventh on a small phone cannot strand the last one. */
  .nav.is-open { max-height: calc(100dvh - 100%); overflow-y: auto; }

  .hero__grid, .split { grid-template-columns: 1fr; }
  .hero__grid { align-items: start; }
  .g-3, .g-4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .foot__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 620px) {
  .g-2, .g-3, .g-4 { grid-template-columns: minmax(0, 1fr); }
  /* One card at a time. A 150px minimum still fitted two squad cards
     across a phone, which left every figure and every name cramped. */
  .g-auto { grid-template-columns: minmax(0, 1fr); }

  /* Square, matching the source photographs. 16/10 kept the card short
     but cropped a 700x700 portrait top and bottom — at one card to a
     row there is width to spare, and a square panel shows the whole
     frame with nothing cut off. */
  .player__shot { aspect-ratio: 1 / 1; }
  .player__body { padding: 1rem 1.1rem 1.15rem; }
  .player__stats { gap: .5rem .75rem; padding-bottom: .85rem; }
  .player__stats .stat__v { font-size: 1.15rem; }
  .player__note {
    font-size: .92rem;
    line-height: 1.5;
    /* Three lines is enough to place a player; the rest is on their
       profile. Without this the note alone ran half a screen. */
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
  }
  .g-auto-lg { grid-template-columns: minmax(0, 1fr); }
  .ranked { grid-template-columns: minmax(0, 1fr) 34px; }
  .ranked__track { grid-column: 1 / -1; order: 3; }
  .foot__grid { grid-template-columns: 1fr; gap: 1.75rem; }
  .rule-list li { grid-template-columns: 1fr; gap: .4rem; }
  .fixture { grid-template-columns: 64px minmax(0,1fr); }
  .fixture > :last-child { grid-column: 1 / -1; }
  .board__plates { grid-template-columns: repeat(2, 1fr); }
  .player__stats { grid-template-columns: repeat(2, 1fr); }
  .fbox { width: 26px; height: 26px; font-size: .62rem; }
}

@media (max-width: 400px) {
  .masthead__bar { gap: .75rem; }
  .mark__badge { width: 44px; height: 44px; }
  .mark__name { font-size: 1.08rem; }
  .mark__sub { font-size: .52rem; letter-spacing: .1em; }
  .nav-toggle { padding: .45rem .6rem; font-size: .66rem; }
}

/* ---------- 21. Ethos & ticked lists ---------- */
.ethos__body {
  font-size: clamp(1rem, 1.2vw, 1.12rem);
  line-height: 1.55;
  margin: 0;
}
.on-pitch .ethos__body { color: var(--chalk); }

/* On the sponsors page the ruled list sits beside a tall photograph, so
   left to itself it stopped a long way short and left a hole. Stretching
   the column and letting each row grow equally keeps the rules
   continuous — spacing the rows apart instead would have broken the list
   into floating fragments. Only above the breakpoint: once the two stack
   there is no photo to match and the rows should size to their text. */
@media (min-width: 1101px) {
  .sponsor-split { align-items: stretch; }
  .sponsor-split > :last-child { display: flex; }
  .sponsor-split ul.ticked-list {
    flex: 1;
    display: flex;
    flex-direction: column;
  }
  .sponsor-split ul.ticked-list .ticked {
    flex: 1;
    display: flex;
    align-items: center;
  }
  /* The tick is pinned to a fixed offset from the top, which only reads
     as centred while the row is one line tall. */
  .sponsor-split ul.ticked-list .ticked::before {
    top: 50%;
    transform: translateY(-50%);
  }
}

.ticked { list-style: none; }
ul.ticked-list { list-style: none; margin: 0; padding: 0; }
ul.ticked-list .ticked {
  position: relative;
  padding: .7rem 0 .7rem 2rem;
  border-bottom: 1px solid var(--paper-line);
  font-size: .98rem;
}
.on-pitch ul.ticked-list .ticked { border-bottom-color: var(--pitch-line); }
ul.ticked-list .ticked::before {
  content: "";
  position: absolute;
  left: .25rem;
  top: 1.15rem;
  width: 11px; height: 6px;
  /* Claret on light: the crest cyan only reaches 2.4:1 on paper, so it
     is reserved for dark surfaces where it clears 5.8:1. */
  border-left: 2px solid var(--claret);
  border-bottom: 2px solid var(--claret);
  transform: rotate(-45deg);
}
.on-pitch ul.ticked-list .ticked::before {
  border-left-color: var(--cyan);
  border-bottom-color: var(--cyan);
}

/* ---------- 22. Acknowledgement of Country ----------
   Given plainly in type. No artwork: the dot-painting style used by
   other cricket bodies is an Aboriginal artist's copyrighted work and
   is not ours to reproduce. */
.ack {
  margin: 0 0 1.5rem;
  padding-top: 1.75rem;
  border-top: 1px solid var(--pitch-line);
  max-width: 78ch;
}
.ack__label {
  margin: 0 0 .55rem;
  font-size: .64rem;
  font-weight: 600;
  letter-spacing: .11em;
  text-transform: uppercase;
  color: var(--amber);
}
.ack__body {
  margin: 0;
  font-size: .84rem;
  line-height: 1.7;
  color: var(--ash);
}

.ack-full p {
  font-size: clamp(1rem, 1.2vw, 1.1rem);
  line-height: 1.7;
  max-width: var(--measure);
}
.ack-full p:first-child { color: var(--chalk); }

/* ---------- 23. Next-page control ----------
   Sits at the end of every page's main content. A link the reader
   chooses, not a scroll-triggered jump. */
.pagenext {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 2rem;
  padding: clamp(2.25rem, 4.5vw, 3.75rem) var(--gutter);
  background: var(--pitch-2);
  border-top: 1px solid var(--pitch-line);
  color: var(--chalk);
  text-decoration: none;
  position: relative;
  overflow: hidden;
  transition: background-color .3s var(--ease);
}
.pagenext::before {
  content: "";
  position: absolute; inset: 0;
  background: repeating-linear-gradient(to bottom, rgba(255,255,255,.028) 0 1px, transparent 1px 46px);
  pointer-events: none;
}
.pagenext:hover { background: var(--pitch-3); }

.pagenext__inner {
  position: relative;
  max-width: var(--maxw);
  margin-inline: auto;
  flex: 1;
  min-width: 0;
}
.pagenext__label {
  display: block;
  font-size: .68rem;
  font-weight: 600;
  letter-spacing: .11em;
  text-transform: uppercase;
  color: var(--amber);
  margin-bottom: .5rem;
}
.pagenext__title {
  display: block;
  font-family: var(--display);
  font-weight: 800;
  font-size: clamp(1.9rem, 4.2vw, 3.2rem);
  line-height: 1;
  letter-spacing: -.025em;
  text-transform: uppercase;
}
.pagenext__blurb {
  display: block;
  margin-top: .6rem;
  font-size: .94rem;
  color: var(--ash);
  max-width: 48ch;
}
.pagenext__arrow {
  position: relative;
  flex: none;
  font-size: clamp(1.6rem, 3vw, 2.4rem);
  line-height: 1;
  color: var(--amber);
  transition: transform .3s var(--ease);
}
.pagenext:hover .pagenext__arrow { transform: translateX(10px); }

@media (prefers-reduced-motion: reduce) {
  .pagenext:hover .pagenext__arrow { transform: none; }
}

@media (max-width: 620px) {
  .pagenext { gap: 1rem; }
}

/* ---------- 24. Partner wall ----------
   Dark tiles: sponsor marks are usually supplied light or single
   colour, and New Paradise Biryani's yellow (#F8DC20) measures well
   under 2:1 on white but clears 9:1 on the pitch ground. */
.partners {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  gap: clamp(.85rem, 1.6vw, 1.25rem);
}
.partner {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: .85rem;
  min-height: 190px;
  padding: 1.5rem 1.25rem;
  background: var(--pitch-2);
  border: 1px solid var(--pitch-line);
  border-radius: var(--r);
  text-decoration: none;
  color: var(--chalk);
  text-align: center;
  transition: border-color .25s var(--ease), background-color .25s var(--ease);
}
a.partner:hover { border-color: var(--amber); background: var(--pitch-3); }

.partner img {
  max-width: 82%;
  max-height: 96px;
  width: auto;
  object-fit: contain;
}
.partner__name {
  font-size: .84rem;
  font-weight: 600;
  line-height: 1.3;
}
.partner__tier {
  font-size: .58rem;
  font-weight: 600;
  letter-spacing: .11em;
  text-transform: uppercase;
  color: var(--amber);
}

.partner.is-empty,
.partner.is-open {
  border-style: dashed;
  color: var(--ash);
  font-size: .62rem;
  font-weight: 600;
  letter-spacing: .11em;
  text-transform: uppercase;
}
.partner.is-open { color: var(--amber); border-color: rgba(245,130,31,.45); }
.partner.is-open:hover { background: var(--pitch-3); border-color: var(--amber); }

/* ---------- 25. Back to top ---------- */
.totop {
  position: fixed;
  right: clamp(14px, 2.4vw, 28px);
  /* clears the iOS browser chrome and any home indicator */
  bottom: calc(clamp(14px, 2.4vw, 28px) + env(safe-area-inset-bottom, 0px));
  z-index: 90;

  /* 48px square — comfortably over the 44px touch-target minimum */
  width: 48px; height: 48px;
  display: grid; place-items: center;
  padding: 0;
  border: 1px solid var(--pitch-line);
  border-radius: 50%;
  background: rgba(10,51,60,.92);
  backdrop-filter: blur(8px);
  color: var(--amber);
  font-size: 1.15rem;
  line-height: 1;
  cursor: pointer;

  opacity: 0;
  /* visibility, not opacity alone: opacity-0 would leave the button in
     the tab order while invisible. It is stepped rather than
     transitioned — instantly on show, delayed until the fade finishes
     on hide — so the button is never waiting on a transition to become
     visible. */
  visibility: hidden;
  transform: translateY(10px);
  transition: opacity .25s var(--ease), transform .25s var(--ease),
              background-color .2s, border-color .2s,
              visibility 0s linear .25s;
}
.totop.is-in {
  opacity: 1;
  visibility: visible;
  transform: none;
  transition: opacity .25s var(--ease), transform .25s var(--ease),
              background-color .2s, border-color .2s,
              visibility 0s linear 0s;
}
.totop:hover { background: var(--claret); border-color: var(--claret); color: var(--chalk); }
.totop:active { transform: translateY(1px); }

@media (prefers-reduced-motion: reduce) {
  .totop { transition: opacity .001ms, visibility .001ms; transform: none; }
  .totop.is-in { transform: none; }
}

@media print { .totop { display: none; } }

/* ---------- 26. Founding story ----------
   The emotional centre of the site, so it gets a wider measure and
   more air than ordinary body copy, and the club's own pivot lines
   are allowed to carry weight. */
/* The story runs as a single centred column rather than sitting in a
   split beside the club facts — longform needs one uninterrupted
   measure, and the facts were competing with it for the same vertical
   run. */
/* Centred, and wide enough that the margins either side are small.
   Left-aligning it had only moved the empty space to one end.

   The type scales up with the column instead of staying at reading
   size: a wider measure at the same size is what makes long prose
   hard to track, so the two move together and the line stays near
   90 characters. */
.story__head {
  max-width: min(100%, 1180px);
  margin: 0 auto clamp(2rem, 3.5vw, 3rem);
}
.story {
  max-width: min(100%, 1180px);
  margin-inline: auto;
}
.story p {
  font-size: clamp(1.05rem, .55vw + .78rem, 1.32rem);
  line-height: 1.72;
  margin: 0 0 1.4em;
}
.story__lead {
  font-family: var(--display);
  font-weight: 700;
  font-size: clamp(1.35rem, 2.2vw, 1.9rem) !important;
  line-height: 1.15 !important;
  letter-spacing: -.02em;
  text-transform: uppercase;
  color: var(--chalk);
  margin: 2rem 0 1.5em !important;
}
.on-book .story__lead, .section:not(.on-pitch) .story__lead { color: var(--ink); }

.story__pull {
  margin: 2.5rem 0;
  padding: 0 0 0 1.5rem;
  border-left: 3px solid var(--amber);
  font-family: var(--display);
  font-weight: 800;
  font-size: clamp(1.6rem, 3.2vw, 2.6rem);
  line-height: 1.05;
  letter-spacing: -.025em;
  text-transform: uppercase;
  color: var(--amber);
  max-width: 20ch;
}

/* ---------- vision & mission ---------- */
.vm + .vm { margin-top: clamp(1.5rem, 3vw, 2.25rem); }
.vm__label {
  font-family: var(--mono);
  font-size: .7rem;
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--claret-ink);
  margin: 0 0 .5rem;
}
.vm__text {
  font-size: clamp(1.1rem, 1.5vw, 1.3rem);
  line-height: 1.55;
  color: var(--ink);
  margin: 0;
  max-width: 52ch;
}

/* ---------- club manager ---------- */
/* ---------- the Kannada mark ----------
   The badge is a navy disc, which on the dark theme sits close to the
   page behind it. The hairline ring keeps its edge readable there
   without drawing a box around it in light mode. */
.kmark {
  display: flex; align-items: center; flex-wrap: wrap;
  gap: clamp(1.25rem, 3vw, 2.75rem);
}
.kmark__badge {
  width: clamp(130px, 16vw, 190px); height: auto; flex: none;
  border-radius: 50%;
  box-shadow: 0 0 0 1px rgba(255,255,255,.10), var(--e2);
}
.kmark__name {
  font-family: var(--display);
  font-size: clamp(1.6rem, 3.6vw, 2.6rem);
  font-weight: 700; line-height: 1.25; color: var(--ink);
  margin: .35rem 0 0;
}

/* The manager block sits in the full-width grid, so the prose finished
   38px from the right edge — the page gutter and nothing else — while
   the portrait beside it had 133px of air on its left. Lopsided, and the
   line ran to 85 characters, which is what made it feel cramped.

   Two things fix it. The inset pads the text column back so both sides
   of the photo carry similar space, and it scales with the viewport so
   it does the work at laptop widths. The cap then holds the measure down
   on a large display, where the column would otherwise keep growing.
   Both collapse on a phone, where the two stack and the page gutter is
   the only spacing wanted.

   Vertically the portrait is the tallest thing in the row, so it set the
   band's height and got only the section's own padding above and below
   it — it read as filling the section edge to edge. The extra block
   padding here is on top of that, so the photo sits in open space rather
   than defining the band. */
.manager-split {
  --manager-inset: clamp(0px, 4.5vw, 88px);
  padding-block: clamp(0.5rem, 3.4vw, 3.75rem);
}

/* The club mark and the manager sit on the same paper background, so
   with nothing between them they read as one long band. A hairline on
   the wrap rather than the section keeps it aligned to the content
   column instead of running the full bleed. --paper-line is defined in
   both themes, so it holds up dark as well as light. */
.section-rule { border-top: 1px solid var(--paper-line); }
.manager-split > :last-child {
  padding-inline-end: var(--manager-inset);
  max-width: 76ch;
}
@media (max-width: 900px) {
  .manager-split > :last-child { padding-inline-end: 0; }
}

/* The portrait is 3:4, so on a wide screen it grows taller while the
   text beside it needs fewer lines — the column ends up floating in the
   middle of a tall photo. Capping the width caps the height, and past
   about 1400px the two stay within a tenth of each other. */
.manager__shot { margin: 0 auto; max-width: 34rem; width: 100%; }
.manager__shot img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: var(--r);
  border: 1px solid var(--paper-line);
  box-shadow: var(--e2);
}

/* ---------- club socials ----------
   One grid for video and stills together. Everything sits in the same
   4:3 box: photographs fill it, video is fitted inside it. Mixing the
   two at their own ratios left portrait clips twice the height of
   their neighbours and holes in the rows. */
.media-grid {
  display: grid;
  gap: clamp(1.25rem, 2.4vw, 2.25rem);
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 300px), 1fr));
}
.media { margin: 0; }
.media__frame {
  position: relative;
  aspect-ratio: 4 / 3;
  overflow: hidden;
  border-radius: var(--r);
  border: 1px solid var(--paper-line);
  background: var(--pitch-deep);
  box-shadow: var(--e1);
}
.media__frame > img,
.media__frame > video {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  display: block;
}
/* A still can be cropped to the tile; a clip cannot, because the crop
   would hide whatever is being filmed. */
.media__frame > img   { object-fit: cover; }
.media__frame > video { object-fit: contain; background: var(--pitch-deep); }
.media figcaption {
  margin-top: .8rem;
  font-size: .9rem;
  line-height: 1.5;
  color: var(--ash-deep);
  max-width: 44ch;
}

/* The column beside the join form ran a long way down as empty paper —
   that form is tall and the copy next to it is three lines. */
.join-shot {
  margin: clamp(1.75rem, 3vw, 2.75rem) 0 0;
}
.join-shot img {
  width: 100%;
  height: auto;
  border-radius: var(--r);
  border: 1px solid var(--paper-line);
}
.join-shot figcaption {
  margin-top: .8rem;
  font-size: .84rem;
  line-height: 1.5;
  color: var(--ash-deep);
  max-width: 46ch;
}

.story__signoff {
  margin-top: 2.25rem !important;
  font-style: italic;
  color: var(--ash);
}
.on-book .story__signoff { color: var(--ash-deep); }

@media (max-width: 620px) {
  .story__pull { padding-left: 1rem; margin-block: 1.75rem; }
}


/* ---------- 27. Club facts ---------- */
.fact {
  display: flex;
  flex-direction: column;
  gap: .6rem;
  padding: 1.4rem 0 0;
  border-top: 2px solid var(--claret);
}
.fact__k {
  font-size: .64rem;
  font-weight: 600;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--claret-ink);
}
.fact__v {
  font-size: 1rem;
  line-height: 1.5;
  color: var(--ink);
}
.on-pitch .fact { border-top-color: var(--amber); }
.on-pitch .fact__k { color: var(--amber); }
.on-pitch .fact__v { color: var(--chalk); }
