/* ==========================================================================
   EVAA — HOMEPAGE V4  ·  built 2026-07-28
   Copy: Sean's own doc 1R9amr1uXAH5m8Zsi8rjceUiWJqxnUv3W2gXPd5b4gsQ
         "EVAA Homepage - WHAT WE WERE THINKING" (7/24) — VERBATIM, 10 sections.

   ⚠️ THIS FILE IS SEPARATE FROM site.css ON PURPOSE.
   V2 was built by APPENDING to the shared stylesheet. When index-v2.html was
   deleted on 7/28 it left 1,049 lines / 54KB of orphaned rules still being
   parsed and served on every page. That is the mistake this file exists to
   avoid. Everything below is namespaced `.v4-` so that:
     · V4 cannot affect index-v1.html, ever;
     · if V4 is rolled back, restoring index-v1.html and deleting this file is a
       complete removal
       and `.shots/dead-rules.js` will confirm it with zero ambiguity.
   Inherited from site.css and NOT redefined here: the :root tokens, the reset,
   .skip-link, :focus-visible, .btn*, .eyebrow, .wrap, .reveal, .section--dark.

   ---- STRUCTURE, and where it came from ----------------------------------
   SECTION ORDER IS SEAN'S, EXACTLY (Brees, 7/28). §1 is the LIVE pinned hero,
   kept deliberately: "the grid of photos that then transforms into a
   full-width video". That whole sequence — drifting grid, video growing to
   full-bleed, copy overlay — is one mechanic, which is why Brees calls it
   "section one to two". It comes from site.css (.pf-*) + main.js unchanged.

   From §5 on, Sean's copy has three consecutive list sections (§5 eight
   checkmarks, §6 eight services, §7 nine bullets = 25 bullets in a row).
   Stacked as three grids the reader stops at about item 11. So each gets a
   DIFFERENT structure and a different scroll axis:
     §3 a held stage, content moving through ← 21st.dev sticky-scroll-reveal 952
     §4 numbered blocks on a growing rail    ← 21st.dev scroll-reveal-content-a 19203
     §5 all-visible list, moving focus band  ← 21st.dev scrolling-animation 5857
     §6 held argument, rows travelling past  ← 21st.dev scroll-01 18041
     §7 future-tense roll (staggered, dark)
     §8 monochrome magazine two-up           ← 21st.dev team-showcase 10098
   Plus PAGE-WIDE SMOOTH SCROLL (initSmoothScroll in v4.js) — the connective
   tissue every one of Brees' references shares, and the thing that makes the
   separate scroll-driven sections read as one journey instead of three tricks.

   ⚠️ THE LIST SECTIONS MUST NOT CONVERGE. §5 keeps ALL eight items on screen
   and moves a focus band down them; §6 holds the argument still while rows
   travel past it. If a future edit makes any two behave alike, Sean's 8+8+9
   run collapses back into one 25-item slab and the reader stops around item 11.
   ✅ RESOLVED 7/29 — §3 AND §6 BOTH KEEP THEIR STICKY. An earlier note here
   claimed they were "mirror images" and that §6 should give up its sticky.
   Reviewed with Brees 7/29 and the claim does not hold:
     · They mirror in LAYOUT (.82/1.18 vs .8/1.2) but not in BEHAVIOUR. §3's
       panel CHANGES while pinned — seven words swap, the step counter
       increments, all JS-driven. §6's pinned column is inert; it only stays
       put. A pin whose content changes and a pin that merely holds are not
       the same device.
     · They are two sections apart, with §4's rail and §5's marquee between.
     · §6's pin is load-bearing: its left column (heading + lede + image) is
       far shorter than the eight service rows beside it. Dropping the sticky
       leaves ~4 rows of dead gutter on the left.
     · The held image was an explicit Brees request (7/28).
   DECISION: leave §6 as-is. Do not re-raise this. The convergence warning
   above still stands for any FUTURE edit — it just isn't triggered today.

   ⚠️ AND NOTE WHAT IS *NOT* PHOTOGRAPHED. 952 and 5857 are photo-backed in
   their originals. Neither is here: §3's only candidate images were verified
   on 7/28 to be one stock burst (fwd-*.jpg — same vet, same Pomeranian, same
   room), and §5 has no image content at all; its eight claims ARE the moving
   content, per Brees. Do not "finish" these sections with stock later; the
   absence is the decision.
   From 21st we took STRUCTURE AND MOTION ONLY. None of its surface treatment
   came across: its most-copied blocks are gradients, shaders and "liquid metal",
   and gradients are a standing house rule. This palette already died once as
   "a bank or lawyer site" — it does not need help.

   NB an interim draft also pulled V1's three-card mosaic in at §2, on a
   misread of "Sec1 - Sec2". Removed once corrected, together with its
   .stack-over pin wiring and the .v4-stack-top rule — not left behind.

   ---- THE SPINE ----------------------------------------------------------
   A 1px vertical rule at a fixed left offset recurs in §2 and §6, and the §4
   rail is its scroll-driven form. It is the page's continuity thread (per the
   standing note that "immersive" means a common visual thread between
   sections, not just reveals).

   ---- COLOUR DISCIPLINE --------------------------------------------------
   site.css states the rule: red belongs to CREAM sections, gold to NAVY ones,
   neither crosses. Measured: gold on navy-deep 8.78:1 ✅ / gold on cream 1.77:1
   ❌ / red on cream 5.09:1 ✅ / red on navy-deep 3.05:1 ❌.
   GOLD APPEARS EXACTLY ONCE on this page — §4, on the words "Ever Upward",
   where the copy literally translates Excelsior. That is the one moment the
   logo's swoosh means something. v1 died on navy+gold spread everywhere;
   spreading it again re-creates v1. `.shots/v4-gold.js` counts it.
   No gradients. No coloured left borders on cards.
   ========================================================================== */

/* --- V4 page shell -------------------------------------------------------- */
.v4 { overflow-x: hidden; }

/* Section rhythm. Alternates cream / band / navy so no two adjacent sections
   share a ground — the cheapest possible defence against a 10-section page
   reading as one undifferentiated scroll. */
.v4-sec { padding-block: clamp(72px, 8vw, 128px); position: relative; }
.v4-sec--band { background: var(--cream-2); }
.v4-sec--dark { background: var(--navy-deep); color: var(--cream); }
.v4-sec--dark .v4-eyebrow { color: #9AA6C4; }          /* 6.82:1 on navy-deep */
.v4-sec--dark .v4-lede    { color: rgba(251,247,239,.80); }
.v4-sec--tight { padding-block: clamp(56px, 6vw, 88px); }

.v4-eyebrow {
  font-family: var(--font-body);
  font-size: 12px; font-weight: 700;
  letter-spacing: .18em; text-transform: uppercase;
  color: var(--navy-soft);
  margin: 0 0 var(--s3);
}
/* Red rule mark. CREAM SECTIONS ONLY — on navy it measures 3.05:1 and muddies. */
.v4-eyebrow--mark::before {
  content: ""; display: block;
  width: 28px; height: 2px; background: var(--red);
  margin-bottom: 14px;
}
.v4-lede {
  font-size: clamp(17px, 1.4vw, 20px); line-height: 1.62;
  color: var(--navy-soft); max-width: 60ch;
}
.v4-h2 { font-size: clamp(1.9rem, 3.6vw, 2.9rem); max-width: 20ch; }
.v4-h2--wide { max-width: 26ch; }

/* The recurring spine. Decorative only — never carries meaning. */
.v4-spine { position: relative; padding-left: clamp(0px, 4vw, 56px); }
.v4-spine::before {
  content: ""; position: absolute; left: 0; top: 6px; bottom: 6px;
  width: 1px; background: var(--rule);
}
.v4-sec--dark .v4-spine::before { background: rgba(251,247,239,.22); }
@media (max-width: 760px) { .v4-spine { padding-left: 0; } .v4-spine::before { display: none; } }

/* ==========================================================================
   §1 — THE LIVE PINNED HERO, REUSED
   Brees, 7/28: "Sec1 - Sec2 should stay as it is (design wise)... I mean the
   grid of photos that then transforms into a full-width video." One mechanic,
   not two sections — the drifting grid, the video growing to full-bleed, and
   the copy overlay. It comes from site.css (.pf-*) and main.js unchanged, and
   V4 adds only the one rule below. Read-only dependency: if V1's hero
   changes, V4's changes with it, which is the point of keeping them the same.

   An earlier draft of V4 REPLACED the pinned hero with an ordinary-height
   one, on V3's reasoning (it clipped its own content under reduced motion,
   sheared 185px at 375px, and read as a 2024-26 agency default). That was
   reverted on instruction. The reduced-motion P0 has since been fixed in
   site.css (commit 517a7c4), so the standing objection is now the mobile
   shear, not content loss.
   ========================================================================== */

/* ⚠️ V4-SCOPED OVERRIDE OF A SHARED site.css RULE — note the `.v4` prefix.
   site.css sizes .pf-payoff at clamp(2rem,3.8vw,3.1rem) / max-width 19ch,
   which was tuned for ALIE'S payoff on the live homepage: "Your practice keeps
   its name, its team, and its medicine." — nine words. Sean's is twenty:
   "Your hospital keeps its name. Your culture remains your own. Your team
   stays together. Your medicine stays in your hands." At the inherited size
   and a 19ch measure that ran to ~7 lines and pushed the overlay past the
   pinned viewport.
   Smaller type AND a wider measure — the narrow measure was doing as much
   damage as the size. Brees, 7/28: "a bit smaller so the whole section fits
   better on the page".
   ⚠️ MUST stay scoped to .v4. Editing .pf-payoff in site.css would resize the
   live signed-off homepage, which is the one thing V4 may never touch. */
.v4 .pf-payoff {
  font-size: clamp(1.4rem, 2.4vw, 2.1rem);
  max-width: 32ch;
}

/* Sits between the four promises and the standing line, matching Sean's order.
   Deliberately smaller than .pf-payoff: the overlay has to stay inside the
   pinned viewport, and this is the third block of copy in it. */
.v4-pf-because {
  font-size: clamp(15px, 1.15vw, 17px); line-height: 1.55;
  color: rgba(255,255,255,.86);
  max-width: 46ch; margin-top: var(--s3);
}

/* ⚠️ SHORT-VIEWPORT TRIM. The overlay is pinned, so it cannot scroll — if it
   grows taller than the stage it is simply clipped, silently. Adding the
   "Because we believe" paragraph pushed it to 679px, which overflowed a
   667px stage at 1440x760 by 6px top and bottom. These are height-based, not
   width-based: the constraint is vertical space, and a wide-but-short window
   is exactly where it bites. Re-measure if anything is added to the overlay. */
@media (max-height: 820px) {
  .v4-pf-because { margin-top: var(--s2); font-size: 14px; }
  .v4-pf-standing { margin: var(--s2) 0 var(--s3); }
}
@media (max-height: 749px) {
  /* Measured: at 1366x720 the overlay still overflowed by 14px even after the
     820px trim. Below 750 this paragraph is dropped rather than clipped —
     silent clipping of a pinned overlay is the failure mode to avoid, and the
     four promises above it are the load-bearing copy. */
  .v4-pf-because { display: none; }
}

/* Sean's standing line, inside the pinned hero's overlay. White on the .72
   scrim, which is contrast-critical — see the note in site.css. */
.v4-pf-standing {
  font-family: var(--font-head);
  font-size: clamp(1.05rem, 1.6vw, 1.3rem);
  color: var(--white);
  margin: var(--s3) 0 var(--s4);
}

/* ==========================================================================
   §4 OUR THREE PILLARS — the centrepiece
   Structure from 21st.dev "Scroll Reveal Content A" (19203): numbered blocks
   threaded on a vertical progress line that grows as you scroll. Rebuilt in
   vanilla CSS + a rAF-throttled scroll handler; the original is React + Motion
   and this site has no build step.
   ========================================================================== */
/* Space under the section head — the first image used to sit tight
   against "Preserve. Support. Connect." (Brees, 7/28). */
.v4-pillars { position: relative; margin-top: clamp(40px, 5vw, 72px); }
.v4-pillars__track { position: relative; display: grid; gap: clamp(56px, 7vw, 104px); }

/* The rail. Its FILL is the only scroll-driven element outside the hero. */
.v4-rail {
  position: absolute; left: 0; top: 0; bottom: 0; width: 2px;
  background: var(--rule); border-radius: 2px;
}
.v4-rail__fill {
  position: absolute; left: 0; top: 0; width: 100%; height: 0;
  background: var(--navy); border-radius: 2px;
  transition: height .12s linear;
}
/* Under reduced motion the rail is simply complete: no scroll dependency, and
   crucially the CONTENT never depended on it in the first place. */
@media (prefers-reduced-motion: reduce) {
  .v4-rail__fill { height: 100% !important; transition: none; }
}

.v4-pillar {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, .85fr);
  gap: clamp(28px, 4vw, 64px); align-items: center;
  padding-left: clamp(28px, 5vw, 72px);
}
/* Number only. It used to read "01 — Preserve" directly above a heading
   that already says "Preserve." — the word was pure repetition. */
.v4-pillar__num {
  font-family: var(--font-body); font-size: 12px; font-weight: 700;
  letter-spacing: .18em; color: var(--navy-soft); margin-bottom: var(--s2);
  display: block;
}
.v4-pillar__title { font-size: clamp(1.7rem, 3.2vw, 2.5rem); margin-bottom: var(--s3); }
.v4-pillar__body { display: grid; gap: var(--s2); color: var(--navy-soft); max-width: 48ch; }
/* The copy's short fragment stacks ("A culture. / A reputation. / A team.")
   set as a compact run rather than one line each — eight one-line paragraphs
   would double this section's height for no gain. */
.v4-frag { color: var(--navy); font-weight: 500; }
.v4-pillar__img {
  width: 100%; aspect-ratio: 4 / 3; object-fit: cover; border-radius: var(--r-lg);
}
/* ⚠️ nth-child counts the .v4-rail div too, so the FIRST pillar is child 2.
   Alternating on :nth-child(even) therefore flips Preserve and Connect to
   media-left and leaves Support media-right. That is the intended zigzag —
   verify against a shot, not against the parity you expect. */
.v4-pillar:nth-child(even) .v4-pillar__media { order: -1; }
@media (max-width: 880px) {
  .v4-pillar { grid-template-columns: 1fr; padding-left: 28px; }
  .v4-pillar:nth-child(even) .v4-pillar__media { order: 0; }
}

/* ==========================================================================
   §2 WHY WE EXIST — editorial two-up, narrow measure.
   No photograph here on purpose: §1 already spends seven and §4 spends three,
   and here the argument IS the content.
   ========================================================================== */
.v4-why__grid {
  display: grid; grid-template-columns: minmax(0, 1.05fr) minmax(0, .95fr);
  gap: clamp(32px, 5vw, 72px);
  /* Centred, not top-aligned: the copy column is taller than the square grid,
     so `start` left the photos hanging off the top of the section. */
  align-items: center;
}

/* --------------------------------------------------------------------------
   §2 — THE SHUFFLE GRID
   Structure from 21st.dev "Shuffle Grid" (2439): a grid of images that
   reorders itself on a timer. 3×3 at Brees' call (the canonical version is
   4×4 — sixteen cells — and we have nine).

   ⚠️ THE IMAGES ARE NEW, FROM PEXELS, AND THAT WAS THE BLOCKER. Audited 7/28:
   22 photos in the library, 15 already used on this page, 7 free — of which
   five are the fwd-* stock burst and one (choose-clinic.jpg) has a dog panting
   indoors plus a practitioner's face. Exactly ONE was usable. A shuffle grid
   cannot be faked from that, so nine were licensed rather than reusing photos
   already on the page.
   Chosen against the image rules, not just for looks: dogs appear ONLY where
   they are outdoors walking or playing (panting reads as happy exertion
   there); the calm indoor shots are cats, which have no panting tell; no
   practitioners, no clinics, no gloves. One candidate was rejected after
   review — a Dalmatian being hugged with its mouth wide open, which reads as
   stressed. No architecture either: stock exteriors imply "this specific
   building is one of ours", which V1 rejected for the same reason.

   ⚠️ WCAG 2.2.2 AGAIN. This shuffles on a timer, so like §5 it needs a real
   pause control — see .v4-shuffle__pause. Two auto-moving sections, two
   controls; hover-pause is not a mechanism for keyboard or touch.

   ⚠️ TILES ARE ABSOLUTELY POSITIONED AND MOVED BY TRANSFORM, not reordered in
   the DOM. CSS grid cannot animate a change of cell, and re-inserting nodes
   would restart image decode and flash. Transform gives a real spring-ish
   glide and never touches the DOM after first paint.
   -------------------------------------------------------------------------- */
.v4-shuffle { position: relative; width: 100%; aspect-ratio: 1 / 1; }
.v4-shuffle__tile {
  position: absolute; top: 0; left: 0;
  width: calc((100% - 2 * var(--sh-gap)) / 3);
  height: calc((100% - 2 * var(--sh-gap)) / 3);
  border-radius: var(--r-sm); overflow: hidden;
  transition: transform 1.15s cubic-bezier(.22,.61,.36,1);
  will-change: transform;
}
.v4-shuffle { --sh-gap: 10px; }
.v4-shuffle__tile img { width: 100%; height: 100%; object-fit: cover; display: block; }

.v4-shuffle__pause {
  margin-top: var(--s3);
  font-family: var(--font-body); font-size: 12px; font-weight: 700;
  letter-spacing: .14em; text-transform: uppercase;
  color: var(--navy); background: transparent;
  border: 1px solid var(--rule); border-radius: var(--r-pill);
  padding: 9px 18px; cursor: pointer;
  transition: border-color .2s var(--ease), background-color .2s var(--ease);
}
.v4-shuffle__pause:hover { border-color: var(--navy); background: var(--cream-3); }

/* Reduced motion: the grid still shows all nine, it simply never moves.
   The tiles keep their initial slots, so nothing is hidden or overlapping. */
@media (prefers-reduced-motion: reduce) {
  .v4-shuffle__tile { transition: none; }
  .v4-shuffle__pause { display: none; }
}
@media (max-width: 900px) { .v4-shuffle { --sh-gap: 7px; } }
.v4-why__body { display: grid; gap: var(--s3); max-width: 56ch; }
.v4-why__body p { color: var(--navy-soft); }
.v4-why__body .v4-turn {
  font-family: var(--font-head); font-size: clamp(1.25rem, 2.1vw, 1.7rem);
  color: var(--navy); line-height: 1.3;
  /* The hard <br> is gone (Brees, 7/28) — it runs as ONE continuous statement.
     The measure is widened to match: at 24ch the two sentences broke into
     three ragged lines, which is what the <br> was papering over. */
  max-width: 38ch;
  /* Pulled up (Brees, 7/28). The grid gap plus this line's larger leading made
     the closing statement read as detached from the paragraph it answers. */
  margin-top: calc(var(--s3) * -0.6);
}
@media (max-width: 900px) {
  .v4-why__grid { grid-template-columns: 1fr; }
}

/* ==========================================================================
   §3 EVER UPWARD — A HELD STAGE, NOT A WIDGET IN A SECTION
   Structure from 21st.dev "Sticky Scroll Reveal" (952, Aceternity), rebuilt
   in vanilla CSS + one rAF-throttled handler.

   ---- WHY THIS IS THE THIRD ATTEMPT AT THIS SECTION ----------------------
   Attempt 1 was a small static list. Attempt 2 was a pinned full-screen type
   stage (21st 5794) — Brees: "it's just not hitting the mark". Shortening it
   did not help, because LENGTH WAS NEVER THE PROBLEM. It was a set piece
   BOLTED INTO THE MIDDLE OF A SECTION: heading and copy above it, more copy
   below it, a pinned stage in between. So it read as an interruption in a
   document rather than a passage of one.

   The diagnosis that matters, and the reason this file now looks like it
   does: §1 already IS a continuous scroll journey (photo grid drifts, video
   grows, copy lands). From §2 on, the page had reverted to a document with
   effects applied — things fade in, a rail fills, a focus band moves. Three
   unrelated tricks. The hero was making a promise the rest of the page did
   not keep.

   So this section is now ONE STAGE that holds while its whole content moves
   through it — the Latin line, all five "Forward in…" lines, and the payoff.
   Nothing is bolted in; the section IS the effect. It is also roughly half
   the height of attempt 2, because blocks size to their content instead of
   costing a fixed slice of viewport each.

   ⚠️ STILL NO PHOTOGRAPHY, for the reason verified on 7/28: the only five
   candidate images (fwd-*.jpg) are five frames of ONE stock burst — same
   vet, same Pomeranian, same room, same table, same ultrasound unit. V2 was
   criticised for exactly that and V3 removed it. Do not "finish" this
   section by adding stock later; the absence is the decision.

   ✅ 7/29: the "§6 mirrors this section" concern was reviewed and CLOSED —
   §6 keeps its sticky. This panel's content changes while pinned; §6's does
   not, and §6's pin is holding up an unequal-height grid. See the full
   reasoning in the file header. Do not re-raise.
   ========================================================================== */
.v4-ssr__grid {
  display: grid; grid-template-columns: minmax(0, .82fr) minmax(0, 1.18fr);
  gap: clamp(28px, 5vw, 72px); align-items: start;
  margin-top: var(--s5);
}

/* The panel that holds. Decorative and aria-hidden — every word in it is a
   fragment of the block beside it, so announcing it would read the section
   twice. Nothing here carries content; if the handler never runs, the flow
   column is still the complete section. */
.v4-ssr__panel {
  position: sticky; top: 26vh; align-self: start;
  display: grid; gap: var(--s3);
  transition: opacity .45s var(--ease);
}
/* Closing block: no step label — see the note in v4.js initHeldStage(). */
.v4-ssr__panel.is-blank { opacity: 0; }
.v4-ssr__count {
  font-family: var(--font-body); font-size: 11px; font-weight: 700;
  letter-spacing: .18em; color: rgba(251,247,239,.62);
}
.v4-ssr__count b { color: var(--cream); font-weight: 700; }
/* All seven words live in the DOM and share one grid cell; only paint moves. */
.v4-ssr__words { display: grid; min-height: 4.6em; }
.v4-ssr__word {
  grid-area: 1 / 1;
  font-family: var(--font-head); line-height: 1.05;
  font-size: clamp(1.8rem, 4.4vw, 3.4rem);
  color: var(--cream);
  opacity: 0; transform: translateY(14px);
  transition: opacity .5s var(--ease), transform .5s var(--ease);
}
.v4-ssr__word.is-on { opacity: 1; transform: none; }
.v4-ssr__rule { height: 1px; background: rgba(251,247,239,.22); }

/* The column that moves through it. */
.v4-ssr__flow { display: grid; }
.v4-ssr__block {
  padding-block: clamp(30px, 5vh, 62px);
  /* ⚠️ .55 IS THE AA FLOOR ON navy-deep (5.70:1). The dim state is still text
     a reader has to be able to read at any scroll position — see the same
     note on .v4-keep. Do not dim further to sharpen the active state. */
  color: rgba(251,247,239,.55);
  transition: color .5s var(--ease);
}
.v4-ssr__block.is-on { color: var(--cream); }
.v4-ssr__lead {
  font-family: var(--font-head); font-size: clamp(1.4rem, 2.6vw, 2.1rem);
  line-height: 1.25; margin-bottom: var(--s3); color: inherit;
}
.v4-ssr__body { font-size: clamp(16px, 1.3vw, 18px); max-width: 54ch; color: inherit; }
.v4-ssr__body + .v4-ssr__body { margin-top: var(--s3); }
.v4-ssr__line {
  font-family: var(--font-head); line-height: 1.2;
  font-size: clamp(1.5rem, 3.2vw, 2.6rem);
  color: inherit;
}
.v4-ssr__payoff {
  font-family: var(--font-head); font-size: clamp(1.3rem, 2.4vw, 1.9rem);
  line-height: 1.3; margin-top: var(--s3); color: inherit; max-width: 26ch;
}

/* ⚠️ THE ONE GOLD INSTANCE ON THE PAGE. 8.78:1 on navy-deep.
   It sits inside a block whose colour animates, so it MUST declare its own
   colour or it inherits the dim state and stops being gold.
   Do not add a second. See the colour note at the top of this file. */
.v4-gold { color: var(--gold); }

@media (max-width: 860px) {
  .v4-ssr__grid { grid-template-columns: 1fr; gap: var(--s3); }
  .v4-ssr__panel { display: none; }        /* decorative — nothing lost */
  .v4-ssr__block { padding-block: var(--s3); color: var(--cream); }
}

/* Reduced motion: no hold, no dimming, the whole section simply reads. */
@media (prefers-reduced-motion: reduce) {
  .v4-ssr__panel { position: static; }
  .v4-ssr__block { color: var(--cream); transition: none; }
  .v4-ssr__word { opacity: 1; transform: none; transition: none; position: static; }
  .v4-ssr__words { min-height: 0; }
  .v4-ssr__word + .v4-ssr__word { display: none; }  /* one label, not seven */
}

/* --------------------------------------------------------------------------
   §5 — TWO-COLUMN VERTICAL MARQUEE
   Structure from 21st.dev "Vertical Marquee" (20151): two columns drifting in
   OPPOSITE directions behind a soft top/bottom fade. Their version carries
   tweet cards; ours carries Sean's eight "what you keep" claims, per Brees'
   earlier call that the list items are the moving content, not imagery.

   ⚠️ THIS IS THE ONLY AMBIENT MOTION ON THE PAGE. Everything else is
   scroll-driven — it moves because the reader moves. This drifts on its own,
   which is what makes it a genuinely different beat from §3 (a held stage)
   and §6 (rows travelling past a held argument), rather than a third variation
   on the same idea.

   ⚠️ WCAG 2.2.2 (Pause, Stop, Hide) APPLIES HERE and to nothing else on this
   page: motion that starts automatically, runs over 5s, and sits alongside
   other content needs a mechanism to stop it. Hover-pause alone does NOT
   satisfy it — a keyboard or touch user has no hover. Hence the real button.
   Do not remove it to tidy the layout.

   ⚠️ NO `gap` ON THE TRACK, DELIBERATELY. The seamless loop is translateY(-50%)
   over the item set duplicated twice, which is only exact if every item is
   spaced identically INCLUDING the last one. `gap` omits the final space, so
   -50% lands slightly off and the loop visibly jumps once per cycle. Spacing
   lives on each card's margin-bottom instead.
   -------------------------------------------------------------------------- */
.v4-mq__head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--s3); flex-wrap: wrap; }
/* On navy — see the ground note on .v4-mq__card. cream on navy-deep ~15:1. */
.v4-mq__pause {
  font-family: var(--font-body); font-size: 12px; font-weight: 700;
  letter-spacing: .14em; text-transform: uppercase;
  color: var(--cream); background: transparent;
  border: 1px solid rgba(251, 247, 239, .38); border-radius: var(--r-pill);
  padding: 10px 20px; cursor: pointer;
  transition: border-color .2s var(--ease), background-color .2s var(--ease);
}
.v4-mq__pause:hover { border-color: var(--cream); background: rgba(251, 247, 239, .08); }

.v4-mq {
  margin-top: var(--s4);
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s2);
  height: clamp(400px, 56vh, 600px);
  overflow: hidden;
  /* A MASK, not a background gradient — it introduces no colour, it just
     reveals the section's own solid ground. The house "no gradients" rule is
     about gradient FILLS; this is an alpha mask and was a deliberate call. */
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 12%, #000 88%, transparent 100%);
          mask-image: linear-gradient(to bottom, transparent 0, #000 12%, #000 88%, transparent 100%);
}
.v4-mq__col { overflow: hidden; }
.v4-mq__track { animation: v4-mq-up 42s linear infinite; will-change: transform; }
.v4-mq__col--down .v4-mq__track { animation-name: v4-mq-down; }

@keyframes v4-mq-up   { from { transform: translateY(0);     } to { transform: translateY(-50%); } }
@keyframes v4-mq-down { from { transform: translateY(-50%);  } to { transform: translateY(0);    } }

/* Three ways to stop it: the button, hover, and keyboard focus landing inside. */
.v4-mq.is-paused .v4-mq__track,
.v4-mq:hover .v4-mq__track,
.v4-mq:focus-within .v4-mq__track { animation-play-state: paused; }

/* ⚠️ THIS SECTION IS NAVY, NOT CREAM (Brees, 7/28: "not a fan of the cream bg
   in that section"). That is not just a background swap — two things in the
   original cream build become ILLEGAL here and had to change with it:
     · the tick was --red (5.09:1 on cream, but only 3.05:1 on navy-deep — it
       fails as a graphic and looks muddy), now cream;
     · the eyebrow's --mark rule is also red, so §5 uses the plain eyebrow,
       matching the other dark sections (§3, §7).
   Gold is NOT an option for either: it is spent, once, in §3.
   Section rhythm still alternates — §4 cream, §5 navy, §6 cream, §7 navy. */
.v4-mq__card {
  background: rgba(251, 247, 239, .05);
  border: 1px solid rgba(251, 247, 239, .18);
  border-radius: var(--r-md);
  padding: clamp(18px, 2vw, 26px);
  margin-bottom: var(--s2);            /* NOT gap — see the note above */
  display: grid; grid-template-columns: 22px 1fr; gap: var(--s2); align-items: start;
  font-family: var(--font-head); line-height: 1.3;
  font-size: clamp(1rem, 1.5vw, 1.24rem);
  color: var(--cream);                 /* on navy-deep: ~15:1, no dim state */
}
.v4-mq__tick { width: 22px; height: 22px; color: var(--cream); }

@media (max-width: 700px) {
  .v4-mq { grid-template-columns: 1fr; height: clamp(360px, 62vh, 520px); }
  .v4-mq__col--down { display: none; }   /* one column reads better than two */
}

/* Reduced motion: no drift at all. Becomes a plain two-column list, the
   duplicate set is removed, and the pause button has nothing to control. */
@media (prefers-reduced-motion: reduce) {
  .v4-mq {
    height: auto; overflow: visible;
    -webkit-mask-image: none; mask-image: none;
  }
  .v4-mq__col--down { display: block; }
  .v4-mq__track { animation: none; }
  .v4-mq__dup { display: none; }
  .v4-mq__pause { display: none; }
}

/* ==========================================================================
   §6 COMPREHENSIVE OPERATIONAL SUPPORT — sticky heading, scrolling column
   Structure from 21st.dev "Scroll 01" (18041): one side holds while the other
   travels. Their version pins media and scrolls text; ours pins the ARGUMENT
   and scrolls the eight services, because the services are the evidence and
   the argument is what has to stay on screen while you read them.
   ========================================================================== */
.v4-ops__grid {
  display: grid; grid-template-columns: minmax(0, .8fr) minmax(0, 1.2fr);
  gap: clamp(32px, 5vw, 80px); align-items: start;
}
.v4-ops__sticky { position: sticky; top: clamp(96px, 12vh, 140px); }
.v4-ops__img {
  width: 100%; aspect-ratio: 4 / 3; object-fit: cover;
  border-radius: var(--r-lg); margin-top: var(--s4);
}
/* The image is the FIRST thing to go when the column stops being sticky —
   below 900px the argument and the services stack, and a held photo between
   them just pushes the evidence off the first screen. */
@media (max-width: 900px) { .v4-ops__img { display: none; } }
@media (max-width: 900px) {
  .v4-ops__grid { grid-template-columns: 1fr; }
  .v4-ops__sticky { position: static; }
}
.v4-ops__list { display: grid; }
.v4-op {
  padding-block: clamp(20px, 2.4vw, 28px);
  border-top: 1px solid var(--rule);
  display: grid; grid-template-columns: minmax(0, .8fr) minmax(0, 1.2fr);
  gap: var(--s3); align-items: baseline;
}
.v4-op:last-child { border-bottom: 1px solid var(--rule); }
.v4-op__name { font-family: var(--font-head); font-size: clamp(1.05rem, 1.7vw, 1.3rem); color: var(--navy); }
.v4-op__desc { color: var(--navy-soft); font-size: 16px; }
@media (max-width: 620px) { .v4-op { grid-template-columns: 1fr; gap: 6px; } }

/* ==========================================================================
   §7 THE EXCELSIOR NETWORK — future tense, and it must LOOK future tense
   The Impeccable critique's structural finding on V2: the page was composed
   for a network, but EVAA is three people and one hospital, so every
   display-scale claim reads as inflation once the reader works out the size.
   Sean's copy is honest about this — "As our network grows, our hospitals WILL
   benefit from opportunities including". The copy never overclaimed; V2's
   layout did. So this section is explicitly labelled as forthcoming and is
   NOT styled as a roster of things that already exist.
   ========================================================================== */
.v4-net__head { max-width: 62ch; }

/* --------------------------------------------------------------------------
   §7 — THE HORIZONTAL ICON SCROLLER
   Brees, 7/28, on 21st.dev "Scrolling animation" (5857): "Source icons instead
   of images and have the titles under the icon."

   ⚠️ A FOURTH DISTINCT BEHAVIOUR, ON THE X AXIS. §3 holds a panel while
   content moves vertically past it, §5 drifts two columns vertically on its
   own, §6 holds an argument while rows travel vertically. This one travels
   HORIZONTALLY, driven by vertical scroll. That is the only reason a fourth
   scroll effect earns its place on the page — if it moved vertically too it
   would just be a third variation and Sean's list sections would blur together.

   ⚠️ ICONS ARE DRAWN, NOT SOURCED. Inline SVG, stroke: currentColor, no
   external requests and no icon font. Every one is aria-hidden — the title
   under it carries the meaning, so a screen reader reads nine titles, not
   nine "image" nodes.

   ⚠️ ALL NINE MUST REMAIN REACHABLE. The drift is sized so every card passes
   through the viewport as the section is scrolled, and BOTH reduced-motion and
   narrow screens drop to a static wrapped grid showing all nine at once. A
   horizontal effect that hides content from anyone who cannot trigger it is a
   content-loss bug, not a flourish.
   -------------------------------------------------------------------------- */
/* --------------------------------------------------------------------------
   §7 — THE RADIAL BURST ("The Excelsior Network")
   Structure from 21st.dev "Scrolling animation" (5857, minhxthanh). Nine
   chips start stacked at the centre of concentric rings and fly OUTWARD along
   evenly spaced angles as the reader scrolls, while the centre copy fades in
   behind them.

   ⚠️ THIRD ATTEMPT AT THIS SECTION, and the first one that is actually the
   reference. Attempt 1 was a horizontal conveyor, attempt 2 a staggered grid —
   both invented, because 21st's retrieval limit meant I never saw 5857's
   source and I filled the gap with a guess instead of asking. Brees supplied
   the source. The mechanic is radial. Do not "simplify" it back to a grid.

   It also happens to be the right idea for this copy: Sean's line is "As our
   network grows, our hospitals WILL benefit" — a network expanding outward is
   the literal picture of it.

   ---- WHAT DID NOT SURVIVE TRANSLATION ----------------------------------
   1. The original's inner ring is `bg-gradient-to-r from-purple-400 via-
      pink-500 to-red-500`. Gradients are a standing house rule AND those are
      not EVAA colours. Rings here are flat 1px cream borders at low alpha.
   2. The original drives everything off `Math.min(scrollY / 500, 1)` — RAW
      PAGE SCROLL. That only works while the component is the first thing on
      the page. §7 is seven sections down, where scrollY is already ~9000 and
      the animation would be finished before the section ever appeared.
      Progress is measured against the track's own box instead.
   3. Its chips are bare images. Ours carry an icon AND a label (Brees), so
      they need real width and cannot simply pile up at the centre — see the
      start-radius note in v4.js.
   -------------------------------------------------------------------------- */
/* ⚠️ 140vh, NOT 190vh. At 190 there was ~20vh of EMPTY NAVY before anything
   appeared — Brees: "scroll needs to be fixed - it takes too long for it to
   show up", with a screenshot of a blank section. Track length and the reveal
   thresholds in v4.js are one system: shortening this without moving those
   just makes the burst finish early instead. */
/* Background layer. Flat rgba scrim, never a gradient (house rule). The
   section keeps its own navy-deep background underneath as the fallback, so a
   failed image download degrades to exactly what was there before. */
.v4-orbit__bg {
  position: absolute; inset: 0; z-index: 0;
  width: 100%; height: 100%; object-fit: cover;
}
.v4-orbit__scrim {
  position: absolute; inset: 0; z-index: 1;
  background: rgba(18, 29, 59, .90);
}
/* Everything real sits above both. */
.v4-orbit__track, .v4-orbit > .wrap { position: relative; z-index: 2; }

.v4-orbit__track { position: relative; height: 110vh; margin-top: var(--s3); }
/* ⚠️ THE STAGE MUST CLEAR THE STICKY HEADER. top:0 pinned the field directly
   under the cream header bar and the ring collided with the nav — Brees:
   "this section is touching the menu". --header-h is measured in JS once the
   partials land; 93px is the same fallback site.css uses for the pinned hero. */
.v4-orbit__stage {
  position: sticky;
  top: var(--header-h, 93px);
  height: calc(100vh - var(--header-h, 93px));
  display: grid; place-items: center; overflow: hidden;
}
.v4-orbit__field {
  position: relative;
  width: min(760px, 92vmin); height: min(760px, 92vmin);
  display: grid; place-items: center;
}
/* Concentric rings. Flat borders, never fills — see note 1 above. */
/* Rings and core are now ALWAYS visible. The old is-open gating existed to
   hold the text back until the chips cleared it; with a headline-only core
   they clear it immediately, so gating it just produced an empty middle. */
.v4-orbit__ring {
  position: absolute; border-radius: 50%;
  border: 1px solid rgba(251,247,239,.14);
}
.v4-orbit__ring--1 { width: 100%;   height: 100%; }
.v4-orbit__ring--2 { width: 74%;    height: 74%; }
.v4-orbit__ring--3 { width: 48%;    height: 48%; border-color: rgba(251,247,239,.22); }

/* The centre copy, revealed once the chips have moved out of its way. */
.v4-orbit__core {
  position: relative; z-index: 2; text-align: center;
  /* ⚠️ BOTH WIDTH AND HEIGHT ARE CAPPED BY GEOMETRY, and by DIFFERENT chips.
     At the start radius (~228px) the core must clear:
       the near-horizontal chips (-10/190deg) — x = 0.985*R = 225, so
         coreHalfWidth + chipHalfWidth(59) <= 225  ->  width <= ~332px
       the diagonal chips (30/150deg) — y = 0.5*R = 114, so
         coreHalfHeight + chipHalfHeight(52) <= 114 ->  height <= ~124px
     That height cap is why the headline gets its own smaller size below: at
     the section default it wrapped to three lines and blew past it.
     ORIGINAL NOTE, still true about the width: The binding collision is not the chips
     on the horizontal — it is the ones at 30/150/210/330 degrees, which sit
     at x = 0.866*R and are still inside the core's vertical band. Clearing
     them needs 0.866*R >= coreHalfWidth + chipHalfWidth. At the reveal radius
     of 278 that allows coreHalfWidth <= ~184, i.e. a 368px box; 340 leaves
     real margin. A wider core pushes the whole ring outward, and the field
     cannot grow — it is already as tall as the stage.
     Measured at 400px: two chips ("Shared operational best practices",
     "Annual Excelsior Summit") overlapped the text. */
  width: min(330px, 48vmin);
}
/* Smaller than .v4-h2 elsewhere, to stay inside the ~124px height cap above.
   Raising this re-introduces the chip collision. */
.v4-orbit__core .v4-h2 {
  margin-inline: auto; margin-bottom: 0;
  font-size: clamp(1.45rem, 2.3vw, 2rem);
}
.v4-orbit__lede {
  color: rgba(251,247,239,.80); font-size: clamp(15px, 1.2vw, 17px);
  line-height: 1.55; margin-bottom: var(--s3);
}

/* The nine chips. Absolutely centred; JS moves each one out along its angle. */
.v4-orbit__items { position: absolute; inset: 0; display: grid; place-items: center; }
.v4-orbit__item {
  position: absolute;
  /* Width AND height are load-bearing — see the collision note in v4.js
     initOrbit(). The min-height keeps every chip the same size so a long
     three-line label cannot bulge into its neighbour and re-introduce the
     overlap the start radius was tuned to avoid. */
  /* ⚠️ THE UPPER CAP MUST NOT EXCEED WHAT THE RING CAN HOLD. .v4-orbit__field
     is min(760px, 92vmin) — it stops growing at 760 — but chip width was
     clamped at 128px via 13vmin, so on a tall viewport (1600x950) the chips
     kept growing while their orbit did not, and two of them collided again.
     118px is the largest that still clears at the 760px field. */
  width: clamp(104px, 13vmin, 118px);
  min-height: 104px; align-content: center;
  display: grid; justify-items: center; gap: 8px; text-align: center;
  padding: 12px 10px;
  border: 1px solid rgba(251,247,239,.18);
  border-radius: var(--r-md);
  background: var(--navy-deep);          /* opaque: chips overlap the rings */
  color: var(--cream);
  will-change: transform;
}
.v4-orbit__icon { width: 26px; height: 26px; color: var(--cream); }
.v4-orbit__label {
  font-family: var(--font-head); font-size: clamp(12px, 1.05vmin, 14px);
  line-height: 1.25; color: var(--cream);
}

/* ⚠️ THE FLAT FALLBACK IS NOT A DEGRADED VERSION — IT IS THE WHOLE SECTION.
   A radial layout on a phone, or with motion disabled, either overlaps into
   mush or never expands at all, and nine labels would be unreadable either
   way. So below 820px and under reduce, the track collapses to an ordinary
   grid with every chip and the centre copy fully visible. */
@media (max-width: 820px), (prefers-reduced-motion: reduce) {
  .v4-orbit__track { height: auto; }
  .v4-orbit__stage { position: static; height: auto; display: block; }
  .v4-orbit__field { width: 100%; height: auto; display: block; }
  .v4-orbit__ring { display: none; }
  .v4-orbit__core { width: auto; text-align: left; margin-bottom: var(--s5); }
  .v4-orbit__core .v4-h2 { margin-inline: 0; }
  .v4-orbit__items {
    position: static; display: grid;
    grid-template-columns: repeat(3, minmax(0,1fr)); gap: var(--s2);
  }
  .v4-orbit__item {
    position: static; width: auto;
    transform: none !important;          /* beats the inline transform */
    justify-items: start; text-align: left;
  }
}
@media (max-width: 520px) { .v4-orbit__items { grid-template-columns: repeat(2, minmax(0,1fr)); } }
.v4-net__close {
  margin-top: var(--s5); font-family: var(--font-head);
  font-size: clamp(1.2rem, 2.2vw, 1.7rem); color: var(--cream); max-width: 30ch;
  text-align: center; margin-inline: auto;
}

/* ==========================================================================
   §8 OUR LEADERSHIP — monochrome magazine two-up
   Structure from 21st.dev "Team Showcase" (10098).
   ⚠️ THE GRAYSCALE IS NOT ONLY AESTHETIC. The only portrait of Sean has him in
   SUNGLASSES, on a page whose entire job is "are these people real". Monochrome
   reads as deliberate editorial styling and takes most of the sting out of a
   weak source photo, which is what lets V4 ship before the reshoot. The day a
   real portrait lands, delete the filter — nothing else needs to change.
   Colour returns on hover/focus, so the photo is never withheld, only quieted.
   ========================================================================== */
.v4-leaders { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: clamp(28px, 4vw, 64px); margin-top: var(--s5); }
.v4-leader__figure { margin: 0 0 var(--s3); overflow: hidden; border-radius: var(--r-md); background: var(--cream-2); }
.v4-leader__img {
  width: 100%; aspect-ratio: 4 / 5; object-fit: cover;
  filter: grayscale(1) contrast(1.04);
  transition: filter .5s var(--ease), transform .6s var(--ease);
}
.v4-leader:hover .v4-leader__img,
.v4-leader:focus-within .v4-leader__img { filter: grayscale(0) contrast(1); transform: scale(1.02); }
@media (prefers-reduced-motion: reduce) {
  .v4-leader__img { transition: none; }
  .v4-leader:hover .v4-leader__img, .v4-leader:focus-within .v4-leader__img { transform: none; }
}
.v4-leader__name { font-size: clamp(1.3rem, 2.2vw, 1.7rem); margin-bottom: 4px; }
.v4-leader__role {
  font-family: var(--font-body); font-size: 12px; font-weight: 700;
  letter-spacing: .16em; text-transform: uppercase; color: var(--navy-soft);
  margin-bottom: var(--s3);
}
.v4-leader__bio { display: grid; gap: var(--s2); color: var(--navy-soft); font-size: 16px; }
@media (max-width: 760px) { .v4-leaders { grid-template-columns: 1fr; } }

/* ==========================================================================
   §9 OUR PROMISE — quiet on purpose
   This is the section most at risk of reading as inflation ("one of the
   largest… in the United States"). The copy immediately undercuts it — "But
   size is not our purpose." — so the layout stays small and centred and lets
   the sentence do it. Nothing display-scale here.
   ========================================================================== */
.v4-promise__grid {
  display: grid; grid-template-columns: minmax(0, .8fr) minmax(0, 1.2fr);
  gap: clamp(32px, 5vw, 72px); align-items: center;
}
.v4-promise__media { margin: 0; }
.v4-promise__img {
  width: 100%; aspect-ratio: 4 / 5; object-fit: cover;
  border-radius: var(--r-lg);
}
/* Was centred and max-62ch when it stood alone; beside an image it reads
   better ranged left with a tighter measure. */
.v4-promise { max-width: 54ch; margin: 0; text-align: left; }
@media (max-width: 860px) {
  .v4-promise__grid { grid-template-columns: 1fr; gap: var(--s4); }
  .v4-promise__img { aspect-ratio: 16 / 10; }
}
.v4-promise__aspire { color: var(--navy-soft); margin-bottom: var(--s3); }
.v4-promise__values {
  font-family: var(--font-head); font-size: clamp(1.5rem, 3vw, 2.2rem);
  color: var(--navy); line-height: 1.25; margin-bottom: var(--s4);
}
.v4-promise__body { display: grid; gap: var(--s3); color: var(--navy-soft); }
.v4-promise__last { font-family: var(--font-head); font-size: clamp(1.2rem, 2.2vw, 1.6rem); color: var(--navy); }

/* ==========================================================================
   §10 LET'S START THE CONVERSATION
   ========================================================================== */
/* ⚠️ background-color IS REQUIRED, not decoration. Three reasons, learned the
   hard way when deep-a11y.js failed this section at 1.07:1:
   1. The scrim is a SIBLING overlay, so anything walking ANCESTORS for the
      painted background (deep-a11y.js, and every real contrast checker) walks
      straight past this section to the cream body and measures white-on-cream.
   2. If cta-reception.jpg 404s or is slow, the scrim composites over CREAM
      rather than a dark ground — still 8.4:1, but not what was designed.
   3. The footer is also navy-deep. Without this the close seams against it the
      moment the image fails. V1's .close sets this for the same reason. */
.v4-close {
  position: relative; isolation: isolate; overflow: hidden; text-align: center;
  background: var(--navy-deep); color: var(--white);
}
.v4-close__bg { position: absolute; inset: 0; z-index: 0; width: 100%; height: 100%; object-fit: cover; }
/* Flat scrim, not a gradient (house rule). .72 measured to hold white text at
   AAA over the brightest region of cta-reception.jpg. */
.v4-close__scrim { position: absolute; inset: 0; z-index: 1; background: rgba(18, 29, 59, .78); }
.v4-close .wrap { position: relative; z-index: 2; }
.v4-close__title { font-size: clamp(2rem, 4.2vw, 3.2rem); color: var(--white); margin-bottom: var(--s3); }
.v4-close__sub { color: rgba(255,255,255,.82); max-width: 60ch; margin: 0 auto var(--s3); }

.v4-close__ever { font-family: var(--font-head); font-size: clamp(1.3rem, 2.4vw, 1.9rem); color: var(--white); margin-bottom: var(--s5); }
.v4-close .v4-btn-row { justify-content: center; }
.v4-btn-row { display: flex; flex-wrap: wrap; gap: var(--s2); }
/* 🔴 CENTRED IN §7 ONLY — QA'd by Brees 7/31: the "Explore The Excelsior
   Network" button sat 414px left of centre under a centred paragraph.
   THE CAUSE, AND IT IS THE THIRD INSTANCE OF THIS FAMILY IN ONE DAY: the row is
   `display:flex`, so `text-align` from an ancestor does nothing to it and the
   default `justify-content` puts the button hard left. `.v4-net__close`
   directly above it is centred with `margin-inline:auto`, so the two never
   agreed. (The other two: PWU's close paragraphs, and the hero eyebrow dash.)
   📌 THE PATTERN WORTH REMEMBERING — "centre this" has three different answers
   depending on the box: `margin-inline:auto` for a block, `justify-content` for
   a flex row, `text-align` only for inline content. Getting the wrong one looks
   deliberate and ships.
   ⚠️ SCOPED TO `.v4-orbit`. The page's other `.v4-btn-row` (§10's close, two
   buttons) is intentionally left alone — it is in a section with its own
   alignment and was not part of the QA note. Do not globalise this. */
.v4-orbit .v4-btn-row { justify-content: center; }
.v4-close :focus-visible { outline-color: var(--white); }

/* ==========================================================================
   NOSCRIPT ESCAPE
   site.css sets .reveal{opacity:0} and only JS adds .is-in. With scripting
   blocked, every revealed block on V1 is permanently invisible — a blank page.
   V1 and partner-with-us still carry that bug; V4 does not.
   ========================================================================== */
.v4-noscript .reveal { opacity: 1 !important; transform: none !important; }
.v4-noscript .v4-ssr__block { color: var(--cream) !important; }
.v4-noscript .v4-rail__fill { height: 100% !important; }
