/*
 * ZenRows Home — section-by-section styling.
 * v0 ships only Act 1 (art-house hero) + universal chrome
 * (topbar).  Acts 2 through 7 inherit body defaults so the
 * skeleton is browsable while we design them one by one.
 *
 * Tokens, fonts, and motion durations come from the prototype
 * DS (src/styles/*) via the /_ds/ alias the website middleware
 * exposes.  Never redefine tokens here; if something is
 * missing, add it upstream in src/styles/theme.css first.
 */

@import url("../_ds/fonts.css");
@import url("../_ds/theme.css");
@import url("../_shared/chrome.css");
@import url("../_shared/components.css");

/* Local overrides specific to Home.  Marketing-only spacing.
   Headings always come from the DS scale (--heading-*). */
:root {
  --gap-landmark: 160px;
}

/* Topbar, nav, dropdowns, cursor — see ../_shared/chrome.css */

/* Suppress the native cursor inside the interactive boxes
   that get the O follower — Act 5 visuals + Act 6 chips. */
[data-act="5"] .code-block,
[data-act="5"] .destinations li,
[data-act="6"] .connectors li {
  cursor: pointer;
}

.portal-flow {
  position: fixed;
  top: 0;
  left: 0;
  /* 100% (not 100vw): on a fixed full-bleed canvas these look identical,
     but 100vw includes the scrollbar gutter and pushes a ~5px horizontal
     scroll on the whole page. */
  width: 100%;
  /* Full viewport height — particles need room to curve.  All
     content (hero, etc.) paints on top of the body's dark bg
     above this canvas (z-index 0).  Hero is z-index auto on a
     relative-positioned section, so type still wins; particles
     read as subtle ambient flow in the background.  Sits below
     the topbar's z-index 50 so the navbar's backdrop-blur can
     soften the particles behind it. */
  height: 100vh;
  z-index: 0;
  pointer-events: none;
  display: block;
}

/* ── Trust bar ───────────────────────────────────────────
   Bottom-right of the viewport, fixed.  Minimal proof during
   the hero — label + greyscale logos via clearbit filtered to
   light silhouettes so they sit on the dark mode page without
   shouting.  Same scroll-fade behaviour as the portal-flow so
   the social proof lives only at the hero. */
.trust-bar {
  position: fixed;
  bottom: 28px;
  right: clamp(20px, 3vw, 40px);
  /* Sits BELOW the page's flow content (main has z-index 1 so
     hero typography paints over it).  Result: trust bar is
     visible against the empty body background, hidden behind
     any text or card that overlaps it.  Always BG-only. */
  z-index: -1;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 12px;
  pointer-events: none;
  /* backwards (not both): holds the hidden start-state during the delay,
     but releases after the rise so the scroll-fade inline opacity in
     home.js can take over. `both` was pinning opacity:1 and blocking it. */
  animation: trust-bar-rise 800ms cubic-bezier(0.22, 1, 0.36, 1) 400ms backwards;
}

.trust-bar-label {
  margin: 0;
  font-family: var(--font-base);
  font-size: var(--text-tiny, 11px);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--text-tertiary);
  font-weight: var(--font-weight-medium);
}

.trust-bar-logos {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  gap: 22px;
  align-items: center;
  pointer-events: auto;
}

.trust-bar-logos li {
  font-family: var(--font-base);
  font-size: 14px;
  font-weight: var(--font-weight-medium);
  letter-spacing: 0.01em;
  color: rgba(255, 255, 255, 0.6);
  transition: color 200ms ease;
  white-space: nowrap;
}

.trust-bar-logos li:hover {
  color: rgba(255, 255, 255, 0.95);
}

@keyframes trust-bar-rise {
  from { opacity: 0; transform: translateY(12px); }
  to   { opacity: 1; transform: translateY(0); }
}

html:not(.js) .trust-bar { animation: none; opacity: 1; }

@media (max-width: 768px) {
  .trust-bar { display: none; }
}

/* .topbar-brand, .topbar-actions, .btn-marketing — see ../_shared/chrome.css.
   The ZenButton port (.zen-btn*) was unused on Home and has
   been retired; reintroduce it as needed in a shared file. */

/* ──────────────────────────────────────────────────────────
   Act 1 — The question
   Art-house full-viewport hero.  One line dominates; subhead
   reads quiet underneath; scroll cue lives at the bottom edge.
   No CTAs in this section (they're in the topbar).
─────────────────────────────────────────────────────────── */

[data-act="1"] {
  position: relative;
  min-height: 100svh;
  min-height: 100vh;
  padding: 0 clamp(20px, 4vw, 48px);
  display: grid;
  grid-template-rows: 1fr auto;
  align-items: center;
  justify-items: start;
}

/* Hero — two-column grid on desktop.  Left = the editorial
   stack (H1 + sub at top, CTA pinned to bottom).  Right = a
   proof-roll component that auto-scrolls a list of stats +
   capability badges so the reader catches the breadth of
   what Zenrows ships without leaving the hero.  Collapses to
   a single column below 1024px (the roll hides — too much
   for a phone hero). */
[data-act="1"] .hero-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(280px, 360px);
  align-items: stretch;
  column-gap: clamp(40px, 6vw, 96px);
  width: 100%;
  max-width: 1440px;
  /* Override parent's align-items: center so the grid fills
     the available row height — without this, hero-content
     stays at its natural height and margin-top:auto on the
     CTA-row has no space to push into. */
  align-self: stretch;
}

[data-act="1"] .hero-content {
  /* Internal 3-row grid mirrors the parent section pattern:
     row 1 = 1fr (H1 centred vertically inside), rows 2/3 = auto
     (CTA-row + sub stack at the bottom of the column).  Keeps
     the H1 in its original mid-column position regardless of
     viewport height, and pins the CTA + tagline + sub near the
     scroll cue. */
  display: grid;
  grid-template-rows: 1fr auto auto;
  align-items: center;
  justify-items: start;
  height: 100%;
  padding-top: clamp(80px, 12vh, 140px);
  padding-bottom: clamp(40px, 6vh, 80px);
  /* Same reasoning as .hero-proof: the H1 contains nowrap
     hero-marks ("the open web", "runs on data") whose width
     at display-xlarge sizes exceeds narrow viewports.  Without
     min-width: 0 the grid cell's min-content forces the grid
     and section wider than the viewport. */
  min-width: 0;
}

/* Below 1024px the hero collapses to a single column.  The
   proof-roll stays visible — stacked below the editorial
   content — and shrinks to a friendlier mobile height so it
   doesn't push the scroll cue off-screen.  Touch surfaces
   get auto-scroll only (no wheel override); the rest of the
   page scrolls normally when the reader swipes outside the
   roll. */
@media (max-width: 1023px) {
  [data-act="1"] .hero-grid {
    grid-template-columns: 1fr;
    row-gap: clamp(24px, 4vh, 40px);
  }
  [data-act="1"] .hero-content {
    /* Single column = content flows naturally.  Drop the
       1fr/auto/auto grid that vertically centres the H1 on
       desktop; on mobile we want H1 → CTA → sub stacked in
       order, no artificial spacing. */
    grid-template-rows: auto auto auto;
    height: auto;
    padding-top: clamp(56px, 9vh, 96px);
    padding-bottom: 0;
    row-gap: clamp(20px, 3vh, 32px);
  }
  [data-act="1"] .hero-proof {
    width: 100%;
    justify-self: stretch;
  }
  /* Note: the proof-roll's mobile axis swap (vertical column →
     horizontal ticker) lives in a media query AFTER the base
     .proof-roll / .proof-roll-list / .proof-row definitions
     below — otherwise the later base rules win on source order
     since media queries don't add specificity. */
}

/* Laptop / desktop range — the roll on the right narrows the
   H1 column, so the display H1 has to step down or it wraps
   cramped.  Only above 1920px (iMac-class wide) does the
   column have enough room to let the H1 grow to its full
   display size; in between, scale the H1 down and tighten the
   column gap so the H1 still reads as the lead and the roll
   stays usable. */
@media (min-width: 1024px) and (max-width: 1919px) {
  [data-act="1"] .hero-grid {
    grid-template-columns: minmax(0, 1fr) minmax(240px, 300px);
    column-gap: clamp(28px, 3vw, 56px);
  }
  [data-act="1"] h1 {
    font-size: var(--mkt-h3);
  }
}

/* ── Proof roll ───────────────────────────────────────────
   Reusable component: a tall column of stats + badges that
   auto-scrolls upward.  Hovering pauses the loop; wheel
   events while hovered translate the roll instead of the
   page (home.js prevents the wheel from bubbling).  The
   list is JS-cloned at mount for an infinite loop so the
   reader never sees the seam.

   Container shell matches the unified card pattern: page-bg
   fill + display radius + strong down-shadow + no border. */
.hero-proof {
  width: 100%;
  align-self: center;
  justify-self: end;
  /* Grid items default to min-width: auto which uses min-content
     as the floor — when the proof-roll-list goes horizontal on
     mobile, its min-content is the sum of all (cloned) items
     and would force the grid cell, the hero grid, and the page
     itself wider than the viewport.  min-width: 0 lets the cell
     clip cleanly via the .proof-roll's overflow: hidden below. */
  min-width: 0;
}

.proof-roll {
  position: relative;
  width: 100%;
  min-width: 0;
  max-width: 100%;
  height: clamp(360px, 62vh, 560px);
  background: var(--background);
  border-radius: var(--radius-display, 20px);
  box-shadow: 0 28px 64px -20px rgba(0, 0, 0, 0.55);
  overflow: hidden;
  -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%
  );
}

.proof-roll-list {
  --roll-y: 0px;
  list-style: none;
  margin: 0;
  padding: clamp(28px, 3.5vh, 44px) clamp(20px, 1.8vw, 28px);
  display: flex;
  flex-direction: column;
  gap: clamp(20px, 2.4vh, 30px);
  transform: translate3d(0, var(--roll-y), 0);
  will-change: transform;
}

.proof-row {
  display: flex;
  flex-direction: column;
}

.proof-row--stat {
  gap: 2px;
}

.proof-row--stat .proof-stat {
  font-family: "Rawest", system-ui, sans-serif;
  font-size: var(--mkt-h3);
  line-height: 1;
  letter-spacing: -0.022em;
  font-weight: 500;
  color: var(--text-primary);
}

.proof-row--stat .proof-label {
  font-family: var(--font-base);
  font-size: var(--text-small, 13px);
  line-height: 1.4;
  color: var(--text-tertiary);
}

.proof-row--badge {
  display: flex;
  flex-direction: row;
  align-items: baseline;
  justify-content: space-between;
  gap: 14px;
  padding-top: 12px;
  border-top: 1px solid var(--border-subtle);
}

.proof-row--badge .proof-name {
  font-family: var(--font-base);
  font-size: var(--mkt-body);
  line-height: 1.3;
  color: var(--text-primary);
  font-weight: 500;
  letter-spacing: -0.005em;
}

.proof-row--badge .proof-meta {
  font-family: var(--font-code, monospace);
  font-size: 11px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-quaternary);
  flex-shrink: 0;
}

/* Proof-roll mobile axis swap — has to live AFTER the base
   .proof-roll-list rule above or source order would override
   it (media queries don't add specificity).  Switches the
   credits-roll from a tall vertical column on the right of
   the hero to a thin horizontal ticker below it on mobile. */
@media (max-width: 1023px) {
  .proof-roll {
    height: auto;
    width: 100%;
    /* Mask fades left + right so ticker items slide in/out
       softly at the edges (rotated from desktop's top/bottom). */
    -webkit-mask-image: linear-gradient(
      to right, transparent 0%, #000 6%, #000 94%, transparent 100%
    );
            mask-image: linear-gradient(
      to right, transparent 0%, #000 6%, #000 94%, transparent 100%
    );
  }
  .proof-roll-list {
    flex-direction: row;
    align-items: center;
    gap: clamp(28px, 4vw, 40px);
    padding: clamp(18px, 2.4vh, 24px) clamp(20px, 4vw, 32px);
    /* JS sets --roll-x on mobile (and zeroes --roll-y). */
    transform: translate3d(var(--roll-x, 0px), 0, 0);
  }
  .proof-row {
    flex-shrink: 0;
    flex-direction: row;
    align-items: baseline;
    gap: 8px;
  }
  .proof-row--stat .proof-stat {
    font-size: var(--mkt-h4);
    line-height: 1;
  }
  .proof-row--stat .proof-label {
    font-size: var(--text-small, 13px);
    color: var(--text-tertiary);
    white-space: nowrap;
  }
  .proof-row--badge {
    border-top: 0;
    padding-top: 0;
    align-items: center;
    justify-content: flex-start;
  }
  .proof-row--badge .proof-name {
    font-size: var(--text-base, 15px);
    white-space: nowrap;
  }
  .proof-row--badge .proof-meta {
    font-size: 10px;
  }
}

@media (max-width: 600px) {
  .proof-roll-list {
    gap: 24px;
    padding: 16px 18px;
  }
  .proof-row--stat .proof-stat {
    font-size: 22px;
  }
  .proof-row--badge .proof-name {
    font-size: 14px;
  }
}

[data-act="1"] h1 {
  margin: 0;
  font-family: "Rawest", system-ui, sans-serif;
  font-size: var(--display-xlarge);
  line-height: var(--leading-display-xlarge);
  letter-spacing: var(--tracking-dense);
  font-weight: 500;
  /* H1 stays pure white across the three lines.  The narrative
     accent is the purple-7 underline on the marked phrases, not
     a text colour shift. */
  color: #fff;
  text-align: left;
  width: 100%;
  max-width: 960px;
  text-wrap: balance;
  overflow-wrap: break-word;
}

[data-act="1"] .hero-line {
  display: block;
}

/* Marked phrase per line.  Painted by shifting the text
   colour from inherit (white) to purple-7.  Mark 1 paints on
   load (visible from scrollY=0), mark 2 reveals when the
   reader scrolls a little, mark 3 a little more.  Read
   together the three marks deliver the brand thesis in a
   single arc: "runs on data → the open web → readable". */
.hero-mark {
  font-style: normal;
  color: inherit;
  /* Keep multi-word marks atomic so the colour shift never
     splits mid-phrase if a line wraps tightly. */
  white-space: nowrap;
  transition: color 800ms cubic-bezier(0.22, 1, 0.36, 1);
}

.hero-mark.painted {
  color: #7C4FE8;
}

/* No-JS fallback — only the first mark stays purple, so the
   single-active design intent survives.  The other two read as
   plain H1 text. */
html:not(.js) .hero-mark[data-mark="1"] {
  color: #7C4FE8;
}

/* Mobile H1 sizing — the display-xlarge token resolves too
   large on phones (the hero-marks "runs on data", "the open
   web" are nowrap and would each be wider than the viewport
   at the default size, leaving the lines visibly cut on the
   right edge).  Force a sub-viewport size so each marked
   phrase fits atomically.  Sized to keep "the open web" (the
   widest mark, 12 chars) fitting in the viewport content area
   at every phone width down to ~320. */
@media (max-width: 600px) {
  [data-act="1"] h1 {
    font-size: var(--mkt-h2);
    line-height: 1.05;
    /* text-wrap: balance can produce awkward breaks at small
       widths.  Let lines wrap naturally on phone. */
    text-wrap: initial;
  }
  /* The nowrap marks ("the open web", "runs on data") still exceed
     the content width on the narrowest phones, spilling a few px past
     the viewport. Let them wrap; the paint is one span, so the colour
     shift stays whole across the wrap. */
  [data-act="1"] .hero-mark { white-space: normal; }
}

@media (max-width: 380px) {
  [data-act="1"] h1 {
    font-size: 36px;
  }
}

/* Primary CTA + ready-to-use tagline.  Home hero variant:
   inline (tagline to the right of the button).  Lives in the
   .hero-content grid's row 2 (auto), with the H1 above in
   row 1 (1fr) and the subhead in row 3 (auto). */
[data-act="1"] .hero-cta-row {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: clamp(14px, 1.6vw, 24px);
  flex-wrap: wrap;
}

[data-act="1"] .hero-cta-note {
  margin: var(--space-300, 10px) 0 0;
  font-size: var(--text-small, 13px);
  line-height: 1.5;
  color: var(--text-tertiary);
}

/* Secondary CTA — the shared .link-cta atom (components.css). */

@media (max-width: 600px) {
  [data-act="1"] .hero-cta-row {
    flex-direction: column;
    align-items: stretch;
    gap: 10px;
  }
  [data-act="1"] .hero-cta-row .btn-marketing {
    justify-content: center;
  }
  [data-act="1"] .hero-cta-note {
    text-align: center;
  }
}

[data-act="1"] .hero-subhead {
  margin: var(--space-800, 32px) 0 0;
  font-size: var(--text-large, 15px);
  line-height: 1.55;
  color: var(--text-tertiary);
  text-align: left;
  max-width: 560px;
  text-wrap: balance;
}

/* Scroll cue — bottom row of the grid.  Quiet, ambient. */
.scroll-cue {
  align-self: end;
  justify-self: start;
  padding-bottom: 32px;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: var(--text-tiny, 11px);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-quaternary, var(--text-tertiary));
  animation: scroll-cue-pulse 2.4s ease-in-out infinite;
}

.scroll-cue::after {
  content: "";
  display: inline-block;
  width: 1px;
  height: 28px;
  background: var(--text-quaternary, var(--text-tertiary));
  transform-origin: top;
  animation: scroll-cue-line 2.4s ease-in-out infinite;
}

@keyframes scroll-cue-pulse {
  0%, 100% { opacity: 0.4; }
  50% { opacity: 1; }
}

@keyframes scroll-cue-line {
  0% { transform: scaleY(0); }
  50% { transform: scaleY(1); }
  100% { transform: scaleY(0); transform-origin: bottom; }
}

/* ──────────────────────────────────────────────────────────
   Act 2 — The pattern
   Vignette stack.  The frame line sits at display-large so it
   reads as a follow-up beat to Act 1's display-xlarge question.
   Vignettes use heading-xlarge with weight + color contrast
   between setup and punchline so each two-beat lands.  Closer
   matches the frame line size but softens to text-secondary so
   it reads as a comment, not a new claim.

   Shared building blocks declared here (.act-kicker, .act-frame,
   .act-closer) will be reused by Acts 3 through 7 as we design
   them; they're scoped by data-act for now and we'll promote
   them to top-level helpers once they prove out.
─────────────────────────────────────────────────────────── */

/* The eyebrow is now the shared .kicker--home component (see
   _shared/components.css). */

/* ── Act 2 stage — sticky frame with text + scroll-linked cards ── */

[data-act="2"] {
  /* Reset the generic act padding; the stage owns its own layout. */
  padding: 0;
}

.act2-stage {
  position: relative;
  /* 4 viewports of scroll room: 1 for the stage to land, 3 for the
     cards to drift across.  Tune up for slower, down for snappier. */
  min-height: 400vh;
}

.act2-frame {
  position: sticky;
  top: 0;
  height: 100vh;
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr);
  align-items: center;
  /* No frame padding so the cards column reaches the viewport's
     right edge.  Left padding lives on the text column so the
     hero gutter still aligns with Act 1. */
  padding: 0;
  overflow: hidden;
}

.act2-text {
  z-index: 2;
  max-width: 560px;
  padding-left: clamp(20px, 4vw, 48px);
}

.act2-text .act-frame-line {
  margin: 0;
  font-family: "Rawest", system-ui, sans-serif;
  font-size: var(--display-large);
  line-height: var(--leading-display-large);
  letter-spacing: var(--tracking-dense);
  font-weight: 500;
  color: var(--text-primary);
  text-wrap: balance;
}

.act2-cards {
  position: relative;
  height: 100%;
  /* Hard clip at the column's left edge.  No mask gradient —
     the fade was reading as a blur shadow because it ramped
     opacity across the already-blurred mesh edge.  Cards now
     clip cleanly against the invisible container. */
  overflow: hidden;
}

.act2-track {
  position: absolute;
  top: 50%;
  /* Start with cards 1 and 2 visible on arrival.  The track
     pins to a small left pad inside the column so card 1 sits
     past the mask wipe but close to the column's left edge.
     home.js then translates the track left as the user scrolls
     so all four cards traverse the column. */
  left: clamp(16px, 2vw, 32px);
  display: flex;
  gap: clamp(24px, 3vw, 48px);
  transform: translate3d(0, -50%, 0);
  will-change: transform;
}

.act2-card {
  flex-shrink: 0;
  width: clamp(260px, 26vw, 380px);
  aspect-ratio: 5 / 6;
  border-radius: var(--radius-lg, 12px);
  padding: clamp(20px, 2.4vw, 36px);
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  position: relative;
  overflow: hidden;
  isolation: isolate;
  /* contain: paint guarantees the mesh's blur(36px) is clipped
     to the card boundary in every browser.  Without this some
     engines let the blur halo leak past overflow:hidden when
     it's combined with border-radius, which read as a soft
     shadow around the card. */
  contain: paint;
  /* No border on the card itself — on a dark background the
     1px border-subtle reads as a light vertical line on the
     card edge.  The mesh fills the card cleanly without it. */
}

/* Mesh-gradient backdrop.  Sits flush with the card (inset: 0)
   so the gradient positions resolve in card coordinates.  A
   baseline scale(1.2) lets the JS scroll-linked transform pan
   / rotate the mesh up to a few percent without ever revealing
   a hard card edge underneath.  No animation here — home.js
   applies the transform directly, tied to scroll progress, so
   the mesh actually moves when the reader scrolls.  Respects
   prefers-reduced-motion via that same JS path. */
.act2-card .card-mesh {
  position: absolute;
  inset: 0;
  z-index: 0;
  filter: blur(36px);
  opacity: 1;
  transform: translateZ(0) scale(1.2);
  backface-visibility: hidden;
  transform-origin: center;
  will-change: transform;
}

/* Per-card mesh palettes.  Each card's background is a
   saturated mid-tone linear-gradient (level-5 tokens in dark
   mode) so there is no "dark hole" anywhere on the card: even
   where the radials fade out, the surface stays in-palette
   instead of dropping to near-black.  Radial mesh on top adds
   bright pops via level-6 tokens; positions are spread across
   the card (not clustered at corners) so coverage stays even. */

.act2-card[data-card="1"] {
  background: linear-gradient(135deg, var(--zen-purple-5) 0%, var(--zen-blue-5) 100%);
}
.act2-card[data-card="1"] .card-mesh {
  background:
    radial-gradient(ellipse 85% 75% at 28% 30%, var(--zen-purple-6) 0%, transparent 78%),
    radial-gradient(ellipse 85% 80% at 78% 38%, var(--zen-cyan-6)   0%, transparent 78%),
    radial-gradient(ellipse 80% 80% at 52% 80%, var(--zen-blue-6)   0%, transparent 78%);
}

.act2-card[data-card="2"] {
  background: linear-gradient(135deg, var(--zen-yellow-5) 0%, var(--zen-purple-5) 100%);
}
.act2-card[data-card="2"] .card-mesh {
  background:
    radial-gradient(ellipse 85% 75% at 25% 72%, var(--zen-purple-6) 0%, transparent 78%),
    radial-gradient(ellipse 85% 80% at 76% 30%, var(--zen-yellow-6) 0%, transparent 78%),
    radial-gradient(ellipse 80% 80% at 55% 65%, var(--zen-orange-6) 0%, transparent 78%);
}

.act2-card[data-card="3"] {
  background: linear-gradient(135deg, var(--zen-red-5) 0%, var(--zen-orange-5) 100%);
}
.act2-card[data-card="3"] .card-mesh {
  background:
    radial-gradient(ellipse 85% 75% at 24% 32%, var(--zen-purple-6) 0%, transparent 78%),
    radial-gradient(ellipse 85% 80% at 76% 68%, var(--zen-red-6)    0%, transparent 78%),
    radial-gradient(ellipse 80% 80% at 52% 80%, var(--zen-orange-6) 0%, transparent 78%);
}

.act2-card[data-card="4"] {
  background: linear-gradient(135deg, var(--zen-green-5) 0%, var(--zen-purple-5) 100%);
}
.act2-card[data-card="4"] .card-mesh {
  background:
    radial-gradient(ellipse 85% 75% at 28% 75%, var(--zen-purple-6) 0%, transparent 78%),
    radial-gradient(ellipse 85% 80% at 75% 32%, var(--zen-green-6)  0%, transparent 78%),
    radial-gradient(ellipse 80% 80% at 55% 58%, var(--zen-cyan-6)   0%, transparent 78%);
}

.act2-card .setup {
  position: relative;
  z-index: 1;
  margin: 0 0 var(--space-300, 12px);
  font-family: "Rawest", system-ui, sans-serif;
  font-size: var(--heading-large);
  line-height: var(--leading-heading-large);
  letter-spacing: var(--tracking-dense);
  font-weight: var(--font-weight-regular);
  color: rgba(255, 255, 255, 0.72);
}

.act2-card .punchline {
  position: relative;
  z-index: 1;
  margin: 0;
  font-family: "Rawest", system-ui, sans-serif;
  font-size: var(--heading-xlarge);
  line-height: var(--leading-heading-xlarge);
  letter-spacing: var(--tracking-dense);
  font-weight: var(--font-weight-medium);
  color: #fff;
}

[data-act="2"] .act-closer {
  margin: 0;
  padding: var(--space-3400, 136px) clamp(20px, 4vw, 48px);
  font-family: "Rawest", system-ui, sans-serif;
  font-size: var(--display-medium);
  line-height: var(--leading-display-medium);
  letter-spacing: var(--tracking-dense);
  font-weight: 500;
  color: var(--text-secondary);
  max-width: calc(720px + clamp(20px, 4vw, 48px) * 2);
  text-wrap: balance;
}

/* Below 1200px the sticky scroll-linked horizontal drift stops
   making sense — the cards column gets too narrow for the cards
   to traverse cleanly, and on tablet / mobile they already fit
   on a single scroll pass.  Fall back to a vertical stack:
   text on top, cards stacked full-width below (one per row,
   up to 480px), no scroll-tied translation.  The mesh scroll
   transform (home.js) keeps running so the cards still feel
   alive as the reader scrolls past. */
@media (max-width: 1199px) {
  .act2-stage {
    min-height: 0;
  }
  .act2-frame {
    position: static;
    height: auto;
    grid-template-columns: 1fr;
    gap: var(--space-1200, 48px);
    padding: var(--space-2400, 96px) clamp(20px, 4vw, 48px);
    overflow: visible;
  }
  .act2-text {
    padding-left: 0;
    max-width: none;
  }
  .act2-cards {
    height: auto;
    overflow: visible;
    -webkit-mask-image: none;
            mask-image: none;
  }
  .act2-track {
    position: static;
    transform: none;
    left: 0;
    flex-direction: column;
    gap: var(--space-800, 32px);
    width: 100%;
    align-items: stretch;
  }
  .act2-card {
    width: 100%;
    max-width: 480px;
    margin: 0 auto;
    aspect-ratio: 5 / 5;
  }
}

/* ──────────────────────────────────────────────────────────
   Act 3 — The layer
   Sticky purple anchor band.  Background sits on saturated
   --zen-purple-7 with two mesh layers drifting on top (scroll-
   linked, same primitives as Act 2).  Three beats stack
   vertically and fade in one by one as the reader scrolls;
   final tableau is the full anchor claim visible at once.
─────────────────────────────────────────────────────────── */

[data-act="3"] {
  padding: 0;
  max-width: none;
  margin: 0;
}

.act3-stage {
  position: relative;
  min-height: 300vh;
}

.act3-frame {
  position: sticky;
  top: 0;
  height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  /* No background here — .act3-curtain carries the purple and
     home.js animates its clip-path so the colour blooms into
     view as the reader scrolls in. */
  overflow: hidden;
}

/* Scroll-tied "bloom" curtain.  Starts as a 0% circle at the
   anchor (70%, 0%), grows to 150% over the first ~12% of the
   stage's progress so the saturated purple expands into the
   viewport instead of cutting in.  Behind the mesh + content. */
.act3-curtain {
  position: absolute;
  inset: 0;
  z-index: 0;
  background: #7C4FE8;
  clip-path: circle(0% at 70% 0%);
  will-change: clip-path;
}

html:not(.js) .act3-curtain {
  clip-path: none;
}

/* Editorial spread.  Two-column grid for headline + aside,
   manifesto spans full width below.  Asymmetric proportions
   (1.5fr / 1fr) so the headline owns the spread and the aside
   reads as a sidebar.  Max-width pegged to a comfortable
   editorial measure (1200px) so the columns don't stretch. */
.act3-layout {
  position: relative;
  z-index: 1;
  width: 100%;
  max-width: 1200px;
  padding: 0 clamp(20px, 4vw, 48px);
  display: grid;
  grid-template-columns: 1.6fr 1fr;
  column-gap: clamp(40px, 6vw, 96px);
  align-items: end;
}

.act3-headline {
  grid-column: 1;
  grid-row: 1;
  margin: 0;
  font-family: "Rawest", system-ui, sans-serif;
  font-size: var(--display-large);
  line-height: var(--leading-display-large);
  letter-spacing: var(--tracking-dense);
  font-weight: 500;
  color: #fff;
  display: flex;
  flex-direction: column;
  /* Slight pull-up between verse lines so the headline reads
     as one tight stanza, not three independent sentences. */
  gap: 0.05em;
}

.act3-line {
  display: block;
  /* Default state — JS toggles .visible at scroll thresholds. */
  opacity: 0;
  transform: translateY(16px);
  transition:
    opacity 650ms cubic-bezier(0.22, 1, 0.36, 1),
    transform 650ms cubic-bezier(0.22, 1, 0.36, 1);
}

.act3-line.visible {
  opacity: 1;
  transform: translateY(0);
}

/* Aside — sits in the right column, baseline near the last
   headline line so the two read in tandem.  Small-caps tag
   above (via ::before) marks it as a sidebar voice rather
   than a continuation of the headline. */
.act3-aside {
  grid-column: 2;
  grid-row: 1;
  align-self: end;
  margin: 0;
  font-family: var(--font-base);
  font-size: var(--text-large);
  line-height: 1.55;
  font-weight: var(--font-weight-regular);
  color: rgba(255, 255, 255, 0.82);
  max-width: 380px;
  /* JS slides this in from the right with a fade. */
  opacity: 0;
  transform: translateX(20px);
  transition:
    opacity 700ms cubic-bezier(0.22, 1, 0.36, 1),
    transform 700ms cubic-bezier(0.22, 1, 0.36, 1);
}

.act3-aside.visible {
  opacity: 1;
  transform: translateX(0);
}

.act3-aside::before {
  content: "The layer";
  display: block;
  margin-bottom: var(--space-300, 12px);
  font-family: var(--font-base);
  font-size: var(--text-tiny);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  font-weight: var(--font-weight-medium);
  color: rgba(255, 255, 255, 0.6);
}

/* No-JS fallback — render everything visible if the script
   never runs.  html.js gate disables this once home.js boots. */
html:not(.js) [data-act="3"] .act3-line,
html:not(.js) [data-act="3"] .act3-aside {
  opacity: 1;
  transform: none;
}

/* Below 1200px the asymmetric two-column spread loses its
   editorial breath.  Collapse to a single column: headline,
   aside, manifesto stacked vertically.  Everyone visible from
   the start; mesh still drifts at any width so the band feels
   alive. */
@media (max-width: 1199px) {
  .act3-stage { min-height: 0; }
  .act3-frame {
    position: static;
    height: auto;
    padding: var(--space-3400, 136px) 0;
  }
  /* Curtain skips its scroll-tied bloom on tablet / mobile;
     purple fills the section statically so the act still
     reads as a coloured band. */
  .act3-curtain { clip-path: none; }
  .act3-layout {
    grid-template-columns: 1fr;
    row-gap: var(--space-1200, 48px);
  }
  /* Both headline + aside were pinned to grid-row 1 in the
     desktop layout (side-by-side columns).  Without re-setting
     them in the single-column mobile grid they stack ON TOP of
     each other.  Explicit rows fix the overlap. */
  .act3-headline { grid-column: 1; grid-row: 1; }
  .act3-aside { grid-column: 1; grid-row: 2; align-self: start; max-width: 560px; }
  .act3-line,
  .act3-aside {
    opacity: 1;
    transform: none;
  }
}

/* ──────────────────────────────────────────────────────────
   Act 4 — What changes
   Editorial stack: section intro, then a full-bleed AI lead
   card (mesh treatment, agent-native wedge), then a 3-card
   grid for the supporting audiences.  Each block reveals on
   IntersectionObserver as it enters the viewport; nth-child
   delays stagger the verse lines and the bullets inside.
─────────────────────────────────────────────────────────── */

[data-act="4"] {
  padding: var(--space-3400, 136px) 0;
  margin: 0;
}

.act4-container {
  max-width: 1200px;
  width: 100%;
  margin: 0 auto;
  padding: 0 clamp(20px, 4vw, 48px);
  display: flex;
  flex-direction: column;
  gap: var(--space-1400, 56px);
}

.act4-intro {
  /* Inherits container layout; no extra width / padding needed
     since the parent already handles them. */
}

.act4-headline {
  margin: 0;
  font-family: "Rawest", system-ui, sans-serif;
  font-size: var(--display-large);
  line-height: var(--leading-display-large);
  letter-spacing: var(--tracking-dense);
  font-weight: 500;
  color: var(--text-primary);
  display: flex;
  flex-direction: column;
  gap: 0.05em;
  text-wrap: balance;
  max-width: 920px;
}

.act4-headline .act4-line {
  display: block;
}

.act4-headline .act4-line:nth-child(2) { color: var(--text-secondary); }
.act4-headline .act4-line:nth-child(3) { color: var(--accent); }

/* AI lead card — saturated brand purple (zen-purple-7).
   White text reads on top.  A teal corner mesh adds depth and
   visually bridges to the supporting teal-5 cards below. */
.act4-card--lead {
  position: relative;
  width: 100%;
  margin: 0;
  padding: clamp(32px, 4vw, 56px);
  overflow: hidden;
  isolation: isolate;
  contain: paint;
  /* Hardcoded brand purple (zen-purple-7 light-mode hex) — the
     dark-mode override resolves to a lighter, less saturated
     purple that loses the brand-anchor feel.  We want the
     Figma value here regardless of theme. */
  background: #7C4FE8;
  border: none;
  border-radius: var(--radius-lg, 12px);
}

/* Teal mesh glow in the top-right corner.  Bridges to the
   teal supporting cards below; scroll-linked drift on desktop
   keeps it alive. */
.act4-card--lead .card-mesh {
  position: absolute;
  top: -40%;
  right: -20%;
  width: 70%;
  height: 130%;
  z-index: 0;
  filter: blur(90px);
  transform: translateZ(0);
  backface-visibility: hidden;
  opacity: 0.5;
  transform-origin: center;
  will-change: transform;
  background:
    radial-gradient(circle at 50% 40%, var(--zen-teal-5), transparent 65%),
    radial-gradient(circle at 60% 75%, var(--zen-purple-9), transparent 65%);
  pointer-events: none;
}

/* Agent character — peeks from the bottom-right corner of the
   AI lead card.  Anchored flush to the right + bottom edges
   (no padding), then pulled down by exactly 40% so the dome
   sits right at the card's bottom edge and the empty viewBox
   space below clips out — reads as "body's down there, you
   just see the head."

   Starts further down (120%) and rises to 40% when the card
   enters the viewport.  Ease-out cubic-bezier so the rise
   feels like a deliberate landing, not a snap. */
.act4-card--lead .agent-peek {
  position: absolute;
  right: 0;
  bottom: 0;
  width: clamp(220px, 22vw, 320px);
  height: auto;
  transform: translateY(120%);
  pointer-events: none;
  user-select: none;
  z-index: 0;
  transition: transform 1200ms cubic-bezier(0.22, 1, 0.36, 1) 150ms;
}

.act4-card--lead.revealed .agent-peek {
  transform: translateY(40%);
}

html:not(.js) .act4-card--lead .agent-peek {
  transform: translateY(40%);
}

@media (prefers-reduced-motion: reduce) {
  .act4-card--lead .agent-peek {
    transition: none;
    transform: translateY(40%);
  }
}

@media (max-width: 768px) {
  .act4-card--lead .agent-peek {
    /* On narrow viewports the lead card is single-column with
       the prompt block taking real estate; the agent peek
       shrinks but keeps the same final landing position. */
    width: clamp(140px, 32vw, 200px);
  }
  .act4-card--lead.revealed .agent-peek {
    transform: translateY(40%);
  }
}

.act4-card-inner {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  gap: var(--space-600, 24px);
  max-width: 760px;
}

.act4-card--lead .audience-label {
  margin: 0;
  font-family: var(--font-base);
  font-size: var(--text-tiny);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  font-weight: var(--font-weight-medium);
  color: rgba(255, 255, 255, 0.7);
}

.act4-card--lead .audience-headline {
  margin: 0;
  font-family: "Rawest", system-ui, sans-serif;
  font-size: var(--display-medium);
  line-height: var(--leading-display-medium);
  letter-spacing: var(--tracking-dense);
  font-weight: 500;
  color: #fff;
  text-wrap: balance;
}

.act4-card--lead .audience-bullets {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-400, 16px);
  font-family: var(--font-base);
  font-size: var(--text-large);
  line-height: 1.55;
  color: rgba(255, 255, 255, 0.85);
}

.act4-card--lead .audience-bullets li {
  position: relative;
  padding-left: 1.6em;
}

.act4-card--lead .audience-bullets li::before {
  content: "\2192";
  position: absolute;
  left: 0;
  color: rgba(255, 255, 255, 0.55);
}

/* Install prompt — terminal-style copy-block.  Sits as the
   final beat inside the AI lead card.  Dark scrim on the
   purple-7 surface so the code reads as a terminal.  Hugs its
   content (width: fit-content) so it doesn't stretch to the
   card edges — reads as a code chip, not a panel. */
.agent-install-prompt {
  margin: 0;
  background: rgba(0, 0, 0, 0.35);
  border: 1px solid rgba(255, 255, 255, 0.15);
  border-radius: var(--radius, 6px);
  padding: clamp(16px, 1.8vw, 24px);
  width: fit-content;
  max-width: 100%;
}

.agent-install-prompt figcaption {
  font-family: var(--font-base);
  font-size: var(--text-tiny);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  font-weight: var(--font-weight-medium);
  color: rgba(255, 255, 255, 0.55);
  margin-bottom: var(--space-300, 12px);
}

.agent-install-prompt pre {
  margin: 0;
  white-space: pre-wrap;
  word-break: break-word;
}

.agent-install-prompt code {
  font-family: var(--font-mono);
  font-size: var(--text-base);
  color: #fff;
  background: none;
  padding: 0;
}

/* Supporting audience grid — 3 cards in a row, inside the
   shared act4-container.  Same chrome as the AI lead card
   (sunken bg + subtle border) so the whole act reads as one
   editorial grouping. */
.act4-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--space-600, 24px);
}

.act4-grid .act4-card {
  /* Hardcoded bright teal (zen-teal-5 light-mode hex) — the
     dark-mode token resolves to a dark gray-teal that doesn't
     read as teal at all.  Light surface here means the trio
     pops against the dark page and contrasts the saturated
     purple lead. */
  background: #7ADAE6;
  border: none;
  border-radius: var(--radius-lg, 12px);
  padding: clamp(24px, 2.5vw, 32px);
  display: flex;
  flex-direction: column;
  gap: var(--space-500, 20px);
}

/* Dark type on the bright teal cards so the reader can
   actually parse them.  Hardcoded slate-900 alpha so the
   colour doesn't flip with the global theme. */
.act4-grid .audience-label {
  margin: 0;
  font-family: var(--font-base);
  font-size: var(--text-tiny);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  font-weight: var(--font-weight-medium);
  color: rgba(29, 22, 18, 0.7);
}

.act4-grid .audience-headline {
  margin: 0;
  font-family: "Rawest", system-ui, sans-serif;
  font-size: var(--heading-xlarge);
  line-height: var(--leading-heading-xlarge);
  letter-spacing: var(--tracking-dense);
  font-weight: 500;
  color: rgba(29, 22, 18, 1);
  text-wrap: balance;
}

.act4-grid .audience-bullets {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-300, 12px);
  font-family: var(--font-base);
  font-size: var(--text-large);
  line-height: 1.55;
  color: rgba(29, 22, 18, 0.82);
}

.act4-grid .audience-bullets li {
  position: relative;
  padding-left: 1.4em;
}

.act4-grid .audience-bullets li::before {
  content: "\2192";
  position: absolute;
  left: 0;
  color: rgba(29, 22, 18, 0.5);
}

/* Reveal motion — IntersectionObserver toggles .revealed
   on each [data-reveal] block.  Default state lifts content
   slightly and hides it; .revealed releases it.  Inside the
   revealed block, child transitions stagger by nth-child so
   the lines / bullets / cards land as a sequence. */
[data-act="4"] [data-reveal] {
  opacity: 0;
  transform: translateY(24px);
  transition:
    opacity 800ms cubic-bezier(0.22, 1, 0.36, 1),
    transform 800ms cubic-bezier(0.22, 1, 0.36, 1);
}

[data-act="4"] [data-reveal].revealed {
  opacity: 1;
  transform: translateY(0);
}

.act4-intro.revealed .act4-line {
  animation: act4-line-rise 700ms cubic-bezier(0.22, 1, 0.36, 1) both;
}
.act4-intro.revealed .act4-line:nth-child(1) { animation-delay: 100ms; }
.act4-intro.revealed .act4-line:nth-child(2) { animation-delay: 240ms; }
.act4-intro.revealed .act4-line:nth-child(3) { animation-delay: 380ms; }

@keyframes act4-line-rise {
  from { opacity: 0; transform: translateY(20px); }
  to   { opacity: 1; transform: translateY(0); }
}

.act4-card--lead.revealed .audience-bullets li {
  animation: act4-bullet-rise 600ms cubic-bezier(0.22, 1, 0.36, 1) both;
}
.act4-card--lead.revealed .audience-bullets li:nth-child(1) { animation-delay: 200ms; }
.act4-card--lead.revealed .audience-bullets li:nth-child(2) { animation-delay: 320ms; }
.act4-card--lead.revealed .audience-bullets li:nth-child(3) { animation-delay: 440ms; }

.act4-card--lead.revealed .agent-install-prompt {
  animation: act4-bullet-rise 700ms cubic-bezier(0.22, 1, 0.36, 1) 560ms both;
}

@keyframes act4-bullet-rise {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: translateY(0); }
}

.act4-grid.revealed .act4-card {
  animation: act4-card-rise 700ms cubic-bezier(0.22, 1, 0.36, 1) both;
}
.act4-grid.revealed .act4-card:nth-child(1) { animation-delay: 80ms; }
.act4-grid.revealed .act4-card:nth-child(2) { animation-delay: 200ms; }
.act4-grid.revealed .act4-card:nth-child(3) { animation-delay: 320ms; }

@keyframes act4-card-rise {
  from { opacity: 0; transform: translateY(20px); }
  to   { opacity: 1; transform: translateY(0); }
}

html:not(.js) [data-act="4"] [data-reveal],
html:not(.js) [data-act="4"] .act4-line,
html:not(.js) [data-act="4"] .audience-bullets li,
html:not(.js) [data-act="4"] .agent-install-prompt,
html:not(.js) [data-act="4"] .act4-card {
  opacity: 1;
  transform: none;
  animation: none;
}

@media (prefers-reduced-motion: reduce) {
  [data-act="4"] [data-reveal],
  [data-act="4"] .act4-line,
  [data-act="4"] .audience-bullets li,
  [data-act="4"] .agent-install-prompt,
  [data-act="4"] .act4-card {
    animation: none;
    transition: none;
  }
}

@media (max-width: 1024px) {
  .act4-grid {
    grid-template-columns: 1fr;
    gap: var(--space-800, 32px);
  }
}

@media (max-width: 768px) {
  [data-act="4"] {
    gap: var(--space-1400, 56px);
    padding: var(--space-2400, 96px) 0;
  }
  .act4-card--lead {
    /* Keep horizontal padding so the AI card content doesn't
       touch the card edges on mobile.  Vertical padding still
       gets a generous boost for the closer beat. */
    padding: clamp(32px, 7vw, 56px) clamp(24px, 5vw, 32px);
  }
  .act4-card-inner {
    gap: var(--space-600, 24px);
  }
}

/* ──────────────────────────────────────────────────────────
   Act 5 — How it disappears
   Three claims, alternating left/right two-column grid.
   Each claim shows the product working: code (claim 1), data
   transformation (claim 2), destinations (claim 3).  Reveals
   on scroll with internal nth-child stagger.
─────────────────────────────────────────────────────────── */

[data-act="5"] {
  padding: var(--space-3400, 136px) 0;
  margin: 0;
}

.act5-container {
  max-width: 1200px;
  width: 100%;
  margin: 0 auto;
  padding: 0 clamp(20px, 4vw, 48px);
  display: flex;
  flex-direction: column;
  gap: clamp(64px, 9vw, 128px);
}

.act5-frame {
  margin: 0;
  font-family: "Rawest", system-ui, sans-serif;
  font-size: var(--display-medium);
  line-height: var(--leading-display-medium);
  letter-spacing: var(--tracking-dense);
  font-weight: 500;
  color: var(--text-primary);
  max-width: 820px;
  text-wrap: balance;
}

.act5-claim {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr);
  gap: clamp(40px, 5vw, 80px);
  align-items: center;
}

/* Alternate sides: claims 1 and 3 keep text left / visual
   right; claim 2 flips so the rhythm reads as a zig-zag. */
.act5-claim[data-claim="2"] {
  grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr);
}
.act5-claim[data-claim="2"] .act5-claim-visual { order: 0; }
.act5-claim[data-claim="2"] .act5-claim-text { order: 1; }

.act5-claim-text {
  display: flex;
  flex-direction: column;
  gap: var(--space-500, 20px);
  max-width: 460px;
}

.act5-claim-number {
  position: relative;
  margin: 0;
  font-family: "Rawest", system-ui, sans-serif;
  font-size: var(--heading-xlarge);
  line-height: 1;
  letter-spacing: -0.01em;
  font-weight: 500;
  /* Gradient text per claim — each claim carries its own
     palette so the section reads as a controlled colour
     progression: purple → teal → pink. */
  background: linear-gradient(135deg, var(--zen-purple-7), var(--zen-purple-9));
  -webkit-background-clip: text;
          background-clip: text;
  color: transparent;
  width: max-content;
}

/* Soft colour glow behind each number — hidden by default,
   revealed only when the reader hovers the claim's visual
   block (code, transform diagram, or destination pills) on
   the opposite side.  Uses :has() so the claim element can
   detect a hover on its sibling visual regardless of DOM
   order.  Built with a blurred radial-gradient on ::after so
   it colours the area around the number without competing
   with the type itself. */
.act5-claim-number::after {
  content: "";
  position: absolute;
  inset: -80% -80%;
  background: radial-gradient(circle, currentColor 0%, transparent 65%);
  opacity: 0;
  transform: translateZ(0) scale(0.7);
  backface-visibility: hidden;
  filter: blur(20px);
  z-index: -1;
  pointer-events: none;
  transition:
    opacity 420ms cubic-bezier(0.22, 1, 0.36, 1),
    transform 420ms cubic-bezier(0.22, 1, 0.36, 1);
}

.act5-claim:has(.act5-claim-visual:hover) .act5-claim-number::after {
  opacity: 0.55;
  transform: scale(1.15);
}

.act5-claim[data-claim="1"] .act5-claim-number {
  background-image: linear-gradient(135deg, var(--zen-purple-7), var(--zen-cyan-6));
}
.act5-claim[data-claim="1"] .act5-claim-number::after { color: var(--zen-purple-7); }

.act5-claim[data-claim="2"] .act5-claim-number {
  background-image: linear-gradient(135deg, var(--zen-cyan-6), var(--zen-teal-6));
}
.act5-claim[data-claim="2"] .act5-claim-number::after { color: var(--zen-cyan-6); }

.act5-claim[data-claim="3"] .act5-claim-number {
  background-image: linear-gradient(135deg, var(--zen-pink-6), var(--zen-purple-9));
}
.act5-claim[data-claim="3"] .act5-claim-number::after { color: var(--zen-pink-6); }


.act5-claim-headline {
  margin: 0;
  font-family: "Rawest", system-ui, sans-serif;
  font-size: var(--display-large);
  line-height: var(--leading-display-large);
  letter-spacing: var(--tracking-dense);
  font-weight: 500;
  color: var(--text-primary);
  text-wrap: balance;
}

.act5-claim-body {
  margin: 0;
  font-family: var(--font-base);
  font-size: var(--text-large);
  line-height: 1.55;
  color: var(--text-secondary);
}

.act5-claim-visual {
  display: flex;
  flex-direction: column;
  gap: var(--space-500, 20px);
}

/* ── Code block (shared) ── */
.act5-claim-visual .code-block {
  position: relative;
  margin: 0;
  background: var(--sunken-base, rgba(255, 255, 255, 0.03));
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius, 6px);
  padding: clamp(16px, 1.8vw, 24px);
  overflow: hidden;
  transition: border-color 220ms ease;
}

/* Thin gradient accent at the top of each code block —
   reads like a code-editor tab marker.  Hidden by default so
   the boxes stay calm; lights up only on hover. */
.act5-claim-visual .code-block::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 2px;
  background: linear-gradient(
    90deg,
    var(--zen-purple-7) 0%,
    var(--zen-cyan-6) 35%,
    transparent 80%
  );
  opacity: 0;
  transition: opacity 220ms ease;
}

.act5-claim-visual .code-block:hover::before {
  opacity: 1;
}

/* Claim 2 code blocks tint to the teal/cyan family so the
   transform visual's palette stays cohesive. */
[data-claim="2"] .code-block::before {
  background: linear-gradient(
    90deg,
    var(--zen-cyan-6) 0%,
    var(--zen-teal-6) 35%,
    transparent 80%
  );
}

.act5-claim-visual .code-block figcaption {
  font-family: var(--font-base);
  font-size: var(--text-tiny);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  font-weight: var(--font-weight-medium);
  color: var(--text-tertiary);
  margin-bottom: var(--space-300, 12px);
}

.act5-claim-visual .code-block pre {
  margin: 0;
  white-space: pre-wrap;
  word-break: break-word;
}

.act5-claim-visual .code-block code {
  font-family: var(--font-mono);
  font-size: var(--text-base);
  line-height: 1.55;
  color: var(--text-primary);
  background: none;
  padding: 0;
}

/* ── BU summary atom (mirrors .ci-how-step-summary / .pm-how-step-summary).
   Replaces the tech code/curl/JSON block with a Key/Value rows widget so
   business-user mode reads as plain language inside the same container
   chrome (border + radius + figcaption). ── */
.act5-claim-summary {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.act5-claim-summary li {
  display: grid;
  grid-template-columns: minmax(64px, 90px) 1fr;
  gap: 12px;
  align-items: baseline;
  padding: 6px 0;
}

.act5-claim-summary li + li {
  border-top: 1px solid var(--border-subtle, rgba(255, 255, 255, 0.06));
}

.act5-claim-summary-k {
  font-family: var(--font-base);
  font-size: var(--text-small);
  font-weight: var(--font-weight-medium);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-tertiary);
}

.act5-claim-summary-v {
  font-family: var(--font-base);
  font-size: var(--text-base);
  line-height: 1.5;
  color: var(--text-primary);
}

/* ── Claim 2: URL → JSON transformation ── */
.transform-pair {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1.2fr);
  gap: var(--space-400, 16px);
  align-items: center;
}

.transform-arrow {
  font-family: var(--font-base);
  font-size: var(--heading-xlarge);
  text-align: center;
  font-weight: var(--font-weight-regular);
  /* Gradient arrow keyed to claim 2's palette — purple at the
     URL side, cyan at the JSON side, suggesting the flow. */
  background: linear-gradient(90deg, var(--zen-purple-7), var(--zen-cyan-6));
  -webkit-background-clip: text;
          background-clip: text;
  color: transparent;
}

@media (max-width: 768px) {
  .transform-pair {
    grid-template-columns: 1fr;
  }
  .transform-arrow {
    transform: rotate(90deg);
  }
}

/* ── Claim 3: destinations list ── */
.destinations {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-300, 12px);
}

.destinations li {
  display: inline-flex;
  align-items: center;
  font-family: var(--font-base);
  font-size: var(--text-large);
  line-height: 1.4;
  font-weight: var(--font-weight-medium);
  color: var(--text-primary);
  padding: var(--space-300, 12px) var(--space-500, 20px);
  background: var(--sunken-base, rgba(255, 255, 255, 0.04));
  border: 1px solid var(--border-subtle);
  border-radius: 999px;
  transition:
    border-color 220ms ease,
    background-color 220ms ease,
    color 220ms ease,
    transform 220ms cubic-bezier(0.22, 1, 0.36, 1);
  cursor: default;
}

/* Favicon — hidden by default (width 0 + opacity 0 + scaled
   down), reveals on hover with a scale + width animation so it
   pops out to the left of the label and the pill expands to
   fit.  border-radius: 50% rounds the favicon to a circle. */
.destinations li img {
  width: 0;
  height: 18px;
  border-radius: 50%;
  opacity: 0;
  transform: scale(0.4);
  transform-origin: center;
  margin-right: 0;
  flex-shrink: 0;
  transition:
    width 240ms cubic-bezier(0.22, 1, 0.36, 1),
    margin-right 240ms cubic-bezier(0.22, 1, 0.36, 1),
    opacity 220ms ease,
    transform 240ms cubic-bezier(0.34, 1.56, 0.64, 1);
}

.destinations li:hover img {
  width: 18px;
  margin-right: 10px;
  opacity: 1;
  transform: scale(1);
}

/* Hover bumps each pill toward the claim's pink palette.
   Subtle colour shift + 1px lift keep the interaction tactile
   without being shouty. */
.destinations li:hover {
  border-color: color-mix(in srgb, var(--zen-pink-6) 50%, var(--border-subtle));
  background: color-mix(in srgb, var(--zen-pink-6) 10%, var(--sunken-base, rgba(255, 255, 255, 0.04)));
  color: color-mix(in srgb, var(--zen-pink-6) 35%, var(--text-primary));
  transform: translateY(-1px);
}

/* ── Reveal motion ── */
[data-act="5"] [data-reveal] {
  opacity: 0;
  transform: translateY(24px);
  transition:
    opacity 800ms cubic-bezier(0.22, 1, 0.36, 1),
    transform 800ms cubic-bezier(0.22, 1, 0.36, 1);
}

[data-act="5"] [data-reveal].revealed {
  opacity: 1;
  transform: translateY(0);
}

/* Internal stagger inside each revealed claim: number lands
   first, then headline, then body, then visual. */
[data-act="5"] .act5-claim.revealed .act5-claim-number,
[data-act="5"] .act5-claim.revealed .act5-claim-headline,
[data-act="5"] .act5-claim.revealed .act5-claim-body,
[data-act="5"] .act5-claim.revealed .act5-claim-visual > * {
  animation: act5-stagger-rise 700ms cubic-bezier(0.22, 1, 0.36, 1) both;
}

[data-act="5"] .act5-claim.revealed .act5-claim-number   { animation-delay: 100ms; }
[data-act="5"] .act5-claim.revealed .act5-claim-headline { animation-delay: 220ms; }
[data-act="5"] .act5-claim.revealed .act5-claim-body     { animation-delay: 340ms; }
[data-act="5"] .act5-claim.revealed .act5-claim-visual > *:nth-child(1) { animation-delay: 460ms; }
[data-act="5"] .act5-claim.revealed .act5-claim-visual > *:nth-child(2) { animation-delay: 560ms; }
[data-act="5"] .act5-claim.revealed .act5-claim-visual > *:nth-child(3) { animation-delay: 660ms; }

@keyframes act5-stagger-rise {
  from { opacity: 0; transform: translateY(14px); }
  to   { opacity: 1; transform: translateY(0); }
}

html:not(.js) [data-act="5"] [data-reveal],
html:not(.js) [data-act="5"] .act5-claim-number,
html:not(.js) [data-act="5"] .act5-claim-headline,
html:not(.js) [data-act="5"] .act5-claim-body,
html:not(.js) [data-act="5"] .act5-claim-visual > * {
  opacity: 1;
  transform: none;
  animation: none;
}

@media (prefers-reduced-motion: reduce) {
  [data-act="5"] [data-reveal],
  [data-act="5"] .act5-claim-number,
  [data-act="5"] .act5-claim-headline,
  [data-act="5"] .act5-claim-body,
  [data-act="5"] .act5-claim-visual > * {
    animation: none;
    transition: none;
  }
}

@media (max-width: 1024px) {
  .act5-claim,
  .act5-claim[data-claim="2"] {
    grid-template-columns: 1fr;
    gap: var(--space-800, 32px);
  }
  .act5-claim[data-claim="2"] .act5-claim-visual { order: unset; }
  .act5-claim[data-claim="2"] .act5-claim-text { order: unset; }
  .act5-claim-text { max-width: none; }
}

@media (max-width: 768px) {
  [data-act="5"] {
    padding: var(--space-2400, 96px) 0;
  }
  .act5-container {
    gap: var(--space-2400, 96px);
  }
}

/* ──────────────────────────────────────────────────────────
   Act 6 — Where it lives
   Editorial chip grids in two labelled groups: agent surfaces
   + data destinations.  Each chip is a sunken pill that reveals
   a rounded favicon on hover (mirrors Act 5's destinations).
   Reveal on scroll, with the chips inside each group staggering
   in with a slight overshoot.
─────────────────────────────────────────────────────────── */

[data-act="6"] {
  padding: var(--space-3400, 136px) 0;
  margin: 0;
}

/* Editorial spread.  Two-column grid: intro on the left as a
   sticky sidebar (kicker + frame line), content on the right
   (chip groups + closer).  Container expands close to viewport
   width so the chip clouds spread across the full right
   column instead of wrapping into a tall stack. */
.act6-container {
  width: 100%;
  max-width: min(1800px, 95vw);
  margin: 0 auto;
  padding: 0 clamp(20px, 4vw, 48px);
  display: grid;
  grid-template-columns: minmax(260px, 380px) minmax(0, 1fr);
  gap: clamp(48px, 6vw, 96px);
  align-items: start;
}

.act6-intro {
  position: sticky;
  top: 120px;
  display: flex;
  flex-direction: column;
  gap: var(--space-500, 20px);
}

.act6-frame {
  margin: 0;
  font-family: "Rawest", system-ui, sans-serif;
  font-size: var(--display-medium);
  line-height: var(--leading-display-medium);
  letter-spacing: var(--tracking-dense);
  font-weight: 500;
  color: var(--text-primary);
  text-wrap: balance;
}

.act6-content {
  display: flex;
  flex-direction: column;
  gap: clamp(40px, 5vw, 64px);
}

/* Each group: small label on top of the chip cloud.  The chip
   cloud expands across the right-column width so a 13-chip
   destinations group fits in 1-2 rows on wide screens. */
.act6-group {
  display: flex;
  flex-direction: column;
  gap: var(--space-500, 20px);
}

.act6-group-label {
  margin: 0;
  font-family: var(--font-base);
  font-size: var(--text-tiny);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  font-weight: var(--font-weight-medium);
  color: var(--text-tertiary);
}

.connectors {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}

.connectors li {
  display: inline-flex;
  align-items: center;
  font-family: var(--font-base);
  font-size: var(--text-base);
  line-height: 1.4;
  font-weight: var(--font-weight-medium);
  color: var(--text-primary);
  padding: 10px 18px;
  background: var(--sunken-base, rgba(255, 255, 255, 0.04));
  border: 1px solid var(--border-subtle);
  border-radius: 999px;
  transition:
    border-color 220ms ease,
    background-color 220ms ease,
    color 220ms ease,
    transform 220ms cubic-bezier(0.22, 1, 0.36, 1);
  cursor: default;
}

/* Favicon — hidden by default, scale-in on hover (mirrors Act 5
   destinations).  Each connector gets its own brand mark left
   of the label. */
.connectors li img {
  width: 0;
  height: 16px;
  border-radius: 50%;
  opacity: 0;
  transform: scale(0.4);
  transform-origin: center;
  margin-right: 0;
  flex-shrink: 0;
  transition:
    width 240ms cubic-bezier(0.22, 1, 0.36, 1),
    margin-right 240ms cubic-bezier(0.22, 1, 0.36, 1),
    opacity 220ms ease,
    transform 240ms cubic-bezier(0.34, 1.56, 0.64, 1);
}

.connectors li:hover img {
  width: 16px;
  margin-right: 8px;
  opacity: 1;
  transform: scale(1);
}

.connectors li:hover {
  border-color: color-mix(in srgb, var(--accent) 45%, var(--border-subtle));
  background: color-mix(in srgb, var(--accent) 8%, var(--sunken-base, rgba(255, 255, 255, 0.04)));
  transform: translateY(-1px);
}

.act6-closer {
  margin: 0;
  font-family: "Rawest", system-ui, sans-serif;
  font-size: var(--display-medium);
  line-height: var(--leading-display-medium);
  letter-spacing: var(--tracking-dense);
  font-weight: 500;
  color: var(--text-secondary);
  max-width: 720px;
  text-wrap: balance;
}

/* ── Reveal motion ── */
[data-act="6"] [data-reveal] {
  opacity: 0;
  transform: translateY(24px);
  transition:
    opacity 800ms cubic-bezier(0.22, 1, 0.36, 1),
    transform 800ms cubic-bezier(0.22, 1, 0.36, 1);
}

[data-act="6"] [data-reveal].revealed {
  opacity: 1;
  transform: translateY(0);
}

/* Chip stagger inside each revealed group — 50ms steps so the
   chips drop in like a flock.  Overshoot easing for tactile
   pop.  Covers up to 16 chips per group; the data group has
   13 so we're well within bounds. */
[data-act="6"] .act6-group.revealed .connectors li {
  animation: act6-chip-pop 500ms cubic-bezier(0.34, 1.56, 0.64, 1) both;
}

[data-act="6"] .act6-group.revealed .connectors li:nth-child(1)  { animation-delay:  120ms; }
[data-act="6"] .act6-group.revealed .connectors li:nth-child(2)  { animation-delay:  180ms; }
[data-act="6"] .act6-group.revealed .connectors li:nth-child(3)  { animation-delay:  240ms; }
[data-act="6"] .act6-group.revealed .connectors li:nth-child(4)  { animation-delay:  300ms; }
[data-act="6"] .act6-group.revealed .connectors li:nth-child(5)  { animation-delay:  360ms; }
[data-act="6"] .act6-group.revealed .connectors li:nth-child(6)  { animation-delay:  420ms; }
[data-act="6"] .act6-group.revealed .connectors li:nth-child(7)  { animation-delay:  480ms; }
[data-act="6"] .act6-group.revealed .connectors li:nth-child(8)  { animation-delay:  540ms; }
[data-act="6"] .act6-group.revealed .connectors li:nth-child(9)  { animation-delay:  600ms; }
[data-act="6"] .act6-group.revealed .connectors li:nth-child(10) { animation-delay:  660ms; }
[data-act="6"] .act6-group.revealed .connectors li:nth-child(11) { animation-delay:  720ms; }
[data-act="6"] .act6-group.revealed .connectors li:nth-child(12) { animation-delay:  780ms; }
[data-act="6"] .act6-group.revealed .connectors li:nth-child(13) { animation-delay:  840ms; }

@keyframes act6-chip-pop {
  from { opacity: 0; transform: translateY(10px) scale(0.92); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}

html:not(.js) [data-act="6"] [data-reveal],
html:not(.js) [data-act="6"] .connectors li {
  opacity: 1;
  transform: none;
  animation: none;
}

@media (prefers-reduced-motion: reduce) {
  [data-act="6"] [data-reveal],
  [data-act="6"] .connectors li {
    animation: none;
    transition: none;
  }
}

/* Editorial spread collapses to a single column on tablet/
   mobile: intro stops being sticky and stacks on top, content
   flows below. */
@media (max-width: 1024px) {
  .act6-container {
    grid-template-columns: 1fr;
    gap: var(--space-1400, 56px);
  }
  .act6-intro {
    position: static;
    top: auto;
  }
}

@media (max-width: 768px) {
  [data-act="6"] {
    padding: var(--space-2400, 96px) 0;
  }
  .act6-container {
    gap: var(--space-1400, 56px);
  }
  .connectors {
    gap: 8px;
  }
  .connectors li {
    padding: 8px 14px;
    font-size: var(--text-small);
  }
}

/* ──────────────────────────────────────────────────────────
   Act 7 — The step
   Closer.  Sticky purple-7 band entered via a curtain that
   blooms from the centre (the opposite of Act 3's top-right
   bloom — together they bookend the saturated beats).  H2 in
   --display-huge, split into two verse lines; the second line
   pops word-by-word with overshoot, and the word "deserves"
   gets a brushstroke underline that draws on reveal.  Below:
   primary CTA + enterprise link + agent install prompt.
─────────────────────────────────────────────────────────── */

[data-act="7"] {
  padding: 0;
  margin: 0;
  max-width: none;
}

.act7-stage {
  position: relative;
  min-height: 200vh;
}

.act7-frame {
  position: sticky;
  top: 0;
  height: 100vh;
  display: flex;
  align-items: center;
  justify-content: flex-start;
  overflow: hidden;
}

.act7-curtain {
  position: absolute;
  inset: 0;
  z-index: 0;
  background: #7C4FE8;
  clip-path: circle(0% at 50% 50%);
  will-change: clip-path;
}

html:not(.js) .act7-curtain { clip-path: none; }

.act7-content {
  position: relative;
  z-index: 1;
  max-width: 1100px;
  width: 100%;
  margin: 0 auto;
  padding: 0 clamp(20px, 4vw, 48px);
  display: flex;
  flex-direction: column;
  gap: clamp(28px, 3vw, 44px);
}

.act7-headline {
  margin: 0;
  font-family: "Rawest", system-ui, sans-serif;
  font-size: var(--display-xlarge);
  line-height: var(--leading-display-xlarge);
  letter-spacing: var(--tracking-dense);
  font-weight: 500;
  color: #fff;
  text-wrap: balance;
}

.act7-line {
  display: block;
}

/* Bottom line stays on a single row at display-xlarge sizes —
   "deserves" was wrapping to a third line on some viewports.
   Relaxed on narrow viewports where the line can't fit. */
.act7-line--bottom {
  white-space: nowrap;
}

@media (max-width: 720px) {
  .act7-line--bottom {
    white-space: normal;
  }
}

/* Top line slides in as a single block.  Bottom line lands
   word-by-word with overshoot so "deserves" arrives as a
   small punctuated beat. */
.act7-line--top {
  opacity: 0;
  transform: translateY(20px);
  transition:
    opacity 700ms cubic-bezier(0.22, 1, 0.36, 1),
    transform 700ms cubic-bezier(0.22, 1, 0.36, 1);
}

.act7-line--top.visible {
  opacity: 1;
  transform: translateY(0);
}

.act7-line--bottom .word {
  display: inline-block;
  opacity: 0;
  transform: translateY(12px) scale(0.92);
  transition:
    opacity 700ms ease,
    transform 700ms cubic-bezier(0.34, 1.56, 0.64, 1);
}

.act7-line--bottom .word + .word { margin-left: 0.18em; }

.act7-line--bottom.visible .word { opacity: 1; transform: translateY(0) scale(1); }
.act7-line--bottom.visible .word:nth-child(1) { transition-delay:  80ms; }
.act7-line--bottom.visible .word:nth-child(2) { transition-delay: 200ms; }
.act7-line--bottom.visible .word:nth-child(3) { transition-delay: 340ms; }

/* "deserves" gets a brushstroke underline that draws from
   left to right when the line lands.  Painted via a CSS
   background-image gradient so the bar matches the text's
   actual run-width exactly — no inline-block measurement
   slop, no overflow on the right when the line wraps on
   narrow viewports.  Hardcoded teal-5 hex so the colour
   pops against the purple bg regardless of theme. */
.act7-emphasis {
  font-style: normal;
  background-image: linear-gradient(#7ADAE6, #7ADAE6);
  background-repeat: no-repeat;
  background-position: 0 calc(100% + 0.08em);
  background-size: 0% 4px;
  transition: background-size 900ms cubic-bezier(0.22, 1, 0.36, 1) 600ms;
  padding-bottom: 0.16em;
}

.act7-line--bottom.visible .act7-emphasis {
  background-size: 100% 4px;
}

/* Supporting line — sits as a soft echo under the headline.
   Reveals with the headline's bottom line for one cohesive
   beat. */
.act7-supporting {
  margin: 0;
  font-family: var(--font-base);
  font-size: var(--text-large);
  line-height: 1.55;
  color: rgba(255, 255, 255, 0.78);
  max-width: 520px;
  opacity: 0;
  transform: translateY(12px);
  transition:
    opacity 700ms cubic-bezier(0.22, 1, 0.36, 1) 200ms,
    transform 700ms cubic-bezier(0.22, 1, 0.36, 1) 200ms;
}

.act7-supporting.visible {
  opacity: 1;
  transform: translateY(0);
}

/* Action row — primary CTA + enterprise link.  Inline flex so
   the link sits next to the pill on wide viewports, drops
   below it on narrower. */
.act7-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: clamp(20px, 2.4vw, 32px);
  opacity: 0;
  transform: translateY(12px);
  transition:
    opacity 700ms cubic-bezier(0.22, 1, 0.36, 1),
    transform 700ms cubic-bezier(0.22, 1, 0.36, 1);
}

.act7-actions.visible {
  opacity: 1;
  transform: translateY(0);
}

.act7-secondary {
  font-family: var(--font-base);
  font-size: var(--text-large);
  font-weight: var(--font-weight-medium);
  color: rgba(255, 255, 255, 0.85);
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  transition: color 200ms ease, gap 200ms ease;
}

.act7-secondary:hover {
  color: #fff;
  gap: 10px;
}

.act7-secondary span {
  transition: transform 200ms cubic-bezier(0.22, 1, 0.36, 1);
}

.act7-secondary:hover span {
  transform: translateX(2px);
}

/* Agent install prompt — terminal-style block sitting as the
   third path.  Hugs its content so the chip doesn't stretch
   to the container width; dark scrim background on the purple
   surface so it reads as a terminal. */
.act7-agent-prompt {
  margin: 0;
  background: rgba(0, 0, 0, 0.35);
  border: 1px solid rgba(255, 255, 255, 0.15);
  border-radius: var(--radius, 6px);
  padding: clamp(16px, 1.8vw, 24px);
  width: fit-content;
  max-width: 100%;
  opacity: 0;
  transform: translateY(12px);
  transition:
    opacity 700ms cubic-bezier(0.22, 1, 0.36, 1) 120ms,
    transform 700ms cubic-bezier(0.22, 1, 0.36, 1) 120ms;
}

.act7-agent-prompt.visible {
  opacity: 1;
  transform: translateY(0);
}

.act7-agent-prompt figcaption {
  font-family: var(--font-base);
  font-size: var(--text-tiny);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  font-weight: var(--font-weight-medium);
  color: rgba(255, 255, 255, 0.55);
  margin-bottom: var(--space-300, 12px);
}

.act7-agent-prompt pre {
  margin: 0;
  white-space: pre-wrap;
  word-break: break-word;
}

.act7-agent-prompt code {
  font-family: var(--font-mono);
  font-size: var(--text-base);
  color: #fff;
  background: none;
  padding: 0;
}

/* O cursor extended to the agent prompt — closes the loop
   with Acts 5 and 6 where the cursor lived on similar boxes. */
[data-act="7"] .act7-agent-prompt {
  cursor: pointer;
}

html:not(.js) [data-act="7"] .act7-line--top,
html:not(.js) [data-act="7"] .act7-line--bottom .word,
html:not(.js) [data-act="7"] .act7-supporting,
html:not(.js) [data-act="7"] .act7-actions,
html:not(.js) [data-act="7"] .act7-agent-prompt {
  opacity: 1;
  transform: none;
}
html:not(.js) [data-act="7"] .act7-emphasis {
  background-size: 100% 4px;
}

@media (prefers-reduced-motion: reduce) {
  [data-act="7"] .act7-line--top,
  [data-act="7"] .act7-line--bottom .word,
  [data-act="7"] .act7-supporting,
  [data-act="7"] .act7-actions,
  [data-act="7"] .act7-agent-prompt {
    transition: none;
  }
  .act7-emphasis::after { transition: none; }
}

@media (max-width: 1199px) {
  .act7-stage { min-height: 0; }
  .act7-frame {
    position: static;
    height: auto;
    padding: var(--space-3400, 136px) 0;
    background: #7C4FE8;
  }
  .act7-curtain { clip-path: none; }
  .act7-line--top,
  .act7-line--bottom .word,
  .act7-supporting,
  .act7-actions,
  .act7-agent-prompt {
    opacity: 1;
    transform: none;
  }
  /* Mobile: drop the teal underline entirely. The scroll-reveal that
     draws it is off here, so forcing it on read as misaligned + out of
     step with desktop. Plain white emphasis word instead. */
  .act7-emphasis { background-image: none; padding-bottom: 0; }
}

@media (max-width: 768px) {
  .act7-frame {
    padding: var(--space-2400, 96px) 0;
  }
}

/* ──────────────────────────────────────────────────────────
   Site footer — sits on the body's dark background.  No
   surface colour, no border around the section.  Brand block
   on the left + 5 columns of links on the right.  Copyright
   row at the bottom separated by a thin border-subtle line.
─────────────────────────────────────────────────────────── */

/* .site-footer and .footer-* rules — see ../_shared/chrome.css */

/* ──────────────────────────────────────────────────────────
   Acts 8+ — skeleton placeholder
   Default browser flow.  Each act gets a min vertical rhythm
   so the page is at least readable while we design each one.
   These styles will be replaced as we design each act in
   turn; consider this a holding pattern, not a baseline.
─────────────────────────────────────────────────────────── */

main > section:not([data-act="1"]):not([data-act="2"]):not([data-act="3"]):not([data-act="4"]):not([data-act="5"]):not([data-act="6"]):not([data-act="7"]):not(.hero-marquee) {
  max-width: 720px;
  margin: 0 auto;
  padding: 96px clamp(20px, 4vw, 48px);
  border-top: 1px dashed var(--border-subtle, rgba(0,0,0,0.08));
  color: var(--text-secondary);
}

main > section:not([data-act="1"]):not([data-act="2"]):not([data-act="3"]):not([data-act="4"]):not([data-act="5"]):not([data-act="6"]):not([data-act="7"]) ul {
  padding-left: 20px;
}

main > section:not([data-act="1"]):not([data-act="2"]):not([data-act="3"]):not([data-act="4"]):not([data-act="5"]):not([data-act="6"]):not([data-act="7"]) figure {
  margin: 16px 0;
}

main > section:not([data-act="1"]):not([data-act="2"]):not([data-act="3"]):not([data-act="4"]):not([data-act="5"]):not([data-act="6"]):not([data-act="7"]) pre {
  margin: 0;
  padding: 12px 14px;
  background: var(--card, #fff);
  border: 1px solid var(--border-subtle, rgba(0,0,0,0.08));
  border-radius: 10px;
  font-family: var(--font-mono, "IBM Plex Mono", monospace);
  font-size: var(--text-small, 13px);
  overflow-x: auto;
}

main > section:not([data-act="1"]):not([data-act="2"]):not([data-act="3"]):not([data-act="4"]):not([data-act="5"]):not([data-act="6"]):not([data-act="7"]) figcaption {
  font-size: var(--text-tiny, 11px);
  color: var(--text-tertiary);
  margin-bottom: 6px;
}

main > section:not([data-act="1"]):not([data-act="2"]):not([data-act="3"]):not([data-act="4"]):not([data-act="5"]):not([data-act="6"]):not([data-act="7"]):not(.hero-marquee)::before {
  content: "Act " attr(data-act) " · placeholder";
  display: block;
  font-size: var(--text-tiny, 11px);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-quaternary, var(--text-tertiary));
  margin-bottom: 16px;
}

/* ──────────────────────────────────────────────────────────
   Small-screen tuning
   The 1199px breakpoint switches the desktop scroll-linked
   layouts to vertical stacks.  These tighter queries fix
   chrome and rhythm that suffer below the standard "tablet"
   range — paddings, topbar density, closer beat spacing.
─────────────────────────────────────────────────────────── */

/* Tablet + mobile — tighten the marquee paddings so the
   landmark spacing doesn't dominate the read on shorter
   viewports. */
@media (max-width: 768px) {
  [data-act="2"] .act-closer {
    padding: var(--space-2400, 96px) clamp(20px, 4vw, 48px);
    max-width: none;
  }
  .act3-frame {
    padding: var(--space-2400, 96px) 0;
  }
}

/* Phone — narrow-viewport tweaks for home act layouts.
   Chrome's own phone breakpoint lives in ../_shared/chrome.css. */
@media (max-width: 479px) {
  [data-act="1"] {
    padding: 0 16px;
  }
  [data-act="2"] .act-closer {
    padding: var(--space-2400, 96px) 16px;
  }
  .act2-card {
    aspect-ratio: 6 / 5;
  }
}

/* ── Audience switcher — fixed vertical handle, right edge ──
   Tech (default) keeps the existing dark theme intact.  BU swaps
   to light theme + business-flavoured content.  Choice persists
   via localStorage.

   The hero's `.hero-content` is a flex column with gap between
   H1 / CTA-row / sub / note — wrapping these in a single audience
   div would collapse the gap (one flex child instead of four).
   So inside the hero ONLY, the wrappers get display:contents so
   they're transparent to the flex layout.  Elsewhere on the page,
   data-audience-* attributes live directly on structural elements
   (H2, .act2-track, .act6-group, etc.) where display:contents
   would destroy their own layout — so we DO NOT apply it globally.
   The native [hidden] (display:none) handles show/hide on those. */
.hero-content > [data-audience-tech],
.hero-content > [data-audience-bu] {
  display: contents;
}
/* Author display rules on .act2-card / .act4-card / .destinations / .act6-group
   / .act7-agent-prompt etc. override the UA's [hidden] = display:none.
   Force-hide every audience-tagged element when hidden, regardless of
   its class-defined display value. */
[data-audience-tech][hidden],
[data-audience-bu][hidden] { display: none !important; }

.audience-switch {
  position: fixed;
  top: 50%;
  right: 16px;
  z-index: 250;
  /* Grid with equal rows so TECH (4 chars) and BUSINESS (8 chars)
     buttons get identical height even though their vertical text
     length differs.  Flex column would size each to its content,
     producing unequal heights that misalign the sliding indicator. */
  display: grid;
  grid-template-rows: 1fr 1fr;
  border: 1px solid var(--border-subtle);
  border-radius: 999px;
  overflow: hidden;
  background: color-mix(in srgb, var(--background) 88%, transparent);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  will-change: transform;
  transform: translateY(-50%) translateZ(0);
  backface-visibility: hidden;
}
/* Sliding indicator — a purple pill that animates between the two
   segments instead of crossfading.  Sits behind both buttons (z-0)
   so the labels (z-1) read on top.  Position is driven by
   body[data-audience] so a click triggers the slide via CSS only. */
.audience-switch::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  height: 50%;
  background: #7C4FE8;
  z-index: 0;
  transition: transform 320ms cubic-bezier(0.22, 1, 0.36, 1);
  pointer-events: none;
}
body[data-audience="bu"] .audience-switch::before {
  transform: translateY(100%);
}
.audience-switch-btn {
  position: relative;
  z-index: 1;
  appearance: none;
  -webkit-appearance: none;
  background: transparent;
  border: 0;
  padding: 16px 11px;
  cursor: pointer;
  font-family: var(--font-code, var(--font-mono), "IBM Plex Mono", monospace);
  font-size: 9.5px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--text-tertiary);
  transition: color 220ms cubic-bezier(0.22, 1, 0.36, 1);
  writing-mode: vertical-rl;
}
.audience-switch-btn[aria-pressed="true"] {
  color: #FFFFFF;
}
.audience-switch-btn:hover:not([aria-pressed="true"]) {
  color: var(--text-secondary);
}
/* Mobile — switch to a compact horizontal pill anchored bottom-right
   (FAB-style).  The vertical right-edge placement overlaps the hero
   text on narrow viewports.  Indicator now slides horizontally
   between segments. */
@media (max-width: 720px) {
  .audience-switch {
    top: auto;
    bottom: 20px;
    right: 16px;
    transform: translateZ(0);
    grid-template-rows: none;
    grid-template-columns: 1fr 1fr;
  }
  .audience-switch::before {
    top: 0;
    left: 0;
    right: auto;
    bottom: auto;
    width: 50%;
    height: 100%;
  }
  body[data-audience="bu"] .audience-switch::before {
    transform: translateX(100%);
  }
  .audience-switch-btn {
    writing-mode: horizontal-tb;
    padding: 10px 18px;
    font-size: 10px;
  }
}

/* ── BU mode: light theme overrides ──
   Triggered by html NOT having .dark (JS toggles).  Brand purple
   #7C4FE8 and brand teal #7ADAE6 stay untouched.  Body bg uses
   --sunken-base (a touch recessed below default --background) so
   the surface feels distinct from a plain white page.

   Overrides below are because the home + chrome were authored with
   hardcoded #fff / rgba(255,...) instead of semantic tokens.  This
   block is the BU-light patch surface; long-term these should be
   converted to var(--text-primary) etc. at the source. */
html:not(.dark) body {
  background: var(--zen-gray-3);
  color: var(--text-primary);
}

/* Particle color is declared on <html> so getComputedStyle on
   documentElement picks it up (custom props don't bubble up from
   <body>).  Brand teal in dark; brand purple in light. */
html.dark { --portal-particle-color: #7ADAE6; }
html:not(.dark) { --portal-particle-color: #7C4FE8; }

/* Topbar (canonical inversion selectors per docs/patterns.md → body-is-on-light).
   These only apply when the topbar slot sits on the light page bg,
   i.e. body is NOT on a saturated brand band (purple / teal / mesh).
   When a band covers the topbar slot, the existing body.is-on-purple
   etc. rules (on dark) keep working — navbar stays white on band. */

/* Mq12 display face on every heading in BU.  Rawest is the
   tech brand display face; Mq12 signals BU as a different
   surface — type-shock that reinforces the audience switch.
   Force font-weight: 500 (Mq12 ships Medium-only) so the browser
   doesn't faux-bold an already-grotesque face. */
html:not(.dark) [data-act="1"] h1,
html:not(.dark) .act-frame-line,
html:not(.dark) .act3-headline,
html:not(.dark) .act4-headline,
html:not(.dark) .act5-frame,
html:not(.dark) .act5-claim-headline,
html:not(.dark) .act6-frame,
html:not(.dark) .act7-headline,
html:not(.dark) .audience-headline {
  font-family: 'Mq12', system-ui, sans-serif;
  font-weight: 500;
}

/* Hero (Act 1) copy */
html:not(.dark) [data-act="1"] h1 { color: var(--text-primary); }
html:not(.dark) .hero-mark { color: inherit; }
html:not(.dark) .hero-mark.painted,
html:not(.dark) html:not(.js) .hero-mark[data-mark="1"] { color: #7C4FE8; }
html:not(.dark) .hero-subhead { color: var(--text-secondary); }
html:not(.dark) .hero-cta-note { color: var(--text-tertiary); }
html:not(.dark) .scroll-cue { color: var(--text-tertiary); }

/* Audience switcher — in BU light the default
   color-mix(--background 88%, transparent) renders near-white
   against the sunken-grey page bg, so the handle barely reads.
   Use a solid contrasted fill + stronger border so the control
   stands out at the page edge. */
html:not(.dark) .audience-switch {
  background: var(--zen-gray-1);
  border-color: var(--border-default);
  box-shadow: 0 12px 28px -12px rgba(29, 22, 18, 0.18);
}

/* Proof-roll: drop the page-bg fill + heavy shadow in BU so the
   credits ladder reads as a transparent column, not a card stuck
   on top of the page. */
html:not(.dark) .proof-roll {
  background: transparent;
  box-shadow: none;
}

/* Canonical body.is-on-purple inversion — fires while act 3 or
   act 7's purple curtain covers the 72px topbar slot.  Mirror of
   workflows/competitor-intel/styles.css → body-is-on-purple
   block.  Applies in BOTH tech and BU modes because the band
   itself is brand-coloured (theme-invariant). */
/* No .btn-marketing override here on purpose — chrome.css already
   ships #7ADAE6 hardcoded as the canonical brand teal pill.  An
   override with var(--zen-teal-5) would flip to dark teal in dark
   mode and make the button unreadable on the purple band. */

/* Nav popover (.nav-dropdown + .nav-mega-*) — light variant.
   The widget half stays untouched: its bg is brand-colored
   (widget-gradient-header mesh), theme-invariant, white text on
   brand stays correct.  Only the LEFT list pane + the dropdown
   chrome (bg, border, shadow, group label) flip. */
html:not(.dark) .nav-dropdown {
  background: #FFFFFF;
  border-color: var(--border-subtle);
  box-shadow: 0 28px 56px -20px rgba(29, 22, 18, 0.18);
}
html:not(.dark) .nav-mega-item > a { color: var(--text-secondary); }
html:not(.dark) .nav-mega-item > a:hover,
html:not(.dark) .nav-mega-item > a:focus-visible {
  background: var(--sunken-hover);
  color: var(--text-primary);
}
html:not(.dark) .nav-mega-item.is-active > a {
  background: var(--sunken-active);
  color: var(--text-primary);
}
html:not(.dark) .nav-mega-item > span { color: var(--text-tertiary); }
html:not(.dark) .nav-mega-item.is-active > span {
  background: var(--sunken-active);
  color: var(--text-secondary);
}
html:not(.dark) .nav-mega-group-label { color: var(--text-tertiary); }
/* Headline preview container (e.g. Products overview): default
   chrome uses rgba(255,255,255,0.025) bg + rgba(255,255,255,0.08)
   border — both invisible on white popover.  Override with subtle
   warm-grey fill + dark hairline so the container reads.
   Per-competitor variants ([data-key="<x>-alternative"]) ship their
   own brand-mesh bg + colored border with the same specificity, so
   if/when Compare popover supports BU we'll need explicit allow-list. */
html:not(.dark) .nav-mega-headline {
  background-color: #1D1612;
  border-color: rgba(29, 22, 18, 0.18);
}

/* Preview pane right-side: title / path / description.  These sit
   on the dropdown's light bg (not on the widget mesh), so they
   need to flip from white to dark.  The widget content INSIDE
   the green mesh half stays white because the mesh is brand-colored. */
html:not(.dark) .nav-mega-preview-desc { color: var(--text-secondary); }

/* Act 5 code-block hover gradient: in tech the subtle teal/purple
   tab marker reads quiet on warm dark.  In BU the same gradient
   pops aggressively against the near-white card.  Suppress in BU
   so the cards stay calm; the border + content carry the eye. */
html:not(.dark) .act5-claim-visual .code-block:hover::before { opacity: 0; }

/* Trusted-by bar */
html:not(.dark) .trust-bar-label { color: var(--text-tertiary); }
html:not(.dark) .trust-bar-logos li { color: var(--text-secondary); }
html:not(.dark) .trust-bar-logos li:hover { color: var(--text-primary); }

/* ── Trusted-by marquee (under the hero) ───────────────────────────────────
   Favicon logos on a seamless infinite scroll. The track holds the set twice;
   translateX(-50%) moves exactly one set, so it loops with no seam. Edges
   fade via a mask. Pauses on hover; static (wrapped) under reduced-motion. */
.hero-marquee {
  padding: clamp(44px, 8vh, 96px) 0;
  background: var(--background);
}
.hmq-label {
  text-align: center; margin: 0 0 clamp(28px, 4vh, 48px);
  font-family: var(--font-code); font-size: 12px; letter-spacing: 0.16em;
  text-transform: uppercase; color: var(--text-quaternary);
}
.hmq-viewport {
  overflow: hidden;
  -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 8%, #000 92%, transparent 100%);
  mask-image: linear-gradient(90deg, transparent 0, #000 8%, #000 92%, transparent 100%);
}
.hmq-track {
  display: flex; align-items: center; width: max-content;
  gap: clamp(44px, 6vw, 96px); margin: 0; padding: 0 clamp(22px, 3vw, 48px);
  list-style: none;
  animation: hmq-scroll 42s linear infinite;
  will-change: transform;
}
.hero-marquee:hover .hmq-track { animation-play-state: paused; }
.hmq-item { display: inline-flex; align-items: center; gap: 16px; flex: none; }
.hmq-item img { width: 40px; height: 40px; border-radius: 9px; object-fit: contain; opacity: 0.95; }
.hmq-item span {
  font-family: var(--font-display); font-weight: 500;
  font-size: var(--mkt-h3); letter-spacing: -0.02em;
  color: var(--text-secondary); white-space: nowrap;
}
@keyframes hmq-scroll { from { transform: translateX(0); } to { transform: translateX(-50%); } }
@media (prefers-reduced-motion: reduce) {
  .hmq-track { animation: none; flex-wrap: wrap; justify-content: center; row-gap: 24px; width: auto; }
  .hmq-item[aria-hidden="true"] { display: none; }
}
@media (min-width: 1600px) { .hmq-item span { font-size: 48px; } .hmq-item img { width: 46px; height: 46px; } }
