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

/* ──────────────────────────────────────────────────────────
   /products/extract — Pages in. Data out.

   Same dark editorial canvas as the other coder spokes
   (/api, /browser-sessions).  Each act tracks the journey
   from raw HTML to typed JSON:

     Act 1 — Hero: side-by-side HTML→JSON demo, wires drawn
     Act 2 — Thesis: stop writing parsers (planned)
     Act 3 — Autoparse: field detection mechanic (planned)
     Act 4 — Schemas: versioned schema timeline (planned)
     Act 5 — Drift: live alert monitor (planned)
     Act 6 — Integration: code grid (planned)
     Act 7 — Closer: extract endpoint poster (planned)
─────────────────────────────────────────────────────────── */

/* ── Page-level shared tokens / helpers ─────────────────── */


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

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

[data-act="1"] {
  position: relative;
  /* Full-viewport hero like the other spokes.  100svh respects
     mobile address-bar collapse so the demo panel never
     vertically clips below the fold. */
  min-height: 100svh;
  min-height: 100vh;
  padding: clamp(96px, 12vh, 140px) clamp(20px, 4vw, 48px) clamp(56px, 8vh, 96px);
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  background: var(--background);
  /* Own stacking context so the wire SVG (z-index: 4 inside
     the demo) can't escape into the page-level stack and
     paint on top of Act 2's saturated band when the reader
     scrolls past.  Act 2 / Act 3 use z-index: 2 / 3 in their
     own contexts so they slot above Act 1 cleanly. */
  z-index: 1;
}

/* Hero composition — centred editorial stack above a
   horizontal demo row.  Two side-by-side panels (HTML source
   on the left, JSON output on the right) joined by SVG wires.
   Earlier draft had an asymmetric split (text left, stacked
   demo right with a CTA below); Pablo's call: centre the
   editorial, drop the in-hero CTA + scroll cue (the topbar
   already carries the primary CTA), and lay the demo
   horizontally beneath. */
.ex-hero {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: clamp(56px, 8vh, 96px);
  width: 100%;
  max-width: 1180px;
  margin: 0 auto;
}

.ex-hero-text {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  max-width: 880px;
  min-width: 0;
}

.ex-hero-h1 {
  margin: 0;
  font-family: var(--font-display, "Rawest", system-ui, sans-serif);
  font-size: var(--mkt-hero-xl);
  line-height: 0.98;
  letter-spacing: var(--tracking-dense, -0.022em);
  font-weight: 500;
  color: var(--text-primary);
  text-wrap: balance;
}

.ex-hero-line {
  display: block;
}

/* Marked phrase — same painted-purple-7 mechanic as the home
   hero, applied immediately on load (no scroll trigger here;
   the hero shows the final state right away).  Atomic via
   nowrap so the colour never splits mid-phrase. */
.ex-hero-mark {
  font-style: normal;
  color: #7C4FE8;
  white-space: nowrap;
}

.ex-hero-sub {
  margin: clamp(20px, 2.4vh, 32px) 0 0;
  max-width: 580px;
  font-family: var(--font-base);
  font-size: var(--mkt-body);
  line-height: 1.55;
  color: var(--text-secondary);
}

/* ── Demo row — horizontal pair on desktop ──────────────── */

.ex-hero-demo {
  position: relative;
  display: flex;
  flex-direction: row;
  align-items: stretch;
  justify-content: center;
  /* Gap = the horizontal wire-bus channel between the two
     panels.  The SVG is position: absolute over the whole row,
     so wires can run from source field right-edges across the
     gap to JSON key left-edges. */
  gap: clamp(60px, 7vw, 120px);
  width: 100%;
  max-width: 1080px;
  min-width: 0;
}

.ex-demo-source,
.ex-demo-output {
  flex: 1 1 0;
  min-width: 0;
}

/* Source + output panels share the unified card shell:
   page-bg fill, display radius, strong down-shadow.  Each
   panel is grabbable — pointer down + drag repositions the
   panel via a CSS transform, and the wire bus repaints in
   real time to stay connected. */
.ex-demo-source,
.ex-demo-output {
  position: relative;
  background: var(--background);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-display, 20px);
  box-shadow: 0 28px 64px -20px rgba(0, 0, 0, 0.55);
  overflow: hidden;
  cursor: grab;
  transition: box-shadow 240ms cubic-bezier(0.22, 1, 0.36, 1);
  /* Idle panels sit BELOW the SVG wires so the wires read as
     plugged INTO the field pills / JSON keys (i.e. the wire
     line is visible right up to its endpoint inside the panel
     rect, not clipped at the panel edge by z-stacking).  The
     SVG has pointer-events: none so panels stay grabbable. */
  z-index: 2;
}

.ex-demo-source.is-dragging,
.ex-demo-output.is-dragging {
  cursor: grabbing;
  /* Lifted feel comes from the shadow boost only — the panel
     stays at the same z-index as idle so the wires remain
     visible across the panel edges while dragging.  Earlier
     attempt popped the dragged panel above the wires; Pablo
     flagged that the cables vanished behind the panel during
     drag and reappeared on release. */
  box-shadow: 0 44px 90px -16px rgba(0, 0, 0, 0.7);
}

/* Top bar of each panel — three dots + URL/file name in code
   font.  Source uses the browser-window pattern (centered URL,
   live look); output uses a file-tab pattern (left filename,
   right status). */
.ex-demo-bar {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 11px 16px;
  background: transparent;
  border-bottom: 1px solid var(--border-subtle);
  font-family: var(--font-code, monospace);
  font-size: 11px;
  letter-spacing: 0.02em;
}

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

.ex-demo-bar-name {
  margin-left: 8px;
  color: var(--text-secondary);
}

.ex-demo-bar-status {
  margin-left: auto;
  padding: 2px 8px;
  border-radius: var(--radius-full, 999px);
  background: rgba(120, 200, 140, 0.14);
  color: var(--zen-green-6, #6FDB97);
  font-weight: 500;
  letter-spacing: 0.05em;
}

.ex-demo-bar--output .ex-demo-bar-name {
  margin-left: 0;
  color: var(--text-primary);
  font-weight: 500;
}

/* Source body — stylised product page.  Each field row has a
   tag pill (h1, .price, etc.) and a value.  The tag is the
   wire's source anchor — the SVG path docks at the right edge
   of the field row. */
.ex-demo-source-body {
  padding: clamp(18px, 2vh, 24px) clamp(18px, 1.8vw, 22px);
  display: flex;
  flex-direction: column;
  gap: clamp(10px, 1.2vh, 14px);
}

.ex-demo-field {
  /* Wire anchor in JS reads getBoundingClientRect of each
     field — keep position: relative so layout calculations
     stay stable across reflows. */
  position: relative;
}

.ex-demo-field-row {
  display: inline-flex;
  align-items: baseline;
  gap: 10px;
  padding: 5px 10px;
  border-radius: 6px;
  background: transparent;
  border: 1px solid var(--border-subtle);
  font-family: var(--font-code, monospace);
  font-size: var(--mkt-body-xs);
  /* Animated highlight pulse — driven by the wire activation
     in extract.js.  Adds a subtle purple-7 glow when this
     field's wire is the one pulsing. */
  transition:
    border-color 240ms cubic-bezier(0.22, 1, 0.36, 1),
    background 240ms cubic-bezier(0.22, 1, 0.36, 1),
    box-shadow 240ms cubic-bezier(0.22, 1, 0.36, 1);
}

.ex-demo-field[data-pulsing="true"] .ex-demo-field-row {
  border-color: rgba(124, 79, 232, 0.5);
  background: rgba(124, 79, 232, 0.08);
  box-shadow: 0 0 0 4px rgba(124, 79, 232, 0.10);
}

.ex-demo-field-tag {
  color: var(--text-quaternary);
  font-weight: 500;
}

.ex-demo-field-value {
  color: var(--text-primary);
  letter-spacing: -0.005em;
}

/* Output body — JSON with code-styled coloring.  Each value
   span has a data-key attribute that matches the source
   field's data-field; the JS pairs them for wires + pulse. */
.ex-demo-output-code {
  margin: 0;
  padding: clamp(18px, 2vh, 24px) clamp(18px, 1.8vw, 22px);
  font-family: var(--font-code, monospace);
  font-size: var(--mkt-body-xs);
  line-height: 1.65;
  color: var(--text-secondary);
}

.ex-code-key   { color: var(--zen-cyan-6, #7ADAE6); }
.ex-code-str   { color: var(--text-primary); }
.ex-code-num   { color: #FFC76B; }
.ex-code-bool  { color: #7C4FE8; font-weight: 500; }
.ex-code-punct { color: var(--text-tertiary); }

.ex-code-str[data-key],
.ex-code-num[data-key],
.ex-code-bool[data-key] {
  position: relative;
  display: inline-block;
  padding: 0 4px;
  margin: 0 -4px;
  border-radius: 4px;
  transition:
    background 240ms cubic-bezier(0.22, 1, 0.36, 1),
    box-shadow 240ms cubic-bezier(0.22, 1, 0.36, 1);
}

.ex-code-str[data-pulsing="true"],
.ex-code-num[data-pulsing="true"],
.ex-code-bool[data-pulsing="true"] {
  background: rgba(124, 79, 232, 0.12);
  box-shadow: 0 0 0 3px rgba(124, 79, 232, 0.10);
}

/* ── Wire bus — SVG overlay between source and output ──── */

.ex-demo-wires {
  /* Spans the full demo column (covers both panels + the gap
     between them).  Pointer-events: none so the panels above
     stay grabbable.  Overflow visible so wires can extend
     outside the SVG box when the user drags a panel beyond
     the demo column. */
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  overflow: visible;
  /* Above the idle panels (z-index: 2) so the wires read as
     plugged INTO the field pills + JSON keys — i.e. the line
     remains visible right up to its endpoint inside the panel
     rect, not clipped at the panel edge by stacking order.
     A panel being grabbed pops to z-index: 6 (above the
     wires) so the dragged panel reads as the focus, with the
     wire visually attaching to its edge. */
  z-index: 4;
  /* SVG paths get drawn by extract.js — each path connects a
     source field's right-edge centre to a JSON key's left-edge
     centre, with a horizontal-then-diagonal-then-horizontal
     bezier so the wires read as cable runs.  stroke-dasharray
     + stroke-dashoffset gives the draw-in animation on mount;
     after draw-in, panel-drag updates simply mutate the path
     d-attribute and --wire-len so the dasharray pattern keeps
     matching the new total length. */
}

.ex-demo-wire {
  fill: none;
  stroke: url(#ex-wire-grad);
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-dasharray: var(--wire-len, 400);
  stroke-dashoffset: var(--wire-len, 400);
  /* On mount the JS sets --wire-progress for each wire as it
     enters; the draw-in completes within ~900ms per wire,
     staggered 120ms apart. */
  transition: stroke-dashoffset 900ms cubic-bezier(0.22, 1, 0.36, 1),
              opacity 240ms ease;
  opacity: 0.85;
}

.ex-demo-wire[data-drawn="true"] {
  stroke-dashoffset: 0;
}

.ex-demo-wire[data-pulsing="true"] {
  opacity: 1;
  stroke-width: 2;
}

/* Endpoint dots — small filled circles at each wire's source
   and destination so the connection reads as a tangible cable
   plug, not just a stroke ending in space. */
.ex-demo-wire-dot {
  fill: #7C4FE8;
  opacity: 0;
  transition: opacity 320ms ease 600ms;
}

.ex-demo-wire-dot[data-visible="true"] { opacity: 1; }

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

@media (max-width: 1023px) {
  [data-act="1"] {
    padding: clamp(80px, 11vh, 120px) clamp(18px, 5vw, 32px) clamp(48px, 8vh, 80px);
  }
  .ex-hero {
    gap: clamp(40px, 6vh, 64px);
  }
  /* Phone / narrow tablet: two static panels stacked.  Drop
     the wires, drag, and pulse cycle — touch fights native
     scroll, the absolute SVG misreflows on layout changes, and
     the row highlights without visible wires read as noise. */
  .ex-hero-demo {
    flex-direction: column;
    align-items: stretch;
    gap: clamp(28px, 4vh, 40px);
    max-width: 480px;
    margin: 0 auto;
  }
  .ex-demo-source,
  .ex-demo-output {
    cursor: default;
    /* Stacked column: size each panel to its content. The base
       `flex: 1 1 0` + overflow:hidden collapses them to ~0 (just the
       border) in an auto-height column, since overflow:hidden zeroes the
       flex auto-minimum. flex:none restores intrinsic height. */
    flex: 0 0 auto;
  }
  .ex-demo-source.is-dragging,
  .ex-demo-output.is-dragging {
    cursor: default;
  }
  .ex-demo-wires { display: none; }
}

@media (max-width: 600px) {
  .ex-hero-h1 {
    font-size: clamp(40px, 11vw, 60px);
    line-height: 1.02;
  }
  .ex-hero-sub {
    font-size: 16px;
  }
  .ex-demo-source-body {
    padding: 16px;
  }
  .ex-demo-output-code {
    font-size: 11px;
    padding: 16px;
  }
}

@media (max-width: 380px) {
  
}

/* ──────────────────────────────────────────────────────────
   Act 2 — The thesis.  "The page knows itself."

   Sticky-stage scene.  ~320vh of scroll budget.  Inside the
   stage a sticky frame pins to top: 0 and renders three
   stacked blocks: editorial header → 4×3 vocabulary grid →
   single-line code call at the bottom.  JS reads the stage's
   scroll progress (0-1) and toggles data-lit="true" on each
   tile in cascading order so the grid fills tile by tile as
   the reader scrolls.  After the cascade completes the call
   line gets data-pulsing="true" — a quiet rhythm-break that
   says "the whole grid is one call".
─────────────────────────────────────────────────────────── */

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

.ex-thesis-stage {
  position: relative;
  min-height: 320vh;
}

.ex-thesis-frame {
  position: sticky;
  top: 0;
  height: 100vh;
  display: grid;
  /* Asymmetric horizontal split — left column carries the
     editorial copy + the proof call line, right column carries
     the vocabulary grid that the cascade lights up.  Different
     composition from Act 1 (centred manifesto + horizontal
     demo below) so the page rhythm reads as deliberate
     variation, not repetition. */
  grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.15fr);
  column-gap: clamp(40px, 5vw, 80px);
  align-items: center;
  padding: clamp(96px, 12vh, 140px) clamp(24px, 4vw, 64px) clamp(48px, 8vh, 80px);
  max-width: 1280px;
  margin: 0 auto;
}

.ex-thesis-text {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: clamp(24px, 3.4vh, 40px);
  min-width: 0;
}

.ex-thesis-header {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  text-align: left;
  gap: 14px;
  width: 100%;
}

.ex-thesis-h2 {
  margin: 0;
  font-family: var(--font-display, "Rawest", system-ui, sans-serif);
  /* Smaller than Act 1's hero H1 — second-beat editorial,
     not a competing landmark. */
  font-size: var(--mkt-h3);
  line-height: 1.05;
  letter-spacing: var(--tracking-dense, -0.022em);
  font-weight: 500;
  color: var(--text-primary);
}

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

/* Vocabulary grid — 3-column on desktop (3×4 = 12 tiles) so
   each tile keeps a comfortable width inside the right
   column of the asymmetric split.  Each tile is a small
   typed-field card: tag pill on top, sample value underneath.
   Dimmed by default; lit per beat by the scroll-driven JS
   adding data-lit="true". */
.ex-thesis-grid {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(10px, 1vw, 16px);
  width: 100%;
  min-width: 0;
}

.ex-thesis-tile {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: clamp(12px, 1.4vh, 18px) clamp(14px, 1.2vw, 18px);
  border-radius: 12px;
  /* Default state: frame-only.  No fill, no shadow — only the
     subtle border ring.  Lit state below brings back fill +
     purple-7 glow. */
  background: transparent;
  border: 1px solid var(--border-subtle);
  color: var(--text-quaternary);
  transition:
    color 320ms cubic-bezier(0.22, 1, 0.36, 1),
    background 320ms cubic-bezier(0.22, 1, 0.36, 1),
    border-color 320ms cubic-bezier(0.22, 1, 0.36, 1),
    box-shadow 360ms cubic-bezier(0.22, 1, 0.36, 1);
  min-width: 0;
}

.ex-thesis-tile-tag {
  font-family: var(--font-code, monospace);
  font-size: 11px;
  letter-spacing: 0.06em;
  color: var(--text-quaternary);
  transition: color 320ms cubic-bezier(0.22, 1, 0.36, 1);
}

.ex-thesis-tile-value {
  font-family: var(--font-base);
  font-size: var(--mkt-body-sm);
  font-weight: 500;
  color: var(--text-tertiary);
  letter-spacing: -0.005em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  transition: color 320ms cubic-bezier(0.22, 1, 0.36, 1);
}

.ex-thesis-tile[data-lit="true"] {
  background: var(--background);
  border-color: rgba(124, 79, 232, 0.45);
  box-shadow: 0 0 0 1px rgba(124, 79, 232, 0.18),
              0 12px 28px -10px rgba(124, 79, 232, 0.30);
  color: var(--text-primary);
}

.ex-thesis-tile[data-lit="true"] .ex-thesis-tile-tag {
  color: var(--zen-purple-7, #7C4FE8);
}

.ex-thesis-tile[data-lit="true"] .ex-thesis-tile-value {
  color: var(--text-primary);
}

/* Single line of code anchoring the whole grid.  Now sits in
   the left column beneath the editorial copy (was centred
   below the grid in the previous symmetric layout).  Tile
   cascade ends with the call line getting data-pulsing="true"
   so the URL segment glows once the reader has seen every
   field light up. */
.ex-thesis-call {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 10px;
}

.ex-thesis-call-code {
  font-family: var(--font-code, monospace);
  font-size: var(--mkt-body-xs);
  letter-spacing: -0.005em;
  color: var(--text-secondary);
  padding: 7px 13px;
  border-radius: var(--radius-full, 999px);
  background: transparent;
  border: 1px solid var(--border-subtle);
  white-space: nowrap;
}

.ex-thesis-call-await { color: var(--zen-cyan-6, #7ADAE6); font-weight: 500; }
.ex-thesis-call-punct { color: var(--text-tertiary); }
.ex-thesis-call-url   {
  color: var(--text-primary);
  padding: 0 4px;
  margin: 0 -4px;
  border-radius: 4px;
  transition:
    background 280ms cubic-bezier(0.22, 1, 0.36, 1),
    box-shadow 280ms cubic-bezier(0.22, 1, 0.36, 1);
}

.ex-thesis-call[data-pulsing="true"] .ex-thesis-call-url {
  background: rgba(124, 79, 232, 0.14);
  box-shadow: 0 0 0 3px rgba(124, 79, 232, 0.10);
}

.ex-thesis-call-note {
  margin: 0;
  font-family: var(--font-base);
  font-size: var(--text-small, 13px);
  color: var(--text-quaternary);
  letter-spacing: -0.005em;
}

/* Mobile / tablet — collapse the asymmetric split back to a
   single column.  Text + call on top, grid below.  Tiles wrap
   from 3-col → 2-col on tablet → 1-col on phone. */
@media (max-width: 1023px) {
  .ex-thesis-frame {
    grid-template-columns: 1fr;
    row-gap: clamp(28px, 4vh, 48px);
    height: auto;
    min-height: 100vh;
    align-items: start;
    padding-top: clamp(80px, 11vh, 120px);
  }
  .ex-thesis-stage {
    /* Drop the sticky budget — natural scroll on tablet so
       the user doesn't fight a tall sticky scene at low vh. */
    min-height: auto;
  }
  .ex-thesis-frame { position: static; }
  .ex-thesis-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    max-width: 560px;
  }
  .ex-thesis-call-code {
    white-space: normal;
    max-width: 100%;
  }
}

@media (max-width: 600px) {
  .ex-thesis-grid {
    grid-template-columns: 1fr;
    max-width: 360px;
  }
  .ex-thesis-tile-value {
    white-space: normal;
  }
  .ex-thesis-h2 {
    font-size: var(--mkt-h3);
  }
}

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

@media (min-width: 1600px) {
  .ex-hero { max-width: 1380px; }
  
  .ex-hero-demo { max-width: 1200px; }
  .ex-thesis-frame { max-width: 1440px; }
  .ex-thesis-h2 { font-size: 52px; }
}

@media (min-width: 1920px) {
  .ex-hero { max-width: 1520px; }
  
  .ex-hero-demo { max-width: 1320px; }
  .ex-thesis-frame { max-width: 1600px; }
  .ex-thesis-h2 { font-size: 60px; }
}

@media (min-width: 2400px) {
  .ex-hero { max-width: 1700px; }
  
  .ex-hero-demo { max-width: 1480px; }
  .ex-thesis-frame { max-width: 1800px; }
  .ex-thesis-h2 { font-size: 68px; }
}

/* ──────────────────────────────────────────────────────────
   Act 3 — Autoparse discovery.  Saturated teal-5 band.

   First colour break of the page (Acts 1 & 2 lived on dark).
   Bloom curtain reveals from the top-right corner.  Topbar
   swaps to dark text via body.is-on-teal once the act covers
   the topbar slot.

   Layout: 2-column grid inside the sticky frame.  Editorial
   sits in the bottom-LEFT cell as a footer caption; the
   spread (page mock + numbered callouts) lives top-RIGHT.
   Acts 1 / 2 / 3 now read centred / left+right / bottom-
   left+top-right — no two compositions alike.

   Choreography (extract.js): scroll progress across the
   stage drives:
     - bloom curtain growth (0→100% over the first ~5%)
     - per-field data-discovered swap (6 fields at thresholds
       0.10, 0.22, 0.34, 0.46, 0.58, 0.70)
     - topbar swap on/off
─────────────────────────────────────────────────────────── */

[data-act="3"] {
  position: relative;
  /* Bumped above Act 4's z-index so the iPhone's overflow
     (clay SVG dipping past the frame's bottom edge) paints
     ON TOP of Act 4 instead of being covered by it.  Future
     acts that need to cover Act 3 use z-index 6+. */
  z-index: 5;
  background: var(--background);
}

.ex-discover-stage {
  position: relative;
  /* Six discovery beats spread across ~70% of the stage,
     bloom in the first 5%, breathing room at the end so the
     final state lingers before scrolling past. */
  min-height: 500vh;
}

/* Bloom curtain — teal-5 brand hex per DESIGN.md's
   saturated-bands exception (DS token inverts in dark mode
   and would mute the brand band). */
.ex-discover-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) .ex-discover-curtain {
  /* No-JS — show the full band immediately so the act is
     legible without scroll choreography. */
  clip-path: none;
}

.ex-discover-frame {
  position: sticky;
  top: 0;
  height: 100vh;
  display: grid;
  /* 2-col, 2-row.  Editorial in bottom-left only; spread
     spans both rows on the right. */
  grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.15fr);
  grid-template-rows: 1fr auto;
  grid-template-areas:
    ".      spread"
    "header spread";
  column-gap: clamp(40px, 5vw, 80px);
  row-gap: clamp(24px, 3vh, 40px);
  padding: clamp(96px, 14vh, 140px) clamp(24px, 4vw, 64px) clamp(48px, 8vh, 80px);
  max-width: 1480px;
  margin: 0 auto;
  z-index: 1;
}

.ex-discover-header {
  grid-area: header;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 14px;
  align-self: end;
  /* Hardcoded warm dark — teal band is theme-invariant; text stays
     dark in BOTH tech and BU.  var(--background) would flip to
     light in BU and disappear on the bright teal. */
  color: #1D1612;
}

.ex-discover-header .act-kicker {
  color: rgba(29, 22, 18, 0.65);
}

.ex-discover-h2 {
  margin: 0;
  font-family: var(--font-display, "Rawest", system-ui, sans-serif);
  font-size: var(--mkt-h2);
  line-height: 1.02;
  letter-spacing: var(--tracking-dense, -0.022em);
  font-weight: 500;
  color: #1D1612;
}

.ex-discover-mark {
  font-style: normal;
  color: #7C4FE8;
  white-space: nowrap;
}

.ex-discover-sub {
  margin: 0;
  font-family: var(--font-base);
  font-size: var(--mkt-body);
  line-height: 1.5;
  color: rgba(29, 22, 18, 0.78);
  max-width: 440px;
}

/* Spread — page mock left + callouts right, top-aligned. */
/* Spread — 2-col layout.  Phone (left): explicit-dim
   container, bottom-aligned to the spread cell so the model
   dips past the frame's bottom edge.  Annotations (right):
   centre-aligned minimal JSON-style stack. */
.ex-discover-spread {
  grid-area: spread;
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  column-gap: clamp(32px, 3.6vw, 64px);
  align-items: end;
  align-self: end;
  width: 100%;
  min-width: 0;
}

.ex-discover-phone {
  position: relative;
  /* +20% from the previous size so the phone reads as the
     dominant visual on the band.  Width / height clamps
     keep portrait proportion at every viewport. */
  width: clamp(336px, 38vw, 528px);
  height: clamp(528px, 92vh, 760px);
  justify-self: center;
  /* Deeper negative margin so the phone visibly bursts past
     the frame's bottom edge into the saturated band beneath. */
  margin-bottom: clamp(-240px, -22vh, -160px);
}


/* Clay iPhone — 2D SVG fills the phone container 100% × 100%;
   the container's explicit dimensions + negative margin-bottom
   drive the "phone overflows the frame's bottom edge" effect.
   Subtle drop-shadow blends into the saturated teal band
   instead of cutting against it.

   --phone-parallax is written by extract.js per scroll frame;
   the SVG drifts vertically across the stage so the phone reads
   as a layer slightly slower than the page. */
.ex-clay-iphone {
  position: relative;
  z-index: 1;
  width: 100%;
  height: 100%;
  display: block;
  transform: translateY(var(--phone-parallax, 0px));
  will-change: transform;
  filter: drop-shadow(0 22px 44px rgba(0, 0, 0, 0.14))
          drop-shadow(0 6px 14px rgba(0, 0, 0, 0.08));
}

/* Field-detection borders inside the clay iPhone.  Each
   <g.ex-clay-field data-field="X"> has a dashed purple-7
   rect child that draws stroke-width 2 when the group's
   data-discovered toggles to "true".  No floating tag pills
   (Pablo: less visual chatter on the phone face). */
.ex-clay-field-border {
  transition: stroke-width 380ms cubic-bezier(0.22, 1, 0.36, 1);
}

.ex-clay-field[data-discovered="true"] .ex-clay-field-border {
  stroke-width: 2;
}

/* Minimal JSON-style annotations — vertical stack of typed
   key:value lines.  No card background, no border.  Default
   state is dimmed (muted text on the teal band); discovered
   state brightens the colour-coded segments. */
.ex-discover-annotations {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: clamp(10px, 1.6vh, 18px);
  align-self: center;
  min-width: 0;
  width: 100%;
}

.ex-discover-annotation {
  display: flex;
  align-items: baseline;
  gap: 14px;
  opacity: 0;
  transform: translateX(12px);
  transition:
    opacity 480ms cubic-bezier(0.22, 1, 0.36, 1),
    transform 540ms cubic-bezier(0.22, 1, 0.36, 1);
}

.ex-discover-annotation[data-discovered="true"] {
  opacity: 1;
  transform: translateX(0);
}

.ex-discover-annotation-num {
  flex-shrink: 0;
  font-family: var(--font-code, monospace);
  font-size: 10px;
  letter-spacing: 0.18em;
  color: rgba(29, 22, 18, 0.4);
  transition: color 320ms cubic-bezier(0.22, 1, 0.36, 1);
}

.ex-discover-annotation[data-discovered="true"] .ex-discover-annotation-num {
  color: rgba(29, 22, 18, 0.85);
}

.ex-discover-annotation-code {
  font-family: var(--font-code, monospace);
  font-size: var(--mkt-body-sm);
  letter-spacing: -0.005em;
  color: rgba(29, 22, 18, 0.85);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}

/* Colour roles for the JSON segments — these are the brand
   accents that pop on the teal band when a field is
   discovered. */
.ex-discover-key   { color: #5C36C2; font-weight: 600; }
.ex-discover-str   { color: rgba(29, 22, 18, 0.92); font-weight: 500; }
.ex-discover-num   { color: #C46A1E; font-weight: 600; }
.ex-discover-bool  { color: #5C36C2; font-weight: 600; }
.ex-discover-punct { color: rgba(29, 22, 18, 0.45); }

/* Pre-discovery state — segments are nearly invisible so the
   line reads as a faint placeholder before autoparse "lands"
   on the field. */
.ex-discover-annotation:not([data-discovered="true"]) .ex-discover-annotation-code {
  color: rgba(29, 22, 18, 0.18);
}
.ex-discover-annotation:not([data-discovered="true"]) .ex-discover-key,
.ex-discover-annotation:not([data-discovered="true"]) .ex-discover-str,
.ex-discover-annotation:not([data-discovered="true"]) .ex-discover-num,
.ex-discover-annotation:not([data-discovered="true"]) .ex-discover-bool,
.ex-discover-annotation:not([data-discovered="true"]) .ex-discover-punct {
  color: rgba(29, 22, 18, 0.18);
  font-weight: 400;
}

/* Topbar adaptation — body.is-on-teal flips chrome to dark
   text + black logo so it reads on the bright teal band.
   Mirrors the /products/fetch Act 3 rules verbatim. */
/* 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 is theme-invariant brand colour. */

/* Mobile / tablet — collapse to single column.  Drop sticky
   so the user doesn't fight a tall pinned scene on phones. */
@media (max-width: 1023px) {
  .ex-discover-stage { min-height: auto; }
  .ex-discover-frame {
    position: static;
    height: auto;
    grid-template-columns: 1fr;
    grid-template-rows: auto auto;
    grid-template-areas:
      "header"
      "spread";
    padding-top: clamp(80px, 11vh, 120px);
    padding-bottom: clamp(48px, 8vh, 80px);
  }
  .ex-discover-curtain {
    clip-path: none;
  }
  .ex-discover-spread {
    grid-template-columns: 1fr;
    row-gap: clamp(28px, 4vh, 48px);
    align-self: start;
  }
  .ex-discover-phone {
    width: clamp(260px, 70vw, 380px);
    height: clamp(400px, 70vw, 600px);
    justify-self: center;
    margin-bottom: 0;
  }
  /* On mobile / tablet the stage is no longer sticky and the
     parallax math gives weird offsets at small scroll budgets.
     Pin the phone to translateY(0). */
  .ex-clay-iphone {
    transform: none;
  }
}

@media (max-width: 600px) {
  .ex-discover-h2 { font-size: var(--mkt-h3); }
  .ex-discover-annotation-code { font-size: 12px; }
}

/* ── Wide-desktop tiers for the Discover act ─────────────────
   Placed here (after the base rules above) so they actually
   override at >=1600px.  Earlier the tiered values lived in
   the page-wide tier block at the top of the file and never
   won the cascade because the base .ex-discover-phone rule
   was sourced after them. */

@media (min-width: 1600px) {
  .ex-discover-frame { max-width: 1600px; }
  .ex-discover-h2 { font-size: 64px; }
  .ex-discover-sub { font-size: 18px; max-width: 520px; }
  .ex-discover-phone {
    width: clamp(528px, 36vw, 612px);
    height: clamp(760px, 88vh, 880px);
  }
}

@media (min-width: 1920px) {
  .ex-discover-frame { max-width: 1700px; }
  .ex-discover-h2 { font-size: 76px; }
  .ex-discover-sub { font-size: 19px; max-width: 580px; }
  .ex-discover-phone {
    width: clamp(612px, 36vw, 680px);
    height: clamp(880px, 88vh, 960px);
  }
}

@media (min-width: 2400px) {
  .ex-discover-frame { max-width: 1900px; }
  .ex-discover-h2 { font-size: 88px; }
  .ex-discover-sub { font-size: 21px; max-width: 640px; }
  .ex-discover-phone {
    width: clamp(680px, 32vw, 780px);
    height: clamp(960px, 86vh, 1100px);
  }
}

/* ──────────────────────────────────────────────────────────
   Act 4 — Persistent schemas.  Same teal-5 brand band as Act
   3, but the bloom is mirrored: top-right corner in Act 3,
   bottom-LEFT here.  Composition flipped too — editorial top-
   right, vertical schema-version timeline on the left.

   Choreography (extract.js initSchemaStage):
     - bloom curtain bottom-left over the first ~5% of stage
     - each .ex-schema-node reveals on cascade (data-revealed
       toggles true as the scroll progress crosses its
       threshold)
     - the [data-current="true"] node gets data-pulsing="true"
       once the cascade finishes — its pill keeps a quiet
       breathing animation
     - body.is-on-teal toggles on/off as before so the chrome
       reads dark on the bright band
─────────────────────────────────────────────────────────── */

/* Per Pablo's call: don't flash dark between Act 3's teal and
   Act 4's teal.  Act 4's section bg is the brand teal directly
   so the saturated surface reads as ONE continuous band that
   spans both acts.  No new bloom — Act 3's bloom already
   played as the entry; Act 4 inherits the colour and pivots
   the composition instead. */
[data-act="4"] {
  position: relative;
  z-index: 4;
  background: #7ADAE6;
}

.ex-schema-stage {
  position: relative;
  min-height: 460vh;
}

.ex-schema-frame {
  position: sticky;
  top: 0;
  height: 100vh;
  display: grid;
  /* 2-col grid: timeline left (1.1fr), editorial right
     (0.9fr).  Editorial sits in the top portion of the right
     column; timeline runs the full height of the left. */
  grid-template-columns: minmax(0, 1.1fr) minmax(0, 0.9fr);
  grid-template-rows: auto 1fr;
  grid-template-areas:
    "timeline header"
    "timeline .";
  column-gap: clamp(40px, 5vw, 80px);
  row-gap: clamp(24px, 3vh, 40px);
  padding: clamp(96px, 14vh, 140px) clamp(24px, 4vw, 64px) clamp(48px, 8vh, 80px);
  max-width: 1480px;
  margin: 0 auto;
  z-index: 1;
}

.ex-schema-header {
  grid-area: header;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  text-align: right;
  gap: 14px;
  max-width: 460px;
  justify-self: end;
  /* Hardcoded warm dark — sits on teal band, theme-invariant. */
  color: #1D1612;
}

.ex-schema-header .act-kicker {
  color: rgba(29, 22, 18, 0.65);
}

.ex-schema-h2 {
  margin: 0;
  font-family: var(--font-display, "Rawest", system-ui, sans-serif);
  font-size: var(--mkt-h2);
  line-height: 1.02;
  letter-spacing: var(--tracking-dense, -0.022em);
  font-weight: 500;
  color: #1D1612;
}

.ex-schema-mark {
  font-style: normal;
  color: #7C4FE8;
  white-space: nowrap;
}

.ex-schema-sub {
  margin: 0;
  font-family: var(--font-base);
  font-size: var(--mkt-body);
  line-height: 1.5;
  color: rgba(29, 22, 18, 0.78);
}

/* Timeline — vertical list of version cards on the left. */
.ex-schema-timeline {
  grid-area: timeline;
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: clamp(16px, 2.4vh, 32px);
  align-self: stretch;
  min-width: 0;
  width: 100%;
  max-width: 460px;
}

.ex-schema-node {
  position: relative;
  display: grid;
  grid-template-columns: 32px minmax(0, 1fr);
  column-gap: 14px;
  align-items: start;
  /* IO / scroll-driven reveal: dimmed + translated at start,
     lifts to full on cascade. */
  opacity: 0;
  transform: translateY(14px);
  transition:
    opacity 540ms cubic-bezier(0.22, 1, 0.36, 1),
    transform 620ms cubic-bezier(0.22, 1, 0.36, 1);
}

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

/* Vertical rail running through the dots — drawn behind each
   node so they all connect.  Hides on the last node so the
   line ends at the current version. */
.ex-schema-rail {
  position: absolute;
  top: 14px;
  bottom: -28px;
  left: 15px;
  width: 1px;
  background: rgba(29, 22, 18, 0.30);
  z-index: 0;
}

.ex-schema-node:last-of-type .ex-schema-rail {
  display: none;
}

.ex-schema-dot {
  position: relative;
  z-index: 1;
  width: 12px;
  height: 12px;
  margin-top: 7px;
  margin-left: 10px;
  border-radius: 50%;
  background: var(--background);
  border: 2px solid rgba(29, 22, 18, 0.50);
  transition:
    background 320ms cubic-bezier(0.22, 1, 0.36, 1),
    border-color 320ms cubic-bezier(0.22, 1, 0.36, 1),
    box-shadow 320ms cubic-bezier(0.22, 1, 0.36, 1);
}

.ex-schema-node[data-revealed="true"] .ex-schema-dot {
  background: var(--background);
  border-color: var(--background);
}

.ex-schema-node[data-current="true"][data-revealed="true"] .ex-schema-dot {
  background: #7C4FE8;
  border-color: #7C4FE8;
  box-shadow: 0 0 0 4px rgba(124, 79, 232, 0.18);
  animation: ex-schema-pulse 2200ms cubic-bezier(0.4, 0, 0.6, 1) infinite;
}

@keyframes ex-schema-pulse {
  0%, 100% { box-shadow: 0 0 0 4px rgba(124, 79, 232, 0.18); }
  50%      { box-shadow: 0 0 0 9px rgba(124, 79, 232, 0.06); }
}

@media (prefers-reduced-motion: reduce) {
  .ex-schema-node[data-current="true"][data-revealed="true"] .ex-schema-dot {
    animation: none;
  }
}

/* Glass cards — frosted white pane.  Heavy blur + an opaque-
   enough white wash so the teal underneath reads as soft
   light through frosted glass rather than a teal tint.  No
   saturate filter (kept colour neutral per Pablo's call). */
.ex-schema-card {
  background: rgba(255, 255, 255, 0.55);
  backdrop-filter: blur(22px);
  transform: translateZ(0);
  backface-visibility: hidden;
  -webkit-backdrop-filter: blur(22px);
  border: 1px solid rgba(255, 255, 255, 0.55);
  border-radius: 12px;
  padding: clamp(12px, 1.6vh, 18px) clamp(14px, 1.4vw, 20px);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.5),
    0 14px 36px -16px rgba(0, 0, 0, 0.16);
}

.ex-schema-node[data-current="true"] .ex-schema-card {
  background: rgba(255, 255, 255, 0.72);
  border-color: rgba(124, 79, 232, 0.50);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.6),
    0 0 0 1px rgba(124, 79, 232, 0.18) inset,
    0 22px 50px -16px rgba(124, 79, 232, 0.30);
}

.ex-schema-card-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 8px;
}

.ex-schema-pill {
  display: inline-flex;
  align-items: center;
  padding: 3px 8px;
  border-radius: 6px;
  background: rgba(29, 22, 18, 0.10);
  color: rgba(29, 22, 18, 0.85);
  font-family: var(--font-code, monospace);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.04em;
}

.ex-schema-pill--current {
  background: rgba(124, 79, 232, 0.18);
  color: #5C36C2;
}

.ex-schema-date {
  font-family: var(--font-code, monospace);
  font-size: 10px;
  letter-spacing: 0.06em;
  color: rgba(29, 22, 18, 0.55);
  text-transform: uppercase;
}

.ex-schema-diff {
  margin: 0 0 6px;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.ex-schema-diff-row {
  font-family: var(--font-code, monospace);
  font-size: 12px;
  letter-spacing: -0.005em;
  color: rgba(29, 22, 18, 0.85);
}

.ex-schema-diff-plus {
  color: #4fa852;
  font-weight: 700;
  margin-right: 4px;
}

.ex-schema-note {
  margin: 0;
  font-family: var(--font-base);
  font-size: 11px;
  line-height: 1.5;
  color: rgba(29, 22, 18, 0.55);
}

@media (max-width: 1023px) {
  .ex-schema-stage { min-height: auto; }
  .ex-schema-frame {
    position: static;
    height: auto;
    grid-template-columns: 1fr;
    grid-template-rows: auto auto;
    grid-template-areas:
      "header"
      "timeline";
    padding-top: clamp(80px, 11vh, 120px);
    padding-bottom: clamp(48px, 8vh, 80px);
  }
  .ex-schema-curtain {
    clip-path: none;
  }
  .ex-schema-header {
    align-items: flex-start;
    text-align: left;
    justify-self: start;
    max-width: 100%;
  }
  .ex-schema-timeline {
    max-width: 100%;
  }
}

@media (max-width: 600px) {
  .ex-schema-h2 { font-size: var(--mkt-h3); }
  .ex-schema-card {
    padding: 12px 14px;
  }
}

/* Wide-desktop tiers — bump frame max-width + h2 sizes. */
@media (min-width: 1600px) {
  .ex-schema-frame { max-width: 1600px; }
  .ex-schema-h2 { font-size: 64px; }
}

@media (min-width: 1920px) {
  .ex-schema-frame { max-width: 1720px; }
  .ex-schema-h2 { font-size: 76px; }
}

@media (min-width: 2400px) {
  .ex-schema-frame { max-width: 1900px; }
  .ex-schema-h2 { font-size: 88px; }
}

/* ──────────────────────────────────────────────────────────
   Act 5 — Drift alerts.  Back to the warm-dark page canvas
   after Acts 3-4's continuous teal band.  No saturated
   surface — the restraint IS the rhythm break.

   Composition: centred editorial intro at the top, then a
   feed-style block beneath it (live pulse + label + count
   header, then a vertical list of drift-alert cards).  Each
   card carries a coloured severity dot, a timestamp, a
   field-shape diff, and a note.  Cards reveal on
   IntersectionObserver cascade.
─────────────────────────────────────────────────────────── */

[data-act="5"] {
  position: relative;
  z-index: 6;
  background: var(--background);
  padding: clamp(120px, 16vh, 200px) clamp(24px, 4vw, 64px) clamp(96px, 14vh, 160px);
}

.ex-drift-header {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 14px;
  max-width: 720px;
  margin: 0 auto;
}

.ex-drift-h2 {
  margin: 0;
  font-family: var(--font-display, "Rawest", system-ui, sans-serif);
  font-size: var(--mkt-h2);
  line-height: 1.02;
  letter-spacing: var(--tracking-dense, -0.022em);
  font-weight: 500;
  color: var(--text-primary);
}

.ex-drift-mark {
  font-style: normal;
  color: #7C4FE8;
  white-space: nowrap;
}

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

/* Feed wrapper — sits beneath the header.  Wider channel
   (max 1080) so the diagonal staircase has horizontal room
   to step right while each card keeps a consistent width. */
.ex-drift-feed-wrap {
  margin: clamp(48px, 7vh, 80px) auto 0;
  max-width: 1080px;
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: clamp(14px, 1.8vh, 22px);
}

/* Feed = ordered list of alerts.  Cards step down-and-right
   in a diagonal — each card carries a wider --step shift
   than the one above it, so the stack reads as a staircase
   instead of a column. */
.ex-drift-feed {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: clamp(10px, 1.4vh, 16px);
}

/* Each card holds a consistent width; nth-child margins push
   them progressively right.  Wrap's max-width (1080) gives
   the staircase room without crowding the card body. */
.ex-drift-feed .ex-drift-alert {
  width: clamp(380px, 60vw, 640px);
}
.ex-drift-feed .ex-drift-alert:nth-child(1) { margin-left: 0;                       }
.ex-drift-feed .ex-drift-alert:nth-child(2) { margin-left: clamp(40px, 9vw, 140px); }
.ex-drift-feed .ex-drift-alert:nth-child(3) { margin-left: clamp(80px, 18vw, 280px);}
.ex-drift-feed .ex-drift-alert:nth-child(4) { margin-left: clamp(120px, 27vw, 420px);}

.ex-drift-alert {
  position: relative;
  overflow: hidden;
  display: grid;
  grid-template-columns: minmax(100px, 130px) minmax(0, 1fr);
  column-gap: clamp(16px, 2vw, 28px);
  padding: clamp(14px, 1.8vh, 20px) clamp(16px, 1.6vw, 22px);
  border-radius: var(--radius-display, 20px);
  /* Calm card — page-bg fill, no shadow at rest.  Hover
     brings in the down-shadow + the top accent gradient bar
     for the moment of focus. */
  background: var(--background);
  /* Reveal cascade — start dimmed + translated, lift to full
     when IntersectionObserver fires. */
  opacity: 0;
  transform: translateY(14px);
  transition:
    opacity 540ms cubic-bezier(0.22, 1, 0.36, 1),
    transform 620ms cubic-bezier(0.22, 1, 0.36, 1),
    box-shadow 280ms cubic-bezier(0.22, 1, 0.36, 1);
}

.ex-drift-alert::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 2px;
  background: linear-gradient(
    90deg,
    #7C4FE8 0%,
    var(--zen-cyan-6, #7ADAE6) 35%,
    transparent 80%
  );
  opacity: 0;
  transition: opacity 220ms ease;
}

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

.ex-drift-alert:hover {
  box-shadow: 0 32px 72px -20px rgba(0, 0, 0, 0.62);
  /* Hide the system cursor — the O follower takes over via
     initCustomCursor in extract.js. */
  cursor: pointer;
}

.ex-drift-alert:hover::before {
  opacity: 1;
}

.ex-drift-alert-meta {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.ex-drift-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  margin-bottom: 4px;
}

.ex-drift-dot--low      { background: #6B7C8C; box-shadow: 0 0 0 3px rgba(107, 124, 140, 0.14); }
.ex-drift-dot--med      { background: #FFC76B; box-shadow: 0 0 0 3px rgba(255, 199, 107, 0.14); }
.ex-drift-dot--high     { background: #ff8a4f; box-shadow: 0 0 0 3px rgba(255, 138, 79, 0.16); }
.ex-drift-dot--critical { background: #ff5b6e; box-shadow: 0 0 0 3px rgba(255, 91, 110, 0.16); }

.ex-drift-time {
  font-family: var(--font-code, monospace);
  font-size: 11px;
  letter-spacing: 0.04em;
  color: var(--text-tertiary);
}

.ex-drift-sev {
  font-family: var(--font-base);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: -0.005em;
  color: var(--text-quaternary);
}

.ex-drift-alert[data-severity="critical"] .ex-drift-sev { color: #ff5b6e; }
.ex-drift-alert[data-severity="high"]     .ex-drift-sev { color: #ff8a4f; }
.ex-drift-alert[data-severity="med"]      .ex-drift-sev { color: #FFC76B; }
.ex-drift-alert[data-severity="low"]      .ex-drift-sev { color: var(--text-tertiary); }

.ex-drift-alert-body {
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-width: 0;
}

.ex-drift-alert-head {
  margin: 0;
  font-family: var(--font-base);
  font-size: var(--mkt-body-sm);
  line-height: 1.35;
  color: var(--text-primary);
  font-weight: 500;
}

.ex-drift-field {
  font-family: var(--font-code, monospace);
  font-size: 0.92em;
  padding: 1px 5px;
  border-radius: 4px;
  background: rgba(124, 79, 232, 0.14);
  color: #B299F0;
  font-weight: 500;
  letter-spacing: 0.02em;
}

.ex-drift-alert-diff {
  margin: 0;
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  font-family: var(--font-code, monospace);
  font-size: var(--mkt-body-xs);
}

.ex-drift-was,
.ex-drift-is {
  padding: 3px 8px;
  border-radius: 6px;
  background: var(--background);
  border: 1px solid var(--border-subtle);
  color: var(--text-secondary);
}

.ex-drift-arrow {
  color: var(--text-quaternary);
  font-family: var(--font-base);
  font-size: 14px;
}

.ex-drift-alert-note {
  margin: 0;
  font-family: var(--font-base);
  font-size: var(--text-small, 13px);
  line-height: 1.5;
  color: var(--text-tertiary);
  letter-spacing: -0.005em;
}

.ex-drift-inline {
  font-family: var(--font-code, monospace);
  font-size: 0.92em;
  padding: 1px 5px;
  border-radius: 4px;
  background: var(--background);
  color: var(--text-secondary);
}

@media (max-width: 1023px) {
  .ex-drift-alert {
    grid-template-columns: 1fr;
    row-gap: 10px;
  }
  .ex-drift-alert-meta {
    flex-direction: row;
    align-items: center;
    gap: 10px;
  }
  .ex-drift-dot {
    margin-bottom: 0;
  }
  /* Flatten the staircase on tablet / phone — the diagonal
     would crush the card's content column at narrow widths.
     Card width also falls back to 100% so it fills the
     viewport. */
  .ex-drift-feed .ex-drift-alert {
    width: 100%;
  }
  .ex-drift-feed .ex-drift-alert:nth-child(1),
  .ex-drift-feed .ex-drift-alert:nth-child(2),
  .ex-drift-feed .ex-drift-alert:nth-child(3),
  .ex-drift-feed .ex-drift-alert:nth-child(4) {
    margin-left: 0;
  }
}

@media (max-width: 600px) {
  .ex-drift-h2 { font-size: var(--mkt-h3); }
}

@media (min-width: 1600px) {
  .ex-drift-feed-wrap { max-width: 1200px; }
  .ex-drift-h2 { font-size: 64px; }
}

@media (min-width: 1920px) {
  .ex-drift-feed-wrap { max-width: 1340px; }
  .ex-drift-h2 { font-size: 76px; }
}

@media (min-width: 2400px) {
  .ex-drift-feed-wrap { max-width: 1520px; }
  .ex-drift-h2 { font-size: 88px; }
}

/* ──────────────────────────────────────────────────────────
   Act 6 — Closer.  No saturated band (Pablo's call); single
   centred editorial beat on the warm-dark canvas.  The H2
   reprises Act 1's "Stop writing parsers." so the page reads
   as a full circle.  A quiet endpoint poster sits below the
   CTA row as the technical handoff.
─────────────────────────────────────────────────────────── */

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

.ex-closer {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: clamp(18px, 2.4vh, 28px);
  padding: clamp(140px, 22vh, 240px) clamp(24px, 4vw, 64px) clamp(120px, 18vh, 200px);
  max-width: 1080px;
  margin: 0 auto;
}

.ex-closer .act-kicker {
  margin: 0;
  color: var(--text-tertiary);
}

.ex-closer-h2 {
  margin: 0;
  font-family: var(--font-display, "Rawest", system-ui, sans-serif);
  font-size: var(--mkt-hero);
  line-height: 0.98;
  letter-spacing: var(--tracking-dense, -0.022em);
  font-weight: 500;
  color: var(--text-primary);
  text-wrap: balance;
}

.ex-closer-line {
  display: block;
}

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

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

.ex-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;
}

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

.ex-closer-secondary:hover {
  color: var(--text-primary);
  border-bottom-color: var(--border-default, rgba(255, 255, 255, 0.18));
}

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

/* Endpoint poster — quiet code-typeface line under the CTAs.
   Coloured spans per role (method dim, host primary, path
   dim), so the eye lands on the host segment without the
   line shouting. */
.ex-closer-endpoint {
  margin-top: clamp(20px, 3vh, 32px);
  padding: 8px 16px;
  border-radius: var(--radius-full, 999px);
  background: transparent;
  border: 1px solid var(--border-subtle);
  font-family: var(--font-code, monospace);
  font-size: var(--mkt-body-xs);
  letter-spacing: -0.005em;
  color: var(--text-tertiary);
  display: inline-flex;
  align-items: baseline;
  gap: 10px;
}

.ex-closer-endpoint-method {
  color: var(--zen-cyan-6, #7ADAE6);
  font-weight: 500;
  letter-spacing: 0.04em;
}

.ex-closer-endpoint-host {
  color: var(--text-primary);
  font-weight: 500;
}

.ex-closer-endpoint-path {
  color: var(--text-tertiary);
}

@media (max-width: 600px) {
  
  .ex-closer-ctas {
    flex-direction: column;
    align-items: stretch;
    width: 100%;
    max-width: 320px;
  }
  .ex-closer-ctas .btn-marketing { justify-content: center; }
  .ex-closer-secondary { justify-content: center; }
}

@media (min-width: 1600px) {
  .ex-closer { max-width: 1280px; }
  
}

@media (min-width: 1920px) {
  
}

@media (min-width: 2400px) {
  
}

/* ══════════════════════════════════════════════════════════
   Audience switcher + BU light theme.
   Same mechanism as home / Fetch / Browser Sessions.  Lift to a
   shared file once Pablo confirms direction across all pages. */
[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) 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);
}
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 weight 500. */
html:not(.dark) .ex-hero-h1,
html:not(.dark) .ex-thesis-h2,
html:not(.dark) .ex-discover-h2,
html:not(.dark) .ex-schema-h2,
html:not(.dark) .ex-drift-h2,
html:not(.dark) .ex-closer-h2 {
  font-family: 'Mq12', system-ui, sans-serif;
  font-weight: 500;
}

/* Topbar inversion — yields to body.is-on-teal (which fires on
   acts 3+4 via the canonical watcher). */

/* 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); }

