/* =============================================================================
   STRATO TECHNOLOGIES — Main Stylesheet
   Architecture: BEM, mobile-first, CSS custom properties.

   Three rules govern this file:
     1. No colour is ever hardcoded — every value comes from a token in §1.
     2. A theme-varying token is written ONCE, as light-dark(light, dark) in §1.
        §18 switches themes with `color-scheme` alone and defines no values, so
        there is no second palette to keep in sync. No component is styled twice.
     3. Every heading uses one of the five scale steps in §1. No per-section
        clamp() curves — that is what made the old typography incoherent.

   TABLE OF CONTENTS
    1. Design tokens
    2. Reset & base
    3. Typography & section utilities
    4. Container
    5. Buttons
    6. Header & navigation
    7. Hero
    8. Tagline cards
    9. Capability overview (data-viz)
   10. Platform sections & screen mockups
   11. Success stories grid
   12. Stats
   13. CTA banner
   14. Contact form
   15. Footer
   16. Reveals & motion
   17. Media queries
   18. Theme switching (color-scheme only)
   ============================================================================= */

/* =============================================================================
   1. DESIGN TOKENS
   ============================================================================= */
:root {
  /* ── Brand ramp — hue 196.8°, taken from the logo mark ──────────────
     --brand-300/400/500 are the literal logo colours. The ramp extends
     them so the SAME hue can carry text and fills at AA contrast in both
     themes: only lightness changes between themes, never the hue.        */
  --brand-200: #c7ecfb;
  --brand-300: #98daf8; /* logo */
  --brand-400: #46b0df; /* logo */
  --brand-500: #109dd4; /* logo */
  --brand-600: #0b7fae;
  --brand-700: #08678c;
  --brand-800: #064e6a;

  /* ── Neutral ramp ───────────────────────────────────────────────────
     The 900/800/700 steps were widened once the alternating section
     backgrounds came out: a card at the old #18181b measured 1.12:1 against
     the ground, which is no hierarchy at all. #232329 takes that to 1.27:1.
     Dark builds depth with lightness; light does it with shadow (§18).      */
  --gray-950: #09090b;
  --gray-900: #141418;
  --gray-800: #232329;
  --gray-700: #2b2b32;
  --gray-400: #a1a1aa;
  --gray-50: #fafafa;

  /* ── Semantic tokens ────────────────────────────────────────────────
     Every theme-varying token is declared ONCE, as light-dark(light, dark).
     Which arm resolves is decided entirely by `color-scheme` (set at the very
     bottom of this block and narrowed in §18), so switching themes is a single
     property change rather than a second copy of the palette. This is what
     used to be ~90 duplicated lines across two selectors that had to be kept
     in sync by hand.

     Contrast against --color-bg — dark (#09090b):
       text 19.06 · muted 7.76 · dim 5.89 · accent 8.09 — all AA.
     light (#ffffff): text 19.06 · muted 7.73 · dim 5.27 — all AA.          */
  --color-bg: light-dark(#ffffff, var(--gray-950));
  --color-bg-raised: light-dark(#fafafa, var(--gray-900));
  --color-bg-surface: light-dark(#f4f5f7, var(--gray-800));
  --color-bg-subtle: light-dark(#ececee, var(--gray-700));

  /* Section ground — a different job from the component surfaces above, which
     is why it gets its own token rather than reusing --color-bg-raised. One
     consumer now: the footer, so the page closes on a band instead of running
     out (§15). The five services used to share it too, until the run grew long
     enough that the tint stopped reading as a chapter (§10).
     Judged by L* difference, not WCAG ratio — contrast ratios compress badly
     at the light end and understate large flat areas. Light is L* 95.0 against
     white's 100; #fafafa would have been Δ1.5, below the threshold at which a
     large flat area reads as different at all. */
  --color-bg-section: light-dark(#edf1f6, var(--gray-900));

  --color-border: light-dark(rgba(0, 0, 0, 0.09), rgba(255, 255, 255, 0.08));
  --color-border-mid: light-dark(
    rgba(0, 0, 0, 0.16),
    rgba(255, 255, 255, 0.14)
  );
  /* One step past -mid, for a hairline that has to register as "raised" without
     becoming a drawn outline. --color-text-dim was standing in for this and is
     far too heavy at frame scale: it is a text colour, opaque, and it turns a
     1px edge into a rule. Alpha, not a solid, so it keeps reading as an edge on
     whatever ground it lands on. */
  --color-border-strong: light-dark(
    rgba(0, 0, 0, 0.26),
    rgba(255, 255, 255, 0.22)
  );

  /* Sits directly under a hairline so the pair reads as an engraved seam rather
     than a drawn line. The two themes get there differently, because the trick
     depends on having a value to catch light with:
       dark  — a darker line under the light hairline, a true two-tone groove;
       light — the ground is already pure white, so there is nothing lighter to
               highlight with. A second, weaker line of the SAME tone instead,
               which reads as the hairline falling off rather than stopping.
     Section boundaries only; components use the hairline alone. */
  --color-keyline: light-dark(rgba(0, 0, 0, 0.035), rgba(0, 0, 0, 0.5));

  /* The lit top edge of a raised panel. Weaker in dark than the keyline above
     because it sits on a surface that is already lighter than the page. */
  --color-edge-highlight: light-dark(
    rgba(255, 255, 255, 0.9),
    rgba(255, 255, 255, 0.06)
  );

  --color-text: light-dark(#09090b, var(--gray-50));
  --color-text-muted: light-dark(#52525b, var(--gray-400));
  --color-text-dim: light-dark(#6b6b74, #8b8b94);

  /* Action fill — identical in both themes. White on it = 4.51 ✓ AA */
  --color-primary: var(--brand-600);
  --color-primary-hi: var(--brand-500);
  --color-primary-dark: var(--brand-700);
  --color-on-primary: #ffffff;

  /* Accent text — same hue, lightness flipped per theme */
  --color-primary-light: light-dark(var(--brand-700), var(--brand-400));
  --color-accent: light-dark(var(--brand-700), var(--brand-400));
  --color-accent-wash: light-dark(
    rgba(8, 103, 140, 0.08),
    rgba(70, 176, 223, 0.1)
  );
  --color-accent-line: light-dark(
    rgba(8, 103, 140, 0.22),
    rgba(70, 176, 223, 0.28)
  );

  /* ── Inverted surface ───────────────────────────────────────────────
     Pinned: these are defined here and NEVER redefined in §18, which is
     what makes the block that uses them theme-independent — the same
     exception .platform-screen__diagram takes in §10, the way a code
     editor stays dark inside a light UI. One block on the page uses this. */
  --color-bg-invert: var(--gray-950);

  /* The plot ground inside a screen mockup — a touch above --color-bg-invert
     so the diagram frame reads as a panel rather than a hole. Pinned like the
     rest of this group; it was the one grey in the sheet living outside the
     ramp as a bare hex. */
  --color-bg-invert-raised: #0b0b0f;

  --color-text-invert: var(--gray-50);
  --color-text-invert-muted: var(--gray-400);
  --color-accent-invert: var(--brand-400);
  --color-accent-wash-invert: rgba(70, 176, 223, 0.1);
  --color-accent-line-invert: rgba(70, 176, 223, 0.28);

  /* Follows the theme everywhere except the pinned-dark blocks, which override
     it locally to the invert accent (§2). */
  --color-focus-ring: var(--color-accent);

  /* Status + categorical set. Together with --color-accent these are the five
     hues used by diagrams and the story pages (set inline as --step-color,
     --track-color, --tag-color, --ec-color). Each is verified >= 4.5:1 on its
     own surface in BOTH themes, so a legend stays readable when the theme
     flips — which the old hardcoded hex values did not. */
  --color-success: light-dark(#15803d, #22c55e);
  --color-warning: light-dark(#a16207, #f59e0b);
  --color-danger: light-dark(#b91c1c, #f87171);
  --color-info: light-dark(#6d28d9, #a78bfa);

  /* Status washes — the tinted fill/edge pair that .diagram-node--warn and the
     form error state used to hand-mix from raw hex, which left them frozen at
     the dark-theme orange and red no matter which theme was showing. */
  --color-warning-wash: light-dark(
    rgba(161, 98, 7, 0.1),
    rgba(245, 158, 11, 0.1)
  );
  --color-warning-line: light-dark(
    rgba(161, 98, 7, 0.28),
    rgba(245, 158, 11, 0.28)
  );
  --color-danger-wash: light-dark(
    rgba(185, 28, 28, 0.14),
    rgba(248, 113, 113, 0.18)
  );

  /* Elevation — one geometry per step in both themes; only the alpha moves.
     --shadow-card resolves to a transparent (i.e. invisible) shadow in dark,
     because a shadow does nothing on #09090b — dark cards read via the lighter
     --color-bg-surface instead, while light builds the same depth with shadow. */
  --shadow-card:
    0 1px 2px light-dark(rgb(0 0 0 / 0.04), transparent),
    0 4px 12px light-dark(rgb(0 0 0 / 0.06), transparent);
  --shadow-sm: 0 1px 2px light-dark(rgb(0 0 0 / 0.06), rgb(0 0 0 / 0.3));
  --shadow-md: 0 4px 16px light-dark(rgb(0 0 0 / 0.08), rgb(0 0 0 / 0.4));
  --shadow-lg: 0 16px 48px light-dark(rgb(0 0 0 / 0.1), rgb(0 0 0 / 0.5));
  --shadow-panel:
    0 0 0 1px var(--color-border),
    0 20px 48px light-dark(rgb(0 0 0 / 0.08), rgb(0 0 0 / 0.45));

  /* ── Typography ─────────────────────────────────────────────────── */
  --font-family:
    "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;

  /* Monospace is confined to the diagrams and their controls — labels inside
     the screen frames already used this stack inline. It is instrument
     lettering, never UI or body copy. */
  --font-mono: ui-monospace, SFMono-Regular, Menlo, monospace;

  /* One fluid curve: every step hits its minimum at 375px and its maximum
     at 1280px, so headings scale in lockstep and peer-level headings never
     cross sizes at an intermediate width. Ceilings are sized for the
     two-column hero — 72px display overwhelmed a half-width column. */
  --text-display: clamp(2.75rem, 2.232rem + 2.21vw, 4rem); /*    44 → 64 */
  --text-h1: clamp(2.25rem, 1.939rem + 1.33vw, 3rem); /*         36 → 48 */
  --text-h2: clamp(1.75rem, 1.491rem + 1.1vw, 2.375rem); /*      28 → 38 */
  --text-h3: clamp(1.25rem, 1.146rem + 0.44vw, 1.5rem); /*       20 → 24 */
  --text-h4: 1.125rem; /*                                             18 */

  --font-size-xs: 0.75rem; /* 12 */
  --font-size-sm: 0.875rem; /* 14 */
  --font-size-base: 1rem; /* 16 */
  --font-size-lg: 1.125rem; /* 18 */
  --font-size-xl: 1.25rem; /* 20 */
  --font-size-2xl: 1.5rem; /* 24 */
  --font-size-3xl: 1.875rem; /* 30 */
  --font-size-4xl: 2.25rem; /* 36 */

  --font-weight-normal: 400;
  --font-weight-medium: 500;
  --font-weight-semibold: 600;
  --font-weight-bold: 700;

  /* Tracking tightens as size grows — one rule, applied consistently */
  --tracking-display: -0.03em;
  --tracking-h1: -0.025em;
  --tracking-h2: -0.02em;
  --tracking-h3: -0.015em;
  --tracking-label: 0.08em;

  --line-height-tight: 1.1;
  --line-height-snug: 1.35;
  --line-height-normal: 1.5;
  --line-height-relaxed: 1.65;

  /* ── Spacing — 4px base ─────────────────────────────────────────── */
  --space-1: 0.25rem;
  --space-2: 0.5rem;
  --space-3: 0.75rem;
  --space-4: 1rem;
  --space-5: 1.25rem;
  --space-6: 1.5rem;
  --space-8: 2rem;
  --space-10: 2.5rem;
  --space-12: 3rem;
  --space-16: 4rem;
  --space-20: 5rem;
  --space-24: 6rem;
  --space-32: 8rem;

  /* ── Radius — two shapes, and that is the whole system ──────────────
     Controls and surfaces. 16-20px radii read as consumer SaaS; 8/12 reads
     as instrument panel. The six names below are kept as aliases so the
     existing call sites need no churn — prefer the two real tokens in
     anything new.                                                        */
  --radius-control: 8px; /* buttons, inputs, nav links, toggles, icon chips */
  --radius-surface: 12px; /* cards, panels, screen mockups */

  --radius-btn: var(--radius-control);
  --radius-sm: var(--radius-control);
  --radius-md: var(--radius-control);
  --radius-lg: var(--radius-surface);
  --radius-xl: var(--radius-surface);
  --radius-2xl: var(--radius-surface);
  --radius-full: 9999px; /* dots, bullets, pills */

  /* ── Rhythm ─────────────────────────────────────────────────────────
     One vertical rhythm for every top-level section, in every stylesheet.
     No section sets its own padding-block. Scaled up at the two
     breakpoints in §17 — generous desktop air is the whole point.        */
  --section-y: var(--space-16);

  /* ── Controls ───────────────────────────────────────────────────── */
  --btn-h: 40px;
  --btn-h-lg: 46px;

  /* ── Motion ─────────────────────────────────────────────────────────
     The house curve is ease-out-expo, and every entrance animation in this
     file already used it while these tokens still carried the browser default
     `ease` — so reveals moved with intent and all 45-odd hovers moved like an
     unstyled page. The curve now lives in the tokens.

     Two curves, because duration decides which reads correctly: expo front-
     loads so hard that at 150ms it looks like a dropped frame, while over
     350ms+ it is what makes a reveal feel weighted. Quart is the same shape,
     less extreme, and is the right answer for anything hover-length. */
  --ease-out-quart: cubic-bezier(0.25, 1, 0.5, 1);
  --ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);

  --transition-fast: 150ms var(--ease-out-quart);
  --transition-base: 250ms var(--ease-out-quart);
  --transition-slow: 350ms var(--ease-out-expo);

  --container-max: 1200px;
  --header-height: 72px;

  /* Both arms are live, so light-dark() above follows the OS for anyone whose
     theme was never stamped (JS disabled). §18 narrows this to a single arm
     once data-theme is present, which is what the toggle actually switches. */
  color-scheme: light dark;
}

/* =============================================================================
   2. RESET & BASE
   ============================================================================= */
*,
*::before,
*::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

html {
  scroll-behavior: smooth;
  -webkit-text-size-adjust: 100%;
}

/* Every nav link on the site is an in-page anchor and the header is sticky, so
   without this every jump lands with the target heading underneath the header.
   Set on [id] rather than :target so it applies to the smooth scroll as well
   as to a cold load on a #hash. The extra --space-8 keeps the heading off the
   header edge instead of flush against it. */
[id] {
  scroll-margin-top: calc(var(--header-height) + var(--space-8));
}

body {
  font-family: var(--font-family);
  font-size: var(--font-size-base);
  font-weight: var(--font-weight-normal);
  line-height: var(--line-height-relaxed);
  color: var(--color-text);
  background-color: var(--color-bg);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;

  /* cv11 is Inter's single-storey `a` and straight-tailed `l` — the pair that
     stops "1 / l / I" reading as the same glyph in part numbers and model
     names, which this site is full of. Now that the variable file is loaded,
     optical sizing lets the display sizes use their own outlines rather than a
     scaled-up body cut. */
  font-feature-settings: "cv11";
  font-optical-sizing: auto;
}

img,
svg {
  display: block;
  max-width: 100%;
}

a {
  color: inherit;
  text-decoration: none;
}

ul,
ol {
  list-style: none;
}

button {
  cursor: pointer;
  font-family: inherit;
  border: none;
  background: none;
  color: inherit;
}

/* Selection picks up the brand instead of the browser's default blue, which is
   the one piece of UI chrome a visitor sees in a colour the site never uses.
   The wash is deliberately low-alpha so selected text keeps its own colour and
   stays at full contrast rather than being knocked out to white. */
::selection {
  background-color: var(--color-accent-wash);
}

/* Native control chrome — checkboxes, radios, range thumbs, and the caret —
   follow the brand as well. accent-color is inherited, so once here it is done. */
:root {
  accent-color: var(--color-primary);
}

/* Scroll containers sit inside dark panels (the wide diagrams scroll on every
   phone), where a default OS scrollbar is a bright slab across the artwork. */
.platform-screen__diagram {
  scrollbar-width: thin;
  scrollbar-color: var(--color-border-mid) transparent;
}

/* Single global focus ring — every interactive element, both themes.
   Replaces the four scattered :focus rules the old sheet had.

   No border-radius here. It used to set one, which does not round the OUTLINE
   — it rounds the element, so every focusable thing with no radius of its own
   (footer links, story-card links, breadcrumbs) silently grew an 8px corner
   for as long as it held focus. Modern outlines follow the element's own
   border-radius already, so there is nothing to restate. */
:focus-visible {
  outline: 2px solid var(--color-focus-ring);
  outline-offset: 2px;
}

/* The two pinned-dark blocks keep the dark-theme ring regardless of theme.
   --color-accent resolves to #08678c in light, which is ~2.1:1 against the
   near-black these sit on — under the 3:1 floor for non-text. Same reasoning
   as the tokens themselves being pinned: the surface does not flip, so nothing
   drawn on it may flip either. */
.cta-banner,
.platform-screen__diagram {
  --color-focus-ring: var(--color-accent-invert);
}

/* =============================================================================
   3. TYPOGRAPHY & SECTION UTILITIES
   ============================================================================= */
h1,
h2,
h3,
h4,
h5,
h6 {
  color: inherit;
  line-height: var(--line-height-tight);
  font-weight: var(--font-weight-semibold);
}

/* 1.1 is a display leading. It is correct at 64px and much too tight at 18-24px,
   where the descenders of one line nearly touch the caps of the next — so the
   two small steps take a leading of their own rather than inheriting the
   display value. This is the same distinction --text-h4 makes by being the one
   step with no clamp() curve. */
h4,
h5,
h6 {
  line-height: var(--line-height-snug);
}

/* `pretty` is the body-copy counterpart to the `text-wrap: balance` already on
   the five display headings: it costs nothing, and it stops a paragraph ending
   on a one-word last line — the single most visible sign that text was poured
   rather than set. */
p {
  line-height: var(--line-height-relaxed);
  text-wrap: pretty;
}

.u-visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* Small caps label above a section heading */
.section-eyebrow {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-semibold);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--color-accent);
  margin-bottom: var(--space-4);
}

/* Kept as an alias — the accent token is already theme-aware, so the
   "light section" variant no longer needs a different colour. */
.section-eyebrow--light {
  color: var(--color-accent);
}

/* =============================================================================
   4. CONTAINER
   ============================================================================= */
/* Gutters only. Never use the `padding` shorthand here: sections pair
   `.container` with a companion class that sets its own padding-top/bottom
   (`.data-viz__content`, `.footer__inner`), and a shorthand in the responsive
   override further down would silently reset those back to zero. */
.container {
  width: 100%;
  max-width: var(--container-max);
  margin: 0 auto;
  padding-inline: var(--space-5);
}

/* =============================================================================
   5. BUTTONS
   Sentence case, 8px radius, 1px border, no coloured glow — the enterprise
   convention. The old pill + uppercase + 12px treatment read as agency work.
   ============================================================================= */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  height: var(--btn-h);
  padding: 0 var(--space-5);
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-semibold);
  line-height: 1;
  letter-spacing: 0;
  border-radius: var(--radius-btn);
  border: 1px solid transparent;
  white-space: nowrap;
  transition:
    background-color var(--transition-fast),
    border-color var(--transition-fast),
    color var(--transition-fast),
    transform var(--transition-fast);
}

.btn--primary {
  background-color: var(--color-primary);
  border-color: var(--color-primary);
  color: var(--color-on-primary);
}

/* Opaque, not transparent: this sits over the hero's dot grid, and letting the
   pattern read through the fill made it look like a hole rather than a control. */
.btn--secondary {
  background-color: var(--color-bg);
  border-color: var(--color-border-mid);
  color: var(--color-text);
}

/* Press feedback, and deliberately NOT inside the hover guard below: a touch
   device has no hover but does have a press, and this is the only confirmation
   it gets that the tap landed. */
.btn:active {
  transform: scale(0.97);
}

.btn:disabled,
.btn[aria-disabled="true"] {
  opacity: 0.55;
  cursor: not-allowed;
  transform: none;
}

/* Hover is guarded so it never sticks on touch, where :hover latches after a
   tap and leaves the control looking permanently pressed. */
@media (any-hover: hover) {
  .btn--primary:hover {
    background-color: var(--color-primary-dark);
    border-color: var(--color-primary-dark);
  }

  .btn--secondary:hover {
    border-color: var(--color-text-dim);
    background-color: var(--color-bg-surface);
  }
}

.btn--large {
  height: var(--btn-h-lg);
  padding: 0 var(--space-6);
  font-size: var(--font-size-base);
}

/* =============================================================================
   6. HEADER & NAVIGATION
   ============================================================================= */
.header {
  position: sticky;
  top: 0;
  z-index: 100;
  background-color: var(--color-bg);
  border-bottom: 1px solid var(--color-border);
  transition: box-shadow var(--transition-base);
}

@supports (backdrop-filter: blur(16px)) {
  .header {
    background-color: color-mix(in srgb, var(--color-bg) 82%, transparent);
    backdrop-filter: blur(16px);
    -webkit-backdrop-filter: blur(16px);
  }
}

.header--scrolled {
  box-shadow: var(--shadow-md);
}

.header__inner {
  display: flex;
  align-items: center;
  height: var(--header-height);
  gap: var(--space-8);
}

.header__logo {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-shrink: 0;
}

.header__logo-wordmark {
  display: block;
  height: 22px;
  width: auto;
}

/* Dark theme shows the light wordmark; §18 flips this for the light theme. */
.header__logo-wordmark--dark {
  display: none;
}

.header__nav {
  flex: 1;
}

.header__nav-list {
  display: flex;
  align-items: center;
  gap: var(--space-1);
}

.header__nav-link {
  display: inline-block;
  padding: var(--space-2) var(--space-3);
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-medium);
  color: var(--color-text-muted);
  border-radius: var(--radius-md);
  transition:
    color var(--transition-fast),
    background-color var(--transition-fast);
}

@media (any-hover: hover) {
  .header__nav-link:hover {
    color: var(--color-text);
    background-color: var(--color-bg-surface);
  }
}

/* Which section you are in. The nav had no current state at all, so on a page
   that is one long scroll every link looked equally inactive. Set by the
   scroll-spy in §7 of the script, which already tracks the visible section. */
.header__nav-link[aria-current] {
  color: var(--color-text);
}

.header__nav-link[aria-current]::after {
  content: "";
  display: block;
  height: 2px;
  margin: var(--space-1) var(--space-2) calc(var(--space-1) * -1);
  border-radius: var(--radius-full);
  background-color: var(--color-accent);
}

.header__actions {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-shrink: 0;
}

/* Theme toggle */
.header__theme-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--btn-h);
  height: var(--btn-h);
  border-radius: var(--radius-btn);
  border: 1px solid var(--color-border);
  color: var(--color-text-muted);
  transition:
    color var(--transition-fast),
    background-color var(--transition-fast),
    border-color var(--transition-fast);
}

.header__theme-toggle:hover {
  color: var(--color-text);
  background-color: var(--color-bg-surface);
  border-color: var(--color-border-mid);
}

/* Icon swap is driven purely by the theme tokens block in §18 */
.theme-icon--sun {
  display: block;
}

.theme-icon--moon {
  display: none;
}

/* Mobile menu toggle */
.header__menu-toggle {
  display: none;
  width: var(--btn-h);
  height: var(--btn-h);
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-btn);
  color: var(--color-text-muted);
  transition: background-color var(--transition-fast);
  flex-shrink: 0;
}

.header__menu-toggle:hover {
  background-color: var(--color-bg-surface);
}

.header__menu-toggle-icon,
.header__menu-toggle-icon::before,
.header__menu-toggle-icon::after {
  display: block;
  width: 20px;
  height: 1.5px;
  background-color: currentColor;
  border-radius: var(--radius-full);
  transition:
    transform var(--transition-base),
    opacity var(--transition-base);
}

.header__menu-toggle-icon {
  position: relative;
}

.header__menu-toggle-icon::before,
.header__menu-toggle-icon::after {
  content: "";
  position: absolute;
  left: 0;
}

.header__menu-toggle-icon::before {
  top: -6px;
}

.header__menu-toggle-icon::after {
  top: 6px;
}

.header__menu-toggle[aria-expanded="true"] .header__menu-toggle-icon {
  background-color: transparent;
}

.header__menu-toggle[aria-expanded="true"] .header__menu-toggle-icon::before {
  transform: translateY(6px) rotate(45deg);
}

.header__menu-toggle[aria-expanded="true"] .header__menu-toggle-icon::after {
  transform: translateY(-6px) rotate(-45deg);
}

/* Mobile drawer */
.header__mobile-nav {
  background-color: var(--color-bg-raised);
  border-top: 1px solid var(--color-border);
  padding: var(--space-4) var(--space-5) var(--space-6);
}

.header__mobile-nav[hidden] {
  display: none;
}

.header__mobile-nav-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.header__mobile-nav-link {
  display: block;
  padding: var(--space-3) var(--space-4);
  font-size: var(--font-size-base);
  font-weight: var(--font-weight-medium);
  color: var(--color-text-muted);
  border-radius: var(--radius-md);
  transition:
    background-color var(--transition-fast),
    color var(--transition-fast);
}

@media (any-hover: hover) {
  .header__mobile-nav-link:hover {
    background-color: var(--color-bg-surface);
    color: var(--color-text);
  }
}

/* The drawer gets the same current-section mark as the desktop nav, as a left
   edge rather than an underline — these are stacked rows, not a row of tabs. */
.header__mobile-nav-link[aria-current] {
  color: var(--color-text);
  box-shadow: inset 2px 0 0 var(--color-accent);
}

.header__mobile-cta {
  margin-top: var(--space-3);
  width: 100%;
}

/* =============================================================================
   7. HERO
   ============================================================================= */
.hero {
  position: relative;
  overflow: hidden;
  padding: var(--section-y) 0;
  background-color: var(--color-bg);
}

/* Shared interactive dot-grid canvas — the site's single signature motion */
.dot-grid-canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 1;
  pointer-events: none;
}

/* Keeps text legible over the grid without adding colour of its own */
.hero__overlay {
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  background: radial-gradient(
    ellipse 60% 50% at 50% 45%,
    transparent 0%,
    var(--color-bg) 100%
  );
  opacity: 0.55;
}

.hero__inner {
  position: relative;
  z-index: 3;
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-12);
  align-items: center;
}

/* Wider measure than the old two-column hero allowed: with the product panel
   gone the headline runs the full column, and 640px left it wrapping to five
   cramped lines with the right half of the container empty. */
.hero__content {
  max-width: 820px;
}

.hero__eyebrow {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-medium);
  color: var(--color-text-muted);
  background: var(--color-bg-surface);
  border: 1px solid var(--color-border-mid);
  border-radius: var(--radius-full);
  padding: 5px 14px;
  margin-bottom: var(--space-6);
  transition:
    color var(--transition-fast),
    border-color var(--transition-fast);
}

.hero__eyebrow:hover {
  color: var(--color-text);
  border-color: var(--color-accent);
}

.hero__eyebrow-arrow {
  color: var(--color-accent);
}

.hero__heading {
  font-size: var(--text-display);
  font-weight: var(--font-weight-bold);
  line-height: var(--line-height-tight);
  letter-spacing: var(--tracking-display);
  color: var(--color-text);
  margin-bottom: var(--space-6);
  text-wrap: balance;
}

/* The colour stays unscoped: all four success-story pages use this class for
   their own accent word. */
.hero__heading-accent {
  color: var(--color-accent);
}

/* The rule under "actually" is a background, not an ::after. An absolutely
   positioned pseudo needs a containing block with a resolvable width, and this
   span is inline: that works while the heading groups are inline-block, but
   the moment they collapse to plain inline at mobile widths the pseudo
   measures 0px and the rule silently disappears. A background paints across an
   inline's fragments in every case. The padding is background-box only —
   vertical padding on an inline does not move a line — and drops the rule
   clear of the y descender.

   Scoped by .hero__heading-line so it stays on this hero. The story pages
   share this stylesheet and this class, and they must not pick up an
   underline nobody asked for. */
.hero__heading-line .hero__heading-accent {
  padding-bottom: 0.04em;
  background-image: linear-gradient(currentColor, currentColor);
  background-repeat: no-repeat;
  background-position: 0 100%;
  background-size: 100% 0.055em;
}

/* ── Headline entrance ──
   Each group is a mask box; the inner span is the only thing that moves. The
   groups are inline-block, so they still wrap against each other at a space
   and `text-wrap: balance` now balances within a group rather than across the
   whole heading — which is the point of choosing the breaks by hand.

   The padding/negative-margin pair is load-bearing: overflow clips at the
   PADDING edge, so without it the mask would cut the descenders off the p in
   "Deep-learning" and the y in "way"/"actually", and would also clip the
   accent rule below. The negative margin gives the space straight back so the
   line rhythm is unchanged. */
.hero__heading-line {
  display: inline-block;
  overflow: hidden;
  vertical-align: bottom;
  padding-bottom: 0.2em;
  margin-bottom: -0.2em;
}

.hero__heading-line-inner {
  display: inline-block;
}

/* Same gating idiom as the diagram entrance in §11: `both` fill holds each
   element at its from-state while paused, so the end state lives in exactly
   one place and no separate hidden rules are needed. */
.js-enabled .reveal .hero__heading-line-inner {
  animation: hero-line-rise 0.75s var(--ease-out-expo) both;
  animation-play-state: paused;
}

.js-enabled .reveal .hero__heading-line:nth-child(2) .hero__heading-line-inner {
  animation-delay: 0.09s;
}

.js-enabled .reveal .hero__heading-line:nth-child(3) .hero__heading-line-inner {
  animation-delay: 0.18s;
}

.js-enabled .reveal .hero__heading-line .hero__heading-accent {
  /* 0.18s stagger + 0.75s rise = the third group lands at 0.93s. Starting the
     rule any earlier draws it while its own line is still climbing, which
     reads as a stray mark rather than a final beat. */
  animation: hero-rule-draw 0.45s var(--ease-out-expo) 0.95s both;
  animation-play-state: paused;
}

.js-enabled .reveal--visible .hero__heading-line-inner,
.js-enabled .reveal--visible .hero__heading-line .hero__heading-accent {
  animation-play-state: running;
}

@keyframes hero-line-rise {
  from {
    transform: translateY(100%);
  }
  to {
    transform: none;
  }
}

@keyframes hero-rule-draw {
  from {
    background-size: 0 0.055em;
  }
  to {
    background-size: 100% 0.055em;
  }
}

.hero__subheading {
  font-size: var(--font-size-lg);
  color: var(--color-text-muted);
  line-height: var(--line-height-relaxed);
  margin-bottom: var(--space-8);
  max-width: 34em;
}

.hero__cta-group {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
}

/* ── Practice overview panel ──
   The four numbers, framed. This is the hero's proof element now; the forecast
   screen that used to sit here duplicated section 1.0's, title and value both. */
.hero__stats {
  position: relative;
  z-index: 3;
  margin-top: var(--space-12);
  background: var(--color-bg-raised);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-surface);
  box-shadow: var(--shadow-card);
  overflow: hidden;
}

.hero__stats-label {
  padding: var(--space-3) var(--space-5);
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-semibold);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--color-text-dim);
  background: var(--color-bg-surface);
  border-bottom: 1px solid var(--color-border);
}

/* =============================================================================
   8. TAGLINE CARDS
   ============================================================================= */
/* Sections are separated by exactly one hairline and never also by a
   background change — doubled separation is what made the old page read as
   striped. Surfaces are reserved for things that are genuinely raised. */
.tagline-cards {
  padding: var(--section-y) 0;
  background-color: var(--color-bg);
  border-top: 1px solid var(--color-border);
}

.tagline-cards__headline {
  font-size: var(--text-h2);
  font-weight: var(--font-weight-semibold);
  color: var(--color-text);
  letter-spacing: var(--tracking-h2);
  line-height: var(--line-height-tight);
  text-align: center;
  margin-bottom: var(--space-16);
  text-wrap: balance;
}

.tagline-cards__grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-10);
}

/* Not a box: these are <li>, not links, so a border-colour hover was feedback
   for an interaction that does not exist. A rule above each column separates
   them and the extra grid gap carries the rest. */
.tagline-card {
  padding: var(--space-6) 0 0;
  border-top: 1px solid var(--color-border);
}

.tagline-card__icon {
  width: 32px;
  height: 32px;
  color: var(--color-accent);
  margin-bottom: var(--space-5);
}

.tagline-card__icon svg {
  width: 100%;
  height: 100%;
}

.tagline-card__title {
  font-size: var(--text-h4);
  font-weight: var(--font-weight-semibold);
  color: var(--color-text);
  letter-spacing: var(--tracking-h3);
  margin-bottom: var(--space-3);
}

.tagline-card__body {
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
  line-height: var(--line-height-relaxed);
}

/* =============================================================================
   9. CAPABILITY OVERVIEW (data-viz)
   ============================================================================= */
.data-viz {
  position: relative;
  overflow: hidden;
  background-color: var(--color-bg);
  border-top: 1px solid var(--color-border);
}

/* All four grids now render at full canvas opacity; the difference between
   them lives in their baseA/hiA pairs in the script’s §8 instead, which is the
   honest place for it — a canvas-wide opacity was fighting those values. */
.data-viz__dot-canvas {
  z-index: 1;
}

/* Half rhythm, the counterpart to the CTA banner's 1.5×. This is an intro — an
   eyebrow, a heading and four cards — and at full --section-y the desktop step
   put 128px of air above and below a block that short, which read as a gap
   rather than as breathing room. The multiplier keeps it tied to the one
   rhythm, so it still scales at both breakpoints. */
.data-viz__content {
  position: relative;
  z-index: 3;
  text-align: center;
  padding-top: calc(var(--section-y) * 0.7);
  padding-bottom: calc(var(--section-y) * 0.7);
}

.data-viz__eyebrow {
  color: var(--color-accent);
}

.data-viz__heading {
  font-size: var(--text-h1);
  font-weight: var(--font-weight-semibold);
  color: var(--color-text);
  line-height: var(--line-height-tight);
  letter-spacing: var(--tracking-h1);
  margin-bottom: var(--space-5);
  text-wrap: balance;
}

.data-viz__subheading {
  font-size: var(--font-size-lg);
  color: var(--color-text-muted);
  max-width: 620px;
  margin: 0 auto var(--space-16);
  line-height: var(--line-height-relaxed);
}

.data-viz__pillars {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-5);
}

.data-viz__pillar {
  background: var(--color-bg-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-card);
  padding: var(--space-8) var(--space-6);
  text-align: left;
  transition:
    border-color var(--transition-base),
    background-color var(--transition-base);
}

.data-viz__pillar:hover {
  border-color: var(--color-border-mid);
  background: var(--color-bg-subtle);
}

.data-viz__pillar-icon {
  width: 40px;
  height: 40px;
  border-radius: var(--radius-lg);
  background: var(--color-accent-wash);
  border: 1px solid var(--color-accent-line);
  display: flex;
  align-items: center;
  justify-content: center;
  margin-bottom: var(--space-4);
  color: var(--color-accent);
}

.data-viz__pillar-icon svg {
  width: 20px;
  height: 20px;
}

.data-viz__pillar-title {
  font-size: var(--font-size-base);
  font-weight: var(--font-weight-semibold);
  color: var(--color-text);
  letter-spacing: var(--tracking-h3);
  margin-bottom: var(--space-2);
}

.data-viz__pillar-body {
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
  line-height: var(--line-height-relaxed);
}

/* =============================================================================
   10. PLATFORM SECTIONS & SCREEN MOCKUPS
   ============================================================================= */
/* The run sits on the base ground, like everything between the hero and the
   CTA. It briefly carried a tint of its own so the five services would read as
   one chapter — but five sections at --section-y is ~40% of the page, and a
   tint held that long stops marking a chapter and becomes the page's default
   colour. The page now spends its single contrast break on the CTA banner and
   nowhere else, which is the discipline every comparable site keeps.

   Separation here comes from the rhythm, the mirrored --alt layout, the 1.0–5.0
   numbering and the keyline below — never from the ground. Alternating per
   section would be worse than either: it fragments continuous reading, and it
   is what makes a page read as a template. */
.platform-section {
  padding: var(--section-y) 0;

  /* Engraved keyline — a hairline with a counter-toned line directly beneath
     it, which reads as a machined panel gap rather than as a border. One rule
     per boundary, including the top of the run; .stories closes the bottom. */
  border-top: 1px solid var(--color-border);
  box-shadow: inset 0 1px 0 var(--color-keyline);
}

.platform-section__inner {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-12);
}

.platform-section__meta {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-bottom: var(--space-6);
}

/* These carry the numbered-section device. They used to sit on a 2.57:1
   token — the least readable text on the page. Now 5.89:1. */
.platform-section__number,
.platform-section__label {
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-semibold);
  color: var(--color-text-dim);
}

/* slashed-zero and lining figures alongside the tabular widths: these are
   instrument numerals, and 1.0/2.0/3.0 should sit on an exact column. */
.platform-section__number {
  font-variant-numeric: lining-nums tabular-nums slashed-zero;
  letter-spacing: 0.02em;
}

.platform-section__label {
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
}

.platform-section__arrow {
  font-size: 0.8em;
  color: var(--color-text-dim);
}

.platform-section__heading {
  font-size: var(--text-h2);
  font-weight: var(--font-weight-semibold);
  color: var(--color-text);
  line-height: var(--line-height-tight);
  letter-spacing: var(--tracking-h2);
  margin-bottom: var(--space-5);
  text-wrap: balance;
}

/* The two-column split only starts at 1024px, so between 768 and 1023 this
   paragraph runs the full container — about 115 characters, well past the
   65-75 a reader tracks comfortably. The cap only binds in that band; above
   1024 the grid column is already narrower than 60ch. */
.platform-section__body {
  max-width: 60ch;
  font-size: var(--font-size-base);
  color: var(--color-text-muted);
  line-height: var(--line-height-relaxed);
  margin-bottom: var(--space-6);
}

.platform-section__bullets {
  max-width: 60ch;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.platform-section__bullets li {
  position: relative;
  padding-left: var(--space-5);
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
  line-height: var(--line-height-normal);
}

.platform-section__bullets li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.55em;
  width: 5px;
  height: 5px;
  border-radius: var(--radius-full);
  background: var(--color-accent);
}

/* Both halves are grid items, and a grid item's automatic minimum size is its
   MIN-CONTENT, not zero. Without this the mockup's widest unshrinkable part —
   the chrome bar's button row, or the diagram's own min-width — sets the track
   width, the track outgrows .container, and the whole document scrolls
   sideways on a phone. min-width: 0 is also what makes the overflow-x: auto on
   .platform-screen__diagram do anything: that rule's zero-minimum only applies
   to a scroll container that IS the grid item, and this one is two levels
   below it. */
/* ── Screen mockup chrome ── */
.platform-section__content,
.platform-section__visual {
  min-width: 0;
}

.platform-section__visual {
  width: 100%;
}

/* The panel layer is what carries separation now that the run shares one
   ground, so the frame gets a top edge that catches light — a 1px inner
   highlight above the fill, the way a physical bezel reads. Inset shadow
   rather than a masked pseudo-element: it needs no positioning context, so it
   cannot disturb anything absolutely positioned inside the mockup. */
.platform-section__screen {
  background: var(--color-bg-surface);
  border: 1px solid var(--color-border-mid);
  border-radius: var(--radius-xl);
  overflow: hidden;
  box-shadow:
    var(--shadow-panel),
    inset 0 1px 0 var(--color-edge-highlight);
  transition:
    border-color var(--transition-base),
    box-shadow var(--transition-base);
}

/* The frames stay neutral where the story cards take accent — the colour in
   this component belongs to the traffic lights below, and a blue outline
   competing with them made the chrome fight itself.

   Exactly ONE line changes colour here: the 1px border, --color-border-mid to
   --color-border-strong. Everything else on this edge stays put, which is the
   whole point — an earlier pass had three dark lines stacking on the same
   edge (the border, a `0 0 0 1px` ring sitting directly outside it, and the
   inset bezel turned dark) and the result read as a 3px drawn outline rather
   than a frame catching light. The ring is gone; --shadow-panel's own
   --color-border ring carries through untouched, and the inset stays
   --color-edge-highlight so the top bezel still catches light on hover
   instead of going dark.

   The lift does the rest of the work: --shadow-panel to --shadow-lg. That is
   the beat the eye reads, not the hairline.

   :focus-within, not :focus-visible — several of these frames hold real
   controls (the scenario tabs, the [data-hit] diagram hotspots), so the frame
   should read as lit while focus is anywhere inside it. No transform, for the
   same reason as the cards: .reveal owns that property on these elements. */
.platform-section__screen:hover,
.platform-section__screen:focus-within {
  border-color: var(--color-border-strong);
  box-shadow: var(--shadow-lg), inset 0 1px 0 var(--color-edge-highlight);
}

/* Wraps because it has to: with a switcher in it this row wants ~420px, more
   than a phone gives the card. The frame clips (overflow: hidden), so without
   wrapping the controls are not merely cramped, they are cut off and
   unreachable. Wrapped, they drop to a second row and stay usable. */
.platform-screen__bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  padding: 12px var(--space-4);
  background: var(--color-bg-subtle);
  border-bottom: 1px solid var(--color-border);
}

/* Controls sit at the far end of the chrome bar, opposite the title. */
.platform-screen__bar-controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: var(--space-2);
  margin-left: auto;
}

.platform-screen__dot {
  display: inline-block;
  width: 9px;
  height: 9px;
  border-radius: var(--radius-full);
  background: var(--color-text-dim);
  opacity: 0.35;
  transition:
    background-color var(--transition-base),
    opacity var(--transition-base);
}

/* At rest the lights are three grey pips — chrome, not decoration competing
   with the diagram. Hovering the frame brings them up to the macOS window
   colours, which is the one moment the mockup admits what it is imitating.
   The dots are always the first three children of the bar (the title follows),
   so :nth-child is safe here.

   These three hex values are pinned, not tokenised: they are quotations of a
   specific OS's window controls, not part of this site's palette, and they
   must not flip with the theme any more than the dark diagram canvas does.
   They are decorative and carry no meaning alone — the frames they sit in are
   aria-hidden or labelled by their title — so nothing depends on the colours
   being distinguishable. */
.platform-section__screen:hover .platform-screen__dot,
.platform-section__screen:focus-within .platform-screen__dot {
  opacity: 1;
}

.platform-section__screen:hover .platform-screen__dot:nth-child(1),
.platform-section__screen:focus-within .platform-screen__dot:nth-child(1) {
  background: #ff5f57;
}

.platform-section__screen:hover .platform-screen__dot:nth-child(2),
.platform-section__screen:focus-within .platform-screen__dot:nth-child(2) {
  background: #febc2e;
}

.platform-section__screen:hover .platform-screen__dot:nth-child(3),
.platform-section__screen:focus-within .platform-screen__dot:nth-child(3) {
  background: #28c840;
}

/* --color-text-dim lands at 4.41:1 on the chrome bar's lighter surface, just
   under AA. The muted step clears it on both themes. */
/* Takes the slack in the bar and wraps inside itself. min-width: 0 is the
   point: as a flex item it would otherwise refuse to go below its longest
   word, and these titles are long — "Effectivity Lookup — Part Number → EO →
   IPC → Aircraft Config". */
.platform-screen__title {
  flex: 1 1 auto;
  min-width: 0;
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-medium);
  color: var(--color-text-muted);
  margin-left: var(--space-2);
}

/* Technical diagrams are drawn light-on-dark and stay dark in both themes —
   a deliberate exception, the way a code editor stays dark inside a light UI. */
.platform-screen__diagram {
  background: var(--color-bg-invert-raised);
  padding: var(--space-5) var(--space-4);
  overflow-x: auto;
}

.platform-screen__diagram svg {
  display: block;
  width: 100%;
  height: auto;
  min-width: 280px;
}

.platform-screen__diagram--wide svg {
  min-width: 560px;
}

/* ── Screen controls & panels ──
   The segmented switcher, on/off chip, readout strip and detail panel that the
   interactive diagrams use. All monospace, matching the labels already inside
   the SVGs, and all built from existing tokens — no new colour. */
/* A dead control is worse than no control: without JS these do nothing, so
   they are not shown at all. The diagrams stay fully readable either way. */
html:not(.js-enabled) .platform-screen__bar-controls {
  display: none;
}

/* Three fleet buttons at ~52px each: the group fits one phone row on its own,
   but not beside a toggle chip. Wrapping inside the group is the last resort
   after the bar itself has wrapped. */
.screen-switch {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 2px;
  padding: 2px;
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-control);
}

.screen-switch__btn,
.screen-toggle {
  font-family: var(--font-mono);
  font-size: 11px;
  line-height: 1;
  padding: var(--space-2) var(--space-3);
  color: var(--color-text-dim);
  background: none;
  border: 1px solid transparent;
  border-radius: var(--radius-control);
  transition:
    color var(--transition-fast),
    background-color var(--transition-fast),
    border-color var(--transition-fast);
}

.screen-switch__btn:hover,
.screen-toggle:hover {
  color: var(--color-text);
}

.screen-switch__btn[aria-pressed="true"] {
  color: var(--color-accent);
  font-weight: var(--font-weight-semibold);
  background: var(--color-accent-wash);
  border-color: var(--color-accent-line);
}

.screen-toggle {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  background: var(--color-bg);
  border-color: var(--color-border);
}

.screen-toggle[aria-pressed="true"] {
  color: var(--color-accent);
  background: var(--color-accent-wash);
  border-color: var(--color-accent-line);
}

/* The chip's own status dot, and the one in the callout bar. */
.screen-dot {
  position: relative;
  display: inline-block;
  width: 7px;
  height: 7px;
  border-radius: var(--radius-full);
  background: currentColor;
  flex-shrink: 0;
}

.screen-dot--live::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: currentColor;
  animation: screen-ping 1.8s cubic-bezier(0, 0, 0.2, 1) infinite;
}

@keyframes screen-ping {
  from {
    transform: scale(1);
    opacity: 0.7;
  }
  to {
    transform: scale(2.6);
    opacity: 0;
  }
}

/* Metric strip and selection-driven detail panel, both below the diagram. */
/* minmax(0, 1fr), not 1fr: a bare 1fr is minmax(AUTO, 1fr), so each column
   refuses to go below its longest word and the strip sets a floor under the
   whole card — "consumables" and "MONITORED PARTS" are enough to do it. The
   cells are bare divs built by readout() in main.js, hence the child selector. */
.screen-readout {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--space-4);
  padding: var(--space-3) var(--space-4);
  background: var(--color-bg-subtle);
  border-top: 1px solid var(--color-border);
  font-family: var(--font-mono);
}

.screen-readout > * {
  min-width: 0;
}

/* --color-text-dim is a page-ground token: it clears AA on --color-bg, not on
   the lighter --color-bg-subtle this strip sits on, where it measured 4.16:1
   in dark at 10px. Muted instead, which is the same fix .platform-screen__title
   already took a few rules up. */
.screen-readout__label {
  display: block;
  font-size: 10px;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--color-text-muted);
  margin-bottom: var(--space-1);
}

.screen-readout__value {
  display: block;
  font-size: 11px;
  font-weight: var(--font-weight-semibold);
  color: var(--color-text);
  font-variant-numeric: tabular-nums;
}

.screen-readout__value--accent {
  color: var(--color-accent);
}

.screen-detail {
  padding: var(--space-3) var(--space-4);
  background: var(--color-bg-subtle);
  border-top: 1px solid var(--color-border);
  font-family: var(--font-mono);
  font-size: 11px;
  line-height: var(--line-height-normal);
  color: var(--color-text-muted);
}

.screen-detail__head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  margin-bottom: var(--space-2);
}

.screen-detail__tag {
  padding: 2px var(--space-2);
  font-size: 10px;
  color: var(--color-accent);
  background: var(--color-accent-wash);
  border: 1px solid var(--color-accent-line);
  border-radius: var(--radius-control);
}

.screen-detail__tag--warn {
  color: var(--color-warning);
  background: var(--color-warning-wash);
  border-color: var(--color-warning-line);
}

/* ── In-diagram interaction states ──
   Driven by class on SVG children so the geometry never moves. */
.platform-screen__diagram [data-hit] {
  cursor: pointer;
  outline: none;
}

.diagram-point,
.diagram-node,
.diagram-link,
.diagram-label {
  transition:
    r var(--transition-fast),
    fill var(--transition-fast),
    stroke var(--transition-fast),
    stroke-width var(--transition-fast),
    opacity var(--transition-fast);
}

.diagram-halo {
  opacity: 0;
  transition: opacity var(--transition-base);
}

.is-selected .diagram-halo,
.diagram-halo.is-on {
  opacity: 1;
}

.diagram-crosshair {
  opacity: 0;
  transition: opacity var(--transition-fast);
}

.diagram-crosshair.is-on {
  opacity: 0.8;
}

/* Focus ring for keyboard scrubbing — the global :focus-visible outline in §2
   does not render usefully on an invisible SVG rect. */
.platform-screen__diagram [data-hit]:focus-visible ~ .diagram-crosshair,
.platform-screen__diagram [data-hit]:focus-visible + .diagram-crosshair {
  opacity: 1;
}

/* ── Focus / dim ──
   Story-page diagrams: hovering one shape emphasises it by dropping its peers
   back. Only the focusable set dims — labels stay at full strength so the
   diagram keeps its context — and 0.28 rather than near-zero, because this is
   emphasis, not concealment.

   No .js-enabled guard is needed: .has-focus is only ever added by JS, so with
   scripting off nothing can dim and no shape can be left hidden. */
.diagram-focusable {
  cursor: pointer;
  transition:
    opacity var(--transition-fast),
    stroke-width var(--transition-fast);
}

.has-focus .diagram-focusable {
  opacity: 0.28;
}

.has-focus .diagram-focusable.is-focused {
  opacity: 1;
}

/* Node graphs: selection changes weight, never geometry. */
.diagram-node,
.diagram-row {
  cursor: pointer;
}

.diagram-node.is-selected {
  stroke-width: 3.5;
}

.diagram-node:hover {
  stroke-width: 3;
}

.diagram-link {
  transition:
    stroke var(--transition-base),
    stroke-width var(--transition-base);
}

.diagram-link.is-selected {
  stroke: var(--color-accent-invert);
  stroke-width: 2.5;
}

/* Day rows in the data-health panel */
.diagram-row .diagram-bar {
  transition:
    opacity var(--transition-fast),
    height var(--transition-fast);
  opacity: 0.55;
}

.diagram-row:hover .diagram-bar,
.diagram-row.is-selected .diagram-bar {
  opacity: 1;
}

.diagram-row.is-selected text {
  fill: var(--color-text-invert);
}

/* Link traffic — only while the "Live sync" chip is on. */
.diagram-traffic {
  opacity: 0;
  transition: opacity var(--transition-base);
}

.is-live .diagram-traffic {
  opacity: 1;
}

/* Parked by §9 when the diagram scrolls off screen. pauseAnimations() stops
   the SMIL packets; this stops the CSS keyframe loops, which are a separate
   clock and would otherwise keep running. */
.diagram-parked .diagram-march,
.diagram-parked .diagram-breathe {
  animation-play-state: paused;
}

/* Ambient: the route line's dashes march, and a halo breathes. Both are
   paused by pauseAnimations() when the diagram scrolls off screen (§9). */
@keyframes diagram-march {
  to {
    stroke-dashoffset: -24;
  }
}

@keyframes diagram-breathe {
  0%,
  100% {
    opacity: 0.25;
    transform: scale(1);
  }
  50% {
    opacity: 0.6;
    transform: scale(1.12);
  }
}

.diagram-march {
  stroke-dasharray: 6 6;
  animation: diagram-march 1.2s linear infinite;
}

.diagram-breathe {
  transform-box: fill-box;
  transform-origin: center;
  animation: diagram-breathe 2.8s ease-in-out infinite;
}

/* ── Diagram entrance ──
   The diagram renders itself in: a wipe carries the axes and data lines
   across, the points that mark them pop once it lands, then the labels.
   The element type is the layer — in every one of these diagrams line/path
   is structure and data, circle is a node or endpoint, text is a label — so
   this needs no classes in the markup.

   Two scoping guards, both load-bearing:
     .js-enabled  — with JS off the class never lands, so nothing is hidden.
     .reveal      — two diagrams on the supply-chain page have no reveal
                    ancestor; unscoped, they would be hidden forever.

   The wipe clips the svg, NOT .platform-screen__diagram: the wrapper carries
   the #0b0b0f plot ground, and clipping that would wipe the background in
   too. This way the drawing appears inside a console that is already there. */
.js-enabled .reveal .platform-screen__diagram > svg {
  animation: diagram-wipe 0.7s var(--ease-out-expo) 0.15s both;
  animation-play-state: paused;
}

.js-enabled .reveal .platform-screen__diagram > svg circle {
  /* fill-box or the scale happens about the SVG's origin, not the element's
     own centre — circles would fly in from the top-left corner. */
  transform-box: fill-box;
  transform-origin: center;
  animation: diagram-pop 0.35s var(--ease-out-expo) 0.7s both;
  animation-play-state: paused;
}

.js-enabled .reveal .platform-screen__diagram > svg text {
  animation: diagram-fade 0.4s var(--ease-out-quart) 0.85s both;
  animation-play-state: paused;
}

/* `both` fill holds each element at its from-state while paused, so the end
   state lives in exactly one place and no separate hidden rules are needed. */
.js-enabled .reveal--visible .platform-screen__diagram > svg,
.js-enabled .reveal--visible .platform-screen__diagram > svg circle,
.js-enabled .reveal--visible .platform-screen__diagram > svg text {
  animation-play-state: running;
}

@keyframes diagram-wipe {
  from {
    clip-path: inset(0 100% 0 0);
  }
  to {
    clip-path: inset(0 0 0 0);
  }
}

@keyframes diagram-pop {
  from {
    opacity: 0;
    transform: scale(0.6);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

@keyframes diagram-fade {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

.platform-screen__callout {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-3) var(--space-4);
  border-top: 1px solid var(--color-border);
  background: var(--color-bg-subtle);
  font-size: var(--font-size-xs);
}

.platform-screen__callout-label {
  color: var(--color-text-muted);
}

.platform-screen__callout-value {
  font-weight: var(--font-weight-semibold);
  font-variant-numeric: tabular-nums;
  color: var(--color-success);
}

.platform-screen__callout-value--warn {
  color: var(--color-warning);
}

/* =============================================================================
   11. SUCCESS STORIES GRID
   ============================================================================= */
/* Closes the .platform run. This used to have no border because the ground
   changed back to base here and that was the boundary; now the ground is
   continuous, so this boundary needs its keyline like every other one. */
.stories {
  padding: var(--section-y) 0;
  background-color: var(--color-bg);
  border-top: 1px solid var(--color-border);
  box-shadow: inset 0 1px 0 var(--color-keyline);
}

.stories__header {
  max-width: 640px;
  margin-bottom: var(--space-12);
}

.stories__heading {
  font-size: var(--text-h2);
  font-weight: var(--font-weight-semibold);
  letter-spacing: var(--tracking-h2);
  color: var(--color-text);
  margin-bottom: var(--space-4);
}

.stories__subheading {
  font-size: var(--font-size-base);
  color: var(--color-text-muted);
}

.stories__grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-5);
}

.stories__grid > li {
  display: flex;
}

.story-card {
  display: flex;
  flex-direction: column;
  width: 100%;
  padding: var(--space-8);
  background: var(--color-bg-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-card);
  transition:
    border-color var(--transition-base),
    background-color var(--transition-base),
    box-shadow var(--transition-base);
}

/* The whole outline used to turn a saturated accent once — the loudest thing on
   the page for a mere hover, and it was pulled for that. The outline is back in
   the gesture but tinted, not saturated: --color-accent-line is the accent at
   0.22/0.28 alpha, so the hue reads without the line shouting. Weight carries
   the rest — the ground lifts and the shadow deepens to --shadow-md, which
   (unlike --shadow-card) has a real dark-theme arm, so the raise is visible in
   both themes. No transform here on purpose: .reveal owns the transform
   transition on this element at higher specificity, and a lift would inherit
   its 0.5s and go slack. */
.story-card:hover,
.story-card:focus-visible {
  border-color: var(--color-accent-line);
  background: var(--color-bg-subtle);
  box-shadow: var(--shadow-md);
}

.story-card__tag {
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-semibold);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--color-accent);
  margin-bottom: var(--space-4);
}

.story-card__title {
  font-size: var(--text-h3);
  font-weight: var(--font-weight-semibold);
  line-height: var(--line-height-snug);
  letter-spacing: var(--tracking-h3);
  color: var(--color-text);
  margin-bottom: var(--space-3);
}

.story-card__body {
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
  margin-bottom: var(--space-6);
  flex-grow: 1;
}

.story-card__stat {
  padding-top: var(--space-4);
  border-top: 1px solid var(--color-border);
  font-size: var(--font-size-xs);
  color: var(--color-text-muted);
  margin-bottom: var(--space-5);
}

.story-card__stat-value {
  display: block;
  font-size: var(--font-size-3xl);
  font-weight: var(--font-weight-bold);
  letter-spacing: var(--tracking-h2);
  color: var(--color-text);
  margin-bottom: var(--space-1);
  transition: color var(--transition-base);
}

/* The number is the card's payload. Hover tints it to the accent the arrow
   already carries, so the two beats read as one gesture rather than two. */
.story-card:hover .story-card__stat-value,
.story-card:focus-visible .story-card__stat-value {
  color: var(--color-accent);
}

.story-card__link {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-semibold);
  color: var(--color-accent);
}

.story-card__link-arrow {
  transition: transform var(--transition-base);
}

.story-card:hover .story-card__link-arrow,
.story-card:focus-visible .story-card__link-arrow {
  transform: translateX(6px);
}

/* =============================================================================
   12. STATS
   ============================================================================= */
/* Cells inside the hero panel. The dividers are the 1px grid gap showing the
   panel's ground through — the same idiom `.story-meta` uses in
   the success-stories sheet. No nth-child maths, correct at two columns or four.
   The panel supplies the outer frame, so there is no border here. */
.stats__grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 1px;
  background: var(--color-border);
}

.stats__item {
  display: flex;
  flex-direction: column;
  padding: var(--space-8) var(--space-5);
  background: var(--color-bg-raised);
}

/* Deliberately NOT display scale: these now sit a few hundred pixels below the
   h1, which is display. Two display-size elements in one viewport cancel each
   other out — the headline has to win. */
.stats__value {
  font-size: var(--text-h2);
  font-weight: var(--font-weight-bold);
  color: var(--color-text);
  line-height: 1;
  letter-spacing: var(--tracking-h2);
  margin-bottom: var(--space-2);
  font-variant-numeric: tabular-nums;
}

.stats__label {
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
  line-height: var(--line-height-snug);
}

/* =============================================================================
   13. CTA BANNER
   ============================================================================= */
/* The one deliberate exception to the single rhythm: an ending needs more air
   than a middle. Everything else in §13 exists to make this the loudest block
   on the page rather than the quietest. */
/* Dark in BOTH themes — the page's one punctuation mark, taking the same
   exception .platform-screen__diagram takes in §10. In dark it is the same
   value as the ground, so the border-top still does the separating; in light
   it becomes the single contrast block. Every colour below is an -invert
   token, pinned in §1 and absent from §18. */
.cta-banner {
  position: relative;
  overflow: hidden;
  padding: calc(var(--section-y) * 1.5) 0;
  background-color: var(--color-bg-invert);
  color: var(--color-text-invert);
  border-top: 1px solid var(--color-border);
}

/* Full strength — the hero opens with the grid and the CTA closes with it.
   The two instances in between stay dialled back to 0.45. */
.cta-banner__dot-canvas {
  z-index: 0;
}

.cta-banner__inner {
  position: relative;
  z-index: 1;
  text-align: center;
}

/* Display scale — the second documented exception. The page opens and closes
   at the same size; everything between is smaller. */
.cta-banner__heading {
  font-size: var(--text-display);
  font-weight: var(--font-weight-semibold);
  color: var(--color-text-invert);
  line-height: var(--line-height-tight);
  letter-spacing: var(--tracking-display);
  max-width: 900px;
  margin: 0 auto var(--space-5);
  text-wrap: balance;
}

.cta-banner__subheading {
  font-size: var(--font-size-lg);
  color: var(--color-text-invert-muted);
  max-width: 520px;
  margin: 0 auto var(--space-10);
  line-height: var(--line-height-relaxed);
}

.cta-banner__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  justify-content: center;
}

.cta-banner__visual {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
}

/* Single-hue glow, keyed to the brand tokens. Three stops rather than two so
   it has a centre to it — still the same hue, still no literal colour. */
.cta-banner__glow {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 1200px;
  max-width: 140%;
  height: 560px;
  background: radial-gradient(
    ellipse at center,
    var(--color-accent-line-invert) 0%,
    var(--color-accent-wash-invert) 45%,
    transparent 70%
  );
}

/* =============================================================================
   14. CONTACT FORM
   ============================================================================= */
.contact-form {
  position: relative;
  padding: var(--section-y) 0;
  background-color: var(--color-bg);
  border-top: 1px solid var(--color-border);
}

.contact-form__inner {
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-12);
  align-items: start;
}

.contact-form__heading {
  font-size: var(--text-h2);
  font-weight: var(--font-weight-semibold);
  color: var(--color-text);
  line-height: var(--line-height-tight);
  letter-spacing: var(--tracking-h2);
  margin-bottom: var(--space-4);
}

.contact-form__subheading {
  font-size: var(--font-size-base);
  color: var(--color-text-muted);
  line-height: var(--line-height-relaxed);
  margin-bottom: var(--space-8);
}

.contact-form__trust {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.contact-form__trust-item {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
}

.contact-form__check {
  width: 20px;
  height: 20px;
  flex-shrink: 0;
  color: var(--color-accent);
}

.contact-form__direct {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  margin-top: var(--space-8);
  padding-top: var(--space-8);
  border-top: 1px solid var(--color-border);
}

.contact-form__direct-link {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-medium);
  color: var(--color-text-muted);
  transition: color var(--transition-fast);
}

.contact-form__direct-link:hover {
  color: var(--color-text);
}

.contact-form__direct-link svg {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
  opacity: 0.7;
}

.contact-form__card {
  background-color: var(--color-bg-surface);
  border-radius: var(--radius-2xl);
  padding: var(--space-10);
  border: 1px solid var(--color-border);
  box-shadow: var(--shadow-card);
}

.contact-form__form {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
}

.form-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-4);
}

.form-group {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.form-label {
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-medium);
  color: var(--color-text-muted);
}

.form-label span {
  color: var(--color-accent);
}

.form-input {
  width: 100%;
  padding: var(--space-3) var(--space-4);
  font-size: var(--font-size-sm);
  font-family: inherit;
  color: var(--color-text);
  caret-color: var(--color-accent);
  background-color: var(--color-bg);
  border: 1px solid var(--color-border-mid);
  border-radius: var(--radius-md);
  transition:
    border-color var(--transition-fast),
    box-shadow var(--transition-fast);
  -webkit-appearance: none;
  appearance: none;
}

/* iOS Safari zooms the viewport when a field under 16px takes focus. Touch
   devices only, so the denser desktop size is unchanged. */
@media (pointer: coarse) {
  .form-input {
    font-size: max(1rem, 16px);
  }
}

.form-input::placeholder {
  color: var(--color-text-dim);
}

/* The wash + border is the visual affordance; the outline from §2 still comes
   through underneath it. A box-shadow ring alone disappears under Windows
   High Contrast, which is why the outline is not suppressed here. */
.form-input:focus-visible {
  border-color: var(--color-accent);
  box-shadow: 0 0 0 3px var(--color-accent-wash);
}

.form-input--textarea {
  resize: vertical;
  min-height: 110px;
  line-height: var(--line-height-relaxed);
}

.form-input--error {
  border-color: var(--color-danger);
}

.form-input--error:focus {
  box-shadow: 0 0 0 3px var(--color-danger-wash);
}

.form-error {
  display: none;
  font-size: var(--font-size-xs);
  color: var(--color-danger);
  font-weight: var(--font-weight-medium);
}

.form-group:has(.form-input--error) .form-error {
  display: block;
}

.contact-form__submit {
  width: 100%;
  margin-top: var(--space-2);
}

.contact-form__note {
  font-size: var(--font-size-xs);
  color: var(--color-text-dim);
  text-align: center;
  line-height: var(--line-height-relaxed);
}

/* =============================================================================
   15. FOOTER
   ============================================================================= */
/* The footer takes the section ground, not the page ground: on every layout the
   block above it (contact form, legal body, CTA banner) already sits on
   --color-bg or the pinned invert, so a footer on --color-bg had nothing but a
   1px rule to separate it. The smallest text here, --color-text-dim, still
   measures AA on this ground — 5.44 dark, 4.65 light. */
.footer {
  position: relative;
  overflow: hidden;
  background-color: var(--color-bg-section);
  border-top: 1px solid var(--color-border);
}

.footer__inner {
  position: relative;
  z-index: 2;
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-10);
  padding-top: var(--section-y);
  padding-bottom: var(--space-12);
}

.footer__brand {
  max-width: 320px;
}

.footer__logo {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin-bottom: var(--space-5);
}

.footer__logo-text {
  font-size: var(--font-size-lg);
  font-weight: var(--font-weight-semibold);
  color: var(--color-text);
  letter-spacing: var(--tracking-h3);
}

.footer__tagline {
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
  line-height: var(--line-height-relaxed);
  margin-bottom: var(--space-4);
}

.footer__address {
  font-size: var(--font-size-sm);
  font-style: normal;
  color: var(--color-text-dim);
  line-height: var(--line-height-relaxed);
}

.footer__address a {
  transition: color var(--transition-fast);
}

.footer__address a:hover {
  color: var(--color-text);
}

.footer__nav-heading {
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-semibold);
  color: var(--color-text);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  margin-bottom: var(--space-5);
}

.footer__nav-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.footer__nav-link {
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
  transition: color var(--transition-fast);
}

.footer__nav-link:hover {
  color: var(--color-text);
}

.footer__bar {
  position: relative;
  z-index: 2;
  border-top: 1px solid var(--color-border);
  padding: var(--space-4) 0;
}

.footer__bar-inner {
  display: flex;
  flex-direction: row;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-3);
}

.footer__copyright {
  font-size: var(--font-size-sm);
  color: var(--color-text-dim);
}

.footer__legal {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4);
}

.footer__legal-link {
  font-size: var(--font-size-sm);
  color: var(--color-text-dim);
  transition: color var(--transition-fast);
}

.footer__legal-link:hover {
  color: var(--color-text);
}

.footer__credit {
  font-size: var(--font-size-xs);
  color: var(--color-text-dim);
}

.footer__credit-link {
  transition: color var(--transition-fast);
}

.footer__credit-link:hover {
  color: var(--color-text);
}

/* =============================================================================
   16. REVEALS & MOTION
   ============================================================================= */
/* Short travel on an ease-out-expo. Long travel on a symmetric curve is what
   makes a reveal read as a theme rather than as craft. */
.js-enabled .reveal {
  opacity: 0;
  transform: translateY(8px);
  transition:
    opacity 0.5s var(--ease-out-expo),
    transform 0.5s var(--ease-out-expo);
}

.js-enabled .reveal--delay-1 {
  transition-delay: 0.06s;
}

.js-enabled .reveal--delay-2 {
  transition-delay: 0.12s;
}

.js-enabled .reveal--delay-3 {
  transition-delay: 0.18s;
}

.js-enabled .reveal--visible {
  opacity: 1;
  transform: none;
}

/* Motion is opt-out everywhere, not just on three keyframe rules. */
@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }

  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }

  .js-enabled .reveal {
    opacity: 1;
    transform: none;
  }

  /* Same trap as the diagrams below: the blanket rule collapses duration but
     not delay, so the headline's staggered groups and the 0.62s accent rule
     would still arrive late, one after another. Cancel the entrance outright —
     the heading is simply present. */
  .js-enabled .reveal .hero__heading-line-inner {
    animation: none !important;
    transform: none;
  }

  .js-enabled .reveal .hero__heading-line .hero__heading-accent {
    animation: none !important;
    background-size: 100% 0.055em;
  }

  /* The blanket rule above collapses animation-duration but NOT
     animation-delay, so the diagram's 0.85s label step would survive and flash
     each chart in without its labels. Cancel the entrance outright. */
  .js-enabled .reveal .platform-screen__diagram > svg,
  .js-enabled .reveal .platform-screen__diagram > svg circle,
  .js-enabled .reveal .platform-screen__diagram > svg text {
    animation: none !important;
    clip-path: none;
    opacity: 1;
    transform: none;
  }

  /* Ambient diagram motion is cancelled outright. Selection, scrubbing and
     the toggles all still work — those are interactions, not motion. */
  .diagram-march,
  .diagram-breathe,
  .screen-dot--live::after {
    animation: none !important;
  }

  .diagram-march {
    stroke-dashoffset: 0;
  }

  .diagram-breathe {
    opacity: 0.45;
  }
}

/* =============================================================================
   17. MEDIA QUERIES
   ============================================================================= */
@media (max-width: 519px) {
  .form-row {
    grid-template-columns: 1fr;
  }
}

/* The headline groups are inline-block, so `text-wrap: balance` can only
   balance WITHIN a group — measured at a 342px content column that costs one
   whole extra line (6 rather than 5), pushing the CTAs down the fold. Below
   the tablet breakpoint the groups collapse back to plain inline text: the
   original wrap returns and the mask goes with it. No loss of intent — phones
   are already the reduced-effects tier here, with no dot-grid at all. */
@media (max-width: 767px) {
  .hero__heading-line,
  .hero__heading-line-inner {
    display: inline;
  }

  .js-enabled .reveal .hero__heading-line-inner {
    animation: none;
  }

  /* The rule stays — it is the accent, not the entrance — but it is simply
     there rather than drawing itself, since the entrance it punctuated is
     gone at this width. */
  .js-enabled .reveal .hero__heading-line .hero__heading-accent {
    animation: none;
    background-size: 100% 0.055em;
  }
}

@media (min-width: 600px) {
  .data-viz__pillars {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (min-width: 768px) {
  /* The one section rhythm opens up. Nothing else needs touching: every
     section, in all three stylesheets, reads this token. */
  :root {
    --section-y: var(--space-24);
  }

  .container {
    padding-inline: var(--space-8);
  }

  .tagline-cards__grid {
    grid-template-columns: repeat(3, 1fr);
  }

  .stories__grid {
    grid-template-columns: repeat(2, 1fr);
    gap: var(--space-6);
  }

  .footer__inner {
    grid-template-columns: 2fr 1fr 1fr;
    gap: var(--space-12);
  }

  .footer__bar-inner {
    flex-wrap: nowrap;
  }
}

/* Mobile only — swap desktop nav for the drawer toggle */
@media (max-width: 1023px) {
  .header__nav,
  .header__actions {
    display: none;
  }

  .header__menu-toggle {
    display: inline-flex;
    margin-left: auto;
  }
}

@media (min-width: 1024px) {
  :root {
    --section-y: var(--space-32);
  }

  .header__nav {
    display: block;
  }

  .header__actions {
    display: flex;
  }

  .header__menu-toggle {
    display: none;
  }

  .platform-section__inner {
    grid-template-columns: 1fr 1fr;
    gap: var(--space-16);
    align-items: start;
  }

  /* meta + content stack in the left column; the screen mockup spans both
     rows on the right */
  .platform-section__meta {
    grid-column: 1;
    grid-row: 1;
    margin-bottom: 0;
  }

  .platform-section__content {
    grid-column: 1;
    grid-row: 2;
  }

  .platform-section__visual {
    grid-column: 2;
    grid-row: 1 / span 2;
    align-self: center;
  }

  /* Sections 2.0 and 4.0 mirror the layout, so the run of five reads
     left / right / left / right / left instead of five identical rows. The
     rows are untouched — only the columns swap. */
  .platform-section--alt .platform-section__meta,
  .platform-section--alt .platform-section__content {
    grid-column: 2;
  }

  .platform-section--alt .platform-section__visual {
    grid-column: 1;
  }

  .data-viz__pillars {
    grid-template-columns: repeat(4, 1fr);
  }

  .stats__grid {
    grid-template-columns: repeat(4, 1fr);
  }

  .footer__inner {
    grid-template-columns: 2fr 1fr 1fr;
    gap: var(--space-16);
  }

  .contact-form__inner {
    grid-template-columns: 1fr 1.1fr;
    gap: var(--space-16);
  }
}

/* The hero claims the first screen — but only when there is a screen to claim.
   Below 640px of viewport height (landscape laptops, split windows, short
   browser chrome) it stays content-sized so nothing is ever cropped. */
@media (min-width: 1024px) and (min-height: 640px) {
  .hero {
    /* 88 rather than 100: the sticky header takes the first 72px, so the hero
       ends at exactly 88vh and leaves ~12vh of the next section showing. The
       fold reads as a continuation rather than as the end of the page. On
       screens too short for the content the floor is simply ignored and the
       content governs, which is the correct fallback. */
    min-height: calc(88vh - var(--header-height));
    min-height: calc(88svh - var(--header-height));
    display: grid;
    align-content: center;

    /* Tighter than --section-y. The min-height and centring do the work of
       filling the screen; full rhythm padding on top of the overview panel
       pushed the hero past the fold on 768px-tall laptops. */
    padding-block: var(--space-16);
  }
}

/* =============================================================================
   18. THEME SWITCHING — COLOR-SCHEME ONLY
   There is no second palette here any more. Every theme-varying token is a
   single light-dark() declaration in §1, so the entire theme switch is the one
   property that decides which arm of those resolves.

   This block used to be ~90 lines of light values written out twice — once
   under prefers-color-scheme for anyone with JS off, once under [data-theme]
   for the toggle — two copies that had to be kept in sync by hand. :root
   already declares `color-scheme: light dark`, which covers the no-JS case on
   its own, so all that is left is to pin an arm when a theme has been stamped.
   ============================================================================= */
:root[data-theme="light"] {
  color-scheme: light;
}

:root[data-theme="dark"] {
  color-scheme: dark;
}

/* ── Fallback for engines without light-dark() (Safari < 17.5) ──
   There the §1 declarations are invalid and every themed token would be
   unset. Same values as §1, written out per theme. Engines that support
   light-dark() skip this block, so §1 stays the source of truth: edit both
   when a themed token changes. */
@supports not (color: light-dark(#000, #fff)) {
  :root,
  :root[data-theme="dark"] {
    --color-bg: var(--gray-950);
    --color-bg-raised: var(--gray-900);
    --color-bg-surface: var(--gray-800);
    --color-bg-subtle: var(--gray-700);
    --color-bg-section: var(--gray-900);
    --color-border: rgba(255, 255, 255, 0.08);
    --color-border-mid: rgba(255, 255, 255, 0.14);
    --color-border-strong: rgba(255, 255, 255, 0.22);
    --color-keyline: rgba(0, 0, 0, 0.5);
    --color-edge-highlight: rgba(255, 255, 255, 0.06);
    --color-text: var(--gray-50);
    --color-text-muted: var(--gray-400);
    --color-text-dim: #8b8b94;
    --color-primary-light: var(--brand-400);
    --color-accent: var(--brand-400);
    --color-accent-wash: rgba(70, 176, 223, 0.1);
    --color-accent-line: rgba(70, 176, 223, 0.28);
    --color-success: #22c55e;
    --color-warning: #f59e0b;
    --color-danger: #f87171;
    --color-info: #a78bfa;
    --color-warning-wash: rgba(245, 158, 11, 0.1);
    --color-warning-line: rgba(245, 158, 11, 0.28);
    --color-danger-wash: rgba(248, 113, 113, 0.18);
    --shadow-card:
      0 1px 2px transparent,
      0 4px 12px transparent;
    --shadow-sm: 0 1px 2px rgb(0 0 0 / 0.3);
    --shadow-md: 0 4px 16px rgb(0 0 0 / 0.4);
    --shadow-lg: 0 16px 48px rgb(0 0 0 / 0.5);
    --shadow-panel:
      0 0 0 1px var(--color-border),
      0 20px 48px rgb(0 0 0 / 0.45);
  }

  :root[data-theme="light"] {
    --color-bg: #ffffff;
    --color-bg-raised: #fafafa;
    --color-bg-surface: #f4f5f7;
    --color-bg-subtle: #ececee;
    --color-bg-section: #edf1f6;
    --color-border: rgba(0, 0, 0, 0.09);
    --color-border-mid: rgba(0, 0, 0, 0.16);
    --color-border-strong: rgba(0, 0, 0, 0.26);
    --color-keyline: rgba(0, 0, 0, 0.035);
    --color-edge-highlight: rgba(255, 255, 255, 0.9);
    --color-text: #09090b;
    --color-text-muted: #52525b;
    --color-text-dim: #6b6b74;
    --color-primary-light: var(--brand-700);
    --color-accent: var(--brand-700);
    --color-accent-wash: rgba(8, 103, 140, 0.08);
    --color-accent-line: rgba(8, 103, 140, 0.22);
    --color-success: #15803d;
    --color-warning: #a16207;
    --color-danger: #b91c1c;
    --color-info: #6d28d9;
    --color-warning-wash: rgba(161, 98, 7, 0.1);
    --color-warning-line: rgba(161, 98, 7, 0.28);
    --color-danger-wash: rgba(185, 28, 28, 0.14);
    --shadow-card:
      0 1px 2px rgb(0 0 0 / 0.04),
      0 4px 12px rgb(0 0 0 / 0.06);
    --shadow-sm: 0 1px 2px rgb(0 0 0 / 0.06);
    --shadow-md: 0 4px 16px rgb(0 0 0 / 0.08);
    --shadow-lg: 0 16px 48px rgb(0 0 0 / 0.1);
    --shadow-panel:
      0 0 0 1px var(--color-border),
      0 20px 48px rgb(0 0 0 / 0.08);
  }

  @media (prefers-color-scheme: light) {
    :root:not([data-theme]) {
      --color-bg: #ffffff;
      --color-bg-raised: #fafafa;
      --color-bg-surface: #f4f5f7;
      --color-bg-subtle: #ececee;
      --color-bg-section: #edf1f6;
      --color-border: rgba(0, 0, 0, 0.09);
      --color-border-mid: rgba(0, 0, 0, 0.16);
      --color-border-strong: rgba(0, 0, 0, 0.26);
      --color-keyline: rgba(0, 0, 0, 0.035);
      --color-edge-highlight: rgba(255, 255, 255, 0.9);
      --color-text: #09090b;
      --color-text-muted: #52525b;
      --color-text-dim: #6b6b74;
      --color-primary-light: var(--brand-700);
      --color-accent: var(--brand-700);
      --color-accent-wash: rgba(8, 103, 140, 0.08);
      --color-accent-line: rgba(8, 103, 140, 0.22);
      --color-success: #15803d;
      --color-warning: #a16207;
      --color-danger: #b91c1c;
      --color-info: #6d28d9;
      --color-warning-wash: rgba(161, 98, 7, 0.1);
      --color-warning-line: rgba(161, 98, 7, 0.28);
      --color-danger-wash: rgba(185, 28, 28, 0.14);
      --shadow-card:
        0 1px 2px rgb(0 0 0 / 0.04),
        0 4px 12px rgb(0 0 0 / 0.06);
      --shadow-sm: 0 1px 2px rgb(0 0 0 / 0.06);
      --shadow-md: 0 4px 16px rgb(0 0 0 / 0.08);
      --shadow-lg: 0 16px 48px rgb(0 0 0 / 0.1);
      --shadow-panel:
        0 0 0 1px var(--color-border),
        0 20px 48px rgb(0 0 0 / 0.08);
    }
  }
}

/* ── Assets that cannot be tokenised ──
   Two logo lockups and two toggle glyphs, swapped rather than recoloured.
   The :root:not([data-theme]) arm keeps the light lockup correct for a no-JS
   visitor on a light OS, which is the one case data-theme cannot answer. */
@media (prefers-color-scheme: light) {
  :root:not([data-theme]) .header__logo-wordmark--light,
  :root:not([data-theme]) .theme-icon--sun {
    display: none;
  }

  :root:not([data-theme]) .header__logo-wordmark--dark,
  :root:not([data-theme]) .theme-icon--moon {
    display: block;
  }
}

:root[data-theme="light"] .header__logo-wordmark--light,
:root[data-theme="light"] .theme-icon--sun {
  display: none;
}

:root[data-theme="light"] .header__logo-wordmark--dark,
:root[data-theme="light"] .theme-icon--moon {
  display: block;
}

:root[data-theme="dark"] .theme-icon--sun {
  display: block;
}

:root[data-theme="dark"] .theme-icon--moon {
  display: none;
}
