/* Homepage — the eight-section fluid page (Phase 4). Loaded ONLY on `/`
   (index.html links it via {% block extra_head %}), so :root and html rules
   here scope to the homepage.

   This dispatch styles the top four sections: #reach, #why, #engine, #sampling.

   The merge (spec §5.5), applied here rather than ported literally:
     - NO device-frame chrome. There is no #top wrapper; sections sit in <main>.
       The mockups' fake phone frame (fixed 390px column, a 3D transform, a 44px
       radius, a drop shadow) is gone — the 3D transform especially would
       re-parent the fixed nav and trap it.
     - `body` stays --paper (Phase 1's .oi-body-bg), never the frame surround.
     - Every container-query unit becomes a viewport unit. Section heights ->
       svh; #engine is 155svh >1024 and 280svh <=1024 (D15); the rib card is
       86vw on phones.
     - The hero headline is a single fluid clamp() fitted through the three anchor
       sizes (30/40/60px at 390/834/1440) instead of the mockups' fixed-rem
       !important overrides, which step-function at the breakpoints.
     - The marquee gets the prefers-reduced-motion pause the design's CSS lacks.

   Breakpoints: 1024 (desktop|device split, per D14/D15) and 480 (phone). 768 is
   a synthesized intermediate that collapses #sampling to one column before its
   two-column layout gets too tight — flagged for the Task 6 responsive pass. */

:root {
  /* The sections read var(--nav-h) for their top padding; the #top wrapper that
     defined it is gone, so it lives here (homepage-only stylesheet). */
  --nav-h: 72px;
}

/* Section rhythm. Every section is exactly one viewport (100svh), so the page
   reads as a vertical deck — by GEOMETRY first. There is NO mandatory snap,
   NO wheel/touch capture, and NO programmatic scrolling anywhere on this
   page: the user's scroll is never contested or redirected. (Mandatory snap
   + the old wheel-hijack pinning caused the Safari renderer kills, the
   auto-advance-to-next-section behavior, and the snapped-somewhere-I-didn't-
   ask-for feel — all three retired together, 2026-07-23.)

   The ONE assist that remains is the gentlest the platform offers: `y
   proximity` snap on mouse/trackpad devices only, which settles a stop that
   lands NEAR a section boundary and touches nothing else — see the GENTLE
   SETTLE block at the end of this file for the full rules and why touch is
   exempt (2026-07-24, at the owner's request).

   The two scrubbed sections (#engine, #monitor) hold the viewport while their
   animations play via `position: sticky` stages inside taller scrub tracks —
   see the SCRUB TRACKS block at the end of this file and
   static/js/scroll/scrub-track.js. Native scroll is the only driver; the
   covering rule keeps every mid-scrub position a legal rest.

   No scroll-behavior:smooth either — user scrolling ignores it, and every
   programmatic jump the site makes (modal close restore, scrub focus reveal)
   passes an explicit behavior. */

/* The eyebrow spectrum bar sits in a flex column in every section head; zero the
   utility's own margins so only the column gap spaces it. */
.oi-hero__eyebrow-wrap .oi-spectrum-bar,
.oi-why__eyebrow-wrap .oi-spectrum-bar,
.oi-engine__eyebrow-wrap .oi-spectrum-bar,
.oi-sampling__eyebrow-wrap .oi-spectrum-bar,
.oi-assurance__eyebrow-wrap .oi-spectrum-bar,
.oi-monitor__eyebrow-wrap .oi-spectrum-bar,
.oi-monitor__cardhead-label .oi-spectrum-bar,
.oi-spoiler__eyebrow-wrap .oi-spectrum-bar,
.oi-close__eyebrow-wrap .oi-spectrum-bar { margin: 0; }
/* The live-monitor card head draws the bar a touch narrower (52px). */
.oi-spectrum-bar--sm { width: 52px; }

@keyframes oi-vmarquee { from { transform: translateY(0); } to { transform: translateY(-50%); } }
@keyframes oi-hmarquee { from { transform: translateX(0); } to { transform: translateX(-50%); } }

/* ============================================================ #reach (hero) */
/* A globe canvas fills a dark section; the headline sits over it. Phase 5 draws
   the globe — here the canvas is empty and correctly sized. That is correct. */
.oi-hero {
  position: relative; height: 100svh; overflow: hidden;
  background: transparent; color: var(--text-on-ink);
}
.oi-hero__sticky {
  position: sticky; top: 0; height: 100svh; overflow: hidden; z-index: 6;
}
.oi-hero__dark {
  position: absolute; inset: 0; z-index: 1; background: #0E1626; pointer-events: none;
}
.oi-hero__layer {
  position: absolute; inset: 0; z-index: 2; display: flex; align-items: center;
}
.oi-hero__canvas {
  position: absolute; inset: 0; width: 100%; height: 100%; z-index: 1; display: block;
  -webkit-mask-image: linear-gradient(to bottom, #000 0, #000 calc(100% - 140px), transparent 100%);
          mask-image: linear-gradient(to bottom, #000 0, #000 calc(100% - 140px), transparent 100%);
}
.oi-hero__scrim {
  position: absolute; inset: 0; z-index: 2; pointer-events: none;
  background:
    radial-gradient(105% 92% at 3% 26%, rgba(8,13,24,0.97) 0%, rgba(8,13,24,0.68) 32%, rgba(8,13,24,0) 60%),
    linear-gradient(270deg, rgba(8,13,24,0.86) 0%, rgba(8,13,24,0.5) 26%, rgba(8,13,24,0) 50%);
}
.oi-hero__inner {
  position: relative; z-index: 3; width: 100%;
  max-width: var(--container); margin: 0 auto; padding: 0 var(--gutter);
}
.oi-hero__scrub { max-width: 780px; padding: var(--nav-h) 0 14px; }
.oi-hero__eyebrow-wrap { display: flex; flex-direction: column; gap: 10px; margin-bottom: 20px; }
.oi-hero__eyebrow {
  font-family: var(--font-mono); font-size: 12px; font-weight: 500;
  letter-spacing: .12em; text-transform: uppercase; color: var(--amber-400);
}
/* Fluid through 390/834/1440 -> 30/~43/60px. One clamp, no step at the
   breakpoints (spec §5.5.6). The mockups' floor token (2.5rem) is too big for a
   phone, so this drops below it, hitting the design's 30px at 390. */
.oi-hero__headline {
  font-size: clamp(1.875rem, 2.86vw + 1.18rem, 3.75rem);
  letter-spacing: -.025em; margin: 0; color: #fff; line-height: 1.04;
  text-shadow: 0 2px 34px rgba(8,13,24,0.72), 0 1px 4px rgba(8,13,24,0.55);
}
.oi-hero__sub {
  font-size: var(--text-body-lg); line-height: 1.6; color: #d3dbe9;
  margin: 22px 0 0; max-width: 52ch; text-shadow: 0 1px 16px rgba(8,13,24,0.8);
}
.oi-hero__cta-row { display: flex; gap: 14px; margin-top: 30px; flex-wrap: wrap; }

/* ============================================================ #why (2×2) */
/* Never collapses to one column — 2×2 at every width (verified). */
.oi-why {
  position: relative; z-index: 1;
  background: var(--paper-2); border-top: 1px solid var(--line);
  height: 100svh; overflow: hidden; display: flex;
  /* safe center: centre when the content fits, but top-align (clearing the
     fixed nav) when it is too tall — the topmost element never tucks under. */
  align-items: safe center;
  /* Reserve the fixed nav so centered content never tucks under it (the nav is
     a 72px overlay). box-sizing:border-box keeps the section one viewport. */
  padding-block: var(--nav-h) clamp(16px, 3vh, 32px);
}
.oi-why__inner {
  width: 100%; max-width: var(--container); margin: 0 auto;
  /* Deck spacing: ~nav clearance up top, a slim floor. Was clamp(4.5rem,8vw,8rem)
     both sides (up to 230px total) — far too tall for a one-viewport section. */
  padding: clamp(76px, 10vh, 104px) var(--gutter) clamp(22px, 5vh, 48px);
}
.oi-why__head { max-width: 880px; margin-bottom: clamp(22px, 3.4vh, 52px); }
.oi-why__eyebrow-wrap { display: flex; flex-direction: column; gap: 12px; margin-bottom: 24px; }
.oi-why__eyebrow {
  font-family: var(--font-mono); font-size: 12px; font-weight: 500;
  letter-spacing: .12em; text-transform: uppercase; color: var(--amber-600);
}
/* Fluid through the design's per-device anchors: 24.8px @390 (mobile inline
   1.55rem), ~38px @834, 56px @1440 (desktop clamp ceiling 3.5rem). One
   continuous clamp — no step, and no frozen-too-big mobile size (the ported
   desktop floor was 2.3rem/36.8px, 12px over the mobile design). */
.oi-why__title {
  font-family: var(--font-sans); font-size: clamp(1.55rem, 2.97vw + 0.826rem, 3.5rem);
  font-weight: 700; letter-spacing: -.024em; line-height: 1.04;
  color: var(--navy-700); margin: 0; text-wrap: balance;
}
.oi-why__grid {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(20px, 2vw, 28px);
}
/* Tall viewports: lift the content modestly (trim the top padding) but keep the
   cards CONTENT-sized and centred. Stretching the cards to fill the height left
   them hollow with ~220px empty bands below the text — worse than the balanced
   whitespace of a compact centred grid. */
@media (min-height: 780px) {
  .oi-why__inner { padding-top: clamp(16px, 3vh, 40px); }
}
.oi-why__card {
  position: relative; overflow: hidden;
  background: var(--white); border: 1px solid var(--line);
  border-top: 3px solid var(--why-accent);
  border-radius: var(--radius-lg); box-shadow: var(--shadow-sm);
  padding: clamp(30px, 3vw, 44px);
  transition: transform .2s var(--ease-out), box-shadow .2s var(--ease-out);
}
.oi-why__card:hover { transform: translateY(-4px); box-shadow: var(--shadow-md); }
/* Per-card accent — kept in CSS so why_cards.py stays copy-only.
   amber / green / blue / terracotta, verbatim from the design. */
.oi-why__card:nth-child(1) { --why-accent: #C9842F; }
.oi-why__card:nth-child(2) { --why-accent: #3E9E73; }
.oi-why__card:nth-child(3) { --why-accent: #3E78AE; }
.oi-why__card:nth-child(4) { --why-accent: #C65F3C; }
.oi-why__ghost {
  position: absolute; top: clamp(6px, 1vw, 14px); right: clamp(14px, 2vw, 26px);
  font-family: var(--font-mono); font-size: clamp(72px, 7vw, 104px); font-weight: 700;
  line-height: 1; color: var(--why-accent); opacity: .09; pointer-events: none;
}
.oi-why__num {
  position: relative; font-family: var(--font-mono); font-size: 12px;
  font-weight: 600; letter-spacing: .1em; color: var(--why-accent);
}
.oi-why__card-title {
  position: relative; font-family: var(--font-sans);
  font-size: clamp(1.35rem, 1.9vw, 1.7rem); font-weight: 600;
  letter-spacing: -.017em; line-height: 1.15; color: var(--text-strong); margin: 16px 0 0;
  /* Reserve the ghost numeral's corner: one-line titles otherwise run straight
     through the giant "01"-"04" watermark ("...it gets hard." ending inside the
     numeral — audited at 574/1180/1440/1920). Sized to the numeral's clamp. */
  padding-right: clamp(58px, 5.8vw, 96px);
}
.oi-why__card-body {
  position: relative; font-size: 16.5px; line-height: 1.6;
  color: var(--text-muted); margin: 14px 0 0; max-width: 46ch;
}

/* ============================================================ #engine (pipeline) */
/* A 100svh sticky stage inside its scrub track. The inner fills it and centres
   the pipeline; the 4-up grid (desktop) / single stacked card (≤1024) flips as
   native scroll sweeps p through the track (scroll/scrub-track.js driving
   scroll/spine.js's updatePipeline). */
.oi-engine {
  position: relative; z-index: 1; margin-top: 0; height: 100svh; overflow: hidden; background: transparent;
  /* Nav clearance on the SECTION, and the section itself centres the inner in the
     BALANCED remaining area (was padding-top on the inner + inner height:100%,
     which biased the centred content downward and left a big gap under the nav). */
  padding-block: var(--nav-h) clamp(16px, 3vh, 32px); box-sizing: border-box;
  display: flex; align-items: safe center;
}
.oi-engine__inner {
  position: relative; width: 100%;
  display: flex; flex-direction: column; overflow: hidden;
}
.oi-engine__wrap {
  width: 100%; max-width: var(--container); margin: 0 auto; padding: 0 var(--gutter);
}
.oi-engine__head { max-width: 680px; margin-bottom: 8px; }
.oi-engine__eyebrow-wrap { display: flex; flex-direction: column; gap: 9px; margin-bottom: 12px; }
.oi-engine__eyebrow {
  font-family: var(--font-mono); font-size: 12px; font-weight: 500;
  letter-spacing: .12em; text-transform: uppercase; color: var(--text-accent);
}
.oi-engine__title {
  font-size: clamp(1.65rem, 2.6vw, 2.2rem); letter-spacing: -.02em;
  margin: 0; color: var(--text-strong);
}
.oi-engine__lead {
  font-size: var(--text-body-lg); line-height: 1.55; color: var(--text-muted);
  margin: 18px 0 0; max-width: 58ch;
}
.oi-engine__rail-wrap { position: relative; margin-top: 54px; }
.oi-engine__rail {
  position: relative; height: 2px; background: var(--line);
  margin: 0 6px 36px; border-radius: 2px;
}
.oi-engine__rail-fill {
  position: absolute; left: 0; top: 0; height: 100%; width: 0%;
  background: var(--spectrum); border-radius: 2px;
}
.oi-engine__pulse {
  position: absolute; top: 50%; left: 0%; width: 13px; height: 13px;
  margin: -6.5px 0 0 -6.5px; border-radius: 50%; background: var(--amber-400);
  box-shadow: 0 0 0 6px rgba(226,162,76,.18); opacity: 0;
}
.oi-engine__grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 18px; }

.oi-pipe {
  --pipe-text: var(--pipe-accent);
  position: relative; perspective: 1500px; -webkit-perspective: 1500px;
  display: flex; flex-direction: column;
}
.oi-pipe__inner {
  position: relative; flex: 1; min-height: 0;
  display: flex; flex-direction: column;
  transform-style: preserve-3d; -webkit-transform-style: preserve-3d;
  transition: transform .72s cubic-bezier(.62,.04,.2,1);
  transform: rotateY(0deg); will-change: transform;
}
.oi-pipe__back {
  position: relative; flex: 1;
  display: flex; flex-direction: column; gap: 10px;
  padding: 20px; box-sizing: border-box;
  background: var(--white); border: 1px solid var(--line);
  border-radius: var(--radius-lg); box-shadow: var(--shadow-sm); overflow: hidden;
  -webkit-backface-visibility: hidden; backface-visibility: hidden;
  transform: rotateY(180deg);
}
.oi-pipe__front {
  position: absolute; inset: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  text-align: center; gap: 14px; padding: 22px; box-sizing: border-box;
  background: var(--white); border: 1px solid var(--line);
  border-radius: var(--radius-lg); box-shadow: var(--shadow-sm); overflow: hidden;
  -webkit-backface-visibility: hidden; backface-visibility: hidden;
  transform: rotateY(0deg);
}
.oi-pipe__bar {
  position: absolute; left: 0; top: 0; height: 3px; width: 100%; background: var(--pipe-accent);
}
.oi-pipe__tag { display: flex; align-items: center; gap: 10px; }
.oi-pipe__dot {
  width: 10px; height: 10px; border-radius: 50%; background: var(--pipe-accent);
  box-shadow: 0 0 0 5px color-mix(in srgb, var(--pipe-accent) 15%, transparent);
  flex: 0 0 auto;
}
.oi-pipe__eyebrow {
  font-family: var(--font-mono); font-size: 12px; letter-spacing: .12em;
  text-transform: uppercase; color: var(--pipe-text);
}
.oi-pipe__title {
  font-size: var(--text-h4); font-weight: 700; color: var(--text-strong); letter-spacing: -.01em;
}
.oi-pipe__body { font-size: var(--text-body-sm); color: var(--text-muted); line-height: 1.5; }
.oi-pipe__stats {
  margin-top: auto; padding-top: 14px; border-top: 1px solid var(--line-soft);
  display: flex; gap: 20px;
}
.oi-pipe__stat { display: flex; flex-direction: column; gap: 2px; }
.oi-pipe__stat-value {
  font-family: var(--font-mono); font-size: 15px; font-weight: 500; color: var(--text-strong);
}
.oi-pipe__stat-label {
  font-family: var(--font-mono); font-size: 10px; letter-spacing: .07em;
  text-transform: uppercase; color: var(--text-faint);
}
.oi-pipe__explore {
  display: flex; align-items: center; justify-content: center; gap: 8px;
  height: 38px; border-radius: var(--radius-md);
  border: 1.5px solid color-mix(in srgb, var(--pipe-text) 50%, transparent);
  color: var(--pipe-text); font-size: 13.5px; font-weight: 600; text-decoration: none;
  transition: background .16s, color .16s, border-color .16s;
}
.oi-pipe__explore:hover { background: var(--pipe-text); border-color: var(--pipe-text); color: #fff; }
.oi-pipe__arrow {
  position: absolute; top: 14px; right: 14px; width: 34px; height: 34px;
  border-radius: 50%; display: flex; align-items: center; justify-content: center;
  background: color-mix(in srgb, var(--pipe-accent) 13%, transparent);
  color: var(--pipe-text); text-decoration: none;
  transition: transform .14s var(--ease-out), background .14s, color .14s;
}
.oi-pipe__arrow:hover { transform: translateX(2px); background: var(--pipe-text); color: #fff; }
.oi-pipe__icon {
  width: 76px; height: 76px; border-radius: 50%; flex: 0 0 auto;
  display: flex; align-items: center; justify-content: center;
  background: color-mix(in srgb, var(--pipe-accent) 13%, transparent);
  color: var(--pipe-text);
}
.oi-pipe__front-label { display: flex; flex-direction: column; gap: 5px; }
.oi-pipe__front .oi-pipe__eyebrow { font-size: 11px; letter-spacing: .14em; }

/* ============================================================ #sampling (marquee) */
.oi-sampling {
  background: transparent; position: relative;
  height: 100svh; overflow: hidden; display: flex;
  /* safe center: centre when the content fits, but top-align (clearing the
     fixed nav) when it is too tall — the topmost element never tucks under. */
  align-items: safe center;
  padding: var(--nav-h) 0 14px;
}
.oi-sampling__inner {
  width: 100%; max-width: var(--container); margin: 0 auto; padding: 0 var(--gutter);
}
.oi-sampling__grid {
  display: grid; grid-template-columns: minmax(0, 1fr) 384px;
  gap: clamp(32px, 5vw, 72px); align-items: center;
}
.oi-sampling__copy { max-width: 560px; }
.oi-sampling__eyebrow-wrap { display: flex; flex-direction: column; gap: 9px; margin-bottom: 12px; }
.oi-sampling__eyebrow {
  font-family: var(--font-mono); font-size: 12px; font-weight: 500;
  letter-spacing: .12em; text-transform: uppercase; color: var(--text-accent);
}
/* Fluid through the design's anchors: 20.8px @390 (mobile inline 1.3rem),
   ~27px @834, 35.2px @1440 (ceiling 2.2rem). Diverges from #engine on mobile —
   the design gives #sampling a smaller phone heading (1.3rem vs 1.65rem). */
.oi-sampling__title {
  font-size: clamp(1.3rem, 1.37vw + 0.966rem, 2.2rem); letter-spacing: -.02em;
  margin: 0; color: var(--text-strong);
}
/* --text-size-body (16px): the design writes var(--text-body), which is a COLOR
   token in ds.css (Phase 1 renamed the size). See site.css's same note. */
.oi-sampling__lead {
  font-size: var(--text-size-body); line-height: 1.5; color: var(--text-muted);
  margin: 14px 0 18px; max-width: 54ch;
}
.oi-sampling__points { display: flex; flex-direction: column; gap: 12px; }
.oi-sampling__point { display: flex; gap: 14px; }
.oi-sampling__bullet {
  margin-top: 7px; width: 7px; height: 7px; border-radius: 50%;
  background: var(--amber-500); flex: 0 0 auto;
}
.oi-sampling__point-title { font-weight: 600; color: var(--text-strong); margin-bottom: 2px; }
.oi-sampling__point-body { font-size: 15px; color: var(--text-muted); line-height: 1.55; }
.oi-sampling__cta-row { display: flex; align-items: center; gap: 16px; margin-top: 26px; flex-wrap: wrap; }

/* Navy pill CTA — the design's "Run free feasibility" / "Learn More" button.
   Distinct from .oi-btn-dark (42px, radius-md): this is 50px on the pill radius. */
.oi-btn-navy {
  display: inline-flex; align-items: center; justify-content: center; gap: 9px;
  height: 50px; padding: 0 26px;
  font-family: var(--font-sans); font-size: var(--text-size-body); font-weight: 600; line-height: 1;
  color: #fff; background: var(--navy-800); border: none; border-radius: var(--radius-pill);
  cursor: pointer; text-decoration: none;
  transition: background .18s ease, transform .18s ease;
}
.oi-btn-navy:hover { background: var(--navy-900); transform: translateY(-1px); }
.oi-btn-navy__arrow { font-size: 1.1em; line-height: 0; }

.oi-rib__head { display: flex; align-items: center; gap: 12px; margin-bottom: 16px; }
.oi-rib__head-label {
  font-family: var(--font-mono); font-size: 11px; font-weight: 600;
  letter-spacing: .12em; text-transform: uppercase; color: var(--amber-600); white-space: nowrap;
}
.oi-rib__head-rule { flex: 1; height: 1px; background: var(--line); }
.oi-rib__viewport {
  position: relative; height: min(680px, 78svh); overflow: hidden;
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 90px, #000 calc(100% - 90px), transparent 100%);
          mask-image: linear-gradient(to bottom, transparent 0, #000 90px, #000 calc(100% - 90px), transparent 100%);
}
.oi-rib__track {
  display: flex; flex-direction: column; gap: 16px;
  animation: oi-vmarquee 48s linear infinite; will-change: transform;
}
.oi-rib__card {
  flex: 0 0 auto; box-sizing: border-box; width: 100%; height: 222px; margin: 0;
  display: flex; flex-direction: column; gap: 13px; padding: 22px 22px 20px;
  background: var(--white); border: 1px solid var(--line);
  border-radius: var(--radius-lg); box-shadow: var(--shadow-sm);
}
.oi-rib__card-top { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.oi-rib__label {
  font-family: var(--font-mono); font-size: 10px; font-weight: 600;
  letter-spacing: .09em; color: var(--amber-600); white-space: nowrap;
}
.oi-rib__card-bar {
  width: 34px; height: 3px; border-radius: 2px; background: var(--spectrum); flex: 0 0 auto;
}
.oi-rib__audience {
  font-family: var(--font-sans); font-size: 19px; font-weight: 600;
  letter-spacing: -.01em; color: var(--text-strong); line-height: 1.2;
}
.oi-rib__qualifier {
  flex: 1; font-family: var(--font-mono); font-size: 11px; letter-spacing: .02em;
  color: var(--text-muted); line-height: 1.4;
}
.oi-rib__stats {
  display: flex; align-items: flex-end; gap: 26px;
  padding-top: 14px; border-top: 1px solid var(--line-soft);
}
.oi-rib__stat { display: flex; flex-direction: column; gap: 2px; }
.oi-rib__stat-value {
  font-family: var(--font-mono); font-size: 21px; font-weight: 600;
  letter-spacing: -.01em; color: var(--navy-800); line-height: 1;
}
.oi-rib__stat-label {
  font-family: var(--font-mono); font-size: 9.5px; font-weight: 500;
  letter-spacing: .07em; color: var(--text-muted);
}

/* ============================================================ <=1024 (device) */
@media (max-width: 1024px) {
  /* #engine serial flip setup: stack the four cards absolutely; the entry-driven
     rAF in spine.js swings them 90→180→270 in sequence. The section is one
     viewport (100svh) — the height override the scroll runway needed is gone. */
  .oi-engine__grid {
    display: block; position: relative; height: 470px;
    max-width: 600px; margin-left: auto; margin-right: auto; gap: 0;
  }
  .oi-pipe { position: absolute; inset: 0; transition: none; }

  /* Tablet mirrors the user's MOBILE design. The tablet 2-col sampling, the
     interactive spoiler card, and the on-chart monitor labels all cramp/break at
     tablet widths; the mobile design already solves them (stack + simplify).
     Apply those treatments across the whole tablet range; >1024 keeps desktop. */

  /* #reach: drop the globe to the bottom 46% (mobile treatment) so the lead copy
     sits ABOVE it on clean dark ground instead of over the bright landmass with
     city labels bleeding through the paragraph. The mask gains a TOP fade the
     full-viewport desktop canvas doesn't need: at squat aspect ratios the
     sphere (R = min(.42w, .66h), cy = .62h) rides above the canvas top and the
     clip drew a hard horizontal line across the page. */
  .oi-hero__canvas {
    inset: auto 0 0 0; height: 46%;
    -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 96px, #000 calc(100% - 120px), transparent 100%);
            mask-image: linear-gradient(to bottom, transparent 0, #000 96px, #000 calc(100% - 120px), transparent 100%);
  }

  /* The scrim exists to shield copy that sits OVER the globe — desktop only.
     Here the copy is above the canvas, so the scrim just murdered the globe's
     left-side colour and dimmed the city labels. */
  .oi-hero__scrim { background: none; }

  /* With the globe at the bottom, the copy anchors under the nav instead of
     keeping the desktop's vertical centering — centered copy left a dead band
     below the nav and dropped the CTA onto the landmass (seen at ~950x840). */
  .oi-hero__layer { align-items: flex-start; }
  .oi-hero__scrub { padding-top: calc(var(--nav-h) + clamp(26px, 7vh, 72px)); }

  /* The design's min-height model is restored in the FINAL @media block at the
     end of this file — it must come after every section's base rule to win the
     cascade (same specificity, source order decides). */

  /* Sampling: stack the copy over a HORIZONTAL marquee (was 2-col, which crushed
     the text column and left the marquee sparse with half-cut cards). Cap +
     centre the column so a WIDE-but-short landscape tablet (1024x768) doesn't
     strand the content in the left half with a dead right column. */
  .oi-sampling__grid { grid-template-columns: minmax(0, 1fr); gap: clamp(22px, 3.4vh, 40px); max-width: 820px; margin-inline: auto; }
  .oi-rib__viewport {
    width: 100%; max-width: 100%; height: auto;
    -webkit-mask-image: linear-gradient(to right, transparent 0, #000 20px, #000 calc(100% - 20px), transparent 100%);
            mask-image: linear-gradient(to right, transparent 0, #000 20px, #000 calc(100% - 20px), transparent 100%);
  }
  .oi-rib__track { flex-direction: row; width: max-content; align-items: stretch; animation-name: oi-hmarquee; animation-duration: 64s; }
  .oi-rib__card { width: min(86vw, 360px); height: auto; min-height: 172px; }
  /* Spoiler + monitor mobile-mirror rules live in a LATER @media block (after the
     base .oi-spmini rules) so they actually win — see below. */
}

/* ============================================================ <=560 (phone) */
/* Only the narrowest phones collapse to one column — below this a 2-col marquee
   card is too cramped. The single-column content is short enough at phone width
   (tall viewport) that the eyebrow still clears the nav. */
@media (max-width: 560px) {
  .oi-sampling__grid { grid-template-columns: minmax(0, 1fr); }
}

/* ============================================================ <=480 (phone) */
@media (max-width: 480px) {
  /* Globe drops to the bottom 46% (empty until Phase 5; carried for it). */
  .oi-hero__canvas { inset: auto 0 0 0; height: 46%; }

  /* #why stays 2×2 — only the gap tightens. */
  .oi-why__grid { gap: 12px; }

  /* #engine stack shortens on the phone; slightly roomier card padding. The grid
     is a FIXED height (the flip stacks cards position:absolute, so it can't be
     content-sized) — keep it close to the card's real content height so the
     single device card doesn't leave a big empty void between body and stats. */
  .oi-engine__grid { height: 316px; }
  .oi-pipe__back { padding: 22px; }

  /* Audiences marquee goes horizontal: cards flow in a row, 86vw wide (capped),
     animating -50% on X. */
  .oi-rib__viewport {
    width: 100%; max-width: 100%; height: auto;
    -webkit-mask-image: linear-gradient(to right, transparent 0, #000 20px, #000 calc(100% - 20px), transparent 100%);
            mask-image: linear-gradient(to right, transparent 0, #000 20px, #000 calc(100% - 20px), transparent 100%);
  }
  .oi-rib__track {
    flex-direction: row; width: max-content; align-items: stretch;
    animation-name: oi-hmarquee; animation-duration: 64s;
  }
  .oi-rib__card {
    width: 86vw; max-width: 330px; height: auto; min-height: 172px;
    padding: 14px 16px; gap: 8px;
  }
}

/* ============================================================ #assurance */
/* "Data you can defend." + the <signal-refine> graphic. The element paints
   NOTHING until Phase 5 — an empty box is the correct static state. Both
   orientation variants ship (attributes can't flip in CSS); one shows per
   breakpoint. */
.oi-assurance {
  position: relative; height: 100svh; background: var(--paper);
  overflow: hidden; display: flex; align-items: safe center;
  padding-block: var(--nav-h) clamp(16px, 3vh, 32px);  /* clear the fixed nav */
}
.oi-assurance__inner {
  position: relative; z-index: 3; width: 100%; max-width: var(--container);
  margin: 0 auto; padding: clamp(76px, 11vh, 104px) var(--gutter) clamp(20px, 4vh, 32px);
  display: flex; flex-direction: column; align-items: center; gap: clamp(18px, 2.6vh, 40px);
}
.oi-assurance__head { max-width: 660px; text-align: center; }
.oi-assurance__eyebrow-wrap {
  display: flex; flex-direction: column; align-items: center; gap: 10px; margin-bottom: 20px;
}
.oi-assurance__eyebrow {
  font-family: var(--font-mono); font-size: 12px; font-weight: 500;
  letter-spacing: .12em; text-transform: uppercase; color: var(--text-accent);
}
.oi-assurance__title {
  font-size: clamp(1.95rem, 3.2vw, 2.9rem); letter-spacing: -.025em;
  line-height: 1.04; margin: 0; color: var(--text-strong);
}
.oi-assurance__lead {
  font-size: var(--text-body-lg); line-height: 1.55; color: var(--text-muted);
  margin: 22px auto 0; max-width: 56ch;
}
.oi-assurance__cta-row {
  display: flex; align-items: center; justify-content: center; gap: 16px;
  margin-top: 28px; flex-wrap: wrap;
}
.oi-assurance__graphic { position: relative; width: 100%; }
/* The graphic is the section's tallest element; cap it by viewport height so it
   shrinks on short/laptop screens rather than pushing the caption off-screen. */
.oi-assurance__graphic--h { height: clamp(180px, 34vh, 392px); }
.oi-assurance__graphic--v { display: none; height: clamp(230px, 40vh, 354px); }

/* Fill the height on tall viewports (tablet portrait, tall phones): stretch the
   inner to the full section and let the funnel graphic grow into the leftover
   space, so the section reads as composed to the viewport rather than a compact
   block stranded in the centre with dead bands above and below. Height-gated so
   short screens keep their scale-to-fit treatment. */
@media (min-height: 780px) {
  .oi-assurance { align-items: stretch; }
  .oi-assurance__inner { justify-content: center; padding-top: clamp(14px, 2.4vh, 30px); }
  .oi-assurance__graphic--h { flex: 1 1 auto; height: auto; min-height: 260px; max-height: 560px; }
  .oi-assurance__graphic--v { flex: 1 1 auto; height: auto; min-height: 280px; max-height: 520px; }
}
.oi-assurance__signal { display: block; }
.oi-assurance__caption {
  display: flex; flex-wrap: wrap; align-items: baseline; justify-content: center;
  gap: 9px 18px; font-family: var(--font-mono); font-size: 13px; letter-spacing: .02em;
  color: var(--text-muted); text-align: center;
}
.oi-assurance__caption-item { white-space: nowrap; }
.oi-assurance__caption-strong { color: var(--text-strong); font-weight: 600; }
.oi-assurance__caption-dot { color: var(--amber-600); }
/* The caption fits one line only above ~800px (its natural width is ~749px);
   below that the row wraps and the amber middots strand at line ends. When it
   can't be one line, stack the three items and drop the separators. */
@media (max-width: 820px) {
  .oi-assurance__caption { flex-direction: column; gap: 6px; }
  .oi-assurance__caption-dot { display: none; }
}

/* ============================================================ #monitor */
/* A 100svh sticky stage inside its scrub track; the inner stage div fills it.
   The chart growth + summary card reveal scrub with native scroll through the
   track (scroll/scrub-track.js driving graphics/monitor-scrub.js's render).
   Progressive enhancement: [data-mon-grow] ships the pre-scrub transform
   (scale(1 0.0001) — curves collapsed onto the baseline). Without JS we reset it
   so the hand-authored SVG reads in full. WITH JS, monitor-scrub.js drives that
   same transform ATTRIBUTE frame-by-frame — and a CSS `transform` property beats
   the SVG attribute, so this reset must be gated to no-JS or it would pin the
   chart flat forever (see the html:not(.js) selector below). */
.oi-monitor { position: relative; height: 100svh; overflow: hidden; background: var(--paper); }
.oi-monitor__stage { position: relative; height: 100svh; overflow: hidden; }
.oi-monitor__bg { position: absolute; inset: 0; z-index: 1; }
/* The chart's SVG text labels (THREATS FILTERED, LIVE MONITOR, QUOTA, VERIFIED
   COMPLETES) collide with each other and the copy, and clip at the viewport edge
   even on desktop ("QUOTA" -> "QUO A"). The growth chart reads fine as a pure
   line flourish, so drop the labels at every width. */
.oi-monitor__bg text { display: none; }
.oi-monitor__svg {
  position: absolute; inset: 0; display: block;
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 120px, #000 100%);
          mask-image: linear-gradient(to bottom, transparent 0, #000 120px, #000 100%);
}
html:not(.js) .oi-monitor__svg [data-mon-grow] { transform: none; }
.oi-monitor__scrim {
  position: absolute; inset: 0; z-index: 2; pointer-events: none;
  /* Solid through the copy column (which ends ~46% at 1920), then release — the
     old 28%/46% stops let the dashed target line and the orange curve print
     through the paragraph at 1280-1920 (audited). The chart stays visible right
     of ~66%, where the dashboard card and its margin live. */
  background: linear-gradient(90deg, rgba(251,250,247,0.6) 0%, rgba(251,250,247,0.42) 55%, rgba(251,250,247,0) 82%);
}
.oi-monitor__copy {
  position: relative; z-index: 3; height: 100%; display: flex; align-items: center;
  /* The animated scrim's element opacity is JS-driven and CAPS AT 0.66, so no
     scrim gradient alone can fully shield the copy — the chart's dashed target
     line and curve printed through the paragraph at 1280-1920. The copy layer
     carries its own permanent paper backdrop instead. */
  /* Translucent, not solid: the chart's colour must survive behind the copy
     (client call 2026-07-22) — the wash keeps the text comfortably readable
     while the curves show through as soft traces. */
  background: linear-gradient(90deg, rgba(251,250,247,0.82) 0%, rgba(251,250,247,0.72) 46%, rgba(251,250,247,0) 70%);
}
.oi-monitor__panel {
  width: 100%; max-width: var(--container); margin: 0 auto; padding: var(--nav-h) var(--gutter) 0;
}
.oi-monitor__panel-inner { max-width: 530px; }
.oi-monitor__eyebrow-wrap { display: flex; flex-direction: column; gap: 10px; margin-bottom: 18px; }
.oi-monitor__eyebrow {
  font-family: var(--font-mono); font-size: 12px; font-weight: 500;
  letter-spacing: .12em; text-transform: uppercase; color: var(--text-accent);
}
/* Fluid through the design's anchors: 24px @390 (mobile inline 1.5rem),
   ~33px @834, 46.4px @1440 (ceiling 2.9rem). Was frozen at 2rem/32px on the
   phone — 8px over the mobile design. */
.oi-monitor__title {
  font-size: clamp(1.5rem, 2.13vw + 0.98rem, 2.9rem); letter-spacing: -.025em;
  line-height: 1.04; margin: 0; color: var(--text-strong);
}
.oi-monitor__lead {
  font-size: var(--text-body-lg); line-height: 1.6; color: var(--text-muted);
  margin: 20px 0 0; max-width: 42ch;
}
.oi-monitor__panel-foot { margin-top: 30px; padding-top: 24px; border-top: 1px solid var(--line); }
.oi-monitor__cardwrap { position: absolute; inset: 0; z-index: 5; pointer-events: none; }
.oi-monitor__cardwrap-inner {
  height: 100%; max-width: var(--container); margin: 0 auto;
  padding: calc(var(--nav-h) + 10px) var(--gutter) 26px; box-sizing: border-box;
  display: flex; align-items: center; justify-content: flex-end;
}
.oi-monitor__summary {
  width: min(700px, calc(100% - 558px)); max-height: 100%; overflow: auto;
  pointer-events: auto; background: var(--paper-2); border: 1px solid var(--line);
  border-radius: var(--radius-lg); box-shadow: var(--shadow-lg); padding: clamp(20px, 2.2vw, 28px);
}
.oi-monitor__cardhead { display: flex; align-items: flex-start; justify-content: space-between; gap: 14px; }
.oi-monitor__cardhead-label { display: flex; flex-direction: column; gap: 9px; }
.oi-monitor__cardhead-eyebrow {
  font-family: var(--font-mono); font-size: 11.5px; letter-spacing: .12em;
  text-transform: uppercase; color: var(--text-accent); white-space: nowrap;
}
.oi-monitor__cardhead-status {
  display: inline-flex; align-items: center; gap: 7px; white-space: nowrap;
  font-family: var(--font-mono); font-size: 11px; letter-spacing: .05em; color: var(--text-muted);
}
.oi-monitor__status-dot {
  width: 7px; height: 7px; border-radius: 50%; background: var(--verify-500);
  box-shadow: 0 0 0 3px rgba(47,125,91,.15); flex: 0 0 auto;
}
.oi-monitor__health { margin-top: 22px; }
.oi-monitor__section-label {
  display: block; font-family: var(--font-mono); font-size: 10.5px; letter-spacing: .1em;
  text-transform: uppercase; color: var(--text-faint); margin-bottom: 11px;
}
.oi-monitor__health-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px 18px; }
.oi-monitor__stat { display: flex; flex-direction: column; gap: 5px; }
.oi-monitor__stat--completes { gap: 7px; }
.oi-monitor__stat-value {
  font-family: var(--font-mono); font-size: clamp(1.4rem, 2.2vw, 1.8rem);
  font-weight: 500; color: var(--text-strong); line-height: 1;
}
.oi-monitor__stat-label {
  font-family: var(--font-mono); font-size: 9.5px; letter-spacing: .06em;
  text-transform: uppercase; color: var(--text-muted); line-height: 1.3;
}
/* Progress-track fill: the design's warm-grey "well" colour. --surface-inset is
   the semantic token for a sunken surface (its docs read "subtle wells") — the
   right token for a track, aliasing the same palette value the device-frame
   surround uses. Referencing the surround token directly is banned on the
   homepage (it must never be a page background), and a magic hex would drift
   from the token; the inset alias is neither. */
.oi-monitor__bar-track {
  display: block; height: 5px; border-radius: 3px; background: var(--surface-inset);
  overflow: hidden; margin-top: 2px;
}
.oi-monitor__bar-fill { display: block; height: 100%; border-radius: 3px; background: var(--verify-500); }
.oi-monitor__stat-note { font-family: var(--font-mono); font-size: 10.5px; color: var(--text-faint); line-height: 1.3; }
.oi-monitor__week { margin-top: 24px; padding-top: 20px; border-top: 1px solid var(--line); }
.oi-monitor__week-rows { display: flex; flex-direction: column; gap: 8px; }
.oi-monitor__week-row {
  display: flex; align-items: flex-start; gap: 11px; padding: 9px 12px;
  background: var(--white); border: 1px solid var(--line-soft); border-radius: var(--radius-md);
}
.oi-monitor__week-dot {
  flex: 0 0 auto; width: 6px; height: 6px; border-radius: 50%;
  background: var(--amber-500); margin-top: 6px;
}
.oi-monitor__week-text { flex: 1; font-size: 13px; line-height: 1.45; color: var(--text-body); }
.oi-monitor__note {
  margin: 22px 0 0; font-family: var(--font-mono); font-size: 11px;
  letter-spacing: .03em; color: var(--text-faint);
}

/* ============================================================ #spoiler */
/* The demo card's two canvases ship empty (Phase 5 draws them). The FSM
   overlays ship in the DOM; the "proposed" coding frame renders at rest so the
   eleven rows + the data-driven total are visible now. */
.oi-spoiler {
  background: transparent; position: relative; height: 100svh; overflow: hidden;
  display: flex; align-items: safe center; padding: var(--nav-h) 0 14px;
}
.oi-spoiler__inner { width: 100%; max-width: var(--container); margin: 0 auto; padding: 0 var(--gutter); }
.oi-spoiler__split { display: grid; grid-template-columns: 1fr 1.08fr; gap: var(--space-9); align-items: center; }
.oi-spoiler__eyebrow-wrap { display: flex; flex-direction: column; gap: 10px; margin-bottom: 20px; }
.oi-spoiler__eyebrow {
  font-family: var(--font-mono); font-size: 12px; font-weight: 500;
  letter-spacing: .12em; text-transform: uppercase; color: var(--amber-600);
}
.oi-spoiler__logo { display: block; width: min(330px, 80%); height: auto; aspect-ratio: 1150 / 1024; margin: 0 0 30px; }
/* Fluid through the design's anchors: 26.4px @390 (mobile #spoiler h2 override
   1.65rem !important), ~34px @834, 44.8px @1440 (ceiling 2.8rem). Was frozen at
   2rem/32px on the phone. */
.oi-spoiler__title {
  font-size: clamp(1.65rem, 1.75vw + 1.223rem, 2.8rem); font-weight: 700; letter-spacing: -.025em;
  line-height: 1.06; margin: 0; color: var(--text-strong);
}
.oi-spoiler__title em { font-style: italic; color: var(--text-strong); }
.oi-spoiler__tagline {
  margin-top: 14px; font-family: var(--font-mono); font-size: 12.5px; font-weight: 500;
  letter-spacing: .22em; text-transform: uppercase; color: var(--text-muted);
}
/* Keep "EVIDENCE · CONFIDENCE · CONTROL" on ONE line at narrow widths — when it
   wraps, a separator dot dangles at the line end. Tighter tracking + size fits
   it at 360. */
@media (max-width: 700px) {
  .oi-spoiler__tagline { font-size: 11.5px; letter-spacing: .13em; white-space: nowrap; }
}
.oi-spoiler__lead {
  font-size: var(--text-size-body); line-height: 1.55; color: var(--text-muted);
  margin: 22px 0 22px; max-width: 46ch;
}
/* The red "Request a demo" button (design #C82828 → #A81F1F on hover). */
.oi-spoiler__demo {
  display: inline-flex; align-items: center; justify-content: center; gap: 9px;
  height: 48px; padding: 0 26px; font-family: var(--font-sans); font-size: var(--text-size-body);
  font-weight: 600; line-height: 1; border-radius: var(--radius-md);
  border: 1.5px solid #C82828; background: #C82828; color: #fff;
  box-shadow: var(--shadow-sm); cursor: pointer; transition: background .18s, transform .12s;
}
.oi-spoiler__demo:hover { background: #A81F1F; border-color: #A81F1F; }
.oi-spoiler__demo:active { transform: scale(.98); }

/* -- the demo card -- */
.oi-sp {
  background: var(--white); border: 1px solid var(--line); border-radius: var(--radius-lg);
  box-shadow: var(--shadow-md); overflow: hidden;
}
.oi-sp__head {
  display: flex; align-items: center; gap: 10px; padding: 15px 20px;
  border-bottom: 1px solid var(--line-soft); background: var(--paper-2);
}
.oi-sp__head-chip { width: 22px; height: 4px; border-radius: 2px; background: var(--spectrum); flex: 0 0 auto; }
.oi-sp__head-label { font-family: var(--font-mono); font-size: 12px; letter-spacing: .05em; color: var(--text-muted); }
.oi-sp__status {
  margin-left: auto; font-family: var(--font-mono); font-size: 10px; font-weight: 600;
  letter-spacing: .1em; color: var(--amber-700); background: var(--amber-100);
  padding: 4px 9px; border-radius: var(--radius-pill); min-width: 62px; text-align: center;
}
.oi-sp__body { position: relative; height: 454px; overflow: hidden; }
.oi-sp__canvas { position: absolute; inset: 0; width: 100%; height: 100%; opacity: 0; }
.oi-sp__analyzing {
  position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center;
  justify-content: center; gap: 20px; padding: 0 40px; background: var(--white);
  opacity: 0; pointer-events: none;
}
.oi-sp__blink { display: flex; gap: 6px; align-items: center; height: 22px; }
.oi-sp__blink span { width: 6px; height: 18px; border-radius: 3px; background: var(--accent-strong); opacity: .3; }
.oi-sp__analyzing-text { font-family: var(--font-mono); font-size: 13.5px; color: var(--text-muted); letter-spacing: .03em; }
.oi-sp__ancount { color: var(--text-strong); font-weight: 500; }
.oi-sp__antrack { width: min(280px, 72%); height: 5px; border-radius: 3px; background: var(--surface-inset); overflow: hidden; }
.oi-sp__anprog { display: block; height: 100%; width: 0%; background: var(--spectrum); border-radius: 3px; }
.oi-sp__analyzing-note { font-family: var(--font-mono); font-size: 10.5px; color: var(--text-faint); letter-spacing: .05em; }
.oi-sp__proposed {
  position: absolute; inset: 0; display: flex; flex-direction: column; background: var(--white);
  opacity: 1; /* resting frame (design ships 0 as a Phase-5 entrance state) */
}
.oi-sp__prompt { padding: 15px 20px 9px; font-size: 12px; color: var(--text-muted); }
.oi-sp__grid { flex: 1; display: grid; grid-template-columns: 1fr 1fr; min-height: 0; }
.oi-sp__rows { padding: 2px 6px 2px 14px; display: flex; flex-direction: column; justify-content: center; }
.oi-sp__prow {
  display: flex; align-items: center; gap: 10px; padding: 6px 10px; border-radius: 7px;
  cursor: default; transition: background .14s;
}
.oi-sp__prow-num { font-family: var(--font-mono); font-size: 10.5px; color: var(--text-faint); width: 16px; flex: 0 0 auto; }
.oi-sp__prow-name {
  flex: 1; font-size: 12.5px; font-weight: 500; color: var(--text-strong);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.oi-sp__prow-count { font-family: var(--font-mono); font-size: 11px; color: var(--text-muted); }
.oi-sp__prow-swatch { width: 8px; height: 8px; border-radius: 2px; flex: 0 0 auto; }
/* The eleven code swatches — kept in CSS so spoiler_codes.py stays copy-only. */
.oi-sp__rows .oi-sp__prow:nth-child(1)  .oi-sp__prow-swatch { background: rgba(42,111,219,0.9); }
.oi-sp__rows .oi-sp__prow:nth-child(2)  .oi-sp__prow-swatch { background: rgba(62,120,174,0.9); }
.oi-sp__rows .oi-sp__prow:nth-child(3)  .oi-sp__prow-swatch { background: rgba(47,143,107,0.9); }
.oi-sp__rows .oi-sp__prow:nth-child(4)  .oi-sp__prow-swatch { background: rgba(62,158,115,0.9); }
.oi-sp__rows .oi-sp__prow:nth-child(5)  .oi-sp__prow-swatch { background: rgba(201,132,47,0.9); }
.oi-sp__rows .oi-sp__prow:nth-child(6)  .oi-sp__prow-swatch { background: rgba(226,162,76,0.9); }
.oi-sp__rows .oi-sp__prow:nth-child(7)  .oi-sp__prow-swatch { background: rgba(198,95,60,0.9); }
.oi-sp__rows .oi-sp__prow:nth-child(8)  .oi-sp__prow-swatch { background: rgba(168,83,58,0.9); }
.oi-sp__rows .oi-sp__prow:nth-child(9)  .oi-sp__prow-swatch { background: rgba(75,94,126,0.9); }
.oi-sp__rows .oi-sp__prow:nth-child(10) .oi-sp__prow-swatch { background: rgba(110,126,153,0.9); }
.oi-sp__rows .oi-sp__prow:nth-child(11) .oi-sp__prow-swatch { background: rgba(154,166,184,0.9); }
.oi-sp__verbatims {
  padding: 15px 20px 14px; border-left: 1px solid var(--line-soft);
  display: flex; flex-direction: column; gap: 10px; background: var(--paper);
}
.oi-sp__verbatims-label { font-family: var(--font-mono); font-size: 10px; letter-spacing: .09em; text-transform: uppercase; color: var(--text-faint); }
.oi-sp__ex-name { font-size: 14px; font-weight: 600; color: var(--text-strong); }
.oi-sp__ex-quotes { display: flex; flex-direction: column; gap: 9px; }
.oi-sp__ex-count { margin-top: auto; padding-top: 8px; font-family: var(--font-mono); font-size: 11px; color: var(--text-muted); }
.oi-sp__approve-row {
  display: flex; align-items: center; gap: 12px; padding: 12px 20px;
  border-top: 1px solid var(--line-soft); background: var(--paper-2);
}
.oi-sp__approve {
  margin-left: auto; display: inline-flex; align-items: center; gap: 8px; height: 40px;
  padding: 0 20px; font-family: var(--font-sans); font-size: 13.5px; font-weight: 600;
  border-radius: var(--radius-md); border: 1.5px solid var(--navy-800); background: var(--navy-800);
  color: #fff; cursor: pointer; transition: background .16s, transform .1s;
}
.oi-sp__approve:hover { background: var(--navy-950); }
.oi-sp__approve:active { transform: scale(.98); }
.oi-sp__chart {
  position: absolute; inset: 0; display: flex; flex-direction: column; background: var(--white);
  opacity: 0; pointer-events: none;
}
.oi-sp__chart-head { padding: 14px 20px 9px; display: flex; align-items: center; gap: 12px; }
.oi-sp__chart-check {
  display: inline-flex; align-items: center; justify-content: center; width: 24px; height: 24px;
  border-radius: 50%; background: var(--verify-100); flex: 0 0 auto;
}
.oi-sp__chart-titles { flex: 1; min-width: 0; }
.oi-sp__chart-title { font-size: 14px; font-weight: 600; color: var(--text-strong); }
.oi-sp__chart-sub { font-size: 11.5px; color: var(--text-muted); margin-top: 2px; }
.oi-sp__modes { display: inline-flex; gap: 2px; background: var(--surface-inset); border-radius: var(--radius-pill); padding: 3px; flex: 0 0 auto; }
.oi-sp__mode {
  position: relative;
  font-family: var(--font-mono); font-size: 10.5px; font-weight: 600; letter-spacing: .03em;
  padding: 5px 10px; border: none; border-radius: var(--radius-pill); cursor: pointer;
  transition: background .15s, color .15s, box-shadow .15s; background: transparent; color: var(--text-muted);
}
/* Touch target: the segmented control is ~24px tall by design, under the 44px
   mandate. A transparent ::after grows the *hit area* to 44px without changing
   a pixel of the visible control. It extends ~10px above and below the button,
   which stays clear of the chart canvas below (measured: >=8px gap at 1440,
   ~29px at 834) — so it never steals the canvas's hover. Width is the button's
   own (already >=46px), separated from its neighbours by the 2px gap. */
.oi-sp__mode::after {
  content: ""; position: absolute; left: 0; right: 0;
  top: 50%; transform: translateY(-50%);
  height: 44px; min-height: 100%;
}
.oi-sp__mode--on { background: var(--white); color: var(--text-strong); box-shadow: var(--shadow-sm); }
.oi-sp__chart-canvas { flex: 1; width: 100%; display: block; min-height: 0; }
.oi-sp__ctip {
  padding: 10px 20px; border-top: 1px solid var(--line-soft); background: var(--paper-2);
  font-size: 12px; color: var(--text-muted); min-height: 18px;
}
.oi-sp__foot {
  display: flex; align-items: center; gap: 10px; padding: 13px 20px;
  border-top: 1px solid var(--line-soft); background: var(--paper-2);
}
.oi-sp__foot-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--verify-500); flex: 0 0 auto; }
.oi-sp__foot-text { font-size: 12px; color: var(--text-muted); }

/* -- the phone-only mini card (hidden >480; shown ≤480) -- */
.oi-spmini {
  display: none; margin: 2px 0 22px; background: var(--white); border: 1px solid var(--line);
  border-radius: var(--radius-lg); box-shadow: var(--shadow-md); overflow: hidden;
}
.oi-spmini__head {
  display: flex; align-items: center; gap: 9px; padding: 10px 14px;
  border-bottom: 1px solid var(--line-soft); background: var(--paper-2);
}
.oi-spmini__chip { width: 20px; height: 4px; border-radius: 2px; background: var(--spectrum); flex: 0 0 auto; }
.oi-spmini__label { font-family: var(--font-mono); font-size: 11px; letter-spacing: .05em; color: var(--text-muted); }
.oi-spmini__pill {
  margin-left: auto; font-family: var(--font-mono); font-size: 9.5px; font-weight: 600;
  letter-spacing: .1em; color: var(--amber-700); background: var(--amber-100);
  padding: 3px 8px; border-radius: var(--radius-pill);
}
.oi-spmini__body { padding: 4px 14px 12px; }
.oi-spmini__row { padding: 10px 0 8px; border-bottom: 1px solid var(--line-soft); }
.oi-spmini__row--last { padding: 10px 0 2px; border-bottom: none; }
.oi-spmini__row-head { display: flex; align-items: baseline; gap: 8px; margin-bottom: 7px; }
.oi-spmini__row-title { font-size: 13.5px; font-weight: 600; color: var(--text-strong); }
.oi-spmini__row-n { font-family: var(--font-mono); font-size: 10.5px; color: var(--text-faint); }
.oi-spmini__row-pct { margin-left: auto; font-family: var(--font-mono); font-size: 11px; font-weight: 600; }
.oi-spmini__row-pct--good { color: #2F7F5B; }
.oi-spmini__row-pct--warn { color: var(--amber-700); }
.oi-spmini__track { display: block; height: 5px; border-radius: 3px; background: var(--surface-inset); overflow: hidden; }

/* Tablet (<=1024) mirrors the mobile design for #spoiler and #monitor. Placed
   AFTER the base .oi-spmini / .oi-sp rules so it wins (an earlier copy would be
   overridden by the base display:none on .oi-spmini). >1024 keeps the desktop
   interactive card + on-chart labels. */
@media (max-width: 1024px) {
  /* Spoiler: drop the interactive canvas card (its labels don't lay out at this
     width — the whole reason to mirror mobile) and show the static mini card. Cap
     + centre so a wide landscape tablet doesn't strand it in the left half. */
  .oi-spoiler__split { grid-template-columns: 1fr; gap: 22px; max-width: 560px; margin-inline: auto; }
  .oi-sp { display: none; }
  .oi-spmini { display: block; }
  /* Monitor: hide the background chart TEXT labels so they can't collide with the
     lead copy (THREATS FILTERED etc. were overlapping the paragraph). */
  .oi-monitor__bg text { display: none; }
}
.oi-spmini__fill { display: block; height: 100%; border-radius: 3px; }
.oi-spmini__fill--good { background: #3E9E73; }
.oi-spmini__fill--warn { background: var(--amber-500); }
.oi-spmini__foot {
  display: flex; align-items: center; gap: 6px; margin-top: 12px;
  font-family: var(--font-mono); font-size: 9.5px; letter-spacing: .03em; color: var(--text-faint);
}

/* ============================================================ #close */
.oi-close {
  position: relative; background: var(--navy-900); overflow: hidden;
  height: 100svh; display: flex; align-items: safe center;
  padding-block: var(--nav-h) clamp(16px, 3vh, 32px);  /* clear the fixed nav */
}
.oi-close__glow {
  position: absolute; inset: 0; z-index: 1; pointer-events: none;
  background: radial-gradient(58% 92% at 88% 2%, rgba(226,162,76,0.16) 0%, rgba(226,162,76,0.05) 34%, rgba(226,162,76,0) 62%);
}
.oi-close__rule { position: absolute; top: 0; left: 0; right: 0; height: 2px; background: var(--spectrum); opacity: .55; z-index: 2; }
.oi-close__inner {
  position: relative; z-index: 3; width: 100%; max-width: var(--container); margin: 0 auto;
  padding: clamp(80px, 11vh, 116px) var(--gutter) clamp(24px, 5vh, 56px);
}
.oi-close__lead-row { display: flex; align-items: center; gap: clamp(28px, 5vw, 72px); flex-wrap: wrap; }
.oi-close__copy { flex: 1 1 440px; max-width: 840px; }
.oi-close__eyebrow-wrap { display: flex; flex-direction: column; gap: 11px; margin-bottom: 22px; }
.oi-close__eyebrow {
  font-family: var(--font-mono); font-size: 12px; font-weight: 500;
  letter-spacing: .12em; text-transform: uppercase; color: var(--amber-400);
}
/* Fluid through all THREE of the design's explicit anchors: 25.6px @390 (mobile
   #close h2 override 1.6rem !important), 35.2px @834 (tablet override 2.2rem
   !important), 60px @1440 (desktop var(--text-display-l) ceiling 3.75rem). The
   linear term is fitted through the tablet+desktop pair; the 1.6rem floor holds
   across the phone band. Replaces the raw --text-display-l, which froze at
   2.5rem/40px on the phone — 14px over the mobile design. */
.oi-close__title {
  font-family: var(--font-sans); font-size: clamp(1.6rem, 4.09vw + 0.067rem, 3.75rem); font-weight: 700;
  letter-spacing: -.024em; line-height: 1.04; color: #fff; margin: 0; text-wrap: balance;
}
.oi-close__lead { font-size: var(--text-lead); line-height: 1.5; color: #d3dbe9; margin: 20px 0 0; max-width: 60ch; }
.oi-close__lead--mobile { display: none; }
.oi-close__grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(238px, 1fr)); gap: clamp(20px, 3vh, 40px) var(--space-8);
  margin-top: clamp(24px, 5vh, 64px); padding-top: clamp(22px, 4vh, 56px); border-top: 1px solid var(--border-ink);
}
/* auto-fit misfires in two bands: it yields exactly 2 columns from ~481-819
   (orphaning the 3rd card bottom-left — audited at 574) and again at 820-900
   (audited at 834, where 3x238px + gaps just misses). Single column through
   819, then an explicit 3-up until auto-fit can genuinely fit three. */
@media (min-width: 481px) and (max-width: 819px) {
  .oi-close__grid { grid-template-columns: 1fr; }
}
@media (min-width: 820px) and (max-width: 1024px) {
  .oi-close__grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
/* Tall viewports: lift the content modestly but keep it centred as one block.
   space-between distributed the rows into disjoint clumps with big voids between
   the lead, the icon rows, and the CTAs. */
@media (min-height: 780px) {
  .oi-close__inner { padding-top: clamp(18px, 3.2vh, 46px); }
}
.oi-close__card { display: flex; flex-direction: column; gap: 15px; }
.oi-close__icon {
  display: inline-flex; align-items: center; justify-content: center; width: 46px; height: 46px;
  border-radius: var(--radius-md); background: rgba(226,162,76,0.12); border: 1px solid rgba(226,162,76,0.28);
  color: var(--amber-400); flex: 0 0 auto;
}
.oi-close__card-title { font-family: var(--font-sans); font-size: var(--text-h4); font-weight: 600; letter-spacing: -.01em; color: #fff; margin: 0; }
.oi-close__card-body { font-size: var(--text-body-sm); line-height: 1.62; color: var(--text-on-ink-muted); margin: 9px 0 0; }
.oi-close__cta { display: flex; align-items: center; gap: 16px; margin-top: clamp(24px, 4.5vh, 64px); flex-wrap: wrap; }
.oi-close__ghost {
  display: inline-flex; align-items: center; gap: 9px; height: 54px; padding: 0 26px;
  font-family: var(--font-sans); font-size: var(--text-body-lg); font-weight: 600; line-height: 1;
  border-radius: var(--radius-md); border: 1.5px solid rgba(255,255,255,0.30); background: transparent;
  color: #fff; text-decoration: none; white-space: nowrap;
  transition: border-color .18s, background .18s, transform .12s;
}
.oi-close__ghost:hover { border-color: rgba(255,255,255,0.6); background: rgba(255,255,255,0.06); }
.oi-close__ghost:active { transform: scale(.98); }
/* The design pairs the phone with the team link at 54px (the shared ghost tag
   is 52px); nudge it up to match, in this section only. */
.oi-close__cta .oi-phone--ghost { height: 54px; padding: 0 26px; }

/* ============================================================ <=1024 (tablet, bottom 4) */
@media (max-width: 1024px) {
  /* Scrolling is free at every width (no snap anywhere — see the top of this
     file). Sections read as one-viewport blocks by geometry; the height model
     at the end of this file lets them GROW where content needs it. */

  /* #monitor restacks here: the copy and the dashboard card sit in normal
     flow (a phone can't fit them side-by-side without the card burying the
     copy). Where the stacked stage still fits one viewport (390-1024
     portrait, thanks to the compaction tiers) the scrub track keeps it pinned
     and scrubbing; where it genuinely cannot fit (360x640, landscape phones)
     the track's JS drops to flow mode — the section grows past one viewport
     so BOTH stay fully visible, at the animation's end state. */
  .oi-monitor { height: auto; min-height: 100svh; overflow: visible; }
  .oi-monitor__stage { height: auto; min-height: 100svh; overflow: visible; }
  .oi-monitor__bg, .oi-monitor__scrim { height: 100svh; bottom: auto; }   /* chart stays one screen at the top, no stretch */
  /* Copy is stacked on TOP here, so the scrim must fade the chart from the TOP
     down (was the desktop left->right gradient), masking the chart lines behind
     the paragraph. Solid through ~34% (the copy runs past 60% of the chart zone
     on a 360 phone, where the curve was still crossing the last lines). */
  .oi-monitor__scrim { background: linear-gradient(to bottom, rgba(251,250,247,0.6) 0%, rgba(251,250,247,0.6) 34%, rgba(251,250,247,0.4) 55%, rgba(251,250,247,0) 82%); }
  .oi-monitor__copy {
    height: auto; display: block;
    /* Self-shielding paper under the stacked copy (the JS scrim caps at 0.66
       opacity; the orange curve printed through the last paragraph lines). */
    background: linear-gradient(to bottom, rgba(251,250,247,0.82) 0%, rgba(251,250,247,0.74) 86%, rgba(251,250,247,0) 100%);
  }
  .oi-monitor__panel-inner { max-width: 640px; }
  /* relative, not static: keeps the card in normal flow (below the copy) while
     preserving its z-index:5, so the opaque card paints ABOVE the absolute chart
     (z-1) instead of the chart bleeding through the stats. */
  .oi-monitor__cardwrap { position: relative; inset: auto; pointer-events: auto; }
  .oi-monitor__cardwrap-inner {
    height: auto; align-items: center; justify-content: center;
    padding: 8px var(--gutter) 56px;
  }
  .oi-monitor__summary { width: 100%; max-width: 600px; max-height: none; overflow: visible; }

  /* #spoiler stacks at <=1024 (the split's single-column rule lives in the
     mobile-mirror block). The old "tablet keeps two columns" rule that used to
     live here was a GHOST: it kept a 2-col grid whose second column (the hidden
     interactive card) was empty, stranding all content in a narrow left column
     across 574-1024. */
  .oi-spoiler__logo { width: min(220px, 58%); margin-bottom: 16px; }
}

/* ============================================================ <=480 (phone, bottom 4) */
/* #assurance flips the graphic to the vertical variant. Extended from <=480 to
   <=640: in the 481-640 band the horizontal funnel's gate labels no longer fit
   side by side (audited at 574: the SURVEY PLATFORM PROTECTIONS chip printed
   over DEVICE FORENSIQ IQ, hiding its "IQ"). */
@media (max-width: 640px) {
  .oi-assurance__graphic--h { display: none; }
  .oi-assurance__graphic--v { display: block; }
}

@media (max-width: 480px) {

  /* #monitor: card anchors bottom-centre, chart labels drop, scrim goes vertical.
     Solid through 38% — the copy runs past a third of the chart zone on a 360
     phone, where the orange curve was still printing through the last lines. */
  .oi-monitor__scrim {
    background: linear-gradient(to bottom, rgba(251,250,247,0.62) 0%, rgba(251,250,247,0.62) 38%, rgba(251,250,247,0.4) 60%, rgba(251,250,247,0) 86%);
  }
  .oi-monitor__copy { align-items: flex-start; }
  .oi-monitor__panel { padding: calc(var(--nav-h) + 6px) var(--gutter) 0; }
  .oi-monitor__panel-inner { max-width: 100%; }
  .oi-monitor__bg text { display: none; }
  .oi-monitor__svg {
    -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 44px, #000 100%);
            mask-image: linear-gradient(to bottom, transparent 0, #000 44px, #000 100%);
  }
  .oi-monitor__cardwrap-inner {
    align-items: center; justify-content: center;
    padding: 4px var(--gutter) 40px;
  }
  .oi-monitor__summary { width: 100%; max-width: 520px; max-height: none; }
  .oi-monitor__cardhead { flex-direction: column; align-items: flex-start; gap: 10px; }

  /* #spoiler: one column; hide the interactive card, show the mini card. */
  .oi-spoiler__split { grid-template-columns: 1fr; gap: 22px; }
  .oi-spoiler__lead { font-size: 13.5px; line-height: 1.5; margin: 14px 0 18px; max-width: 100%; }
  .oi-sp { display: none; }
  .oi-spmini { display: block; }

  /* #close: mobile lead copy, single-column value grid, full-width CTAs, no team link. */
  .oi-close__lead--full { display: none; }
  .oi-close__lead--mobile { display: block; }
  .oi-close__lead-row { gap: 28px; }
  .oi-close__grid { grid-template-columns: 1fr; gap: 13px; margin-top: 20px; padding-top: 16px; }
  .oi-close__card { flex-direction: row; align-items: flex-start; gap: 12px; }
  .oi-close__icon { width: 34px; height: 34px; }
  .oi-close__icon svg { width: 17px; height: 17px; }
  .oi-close__card-title { font-size: 15px; }
  .oi-close__card-body { font-size: 12px; line-height: 1.45; margin-top: 4px; }
  .oi-close__cta { margin-top: 20px; gap: 10px; }
  .oi-close__cta > * { width: 100%; height: 48px; }
  .oi-close__ghost--team { display: none; }
}

/* ============================================================ one-viewport scale-to-fit */
/* Every section is exactly 100svh (a vertical deck), so long-form leads, generous
   gaps and the tall graphic/marquee/card blocks must scale DOWN on short and narrow
   viewports to fit ONE screen without clipping. Section HEADINGS keep their fluid
   clamps (the type-scale is fixed by design and asserted in check_chrome); every
   other measure — leads, card bodies, gaps, margins, and the oversized graphic /
   flip-card / logo blocks — flexes here by viewport HEIGHT (smooth, no width step)
   and, on phones, by width where the 2-up columns wrap tall. */

/* --- short-viewport compaction (laptops at 720/900 + phones): scale leads and
   vertical rhythm by height. Tablet portrait (>=1000px tall) keeps its roomy
   design spacing. --- */
@media (max-height: 920px) {
  .oi-hero__sub       { font-size: clamp(13.5px, 1.8vh, 18px); margin-top: clamp(12px, 2vh, 22px); }
  .oi-engine__lead    { font-size: clamp(12.5px, 1.6vh, 18px); margin-top: clamp(8px, 1.5vh, 18px); line-height: 1.45; }
  .oi-sampling__lead  { font-size: clamp(12.5px, 1.5vh, 16px); margin: clamp(8px, 1.3vh, 14px) 0 clamp(10px, 1.6vh, 18px); line-height: 1.45; }
  .oi-assurance__lead { font-size: clamp(13px, 1.65vh, 18px);  margin-top: clamp(10px, 1.7vh, 22px); line-height: 1.45; }
  .oi-monitor__lead   { font-size: clamp(13px, 1.65vh, 18px);  margin-top: clamp(10px, 1.7vh, 20px); line-height: 1.5; }
  .oi-spoiler__lead   { font-size: clamp(12.5px, 1.5vh, 16px); margin: clamp(10px, 1.7vh, 22px) 0; line-height: 1.45; }
  .oi-close__lead     { font-size: clamp(13px, 1.7vh, 22px);   margin-top: clamp(10px, 1.7vh, 20px); line-height: 1.45; }

  /* Eyebrow-wrap bottom margins tighten uniformly. */
  .oi-why__eyebrow-wrap, .oi-engine__eyebrow-wrap, .oi-sampling__eyebrow-wrap,
  .oi-assurance__eyebrow-wrap, .oi-monitor__eyebrow-wrap, .oi-spoiler__eyebrow-wrap,
  .oi-close__eyebrow-wrap { margin-bottom: clamp(10px, 1.5vh, 20px); }

  /* #why cards + grid. */
  .oi-why__grid { gap: clamp(10px, 1.6vh, 26px); }
  .oi-why__card { padding: clamp(15px, 2.2vh, 44px); }
  .oi-why__card-title { margin-top: clamp(6px, 1vh, 16px); }
  .oi-why__card-body  { margin-top: clamp(6px, 1vh, 14px); }

  /* #engine: shrink the ≤1024 single-card flip runway by height. */
  .oi-engine__rail-wrap { margin-top: clamp(18px, 3vh, 54px); }
  .oi-engine__rail { margin-bottom: clamp(16px, 2.6vh, 36px); }

  /* #assurance graphic is the tallest element — already vh-capped; tighten caption gap. */
  .oi-assurance__caption { gap: 6px 14px; }

  /* #close cards. */
  .oi-close__grid { gap: clamp(14px, 2.2vh, 40px) var(--space-8); }
  .oi-close__card { gap: clamp(9px, 1.4vh, 15px); }

  /* #spoiler logo scales with height (it drives the intro column's height). */
  .oi-spoiler__logo { width: min(300px, 62%); margin-bottom: clamp(12px, 2vh, 30px); }
  .oi-spoiler__tagline { margin-top: clamp(8px, 1.2vh, 14px); }
}

/* --- #engine ≤1024 single-card runway: sized close to the card's real content
   (was clamp(...,470px), which stretched the single card and left a big empty
   void between its body and stat row). --- */
@media (max-width: 1024px) {
  .oi-engine__grid { height: clamp(246px, 30vh, 300px); }
}
/* Tall tablet portrait / tall phone: grow the head->rail gap modestly so #engine
   fills more of the viewport without opening an awkward lead-to-card chasm. */
@media (max-width: 1024px) and (min-height: 820px) {
  .oi-engine__rail-wrap { margin-top: clamp(40px, 6vh, 84px); }
}

/* --- phones (≤480): the 2-up / stacked columns wrap tall, so scale the card and
   copy TYPE down (headings excepted). This is where 360×640 is won. --- */
@media (max-width: 480px) {
  /* #why: the four cards are ~150px-wide columns — shrink their type + chrome so
     all four + the heading fit one screen. */
  .oi-why__card { padding: 13px 13px 14px; }
  .oi-why__grid { gap: 8px; }
  .oi-why__ghost { font-size: 48px; top: 4px; right: 10px; }
  .oi-why__num { font-size: 10.5px; }
  .oi-why__card-title { font-size: 14px; line-height: 1.16; margin-top: 6px; }
  .oi-why__card-body  { font-size: 11.5px; line-height: 1.34; margin-top: 6px; max-width: 100%; }

  /* #engine: the long lead + stacked flip card. Shrink lead and card internals. */
  .oi-engine__lead { font-size: 12px; line-height: 1.4; margin-top: 8px; }
  .oi-engine__grid { height: clamp(280px, 44vh, 340px); }
  .oi-pipe__back { padding: 14px; gap: 7px; }
  .oi-pipe__body { font-size: 12px; line-height: 1.4; }
  .oi-pipe__stats { padding-top: 9px; gap: 14px; }
  .oi-pipe__explore { height: 34px; }

  /* #sampling: copy (lead + 3 points + CTA) stacked above the marquee. Tighten. */
  .oi-sampling__lead { font-size: 12px; line-height: 1.38; margin: 7px 0 10px; }
  .oi-sampling__points { gap: 6px; }
  .oi-sampling__point { gap: 9px; }
  .oi-sampling__point-title { font-size: 12.5px; margin-bottom: 1px; }
  .oi-sampling__point-body { font-size: 10.5px; line-height: 1.34; }
  .oi-sampling__cta-row { margin-top: 9px; }
  .oi-sampling__grid { gap: 9px; }
  .oi-rib__head { margin-bottom: 6px; }
  /* The horizontal phone marquee cards shrink so the strip clears the fold. */
  .oi-rib__card { min-height: 104px; padding: 10px 12px; gap: 4px; }
  .oi-rib__stats { padding-top: 7px; gap: 16px; }
  .oi-rib__audience { font-size: 15.5px; }
  .oi-rib__stat-value { font-size: 17px; }
  .oi-btn-navy { height: 42px; }

  /* #assurance: the vertical funnel draws SIX gate labels — below ~300px they
     collide with each other and the particle field (seen live on an iPhone at
     711px effective viewport, where the old 30vh squeeze gave 213px). The
     section grows past one viewport under the ≤1024 height model and scrolls
     freely, so the funnel gets a firm floor instead of a fit-the-fold squeeze
     (tall phones ≥780px keep the flex auto height from the base rules). */
  .oi-assurance__graphic--v { height: clamp(300px, 40vh, 354px); }
  .oi-assurance__lead { font-size: 12.5px; line-height: 1.4; }
  .oi-assurance__caption { font-size: 11.5px; }

  /* #spoiler: shrink the logo (its aspect makes it the tallest intro element). */
  .oi-spoiler__logo { width: min(92px, 28%); margin-bottom: 8px; }
  .oi-spoiler__lead { font-size: 11.5px; line-height: 1.36; margin: 8px 0 10px; }
  .oi-spoiler__tagline { margin-top: 6px; letter-spacing: .13em; font-size: 11px; }
  .oi-spoiler__demo { height: 42px; }
  .oi-spmini { margin: 0 0 10px; }
  .oi-spmini__head { padding: 8px 14px; }
  .oi-spmini__body { padding: 2px 14px 8px; }
  .oi-spmini__row { padding: 5px 0 5px; }
  .oi-spmini__row--last { padding: 5px 0 0; }
  .oi-spmini__foot { margin-top: 7px; }

  /* #close: shrink the mobile lead + card body + spacers for 360-wide. */
  .oi-close__lead--mobile { font-size: 12.5px; line-height: 1.38; }
  .oi-close__grid { margin-top: 13px; padding-top: 12px; gap: 11px; }
  .oi-close__card-body { font-size: 11.5px; line-height: 1.4; }
  .oi-close__cta { margin-top: 14px; }
  .oi-close__cta > * { height: 45px; }
}

/* ============================================================ single-column #sampling (481–768) */
/* ≤768 collapses #sampling to one column, stacking the copy above the vertical
   marquee. On a portrait tablet (e.g. 768×1024) that stack is taller than the
   viewport, so the vertically-centred inner clips its top (the eyebrow/heading)
   AND the marquee bottom. Cap the marquee height and tighten the copy rhythm so
   the whole stack fits one screen. Scoped to the 481–768 band the original port
   flagged as "no design reference"; the protected 2-column desktop/tablet
   marquee (its intentionally-tall masked track) is untouched. */
@media (min-width: 481px) and (max-width: 768px) {
  .oi-sampling__lead { margin: 10px 0 12px; }
  .oi-sampling__points { gap: 10px; }
  .oi-sampling__cta-row { margin-top: 16px; }
  .oi-rib__viewport { height: min(680px, 32svh); }
}

/* ============================================================ short-viewport scale-to-fit (≤700 / ≤600 tall) */
/* The deck is 100svh per section, so on SHORT screens (small phones, short
   laptops) the content-heavy cards/graphics overflow the one-viewport box and
   get cropped by overflow:hidden. These tiers are HEIGHT-driven — the constraint
   is vertical — so tall phones (390×844, 414×896) and every viewport ≥701 tall
   (all tablets/desktops in the matrix) are untouched. Headings keep their fluid
   clamps (the type-scale is asserted in check_chrome); only leads, card bodies,
   gaps, paddings and the field-dashboard card's internals flex here. */
@media (max-height: 700px) {
  /* #monitor — the field-dashboard card is the tallest block on a short phone
     (94px clip @375×667, 121px @360×640, the priority fix). Shrink the stat
     numbers, the card padding, the row gaps and the THIS-WEEK note rows so the
     whole card + heading clears the fold. Height-gated so the tall-phone and
     desktop card layouts keep their roomy design spacing. */
  .oi-monitor__summary { padding: 13px 14px; }
  .oi-monitor__cardhead { gap: 8px; }
  .oi-monitor__cardhead-label { gap: 6px; }
  .oi-monitor__health { margin-top: 8px; }
  .oi-monitor__section-label { margin-bottom: 5px; }
  .oi-monitor__health-grid { gap: 6px 14px; }
  .oi-monitor__stat { gap: 3px; }
  .oi-monitor__stat--completes { gap: 4px; }
  .oi-monitor__stat-value { font-size: clamp(1.05rem, 4.6vw, 1.4rem); }
  .oi-monitor__stat-note { font-size: 10px; }
  .oi-monitor__week { margin-top: 8px; padding-top: 8px; }
  .oi-monitor__week-rows { gap: 4px; }
  .oi-monitor__week-row { padding: 5px 10px; }
  .oi-monitor__week-text { font-size: 12px; line-height: 1.3; }
  .oi-monitor__note { margin-top: 8px; }
}

/* ============================================================ nav-clear reclaim (≤760 tall) */
/* The content sections now reserve the fixed nav via `padding-block: var(--nav-h) …`,
   so each inner's OWN top padding (76–116px — it used to BE the nav clearance) is
   now redundant, doubled on top of the section's 72px. On short viewports that
   doubled top padding pushed the vertically-centred content past the nav-cleared
   area, tucking the heading under the nav and clipping the bottom. Reclaim the
   redundant inner padding (and a little deck rhythm) here — height-gated so the
   roomy tall/desktop layouts (≥761 tall: 390×844, 414×896, and every desktop
   ≥900 tall) are untouched. Wins the short phones (568/640/667) and the short
   laptop (1280×720). The ≤600 tier below overrides with its deeper squeeze at 568. */
@media (max-height: 760px) {
  .oi-why__inner       { padding-top: clamp(8px, 1.8vh, 18px); padding-bottom: clamp(10px, 2vh, 20px); }
  .oi-why__head        { margin-bottom: clamp(8px, 1.6vh, 16px); }
  .oi-assurance__inner { padding-top: clamp(8px, 1.8vh, 18px); padding-bottom: clamp(10px, 2vh, 20px); gap: clamp(10px, 1.8vh, 20px); }
  .oi-close__inner     { padding-top: clamp(8px, 1.8vh, 18px); padding-bottom: clamp(10px, 2vh, 20px); }
}

@media (max-height: 600px) {
  /* Smallest/oldest phones (320×568). Push the #monitor card further, and give
     #why / #sampling / #assurance / #spoiler / #close the small trims that clear
     568 (their ≤480 width tier gets them most of the way; this closes the gap). */

  /* #monitor card — deeper compaction (233px clip at 320×568; the card's own
     overflow:auto must not kick in — content must FIT, not scroll). Also reclaim
     a little card height from the bottom-anchor gap on the shortest phones. */
  .oi-monitor__cardwrap-inner { padding-bottom: 14px; }
  .oi-monitor__summary { max-height: calc(100% - var(--nav-h) - 14px); padding: 11px 13px; }
  .oi-monitor__cardhead { gap: 6px; }
  .oi-monitor__cardhead-eyebrow { font-size: 11px; }
  .oi-monitor__health { margin-top: 6px; }
  .oi-monitor__section-label { margin-bottom: 4px; }
  .oi-monitor__health-grid { gap: 4px 12px; }
  .oi-monitor__stat-value { font-size: clamp(0.9rem, 4.4vw, 1.15rem); }
  .oi-monitor__stat-label { font-size: 9px; }
  .oi-monitor__stat-note { font-size: 9.5px; }
  .oi-monitor__week { margin-top: 6px; padding-top: 6px; }
  .oi-monitor__week-rows { gap: 3px; }
  .oi-monitor__week-row { padding: 4px 9px; }
  .oi-monitor__week-text { font-size: 11px; line-height: 1.25; }
  .oi-monitor__note { margin-top: 5px; font-size: 10.5px; }

  /* #why — the 2×2 grid: tighten padding, gaps, ghost and body type. The inner's
     own top padding is now redundant with the section's nav-h padding-block, so
     it drops to a slim floor (the section clears the nav). */
  .oi-why__inner { padding-top: 10px; padding-bottom: 11px; }
  .oi-why__head { margin-bottom: 9px; }
  .oi-why__eyebrow-wrap { margin-bottom: 9px; }
  .oi-why__grid { gap: 6px; }
  .oi-why__card { padding: 9px 11px 10px; }
  .oi-why__ghost { font-size: 40px; }
  .oi-why__card-title { font-size: 13px; line-height: 1.13; margin-top: 4px; }
  .oi-why__card-body { font-size: 11px; line-height: 1.26; margin-top: 4px; }

  /* #sampling — the densest short-phone section (a long lead + 3 detailed points
     + the audiences strip). Squeeze the rhythm and the marquee card hard; primary
     reading text (lead 11.5) stays legible, the secondary micro-copy sits at the
     ≤480 design floor (point-body 10.5, mono qualifier 10.5). */
  .oi-sampling__eyebrow-wrap { gap: 6px; margin-bottom: 5px; }
  .oi-sampling__lead { font-size: 11.5px; line-height: 1.16; margin: 4px 0 5px; }
  .oi-sampling__points { gap: 2px; }
  .oi-sampling__point { gap: 8px; }
  .oi-sampling__point-title { font-size: 12px; margin-bottom: 0; }
  .oi-sampling__point-body { font-size: 10.5px; line-height: 1.14; }
  .oi-sampling__cta-row { margin-top: 3px; }
  .oi-btn-navy { height: 40px; }
  .oi-rib__head { margin-bottom: 2px; }
  .oi-rib__card { min-height: 74px; padding: 4px 10px; gap: 2px; }
  .oi-rib__audience { font-size: 12.5px; }
  .oi-rib__qualifier { font-size: 10.5px; line-height: 1.28; }
  .oi-rib__stats { padding-top: 4px; gap: 14px; }
  .oi-rib__stat-value { font-size: 14px; }
  .oi-rib__stat-label { font-size: 9px; }

  /* #assurance — the capped graphic + lead + caption. */
  .oi-assurance__inner { padding-top: 10px; padding-bottom: 12px; gap: 10px; }
  .oi-assurance__eyebrow-wrap { margin-bottom: 10px; }
  .oi-assurance__lead { font-size: 11.5px; line-height: 1.34; margin-top: 8px; }
  .oi-assurance__cta-row { margin-top: 14px; }
  /* Same six-label floor logic as the ≤480 tier: 320×568-class screens get a
     legible 240px funnel and the section grows, rather than a 136px smoosh. */
  .oi-assurance__graphic--v { height: clamp(240px, 40vh, 300px); }
  .oi-assurance__caption { font-size: 11px; gap: 4px 12px; }

  /* #spoiler — the intro column above the mini card. */
  .oi-spoiler__eyebrow-wrap { margin-bottom: 8px; }
  .oi-spoiler__logo { width: min(68px, 21%); margin-bottom: 5px; }
  .oi-spoiler__lead { font-size: 11px; line-height: 1.28; margin: 5px 0 6px; }
  .oi-spoiler__tagline { margin-top: 4px; }
  .oi-spoiler__demo { height: 40px; }
  .oi-spmini__head { padding: 7px 14px; }
  .oi-spmini__body { padding: 2px 14px 5px; }
  .oi-spmini__row { padding: 3px 0; }
  .oi-spmini__row--last { padding: 3px 0 0; }
  .oi-spmini__row-head { margin-bottom: 4px; }
  .oi-spmini__foot { margin-top: 5px; }

  /* #close — the lead + 3 value cards + both CTAs on navy. The inner's own top
     padding is now redundant with the section's nav-h padding-block. */
  .oi-close__inner { padding-top: 10px; padding-bottom: 11px; }
  .oi-close__eyebrow-wrap { margin-bottom: 10px; }
  .oi-close__lead--mobile { font-size: 11.5px; line-height: 1.3; margin-top: 9px; }
  .oi-close__grid { margin-top: 8px; padding-top: 8px; gap: 4px; }
  .oi-close__card { gap: 6px; }
  .oi-close__card-title { font-size: 14px; }
  .oi-close__card-body { font-size: 11px; line-height: 1.26; }
  .oi-close__cta { margin-top: 6px; gap: 8px; }
  .oi-close__cta > * { height: 42px; }
}

/* ============================================================ medium-width band (481–833) */
/* At 481–833px wide, #close leaves its wide single-row 3-card layout (the value
   grid auto-fits to two rows) but keeps the tall double-padded inner, so it
   overruns even a 900–1024-tall viewport (600×900 was ~189px over). Reclaim the
   redundant inner padding and tighten the value-grid + CTA rhythm. Phones ≤480
   (their own tier) and the design's ≥834 two-column layout are untouched. */
@media (min-width: 481px) and (max-width: 833px) {
  .oi-close__inner { padding-top: clamp(14px, 1.8vh, 24px); padding-bottom: clamp(14px, 1.8vh, 24px); }
  .oi-close__grid  { margin-top: clamp(12px, 1.8vh, 22px); padding-top: clamp(12px, 1.8vh, 22px); gap: clamp(12px, 1.6vh, 22px) var(--space-8); }
  .oi-close__card  { gap: clamp(9px, 1.3vh, 15px); }
  .oi-close__cta   { margin-top: clamp(12px, 1.8vh, 22px); }
}

/* #sampling single-column (481–768) on a SHORT-ish viewport (≤920 tall, e.g.
   600×900): the copy stack + vertical marquee overran the fold. Cap the marquee
   shorter and tighten the copy rhythm. Taller viewports in the band (700×1000,
   768×1024) keep the roomier single-column spacing. */
@media (min-width: 481px) and (max-width: 768px) and (max-height: 920px) {
  .oi-sampling__lead    { margin: 8px 0 10px; }
  .oi-sampling__points  { gap: 8px; }
  .oi-sampling__cta-row { margin-top: 12px; }
  .oi-rib__viewport     { height: min(680px, 26svh); }
}

/* ============================================================ reduced motion */
/* The design's @media only kills scroll-snap and lets the 48s marquee and the
   card hovers keep running (spec §3, §5.5.3). Pause the marquee and drop the
   motion here. */
@media (prefers-reduced-motion: reduce) {
  .oi-rib__track { animation-play-state: paused; }
  .oi-why__card { transition: box-shadow .2s var(--ease-out); }
  .oi-why__card:hover { transform: none; }
  .oi-pipe__inner { transition: none; }
}

/* ============================================================ reveal-on-scroll */
/* [data-reveal] content fades / rises in as it enters the viewport (JS adds
   `is-revealed` via IntersectionObserver in scroll/reveal.js). This is gated on
   `.js` — added by pages/home.js only when the module actually runs — so a no-JS
   user (or a failed module load) sees every [data-reveal] at its natural, final
   state rather than stranded at opacity:0. */
.js [data-reveal],
.js [data-reveal-group] > * {
  opacity: 0;
  transform: translateY(14px);
  transition: opacity .5s var(--ease-out), transform .5s var(--ease-out);
}
.js [data-reveal].is-revealed,
.js [data-reveal-group].is-revealed > * {
  opacity: 1;
  transform: none;
}
/* Reduced motion: show the final state immediately, from CSS alone, regardless
   of when (or whether) the JS runs — no fade, no rise. */
@media (prefers-reduced-motion: reduce) {
  .js [data-reveal],
  .js [data-reveal-group] > * {
    opacity: 1;
    transform: none;
    transition: none;
  }
}

/* ============================================================ progress spine */
/* A fixed hairline on the right edge that tracks overall scroll (scroll/spine.js
   drives the fill / node / % / label). Ported from the design's [data-spine]
   inline styles; progressStyle frozen to 'spine' (D12 — no topbar).

   Pure scroll-motion, so shown only under `.js`: a static spine pinned at 0%
   would be a progress bar that lies. Hidden ≤820px (too narrow to earn the
   gutter); the label/% block hides ≤1400px, leaving just the rail + node. */
.oi-spine { display: none; }
/* Only on wide desktop (>=1440), where there is real margin room and the labelled
   rail reads as a deliberate progress indicator. Below that it looked like a stray
   line in the gutter (every critical reviewer flagged it), so it is hidden. */
@media (min-width: 1440px) {
  .js .oi-spine {
    display: block;
    position: fixed; right: clamp(40px, 3.4vw, 64px); left: auto; top: 22vh; bottom: 22vh;
    z-index: 60; pointer-events: none;
  }
}
.oi-spine__rail { position: relative; width: 2px; height: 100%; }
.oi-spine__track,
.oi-spine__fill { position: absolute; inset: 0; width: 2px; border-radius: 2px; }
.oi-spine__track { background: var(--line); }
.oi-spine__fill {
  background: linear-gradient(to bottom, #233351 0%, #3E78AE 48%, #E2A24C 100%);
  clip-path: inset(0 0 100% 0);
}
.oi-spine__node {
  position: absolute; left: 1px; top: 0%; width: 9px; height: 9px;
  margin: -4.5px 0 0 -4.5px; border-radius: 50%; background: #E2A24C;
  box-shadow: 0 0 0 4px rgba(226, 162, 76, 0.18);
  transition: background .4s var(--ease-out), box-shadow .4s var(--ease-out);
}
.oi-spine__info {
  position: absolute; right: 20px; left: auto; top: 0%;
  transform: translateY(-50%); display: flex; flex-direction: column;
  align-items: flex-end; gap: 4px; white-space: nowrap;
}
/* Show the section label wherever the margin has room for it (was 1400). Below
   that the pulled-in rail + node reads as an intentional progress marker on its own. */
@media (max-width: 1280px) { .oi-spine__info { display: none; } }
.oi-spine__label {
  font-family: var(--font-mono); font-size: 10.5px; letter-spacing: .13em;
  text-transform: uppercase; color: var(--text-body);
  transition: color .4s var(--ease-out);
}
.oi-spine__pct { font-family: var(--font-mono); font-size: 10.5px; color: var(--text-muted); }
/* Reduced motion: the spine still tracks scroll (position, not animation), but
   the node/label colour hand-offs settle instantly. */
@media (prefers-reduced-motion: reduce) {
  .oi-spine__node,
  .oi-spine__label { transition: none; }
}

/* ============================================================ THE DESIGN'S HEIGHT MODEL (<=1024) */
/* LAST in the file on purpose: these must beat every section's base rule (equal
   specificity — source order decides). The user's design files give every
   section a one-viewport MINIMUM height: at least one screen, GROWING when
   content needs it. The port hard-boxed them (`height:100svh; overflow:hidden`), which
   amputated content at in-between sizes (574px wide: text wraps taller and the
   box guillotines the "Request a demo" / "Scope a study" CTAs). Scrolling is
   free (no snap anywhere), so let every section grow like the design does. The
   hero is exempt (absolute layers would collapse an auto-height box; its
   content is short). The scrub tracks' ACTIVE mode re-locks #engine/#monitor
   to one viewport with higher specificity (see SCRUB TRACKS below); their FLOW
   mode hands back to this growth model. */
@media (max-width: 1024px) {
  .oi-why, .oi-engine, .oi-sampling, .oi-assurance, .oi-spoiler, .oi-close,
  .oi-monitor, .oi-monitor__stage {
    height: auto; min-height: 100svh; overflow: visible;
  }
  .oi-engine__inner { overflow: visible; height: auto; }

  /* Engine card runway sized to the card's REAL content (measured: ~256px at
     phone widths, ~287px at tablet) — earlier clamps left a ~90px void between
     the card body and its stat row. Fixed height is required (the flip stacks
     cards position:absolute), so size it per band instead. */
  .oi-engine__grid { height: 268px; }

  /* Sections can now scroll UNDER the nav (min-height model). Over dark
     sections the nav is transparent by design, which desktop's one-viewport
     deck never exposed; here white copy would glide beneath white nav text.
     A soft navy gradient keeps the nav band legible while staying invisible
     against the dark sections it sits on. */
  .oi-nav--tint.oi-nav--dark:not(.oi-nav--solid) {
    background: linear-gradient(to bottom, rgba(14,22,38,0.94) 55%, rgba(14,22,38,0));
  }
}
@media (min-width: 601px) and (max-width: 1024px) {
  .oi-engine__grid { height: 296px; }
}


/* ============================================================ 481-680 compaction */
/* Small-window band (a Safari window floors at ~574px wide): the stacked
   layouts overshot the viewport by 34-98px, so a section's eyebrow was always
   sliding under the fixed nav mid-scroll. Levers: strip the DOUBLE top padding
   (sections carry nav clearance already; the design's big inner paddings were
   invisible under the old centered model but pure height under the growing
   model), lay the close promise cards icon-LEFT like the phone design, and
   tighten the tall blocks. Measured at 574x835 + 640x960: every section fits
   one viewport. */
@media (min-width: 481px) and (max-width: 680px) {
  .oi-why__inner { padding-top: 12px; padding-bottom: 16px; }
  .oi-why__head { margin-bottom: 12px; }
  .oi-why__card { padding: 14px 16px 16px; }
  .oi-why__card-title { margin-top: 8px; }
  .oi-why__card-body { margin-top: 8px; font-size: 14.5px; line-height: 1.5; }

  /* Engine: keep the TALLEST capability card compact enough that the JS-sized
     runway (max card height) still fits the section in one viewport. */
  .oi-engine__lead { margin-top: 12px; }
  .oi-engine__rail-wrap { margin-top: 28px; }
  .oi-engine__rail { margin-bottom: 24px; }
  .oi-pipe__back { padding: 16px; gap: 6px; }
  .oi-pipe__body { font-size: 13px; line-height: 1.45; }

  .oi-sampling { padding-bottom: 10px; }
  .oi-sampling__grid { gap: 12px; }
  .oi-sampling__eyebrow-wrap { margin-bottom: 8px; }
  .oi-rib__head { margin-bottom: 10px; }
  .oi-sampling__lead { margin: 10px 0 10px; }
  .oi-sampling__points { gap: 7px; }
  .oi-sampling__cta-row { margin-top: 10px; }
  .oi-rib__card { min-height: 0; padding: 10px 12px; gap: 4px; }

  .oi-close__inner { padding-top: 14px; padding-bottom: 20px; }
  .oi-close__lead { margin-top: 12px; }
  .oi-close__grid { margin-top: 16px; padding-top: 14px; gap: 12px; }
  /* Icon-left rows (the phone design's treatment) — icon-above-text columns
     cost ~450px for three promises; rows cost ~230px. */
  .oi-close__card { flex-direction: row; align-items: flex-start; gap: 13px; }
  .oi-close__icon { width: 38px; height: 38px; }
  .oi-close__icon svg { width: 19px; height: 19px; }
  .oi-close__card-title { font-size: 16px; }
  .oi-close__card-body { font-size: 13px; line-height: 1.5; margin-top: 5px; }
  .oi-close__cta { margin-top: 16px; }
}

/* Phones (<=480): the monitor stack ran 86-185px past the viewport (its card
   internals only had trims in the 481-768 band). Same treatment, phone-tuned,
   so the stage fits one viewport on 390x844/414x896/480x854 and the scrub
   track can run in active mode. (360x640 drops to flow mode: that height
   genuinely cannot hold the why grid or this card at legible sizes.) */
@media (max-width: 480px) {
  .oi-monitor__panel { padding-bottom: 0; }
  .oi-monitor__lead { margin-top: 10px; }
  .oi-monitor__panel-foot { margin-top: 10px; padding-top: 8px; }
  .oi-monitor__summary { padding: 12px 14px; }
  .oi-monitor__cardhead { gap: 8px; }
  .oi-monitor__health { margin-top: 8px; }
  .oi-monitor__section-label { margin-bottom: 5px; }
  .oi-monitor__health-grid { gap: 8px 14px; }
  .oi-monitor__stat { gap: 3px; }
  .oi-monitor__week { margin-top: 8px; padding-top: 8px; }
  .oi-monitor__week-rows { gap: 5px; }
  .oi-monitor__week-row { padding: 8px 10px; }
  .oi-monitor__cardwrap-inner { padding-top: 4px; padding-bottom: 18px; }
}

/* Monitor + spoiler stacks run slightly tall through 768 as well (audited +39 /
   +16 at 700x900) — their trims cover the whole 481-768 band. */
@media (min-width: 481px) and (max-width: 768px) {
  .oi-monitor__panel { padding-bottom: 0; }
  .oi-monitor__lead { margin-top: 12px; }
  .oi-monitor__panel-foot { margin-top: 14px; padding-top: 10px; }
  .oi-monitor__summary { padding: 14px 16px; }
  .oi-monitor__health { margin-top: 10px; }
  .oi-monitor__health-grid { gap: 10px 16px; }
  .oi-monitor__week { margin-top: 10px; padding-top: 10px; }
  .oi-monitor__week-rows { gap: 6px; }
  .oi-monitor__cardwrap-inner { padding-top: 6px; padding-bottom: 26px; }

  .oi-spoiler__logo { width: min(150px, 38%); margin-bottom: 12px; }
  .oi-spoiler__lead { margin: 12px 0 14px; }
  .oi-spmini { margin-bottom: 14px; }
  .oi-spmini__row { padding: 7px 0 6px; }
  .oi-spmini__row--last { padding: 7px 0 0; }
  .oi-spmini__foot { margin-top: 8px; }
}

/* ============================================================ monitor labels */
/* The day-axis labels return whenever the full 1600x900 chart fits without
   horizontal cropping — below 16:9 the slice crop amputates the edge labels
   ("ay 1"), and below 1025px the copy stacks over the chart. The QUOTA and
   curve-name labels stay off at EVERY width: the dashboard card covers the
   curve names' anchor (x=858) and the LIVE MONITOR chip owns the quota line's
   right edge, so those two collide structurally, not situationally. */
@media (min-width: 1025px) and (min-aspect-ratio: 16/9) {
  .oi-monitor__bg .oi-mon-axis text { display: block; }
}

/* ============================================================ SCRUB TRACKS */
/* The two scroll-scrubbed sections (#engine, #monitor) ride as sticky stages
   inside taller scrub tracks (templates wrap each <section> in a
   <div data-scrub-track>). Native scroll through the track IS the animation
   timeline: static/js/scroll/scrub-track.js reads the track's live rect each
   frame and sweeps p = -top / (trackH - stageH), clamped 0..1. Nothing
   intercepts the user's scroll — the stage holds the viewport by sticky
   geometry alone, and stopping mid-scrub parks exactly there.

   210svh = one viewport of stage + 1.1 viewports of scrub travel, matching
   the feel budget of the retired wheel-capture (one full animation per ~1.1
   viewports of input). Tune per-track here if a story needs more dwell.

   `.js`-gated: without the module graph, tracks collapse and the sections lay
   out exactly as the static no-JS page always has (min-height blocks, end
   states via CSS). The whole ancestor chain (html → body → main → track) must
   stay free of overflow (other than visible/clip), transform, filter,
   will-change, and contain — any of those retargets or breaks sticky.

   ACTIVE mode locks the stage to one viewport with 0,3,0 specificity (beats
   both the section base rules and the ≤1024 height model, wherever they sit
   in the file). FLOW mode (`.oi-track--flow`, toggled by the driver when the
   stage's content cannot fit one viewport, and under reduced motion) collapses
   the runway and hands the section back to the growth model at its end state. */
.js [data-scrub-track] { height: 210svh; }
.js [data-scrub-track] > [data-section] {
  position: sticky; top: 0; height: 100svh; overflow: hidden;
}
/* The lvh sliver: when a phone's URL bar collapses mid-scrub the visual
   viewport outgrows the 100svh stage; the track paints the stage's paper
   behind it so the reveal is invisible. #engine is transparent over the body's
   dot grid, so its track stays transparent for the same continuity. */
.js [data-scrub-track].oi-track--paper { background: var(--paper); }
.js [data-scrub-track].oi-track--flow { height: auto; }
.js [data-scrub-track].oi-track--flow > [data-section] {
  position: static; height: auto; min-height: 100svh; overflow: visible;
}

/* ============================================================ GENTLE SETTLE */
/* The one assist the deck keeps: `y proximity` snap, tuned to be as gentle as
   the platform allows. Mandatory snap (retired) FORCED every stop onto a
   boundary; proximity only settles a stop that is already NEAR one (Chrome:
   within ~30% of the viewport) and leaves every other position alone.

   Why each line is shaped the way it is:
   - Pointer-gated: WebKit's proximity implementation currently breaks iOS
     flick momentum (bug 243582 — fast swipes snap on finger-lift instead of
     coasting), which reads as "the page stops me where I didn't ask". Touch
     devices therefore keep pure native scrolling; mouse/trackpad gets the
     settle. Remove the @media to extend it to touch if that bug heals.
   - The scrub TRACKS carry the snap area, never their sticky stages (a stuck
     stage's box rides the viewport, which degenerates snap resolution). And
     because a 210svh track COVERS the snapport while its stage is pinned, the
     CSS covering rule makes every mid-scrub position a legal rest — stopping
     mid-animation NEVER pulls. Only approaching a track's top edge from
     above settles you onto p=0, poised.
   - Footer align:end gives the document end a resting position; without it a
     stop with the footer half-open can settle BACK up and hide it.
   - scroll-snap-stop stays default (normal): a hard flick sails past as many
     boundaries as its momentum earns — snap must never trap. */
@media (hover: hover) and (pointer: fine) {
  html { scroll-snap-type: y proximity; scroll-padding-top: 0; }
  [data-section] { scroll-snap-align: start; }
  [data-scrub-track] > [data-section] { scroll-snap-align: none; }
  [data-scrub-track] { scroll-snap-align: start; }
  .oi-footer { scroll-snap-align: end; }
}

/* ============================================================ print */
/* The scrub runways are a scroll-interaction device; on paper they are just
   1.1 pages of blank. Collapse them and let the sections print as the plain
   one-screen blocks they are (JS end-states don't run in print anyway). */
@media print {
  [data-scrub-track] { height: auto !important; }
  [data-scrub-track] > [data-section] { position: static !important; height: auto !important; overflow: visible !important; }
}
