/*!
 * Joe Stocker Fundraiser — FILM + REVEAL presentation (production).
 *
 * Loaded from the plugin, so unlike the page's Elementor custom CSS this file is
 * NOT passed through WordPress's escaping. That matters concretely: page custom
 * CSS has its child-combinator character rewritten to an HTML entity, which
 * silently invalidated twelve reveal rules on the live page — no console error,
 * no visual clue except that nothing ever hid before revealing. Combinators are
 * safe here. Everything is namespaced under `.camp` so no unrelated page,
 * template or Elementor document can be touched.
 *
 * Two blocks:
 *   1 · THE FILM — ported verbatim from the approved Design Lab composition.
 *   2 · REVEAL CHOREOGRAPHY — the approved motion contract.
 */

/* ══════════════════════════ 1 · THE FILM ═════════════════════════════════ */

/* ── TYPOGRAPHY CHAIN REPAIR ──────────────────────────────────────────────────
   Body copy on the live page rendered in the THEME's font, not Inter. The cause
   is a broken inheritance chain, not a missing declaration:

     • `.camp` correctly sets `font-family: var(--font-body)` (Inter), and wins
       against the theme because a class beats a type selector.
     • The campaign stylesheet then sets `font-family: inherit` on copy elements
       (p, li, figcaption …) — which is only correct if every ancestor between
       them and `.camp` also resolves to Inter.
     • The theme ships `p, td, span, li, dd, div { font-family: NeueHelveticaPro45Light }`.
       `div` matches EVERY intermediate container inside .camp — .container,
       .prose, .chapter__head — so those divs take the theme font, and the copy
       elements faithfully `inherit` it. Measured live: `.camp` = Inter, but
       `.prose p` = NeueHelveticaPro45Light.

   Restating `inherit` on the containers with one class of specificity repairs
   the chain without hard-coding a family, so any element with its own explicit
   font (the display headings, blockquotes) is untouched.

   NB: the plugin's campaign.css also carries a namespacing bug in the same area —
   `.camp :where(h1, .camp h2, .camp p, …)` prefixes `.camp` INSIDE :where(), so
   every entry after the first requires two nested `.camp` ancestors and can
   never match. The page's own generated CSS happens to carry a correct copy of
   that rule, so this file only needs to repair the chain above. */
.camp div, .camp span, .camp td,
.camp p, .camp li, .camp dd, .camp dt,
.camp figcaption, .camp label, .camp legend { font-family: inherit; }

/* ── [hidden] MUST WIN ────────────────────────────────────────────────────────
   Not cosmetic — load-bearing. The film's component classes set display:flex or
   display:grid, and a class selector outranks the user-agent [hidden] rule. So
   `endCard.hidden = true` and `playBtn.hidden = true` are silently ignored and
   the end card renders OVER THE SPEAKER'S FACE during playback while the poster
   button and the transport both show in the rail at once. A local replica of
   this delivery path reproduced exactly that.

   The approved stylesheet carries this rule; the plugin's own campaign.css does
   not, so it is restored here. Every hidden-attribute state in the film — end
   card, poster affordance, transport, loading — depends on it. */
.camp [hidden] { display: none !important; }


/* ── Motion tokens, declared HERE and not merely inherited ────────────────────
   The film and the choreography below are written in terms of --m-ease,
   --m-ease-out and --m-reveal. Those were previously defined only in the PAGE's
   Elementor custom CSS, which is a different stylesheet under different
   ownership — and a CSS custom property that fails to resolve makes the whole
   `animation` shorthand invalid, so the animation-name silently becomes `none`
   and every reveal renders fully visible with no error anywhere. That is exactly
   what a local replica of this delivery path reproduced.

   Declaring them in the plugin makes the runtime self-contained. They are plain
   declarations, not !important, and this file loads before the page's generated
   CSS, so the page can still override any of them. */
.camp {
  --m-ease: cubic-bezier(0.22, 1, 0.36, 1);
  --m-ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --m-fast: 190ms;
  --m-reveal: 660ms;
  --m-narrative: 1100ms;
  --m-story: 1900ms;
  --m-step: 90ms;
}

.camp .film .chapter__lede{max-width: 52ch;}
.camp .film__credit{max-width: 1280px; margin: clamp(20px, 2.4vw, 30px) auto 0;
  padding-top: clamp(16px, 2vw, 24px);
  border-top: 1px solid rgba(244, 239, 226, 0.13);
  display: flex; align-items: center; gap: clamp(14px, 1.8vw, 22px);}
.camp .film__credit-mark{height: clamp(72px, 6vw, 92px); width: auto; flex: 0 0 auto; display: block;
  filter: brightness(0) invert(1); opacity: 0.85;}
.camp .film__credit-text{margin: 0; display: flex; flex-direction: column; gap: 5px;}
.camp .film__credit-text span{font-size: 0.72rem; letter-spacing: 0.18em; text-transform: uppercase;
  font-weight: 600; color: var(--on-dark-mut);}
/* POLISH — was 1.6vw (23px at 1440), the loudest line in the lower half and a
   third announcement of a name the poster already burns in and the ask repeats.
   1.18vw reads 17px: a credit bound to the film, under the context paragraph. */
.camp .film__credit-text b{font-family: var(--font-display); font-weight: 500; letter-spacing: 0.005em;
  font-size: clamp(1rem, 1.18vw, 1.15rem); line-height: 1.15; color: var(--on-dark);}
/* POLISH — the stack stays, the centring goes. Centred, this block was the one
   island in a section whose quote, context and film all sit hard on the 24px
   gutter, so it read as a detached badge rather than as the film's credit. */
@media (max-width: 560px) {
  .camp .film__credit{flex-direction: column; align-items: flex-start; text-align: left; gap: 12px;}
  .camp .film__credit-text{align-items: flex-start;}
}
/* SECTION PASS 02 — the duplicate 99% quote, the Chapters navigation and the
   grid that held them side by side are GONE, and so are their rules:
   .film__under / .film__quote / .film__chapters(-title) / .film__list, plus
   .film__cue, which only ever styled the two timestamp buttons those blocks
   owned. The runtime still queries [data-film-seek]; it now matches nothing,
   which is a valid empty NodeList, so no seek wiring is left dangling.

   The freed height is NOT refilled. It is spent on the film itself: the shell
   goes from 1180px to 1280px, and the space around it grows, which is the
   whole point of taking the navigation out. */

/* JOE'S ASK — the section's last word, and the only quote in it now. Larger
   than the old handoff because it no longer competes with a pull-quote 80px
   above it, and centred on a short measure so the line lands as one breath.
   The attribution is two lines, name over role, in the campaign's existing
   eyebrow idiom; no timestamp, because the cue it used to carry pointed at a
   sentence this quote no longer reproduces verbatim. */
/* POLISH — the border-top is GONE. It and the credit's rule sat 106px apart at
   1440 spanning 780 and 660 from left edges 60px apart at two different
   opacities: two near-parallel lines that read as one broken grid. The credit's
   rule survives as the lower third's single separator because it does work this
   one never did — it binds the credit to the film above it. The ask is then set
   apart by space and by its own display scale, which is what an editorial
   closing statement uses. text-align goes with it; the ask now joins the
   section's left spine rather than floating on its own centre. */
.camp .film__handoff{max-width: 820px; margin: clamp(36px, 7vw, 96px) 0 0; text-align: left;}
.camp .film__handoff blockquote{font-family: var(--font-display); font-weight: 500; font-size: clamp(1.35rem, 2.4vw, 2rem);
  line-height: 1.3; letter-spacing: -0.012em; color: var(--on-dark); text-wrap: balance;}
.camp .film__handoff figcaption{margin-top: clamp(18px, 2vw, 26px); display: flex; flex-direction: column; gap: 4px;}
.camp .film__handoff figcaption b{font-family: var(--font-display); font-weight: 500;
  font-size: clamp(1rem, 1.3vw, 1.12rem); color: var(--on-dark);}
.camp .film__handoff figcaption span{font-size: 0.74rem; letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--on-dark-mut);}
@media (max-width: 560px) {
  .camp .film__shell{border-radius: var(--radius-md);}
  .camp .film__meta-opt{display: none;}
}
.camp .matchby{max-width: 980px;
  margin-top: clamp(34px, 5vw, 60px);
  padding-top: clamp(24px, 3vw, 36px);
  border-top: 1px solid var(--rule);
  display: flex; flex-direction: column; align-items: center; gap: 18px;
  text-align: center;}
.camp .matchby__eyebrow{margin: 0; font-size: 0.72rem; letter-spacing: 0.18em; text-transform: uppercase;
  font-weight: 600; color: var(--text-muted);}
.camp .matchby__mark{height: clamp(72px, 7vw, 96px); width: auto; display: block;}
@media (prefers-reduced-motion: reduce) {
  .camp .film__shell, .camp .film__handoff{opacity: 1 !important; transform: none !important;}
}
@media (pointer: coarse), (max-width: 768px) {
  .camp .film__ctl{width: 44px; height: 44px;}
}
.camp .film__shell{max-width: 1280px; margin: clamp(40px, 5.6vw, 76px) auto 0;
  background: #0b0a07; border-radius: var(--radius-lg); overflow: hidden;
  box-shadow: 0 40px 90px -30px rgba(0, 0, 0, 0.85), 0 0 0 1px rgba(218, 165, 32, 0.22);}
.camp .film__viewport{position: relative; aspect-ratio: 16 / 9; overflow: hidden; background: #0b0a07;}
.camp .film__stage, .camp .film__embed, .camp .film__poster, .camp .film__grade, .camp .film__surface, .camp .film__endcard{position: absolute; inset: 0; width: 100%; height: 100%;}
.camp .film__embed{border: 0; display: block;}
.camp .film__poster{object-fit: cover; object-position: 50% 42%; transition: opacity var(--m-reveal) var(--m-ease);}
/* SECTION PASS 02 — the grade was 0.72 at the foot, and that number existed for
   ONE job: to seat the .film__slate lower-third against a bright classroom
   frame. The slate is gone and the approved Joe frame carries its own burned-in
   identification in campaign gold at exactly that spot, so the old value was
   doing nothing but dulling it. Nothing needs legibility over this poster —
   the transport rail sits BELOW the picture, not on it — so this is now only a
   whisper to seat the frame in the rounded shell. The `ended` state keeps its
   own 0.88 wash for the end card, which is unchanged. */
.camp .film__grade{background: linear-gradient(to top, rgba(11, 10, 7, 0.2) 0%, rgba(11, 10, 7, 0) 34%);
  transition: opacity var(--m-reveal) var(--m-ease);}
.camp .film__shell[data-state="loading"] .film__poster, .camp .film__shell[data-state="loading"] .film__grade, .camp .film__shell[data-state="playing"] .film__poster, .camp .film__shell[data-state="playing"] .film__grade, .camp .film__shell[data-state="paused"]  .film__poster, .camp .film__shell[data-state="paused"]  .film__grade, .camp .film__shell[data-state="loading"] .film__slate, .camp .film__shell[data-state="playing"] .film__slate, .camp .film__shell[data-state="paused"]  .film__slate{opacity: 0; pointer-events: none;}
.camp .film__shell[data-state="ended"] .film__poster{opacity: 1;}
.camp .film__shell[data-state="ended"] .film__grade{opacity: 1; background: rgba(11, 10, 7, 0.88);}
.camp .film__slate{position: absolute; left: clamp(16px, 2.4vw, 30px); bottom: clamp(14px, 2vw, 26px);
  color: var(--on-dark); transition: opacity var(--m-reveal) var(--m-ease); pointer-events: none;}
.camp .film__slate b{display: block; font-size: 0.72rem; letter-spacing: 0.2em; text-transform: uppercase; color: var(--gold);}
.camp .film__slate span{display: block; margin-top: 5px; font-family: var(--font-display); font-size: clamp(1rem, 1.5vw, 1.35rem);}
.camp .film__surface{background: none; border: 0; padding: 0; margin: 0; cursor: pointer; z-index: 2;}
.camp .film__shell[data-state="ended"] .film__surface{display: none;}
.camp .film__loading{position: absolute; left: 50%; bottom: 10%; transform: translateX(-50%); display: none; gap: 7px; z-index: 3;}
.camp .film__shell[data-state="loading"] .film__loading{display: flex;}
.camp .film__loading i{width: 7px; height: 7px; border-radius: 50%; background: var(--gold); opacity: 0.35;}
@media (prefers-reduced-motion: no-preference) {
  .camp .film__loading i{animation: filmPulse 1.1s var(--m-ease) infinite;}
  .camp .film__loading i:nth-child(2){animation-delay: 0.16s;}
  .camp .film__loading i:nth-child(3){animation-delay: 0.32s;}
  @keyframes filmPulse{0%, 100% { opacity: 0.25; } 50% { opacity: 1; }}
}
.camp .film__endcard{z-index: 5; display: grid; place-content: center; justify-items: center;
  gap: clamp(16px, 2.2vw, 26px); padding: clamp(20px, 4vw, 44px); text-align: center;}
.camp .film__end-line{font-family: var(--font-display); font-weight: 500; color: var(--on-dark); font-size: clamp(1.1rem, 2.3vw, 1.95rem); line-height: 1.25; max-width: 20ch; text-wrap: balance;}
.camp .film__end-cta{display: flex; flex-wrap: wrap; gap: 12px; justify-content: center;}
.camp .film__end-cta .btn--ghost{border-color: rgba(244, 239, 226, 0.5);}
.camp .film__rail{display: flex; align-items: center;
  padding: clamp(10px, 1.4vw, 16px) clamp(12px, 1.8vw, 22px);
  background: #0b0a07; border-top: 1px solid rgba(244, 239, 226, 0.12); color: var(--on-dark);
  min-height: 62px;}
.camp .film__start{display: flex; align-items: center; gap: clamp(10px, 1.4vw, 16px);
  background: none; border: 0; padding: 4px 2px; margin: 0; cursor: pointer;
  color: var(--on-dark); font-family: inherit; text-align: left; width: 100%;}
.camp .film__start-disc{flex: 0 0 auto; width: 42px; height: 42px; border-radius: 50%; display: grid; place-items: center;
  padding-left: 3px; background: var(--gold); color: var(--ink);
  box-shadow: 0 6px 20px rgba(218, 165, 32, 0.4);
  transition: transform var(--dur-slow) var(--m-ease), background var(--dur-base) linear;}
/* POLISH — was 1.5vw (20.8px at 1440), which put a playback control above the
   context paragraph (18.4px) in the type ranking. 1.15vw reads 16.6px: a clear,
   tappable affordance that sits where controls belong, under the copy. */
.camp .film__start b{font-family: var(--font-display); font-weight: 500; font-size: clamp(0.98rem, 1.15vw, 1.12rem); letter-spacing: -0.005em;}
.camp .film__start-meta{margin-left: auto; font-size: 0.78rem; letter-spacing: 0.12em; text-transform: uppercase; color: rgba(244, 239, 226, 0.62); font-variant-numeric: tabular-nums;}
.camp .film__start:hover .film__start-disc, .camp .film__shell[data-state="poster"] .film__surface:hover ~ .film__rail .film__start-disc{transform: scale(1.07); background: #f0bb3a;}
.camp .film__start:focus-visible{outline: 2px solid var(--gold); outline-offset: 3px; border-radius: var(--radius-sm);}
.camp .film__transport{display: flex; align-items: center; gap: clamp(8px, 1.2vw, 14px); width: 100%;}
.camp .film__ctl{flex: 0 0 auto; width: 36px; height: 36px; display: grid; place-items: center;
  background: none; border: 0; border-radius: 50%; color: var(--on-dark); cursor: pointer;
  transition: background var(--dur-base) linear, color var(--dur-base) linear;}
.camp .film__ctl:hover{background: rgba(244, 239, 226, 0.16);}
.camp .film__ctl:focus-visible{outline: 2px solid var(--gold); outline-offset: 2px;}
.camp .film__ctl[aria-pressed="false"]{color: rgba(244, 239, 226, 0.45);}
.camp .film__ctl--toggle{background: rgba(244, 239, 226, 0.14);}
.camp .film__ico{display: none;}
.camp .film__ctl--toggle[data-mode="pause"] .film__ico--pause{display: block;}
.camp .film__ctl--toggle[data-mode="play"]  .film__ico--play{display: block;}
.camp .film__time{flex: 0 0 auto; font-size: 0.8rem; font-variant-numeric: tabular-nums; letter-spacing: 0.03em; display: flex; gap: 5px;}
.camp .film__time i{font-style: normal; opacity: 0.4;}
.camp .film__time > span{opacity: 0.7;}
.camp .film__scrub{flex: 1 1 auto; min-width: 0; height: 22px; margin: 0; cursor: pointer; -webkit-appearance: none; appearance: none; background: none;}
.camp .film__scrub::-webkit-slider-runnable-track{height: 3px; border-radius: 2px; background: rgba(244,239,226,0.28);}
.camp .film__scrub::-moz-range-track{height: 3px; border-radius: 2px; background: rgba(244,239,226,0.28);}
.camp .film__scrub::-webkit-slider-thumb{-webkit-appearance: none; appearance: none; width: 13px; height: 13px; border-radius: 50%; background: var(--gold); margin-top: -5px; box-shadow: 0 0 0 4px rgba(218,165,32,0.22);}
.camp .film__scrub::-moz-range-thumb{width: 13px; height: 13px; border: 0; border-radius: 50%; background: var(--gold);}
.camp .film__scrub:focus-visible{outline: 2px solid var(--gold); outline-offset: 4px; border-radius: 3px;}
.camp .film__shell.has-error .film__start b::after{content: " — opens on YouTube"; font-size: 0.7em; opacity: 0.75; font-family: var(--font-body); font-weight: 400;}
@media (max-width: 560px) {
  .camp .film__rail{padding: 10px 12px; min-height: 56px;}
  .camp .film__start-disc{width: 38px; height: 38px;}
  .camp .film__start-meta{font-size: 0.7rem; letter-spacing: 0.08em;}
  .camp .film__slate span{font-size: 0.95rem;}
}
/* SECTION PASS 02 — this was `max-width: 400px`, which hid "5:58 · CAPTIONS"
   at 375 and 390, two of the three widths this section is composed for, and the
   runtime and the captions promise are both things the film is supposed to
   state up front. Measured at 375 the rail has 303px of inner width against
   ~265px of content, so it fits; it only stops fitting near 320. The threshold
   now sits where the constraint actually is rather than 60px early. */
@media (max-width: 340px) {
  .camp .film__start-meta{display: none;}
}
.camp .film__slate{visibility: visible; transition: opacity var(--m-reveal) var(--m-ease), visibility 0s linear 0s;}
.camp .film__shell[data-state="loading"] .film__slate, .camp .film__shell[data-state="playing"] .film__slate, .camp .film__shell[data-state="paused"] .film__slate{visibility: hidden; transition: opacity var(--m-reveal) var(--m-ease), visibility 0s linear var(--m-reveal);}
@media (prefers-reduced-motion: reduce) {
  .camp .film__handoff figcaption{opacity: 1 !important;}
}
/* ── REMOVED: a SECOND, competing choreography ───────────────────────────────
   The extraction that ported the film composition also swept in the approved
   prototype's whole reveal layer, keyed on `[data-scene]` and on keyframe names
   (copyRise / sceneRise / filmOpen) that are NOT defined in this file. Two
   consequences, both bad:

     • `copyRise` happens to be defined in the PAGE's Elementor CSS, so those
       rules would really animate — two systems driving the same elements with
       different names and delays, last-declaration-wins per property.
     • Its reduced-motion block carried the same malformed pattern being fixed
       in campaign.css: `.camp [data-scene] :is(.chapter__num, .camp .chapter__title, …)`,
       where every entry after the first demands two nested `.camp` ancestors and
       can therefore never match.

   Section 2 below is the single choreography, on `.jsf-scene` with `jsf`-prefixed
   keyframes that are defined here. `filmOpen` had no remaining referrer and was
   dropped with it; `filmPulse` stays, defined beside the loading pips that use
   it. Do not reintroduce a second reveal layer in this file. */

/* ═════════════════════ 2 · REVEAL CHOREOGRAPHY ═══════════════════════════
   HOW IT WORKS — read this before editing anything below.

   Every reveal target carries its FULL animation from the start, with `both`
   fill and `animation-play-state: paused`. A paused animation holding its
   backwards fill renders frame 0 — that IS the pre-reveal state. No separate
   `opacity: 0` rule exists to drift out of sync with the animation, which is
   what made the previous system fragile.

   campaign-reveal.js adds `.is-revealed` (and `.is-in`) to the scene root; one
   rule flips its subtree to `running` and the authored sequence plays, in TIME,
   once. `.is-instant` — a scene the visitor already scrolled past, e.g. after a
   refresh halfway down — kills the animation outright.

   EVERYTHING here is gated on `html[data-reveal="on"]`, set by the head probe
   the plugin prints, only when JS + IntersectionObserver are present and motion
   is allowed. No JS, a broken bundle, or `prefers-reduced-motion: reduce` → the
   attribute never appears (or the watchdog strips it) → every rule below stops
   applying and the page is complete, readable and donatable.

   `[data-scene]` is the approved contract; `.reveal` is what the currently
   deployed page carries. Both are honoured so the runtime never depends on a
   page write having landed first.
   ═══════════════════════════════════════════════════════════════════════════ */
@media (prefers-reduced-motion: no-preference) {
  html[data-reveal="on"] .camp {
    --r-rise: 700ms;   /* headline-weight entrance         */
    --r-copy: 560ms;   /* body copy, list items            */
    --r-story: 1100ms; /* photography, the film, the prism */
  }

  /* ── chapter heads · numeral → headline → lede ─────────────────────────── */
  html[data-reveal="on"] .camp .jsf-scene .chapter__num   { animation: jsfSceneRise var(--r-rise) var(--m-ease)   0ms both; }
  html[data-reveal="on"] .camp .jsf-scene .chapter__title { animation: jsfSceneRise 760ms          var(--m-ease)  90ms both; }
  html[data-reveal="on"] .camp .jsf-scene .chapter__lede  { animation: jsfCopyRise  620ms          var(--m-ease) 220ms both; }

  /* ── prose, lists, proof ──────────────────────────────────────────────── */
  html[data-reveal="on"] .camp .jsf-scene .prose > p                 { animation: jsfCopyRise var(--r-copy) var(--m-ease) 0ms both; }
  html[data-reveal="on"] .camp .jsf-scene .prose > p:nth-of-type(2)  { animation-delay:  95ms; }
  html[data-reveal="on"] .camp .jsf-scene .prose > p:nth-of-type(3)  { animation-delay: 190ms; }
  html[data-reveal="on"] .camp .jsf-scene .caps > li                 { animation: jsfCopyRise 480ms var(--m-ease) 300ms both; }
  html[data-reveal="on"] .camp .jsf-scene .caps > li:nth-child(2)    { animation-delay: 385ms; }
  html[data-reveal="on"] .camp .jsf-scene .caps > li:nth-child(3)    { animation-delay: 470ms; }
  html[data-reveal="on"] .camp .jsf-scene .caps > li:nth-child(4)    { animation-delay: 555ms; }
  html[data-reveal="on"] .camp .jsf-scene .caps > li:nth-child(5)    { animation-delay: 640ms; }
  html[data-reveal="on"] .camp .jsf-scene .proofline__title          { animation: jsfSceneRise var(--r-rise) var(--m-ease) 0ms both; }
  html[data-reveal="on"] .camp .jsf-scene .proofline__stats li       { animation: jsfCopyRise var(--r-copy) var(--m-ease) 150ms both; }
  html[data-reveal="on"] .camp .jsf-scene .proofline__stats li:nth-child(2) { animation-delay: 260ms; }
  html[data-reveal="on"] .camp .jsf-scene .proofline__stats li:nth-child(3) { animation-delay: 370ms; }
  /* Chapter I's evidence rail carries a FOURTH proof point (returned alumni).
     Without this the fourth item animated in on the same 150ms as the first and
     broke the left-to-right read of the row. */
  html[data-reveal="on"] .camp .jsf-scene .proofline__stats li:nth-child(4) { animation-delay: 480ms; }
  html[data-reveal="on"] .camp .jsf-scene .split__text > *           { animation: jsfCopyRise var(--r-copy) var(--m-ease) 120ms both; }

  /* ── photography settles into its frame ───────────────────────────────── */
  html[data-reveal="on"] .camp .jsf-scene .figure img,
  html[data-reveal="on"] .camp .figure.jsf-scene img       { animation: jsfPhotoSettle var(--r-story) var(--m-ease-out) 0ms both; }
  html[data-reveal="on"] .camp .figure.jsf-scene figcaption { animation: jsfCopyRise var(--r-copy) var(--m-ease) 340ms both; }
  .camp .figure { overflow: clip; }

  /* ── CHAPTER II · THE FILM ────────────────────────────────────────────
     The shell is BOTH the scene root and the only thing that moves — picture,
     overlays and rail are its children, so ONE object arrives, not five. */
  html[data-reveal="on"] .camp .film__shell.jsf-scene            { animation: jsfFilmOpen var(--r-story) var(--m-ease-out) 0ms both; }
  html[data-reveal="on"] .camp .film__handoff.jsf-scene blockquote  { animation: jsfCopyRise var(--r-rise) var(--m-ease)   0ms both; }
  html[data-reveal="on"] .camp .film__handoff.jsf-scene figcaption  { animation: jsfCopyRise var(--r-copy) var(--m-ease) 230ms both; }
  html[data-reveal="on"] .camp .film__credit.jsf-scene .film__credit-text { animation: jsfCopyRise var(--r-copy) var(--m-ease)   0ms both; }
  html[data-reveal="on"] .camp .film__credit.jsf-scene .film__credit-mark { animation: jsfCopyRise var(--r-copy) var(--m-ease) 140ms both; }

  /* ── SIGNATURE · THE DOUBLING ─────────────────────────────────────────
     The one sequence the campaign rests on, and it must be WITNESSED:
        0ms    $1 arrives             "you give"
        420ms  the prism draws        ← the transformation
        900ms  the beams name themselves
        1180ms $2 arrives             "of impact"
     ~1.9s end to end, played in time. */
  html[data-reveal="on"] .camp .doubling.jsf-scene .doubling__side       { animation: jsfSceneRise var(--r-rise) var(--m-ease) 0ms both; }
  html[data-reveal="on"] .camp .doubling.jsf-scene .doubling__prism,
  html[data-reveal="on"] .camp .doubling.jsf-scene .prism__svg           { animation: jsfPrismDraw 900ms var(--m-ease-out) 420ms both; }
  html[data-reveal="on"] .camp .doubling.jsf-scene .doubling__beamlbl    { animation: jsfFade 500ms var(--m-ease) 900ms both; }
  html[data-reveal="on"] .camp .doubling.jsf-scene .doubling__flow-label { animation: jsfFade 500ms var(--m-ease) 1000ms both; }
  html[data-reveal="on"] .camp .doubling.jsf-scene .doubling__side--sum  { animation: jsfSceneRise 760ms var(--m-ease) 1180ms both; }

  /* ── the Patriot signature under the equation ─────────────────────────── */
  html[data-reveal="on"] .camp .matchby.jsf-scene .matchby__eyebrow { animation: jsfCopyRise var(--r-copy) var(--m-ease)   0ms both; }
  html[data-reveal="on"] .camp .matchby.jsf-scene .matchby__mark    { animation: jsfCopyRise var(--r-copy) var(--m-ease) 140ms both; }

  /* ── CHAPTER · THE IMPACT ENGINE ──────────────────────────────────────
     The instrument's own three-beat fill is time-driven and owned by
     campaign-flagship.js, which starts it on viewport entry. CSS only brings
     the frame in around it. */
  html[data-reveal="on"] .camp .engine.jsf-scene .engine__stage    { animation: jsfCopyRise var(--r-rise) var(--m-ease)   0ms both; }
  html[data-reveal="on"] .camp .engine.jsf-scene .engine__controls { animation: jsfCopyRise var(--r-copy) var(--m-ease) 260ms both; }
  html[data-reveal="on"] .camp .engine.jsf-scene .engine__note     { animation: jsfCopyRise var(--r-copy) var(--m-ease) 360ms both; }

  /* ── CHAPTER · THE ASK — established fast; it must be usable at once ──── */
  html[data-reveal="on"] .camp .jsf-scene .give6__head > *  { animation: jsfCopyRise var(--r-copy) var(--m-ease)   0ms both; }
  html[data-reveal="on"] .camp .jsf-scene .give6__avail     { animation: jsfCopyRise var(--r-copy) var(--m-ease) 200ms both; }
  html[data-reveal="on"] .camp .jsf-scene .inst             { animation: jsfSceneRise var(--r-rise) var(--m-ease) 120ms both; }

  @keyframes jsfSceneRise   { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: none; } }
  @keyframes jsfCopyRise    { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: none; } }
  @keyframes jsfFade        { from { opacity: 0; } to { opacity: 1; } }
  @keyframes jsfPhotoSettle { from { opacity: 0.55; transform: scale(1.03); clip-path: inset(0 0 6% 0); } to { opacity: 1; transform: none; clip-path: inset(0 0 0 0); } }
  @keyframes jsfPrismDraw   { from { clip-path: inset(0 100% 0 0); opacity: 0.35; } to { clip-path: inset(0 0 0 0); opacity: 1; } }
  @keyframes jsfFilmOpen    { from { opacity: 0; transform: translateY(26px) scale(0.985); } to { opacity: 1; transform: none; } }

  /* ── The two control rules. Nothing else gates visibility. ──────────────
     `animation:` is a SHORTHAND: every rule above resets animation-play-state to
     `running`, and scene-scoped selectors also outweigh this one on specificity,
     so source order alone does not settle it. `!important` is the honest fix —
     the pause is a hard state, not a preference. It stops applying the instant
     `.is-revealed` lands, and the shorthand's own `running` takes over. */
  html[data-reveal="on"] .camp .jsf-scene:not(.is-revealed),
  html[data-reveal="on"] .camp .jsf-scene:not(.is-revealed) * { animation-play-state: paused !important; }
  html[data-reveal="on"] .camp .jsf-scene.is-instant,
  html[data-reveal="on"] .camp .jsf-scene.is-instant * { animation: none !important; }
}

/* ── Reduced motion — complete, resolved composition; no motion at all ─────
   The head probe never arms data-reveal here, so none of the above applies.
   This is the belt-and-braces floor. An explicit target list, NOT a universal
   selector: `.jsf-scene *` would also force-show things that are legitimately
   hidden state (the engine beams, the film's loading pips, the end card). */
@media (prefers-reduced-motion: reduce) {
  .camp .jsf-scene, .camp .jsf-scene :is(
    .chapter__num, .chapter__title, .chapter__lede,
    p, li, .caps, .proofline__title, .proofline__stats li, figcaption,
    .pullquote, .pullquote__cite, .split__text > *,
    .doubling__side, .doubling__flow-label, .doubling__beamlbl, .doubling__prism, .prism__svg,
    .film__shell,
    .film__credit-text, .film__credit-mark, .matchby__eyebrow, .matchby__mark,
    .give6__head > *, .give6__lede, .give6__avail, .inst,
    .engine__stage, .engine__controls, .engine__note
  ) { animation: none !important; opacity: 1 !important; transform: none !important; }
  .camp .jsf-scene :is(.doubling__prism, .prism__svg, .figure img) { clip-path: none !important; }
  .camp .figure.jsf-scene img, .camp .film__shell.jsf-scene { animation: none !important; opacity: 1 !important; transform: none !important; }
}

/* ── THEME RESET CONTAINMENT ──────────────────────────────────────────────────
   Hello Elementor's reset.css (wp-content/themes/hello-elementor/assets/css/
   reset.css, v3.4.5) ships:

     [type=button]:focus,[type=button]:hover,[type=submit]:focus,
     [type=submit]:hover,button:focus,button:hover{background-color:#c36;color:#fff}

   Every film control is a <button>, so the theme repainted them raspberry
   (#c36 = rgb(204,51,102)) on hover AND focus. The worst case was
   .film__surface, which spans the whole picture at z-index 2: hovering the
   playing video turned it into a solid magenta rectangle with the transport
   still visible below. `.film__ctl:hover` was already defended; hover on
   .film__surface/.film__start and :focus on all three were not.

   Neutralised at equal-or-higher specificity, scoped under .camp — no
   !important needed (theme rule is 0-1-1, these are 0-3-0). Focus stays
   visible through the existing :focus-visible outlines above. */
.camp .film__surface:hover, .camp .film__surface:focus, .camp .film__surface:active,
.camp .film__start:hover,   .camp .film__start:focus,   .camp .film__start:active{
  background-color: transparent;
}
.camp .film__ctl:focus, .camp .film__ctl:active{ background-color: rgba(244, 239, 226, 0.16); }
/* after .film__ctl so the toggle keeps its own resting tint when focused */
.camp .film__ctl--toggle:focus, .camp .film__ctl--toggle:active{ background-color: rgba(244, 239, 226, 0.14); }

/* ══════════ SECTION II · SCALE, MEASURE AND RHYTHM ══════════════════════════
   Content and typography families are approved and untouched. This is a
   proportion pass, and every selector is scoped to `.camp .film` because
   `.chapter`, `.chapter__head`, `.chapter__title` and `.chapter__lede` are
   SHARED with the Why and Match chapters, which are locked.

   WHAT WAS WRONG AT 1440, measured off the render:
     film shell   1280px against a 1440 viewport — 89% of the screen, so the
                  video read as the page rather than as an element in a section
     opening quote 54.4px against the hero's 100px — not subordinate enough
                  once the video beneath it was that large
     lede         20.8px, the same size as the hero's body copy, over a 682px
                  measure, so it read as a second headline rather than as copy
     Patriot mark 86px — supporting credibility at display scale

   THE EDITORIAL COLUMN. One shared 1040px column now carries the head, the
   film and the credit, so those three sit on a common left and right edge and
   read as one story instead of three independently centred blocks. At 1440
   that leaves ~176px of stage either side of the film: the viewer sees
   section, then film, rather than ENTIRE SCREEN = VIDEO. The closing ask then
   steps IN to 720px — a deliberate taper, wide question -> widest film ->
   narrower invitation — which is what hands the eye down the section instead
   of a stack of equal-width slabs.

   The two head gaps were also doing one job twice: `.chapter__head`'s
   margin-bottom plus the shell's margin-top measured 128px of combined air at
   1440. The head's margin is zeroed here and the shell's margin carries the
   beat alone, so the space is controlled rather than accumulated. */
/* ══ SECTION II DENSITY SYSTEM ════════════════════════════════════════════
   Every measure and every vertical beat in #film derives from the four tokens
   below, so the section scales as ONE composition rather than as six blocks
   that each grew on their own. That independence was the actual defect: the
   film had been corrected three times while the quote, the intro, the Patriot
   lockup, the ask and the air between them all kept display-scale values, so
   the section still read GIANT QUOTE / GIANT VIDEO / GIANT BRAND / GIANT QUOTE
   however narrow the player got.

     --f-col   780px  the film, and the credit that belongs to it
     --f-text  700px  the opening question's measure
     --f-read  660px  body copy, and the closing ask
     --f-gut    24px  the container's own gutter

   The head is --f-col + 2 gutters, so its TEXT box lands on the film's left
   edge rather than 24px inside it. The taper is deliberate and is what hands
   the eye down the section: question 700 -> film 780 -> ask 660. The hero
   stays the one oversized moment; Chapter II steps down to meet it.

   Scoped to >=601px on purpose. Mobile is gutter-limited at these widths and
   its type is set by the 600px block below, so nothing here can reach it. */
@media (min-width: 601px) {
  .camp .film{
    --f-col: 780px; --f-text: 700px; --f-read: 660px; --f-gut: 24px;
    /* was 8vw (115px a side at 1440) — the single largest block of air in the
       section and the reason it opened and closed like a full-screen event */
    padding-block: clamp(48px, 5vw, 78px);
  }
  /* THE SPINE OWNS THE LEFT EDGE (see "THE CAMPAIGN SPINE" in campaign.css).
     This chapter used to build a local spine at x=319 by centring a 780 column
     inside a 1120 container. That was internally consistent and externally
     wrong: it put Chapter II's reading edge 186px right of Chapter I's. The
     column widths below are UNCHANGED — only the anchoring is, from "centred in
     a local container" to "hung on the campaign spine", so every type scale
     tuned against --f-col still holds. */
  /* BOTH SIDES. `var(--spine) auto` gave the film chapter a left gutter and no
     right one: with `width: auto` an auto end margin resolves to zero, so the
     chapter head, the lede and the credit ran flush to the bezel from 601px up
     while their own left edge stayed inset. This is leadership's "content
     touching the screen edges", and it ships on production today. A spine is
     reserved on both sides by definition. */
  .camp .film > .container{ max-width: var(--reach-full); margin-inline: var(--spine); padding-inline: 0; width: auto; flex-grow: 1; min-width: 0; }

  .camp .film .chapter__head{ max-width: var(--reach-text); margin-bottom: 0; }
  /* was 3.05vw = 44px, which competed with the hero. 2.65vw reads 38px at 1440:
     still unmistakably the section's opening statement, now clearly one level
     below "Each dollar, doubled." 1.08 keeps a 3-line quote tight. */
  .camp .film .chapter__title{ font-size: clamp(1.5rem, 2.65vw, 2.4rem); line-height: 1.08; max-width: var(--f-text); }
  /* was 1.2vw = 17px, which sat under body scale and read as caption. 1.28vw
     renders 18.4px: body copy that looks like body copy, on a measure narrow
     enough that it cannot be mistaken for a second headline. */
  /* POLISH — `pretty` only protects the LAST line from being a single word; it
     left the rag at 590/659/588/106, stranding "they'll enter." as a 106px tail
     on a 660px measure right where the eye should be pushed into the film.
     `balance` distributes all four lines to an even measure instead, which ends
     the paragraph on a full line. Copy is untouched: this is wrapping only. */
  .camp .film .chapter__lede{ font-size: clamp(1.02rem, 1.28vw, 1.15rem); line-height: 1.55; max-width: var(--f-read); margin-top: clamp(14px, 1.4vw, 20px); text-wrap: balance; }

  /* THE width owner. The shell sizes the picture, the rail and the shadow; the
     16:9 viewport carries the height down with it (439px at 780 against 484 at
     860 and 585 at 1040), so the player and its transport no longer fill a
     laptop screen. The runtime mount around it is a bare block div with no
     width of its own, so this is the last word on the player's size. */
  /* POLISH — the centrepiece now gets the section's widest approach. It was 40
     against the ask's 43, so the closing line arrived with more ceremony than
     the film; 56 here against 32 below puts B above D in the vertical rhythm,
     and the two moves net out at -7px so the section stays as compact. */
  /* THE FILM REACHES LIKE AN EVIDENCE BAND. Hung on the spine rather than
     centred, a 780 frame left 516px of dead field to its right at 1440 — the
     chapter read as a narrow column adrift on a black plane. --reach-band is
     the same 940 the equipment band (IV) and the campaign instrument (V) use,
     so the three pieces of evidence in this campaign now share one width as
     well as one left edge, and the film is visibly the chapter's centre of
     gravity rather than a column of the same weight as the text above it. */
  .camp .film .film__shell{ max-width: var(--reach-band); margin-top: clamp(40px, 3.9vw, 56px); margin-inline: 0; }
  .camp .film .film__credit{ max-width: var(--reach-band); margin-top: clamp(16px, 1.6vw, 23px); padding-top: clamp(12px, 1.2vw, 17px); margin-inline: 0; }
  /* supporting credibility at supporting scale — was 4.2vw (60px), display size
     for a mark that is a footnote to the film, not a second brand moment */
  .camp .film .film__credit-mark{ height: clamp(40px, 3.1vw, 48px); }

  /* the closing invitation: below the opening question in the hierarchy and in
     the type. 2.0vw reads 28.8px against the question's 38px. */
  /* POLISH — margin-left 0 puts the ask on the same x as the marker, the quote,
     the context, the film and the credit: one spine, six elements, widths still
     staggered 700 / 660 / 780 / 780 / 660. The old rule's 30px of inner padding
     goes with the rule, so the credit-to-ask gap is one honest number (50px to
     the text, down from 73) instead of a margin plus a padding. */
  /* The spine at x=319 is produced by CENTRING the 780 column inside the 1072
     container, not by a left margin, so the ask joins it by taking the film's
     own box and holding its 660 measure on the text inside. Marker, quote,
     context, film, credit and ask now share one left edge; the widths stay
     staggered 700 / 660 / 780 / 780 / 660. The old rule's 30px of inner padding
     went with the rule, so credit-to-ask is one honest number. */
  .camp .film .film__handoff{ max-width: var(--reach-text); margin-top: clamp(30px, 3.5vw, 50px); margin-inline: 0; padding-top: 0; }
  .camp .film .film__handoff blockquote{ max-width: var(--f-read); }
  .camp .film .film__handoff blockquote{ font-size: clamp(1.3rem, 2vw, 1.85rem); }
  .camp .film .film__handoff figcaption{ margin-top: clamp(12px, 1.3vw, 18px); }
}

/* MOBILE — the column collapses to the gutter, so the only thing left to
   control is type. The quote comes down again so it cannot run to five lines
   and eat the screen, and the ask stays a step below it. */
@media (max-width: 600px) {
  .camp .film .chapter__title{ font-size: clamp(1.5rem, 6.4vw, 1.95rem); line-height: 1.14; max-width: none; }
  .camp .film .chapter__lede{ font-size: 1rem; max-width: none; }
  .camp .film .film__handoff blockquote{ font-size: clamp(1.1rem, 4.8vw, 1.32rem); }
  .camp .film .film__credit-mark{ height: 54px; }
}
