/* Exeter Panthers AFC — shared site styles
   Palette: near-black base, hot pink accent, purple secondary accent.
   Display font: Inter Bold, uppercase (2026-08-19, replacing Anton). Body font: Inter.
   The Anton @font-face rule below and its .woff2 asset are kept in place (still
   referenced by the deploy script) even though nothing applies it any more —
   --font-display now points at Inter. Removing the asset is a separate call. */

/* ---- Self-hosted webfonts (2026-08-19) ---------------------------------
   Both families are served from this origin, from assets/fonts/. They were
   loaded from fonts.googleapis.com on all ten pages until 2026-08-19, which
   would have failed COMPLETELY on the deployed site: _headers sets
   `style-src 'self'` and `font-src 'self'`, so the Google stylesheet AND the
   font files it points at would both have been refused, and every heading on
   the site — Anton is the display face for all of them — would have rendered
   in the fallback. Local dev sends no CSP at all, so nothing could show you
   that before deploy day. The CSP was NOT relaxed to fix it; the fonts moved
   here instead, which keeps the header set at zero external origins.

   Files come from Google's own CSS2 API (see assets/fonts/README.md for the
   exact curl), so these are the same bytes fonts.gstatic.com was serving. Both
   families are OFL.

   Inter is ONE variable file per subset covering the whole 400–700 range,
   hence `font-weight: 400 700` rather than four static faces: css/style.css
   asks for 400, 600 and 700 and the browser picks the instance. The file is
   byte-identical to the `inter-400-600-*.woff2` it replaced — the variable
   font always carried the full range; only the declared range was narrower.

   `font-display: swap` on all four: the fallback stacks in --font-display /
   --font-body are close enough in metrics that a swap is less disruptive than
   invisible headings while the font loads.

   The unicode-range values are Google's own for the latin / latin-ext subsets
   and are what makes the split worthwhile — a page of English text never
   downloads the latin-ext file at all. Do not merge the two blocks per family
   or drop the ranges; that would pull an extra 114KB onto every page load for
   characters the site does not use. */
@font-face {
  font-family: "Anton";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("../assets/fonts/anton-400-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Anton";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("../assets/fonts/anton-400-latinext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("../assets/fonts/inter-400-700-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("../assets/fonts/inter-400-700-latinext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

:root {
  --bg: #0a0a12;
  --bg-alt: #12121e;
  --bg-card: rgba(255, 255, 255, 0.03);
  --purple: #3a1a6e;
  --pink: #e6157a;
  --pink-bright: #ff2d8e;
  --white: #ffffff;
  --muted: #b9b6c6;
  --border: rgba(230, 21, 122, 0.25);

  --font-display: "Inter", system-ui, -apple-system, "Segoe UI", sans-serif;
  --font-body: "Inter", system-ui, -apple-system, "Segoe UI", sans-serif;

  --max-width: 1400px;
  --radius: 10px;
  --btn-radius: 6px;
  --accent-glow: 0 0 0 3px rgba(230, 21, 122, 0.35);

  --fs-display: clamp(3.5rem, 9vw, 7.5rem);
  --fs-h2: clamp(1.75rem, 3vw, 2.5rem);
  --fs-lead: 1.15rem;
}

* {
  box-sizing: border-box;
}

[hidden] {
  display: none !important;
}

html {
  scroll-behavior: smooth;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--white);
  font-family: var(--font-body);
  line-height: 1.6;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

/* ---- Interior full-page wallpaper (every page except home) -------------
   index.html keeps its own hero (.hero-bg with final-wallpaper.webp) and is
   never given this class. The dark gradient is layered ON TOP of the image
   in the same background-image list, not applied as a separate scrim
   element — it exists because plenty of body copy on these pages (team
   descriptions, coach names, policy text) sits directly on the section
   background with no card wrapper at all, so the image alone would put
   text straight over a bright pink streak somewhere on every page. Flattened
   to ~20% visible brightness the wallpaper still reads as a moody, textured
   backdrop while every other rule below (`--bg-card` override, explicit
   backdrop-filter panels) is belt-and-braces for the components that DO have
   their own fill.
   background-position leans toward the image's own dark upper-left quadrant
   (see the source at assets/images/wallpaper/interior-bg.jpg) so the busiest
   diagonal streak sits off toward the right/bottom, away from where page
   headings and lead paragraphs are set.
   background-attachment: fixed is desktop-only — see the max-width: 900px
   override below; it's a known source of jank/repaint bugs on mobile
   Safari/Chrome, and the fix costs nothing since a mobile viewport is
   scrolled past a background in under a second anyway. */
body.has-wallpaper {
  --bg-card: rgba(10, 10, 18, 0.72);
  background-image:
    linear-gradient(rgba(8, 8, 14, 0.82), rgba(8, 8, 14, 0.82)),
    url("../assets/images/wallpaper/interior-bg.jpg");
  background-size: cover;
  background-position: 25% 30%;
  background-repeat: no-repeat;
  background-attachment: fixed;
}

body.has-wallpaper .info-card,
body.has-wallpaper .vacancy-card,
body.has-wallpaper .contact-info-card,
body.has-wallpaper .policy-card,
body.has-wallpaper .coach-avatar,
body.has-wallpaper .welfare-callout {
  backdrop-filter: blur(4px);
}

/* These three don't read --bg-card (their fill is a hardcoded near-0%-alpha
   white), so the --bg-card override above doesn't reach them — darken them
   directly to the same panel tone. */
body.has-wallpaper .sponsor-slot,
body.has-wallpaper .hero-photo-placeholder,
body.has-wallpaper .form-group input,
body.has-wallpaper .form-group textarea {
  background: rgba(10, 10, 18, 0.72);
  backdrop-filter: blur(4px);
}

@media (max-width: 900px) {
  body.has-wallpaper {
    background-attachment: scroll;
  }
}

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

a {
  color: inherit;
  text-decoration: none;
}

h1, h2, h3 {
  font-family: var(--font-display);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  line-height: 1.05;
  margin: 0 0 0.5em;
}

p {
  margin: 0 0 1em;
}

button {
  font-family: inherit;
}

.container {
  max-width: var(--max-width);
  margin: 0 auto;
  padding: 0 1.5rem;
}

main {
  flex: 1;
}

/* ---- Buttons -------------------------------------------------------- */
.btn {
  display: inline-block;
  padding: 0.9rem 2rem;
  border-radius: var(--btn-radius);
  font-family: var(--font-display);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  font-size: 1rem;
  border: 2px solid transparent;
  cursor: pointer;
  transition: transform 0.15s ease, box-shadow 0.25s ease, background 0.2s ease, color 0.2s ease;
}

.btn-primary {
  background: var(--pink);
  color: var(--white);
}

.btn-primary:hover,
.btn-primary:focus-visible {
  background: var(--pink-bright);
  transform: translateY(-2px);
  box-shadow: var(--accent-glow);
}

/* Disabled controls. The contact and sponsor enquiry forms ship disabled until
   their backend lands, and without these rules a disabled .btn-primary rendered
   pixel-identical to a live one — full --pink fill, white text, opacity 1, and
   cursor: pointer — so the honest note underneath was the only thing telling a
   visitor the button does nothing. Declared after .btn-primary:hover so it wins
   the same-specificity tie, and repeated on :hover because a disabled button
   still matches :hover in every browser that matters. */
.btn:disabled,
.btn[disabled] {
  background: var(--bg-alt);
  color: var(--muted);
  border-color: var(--border);
  opacity: 0.55;
  cursor: not-allowed;
  transform: none;
  box-shadow: none;
}

.btn:disabled:hover,
.btn[disabled]:hover,
.btn:disabled:focus-visible,
.btn[disabled]:focus-visible {
  background: var(--bg-alt);
  color: var(--muted);
  transform: none;
  box-shadow: none;
}

.btn-outline {
  background: transparent;
  border-color: var(--white);
  color: var(--white);
}

.btn-outline:hover,
.btn-outline:focus-visible {
  border-color: var(--pink-bright);
  color: var(--pink-bright);
  transform: translateY(-2px);
}

/* ---- Header / nav ----------------------------------------------------- */
.site-header {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 100;
  background: linear-gradient(to bottom, rgba(10, 10, 18, 0.85), transparent);
  transition: background 0.3s ease;
}

.site-header.is-scrolled {
  background: rgba(10, 10, 18, 0.92);
  backdrop-filter: blur(8px);
  border-bottom: 1px solid var(--border);
}

.header-inner {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-between;
  /* padding-block only — a `padding: 0.85rem 0` shorthand here would wipe out
     .container's horizontal padding and leave the CTA flush to the page edge. */
  padding-block: 0.85rem;
  gap: 1rem;
}

.logo-link {
  display: inline-flex;
  flex-shrink: 0;
}

/* Desktop only — on mobile the nav is a fullscreen overlay, not an inline
   row, so there's no "same x as the home page's nav" to match and the
   crest stays in normal flow (full crest visible, not partly off-canvas).
   Above 900px, taken out of the flex flow so its width never shifts
   .site-nav: interior pages' nav must land at the exact same x as the home
   page's (which carries no crest), so the crest sits in reserved space to
   the left of the container's own padding rather than pushing the nav
   rightward. -1rem is derived, not eyeballed: with .site-nav unchanged
   (flex-start, margin-left: 1.5rem) its "Home" link always lands at
   (container box left + 3rem) regardless of viewport width. Placing the
   crest at left: -1rem keeps a fixed 12px gap to that point in every case:
   crest right edge = (container box left - 1rem + 52px) = (container box
   left + 36px), 12px short of the nav's (container box left + 48px). */
@media (min-width: 901px) {
  .logo-link {
    position: absolute;
    left: -1rem;
    top: 50%;
    transform: translateY(-50%);
  }
}

.logo-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* object-fit: contain, not cover — the crest PNG is transparent, not a
   circular photo, so with no white disc behind it there's no frame to crop
   to. cover would crop the shield's shoulders off a non-square source. */
.logo {
  display: block;
  height: 52px;
  width: 52px;
  object-fit: contain;
}

.nav-toggle {
  position: relative;
  display: none;
  flex-direction: column;
  justify-content: center;
  gap: 5px;
  width: 32px;
  height: 32px;
  background: none;
  border: none;
  cursor: pointer;
  padding: 0;
  z-index: 101;
}

.nav-toggle::before {
  content: "";
  position: absolute;
  inset: -6px;
}

.nav-toggle span {
  display: block;
  height: 2px;
  background: var(--white);
  border-radius: 2px;
  transition: transform 0.2s ease, opacity 0.2s ease;
}

.site-nav {
  flex: 1;
  display: flex;
  justify-content: flex-start;
  margin-left: 1.5rem;
}

.site-nav ul {
  display: flex;
  gap: 2rem;
  list-style: none;
  margin: 0;
  padding: 0;
}

.site-nav a {
  font-weight: 600;
  font-size: 1.025rem;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  padding: 0.35rem 0;
  border-bottom: 2px solid transparent;
  text-shadow: 0 1px 4px rgba(0, 0, 0, 0.8);
  transition: color 0.2s ease, border-color 0.2s ease;
}

.site-nav a:hover,
.site-nav a:focus-visible,
.site-nav a[aria-current="page"] {
  color: var(--pink-bright);
  border-bottom-color: var(--pink-bright);
}

.header-cta {
  flex-shrink: 0;
  padding: 0.65rem 1.5rem;
  font-size: 0.85rem;
}

.header-cta-mobile {
  display: none;
}

@media (max-width: 900px) {
  .nav-toggle {
    display: flex;
  }

  .site-nav {
    position: fixed;
    inset: 0 0 0 30%;
    background: rgba(10, 10, 18, 0.98);
    display: none;
    padding: 6rem 2rem 2rem;
    margin-left: 0;
  }

  .site-nav.is-open {
    display: block;
  }

  .site-nav ul {
    flex-direction: column;
    gap: 0;
  }

  .site-nav li {
    border-bottom: 1px solid var(--border);
  }

  .site-nav a {
    display: block;
    padding: 1rem 0;
  }

  .header-cta {
    display: none;
  }

  .site-nav.is-open .header-cta-mobile {
    display: inline-block;
    margin-top: 1rem;
  }
}

/* ---- Hero -------------------------------------------------------------- */
/* The info cards are a child of .hero (they overlay the wallpaper at desktop),
   so the hero is a column at every width. The "first screen" sizing that used
   to live here — min-height + header offset — moved to .hero-inner so mobile
   renders exactly as it did before the cards were nested.

   No `isolation: isolate`: the hero-corner logo has to out-paint the fixed
   header's gradient, which it can only do from the root stacking context.
   .hero-bg's z-index: -2 still lands behind the hero content and in front of
   the body background, and the footer's own opaque background covers whatever
   the parallax pushes past the hero's bottom edge.

   overflow-x: clip + overflow-y: visible, not `overflow: hidden`: the logo is
   deliberately hung above the hero's top edge and must not be sheared off. */
.hero {
  position: relative;
  display: flex;
  flex-direction: column;
  overflow-x: clip;
  overflow-y: visible;
}

.hero-bg {
  position: absolute;
  inset: 0;
  background-image: url("../assets/images/wallpaper/final-wallpaper.webp");
  background-size: cover;
  background-position: center;
  z-index: -2;
  will-change: transform;
}

.hero-bg::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
}

/* The scrim between the hero photo and the hero text. It carries a background
   ONLY under .page-hero — index.html's own hero (section.hero) is the
   reference design's artwork and must stay unflattened; its copy is set left,
   in the image's own dark quadrant, so it needs no scrim and a scrim would
   dull both the panther and the floodlight glow tuned over it.

   The interior heroes are the opposite case. .page-hero-inner is centred, so a
   two-to-six-line subtext lands squarely on the players and the bright pink
   crowd, and .hero-subtext is --muted (#b9b6c6) with no text-shadow of its own
   — the h1 already carries one, the subtext never did.

   The alpha is MEASURED, not nudged. Sampled by hiding .page-hero-inner and
   reading the rendered pixels underneath it on the site's worst case, which is
   vacancies.html at 390px — a six-line paragraph over the pink crowd:

     scrim   subtext #b9b6c6        tagline #ff2d8e bold     h1 white
     0.66    6.16:1 / 3.81:1 worst  3.51:1 / 2.17:1 worst    —
     0.76    7.45:1 / 5.45:1 worst  4.24:1 / 3.10:1 worst    —
     0.82    8.18:1 / 6.55:1 worst  4.66:1 / 3.73:1 worst    16.26:1 / 13.01:1

   (first figure = against the brightest 1% of background, second = against the
   single brightest background pixel.) 0.82 is the first value where the pink
   .tagline clears WCAG AA 4.5:1 rather than only AA-large 3:1 — the subtext and
   h1 clear AA well before that, the tagline is the binding constraint because
   --pink-bright tops out at 6.02:1 even on pure black. At 0.82 a few of the
   brightest pixels still sit at 3.73:1 for the tagline; that is above AA-large
   and is as far as a scrim can go without flattening the photo entirely.

   0.82 is also exactly the interior wallpaper's own flattening scrim (see
   body.has-wallpaper above), so the hero band and the page beneath it are now
   literally the same tint rather than two dark values that nearly match. If one
   moves, move both. */
.page-hero .hero-bg::after {
  background: rgba(8, 8, 14, 0.82);
}

/* Teams page hero swaps in its own photo instead of final-wallpaper.webp —
   the scrim above still applies (it's keyed off .page-hero, not the image).
   The floodlight glow below is measured against final-wallpaper.webp's own
   lamp position, so it's switched off here rather than left to land on the
   wrong spot of a different photo.

   background-size: cover still comes from the base .hero-bg rule above, the
   same rule the HOME hero uses, so this stays full-bleed edge-to-edge like
   the home hero. background-position is overridden here because "center"
   crops the ball at the bottom of the shot at every width: on the desktop
   .page-hero box (short and wide) cover scales by width and crops top/bottom,
   and centered loses the grass/ball strip; on the mobile box (tall and
   narrow) cover scales by height and crops left/right instead, and centered
   sits the visible window to the left of the ball. 80% 75% pushes both crops
   toward the ball's actual position (lower-right of the shot) while still
   keeping both players in frame. */
body[data-page="teams"] .hero-bg {
  background-image: url("../assets/images/wallpaper/teams-wallpaper.webp");
  background-position: 80% 75%;
}

body[data-page="teams"] .hero-bg::before {
  display: none;
}

/* ---- Floodlight glow ---------------------------------------------------
   The stadium floodlight is baked into final-wallpaper.webp (1536x1024); its
   lamp head measures out at 92.6% across / 25.5% down the IMAGE. This adds a
   soft pulsing halo over that point.

   It is a ::before on .hero-bg, not a separate element, so it inherits the
   parallax translateY() main.js writes onto .hero-bg — a sibling would slide
   off the lamp the moment the page scrolled.

   The position tracks `background-size: cover`. Where the box is wider than
   the image's 1.5 aspect, cover scales by WIDTH, so:
     rendered image height H = 100vw / 1.5 = 66.667vw
     lamp y from box top    = (box_h - H)/2 + 0.255H = 50% - 0.245H
                            = 50% - 16.33vw
   and x is simply 92.6% of the box width, since no width is cropped away.
   That is why the gate below is `min-aspect-ratio: 3/2` and not a width alone:
   on a taller box cover would scale by height instead and this arithmetic
   would be wrong. Outside the gate — mobile, and tall desktop windows — the
   glow is absent rather than guessed at.

   BLEND MODE — the reason this is `plus-lighter` and not `screen`. The lamp
   head in the wallpaper is already RGB 251,250,251, i.e. effectively white.
   `screen` is 1-(1-a)(1-b), so over near-white it has no headroom and adds
   literally nothing: measured delta dead-centre on the lamp was 1-2 of 255
   with the first version of this effect, which is why it read as invisible on
   a real screen while still showing up in a pixel diff. `plus-lighter` is
   genuinely additive and keeps working over bright pixels.

   Where `plus-lighter` is unsupported (pre-111 Chrome, pre-16 Safari, pre-116
   Firefox) `mix-blend-mode` would fall back to `normal` — a flat pink disc
   painted over the wallpaper, which is worse than the subtle version. So
   `screen` is declared first as the floor and the upgrade is behind @supports.

   .hero-bg already creates a stacking context (z-index + transform), so either
   blend resolves against the wallpaper and cannot reach the page behind it.

   SIZE — 34vw, not the lamp's own ~4vw. The halo has to reach past the bright
   lamp into the DARK sky, which is where an additive blend has headroom to
   show; a halo confined to the lamp is confined to the one region that cannot
   get any brighter. */
@media (min-width: 1240px) and (min-aspect-ratio: 3/2) {
  .hero-bg::before {
    content: "";
    position: absolute;
    left: 92.6%;
    top: calc(50% - 16.33vw);
    width: 34vw;
    height: 34vw;
    transform: translate(-50%, -50%);
    border-radius: 50%;
    /* The gradient is a RING, not a disc — transparent at the centre, peaking
       at 46%. That is not a style choice, it is what stops the lamp blowing
       out. A solid core measured 38.9% of the bulb array fully clipped to 255
       at peak, with the dark gaps between bulbs lifted from 54 to 135, i.e.
       the nine bulbs merging into one blob. Punching the centre out puts the
       added light in the DARK SKY, which is both where an additive blend has
       headroom and where a real floodlight's spill actually goes. Measured
       at peak: 4.2% clipped against the wallpaper's own 0.7%, and bulb-array
       contrast 53.3 against the wallpaper's own 54.6 — i.e. essentially
       untouched. Blur is 2vw (38px at 1920), so the ring must start well
       outside the ~82px bulb array or the blur smears it back over the bulbs;
       that is why the first stop past 0 sits at 26%. */
    background: radial-gradient(
      circle closest-side,
      rgba(255, 45, 142, 0) 0%,
      rgba(255, 80, 165, 0.03) 26%,
      rgba(255, 115, 182, 0.32) 46%,
      rgba(255, 50, 145, 0.16) 66%,
      rgba(255, 45, 142, 0) 88%
    );
    filter: blur(2vw);
    mix-blend-mode: screen;
    pointer-events: none;
    z-index: -1;
    animation: floodlight-pulse 7s ease-in-out infinite alternate;
  }

  @supports (mix-blend-mode: plus-lighter) {
    .hero-bg::before {
      mix-blend-mode: plus-lighter;
    }
  }
}

/* The committee's off switch for the glow, set from the admin dashboard
   (Home → "Floodlight glow effect") and applied by js/pageContent.js, which
   adds `glow-off` to .hero-bg when the saved value is "off".

   Deliberately OUTSIDE the media query above: the glow's own rules are gated
   on viewport, an admin turning it off is not, and a rule that only took
   effect above 1240px would look broken on the phone the committee member is
   most likely holding when they check their change. Two classes to the glow's
   one, so it wins on specificity from anywhere in the file rather than
   depending on staying below it.

   Default is ON — the absence of a saved value leaves the glow exactly as it
   renders today, so nothing changes for a club that never touches the
   setting. */
.hero-bg.glow-off::before {
  display: none;
}

@keyframes floodlight-pulse {
  from {
    opacity: 0.45;
    transform: translate(-50%, -50%) scale(0.88);
  }
  to {
    opacity: 1;
    transform: translate(-50%, -50%) scale(1.12);
  }
}

.hero-inner {
  display: grid;
  grid-template-columns: 1.3fr 1fr;
  align-items: center;
  gap: 2rem;
  width: 100%;
  /* Inherited from .hero before the cards were nested — keeps the pre-existing
     "copy centred in the first screen, cards below" layout on mobile. */
  min-height: 100vh;
  padding-top: 6rem;
}

.hero-logo {
  display: block;
  height: 200px;
  width: auto;
  margin-bottom: 1rem;
}

.hero-copy {
  max-width: 640px;
}

.hero-heading {
  position: relative;
  margin-bottom: 0.3em;
  line-height: 0;
}

.hero-heading-img {
  display: block;
  width: clamp(220px, 60vw, 480px);
  height: auto;
  cursor: pointer;
}

/* Click-to-roar (2026-08-19). Triggered from js/main.js on click/tap of
   .hero-heading-img; both classes are added and removed together so the
   shake and the ripple always start and end in step. */
@keyframes roar-shake {
  0%   { transform: translate(0, 0) rotate(0deg) scale(1); }
  5%   { transform: translate(-8px, -6px) rotate(-3deg) scale(1.06); }
  10%  { transform: translate(9px, 7px) rotate(3deg) scale(1.09); }
  15%  { transform: translate(-10px, 5px) rotate(-4deg) scale(1.11); }
  20%  { transform: translate(10px, -8px) rotate(4deg) scale(1.10); }
  25%  { transform: translate(-9px, 8px) rotate(-3.5deg) scale(1.09); }
  30%  { transform: translate(8px, -6px) rotate(3deg) scale(1.08); }
  38%  { transform: translate(-7px, 5px) rotate(-2.5deg) scale(1.06); }
  46%  { transform: translate(6px, -4px) rotate(2deg) scale(1.05); }
  54%  { transform: translate(-5px, 3px) rotate(-1.5deg) scale(1.04); }
  62%  { transform: translate(4px, -2px) rotate(1deg) scale(1.03); }
  70%  { transform: translate(-3px, 2px) rotate(-0.8deg) scale(1.02); }
  80%  { transform: translate(2px, -1px) rotate(0.5deg) scale(1.01); }
  90%  { transform: translate(-1px, 1px) rotate(0deg) scale(1.005); }
  100% { transform: translate(0, 0) rotate(0deg) scale(1); }
}

.hero-heading-img.is-roaring {
  animation: roar-shake 0.9s ease-in-out;
}

@keyframes roar-ripple {
  0% { transform: translate(-50%, -50%) scale(0); opacity: 1; }
  100% { transform: translate(-50%, -50%) scale(30); opacity: 0; }
}

.roar-ripple {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  border: 4px solid #e8187a;
  transform: translate(-50%, -50%) scale(0);
  pointer-events: none;
  animation: roar-ripple 0.9s ease-out forwards;
}

.hero-subtext {
  font-size: var(--fs-lead);
  color: var(--muted);
  max-width: 520px;
}

.hero-subtext .tagline {
  color: var(--pink-bright);
  font-weight: 700;
}

.hero-ctas {
  display: flex;
  gap: 1rem;
  flex-wrap: wrap;
  margin-top: 1.75rem;
}

/* `.hero-photo-wrap` / `.hero-photo` lived here until 2026-08-18. They were
   the styling half of the homepage `hero_photo` image slot, which was deleted
   the same day for never having had an element to attach to — the hero
   heading is artwork (together-we-roar.png), not a photo beside text. The
   validator had been reporting both as dead CSS. `.hero-photo-placeholder`
   below is a DIFFERENT class and is live: teams.html uses it for all four
   team photo slots. */
.hero-photo-placeholder {
  width: 100%;
  min-height: 380px;
  border: 2px dashed rgba(255, 255, 255, 0.35);
  border-radius: var(--radius);
  background: rgba(255, 255, 255, 0.04);
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  color: var(--muted);
  font-size: 0.9rem;
  padding: 2rem;
}

@media (max-width: 900px) {
  .hero-logo {
    height: 110px;
    width: auto;
    margin: 0 auto 1rem;
  }

  .hero-inner {
    grid-template-columns: 1fr;
    text-align: center;
  }

  .hero-copy {
    max-width: none;
    margin: 0 auto;
  }

  .hero-heading-img {
    margin: 0 auto;
  }

  .hero-subtext {
    margin-left: auto;
    margin-right: auto;
  }

  .hero-ctas {
    justify-content: center;
  }
}

/* ---- Info cards ---------------------------------------------------------- */
.info-section {
  padding: 4rem 0 6rem;
}

.info-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr) 1.4fr;
  gap: 1.25rem;
}

.info-card {
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 1.75rem 1.5rem;
  transition: box-shadow 0.25s ease, border-color 0.25s ease, transform 0.2s ease;
}

.info-card:hover {
  border-color: var(--pink);
  box-shadow: 0 0 0 1px var(--pink), 0 0 24px rgba(230, 21, 122, 0.35);
  transform: translateY(-4px);
}

.info-card-icon {
  color: var(--pink-bright);
  margin-bottom: 1rem;
}

.info-card-icon svg {
  width: 32px;
  height: 32px;
}

.info-card h3 {
  font-family: var(--font-body);
  font-weight: 700;
  font-size: 1.05rem;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  margin-bottom: 0.5em;
}

.info-card p {
  color: var(--muted);
  font-size: 0.92rem;
  margin: 0;
}

/* Homepage info cards riding over the hero image below the one-viewport
   breakpoint. index.html deliberately carries no .has-wallpaper class (it has
   its own hero), so --bg-card stays the sitewide near-transparent
   rgba(255,255,255,0.03) and the cards get no backdrop-filter.

   Above 1240px that is correct and must not change: the measured one-viewport
   layout pins the row to the bottom of the hero, where final-wallpaper.webp is
   dark, and the cards read as intended glass. Below 1240px the page reverts to
   the stacked scrolling layout, the same hero image runs the full height behind
   the stack, and cards 2-4 ride up onto the panther's jaw and the bright pink
   crowd — where a 3%-white fill leaves the body copy genuinely unreadable at
   390px.

   The query is the exact complement of the (min-width: 1240px) block further
   down, so the measured desktop layout is untouched by construction rather than
   by inspection. That block ALREADY gives .info-grid .info-card:not(.callout-card)
   this same rgba(10,10,18,0.72) + blur(4px) — which is why the cards read fine
   at 1440px and only broke below it. The values here are deliberately identical
   (and identical again to the .has-wallpaper panel fill), so the card is one
   object either side of the breakpoint rather than two that happen to look
   similar. If one is ever retuned, retune both.

   .callout-card is excluded: its own opaque purple gradient already covers the
   image, and overriding it here would flatten it to a plain dark card. */
@media (max-width: 1239.98px) {
  body[data-page="home"] .info-card:not(.callout-card) {
    background: rgba(10, 10, 18, 0.72);
    backdrop-filter: blur(4px);
  }
}

.callout-card {
  background: linear-gradient(160deg, var(--purple) 0%, var(--bg-alt) 100%);
  border: 1px solid var(--pink);
}

.callout-card h3 {
  font-family: var(--font-display);
  font-weight: 700;
  text-transform: none;
  font-size: 1.4rem;
  letter-spacing: 0.01em;
}

.callout-card h3 .accent {
  color: var(--pink-bright);
  display: block;
}

.callout-card p {
  color: rgba(255, 255, 255, 0.85);
}

.callout-card .btn {
  margin-top: 0.75rem;
  padding: 0.65rem 1.5rem;
  font-size: 0.85rem;
}

@media (max-width: 1100px) {
  .info-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (max-width: 560px) {
  .info-grid {
    grid-template-columns: 1fr;
  }
}

/* ---- Home page: one-viewport layout (desktop) ----------------------------
   Hero copy top-left under the corner crest, wallpaper behind, and all five
   info cards in a row along the bottom of the SAME screen. Only the footer
   sits below the fold.

   MEASURED, NOT EYEBALLED. Every number below is a measured percentage of
   Brad's reference mockup, `_vault/clients/panthers/assets/other/Home Page
   design and layout.png` (1536x1024). The crest box was derived from the green
   laurel's bounding box scaled by the crest PNG's own laurel fractions; the
   heading box by template-matching together-we-roar.png's white "TOGETHER"
   mask against the reference at every scale/offset; the rest read straight off
   pixel row/column profiles. Reference values, as % of the 1024px frame:

     crest       top  2.39   bottom 25.64   (height 23.25)
     heading img top 18.07   bottom 56.06   (height 37.99, incl. the PNG's
                                             transparent margin — the white
                                             "TOGETHER" ink starts at 20.58)
     tagline ink top 56.45   bottom 61.91
     buttons     top 65.23   bottom 70.31
     cards       top 74.80   bottom 96.88   (height 22.07 — superseded 2026-08-18,
                                             see the note below the media query)

   Horizontally, off the 1536px frame: text column left 4.82, heading image
   left 1.63 (so it hangs 3.19 left of the column), crest left 2.11, card row
   4.82 to 95.18.

   Vertical values are svh so the rhythm survives a 780px-tall laptop as well
   as a 1080px desktop; horizontal values are vw because `background-size:
   cover` on a 1536x1024 wallpaper always scales by width on a desktop-shaped
   viewport, which pins the panther's jaw to a constant 36.1% of viewport
   width. Sizing the heading off svh rather than vw is deliberate: it keeps the
   vertical rhythm exact and leaves the heading's right edge at 30-35% on every
   frame tested, i.e. always clear of that jaw.

   Gated at 1240px rather than 901px: below roughly 1240 the three fixed pieces
   of the top row — the crest, eight nav labels and the "Join the Panthers"
   button — cannot sit side by side without the crest landing underneath the
   nav links, and the five-card row drops to two columns at 1100px regardless.
   Narrower screens keep the existing stacked, scrolling layout untouched. */
@media (min-width: 1240px) {
  body[data-page="home"] .hero {
    /* One source of truth for the copy column's geometry — see the table
       above for where each number comes from. */
    --crest-top: 2.39svh;
    --crest-h: clamp(120px, 23.25svh, 340px);
    --head-top: 18.07svh;
    --head-h: clamp(190px, 37.99svh, 560px);
    --col-left: 4.82vw;
    --head-hang: 3.19vw;
    --card-gap-bottom: 3.12svh;

    min-height: 100svh;
    justify-content: space-between;
    align-items: stretch;
    padding-top: 0;
    padding-bottom: var(--card-gap-bottom);
  }

  /* The copy column tracks the viewport's left edge, like the crest — NOT the
     centred 1400px .container. Bound to the centred container the column
     drifted right as the screen widened (x=285 at 1920 vs x=37 at 1440) and
     ran the heading into the panther's face; pinned to the edge it holds the
     reference's 4.82% at every width. */
  body[data-page="home"] .hero-inner {
    max-width: none;
    margin-inline: 0;
    padding-left: var(--col-left);
    padding-right: 1.5rem;
    min-height: 0;
    padding-top: 0;
    align-items: start;
  }

  /* .hero-copy carries .fade-in, and both of its properties break the crest:
     `transform` would make .hero-copy the crest's containing block (dropping it
     ~62px and straight onto "TOGETHER"), and `opacity` would open a stacking
     context that traps z-index: 101 below the header. The crest is the first
     thing on screen, so the fade it loses here is imperceptible. Mobile keeps
     its fade-in. */
  body[data-page="home"] .hero-copy.fade-in {
    opacity: 1;
    transform: none;
    transition: none;
  }

  /* Crest pinned to the hero's top-left corner and painted above the header
     gradient so it keeps full brightness. */
  body[data-page="home"] .hero-logo {
    position: absolute;
    top: var(--crest-top);
    left: 2.1vw;
    height: var(--crest-h);
    width: auto;
    margin: 0;
    z-index: 101;
  }

  /* The crest is out of flow, so .hero-copy's own padding-top places the
     heading image directly — no chain of derived offsets to drift. In the
     reference the laurel's lower leaves deliberately overlap the top of
     "TOGETHER"; that overlap is the 18.07 vs 25.64 gap above, not a mistake. */
  body[data-page="home"] .hero-copy {
    padding-top: var(--head-top);
    max-width: none;
  }

  /* Height-driven so the vertical rhythm holds; the negative margin reproduces
     the reference's heading image hanging left of the text column. */
  body[data-page="home"] .hero-heading-img {
    width: auto;
    height: var(--head-h);
    max-width: none;
    margin-left: calc(-1 * var(--head-hang));
  }

  body[data-page="home"] .hero-heading {
    margin-bottom: 0;
  }

  /* Width-relative, unlike everything vertical: the reference sets this text
     at 18px on a 1536px frame and wraps it to exactly two lines, so the line
     break only survives if the type scales with the column, which is vw.

     The negative margin is the heading PNG's own transparent bottom edge being
     cancelled: the artwork's pink underline ends at 54.19svh but its image box
     runs to 56.06, and the reference puts this paragraph's cap-height at
     56.45. Half-leading then carries the visible ink down to the reference row.

     min-height, not margin-bottom on this or margin-top on .hero-ctas: this
     paragraph is admin-editable and its rendered height is vw-driven, so any
     spacing measured from its BOTTOM lands the buttons on a different row at
     every aspect ratio (67.9% at 1440x780 vs 66.9% at 1440x900). Measuring
     from its top instead pins the buttons to the reference's 65.23 at every
     frame, and min-height rather than height means a longer tagline pushes the
     buttons down gracefully instead of overprinting them. */
  body[data-page="home"] .hero-subtext {
    font-size: clamp(0.95rem, 1.17vw, 1.4rem);
    line-height: 1.72;
    max-width: 34vw;
    margin-top: -0.7svh;
    margin-bottom: 0;
    min-height: 9.87svh;
  }

  body[data-page="home"] .hero-ctas {
    margin-top: 0;
  }

  body[data-page="home"] .hero-ctas .btn {
    padding: 0.7rem 1.6rem;
  }

  /* Unscoped from body[data-page="home"] on 2026-08-19 — this used to apply
     to the home page only, so the nav's "Home" link landed at a different x
     than on the other 7 pages (which have no reserve at this breakpoint).
     Removing the reserve on home instead of widening it everywhere is not an
     option: without it, "CLUB VALUES" sits directly across the club crest in
     .hero-copy (confirmed by disabling the rule and screenshotting — the
     text visibly crosses the badge and wreath). Applying the same reserve on
     every page instead makes the nav genuinely pixel-identical across all 8
     at this breakpoint, and is a no-op change for home itself. Nav centres
     in the gap between the crest and the CTA rather than across the whole
     header — same svh driver again so the reserve tracks the crest. */
  .site-nav {
    padding-left: clamp(9rem, 24svh, 15rem);
  }

  .site-nav ul {
    gap: 1.5rem;
  }

  /* Cards now sit on the wallpaper, not on flat --bg, so they need a real
     translucent fill — 3% white is invisible over a lit photograph. The row
     also drops .container's 1400px cap: the reference runs the cards from
     4.82% to 95.18% of the frame, which a centred 1400px box cannot do past
     ~1450px wide. */
  .info-section {
    padding: 0;
  }

  body[data-page="home"] .info-section > .container {
    max-width: none;
    padding-inline: var(--col-left);
  }

  .info-grid {
    gap: clamp(0.55rem, 0.78vw, 1.1rem);
  }

  /* Card height is intrinsic, not the measured --card-h svh value (dropped
     2026-08-18 — it forced every card to a fixed 22.07svh box regardless of
     content, which left 4-5 lines of empty space under two lines of copy).
     .info-grid is a grid with no align-items override, so its default
     `align-items: stretch` already does exactly what's wanted: every card in
     the row stretches to match the tallest card's own content height — equal
     across the row, and no taller than the longest card actually needs. No
     min-height here at all; padding is still svh/vw-driven so it keeps
     scaling with viewport the way the rest of this block does. */
  .info-grid .info-card {
    padding: clamp(0.7rem, 2svh, 1.6rem) clamp(0.9rem, 1.15vw, 1.6rem);
  }

  .info-grid .info-card:not(.callout-card) {
    background: rgba(10, 10, 18, 0.72);
    backdrop-filter: blur(4px);
    display: grid;
    grid-template-columns: auto 1fr;
    column-gap: 0.7rem;
    align-content: start;
  }

  .info-grid .info-card-icon {
    display: flex;
    grid-column: 1;
    grid-row: 1;
    align-self: center;
    margin-bottom: 0;
  }

  .info-grid .info-card-icon svg {
    width: clamp(22px, 3.1svh, 34px);
    height: clamp(22px, 3.1svh, 34px);
  }

  .info-grid .info-card:not(.callout-card) h3 {
    grid-column: 2;
    grid-row: 1;
    align-self: center;
    font-size: clamp(0.85rem, 1.75svh, 1.3rem);
    line-height: 1.15;
    margin: 0;
  }

  .info-grid .info-card:not(.callout-card) p {
    grid-column: 1 / -1;
    grid-row: 2;
    margin-top: clamp(0.5rem, 2.5svh, 1.8rem);
    font-size: clamp(0.75rem, 1.5svh, 1.1rem);
    line-height: 1.45;
  }

  .info-grid .callout-card {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
  }

  /* Anton on two tight lines, as in the reference. The callout is the
     tallest card (h3 + p + btn vs. the others' icon + h3 + p), so with the
     row's height now intrinsic (see the .info-grid .info-card note above)
     it's the callout's own height that sets the whole row's — keeping its
     line-height and margins tight here is what keeps the one-viewport hero
     inside the fold at 1440x780, not a fixed row height doing that job. */
  .info-grid .callout-card h3 {
    font-size: clamp(1.05rem, 2.35svh, 1.8rem);
    line-height: 1.05;
    margin-bottom: 0;
  }

  .info-grid .callout-card p {
    font-size: clamp(0.75rem, 1.5svh, 1.1rem);
    line-height: 1.45;
    margin-top: clamp(0.35rem, 1.4svh, 1.1rem);
  }

  .info-grid .callout-card .btn {
    margin-top: clamp(0.4rem, 1.35svh, 1.1rem);
    padding: 0.55rem 1.2rem;
  }
}

/* Escape hatch: on a genuinely short screen, stop forcing one viewport and let
   the page scroll rather than crushing the type to fit. */
@media (min-width: 1240px) and (max-height: 680px) {
  body[data-page="home"] .hero {
    min-height: 0;
  }

  body[data-page="home"] .info-section {
    padding-bottom: 3rem;
  }
}

/* ---- Fade-in on scroll ---------------------------------------------------- */
.fade-in {
  opacity: 0;
  transform: translateY(24px);
  transition: opacity 0.6s ease, transform 0.6s ease;
}

.fade-in.is-visible {
  opacity: 1;
  transform: translateY(0);
}

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }

  .fade-in {
    opacity: 1;
    transform: none;
    transition: none;
  }

  .hero-bg {
    transform: none !important;
  }

  /* Glow stays — it is light, not motion — but stops breathing. Held at the
     bright end of the cycle so it still reads as a lit floodlight. */
  .hero-bg::before {
    animation: none;
    opacity: 1;
    transform: translate(-50%, -50%) scale(1);
  }
}

/* ---- Footer ------------------------------------------------------------ */
.site-footer {
  position: relative;
  border-top: 1px solid var(--border);
  background: var(--bg-alt);
  padding: 2rem 0;
}

/* Same glow divider as body.has-wallpaper .page-hero::after, at the footer's
   top edge instead of the hero's bottom — frames every page (including
   index.html, which has no .has-wallpaper hero of its own) consistently. */
.site-footer::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  height: 3px;
  background: var(--pink-bright);
  box-shadow: 0 0 12px 3px rgba(255, 45, 142, 0.6);
}

/* Three zones — copyright / Facebook / credit — as a 3-column grid so the
   centre item is truly centred in the bar rather than merely sitting between
   the other two (a flex space-between drifts the middle item off-centre the
   moment the two end items differ in width). The two 1fr edge columns stay
   equal so the centre column's centre is the bar's centre. */
.footer-inner {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 1rem;
}

.footer-meta {
  justify-self: start;
}

.footer-social {
  justify-self: center;
}

.footer-credit {
  justify-self: end;
}

.footer-meta p {
  margin: 0;
  color: var(--muted);
  font-size: 0.975rem;
}

.footer-credit {
  margin: 0;
  color: var(--muted);
  font-size: 0.975rem;
}

/* --pink-bright, not --pink: --pink (#e6157a) is 4.20:1 against --bg-alt and
   fails WCAG AA for normal text at this size, while --pink-bright (#ff2d8e)
   clears it at 5.32:1. Both read as the club pink. */
.footer-credit-link {
  color: var(--pink-bright);
  transition: color 0.2s ease;
}

.footer-credit-link:hover,
.footer-credit-link:focus-visible {
  color: var(--white);
}

/* One anchor wrapping label + icon, so the whole phrase is the click target
   and the icon needs no aria-label of its own — the visible text names it. */
.footer-social {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  color: var(--muted);
  font-size: 0.975rem;
  transition: color 0.2s ease;
}

.footer-social:hover,
.footer-social:focus-visible {
  color: var(--pink-bright);
}

.social-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  /* --pink-bright to match the "Built by Octopathic" credit link, and thick
     enough (3px) to read clearly against the dark footer bar. */
  border: 3px solid var(--pink-bright);
  transition: box-shadow 0.2s ease, border-color 0.2s ease;
}

.social-icon svg {
  width: 18px;
  height: 18px;
  fill: var(--white);
}

.footer-social:hover .social-icon,
.footer-social:focus-visible .social-icon {
  border-color: var(--pink);
  box-shadow: var(--accent-glow);
}

/* ~1.3x the base 36px/18px icon, per the redesign. */
.social-icon-lg {
  width: 47px;
  height: 47px;
}

.social-icon-lg svg {
  width: 23px;
  height: 23px;
}

/* Stack the three zones centred, in the same left-to-right reading order, once
   the bar is too narrow for a single row. */
@media (max-width: 560px) {
  .footer-inner {
    grid-template-columns: 1fr;
    justify-items: center;
    text-align: center;
    gap: 1.25rem;
  }

  .footer-meta,
  .footer-social,
  .footer-credit {
    justify-self: center;
  }
}

/* ==== Inner-page components (Phase 1b) =================================== */

/* ---- Page hero (shorter hero for non-home pages) ----------------------- */
.page-hero {
  position: relative;
  min-height: 48vh;
  display: flex;
  align-items: center;
  overflow: hidden;
  padding: 9rem 0 3.5rem;
  isolation: isolate;
}

.page-hero-inner {
  text-align: center;
  max-width: 760px;
  margin: 0 auto;
}

.page-hero h1 {
  font-size: clamp(2.4rem, 5.5vw, 4.25rem);
  color: var(--white);
  text-shadow: 0 4px 24px rgba(0, 0, 0, 0.5);
}

.page-hero h1 .accent {
  color: var(--pink-bright);
}

.page-hero .hero-subtext {
  margin: 0 auto;
  /* Belt-and-braces over the scrim above, and the same device .page-hero h1
     already uses — the photo behind is high-frequency, so a flat scrim alone
     still leaves individual letters sitting on a bright shirt number. */
  text-shadow: 0 2px 12px rgba(0, 0, 0, 0.65);
}

/* Glow divider at the base of the interior hero. Scoped to body.has-wallpaper
   so index.html's own hero (section.hero, no .page-hero) never gets it. */
body.has-wallpaper .page-hero::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 3px;
  background: var(--pink-bright);
  box-shadow: 0 0 12px 3px rgba(255, 45, 142, 0.6);
}

/* Interior pages (body.has-wallpaper) want every heading fully white — no
   pink/white split. index.html carries no .has-wallpaper class, so its own
   .accent spans (still pink) are untouched by this override. Each selector
   below mirrors an existing "<context> .accent { color: pink }" rule with
   body.has-wallpaper prepended — a single bare "body.has-wallpaper .accent"
   isn't specific enough to beat rules like .content-section .section-heading
   .accent (0,3,0) or .vacancy-body h3 .accent (declared later in the file). */
body.has-wallpaper .page-hero h1 .accent,
body.has-wallpaper .content-section .section-heading .accent,
body.has-wallpaper .callout-card h3 .accent,
body.has-wallpaper .team-name .accent,
body.has-wallpaper .vacancy-body h3 .accent {
  color: var(--white);
}

/* ---- Generic content sections ------------------------------------------ */
.content-section {
  padding: 4.5rem 0;
}

.content-section.alt {
  background: var(--bg-alt);
}

.content-section .section-heading {
  font-size: var(--fs-h2);
  margin-bottom: 0.6em;
}

.content-section .section-heading .accent {
  color: var(--pink-bright);
}

.content-section .lead {
  color: var(--muted);
  font-size: var(--fs-lead);
  max-width: 820px;
}

.content-narrow {
  max-width: 820px;
}

.value-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 1.25rem;
  margin-top: 2rem;
}

@media (max-width: 1100px) {
  .value-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (max-width: 560px) {
  .value-grid {
    grid-template-columns: 1fr;
  }
}

/* ---- Team nav (teams.html) --------------------------------------------- */
.team-nav {
  position: sticky;
  top: 84px;
  z-index: 90;
  background: rgba(10, 10, 18, 0.96);
  backdrop-filter: blur(8px);
  border-bottom: 1px solid var(--border);
  padding: 1rem 0;
}

.team-nav ul {
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  gap: 0.75rem;
  list-style: none;
  margin: 0;
  padding: 0;
}

.team-nav a {
  display: inline-block;
  padding: 0.55rem 1.4rem;
  border-radius: 999px;
  border: 1px solid var(--border);
  font-family: var(--font-display);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  font-size: 0.95rem;
  color: var(--white);
  transition: border-color 0.2s ease, color 0.2s ease, background 0.2s ease;
}

.team-nav a:hover,
.team-nav a:focus-visible {
  border-color: var(--pink);
  color: var(--pink-bright);
  background: rgba(230, 21, 122, 0.1);
}

/* ---- Team sections (teams.html) ----------------------------------------- */
.team-section {
  padding: 5rem 0;
  scroll-margin-top: 90px;
}

.team-section.alt {
  background: var(--bg-alt);
}

.team-layout {
  display: grid;
  grid-template-columns: 1fr 1.3fr;
  gap: 3rem;
  align-items: center;
}

.team-layout.reverse .team-photo-col {
  order: 2;
}

.team-name {
  font-size: clamp(2rem, 4vw, 2.75rem);
  margin-bottom: 0.4em;
}

.team-name .accent {
  color: var(--pink-bright);
}

.team-desc {
  color: var(--muted);
  font-size: var(--fs-lead);
  max-width: 560px;
}

.coach-list {
  display: flex;
  gap: 1.5rem;
  flex-wrap: wrap;
  margin: 1.5rem 0;
  list-style: none;
  padding: 0;
}

.coach-item {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  font-size: 0.9rem;
  color: var(--white);
}

.coach-avatar {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background: var(--bg-card);
  border: 1px solid var(--border);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--pink-bright);
  flex-shrink: 0;
}

.coach-avatar svg {
  width: 20px;
  height: 20px;
}

.team-meta {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  margin-top: 1.5rem;
  font-size: 0.92rem;
  color: var(--muted);
}

.team-meta-item {
  display: flex;
  align-items: center;
  gap: 0.6rem;
}

.team-meta-item svg {
  width: 18px;
  height: 18px;
  color: var(--pink-bright);
  flex-shrink: 0;
}

@media (max-width: 900px) {
  .team-layout,
  .team-layout.reverse .team-photo-col {
    order: initial;
  }

  .team-layout {
    grid-template-columns: 1fr;
    text-align: center;
  }

  .team-desc,
  .coach-list,
  .team-meta {
    margin-left: auto;
    margin-right: auto;
  }

  .coach-list,
  .team-meta-item {
    justify-content: center;
  }
}

/* ---- Vacancy cards ------------------------------------------------------- */
.vacancy-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 1.5rem;
  margin-top: 2.5rem;
}

.vacancy-card {
  display: flex;
  flex-direction: column;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg-card);
  overflow: hidden;
  transition: box-shadow 0.25s ease, border-color 0.25s ease, transform 0.2s ease;
}

.vacancy-card:hover {
  border-color: var(--pink);
  box-shadow: 0 0 0 1px var(--pink), 0 0 24px rgba(230, 21, 122, 0.35);
  transform: translateY(-4px);
}

.vacancy-ribbon {
  background: var(--pink);
  color: var(--white);
  text-align: center;
  padding: 0.6rem;
  font-family: var(--font-display);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  font-size: 0.85rem;
}

.vacancy-body {
  padding: 1.5rem;
  display: flex;
  flex-direction: column;
  gap: 1rem;
  flex: 1;
}

.vacancy-body h3 {
  font-size: 1.6rem;
  margin-bottom: 0;
}

.vacancy-body h3 .accent {
  color: var(--pink-bright);
}

.vacancy-body p {
  color: var(--muted);
  font-size: 0.92rem;
  margin: 0;
}

.vacancy-meta {
  display: flex;
  flex-direction: column;
  gap: 0.65rem;
  font-size: 0.85rem;
  color: var(--muted);
  border-top: 1px solid var(--border);
  padding-top: 1rem;
}

.vacancy-meta-item {
  display: flex;
  gap: 0.6rem;
  align-items: flex-start;
}

.vacancy-meta-item svg {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
  color: var(--pink-bright);
  margin-top: 1px;
}

.vacancy-cta {
  display: block;
  text-align: center;
  margin-top: auto;
}

@media (max-width: 1100px) {
  .vacancy-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (max-width: 560px) {
  .vacancy-grid {
    grid-template-columns: 1fr;
  }
}

/* ---- Forms (contact, sponsors) ------------------------------------------ */
.form-layout {
  display: grid;
  grid-template-columns: 1.2fr 1fr;
  gap: 3rem;
  align-items: start;
}

.site-form {
  display: grid;
  gap: 1.25rem;
}

.form-row {
  display: grid;
  gap: 1.25rem;
  grid-template-columns: 1fr 1fr;
}

.form-group {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}

.form-group label {
  font-size: 0.8rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--muted);
  font-weight: 600;
}

.form-group input,
.form-group textarea {
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid var(--border);
  border-radius: var(--btn-radius);
  padding: 0.8rem 1rem;
  color: var(--white);
  font-family: var(--font-body);
  font-size: 0.95rem;
  width: 100%;
}

.form-group input:focus,
.form-group textarea:focus {
  outline: none;
  border-color: var(--pink);
  box-shadow: var(--accent-glow);
}

.form-group textarea {
  resize: vertical;
  min-height: 130px;
}

.form-note {
  font-size: 0.82rem;
  color: var(--muted);
  margin-top: 0.25rem;
}

.form-note a {
  color: var(--pink-bright);
  text-decoration: underline;
}

.contact-info-card {
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 2rem;
}

.contact-info-card h3 {
  font-family: var(--font-body);
  font-weight: 700;
  font-size: 1.05rem;
  text-transform: uppercase;
  letter-spacing: 0.02em;
}

.contact-line {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin-bottom: 1rem;
  font-size: 0.95rem;
}

.contact-line svg {
  width: 20px;
  height: 20px;
  color: var(--pink-bright);
  flex-shrink: 0;
}

.contact-line a:hover {
  color: var(--pink-bright);
}

@media (max-width: 900px) {
  .form-layout {
    grid-template-columns: 1fr;
  }

  .form-row {
    grid-template-columns: 1fr;
  }
}

/* ---- Sponsors ------------------------------------------------------------ */
.sponsor-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1.5rem;
  margin-top: 2rem;
}

.sponsor-slot {
  aspect-ratio: 16 / 9;
  border: 2px dashed rgba(255, 255, 255, 0.25);
  border-radius: var(--radius);
  background: rgba(255, 255, 255, 0.03);
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  color: var(--muted);
  font-size: 0.9rem;
  padding: 1rem;
}

@media (max-width: 700px) {
  .sponsor-grid {
    grid-template-columns: 1fr;
  }
}

/* ---- Shop -------------------------------------------------------------- */
/* Shared, despite the name: this is the site's generic "one centred CTA button
   below the hero" band, and 404.html uses it for its Back to Home button as
   well as shop.html for Visit Our Shop. Reused rather than duplicated under a
   second name — if you retune the padding here, check both pages. */
.shop-cta-wrap {
  text-align: center;
  padding: 5rem 0 6rem;
}

.btn-large {
  padding: 1.1rem 2.75rem;
  font-size: 1.15rem;
}

/* ---- Safeguarding -------------------------------------------------------- */
.welfare-callout {
  background: linear-gradient(160deg, var(--purple) 0%, var(--bg-alt) 100%);
  border: 1px solid var(--pink);
  border-radius: var(--radius);
  padding: 2rem;
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 1.5rem;
  margin-bottom: 2.5rem;
}

.welfare-callout-copy h3 {
  font-family: var(--font-body);
  text-transform: uppercase;
  font-size: 1rem;
  letter-spacing: 0.03em;
  color: var(--muted);
  margin-bottom: 0.4em;
}

.welfare-callout-copy a.welfare-email {
  font-family: var(--font-display);
  font-size: 1.6rem;
  color: var(--pink-bright);
  letter-spacing: 0.01em;
  word-break: break-word;
}

.policy-meta {
  color: var(--muted);
  font-size: 0.85rem;
  margin-bottom: 2rem;
}

/* ---- Safeguarding policy cards ------------------------------------------
   The 32 policy sections are a two-column directory of cards rather than one
   long accordion: icon + section title + a one-line plain-English summary,
   with the full (unaltered) policy text revealed on click.

   The expand is native <details>/<summary> — no JS, keyboard-operable and
   findable by the browser's own in-page search. The one rule that makes the
   grid work is `.policy-card[open] { grid-column: 1 / -1 }`: an open card
   promotes itself to a full-width row instead of growing inside its column,
   so a 900px section never drags an empty 900px sibling up beside it. Closed
   cards are near-uniform height by construction (one line of title, one or
   two of summary), which is why the grid keeps CSS Grid's default `stretch`
   rather than a min-height — no forced box, no short-card void.

   Body copy is capped at 78ch and indented under the text column so the
   full-width open state doesn't produce 900px measure. */
.content-policy {
  max-width: 1080px;
}

.policy-sprite {
  position: absolute;
  width: 0;
  height: 0;
  overflow: hidden;
}

.policy-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1.1rem;
}

.policy-card {
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
  transition: border-color 0.25s ease, box-shadow 0.25s ease;
}

.policy-card:hover {
  border-color: var(--pink);
  box-shadow: 0 0 0 1px var(--pink), 0 0 24px rgba(230, 21, 122, 0.28);
}

.policy-card[open] {
  grid-column: 1 / -1;
  border-color: var(--pink);
  box-shadow: 0 0 0 1px var(--pink), 0 0 32px rgba(230, 21, 122, 0.22);
  scroll-margin-top: 110px;
}

.policy-card summary {
  cursor: pointer;
  list-style: none;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: start;
  gap: 1rem;
  padding: 1.35rem 1.4rem;
}

.policy-card summary::-webkit-details-marker {
  display: none;
}

.policy-card summary:focus-visible {
  outline: 2px solid var(--pink-bright);
  outline-offset: -3px;
}

.policy-card-icon {
  display: block;
  color: var(--pink-bright);
  margin-top: 0.1rem;
}

.policy-card-icon svg {
  width: 26px;
  height: 26px;
  display: block;
}

.policy-card-title {
  display: block;
  font-family: var(--font-display);
  font-weight: 700;
  text-transform: uppercase;
  font-size: 1.02rem;
  letter-spacing: 0.015em;
  line-height: 1.25;
  transition: color 0.2s ease;
}

.policy-card:hover .policy-card-title,
.policy-card[open] .policy-card-title {
  color: var(--pink-bright);
}

.policy-card-summary {
  display: block;
  margin-top: 0.35rem;
  color: var(--muted);
  font-size: 0.88rem;
  line-height: 1.5;
}

.policy-card-toggle {
  position: relative;
  width: 18px;
  height: 18px;
  margin-top: 0.3rem;
}

.policy-card-toggle::before,
.policy-card-toggle::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: 14px;
  height: 2px;
  border-radius: 2px;
  background: var(--pink-bright);
  transform: translate(-50%, -50%);
  transition: transform 0.25s ease;
}

.policy-card-toggle::after {
  transform: translate(-50%, -50%) rotate(90deg);
}

.policy-card[open] .policy-card-toggle::after {
  transform: translate(-50%, -50%) rotate(0deg);
}

.policy-section-body {
  margin: 0 1.4rem;
  padding: 1.3rem 0 1.5rem calc(26px + 1rem);
  border-top: 1px solid var(--border);
  color: var(--muted);
  font-size: 0.92rem;
  max-width: 78ch;
}

.policy-section-body p {
  margin-bottom: 0.9em;
}

.policy-section-body p:last-child {
  margin-bottom: 0;
}

.policy-section-body a {
  color: var(--pink-bright);
  text-decoration: underline;
  text-underline-offset: 2px;
  word-break: break-word;
}

.policy-section-body ul {
  padding-left: 1.25rem;
  margin: 0 0 0.9em;
}

.policy-section-body li {
  margin-bottom: 0.4em;
}

@media (max-width: 820px) {
  .policy-grid {
    grid-template-columns: 1fr;
  }

  .policy-card summary {
    padding: 1.15rem 1.15rem;
    gap: 0.85rem;
  }

  .policy-section-body {
    margin: 0 1.15rem;
    padding-left: 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  .policy-card,
  .policy-card-title,
  .policy-card-toggle::before,
  .policy-card-toggle::after {
    transition: none;
  }
}

/* ---- Join the Panthers modal --------------------------------------------- */
body.modal-open {
  overflow: hidden;
}

.modal-overlay {
  position: fixed;
  inset: 0;
  z-index: 200;
  background: rgba(5, 5, 10, 0.8);
  backdrop-filter: blur(4px);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.5rem;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.25s ease;
}

.modal-overlay.is-open {
  opacity: 1;
  pointer-events: auto;
}

.join-modal {
  position: relative;
  width: 100%;
  max-width: 480px;
  max-height: 90vh;
  overflow-y: auto;
  background: var(--bg-alt);
  border: 1px solid var(--pink);
  border-radius: var(--radius);
  padding: 2.5rem 2rem 2rem;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.6), 0 0 40px rgba(230, 21, 122, 0.2);
  transform: translateY(16px) scale(0.97);
  opacity: 0;
  transition: transform 0.25s ease, opacity 0.25s ease;
}

.modal-overlay.is-open .join-modal {
  transform: translateY(0) scale(1);
  opacity: 1;
}

.modal-close {
  position: absolute;
  top: 1rem;
  right: 1rem;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: rgba(255, 255, 255, 0.04);
  color: var(--white);
  font-size: 1.3rem;
  line-height: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: border-color 0.2s ease, color 0.2s ease, background 0.2s ease;
}

.modal-close:hover,
.modal-close:focus-visible {
  border-color: var(--pink);
  color: var(--pink-bright);
  background: rgba(230, 21, 122, 0.1);
}

.modal-header {
  text-align: center;
  margin-bottom: 1.75rem;
}

/* object-fit: contain, not cover — see .logo above; same reasoning applies
   here now the white disc is gone. */
.modal-logo {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 64px;
  height: 64px;
  margin: 0 auto 1rem;
  object-fit: contain;
}

.modal-header h2 {
  font-size: 1.75rem;
  margin-bottom: 0.35em;
}

.modal-header .modal-subtext {
  color: var(--muted);
  font-size: 0.95rem;
  margin: 0;
}

.btn-block {
  display: block;
  width: 100%;
  text-align: center;
}

.join-success {
  text-align: center;
  padding: 1.5rem 0 0.5rem;
}

.join-success-icon {
  width: 56px;
  height: 56px;
  border-radius: 50%;
  background: rgba(230, 21, 122, 0.15);
  border: 1px solid var(--pink);
  color: var(--pink-bright);
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 0 auto 1.25rem;
}

.join-success-icon svg {
  width: 28px;
  height: 28px;
}

.join-success h3 {
  font-size: 1.4rem;
  margin-bottom: 0.4em;
}

.join-success p {
  color: var(--muted);
  margin: 0;
}

@media (max-width: 560px) {
  .join-modal {
    padding: 2.25rem 1.25rem 1.5rem;
  }
}
