/* ── /products/browser-sessions — Drive a real browser ────
   Spoke page, built act-by-act.

   This page intentionally diverges from /products/fetch in
   composition:
   - Hero is asymmetric (40/60 split) instead of centered
     try-widget — left carries editorial copy, right carries
     a live Chromium mock.  Reads as "code on one side, the
     browser it drives on the other" from the first frame.

   Universal chrome + DS tokens inherited from _shared and _ds.
   ──────────────────────────────────────────────────────────── */

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

html, body {
  background: var(--background);
}

body {
  position: relative;
  min-height: 100vh;
}

main {
  position: relative;
  z-index: 1;
}

/* ── Act 1 — Asymmetric hero ─────────────────────────────── */

[data-act="1"] {
  min-height: calc(100vh - 60px);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: clamp(96px, 14vh, 160px) clamp(24px, 5vw, 64px) clamp(56px, 9vh, 120px);
}

.sb-hero {
  width: 100%;
  max-width: 1320px;
  display: grid;
  grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.15fr);
  column-gap: clamp(36px, 5vw, 80px);
  align-items: center;
}

.sb-hero-text {
  display: flex;
  flex-direction: column;
  gap: clamp(18px, 2.4vw, 28px);
}


.sb-hero-h1 {
  margin: 0;
  font-family: var(--font-display, var(--font-base));
  font-size: var(--mkt-hero-xl);
  line-height: 1;
  letter-spacing: -0.025em;
  color: var(--text-primary);
  font-weight: 500;
}

.sb-hero-sub {
  margin: 0;
  max-width: 460px;
  font-family: var(--font-base);
  font-size: var(--mkt-body);
  line-height: 1.55;
  color: var(--text-secondary);
}

/* Layout comes from .cta-stack (column) in components.css.
   .sb-hero-note is intentionally NOT the canonical mono
   cta-stack-tag — it uses base sans @ 13px to read as a
   browser-minutes footnote rather than a credits tagline. */
.sb-hero-cta {
  margin-top: clamp(4px, 1vh, 12px);
}

.sb-hero-note {
  font-family: var(--font-base);
  font-size: var(--text-small, 13px);
  color: var(--text-tertiary);
}

/* ── Hero browser anchor ─────────────────────────────────────
   Invisible placeholder.  Takes the same space the inline mock
   used to take so the hero grid layout stays identical; the
   actual visual lives in the page-level .sb-frame, positioned
   over this anchor's bounding rect in hero mode. */
.sb-hero-browser-anchor {
  width: 100%;
  max-width: 720px;
  margin-left: auto;
  aspect-ratio: 16 / 11;
  visibility: hidden;
}

/* Inline mock kept for the bar/URL/canvas leaf-styles below
   — the .sb-frame element reuses these class names so the
   page-level frame's bar matches the design that lived inline
   in earlier iterations.  No .sb-hero-browser block any more. */

.sb-browser-bar {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: clamp(12px, 1vw, 16px) clamp(14px, 1.2vw, 20px);
  background: transparent;
  border: 1px solid var(--border-subtle);
}

.sb-browser-dot {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--zen-gray-5);
  flex-shrink: 0;
}

.sb-browser-url {
  margin-left: clamp(8px, 1vw, 14px);
  flex: 1 1 auto;
  min-width: 0;
  padding: 6px clamp(10px, 1vw, 14px);
  border-radius: var(--radius-full, 999px);
  background: var(--background);
  font-family: var(--font-code, monospace);
  font-size: var(--mkt-body-xs);
  line-height: 1;
  color: var(--text-secondary);
  display: flex;
  align-items: center;
  gap: 0;
  overflow: hidden;
  white-space: nowrap;
}

.sb-browser-url-protocol { color: var(--text-quaternary); }
.sb-browser-url-host {
  color: var(--text-primary);
  font-weight: 500;
}
.sb-browser-url-path { color: var(--text-tertiary); }

/* Blinking cursor at the end of the URL — signals "your
   connection lands here".  Steps animation for the hard
   blink (no fade in / out) so it reads like a real terminal
   caret, not a fade pulse. */
.sb-browser-url-cursor {
  display: inline-block;
  width: 2px;
  height: 12px;
  margin-left: 4px;
  background: var(--text-primary);
  animation: sb-cursor-blink 1.1s steps(1) infinite;
}

@keyframes sb-cursor-blink {
  0%, 50% { opacity: 1; }
  50.01%, 100% { opacity: 0; }
}

.sb-browser-canvas {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  padding: clamp(20px, 2vw, 32px);
  background: var(--background);
  border-top: 1px solid var(--border-subtle);
}

.sb-browser-content {
  display: flex;
  flex-direction: column;
  gap: clamp(10px, 1.2vh, 14px);
  flex: 1 1 auto;
}

.sb-browser-line {
  height: 8px;
  border-radius: 4px;
  background: var(--zen-gray-4);
}

.sb-browser-line--xl { width: 88%; height: 14px; background: var(--zen-gray-5); }
.sb-browser-line--lg { width: 72%; }
.sb-browser-line--md { width: 56%; }
.sb-browser-line--sm { width: 38%; }

.sb-browser-block {
  height: clamp(60px, 8vh, 96px);
  border-radius: 8px;
  background: linear-gradient(135deg, var(--zen-purple-5), var(--zen-purple-7));
  opacity: 0.55;
  margin: 2px 0;
}

/* ── Act 2 — Thesis ("Your code is the driver") ─────────────
   First act under the sticky browser frame.  Section pinning
   pattern (z-index 2, page-bg) so it rises over Act 1 as the
   reader scrolls down. */

[data-act="2"] {
  position: relative;
  z-index: 2;
  background: var(--background);
  min-height: 100vh;
  /* Padding clears the sticky frame (topbar 72px + frame bar
     ~48px + breathing) and respects the frame's interior
     horizontal padding (24px mounted side + breathing). */
  padding: clamp(180px, 24vh, 240px) clamp(56px, 7vw, 96px) clamp(80px, 12vh, 120px);
}

.sb-act {
  width: 100%;
  max-width: 1280px;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: clamp(40px, 6vh, 64px);
}

.sb-act-header {
  display: flex;
  flex-direction: column;
  gap: 14px;
  max-width: 760px;
}

.sb-act-h2 {
  margin: 0;
  font-family: var(--font-display, var(--font-base));
  font-size: var(--mkt-h2);
  line-height: 1.02;
  letter-spacing: -0.024em;
  color: var(--text-primary);
  font-weight: 500;
}

.sb-act-sub {
  margin: 0;
  font-family: var(--font-base);
  font-size: var(--mkt-body);
  line-height: 1.55;
  color: var(--text-secondary);
  max-width: 620px;
}

/* Two-column split — code on the left (dominant), short
   editorial aside on the right.  Asymmetric on purpose: the
   code is the proof, the aside just frames why it matters. */
.sb-act-split {
  display: grid;
  grid-template-columns: minmax(0, 1.5fr) minmax(220px, 0.6fr);
  column-gap: clamp(40px, 5vw, 80px);
  align-items: start;
}

/* Code wrap — single grid cell where the two framework
   snippets stack via grid-area: 1 / 1.  Only the active one
   is opaque; the other crossfades behind it. */
.sb-act-code-wrap {
  display: grid;
  /* Grid items default to min-width:auto (min-content). The pre code
     inside forces that to the longest line, blowing the column past the
     viewport on phones. Let it shrink to the track; the code scrolls
     inside .sb-act-code. */
  min-width: 0;
}

.sb-act-code {
  grid-area: 1 / 1;
  margin: 0;
  padding: clamp(24px, 2.4vw, 36px);
  border-radius: var(--radius-display, 20px);
  background: var(--background);
  box-shadow: 0 28px 64px -20px rgba(0, 0, 0, 0.55);
  font-family: var(--font-code, monospace);
  font-size: var(--mkt-body-sm);
  line-height: 1.65;
  color: var(--text-secondary);
  white-space: pre;
  min-width: 0;
  overflow-x: auto;
  scrollbar-width: none;
  opacity: 0;
  pointer-events: none;
  transition: opacity 240ms cubic-bezier(0.22, 1, 0.36, 1);
}
.sb-act-code::-webkit-scrollbar { display: none; }

.sb-act-code[data-active="true"] {
  opacity: 1;
  pointer-events: auto;
}

.sb-act-code code {
  font-family: inherit;
  color: inherit;
}

.sb-code-kw  { color: var(--zen-purple-7); }
.sb-code-str { color: var(--zen-yellow-6); }

/* Framework segmented control — same DNA as the /products/fetch
   hero widget's chips.  Lives at the bottom of the right
   aside; clicking a segment crossfades the code panel on the
   left between Playwright and Puppeteer. */
.sb-act-segmented {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 4px;
  background: var(--background);
  border: 1px solid var(--border);
  border-radius: var(--radius-full, 999px);
  align-self: flex-start;
  margin-top: clamp(8px, 1vh, 14px);
}

.sb-act-seg {
  appearance: none;
  border: none;
  background: transparent;
  color: var(--text-secondary);
  font-family: var(--font-base);
  font-size: var(--text-small, 13px);
  font-weight: 500;
  letter-spacing: -0.005em;
  padding: 6px 14px 6px 10px;
  border-radius: var(--radius-full, 999px);
  cursor: pointer;
  white-space: nowrap;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  transition:
    background 180ms cubic-bezier(0.22, 1, 0.36, 1),
    color 180ms cubic-bezier(0.22, 1, 0.36, 1);
}

.sb-act-seg:hover {
  color: var(--text-primary);
  background: var(--sunken-hover);
}

.sb-act-seg[data-active="true"] {
  background: var(--text-primary);
  color: var(--background);
}

.sb-act-seg-favicon {
  display: block;
  width: 16px;
  height: 16px;
  min-width: 16px;
  min-height: 16px;
  border-radius: 50%;
  background: var(--text-primary);
  object-fit: cover;
  flex-shrink: 0;
}

.sb-act-aside {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding-top: clamp(16px, 2vh, 24px);
}

.sb-act-aside-tag {
  margin: 0;
  font-family: var(--font-base);
  font-size: var(--text-tiny, 11px);
  font-weight: 500;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--text-tertiary);
}

.sb-act-aside-text {
  margin: 0;
  font-family: var(--font-base);
  font-size: var(--mkt-body);
  line-height: 1.55;
  color: var(--text-secondary);
}

/* ── Page-level browser frame ──────────────────────────────
   Always `position: fixed`.  browser.js writes four CSS vars
   (--frame-top, --frame-left, --frame-width, --frame-height)
   each scroll frame, interpolating between two states:

   - Hero mode (data-mode="hero"): top/left/width/height match
     the .sb-hero-browser-anchor's bounding rect.  The frame
     visually IS the hero browser, scrolling with the page as
     the anchor scrolls.
   - Mounted mode (data-mode="mounted"): top sits below the
     real topbar (~72px), left/right padded from viewport
     edges, height extends past viewport bottom.  Top corners
     rounded.  Acts 2+ content (with matching horizontal
     padding) sits visually inside the frame.

   Z-index 50 — below the topbar (90) so the topbar always
   floats above; above static content so the frame's side
   chrome and top bar overlay the page edges. */
.sb-frame {
  position: fixed;
  top: var(--frame-top, 0px);
  left: var(--frame-left, 0px);
  width: var(--frame-width, 100vw);
  height: var(--frame-height, 100vh);
  z-index: 50;
  /* Default = hero state: all four corners rounded (small
     contained widget feel).  Mounted state overrides to
     top-only since the bottom extends past the viewport. */
  border-radius: 20px;
  background: var(--background);
  box-shadow: 0 28px 64px -20px rgba(0, 0, 0, 0.55);
  overflow: hidden;
  display: flex;
  flex-direction: column;
  pointer-events: none;
  transition: background 320ms cubic-bezier(0.22, 1, 0.36, 1),
              border-radius 320ms cubic-bezier(0.22, 1, 0.36, 1);
  /* No CSS transition on the size vars — JS interpolates per
     frame so the morph reads as scroll-driven, not state-toggle. */
}

.sb-frame[data-mode="mounted"] {
  border-radius: 20px 20px 0 0;
}

.sb-frame[data-mode="mounted"] {
  background: transparent;
  /* Visible side rules so the frame's edges read as a real
     browser outline against the Acts 2+ content showing
     through the transparent middle.  Top edges follow the
     border-radius automatically. */
  border-left: 1px solid var(--border-subtle);
  border-right: 1px solid var(--border-subtle);
  border-top: 1px solid var(--border-subtle);
  /* Tabs need clicks; rest of the frame area (transparent
     canvas) passes clicks through to the Acts content below. */
  pointer-events: none;
}
.sb-frame[data-mode="mounted"] .sb-frame-bar { pointer-events: auto; }

/* ── Side masks ────────────────────────────────────────────
   Two fixed strips at the viewport's left + right edges that
   tap-cover Acts 2+ content scroll-bleeding past the frame's
   interior in mounted mode.  Z-index between frame (50) and
   topbar (90) so they cover content but stay below chrome.
   Their width matches the mounted frame's side padding;
   opacity ramps with the morph progress (--frame-progress is
   set on body by the JS). */
.sb-frame-mask {
  position: fixed;
  background: var(--background);
  z-index: 55;
  pointer-events: none;
  opacity: var(--frame-progress, 0);
  transition: opacity 160ms linear;
}

.sb-frame-mask--left,
.sb-frame-mask--right {
  top: 0;
  bottom: 0;
  width: var(--frame-mask-width, 24px);
}
.sb-frame-mask--left  { left: 0; }
.sb-frame-mask--right { right: 0; }

/* Top mask covers the topbar slot — the real topbar is
   transparent, and without this the Acts 2+ content scrolls
   visible behind the nav-link / logo / pills. */
.sb-frame-mask--top {
  top: 0;
  left: 0;
  right: 0;
  height: 72px;
}

/* Corner masks — the frame's top corners are rounded (border-
   radius 20px), but the side + top rectangular masks don't
   follow that curve.  At each top corner there's a tiny
   L-wedge between (a) the rectangular mask edges, (b) the
   frame's outer bbox, and (c) the frame's curved interior —
   that's where Acts 2+ content scroll-bleeds through.

   Each corner mask is a 20×20 page-bg box sitting at the
   frame's outer corner.  border-radius rounds the corner
   that points INTO the frame interior with a 100% radius
   (full quarter-circle cut), so the painted area is exactly
   the L-wedge outside the curve.  Top-left mask cuts its
   bottom-right corner; top-right mask cuts its bottom-left. */
.sb-frame-mask--corner-tl,
.sb-frame-mask--corner-tr {
  width: 20px;
  height: 20px;
  top: var(--frame-top, 0px);
}
.sb-frame-mask--corner-tl {
  left: var(--frame-left, 0px);
  border-radius: 0 0 100% 0;
}
.sb-frame-mask--corner-tr {
  /* Place the right edge of the 20px box at the frame's right
     edge: frame-left + frame-width is the frame's right edge;
     subtract 20px to get the corner box's left edge. */
  left: calc(var(--frame-left, 0px) + var(--frame-width, 100vw) - 20px);
  border-radius: 0 0 0 100%;
}

@media (max-width: 900px) {
  .sb-frame-mask--left,
  .sb-frame-mask--right {
    width: var(--frame-mask-width, 8px);
  }
}

.sb-frame-bar {
  flex-shrink: 0;
  display: flex;
  align-items: flex-end;
  gap: clamp(12px, 1.4vw, 22px);
  padding: 10px clamp(14px, 1.6vw, 22px) 0;
  /* Opaque chrome — this is the simulated browser's tab strip, not an
     in-act content widget, so it takes the frame's page-bg fill (same
     as .sb-frame hero + .sb-frame-mask), NOT var(--card).  In mounted
     mode .sb-frame goes transparent so Acts show through the canvas;
     without an opaque bar the hero behind bleeds through the tabs. */
  background: var(--background);
  border-bottom: 1px solid var(--border-subtle);
  height: 52px;
}

/* Dots + URL pill (hero mode) sit at vertical centre.  Tabs
   (mounted mode) sit flush with the bar's bottom so the
   active tab can merge with the canvas. */
.sb-frame-dots,
.sb-frame-mode--hero {
  align-self: center;
}

.sb-frame-mode--mounted {
  align-self: flex-end;
  align-items: flex-end;
}

/* URL bar in mounted mode sits at vertical centre of the
   bar — gives the chrome two horizontal "lanes" (tabs at
   bottom, URL bar floating slightly above on the right). */
.sb-frame-mode--mounted .sb-frame-url {
  align-self: center;
  margin-bottom: 4px;
}

.sb-frame-dots {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  flex-shrink: 0;
}

.sb-frame-dot {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--zen-gray-5);
}

/* Bar mode slots — crossfade between hero content (URL pill
   with blinking cursor) and mounted content (tab row + URL
   bar) as the frame morphs.  Both slots share the same area
   via grid stacking so the crossfade reads as a swap. */
.sb-frame-mode {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: clamp(10px, 1.2vw, 18px);
  transition: opacity 240ms cubic-bezier(0.22, 1, 0.36, 1);
}

.sb-frame[data-mode="hero"]    .sb-frame-mode--hero    { opacity: 1; pointer-events: auto; }
.sb-frame[data-mode="hero"]    .sb-frame-mode--mounted { opacity: 0; pointer-events: none; display: none; }
.sb-frame[data-mode="mounted"] .sb-frame-mode--hero    { opacity: 0; pointer-events: none; display: none; }
.sb-frame[data-mode="mounted"] .sb-frame-mode--mounted { opacity: 1; pointer-events: auto; }

/* Hero-mode bar — URL pill matches the original inline mock. */
.sb-frame-url-pill {
  margin-left: clamp(8px, 1vw, 14px);
  flex: 1 1 auto;
  min-width: 0;
  padding: 6px clamp(10px, 1vw, 14px);
  border-radius: var(--radius-full, 999px);
  background: var(--background);
  font-family: var(--font-code, monospace);
  font-size: var(--mkt-body-xs);
  line-height: 1;
  color: var(--text-secondary);
  display: flex;
  align-items: center;
  overflow: hidden;
  white-space: nowrap;
}

/* Mounted-mode bar — tabs row on the left, URL on the right. */
.sb-frame-tabs {
  display: flex;
  align-items: flex-end;
  gap: 4px;
  flex: 1 1 auto;
  min-width: 0;
  overflow-x: auto;
  scrollbar-width: none;
}
.sb-frame-tabs::-webkit-scrollbar { display: none; }

/* Browser tab — bottom-anchored block in the bar with
   rounded top corners.  Vertical padding controls the tab
   height (consistent across tabs regardless of label
   length); the bar's align-items: flex-end pins them to the
   bottom edge so the active tab can cut into the canvas. */
.sb-frame-tab {
  appearance: none;
  border: none;
  background: var(--zen-gray-3);
  padding: 9px 18px;
  border-radius: 10px 10px 0 0;
  font-family: var(--font-base);
  font-size: var(--text-small, 13px);
  font-weight: 500;
  line-height: 1.2;
  color: var(--text-tertiary);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  max-width: 240px;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  position: relative;
  transition:
    color 180ms cubic-bezier(0.22, 1, 0.36, 1),
    background 180ms cubic-bezier(0.22, 1, 0.36, 1);
}

.sb-frame-tab:hover {
  color: var(--text-primary);
  background: var(--zen-gray-4);
}

.sb-frame-tab[data-active="true"] {
  color: var(--text-primary);
  background: var(--background);
  /* Cut into the bar's bottom border so the active tab reads
     as the foreground page — same bg as the canvas, edge
     fused with the page area below. */
  margin-bottom: -1px;
  border-bottom: 1px solid var(--background);
  z-index: 1;
}

.sb-frame-tab-name {
  overflow: hidden;
  text-overflow: ellipsis;
  letter-spacing: -0.005em;
  line-height: 1.2;
}

.sb-frame-url {
  flex-shrink: 0;
  padding: 6px 14px;
  border-radius: var(--radius-full, 999px);
  background: var(--background);
  font-family: var(--font-code, monospace);
  font-size: 11px;
  letter-spacing: 0.02em;
  color: var(--text-tertiary);
  max-width: 360px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Frame canvas — visible in hero mode (placeholder sketch),
   transparent in mounted mode so Acts 2+ content shows
   through. */
.sb-frame-canvas {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  padding: clamp(20px, 2vw, 32px);
  border-top: 1px solid var(--border-subtle);
  transition: opacity 320ms cubic-bezier(0.22, 1, 0.36, 1),
              background 320ms cubic-bezier(0.22, 1, 0.36, 1);
}

.sb-frame[data-mode="hero"]    .sb-frame-canvas { background: var(--background); opacity: 1; }
.sb-frame[data-mode="mounted"] .sb-frame-canvas { background: transparent; opacity: 0; pointer-events: none; }

.sb-frame-canvas-content {
  display: flex;
  flex-direction: column;
  gap: clamp(10px, 1.2vh, 14px);
  flex: 1 1 auto;
}

/* Inherit line / block styles already defined for the inline
   mock (.sb-browser-line, .sb-browser-block) — the frame's
   canvas reuses them. */

/* ── Mobile responsive — Act 2 + frame ────────────────────── */

@media (max-width: 900px) {
  /* Acts inner padding on mobile = frame side gutter + 20px of
     breathing room, so content visibly sits INSIDE the browser
     chrome's left + right borders instead of touching them.
     --frame-mask-width is set on body by the JS each frame
     (equals the mounted side pad: 16 at <=479, scaling up to
     36 at 900).  Children inherit through CSS variable
     inheritance. */
  [data-act="2"] {
    padding: 120px calc(var(--frame-mask-width, 16px) + 20px) 80px;
    min-height: auto;
  }
  .sb-act-split {
    grid-template-columns: 1fr;
    row-gap: 28px;
  }
  .sb-frame-url { display: none; }
  .sb-frame-bar {
    padding: 8px 14px 0;
    gap: 10px;
    height: 48px;
  }
  /* Tighten tabs on mobile so more fit before overflow-scroll
     kicks in.  Smaller padding + slightly smaller text reads as
     a mobile-chrome density without losing legibility. */
  .sb-frame-tab {
    padding: 8px 12px;
    font-size: 12px;
    max-width: 160px;
  }
  .sb-frame-tabs { gap: 2px; }
}

@media (max-width: 600px) {
  .sb-act-h2 { font-size: var(--mkt-h3); }
  .sb-act-code { font-size: 12px; padding: 20px; }
  .sb-frame-dots { display: none; }
  /* Even tighter tabs on phone — kill side padding on the bar
     since the dots are gone, give the tab row the whole bar. */
  .sb-frame-bar { padding: 8px 10px 0; gap: 8px; }
  .sb-frame-tab { padding: 7px 10px; font-size: 11px; }
}

/* ── Act 3 — "Six actions. Endless flows." ─────────────────
   No saturated band — the page lives inside the .sb-frame
   already, so the dark restraint reads as the browser's own
   canvas.  Sticky stage holds an editorial scene: list of
   six actions on the left, mock target page on the right
   with a cursor SVG that hops between elements per beat. */

[data-act="3"] {
  position: relative;
  z-index: 3;
  background: var(--background);
  padding: 0 clamp(56px, 7vw, 96px);
}

/* Intro header — sits at the top of the act, scrolls past
   naturally.  Clears the topbar + browser-frame chrome with
   generous top padding.  Max-width matches Act 2 (Thesis) +
   centred so all acts share the same horizontal alignment. */
.sb-actions-intro {
  max-width: 1280px;
  margin: 0 auto;
  padding: clamp(180px, 24vh, 240px) 0 clamp(48px, 8vh, 80px);
  display: flex;
  flex-direction: column;
  gap: 14px;
}

/* The actual H1 + sub still wrap to a comfortable reading
   measure inside the wider intro container. */
.sb-actions-intro .act-kicker,
.sb-actions-intro .sb-act-h2,
.sb-actions-intro .sb-act-sub {
  max-width: 760px;
}

.sb-actions-stage {
  position: relative;
  min-height: 540vh;
}

/* Sticky scene — holds only the spread (action list + mock).
   100vh tall.  Top padding clears the topbar (72px) + the
   browser frame's bar (~52px) + breathing so the first
   action row doesn't crowd the chrome. */
.sb-actions-frame {
  position: sticky;
  top: 0;
  height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: clamp(180px, 22vh, 240px) 0 clamp(56px, 9vh, 96px);
  overflow: hidden;
}

.sb-actions-spread {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.1fr);
  column-gap: clamp(40px, 5vw, 80px);
  align-items: center;
  width: 100%;
  max-width: 1280px;
  margin: 0 auto;
}

/* Action list — each row brightens + grows a left rule when
   active; inactive rows dim to text-quaternary. */
.sb-actions-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: clamp(14px, 1.8vh, 20px);
}

.sb-action {
  display: grid;
  grid-template-columns: auto 1fr;
  grid-template-areas:
    "num name"
    ".   desc";
  gap: 4px 14px;
  padding: 10px 0 10px 18px;
  position: relative;
  color: var(--text-quaternary);
  transition: color 260ms cubic-bezier(0.22, 1, 0.36, 1);
}

.sb-action::before {
  content: "";
  position: absolute;
  left: 0;
  top: 12px;
  bottom: 12px;
  width: 2px;
  background: currentColor;
  opacity: 0.4;
  transform: scaleY(0.4);
  transform-origin: top;
  transition: transform 320ms cubic-bezier(0.22, 1, 0.36, 1),
              opacity 240ms cubic-bezier(0.22, 1, 0.36, 1);
}

.sb-action[data-active="true"] {
  color: var(--text-primary);
}

.sb-action[data-active="true"]::before {
  transform: scaleY(1);
  opacity: 1;
}

.sb-action-num {
  grid-area: num;
  font-family: var(--font-code, monospace);
  font-size: 11px;
  letter-spacing: 0.18em;
  align-self: baseline;
  padding-top: 4px;
}

.sb-action-name {
  grid-area: name;
  font-family: var(--font-code, monospace);
  font-size: var(--mkt-h4);
  font-weight: 500;
  letter-spacing: -0.012em;
  color: inherit;
  line-height: 1.05;
}

.sb-action-desc {
  grid-area: desc;
  font-family: var(--font-base);
  font-size: var(--mkt-body-sm);
  line-height: 1.5;
  color: var(--text-tertiary);
  transition: color 260ms cubic-bezier(0.22, 1, 0.36, 1);
}

.sb-action[data-active="true"] .sb-action-desc {
  color: var(--text-secondary);
}

/* Mock target page — stylised login form, no browser chrome
   (the outer .sb-frame IS the chrome).  Cursor SVG sits on
   top, JS positions it per beat. */
.sb-actions-mock {
  position: relative;
  width: 100%;
  max-width: 480px;
  margin-left: auto;
  aspect-ratio: 4 / 3;
}

.sb-mock-page {
  position: relative;
  width: 100%;
  height: 100%;
  border-radius: 14px;
  background: transparent;
  border: 1px solid var(--border-subtle);
  padding: clamp(20px, 2vw, 28px);
  display: flex;
  flex-direction: column;
  gap: clamp(14px, 1.6vh, 18px);
  overflow: hidden;
}

.sb-mock-url {
  font-family: var(--font-code, monospace);
  font-size: 11px;
  letter-spacing: 0.04em;
  color: var(--text-tertiary);
  padding-bottom: 4px;
  border-bottom: 1px solid var(--border-subtle);
}

.sb-mock-body {
  position: relative;
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  gap: clamp(10px, 1.4vh, 14px);
}

.sb-mock-title {
  margin: 0;
  font-family: var(--font-display, var(--font-base));
  font-size: clamp(18px, 1.6vw, 22px);
  font-weight: 500;
  color: var(--text-primary);
}

.sb-mock-field {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.sb-mock-field-label {
  font-family: var(--font-base);
  font-size: 10px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-quaternary);
}

.sb-mock-field-input {
  padding: 8px 12px;
  border-radius: 6px;
  background: var(--background);
  border: 1px solid var(--border);
  font-family: var(--font-code, monospace);
  font-size: 12px;
  color: var(--text-tertiary);
  min-height: 32px;
  display: flex;
  align-items: center;
  transition: border-color 220ms cubic-bezier(0.22, 1, 0.36, 1);
}

.sb-mock-field-value {
  opacity: 0;
  transition: opacity 220ms cubic-bezier(0.22, 1, 0.36, 1);
}

.sb-mock-button {
  appearance: none;
  border: none;
  align-self: flex-start;
  margin-top: 4px;
  padding: 8px 18px;
  border-radius: var(--radius-full, 999px);
  background: var(--text-primary);
  color: var(--background);
  font-family: var(--font-base);
  font-size: 12px;
  font-weight: 500;
  cursor: pointer;
  transition: transform 180ms cubic-bezier(0.34, 1.56, 0.64, 1);
}

/* Success state — appears after submit (beat 4 onwards). */
.sb-mock-success {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 8px;
  background: transparent;
  border: 1px solid var(--border-subtle);
  opacity: 0;
  pointer-events: none;
  transition: opacity 320ms cubic-bezier(0.22, 1, 0.36, 1);
}

.sb-mock-success-icon {
  width: 36px;
  height: 36px;
  border-radius: 50%;
  background: var(--zen-green-6);
  /* Hardcoded white — bg is brand green (theme-invariant);
     var(--background) flips to light in BU, washing the check. */
  color: #FFFFFF;
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 700;
}

.sb-mock-success-text {
  font-family: var(--font-display, var(--font-base));
  font-size: var(--mkt-lead);
  font-weight: 500;
  color: var(--text-primary);
}

/* Screenshot shutter — full-page flash overlay for the
   capture beat (5).  Pulses once when active. */
.sb-mock-shutter {
  position: absolute;
  inset: 0;
  background: var(--text-primary);
  opacity: 0;
  pointer-events: none;
  transition: opacity 80ms linear;
}

/* Per-beat states.  data-action on the section drives the
   mock's data-state which cascades to the elements below. */
[data-act="3"][data-action="1"] .sb-mock-field[data-field="email"] .sb-mock-field-input,
[data-act="3"][data-action="2"] .sb-mock-field[data-field="email"] .sb-mock-field-input,
[data-act="3"][data-action="3"] .sb-mock-field[data-field="email"] .sb-mock-field-input,
[data-act="3"][data-action="4"] .sb-mock-field[data-field="email"] .sb-mock-field-input,
[data-act="3"][data-action="5"] .sb-mock-field[data-field="email"] .sb-mock-field-input {
  border-color: var(--border-accent);
}

[data-act="3"][data-action="1"] .sb-mock-field[data-field="email"] .sb-mock-field-value,
[data-act="3"][data-action="2"] .sb-mock-field .sb-mock-field-value,
[data-act="3"][data-action="3"] .sb-mock-field .sb-mock-field-value,
[data-act="3"][data-action="4"] .sb-mock-field .sb-mock-field-value,
[data-act="3"][data-action="5"] .sb-mock-field .sb-mock-field-value {
  opacity: 1;
}

[data-act="3"][data-action="2"] .sb-mock-field[data-field="password"] .sb-mock-field-input,
[data-act="3"][data-action="3"] .sb-mock-field[data-field="password"] .sb-mock-field-input,
[data-act="3"][data-action="4"] .sb-mock-field[data-field="password"] .sb-mock-field-input,
[data-act="3"][data-action="5"] .sb-mock-field[data-field="password"] .sb-mock-field-input {
  border-color: var(--border-accent);
}

[data-act="3"][data-action="3"] .sb-mock-button,
[data-act="3"][data-action="4"] .sb-mock-button,
[data-act="3"][data-action="5"] .sb-mock-button {
  transform: scale(0.96);
  background: #7C4FE8;
  /* Hardcoded white — bg is brand purple #7C4FE8 (theme-invariant);
     var(--text-primary) flips to dark in BU and disappears on purple. */
  color: #FFFFFF;
}

[data-act="3"][data-action="4"] .sb-mock-success,
[data-act="3"][data-action="5"] .sb-mock-success {
  opacity: 1;
}

[data-act="3"][data-action="4"] .sb-mock-shutter {
  opacity: 0.8;
}

/* Cursor SVG.  Position set by JS via --cursor-x / --cursor-y
   CSS vars (percentages of the mock container).  Uses left/top
   instead of transform% — percentages on transform resolve
   against the element's own size (24px), not the parent;
   left/top % correctly reference the .sb-actions-mock box. */
.sb-mock-cursor {
  position: absolute;
  width: clamp(24px, 2vw, 32px);
  height: clamp(24px, 2vw, 32px);
  color: #7ADAE6;
  left: var(--cursor-x, 60%);
  top: var(--cursor-y, 80%);
  transition: left 560ms cubic-bezier(0.34, 1.56, 0.64, 1),
              top 560ms cubic-bezier(0.34, 1.56, 0.64, 1);
  pointer-events: none;
  filter: drop-shadow(0 4px 12px rgba(0, 0, 0, 0.4));
}

/* Mobile responsive — stack the spread, shrink the mock,
   drop sticky.  Intro + spread flow naturally. */
@media (max-width: 900px) {
  [data-act="3"] { padding: 0 calc(var(--frame-mask-width, 16px) + 20px); }
  .sb-actions-intro { padding: 140px 0 40px; }
  .sb-actions-stage { min-height: auto; }
  .sb-actions-frame {
    position: static;
    height: auto;
    padding: 40px 0 80px;
    overflow: visible;
  }
  .sb-actions-spread {
    grid-template-columns: 1fr;
    row-gap: 36px;
  }
  .sb-actions-mock {
    max-width: 360px;
    margin: 0 auto;
  }
  /* On mobile, light up all actions equally (no scroll-driven
     spotlight since the frame isn't sticky). */
  .sb-action { color: var(--text-secondary); }
  .sb-action::before { transform: scaleY(1); opacity: 0.5; }
  .sb-action[data-active="true"] { color: var(--text-primary); }
  /* Drop the choreographed cursor on mobile — without the
     sticky stage there's no narrative pacing for it to follow,
     and a hopping arrow on a 360px mock reads as noise. */
  .sb-actions-cursor { display: none; }
}

/* ── Act 4 — "Sessions persist." (editorial timeline) ──────
   New composition: vertical timeline of an authenticated
   scrape flow.  Each step is a row with caption text + a
   small browser-state mock.  A 1px rail runs down the left
   edge connecting numbered bubbles so the sequence reads as
   one continuous session.  No sticky stage — natural scroll
   through rows, with each step fading in on enter via JS. */

[data-act="4"] {
  position: relative;
  z-index: 4;
  background: var(--background);
  padding: 0 clamp(56px, 7vw, 96px);
}

.sb-sessions-intro {
  max-width: 1280px;
  margin: 0 auto;
  padding: clamp(180px, 24vh, 240px) 0 clamp(48px, 8vh, 80px);
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.sb-sessions-intro .act-kicker,
.sb-sessions-intro .sb-act-h2,
.sb-sessions-intro .sb-act-sub {
  max-width: 760px;
}

.sb-sessions-timeline {
  list-style: none;
  margin: 0 auto;
  padding: 0 0 clamp(80px, 12vh, 140px);
  max-width: 1280px;
  display: flex;
  flex-direction: column;
}

.sb-step {
  position: relative;
  display: grid;
  grid-template-columns: 64px minmax(0, 1fr);
  column-gap: clamp(24px, 3vw, 48px);
  padding: clamp(28px, 4vh, 48px) 0;
  /* Each step reveals on entry via IntersectionObserver in JS;
     the data-revealed attribute toggles to "true" when the
     row crosses the viewport's 75% line. */
  opacity: 0;
  transform: translateY(18px);
  transition:
    opacity 540ms cubic-bezier(0.22, 1, 0.36, 1),
    transform 620ms cubic-bezier(0.22, 1, 0.36, 1);
}

.sb-step[data-revealed="true"] {
  opacity: 1;
  transform: translateY(0);
}

/* Rail column — vertical 1px line connecting the numbered
   bubbles down the entire timeline.  Each step's rail shows a
   number bubble at the top; the line fills the rest of the
   row's height. */
.sb-step-rail {
  position: relative;
  display: flex;
  justify-content: center;
  padding-top: 6px;
}

.sb-step-rail::before {
  content: "";
  position: absolute;
  left: 50%;
  top: 0;
  bottom: -1px;
  width: 1px;
  background: var(--border-subtle);
  transform: translateX(-0.5px);
}

/* First step rail starts at the bubble (not above), last step
   rail ends at the bubble. */
.sb-step:first-child .sb-step-rail::before { top: 24px; }
.sb-step:last-child  .sb-step-rail::before { bottom: calc(100% - 32px); }

.sb-step-num {
  position: relative;
  z-index: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: transparent;
  border: 1px solid var(--border-subtle);
  font-family: var(--font-code, monospace);
  font-size: 11px;
  letter-spacing: 0.04em;
  color: var(--text-secondary);
}

.sb-step[data-revealed="true"] .sb-step-num {
  background: var(--text-primary);
  color: var(--background);
  border-color: var(--text-primary);
  transition: background 420ms cubic-bezier(0.22, 1, 0.36, 1),
              color 420ms cubic-bezier(0.22, 1, 0.36, 1),
              border-color 420ms cubic-bezier(0.22, 1, 0.36, 1);
}

/* Step content — 2-col internal grid: caption left, mock right. */
.sb-step-content {
  display: grid;
  grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr);
  column-gap: clamp(32px, 5vw, 64px);
  align-items: center;
}

.sb-step-text {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.sb-step-tag {
  margin: 0;
  font-family: var(--font-code, monospace);
  font-size: 11px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--text-tertiary);
}

.sb-step-name {
  margin: 0;
  font-family: var(--font-display, var(--font-base));
  font-size: var(--mkt-h3);
  line-height: 1.08;
  letter-spacing: -0.022em;
  color: var(--text-primary);
  font-weight: 500;
}

.sb-step-desc {
  margin: 0;
  font-family: var(--font-base);
  font-size: var(--mkt-body);
  line-height: 1.55;
  color: var(--text-secondary);
  max-width: 520px;
}

.sb-step-code {
  margin: 8px 0 0;
  padding: 14px 16px;
  border-radius: 10px;
  background: transparent;
  border: 1px solid var(--border-subtle);
  font-family: var(--font-code, monospace);
  font-size: var(--mkt-body-xs);
  line-height: 1.55;
  color: var(--text-secondary);
  white-space: pre-wrap;
  overflow-x: clip;
  scrollbar-width: none;
}
.sb-step-code::-webkit-scrollbar { display: none; }

.sb-code-com { color: var(--text-quaternary); font-style: italic; }

/* Mock — small browser-canvas snapshot for each step.  No
   browser chrome (outer .sb-frame is the chrome); the mock is
   just the page content visible at this step. */
.sb-step-mock {
  display: flex;
  align-items: center;
  justify-content: center;
}

.sb-step-mock-frame {
  width: 100%;
  max-width: 360px;
  border-radius: 14px;
  background: transparent;
  border: 1px solid var(--border-subtle);
  padding: clamp(16px, 1.8vw, 24px);
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.sb-step-mock-line {
  height: 8px;
  border-radius: 4px;
  background: var(--zen-gray-4);
}
.sb-step-mock-line--xl { width: 70%; height: 12px; background: var(--zen-gray-5); }
.sb-step-mock-line--lg { width: 60%; }
.sb-step-mock-line--md { width: 48%; }
.sb-step-mock-line--sm { width: 30%; }

.sb-step-mock-input {
  padding: 8px 12px;
  border-radius: 6px;
  background: var(--background);
  border: 1px solid var(--border);
  font-family: var(--font-code, monospace);
  font-size: 12px;
  color: var(--text-secondary);
  min-height: 32px;
  display: flex;
  align-items: center;
}

.sb-step-mock-btn {
  align-self: flex-start;
  padding: 8px 18px;
  border-radius: var(--radius-full, 999px);
  background: #7C4FE8;
  /* Hardcoded white — bg is brand purple (theme-invariant). */
  color: #FFFFFF;
  font-family: var(--font-base);
  font-size: 12px;
  font-weight: 500;
  transform: scale(0.96);
  margin-top: 4px;
}

/* Step 2 — cookie chip moment.  A single chip showing the
   session cookie key=value, with a caption beneath. */
.sb-step-cookie {
  align-self: flex-start;
  display: inline-flex;
  align-items: baseline;
  gap: 4px;
  padding: 10px 16px;
  border-radius: var(--radius-full, 999px);
  background: var(--background);
  border: 1px solid var(--zen-purple-5);
  font-family: var(--font-code, monospace);
  font-size: 13px;
  margin: 8px 0;
}

.sb-step-cookie-key { color: var(--text-primary); font-weight: 600; }
.sb-step-cookie-eq  { color: var(--text-quaternary); }
.sb-step-cookie-val { color: var(--zen-purple-7); }

.sb-step-mock-caption {
  margin: 0;
  font-family: var(--font-base);
  font-size: 12px;
  line-height: 1.45;
  color: var(--text-tertiary);
}

.sb-step-mock-greeting {
  margin: 0 0 6px;
  font-family: var(--font-display, var(--font-base));
  font-size: var(--mkt-body-sm);
  font-weight: 500;
  color: var(--text-primary);
}

.sb-step-mock-json {
  margin: 0;
  padding: 14px;
  border-radius: 8px;
  background: var(--background);
  border: 1px solid var(--border);
  font-family: var(--font-code, monospace);
  font-size: 12px;
  line-height: 1.55;
  color: var(--text-secondary);
  white-space: pre;
  overflow-x: auto;
  scrollbar-width: none;
}
.sb-step-mock-json::-webkit-scrollbar { display: none; }

/* Mobile responsive — stack step content (text above mock),
   thinner rail, smaller code blocks. */
@media (max-width: 900px) {
  [data-act="4"] { padding: 0 calc(var(--frame-mask-width, 16px) + 20px); }
  .sb-sessions-intro { padding: 140px 0 40px; }
  .sb-step {
    grid-template-columns: 40px minmax(0, 1fr);
    column-gap: 18px;
    padding: 24px 0;
  }
  .sb-step-num { width: 28px; height: 28px; font-size: 10px; }
  .sb-step-content {
    grid-template-columns: 1fr;
    row-gap: 20px;
  }
  .sb-step-mock-frame { max-width: 100%; }
}

/* ── Act 5 — "Already in your stack." (horizontal marquee) ─
   Six framework cards in a row that auto-scroll left.  Hover
   pauses the loop; wheel events on the marquee translate the
   track horizontally instead of scrolling the page (same
   containment trick as the home proof-roll, rotated 90°).
   Cloned-at-mount infinite loop so there's no visible seam. */

[data-act="5"] {
  position: relative;
  z-index: 5;
  background: var(--background);
  /* No horizontal padding — the marquee runs the full width
     of the frame interior; the side masks (and the frame's
     own borders) define the visible edges. */
}

.sb-frameworks-intro {
  max-width: 1280px;
  margin: 0 auto;
  padding: clamp(180px, 24vh, 240px) clamp(56px, 7vw, 96px) clamp(48px, 8vh, 80px);
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.sb-frameworks-intro .act-kicker,
.sb-frameworks-intro .sb-act-h2,
.sb-frameworks-intro .sb-act-sub {
  max-width: 760px;
}

/* Marquee viewport — fades at left + right edges so cards
   slide in / out softly. */
.sb-frameworks-marquee {
  position: relative;
  width: 100%;
  margin-bottom: clamp(80px, 12vh, 140px);
  overflow: hidden;
  -webkit-mask-image: linear-gradient(
    to right, transparent 0%, #000 8%, #000 92%, transparent 100%
  );
          mask-image: linear-gradient(
    to right, transparent 0%, #000 8%, #000 92%, transparent 100%
  );
}

.sb-frameworks-track {
  --roll-x: 0px;
  display: flex;
  align-items: stretch;
  gap: clamp(20px, 2vw, 32px);
  padding: 8px clamp(20px, 4vw, 48px);
  transform: translate3d(var(--roll-x), 0, 0);
  will-change: transform;
}

.sb-fw-card {
  flex: 0 0 clamp(280px, 28vw, 380px);
  width: clamp(280px, 28vw, 380px);
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding: clamp(20px, 1.8vw, 28px);
  border-radius: var(--radius-display, 20px);
  background: var(--background);
  min-height: 240px;
  position: relative;
  overflow: hidden;
  transition:
    transform 240ms cubic-bezier(0.22, 1, 0.36, 1),
    box-shadow 280ms cubic-bezier(0.22, 1, 0.36, 1);
}

/* Top-edge accent — same DNA as /api Act 6 code cards.  2px
   gradient bar on top, purple-7 → cyan-6 → transparent.
   Hidden by default; lights up alongside the shadow on hover. */
.sb-fw-card::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 2px;
  background: linear-gradient(
    90deg,
    #7C4FE8 0%,
    var(--zen-cyan-6) 35%,
    transparent 80%
  );
  opacity: 0;
  transition: opacity 220ms ease;
}

.sb-fw-card:hover {
  transform: translateY(-3px);
  box-shadow: 0 28px 64px -20px rgba(0, 0, 0, 0.55);
}

.sb-fw-card:hover::before {
  opacity: 1;
}

.sb-fw-card-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  padding-bottom: clamp(10px, 1vh, 14px);
  border-bottom: 1px solid var(--border-subtle);
}

.sb-fw-card-name {
  font-family: var(--font-display, var(--font-base));
  font-size: var(--mkt-h4);
  font-weight: 500;
  letter-spacing: -0.012em;
  color: var(--text-primary);
  line-height: 1.05;
}

.sb-fw-card-lang {
  padding: 4px 10px;
  border-radius: var(--radius-full, 999px);
  background: transparent;
  border: 1px solid var(--border-subtle);
  font-family: var(--font-code, monospace);
  font-size: 11px;
  letter-spacing: 0.04em;
  color: var(--text-tertiary);
  white-space: nowrap;
  flex-shrink: 0;
}

.sb-fw-card-code {
  margin: 0;
  font-family: var(--font-code, monospace);
  font-size: var(--mkt-body-xs);
  line-height: 1.55;
  color: var(--text-secondary);
  white-space: pre;
  overflow: hidden;
  flex: 1 1 auto;
}

.sb-fw-card-code code {
  font-family: inherit;
  color: inherit;
}

/* Mobile responsive — drop the marquee, render as a
   horizontal scroll-snap track instead (lighter on touch,
   user controls pacing). */
@media (max-width: 900px) {
  .sb-frameworks-intro {
    padding: 140px calc(var(--frame-mask-width, 16px) + 20px) 36px;
  }
  .sb-frameworks-marquee {
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    -webkit-mask-image: none;
            mask-image: none;
    margin-bottom: 80px;
  }
  .sb-frameworks-track {
    transform: none !important;
    /* Track padding sits inside the frame so the first / last
       card has breathing room before the frame's side border. */
    padding: 8px calc(var(--frame-mask-width, 16px) + 20px);
  }
  .sb-fw-card { scroll-snap-align: start; }
}

/* ── Act 6 — Closer.  "Connect the browser." ───────────────
   Editorial dark closer.  The WebSocket endpoint is the
   visual centrepiece — code-as-poster typography with the
   api-key placeholder highlighted in purple-7.  CTA row sits
   below.  No saturated band, no curtain bloom — restraint
   after Act 5's marquee. */

[data-act="6"] {
  position: relative;
  z-index: 6;
  background: var(--background);
  padding: 0 clamp(56px, 7vw, 96px);
}

.sb-closer {
  max-width: 1280px;
  margin: 0 auto;
  padding: clamp(180px, 24vh, 240px) 0 clamp(120px, 16vh, 200px);
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: clamp(20px, 3vh, 32px);
}

.sb-closer .act-kicker {
  color: var(--text-tertiary);
}

.sb-closer-h2 {
  margin: 0;
  font-family: var(--font-display, var(--font-base));
  font-size: var(--mkt-hero);
  line-height: 1;
  letter-spacing: -0.028em;
  color: var(--text-primary);
  font-weight: 500;
}

/* Endpoint poster — the WebSocket URL typeset large as the
   visual centre.  Each part of the URL coloured by role so
   the reader's eye lands on the highlighted YOUR_KEY token. */
.sb-closer-endpoint {
  margin: clamp(12px, 2vh, 24px) 0 4px;
  font-family: var(--font-code, monospace);
  font-size: var(--mkt-h4);
  line-height: 1.3;
  letter-spacing: -0.012em;
  display: inline-flex;
  flex-wrap: wrap;
  justify-content: center;
  max-width: 100%;
  /* Allow the host segment to break inside if the viewport is
     too narrow for it as a single word — defensive only; on
     normal phones the segments wrap between spans first. */
  overflow-wrap: anywhere;
  word-break: break-word;
}

.sb-closer-endpoint-protocol { color: var(--text-quaternary); }
.sb-closer-endpoint-host     { color: var(--text-primary); font-weight: 500; }
.sb-closer-endpoint-path     { color: var(--text-tertiary); }
.sb-closer-endpoint-query    { color: var(--text-tertiary); }
.sb-closer-endpoint-token {
  color: var(--zen-purple-7);
  font-weight: 500;
  padding: 0 4px;
  border-radius: 6px;
  background: rgba(124, 79, 232, 0.10);
}

.sb-closer-sub {
  margin: 0;
  max-width: 580px;
  font-family: var(--font-base);
  font-size: var(--mkt-body);
  line-height: 1.55;
  color: var(--text-secondary);
}

.sb-closer-ctas {
  margin-top: clamp(12px, 2vh, 20px);
  display: flex;
  align-items: center;
  gap: clamp(16px, 2vw, 28px);
  flex-wrap: wrap;
  justify-content: center;
}

.sb-closer-secondary {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 6px 4px;
  font-family: var(--font-base);
  font-size: var(--text-base, 15px);
  font-weight: 500;
  color: var(--text-secondary);
  text-decoration: none;
  border-bottom: 1px solid transparent;
  cursor: pointer;
  transition: color 200ms ease, border-color 200ms ease;
}

.sb-closer-secondary svg {
  width: 18px;
  height: 18px;
  transition: transform 220ms cubic-bezier(0.34, 1.56, 0.64, 1);
}

.sb-closer-secondary:hover {
  color: var(--text-primary);
  border-bottom-color: var(--border-default);
}

.sb-closer-secondary:hover svg { transform: translateX(4px); }

/* Mobile responsive — endpoint wraps comfortably, type
   scales down so it doesn't bust out of the frame. */
@media (max-width: 900px) {
  [data-act="6"] { padding: 0 calc(var(--frame-mask-width, 16px) + 20px); }
  .sb-closer { padding: 140px 0 100px; }
  
  .sb-closer-endpoint { font-size: var(--mkt-lead); }
}

/* ── Footer override ───────────────────────────────────────
   On this page the browser frame sticks at left/right with
   clamp(20px, 4vw, 48px) of side padding.  The shared footer
   would otherwise use the same value, touching the frame's
   side borders.  Bump the footer's horizontal padding so it
   sits visibly INSIDE the frame interior with breathing room. */
.site-footer .footer-container {
  padding: 0 clamp(56px, 7vw, 120px);
}

/* On mobile, the frame side gutter drops to ~16px and the
   60+px footer padding from above would feel double-indented.
   Scale the footer padding down so it reads as part of the
   same interior page. */
@media (max-width: 600px) {
  /* Match the page content's horizontal padding inside the frame so the
     footer lines up with every act above (frame side rail + 20px), instead
     of sitting flush against the frame's interior edge. */
  .site-footer .footer-container {
    padding: 0 calc(var(--frame-mask-width, 16px) + 20px);
  }
  /* The footer is the last block inside the browser frame's side rails.
     Give it clear breathing room top + bottom so its content (and the
     copyright row) never kisses the frame interior edge on phones. */
  .site-footer {
    padding-top: clamp(48px, 12vw, 72px);
    padding-bottom: clamp(64px, 18vw, 104px);
  }
}

/* ── Wide-desktop tiers ───────────────────────────────────── */

@media (min-width: 1600px) {
  .sb-hero { max-width: 1480px; }
  
  .sb-hero-browser-anchor { max-width: 860px; }
  /* Every act shares the same wide-desktop max-width so all
     content sits in one horizontal channel — same alignment
     as Act 2 (Thesis), centred via margin auto. */
  .sb-act,
  .sb-actions-intro,
  .sb-actions-spread,
  .sb-sessions-intro,
  .sb-sessions-timeline,
  .sb-frameworks-intro,
  .sb-closer { max-width: 1440px; }
  .sb-act-h2 { font-size: 60px; }
  .sb-actions-mock { max-width: 560px; }
  .sb-action-name { font-size: 36px; }
  
  .sb-closer-endpoint { font-size: 38px; }
}

@media (min-width: 1920px) {
  .sb-hero { max-width: 1640px; }
  
  .sb-hero-browser-anchor { max-width: 980px; }
  .sb-act,
  .sb-actions-intro,
  .sb-actions-spread,
  .sb-sessions-intro,
  .sb-sessions-timeline,
  .sb-frameworks-intro,
  .sb-closer { max-width: 1600px; }
  .sb-act-h2 { font-size: 72px; }
  .sb-actions-mock { max-width: 640px; }
  .sb-action-name { font-size: 42px; }
  
  .sb-closer-endpoint { font-size: 44px; }
}

@media (min-width: 2400px) {
  .sb-hero { max-width: 1840px; }
  
  .sb-hero-browser-anchor { max-width: 1160px; }
  .sb-act,
  .sb-actions-intro,
  .sb-actions-spread,
  .sb-sessions-intro,
  .sb-sessions-timeline,
  .sb-frameworks-intro,
  .sb-closer { max-width: 1800px; }
  .sb-act-h2 { font-size: 84px; }
  .sb-actions-mock { max-width: 760px; }
  .sb-action-name { font-size: 52px; }
  
  .sb-closer-endpoint { font-size: 52px; }
}

/* ── Mobile responsive ────────────────────────────────────── */

@media (max-width: 1023px) {
  .sb-hero {
    grid-template-columns: 1fr;
    row-gap: clamp(36px, 5vh, 56px);
  }
  .sb-hero-browser {
    margin-left: 0;
    max-width: 100%;
  }
}

@media (max-width: 600px) {
  [data-act="1"] {
    padding: clamp(80px, 16vh, 120px) 18px 64px;
  }
  
  .sb-hero-browser { aspect-ratio: 16 / 13; }
  .sb-browser-url { font-size: 10px; }
}

/* ══════════════════════════════════════════════════════════
   Audience switcher + BU light theme.
   Same mechanism as home + /products/fetch.  Switcher fixed on
   right edge.  Tech (default) keeps the dark theme intact.  BU
   swaps to light theme + Mq12 display face + business-benefits
   copy.  Lift to a shared file once a third page joins. */
[data-audience-tech][hidden],
[data-audience-bu][hidden] { display: none !important; }

.audience-switch {
  position: fixed;
  top: 50%;
  right: 16px;
  z-index: 250;
  /* Grid 1fr 1fr forces equal-height rows so the sliding indicator
     aligns with each segment regardless of label length. */
  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 purple pill indicator — moves between tech/bu segments
   based on body[data-audience].  CSS-only animation. */
.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 — compact horizontal pill bottom-right (FAB-style). */
@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 light mode body + chrome ──
   Body bg matches var(--background) (the same surface individual
   sections paint) so there's no visible seam between act sections.
   Sections on Browser Sessions paint full-width white surfaces;
   if body were gray-3 (sunken), the body would show as a grey band
   between sections.  Flat-light here is the right call. */
html:not(.dark) body {
  background: var(--background);
  color: var(--text-primary);
}
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);
}

/* Mq12 display face on every heading in BU.
   Browser Sessions H2s share class .sb-act-h2, closer is
   .sb-closer-h2.  Force weight 500 (Mq12 Medium intrinsic).
   .sb-action-name uses mono in tech (it's a code identifier);
   in BU the verbs are English so flip to Mq12 too. */
html:not(.dark) .sb-hero-h1,
html:not(.dark) .sb-act-h2,
html:not(.dark) .sb-closer-h2,
html:not(.dark) .sb-action-name {
  font-family: 'Mq12', system-ui, sans-serif;
  font-weight: 500;
}

/* Topbar inversion — no saturated bands cover the topbar on
   Browser Sessions, but defensive yields keep the pattern
   consistent if a band is added later. */

/* Nav popover (.nav-dropdown) light variant. */
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); }
html:not(.dark) .nav-mega-headline {
  background-color: #1D1612;
  border-color: rgba(29, 22, 18, 0.18);
}
html:not(.dark) .nav-mega-preview-desc { color: var(--text-secondary); }

/* Teal text on light bg — mock cursor flip. */
html:not(.dark) .sb-mock-cursor {
  color: #1C9E97;
}

/* ── BU summary widgets ─────────────────────────────────────
   Hairline-bordered key/value lists that replace the dev-jargon
   Playwright/Puppeteer snippet (Act 2) and the WebSocket
   endpoint mock (Act 6) when the audience switcher is in BU
   mode.  Mirrors the .ci-how-step-summary atom.
*/
.sb-act-summary,
.sb-closer-summary {
  list-style: none;
  margin: 0;
  padding: clamp(20px, 2.2vh, 28px) clamp(22px, 2vw, 30px);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-display, 20px);
  display: flex;
  flex-direction: column;
  gap: 12px;
  font-family: var(--font-base);
  background: transparent;
}

.sb-act-summary li,
.sb-closer-summary li {
  display: grid;
  grid-template-columns: 110px minmax(0, 1fr);
  gap: 16px;
  align-items: baseline;
  font-size: var(--mkt-body-sm);
  line-height: 1.55;
}

.sb-act-summary li + li,
.sb-closer-summary li + li {
  padding-top: 12px;
  border-top: 1px solid var(--border-subtle);
}

.sb-act-summary-k,
.sb-closer-summary-k {
  font-family: var(--font-code, "IBM Plex Mono", monospace);
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-tertiary);
}

.sb-act-summary-v,
.sb-closer-summary-v {
  color: var(--text-secondary);
}

.sb-act-summary-v strong,
.sb-closer-summary-v strong {
  color: var(--text-primary);
  font-weight: 600;
}

/* Act 6 closer — BU summary occupies the slot where the
   endpoint poster sits for tech; centred inline-block so it
   matches the closer composition. */
.sb-closer-summary {
  max-width: 560px;
  margin-inline: auto;
  text-align: left;
}
