/* ==========================================================================
   Village Lions RFC
   Black and white like the Barbarians. Red carries the Red Lion story.
   Display: Sportize. Body: JUST Sans. Hero cry: The Wild Strokes.
   All licensed via Envato Elements, self hosted. See /fonts/README.md.
   ========================================================================== */

/* Fonts: drop the licensed files into /fonts using these exact names. Only one
   format per name is needed; woff2 is listed first because it is the smallest,
   and browsers skip sources they cannot load. Until the files land the site
   renders on the fallback stacks. */
@font-face {
  font-family: "Sportize";
  src: url("/fonts/Sportize-ExtraBold.woff2") format("woff2");
  font-weight: 900;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "JUST Sans";
  src: url("/fonts/JUSTSans-Regular.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "JUST Sans";
  src: url("/fonts/JUSTSans-Medium.woff2") format("woff2");
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "JUST Sans";
  src: url("/fonts/JUSTSans-Bold.woff2") format("woff2");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}
/* Hero rallying cry brush font. CaveatBrush is the bundled OFL fallback. */
@font-face {
  font-family: "The Wild Strokes";
  src: url("/fonts/TheWildStrokes.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Caveat Brush";
  src: url("/fonts/CaveatBrush-Regular.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

/* Tokens */
:root {
  /* Brand constants */
  --ink: #231F20;          /* logo black */
  --paper: #FFFFFF;        /* kit white */
  /* The crest red only reaches 4.38:1 against white, under the 4.5:1 that
     small text needs, so it fills nothing that carries a word. It stays on
     rules, bars and other marks where 3:1 is the bar. */
  --red: #ED1C24;          /* the Red Lion. Decorative fills only. */
  --red-deep: #C4141B;     /* 6.06:1 on white: every fill behind white text */
  --red-deepest: #9E1016;  /* hover for those fills */
  --red-ink: #FF5A5F;      /* red as text. The crest red only reaches 3.3:1 on a
                              dark panel, so small red type uses this tint. */

  /* Surfaces. The page runs dark, so these carry the theme. */
  --bg: var(--ink);        /* page background */
  --fg: var(--paper);      /* primary text */
  --smoke: #2C2829;        /* lifted panels */
  --line: #3B3738;         /* hairline rules */
  --steel: #B9B6B3;        /* secondary text */

  --font-display: "Sportize", "Arial Narrow", "Helvetica Neue", Impact, sans-serif;
  --font-body: "JUST Sans", "Helvetica Neue", Arial, sans-serif;
  --font-cry: "The Wild Strokes", "Caveat Brush", "Bradley Hand", "Segoe Script", cursive;

  --wrap: 1160px;
  --gutter: clamp(1.25rem, 4vw, 2.5rem);
  --section-pad: clamp(4rem, 9vw, 7.5rem);

  --hoop: 14px; /* Barbarians hoop stripe height */
}

/* Reset */
*, *::before, *::after { box-sizing: border-box; }
body, h1, h2, h3, p, ul { margin: 0; }
ul { padding: 0; list-style: none; }
img { display: block; max-width: 100%; height: auto; }
a { color: inherit; }

html { scroll-behavior: smooth; color-scheme: dark; }
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { animation: none !important; transition: none !important; }
}

body {
  font-family: var(--font-body);
  font-size: 1.0625rem;
  line-height: 1.6;
  color: var(--fg);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
}

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

.visually-hidden {
  position: absolute; width: 1px; height: 1px;
  clip-path: inset(50%); overflow: hidden; white-space: nowrap;
}

.skip-link {
  position: absolute; left: 1rem; top: -3rem;
  background: var(--fg); color: var(--bg);
  padding: 0.5rem 1rem; z-index: 100; transition: top 0.15s;
}
.skip-link:focus { top: 1rem; }

:focus-visible { outline: 3px solid var(--red-ink); outline-offset: 2px; }

/* The header is sticky and about 85px tall, so an anchor lands behind it
   without this. Measured on a phone, where the section title sat 22px under
   the bar. */
:target { scroll-margin-top: 6.5rem; }

/* Type */
.eyebrow {
  font-weight: 700;
  font-size: 0.8125rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--red-ink);
  margin-bottom: 1rem;
}

.section__title {
  font-family: var(--font-display);
  font-weight: 900;
  font-size: clamp(2.5rem, 6vw, 4.25rem);
  line-height: 0.95;
  letter-spacing: 0.01em;
  text-transform: uppercase;
}

.section__sub { color: var(--steel); margin-top: 0.75rem; max-width: 46ch; }

/* Buttons */
.btn {
  display: inline-block;
  font-family: var(--font-body);
  font-weight: 700;
  font-size: 0.9375rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  text-decoration: none;
  padding: 0.9rem 1.6rem;
  border: 2px solid transparent;
  transition: background-color 0.15s, color 0.15s, border-color 0.15s;
}
.btn--small { padding: 0.55rem 1.05rem; font-size: 0.8125rem; }
.btn--red { background: var(--red-deep); color: var(--paper); }
.btn--red:hover { background: var(--red-deepest); }
.btn--outline { border-color: var(--fg); color: var(--fg); }
.btn--outline:hover { background: var(--fg); color: var(--bg); }
.btn--ghost { border-color: var(--line); color: var(--fg); }
.btn--ghost:hover { border-color: var(--fg); }

/* Hoops: the Barbarians jersey stripe, used as a structural divider */
.hoops {
  height: calc(var(--hoop) * 3);
  background: repeating-linear-gradient(
    to bottom,
    var(--ink) 0,
    var(--ink) var(--hoop),
    var(--paper) var(--hoop),
    var(--paper) calc(var(--hoop) * 2)
  );
}

/* Promotion strip. Surface comes from its band class. */
.promo-strip {
  font-size: 0.875rem;
  letter-spacing: 0.02em;
}
.promo-strip p { padding: 0.55rem var(--gutter); text-align: center; }
.promo-strip__flag {
  display: inline-block;
  background: var(--red-deep);
  color: var(--paper);
  font-weight: 700;
  text-transform: uppercase;
  font-size: 0.6875rem;
  letter-spacing: 0.14em;
  padding: 0.15rem 0.5rem;
  margin-right: 0.6rem;
  transform: translateY(-1px);
}

/* Header */
.site-header {
  position: sticky; top: 0; z-index: 50;
  background: var(--bg);
  border-bottom: 1px solid var(--line);
}
.site-header__inner {
  display: flex; align-items: center; justify-content: space-between;
  gap: 1.5rem; padding-block: 0.7rem;
}
.site-header__brand {
  display: flex; align-items: center; gap: 0.7rem;
  text-decoration: none;
}
/* The image pipeline rewrites width and height to the largest variant it
   generated, so the crest needs its display size in CSS or the header grows
   to fit a 132px logo. */
.site-header__brand img { width: 44px; height: auto; }
.site-footer__brand img { width: 52px; height: auto; }
.site-header__wordmark {
  font-family: var(--font-display);
  font-weight: 900;
  font-size: 1.35rem;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  line-height: 1;
}
.site-header__wordmark em { font-style: normal; }

.site-nav ul { display: flex; align-items: center; gap: 1.6rem; }
.site-nav a:not(.btn) {
  font-weight: 500; font-size: 0.9375rem; text-decoration: none;
  padding-block: 0.4rem;
  border-bottom: 2px solid transparent;
}
.site-nav a:not(.btn):hover { border-bottom-color: var(--red-ink); }

/* Teams dropdown. The word is a link to the index; the chevron discloses
   the sides. Hover and keyboard focus open it on desktop too, and the phone
   menu shows the sides as an indented list instead. */
.site-nav__group { position: relative; display: flex; align-items: center; gap: 0.1rem; }
.site-nav__more {
  display: none;
  align-items: center; justify-content: center;
  background: none; border: 0; padding: 0;
  /* At least 32px square: the icon alone left a 21px target. */
  width: 2rem; height: 2rem;
  color: var(--fg); cursor: pointer;
}
.js .site-nav__more { display: inline-flex; }
.site-nav__more svg { width: 0.9rem; height: 0.9rem; transition: transform 0.15s; }
.site-nav__group.is-open .site-nav__more svg { transform: rotate(180deg); }
.site-nav .site-nav__sub {
  display: none;
  position: absolute; top: 100%; left: -0.85rem; z-index: 60;
  flex-direction: column; align-items: stretch; gap: 0;
  min-width: 13rem;
  padding: 0.5rem 0;
  /* Transparent top border keeps the hover unbroken across the header's padding. */
  border: 1px solid var(--line);
  border-top: 0.7rem solid transparent;
  background: var(--smoke);
  background-clip: padding-box;
}
.site-nav__group.is-open .site-nav__sub { display: flex; }
.site-nav .site-nav__sub a:not(.btn) { display: block; padding: 0.5rem 1rem; border-bottom: 0; }
.site-nav .site-nav__sub a:not(.btn):hover,
.site-nav .site-nav__sub a[aria-current="page"] { color: var(--red-ink); }

.nav-toggle {
  display: none;
  background: none; border: 0; padding: 0.5rem; cursor: pointer;
}
.nav-toggle__bar {
  display: block; width: 24px; height: 2px;
  background: var(--fg); margin-block: 5px;
  transition: transform 0.2s, opacity 0.2s;
}
.nav-toggle[aria-expanded="true"] .nav-toggle__bar:nth-child(1) { transform: translateY(7px) rotate(45deg); }
.nav-toggle[aria-expanded="true"] .nav-toggle__bar:nth-child(2) { opacity: 0; }
.nav-toggle[aria-expanded="true"] .nav-toggle__bar:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }

/* Photographs.
   Monochrome wherever a photo sits behind type: it guarantees the type stays
   legible and it unifies source photos shot on a dozen different phones. Photos
   that are themselves the content, team shots and headshots, keep their colour. */
.photo--mono { filter: grayscale(1); }

/* Fixed slots, so mixed originals do not produce a ragged page. */
.team-shot, .coach-card__photo { display: block; width: 100%; object-fit: cover; }
.team-shot { aspect-ratio: 3 / 2; }
.coach-card__photo { aspect-ratio: 1 / 1; margin-bottom: 1rem; }
.team-shot-figure { margin: 0 0 clamp(2rem, 5vw, 3rem); }
.team-shot-figure figcaption {
  margin-top: 0.75rem;
  font-size: 0.875rem;
  color: var(--steel);
}

/* A row of action shots. Each sits at a third of the measure, which is where
   photographer proofs around 1400px wide still look crisp on dense screens. */
.photo-strip { margin: clamp(2rem, 5vw, 3rem) 0 0; }
.photo-strip__grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1rem;
}
.photo-strip__item { display: block; width: 100%; aspect-ratio: 3 / 2; object-fit: cover; }
.photo-strip figcaption { margin-top: 0.75rem; font-size: 0.875rem; color: var(--steel); }
@media (max-width: 720px) {
  .photo-strip__grid { grid-template-columns: 1fr; }
}

/* Hero */
.hero { overflow: hidden; }
.hero--photo { position: relative; isolation: isolate; }
.hero--photo .hero__inner { position: relative; z-index: 1; }
.hero__slides { position: absolute; inset: 0; z-index: -2; }
.hero__photo { width: 100%; height: 100%; object-fit: cover; }

/* Slideshows. Slides stack in one box and crossfade on .is-active, and the
   active photo drifts in by a few percent over its stay so the frame never
   sits still. The homepage hero variant is decoration under text and has no
   controls; the team page hero (.page-hero) is content and gets overlaid
   arrows, a counter, a red progress bar timing the next slide, and a
   thumbnail strip that doubles as the dots. The controller in main.js
   handles autoplay and sets --slide-interval from data-interval. Everything
   renders fine without it: the first slide is visible by default and the
   arrows stay hidden until JS marks the slideshow ready. */
.slideshow__slide {
  position: absolute; inset: 0;
  margin: 0;
  opacity: 0;
  transition: opacity 1.2s ease;
}
/* Everything except the showing slide and the one queued behind it is taken
   out of the layout, which is the only thing that keeps the browser from
   fetching it: an opacity-zero image stacked in the viewport still counts as
   in the viewport, so loading="lazy" defers nothing on its own. The active
   slide is exempt so the server-rendered first frame survives without JS. */
.slideshow__slide:not(.is-shown):not(.is-active) { display: none; }
.slideshow__slide.is-active { opacity: 1; z-index: 1; }
.slideshow__slide picture, .slideshow__slide img { display: block; width: 100%; height: 100%; }
.slideshow__slide img {
  object-fit: cover;
  transform: scale(1);
  transition: transform calc(var(--slide-interval, 6s) + 1.5s) linear;
}
.slideshow__slide.is-active img { transform: scale(1.07); }
@media (prefers-reduced-motion: reduce) {
  .slideshow__slide, .slideshow__slide img { transition: none; }
  .slideshow__slide.is-active img { transform: none; }
}

/* Team page hero. The team shot sits full bleed behind the masthead on
   desktop, title bottom left where the scrim is heaviest. On phones the
   photo stacks above the title at its own 3:2 shape instead, because a full
   bleed crop there would cut both sides off a group photo. The photo is the
   content, so unlike the homepage hero it keeps its colour; the scrim is
   verified by pixel sampling. */
.page-head.page-head--photo { padding-top: 0; }
.page-head.page-head--photo .hoops { margin-top: 0; }
.page-hero {
  position: relative;
  isolation: isolate;
  display: flex; flex-direction: column; justify-content: flex-end;
  /* --hero-aspect, set per photo, keeps the frame from growing taller than
     the picture is wide, which is what would crop the ends off a panorama.
     Unset, the fallback is small enough never to bind. */
  min-height: min(clamp(28rem, 70vh, 42rem), calc(100vw / var(--hero-aspect, 0.5)));
  overflow: hidden;
}
.page-hero__media { position: absolute; inset: 0; z-index: 0; background: var(--ink); }
.page-hero__scrim {
  position: absolute; inset: 0; z-index: 2;
  background:
    linear-gradient(to top,
      rgba(35,31,32,0.96) 0%,
      rgba(35,31,32,0.9) 26%,
      rgba(35,31,32,0.82) 44%,
      rgba(35,31,32,0.36) 64%,
      rgba(35,31,32,0.04) 100%),
    linear-gradient(to right,
      rgba(35,31,32,0.42) 0%,
      rgba(35,31,32,0.14) 45%,
      rgba(35,31,32,0) 100%),
    linear-gradient(to bottom, rgba(35,31,32,0.72) 0%, rgba(35,31,32,0) 28%);
}
.page-hero__copy {
  position: relative; z-index: 3;
  width: 100%;
  padding-top: clamp(7rem, 16vw, 10rem);
  padding-bottom: clamp(2.25rem, 4vw, 3.25rem);
}
/* The masthead photo is one still picture, not a slideshow, so it skips the
   slow drift. Scaling it up would crop the edges the frame is sized to keep. */
.page-hero .slideshow__slide img,
.page-hero .slideshow__slide.is-active img { transform: none; }
/* Crest red only manages 3.4:1 once a photo lifts the backdrop. */
.page-head--photo .eyebrow { color: var(--fg); }
@media (max-width: 720px) {
  /* Stacked, and at the photo's own shape rather than a fixed one, so a
     panorama keeps its ends and a squarer frame keeps its top. */
  .page-hero { display: block; min-height: 0; }
  .page-hero__media { position: relative; }
  .page-hero .slideshow__slide { position: static; }
  .page-hero__photo { height: auto; }
  /* Only a foot fade, so the photo melts into the black band the title sits on. */
  .page-hero__scrim { background: linear-gradient(to top, rgba(35,31,32,0.85) 0%, rgba(35,31,32,0) 32%); }
  .page-hero__copy { padding-top: 1.5rem; padding-bottom: 2rem; }
}

/* Gallery. The side's action shots, edge to edge, further down the page.
   Sixteen by nine on desktop and the photos' own 3:2 on phones so nothing
   is cropped. Nothing is written over the photo: the counter, credit and
   thumbnails sit in the black band beneath it. */
.gallery { padding-bottom: 0.25rem; }
.gallery__head { padding-block: var(--section-pad) clamp(1.75rem, 4vw, 2.5rem); }
.gallery__media {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  /* Three by two, the shape of nearly every photograph in the set, and as
     wide as the window allows while the whole frame still fits the height.
     Full bleed on a phone and on a tall window, a little short of it on a
     wide one, which is the price of never cutting a photo. */
  width: min(100%, calc(82vh * 3 / 2));
  margin-inline: auto;
  aspect-ratio: 3 / 2;
  background: var(--ink);
}
/* Nothing here is cropped. The odd 4:3 or upright frame sits inside the box
   against the black rather than losing its edges, and the slow drift is off
   because scaling up would push those edges back out of the frame. */
.gallery .slideshow__slide img,
.gallery .slideshow__slide.is-active img {
  object-fit: contain;
  transform: none;
}
.gallery__bar {
  display: flex; align-items: center; gap: 1.25rem;
  padding-block: 0.6rem 1.1rem;
}
/* A visible way to stop content that moves on its own, rather than relying
   on hover, which a keyboard or screen reader visitor never triggers. */
.slideshow__toggle {
  flex: 0 0 auto;
  display: none;
  align-items: center; justify-content: center;
  width: 2.75rem; height: 2.75rem;
  padding: 0; border: 1px solid var(--line); border-radius: 50%;
  background: none; color: var(--fg); cursor: pointer;
  transition: border-color 0.15s;
}
.is-ready .slideshow__toggle { display: inline-flex; }
.slideshow__toggle:hover { border-color: var(--fg); }
.slideshow__toggle svg { width: 1.1rem; height: 1.1rem; }
.slideshow__icon-play { display: none; }
.is-stopped .slideshow__icon-play { display: block; }
.is-stopped .slideshow__icon-pause { display: none; }
@media (prefers-reduced-motion: reduce) {
  /* Nothing autoplays, so there is nothing to pause. */
  .slideshow__toggle { display: none !important; }
}
.gallery__bar .slideshow__thumbs { flex: 1 1 auto; min-width: 0; margin-top: 0; }
.gallery__meta {
  flex: 0 0 auto;
  order: 1;
  margin: 0;
  display: flex; flex-direction: column; align-items: flex-end; gap: 0.3rem;
  text-align: right;
  color: var(--fg);
}
@media (max-width: 720px) {
  .gallery__bar { flex-direction: column; align-items: stretch; gap: 0.6rem; }
  .gallery__meta { order: 0; flex-direction: row; align-items: baseline; justify-content: space-between; text-align: left; }
}

/* Counter and credit, top right of a photo, inside the measure on wide screens. */
.slideshow__meta {
  position: absolute; top: 0; z-index: 3;
  right: max(var(--gutter), calc((100% - var(--wrap)) / 2 + var(--gutter)));
  margin: 0;
  padding-top: clamp(1.25rem, 3vw, 2rem);
  display: flex; flex-direction: column; align-items: flex-end; gap: 0.35rem;
  text-align: right;
  color: var(--fg);
  pointer-events: none;
}
.slideshow__count {
  font-family: var(--font-display);
  font-weight: 900;
  font-size: clamp(1.75rem, 3vw, 2.5rem);
  line-height: 1;
  letter-spacing: 0.02em;
}
.slideshow__count-total {
  margin-left: 0.4rem;
  font-size: 0.875rem;
  letter-spacing: 0.1em;
}
.slideshow__text { font-size: 0.9375rem; }
.slideshow__credit {
  font-size: 0.6875rem; font-weight: 700;
  letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--steel);
}
.slideshow__text:empty, .slideshow__credit:empty { display: none; }
.slideshow__btn {
  position: absolute; top: 50%; z-index: 3;
  transform: translateY(-50%);
  width: 3rem; height: 3rem;
  display: none; align-items: center; justify-content: center;
  padding: 0; border: 0; border-radius: 50%;
  background: rgba(35,31,32,0.62);
  color: var(--paper);
  cursor: pointer;
  opacity: 0;
  transition: opacity 0.2s, background-color 0.15s;
}
.slideshow__btn svg { width: 1.25rem; height: 1.25rem; }
.slideshow__btn--prev { left: 1rem; }
.slideshow__btn--next { right: 1rem; }
.is-ready .slideshow__btn { display: flex; }
[data-slideshow]:hover .slideshow__btn,
[data-slideshow]:focus-within .slideshow__btn { opacity: 1; }
.slideshow__btn:hover { background: var(--red); }
@media (hover: none) {
  .slideshow__btn { opacity: 1; }
}
/* The bar fills over one interval and pauses with the autoplay. */
.slideshow__progress {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 3;
  height: 3px;
  background: rgba(255,255,255,0.18);
}
.slideshow__progress span { display: block; height: 100%; width: 0; background: var(--red); }
.is-playing .slideshow__progress span {
  animation: slideshow-progress var(--slide-interval, 6s) linear forwards;
}
.is-paused .slideshow__progress span { animation-play-state: paused; }
@keyframes slideshow-progress { from { width: 0; } to { width: 100%; } }
@media (prefers-reduced-motion: reduce) {
  .slideshow__progress { display: none; }
}
/* Thumbs hold their 64px basis and scroll sideways past what fits. How many
   fit varies: .gallery__meta takes what the caption and credit need, so the
   strip measures 934px on a slide with no credit and 724px on one with, which
   fits 13 and 10. That also means the bar jumps width as the carousel moves
   between them. See CLAUDE.md. */
.slideshow__thumbs {
  display: none; gap: 0.35rem;
  /* Room inside the scrollport for the focus ring, which overflow would
     otherwise shave off the top and bottom. */
  padding-block: 4px;
  margin-top: 0.5rem;
  overflow-x: auto;
  scroll-snap-type: x proximity;
  scrollbar-width: none;
}
.slideshow__thumbs::-webkit-scrollbar { display: none; }
.is-ready .slideshow__thumbs { display: flex; }
.slideshow__thumb {
  position: relative;
  flex: 1 0 64px; max-width: 120px;
  aspect-ratio: 3 / 2;
  padding: 0; border: 0;
  background: var(--ink);
  cursor: pointer;
  opacity: 0.45;
  scroll-snap-align: center;
  transition: opacity 0.2s;
}
.slideshow__thumb::after {
  content: "";
  position: absolute; left: 0; right: 0; bottom: 0;
  height: 3px;
  background: var(--red);
  transform: scaleX(0);
  transition: transform 0.2s;
}
.slideshow__thumb:hover { opacity: 0.85; }
.slideshow__thumb[aria-current="true"] { opacity: 1; }
.slideshow__thumb[aria-current="true"]::after { transform: scaleX(1); }
.slideshow__thumb picture, .slideshow__thumb img { display: block; width: 100%; height: 100%; object-fit: cover; }
/* Scrim. Heaviest on the left where the cry sits, so the right of the frame
   still reads as a photograph. Tuned against a deliberately bright image. */
.hero__scrim {
  position: absolute; inset: 0; z-index: -1;
  background:
    linear-gradient(to right,
      rgba(35,31,32,0.95) 0%,
      rgba(35,31,32,0.93) 40%,
      rgba(35,31,32,0.86) 58%,
      rgba(35,31,32,0.66) 80%,
      rgba(35,31,32,0.52) 100%),
    linear-gradient(to bottom, rgba(35,31,32,0.45) 0%, rgba(35,31,32,0.18) 40%, rgba(35,31,32,0.65) 100%);
}
/* The crest red only manages 3.4:1 once the scrim lifts the backdrop, so the
   kicker goes white over a photo. Red still carries the button below it. */
.hero--photo .eyebrow { color: var(--fg); }
@media (max-width: 900px) {
  /* Type sits over the whole frame on a narrow screen, so the horizontal
     fade has nothing to protect and the scrim runs top to bottom instead.
     It was a flat 92%, which meant four photographs downloaded on the
     device most visitors use and then sat 92% covered. It lifts at the head
     of the frame, above the kicker, where the photograph gets to be a
     photograph, and settles onto the type below. Verified by pixel sampling
     every slide at 390px: the worst reading is 7.49:1 on the cry and 5.6:1
     on the kicker, against the 3:1 and 4.5:1 those sizes need. */
  .hero__scrim {
    background: linear-gradient(to bottom,
      rgba(35,31,32,0.55) 0%,
      rgba(35,31,32,0.78) 16%,
      rgba(35,31,32,0.86) 42%,
      rgba(35,31,32,0.9) 100%);
  }
}
.hero__inner {
  display: grid;
  grid-template-columns: minmax(0, 1.15fr) minmax(0, 0.85fr);
  align-items: center;
  gap: clamp(2rem, 5vw, 4rem);
  padding-block: clamp(3.5rem, 8vw, 6.5rem);
}
.hero__title {
  font-family: var(--font-display);
  font-weight: 900;
  font-size: clamp(3.4rem, 9.5vw, 7.25rem);
  line-height: 0.88;
  text-transform: uppercase;
  letter-spacing: 0.005em;
}
.hero__title span { display: block; }
/* Rallying cry: brush lettering needs its own case and breathing room. */
.hero__title--cry {
  font-family: var(--font-cry);
  font-weight: 400;
  font-size: clamp(2.9rem, 9vw, 6.5rem);
  line-height: 1.15;
  letter-spacing: normal;
  text-transform: none;
  color: var(--fg);
}
.hero__full-stop, .donate__full-stop { color: var(--red-ink); }
.hero__lede { margin-top: 1.5rem; max-width: 52ch; color: var(--steel); font-size: 1.125rem; }
.hero__actions { margin-top: 2rem; display: flex; flex-wrap: wrap; gap: 0.9rem; }
.hero__crest img { width: min(100%, 420px); margin-inline: auto; }

/* Sections */
.section { padding-block: var(--section-pad); }

/* Bands. The page alternates dark and white down its length. Each band
   re-scopes the surface tokens, and because every component reads those
   tokens, rows, rules, buttons, badges, and focus rings retheme themselves. */
.band--panel {
  --bg: #2C2829;
  --smoke: #383334;        /* nested tiles lift above the band */
  --line: #474243;
  background: var(--bg);
}
.band--light {
  --bg: var(--paper);
  --fg: var(--ink);
  --smoke: #F5F5F3;
  --line: #D3D0CD;
  --steel: #6B6866;
  --red-ink: #C4141B;      /* crest red is 4.38:1 on white; this is 6.06:1 */
  background: var(--bg);
  color: var(--fg);
}
/* Interior page masthead. The homepage has its own hero; every other page
   opens with this. */
.page-head { padding-top: clamp(3rem, 7vw, 5rem); }
.page-head__title {
  font-family: var(--font-display);
  font-weight: 900;
  font-size: clamp(2.75rem, 8vw, 5.5rem);
  line-height: 0.92;
  text-transform: uppercase;
  letter-spacing: 0.01em;
}
.page-head__badge {
  display: inline-block;
  margin-top: 1rem;
  background: var(--red-deep); color: var(--paper);
  font-size: 0.6875rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.12em;
  padding: 0.25rem 0.6rem;
}
.page-head__lede {
  margin-top: 1.25rem;
  max-width: 52ch;
  color: var(--steel);
  font-size: 1.125rem;
}
.page-head .hoops { margin-top: clamp(3rem, 7vw, 5rem); }

/* Body copy at a readable measure. */
.prose { max-width: 68ch; }
.prose p + p { margin-top: 1.1rem; }
.prose h3 {
  font-family: var(--font-display);
  font-weight: 900;
  font-size: 1.5rem;
  text-transform: uppercase;
  margin-top: 2.25rem;
  margin-bottom: 0.6rem;
}
.prose a { color: var(--red-ink); }

/* A photograph set into running copy, at the measure of the prose around it.
   Height follows the file, so archive scans and modern frames both show
   whole rather than being cropped to a common shape. */
.prose-figure { margin: clamp(1.75rem, 4vw, 2.5rem) 0; }
.prose-figure img { display: block; width: 100%; height: auto; }
.prose-figure figcaption {
  margin-top: 0.75rem;
  font-size: 0.875rem;
  color: var(--steel);
}

/* Governing body marks. Supplied artwork, so they keep their own colour and
   sit on a light band where both of them read. */
.affiliations { margin-top: clamp(2.5rem, 6vw, 3.5rem); max-width: 68ch; }
.affiliation-list {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: clamp(1.5rem, 4vw, 3rem);
  margin-top: 1.5rem; padding: 0;
  list-style: none;
}
.affiliation-list a {
  display: block;
  transition: opacity 0.15s;
}
.affiliation-list a:hover { opacity: 0.7; }
.affiliation-list img {
  display: block;
  height: clamp(72px, 9vw, 104px);
  width: auto;
}

/* A mark rather than a photograph: shown whole and centred, never cropped. */
.stub-logo { margin: 0 auto clamp(2rem, 5vw, 3rem); max-width: 400px; }
.stub-logo img { display: block; width: 100%; height: auto; }

/* Unfilled content the club still needs to supply. Deliberately visible so
   nothing placeholder reaches launch unnoticed. */
.placeholder { color: var(--steel); font-style: italic; font-weight: 400; }

/* A single red word inside a heading, the one place red appears as type
   outside the eyebrows. Reads as --red-ink, so it retunes per band. */
.accent { color: var(--red-ink); }

/* Donate page: the case in two columns, then every side it reaches. */
.donate-cols {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(2rem, 5vw, 3.5rem);
}
.donate-grid {
  display: grid;
  /* auto-fit rather than a fixed three, so the row follows how many sides
     carry "donate" in teams.json instead of leaving an empty cell. The min()
     stops the track outgrowing the viewport on a narrow phone. */
  grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr));
  gap: clamp(1rem, 2.5vw, 1.5rem);
}
.donate-card { display: block; text-decoration: none; color: inherit; }
.donate-card__photo {
  display: block; width: 100%;
  aspect-ratio: 3 / 2;
  object-fit: cover;
}
.donate-card__name {
  margin-top: 0.75rem;
  font-family: var(--font-display);
  font-weight: 900;
  font-size: 1.25rem;
  line-height: 1;
  text-transform: uppercase;
  transition: color 0.15s;
}
.donate-card:hover .donate-card__name { color: var(--red-ink); }
@media (max-width: 900px) {
  .donate-cols { grid-template-columns: 1fr; }
}
.page-cta { text-align: center; }
/* Both of these have a max-width, so without an auto margin the box hangs
   off the left of the wrap and its centred text is centred inside that,
   which read as 196px left of centre at 1280 and 70px at 900. */
.page-cta .section__sub,
.page-cta .stub { margin-inline: auto; }
.page-cta .btn { margin-top: 1.75rem; }

.section__head { margin-bottom: clamp(2rem, 5vw, 3.25rem); }
.section__head--split {
  display: flex; align-items: flex-end; justify-content: space-between;
  flex-wrap: wrap; gap: 1.25rem;
}

/* Fixture list: full width rows on hairline rules, no boxes.
   Shared by matches, training, and sides. */
.match-list, .team-list { border-bottom: 1px solid var(--line); }
/* The homepage shows the next four and links on to /matches/. Every fixture
   is in the page, so this counts what is left after main.js has taken out
   the ones whose date has passed. */
.match-list--capped .match-row:nth-child(n + 5) { display: none; }
.section__more { margin-top: 1.75rem; }
.fixtures-block__more { margin-top: 1rem; font-size: 0.9375rem; }
.practice-list { border-bottom: 1px solid var(--line); }

.match-row {
  display: grid;
  grid-template-columns: 6.5rem 1fr auto;
  column-gap: 1.5rem;
  align-items: baseline;
  padding-block: 1.25rem;
  border-top: 1px solid var(--line);
}
.match-row__date {
  font-family: var(--font-display);
  font-weight: 900;
  text-transform: uppercase;
  line-height: 1.05;
}
.match-row__weekday { display: block; color: var(--steel); font-size: 0.875rem; }
.match-row__day { display: block; font-size: 1.25rem; }
.match-row__fixture {
  font-family: var(--font-display);
  font-weight: 900;
  font-size: 1.6rem;
  line-height: 1.05;
  text-transform: uppercase;
}
/* The markup carries real spaces either side of the v, so the accessible
   name reads "Lions v Brooklyn Rugby". The margin only tops that up. */
.match-row__vs { color: var(--steel); font-size: 1.05rem; margin-inline: 0.1rem; }
.match-row__meta {
  margin-top: 0.3rem;
  font-size: 0.8125rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.1em;
  color: var(--steel);
}
.match-row__side { color: var(--red-ink); }
.match-row__when { text-align: right; font-size: 0.9375rem; }
.match-row__time { display: block; font-weight: 500; }
.match-row__venue { display: block; color: var(--steel); }
.match-row__home {
  display: inline-block;
  margin-top: 0.5rem;
  font-size: 0.6875rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.12em;
  border: 1px solid var(--fg);
  padding: 0.2rem 0.55rem;
}
.match-empty { color: var(--steel); }
.match-empty a { color: var(--fg); }

/* Match report, folded into the result row. Closed by default so the list of
   results still reads as a list. */
.match-report { grid-column: 1 / -1; margin-top: 0.9rem; }
.match-report__toggle {
  display: inline-flex; align-items: center; gap: 0.5rem;
  cursor: pointer;
  font-size: 0.75rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.12em;
  color: var(--red-ink);
  list-style: none;
}
.match-report__toggle::-webkit-details-marker { display: none; }
.match-report__toggle::after {
  content: "+";
  font-size: 1rem; line-height: 1;
  transition: transform 0.15s;
}
.match-report[open] .match-report__toggle::after { transform: rotate(45deg); }
.match-report__body {
  max-width: 68ch;
  margin-top: 0.9rem;
  padding-left: 1rem;
  border-left: 2px solid var(--line);
}
.match-report__body p + p { margin-top: 0.9rem; }

/* Scorers and the lineup. Definition lists, because a report sits under an h3
   on /matches/ and an h4 on a team page, so headings here would have to track
   the level. The reset strips list markers globally, so every styled list on
   the site is deliberate: these two, and the sponsorship benefits on
   /sponsors/, which run as rows on hairline rules. */
.match-report__facts, .match-report__lineup {
  margin-top: 1.25rem;
  display: grid;
  grid-template-columns: 9rem minmax(0, 1fr);
  column-gap: 1rem;
  row-gap: 0.4rem;
  align-items: baseline;
  font-size: 0.9375rem;
}
.match-report__facts dt, .match-report__lineup dt {
  font-size: 0.75rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.1em;
  color: var(--steel);
}
.match-report__facts dd, .match-report__lineup dd { margin: 0; }
.match-report__lineup { margin-top: 1rem; }
/* The players themselves. Two columns where there is room, one on a phone.
   Measured at 390px: the longest entry, the bench line carrying a name and a
   substitution minute, is 294px against 350px of body width, so it sits on
   one line without shrinking the type. */
.match-report__lineup ul {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  column-gap: 1.5rem;
  row-gap: 0.2rem;
}
.match-report__lineup li { break-inside: avoid; }

@media (max-width: 720px) {
  .match-report__facts, .match-report__lineup { grid-template-columns: 1fr; row-gap: 0.15rem; }
  .match-report__facts dt, .match-report__lineup dt { margin-top: 0.6rem; }
  .match-report__lineup ul { grid-template-columns: 1fr; }
}

/* Training venue: one card per distinct location under the schedule rows. */
.venue {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.1fr);
  gap: clamp(1.5rem, 4vw, 2.5rem);
  align-items: center;
  margin-top: clamp(2rem, 5vw, 3rem);
}
.venue__name {
  font-family: var(--font-display);
  font-weight: 900;
  font-size: 1.6rem;
  line-height: 1.05;
  text-transform: uppercase;
}
.venue__address { margin-top: 0.6rem; color: var(--steel); }
.venue__note { margin-top: 0.6rem; color: var(--steel); font-size: 0.9375rem; }
.venue .btn { margin-top: 1.25rem; }
/* The alternate ground: copy only, no map, on a hairline so it reads as a
   footnote to the ground above rather than a second venue. */
.venue--alt {
  grid-template-columns: minmax(0, 1fr);
  border-top: 1px solid var(--line);
  padding-top: clamp(1.5rem, 4vw, 2rem);
}
.venue--alt .venue__name { font-size: 1.25rem; }
.venue--alt .venue__note { max-width: 60ch; }
/* The one coloured panel on a dark band. It stays in colour on purpose: this
   is wayfinding, and greyscale would flatten the marker into the streets. */
.venue__map {
  display: block; width: 100%; aspect-ratio: 4 / 3;
  border: 1px solid var(--line);
  background: var(--smoke);
}
@media (max-width: 720px) {
  .venue { grid-template-columns: 1fr; }
  .venue__map { aspect-ratio: 3 / 2; }
}

/* Results and the league table share the fixtures section on team pages. */
.sub-title {
  font-family: var(--font-display);
  font-weight: 900;
  font-size: clamp(1.75rem, 3vw, 2.25rem);
  line-height: 1;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  margin-bottom: 1.25rem;
}
.sub-title--lg { font-size: clamp(2rem, 3.6vw, 2.75rem); margin-bottom: 1.25rem; }
.fixtures-block + .fixtures-block { margin-top: clamp(2.5rem, 6vw, 4rem); }
.match-row__result { text-align: right; }
.match-row__score {
  display: block;
  font-family: var(--font-display);
  font-weight: 900;
  font-size: 1.6rem;
  line-height: 1.05;
}
.match-row__outcome {
  display: inline-block;
  margin-top: 0.4rem;
  font-size: 0.6875rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.12em;
  border: 1px solid var(--fg);
  padding: 0.2rem 0.55rem;
}
.match-row__outcome--w { background: var(--fg); color: var(--bg); }
.standings {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.9375rem;
  font-variant-numeric: tabular-nums;
}
.standings th, .standings td {
  padding: 0.7rem 0.5rem;
  text-align: center;
  border-top: 1px solid var(--line);
}
.standings th {
  border-top: 0;
  font-size: 0.6875rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.12em;
  color: var(--steel);
}
.standings tbody tr:last-child td { border-bottom: 1px solid var(--line); }
.standings-host { overflow-x: auto; }
/* Needs the element in the selector to beat the centring on .standings th. */
.standings th.standings__team, .standings td.standings__team { text-align: left; width: 100%; }
.standings__pts { font-weight: 700; }
.standings .is-lions td { font-weight: 700; }
.standings .is-lions .standings__team { color: var(--red-ink); }
.standings-note { margin-top: 0.75rem; font-size: 0.8125rem; color: var(--steel); }
@media (max-width: 520px) {
  /* The columns all stay. Draws and bonus points decide table position, so
     dropping them changed the meaning; the table scrolls instead. */
  .standings th, .standings td { padding-inline: 0.35rem; }
}

/* Training */
.practice-row {
  display: grid;
  grid-template-columns: minmax(9rem, 1fr) repeat(3, minmax(0, 1.15fr));
  column-gap: 1.5rem;
  align-items: baseline;
  padding-block: 1.35rem;
  border-top: 1px solid var(--line);
}
.practice-row__side {
  font-family: var(--font-display);
  font-weight: 900;
  font-size: 1.6rem;
  line-height: 1.05;
  text-transform: uppercase;
}
.practice-row__label {
  display: block;
  font-weight: 700; font-size: 0.75rem;
  text-transform: uppercase; letter-spacing: 0.12em;
  color: var(--steel);
  margin-bottom: 0.3rem;
}
.practice-row__value { font-size: 0.9375rem; }

/* Sides. The list owns the column tracks and rows inherit them through
   subgrid, so names, badges, and links align down the whole list. */
.team-list {
  display: grid;
  grid-template-columns: minmax(11rem, 1.1fr) 2fr auto auto;
}
.team-row {
  display: grid;
  grid-column: 1 / -1;
  grid-template-columns: subgrid;
  column-gap: 1.5rem;
  align-items: center;
  padding-block: 1.35rem;
  border-top: 1px solid var(--line);
  text-decoration: none;
  color: inherit;
  transition: color 0.15s;
}
.team-row__name {
  font-family: var(--font-display);
  font-weight: 900;
  font-size: 1.75rem;
  line-height: 1;
  text-transform: uppercase;
}
.team-row__desc { color: var(--steel); font-size: 0.9375rem; }
.team-row__badge {
  grid-column: 3;
  justify-self: start;
  background: var(--red-deep); color: var(--paper);
  font-size: 0.6875rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.12em;
  padding: 0.25rem 0.6rem;
}
/* Quieter than the red badge: a ghost chip rather than a filled one. */
.team-row__badge--quiet {
  background: transparent;
  border: 1px solid var(--steel);
  color: var(--steel);
}
/* Sides without a badge skip the column, so the link always sits last. */
.team-row__go {
  grid-column: 4;
  white-space: nowrap;
  font-weight: 700; font-size: 0.8125rem;
  text-transform: uppercase; letter-spacing: 0.08em;
}
.team-row:hover .team-row__name, .team-row:hover .team-row__go { color: var(--red-ink); }

@media (max-width: 720px) {
  .practice-row {
    grid-template-columns: 1fr;
    row-gap: 0.5rem;
  }
  .team-list { grid-template-columns: 1fr; }
  .team-row { row-gap: 0.5rem; }
  /* A fixture row stacked into one column ran to 212px, so four of them
     filled a phone screen. The date keeps its own narrow column instead,
     and the time, the ground and the badge share a line under the fixture
     rather than each taking one. */
  .match-row {
    grid-template-columns: 4.75rem 1fr;
    column-gap: 1rem;
    row-gap: 0.4rem;
    padding-block: 1rem;
  }
  .match-row__date { grid-row: span 2; }
  .match-row__main, .match-row__when, .match-row__result { grid-column: 2; }
  .match-row__when, .match-row__result {
    display: flex; flex-wrap: wrap;
    align-items: baseline;
    gap: 0 0.6rem;
    text-align: left;
  }
  .match-row__outcome { margin-top: 0; }
  /* The meta line already ends "· Home" here, and on a phone the badge sits
     directly under it saying the same thing on a line of its own. */
  .match-row__home { display: none; }
  .team-row__badge, .team-row__go { grid-column: 1; }
}

/* Coaching staff */
.coach-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: 2rem 2.5rem;
}
.coach-card__role {
  font-weight: 700; font-size: 0.75rem;
  text-transform: uppercase; letter-spacing: 0.12em;
  color: var(--red-ink);
  margin-bottom: 0.4rem;
}
.coach-card__name {
  font-family: var(--font-display);
  font-weight: 900;
  font-size: 1.5rem;
  line-height: 1.05;
  text-transform: uppercase;
}
.coach-card__bio { margin-top: 0.6rem; font-size: 0.9375rem; color: var(--steel); }
.coach-group + .coach-group { margin-top: clamp(2.5rem, 6vw, 4rem); }
.coach-group__side {
  font-family: var(--font-display);
  font-weight: 900;
  font-size: 1.75rem;
  text-transform: uppercase;
  padding-bottom: 0.75rem;
  margin-bottom: 1.75rem;
  border-bottom: 1px solid var(--line);
}

/* Sponsors */
.sponsor-grid {
  display: grid;
  /* auto-fit, not auto-fill: with three logos in a 1200px wrap, auto-fill
     kept six 200px tracks and left the tiles at their minimum with dead
     space beside them. auto-fit collapses the empty tracks so the row
     spreads, which matters most for the wide wordmarks. */
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 1.25rem;
}
.sponsor-tile {
  aspect-ratio: 3 / 2;
  border: 1px solid var(--line);
  background: var(--smoke);
  display: flex; align-items: center; justify-content: center;
  padding: 1rem;
  text-align: center;
  font-size: 0.875rem;
  color: var(--steel);
}
/* A real logo rather than a placeholder label. Contain, never cover: a
   sponsor's mark is not a photograph and cropping one is not ours to do.
   Supplied files run from 7:1 (a wordmark) to 1.7:1, so the box holds the
   shape and the artwork sits inside it whatever it is. */
.sponsor-tile--logo { padding: clamp(1rem, 3vw, 1.75rem); }
/* The picture is the flex item, not the img, and it sizes itself from the
   img's sizes attribute, so styling only the img left the logo at 45vw on a
   phone inside a 316px box. Same pair of rules the thumbnail strip uses. */
.sponsor-tile--logo picture,
.sponsor-tile--logo img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
}
/* Artwork drawn for a light ground, black line art for instance, which
   would lose its outlines on the dark band. Same device as .cubs__mark on
   the homepage: give it the white it was drawn against rather than asking
   the band to change. */
.sponsor-tile--plate { background: var(--paper); border-color: var(--paper); }
/* The tile plus the sponsor's name beneath it. The grid lays these out, so
   the tile keeps its 3:2 box whatever the name wraps to. A span carries the
   tile in the linked variant, hence the explicit display. */
.sponsor-card { display: block; }
.sponsor-card .sponsor-tile { display: flex; }
.sponsor-card__name {
  margin-top: 0.8rem;
  font-size: 0.9375rem;
  font-weight: 700;
  text-align: center;
  color: var(--fg);
}
a.sponsor-card { text-decoration: none; }
a.sponsor-card:hover .sponsor-card__name { text-decoration: underline; }

/* Sponsorship levels. Three across, then straight to one: never two and an
   orphan. No background fill on the card, like the by-side coach cards on
   /coaches/: benefits are rows on hairline rules, the site's idiom for a
   list of things, rather than boxes. */
.sponsor-levels {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(1.75rem, 4vw, 3rem);
}
.sponsor-level { border-top: 1px solid var(--line); padding-top: 1.25rem; }
.sponsor-level__name {
  font-family: var(--font-display);
  font-weight: 900;
  font-size: clamp(2.25rem, 4vw, 3rem);
  line-height: 0.95;
  text-transform: uppercase;
  color: var(--fg);
}
.sponsor-level__benefits { margin-top: 1.25rem; font-size: 0.9375rem; }
.sponsor-level__benefits li { padding-block: 0.65rem; }
.sponsor-level__benefits li + li { border-top: 1px solid var(--line); }
@media (max-width: 820px) {
  .sponsor-levels { grid-template-columns: 1fr; }
}

/* Stub pages for sections that are not built yet. */
.stub { max-width: 60ch; }
.stub p + p { margin-top: 1.1rem; }
.stub .btn { margin-top: 1.75rem; }
/* A line of copy under a button, which the donate page uses for the "give
   another way" route. The button is inline-block, so p + p does not reach
   it and the paragraph sits flush against it: measured at 0px at both 1280
   and 390 before this rule. */
.stub .btn + p { margin-top: 1.1rem; }

/* By-laws. The download sits in the masthead; the text runs on the light
   band beside a contents list that stays in view on desktop and sits above
   the text on a phone. The wording is the club's, verbatim, so the styling
   only has to make a long legal document easy to find a place in. */
.bylaws-head__actions {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: 1rem 1.5rem;
  margin-top: 2rem;
}
.bylaws-head__meta { color: var(--steel); font-size: 0.9375rem; }
.bylaws {
  display: grid;
  grid-template-columns: minmax(0, 15rem) minmax(0, 68ch);
  justify-content: space-between;
  gap: clamp(2rem, 5vw, 4rem);
  align-items: start;
}
/* Scrolls itself on a short window rather than hanging off the bottom. */
.bylaws__toc {
  position: sticky; top: 6.5rem;
  max-height: calc(100vh - 8rem); overflow-y: auto;
}
.bylaws__toc-label {
  font-weight: 700; font-size: 0.8125rem;
  letter-spacing: 0.18em; text-transform: uppercase;
  color: var(--steel);
  margin-bottom: 0.75rem;
}
.bylaws__toc ol { margin: 0; padding: 0; list-style: none; }
.bylaws__toc li + li { border-top: 1px solid var(--line); }
.bylaws__toc a {
  display: flex; gap: 0.75rem;
  padding-block: 0.45rem;
  font-size: 0.8125rem; font-weight: 700; line-height: 1.35;
  letter-spacing: 0.04em; text-transform: uppercase;
  text-decoration: none;
  transition: color 0.15s;
}
.bylaws__toc a span { flex: 0 0 2.25rem; color: var(--red-ink); }
.bylaws__toc a:hover { color: var(--red-ink); }
.bylaws__article + .bylaws__article {
  margin-top: clamp(3rem, 7vw, 4.5rem);
  padding-top: clamp(2.5rem, 6vw, 3.5rem);
  border-top: 1px solid var(--line);
}
.bylaws__article .eyebrow { margin-bottom: 0.6rem; }
.bylaws__article-title {
  font-family: var(--font-display);
  font-weight: 900;
  font-size: clamp(2rem, 5vw, 3rem);
  line-height: 0.95;
  text-transform: uppercase;
  margin-bottom: 1.5rem;
}
.bylaws__subtitle { margin-top: -0.75rem; margin-bottom: 1.5rem; color: var(--steel); font-weight: 500; }
.bylaws__text p + p { margin-top: 0.9rem; }
.bylaws__section {
  margin-top: 2rem; margin-bottom: 0.5rem;
  font-size: 1.0625rem; font-weight: 700; line-height: 1.4;
}
.bylaws__section span { margin-left: 0.35rem; }
.bylaws__article-title + .bylaws__section,
.bylaws__subtitle + .bylaws__section { margin-top: 0; }
/* Hanging indent, so the (a), (b), (iv) markers stand in a column of their
   own the way they do in the printed document. */
.bylaws__item { position: relative; padding-left: 3rem; }
.bylaws__marker { position: absolute; left: 0; color: var(--steel); }
@media (max-width: 900px) {
  .bylaws { grid-template-columns: minmax(0, 1fr); }
  .bylaws__toc { position: static; }
  .bylaws__item { padding-left: 2.5rem; }
}

/* Lion Cubs. Sits flat so it alternates against the lifted bands. */
.cubs__inner {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: clamp(2rem, 5vw, 4rem);
  align-items: center;
}
.cubs__copy p:not(.eyebrow) { margin-top: 1.1rem; color: var(--steel); max-width: 50ch; }
.cubs__copy .btn { margin-top: 1.75rem; }
/* The supplied artwork is black line art on flat white with no alpha, so
   the plate is the artwork's own ground given a margin. See CLAUDE.md: if
   new artwork arrives it needs the same flood and trim. */
.cubs__mark {
  background: var(--paper);
  padding: clamp(1.25rem, 3vw, 2rem);
  max-width: 380px;
  margin-inline: auto;
}
.cubs__mark img { display: block; width: 100%; height: auto; }

/* Donate */
.section--donate { position: relative; padding-bottom: 0; }
.donate__inner {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: clamp(2rem, 5vw, 4rem);
  padding-bottom: var(--section-pad);
}
.donate__title {
  font-family: var(--font-display);
  font-weight: 900;
  font-size: clamp(3rem, 8vw, 6rem);
  line-height: 0.9;
  text-transform: uppercase;
}
.donate__copy p { color: var(--steel); max-width: 52ch; }
.donate__copy p + p { margin-top: 1rem; }
.donate__tax { font-size: 0.875rem; }
.donate__copy .btn { margin-top: 1.75rem; }

/* Instagram */
.ig-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 0.75rem;
}
.ig-grid__item {
  aspect-ratio: 1;
  background: var(--smoke);
  border: 1px solid var(--line);
  overflow: hidden;
  text-decoration: none;
  transition: border-color 0.15s;
}
.ig-grid__item:hover { border-color: var(--fg); }
/* The tile's own aspect-ratio is what holds the layout: the box is square
   before an image arrives, so nothing shifts either way. This rule is about
   filling that square. Instagram posts arrive square, 4:5 and 1.91:1, and
   without it a landscape post renders 259 by 173 in a 261 box and letterboxes
   against the panel. Measured both ways.

   Cover means a portrait post is cropped square, which is the Instagram idiom
   and the whole post is one click away. The gallery's never-crop rule is
   about photographs shown as photographs; this is a contact sheet. */
.ig-grid__item img, .ig-grid__item picture {
  display: block;
  width: 100%; height: 100%;
  object-fit: cover;
}
/* No feed: the heading, the sub and the Follow button centre up as a band
   that looks finished rather than like a section missing its contents. */
.ig-follow { display: block; text-align: center; }
.ig-follow .section__sub { max-width: none; margin-bottom: 1.75rem; }

/* Footer */
.site-footer { background: var(--bg); color: var(--fg); }
.site-footer__inner {
  display: grid;
  grid-template-columns: 2fr 1fr 1fr 1fr;
  gap: 2.5rem;
  padding-block: clamp(3rem, 6vw, 4.5rem);
}
.site-footer__name {
  font-family: var(--font-display);
  font-weight: 900;
  font-size: 1.5rem;
  text-transform: uppercase;
  margin-top: 1rem;
}
.site-footer__tag { color: var(--steel); font-size: 0.9375rem; margin-top: 0.5rem; max-width: 34ch; }
.site-footer__col h2 {
  font-size: 0.75rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.14em;
  color: var(--red-ink);
  margin-bottom: 1rem;
}
.site-footer__col li { margin-bottom: 0.6rem; font-size: 0.9375rem; color: var(--steel); }
.site-footer__col a { text-decoration: none; }
.site-footer__col a:hover { color: var(--fg); text-decoration: underline; }
.site-footer__legal {
  border-top: 1px solid var(--line);
  padding-block: 1.25rem;
  font-size: 0.8125rem; color: #999693; /* dimmest tone that still clears 4.5:1 on the footer panel */
}
/* The studio credit. It inherits the line's own grey rather than brightening:
   that tone measures 5.54:1 on the footer black, so it passes on its own, and
   white would make a supplier credit louder than the club's copyright. The
   underline is what marks it as a link, so it does not rely on colour. */
.site-footer__legal a { text-decoration: underline; text-underline-offset: 0.2em; }
.site-footer__legal a:hover { color: var(--fg); }

/* Responsive */
@media (max-width: 900px) {
  /* No JS: the toggle cannot work, so it stays hidden and the nav sits in
     flow under the wordmark. A taller header beats no way off the page.
     With JS, the .js class collapses it behind the toggle as before, and the
     open panel scrolls rather than running off a short screen. */
  .site-header__inner { flex-wrap: wrap; }
  .site-nav { width: 100%; }
  .js .site-header__inner { flex-wrap: nowrap; }
  .js .nav-toggle { display: block; }
  .js .site-nav {
    position: absolute; left: 0; right: 0; top: 100%; width: auto;
    background: var(--bg);
    border-bottom: 1px solid var(--line);
    max-height: calc(100dvh - 100%);
    overflow-y: auto;
    display: none;
  }
  .js .site-nav.is-open { display: block; }
  .site-nav ul {
    flex-direction: column; align-items: stretch; gap: 0;
    padding: 1rem var(--gutter) 1.5rem;
  }
  .site-nav li { border-bottom: 1px solid var(--line); }
  .site-nav li:has(.btn) { border-bottom: 0; padding-top: 0.9rem; }
  .site-nav a:not(.btn) { display: block; padding-block: 0.9rem; }
  .site-nav .btn { display: block; text-align: center; }
  .site-nav__group { display: block; }
  /* The phone menu lists the sides indented, so the chevron has no job. */
  .js .site-nav__more { display: none; }
  .site-nav .site-nav__sub,
  .site-nav__group.is-open .site-nav__sub {
    display: flex; position: static;
    min-width: 0; padding: 0 0 0.6rem 1rem;
    border: 0; background: none;
  }
  .site-nav .site-nav__sub li { border-bottom: 0; }
  .site-nav .site-nav__sub a:not(.btn) { padding: 0.45rem 0; color: var(--steel); }

  .hero__inner { grid-template-columns: 1fr; }
  .hero__crest { order: -1; }
  .hero__crest img { width: min(60%, 280px); }
  .cubs__inner, .donate__inner { grid-template-columns: 1fr; }
  .cubs__mark { max-width: 260px; margin-inline: 0; }
  .ig-grid { grid-template-columns: repeat(2, 1fr); }
  .site-footer__inner { grid-template-columns: 1fr 1fr; }
}

@media (max-width: 520px) {
  .site-footer__inner { grid-template-columns: 1fr; }
}
