/* ==========================================================================
   Hult Prize @ UC Davis — Home page styles (v3: dark / magenta rebuild)
   ========================================================================== */

:root {
  /* Color system v3.
     UC Davis navy/gold live ONLY in the header — the one deliberate anchor.
     Everything below the nav uses the dark charcoal base with Hult Prize's
     verified magenta as the dominant accent, plus a small curated set of
     secondary accents (teal/sky/orange/gold) used sparingly, never as a
     full-page wash. */
  --charcoal: #17141b;
  --charcoal-deep: #0d0b10;
  --charcoal-raised: #221d29;

  --magenta: #ec2088;
  --magenta-dark: #b8156a;
  --magenta-darker: #93124f;

  --teal: #2bbba0;
  --sky: #3aa9e0;
  --orange: #f2994a;
  --gold: #ffbf00;

  /* Metallic placement tones for the Top Teams podium — a specific,
     semantically-tied application of the accent system (1st/2nd/3rd),
     not a new arbitrary palette. Gold reuses the existing --gold token. */
  --silver: #c4c9d2;
  --bronze: #cd7f32;

  --navy: #022851;
  --navy-line: rgba(255, 255, 255, 0.16);

  --white: #ffffff;
  --text-secondary: #ada8b8;
  --line: rgba(255, 255, 255, 0.14);

  /* Low-opacity background texture, shared across the dark sections below
     the pinned sequence for a consistent visual thread. */
  --bg-network: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='240' height='240'%3E%3Cg fill='none' stroke='%23EC2088' stroke-opacity='0.4' stroke-width='1'%3E%3Cline x1='20' y1='30' x2='90' y2='70'/%3E%3Cline x1='90' y1='70' x2='160' y2='40'/%3E%3Cline x1='160' y1='40' x2='210' y2='100'/%3E%3Cline x1='40' y1='160' x2='110' y2='190'/%3E%3Cline x1='110' y1='190' x2='180' y2='150'/%3E%3Cline x1='180' y1='150' x2='230' y2='210'/%3E%3C/g%3E%3Cg fill='%23EC2088' fill-opacity='0.5'%3E%3Ccircle cx='20' cy='30' r='3'/%3E%3Ccircle cx='90' cy='70' r='2.5'/%3E%3Ccircle cx='160' cy='40' r='3'/%3E%3Ccircle cx='210' cy='100' r='2.5'/%3E%3Ccircle cx='40' cy='160' r='3'/%3E%3Ccircle cx='110' cy='190' r='2.5'/%3E%3Ccircle cx='180' cy='150' r='3'/%3E%3Ccircle cx='230' cy='210' r='2.5'/%3E%3C/g%3E%3C/svg%3E");

  /* Type */
  --font-head: "Space Grotesk", "Segoe UI", sans-serif;
  --font-body: "Inter", "Segoe UI", sans-serif;

  /* Layout */
  --container-width: 1200px;
  --gutter: clamp(1.25rem, 4vw, 3rem);
  --radius: 4px;
}

/* --------------------------------------------------------------------------
   Reset & base
   -------------------------------------------------------------------------- */

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

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  /* Home's hero is pinned starting at the very top of the page
     (initPinSequence, ScrollTrigger start: "top top") — without this, an
     elastic overscroll bounce (trackpad scroll past the very top) could
     briefly reveal a sliver of whatever sits behind the pinned, fixed-
     position hero content (a glimpse of the shared .pin-bg network motif
     peeking through at the edges) before the bounce settles back. This
     just disables that rubber-band bounce; it doesn't affect normal
     scrolling, scrollTo/scrollBy, or ScrollTrigger's own mechanics. */
  overscroll-behavior-y: none;
  /* Chrome/Safari paint the overscroll rubber-band area itself using the
     ROOT element's (html's, not body's) background — html had none set,
     so it fell back to the browser's default (a stark white/light canvas)
     regardless of overscroll-behavior above, which only suppresses the
     bounce, not what color briefly shows through it or through any other
     transient gap. <body> already matches this on every page it needs to
     (--charcoal site-wide, --charcoal-deep on Home via .home-page) — this
     just makes sure the very root, underneath that, is never a jarring
     mismatched color anywhere. Invisible in normal use everywhere (body
     always fully covers it), it's only ever a safety net for edge cases
     like this one. */
  background-color: var(--charcoal-deep);
}

body {
  margin: 0;
  font-family: var(--font-body);
  color: var(--white);
  background: var(--charcoal);
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}

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

h1,
h2,
h3 {
  font-family: var(--font-head);
  color: inherit;
  line-height: 1.12;
  margin: 0 0 1rem;
  letter-spacing: -0.01em;
}

p {
  margin: 0 0 1rem;
}

a {
  color: inherit;
}

ul,
ol {
  list-style: none;
  margin: 0;
  padding: 0;
}

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

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

/* Skip link */
.skip-link {
  position: absolute;
  left: 1rem;
  top: -3rem;
  background: var(--magenta);
  color: var(--white);
  padding: 0.75rem 1.25rem;
  font-family: var(--font-head);
  font-weight: 600;
  border-radius: var(--radius);
  z-index: 200;
  transition: top 0.15s ease;
}

.skip-link:focus {
  top: 1rem;
}

/* Focus visibility (keyboard). Magenta reads clearly against the dark base;
   the header overrides this to gold to stay consistent with its own navy/gold
   accent system. */
a:focus-visible,
button:focus-visible {
  outline: 3px solid var(--magenta);
  outline-offset: 3px;
}

.site-header a:focus-visible {
  outline-color: var(--gold);
}

/* --------------------------------------------------------------------------
   Buttons & links
   -------------------------------------------------------------------------- */

.btn {
  display: inline-block;
  font-family: var(--font-head);
  font-weight: 600;
  font-size: 1rem;
  letter-spacing: 0.01em;
  padding: 0.95rem 1.9rem;
  border-radius: var(--radius);
  text-decoration: none;
  border: 2px solid transparent;
}

/* magenta-dark (not the flat brand magenta) so white button text clears
   WCAG AA contrast at this size — see the design notes in the commit. */
.btn-primary {
  background: var(--magenta-dark);
  color: var(--white);
}

.btn-primary:hover,
.btn-primary:focus-visible {
  background: var(--magenta-darker);
}

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

.btn-ghost:hover,
.btn-ghost:focus-visible {
  background: var(--white);
  color: var(--charcoal-deep);
}

.text-link {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  font-family: var(--font-head);
  font-weight: 600;
  color: inherit;
  text-decoration: none;
  border-bottom: 2px solid var(--magenta);
  padding-bottom: 0.15rem;
}

.text-link:hover,
.text-link:focus-visible {
  border-color: currentColor;
}

/* --------------------------------------------------------------------------
   Header — the one deliberate UC Davis anchor point (navy + gold), unchanged
   in spirit from earlier stages.
   -------------------------------------------------------------------------- */

.site-header {
  background: var(--navy);
  border-bottom: 1px solid var(--navy-line);
  position: sticky;
  top: 0;
  z-index: 50;
}

.header-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-block: 1.1rem;
  gap: 1rem;
}

.wordmark {
  font-family: var(--font-head);
  font-weight: 700;
  font-size: 1.1rem;
  color: var(--white);
  text-decoration: none;
  white-space: nowrap;
}

.wordmark span {
  color: var(--gold);
}

/* .primary-nav-panel wraps the Search trigger + <nav> (js/script.js,
   renderSiteHeader/initSiteSearch) so both can become a single off-canvas
   mobile drawer together below NAV_COLLAPSE_MIN_WIDTH (see the mobile nav
   block further down) — `display: contents` here is what keeps that
   wrapper invisible to THIS layout, so its two children still participate
   directly in .header-inner's own flex row exactly as if they had no
   shared wrapper at all. Nothing in this rule, or any rule above it,
   changed to make that true — this file's existing desktop behavior for
   the wordmark/search-trigger/nav is completely unmodified. */
.primary-nav-panel {
  display: contents;
}

/* Stage 11a: the search trigger (js/script.js, initSiteSearch) is inserted
   as .primary-nav-panel's first child, ahead of <nav> — with that wrapper
   at `display: contents` (above), the trigger still renders exactly where
   it always has: between the wordmark and nav, making this a 3-item flex
   row instead of the original 2 — margin-left: auto keeps nav pinned to
   the right edge exactly as `.header-inner`'s justify-content:
   space-between did on its own before, rather than that space getting
   redistributed into a gap on both sides of the trigger. */
#primary-nav {
  margin-left: auto;
}

#primary-nav ul {
  display: flex;
  align-items: center;
  flex-wrap: nowrap;
  /* Stage 2h: tightened from clamp(1rem, 3vw, 2rem) — adding How It Works
     as a 9th nav item pushed the previous gap past what fits on one line
     at common desktop widths (~1300-1400px), wrapping the nav to two
     rows. (A mobile-compatibility audit later found this row has no
     shrink/wrap escape hatch at all below that: nowrap plus this gap
     forced ~1090px of unshrinkable content into whatever width was
     actually available, all the way down to phones. Fixed below,
     NAV_COLLAPSE_MIN_WIDTH — this comment's own gap value is still
     exactly what it was, and still only the desktop-width fix it always
     was.) */
  gap: clamp(0.75rem, 1.8vw, 1.5rem);
}

#primary-nav a {
  color: var(--white);
  text-decoration: none;
  font-size: 0.95rem;
  font-weight: 500;
}

#primary-nav a:not(.nav-cta) {
  position: relative;
  padding-bottom: 3px;
}

#primary-nav a:not(.nav-cta)::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: -1px;
  height: 2px;
  width: 0;
  background: var(--gold);
}

@media (prefers-reduced-motion: no-preference) {
  #primary-nav a:not(.nav-cta)::after {
    transition: width 0.25s ease;
  }
}

#primary-nav a:not(.nav-cta):hover,
#primary-nav a:not(.nav-cta):focus-visible {
  color: var(--gold);
}

#primary-nav a:not(.nav-cta):hover::after,
#primary-nav a:not(.nav-cta):focus-visible::after {
  width: 100%;
}

/* Marks the nav link for whichever page is currently open (e.g. Team, on
   team.html) — reuses the same gold-text/underline language as :hover so it
   reads as "you are here" rather than a new visual idiom. */
#primary-nav a[aria-current="page"] {
  color: var(--gold);
}

#primary-nav a[aria-current="page"]:not(.nav-cta)::after {
  width: 100%;
}

.nav-cta {
  font-family: var(--font-head);
  font-weight: 600;
  background: var(--gold);
  color: var(--navy) !important;
  padding: 0.55rem 1.1rem;
  border-radius: var(--radius);
  white-space: nowrap;
}

.nav-cta:hover,
.nav-cta:focus-visible {
  background: #e0a800;
}

/* --------------------------------------------------------------------------
   Mobile nav drawer. Replaces the old `.nav-link-item { display: none }`
   rule (a max-width: 640px block that never actually did anything — no
   element anywhere on the site ever carried that class, so the nav row it
   was meant to hide never actually collapsed at any width; a
   mobile-compatibility audit is what caught this). NAV_COLLAPSE_MIN_WIDTH
   in js/script.js documents the actual arithmetic behind 1200px — the
   real, measured minimum content width of the wordmark + search trigger +
   9 nav items + gaps, not a guess.

   Base rules here (outside the media query) hide the toggle/backdrop and
   keep .primary-nav-panel inert on desktop — see that class's own
   `display: contents` rule further up this file. Everything that actually
   turns this into a drawer lives inside the max-width query below, so
   desktop is provably unaffected by any of it.
   -------------------------------------------------------------------------- */

.nav-toggle {
  display: none;
}

.nav-backdrop {
  display: none;
}

@media (max-width: 1199px) {
  .nav-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    margin-left: auto;
    border: 0;
    border-radius: 10px;
    background: none;
    color: var(--white);
    cursor: pointer;
  }

  .nav-toggle-icon {
    width: 24px;
    height: 24px;
  }

  @media (prefers-reduced-motion: no-preference) {
    .nav-toggle-bar {
      transition: transform 0.25s ease, opacity 0.25s ease;
      transform-origin: 50% 50%;
    }
  }

  /* Hamburger -> X, driven purely by aria-expanded (no separate JS-toggled
     class needed) — the middle bar fades out, the outer two rotate into an
     X in place. */
  .nav-toggle[aria-expanded="true"] .nav-toggle-bar-top {
    transform: translateY(5px) rotate(45deg);
  }
  .nav-toggle[aria-expanded="true"] .nav-toggle-bar-mid {
    opacity: 0;
  }
  .nav-toggle[aria-expanded="true"] .nav-toggle-bar-bottom {
    transform: translateY(-5px) rotate(-45deg);
  }

  .nav-backdrop {
    display: block;
    position: fixed;
    inset: 0;
    z-index: 199;
    background: rgba(13, 11, 16, 0.7);
  }

  /* [hidden] bug (established elsewhere on this site — see .search-overlay/
     .newsletter-form/.countdown-flaps in this file): an authored `display`
     value beats the browser's own [hidden]{display:none} at equal
     specificity, so both overlay pieces need it spelled out explicitly. */
  .nav-backdrop[hidden] {
    display: none;
  }

  /* Takes over from `display: contents` (desktop, above) — a real box now,
     the fixed-position off-canvas drawer itself. No transform/filter on
     any ancestor between this and the viewport (checked: .header-inner,
     .site-header, .site-header--transparent, .container all carry none),
     so `position: fixed` here is safely viewport-relative regardless of
     where the header itself sits (`position: sticky` on every page but
     Home, `position: fixed` on Home — see the comment on
     `.home-page .site-header--transparent`). */
  .primary-nav-panel {
    display: flex;
    flex-direction: column;
    position: fixed;
    top: 0;
    right: 0;
    bottom: 0;
    width: min(320px, 85vw);
    z-index: 200;
    padding: 5rem 1.5rem 2rem;
    overflow-y: auto;
    background: var(--navy);
    box-shadow: -20px 0 50px -20px rgba(0, 0, 0, 0.6);
  }

  .primary-nav-panel[hidden] {
    display: none;
  }

  #primary-nav {
    /* Resets the desktop-only auto margin (above) — that trick pushes a
       flex ITEM to the end of a ROW; inside the drawer's column layout it
       would instead right-align/shrink-wrap nav's own box, which isn't
       wanted here. */
    margin-left: 0;
  }

  #primary-nav ul {
    flex-direction: column;
    align-items: stretch;
    flex-wrap: wrap;
    gap: 0.25rem;
  }

  #primary-nav a {
    display: flex;
    align-items: center;
    min-height: 44px;
    padding-block: 0.6rem;
    font-size: 1rem;
  }

  #primary-nav a:not(.nav-cta)::after {
    /* The underline-on-hover/current treatment reads fine inline on a
       desktop text link; stacked full-width in a drawer it would just be
       a stray short line under the first few characters. The color
       change alone (already unconditional, not part of this rule) still
       carries hover/current here. */
    display: none;
  }

  /* Solid, high-contrast pill — the transparent header's own .nav-cta
     treatment (translucent + backdrop-blur, styled to sit over a busy
     hero photo) reads muddy against this drawer's solid navy panel, so
     this reverts to the base (non-transparent-header) .nav-cta look:
     solid gold, dark text, no blur. */
  .primary-nav-panel .nav-cta {
    justify-content: center;
    margin-top: 0.75rem;
    background: var(--gold) !important;
    color: var(--navy) !important;
    border: none !important;
    backdrop-filter: none !important;
  }

  .primary-nav-panel .nav-cta:hover,
  .primary-nav-panel .nav-cta:focus-visible {
    background: #e0a800 !important;
  }

  /* Search trigger (js/script.js inserts it as .primary-nav-panel's first
     child): same full-width tappable-row treatment as a nav link, plain
     rather than the transparent header's own floating pill/blur chip —
     same "solid panel, not a floating strip over a photo" reasoning as
     the CTA above. */
  .primary-nav-panel .search-trigger {
    order: -1;
    display: flex;
    align-items: center;
    gap: 0.6rem;
    width: 100%;
    min-height: 44px;
    padding: 0.6rem 0;
    margin-bottom: 0.5rem;
    border: none;
    border-bottom: 1px solid var(--navy-line);
    border-radius: 0;
    background: none !important;
    backdrop-filter: none !important;
    color: var(--white);
    font-size: 1rem;
  }

  .primary-nav-panel .search-trigger svg {
    width: 20px;
    height: 20px;
    flex-shrink: 0;
  }

  .primary-nav-panel .search-trigger-hint {
    border: none !important;
  }
}

/* --------------------------------------------------------------------------
   Site-wide transparent/magenta header (Claude Design export integration,
   originally built for Home only — extended site-wide per explicit
   follow-up: side-by-side screenshots of Home's nav vs. every other
   page's, asking for every page to match Home's look). `.site-header--
   transparent` is now added to every page's <header>, so every rule in
   this block applies everywhere. The `position: fixed` behavior stays
   scoped under `.home-page .site-header--transparent` further below,
   specifically — see that rule's own comment for why: it's not part of
   "matching the look," it's a structural fix for Home's full-bleed hero
   photo specifically, which no other page has.
   -------------------------------------------------------------------------- */

.site-header--transparent {
  background: transparent;
  border-bottom: none;
}

/* Home-only: takes the header out of document flow entirely so its
   full-bleed hero photo can genuinely extend behind it (see the fuller
   history in this project's commits — "black gap above the photo" was
   only ever fixed by this, not by color/behavior patches). Every other
   page keeps the header `position: sticky` (the base .site-header rule,
   unmodified) — they have no hero photo to extend behind it, so there's
   nothing this would fix there, and it would need the same per-page
   top-offset compensation Home needed (--home-header-h below) that isn't
   worth adding just to floaty a header over plain hero sections. */
.home-page .site-header--transparent {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
}

/* Fixes a real, always-visible (not just overscroll) seam: <body>'s own
   background is --charcoal (a lighter gray) site-wide, but .pin-sequence
   sets its own, slightly darker --charcoal-deep. On every other page this
   never matters since .site-header has a solid navy fill of its own. Here
   the header is transparent, so its own ~72px-tall strip showed --charcoal
   (the body color) sitting directly above .pin-sequence's --charcoal-deep
   — two similar-but-different dark grays meeting read as a thin border/
   box line under the nav. Matching body's background to --charcoal-deep,
   scoped to the Home page only (`.home-page`, index.html's own <body>
   class), removes that seam — every other page's body keeps --charcoal
   unchanged. */
.home-page {
  background: var(--charcoal-deep);
  /* The transparent header's own measured height — .site-header--
     transparent is now position: fixed (see its own comment), so
     .hero-inner/.hero-corner-tag use this to compensate their top
     offsets and avoid sitting underneath where it floats. Defined here
     (not on the header itself) because the header and the hero are
     siblings, not ancestor/descendant — a custom property needs a
     shared ancestor to inherit through, and .home-page (this <body>
     class) is the nearest one both actually sit inside. */
  --home-header-h: 4.625rem;
}

.site-header--transparent #primary-nav a:not(.nav-cta) {
  color: rgba(255, 255, 255, 0.85);
}

.site-header--transparent #primary-nav a:not(.nav-cta)::after {
  background: var(--magenta);
}

.site-header--transparent #primary-nav a:not(.nav-cta):hover,
.site-header--transparent #primary-nav a:not(.nav-cta):focus-visible {
  color: var(--magenta);
}

.site-header--transparent #primary-nav a[aria-current="page"] {
  color: var(--magenta);
}

/* Matches the export's own pill CTA (outlined, not filled) rather than the
   site's gold-filled .nav-cta — a filled gold pill would clash with the
   new magenta accent instead of complementing it. */
.site-header--transparent .nav-cta {
  background: rgba(10, 8, 12, 0.25);
  backdrop-filter: blur(6px);
  color: var(--white) !important;
  border: 1px solid rgba(255, 255, 255, 0.42);
  border-radius: 999px;
  padding: 0.6rem 1.3rem;
}

.site-header--transparent .nav-cta:hover,
.site-header--transparent .nav-cta:focus-visible {
  background: rgba(236, 32, 136, 0.16);
  border-color: var(--magenta);
}

/* The search trigger (inserted by initSiteSearch, js/script.js, on every
   page identically) gets a pill/backdrop-blur treatment here so it reads
   clearly against a busy photo instead of the plain icon+text it uses on
   every other page's solid navy header. */
.site-header--transparent .search-trigger {
  padding: 0.5rem 0.9rem;
  border-radius: 999px;
  border: 1px solid rgba(255, 255, 255, 0.22);
  background: rgba(10, 8, 12, 0.3);
  backdrop-filter: blur(6px);
}

.site-header--transparent .search-trigger:hover,
.site-header--transparent .search-trigger:focus-visible {
  color: var(--magenta);
  border-color: rgba(236, 32, 136, 0.6);
}

/* Every other page keeps the "Search" hint's pill border (base rule,
   below) — removed here only, per feedback that it read as a stray box
   against the photo. */
.site-header--transparent .search-trigger-hint {
  border: none;
}

/* --------------------------------------------------------------------------
   Pinned scroll sequence — Hero / Explainer / Impact.

   Baseline (this CSS alone, no JS): `.pin-bg` is `position: sticky`, so the
   full-bleed motif naturally stays put behind the three `.scene` sections as
   they stack and scroll past it in normal document flow. Nothing here is
   animated and nothing hijacks scroll — this is the complete, intentional
   experience for reduced-motion, touch/mobile, and no-JS visitors.

   js/script.js (initPinSequence) progressively enhances this: only when the
   viewport is wide, the pointer is fine, GSAP + ScrollTrigger loaded, and
   the user has no reduced-motion preference, it adds `.pin-active` to
   `.pin-sequence`, which is what switches the scenes to absolute stacking
   for a true pinned, scroll-scrubbed cross-fade. The extra
   `(min-width: 900px)` / `no-preference` guards below are a second,
   CSS-level safety net — even a JS bug can't turn on the pinned layout
   somewhere it shouldn't.
   -------------------------------------------------------------------------- */

.pin-sequence {
  position: relative;
  background: var(--charcoal-deep);
}

.pin-stage {
  position: relative;
}

/* Fallback (default): the illustrated motif sits full-bleed behind the Hero
   scene only — it's absolutely positioned to exactly that first 100vh, not
   trying to persist behind the normally-stacked scenes below it. Explainer
   and Impact get their own lighter texture instead (below). `meet` (not
   `slice`) so the motif never has to crop/zoom to cover odd aspect ratios —
   important on tall narrow phones. */
.pin-bg {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 100vh;
  z-index: 0;
  overflow: hidden;
}

/* Fade-to-black bridge into Top Teams — see the Stage 2g comment in the
   pin-active media block below. Inert unless pin-active. */
.pin-exit-fade {
  display: none;
}

.pin-bg svg {
  width: 100%;
  height: 100%;
  display: block;
}

.pinbg-node {
  transform-box: fill-box;
  transform-origin: center;
}

.scene {
  position: relative;
  z-index: 1;
  min-height: 100vh;
  display: flex;
  align-items: center;
  padding-block: 5rem;
  overflow: hidden;
}

.scene-explainer::before,
.scene-impact::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background-image:
    radial-gradient(700px circle at 80% 15%, rgba(236, 32, 136, 0.16), transparent 60%),
    var(--bg-network);
  background-repeat: no-repeat, repeat;
  background-size: auto, 240px 240px;
  opacity: 0.5;
}

.scene-impact::before {
  background-position: 20% 85%, 0 0;
}

.scene-scrim {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background: linear-gradient(180deg, rgba(13, 11, 16, 0.55) 0%, rgba(13, 11, 16, 0.8) 100%);
}

.scene-inner {
  position: relative;
  z-index: 1;
  width: 100%;
}

@media (prefers-reduced-motion: no-preference) and (min-width: 900px) {
  .pin-active .pin-stage {
    height: 100vh;
    overflow: hidden;
  }

  .pin-active .pin-bg {
    position: absolute;
    inset: 0;
    height: auto;
  }

  .pin-active .scene {
    position: absolute;
    inset: 0;
    min-height: 0;
  }

  /* .scene-impact overrides the shared .scene layout to stack its two flow
     children (gallery, then stats) for the fallback case — see the comment
     by that rule. Once pin-active takes over, the gallery leaves the flex
     flow entirely (position: absolute below) and .scene-inner is once again
     the only flex item, so restore the same row/centered layout every other
     scene uses — otherwise the stats sit pinned to the top instead of
     vertically centered like before this gallery was added. */
  .pin-active .scene-impact {
    flex-direction: row;
    align-items: center;
  }

  /* The Impact gallery switches from a static grid/carousel to a full-bleed
     cross-fading backdrop only in this same enhanced, wide/fine-pointer/
     motion-ok context — see initImpactGallery in js/script.js. */
  .pin-active .scene-impact-media {
    position: absolute;
    inset: 0;
    margin: 0;
  }

  .pin-active .impact-gallery {
    display: block;
    position: absolute;
    inset: 0;
  }

  .pin-active .impact-gallery-img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    aspect-ratio: auto;
    border-radius: 0;
    opacity: 0;
  }

  .pin-active .impact-gallery-tint {
    display: block;
    position: absolute;
    inset: 0;
    background: linear-gradient(160deg, rgba(23, 20, 27, 0.35), rgba(236, 32, 136, 0.2));
  }

  .pin-active .impact-gallery-badge {
    position: absolute;
    left: var(--gutter);
    bottom: 1.25rem;
    z-index: 1;
    margin: 0;
    max-width: 26ch;
    background: rgba(13, 11, 16, 0.85);
    padding: 0.35rem 0.65rem;
    border-radius: var(--radius);
    border: 1px solid var(--line);
  }

  /* Top Teams — tier 3 only: pinned, phased sequence (title -> 3rd -> 2nd ->
     1st + confetti -> release). initTopTeamsPinSequence in js/script.js adds
     `.tt-pin-active` only when motion is fine, the pointer is fine, and the
     viewport is wide (mirroring the gating above). Appended into this same
     media block, rather than a second block with an identical condition,
     because a second `@media (prefers-reduced-motion: no-preference) and
     (min-width: 900px)` block elsewhere in this file was silently dropped
     by the browser at parse time (confirmed: the exact same rule text
     parses fine injected standalone, and fetch() confirms the served bytes
     are correct — the CSSOM built from the <link>-loaded stylesheet was
     just missing that second occurrence, verified by walking
     document.styleSheets). Keeping one media block per unique condition
     sidesteps whatever that is. */
  /* The base .top-teams rule carries padding-block for tiers 1/2's normal-
     flow layout (title sits above the podium with breathing room). In tier
     3, .top-teams-pin-stage is its own full 100vh box — but that box lives
     INSIDE the section's padding, so without zeroing the padding here the
     section's true height becomes 100vh + padding, leaving a band top and
     bottom where the section's own ::before texture shows through
     uncovered by .tt-pin-entry-fade/.tt-pin-exit-fade (which only cover the
     pin-stage's own box, not the section around it). Zeroing it makes the
     pin-stage flush with the section's real edges so the fade overlays
     actually cover the whole thing. */
  .top-teams.tt-pin-active {
    padding-block: 0;
  }

  .tt-pin-active .top-teams-pin-stage {
    height: 100vh;
    overflow: hidden;
  }

  .tt-pin-active .top-teams-pin-bg {
    display: block;
    position: absolute;
    inset: 0;
    z-index: 0;
  }

  .tt-pin-active .top-teams-title-scrim {
    display: block;
    position: absolute;
    inset: 0;
    z-index: 0;
    pointer-events: none;
    background: linear-gradient(180deg, rgba(13, 11, 16, 0.55) 0%, rgba(13, 11, 16, 0.8) 100%);
  }

  .tt-pin-active .top-teams-title {
    position: absolute;
    inset: 0;
    z-index: 1;
    max-width: none;
    margin: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
  }

  .tt-pin-active .top-teams-title h2 {
    font-size: clamp(2rem, 5vw, 3.2rem);
  }

  .tt-pin-active .top-teams-title p {
    max-width: 40ch;
  }

  .tt-pin-active .podium {
    position: absolute;
    inset: 0;
    z-index: 1;
    height: 100%;
    align-content: center;
  }

  /* Cross-section fade bridges (Stage 2g). Two independently-pinned
     sections can't literally overlap/blend into each other — each has its
     own hard pin-release boundary — so the smooth-handoff illusion is built
     from a matching pair of solid-color overlays instead: the outgoing
     section fades to solid charcoal-deep at the tail of its own timeline,
     the incoming section STARTS at that same solid color and fades it away
     at the head of its own timeline. Because both sides land on the exact
     same color, the hard cut in between is imperceptible — it reads as one
     continuous fade-to-black-and-back rather than a jump. Appended into
     this shared media block rather than a new one — see the Top Teams
     comment above this block for why a second identical @media condition
     gets silently dropped in this environment. */
  .pin-active .pin-exit-fade,
  .tt-pin-active .tt-pin-entry-fade,
  .tt-pin-active .tt-pin-exit-fade {
    display: block;
    position: absolute;
    inset: 0;
    z-index: 2;
    background: var(--charcoal-deep);
    pointer-events: none;
  }
}

/* Hero scene copy (Claude Design export integration) — Archivo is used
   ONLY for this content block (`.hero-inner` and its descendants), not the
   shared `<header>` nav, which stays the site's Space Grotesk (--font-head)
   on Home exactly as on every other page — a deliberate scoping choice so
   the nav's typography stays consistent site-wide while the hero content
   itself carries the confirmed design's distinct look. */

.hero-inner {
  font-family: "Archivo", "Helvetica Neue", Helvetica, sans-serif;
  max-width: min(48rem, 64%);
  padding-inline: var(--gutter);
  /* The top value adds --home-header-h on top of the clamp() range below
     (reduced from the original clamp(7.5rem, 16vh, 11.25rem) per
     feedback, to sit higher/closer to the header) — see the comment on
     .site-header--transparent: the header is `position: fixed` (no
     longer reserving its own document-flow space), so this still
     compensates for that, just against a smaller base offset now. */
  padding-block: calc(clamp(5rem, 12vh, 8rem) + var(--home-header-h, 0px)) clamp(6rem, 14vh, 8.25rem);
}

/* Official Hult Prize logo lockup sitting above the eyebrow label — the
   mark itself is untouched (no recoloring/redrawing), but
   assets/hero/hult-prize-logo.png is now cropped to trim the source
   file's own baked-in black padding around the mark (the original PNG
   had noticeably more empty margin above/below the mark than left/
   right of it, so at any given width that extra margin was rendering
   as extra, unwanted height) — per explicit follow-up feedback with a
   reference showing a flatter, more compact proportion than the
   untrimmed original gave. Sized off height with width:auto so the
   (now-cropped) source image's own aspect ratio (1356x806) is
   preserved at any viewport width, still landing close to the eyebrow
   text's own rendered width (measured via a Range on the live text,
   not the eyebrow element's own box — that's a full-width block, not
   the text's actual width) — the two read as one aligned lockup. */
.hero-logo {
  display: block;
  height: clamp(4.5rem, 14.5vh, 8rem);
  width: auto;
  margin: 0 0 clamp(1rem, 3vh, 1.75rem);
}

.hero-eyebrow {
  margin: 0 0 clamp(0.9rem, 2vh, 1.4rem);
  font-size: clamp(0.7rem, 1vw, 0.8rem);
  font-weight: 600;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.72);
}

.scene-hero h1 {
  font-size: clamp(2.1rem, 4.6vw, 4.25rem);
  font-weight: 900;
  line-height: 1.03;
  letter-spacing: -0.028em;
  color: var(--white);
  max-width: 18ch;
  text-shadow: 0 4px 40px rgba(0, 0, 0, 0.6);
}

.hero-lede {
  font-size: clamp(0.95rem, 1.25vw, 1.125rem);
  line-height: 1.62;
  max-width: 52ch;
  color: var(--text-secondary);
}

.hero-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.875rem;
  margin-top: clamp(1.5rem, 3.6vh, 2.375rem);
}

/* Hero-scoped button refinements — the underlying .btn-primary/.btn-ghost
   classes (css above) are unchanged, so every other page still using them
   (CTA band, blog subscribe, Get Involved sign-up) looks exactly as
   before. .btn-ghost happens to only ever be used in this hero anyway,
   but is still scoped here rather than edited directly, for the same
   reason: keep the shared class's own rule a stable, minimal-diff base. */
.scene-hero .btn-primary,
.scene-hero .btn-ghost {
  border-radius: 6px;
  padding: 1rem 1.85rem;
  font-size: 0.9375rem;
}

.scene-hero .btn-primary {
  box-shadow: 0 12px 40px rgba(236, 32, 136, 0.35);
}

.scene-hero .btn-primary:hover,
.scene-hero .btn-primary:focus-visible {
  background: #ff3d9c;
  box-shadow: 0 14px 46px rgba(236, 32, 136, 0.5);
}

.scene-hero .btn-ghost {
  backdrop-filter: blur(6px);
  background: rgba(10, 8, 12, 0.22);
}

.scene-hero .btn-ghost:hover,
.scene-hero .btn-ghost:focus-visible {
  background: rgba(255, 255, 255, 0.1);
  color: var(--white);
}

/* --------------------------------------------------------------------------
   Hero background photo slot + legibility overlay + corner tagline +
   scroll affordance (Claude Design export integration).
   -------------------------------------------------------------------------- */

.hero-photo-bg {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
}

/* The "photo placeholder" graphic that used to live here (a labeled
   stand-in for the export's `<image-slot>` element, shown before a real
   photo existed) has been removed entirely now that a real photo is
   permanently in place — .hero-photo-img below carries it as a static src
   directly in the HTML, so there's no longer a scenario where this slot
   would ever need a stand-in. (It previously also had a real, confirmed
   [hidden]-vs-CSS-display bug of its own — see .newsletter-form/
   .countdown-flaps/.search-overlay for that same pattern elsewhere on the
   site — but removing the element outright makes that moot rather than
   needing its own fix here.) */
.hero-photo-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Nudges the visible crop window rightward across the source photo,
     which shifts its content (the tower) left within the frame —
     requested after the real photo went in, so it reads less like it's
     crowding the corner tagline. */
  object-position: 72% center;
}

/* The legibility treatment from the export: a strong dark gradient over
   the left ~2/3 (where the headline/subtext sit), a lighter dark diagonal
   wash top-left-to-mid, and a soft top-edge darken so the transparent nav
   (.site-header--transparent, above) stays readable over any photo. */
.hero-photo-overlay {
  position: absolute;
  inset: 0;
  background:
    linear-gradient(90deg, rgba(6, 5, 8, 0.97) 0%, rgba(6, 5, 8, 0.94) 26%, rgba(6, 5, 8, 0.72) 44%, rgba(6, 5, 8, 0.28) 60%, rgba(6, 5, 8, 0) 74%),
    linear-gradient(200deg, rgba(6, 5, 8, 0.62) 0%, rgba(6, 5, 8, 0.22) 26%, rgba(6, 5, 8, 0) 46%),
    linear-gradient(0deg, rgba(6, 5, 8, 0.78) 0%, rgba(6, 5, 8, 0.18) 22%, rgba(6, 5, 8, 0) 42%);
}

.hero-corner-tag {
  position: absolute;
  /* Same --home-header-h compensation as .hero-inner above — the header
     now floats over .scene-hero (position: fixed) instead of sitting
     above it in flow, so this needs the extra offset to actually clear
     it rather than sit underneath/behind it. */
  top: calc(clamp(1rem, 3vh, 1.75rem) + var(--home-header-h, 0px));
  right: var(--gutter);
  z-index: 2;
  margin: 0;
  max-width: min(38vw, 18ch);
  font-family: "Caveat", cursive;
  font-weight: 700;
  font-size: clamp(1.5rem, 2.4vw, 2.1rem);
  line-height: 1.05;
  color: var(--white);
  text-align: right;
  text-shadow: 0 2px 18px rgba(0, 0, 0, 0.75);
}

.hero-corner-tag-underline {
  display: block;
  margin: 0.6rem 0 0 auto;
  width: clamp(4.4rem, 10vw, 8rem);
  height: 3px;
  border-radius: 2px;
  background: linear-gradient(90deg, rgba(236, 32, 136, 0), var(--magenta));
  box-shadow: 0 0 16px rgba(236, 32, 136, 0.8);
}

.hero-scroll-affordance {
  position: absolute;
  left: var(--gutter);
  bottom: clamp(1.75rem, 5vh, 2.875rem);
  z-index: 2;
  display: flex;
  align-items: center;
  gap: 1rem;
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
  color: var(--white);
  font-family: "Archivo", "Helvetica Neue", Helvetica, sans-serif;
}

.hero-scroll-circle {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 3.375rem;
  height: 3.375rem;
  border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, 0.5);
  flex-shrink: 0;
}

@media (prefers-reduced-motion: no-preference) {
  .hero-scroll-circle {
    transition: background 0.2s ease, border-color 0.2s ease;
  }
}

.hero-scroll-affordance:hover .hero-scroll-circle,
.hero-scroll-affordance:focus-visible .hero-scroll-circle {
  border-color: var(--magenta);
  background: rgba(236, 32, 136, 0.18);
}

.hero-scroll-label {
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.75);
}

/* Narrower viewports: the photo scales to any aspect ratio (inset: 0 +
   object-fit, above) so nothing extra is needed for that — this re-balances
   the decorative pieces so they don't collide with the headline, AND (per
   explicit director feedback) drops the full-image dark overlay entirely on
   mobile so the photo itself shows unshaded, replacing it with targeted
   per-element legibility treatments below instead. Desktop's `.hero-photo-
   overlay` rule above this media query is untouched — this only overrides it
   inside the query, at this same breakpoint every other mobile-only hero
   rule already uses. */
@media (max-width: 640px) {
  .hero-inner {
    /* The 64% desktop cap (above) exists to leave room for the photo on
       the right of the text — moot on a single-column phone layout, so
       let the headline use the full available width instead of wrapping
       into unnecessarily many short lines. */
    max-width: 100%;
    padding-block: calc(clamp(4.5rem, 10vh, 6.5rem) + var(--home-header-h, 0px)) clamp(5.5rem, 12vh, 7rem);
  }

  /* The overlay's job (nav + text legibility over the photo) is taken over
     by the per-element text-shadow/backdrop treatments below, so this drops
     out entirely rather than just fading — `background: none` (not
     `display: none`) keeps the element itself inert without touching
     anything else that depends on `.hero-photo-bg`'s stacking. */
  .hero-photo-overlay {
    background: none;
  }

  /* Nav wordmark + hamburger: only need this on Home, where the header is
     `.home-page .site-header--transparent` (position: fixed, no fill of its
     own) sitting directly over the hero photo — every other page's header
     keeps its solid navy background and never needed the overlay to begin
     with, so this stays scoped to `.home-page` rather than touching the
     shared header component everywhere. text-shadow doesn't render on SVG
     strokes, so the hamburger icon (an inline <svg>, currentColor strokes)
     needs `filter: drop-shadow(...)` instead of text-shadow. */
  .home-page .wordmark {
    text-shadow: 0 1px 3px rgba(0, 0, 0, 0.95), 0 3px 12px rgba(0, 0, 0, 0.75);
  }

  .home-page .nav-toggle {
    filter: drop-shadow(0 1px 3px rgba(0, 0, 0, 0.95)) drop-shadow(0 2px 8px rgba(0, 0, 0, 0.7));
  }

  /* Eyebrow + headline + lede + corner tag: layered text-shadow (a tight
     near shadow for edge definition, plus a soft far glow) tuned to hold up
     against BOTH the photo's darker upper-left and its brightest sunset-glow
     region on the right (where the corner tag and lede currently sit) —
     verified via measured contrast, see the commit message. The h1 and
     corner tag already carry a text-shadow above (shared with desktop); this
     strengthens it for mobile only, since the overlay that shadow used to
     sit on top of no longer exists here. */
  /* Eyebrow + lede were muted (rgba white 0.72 / var(--text-secondary) gray)
     to sit quietly on top of the old full-image overlay — with that overlay
     gone, the muted color reads as low-contrast gray directly against the
     photo, so both go to the same solid white as the headline on mobile
     only (desktop keeps its muted colors, base rules above untouched). */
  .hero-eyebrow {
    color: var(--white);
    text-shadow: 0 1px 3px rgba(0, 0, 0, 0.95), 0 2px 10px rgba(0, 0, 0, 0.8);
  }

  .scene-hero h1 {
    text-shadow: 0 2px 4px rgba(0, 0, 0, 0.95), 0 4px 40px rgba(0, 0, 0, 0.75);
  }

  .hero-lede {
    color: var(--white);
    text-shadow: 0 1px 3px rgba(0, 0, 0, 0.95), 0 2px 12px rgba(0, 0, 0, 0.8);
  }

  .hero-corner-tag {
    max-width: 9ch;
    font-size: 1.3rem;
    text-shadow: 0 1px 3px rgba(0, 0, 0, 0.95), 0 2px 14px rgba(0, 0, 0, 0.8);
  }

  /* Outlined Instagram button: transparent background + white border/text,
     so it's the one element that loses the most contrast without the
     overlay. Strengthens the existing (desktop-shared) backdrop-blur +
     dark-tint treatment scoped tightly to just this button's own box,
     rather than darkening the surrounding photo. */
  .scene-hero .btn-ghost {
    background: rgba(8, 6, 10, 0.48);
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
    border-color: rgba(255, 255, 255, 0.9);
    box-shadow: 0 2px 16px rgba(0, 0, 0, 0.4);
  }

  .hero-scroll-affordance {
    left: clamp(1rem, 4vw, var(--gutter));
  }

  /* Same muted-white-on-old-overlay issue as the eyebrow/lede above — goes
     solid white on mobile, plus a shadow (it had none before, since the
     overlay used to do this job) to hold contrast against the photo. */
  .hero-scroll-label {
    color: var(--white);
    text-shadow: 0 1px 3px rgba(0, 0, 0, 0.95), 0 2px 10px rgba(0, 0, 0, 0.8);
  }
}

/* Headline cascade: each word is masked by an overflow-hidden wrapper so it
   can rise into place individually. The --i custom property inline on each
   .reveal-word span is legacy from when this was a plain CSS keyframe
   animation (kept on the markup/renderHeroHeadline harmlessly — nothing
   reads it anymore) — it's now driven by initHeroEntrance's GSAP timeline
   (js/script.js), which measures each word's actual rendered LINE (not just
   its index) so the cascade steps line-by-line rather than word-by-word.
   Moved off plain CSS for the same reason every other page's hero entrance
   already is (About/Blog/Gallery/etc.'s own .xx-reveal-word/-inner have no
   accompanying @keyframes block either): it's one element in a single
   coordinated timeline alongside the nav/background/corner-tag/etc., not an
   independent animation. Reduced-motion fallback is the same as those other
   pages too — initHeroEntrance no-ops entirely under prefers-reduced-motion,
   so with no JS ever touching these, they just render at their natural,
   fully-visible default (no transform/opacity applied by CSS here). */
.reveal-word {
  display: inline-block;
  overflow: hidden;
  vertical-align: top;
}

.reveal-word-inner {
  display: inline-block;
}

/* Explainer scene copy */

.scene-explainer h2 {
  font-size: clamp(1.8rem, 4vw, 2.6rem);
  color: var(--white);
  max-width: 16ch;
}

.scene-body {
  color: var(--text-secondary);
  font-size: 1.15rem;
  max-width: 62ch;
}

/* Impact photo gallery.

   Default (fallback — reduced motion, mobile/touch, narrow viewport, or a
   GSAP/ScrollTrigger CDN failure): a plain static grid of thumbnails placed
   above the stats, in normal document flow. Nothing here is scroll-linked or
   animated. On touch/coarse-pointer devices specifically, the grid becomes a
   swipeable scroll-snap carousel instead — simpler and nicer on a phone than
   either a static grid or (per the brief) a forced scroll-jacked sequence.

   Enhanced (.pin-active, added by initImpactGallery in js/script.js only
   alongside the pinned sequence itself): the same 8 images switch to a
   full-bleed absolute stack behind the stats and cross-fade with a scroll-
   scrubbed Ken Burns pan/zoom — see the (min-width:900px) block at the
   bottom of this section. */

/* .scene's flex row was designed around a single content child. Impact now
   has two (the gallery, then the stats) in fallback mode, so it needs to
   stack them vertically instead of splitting the row between them. This is
   moot once pin-active takes over, since both become position: absolute and
   leave the flex flow entirely. */
.scene-impact {
  flex-direction: column;
  align-items: stretch;
}

.scene-impact-media {
  margin-bottom: 2.5rem;
}

/* Fixes a real bug, not an opacity/animation one: .scene-scrim (base rule
   above, shared with Explainer) is `position: absolute; inset: 0` relative
   to .scene-impact, sized to cover the whole section. In this fallback
   layout .scene-impact-media sits beside it as a plain in-flow, NON-
   positioned sibling — and per CSS stacking rules, a non-positioned block
   always paints BELOW any positioned sibling, regardless of DOM order. So
   the scrim's dark gradient (rgba(13, 11, 16, 0.55–0.8)) was painting
   directly over the gallery photos, reading as permanently dimmed/semi-
   transparent no matter how far you scrolled — scrolling was never going to
   fix it, since nothing here is scroll-linked in this tier at all.
   .impact-gallery-img's own opacity was already 1 the whole time; the
   problem was purely this stacking order. Lifting the media to the same
   z-index tier as .scene-inner (the stats, which already had `z-index: 1`
   and so were never affected) fixes it without touching .scene-scrim
   itself, which still needs to darken things for Explainer's fallback text.
   Scoped to exclude `.pin-active` explicitly (rather than a width query)
   because the bug isn't width-specific — it's present in every fallback
   context this site already recognizes (touch, reduced motion, a GSAP CDN
   failure) — and this guarantees zero effect on desktop's pinned crossfade,
   which sets its own `position`/`z-index` for this exact element inside the
   higher-specificity `.pin-active .scene-impact-media` rule below. */
.pin-sequence:not(.pin-active) .scene-impact-media {
  position: relative;
  z-index: 1;
}

.impact-gallery {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 0.6rem;
}

.impact-gallery-img {
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  border-radius: var(--radius);
  filter: grayscale(0.15) saturate(0.9);
}

.impact-gallery-tint {
  display: none;
}

.impact-gallery-badge {
  margin: 0.75rem 0 0;
  font-size: 0.75rem;
  font-style: italic;
  color: var(--text-secondary);
}

/* Touch / coarse-pointer: a swipeable carousel instead of a static grid —
   the brief's suggested mobile fallback for a scroll-linked sequence. */
@media (hover: none), (pointer: coarse) {
  .impact-gallery {
    display: flex;
    gap: 0.75rem;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    -webkit-overflow-scrolling: touch;
    padding-bottom: 0.25rem;
  }

  .impact-gallery-img {
    flex: 0 0 74%;
    scroll-snap-align: center;
  }
}

/* Impact scene — one bold treatment, not repeated stat cards. Gold here is
   deliberate: these are UC Davis-chapter-specific numbers, so they carry the
   one accent thread tied to UC Davis identity, same as the header. */

.impact-grid {
  display: grid;
  gap: 2.5rem;
}

.impact-figure {
  max-width: 30ch;
}

.impact-number {
  font-family: var(--font-head);
  font-weight: 700;
  color: var(--gold);
  line-height: 0.9;
  margin: 0 0 0.6rem;
}

.impact-figure-primary .impact-number {
  font-size: clamp(4.5rem, 14vw, 9rem);
}

.impact-figure-secondary .impact-number {
  font-size: clamp(3rem, 9vw, 5.5rem);
}

.impact-label {
  font-size: 1.05rem;
  color: var(--text-secondary);
  margin: 0;
  max-width: 28ch;
}

@media (min-width: 800px) {
  .impact-grid {
    grid-template-columns: auto 1fr;
    align-items: end;
    column-gap: 4rem;
  }

  .impact-figure-secondary {
    justify-self: start;
    margin-left: 3rem;
    padding-left: 3rem;
    border-left: 1px solid var(--line);
  }
}

/* --------------------------------------------------------------------------
   Top Teams podium
   -------------------------------------------------------------------------- */

.top-teams {
  background: var(--charcoal);
  color: var(--white);
  padding-block: 4.5rem;
  position: relative;
  overflow: hidden;
}

.top-teams::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background-image:
    radial-gradient(650px circle at 15% 90%, rgba(236, 32, 136, 0.14), transparent 60%),
    var(--bg-network);
  background-repeat: no-repeat, repeat;
  background-size: auto, 240px 240px;
  opacity: 0.5;
}

.top-teams-pin-stage {
  position: relative;
  z-index: 1;
}

/* Base (tiers 1 & 2 — reduced motion, or the pinned tier disabled for any
   reason): title sits above the podium in plain normal flow, nothing
   pinned or absolutely positioned. This is the complete, correct rendering
   for those tiers on its own. */
.top-teams-title {
  max-width: 40rem;
  margin: 0 auto 3rem;
  text-align: center;
}

.top-teams-title h2 {
  font-size: clamp(1.5rem, 3vw, 2rem);
}

.top-teams-title p {
  color: var(--text-secondary);
  font-size: 1.05rem;
  margin: 0;
}

/* Full-bleed animated backdrop + scrim, used only by tier 3 (pin-active,
   below) for Phase 0. Inert and invisible otherwise. */
.top-teams-pin-bg {
  display: none;
}

/* Fade-to/from-black bridges at this section's own entry and exit — see the
   Stage 2g comment in the pin-active media block below. Inert unless
   pin-active. */
.tt-pin-entry-fade,
.tt-pin-exit-fade {
  display: none;
}

.top-teams-pin-bg svg {
  width: 100%;
  height: 100%;
  display: block;
}

.tt-pinbg-node {
  transform-box: fill-box;
  transform-origin: center;
}

.top-teams-title-scrim {
  display: none;
}

/* Podium grid. DOM order is placement order (1st, 2nd, 3rd) for a sensible
   reading/tab order; `order` rearranges the visual sequence to the classic
   2nd–1st–3rd podium on wide screens. The 1st-place column is wider, and
   2nd/3rd sit lower via margin-top, so the "results reveal" reads even
   before anyone gets to the badge text. */
.podium {
  display: grid;
  grid-template-columns: 1fr;
  gap: 2rem;
  max-width: 22rem;
  margin: 0 auto;
}

.team-card {
  position: relative;
  background: var(--charcoal-raised);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 1.5rem;
  text-align: center;
}

.team-card-photo {
  margin: 0 0 1rem;
}

.team-card-photo img {
  width: 100%;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  border-radius: var(--radius);
  filter: grayscale(0.1) saturate(0.9);
}

.team-card-badge {
  display: inline-block;
  font-family: var(--font-head);
  font-weight: 700;
  font-size: 0.8rem;
  letter-spacing: 0.02em;
  color: var(--charcoal-deep);
  padding: 0.3rem 0.85rem;
  border-radius: 999px;
  margin: 0 0 0.6rem;
}

.team-card-badge-gold {
  background: var(--gold);
}

.team-card-badge-silver {
  background: var(--silver);
}

.team-card-badge-bronze {
  background: var(--bronze);
}

.team-card-name {
  font-family: var(--font-head);
  font-size: 1.2rem;
  color: var(--white);
  margin: 0 0 0.25rem;
}

.team-card-year {
  font-size: 0.9rem;
  color: var(--text-secondary);
  margin: 0;
}

/* The winner card: a little larger, a warmer border, and an always-on
   static glow behind it (see the comment on .team-card-glow) so it reads
   as "the winner" at a glance, with or without motion/JS. */
.team-card-first {
  position: relative;
  z-index: 1;
  padding: 1.85rem 1.5rem;
  border-color: rgba(255, 191, 0, 0.4);
}

.team-card-first .team-card-name {
  font-size: 1.4rem;
}

.team-card-glow {
  position: absolute;
  z-index: -1;
  top: -18%;
  left: 50%;
  transform: translateX(-50%);
  width: 140%;
  height: 65%;
  /* Ellipse, not circle: this box is short and wide (140% x 65%), and a
     true circle sizes itself to reach the farthest CORNER — dominated by
     the wide dimension — so along the vertical axis the box's own
     top/bottom edge (where the background gets hard-clipped, as all CSS
     backgrounds are) falls well before the gradient has faded out,
     producing a visible flat cutoff instead of a soft glow. An ellipse
     scales independently per axis to the box's own aspect ratio, so it
     fades to transparent right at the edges in both directions. */
  background: radial-gradient(ellipse, rgba(255, 191, 0, 0.35), transparent 70%);
  pointer-events: none;
}

@media (min-width: 760px) {
  .podium {
    grid-template-columns: 1fr 1.2fr 1fr;
    align-items: end;
    gap: 1.5rem;
    max-width: 960px;
  }

  .team-card-first {
    order: 2;
  }

  .team-card-second {
    order: 1;
    margin-top: 3rem;
  }

  .team-card-third {
    order: 3;
    margin-top: 3rem;
  }

  /* About (Stage 8a) — appended here rather than a separate min-width:
     760px block, for the same duplicate-@media-condition reason
     documented at the top of that page's CSS section. Scoped with the
     parent .about-links section (rather than the bare .about-links-grid
     class used everywhere else) because this page's own base rule for
     that same selector is declared near the end of the file — AFTER this
     block — being the About section's own unconditional/mobile rule; at
     equal specificity the later declaration wins the cascade regardless
     of which one's media query currently matches, so without this the
     "always applies" single-column rule was confirmed (via
     getComputedStyle) to override this 3-column rule at every viewport
     width. The extra ancestor class raises specificity just enough to
     win regardless of source order — the same fix already used for
     Blog's newsletter archive and the Gallery lightbox's mobile
     override. */
  .about-links .about-links-grid {
    grid-template-columns: repeat(3, 1fr);
  }

  /* Get Involved (Stage 9a) — appended here rather than a separate
     min-width: 760px block, same reasoning and same ancestor-scoping fix
     as the About merge just above. */
  .involved-links .involved-links-grid {
    grid-template-columns: repeat(3, 1fr);
  }
}

/* --------------------------------------------------------------------------
   Our Story — normal document scroll, restyled into the dark/magenta system.
   -------------------------------------------------------------------------- */

.story-teaser {
  background: var(--charcoal);
  color: var(--white);
  padding-block: 4.5rem;
  position: relative;
  overflow: hidden;
}

.story-teaser::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background-image:
    radial-gradient(650px circle at 82% 8%, rgba(236, 32, 136, 0.16), transparent 62%),
    var(--bg-network);
  background-repeat: no-repeat, repeat;
  background-size: auto, 240px 240px;
  opacity: 0.5;
}

.story-content {
  position: relative;
  z-index: 1;
  max-width: 46rem;
}

.story-intro-row {
  display: flex;
  flex-direction: column-reverse;
  gap: 1.75rem;
  margin-bottom: 2.5rem;
}

.story-intro {
  margin: 0;
}

.story-intro h2 {
  font-size: clamp(1.5rem, 3vw, 2rem);
}

/* Stage 2g: renamed from the tag selector `.story-intro p` to a class
   selector on purpose — Our Story Body is now a markdown CMS field, and
   marked.parse() always wraps its output in its own <p>; a <p> can't
   legally contain a nested <p> (the browser would silently split them into
   two unstyled siblings instead), so the container itself became a <div
   class="story-intro-body"> (index.html) rather than a <p>. Same rule
   otherwise, unchanged. */
.story-intro-body {
  color: var(--text-secondary);
  font-size: 1.05rem;
  max-width: 50ch;
  margin: 0;
}

/* A supporting photo, not a second hero — smaller, asymmetric, tucked beside
   the intro rather than repeating the hero's full-bleed image/text split. */
.story-photo {
  margin: 0;
  max-width: 20rem;
}

.story-photo img {
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  border-radius: var(--radius);
  filter: grayscale(0.1) saturate(0.9);
}

.story-photo figcaption {
  margin-top: 0.5rem;
  font-size: 0.75rem;
  font-style: italic;
  color: var(--text-secondary);
}

@media (min-width: 700px) {
  .story-intro-row {
    flex-direction: row;
    align-items: flex-start;
    justify-content: space-between;
    gap: 2.5rem;
  }

  .story-photo {
    flex-shrink: 0;
    width: 15rem;
    transform: rotate(1.5deg);
  }

  /* Get Involved (Stage 9a) — appended here rather than a separate
     min-width: 700px block, for the same duplicate-@media-condition
     reason documented at the top of that page's CSS section. Scoped
     with the parent .involved-paths section (rather than the bare
     .involved-grid class) because this page's own base rule for that
     same selector is declared near the end of the file, after this
     block — same cascade-order fix as About's/Get Involved's own
     min-width: 760px merges elsewhere in this file. */
  .involved-paths .involved-grid {
    grid-template-columns: repeat(3, 1fr);
  }
}

/* Generic scroll-reveal used by the Story and CTA photos: fade + rise once,
   the first time each element scrolls into view. Base state is fully
   visible (no query) so reduced-motion users just see the final look with
   nothing to animate — see initScrollReveal in js/script.js. */
@media (prefers-reduced-motion: no-preference) {
  .reveal-on-scroll {
    opacity: 0;
    transform: translateY(24px);
    transition: opacity 0.7s cubic-bezier(0.16, 1, 0.3, 1), transform 0.7s cubic-bezier(0.16, 1, 0.3, 1);
  }

  .reveal-on-scroll.is-visible {
    opacity: 1;
    transform: translateY(0);
  }
}

.story-timeline {
  list-style: none;
  margin: 0 0 2.5rem;
  padding: 0;
}

.story-timeline-item {
  position: relative;
  padding: 0 0 2rem 2.25rem;
}

.story-timeline-item:last-child {
  padding-bottom: 0;
}

.story-timeline-item::before {
  content: "";
  position: absolute;
  left: 5px;
  top: 14px;
  bottom: -2px;
  width: 2px;
  background: var(--line);
}

.story-timeline-item:last-child::before {
  display: none;
}

.story-timeline-dot {
  position: absolute;
  left: 0;
  top: 6px;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: var(--gold);
}

.story-timeline-item h3 {
  font-family: var(--font-head);
  font-size: 1.15rem;
  color: var(--white);
  margin: 0 0 0.35rem;
}

.story-timeline-item p {
  color: var(--text-secondary);
  font-size: 1rem;
  max-width: 56ch;
  margin: 0;
}

/* --------------------------------------------------------------------------
   Get Involved CTA band
   -------------------------------------------------------------------------- */

.cta-band {
  background: var(--charcoal-deep);
  color: var(--white);
  padding-block: 4.5rem;
  position: relative;
  overflow: hidden;
}

/* Base rule doubles as the reduced-motion resting state: opacity 0.28 always,
   nothing to animate. Under normal motion, the higher-specificity rules in
   the reveal-on-scroll block below override this to start at 0 and fade
   in to this same 0.28 (not the generic reveal's opacity: 1 — this is a
   subtle background layer, not a foreground photo). */
.cta-photo {
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0.28;
  filter: grayscale(0.2) saturate(0.9);
}

@media (prefers-reduced-motion: no-preference) {
  .cta-photo.reveal-on-scroll {
    opacity: 0;
  }

  .cta-photo.reveal-on-scroll.is-visible {
    opacity: 0.28;
  }
}

.cta-band::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background-image:
    linear-gradient(180deg, rgba(13, 11, 16, 0.75) 0%, rgba(13, 11, 16, 0.92) 100%),
    radial-gradient(900px circle at 20% 30%, rgba(236, 32, 136, 0.16), transparent 55%),
    radial-gradient(700px circle at 85% 90%, rgba(236, 32, 136, 0.12), transparent 55%),
    var(--bg-network);
  background-repeat: no-repeat, no-repeat, no-repeat, repeat;
  background-size: auto, auto, auto, 240px 240px;
  opacity: 0.85;
}

.cta-shimmer {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
}

@media (prefers-reduced-motion: no-preference) {
  .cta-shimmer {
    background: linear-gradient(100deg, transparent 45%, rgba(236, 32, 136, 0.12) 50%, transparent 55%);
    background-size: 220% 220%;
    animation: shimmer-drift 16s linear infinite;
  }

  @keyframes shimmer-drift {
    0% {
      background-position: 0% 50%;
    }
    100% {
      background-position: 100% 50%;
    }
  }
}

.cta-inner {
  position: relative;
  z-index: 2;
  max-width: 56ch;
}

.cta-inner h2 {
  font-size: clamp(1.6rem, 3.4vw, 2.4rem);
}

/* Stage 2g: same rename-to-class-selector reasoning as .story-intro-body
   above — CTA Band Body is now a markdown CMS field, so its container
   became a <div class="cta-inner-body"> rather than a <p>. */
.cta-inner-body {
  color: var(--text-secondary);
  font-size: 1.05rem;
  margin-bottom: 1.75rem;
}

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

.site-footer {
  background: var(--charcoal-deep);
  color: var(--text-secondary);
  padding-block: 2.5rem;
  border-top: 1px solid var(--line);
}

.footer-inner {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

.footer-name {
  font-family: var(--font-head);
  font-weight: 700;
  color: var(--white);
  margin: 0;
}

.footer-name span {
  color: var(--gold);
}

.footer-links {
  display: flex;
  flex-wrap: wrap;
  gap: 1.25rem;
}

.footer-links a {
  text-decoration: none;
  color: var(--text-secondary);
}

.footer-links a:hover,
.footer-links a:focus-visible {
  color: var(--magenta);
}

/* Footer icon links (Email/Instagram/Discord) — replaces the old plain-
   text email address + "@handle" mention with three full-color brand-mark
   SVGs. Each <svg> already carries its own fixed brand fill (var(--sky)
   for email, a gradient for Instagram, Discord's blurple) rather than
   `currentColor`, since these are real brand colors, not theme text — so
   the `.footer-links a` color rule above has no visible effect on these
   specifically, it's only relevant to a future plain-text link sharing
   this same list. Each link is a 44px square tap target (comfortable on
   mobile per WCAG's target-size guidance) built from padding around a
   smaller 26px visual badge (set directly on the <svg> in the markup,
   viewBox 0 0 24 24), rather than making the visible badge itself 44px —
   that would read oversized next to the rest of the footer's small type. */
.footer-links-icons {
  gap: 0.25rem;
}

.footer-icon-link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border-radius: 12px;
}

/* Unconditional (not gated to prefers-reduced-motion or hover: hover) —
   this is the actual keyboard-focus indicator, same reasoning as every
   other :focus-visible state on the site (see the global a:focus-visible
   rule near the top of this file): it must always be available, and a
   static end-state scale with no transition is not itself motion. Only
   the tweened approach to that state (the transition below, and the
   :hover rule alongside the other hover:hover-gated cards further down)
   is gated. */
.footer-icon-link:focus-visible {
  transform: scale(1.08);
}

.footer-copyright {
  font-size: 0.85rem;
  margin: 0;
  color: var(--text-secondary);
  opacity: 0.7;
}

@media (prefers-reduced-motion: no-preference) {
  .footer-icon-link {
    transition: transform 0.2s ease, opacity 0.2s ease;
  }
}

/* .pitch-swiper .swiper-slide's base rule (Stage 4b) is pulled forward to
   sit here, immediately before the shared min-width: 700px block below
   that carries its override — @media adds conditionality, not specificity,
   so this same-specificity base rule must come earlier in the file for the
   override to actually win once its condition matches. See the fuller
   version of this comment further down (search "swiper-slide" there) for
   the concrete bug this avoids. */
.pitch-swiper .swiper-slide {
  width: min(340px, 78vw);
  height: auto;
}

@media (min-width: 700px) {
  .footer-inner {
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
  }

  /* Pitch Videos carousel (Stage 4b) — appended here rather than its own
     min-width: 700px block; a second block with an identical @media
     condition elsewhere in this file gets silently dropped by the
     browser's CSSOM parser for <link>-loaded stylesheets (confirmed
     during an earlier stage; see the comment on the Top Teams tier-3
     rules for the full story). Two min-width: 700px blocks already exist
     in this file, so a third was avoided by appending here instead. */
  .pitch-swiper .swiper-slide {
    width: 360px;
  }
}

/* --------------------------------------------------------------------------
   Custom cursor — added by JS only on fine-pointer, non-touch devices with
   no reduced-motion preference (see initCustomCursor in js/script.js).
   Without the .has-custom-cursor class (mobile, touch, reduced motion),
   none of this applies and the OS cursor is untouched.
   -------------------------------------------------------------------------- */

.has-custom-cursor,
.has-custom-cursor a,
.has-custom-cursor button {
  cursor: none;
}

.custom-cursor-dot,
.custom-cursor-ring {
  position: fixed;
  top: 0;
  left: 0;
  pointer-events: none;
  border-radius: 50%;
  z-index: 999;
  transform: translate(-100px, -100px);
  will-change: transform;
}

.custom-cursor-dot {
  width: 8px;
  height: 8px;
  margin: -4px 0 0 -4px;
  background: var(--magenta);
  transition: width 0.2s ease, height 0.2s ease, margin 0.2s ease;
}

.custom-cursor-ring {
  width: 34px;
  height: 34px;
  margin: -17px 0 0 -17px;
  border: 1.5px solid var(--magenta);
  opacity: 0.7;
  transition: width 0.25s ease, height 0.25s ease, margin 0.25s ease, opacity 0.25s ease;
}

.custom-cursor-dot.is-active {
  width: 10px;
  height: 10px;
  margin: -5px 0 0 -5px;
}

.custom-cursor-ring.is-active {
  width: 50px;
  height: 50px;
  margin: -25px 0 0 -25px;
  opacity: 1;
}

/* ==========================================================================
   Leadership / Team page (Stage 3a)

   A second full page, not a Home section — reuses the same header/footer
   and color system. Cards are plain, fully-visible, normal-flow content by
   default; js/script.js (initLeadershipReveal) progressively enhances the
   entrance into a GSAP ScrollTrigger stagger, and only when motion isn't
   reduced. Hover micro-interactions below are themselves gated to
   (hover: hover) and (pointer: fine) so touch devices get an always-on
   equivalent instead (see that block) rather than a hover that can never
   fire.
   ========================================================================== */

.leadership-page {
  position: relative;
  background: var(--charcoal);
  overflow: hidden;
}

/* Full-bleed textured backdrop behind the whole page — the same network
   motif + soft magenta glow used elsewhere (.top-teams::before,
   .story-teaser::before), not a new visual idiom. */
.leadership-page::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background-image:
    radial-gradient(750px circle at 85% 0%, rgba(236, 32, 136, 0.16), transparent 60%),
    var(--bg-network);
  background-repeat: no-repeat, repeat;
  background-size: auto, 240px 240px;
  opacity: 0.5;
}

.leadership-hero,
.leadership-grid-section {
  position: relative;
  z-index: 1;
}

.leadership-hero {
  padding-block: 5rem 2.5rem;
}

.leadership-hero h1 {
  font-size: clamp(2rem, 5vw, 3rem);
  max-width: 20ch;
}

.leadership-hero-lede {
  color: var(--text-secondary);
  font-size: 1.15rem;
  max-width: 62ch;
  margin: 0;
}

.leadership-grid-section {
  padding-block: 1.5rem 5rem;
}

.leadership-placeholder-note {
  display: inline-block;
  font-family: var(--font-head);
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--text-secondary);
  background: var(--charcoal-raised);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 0.4rem 0.9rem;
  margin: 0 0 2.5rem;
}

/* --------------------------------------------------------------------------
   Grid — a 6-tile bento layout at desktop width rather than a uniform
   directory list. DOM order stays reading order (1 = Campus Director ... 6
   = Treasurer); grid-area assigns each card's position so tab/reading order
   never has to chase the visual layout.
   -------------------------------------------------------------------------- */

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

@media (min-width: 640px) {
  .leadership-grid {
    grid-template-columns: repeat(2, 1fr);
    gap: 1.5rem;
  }

  .leadership-card-featured {
    grid-column: 1 / -1;
  }

  /* At this tablet width the featured card spans the full row, but its
     photo is still square — full-width-and-square makes the card
     excessively tall here. Widen the crop until the desktop bento grid
     (below) narrows the card back down and squares the photo off again. */
  .leadership-card-featured .leadership-card-photo img {
    aspect-ratio: 2.4 / 1;
  }

  .leadership-card-wide {
    flex-direction: row;
  }

  .leadership-card-wide .leadership-card-photo {
    width: 40%;
    max-width: 200px;
    flex-shrink: 0;
  }

  .leadership-card-wide .leadership-card-photo img {
    height: 100%;
  }

  .leadership-card-wide .leadership-card-body {
    justify-content: center;
  }

  /* Blog & Newsletter (Stage 6a) — appended here rather than a separate
     min-width: 640px block, for the same duplicate-@media-condition
     reason documented at the top of that page's CSS section.
     grid-template-columns below is scoped with the parent section
     (.blog-grid-section .blog-grid / .newsletter-section .newsletter-
     archive) rather than the bare class: this file's own base (photo of
     no @media at all) .blog-grid/.newsletter-archive rule, which also
     sets grid-template-columns, is declared LATER in the file (appended
     near the end) than this block — at equal specificity the later rule
     wins the cascade regardless of which one is behind a query that
     currently matches, so the bare-class version here was confirmed (via
     getComputedStyle) to lose to the base rule's grid-template-columns: 1fr
     the moment the viewport was wide enough for both to apply. The extra
     ancestor class raises specificity just enough to win regardless of
     source order, the same fix this project has used before for a base
     rule that has to stay after an earlier @media block it's merged
     into. */
  .blog-grid-section .blog-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 1.5rem;
  }

  .blog-card-featured,
  .blog-card-banner {
    grid-column: 1 / -1;
  }

  .blog-card-banner {
    flex-direction: row;
  }

  .blog-card-banner .blog-card-thumb {
    width: 40%;
    max-width: 280px;
    flex-shrink: 0;
  }

  .blog-card-banner .blog-card-body {
    justify-content: center;
  }

  .newsletter-section .newsletter-archive {
    grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  }

  .newsletter-card-featured {
    grid-column: 1 / -1;
  }

  /* About (Stage 8a) — appended here rather than a separate min-width:
     640px block, same reasoning as the merges above. Scoped with the
     parent .about-values section for the same cascade-order reason
     explained in full on the min-width: 760px merge above (this page's
     own unconditional single-column rule is declared later in the file
     and would otherwise win at every width). */
  .about-values .about-values-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (min-width: 1000px) {
  .leadership-grid {
    grid-template-columns: repeat(4, 1fr);
    grid-template-areas:
      "a a b b"
      "a a c d"
      "e e f f";
    gap: 1.75rem;
  }

  .leadership-card:nth-child(1) { grid-area: a; }
  .leadership-card:nth-child(2) { grid-area: b; }
  .leadership-card:nth-child(3) { grid-area: c; }
  .leadership-card:nth-child(4) { grid-area: d; }
  .leadership-card:nth-child(5) { grid-area: e; }
  .leadership-card:nth-child(6) { grid-area: f; }

  /* Back to square: at this width the featured card is only half the grid
     (2 of 4 columns), so the tablet-only wide crop above would leave it too
     short next to the tall photo/text tile it actually is here. */
  .leadership-card-featured .leadership-card-photo img {
    aspect-ratio: 1 / 1;
  }

  /* Blog & Newsletter (Stage 6a): the featured tile's thumb only — the
     actual desktop bento GRID (grid-template-columns + each card's grid-
     column/grid-row) is applied by initBlogGridLayout (js/script.js)
     instead of a min-width: 1000px rule here. Confirmed via repeated
     getComputedStyle checks during this stage's testing that a stylesheet
     rule placing this same 2x2-spanning featured tile in a 4-column grid
     — tried as fr units, percentages, minmax(0, 1fr), both named
     grid-template-areas and explicit grid-column/grid-row lines — always
     collapsed the two columns the featured tile alone touches down to
     0px ("0px 0px 522px 522px" instead of four equal columns), even on a
     genuinely fresh reload of the changed CSS file (so not a stylesheet-
     edit-caching artifact). The identical placement applied as inline
     styles on the actual elements sized correctly every time, hence the
     JS-driven approach — see that function's own comment for the exact
     placements. */
  .blog-card-featured .blog-card-thumb {
    aspect-ratio: 4 / 3;
  }

  /* About (Stage 8a) — appended here rather than a separate min-width:
     1000px block, same reasoning as the merges above. Plain equal-width
     tracks with no spanning tiles involved, unlike Blog's/Gallery's bento
     grids, so none of the "collapses to 0px" risk documented in the
     comment just above applies here — only the ordinary cascade-order
     issue explained on the min-width: 760px merge does, hence the same
     .about-values ancestor scoping. */
  .about-values .about-values-grid {
    grid-template-columns: repeat(3, 1fr);
  }
}

/* --------------------------------------------------------------------------
   Card
   -------------------------------------------------------------------------- */

.leadership-card {
  position: relative;
  display: flex;
  flex-direction: column;
  background: var(--charcoal-raised);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
}

@media (prefers-reduced-motion: no-preference) {
  .leadership-card {
    transition: transform 0.3s ease, box-shadow 0.3s ease, border-color 0.3s ease;
  }
}

.leadership-card-photo {
  margin: 0;
  overflow: hidden;
}

.leadership-card-photo img {
  width: 100%;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  /* Full color on every device with no interaction required (chapter
     director's call) — the only sensible default for touch, where no hover
     exists. The card's hover lift/tilt/glow (fine pointer only, below) is
     the micro-interaction; the photo no longer participates. */
}

.leadership-card-body {
  padding: 1.25rem;
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  flex: 1;
}

.leadership-card-role {
  font-family: var(--font-head);
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--card-accent, var(--magenta));
  margin: 0;
}

.leadership-card-name {
  font-family: var(--font-head);
  font-size: 1.1rem;
  margin: 0;
}

.leadership-card-bio {
  font-size: 0.92rem;
  color: var(--text-secondary);
  margin: 0;
}

/* Stage 2a: the bio is now a <div> (initTeamContent, js/script.js), not a
   bare <p> — it holds marked.js's rendered markdown output, which can be
   more than one block-level element (multiple paragraphs), something a
   single <p> can't validly contain. The browser's own default <p> margin
   still applies to whatever's inside it though, so it's neutralized here
   the same way .leadership-card-bio itself always was; a link (new as of
   this stage — bios previously had no markdown, so no links) picks up the
   same --card-accent convention already used for the role label above it. */
.leadership-card-bio p {
  margin: 0;
}

.leadership-card-bio p + p {
  margin-top: 0.5em;
}

.leadership-card-bio a {
  color: var(--card-accent, var(--magenta));
}

.leadership-card-featured .leadership-card-role {
  font-size: 0.85rem;
}

.leadership-card-featured .leadership-card-name {
  font-size: 1.4rem;
}

.leadership-card-featured .leadership-card-bio {
  font-size: 1rem;
}

/* Accent color rotation — the same secondary palette used for the Top Teams
   placement badges (teal/sky/orange/gold), cycled across the 6 cards rather
   than introducing a new palette. Drives the role label color and the
   hover/touch glow below via --card-accent. */
.leadership-card-teal { --card-accent: var(--teal); }
.leadership-card-sky { --card-accent: var(--sky); }
.leadership-card-orange { --card-accent: var(--orange); }
.leadership-card-gold { --card-accent: var(--gold); }

/* --------------------------------------------------------------------------
   Hover micro-interactions (fine pointer only) and their touch equivalent.
   -------------------------------------------------------------------------- */

@media (hover: hover) and (pointer: fine) {
  .leadership-card:hover {
    border-color: var(--card-accent, var(--magenta));
    box-shadow: 0 18px 36px -20px var(--card-accent, var(--magenta));
  }

  /* Alternating tilt direction reads as more "designed" than a uniform lift
     across every card. */
  .leadership-card:nth-child(odd):hover {
    transform: translateY(-6px) rotate(-0.5deg);
  }

  .leadership-card:nth-child(even):hover {
    transform: translateY(-6px) rotate(0.5deg);
  }

  /* Event Timeline (Stage 5a) — appended here rather than a separate
     (hover: hover) and (pointer: fine) block, for the same duplicate-
     @media-condition reason documented at the top of that page's CSS
     section. Deliberately only ever sets box-shadow/transform here, never
     border-color or anything else .timeline-node/.timeline-card's own base
     rules already set — that's what keeps this merge safe regardless of
     source order (a same-specificity override only needs to come after its
     base rule when they fight over the SAME property; these never do). */
  .timeline-event:hover .timeline-node {
    transform: translate(-50%, 0) scale(1.3);
    box-shadow: 0 0 16px var(--card-accent, var(--magenta));
  }

  .timeline-card:hover {
    transform: translateY(-4px);
    box-shadow: 0 20px 40px -25px var(--card-accent, var(--magenta));
  }

  /* Blog & Newsletter (Stage 6a) — appended here rather than a separate
     (hover: hover) and (pointer: fine) block; same box-shadow/transform-
     only reasoning as the Event Timeline merge above. */
  .blog-card:hover {
    transform: translateY(-6px);
    box-shadow: 0 20px 40px -25px var(--card-accent, var(--magenta));
  }

  .blog-card:hover .blog-card-thumb img {
    transform: scale(1.06);
  }

  .newsletter-card:hover {
    transform: translateY(-4px);
    box-shadow: 0 18px 36px -22px var(--magenta);
  }

  /* Gallery (Stage 7a) — appended here rather than a separate (hover:
     hover) and (pointer: fine) block, same reasoning as the merges above.
     Tilt + zoom on the photo itself, accent glow on the tile. */
  .gallery-tile:hover {
    border-color: var(--card-accent, var(--magenta));
    box-shadow: 0 0 0 2px var(--card-accent, var(--magenta)), 0 20px 40px -22px var(--card-accent, var(--magenta));
  }

  .gallery-tile:hover .gallery-tile-button img {
    transform: scale(1.08) rotate(0.6deg);
    filter: grayscale(0) saturate(1);
  }

  /* About (Stage 8a) — appended here rather than a separate (hover:
     hover) and (pointer: fine) block, same reasoning as the merges
     above. */
  .about-value-card:hover,
  .about-link-card:hover {
    transform: translateY(-4px);
    box-shadow: 0 18px 36px -22px var(--card-accent, var(--magenta));
  }

  /* FAQ (Stage 8b) — appended here rather than a separate (hover: hover)
     and (pointer: fine) block, same reasoning as the merges above. A
     glow rather than a lift/border-color change, since border-color
     already carries this card's category accent unconditionally (the
     left border), not just on hover. */
  .faq-item:hover {
    box-shadow: 0 14px 30px -20px var(--card-accent, var(--magenta));
  }

  /* Get Involved (Stage 9a) — appended here rather than a separate
     (hover: hover) and (pointer: fine) block, same reasoning as the
     merges above. */
  .involved-card:hover,
  .involved-link-card:hover {
    transform: translateY(-4px);
    box-shadow: 0 18px 36px -22px var(--card-accent, var(--magenta));
  }

  /* Footer icon links — appended here rather than a separate (hover:
     hover) and (pointer: fine) block, same reasoning as the merges above.
     Mouse-only nudge; the :focus-visible scale (unconditional, see the
     footer's own CSS section) is what keyboard users get instead. */
  .footer-icon-link:hover {
    transform: scale(1.08);
    opacity: 0.85;
  }
}

/* No hover gesture exists on touch/coarse-pointer devices — show the
   accented border always, rather than gating a decorative-only effect
   behind a tap that has no other purpose. (Photos are full color on every
   device via the base rule now, so there's no filter override here.) */
@media (hover: none) {
  .leadership-card {
    border-color: var(--card-accent, var(--magenta));
  }

  /* Event Timeline (Stage 5a) — appended here rather than a separate
     (hover: none) block; same box-shadow-only reasoning as the (hover:
     hover) merge above. */
  .timeline-node {
    box-shadow: 0 0 12px var(--card-accent, var(--magenta));
  }

  /* Blog & Newsletter (Stage 6a) — appended here rather than a separate
     (hover: none) block; same "always show the accented state" reasoning
     as Leadership's touch equivalent above, applied to a border instead of
     a photo filter since these cards have no photo to desaturate. */
  .blog-card {
    border-color: var(--card-accent, var(--magenta));
  }

  /* Gallery (Stage 7a) — appended here rather than a separate (hover:
     none) block; same "always show the accented/full-color state"
     reasoning as Leadership's touch equivalent above, applied to both the
     tile border and the photo's own desaturation. */
  .gallery-tile {
    border-color: var(--card-accent, var(--magenta));
  }

  .gallery-tile-button img {
    filter: grayscale(0) saturate(1);
  }

  /* About (Stage 8a) — appended here rather than a separate (hover:
     none) block; same "always show the accented state" reasoning as
     Leadership's touch equivalent above. */
  .about-value-card {
    border-color: var(--card-accent, var(--magenta));
  }
}

/* ==========================================================================
   Pitch Videos page (Stage 4a)

   A third full page, alongside Home and Leadership — same header/footer/
   color system, same progressive-enhancement approach as Leadership's grid
   (js/script.js: initPitchVideoReveal mirrors initLeadershipReveal). Cards
   are plain, fully-visible, normal-flow content by default; the GSAP
   stagger and the hover micro-interactions below are both enhancements
   layered on top, never required to understand or use the page.
   ========================================================================== */

.pitch-videos-page {
  position: relative;
  background: var(--charcoal);
  overflow: hidden;
}

/* Full-bleed textured backdrop — same treatment as .leadership-page::before/
   .top-teams::before, not a new visual idiom. */
.pitch-videos-page::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background-image:
    radial-gradient(750px circle at 12% 0%, rgba(236, 32, 136, 0.16), transparent 60%),
    var(--bg-network);
  background-repeat: no-repeat, repeat;
  background-size: auto, 240px 240px;
  opacity: 0.5;
}

.pitch-videos-hero,
.pitch-videos-grid-section {
  position: relative;
  z-index: 1;
}

/* `position: relative` added here as a real-bug fix — see the matching,
   fuller comment on .hiw-hero. Same missing-containment issue as
   .about-hero/.involved-hero/.gallery-hero/.faq-hero/.blog-hero. */
.pitch-videos-hero {
  position: relative;
  padding-block: 5rem 2.5rem;
  overflow: hidden;
}

/* Sits between .pitch-videos-page::before (the page's own static texture,
   z-index: 0 in that ancestor's stacking context) and this section's own
   content — needs its own z-index/position so paint order can't come down
   to ambiguous default stacking rules. initPitchVideoEntrance (js/
   script.js) is the only thing that ever animates this; base CSS leaves it
   at opacity: 0, so nothing shows if that never runs (reduced motion, or a
   GSAP CDN failure). */
.pitch-videos-flourish {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  opacity: 0;
  background: conic-gradient(
    from 0deg at 50% 30%,
    var(--magenta),
    var(--sky),
    var(--teal),
    var(--gold),
    var(--orange),
    var(--magenta)
  );
  filter: blur(70px);
  mix-blend-mode: screen;
}

.pitch-videos-hero .container {
  position: relative;
  z-index: 1;
}

.pitch-videos-hero h1 {
  font-size: clamp(1.7rem, 4.2vw, 2.6rem);
  max-width: 32ch;
}

/* Same per-word overflow-hidden mask as Home's .reveal-word/-inner —
   independently named so it doesn't also inherit Home's CSS-keyframe
   animation; this page's stagger runs through GSAP instead
   (initPitchVideoEntrance in js/script.js), started from whatever position
   GSAP sets at load time. Base state here is plain and fully visible, same
   reasoning as .pitch-videos-flourish above: nothing is hidden until the
   entrance script actually decides to hide it. */
.pv-reveal-word {
  display: inline-block;
  overflow: hidden;
  vertical-align: top;
}

.pv-reveal-word-inner {
  display: inline-block;
}

.pitch-videos-hero-lede {
  color: var(--text-secondary);
  font-size: 1.15rem;
  max-width: 62ch;
  margin: 0;
}

.pitch-videos-grid-section {
  padding-block: 1.5rem 5rem;
}

.pitch-videos-placeholder-note {
  display: inline-block;
  font-family: var(--font-head);
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--text-secondary);
  background: var(--charcoal-raised);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 0.4rem 0.9rem;
  margin: 0 0 2.5rem;
}


/* --------------------------------------------------------------------------
   Coverflow carousel (Stage 4b — replaced the original bento grid of
   .video-card tiles entirely; see git history for that version). Swiper
   (loaded via CDN, see pitch-videos.html) handles the 3D transforms,
   dragging, keyboard nav, and pagination; js/script.js
   (initPitchVideoCarousel) just configures it. Everything here is either
   Swiper's own customization surface (the --swiper-* custom properties) or
   styling for the slide content Swiper wraps, which stays plain, fully-
   visible, normal-flow markup independent of whether Swiper's JS loads —
   see the comment on the <script> tag in pitch-videos.html for what a CDN
   failure degrades to.
   -------------------------------------------------------------------------- */

.pitch-swiper {
  --swiper-navigation-size: 20px;
  --swiper-navigation-color: var(--white);
  --swiper-pagination-color: var(--magenta);
  --swiper-pagination-bullet-inactive-color: var(--line);
  --swiper-pagination-bullet-inactive-opacity: 1;
  padding-block: 1rem 3.5rem;
  /* Swiper's coverflow effect needs real horizontal room for the angled
     side slides not to clip against the container edge. */
  overflow: visible;
}

/* .pitch-swiper .swiper-slide's own base rule (width: min(340px, 78vw))
   lives up near .footer-inner's min-width: 700px block instead of here —
   see the comment there for why (source-order cascade requirement: a
   same-specificity base rule must sit BEFORE the media-gated override that
   targets it, not just anywhere, or the override loses the cascade even
   when its condition matches — the exact bug documented in detail by the
   Leadership grid's own comment further up this file). */

.pitch-swiper .swiper-button-prev,
.pitch-swiper .swiper-button-next {
  width: 2.75rem;
  height: 2.75rem;
  background: rgba(13, 11, 16, 0.6);
  border: 1px solid var(--line);
  border-radius: 50%;
  top: 40%;
}

@media (prefers-reduced-motion: no-preference) {
  .pitch-swiper .swiper-button-prev,
  .pitch-swiper .swiper-button-next {
    transition: border-color 0.2s ease, color 0.2s ease;
  }
}

.pitch-swiper .swiper-button-prev:hover,
.pitch-swiper .swiper-button-next:hover {
  border-color: var(--magenta);
  color: var(--magenta);
}

.pitch-swiper .swiper-button-prev.swiper-button-disabled,
.pitch-swiper .swiper-button-next.swiper-button-disabled {
  opacity: 0.35;
}

.pitch-swiper .swiper-pagination {
  bottom: 0;
}

.pitch-swiper .swiper-pagination-bullet {
  width: 8px;
  height: 8px;
}

/* --------------------------------------------------------------------------
   Slide content. Thumbnails are 16:9, same as the bento version this
   replaced; the card itself no longer needs its own hover lift/tilt (that
   was a grid-appropriate micro-interaction) since Swiper's own coverflow
   transform already provides the depth cue — the centered slide getting an
   accent glow (below) is this design's equivalent "which one is active"
   signal instead.
   -------------------------------------------------------------------------- */

.pitch-slide-card {
  position: relative;
  display: flex;
  flex-direction: column;
  height: 100%;
  background: var(--charcoal-raised);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
}

@media (prefers-reduced-motion: no-preference) {
  .pitch-slide-card {
    transition: border-color 0.3s ease, box-shadow 0.3s ease;
  }

  .pitch-slide-thumb img {
    transition: filter 0.3s ease;
  }

  .pitch-play-button {
    transition: box-shadow 0.3s ease, border-color 0.3s ease;
  }
}

/* The centered slide — Swiper keeps this class on whichever slide is
   currently active — gets a soft glow ring in its own accent color, per
   the brief's "unambiguous which one is in focus." */
.swiper-slide-active .pitch-slide-card {
  border-color: var(--card-accent, var(--magenta));
  box-shadow: 0 0 0 2px var(--card-accent, var(--magenta)), 0 30px 55px -25px var(--card-accent, var(--magenta));
}

.swiper-slide-active .pitch-slide-thumb img {
  filter: saturate(1) brightness(0.95);
}

.swiper-slide-active .pitch-play-button {
  box-shadow: 0 0 20px var(--card-accent, var(--magenta));
  border-color: var(--card-accent, var(--magenta));
}

.pitch-slide-thumb {
  position: relative;
  /* The aspect-ratio lives on this container, not the <img> inside it:
     initPitchVideoPlayback (js/script.js) replaces that <img> with an
     absolutely-positioned <iframe> on click, and absolutely-positioned
     content never contributes to a parent's height. Without its own
     aspect-ratio, this box would collapse to 0 height the moment that
     swap happens, taking the embedded video down with it — confirmed via
     getBoundingClientRect during this stage's own testing. */
  aspect-ratio: 16 / 9;
  overflow: hidden;
  /* Classic Cover Flow detail: a soft reflection beneath the art. WebKit/
     Blink only (Chrome, Safari, Edge) — Firefox has no equivalent property
     and just ignores this line, so the thumbnail still looks correct
     there, only without the reflection. Applied to the thumbnail alone
     (not the whole card) to match the original Cover Flow look, where only
     the album art reflects. The 10px margin-bottom below gives the
     reflection actual room to render in — without it, .pitch-slide-body's
     opaque background starts immediately where the thumbnail ends and
     covers the reflection entirely, making this rule a no-op in practice
     (confirmed by zooming into a rendered card during this stage's own
     testing: no reflection was visible until this gap was added). */
  -webkit-box-reflect: below 6px linear-gradient(to bottom, transparent, transparent 55%, rgba(255, 255, 255, 0.12));
  margin-bottom: 10px;
}

.pitch-slide-thumb img,
.pitch-slide-thumb iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}

.pitch-slide-thumb img {
  display: block;
  object-fit: cover;
  /* Muted on side slides by default; the active-slide rule above brings it
     to full strength as the "in focus" signal. */
  filter: saturate(0.8) brightness(0.75);
}

.pitch-play-button {
  position: absolute;
  inset: 0;
  margin: auto;
  width: 3.5rem;
  height: 3.5rem;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: rgba(13, 11, 16, 0.55);
  border: 2px solid rgba(255, 255, 255, 0.85);
  color: var(--white);
  cursor: pointer;
}

.pitch-play-button svg {
  width: 1.4rem;
  height: 1.4rem;
  margin-left: 0.15rem; /* optical centering: a triangle's visual weight sits left of its bounding box */
}

.pitch-slide-body {
  padding: 1.25rem;
  text-align: center;
}

.pitch-slide-name {
  font-family: var(--font-head);
  font-size: 1.1rem;
  margin: 0;
}

/* The 3 real placement cards keep the exact Top Teams medal colors (not the
   generic accent rotation below) per the brief's "consistency with Top
   Teams" — .team-card-badge-gold/silver/bronze are reused as-is in the
   HTML from that section, and --card-accent here just extends the same
   medal color to this slide's own glow ring. */
.pitch-slide-gold { --card-accent: var(--gold); }
.pitch-slide-silver { --card-accent: var(--silver); }
.pitch-slide-bronze { --card-accent: var(--bronze); }

/* The 3 generic placeholder slots cycle the same secondary accent palette
   used on Leadership and the original video grid — one each, since there
   are exactly three here. */
.pitch-slide-teal { --card-accent: var(--teal); }
.pitch-slide-sky { --card-accent: var(--sky); }
.pitch-slide-orange { --card-accent: var(--orange); }

/* ==========================================================================
   Event Timeline page (Stage 5a)

   A fourth full page, alongside Home/Leadership/Pitch Videos — same header/
   footer/color system. Mobile-first: base rules below are the single-
   column mobile layout (track + nodes flush left, cards full-width);
   @media (min-width: 680px) switches to the alternating centered layout.
   That breakpoint is a fresh, unused condition and this whole section is
   self-contained (base rules and their override live together, in this one
   contiguous block) specifically to avoid the two real bugs hit building
   the Leadership/Pitch Videos pages: a second block sharing an exact
   @media condition elsewhere in this file gets silently dropped by the
   browser's CSSOM parser, and even when merging into a shared block is
   safe/necessary, a same-specificity base rule must sit BEFORE the
   override that targets it or the override loses the cascade regardless of
   whether its condition matches. Owning a unique breakpoint sidesteps both
   requirements entirely for the properties that need mobile/desktop
   overrides. Hover states below are still merged into the existing shared
   (hover: hover)/(hover: none) blocks — see the comment placed with those
   merged rules for why that stays safe here specifically.
   ========================================================================== */

.timeline-page {
  position: relative;
  background: var(--charcoal);
  overflow: hidden;
}

.timeline-page::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background-image:
    radial-gradient(750px circle at 88% 0%, rgba(236, 32, 136, 0.16), transparent 60%),
    var(--bg-network);
  background-repeat: no-repeat, repeat;
  background-size: auto, 240px 240px;
  opacity: 0.5;
}

.timeline-hero,
.timeline-track-section {
  position: relative;
  z-index: 1;
}

.timeline-hero {
  padding-block: 5rem 2.5rem;
}

.timeline-hero h1 {
  font-size: clamp(1.9rem, 4.6vw, 2.8rem);
  max-width: 24ch;
}

.timeline-hero-lede {
  color: var(--text-secondary);
  font-size: 1.15rem;
  max-width: 62ch;
  margin: 0 0 1.5rem;
}

.timeline-placeholder-note {
  display: inline-block;
  font-family: var(--font-head);
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--text-secondary);
  background: var(--charcoal-raised);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 0.4rem 0.9rem;
  margin: 0;
}

.timeline-track-section {
  padding-block: 1.5rem 6rem;
}

/* --------------------------------------------------------------------------
   Track: the vertical line + the live "You Are Here" marker. Both are
   positioned as a percentage of .timeline-track's own height — which,
   since .timeline-events is its only in-flow child, naturally spans from
   the top of the first event to the bottom of the last one. 0%/100% land
   at the very top/bottom of that box rather than each node's exact center,
   a small, deliberate simplification — see initEventTimelinePositions in
   js/script.js for the actual percentage math.
   -------------------------------------------------------------------------- */

.timeline-track {
  position: relative;
}

.timeline-track-line {
  position: absolute;
  left: 20px;
  top: 0;
  bottom: 0;
  width: 3px;
  background: var(--line);
  z-index: 0;
}

/* top is set inline by initEventTimelinePositions (js/script.js) — a
   percentage of .timeline-track's height computed from where today's real
   date falls between the first and last event. Always applied regardless
   of motion preference: this is the page's actual content (which point in
   the timeline is "now"), not decoration.

   This element itself is a zero-size anchor POINT sitting exactly on the
   track (left here matches .timeline-track-line's own left, same as
   .timeline-node below) — not a box to center as a whole. .timeline-you-
   are-here-dot and -label (Stage 5b fix) are each positioned independently
   relative to that point instead: previously this was display: flex with
   the dot and label as row siblings, and centering the CONTAINER (i.e. the
   midpoint of dot+gap+label combined) put the dot itself visibly off the
   track by about half the label's width. Centering the dot on its own,
   with the label floating to one side of it, is what actually keeps the
   dot itself on the line. */
.timeline-you-are-here {
  position: absolute;
  left: 20px;
  z-index: 3;
  width: 0;
  height: 0;
  pointer-events: none;
}

.timeline-you-are-here-dot {
  position: absolute;
  top: 0;
  left: 0;
  transform: translate(-50%, -50%);
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: var(--magenta);
  border: 3px solid var(--charcoal-deep);
}

/* Mobile default: above the dot rather than to a side — the track sits
   only 20px from the screen edge here, with every card (mobile's single-
   column layout, see .timeline-event below) filling the space to its
   right, so there's no side with real room for a label. min-width: 680px
   overrides this to sit beside the dot instead, on whichever side
   initEventTimelinePositions (js/script.js) determines is opposite the
   nearest event's card. Left-aligned to the dot (not centered on it) and
   left slightly clear of it: the dot sits only 20px from the screen edge,
   so centering the label here (as an earlier version of this rule did)
   pushed roughly half its width past the left edge of the viewport. */
.timeline-you-are-here-label {
  position: absolute;
  left: 4px;
  bottom: 100%;
  margin-bottom: 0.6rem;
  font-family: var(--font-head);
  font-weight: 700;
  font-size: 0.75rem;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  color: var(--magenta);
  background: var(--charcoal-deep);
  border: 1px solid var(--magenta);
  padding: 0.3rem 0.7rem;
  border-radius: 999px;
  white-space: nowrap;
}

/* --------------------------------------------------------------------------
   Events
   -------------------------------------------------------------------------- */

.timeline-events {
  position: relative;
  z-index: 1;
  list-style: none;
  margin: 0;
  padding: 0;
}

.timeline-event {
  position: relative;
  padding-left: 3.5rem;
  padding-block: 1.5rem;
}

.timeline-node {
  position: absolute;
  left: 20px;
  top: 2.75rem;
  transform: translate(-50%, 0);
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--card-accent, var(--magenta));
  border: 3px solid var(--charcoal-deep);
  z-index: 2;
}

.timeline-card {
  position: relative;
  display: block;
  width: 100%;
  text-align: left;
  font: inherit;
  color: inherit;
  background: var(--charcoal-raised);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 1.35rem 1.5rem;
  cursor: pointer;
}

.timeline-card-date {
  font-family: var(--font-head);
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--card-accent, var(--magenta));
  margin: 0 0 0.4rem;
}

.timeline-card-name {
  font-family: var(--font-head);
  font-size: 1.15rem;
  margin: 0 0 0.4rem;
}

.timeline-card-status {
  font-size: 0.9rem;
  color: var(--text-secondary);
  margin: 0;
}

/* Hidden until initEventTimelinePositions (js/script.js) adds .is-completed
   — real content computed from the actual current date, so there's no
   meaningful static default to show instead. */
.timeline-card-completed-icon {
  display: none;
  position: absolute;
  top: 1rem;
  right: 1rem;
  width: 1.4rem;
  height: 1.4rem;
  color: var(--teal);
}

.timeline-card-completed-icon svg {
  width: 100%;
  height: 100%;
}

.timeline-event.is-completed .timeline-card-completed-icon {
  display: block;
}

.timeline-event.is-completed .timeline-card {
  opacity: 0.6;
  filter: saturate(0.6);
}

/* Same reasoning as .timeline-card-completed-icon above — hidden until JS
   determines which single event is actually next. */
.timeline-next-up-badge {
  display: none;
  position: absolute;
  top: -0.7rem;
  left: 1.5rem;
  font-family: var(--font-head);
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--charcoal-deep);
  background: var(--card-accent, var(--magenta));
  padding: 0.3rem 0.7rem;
  border-radius: 999px;
}

.timeline-event.is-next-up .timeline-next-up-badge {
  display: inline-block;
}

.timeline-event.is-next-up .timeline-card {
  border-color: var(--card-accent, var(--magenta));
  box-shadow: 0 0 30px -10px var(--card-accent, var(--magenta));
}


/* Accent color rotation — the same secondary palette used on Leadership/
   Pitch Videos, cycled across the 8 events (2 full passes of the 4
   colors). */
.timeline-event-teal { --card-accent: var(--teal); }
.timeline-event-sky { --card-accent: var(--sky); }
.timeline-event-orange { --card-accent: var(--orange); }
.timeline-event-gold { --card-accent: var(--gold); }

/* --------------------------------------------------------------------------
   Desktop layout: centered track, alternating left/right cards. A fresh,
   unused breakpoint — see this section's opening comment for why.
   -------------------------------------------------------------------------- */

@media (min-width: 680px) {
  .timeline-track-line {
    left: 50%;
    transform: translateX(-50%);
  }

  .timeline-you-are-here {
    left: 50%;
  }

  /* Beside the dot rather than above it now that there's room on either
     side. Default is the right; .label-left (added by
     initEventTimelinePositions, js/script.js, based on which side the
     nearest event's card is actually on) flips it to the left instead —
     see the comment on that function for the "opposite side" logic. */
  .timeline-you-are-here-label {
    top: 0;
    bottom: auto;
    left: 1.2rem;
    margin-bottom: 0;
    transform: translateY(-50%);
  }

  .timeline-you-are-here.label-left .timeline-you-are-here-label {
    left: auto;
    right: 1.2rem;
  }

  .timeline-event {
    padding-left: 0;
    display: flex;
  }

  .timeline-event:nth-child(odd) {
    justify-content: flex-end;
  }

  .timeline-event:nth-child(even) {
    justify-content: flex-start;
  }

  .timeline-node {
    left: 50%;
  }

  .timeline-card {
    width: 44%;
  }

  .timeline-event:nth-child(odd) .timeline-card {
    margin-right: 3rem;
  }

  .timeline-event:nth-child(even) .timeline-card {
    margin-left: 3rem;
  }

  .timeline-next-up-badge {
    left: auto;
  }

  .timeline-event:nth-child(odd) .timeline-next-up-badge {
    right: 1.5rem;
  }

  .timeline-event:nth-child(even) .timeline-next-up-badge {
    left: 1.5rem;
  }
}

/* --------------------------------------------------------------------------
   Motion: track flow, You Are Here pulse, and hover/focus transitions. All
   of it gated here — reduced motion gets a static line, a static (but
   still colored/visible) marker dot, and instant hover/focus states,
   exactly matching the brief's "You Are Here must still appear in the
   correct position... but without the pulsing animation."
   -------------------------------------------------------------------------- */

@media (prefers-reduced-motion: no-preference) {
  .timeline-track-line {
    background: linear-gradient(180deg, var(--magenta), var(--sky), var(--teal), var(--gold), var(--magenta));
    background-size: 100% 300%;
    animation: timeline-flow 7s linear infinite;
  }

  .timeline-you-are-here-dot {
    animation: timeline-pulse 2s ease-out infinite;
  }

  .timeline-node,
  .timeline-card {
    transition: transform 0.25s ease, box-shadow 0.25s ease;
  }

  @keyframes timeline-flow {
    0% {
      background-position: 0% 0%;
    }
    100% {
      background-position: 0% 100%;
    }
  }

  @keyframes timeline-pulse {
    0% {
      box-shadow: 0 0 0 0 rgba(236, 32, 136, 0.55);
    }
    70% {
      box-shadow: 0 0 0 18px rgba(236, 32, 136, 0);
    }
    100% {
      box-shadow: 0 0 0 0 rgba(236, 32, 136, 0);
    }
  }
}

/* ==========================================================================
   Event detail modal (Stage 5c) — Timeline page

   Clicking a .timeline-card expands that SAME element into a full detail
   view via GSAP's Flip plugin (js/script.js: initEventDetailModal) — the
   card visibly grows into the modal rather than a separate, unrelated
   popup appearing. Every rule below that shows/hides something (the
   overlay, the larger .is-modal-open layout, the close button, the detail
   content) is plain unconditional CSS + class toggling, correct with or
   without GSAP/Flip; only the morph/stagger animation itself is gated in
   JS behind GSAP/Flip being available and !prefers-reduced-motion — see
   that function's comment for exactly what falls back to an instant
   appearance instead. */

.timeline-modal-overlay {
  position: fixed;
  inset: 0;
  z-index: 55;
  background: rgba(8, 6, 10, 0.72);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}

.timeline-card-close {
  display: none;
  position: absolute;
  top: 1rem;
  right: 1rem;
  width: 2.25rem;
  height: 2.25rem;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  border: 1px solid var(--line);
  background: var(--charcoal-deep);
  color: var(--white);
  cursor: pointer;
}

.timeline-card-close svg {
  width: 1.1rem;
  height: 1.1rem;
}

.timeline-card.is-modal-open .timeline-card-close {
  display: flex;
}

/* Hidden until a card is open — see initEventDetailModal for the
   staggered title -> meta -> description reveal once it is. */
.timeline-card-detail {
  display: none;
}

.timeline-card.is-modal-open .timeline-card-detail {
  display: block;
  margin-top: 1.1rem;
  padding-top: 1.1rem;
  border-top: 1px solid var(--line);
}

.timeline-card-detail-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1.5rem;
  margin: 0 0 1rem;
  font-size: 0.92rem;
  color: var(--text-secondary);
}

.timeline-card-detail-time,
.timeline-card-detail-location {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
}

.timeline-card-detail-time svg,
.timeline-card-detail-location svg {
  width: 1.1rem;
  height: 1.1rem;
  flex-shrink: 0;
  color: var(--card-accent, var(--magenta));
}

.timeline-card-detail-description {
  margin: 0;
  font-size: 0.98rem;
  line-height: 1.6;
  color: var(--text-secondary);
}

/* Stage 2c: this is now a <div> holding marked's rendered HTML (previously
   a single <p> of plain text), so it needs the same child-element prose
   rules as .blog-post-body — including the ul/ol fix from that stage:
   list-style: revert belongs on the ul/ol itself, not the li, since
   list-style-type is inherited and the site-wide `ul, ol { list-style:
   none }` reset (near the top of this file) sets it directly on that
   element — reverting on the li only undoes an (absent) author rule on
   the li, not the inherited value from its parent. */
.timeline-card-detail-description p {
  margin: 0 0 1em;
}

.timeline-card-detail-description p:last-child {
  margin-bottom: 0;
}

.timeline-card-detail-description strong {
  color: var(--white);
}

.timeline-card-detail-description a {
  color: var(--card-accent, var(--magenta));
}

.timeline-card-detail-description ul,
.timeline-card-detail-description ol {
  list-style: revert;
  margin: 0 0 1em;
  padding-left: 1.4em;
}

.timeline-card-detail-description li {
  margin-bottom: 0.3em;
}

/* The expanded state: position: fixed and centered, larger and scrollable,
   well above the site header's own z-index: 50 so the whole page —
   including the header — sits behind the dimmed backdrop. Deliberately
   redeclares transform/box-shadow/opacity/filter (properties the plain
   hover state and the .is-completed/.is-next-up rules earlier in this
   file also set) and is placed after all of them specifically so it wins
   regardless of this card's hover or event status — same equal-
   specificity-plus-later-in-file technique used elsewhere on this page. */
.timeline-card.is-modal-open {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  z-index: 56;
  width: min(560px, 92vw);
  max-height: 85vh;
  overflow-y: auto;
  cursor: default;
  padding: 2rem 2rem 2.25rem;
  border-radius: 16px;
  border-color: var(--card-accent, var(--magenta));
  box-shadow: 0 30px 70px -20px rgba(0, 0, 0, 0.6), 0 0 0 1px var(--card-accent, var(--magenta));
}

.timeline-event .timeline-card.is-modal-open {
  opacity: 1;
  filter: none;
}

.timeline-card.is-modal-open .timeline-card-completed-icon,
.timeline-card.is-modal-open .timeline-next-up-badge {
  display: none;
}

.timeline-card.is-modal-open .timeline-card-date {
  font-size: 0.85rem;
}

.timeline-card.is-modal-open .timeline-card-name {
  font-size: 1.5rem;
  padding-right: 2.5rem;
}

/* Small viewports: near-full-screen rather than a smaller floating card,
   for readability and an easily reachable close button — a fresh
   breakpoint (max-width: 679px, the exact complement of the min-width:
   680px desktop layout above) chosen for the same duplicate-@media-
   condition reason documented at the top of this page's CSS section. */
@media (max-width: 679px) {
  .timeline-card.is-modal-open {
    top: 0;
    left: 0;
    transform: none;
    width: 100%;
    height: 100%;
    max-height: none;
    border-radius: 0;
    padding: 4.5rem 1.5rem 2.5rem;
  }

  .timeline-card.is-modal-open .timeline-card-close {
    top: 1.25rem;
    right: 1.25rem;
  }

  /* Gallery (Stage 7a) — appended here rather than a separate max-width:
     679px block, same reasoning as the Event Timeline section's opening
     comment. Near-full-screen lightbox with the image filling most of the
     height and the caption/close/prev/next overlaid on it, rather than a
     smaller floating box.

     Scoped with the extra "body" type selector (rather than the bare
     .gallery-tile-button.is-lightbox-open used everywhere else) because
     this page's own base rule for that same selector is declared near the
     end of the file — AFTER this block — being the Gallery section's own
     unconditional/desktop rule; at equal specificity the later declaration
     wins the cascade regardless of which one's media query currently
     matches, so without this the "always applies" desktop-centered rule
     was confirmed (via getBoundingClientRect) to silently override this
     mobile block's width/height/position at every viewport width. Adding
     "body" raises specificity just enough to win regardless of source
     order — the same fix this project has used before (Blog's newsletter
     archive) for a base rule that has to stay positioned after an @media
     block it's merged into. */
  body .gallery-tile-button.is-lightbox-open {
    top: 0;
    left: 0;
    transform: none;
    width: 100%;
    height: 100%;
    max-height: none;
    border-radius: 0;
  }

  .gallery-tile-button.is-lightbox-open .gallery-lightbox-close {
    top: 1.25rem;
    right: 1.25rem;
  }

  .gallery-tile-button.is-lightbox-open .gallery-lightbox-prev {
    left: 0.5rem;
  }

  .gallery-tile-button.is-lightbox-open .gallery-lightbox-next {
    right: 0.5rem;
  }
}

/* ==========================================================================
   Blog & Newsletter page (Stage 6a)

   A fifth full page, alongside Home, Leadership, Pitch Videos, and Event
   Timeline — same header/footer/color system, same progressive-enhancement
   approach (initBlogGridReveal/initNewsletterReveal mirror
   initLeadershipReveal, js/script.js), and the same one-time hero-entrance
   pattern as Pitch Videos (initBlogEntrance mirrors initPitchVideoEntrance)
   rather than a new entry style, per the brief. Cards are plain, fully-
   visible, normal-flow content by default; GSAP and the hover micro-
   interactions merged into the shared blocks earlier in this file are
   enhancements layered on top, never required to read or use the page.
   ========================================================================== */

.blog-page {
  position: relative;
  background: var(--charcoal);
  overflow: hidden;
}

/* Full-bleed textured backdrop — same treatment as .leadership-page::before/
   .pitch-videos-page::before, not a new visual idiom. Glow anchored top-
   right rather than top-left, purely so this page doesn't look like a
   carbon copy of Pitch Videos at a glance. */
.blog-page::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background-image:
    radial-gradient(750px circle at 88% 0%, rgba(236, 32, 136, 0.16), transparent 60%),
    var(--bg-network);
  background-repeat: no-repeat, repeat;
  background-size: auto, 240px 240px;
  opacity: 0.5;
}

.blog-hero,
.blog-grid-section,
.newsletter-section {
  position: relative;
  z-index: 1;
}

/* `position: relative` added here as a real-bug fix — see the matching,
   fuller comment on .hiw-hero. Same missing-containment issue as
   .about-hero/.involved-hero/.gallery-hero/.faq-hero/.pitch-videos-hero. */
.blog-hero {
  position: relative;
  padding-block: 5rem 2.5rem;
  overflow: hidden;
}

/* Sits between .blog-page::before (this page's own static texture,
   z-index: 0 in that ancestor's stacking context) and this section's own
   content — needs its own z-index/position so paint order can't come down
   to ambiguous default stacking rules. initBlogEntrance (js/script.js) is
   the only thing that ever animates this; base CSS leaves it at opacity: 0,
   so nothing shows if that never runs (reduced motion, or a GSAP CDN
   failure) — same reasoning as .pitch-videos-flourish. */
.blog-hero-flourish {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  opacity: 0;
  background: conic-gradient(
    from 0deg at 50% 30%,
    var(--magenta),
    var(--gold),
    var(--teal),
    var(--sky),
    var(--orange),
    var(--magenta)
  );
  filter: blur(70px);
  mix-blend-mode: screen;
}

.blog-hero .container {
  position: relative;
  z-index: 1;
}

.blog-hero h1 {
  font-size: clamp(1.9rem, 4.6vw, 3rem);
  max-width: 20ch;
}

/* Same per-word overflow-hidden mask as Pitch Videos' .pv-reveal-word/-inner
   (itself borrowed from Home's .reveal-word/-inner) — independently named
   so it doesn't also inherit either page's own animation. Base state is
   plain and fully visible; nothing is hidden until initBlogEntrance
   actually decides to hide it. */
.bl-reveal-word {
  display: inline-block;
  overflow: hidden;
  vertical-align: top;
}

.bl-reveal-word-inner {
  display: inline-block;
}

.blog-hero-lede {
  color: var(--text-secondary);
  font-size: 1.15rem;
  max-width: 62ch;
  margin: 0;
}

.blog-placeholder-note {
  display: inline-block;
  font-family: var(--font-head);
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--text-secondary);
  background: var(--charcoal-raised);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 0.4rem 0.9rem;
  margin: 1.25rem 0 0;
}

/* This page visibly labels its two sections ("Latest Posts" / "Newsletter")
   rather than the visually-hidden <h2> pattern used on Leadership/Timeline
   — those pages cover one content type each, so the hero alone orients the
   visitor; this page covers two distinct ones on a single scroll, so a
   visible divider between them earns its place. */
.section-heading {
  font-size: clamp(1.4rem, 3vw, 1.8rem);
  margin: 0 0 1.75rem;
}

/* --------------------------------------------------------------------------
   Blog grid — see the min-width: 640px/1000px merges earlier in this file
   for the responsive bento layout, and the (hover: hover)/(hover: none)
   merges for the hover/touch treatment.
   -------------------------------------------------------------------------- */

.blog-grid-section {
  padding-block: 1.5rem 4rem;
}

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

.blog-card {
  position: relative;
  display: flex;
  flex-direction: column;
  background: var(--charcoal-raised);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
}

@media (prefers-reduced-motion: no-preference) {
  .blog-card {
    transition: transform 0.3s ease, box-shadow 0.3s ease, border-color 0.3s ease;
  }

  .blog-card-thumb img {
    transition: transform 0.4s ease;
  }
}

.blog-card-thumb {
  margin: 0;
  overflow: hidden;
  aspect-ratio: 16 / 9;
}

.blog-card-thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.blog-card-body {
  padding: 1.35rem 1.5rem;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  flex: 1;
}

/* Stage 2b: makes the whole card a link to that post's own page
   (blog-post.html), not just some "read more" text — an empty, visually
   invisible anchor stretched over the card via .blog-card's own
   position: relative. Its accessible name (aria-label, set when
   initBlogContent builds it) is the post's title, so the thumbnail <img>
   right below is deliberately alt="" rather than repeating that same
   text a second time. */
.blog-card-link {
  position: absolute;
  inset: 0;
  z-index: 1;
}

.blog-card-category {
  font-family: var(--font-head);
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--card-accent, var(--magenta));
  margin: 0;
}

.blog-card-title {
  font-size: 1.1rem;
  margin: 0;
}

.blog-card-date {
  font-family: var(--font-head);
  font-size: 0.78rem;
  color: var(--text-secondary);
  margin: 0;
}

.blog-card-excerpt {
  font-size: 0.92rem;
  color: var(--text-secondary);
  margin: 0.15rem 0 0;
}

.blog-card-featured .blog-card-category {
  font-size: 0.8rem;
}

.blog-card-featured .blog-card-title {
  font-size: 1.5rem;
}

.blog-card-featured .blog-card-excerpt {
  font-size: 1rem;
}

/* Category -> accent mapping (Stage 6a) — every post sharing a category
   shares a color, so the coloring reads as a taxonomy rather than
   decoration: Recap/teal, Announcement/sky, Behind the Scenes/orange,
   Tips/gold. Same --card-accent mechanism as Leadership/Pitch Videos/
   Timeline. */
.blog-card-teal { --card-accent: var(--teal); }
.blog-card-sky { --card-accent: var(--sky); }
.blog-card-orange { --card-accent: var(--orange); }
.blog-card-gold { --card-accent: var(--gold); }

/* --------------------------------------------------------------------------
   Newsletter
   -------------------------------------------------------------------------- */

.newsletter-section {
  padding-block: 1rem 5.5rem;
}

.newsletter-lede {
  color: var(--text-secondary);
  font-size: 1.05rem;
  max-width: 62ch;
  margin: -1rem 0 2.5rem;
}

.newsletter-archive {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1.1rem;
  margin: 0 0 3rem;
}

/* A compact "ticket stub" shape (accent bar down the left edge) rather than
   .blog-card's thumbnail-led layout — deliberately different so an issue
   reads as visually distinct from a blog post, not a recolored copy of it.
   One consistent --magenta accent for every card here (issues aren't
   categorized the way posts are), which itself reinforces "this is the
   newsletter" as its own visual identity. */
.newsletter-card {
  position: relative;
  background: var(--charcoal-raised);
  border: 1px solid var(--line);
  border-left: 4px solid var(--magenta);
  border-radius: var(--radius);
  padding: 1.25rem 1.5rem;
}

@media (prefers-reduced-motion: no-preference) {
  .newsletter-card {
    transition: transform 0.3s ease, box-shadow 0.3s ease;
  }
}

.newsletter-card-date {
  font-family: var(--font-head);
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--magenta);
  margin: 0 0 0.3rem;
}

.newsletter-card-title {
  font-size: 1.05rem;
  margin: 0 0 0.35rem;
}

.newsletter-card-summary {
  font-size: 0.9rem;
  color: var(--text-secondary);
  margin: 0;
}

.newsletter-card-featured {
  border-left-width: 6px;
}

.newsletter-card-featured .newsletter-card-title {
  font-size: 1.25rem;
}

.newsletter-signup {
  display: flex;
  flex-direction: column;
  gap: 1.25rem;
  background: var(--charcoal-raised);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 1.75rem;
}

.newsletter-signup-copy h3 {
  font-size: 1.2rem;
  margin: 0 0 0.35rem;
}

.newsletter-signup-copy p {
  color: var(--text-secondary);
  font-size: 0.92rem;
  margin: 0;
}

.newsletter-form {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

/* Setting display: flex above overrides the browser's default [hidden] {
   display: none } rule at equal specificity (author styles always beat the
   UA stylesheet) — confirmed via testing that without this, initNewsletter
   Signup's form.hidden = true left the form still visibly laid out
   alongside the confirmation message instead of replacing it. */
.newsletter-form[hidden] {
  display: none;
}

.newsletter-form input[type="email"] {
  width: 100%;
  font: inherit;
  font-size: 0.95rem;
  color: var(--white);
  background: var(--charcoal-deep);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 0.75rem 1rem;
}

.newsletter-form input[type="email"]::placeholder {
  color: var(--text-secondary);
}

.newsletter-form input[type="email"]:focus-visible {
  outline: 2px solid var(--magenta);
  outline-offset: 2px;
}

.newsletter-form .btn {
  align-self: flex-start;
}

.newsletter-confirmation {
  font-family: var(--font-head);
  font-weight: 600;
  color: var(--teal);
  margin: 0;
}

/* A fresh, unused breakpoint (not already used elsewhere in this file) —
   see this section's opening comment for why that matters. Puts the email
   field and Subscribe button on one row once there's room. */
@media (min-width: 560px) {
  .newsletter-form {
    flex-direction: row;
  }

  .newsletter-form input[type="email"] {
    flex: 1;
  }

  .newsletter-form .btn {
    flex-shrink: 0;
  }
}

/* Another fresh breakpoint — puts the copy and the form side by side once
   there's room for both without cramping either. */
@media (min-width: 780px) {
  .newsletter-signup {
    flex-direction: row;
    align-items: center;
    gap: 2.5rem;
  }

  .newsletter-signup-copy {
    flex: 1;
    max-width: 30ch;
  }

  .newsletter-form {
    flex: 1;
    max-width: 28rem;
  }
}

/* ==========================================================================
   Photo Gallery page (Stage 7a)

   A sixth full page, alongside Home, Leadership, Pitch Videos, Event
   Timeline, and Blog & Newsletter — same header/footer/color system, same
   one-time hero-entrance pattern as Pitch Videos/Blog (initGalleryEntrance
   mirrors initBlogEntrance), same scroll-triggered stagger reveal as
   Leadership/Blog (initGalleryReveal), and the same Flip shared-element
   modal technique as the Event Timeline's event detail view
   (initGalleryLightbox mirrors initEventDetailModal). Tiles are plain,
   fully-visible, normal-flow content by default; GSAP is layered on top,
   never required to browse or open a photo.
   ========================================================================== */

.gallery-page {
  position: relative;
  background: var(--charcoal);
  overflow: hidden;
}

.gallery-page::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background-image:
    radial-gradient(750px circle at 12% 0%, rgba(236, 32, 136, 0.16), transparent 60%),
    var(--bg-network);
  background-repeat: no-repeat, repeat;
  background-size: auto, 240px 240px;
  opacity: 0.5;
}

.gallery-hero,
.gallery-section {
  position: relative;
  z-index: 1;
}

/* `position: relative` added here as a real-bug fix — see the matching,
   fuller comment on .hiw-hero. Same missing-containment issue as
   .about-hero/.involved-hero/.faq-hero/.pitch-videos-hero/.blog-hero. */
.gallery-hero {
  position: relative;
  padding-block: 5rem 2.5rem;
  overflow: hidden;
}

.gallery-hero-flourish {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  opacity: 0;
  background: conic-gradient(
    from 0deg at 50% 30%,
    var(--magenta),
    var(--orange),
    var(--gold),
    var(--teal),
    var(--sky),
    var(--magenta)
  );
  filter: blur(70px);
  mix-blend-mode: screen;
}

.gallery-hero .container {
  position: relative;
  z-index: 1;
}

.gallery-hero h1 {
  font-size: clamp(1.9rem, 4.6vw, 3rem);
  max-width: 20ch;
}

.gl-reveal-word {
  display: inline-block;
  overflow: hidden;
  vertical-align: top;
}

.gl-reveal-word-inner {
  display: inline-block;
}

.gallery-hero-lede {
  color: var(--text-secondary);
  font-size: 1.15rem;
  max-width: 62ch;
  margin: 0;
}

.gallery-placeholder-note {
  display: inline-block;
  font-family: var(--font-head);
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--text-secondary);
  background: var(--charcoal-raised);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 0.4rem 0.9rem;
  margin: 1.25rem 0 0;
}

.gallery-section {
  padding-block: 1.5rem 5rem;
}

/* --------------------------------------------------------------------------
   Filter pills
   -------------------------------------------------------------------------- */

.gallery-filters {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
  margin: 0 0 2rem;
}

.gallery-filter-btn {
  font-family: var(--font-head);
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--text-secondary);
  background: var(--charcoal-raised);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 0.55rem 1.15rem;
  cursor: pointer;
}

@media (prefers-reduced-motion: no-preference) {
  .gallery-filter-btn {
    transition: background 0.2s ease, border-color 0.2s ease, color 0.2s ease;
  }
}

.gallery-filter-btn:hover {
  border-color: var(--magenta);
  color: var(--white);
}

.gallery-filter-btn.is-active {
  background: var(--magenta);
  border-color: var(--magenta);
  color: var(--white);
}

/* --------------------------------------------------------------------------
   Bento masonry grid — grid-template-columns and each tile's grid-column/
   grid-row are applied by initGalleryLayout (js/script.js) as inline
   styles at min-width: 640px/1000px, not by a stylesheet rule here. See
   that function's comment for why: a stylesheet-based version of this
   exact kind of grid (multi-track-spanning tiles in a repeat()-column
   grid) was confirmed, during Stage 6a's Blog page work, to collapse
   columns to 0px, while the identical placement applied inline always
   sized correctly. Below min-width: 640px this stays a plain
   single-column stack (no spanning involved, so no risk there) — each
   tile's own aspect-ratio (by data-size) is what gives that single column
   its masonry-like varied rhythm.
   -------------------------------------------------------------------------- */

.gallery-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1rem;
}

.gallery-tile {
  position: relative;
  overflow: hidden;
  border-radius: var(--radius);
  border: 1px solid var(--line);
  background: var(--charcoal-raised);
  aspect-ratio: 4 / 3;
}

@media (prefers-reduced-motion: no-preference) {
  .gallery-tile {
    transition: border-color 0.3s ease, box-shadow 0.3s ease;
  }
}

.gallery-tile[data-size="tall"] {
  aspect-ratio: 3 / 4;
}

.gallery-tile[data-size="large"] {
  aspect-ratio: 1 / 1;
}

.gallery-tile[data-size="wide"] {
  aspect-ratio: 16 / 9;
}

.gallery-tile-teal { --card-accent: var(--teal); }
.gallery-tile-sky { --card-accent: var(--sky); }
.gallery-tile-orange { --card-accent: var(--orange); }
.gallery-tile-gold { --card-accent: var(--gold); }

/* --------------------------------------------------------------------------
   Tile button / photo — a div[role="button"], not a real <button>, so it
   can legally contain the real nested close/prev/next <button>s that only
   ever appear once it becomes the lightbox (see the comment on this
   markup in gallery.html).
   -------------------------------------------------------------------------- */

.gallery-tile-button {
  position: relative;
  display: block;
  width: 100%;
  height: 100%;
  cursor: pointer;
  overflow: hidden;
  border-radius: inherit;
}

.gallery-tile-button img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  /* Same light desaturation as the Impact/Our Story photos (a brand
     treatment, not a "these are placeholders" signal) — full color is
     restored on hover (fine pointer) or always on touch, matching
     Leadership's card photos. */
  filter: grayscale(0.15) saturate(0.9);
}

@media (prefers-reduced-motion: no-preference) {
  .gallery-tile-button img {
    transition: transform 0.4s ease, filter 0.4s ease;
  }
}

/* Hidden until this tile becomes the lightbox (.is-lightbox-open below) —
   base CSS never shows these, so a GSAP/Flip CDN failure just means
   opening one is instant rather than Flip-morphed, never stuck
   half-visible. */
.gallery-lightbox-close,
.gallery-lightbox-prev,
.gallery-lightbox-next,
.gallery-lightbox-caption {
  display: none;
}

.gallery-tile-button.is-lightbox-open .gallery-lightbox-close,
.gallery-tile-button.is-lightbox-open .gallery-lightbox-prev,
.gallery-tile-button.is-lightbox-open .gallery-lightbox-next,
.gallery-tile-button.is-lightbox-open .gallery-lightbox-caption {
  display: flex;
}

.gallery-lightbox-close,
.gallery-lightbox-prev,
.gallery-lightbox-next {
  position: absolute;
  z-index: 2;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  border: 1px solid var(--line);
  background: var(--charcoal-deep);
  color: var(--white);
  cursor: pointer;
}

.gallery-lightbox-close svg,
.gallery-lightbox-prev svg,
.gallery-lightbox-next svg {
  width: 1.1rem;
  height: 1.1rem;
}

.gallery-lightbox-close {
  top: 1rem;
  right: 1rem;
  width: 2.25rem;
  height: 2.25rem;
}

.gallery-lightbox-prev,
.gallery-lightbox-next {
  top: 50%;
  transform: translateY(-50%);
  width: 2.75rem;
  height: 2.75rem;
}

.gallery-lightbox-prev {
  left: 1rem;
}

.gallery-lightbox-next {
  right: 1rem;
}

.gallery-lightbox-caption {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  margin: 0;
  padding: 0.9rem 3.75rem;
  font-family: var(--font-head);
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--white);
  background: linear-gradient(to top, rgba(0, 0, 0, 0.7), transparent);
  text-align: center;
  align-items: flex-end;
  justify-content: center;
}

/* The expanded state: position: fixed and centered, well above the site
   header's own z-index: 50 so the whole page sits behind the dimmed
   backdrop. Redeclares border-color/box-shadow (properties the plain
   hover state and the (hover: none) touch-equivalent rule earlier in this
   file also set) and is placed after both, same equal-specificity-plus-
   later-in-file technique used for the Event Timeline/Blog modals. */
.gallery-tile-button.is-lightbox-open {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  z-index: 56;
  width: min(1100px, 92vw);
  height: min(760px, 88vh);
  cursor: default;
  border-radius: 16px;
  background: var(--charcoal-deep);
  border-color: var(--card-accent, var(--magenta));
  box-shadow: 0 30px 70px -20px rgba(0, 0, 0, 0.6), 0 0 0 2px var(--card-accent, var(--magenta));
}

.gallery-tile-button.is-lightbox-open img {
  object-fit: contain;
  filter: none;
}

/* --------------------------------------------------------------------------
   Backdrop
   -------------------------------------------------------------------------- */

.gallery-lightbox-overlay {
  position: fixed;
  inset: 0;
  z-index: 55;
  background: rgba(8, 6, 10, 0.78);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}

/* ==========================================================================
   About page (Stage 8a)

   A seventh full page, alongside Home, Leadership, Pitch Videos, Event
   Timeline, Blog & Newsletter, and the Photo Gallery — same header/footer/
   color system, same one-time hero-entrance pattern as Pitch Videos/Blog/
   Gallery (initAboutEntrance mirrors initGalleryEntrance), and the same
   scroll-triggered stagger reveal as Leadership/Blog/Gallery
   (initAboutReveal). Content is real chapter history, not mock/placeholder
   like several of the other pages — see the comment on .about-story in
   about.html.
   ========================================================================== */

.about-page {
  position: relative;
  background: var(--charcoal);
  overflow: hidden;
}

.about-page::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background-image:
    radial-gradient(750px circle at 88% 0%, rgba(236, 32, 136, 0.16), transparent 60%),
    var(--bg-network);
  background-repeat: no-repeat, repeat;
  background-size: auto, 240px 240px;
  opacity: 0.5;
}

.about-hero,
.about-story,
.about-values,
.about-partnership,
.about-legacy,
.about-links {
  position: relative;
  z-index: 1;
}

/* `position: relative` added here as a real-bug fix — see the matching,
   fuller comment on .hiw-hero: without it, .about-hero-flourish's
   `position: absolute; inset: 0` had no positioned ancestor to size
   against (sizing instead against the page's initial containing block,
   several times taller than this section), and this section's own
   `overflow: hidden` never clipped it, since that only clips descendants
   actually positioned relative to it. Invisible in the common case
   (the flourish already fades to opacity: 0), but any time that fade
   doesn't fully complete, the oversized, misplaced element shows through
   well past the hero. */
.about-hero {
  position: relative;
  padding-block: 5rem 2.5rem;
  overflow: hidden;
}

.about-hero-flourish {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  opacity: 0;
  background: conic-gradient(
    from 0deg at 50% 30%,
    var(--magenta),
    var(--teal),
    var(--sky),
    var(--orange),
    var(--gold),
    var(--magenta)
  );
  filter: blur(70px);
  mix-blend-mode: screen;
}

.about-hero .container {
  position: relative;
  z-index: 1;
}

.about-hero h1 {
  font-size: clamp(1.9rem, 4.6vw, 3rem);
  max-width: 24ch;
}

.ab-reveal-word {
  display: inline-block;
  overflow: hidden;
  vertical-align: top;
}

.ab-reveal-word-inner {
  display: inline-block;
}

.about-hero-lede {
  color: var(--text-secondary);
  font-size: 1.15rem;
  max-width: 62ch;
  margin: 0;
}

/* --------------------------------------------------------------------------
   Founding story
   -------------------------------------------------------------------------- */

.about-story {
  padding-block: 1.5rem 4rem;
}

.about-story-copy {
  max-width: 68ch;
}

.about-story-copy h2 {
  font-size: clamp(1.4rem, 3vw, 1.8rem);
  margin: 0 0 1.25rem;
}

.about-story-copy p {
  color: var(--text-secondary);
  font-size: 1.05rem;
  line-height: 1.7;
}

.about-story-copy p:last-child {
  margin-bottom: 0;
}

/* --------------------------------------------------------------------------
   Mission & values
   -------------------------------------------------------------------------- */

.about-values {
  padding-block: 3rem 4rem;
}

.about-values-lede {
  color: var(--text-secondary);
  font-size: 1.05rem;
  max-width: 68ch;
  margin: 0 0 2.5rem;
}

.about-values-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1.25rem;
}

.about-value-card {
  position: relative;
  background: var(--charcoal-raised);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 1.75rem 1.5rem;
}

@media (prefers-reduced-motion: no-preference) {
  .about-value-card {
    transition: transform 0.3s ease, box-shadow 0.3s ease, border-color 0.3s ease;
  }
}

.about-value-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.75rem;
  height: 2.75rem;
  border-radius: 50%;
  margin: 0 0 1rem;
  color: var(--card-accent, var(--magenta));
  background: color-mix(in srgb, var(--card-accent, var(--magenta)) 16%, transparent);
}

.about-value-icon svg {
  width: 1.5rem;
  height: 1.5rem;
}

.about-value-card h3 {
  font-size: 1.1rem;
  margin: 0 0 0.4rem;
}

.about-value-card p {
  font-size: 0.92rem;
  color: var(--text-secondary);
  margin: 0;
}

.about-value-magenta { --card-accent: var(--magenta); }
.about-value-teal { --card-accent: var(--teal); }
.about-value-sky { --card-accent: var(--sky); }
.about-value-orange { --card-accent: var(--orange); }
.about-value-gold { --card-accent: var(--gold); }

/* --------------------------------------------------------------------------
   ENG 108 partnership callout
   -------------------------------------------------------------------------- */

.about-partnership {
  padding-block: 1rem 4rem;
}

.about-partnership-band {
  position: relative;
  max-width: 72ch;
  background: var(--charcoal-raised);
  border: 1px solid var(--line);
  border-left: 4px solid var(--sky);
  border-radius: var(--radius);
  padding: 2rem 2.25rem;
}

.about-partnership-eyebrow {
  font-family: var(--font-head);
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--sky);
  margin: 0 0 0.5rem;
}

.about-partnership-band h2 {
  font-size: clamp(1.3rem, 2.8vw, 1.6rem);
  margin: 0 0 0.9rem;
}

.about-partnership-band p:last-child {
  color: var(--text-secondary);
  font-size: 1.02rem;
  line-height: 1.7;
  margin: 0;
}

/* --------------------------------------------------------------------------
   Built to Last
   -------------------------------------------------------------------------- */

.about-legacy {
  padding-block: 1rem 4rem;
}

.about-legacy-copy {
  max-width: 68ch;
}

.about-legacy-copy h2 {
  font-size: clamp(1.4rem, 3vw, 1.8rem);
  margin: 0 0 1.25rem;
}

.about-legacy-copy p {
  color: var(--text-secondary);
  font-size: 1.05rem;
  line-height: 1.7;
  margin: 0;
}

/* --------------------------------------------------------------------------
   Cross-links
   -------------------------------------------------------------------------- */

.about-links {
  padding-block: 1rem 5.5rem;
}

.about-links-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1.25rem;
}

.about-link-card {
  background: var(--charcoal-raised);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 1.5rem;
}

@media (prefers-reduced-motion: no-preference) {
  .about-link-card {
    transition: transform 0.3s ease, box-shadow 0.3s ease, border-color 0.3s ease;
  }
}

.about-link-card h3 {
  font-size: 1.05rem;
  margin: 0 0 0.4rem;
}

.about-link-card p {
  font-size: 0.9rem;
  color: var(--text-secondary);
  margin: 0 0 1rem;
}

/* ==========================================================================
   FAQ page (Stage 8b)

   An eighth full page, alongside Home, Leadership, Pitch Videos, Event
   Timeline, Blog & Newsletter, the Photo Gallery, and About — same header/
   footer/color system, same one-time hero-entrance pattern as the other
   pages' own hero sections (initFaqEntrance), same scroll-triggered
   stagger reveal (initFaqReveal), and the same category-filter-pill
   interaction as the Gallery page, generalized into the shared
   initFilterGroup (js/script.js) rather than duplicated. A plain vertical
   list of cards, not a grid — no multi-track-spanning tiles involved, so
   none of the stylesheet-grid cascade risk documented elsewhere in this
   file applies to anything below.
   ========================================================================== */

.faq-page {
  position: relative;
  background: var(--charcoal);
  overflow: hidden;
}

.faq-page::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background-image:
    radial-gradient(750px circle at 12% 0%, rgba(236, 32, 136, 0.16), transparent 60%),
    var(--bg-network);
  background-repeat: no-repeat, repeat;
  background-size: auto, 240px 240px;
  opacity: 0.5;
}

.faq-hero,
.faq-section {
  position: relative;
  z-index: 1;
}

/* `position: relative` added here as a real-bug fix — see the matching,
   fuller comment on .hiw-hero. Same missing-containment issue as
   .about-hero/.involved-hero/.gallery-hero/.pitch-videos-hero/.blog-hero. */
.faq-hero {
  position: relative;
  padding-block: 5rem 2.5rem;
  overflow: hidden;
}

.faq-hero-flourish {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  opacity: 0;
  background: conic-gradient(
    from 0deg at 50% 30%,
    var(--magenta),
    var(--sky),
    var(--teal),
    var(--orange),
    var(--gold),
    var(--magenta)
  );
  filter: blur(70px);
  mix-blend-mode: screen;
}

.faq-hero .container {
  position: relative;
  z-index: 1;
}

.faq-hero h1 {
  font-size: clamp(1.9rem, 4.6vw, 3rem);
  max-width: 22ch;
}

.fq-reveal-word {
  display: inline-block;
  overflow: hidden;
  vertical-align: top;
}

.fq-reveal-word-inner {
  display: inline-block;
}

.faq-hero-lede {
  color: var(--text-secondary);
  font-size: 1.15rem;
  max-width: 62ch;
  margin: 0;
}

.faq-section {
  padding-block: 1.5rem 5.5rem;
}

/* --------------------------------------------------------------------------
   Filter pills — visually identical to the Gallery page's own
   .gallery-filter-btn (independently named/declared per this file's
   established per-page class-naming convention), driven by the same
   shared initFilterGroup behind the scenes.
   -------------------------------------------------------------------------- */

.faq-filters {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
  margin: 0 0 2rem;
}

.faq-filter-btn {
  font-family: var(--font-head);
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--text-secondary);
  background: var(--charcoal-raised);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 0.55rem 1.15rem;
  cursor: pointer;
}

@media (prefers-reduced-motion: no-preference) {
  .faq-filter-btn {
    transition: background 0.2s ease, border-color 0.2s ease, color 0.2s ease;
  }
}

.faq-filter-btn:hover {
  border-color: var(--magenta);
  color: var(--white);
}

.faq-filter-btn.is-active {
  background: var(--magenta);
  border-color: var(--magenta);
  color: var(--white);
}

/* --------------------------------------------------------------------------
   Accordion list
   -------------------------------------------------------------------------- */

.faq-list {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.faq-item {
  background: var(--charcoal-raised);
  border: 1px solid var(--line);
  border-left: 4px solid var(--card-accent, var(--magenta));
  border-radius: var(--radius);
  overflow: hidden;
}

@media (prefers-reduced-motion: no-preference) {
  .faq-item {
    transition: box-shadow 0.3s ease;
  }
}

/* Category -> accent mapping — every question in a category shares a
   color, same taxonomy idea as the Blog post categories and the Timeline
   event colors. */
.faq-item-general { --card-accent: var(--teal); }
.faq-item-getting-started { --card-accent: var(--sky); }
.faq-item-competition { --card-accent: var(--orange); }

.faq-question-heading {
  margin: 0;
}

.faq-question {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.25rem;
  width: 100%;
  font: inherit;
  text-align: left;
  color: inherit;
  background: none;
  border: 0;
  padding: 1.25rem 1.5rem;
  cursor: pointer;
}

.faq-question-text {
  font-family: var(--font-head);
  font-weight: 600;
  font-size: 1.02rem;
}

/* A plus that rotates 45deg into an X when open — two bars (one
   horizontal, one vertical) forming the plus; rotating the whole icon,
   rather than each bar individually, is what turns it into an X instead
   of a minus sign. */
.faq-toggle-icon {
  position: relative;
  display: inline-block;
  width: 1.2rem;
  height: 1.2rem;
  flex-shrink: 0;
  color: var(--card-accent, var(--magenta));
}

.faq-toggle-icon span {
  position: absolute;
  top: 50%;
  left: 50%;
  background: currentColor;
  transform: translate(-50%, -50%);
}

.faq-toggle-icon span:first-child {
  width: 100%;
  height: 2px;
}

.faq-toggle-icon span:last-child {
  width: 2px;
  height: 100%;
}

@media (prefers-reduced-motion: no-preference) {
  .faq-toggle-icon {
    transition: transform 0.3s ease;
  }
}

.faq-question[aria-expanded="true"] .faq-toggle-icon {
  transform: rotate(45deg);
}

/* Animated by initFaqAccordion (js/script.js) between height: 0 and a
   measured pixel value, settling on height: auto once open — see that
   function's comment. No padding lives directly on this element on
   purpose: with box-sizing: border-box, padding here would stop it from
   truly collapsing to a height of 0 (the padding on the <p> inside is what
   provides the visual spacing instead). */
.faq-answer {
  height: 0;
  overflow: hidden;
  opacity: 0;
}

.faq-answer p {
  margin: 0;
  padding: 0 1.5rem 1.25rem;
  color: var(--text-secondary);
  font-size: 0.98rem;
  line-height: 1.65;
}

/* Flags the two not-yet-confirmed answers as clearly provisional — not
   just plain body text a reader could mistake for a real answer.
   Selector is .faq-answer p.faq-answer-tbd rather than the bare class:
   .faq-answer p above already sets color at (class + descendant tag)
   specificity, which beats a lone class selector regardless of source
   order — confirmed via getComputedStyle that the bare-class version left
   this rendering in the ordinary --text-secondary color instead of gold. */
.faq-answer p.faq-answer-tbd {
  font-style: italic;
  color: var(--gold);
}

/* ==========================================================================
   Get Involved page (Stage 9a)

   A ninth full page, alongside Home, Leadership, Pitch Videos, Event
   Timeline, Blog & Newsletter, the Photo Gallery, About, and FAQ — same
   header/footer/color system, same one-time hero-entrance pattern as the
   other pages' own hero sections (initInvolvedEntrance), and the same
   ScrollTrigger.batch stagger reveal (initInvolvedReveal). A plain 3-card
   grid and two simple lists — no multi-track-spanning tiles anywhere, so
   none of the stylesheet-grid cascade risk documented elsewhere in this
   file applies to anything below.
   ========================================================================== */

.involved-page {
  position: relative;
  background: var(--charcoal);
  overflow: hidden;
}

.involved-page::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background-image:
    radial-gradient(750px circle at 88% 0%, rgba(236, 32, 136, 0.16), transparent 60%),
    var(--bg-network);
  background-repeat: no-repeat, repeat;
  background-size: auto, 240px 240px;
  opacity: 0.5;
}

.involved-hero,
.involved-paths,
.involved-signup,
.involved-links {
  position: relative;
  z-index: 1;
}

/* `position: relative` added here as a real-bug fix — see the matching,
   fuller comment on .hiw-hero. Same missing-containment issue as
   .about-hero/.gallery-hero/.faq-hero/.pitch-videos-hero/.blog-hero. */
.involved-hero {
  position: relative;
  padding-block: 5rem 2.5rem;
  overflow: hidden;
}

.involved-hero-flourish {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  opacity: 0;
  background: conic-gradient(
    from 0deg at 50% 30%,
    var(--magenta),
    var(--teal),
    var(--sky),
    var(--orange),
    var(--gold),
    var(--magenta)
  );
  filter: blur(70px);
  mix-blend-mode: screen;
}

.involved-hero .container {
  position: relative;
  z-index: 1;
}

.involved-hero h1 {
  font-size: clamp(1.9rem, 4.6vw, 3rem);
  max-width: 20ch;
}

.iv-reveal-word {
  display: inline-block;
  overflow: hidden;
  vertical-align: top;
}

.iv-reveal-word-inner {
  display: inline-block;
}

.involved-hero-lede {
  color: var(--text-secondary);
  font-size: 1.15rem;
  max-width: 62ch;
  margin: 0;
}

/* --------------------------------------------------------------------------
   Three pathway cards
   -------------------------------------------------------------------------- */

.involved-paths {
  padding-block: 1.5rem 4rem;
}

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

.involved-card {
  background: var(--charcoal-raised);
  border: 1px solid var(--line);
  border-left: 4px solid var(--card-accent, var(--magenta));
  border-radius: var(--radius);
  padding: 1.75rem 1.5rem;
}

@media (prefers-reduced-motion: no-preference) {
  .involved-card {
    transition: transform 0.3s ease, box-shadow 0.3s ease;
  }
}

.involved-card-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.75rem;
  height: 2.75rem;
  border-radius: 50%;
  margin: 0 0 1rem;
  color: var(--card-accent, var(--magenta));
  background: color-mix(in srgb, var(--card-accent, var(--magenta)) 16%, transparent);
}

.involved-card-icon svg {
  width: 1.5rem;
  height: 1.5rem;
}

.involved-card h3 {
  font-size: 1.1rem;
  margin: 0 0 0.5rem;
}

.involved-card p {
  font-size: 0.95rem;
  color: var(--text-secondary);
  line-height: 1.6;
  margin: 0;
}

.involved-card p + p {
  margin-top: 0.75rem;
}

.involved-card-tbd {
  font-style: italic;
  color: var(--gold);
  font-size: 0.85rem;
}

.involved-card-teal { --card-accent: var(--teal); }
.involved-card-sky { --card-accent: var(--sky); }
.involved-card-orange { --card-accent: var(--orange); }

/* --------------------------------------------------------------------------
   Sign Up / Compete CTA — the primary conversion moment on this page,
   visually distinct from the pathway cards above: full-width magenta
   band rather than a charcoal card with an accent border.
   -------------------------------------------------------------------------- */

.involved-signup {
  padding-block: 1rem 4rem;
}

.involved-signup-band {
  position: relative;
  overflow: hidden;
  text-align: center;
  background: linear-gradient(135deg, var(--magenta-darker), var(--magenta-dark));
  border-radius: var(--radius);
  padding: 3rem 2rem;
}

.involved-signup-band h2 {
  font-size: clamp(1.5rem, 3.4vw, 2.1rem);
  margin: 0 0 0.75rem;
}

.involved-signup-band p {
  color: rgba(255, 255, 255, 0.85);
  font-size: 1.05rem;
  max-width: 46ch;
  margin: 0 auto 1.75rem;
}

.involved-signup-btn {
  font-size: 1.05rem;
  padding-block: 0.9rem;
  padding-inline: 2rem;
}

/* --------------------------------------------------------------------------
   Cross-links
   -------------------------------------------------------------------------- */

.involved-links {
  padding-block: 1rem 5.5rem;
}

.involved-links-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1.25rem;
}

.involved-link-card {
  background: var(--charcoal-raised);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 1.5rem;
}

@media (prefers-reduced-motion: no-preference) {
  .involved-link-card {
    transition: transform 0.3s ease, box-shadow 0.3s ease, border-color 0.3s ease;
  }
}

.involved-link-card h3 {
  font-size: 1.05rem;
  margin: 0 0 0.4rem;
}

.involved-link-card p {
  font-size: 0.9rem;
  color: var(--text-secondary);
  margin: 0 0 1rem;
}

/* ==========================================================================
   Live Countdown widget (Stage 10a) — Home + Timeline
   ========================================================================== */

.countdown-section {
  padding-block: 1rem 3.5rem;
}

.countdown-widget {
  position: relative;
  background: var(--charcoal-raised);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
  padding: 2.5rem 1.5rem 2.75rem;
  text-align: center;
  max-width: 640px;
  margin-inline: auto;
}

/* Ambient background glow — a static, subtle presence at rest (including
   under reduced motion); initCountdownWidget only ever adds .is-pulsing
   (and only ever sets the --cd-pulse-* custom properties it reads) when
   motion is allowed, so reduced-motion users see this fixed low glow and
   nothing that moves. */
.countdown-glow {
  position: absolute;
  inset: -15%;
  z-index: 0;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(236, 32, 136, 0.32), rgba(236, 32, 136, 0) 68%);
  filter: blur(48px);
  opacity: 0.3;
  pointer-events: none;
}

@media (prefers-reduced-motion: no-preference) {
  .countdown-glow.is-pulsing {
    animation: countdown-pulse var(--cd-pulse-duration, 3.2s) ease-in-out infinite;
  }

  @keyframes countdown-pulse {
    0%,
    100% {
      opacity: var(--cd-pulse-min, 0.2);
      transform: scale(1);
    }
    50% {
      opacity: var(--cd-pulse-max, 0.45);
      transform: scale(1.07);
    }
  }
}

.countdown-inner {
  position: relative;
  z-index: 1;
}

.countdown-label {
  font-size: clamp(1.15rem, 2.6vw, 1.5rem);
  margin: 0 0 0.25rem;
}

.countdown-event-name {
  color: var(--magenta);
}

/* Ring + split-flap cluster: a square stage so the ring reads as a true
   circle wrapped around the digits regardless of viewport width, with the
   flap grid sized down from it so the ring shows as a halo around (rather
   than tight against) the digits. */
.countdown-display {
  position: relative;
  width: clamp(230px, 52vw, 320px);
  aspect-ratio: 1;
  margin: 1.85rem auto 0;
}

.countdown-ring {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 0;
}

.countdown-ring-track {
  fill: none;
  stroke: var(--line);
  stroke-width: 6;
  transform-origin: 50% 50%;
  transform: rotate(-90deg);
}

.countdown-ring-progress {
  fill: none;
  stroke-width: 6;
  stroke-linecap: round;
  transform-origin: 50% 50%;
  transform: rotate(-90deg);
}

@media (prefers-reduced-motion: no-preference) {
  /* Smooths the once-a-second jump between values; under reduced motion
     this rule doesn't apply at all, so JS's per-tick strokeDashoffset
     writes land instantly, per the brief's "ring updates without
     animated transition" requirement. */
  .countdown-ring-progress {
    transition: stroke-dashoffset 0.6s linear;
  }
}

.countdown-ring-milestone {
  fill: var(--charcoal-deep);
  stroke: var(--text-secondary);
  stroke-width: 2;
}

.countdown-ring-milestone.is-reached {
  fill: var(--gold);
  stroke: var(--gold);
}

@media (prefers-reduced-motion: no-preference) {
  .countdown-ring-milestone {
    transition: fill 0.4s ease, stroke 0.4s ease;
  }
}

.countdown-flaps {
  position: absolute;
  inset: 0;
  z-index: 1;
  display: grid;
  grid-template-columns: repeat(2, auto);
  gap: 1rem 1.2rem;
  align-content: center;
  justify-content: center;
  justify-items: center;
}

/* [hidden] bug (established elsewhere on this site — see .newsletter-form
   in css/styles.css): an authored `display` value beats the browser's own
   [hidden]{display:none} at equal specificity, so both toggle targets need
   an explicit override. */
.countdown-flaps[hidden],
.countdown-complete[hidden] {
  display: none;
}

.cd-group {
  display: flex;
  flex-direction: column;
  align-items: center;
  font-size: clamp(1.3rem, 4.8vw, 1.9rem);
}

.cd-group-label {
  margin-top: 0.5rem;
  font-family: var(--font-head);
  font-size: 0.62rem;
  font-weight: 600;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--text-secondary);
}

.cd-digits {
  display: flex;
  gap: 0.15em;
}

.cd-digit {
  position: relative;
  width: 0.85em;
  height: 1.2em;
  background: var(--charcoal-deep);
  border: 1px solid var(--line);
  border-radius: 0.14em;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.4);
  overflow: hidden;
  perspective: 6em;
}

/* The physical "split flap" seam. */
.cd-digit::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 50%;
  height: 1px;
  background: rgba(0, 0, 0, 0.55);
  transform: translateY(-50%);
  z-index: 2;
  pointer-events: none;
}

.cd-digit-inner {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  font-family: var(--font-head);
  font-weight: 700;
  font-size: 0.62em;
  color: var(--white);
  transform-origin: 50% 50%;
}

/* Flip animation — only ever triggered by JS (initCountdownWidget) when a
   digit's value actually changes AND prefers-reduced-motion is off; under
   reduced motion these classes are never added, so the digit's textContent
   just updates in place with no rotation, per the brief's fallback
   requirement. rotateX(-90deg) then instantly rotateX(90deg) (no
   transition) then back to 0 simulates a single flap swinging down through
   its own edge — the two-stage swap happens once the leaf is edge-on
   (heavily foreshortened + opacity-dimmed), which is what hides the
   mid-flip text substitution. */
.cd-digit.is-animating .cd-digit-inner {
  transition: transform 0.15s cubic-bezier(0.4, 0, 1, 1), opacity 0.15s cubic-bezier(0.4, 0, 1, 1);
}

.cd-digit.is-out .cd-digit-inner {
  transform: rotateX(-90deg);
  opacity: 0.35;
}

.cd-digit.is-in-start .cd-digit-inner {
  transition: none;
  transform: rotateX(90deg);
  opacity: 0.35;
}

.countdown-complete {
  position: absolute;
  inset: 0;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
}

.countdown-complete-message {
  margin: 0;
  padding-inline: 1rem;
  font-family: var(--font-head);
  font-weight: 700;
  font-size: clamp(1.05rem, 3.4vw, 1.5rem);
  color: var(--gold);
}

@media (max-width: 420px) {
  .countdown-widget {
    padding: 2rem 1rem 2.25rem;
  }

  .countdown-flaps {
    gap: 0.7rem 0.85rem;
  }
}

/* ==========================================================================
   Site-Wide Search (Stage 11a)
   ========================================================================== */

.search-trigger {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  background: none;
  border: 0;
  color: var(--white);
  cursor: pointer;
  padding: 0.4rem;
  border-radius: var(--radius);
  flex-shrink: 0;
}

.search-trigger svg {
  width: 19px;
  height: 19px;
}

.search-trigger-hint {
  font-family: var(--font-head);
  font-size: 0.72rem;
  color: var(--text-secondary);
  border: 1px solid var(--navy-line);
  border-radius: 3px;
  padding: 0.05rem 0.4rem;
  line-height: 1.4;
}

.search-trigger:hover,
.search-trigger:focus-visible {
  color: var(--gold);
}

@media (max-width: 640px) {
  .search-trigger-hint {
    display: none;
  }
}

/* --------------------------------------------------------------------------
   Overlay
   -------------------------------------------------------------------------- */

.search-overlay {
  position: fixed;
  inset: 0;
  z-index: 300;
  display: flex;
  justify-content: center;
  align-items: flex-start;
  padding: 10vh 1.25rem 2rem;
}

/* [hidden] bug (established elsewhere on this site — see .newsletter-form/
   .countdown-flaps in this file): an authored `display` value beats the
   browser's own [hidden]{display:none} at equal specificity. */
.search-overlay[hidden] {
  display: none;
}

.search-overlay-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(13, 11, 16, 0.82);
}

.search-panel {
  position: relative;
  z-index: 1;
  width: 100%;
  max-width: 600px;
  max-height: 76vh;
  display: flex;
  flex-direction: column;
  background: var(--charcoal-raised);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: 0 30px 70px -20px rgba(0, 0, 0, 0.6);
  overflow: hidden;
}

@media (max-width: 640px) {
  .search-overlay {
    padding: 0;
  }

  .search-panel {
    max-width: none;
    width: 100%;
    height: 100%;
    max-height: none;
    border-radius: 0;
    border: 0;
  }
}

.search-input-row {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 1rem 1.25rem;
  border-bottom: 1px solid var(--line);
  flex-shrink: 0;
}

.search-input-icon {
  width: 20px;
  height: 20px;
  color: var(--text-secondary);
  flex-shrink: 0;
}

.search-input {
  flex: 1;
  min-width: 0;
  background: none;
  border: 0;
  color: var(--white);
  font-family: var(--font-body);
  font-size: 1.05rem;
  outline: none;
}

.search-input::placeholder {
  color: var(--text-secondary);
}

.search-close-btn {
  background: none;
  border: 0;
  color: var(--text-secondary);
  width: 32px;
  height: 32px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius);
  cursor: pointer;
  flex-shrink: 0;
}

.search-close-btn svg {
  width: 16px;
  height: 16px;
}

.search-close-btn:hover,
.search-close-btn:focus-visible {
  color: var(--white);
  background: rgba(255, 255, 255, 0.08);
}

.search-results {
  overflow-y: auto;
  padding: 0.5rem 0;
}

.search-empty-state {
  padding: 2.5rem 1.25rem;
  text-align: center;
  color: var(--text-secondary);
  margin: 0;
}

.search-group + .search-group {
  margin-top: 0.5rem;
}

.search-group-label {
  margin: 0;
  padding: 0.6rem 1.25rem 0.35rem;
  font-family: var(--font-head);
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-secondary);
}

.search-result {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  padding: 0.65rem 1.25rem 0.65rem 1.1rem;
  border-left: 3px solid var(--card-accent, var(--magenta));
  text-decoration: none;
  color: var(--white);
  cursor: pointer;
}

.search-result-teal {
  --card-accent: var(--teal);
}
.search-result-sky {
  --card-accent: var(--sky);
}
.search-result-orange {
  --card-accent: var(--orange);
}
.search-result-gold {
  --card-accent: var(--gold);
}

.search-result:hover,
.search-result.is-active {
  background: rgba(255, 255, 255, 0.07);
}

.search-result-title {
  font-family: var(--font-head);
  font-weight: 600;
  font-size: 0.98rem;
}

.search-result-subtitle {
  font-size: 0.85rem;
  color: var(--text-secondary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.search-result-meta {
  font-size: 0.72rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--card-accent, var(--magenta));
}

.search-hint {
  display: flex;
  gap: 1.1rem;
  padding: 0.6rem 1.25rem;
  border-top: 1px solid var(--line);
  margin: 0;
  font-size: 0.72rem;
  color: var(--text-secondary);
  flex-shrink: 0;
}

.search-hint kbd {
  display: inline-block;
  padding: 0.05rem 0.35rem;
  border: 1px solid var(--line);
  border-radius: 3px;
  font-family: var(--font-body);
  font-size: 0.7rem;
  margin-right: 0.2rem;
}

@media (max-width: 640px) {
  .search-hint {
    display: none;
  }
}

/* --------------------------------------------------------------------------
   Landing highlight — the brief flash applied to whatever blog card/FAQ
   item/leadership card a search result (or any other #hash deep link)
   points at. `outline` rather than `box-shadow`: all three target element
   types (.blog-card/.leadership-card/.faq-item) have their own
   `overflow: hidden` for their thumbnail/photo corners, which would clip a
   box-shadow glow to almost nothing — outline is deliberately excluded
   from an element's own overflow-clipping in every browser, so it stays
   fully visible regardless. The static outline alone (no animation) is
   the reduced-motion state: fully visible, nothing moving. Under normal
   motion the pulse keyframes below take over for as long as the class is
   applied, settling by the time initSearchResultHighlight/
   highlightSearchTarget (js/script.js) removes it.
   -------------------------------------------------------------------------- */

.search-target-highlight {
  outline: 3px solid var(--gold);
  outline-offset: 3px;
}

@media (prefers-reduced-motion: no-preference) {
  .search-target-highlight {
    animation: search-highlight-pulse 1.8s ease-out;
  }

  @keyframes search-highlight-pulse {
    0% {
      outline-offset: 8px;
      outline-color: color-mix(in srgb, var(--gold) 60%, transparent);
    }
    100% {
      outline-offset: 3px;
      outline-color: var(--gold);
    }
  }
}

/* ==========================================================================
   FAQ page: content-load error state (Stage 12a) — shown only if
   content/faq.json fails to fetch/parse; see initFaqContent in
   js/script.js.
   ========================================================================== */

.faq-load-error {
  background: var(--charcoal-raised);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 1.5rem;
  color: var(--text-secondary);
  list-style: none;
}

/* ==========================================================================
   Leadership page: content-load error state (Stage 2a) — shown only if
   content/team.json fails to fetch/parse; see initTeamContent in
   js/script.js. Same treatment as .faq-load-error (Stage 1).
   ========================================================================== */

.leadership-load-error {
  background: var(--charcoal-raised);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 1.5rem;
  color: var(--text-secondary);
  list-style: none;
  grid-column: 1 / -1;
}

/* ==========================================================================
   Blog page: content-load error state (Stage 2b) — shown only if
   content/blog.json fails to fetch/parse; see initBlogContent in
   js/script.js. Same treatment as .faq-load-error/.leadership-load-error.
   ========================================================================== */

.blog-load-error {
  background: var(--charcoal-raised);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 1.5rem;
  color: var(--text-secondary);
  list-style: none;
  grid-column: 1 / -1;
}

/* ==========================================================================
   Blog post page (blog-post.html, Stage 2b) — a single template for every
   post; initBlogPostContent (js/script.js) fills it in from
   content/blog.json at runtime. --card-accent is set inline by that same
   function (the post's category color), same mechanism as every other
   card/page section on the site that uses it.
   ========================================================================== */

.blog-post-article {
  padding-block: 4rem 5rem;
}

/* Narrower than the site's usual 1200px .container: a comfortable reading
   width for prose, scoped to this page only. */
.blog-post-article .container {
  max-width: 760px;
}

.blog-post-back {
  margin-bottom: 2rem;
}

.blog-post-category {
  font-family: var(--font-head);
  font-size: 0.8rem;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--card-accent, var(--magenta));
  margin: 0 0 0.5rem;
}

.blog-post-title {
  font-size: clamp(1.8rem, 4vw, 2.6rem);
  margin: 0 0 0.6rem;
}

.blog-post-date {
  font-family: var(--font-head);
  font-size: 0.9rem;
  color: var(--text-secondary);
  margin: 0 0 2rem;
}

.blog-post-thumb {
  margin: 0 0 2.5rem;
  border-radius: var(--radius);
  overflow: hidden;
  aspect-ratio: 16 / 9;
}

.blog-post-thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.blog-post-body {
  font-size: 1.05rem;
  line-height: 1.7;
  color: var(--text-secondary);
}

.blog-post-body p {
  margin: 0 0 1.25em;
}

.blog-post-body p:last-child {
  margin-bottom: 0;
}

.blog-post-body strong {
  color: var(--white);
}

.blog-post-body a {
  color: var(--card-accent, var(--magenta));
}

.blog-post-body ul,
.blog-post-body ol {
  /* list-style: revert here (not on li below) is deliberate: list-style-
     type is inherited, and the site-wide ul/ol reset near the top of this
     file sets it to none directly ON the ul/ol element. Reverting on li
     only undoes an author rule targeting the li itself — the inherited
     "none" from this ul/ol still wins regardless, leaving bullets/numbers
     invisible. Reverting here, on the exact element the reset rule
     targets, is what actually restores the browser's default disc/
     decimal marker (confirmed live: the li-only revert rendered plain,
     marker-less indented lines). */
  list-style: revert;
  margin: 0 0 1.25em;
  padding-left: 1.4em;
}

.blog-post-body li {
  margin-bottom: 0.4em;
}

.blog-post-body h2,
.blog-post-body h3 {
  color: var(--white);
  margin: 1.75em 0 0.75em;
}

.blog-post-not-found {
  padding-block: 6rem;
  text-align: center;
}

.blog-post-not-found h1 {
  font-size: clamp(1.6rem, 3.5vw, 2.2rem);
  margin: 0 0 0.75rem;
}

.blog-post-not-found p {
  color: var(--text-secondary);
  margin: 0 0 2rem;
}

/* ==========================================================================
   Timeline page: content-load error state (Stage 2c) — shown only if
   content/timeline.json fails to fetch/parse; see initTimelineContent in
   js/script.js. Same treatment as .faq-load-error/.leadership-load-error/
   .blog-load-error.
   ========================================================================== */

.timeline-load-error {
  background: var(--charcoal-raised);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 1.5rem;
  color: var(--text-secondary);
  list-style: none;
}

/* Countdown widget: content-load error state (Stage 2c) — shown only if
   content/timeline.json fails to fetch/parse, on whichever page(s) the
   widget appears (Home, Timeline). Swapped in for the widget's normal
   .countdown-inner contents by initTimelineContent's catch(), so this
   only ever needs to read reasonably inside that card, not match its
   normal flap/ring layout. */
.countdown-load-error {
  margin: 0;
  padding: 1.5rem;
  color: var(--text-secondary);
  text-align: center;
}

/* ==========================================================================
   Pitch Videos page: content-load error state (Stage 2d) — shown only if
   content/pitch-videos.json fails to fetch/parse; see initPitchVideoContent
   in js/script.js. Same treatment as .faq-load-error/.leadership-load-error/
   .blog-load-error/.timeline-load-error.
   ========================================================================== */

.pitch-video-load-error {
  background: var(--charcoal-raised);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 1.5rem;
  color: var(--text-secondary);
  margin: 0;
  text-align: center;
}

/* ==========================================================================
   Gallery page: content-load error state (Stage 2e) — shown only if
   content/gallery.json fails to fetch/parse; see initGalleryContent in
   js/script.js. Same treatment as .faq-load-error/.leadership-load-error/
   .blog-load-error/.timeline-load-error/.pitch-video-load-error.
   ========================================================================== */

.gallery-load-error {
  background: var(--charcoal-raised);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 1.5rem;
  color: var(--text-secondary);
  list-style: none;
  grid-column: 1 / -1;
}

/* ==========================================================================
   About page: content-load error state (Stage 2f) — shown only if
   content/about.json fails to fetch/parse; see initAboutContent in
   js/script.js. Same treatment as .faq-load-error/.leadership-load-error/
   .blog-load-error/.timeline-load-error/.pitch-video-load-error/
   .gallery-load-error, just page-level (not per-item) since this page's
   whole prose content comes from one CMS document rather than a list.
   ========================================================================== */

.about-load-error {
  max-width: 68ch;
  margin: 0 auto 3rem;
  background: var(--charcoal-raised);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 1.5rem;
  color: var(--text-secondary);
  text-align: center;
}

/* ==========================================================================
   Home page: Top Teams content-load error state (Stage 2g) — shown only if
   content/home.json fails to fetch/parse, or its topTeams list doesn't
   contain exactly one 1st/2nd/3rd placement; see initHomeContent/
   renderTopTeamsPodium in js/script.js. Same treatment as
   .faq-load-error/.leadership-load-error/.blog-load-error/
   .timeline-load-error/.pitch-video-load-error/.gallery-load-error/
   .about-load-error — centered like .pitch-video-load-error/.about-load-error
   since the podium is a fixed 3-slot layout, not a grid of many items.
   ========================================================================== */

.top-teams-load-error {
  max-width: 40rem;
  margin: 0 auto;
  background: var(--charcoal-raised);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 1.5rem;
  color: var(--text-secondary);
  text-align: center;
}

/* ==========================================================================
   How It Works page (Stage 2h) — hero, intro/disclaimer, the animated
   stage journey, eligibility, evaluation criteria, the SDG grid + its
   Flip-driven detail dialog, and the resource-link cards.
   ========================================================================== */

/* Real bug, caught live: without `position: relative` here,
   .hiw-hero-flourish's `position: absolute; inset: 0` had no positioned
   ancestor to size against, so it sized itself against the page's initial
   containing block instead — 3x taller than this section (measured 878px
   vs. this section's own 275px) — and `overflow: hidden` on THIS element
   never clipped it, since an element only clips descendants that are
   actually positioned relative to it. Once sized like that, any time its
   fade-out doesn't fully complete (a slow frame, or scrolling away mid-
   animation), the leftover blurred color blob shows through well past the
   hero, over whatever section happens to be at that point in the page —
   exactly the effect reported. `position: relative` makes this section
   itself the containing block, so `inset: 0` and the `overflow: hidden`
   here both apply the way every other page's `-hero-flourish` already
   relies on them to. */
.hiw-hero {
  position: relative;
  padding-block: 5rem 2.5rem;
  overflow: hidden;
}

.hiw-hero-flourish {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  opacity: 0;
  background: conic-gradient(
    from 0deg at 50% 30%,
    var(--magenta),
    var(--teal),
    var(--sky),
    var(--orange),
    var(--gold),
    var(--magenta)
  );
  filter: blur(70px);
  mix-blend-mode: screen;
}

.hiw-hero .container {
  position: relative;
  z-index: 1;
}

.hiw-hero h1 {
  font-size: clamp(1.9rem, 4.6vw, 3rem);
  max-width: 22ch;
}

.hiw-reveal-word {
  display: inline-block;
  overflow: hidden;
  vertical-align: top;
}

.hiw-reveal-word-inner {
  display: inline-block;
}

.hiw-hero-lede {
  color: var(--text-secondary);
  font-size: 1.15rem;
  max-width: 62ch;
  margin: 0;
}

/* --------------------------------------------------------------------------
   Intro + the fixed (non-CMS) disclaimer banner
   -------------------------------------------------------------------------- */

.hiw-intro {
  padding-block: 1rem 2.5rem;
}

.hiw-intro-body {
  color: var(--text-secondary);
  font-size: 1.05rem;
  line-height: 1.7;
  max-width: 68ch;
}

.hiw-intro-body p {
  margin: 0 0 1em;
}

.hiw-intro-body p:last-child {
  margin-bottom: 0;
}

/* Deliberately distinct from the intro copy above — an unmissable, bordered
   callout, not just another paragraph, since its whole job is to stop
   anyone from treating this page's competition-rule content as
   permanently fixed. */
.hiw-disclaimer {
  max-width: 68ch;
  margin: 1.75rem 0 0;
  padding: 1rem 1.25rem;
  background: color-mix(in srgb, var(--gold) 12%, var(--charcoal-raised));
  border: 1px solid var(--gold);
  border-radius: var(--radius);
  color: var(--text-secondary);
  font-size: 0.92rem;
  line-height: 1.6;
}

.hiw-disclaimer strong {
  color: var(--gold);
}

.hiw-disclaimer a {
  color: var(--gold);
  text-decoration: underline;
}

/* --------------------------------------------------------------------------
   The 5-stage journey
   -------------------------------------------------------------------------- */

.hiw-stages {
  padding-block: 1rem 4rem;
}

.hiw-stages-lede {
  color: var(--text-secondary);
  font-size: 1.05rem;
  max-width: 60ch;
  margin: 0 0 2.5rem;
}

.hiw-stages-list {
  position: relative;
  max-width: 42rem;
  display: flex;
  flex-direction: column;
  gap: 2.25rem;
  padding-left: 3rem;
}

/* Absolutely positioned to span the whole list's own height as a single
   straight line — see the comment on renderStages/initStageJourney
   (js/script.js) for why this needs no per-item coordinate math. Its
   stroke-dasharray/-dashoffset are ONLY ever set via GSAP once motion/the
   CDN are confirmed available (initStageJourney); with no inline style at
   all, this renders as a normal, fully-drawn line — the built-in reduced-
   motion/no-JS fallback. */
.hiw-stage-path {
  position: absolute;
  top: 0.4rem;
  bottom: 0.4rem;
  left: 0.9rem;
  width: 4px;
  height: calc(100% - 0.8rem);
  opacity: 0.6;
}

.hiw-stage-item {
  position: relative;
  display: flex;
  gap: 1.25rem;
  align-items: flex-start;
}

.hiw-stage-node {
  flex-shrink: 0;
  z-index: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  border-radius: 50%;
  font-family: var(--font-head);
  font-weight: 700;
  font-size: 0.95rem;
  color: var(--charcoal-deep);
  background: var(--card-accent, var(--magenta));
  box-shadow: 0 0 0 4px var(--charcoal);
}

.hiw-stage-card {
  background: var(--charcoal-raised);
  border: 1px solid var(--line);
  border-left: 3px solid var(--card-accent, var(--magenta));
  border-radius: var(--radius);
  padding: 1.25rem 1.5rem;
  flex: 1;
}

.hiw-stage-date {
  font-family: var(--font-head);
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--card-accent, var(--magenta));
  margin: 0 0 0.35rem;
}

.hiw-stage-name {
  font-size: 1.15rem;
  margin: 0 0 0.5rem;
}

.hiw-stage-description {
  color: var(--text-secondary);
  font-size: 0.95rem;
  line-height: 1.6;
  margin: 0;
}

.hiw-stage-item-magenta { --card-accent: var(--magenta); }
.hiw-stage-item-teal { --card-accent: var(--teal); }
.hiw-stage-item-sky { --card-accent: var(--sky); }
.hiw-stage-item-orange { --card-accent: var(--orange); }
.hiw-stage-item-gold { --card-accent: var(--gold); }

/* --------------------------------------------------------------------------
   Eligibility
   -------------------------------------------------------------------------- */

.hiw-eligibility {
  padding-block: 1rem 4rem;
}

.hiw-eligibility-list {
  display: grid;
  gap: 0.9rem;
  max-width: 44rem;
}

.hiw-eligibility-item {
  display: flex;
  align-items: flex-start;
  gap: 0.75rem;
  color: var(--text-secondary);
  font-size: 1rem;
  line-height: 1.5;
}

.hiw-eligibility-item svg {
  flex-shrink: 0;
  width: 1.3rem;
  height: 1.3rem;
  margin-top: 0.1rem;
  color: var(--teal);
}

/* --------------------------------------------------------------------------
   How teams are evaluated
   -------------------------------------------------------------------------- */

.hiw-evaluation {
  padding-block: 1rem 4rem;
}

.hiw-evaluation-lede {
  color: var(--text-secondary);
  font-size: 1.05rem;
  max-width: 60ch;
  margin: 0 0 2.5rem;
}

.hiw-evaluation-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1.25rem;
}

@media (min-width: 640px) {
  .hiw-evaluation-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

.hiw-evaluation-card {
  position: relative;
  background: var(--charcoal-raised);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 1.75rem 1.5rem;
}

.hiw-evaluation-number {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.5rem;
  height: 2.5rem;
  border-radius: 50%;
  margin: 0 0 1rem;
  font-family: var(--font-head);
  font-weight: 700;
  color: var(--card-accent, var(--magenta));
  background: color-mix(in srgb, var(--card-accent, var(--magenta)) 16%, transparent);
}

.hiw-evaluation-card h3 {
  font-size: 1.1rem;
  margin: 0 0 0.4rem;
}

.hiw-evaluation-card p {
  font-size: 0.92rem;
  color: var(--text-secondary);
  margin: 0;
}

.hiw-evaluation-card-magenta { --card-accent: var(--magenta); }
.hiw-evaluation-card-teal { --card-accent: var(--teal); }
.hiw-evaluation-card-sky { --card-accent: var(--sky); }
.hiw-evaluation-card-orange { --card-accent: var(--orange); }
.hiw-evaluation-card-gold { --card-accent: var(--gold); }

/* --------------------------------------------------------------------------
   Explore the Goals (SDG grid) — an ORIGINAL colorful grid using this
   site's own accent palette, not the UN's actual SDG iconography/color
   coding (see the comment on renderSdgGrid, js/script.js).
   -------------------------------------------------------------------------- */

.hiw-sdgs {
  padding-block: 1rem 4rem;
}

.hiw-sdgs-lede {
  color: var(--text-secondary);
  font-size: 1.05rem;
  max-width: 62ch;
  margin: 0 0 2.5rem;
}

.hiw-sdg-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 0.9rem;
}

@media (min-width: 640px) {
  .hiw-sdg-grid {
    grid-template-columns: repeat(4, 1fr);
  }
}

@media (min-width: 1000px) {
  .hiw-sdg-grid {
    grid-template-columns: repeat(6, 1fr);
  }
}

.sdg-card {
  position: relative;
  width: 100%;
  height: 100%;
  min-height: 8.5rem;
  display: flex;
  flex-direction: column;
  justify-content: center;
  background: var(--charcoal-raised);
  border: 1px solid var(--line);
  border-top: 3px solid var(--card-accent, var(--magenta));
  border-radius: var(--radius);
  padding: 1.1rem 1rem;
  text-align: left;
  cursor: pointer;
}

@media (prefers-reduced-motion: no-preference) {
  .sdg-card {
    transition: transform 0.25s ease, border-color 0.25s ease;
  }

  .sdg-card:hover,
  .sdg-card:focus-visible {
    transform: translateY(-3px);
    border-color: var(--card-accent, var(--magenta));
  }
}

.sdg-card-number {
  font-family: var(--font-head);
  font-weight: 700;
  font-size: 1.6rem;
  color: var(--card-accent, var(--magenta));
  line-height: 1;
  margin-bottom: 0.5rem;
}

.sdg-card-name {
  font-size: 0.92rem;
  line-height: 1.3;
  margin: 0;
}

.sdg-tile-magenta { --card-accent: var(--magenta); }
.sdg-tile-teal { --card-accent: var(--teal); }
.sdg-tile-sky { --card-accent: var(--sky); }
.sdg-tile-orange { --card-accent: var(--orange); }
.sdg-tile-gold { --card-accent: var(--gold); }

/* The SDG detail dialog — same fixed-and-centered "the clicked element IS
   the dialog" treatment as .timeline-card.is-modal-open, reused here
   verbatim (see initSdgModal, js/script.js). */
.sdg-modal-overlay {
  position: fixed;
  inset: 0;
  z-index: 55;
  background: rgba(8, 6, 10, 0.72);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}

.sdg-card-close {
  display: none;
  position: absolute;
  top: 1rem;
  right: 1rem;
  width: 2.25rem;
  height: 2.25rem;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  border: 1px solid var(--line);
  background: var(--charcoal-deep);
  color: var(--white);
  cursor: pointer;
}

.sdg-card-close svg {
  width: 1.1rem;
  height: 1.1rem;
}

.sdg-card.is-modal-open .sdg-card-close {
  display: flex;
}

.sdg-card-detail {
  display: none;
}

.sdg-card.is-modal-open .sdg-card-detail {
  display: block;
  margin-top: 1rem;
  padding-top: 1rem;
  border-top: 1px solid var(--line);
}

.sdg-card-detail-description {
  color: var(--text-secondary);
  font-size: 0.98rem;
  line-height: 1.6;
}

.sdg-card-detail-description p {
  margin: 0 0 1em;
}

.sdg-card-detail-description p:last-child {
  margin-bottom: 0;
}

.sdg-card-detail-example {
  margin-top: 1rem;
  padding: 0.9rem 1rem;
  background: color-mix(in srgb, var(--card-accent, var(--magenta)) 10%, transparent);
  border-left: 3px solid var(--card-accent, var(--magenta));
  border-radius: 4px;
  color: var(--text-secondary);
  font-size: 0.92rem;
  font-style: italic;
  line-height: 1.55;
}

.sdg-card-detail-example p {
  margin: 0;
}

.sdg-card.is-modal-open {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  z-index: 56;
  width: min(480px, 92vw);
  max-height: 85vh;
  overflow-y: auto;
  cursor: default;
  min-height: 0;
  justify-content: flex-start;
  padding: 2rem 2rem 2.25rem;
  border-radius: 16px;
  border-color: var(--card-accent, var(--magenta));
  box-shadow: 0 30px 70px -20px rgba(0, 0, 0, 0.6), 0 0 0 1px var(--card-accent, var(--magenta));
}

.sdg-card.is-modal-open .sdg-card-number {
  font-size: 2.2rem;
}

.sdg-card.is-modal-open .sdg-card-name {
  font-size: 1.3rem;
  padding-right: 2rem;
}

@media (prefers-reduced-motion: no-preference) {
  .sdg-card.is-modal-open:hover {
    transform: translate(-50%, -50%);
  }
}

/* Small viewports: near-full-screen rather than a smaller floating card,
   same reasoning and same breakpoint as .timeline-card.is-modal-open. */
@media (max-width: 679px) {
  .sdg-card.is-modal-open {
    top: 0;
    left: 0;
    transform: none;
    width: 100%;
    height: 100%;
    max-height: none;
    border-radius: 0;
    padding: 4.5rem 1.5rem 2.5rem;
  }

  .sdg-card-close {
    top: 1.25rem;
    right: 1.25rem;
  }
}

/* --------------------------------------------------------------------------
   Resource links — a deliberately distinct, prominent CTA-card set, not
   the plain text-link cross-link treatment used elsewhere.
   -------------------------------------------------------------------------- */

.hiw-resources {
  padding-block: 1rem 5.5rem;
}

.hiw-resources-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1.25rem;
}

@media (min-width: 640px) {
  .hiw-resources-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

.hiw-resource-card {
  background: var(--charcoal-raised);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 1.5rem 1.5rem 1.75rem;
  text-align: center;
}

.hiw-resource-card h3 {
  font-size: 1.05rem;
  margin: 0 0 1rem;
}

.hiw-resource-link {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.65rem 1.4rem;
  border-radius: 999px;
  font-family: var(--font-head);
  font-weight: 700;
  font-size: 0.9rem;
  color: var(--charcoal-deep);
  background: var(--card-accent, var(--magenta));
}

@media (prefers-reduced-motion: no-preference) {
  .hiw-resource-link {
    transition: transform 0.2s ease, box-shadow 0.2s ease;
  }

  .hiw-resource-link:hover {
    transform: translateY(-2px);
    box-shadow: 0 10px 24px -10px var(--card-accent, var(--magenta));
  }
}

.hiw-resource-tbd {
  font-style: italic;
  color: var(--gold);
  font-size: 0.85rem;
  margin: 0;
}

.hiw-resource-card-magenta { --card-accent: var(--magenta); }
.hiw-resource-card-teal { --card-accent: var(--teal); }
.hiw-resource-card-sky { --card-accent: var(--sky); }
.hiw-resource-card-orange { --card-accent: var(--orange); }
.hiw-resource-card-gold { --card-accent: var(--gold); }

/* ==========================================================================
   How It Works page: content-load error state (Stage 2h) — shown only if
   content/how-it-works.json fails to fetch/parse, or is missing its
   stages/sdgs lists; see initHowItWorksContent in js/script.js. Same
   treatment as .faq-load-error/.leadership-load-error/.blog-load-error/
   .timeline-load-error/.pitch-video-load-error/.gallery-load-error/
   .about-load-error/.top-teams-load-error.
   ========================================================================== */

.hiw-load-error {
  max-width: 68ch;
  margin: 0 auto;
  background: var(--charcoal-raised);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 1.5rem;
  color: var(--text-secondary);
  text-align: center;
}
