/* ── Fetch spoke ────────────────────────────────────
   `/products/fetch` — built act-by-act.  Inherits the universal
   site chrome (topbar, footer, mobile nav, O-cursor, back-to-top)
   from `_shared/chrome.css`; this file only carries the spoke's
   own act compositions.

   Layering convention:
   - Page bg: dark page background (DS `--background` in dark mode)
   - Ambient halo: very soft teal radial behind the hero, so the
     spoke reads as the most technical / dev-facing product
     (teal is our developer-side accent on the brand wheel).
   - Hero composition: centred, widget-anchored.  The try widget
     is the focal element on this page — unlike /products where
     it lives clipped inside a card mesh, here it's full and
     prominent because it IS the affordance.
   ──────────────────────────────────────────────────────────── */

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

/* Page bg — solid dark, no ambient gradient haloes.  Same
   rule as the home: backgrounds stay solid, glow effects
   live inside compositions (per-card meshes, etc.) rather
   than as page-level radials. */
html, body {
  background: var(--background);
}

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

/* ── Act 1 — Hero (centered, widget-anchored) ──────────────
   Composition:
   - Vertical stack: kicker → H1 → sub → try widget → proof row
   - All centred; max width caps the line length of the prose
     while the widget gets a slightly wider cap so the segments
     row can breathe.
   - Plenty of top padding to clear the fixed topbar (z-index 90).
*/

/* Section-pinning storytelling.  Act 1 sticks at the top of
   the viewport while the user scrolls; Act 2 (and any later
   act) carries a solid background and a higher z-index so it
   rises from below and "mounts over" Act 1.  The hero never
   scrolls out of view — it sits behind the next act once it's
   covered, freeing memory of the previous beat for the next. */
[data-act="1"] {
  position: sticky;
  top: 0;
  z-index: 1;
  height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: clamp(96px, 14vh, 160px) clamp(20px, 4vw, 56px) clamp(64px, 10vh, 120px);
  background: var(--background);
}

.api-hero {
  width: 100%;
  max-width: 820px;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: clamp(20px, 2.2vw, 28px);
}


.api-hero-h1 {
  margin: 0;
  font-family: var(--font-display, var(--font-base));
  font-size: var(--mkt-hero-xl);
  line-height: 1.02;
  letter-spacing: -0.022em;
  color: var(--text-primary);
  font-weight: 500;
  cursor: default;
}

/* O-hover affordance — identical pattern to /products
   overview hero.  The "O" of "One call" cross-fades into the
   brand O-svg (the same shape as the cursor follower) when
   the reader hovers anywhere on the H1.  Purple-7 hardcoded
   per the saturated-bands exception. */
.api-hero-h1 .hero-o {
  position: relative;
  display: inline-block;
  font-style: normal;
}
.api-hero-h1 .hero-o-letter {
  display: inline-block;
  transition: opacity 280ms cubic-bezier(0.22, 1, 0.36, 1);
}
.api-hero-h1 .hero-o-svg {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 0.92em;
  height: 0.92em;
  transform: translate(-50%, -50%);
  opacity: 0;
  color: #7C4FE8;
  transition: opacity 280ms cubic-bezier(0.22, 1, 0.36, 1);
  pointer-events: none;
}
.api-hero-h1:hover .hero-o-letter { opacity: 0; }
.api-hero-h1:hover .hero-o-svg    { opacity: 1; }

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

/* The try widget — same DNA as the /products card widget but
   wider and standing on its own (no card mesh behind it).  The
   dark-glass treatment still works because the ambient teal
   halo bleeds through the translucent slate. */
.api-try {
  width: 100%;
  max-width: 680px;
  margin-top: clamp(8px, 1.2vw, 16px);
  display: flex;
  flex-direction: column;
  gap: var(--space-400, 14px);
  padding: clamp(20px, 1.8vw, 28px);
  /* Container shell matches /products overview cards: page-bg
     fill + strong down-shadow.  Shadow alone defines the
     boundary — no border, editorial restraint. */
  border-radius: var(--radius-display, 20px);
  background: var(--background);
  box-shadow: 0 28px 64px -20px rgba(0, 0, 0, 0.55);
  text-align: left;
}

.api-try-field {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.api-try-label {
  font-family: var(--font-base);
  font-size: var(--text-tiny, 11px);
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-secondary);
}

.api-try-input {
  width: 100%;
  height: 44px;
  padding: 0 14px;
  border-radius: var(--radius-default, 6px);
  border: 1px solid var(--border-default);
  background: transparent;
  color: var(--text-primary);
  font-family: var(--font-code, "IBM Plex Mono"), monospace;
  font-size: var(--text-small, 13px);
  outline: none;
  transition: border-color 180ms cubic-bezier(0.22, 1, 0.36, 1),
              background 180ms cubic-bezier(0.22, 1, 0.36, 1),
              box-shadow 180ms cubic-bezier(0.22, 1, 0.36, 1);
}

.api-try-input::placeholder { color: var(--text-quaternary); }
.api-try-input:hover { border-color: var(--border-default-hover, var(--zen-gray-6)); }
.api-try-input:focus,
.api-try-input:focus-visible {
  border-color: var(--border-accent);
  background: var(--sunken-hover);
  box-shadow: 0 0 0 3px var(--zen-purple-3);
}

.api-try-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: nowrap;
}

.api-try-segments {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 4px;
  background: var(--background);
  border: 1px solid var(--border);
  border-radius: var(--radius-full, 999px);
  min-width: 0;
  flex-shrink: 1;
  overflow-x: auto;
  scrollbar-width: none;
}
.api-try-segments::-webkit-scrollbar { display: none; }

.api-try-chip {
  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.01em;
  padding: 6px 14px;
  border-radius: var(--radius-full, 999px);
  cursor: pointer;
  white-space: nowrap;
  transition: background 180ms cubic-bezier(0.22, 1, 0.36, 1),
              color 180ms cubic-bezier(0.22, 1, 0.36, 1);
}
.api-try-chip:hover { color: var(--text-primary); background: var(--sunken-hover); }
.api-try-chip.is-active { background: var(--text-primary); color: var(--background); }

.api-try-submit {
  appearance: none;
  display: inline-flex;
  align-items: center;
  gap: 10px;
  height: 44px;
  padding: 0 22px;
  border: none;
  border-radius: var(--radius-full, 999px);
  background: var(--text-primary);
  color: var(--background);
  flex-shrink: 0;
  font-family: var(--font-base);
  font-size: var(--text-small, 13px);
  font-weight: 500;
  cursor: pointer;
  transition: background 180ms cubic-bezier(0.22, 1, 0.36, 1),
              transform 180ms cubic-bezier(0.22, 1, 0.36, 1),
              box-shadow 180ms cubic-bezier(0.22, 1, 0.36, 1);
}
.api-try-submit > span:last-child {
  transition: transform 220ms cubic-bezier(0.34, 1.56, 0.64, 1);
}
.api-try-submit:hover {
  transform: translateY(-1px);
}
.api-try-submit:hover > span:last-child { transform: translateX(4px); }
.api-try-submit:active { transform: translateY(0); }

.api-try-submit:disabled,
.api-try-submit[aria-disabled="true"] {
  cursor: not-allowed;
  background: transparent;
  border: 1px solid var(--border-subtle);
  color: var(--text-quaternary);
  transform: none;
  box-shadow: none;
}
.api-try-submit:disabled:hover,
.api-try-submit[aria-disabled="true"]:hover {
  transform: none;
  box-shadow: none;
}
.api-try-submit:disabled:hover > span:last-child,
.api-try-submit[aria-disabled="true"]:hover > span:last-child {
  transform: none;
}


/* ── Act 2 — "Every protection. One call." (sticky thesis) ─
   Storytelling beat: the curl sits calm in the middle while
   the throws list to the right illuminates one by one as
   the reader scrolls.  At the end, the response slides up
   under the terminal, completing the call.

   Mechanics:
   - Stage gets ~340vh of total height so 8 throws + the
     response have comfortable scroll room.
   - The sticky frame fills one viewport while the stage is in
     view; api.js tracks the stage's getBoundingClientRect()
     and writes a per-throw --throw-progress (0..1) plus a
     --response-progress (0..1) for the final beat.
*/

[data-act="2"] {
  position: relative;
  z-index: 2;
  background: var(--background);
}

.endpoint-stage {
  position: relative;
  min-height: 340vh;
  padding: 0;
}

.endpoint-frame {
  position: sticky;
  top: 0;
  height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: center;
  gap: clamp(28px, 4vh, 48px);
  padding: clamp(80px, 12vh, 120px) clamp(20px, 5vw, 64px) clamp(48px, 8vh, 80px);
  overflow: hidden;
}

.endpoint-frame-header {
  width: 100%;
  max-width: 1080px;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 12px;
  text-align: left;
}

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

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

/* Body — terminal on the left, throws column on the right.
   On narrow widths the throws stack BELOW the terminal so
   the storytelling still works on a phone (each throw
   illuminates as it enters the viewport). */
.endpoint-frame-body {
  width: 100%;
  max-width: 1280px;
  display: grid;
  grid-template-columns: minmax(0, 1.4fr) minmax(200px, 240px) minmax(220px, 280px);
  align-items: stretch;
  gap: clamp(24px, 2.6vw, 36px);
}

/* Terminal — dark warm panel with a fake titlebar and a
   monospace code block.  Flex column so the curl block grows
   to absorb whatever vertical space the throws column needs;
   keeps both sides the same height so the eye reads a single
   horizontal band rather than a tall list next to a stub. */
.endpoint-terminal {
  position: relative;
  display: flex;
  flex-direction: column;
  border-radius: var(--radius-display, 20px);
  background: var(--background);
  box-shadow: 0 28px 64px -20px rgba(0, 0, 0, 0.55);
  overflow: hidden;
  min-height: 0;
}

.endpoint-terminal-bar {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 10px 14px;
  border-bottom: 1px solid var(--border);
  background: transparent;
}

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

.endpoint-terminal-name {
  margin-left: 10px;
  font-family: var(--font-code, monospace);
  font-size: 11px;
  letter-spacing: 0.04em;
  color: var(--text-quaternary);
}

.endpoint-terminal-code {
  margin: 0;
  padding: clamp(18px, 2vw, 26px);
  font-family: var(--font-code, monospace);
  font-size: var(--mkt-body-xs);
  line-height: 1.6;
  color: var(--text-primary);
  white-space: pre;
  overflow-x: clip;
  /* Grow to fill the terminal's available height so the
     terminal box matches the throws column height.  The
     response panel below this still ramps in via its own
     max-height as scroll progresses. */
  flex: 1 1 auto;
  min-height: 0;
}

.endpoint-terminal-code code {
  font-family: inherit;
  color: inherit;
}

.endpoint-prompt {
  color: var(--zen-yellow-6);
  font-weight: 500;
}

/* Response — slides up + fades in as --response-progress
   ramps 0 → 1.  Sits inside the terminal so it reads as the
   call's own output. */
.endpoint-response {
  --response-progress: 0;
  border-top: 1px solid var(--border);
  background: var(--background);
  max-height: calc(var(--response-progress) * 320px);
  opacity: var(--response-progress);
  transition: max-height 240ms cubic-bezier(0.22, 1, 0.36, 1),
              opacity 240ms cubic-bezier(0.22, 1, 0.36, 1);
  overflow: hidden;
}

.endpoint-response-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  padding: 10px 16px;
  font-family: var(--font-code, monospace);
  font-size: 11px;
  letter-spacing: 0.04em;
  border-bottom: 1px solid var(--border);
}

.endpoint-response-status {
  color: var(--zen-yellow-6);
  font-weight: 700;
}

.endpoint-response-meta {
  color: var(--text-tertiary);
}

.endpoint-response-code {
  margin: 0;
  padding: 14px 18px;
  font-family: var(--font-code, monospace);
  font-size: var(--mkt-body-xs);
  line-height: 1.55;
  color: var(--text-secondary);
  white-space: pre;
  overflow-x: auto;
}

/* Throws column — vertical list of protections / hard things
   that the call absorbs.  Each item owns a slice of stage
   progress (api.js writes --throw-progress 0..1) and lights
   up from muted gray to white + teal check when its slice
   plays. */
.endpoint-throws {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.endpoint-throw {
  --throw-progress: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  padding: 12px 16px;
  border-radius: 10px;
  background: transparent;
  border: 1px solid var(--border);
  /* Hide the system cursor here — the O-cursor SVG follower
     (driven by api.js cursorSelector) is the only pointer
     affordance over the pills.  Convention shared with the
     home / products pages. */
  cursor: pointer;
  transition: background 240ms cubic-bezier(0.22, 1, 0.36, 1),
              border-color 240ms cubic-bezier(0.22, 1, 0.36, 1),
              transform 240ms cubic-bezier(0.22, 1, 0.36, 1);
  /* Subtle entrance: each item starts a few pixels below its
     resting position; progress eases it up to 0. */
  transform: translateY(calc((1 - var(--throw-progress)) * 6px));
  opacity: calc(0.35 + var(--throw-progress) * 0.65);
}

.endpoint-throw[data-illuminated="1"] {
  background: var(--zen-purple-2);
  border-color: var(--zen-purple-5);
}

.endpoint-throw-label {
  font-family: var(--font-base);
  font-size: 14px;
  font-weight: 500;
  color: var(--text-secondary);
  transition: color 240ms cubic-bezier(0.22, 1, 0.36, 1);
}

.endpoint-throw[data-illuminated="1"] .endpoint-throw-label {
  color: var(--text-primary);
}

/* Right-edge slot — holds the check by default, swaps to the
   rounded company favicon on hover (for throws with a
   data-domain).  Stacked so the swap is a cross-fade, no
   layout shift. */
.endpoint-throw-end {
  position: relative;
  width: 18px;
  height: 18px;
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.endpoint-throw-check {
  position: absolute;
  inset: 0;
  width: 18px;
  height: 18px;
  color: var(--text-quaternary);
  transition: color 240ms cubic-bezier(0.22, 1, 0.36, 1),
              opacity 200ms cubic-bezier(0.22, 1, 0.36, 1);
}

/* Stroke-dash trick — the check path is 32 units long; we
   draw from 0 to full as --throw-progress moves 0..1. */
.endpoint-throw-check path {
  stroke-dasharray: 32;
  stroke-dashoffset: calc(32 * (1 - var(--throw-progress)));
  transition: stroke-dashoffset 240ms cubic-bezier(0.22, 1, 0.36, 1);
}

.endpoint-throw[data-illuminated="1"] .endpoint-throw-check {
  color: var(--zen-purple-7);
}

.endpoint-throw-favicon {
  position: absolute;
  inset: 0;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: var(--text-primary);
  object-fit: contain;
  opacity: 0;
  transform: scale(0.7);
  transition: opacity 200ms cubic-bezier(0.22, 1, 0.36, 1),
              transform 240ms cubic-bezier(0.34, 1.56, 0.64, 1);
}

/* Hover swap — only fires on throws with a real domain.
   Check fades out, favicon scales/fades in.  No cursor swap:
   the pill isn't actually clickable, so the default arrow
   cursor stays. */
.endpoint-throw:hover {
  border-color: var(--zen-purple-6);
  background: var(--zen-purple-3);
}

.endpoint-throw[data-domain]:hover .endpoint-throw-check {
  opacity: 0;
}

.endpoint-throw[data-domain]:hover .endpoint-throw-favicon {
  opacity: 1;
  transform: scale(1);
}

/* ── Stepper — the live request unfolding column ────────────
   Mirrors the ZenrowsPlus preview stepper (ScrapingStepper):
   vertical list of rows, each with a status indicator on the
   left, label in the middle, and elapsed time on the right.
   Three visual states, driven by data-state set by api.js
   from the same scroll progress that drives the throws:

   - waiting  : muted, hollow dot
   - active   : green ping ring + filled dot, label primary
   - done     : green check, time visible, label secondary
*/
.endpoint-stepper {
  list-style: none;
  margin: 0;
  padding: 14px 4px;
  display: flex;
  flex-direction: column;
  gap: 14px;
  background: var(--background);
  border-radius: var(--radius-display, 20px);
  box-shadow: 0 28px 64px -20px rgba(0, 0, 0, 0.55);
}

.endpoint-step {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 4px 12px;
  font-family: var(--font-base);
  font-size: 13px;
  line-height: 1.4;
  color: var(--text-quaternary);
  transition: color 240ms cubic-bezier(0.22, 1, 0.36, 1);
}

.endpoint-step[data-state="active"] { color: var(--text-primary); }
.endpoint-step[data-state="done"]   { color: var(--text-tertiary); }

/* Indicator — three stacked layers, only one visible per state:
   - .endpoint-step-ping: green ring that pulses when active
   - .endpoint-step-dot:  solid green core when active or done
   - .endpoint-step-check: green check when done */
.endpoint-step-indicator {
  position: relative;
  width: 16px;
  height: 16px;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}

.endpoint-step-dot {
  position: absolute;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--zen-green-6);
  opacity: 0;
  transition: opacity 240ms cubic-bezier(0.22, 1, 0.36, 1);
}

/* Waiting state — small hollow neutral ring so the slot still
   reads as a step rather than blank. */
.endpoint-step[data-state="waiting"] .endpoint-step-indicator::before {
  content: "";
  position: absolute;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  border: 1.5px solid var(--zen-gray-5);
}

.endpoint-step[data-state="active"] .endpoint-step-dot,
.endpoint-step[data-state="done"]   .endpoint-step-dot {
  opacity: 1;
}

/* Active state — solid green dot with a subtle box-shadow
   pulse.  The shadow expands outward and fades, then resets.
   No scaled element behind the dot — the prior "ping" pattern
   read as a frozen pale-green disk rather than a pulse. */
.endpoint-step[data-state="active"] .endpoint-step-dot {
  animation: endpoint-step-pulse 1.8s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}

/* Pulse uses color-mix with transparent so the ring colour
   still tracks the DS token (--zen-green-6) instead of a
   hardcoded green hex.  Falls back gracefully in browsers
   without color-mix — just no expanding ring. */
@keyframes endpoint-step-pulse {
  0%   { box-shadow: 0 0 0 0   color-mix(in srgb, var(--zen-green-6) 55%, transparent); }
  70%  { box-shadow: 0 0 0 5px color-mix(in srgb, var(--zen-green-6) 0%,  transparent); }
  100% { box-shadow: 0 0 0 0   color-mix(in srgb, var(--zen-green-6) 0%,  transparent); }
}

/* Done state — hide the solid dot, show the check instead. */
.endpoint-step[data-state="done"] .endpoint-step-dot { opacity: 0; }

.endpoint-step-check {
  position: absolute;
  width: 14px;
  height: 14px;
  color: var(--zen-green-6);
  opacity: 0;
  transition: opacity 240ms cubic-bezier(0.22, 1, 0.36, 1);
}

.endpoint-step[data-state="done"] .endpoint-step-check { opacity: 1; }

.endpoint-step-label {
  flex: 1 1 auto;
  min-width: 0;
}

.endpoint-step-time {
  font-family: var(--font-code, monospace);
  font-size: 11px;
  color: var(--text-quaternary);
  flex-shrink: 0;
  opacity: 0;
  transition: opacity 240ms cubic-bezier(0.22, 1, 0.36, 1);
}

.endpoint-step[data-state="done"] .endpoint-step-time { opacity: 1; }

/* Mobile responsive — narrow viewports stack the throws below
   the terminal.  Sticky stage shortens so the entire act
   doesn't feel like dead scroll on a phone. */
/* Below 1199px three cramped narrow columns at full width
   become too tight — the terminal spans the full width on a
   first row, and the throws + stepper share the second row
   side-by-side.  Keeps the storytelling alignment intact
   (still scroll-driven) while giving each block enough room. */
@media (max-width: 1199px) {
  .endpoint-frame-body {
    grid-template-areas:
      "terminal terminal"
      "throws   stepper";
    grid-template-columns: 1fr 1fr;
    gap: 20px;
  }
  .endpoint-terminal { grid-area: terminal; }
  .endpoint-throws  { grid-area: throws; }
  .endpoint-stepper { grid-area: stepper; }
}

@media (max-width: 900px) {
  /* Below 900px the sticky-stage choreography becomes a flat
     vertical stack — the frame stops being sticky, the stage
     just takes its content's height, and overflow becomes
     visible so nothing gets clipped off the bottom (the
     stepper kept disappearing behind Act 3 on phones). */
  .endpoint-stage {
    min-height: auto;
  }
  .endpoint-frame {
    position: static;
    height: auto;
    justify-content: flex-start;
    overflow: visible;
    padding: 96px 18px 80px;
    gap: 24px;
  }
  .endpoint-frame-body {
    grid-template-areas:
      "terminal"
      "throws"
      "stepper";
    grid-template-columns: 1fr;
    gap: 16px;
  }
}

@media (max-width: 600px) {
  .endpoint-frame {
    padding: 88px 16px 72px;
    gap: 24px;
  }
  .endpoint-h2 { font-size: var(--mkt-h4); }
  .endpoint-throw { padding: 10px 14px; }
  .endpoint-throw-label { font-size: 13px; }
  .endpoint-step { padding: 4px 10px; font-size: 12px; }
  .endpoint-stepper { padding: 12px 4px; gap: 12px; }
}

/* ── Act 3 — "Five shapes." (teal-5 editorial spread) ───────
   The bright editorial break in the page's narrative.

   Section pinning continues: this act has z-index 3 and rises
   over Act 2.  The section's own bg stays dark (so the rise
   doesn't pre-leak teal); a child .shapes-curtain carries the
   brand teal-5 and reveals via a clip-path circle bloom from
   the top-right corner — api.js grows the radius as the stage
   enters viewport, so the band reads as a deliberate reveal
   instead of a hard cut.

   Inside the sticky frame: an editorial 2-column spread.
   Left = header + the active spotlight card.  Right = a
   numbered list of all five formats with a moving accent
   anchored to the active beat.

   Five beats of ~100vh each = 500vh stage.  Each beat hands
   the spotlight to the next format (data-active swap → CSS
   crossfades the cards), and the list at right tracks along.
*/

[data-act="3"] {
  position: relative;
  z-index: 3;
  background: var(--background);
}

.shapes-stage {
  position: relative;
  min-height: 700vh;
}

/* The bloom curtain — saturated brand teal.  Hardcoded hex
   per DESIGN.md's saturated-bands exception (the DS token
   --zen-teal-5 inverts in dark mode and would mute the brand
   band).  Its clip-path is driven by api.js: 0% at start,
   150% once the bloom finishes (~14% into the stage). */
/* @pattern bloom-curtain-corner-anchor START
   Canonical saturated brand band reveal via clip-path: circle().
   The --bloom CSS variable is JS-driven from scroll progress (0% to
   ~150% across the first ~14% of stage scroll).  Corner anchor
   rotates per act so successive bands feel deliberate counterparts:
     - top-right teal in Act 3 (`at 100% 0%`)
     - bottom-left purple in Act 4 (`at 0% 100%`)
     - center for the closer (`at 50% 50%`)
   Port verbatim, recolor `background:` to your variant (#7C4FE8
   purple / #7ADAE6 teal SOLID — never a linear/radial gradient,
   per DESIGN.md §7.2) and change the corner anchor. */
.shapes-curtain {
  --bloom: 0%;
  position: absolute;
  inset: 0;
  z-index: 0;
  background: #7ADAE6;
  clip-path: circle(var(--bloom) at 100% 0%);
  will-change: clip-path;
  pointer-events: none;
}

html:not(.js) .shapes-curtain {
  /* No-JS — show the full band immediately so the act is
     legible without scroll choreography. */
  clip-path: none;
}
/* @pattern bloom-curtain-corner-anchor END */

.shapes-frame {
  position: sticky;
  top: 0;
  /* min-height (not fixed height) so that if the content ever exceeds the
     viewport — a wrapped format list on a short viewport, or fallback-font
     metrics during load — the frame grows instead of letting centred items
     overflow and collide with the bridge line below. Identical to 100vh when
     the content fits (the normal, fonts-loaded case). */
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: clamp(28px, 5vh, 56px);
  padding: clamp(96px, 14vh, 140px) clamp(24px, 6vw, 80px) clamp(56px, 9vh, 96px);
  z-index: 1;
  /* Defensive: any descendant that miscalculates its width
     (long code lines inside the spotlight body, etc.) doesn't
     create a horizontal scrollbar that breaks the visual
     symmetry of the frame's side padding. */
  overflow-x: hidden;
}

/* Laptop vertical heights (~720-768px) — the 100vh sticky frame
   doesn't have room for header + spotlight + bridge with the
   default padding, so the shape-card content overflows the
   spotlight cell and the tip overlaps the bridge below.  Compress
   the frame padding + gap at short heights so everything fits. */
@media (min-width: 900px) and (max-height: 800px) {
  .shapes-frame {
    padding: 56px clamp(24px, 6vw, 80px) 32px;
    gap: 20px;
  }
}

.shapes-header {
  display: flex;
  flex-direction: column;
  gap: 4px;
  max-width: 1280px;
  width: 100%;
}


.shapes-h2 {
  margin: 0;
  font-family: var(--font-display, var(--font-base));
  font-size: var(--mkt-h2);
  line-height: 1;
  letter-spacing: -0.025em;
  color: #1D1612;
  font-weight: 500;
}

/* Spread — spotlight on the left/centre, format list on the
   right.  Asymmetric proportions so the spotlight owns the
   visual mass and the list reads as a thin sidebar voice. */
.shapes-spread {
  display: grid;
  grid-template-columns: minmax(0, 1.45fr) minmax(220px, 280px);
  column-gap: clamp(32px, 5vw, 80px);
  align-items: stretch;
  width: 100%;
  max-width: 1280px;
  flex: 1 1 auto;
  min-height: 0;
}

/* Spotlight — stacked stage where all five cards live; only
   the active one is fully opaque + interactive.  Cards
   crossfade between beats. */
.shapes-spotlight {
  position: relative;
  display: grid;
  /* Single grid cell — all cards stack via grid-area: 1 / 1. */
}

.shape-card {
  grid-area: 1 / 1;
  display: flex;
  flex-direction: column;
  gap: clamp(20px, 2vw, 28px);
  /* Asymmetric padding: top + sides at the standard rhythm,
     bottom gets extra breathing room so the editorial tip
     line doesn't crowd the card's lower edge. */
  padding:
    clamp(28px, 2.6vw, 40px)
    clamp(28px, 2.6vw, 40px)
    clamp(36px, 3.4vw, 52px);
  border-radius: var(--radius-display, 20px);
  background: var(--background);
  color: var(--text-primary);
  opacity: 0;
  /* visibility ensures the inactive card layer is fully removed
     from paint — opacity:0 alone left subpixel artifacts that read
     as ghost text under the active card's tip. */
  visibility: hidden;
  transform: translateY(12px);
  transition:
    opacity 380ms cubic-bezier(0.22, 1, 0.36, 1),
    transform 460ms cubic-bezier(0.22, 1, 0.36, 1),
    visibility 0s linear 380ms;
  pointer-events: none;
  /* Strong down-shadow — anchors the card "above" the teal
     band, matches the /products overview card shell. */
  box-shadow: 0 28px 64px -20px rgba(0, 0, 0, 0.55);
}

.shape-card[data-active="true"] {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
  transition:
    opacity 380ms cubic-bezier(0.22, 1, 0.36, 1),
    transform 460ms cubic-bezier(0.22, 1, 0.36, 1),
    visibility 0s linear 0s;
  pointer-events: auto;
}

.shape-card-head {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.shape-card-tag {
  font-family: var(--font-code, monospace);
  font-size: 11px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--text-tertiary);
}

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

.shape-card-body {
  margin: 0;
  padding: clamp(16px, 1.6vw, 22px) clamp(18px, 1.8vw, 24px);
  border-radius: 12px;
  background: transparent;
  border: 1px solid var(--border);
  font-family: var(--font-code, monospace);
  font-size: var(--mkt-body-xs);
  line-height: 1.6;
  color: var(--text-secondary);
  white-space: pre;
  overflow: auto;
  flex: 1 1 auto;
  min-height: 0;
  /* Hide internal scrollbar — long code samples scroll
     horizontally inside the card but the scrollbar would steal
     ~15px on Windows / always-on systems and break the symmetry
     of the card's padding.  Content remains scrollable via
     swipe / wheel. */
  scrollbar-width: none;
}
.shape-card-body::-webkit-scrollbar { display: none; }

.shape-card-body code {
  font-family: inherit;
  color: inherit;
}

/* Screenshot card — stylised browser frame mocking a real
   PNG output without shipping an asset.  Inside an opaque
   card, on a sunken inner panel. */
.shape-card-body--shot {
  padding: clamp(14px, 1.2vw, 18px);
  display: flex;
  align-items: stretch;
  justify-content: center;
}

.shot-frame {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  border-radius: 8px;
  background: transparent;
  border: 1px solid var(--border);
  overflow: hidden;
}

.shot-bar {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 8px 12px;
  background: transparent;
  border-bottom: 1px solid var(--border);
}

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

.shot-canvas {
  padding: 18px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  flex: 1 1 auto;
}

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

.shot-line--xl { width: 80%; height: 14px; }
.shot-line--lg { width: 60%; }
.shot-line--md { width: 50%; }
.shot-line--sm { width: 35%; }

.shot-block {
  height: 80px;
  border-radius: 6px;
  background: linear-gradient(135deg, #7ADAE6, #7C4FE8);
  margin: 4px 0;
}

.shape-card-tip {
  margin: 0;
  font-family: var(--font-base);
  font-size: var(--mkt-body-sm);
  line-height: 1.55;
  color: var(--text-secondary);
}

/* Templates card body — different shape from the code blocks:
   a relaxed chip grid showing the built-in extractor templates
   (Images, Links, Headings, Emails, Phone numbers, Metadata,
   Tables).  Reads visually as a "menu of options" rather than
   another monospaced sample. */
.shape-card-body--templates {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 10px;
  align-content: flex-start;
  white-space: normal;
  align-items: flex-start;
}

.template-chip {
  display: inline-flex;
  align-items: center;
  padding: 8px 14px;
  border-radius: var(--radius-full, 999px);
  background: transparent;
  border: 1px solid var(--border);
  font-family: var(--font-base);
  font-size: 13px;
  font-weight: 500;
  color: var(--text-secondary);
  white-space: nowrap;
}

/* Format list — vertical sidebar, dark text on teal.  Active
   row gets the full label + a long rule that extends out;
   inactive rows dim back. */
.shapes-list {
  display: flex;
  align-items: stretch;
}
.shapes-list ol {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: clamp(10px, 1vh, 14px);
  align-self: flex-start;
  padding-top: clamp(8px, 1vw, 16px);
  width: 100%;
}

.shapes-list-item {
  display: grid;
  grid-template-columns: auto minmax(20px, 1fr) auto;
  align-items: center;
  gap: 14px;
  font-family: var(--font-base);
  /* Hardcoded warm dark — same rationale as .eyebrow /
     .shapes-h2 / body.is-on-teal chrome.  The teal band is brand-
     coloured (theme-invariant); text on it must be dark in BOTH
     tech and BU modes.  var(--background) would flip to light. */
  color: #1D1612;
  opacity: 0.45;
  transition: opacity 260ms cubic-bezier(0.22, 1, 0.36, 1);
}

.shapes-list-item[data-active="true"] {
  opacity: 1;
}

.shapes-list-num {
  font-family: var(--font-code, monospace);
  font-size: 11px;
  letter-spacing: 0.22em;
  color: #1D1612;
}

.shapes-list-rule {
  height: 1px;
  background: #1D1612;
  opacity: 0.3;
  transform: scaleX(0.4);
  transform-origin: left;
  transition: transform 360ms cubic-bezier(0.22, 1, 0.36, 1),
              opacity 260ms cubic-bezier(0.22, 1, 0.36, 1);
}

.shapes-list-item[data-active="true"] .shapes-list-rule {
  transform: scaleX(1);
  opacity: 0.55;
}

.shapes-list-name {
  font-size: var(--mkt-body);
  font-weight: 500;
  color: #1D1612;
  letter-spacing: -0.005em;
  white-space: nowrap;
}

/* Bridge line at the bottom of Act 3 — a quiet pointer to
   the Extract spoke for buyers who need typed JSON / schemas
   / drift alerts.  Sits on the teal band so it inherits the
   inverted text colour (--background = page dark). */
.shapes-bridge {
  margin: clamp(28px, 4vh, 48px) 0 0;
  max-width: 1280px;
  width: 100%;
  font-family: var(--font-base);
  font-size: var(--text-small, 13px);
  letter-spacing: -0.005em;
  /* Hardcoded warm dark — sits on the teal band, theme-invariant. */
  color: #1D1612;
  opacity: 0.7;
}

.shapes-bridge-link {
  color: #1D1612;
  font-weight: 500;
  border-bottom: 1px solid currentColor;
  padding-bottom: 1px;
  margin-left: 6px;
  transition: opacity 200ms ease;
}

.shapes-bridge-link:hover {
  opacity: 0.85;
}

/* ── Topbar adaptation on the teal band ─────────────────────
   When body has `is-on-teal` (api.js toggles it when Act 3
   covers the topbar slot), the chrome flips its colour: logo
   inverts to black, nav text becomes dark, text-shadow drops
   so legibility doesn't fight the bright bg. */

/* Hardcoded warm dark #1D1612 (not var(--background), which flips
   per theme) so the topbar reads dark on teal in BOTH tech and BU
   modes.  The teal band itself is theme-invariant brand colour, so
   the chrome inversion against it must be theme-invariant too. */



/* Social pills also flip so the brand marks + metrics read on
   the bright teal band.  Hover stays as a subtle dark overlay. */

/* CTA pill flips to brand purple-7 so it stays legible on the
   teal band (a teal pill would disappear).  Saturated brand
   anchor — hardcoded #7C4FE8 per DESIGN.md's saturated-bands
   rule. */

/* Mobile responsive — narrow viewports collapse the spread to
   single-column.  The list moves below the spotlight (still
   horizontal-scrollable for compactness). */
@media (max-width: 1199px) {
  .shapes-spread {
    grid-template-columns: minmax(0, 1fr);
    row-gap: clamp(24px, 4vh, 36px);
  }
  .shapes-list ol {
    flex-direction: row;
    flex-wrap: wrap;
    gap: 8px 22px;
    padding-top: 0;
  }
  .shapes-list-item {
    grid-template-columns: auto auto;
    gap: 6px;
  }
  .shapes-list-item .shapes-list-rule { display: none; }
}

@media (max-width: 900px) {
  .shapes-stage { min-height: 600vh; }
  .shapes-h2 { font-size: var(--mkt-h2); }
  .shape-card { padding: 22px 22px 32px; gap: 16px; }
  .shape-card-name { font-size: 26px; }
  .shapes-frame { gap: 24px; padding: 80px 18px 56px; }
  /* On mobile the frame's left padding is 18, and each card adds
     22 of internal padding before its content — so card content
     starts 40 from the viewport edge while the header h2
     ("Seven outputs.") sits at 18.  Visually the h2 hangs 22
     past the card content on the left while the card's right
     edge stays at 18, breaking the symmetry Pablo flagged.
     Inset the header by the card's own padding so the h2 lines
     up with the card content on both sides. */
  .shapes-header { padding: 0 22px; }
}

@media (max-width: 600px) {
  .shapes-stage { min-height: 540vh; }
  .shapes-h2 { font-size: var(--mkt-h3); }
  .shape-card-body { font-size: 12px; }
  .shapes-list-name { font-size: 13px; }
}

/* ── Act 4 — "Three switches." (purple-7 band, right-aligned)
   The control-surface beat.  Rhythm break: text aligns RIGHT
   for the first time on the page.  Saturated purple-7 band
   (brand hex hardcoded — DS token inverts in dark mode and
   would mute the bloom).  Curtain blooms from bottom-left,
   mirroring Act 3's top-right teal bloom so the two saturated
   acts feel like deliberate counterparts.

   Three beats of ~100vh = 300vh stage.  Each beat owns a
   distinct visual (stealth rings / proxies globe / browser
   cursor) and a slice of editorial copy.  Inactive visuals
   are static + dimmed so the reader's eye stays on the
   currently animating one. */

[data-act="4"] {
  position: relative;
  z-index: 4;
  background: var(--background);
}

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

/* Bloom curtain — purple-7 brand hex hardcoded.  Clip-path
   circle grows from bottom-left over the first ~14% of the
   stage's scroll. */
.switches-curtain {
  --bloom: 0%;
  position: absolute;
  inset: 0;
  z-index: 0;
  background: #7C4FE8;
  clip-path: circle(var(--bloom) at 0% 100%);
  will-change: clip-path;
  pointer-events: none;
}

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

.switches-frame {
  position: sticky;
  top: 0;
  height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: clamp(96px, 14vh, 140px) clamp(24px, 6vw, 80px) clamp(56px, 9vh, 96px);
  z-index: 1;
  overflow: hidden;
}

/* Two-column spread.  Visual on the left occupies the bigger
   slice; editorial copy lives on the right with right-aligned
   text — the rhythm break Pablo asked for.  Positioned + z-index
   so it paints above the sibling curtain (z-index 0). */
.switches-spread {
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-columns: minmax(0, 1.3fr) minmax(280px, 0.7fr);
  column-gap: clamp(32px, 5vw, 80px);
  align-items: center;
  width: 100%;
  max-width: 1280px;
}

/* Visual stage — square-ish, holds all three visuals stacked
   in a single grid cell so they crossfade in place. */
.switches-visuals {
  position: relative;
  display: grid;
  aspect-ratio: 1 / 1;
  max-width: 600px;
  width: 100%;
  margin: 0 auto;
}

.switch-visual {
  grid-area: 1 / 1;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  opacity: 0;
  transform: scale(0.96);
  transition:
    opacity 420ms cubic-bezier(0.22, 1, 0.36, 1),
    transform 500ms cubic-bezier(0.22, 1, 0.36, 1);
}

.switch-visual[data-active="true"] {
  opacity: 1;
  transform: scale(1);
}

/* Right column — editorial typography, right-aligned. */
.switches-copy {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  text-align: right;
  gap: clamp(24px, 4vh, 40px);
}

.switches-header {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 4px;
}


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

/* Per-beat copy block — only the active beat is visible; the
   others stack behind it and crossfade.  Single grid cell so
   the column height stays steady. */
.switches-beats {
  display: grid;
  width: 100%;
  max-width: 360px;
}

.switch-beat {
  grid-area: 1 / 1;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 10px;
  opacity: 0;
  transform: translateY(10px);
  transition:
    opacity 360ms cubic-bezier(0.22, 1, 0.36, 1),
    transform 460ms cubic-bezier(0.22, 1, 0.36, 1);
}

.switch-beat[data-active="true"] {
  opacity: 1;
  transform: translateY(0);
}

.switch-beat-tag {
  font-family: var(--font-code, monospace);
  font-size: 11px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--text-primary);
  opacity: 0.6;
}

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

.switch-beat-body {
  margin: 0;
  font-family: var(--font-base);
  font-size: var(--mkt-body);
  line-height: 1.55;
  color: var(--text-primary);
  opacity: 0.78;
}

/* Progress dots — three pills under the beat copy.  Active
   dot stretches into a longer bar (delightful little move). */
.switches-progress {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  gap: 8px;
  align-items: center;
}

.switches-progress-dot {
  width: 10px;
  height: 10px;
  border-radius: var(--radius-full, 999px);
  background: var(--text-primary);
  opacity: 0.32;
  transition:
    width 360ms cubic-bezier(0.22, 1, 0.36, 1),
    opacity 240ms cubic-bezier(0.22, 1, 0.36, 1);
}

.switches-progress-dot[data-active="true"] {
  width: 32px;
  opacity: 1;
}

/* ── Visual 1 — STEALTH (concentric rings + central core) ───
   Four dashed rings rotating at different speeds, like a
   sonar / camouflage scan.  Core dot pulses. */
.stealth-art {
  width: 100%;
  max-width: 480px;
  height: auto;
}

.stealth-ring {
  fill: none;
  stroke: var(--text-primary);
  stroke-width: 1.2;
  opacity: 0.5;
  transform-origin: 160px 160px;
}
.stealth-ring--1 { stroke-dasharray: 4 6;  opacity: 0.75; }
.stealth-ring--2 { stroke-dasharray: 12 6; opacity: 0.55; }
.stealth-ring--3 { stroke-dasharray: 20 10; opacity: 0.42; }
.stealth-ring--4 { stroke-dasharray: 30 14; opacity: 0.3;  }

.stealth-core {
  fill: var(--text-primary);
}

/* Only animate while this visual is the active one — keeps
   the inactive rings static so the active one feels alive
   in contrast. */
.switch-visual[data-shape="0"][data-active="true"] .stealth-ring--1 {
  animation: stealth-spin 26s linear infinite;
}
.switch-visual[data-shape="0"][data-active="true"] .stealth-ring--2 {
  animation: stealth-spin 40s linear infinite reverse;
}
.switch-visual[data-shape="0"][data-active="true"] .stealth-ring--3 {
  animation: stealth-spin 58s linear infinite;
}
.switch-visual[data-shape="0"][data-active="true"] .stealth-ring--4 {
  animation: stealth-spin 84s linear infinite reverse;
}
.switch-visual[data-shape="0"][data-active="true"] .stealth-core {
  animation: stealth-pulse 2.4s ease-in-out infinite;
}

@keyframes stealth-spin {
  to { transform: rotate(360deg); }
}
@keyframes stealth-pulse {
  0%, 100% { transform: scale(1);   opacity: 1; }
  50%      { transform: scale(2.2); opacity: 0.4; }
}

/* ── Visual 2 — PROXIES (globe with pulsing IP dots) ─────── */
.proxies-art {
  width: 100%;
  max-width: 480px;
  height: auto;
}

.globe-line {
  fill: none;
  stroke: var(--text-primary);
  stroke-width: 1;
  opacity: 0.42;
}
.globe-line--rim { stroke-width: 1.4; opacity: 0.55; }

.proxy-dot {
  fill: #7ADAE6; /* brand teal accent — counter to the band */
  transform-origin: center;
  transform-box: fill-box;
}

.switch-visual[data-shape="1"][data-active="true"] .proxy-dot {
  animation: proxy-pulse 2.4s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}

.switch-visual[data-shape="1"][data-active="true"] .proxy-dot--1 { animation-delay: 0s;    }
.switch-visual[data-shape="1"][data-active="true"] .proxy-dot--2 { animation-delay: 0.45s; }
.switch-visual[data-shape="1"][data-active="true"] .proxy-dot--3 { animation-delay: 0.90s; }
.switch-visual[data-shape="1"][data-active="true"] .proxy-dot--4 { animation-delay: 1.35s; }
.switch-visual[data-shape="1"][data-active="true"] .proxy-dot--5 { animation-delay: 1.80s; }

@keyframes proxy-pulse {
  0%, 100% { transform: scale(1);   opacity: 0.95; }
  50%      { transform: scale(1.8); opacity: 0.55; }
}

/* ── Visual 3 — BROWSER (window + animated cursor) ──────── */
.browser-mock {
  width: 100%;
  max-width: 480px;
  aspect-ratio: 4 / 3;
  display: flex;
  flex-direction: column;
  border-radius: 12px;
  background: transparent;
  border: 1px solid var(--border);
  overflow: hidden;
  box-shadow: 0 24px 48px -16px rgba(13, 8, 5, 0.4);
}

.browser-bar {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 14px;
  background: transparent;
  border-bottom: 1px solid var(--border);
}

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

.browser-url {
  margin-left: 10px;
  font-family: var(--font-code, monospace);
  font-size: 11px;
  color: var(--text-tertiary);
  letter-spacing: 0.04em;
}

.browser-canvas {
  position: relative;
  flex: 1 1 auto;
  padding: 24px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}

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

.browser-line--lg { width: 70%; height: 14px; }
.browser-line--md { width: 55%; }

.browser-input {
  display: flex;
  align-items: center;
  padding: 10px 14px;
  border-radius: 8px;
  background: var(--background);
  border: 1px solid var(--border-default);
  font-family: var(--font-base);
  font-size: 12px;
  color: var(--text-secondary);
  margin-top: 4px;
}

.browser-cta {
  align-self: flex-start;
  padding: 8px 16px;
  border-radius: var(--radius-full, 999px);
  background: var(--text-primary);
  color: var(--background);
  font-family: var(--font-base);
  font-size: 12px;
  font-weight: 500;
  margin-top: 4px;
}

.browser-cursor {
  position: absolute;
  width: 22px;
  height: 22px;
  fill: #7ADAE6;
  stroke: var(--background);
  stroke-width: 1.5;
  /* Default position — over the input. */
  left: 40%;
  top: 50%;
}

.switch-visual[data-shape="2"][data-active="true"] .browser-cursor {
  animation: browser-cursor-path 5.6s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}

@keyframes browser-cursor-path {
  0%, 8%   { left: 50%; top: 38%; transform: scale(1); }
  18%      { left: 50%; top: 58%; transform: scale(1); }
  22%      { left: 50%; top: 58%; transform: scale(0.82); }
  26%      { left: 50%; top: 58%; transform: scale(1); }
  44%, 56% { left: 16%; top: 78%; transform: scale(1); }
  60%      { left: 16%; top: 78%; transform: scale(0.82); }
  64%      { left: 16%; top: 78%; transform: scale(1); }
  92%, 100%{ left: 50%; top: 38%; transform: scale(1); }
}

/* ── Mobile responsive ─────────────────────────────────────
   Below 1024px the spread collapses to single column with
   the visual stacked above the editorial column.  Text still
   right-aligned (rhythm break preserved).  Stage shortens. */
@media (max-width: 1023px) {
  .switches-spread {
    grid-template-columns: 1fr;
    row-gap: clamp(24px, 4vh, 36px);
  }
  .switches-visuals { max-width: 380px; }
  .switches-stage { min-height: 280vh; }
}

@media (max-width: 600px) {
  .switches-frame {
    padding: 80px 18px 56px;
  }
  .switches-visuals { max-width: 280px; }
  .switches-h2 { font-size: var(--mkt-h3); }
  .switch-beat-name { font-size: 26px; }
  .switch-beat-body { font-size: 14px; }
  .switches-stage { min-height: 260vh; }
}

/* ── Act 5 — "Wherever you scrape." (no band, credits roll) ─
   After two saturated bright bands (teal-5, purple-7) the
   page comes back to the dark warm bg.  The restraint IS the
   rhythm move.  No curtain, no bg accent — just editorial
   typography on `var(--background)`.

   Layout: a sticky two-column spread.  Left = editorial
   header + a list of the five verticals (active one tracks
   the scroll).  Right = a credits-roll style list of 31 sites
   grouped by vertical, each row showing the site name (large
   display) and the anti-bot protection it ships with (small
   mono on the right).  The roll's inner list translates
   upward as the reader scrolls, with mask gradients top/
   bottom so rows fade in and out.

   Storytelling link to Act 4: the protection tags map 1:1 to
   the three switches the reader just saw — Cloudflare /
   DataDome / Akamai → Stealth + Premium proxy; anti-scrape
   headers → Browser. */

[data-act="5"] {
  position: relative;
  z-index: 5;
  background: var(--background);
}

.atlas-stage {
  position: relative;
  min-height: 500vh;
}

.atlas-frame {
  position: sticky;
  top: 0;
  height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: clamp(96px, 14vh, 140px) clamp(24px, 6vw, 80px) clamp(56px, 9vh, 96px);
  overflow: hidden;
}

.atlas-spread {
  display: grid;
  grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.15fr);
  column-gap: clamp(40px, 7vw, 96px);
  align-items: stretch;
  width: 100%;
  max-width: 1280px;
  height: 100%;
  max-height: 720px;
}

/* Left column — editorial header + vertical list. */
.atlas-side {
  display: flex;
  flex-direction: column;
  gap: clamp(20px, 3vh, 32px);
  justify-content: center;
}


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

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

/* Vertical list — tracks the active group in the roll.
   Active row brightens; inactive rows dim.  A short rule on
   the left grows to mark the active row (like Act 3's list,
   horizontal version). */
.atlas-list {
  list-style: none;
  margin: 0;
  padding: clamp(12px, 1.4vh, 18px) 0 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
  border-top: 1px solid var(--border);
}

.atlas-list-item {
  appearance: none;
  background: transparent;
  border: none;
  padding: 4px 0;
  text-align: left;
  width: 100%;
  display: grid;
  grid-template-columns: auto auto 1fr auto;
  align-items: center;
  gap: 14px;
  font-family: var(--font-base);
  color: var(--text-quaternary);
  cursor: pointer;
  transition: color 260ms cubic-bezier(0.22, 1, 0.36, 1);
}

.atlas-list-item:hover {
  color: var(--text-secondary);
}

.atlas-list-item:focus-visible {
  outline: 1px solid var(--border-accent);
  outline-offset: 4px;
  border-radius: 2px;
}

.atlas-list-item[data-active="true"] {
  color: var(--text-primary);
}

.atlas-list-item::before {
  content: "";
  width: 12px;
  height: 1px;
  background: currentColor;
  opacity: 0.4;
  transform: scaleX(0.5);
  transform-origin: left;
  transition: transform 320ms cubic-bezier(0.22, 1, 0.36, 1),
              opacity 240ms cubic-bezier(0.22, 1, 0.36, 1);
}

.atlas-list-item:hover::before {
  transform: scaleX(0.8);
  opacity: 0.7;
}

.atlas-list-item[data-active="true"]::before {
  transform: scaleX(1);
  opacity: 1;
}

.atlas-list-num {
  font-family: var(--font-code, monospace);
  font-size: 11px;
  letter-spacing: 0.22em;
}

.atlas-list-name {
  font-size: var(--mkt-body-sm);
  font-weight: 500;
  letter-spacing: -0.005em;
}

.atlas-list-count {
  font-family: var(--font-code, monospace);
  font-size: 11px;
  opacity: 0.6;
}

/* Right column — the credits roll.  Overflow hidden so the
   inner list translates up out of view; mask gradient fades
   the top and bottom so rows enter/exit softly. */
.atlas-roll {
  position: relative;
  height: 100%;
  overflow: hidden;
  /* Fade rows in at the top and out at the bottom. */
  -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%
  );
}

.atlas-roll-list {
  --roll-translate: 0px;
  display: flex;
  flex-direction: column;
  gap: clamp(24px, 3vh, 36px);
  transform: translate3d(0, var(--roll-translate), 0);
  will-change: transform;
}

.atlas-group {
  display: flex;
  flex-direction: column;
}

.atlas-group-head {
  display: flex;
  align-items: baseline;
  gap: 14px;
  padding: 0 0 clamp(12px, 1.4vh, 18px);
  border-bottom: 1px solid var(--border);
  margin-bottom: clamp(8px, 1vh, 12px);
}

.atlas-group-num {
  font-family: var(--font-code, monospace);
  font-size: 11px;
  letter-spacing: 0.22em;
  color: var(--text-tertiary);
}

.atlas-group-name {
  font-family: var(--font-display, var(--font-base));
  font-size: clamp(18px, 1.6vw, 22px);
  letter-spacing: -0.012em;
  font-weight: 500;
  color: var(--text-primary);
}

.atlas-group-count {
  margin-left: auto;
  font-family: var(--font-code, monospace);
  font-size: 11px;
  color: var(--text-quaternary);
  text-transform: uppercase;
  letter-spacing: 0.18em;
}

.atlas-group-rows {
  list-style: none;
  margin: 0;
  padding: 0;
}

.atlas-row {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: baseline;
  gap: 14px;
  padding: clamp(4px, 0.6vh, 8px) 0;
}

.atlas-row-name {
  font-family: var(--font-display, var(--font-base));
  font-size: var(--mkt-h4);
  line-height: 1.15;
  letter-spacing: -0.018em;
  color: var(--text-primary);
  font-weight: 500;
}

.atlas-row-tag {
  font-family: var(--font-code, monospace);
  font-size: var(--mkt-body-xs);
  letter-spacing: 0.04em;
  color: var(--text-tertiary);
}

/* Below 1024px collapse to single column — header on top,
   sites listed below as a static flat list (no roll, no
   sticky frame).  The credits-roll choreography only really
   works at desktop sticky-stage proportions; on a phone the
   reader scrolls through the groups naturally, all 31 sites
   stacked in document order. */
@media (max-width: 1023px) {
  .atlas-stage { min-height: auto; }
  .atlas-frame {
    position: static;
    height: auto;
    overflow: visible;
    justify-content: flex-start;
    align-items: flex-start;
    padding: 104px 24px 88px;
  }
  .atlas-spread {
    grid-template-columns: 1fr;
    row-gap: clamp(28px, 4vh, 40px);
    max-height: none;
    height: auto;
  }
  .atlas-roll {
    height: auto;
    overflow: visible;
    -webkit-mask-image: none;
            mask-image: none;
  }
  .atlas-roll-list {
    transform: none !important;
  }
}

@media (max-width: 600px) {
  .atlas-frame { padding: 96px 16px 80px; gap: 24px; }
  .atlas-h2 { font-size: var(--mkt-h2); }
  .atlas-row-name { font-size: var(--mkt-h4); }
}

/* ── Act 6 — "In your language." (no band, dealt-in grid) ──
   After Act 5's restraint, energy returns through a dense 4×2
   grid of code panels — one per supported language.  Each card
   reveals as the reader scrolls into the act (staggered deal-
   in by data-card index, opacity + translateY easing in).
   Once all eight are landed, a wave runs across the cards
   highlighting the shared endpoint URL — visual moment that
   says "different syntax, same call".

   Layout: sticky-stage 300vh.  Header centred at the top of
   the frame, grid below.  No saturated band; the dark warm
   bg keeps the spotlight on the typography of the snippets. */

[data-act="6"] {
  position: relative;
  z-index: 6;
  background: var(--background);
}

.codes-stage {
  position: relative;
  /* Three tiers × 160vh of scroll each = 480vh total.  Each
     tier band drives one crossfade + deal-in + wave cycle. */
  min-height: 480vh;
}

.codes-frame {
  position: sticky;
  top: 0;
  height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  justify-content: center;
  gap: clamp(24px, 3.5vh, 36px);
  padding: clamp(96px, 14vh, 140px) clamp(24px, 6vw, 80px) clamp(56px, 9vh, 96px);
  overflow: hidden;
}

/* Header sits BELOW the grid (CSS order: 2).  Splits into two
   parts horizontally: left = editorial text (eyebrow + h2 +
   sub), right = tier-bar segmented control.  Earlier the
   tier-bar lived on its own row above the grid and Pablo
   flagged it pushing off-screen at laptop heights (the frame
   uses justify-content: center, so overflow gets distributed
   above + below).  Folding the bar into the same row as the
   editorial copy removes one stack-block, fits in 100vh, and
   restores the bottom padding around the h2. */
.codes-header {
  order: 2;
  display: flex;
  flex-direction: row;
  align-items: flex-end;
  justify-content: space-between;
  gap: clamp(20px, 3vw, 48px);
  width: 100%;
  max-width: 1280px;
  margin: 0 auto;
  flex-wrap: wrap;
}

.codes-header-text {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 6px;
  text-align: left;
  min-width: 0;
}


.codes-h2 {
  margin: 0;
  font-family: var(--font-display, var(--font-base));
  font-size: var(--mkt-h3);
  line-height: 1.06;
  letter-spacing: -0.02em;
  color: var(--text-primary);
  font-weight: 500;
}

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

/* Grid — 4 columns wide on desktop.  Cards have a fixed aspect
   so the rows line up cleanly even with variable code length. */
.codes-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: clamp(14px, 1.4vw, 20px);
  width: 100%;
  max-width: 1280px;
}

.code-card {
  --card-progress: 0;
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding: clamp(18px, 1.6vw, 22px) clamp(18px, 1.6vw, 22px) clamp(22px, 2vw, 26px);
  border-radius: var(--radius-display, 20px);
  background: var(--background);
  box-shadow: 0 28px 64px -20px rgba(0, 0, 0, 0.55);
  min-height: clamp(180px, 22vh, 220px);
  overflow: hidden;
  /* Hide the system cursor — the O-cursor SVG follower
     (cursorSelector in api.js) takes over and rotates while
     hovering a card. */
  cursor: pointer;
  /* Each card starts hidden + slightly below; api.js writes
     --card-progress per card (0 → 1) as its stagger window
     plays. */
  opacity: var(--card-progress);
  transform: translateY(calc((1 - var(--card-progress)) * 18px));
  transition:
    border-color 240ms cubic-bezier(0.22, 1, 0.36, 1),
    transform 200ms cubic-bezier(0.22, 1, 0.36, 1);
}

/* Top-edge accent — borrowed from the home's .code-block
   pattern (Act 5 of home).  2px gradient bar that fades from
   brand purple → brand cyan → transparent.  Hidden by default
   so the cards stay calm; lights up on hover so the reader's
   eye lands on the active card. */
.code-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;
}

.code-card:hover {
  transform: translateY(calc((1 - var(--card-progress)) * 18px - 2px));
  box-shadow: 0 32px 72px -18px rgba(0, 0, 0, 0.65);
}

.code-card:hover::before {
  opacity: 1;
}

.code-card-head {
  display: flex;
  align-items: center;
  gap: 10px;
}

.code-card-icon {
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--text-primary);
  object-fit: contain;
  flex-shrink: 0;
}

.code-card-name {
  font-family: var(--font-base);
  font-size: var(--mkt-body-sm);
  font-weight: 500;
  letter-spacing: -0.005em;
  color: var(--text-primary);
}

.code-card-body {
  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;
  min-height: 0;
}

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

/* The shared endpoint URL — wrapped in a span across every
   card.  Default state: same colour as the surrounding code
   (no visual emphasis).  When the wave fires, every shared
   span pulses with a purple background sweep + brand purple
   text colour.  Brand purple-7 hex hardcoded per the
   saturated-bands DESIGN.md exception (this is an accent
   sweep, not a band). */
.code-shared {
  display: inline;
  position: relative;
  padding: 1px 3px;
  margin: 0 -3px;
  border-radius: 3px;
  background: transparent;
  color: inherit;
  transition: background 360ms cubic-bezier(0.22, 1, 0.36, 1),
              color 280ms cubic-bezier(0.22, 1, 0.36, 1);
}

[data-act="6"][data-wave="on"] .code-shared {
  background: rgba(124, 79, 232, 0.20);
  color: #7C4FE8;
}

/* Mobile responsive — collapses through 2-col → 1-col, drops
   the sticky frame so all eight cards flow in document order.
   Reveal becomes natural scroll (IntersectionObserver could
   add per-card stagger later if needed). */
@media (max-width: 1199px) {
  .codes-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 767px) {
  .codes-stage { min-height: auto; }
  .codes-frame {
    position: static;
    height: auto;
    overflow: visible;
    justify-content: flex-start;
    padding: 96px 18px 80px;
    gap: 28px;
  }
  .codes-grid { grid-template-columns: 1fr; gap: 14px; }
  .codes-h2 { font-size: var(--mkt-h2); }
  .code-card {
    /* Reveal everything statically on mobile — scroll-driven
       stagger doesn't apply without sticky stage. */
    --card-progress: 1;
  }
}

/* ── Three-doorway sticky stage ─────────────────────────────
   Same composition (4×2 card grid + scroll-driven deal-in +
   shared-element highlight wave) is reused across three
   datasets so the reader meets each doorway in turn without
   the page repeating itself visually.

   Stage height bumped to 480vh (3 tiers × 160vh).  Within the
   sticky frame, a `.codes-stack` holds three `.codes-grid`s
   stacked in the same grid cell — only the active one shows.
   A `.codes-tier-bar` segmented control above the stack
   indicates which doorway is on screen + lets the reader
   jump.  api.js owns the choreography:

     - tierIndex = floor(progress * 3), clamped to [0, 2]
     - data-active flips between grids + tier indicators
     - --card-progress per card stagger replays each tier's
       local 0→1 progress so cards deal in fresh per tier
     - data-wave flips on once the deal-in completes each
       time, pulsing the tier's shared segment. */
/* Doorway selector = the DS .seg-control atom (components.css).
   .codes-tier-bar/.codes-tier keep layout + the number prefix only. */
.codes-tier-bar { flex-shrink: 0; }

.codes-tier-num {
  font-family: var(--font-code, monospace);
  font-size: 10px;
  letter-spacing: 0.12em;
  opacity: 0.55;
}

/* Card stack — three grids superimposed via grid-area.  The
   stack takes the max height of any grid in its layout, so
   the visible grid sets the bounding box; inactive grids
   fade out without shifting layout. */
.codes-stack {
  position: relative;
  display: grid;
  grid-template-areas: "stack";
  width: 100%;
  max-width: 1280px;
  margin: 0 auto;
  order: 1;
}

.codes-stack > .codes-grid {
  grid-area: stack;
  opacity: 0;
  pointer-events: none;
  visibility: hidden;
  transition:
    opacity 360ms cubic-bezier(0.22, 1, 0.36, 1),
    visibility 0s linear 360ms;
}

.codes-stack > .codes-grid[data-active="true"] {
  opacity: 1;
  pointer-events: auto;
  visibility: visible;
  transition:
    opacity 420ms cubic-bezier(0.22, 1, 0.36, 1),
    visibility 0s linear 0s;
}

@media (max-width: 1199px) {
  .codes-tier-bar { font-size: 12px; }
  .codes-tier { padding: 6px 12px; }
}

@media (max-width: 600px) {
  .codes-tier-bar { gap: 2px; padding: 3px; }
  .codes-tier { padding: 6px 10px; gap: 6px; }
  .codes-tier-num { display: none; }
}

/* ── Act 7 — Closer.  Purple-7 reprise + centre bloom ──────
   Final beat of the page.  Saturated brand band returns
   (purple-7 reprise after Act 4) but with two compositional
   differences: the bloom anchor is the CENTRE of the
   viewport (not a corner), and the act is single-beat with no
   sub-choreography.

   Spotlight effect: clip-path circle grows from 0% at centre
   to ~130%, leaving the four extreme corners in a subtle dark
   vignette so the act feels framed.  Topbar hides while this
   act dominates the viewport (initTopbarHide in api.js) so
   the CTAs own the stage. */

[data-act="7"] {
  position: relative;
  z-index: 7;
  background: var(--background);
}

.closer-stage {
  position: relative;
  min-height: 180vh;
}

.closer-curtain {
  --bloom: 0%;
  position: absolute;
  inset: 0;
  z-index: 0;
  /* No purple bg — the closer sits flat on the page background.
     The bloom mechanic stays wired (JS still drives --bloom) but
     renders nothing visible. */
  background: transparent;
  clip-path: circle(var(--bloom) at 50% 50%);
  will-change: clip-path;
  pointer-events: none;
}

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

.closer-frame {
  position: sticky;
  top: 0;
  height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  gap: clamp(16px, 2vh, 24px);
  padding: clamp(96px, 14vh, 140px) clamp(24px, 6vw, 80px) clamp(56px, 9vh, 96px);
  z-index: 1;
  overflow: hidden;
}

.closer-frame > *:not(.closer-curtain) {
  position: relative;
  z-index: 1;
}

.closer-kicker {
  margin: 0;
  font-family: var(--font-base);
  font-size: var(--text-tiny, 11px);
  font-weight: 500;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--text-primary);
  opacity: 0.55;
}

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

.closer-sub {
  margin: 0;
  max-width: 540px;
  font-family: var(--font-base);
  font-size: var(--mkt-body);
  line-height: 1.6;
  color: var(--text-primary);
  opacity: 0.78;
}

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

/* Primary CTA inherits .btn-marketing (teal pill) from chrome
   — high contrast against the purple band. */
.closer-primary {
  /* Marketing pill already sized; no overrides needed. */
}

/* Secondary CTA — ghost link with an arrow that nudges on
   hover.  Same dark-on-purple text contrast as the kicker
   so it doesn't fight the primary. */
.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-primary);
  text-decoration: none;
  border-bottom: 1px solid transparent;
  cursor: pointer;
  transition: opacity 200ms ease, border-color 200ms ease;
  opacity: 0.78;
}

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

.closer-secondary:hover {
  opacity: 1;
  border-bottom-color: rgba(255, 255, 255, 0.45);
}

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

/* Mobile — frame stays sticky (single-beat closer fits
   comfortably in 100vh on most phones).  Just trim type and
   padding for narrow viewports. */
@media (max-width: 767px) {
  
  .closer-sub { font-size: 15px; }
  .closer-frame { padding: 96px 18px 80px; gap: 14px; }
  .closer-stage { min-height: 140vh; }
}

/* ── Wide-desktop scaling (1600 / 1920 / 2400 tiers) ───────
   Mirrors the same wide-desktop pattern used on home and
   products: as the viewport opens up, the hero gets more
   vertical breathing room, the H1 width caps loosen, and the
   widget's max-width grows so the segments row doesn't feel
   cramped at iMac-class widths. */
@media (min-width: 1600px) {
  .api-hero { max-width: 920px; gap: 30px; }
  
  .api-try { max-width: 720px; }
  .endpoint-frame-body {
    max-width: 1400px;
    grid-template-columns: minmax(0, 1.4fr) 240px 280px;
  }
  .endpoint-frame-header { max-width: 1400px; }
  .endpoint-h2 { font-size: 44px; }
  .shapes-h2 { font-size: 72px; }
  .shapes-spread { max-width: 1400px; }
  .switches-h2 { font-size: 72px; }
  .switches-spread { max-width: 1400px; }
  .switches-visuals { max-width: 780px; }
  .stealth-art,
  .proxies-art,
  .browser-mock { max-width: 620px; }
  .atlas-h2 { font-size: 72px; }
  .atlas-spread { max-width: 1400px; max-height: 820px; }
  .atlas-row-name { font-size: 40px; }
  .codes-h2 { font-size: 44px; }
  .codes-grid { max-width: 1400px; }
  .codes-header { max-width: 1400px; }
  
}
@media (min-width: 1920px) {
  .api-hero { max-width: 1040px; gap: 34px; }
  
  .api-try { max-width: 760px; padding: 32px; }
  .endpoint-frame-body {
    max-width: 1560px;
    grid-template-columns: minmax(0, 1.4fr) 260px 300px;
  }
  .endpoint-frame-header { max-width: 1560px; }
  .endpoint-h2 { font-size: 52px; }
  .endpoint-throw-label { font-size: 15px; }
  .endpoint-step { font-size: 14px; }
  .shapes-h2 { font-size: 84px; }
  .shapes-spread { max-width: 1560px; }
  .switches-h2 { font-size: 84px; }
  .switches-spread { max-width: 1560px; }
  .switches-visuals { max-width: 880px; }
  .stealth-art,
  .proxies-art,
  .browser-mock { max-width: 720px; }
  .atlas-h2 { font-size: 84px; }
  .atlas-spread { max-width: 1560px; max-height: 900px; }
  .atlas-row-name { font-size: 46px; }
  .atlas-group-name { font-size: 24px; }
  .codes-h2 { font-size: 52px; }
  .codes-grid { max-width: 1560px; }
  .codes-header { max-width: 1560px; }
  
}
@media (min-width: 2400px) {
  .api-hero { max-width: 1160px; }
  
  .api-try { max-width: 820px; }
  .endpoint-frame-body {
    max-width: 1760px;
    grid-template-columns: minmax(0, 1.4fr) 300px 340px;
  }
  .endpoint-frame-header { max-width: 1760px; }
  .endpoint-h2 { font-size: 60px; }
  .shapes-h2 { font-size: 100px; }
  .shapes-spread { max-width: 1760px; }
  .switches-h2 { font-size: 100px; }
  .switches-spread { max-width: 1760px; }
  .switches-visuals { max-width: 1000px; }
  .stealth-art,
  .proxies-art,
  .browser-mock { max-width: 840px; }
  .atlas-h2 { font-size: 100px; }
  .atlas-spread { max-width: 1760px; max-height: 1020px; }
  .atlas-row-name { font-size: 56px; }
  .atlas-group-name { font-size: 28px; }
  .codes-h2 { font-size: 60px; }
  .codes-grid { max-width: 1760px; }
  .codes-header { max-width: 1760px; }
  
}

/* ── Mobile responsive — narrow viewports ──────────────────
   Below 767px the segments + submit row stacks vertically so
   the segmented control gets full width with horizontal scroll
   if needed, and the submit becomes a full-width button. */
@media (max-width: 767px) {
  [data-act="1"] {
    padding: clamp(80px, 16vh, 120px) 18px 56px;
    min-height: calc(100vh - 56px);
  }
  
  .api-hero-sub { font-size: 15px; }
  .api-try { padding: 18px; }
  .api-try-row {
    flex-direction: column;
    align-items: stretch;
    gap: 12px;
  }
  .api-try-segments {
    align-self: center;
    max-width: 100%;
  }
  .api-try-submit {
    width: 100%;
    justify-content: center;
  }
}

/* ══════════════════════════════════════════════════════════
   Audience switcher + BU light theme.
   Same mechanism as home/styles.css.  Switcher fixed on right
   edge.  Tech (default) keeps the dark theme intact.  BU swaps
   to light theme + Mq12 display face + business-benefits copy.
   Toggle scaffolding mirrored from home so this can be lifted
   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 (TECH and BUSINESS labels
     have different vertical lengths; flex column would size them
     unevenly and 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 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) so sections (which paint their
   own var(--background)) don't show grey seams between them. */
html:not(.dark) body {
  background: var(--background);
  color: var(--text-primary);
}

/* Switcher on light bg needs a solid contrasted fill */
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.  Force font-weight: 500
   (Mq12 ships as Medium only) so the browser doesn't faux-bold on
   top of an already-grotesque face — tech headings use weight 600
   which faux-bolds Rawest, but Mq12 reads visibly heavier at the
   same setting.  500 keeps Mq12 at its intended weight. */
html:not(.dark) .api-hero-h1,
html:not(.dark) .endpoint-h2,
html:not(.dark) .shapes-h2,
html:not(.dark) .switches-h2,
html:not(.dark) .atlas-h2,
html:not(.dark) .codes-h2,
html:not(.dark) .closer-h2 {
  font-family: 'Mq12', system-ui, sans-serif;
  font-weight: 500;
}

/* Topbar inversion — only applies when topbar slot sits on the
   light page bg.  body.is-on-teal still wins on the Act 3 band. */

/* Nav popover (.nav-dropdown) light variant — mirrors home. */
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); }
