/* =========================================================
   THEME TOKENS
   ========================================================= */
:root {
  --bg:      #17130E;
  --ink:     hsl(0, 0%, 70%);
  --muted:   #ffffff73;
  --accent:  #C9A227;

  --font-display: "Adobe Garamond Pro", "AGaramondPro-Bold", Garamond, Georgia, serif;
  --font-ui:      "Inter", system-ui, -apple-system, sans-serif;

  /* How much scroll distance the intro's pinned scrub takes to complete.
     Was 950vh — cut down since the tail end (after the answer fades out)
     had nothing happening in it, which read as a long dead gap before the
     card stack arrived. Must stay well above 100vh, though: .intro-pin is
     the sticky scrub track for .stage (100vh tall), so anything at or
     below 100vh leaves zero pin range and the intro releases on the very
     first scroll tick instead of scrubbing. */
  --scroll-length: 240vh;

  /* Card stack: side margins, the vertical gap between stacked cards,
     and how much of the neighbouring card peeks in above/below the
     current one. */
  --card-side: 15px;
  --card-gap:  10px;
  --card-peek: 60px;

  /* Height of the footer card's "Supported by" strip — shared between
     .footer-supporters itself and .site-footer__subscribe (which needs
     to know it to centre in the space below it), so the two can't
     drift out of sync. */
  --footer-supporters-height: clamp(3.25rem, 8vh, 4.25rem);
}

/* =========================================================
   RESET / BASE
   ========================================================= */
* { margin: 0; padding: 0; box-sizing: border-box; }

html {
  scroll-behavior: auto;
  /* No scroll-snap-type here on purpose. It caused two separate problems:
     "mandatory" auto-corrected the scroll position to the nearest snap
     point (the hero card) on the very first paint, since scrollY 0 wasn't
     itself a snap point — skipping the intro entirely on load. Backing off
     to "proximity" avoided that, but then WebKit's own snap logic fought
     the JS scroll-jack's window.scrollTo({behavior:'smooth'}) calls in
     main.js, cutting card-to-card transitions short partway through. The
     JS scroll-jack already does all the real locking/resistance work, so
     CSS-level snapping isn't needed here at all. */
}

body {
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font-display);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
}

/* =========================================================
   PAGE BACKGROUND — dark texture loop, fixed behind everything: the
   intro and the gaps around the stacked cards alike. Stays dark
   throughout — individual cards carry their own lighter look where
   needed (see .card__texture-bg further down).
   ========================================================= */
.page-bg {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  z-index: -1;
  pointer-events: none;
}

/* =========================================================
   SKIP BUTTON — jumps straight past the intro to the card stack.
   mix-blend-mode inverts against whatever's behind it, so it stays
   legible over both the dark and the white/light backgrounds.
   ========================================================= */
.skip-button {
  position: fixed;
  top: clamp(1.25rem, 3vw, 2rem);
  left: clamp(1.25rem, 3vw, 2rem);
  z-index: 10;
  background: transparent;
  border: 0;
  padding: 0;
  cursor: pointer;
  mix-blend-mode: difference;
  color: #fff;
  /* Same typography as .corner-button (the Menu/Join buttons), so Skip
     reads as part of the same button family. */
  font-family: var(--font-display);
  font-weight: 900;
  font-size: clamp(0.95rem, 1.2vw, 1.15rem);
  letter-spacing: -0.07em;
  opacity: 0.75;
  transition: opacity 0.3s ease;
  will-change: opacity;
}

.skip-button:hover { opacity: 1; }

/* =========================================================
   INTRO — .stage sticks to the top of the viewport for the height
   of .intro-pin, then releases into the card stack below as you
   keep scrolling (a standard sticky-then-release handoff).
   ========================================================= */
.intro-pin {
  position: relative;
  height: var(--scroll-length);
}

.stage {
  position: sticky;
  top: 0;
  height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: clamp(1.5rem, 5vw, 6rem);
  overflow: hidden;
}

/* =========================================================
   STATEMENTS — text container, sits above the video.
   ========================================================= */
.statements {
  display: grid;
  width: 100%;
  position: relative;
  z-index: 1;
}

/* Both sentences overlap in the same grid cell so one dissolves into the other. */
.line {
  grid-area: 1 / 1;
  text-align: center;
  font-weight: 900;
  line-height: 0.85;
  letter-spacing: -0.06em;
  font-size: clamp(2.45rem, 5.6vw, 4.2rem);
  color: var(--ink);
  text-shadow: 0 0 1px rgba(211, 211, 211, 0.9), 0 0 6px rgba(255, 0, 0, 0.2);
}

/* Each word animates independently. */
.word {
  display: inline-block;
  will-change: opacity, transform;
}

.word + .word { margin-left: 0.28em; }

/* Question words start visible; answer words start hidden. */
.line--question .word { opacity: 1; }
.line--answer   .word { opacity: 0; }

/* =========================================================
   CARD STACK — placeholder version. Deliberately plain, differently-
   coloured boxes for now, to get the interaction behaviour right
   before the real card designs go back in.

   It's a FIXED, full-viewport slider, not scrolling content: once the
   intro hands off (see the render() hook in main.js), the whole stack
   sits pinned over the page and .card-track's `transform` is what
   moves. The page itself does not scroll while a card is showing;
   there's nothing below .intro-pin in normal document flow for it to
   scroll into any more.

   Cards are normal-flow children of .card-track (not one-per-100vh
   slot) specifically so the neighbouring cards' edges can peek in
   above AND below the current one: each card is shorter than the
   viewport by two lots of --card-peek (one for the card above, one
   for the card below), with --card-gap of empty space on either side
   of that peek. main.js measures the actual rendered gap between two
   cards at runtime (not a hardcoded step) and moves the track by
   exactly that per card, so this stays correct if the spacing/peek
   values below ever change. .card-track's own top padding matches
   that same peek+gap offset, so the very first card sits at the same
   resting position every later card does (it just has nothing to
   peek in above it, since there's no card before it).
   ========================================================= */
.card-stack {
  position: fixed;
  inset: 0;
  z-index: 3; /* above the intro/page-bg, below skip-button (10) and join-button (4) */
  /* No background of its own — .page-bg (the looping TEXTURE DARK_1.mp4,
     fixed behind everything at z-index:-1) shows through the margins/gaps/
     peeks around the cards, same as it does behind the intro. */
  overflow: hidden;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity 0.4s ease;
}

.card-stack.is-active {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
}

/* While the enquiries takeover is open (see .join-takeover), the whole
   stack fades out — same trick as the FAQ takeover fading one card's own
   layers, just applied to everything at once, revealing .page-bg (the
   dark texture video, already fixed and playing behind everything). */
.card-stack.is-active.is-join-hidden {
  opacity: 0;
  pointer-events: none;
}

.card-track {
  /* No top padding: the first card sits flush against the very top of
     the viewport (see .card:first-child below) rather than leaving
     room for a peek that has nothing above it to show. */
  transition: transform 0.45s cubic-bezier(0.65, 0, 0.35, 1);
  will-change: transform;
  /* Without this, .card:first-child's margin-top collapses straight
     through .card-track (which has no border/padding of its own to
     stop it) instead of actually pushing the card down inside it —
     flow-root gives the track its own block-formatting context so the
     margin behaves the way it looks like it should. */
  display: flow-root;
}

.card {
  /* Default: a middle card, with a neighbour peeking in above AND
     below, so it reserves --card-peek + --card-gap of room on both
     sides for that. */
  position: relative;
  height: calc(100vh - (var(--card-peek) * 2) - (var(--card-gap) * 2));
  margin: 0 var(--card-side) var(--card-gap);
  border-radius: 19px;
  overflow: hidden; /* clips card content — the title card's video, in particular — to the rounded corners */
  /* No isolation/transform/compositing hacks here on purpose: this is
     the element with border-radius + overflow:hidden, and forcing a
     layer on it (isolation: isolate, transform: translateZ(0), either
     one alone) is a known Safari combo that drops the rounded clip
     once the layer settles, squaring off the corners. The mix-blend-
     mode containment fix lives one level down instead, on
     .card__layers — same effect, doesn't touch the clipping element. */
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-display);
  letter-spacing: -0.03em;
  font-size: clamp(1.5rem, 3vw, 2.5rem);
  font-weight: 700;
  color: #353535; /* standard copy colour on a light/white background */
  /* Lives here (not just on the :has() rule that sets opacity:0 below) so
     the fade is smooth in BOTH directions — fading out when the title
     card's film starts playing, and back in when it stops. */
  transition: opacity 0.6s ease;
}

/* First card: nothing above it to peek in, so it fills that space
   instead of leaving it empty — only the bottom peek is reserved, and
   the same --card-side gap used on the left/right frames the top too
   (instead of the 0 the base rule gives every other card, which relies
   on the previous card's peek to fill that space instead). */
.card:first-child {
  height: calc(100vh - var(--card-peek) - var(--card-gap) - var(--card-side));
  margin-top: var(--card-side);
}

/* Last card: same idea at the bottom — only the top peek is reserved,
   and --card-side frames the bottom instead of --card-gap (there's no
   next card to leave that gap for). */
.card:last-child {
  height: calc(100vh - var(--card-peek) - var(--card-gap) - var(--card-side));
  margin-bottom: var(--card-side);
}

/* Only one card total: no neighbours either side, --card-side frames
   all four edges. */
.card:first-child:last-child {
  height: calc(100vh - (var(--card-side) * 2));
  margin-top: var(--card-side);
  margin-bottom: var(--card-side);
}

/* Wraps every card's background layers that need mix-blend-mode
   containment (the texture video/film + .card__texture + .card__wash)
   — NOT the card itself, which owns the border-radius + overflow:hidden
   clip (see the long comment on .card above for why that separation
   matters). Plain inset:0 div, no visual effect of its own. */
.card__layers {
  position: absolute;
  inset: 0;
  isolation: isolate;
  /* Pre-promotes its own compositing layer instead of leaving WebKit to
     build one mid-transition — isolation: isolate alone (see the long
     comment on .card__steps) still let this flash fully invisible for
     a single frame right as the card-track's slide landed, confirmed
     by recording actual frames; will-change closes that gap. */
  will-change: opacity;
  /* Fallback for when .card__texture-bg's video doesn't actually end up
     playing (reported as cards looking flat grey instead of the proper
     warm/textured paper look) — some real-world combination of mobile
     autoplay policy, low power mode, etc. that doesn't reproduce in
     ordinary browser testing. Without this, a video that never renders
     a frame leaves nothing behind .card__wash's semi-transparent tint,
     which alone reads as a flat grey slab. This sits behind the video,
     so it's invisible whenever the video is actually playing. */
  background: #c9c4ba;
}

/* Every card after the title card shares the same looping texture
   background rather than a placeholder colour — add this same video
   block to any new card and it matches automatically, nothing else to
   configure. It's the same dark texture video used for the page
   background, just inverted to a light look here, rather than loading
   a whole separate light video asset. */
.card__texture-bg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: invert(1);
  transition: filter 0.5s ease;
  /* Same WebKit defensive fix as .card__steps (see the long comment
     there): confirmed by screen recording that this video's inverted
     look goes stale/invisible for the whole duration of the
     card-track's slide (not just landing) and only catches up once
     the transform settles — this video wasn't independently
     compositor-promoted, so its filter wasn't repainting each source
     frame while .card-track was mid-transform above it. */
  isolation: isolate;
  will-change: filter;
}

/* Up/down arrows (no more "Keep reading"/"Go back" text) — sit in a
   card's own top or bottom --card-peek strip (data-peek="top"/"bottom"
   picks which — see .card--peek-label--bottom below), so whichever one
   is currently peeking in above or below the current card is what shows
   there. main.js works out visibility generically from each label's own
   position among the cards, in updatePeekLabels() — every card gets one
   at its own top (visible while the card before it is current) and one
   at its own bottom (visible while the card after it is current); the
   first card skips the top one, the last skips the bottom one, since
   there's nothing to peek in from there. The label text stays as an
   aria-label for screen readers even though it's an icon visually.
   Also clickable/tappable — see the click handler in main.js — to step
   a card in that direction, not just decorative. */
.card--peek-label {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: var(--card-peek);
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  /* Clickable/tappable (see main.js) even while faded out — idling out is
     just decoration, the strip itself still steps a card when tapped. */
  cursor: pointer;
  transition: opacity 0.8s ease;
}

.card--peek-label--bottom {
  top: auto;
  bottom: 0;
}

/* Same asset/sizing/invert treatment as .quote-carousel__arrow (see the
   comment there) — these sit on the same light card backgrounds, just
   without that one's hover/cursor styling, since a peek label isn't
   clickable. Arrow_white.svg points down natively: the top label (down,
   "keep scrolling this way") needs no rotation; the bottom one flips
   180deg to point up ("back the way you came"). */
.card--peek-label__icon {
  display: block;
  width: clamp(0.4rem, 0.6vw, 0.5rem);
  height: auto;
  filter: invert(1);
}

.card--peek-label--bottom .card--peek-label__icon {
  transform: rotate(180deg);
}

/* Card split in half: body copy sits in the left side, vertically
   centred (inherited from .card's own align-items: center) but left-
   aligned horizontally rather than centred across the whole card. */
.card--split {
  justify-content: flex-start;
}

.card__body-copy {
  position: relative;
  z-index: 1; /* above .card__layers */
  width: 50%;
  max-width: 50%;
  padding: clamp(1.5rem, 5vw, 4rem);
  padding-left: clamp(3rem, 9vw, 6rem); /* brought in further from the card's outer edge */
  padding-right: clamp(0.75rem, 2.5vw, 2rem); /* halved — inner gap toward .card__steps */
  text-align: left;
  /* Same fix as .card__steps below, for the same reason — see the
     comment there, including the added will-change. */
  isolation: isolate;
  will-change: opacity;
  /* Same family/weight/kern as .card--title__subtitle, but scaled down
     a lot — that size was right for a headline-scale subline, too big
     for a paragraph-length block sitting next to a compact step list. */
  font-family: var(--font-display);
  font-weight: 900;
  font-size: clamp(1rem, 1.5vw, 1.4rem);
  line-height: 1.05;
  letter-spacing: -0.07em;
  color: #353535;
  /* Hidden while card 2 is only peeking in below the title card —
     fades in once it's actually the current card (main.js toggles
     .is-visible in setCardIndex(), at the same moment the slide to it
     starts, so it's mostly faded in by the time the card lands).
     Fading OUT (this rule, once .is-visible is removed) is delayed to
     the back half of the 0.45s slide instead of starting immediately —
     starting it at the same moment as the fade-in made the outgoing
     card's text vanish while it was still mostly on screen, reading as
     a jarring cut rather than a crossfade. This way it stays fully
     visible while the slide gets underway, then fades over the last
     0.225s, landing on 0 right as the slide finishes (0.225s delay + 0.225s
     duration = the track's own 0.45s transition). */
  opacity: 0;
  transition: opacity 0.225s ease 0.225s;
}

.card__body-copy.is-visible {
  opacity: 1;
  transition: opacity 0.5s ease; /* fading IN starts immediately, no delay */
}

.card__body-copy p + p {
  margin-top: 1em;
}

/* =========================================================
   STEPS — right half of card 2. An accordion (native <details>, one
   open at a time via the shared `name` attribute — no JS) rather than
   the reference layout's horizontal strip of all 7 steps at once:
   this is half a card wide, so showing all 7 full paragraphs
   simultaneously would either force tiny type or overflow — cards
   don't scroll internally (wheel input is claimed globally for card-
   to-card navigation, see main.js), so overflowing content would just
   get clipped by the card's own overflow:hidden, not scrollable into
   view. Rows collapse to one line each, and it stacks full-width on
   mobile for free — no separate mobile layout needed.
   ========================================================= */
.card__steps {
  position: relative;
  z-index: 1; /* above .card__layers */
  /* Auto-fit (see main.js) shrinks this whole block via transform if an
     open answer would otherwise run past the card's own edge on a short
     window — transition here just smooths that scale change rather than
     snapping. Default transform-origin (centre) keeps it centred in
     place as it shrinks either the desktop column or the mobile
     takeover overlay. */
  transition: transform 0.25s ease;
  width: 50%;
  max-width: 50%;
  padding: clamp(1.5rem, 5vw, 4rem);
  padding-right: clamp(3rem, 9vw, 6rem); /* brought in further from the card's outer edge */
  padding-left: clamp(0.75rem, 2.5vw, 2rem); /* halved — inner gap toward .card__body-copy */
  color: #353535;
  /* Defensive fix for a reported bug (opacity-faded content sometimes
     staying invisible after scrolling away and back) that we couldn't
     force-reproduce to verify directly — but it matches the same
     category of WebKit glitch as two OTHER bugs already hit in this
     file (the .card__texture blend-mode flicker, and the border-radius
     clip loss): an element's own animated property losing sync with
     an ancestor (.card-track) that's mid-transform at the same time.
     isolation: isolate is what fixed the blend-mode case; giving these
     fading elements the same stable stacking context of their own.
     UPDATE: this reported bug WAS reproduced directly, by recording
     actual frames — a single fully-invisible frame right as the
     card-track's slide landed, still happening with isolation: isolate
     alone. will-change: opacity (below) is what actually closed it —
     pre-promoting the layer instead of leaving WebKit to build one
     mid-transition. Kept isolation too: cheap, and may still help. */
  isolation: isolate;
  will-change: opacity;
  /* Own base size rather than inheriting .card's — that's sized for
     the placeholder cards' big centred labels, way too large for a
     compact step list (.step__label/.step__index were inheriting it
     unset, which is why they were rendering oversized). */
  font-size: clamp(1rem, 1.6vw, 1.4rem);
  /* Same hidden-until-current fade as .card__body-copy — see the
     comment there, including why fading out is delayed. */
  opacity: 0;
  transition: opacity 0.225s ease 0.225s;
}

.card__steps.is-visible {
  opacity: 1;
  transition: opacity 0.5s ease;
}

.step {
  border-top: 1px solid rgba(53, 53, 53, 0.25);
}

.step:last-of-type {
  border-bottom: 1px solid rgba(53, 53, 53, 0.25);
}

/* "Blind from here" / "End of blind" — a small pill straddling the
   divider between two steps (see the HTML comment on this element).
   Zero height itself, so it doesn't add a gap in the list — the two
   .step rows either side stay exactly as adjacent as they'd be without
   it, with their shared border line still the same single rule; the
   pill just floats centred on top of that line via its own absolute
   positioning off this 0-height wrapper. */
.step__blind-marker {
  position: relative;
  height: 0;
  z-index: 2;
}

.step__blind-marker span {
  position: absolute;
  top: 0;
  left: 50%;
  transform: translate(-50%, -50%);
  background: var(--bg);
  color: #fff;
  font-family: var(--font-display);
  font-style: italic;
  font-weight: 700;
  font-size: 0.7rem;
  letter-spacing: -0.01em;
  padding: 0.4em 1.1em;
  border-radius: 999px;
  white-space: nowrap;
}

.step__summary {
  display: flex;
  align-items: baseline;
  gap: 0.75em;
  padding: 0.6em 0;
  cursor: pointer;
  list-style: none; /* hide the native disclosure triangle */
}

.step__summary::-webkit-details-marker {
  display: none; /* Safari/Chrome's version of the above */
}

.step__index {
  flex: none;
  font-size: 0.5em;
  opacity: 0.6;
  white-space: nowrap;
}

.step__label {
  flex: 0.5;
  font-weight: 700;
}

/* Shared by .step__toggle (card 2's steps) and .faq__toggle (card 3's
   FAQ, below) — same "+"-to-"−" disclosure indicator, reused rather
   than duplicated. */
.step__toggle,
.faq__toggle {
  flex: none;
  position: relative;
  width: 0.5em; /* smaller than the label text, not matched to it */
  height: 0.5em;
  /* Pins it flush to the row's right edge regardless of how much the
     label grows — without this, .step__label's own flex-grow was the
     only thing pushing it there, and shrinking that value (to bring the
     label in) left the toggle stranded short of the row's actual right
     edge, with the border line still running full-width past it. */
  margin-left: auto;
}

/* Drawn as two lines forming a "+", the vertical one rotated away to a
   "−" once open — cheaper and crisper at this size than an SVG or
   image asset. */
.step__toggle::before,
.step__toggle::after,
.faq__toggle::before,
.faq__toggle::after {
  content: "";
  position: absolute;
  inset: 0;
  margin: auto;
  background: #353535;
  transition: transform 0.2s ease;
}

.step__toggle::before,
.faq__toggle::before {
  width: 100%;
  height: 1.5px;
}

.step__toggle::after,
.faq__toggle::after {
  width: 1.5px;
  height: 100%;
}

.step[open] .step__toggle::after,
.faq[open] .faq__toggle::after {
  transform: rotate(90deg);
  opacity: 0; /* collapses the "+" down to a "−" */
}

.step__copy {
  padding: 0 0 0.9em;
  font-size: clamp(0.875rem, 0.95vw, 0.95rem); /* small, refined — well under the step titles */
  line-height: 1.35;
  letter-spacing: -0.01em; /* dense paragraph copy — full -0.07em reads too cramped at this size */
}

/* On narrow/portrait screens, .card--split stacks instead of sitting
   side by side: body copy on top, steps underneath, each full-width.
   Worth knowing as you tune this: cards still don't scroll internally
   (see the long comment at the top of the STEPS block above) — on a
   short phone viewport, the body copy plus all 7 collapsed step rows
   may not actually fit within one card's fixed height, and anything
   past the bottom edge gets clipped rather than scrolled into view.
   Worth checking on your actual target devices; if it doesn't fit,
   the fix is trimming copy/step count for mobile, not adding scroll —
   scroll on this axis would fight the wheel/touch gestures main.js
   already claims for moving card-to-card. */
@media (max-width: 700px) {
  .card--split {
    flex-direction: column;
    align-items: stretch;
    /* Clears the fixed Menu/Join corner buttons — at full card width
       they sit beside the content, but stacked full-width there's
       nothing keeping the body copy's top line out from under them. */
    padding-top: 4rem;
    /* Centred rather than the base rule's flex-start (left-alignment in
       the desktop row layout — meaningless once flex-direction flips to
       column here, where it instead reads as top-alignment vertically,
       which isn't what's wanted). */
    justify-content: center;
  }

  /* Smaller and quieter than the desktop pill — at full card width it's a
     nice callout, but stacked full-width on a phone it competes too much
     with the step labels either side of it. */
  .step__blind-marker span {
    font-size: 0.55rem;
    padding: 0.3em 0.8em;
    opacity: 0.7;
  }

  /* Takeover: opening a research question hides the intro copy and every
     OTHER question, and the open one fills the card — mirrors the old
     desktop full-screen takeover. Needed here (unlike card 2's own step
     accordion, whose answers are one short sentence each) because these
     answers run several paragraphs long — too long to fit stacked
     normally in a short mobile card without overflowing its fixed
     height (see the long comment above on the actual overflow risk). */
  .card--split:has(.card__research-steps details[open]) .card__research-copy {
    display: none;
  }

  .card--split .card__research-steps:has(details[open]) {
    position: absolute;
    inset: 0;
    width: 100%;
    max-width: 100%;
    display: flex;
    flex-direction: column;
    justify-content: center;
    padding: 4rem clamp(1rem, 5vw, 2rem) 1.5rem;
  }

  .card--split .card__research-steps:has(details[open]) details:not([open]) {
    display: none;
  }

  /* Stacking the two halves roughly doubles the vertical space this
     content needs (see the long comment above .card--split for why
     that's the actual overflow risk here, not the corner buttons) —
     sized and spaced down aggressively so the intro copy plus a full
     8-row step list (or the research card's stats block) actually
     fits inside one card's fixed height instead of getting clipped by
     its overflow:hidden.
     Scoped under .card--split (rather than these classes bare) so the
     specificity beats each element's own base rule regardless of
     which one happens to come later in the file — several of those
     base rules (.card__research-copy etc.) are defined further down
     than this block, and at equal specificity source order alone
     would otherwise let them win over this media query. */
  .card--split .card__body-copy,
  .card--split .card__steps,
  .card--split .card__research-copy,
  .card--split .card__research-steps {
    width: 100%;
    max-width: 100%;
    padding-top: 0.5rem;
    padding-bottom: 0.5rem;
    padding-left: clamp(1rem, 5vw, 2rem);
    padding-right: clamp(1rem, 5vw, 2rem);
    font-size: 0.8rem;
    line-height: 1.1;
  }

  .card--split .card__body-copy p + p,
  .card--split .card__research-copy p + p {
    margin-top: 0.4em;
  }

  .card--split .step__summary {
    padding: 0.35em 0;
  }

  .card--split .step__copy {
    font-size: 0.875rem;
    padding-bottom: 0.5em;
  }

  .card--split .research-stats {
    gap: 1rem;
  }

  .card--split .research-stat {
    padding: 0.6rem 0.7rem;
  }

  .card--split .research-stat__value {
    font-size: 1.6rem;
  }
}

/* =========================================================
   QUOTES — card 3, testimonials. Small lead-in copy, then one quote at
   a time in a big-statement scale.
   ========================================================= */
.card__quotes {
  position: relative;
  z-index: 1; /* above .card__layers */
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: clamp(1.5rem, 5vw, 4rem);
  color: #353535;
  /* Same WebKit defensive fix as .card__steps — see the comment there.
     Missing here was exactly what caused this card's text to flicker
     invisible right as the card-track's slide landed. isolation alone
     wasn't enough here either — will-change is what actually fixed it. */
  isolation: isolate;
  will-change: opacity;
  /* Fade-out delayed to the back half of the card-track's 0.45s slide —
     see the comment on .card__body-copy for why. */
  opacity: 0;
  transition: opacity 0.225s ease 0.225s;
}

.card__quotes.is-visible {
  opacity: 1;
  transition: opacity 0.5s ease;
}

/* Same point size as .card__body-copy (card 2's intro paragraphs), for
   the same "this is the card's lead-in copy" weight across cards. */
.card__quotes-intro {
  max-width: 46ch;
  font-family: var(--font-display);
  font-weight: 900;
  letter-spacing: -0.07em;
  font-size: clamp(1rem, 1.5vw, 1.4rem);
  line-height: 1.05;
  color: rgba(53, 53, 53, 0.75);
  margin-top: clamp(2rem, 5vw, 3.5rem);
}

.card__quotes-intro + .card__quotes-intro {
  margin-top: 1em;
}

/* One step down from the lead-in line above — same size/tone as the
   accordion body copy elsewhere (.step__copy). */
.card__quotes-intro--sub {
  font-weight: 400;
  font-size: clamp(0.875rem, 0.95vw, 0.95rem);
  line-height: 1.35;
  letter-spacing: -0.01em;
}

.quote-carousel {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: clamp(1rem, 3vw, 2.5rem);
  width: 100%;
  max-width: 56rem;
}

.quote-carousel__viewport {
  position: relative;
  flex: 1;
  display: grid; /* every quote shares the same cell, same overlap trick as .statements — see the HTML comment */
  min-height: clamp(9rem, 26vh, 15rem);
  align-items: center;
}

.quote-carousel__quote {
  grid-area: 1 / 1;
  font-family: var(--font-display);
  font-weight: 900;
  letter-spacing: -0.06em;
  font-size: clamp(1.5rem, 3.6vw, 3rem);
  line-height: 1;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.4s ease;
  /* Same WebKit fading-element-inside-a-transform-animated-ancestor glitch
     as .card__steps/.card__body-copy (see the long comment on .card__steps
     above) — these quotes fade via JS on a card that itself slides via
     .card-track's transform, and without a pre-promoted layer WebKit can
     paint a stale frame of the outgoing quote during the swap. Same fix. */
  isolation: isolate;
  will-change: opacity;
}

.quote-carousel__quote.is-active {
  opacity: 1;
  pointer-events: auto;
}

/* Same arrow asset as the intro's .scroll-cue (Arrow_white.svg, which
   points down) — inverted to dark since it's white-on-transparent and
   this sits on the card's light background (same trick as
   .card__texture-bg's own filter: invert(1)), then rotated per
   direction: 90deg clockwise turns "down" into "left", -90deg into
   "right". */
.quote-carousel__arrow {
  flex: none;
  width: clamp(1.5rem, 2.5vw, 2rem);
  height: auto;
  border: 0;
  border-radius: 0;
  background: transparent;
  padding: 0;
  cursor: pointer;
  opacity: 0.75;
  transition: opacity 0.2s ease, transform 0.2s ease;
}

.quote-carousel__arrow img {
  display: block;
  width: 100%;
  height: auto;
  filter: invert(1);
}

.quote-carousel__arrow--prev img {
  transform: rotate(90deg);
}

.quote-carousel__arrow--next img {
  transform: rotate(-90deg);
}

.quote-carousel__arrow:hover {
  opacity: 1;
  transform: scale(1.08);
}

@media (max-width: 700px) {
  /* Same top clearance as every other card's mobile padding-top (keeps
     content out from under the fixed corner buttons), but centred rather
     than top-anchored — see the note on .card--split for why every
     mobile card moved to centring. */
  .card__quotes {
    padding-top: 4rem;
    justify-content: center;
  }

  /* No prev/next arrows on mobile — swiped left/right instead (see the
     touch handling in main.js scoped to .quote-carousel), same as the
     auto-advance fade already running underneath either way. Flanking
     arrows either side of the quote also put them mid-height against a
     block of text that's several lines tall at phone widths, colliding
     with wrapped text instead of framing it. */
  .quote-carousel__arrow {
    display: none;
  }

  .quote-carousel {
    max-width: 100%;
  }

  .quote-carousel__viewport {
    min-height: 0;
  }

  .quote-carousel__quote {
    font-size: clamp(1.2rem, 6.5vw, 1.6rem);
  }
}

/* =========================================================
   RESEARCH — card 4, "Why are we doing this". Same left/right split as
   card 2 (see the long comments on .card__body-copy and .card__steps
   above, which this mirrors) but under its own class names so main.js's
   per-card visibility fade — keyed off class selector, not a shared
   wrapper — doesn't also fade this card's content in step with card 2's.
   ========================================================= */
.card__research-copy {
  position: relative;
  z-index: 1;
  width: 50%;
  max-width: 50%;
  padding: clamp(1.5rem, 5vw, 4rem);
  padding-left: clamp(3rem, 9vw, 6rem);
  padding-right: clamp(0.75rem, 2.5vw, 2rem);
  text-align: left;
  isolation: isolate;
  will-change: opacity; /* see the comment on .card__steps — isolation alone wasn't enough */
  font-family: var(--font-display);
  font-weight: 900;
  font-size: clamp(1rem, 1.5vw, 1.4rem);
  line-height: 1.05;
  letter-spacing: -0.07em;
  color: #353535;
  /* Fade-out delayed to the back half of the card-track's 0.45s slide —
     see the comment on .card__body-copy for why. */
  opacity: 0;
  transition: opacity 0.225s ease 0.225s;
}

.card__research-copy.is-visible {
  opacity: 1;
  transition: opacity 0.5s ease;
}

.card__research-copy p + p {
  margin-top: 1em;
}

.card__research-steps {
  /* Auto-fit — see the comment on .card__steps' own copy of this. */
  transition: transform 0.25s ease;
  position: relative;
  z-index: 1;
  width: 50%;
  max-width: 50%;
  padding: clamp(1.5rem, 5vw, 4rem);
  padding-right: clamp(3rem, 9vw, 6rem);
  padding-left: clamp(0.75rem, 2.5vw, 2rem);
  color: #353535;
  isolation: isolate;
  will-change: opacity; /* see the comment on .card__steps — isolation alone wasn't enough */
  font-size: clamp(1rem, 1.6vw, 1.4rem);
  /* Fade-out delayed to the back half of the card-track's 0.45s slide —
     see the comment on .card__body-copy for why. */
  opacity: 0;
  transition: opacity 0.225s ease 0.225s;
}

.card__research-steps.is-visible {
  opacity: 1;
  transition: opacity 0.5s ease;
}

.research-link {
  color: inherit;
  text-decoration: underline;
  text-underline-offset: 0.15em;
}

.research-stats {
  display: flex;
  gap: clamp(1rem, 2.5vw, 2rem);
  padding-top: 0.2em;
}

.research-stat {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 0.3em;
  /* Still a boxed data card (padding keeps the two stats visually
     separate/contained), just without a keyline round it. */
  border-radius: 8px;
  padding: 1rem 1.1rem;
}

.research-stat__value {
  font-family: var(--font-display);
  font-weight: 900;
  font-size: clamp(1.8rem, 3.4vw, 2.6rem);
  letter-spacing: -0.04em;
  line-height: 1;
  color: #353535; /* same dark grey as the headings, not the gold accent */
}

.research-stat__label {
  font-size: clamp(0.875rem, 0.85vw, 0.95rem);
  font-weight: 400;
  letter-spacing: -0.01em;
  line-height: 1.3;
}

.research-gap {
  margin-top: 0.9em;
  padding-bottom: 0.9em; /* was 0 — left "What This Means" crowding straight in underneath */
}

/* =========================================================
   FAQ — card 3. Two columns of native <details> (same accordion
   mechanic as the steps on card 2), but NOT grouped by a shared
   `name` — these questions aren't a sequence, so it's reasonable for
   more than one to be open at once, unlike the steps. Same "own base
   font-size" fix and hidden-until-current fade as card 2's content —
   see the long comments on .card__steps and .card__body-copy above
   for why both matter here too.
   ========================================================= */
.card__faq {
  position: relative;
  z-index: 1; /* above .card__layers */
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center; /* centres the intro paragraph and the question stack as blocks, not just their text */
  justify-content: center; /* was top-anchored — now centred in the card's own height */
  padding: clamp(1.5rem, 5vw, 4rem);
  color: #353535;
  font-size: clamp(0.8rem, 1.05vw, 0.95rem); /* smaller than before */
  overflow: hidden; /* belt-and-braces — see the overflow caveat in the HTML comment */
  /* Same WebKit defensive fix as .card__steps — see the comment there.
     Missing here was exactly what caused this card's text to flicker
     invisible right as the card-track's slide landed. isolation alone
     wasn't enough here either — will-change is what actually fixed it. */
  isolation: isolate;
  will-change: opacity;
  /* Fade-out delayed to the back half of the card-track's 0.45s slide —
     see the comment on .card__body-copy for why. */
  opacity: 0;
  transition: opacity 0.225s ease 0.225s;
}

.card__faq.is-visible {
  opacity: 1;
  transition: opacity 0.5s ease;
}

/* Site-default size for short (non-long-form) page copy — same clamp as
   .card__body-copy/.card__research-copy's "Anecdotally..." paragraph.
   Use this same clamp(1rem, 1.5vw, 1.4rem)/900/1.05/-0.07em combo for any
   new short intro/lead-in copy rather than a bespoke size. */
.card__faq-intro {
  max-width: 34ch;
  text-align: center;
  font-family: var(--font-display);
  font-weight: 900;
  font-size: clamp(1rem, 1.5vw, 1.4rem);
  line-height: 1.05;
  letter-spacing: -0.07em;
  margin-bottom: 1.5em;
}

.card__faq-columns {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0 clamp(1.5rem, 4vw, 3rem);
  /* Auto-fit — see the comment on .card__steps' own copy of this. */
  transition: transform 0.25s ease;
  /* Sits as its own centred block beneath the intro paragraph, rather
     than the two columns stretching edge to edge — still two columns
     (collapsing to a single stacked column would double this card's
     content height, which risks overflowing since cards don't scroll
     internally; see the HTML comment on this card). */
  width: min(100%, 50rem);
  margin: 0 auto;
}

.faq {
  border-top: 1px solid rgba(53, 53, 53, 0.25);
  opacity: 1;
  transition: opacity 0.2s ease;
}

/* While any question is open, every other one dims slightly to draw
   focus to it — same :has() trick as .card-stack's own title-card-
   playing rule elsewhere in this file. Scoped to :not([open]) rather
   than the whole card so the open one stays at full opacity. */
.card__faq-columns:has(.faq[open]) .faq:not([open]) {
  opacity: 0.45;
}

.faq-col .faq:last-of-type {
  border-bottom: 1px solid rgba(53, 53, 53, 0.25);
}

.faq__q {
  display: flex;
  align-items: baseline;
  gap: 0.75em;
  padding: 0.7em 0;
  cursor: pointer;
  list-style: none; /* hide the native disclosure triangle */
  font-weight: 700;
  line-height: 1.3;
  letter-spacing: -0.02em; /* full-sentence questions — the site's -0.07em default reads too cramped at this length */
}

.faq__q::-webkit-details-marker {
  display: none; /* Safari/Chrome's version of the above */
}

.faq__a {
  padding: 0 0 0.9em;
  font-size: clamp(0.875rem, 1.1vw, 1rem);
  line-height: 1.35;
  letter-spacing: -0.01em; /* dense paragraph copy — full -0.07em reads too cramped at this size */
}

@media (max-width: 700px) {
  .card__faq {
    /* Clears the fixed Menu/Join corner buttons — same fix as
       .card--split's own padding-top, this card just isn't part of
       that class so it needs its own copy. */
    padding-top: 4rem;
    /* Centred rather than the base rule's flex-start — see the note on
       .card--split for why every mobile card moved to centring. */
    justify-content: center;
    /* rem, not em: em here would resolve against .card's own (much
       larger, placeholder-sized) font-size rather than scaling down
       this element's own desktop clamp(0.8rem, 1.05vw, 0.95rem). */
    font-size: 0.78rem;
  }

  .card__faq-intro {
    font-size: 0.85rem;
    margin-bottom: 0.4em;
  }

  .card__faq-columns {
    grid-template-columns: 1fr;
    gap: 0;
  }

  .faq__q {
    padding: 0.3em 0;
    font-size: 0.88em;
  }

  .faq__a {
    font-size: 0.875rem;
    padding-bottom: 0.6em;
  }

  /* Takeover: opening a question hides the intro copy and every other
     question (across both columns), and the open one fills the card —
     same reasoning/behaviour as the research card's own takeover (see
     the long comment there). Needed for the same reason: some of these
     answers run several paragraphs, too long to fit stacked normally in
     a short mobile card without overflowing its fixed height. */
  .card__faq:has(.faq[open]) .card__faq-intro {
    display: none;
  }

  .card__faq:has(.faq[open]) .card__faq-columns {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    justify-content: center;
    padding: 4rem clamp(1rem, 5vw, 2rem) 1.5rem;
  }

  .card__faq:has(.faq[open]) .faq-col:not(:has(.faq[open])) {
    display: none;
  }

  .card__faq:has(.faq[open]) .faq:not([open]) {
    display: none;
  }
}

/* =========================================================
   ENQUIRIES TAKEOVER — "Join the experiment". Fading .card-stack out
   reveals .page-bg, the dark texture video already fixed and playing
   behind everything, rather than duplicating it here.
   ========================================================= */
.join-takeover {
  position: fixed;
  inset: 0;
  z-index: 7; /* above .card-stack (3) and the corner buttons (4) */
  display: flex;
  align-items: center;
  justify-content: center;
  padding: clamp(2rem, 6vw, 5rem);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.5s ease;
}

.join-takeover.is-active {
  opacity: 1;
  pointer-events: auto;
}

.join-takeover__close {
  position: absolute;
  top: clamp(1.25rem, 3vw, 2rem);
  right: clamp(1.25rem, 3vw, 2rem);
  width: 2.5rem;
  height: 2.5rem;
  border-radius: 50%;
  border: 1px solid var(--ink);
  background: transparent;
  color: var(--ink);
  font-family: var(--font-display);
  font-size: 1.5rem;
  line-height: 1;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background 0.2s ease, transform 0.2s ease;
}

.join-takeover__close:hover {
  background: rgba(255, 255, 255, 0.1);
  transform: scale(1.06);
}

.join-form {
  width: 100%;
  max-width: 30rem;
  display: flex;
  flex-direction: column;
  gap: 1.1rem;
}

.join-form__title {
  font-family: var(--font-display);
  font-weight: 900;
  letter-spacing: -0.07em;
  font-size: clamp(1.8rem, 4vw, 2.6rem);
  line-height: 1;
  color: var(--ink);
  text-align: center;
  margin-bottom: 0.5rem;
}

.join-form__field {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  font-family: var(--font-display);
  font-weight: 700;
  letter-spacing: -0.02em;
  font-size: 0.9rem;
  color: var(--ink);
}

.join-form__field input,
.join-form__field textarea {
  font-family: var(--font-ui);
  font-size: 1rem;
  padding: 0.7em 1em;
  border: 0;
  border-radius: 6px;
  background: #c1c1c1; /* same light grey as .card__wash, not flat white */
  color: #353535; /* same dark grey used for type on every other light surface on the site */
  resize: vertical;
}

.join-form__submit {
  align-self: center;
  margin-top: 0.5rem;
  border: 0;
  border-radius: 6px;
  padding: 0.75em 2em;
  background: var(--ink);
  color: #353535;
  font-family: var(--font-display);
  font-weight: 900;
  letter-spacing: -0.07em;
  font-size: 1.05rem;
  cursor: pointer;
  transition: background 0.2s ease;
}

.join-form__submit:hover {
  background: #fff;
}

/* Same hidden-until-set/opacity-toggle pattern as .site-footer__status
   (see the comment there) and the password gate's .gate__error. */
.join-form__status {
  margin: 0;
  text-align: center;
  font-family: var(--font-ui);
  font-size: 0.9rem;
  color: var(--ink);
  opacity: 0;
  transition: opacity 0.2s ease;
}

.join-form__status.is-visible {
  opacity: 1;
}

.join-form__status.is-error {
  color: #e08383;
}

/* =========================================================
   TITLE CARD — the one real card so far: the Fair Treatment preview
   film, looping behind it. .page-bg (see .card-stack above) still
   shows through every other card's placeholder colour and the space
   around all of them; this is the only card with its own video.
   ========================================================= */
.card--title__bg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Paper grain + white wash — the default finish over every card's own
   background (the title card's film, or .card__texture-bg elsewhere).
   Same two layers, same markup, on every card: add both to any new one
   and it matches automatically, nothing else to configure. */
.card__texture {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Blended so it lightens rather than sitting as a flat layer on top. */
  mix-blend-mode: plus-lighter;
  opacity: 0.2;
  pointer-events: none;
}

.card__wash {
  position: absolute;
  inset: 0;
  background: #c1c1c1;
  opacity: 0.45;
  pointer-events: none;
  /* Same WebKit defensive fix as .card__texture-bg just above — belt
     and braces so this layer stays independently composited too. */
  isolation: isolate;
  will-change: opacity;
}

/* Title card keeps the original white wash — grey is for every other card. */
.card--title .card__wash {
  background: #fff;
}

.card--title__content {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.5rem; /* tighter than .card--title__body's gap below — pulls the subline up closer to the logo */
  text-align: center;
  padding: clamp(1.5rem, 5vw, 4rem);
  transition: opacity 0.6s ease;
  /* Reported: the Play button visible on other cards, not just the title
     card, while scrolling through the site on a real device — couldn't
     reproduce directly, but it matches the exact same category of bug
     hit repeatedly elsewhere in this file (an element sharing a clipped
     ancestor with something else — usually a video — that's mid-
     transform above it, or here, an iframe that now stays permanently in
     the DOM rather than being created/destroyed per play). Same
     established fix: pre-promote this to its own layer instead of
     leaving WebKit to build one mid-transition. */
  isolation: isolate;
  will-change: opacity;
}

.card--title__body {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1.5rem;
}

.card--title__headline {
  width: 80%;
}

.card--title__headline img {
  display: block;
  width: 100%;
  height: auto;
}

.card--title__subtitle {
  max-width: none;
  font-family: var(--font-display);
  font-weight: 900;
  font-size: clamp(1rem, 1.5vw, 1.4rem); /* site-default short-copy size — matches "Working with ECDs & EPs..." */
  line-height: 1.05;
  letter-spacing: -0.07em;
  color: #353535;
  /* Pulls it up close to the logo — about a line's worth above where
     the gap alone would otherwise put it. Tuned for desktop's wide/
     short card proportions; see the mobile override below for why
     narrow cards need less (or none) of this pull. */
  margin-top: -1em;
}

.card--title__subtitle-line {
  white-space: nowrap;
}

@media (max-width: 700px) {
  /* At phone-card proportions the logo lockup renders taller relative
     to card width than it does on desktop's wide/short cards, so the
     same -1em pull (tuned for desktop) drags the subtitle up into it
     instead of just sitting close. */
  .card--title__subtitle {
    margin-top: 0.4em;
  }
}

.card--title__watch {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.75rem;
}

.card--title__watch-label {
  display: block;
  width: clamp(6rem, 10vw, 8rem);
  height: auto;
}

.card--title__play {
  width: clamp(3.5rem, 7vw, 5rem);
  height: clamp(3.5rem, 7vw, 5rem);
  border-radius: 50%;
  border: 0;
  background: rgba(23, 19, 14, 0.55);
  backdrop-filter: blur(2px);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: background 0.2s ease, transform 0.2s ease;
}

.card--title__play:hover {
  background: rgba(23, 19, 14, 0.75);
  transform: scale(1.06);
}

.card--title__play svg {
  width: 38%;
  height: 38%;
  fill: #ffffff;
  margin-left: 4%;
}

/* Wrapper stays in the DOM (unlike the old mount-gets-replaced setup) so
   .card--title__player-toggle below survives alongside the iframe — see
   the HTML comment on this element. */
.card--title__player {
  position: absolute;
  inset: 0;
  /* Off by default so this empty wrapper doesn't itself sit in the way of
     the Play button underneath (a child can still re-enable its own hit-
     testing with pointer-events:auto regardless of this — see
     .card--title__player-toggle, the only child that ever needs to). */
  pointer-events: none;
  /* Same defensive isolation as .card--title__content — see the long
     comment there. This is the wrapper actually holding the iframe, the
     most likely source if that's what's behind the bug it's fixing. */
  isolation: isolate;
  will-change: opacity;
}

/* YT.Player replaces .home-player-target (a child of the wrapper above)
   with its own <iframe>, so this class is applied to that iframe directly
   (via JS) once it exists. It fills the card and inherits its rounded
   corners via the card's own overflow:hidden. */
.card--title__player-frame {
  position: absolute !important;
  inset: 0;
  width: 100% !important;
  height: 100% !important;
  border: 0;
}

/* Invisible full-cover click target for play/pause — the embed has no
   YouTube controls of its own to click (see the playerVars in main.js),
   so this stands in for them. pointer-events stays off except while
   actually playing: otherwise, sitting on top of .card--title__content
   in paint order (a later sibling — see the HTML), it would swallow
   clicks meant for the initial Play button before playback even starts.
   Resuming from pause is still that same Play button reappearing (see
   .card--title.is-playing:not(.is-paused) .card--title__content below),
   not this toggle — it only ever needs to pause. */
.card--title__player-toggle {
  position: absolute;
  inset: 0;
  z-index: 1; /* above the iframe */
  border: 0;
  background: transparent;
  cursor: pointer;
  pointer-events: none;
}

.card--title.is-playing:not(.is-paused) .card--title__player-toggle {
  pointer-events: auto;
}

/* While actively playing, the headline/subtitle/play button fade out of the
   way (they overlap the video). The moment playback pauses or ends, this
   override drops out and they fade gently back in. */
.card--title.is-playing:not(.is-paused) .card--title__content {
  opacity: 0;
  pointer-events: none;
}

/* On pause/end, hide the (frozen-frame) film instead of leaving it sitting
   there — this reveals the looping ambient background video underneath,
   returning the card to how it looked before you pressed play. */
.card--title.is-paused .card--title__player-frame {
  opacity: 0;
  pointer-events: none;
}

/* While the film's playing, every OTHER card fades out too (including the
   next card's peek at the bottom) so it's just the film over the plain
   dark page background — nothing else competing for attention. Reverts
   the moment playback pauses or ends. */
.card-stack:has(.card--title.is-playing:not(.is-paused)) .card:not(.card--title) {
  opacity: 0;
}

/* =========================================================
   SCROLL CUE — an idle nudge, not a constant hint: stays invisible
   until main.js decides the user's done nothing for a few seconds,
   then fades in gently. Any scroll hides it again (and restarts the
   idle timer) — see the idle-cue logic in main.js.
   ========================================================= */
.scroll-cue {
  position: absolute;
  bottom: clamp(1.5rem, 4vh, 3rem);
  left: 50%;
  transform: translateX(-50%);
  width: clamp(0.75rem, 1.5vw, 1.125rem); /* half the original size */
  height: auto;
  opacity: 0;
  pointer-events: none;
  transition: opacity 1s ease;
  z-index: 1;
}

.scroll-cue.is-visible {
  opacity: 0.85;
}

/* =========================================================
   CORNER BUTTON — "Join the experiment" (top-right). Appears once the
   intro finishes (see main.js) and stays on screen at all times,
   sitting inside whichever card is current, 20px in from its corner.
   The card's own side margin is always --card-side (see the card
   stack rules above), so `right` here never needs to change — only
   `top` does, since the current card's top edge itself moves: flush
   against the viewport for the first card (--card-side) vs. lower for
   every other card, where room's reserved for the previous card's
   peek (--card-peek + --card-gap). .is-shifted (toggled by main.js's
   setCardIndex()) switches between the two.
   ========================================================= */
.corner-button {
  position: fixed;
  top: calc(var(--card-side) + 20px);
  z-index: 4;
  display: flex;
  align-items: center;
  justify-content: center;
  background: transparent; /* keyline only, no fill */
  color: #353535;
  font-family: var(--font-display);
  font-weight: 900;
  /* Was 2x smaller than this (rendered at transform: scale(0.5)) — this
     is the same base size as before, just no longer shrunk down, so the
     on-screen result is exactly double what it was. */
  font-size: clamp(0.95rem, 1.2vw, 1.15rem);
  letter-spacing: -0.07em;
  text-decoration: none;
  text-align: center;
  opacity: 0;
  pointer-events: none;
  /* Was scaled down 20% from the "2x" base size above (0.8); now back up
     20% from THAT (0.8 * 1.2 = 0.96), so net ~4% below the base 2x size. */
  transform: scale(0.96);
  transition: transform 0.2s ease, top 0.5s ease;

  /* Nested-corner formula: pick an inner radius (the smallest corner in
     the design — here, the text itself, which is square) and add the
     padding around it to get the outer radius, so the thick border reads
     as evenly "hugging" the padded text instead of looking squashed at
     the corners. outer = inner + padding. */
  --corner-inner-radius: 6px;
  --corner-pad: 0.4em;
  padding: var(--corner-pad) calc(var(--corner-pad) * 1.6);
  border: 3px solid #353535;
  border-radius: calc(var(--corner-inner-radius) + var(--corner-pad));
}

.join-button {
  right: calc(var(--card-side) + 20px);
  transform-origin: top right;
}

.corner-button.is-visible {
  opacity: 1;
  pointer-events: auto;
}

/* Hidden while the enquiries takeover is open (see .join-takeover) —
   "everything else" fades away for that, corner buttons included; the
   takeover gets its own explicit close control instead. */
.corner-button.is-visible.is-join-hidden {
  opacity: 0;
  pointer-events: none;
}

.corner-button.is-shifted {
  top: calc(var(--card-peek) + var(--card-gap) + 20px);
}

.corner-button:hover {
  transform: scale(0.96) translateY(-2px);
}

/* =========================================================
   FOOTER CARD — the last card in the stack, reached the same way as
   every other card transition (pushing past the FAQ card slides it up
   and this one in underneath — see setCardIndex/tryAdvanceCard in
   main.js, unchanged for this card, no special-casing needed). Two
   departures from the standard card look: shorter than a normal card
   ("half a card" of reveal, not a full screen's worth), and it stays
   the raw dark TEXTURE DARK_1.mp4 look rather than every other card's
   inverted-to-light treatment. It gets there by NOT rendering its own
   copy of that video at all (see .card__layers in the HTML — no
   .card__texture-bg here, unlike every other card) — the card itself
   has no fill, so .page-bg (already fixed behind everything, already
   playing) simply shows straight through. Cards that DO render their
   own local copy of the same video only need it because they invert
   it to look light; the footer doesn't invert, so a second copy would
   just be the exact same footage playing out of sync with the first,
   reading as a faint double-exposed "two layers" ghosting. */
.card--footer:last-child {
  /* :last-child matches .card:last-child's own specificity (one class +
     one pseudo-class each) — needs the pseudo-class repeated here too,
     not just the modifier class, or the generic rule's near-full-height
     value would win on specificity regardless of source order. */
  height: clamp(14rem, 42vh, 22rem);
}

.card--footer {
  color: var(--ink);
}

/* This card has no video of its own (see the HTML comment) — .page-bg
   shows straight through instead — so it needs .card__layers' new
   video-failure fallback colour (see the comment there) undone, or it'd
   opaquely block that page-bg instead of just backstopping a video that
   isn't there in the first place. */
.card--footer .card__layers {
  background: transparent;
}

.card--footer .card__wash {
  background: var(--bg);
  opacity: 0.55;
}

/* Light strip across the top of the footer card — the one part of it
   that keeps the standard light/inverted treatment, sitting on top of
   the card's own dark background. position:absolute + inset-x:0 makes
   it exactly as wide as the card itself, same as every other card
   edge-to-edge layer; the top corners are rounded for free by the
   card's own overflow:hidden clip (see .card base rule), but the
   BOTTOM corners are this element's own — nothing else clips those,
   since the dark card body continues underneath it, hence its own
   overflow:hidden to clip .footer-supporters__layers to match. */
/* Hidden by default and only faded in once the footer card is actually
   current (see main.js's setCardIndex, which toggles .is-visible here) —
   without this it's visible the moment the footer card starts peeking
   in at the bottom of the FAQ card, before you've actually arrived. */
.footer-supporters {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  z-index: 1; /* above .card__layers */
  height: var(--footer-supporters-height);
  border-radius: 0 0 19px 19px; /* matches .card's own radius — its top corners are already clipped by the card itself */
  overflow: hidden;
  /* No isolation/will-change/transform here on purpose, same reason as
     .card itself (see the long comment there): this is the element
     with border-radius + overflow:hidden, and forcing a layer on it is
     a known Safari combo that drops the rounded clip once the layer
     settles — square bottom corners instead of curved. The WebKit
     flicker fix (isolation + will-change) lives one level down
     instead, on .footer-supporters__fade, same as .card__layers
     carries it for .card. */
}

/* Fade-in/out + the WebKit defensive fix (see .card__steps for the
   long version) live here rather than on .footer-supporters itself —
   see the comment there for why sharing the clipping element with
   isolation/will-change breaks the rounded corners in Safari. */
.footer-supporters__fade {
  position: absolute;
  inset: 0;
  isolation: isolate;
  will-change: opacity;
  /* Fade-out (scrolling back to the FAQ card) delayed to the back half
     of the card-track's 0.45s slide — see the comment on
     .card__body-copy for why. */
  opacity: 0;
  transition: opacity 0.225s ease 0.225s;
}

.footer-supporters.is-visible .footer-supporters__fade {
  opacity: 1;
  transition: opacity 0.5s ease;
}

/* Own copy of the video+grain+wash recipe every other card's own
   card__layers uses (see .card__texture-bg/.card__texture/.card__wash)
   — same values, but under different class names so the
   .card--footer .card__wash override (which deliberately darkens the
   card body's own wash) can't also catch this one. */
.footer-supporters__layers {
  position: absolute;
  inset: 0;
}

.footer-supporters__texture-bg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: invert(1);
}

.footer-supporters__texture {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  mix-blend-mode: plus-lighter;
  opacity: 0.2;
}

.footer-supporters__wash {
  position: absolute;
  inset: 0;
  background: #c1c1c1;
  opacity: 0.45;
}

/* Actual row content, lifted above .footer-supporters__layers by being
   a later sibling with its own stacking (position:relative + z-index),
   same way every other card's own content sits above its card__layers. */
.footer-supporters__content {
  position: relative;
  z-index: 1;
  height: 100%;
  display: flex;
  align-items: center;
  gap: clamp(0.75rem, 2vw, 1.25rem);
  padding: 0 clamp(1.25rem, 4vw, 2.5rem);
}

.footer-supporters__label {
  flex-shrink: 0;
  font-family: var(--font-display);
  font-weight: 900;
  letter-spacing: -0.07em;
  font-size: clamp(0.95rem, 1.3vw, 1.15rem);
  color: #353535; /* standard copy colour on a light background */
  white-space: nowrap;
}

.footer-supporters__divider {
  flex-shrink: 0;
  align-self: stretch;
  margin: 0.9rem 0;
  width: 1px;
  background: rgba(53, 53, 53, 0.3);
}

/* Clips the reel to the strip's own width — everything past the edge
   (i.e. most of the duplicated second list) stays hidden until the
   reel scrolls it into view. No mask-image edge fade (there was one
   here before): a soft alpha mask over a continuously-transforming
   child is a known WebKit trouble spot — logos would scroll fine, then
   vanish outright near the mask's edge instead of gently fading, and
   pre-promoting the reel to its own layer (isolation/will-change,
   tried first) didn't fix it either. A plain hard clip has none of
   that risk, at the minor cost of logos cutting off cleanly rather
   than fading at the edges. */
.footer-supporters__track {
  flex: 1;
  min-width: 0;
  overflow: hidden;
}

/* Two identical .footer-supporters__list children side by side — the
   reel is exactly double the width of one list, so animating exactly
   -50% ends the loop on a frame identical to the start. */
.footer-supporters__reel {
  display: flex;
  width: max-content;
  animation: footer-supporters-scroll 32s linear infinite;
  /* No isolation/will-change here — tried first (logos vanishing seemed
     like the usual WebKit dropped-paint bug documented elsewhere in this
     file), but it didn't fix it and most likely made it worse: promoting
     a translating element to its own GPU layer inside an overflow:hidden
     ancestor is a known Safari bug trigger — Safari can cull the layer
     against stale bounds once it's translated far enough, making it
     disappear rather than keep scrolling. Left as a plain animation. */
}

.footer-supporters__list {
  display: flex;
  align-items: center;
  flex-shrink: 0;
  gap: clamp(3rem, 6vw, 5rem);
  padding-right: clamp(3rem, 6vw, 5rem);
}

.footer-supporters__item {
  height: clamp(1.1rem, 2.2vw, 1.5rem);
  width: auto;
  flex-shrink: 0;
  /* Logo art is already dark-on-transparent (see Assets/logos) — this
     strip is the one light/inverted surface in the footer, same as the
     text labels it replaces, so no filter needed. */
  object-fit: contain;
}

/* Revolver's mark is a tighter, more square lockup than the wordmarks
   around it (see Assets/logos/Revolver.png) — at the same shared height
   it reads noticeably smaller/lighter in the strip, so it gets a bit
   more height than the rest to sit at an even visual weight. */
.footer-supporters__item[src*="Revolver.png"] {
  height: clamp(1.4rem, 2.75vw, 1.9rem);
}

@keyframes footer-supporters-scroll {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); }
}

/* Positioned (rather than centred via the card's own flex centring,
   which centres against the CARD's full height) so it centres against
   just the space below .footer-supporters — the card's flex centring
   would otherwise centre this against the whole card, nudging it
   visibly below true-centre of the remaining space once the strip's
   own height is taken out. */
.site-footer__subscribe {
  position: absolute;
  inset: var(--footer-supporters-height) 0 0;
  z-index: 1; /* above .card__layers — a plain in-flow box otherwise paints underneath it, same as every other card's content (see .card__body-copy etc.) */
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: clamp(1rem, 2.5vw, 1.75rem);
  padding: 0 clamp(1.5rem, 5vw, 3rem);
}

.site-footer__label {
  font-family: var(--font-display);
  font-weight: 900;
  letter-spacing: -0.07em;
  font-size: clamp(1.1rem, 1.8vw, 1.5rem);
  color: var(--ink);
  white-space: nowrap;
}

.site-footer__form {
  display: flex;
  gap: 0.75rem;
}

.site-footer__input {
  min-width: clamp(11rem, 20vw, 16rem);
  padding: 0.7em 1em;
  border: 0;
  border-radius: 6px;
  background: #fff;
  color: #353535; /* same dark grey used for type on every other light surface on the site */
  font-family: var(--font-ui);
  font-size: 0.95rem;
}

.site-footer__input::placeholder {
  color: rgba(53, 53, 53, 0.5);
}

.site-footer__submit {
  border: 0;
  border-radius: 6px;
  padding: 0.7em 1.6em;
  background: var(--ink);
  color: #353535;
  font-family: var(--font-display);
  font-weight: 900;
  letter-spacing: -0.07em;
  font-size: 1rem;
  cursor: pointer;
  transition: background 0.2s ease;
}

.site-footer__submit:hover {
  background: #fff;
}

/* Hidden until main.js sets a message + .is-visible on submit (success
   or error) — same opacity-toggle pattern as the password gate's own
   .gate__error. flex-basis:100% drops it onto its own line below the
   label/input/button row rather than squeezing the flex row taller. */
.site-footer__status {
  flex-basis: 100%;
  margin: 0;
  text-align: center;
  font-family: var(--font-ui);
  font-size: 0.85rem;
  color: var(--ink);
  opacity: 0;
  transition: opacity 0.2s ease;
}

.site-footer__status.is-visible {
  opacity: 1;
}

.site-footer__status.is-error {
  color: #e08383; /* same error red as .gate__error */
}

.site-footer__credit {
  position: absolute;
  bottom: clamp(1rem, 3vh, 1.5rem);
  font-family: var(--font-display);
  font-size: 0.8rem;
  letter-spacing: -0.02em;
  color: var(--ink);
  opacity: 0.85;
}

.site-footer__credit--left {
  left: clamp(1.5rem, 4vw, 3rem);
}

.site-footer__credit--right {
  right: clamp(1.5rem, 4vw, 3rem);
}

@media (max-width: 700px) {
  .card--footer:last-child {
    height: auto;
    min-height: clamp(16rem, 60vh, 22rem);
  }

  .card--footer {
    flex-direction: column;
    gap: 1rem;
    /* Top padding clears .footer-supporters (an absolute overlay, so it
       doesn't reserve this space itself) — without it the stacked
       content below starts right underneath the strip instead of
       after it. */
    padding: calc(var(--footer-supporters-height) + 1rem) 0 1.5rem;
  }

  /* Desktop centres this against the space below .footer-supporters via
     position:absolute (see the base rule) — on mobile .card--footer is
     already an auto-height flex column with its own top padding doing
     that job, so this just needs to be a normal flowed item again,
     stacking with .site-footer__credit below instead of floating
     independently on top of it. */
  .site-footer__subscribe {
    position: static;
    inset: auto;
    flex-direction: column;
    gap: 0.75rem;
  }

  .site-footer__credit {
    position: static;
  }
}

/* =========================================================
   ACCESSIBILITY — respect reduced-motion.
   ========================================================= */
@media (prefers-reduced-motion: reduce) {
  .word { will-change: auto; }
  .footer-supporters__reel { animation: none; }
}
