/* Self-hosted Geist (Vercel, SIL Open Font License) - no external font
   requests, for GDPR. Never loads from fonts.googleapis.com,
   fonts.gstatic.com or any other outside host. */
@font-face {
  font-family: "Geist";
  src: url("fonts/Geist-Regular.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Geist";
  src: url("fonts/Geist-Medium.woff2") format("woff2");
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Geist";
  src: url("fonts/Geist-SemiBold.woff2") format("woff2");
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}

/* Self-hosted Instrument Serif Italic (SIL Open Font License), same reason
   as Geist above - no external font requests, for GDPR. Italic, regular
   weight only; used nowhere except the hero headline experiment
   (.hero__headline-serif). */
@font-face {
  font-family: "Instrument Serif";
  src: url("fonts/InstrumentSerif-Italic.woff2") format("woff2");
  font-weight: 400;
  font-style: italic;
  font-display: swap;
}

:root {
  --ease-out: cubic-bezier(0.23, 1, 0.32, 1);

  /* GUESS / DESIGN VALUE: shared centred content width for .nav__bar and
     .hero only, so they stay the same width as each other and keep lining
     up. Other sections below the hero (.section__inner, .book__inner,
     .footer, .footer__divider, .footer__bottom, .footer__disclaimer) still
     use their own separate, hardcoded 1320px and are untouched. */
  --content-max-width: 1440px;

  /* GUESS - rounded-rectangle corner radius for every button/button-like
     link site-wide (was a fully-rounded 999px pill). */
  --radius-btn: 8px;

  /* ===== Typography settings - tune here =====
     --font-weight-regular: body text, subtext, nav links, buttons,
       footer links, form fields and labels.
     --font-weight-label: small labels (the eyebrows, footer column
       headings, badges), the footer tagline, and any other bold
       paragraph text.
     --font-weight-heading: headings (hero headline, section headings,
       page titles) and the Interoper wordmark.
     --font-letter-spacing-heading: GUESS - applied to headings only
       (not the wordmark, which keeps its own letter-spacing). */
  --font-sans: "Geist", system-ui, -apple-system, "Segoe UI", sans-serif;
  --font-weight-regular: 400;
  --font-weight-label: 500;
  --font-weight-heading: 600;
  --font-letter-spacing-heading: -0.03em;

  /* Big headlines only (hero headline, section headings) - tune here.
     Kept separate from --font-weight-heading/--font-letter-spacing-
     heading above, which other elements (nav/footer wordmark, legal
     page titles, FAQ, scorecard, etc.) still use unchanged. */
  --font-weight-headline-lg: 500;
  --font-letter-spacing-headline-lg: -0.03em;
  --line-height-headline-lg: 1.08;

  /* Small headings only (step titles, card titles). */
  --font-weight-headline-sm: 500;
  --font-letter-spacing-headline-sm: -0.01em;

  /* Robot hand fine-tuning: nudges the robot hand's position without
     touching the human hand, the rotation angles, sizes, or text. Positive
     --robot-shift-down moves it down; positive --robot-shift-right moves
     it right. Change just these numbers to nudge it (desktop values here,
     mobile equivalents are in the @media block below). */
  --robot-shift-down: 340px;
  --robot-shift-right: -170px;
  /* Mobile: was 136px/-68px, which left the arm floating in the lower
     middle of the hands box, well clear of the rounded divider below.
     Now pushed much further down and further left, on the same two
     variables, so the arm overhangs the bottom of the mobile hero the
     same way it already does on desktop (see --hero-overhang-mobile
     and #the-problem's solid background, neither of which needed to
     change - the mobile hands box was just never asking the arm to
     reach that far before). */
  --robot-shift-down-mobile: 260px;
  --robot-shift-right-mobile: -140px;
  /* Phone only (under 700px - see the @media block near the human hand
     below): raises the robot higher than the 260px above, so its
     fingertip sits close to the human fingertip instead of near the
     bottom of the hero, and enlarges it slightly so its lower end still
     reaches down into the same overhang/cover zone despite sitting
     higher. New variables - nothing above this read them until that
     media block overrides --robot-shift-down-mobile with the first one. */
  /* Was 78px - moved up a further 8px (roughly 1% of a ~400px-tall
     phone screen) per this round's request. */
  --robot-shift-down-mobile-phone: 70px;
  /* Was 1.15 - bumped up slightly to help the arm's lower end keep
     reaching the cover zone below the hero now that it sits higher
     (see the @media block below) - see the report for why this is an
     estimate, not a measured fix: scaling grows the image from its
     centre, so it also raises the fingertip end a little, same
     trade-off as the scale change two tasks ago. */
  --robot-scale-mobile-phone: 1.2;
  /* Was -128px, moved left by 6px (half the requested ~12px/3% step -
     the full 12px would put it back at -140px, the exact value
     previously flagged as pressed against the left edge, so this round
     only moves it halfway there; --robot-shift-right-mobile (-140px)
     stays as the 700-860px value, untouched). */
  --robot-shift-right-mobile-phone: -134px;
  /* Robot hand width (desktop only); its left/bottom offsets below are
     pre-compensated for this value so shrinking it doesn't drift the
     fingertip. If you change this, the fingertip will drift again. */
  --robot-width: 116%;
  /* Robot hand tilt (desktop and mobile share this angle). Its left/bottom
     offsets below are pre-compensated for this value too, so changing the
     angle will drift the fingertip again. */
  --robot-rotate: -36deg;
  /* Where the robot hand image itself starts/finishes fading to
     transparent, in the image's own unrotated frame - aimed at the
     bottom-left corner, where the wrist/arm is cut off, so it dissolves
     there instead of fading the fingers. */
  --robot-fade-angle: 225deg;
  --robot-fade-start: 67%;
  --robot-fade-end: 72%;

  /* Human hand fine-tuning (independent of the robot variables above).
     Positive --human-shift-left moves it left; positive
     --human-shift-down moves it down. Mobile equivalents are scaled down
     for the smaller mobile hands panel. */
  --human-shift-left: 80px;
  --human-shift-down: 55px;
  --human-shift-left-mobile: 28px;
  --human-shift-down-mobile: 22px;
  /* Phone only (under 700px): was 28px (same as the 700-860px value
     above, which stays untouched) - reduced so right: calc(-16% +
     this) becomes more negative, moving the hand right by 12px (~3%
     of a ~400px-wide phone screen). Vertical position, size and
     rotation are untouched. */
  --human-shift-left-mobile-phone: 16px;

  /* Phone only (under 700px): pulls #the-problem up over the hero by
     this amount via a negative margin-top, applied below, so the panel
     sits higher without touching the shared divider offset, the hero's
     height, or --hero-bottom-space-mobile. Kept below the requested
     40px: the robot's own bottom offset currently leaves it roughly
     16-27px short of where #the-problem's top edge sits (less at
     393px wide, more at 440px), so 40px would have pulled the panel
     past that and into the robot's bounding box by up to ~24px at the
     narrower width. That area is specifically the wrist/arm-cutoff
     corner the image's own fade already targets, not the fingers (the
     opposite end), but since I can't see the render to confirm exactly
     how far the still-visible pixels extend, I used a smaller value as
     instructed. */
  --the-problem-shift-up-mobile-phone: 24px;

  /* Load-animation timing: a gentler, slower settle than the quick
     160ms --ease-out used for hover/click transitions elsewhere, so this
     has its own curve instead of reusing that variable. */
  --hand-anim-duration: 3s;
  --hand-anim-ease: cubic-bezier(0.22, 1, 0.36, 1);

  /* Load-animation start offsets: each hand starts this far from its
     resting position, along the same diagonal its own rotation already
     points it along, then glides to translate(0, 0) - i.e. exactly its
     normal resting transform. Mobile values are smaller. */
  --human-anim-x: 32px;
  --human-anim-y: -17px;
  --human-anim-x-mobile: 14px;
  --human-anim-y-mobile: -7px;
  --robot-anim-x: -28px;
  --robot-anim-y: 21px;
  --robot-anim-x-mobile: -13px;
  --robot-anim-y-mobile: 10px;

  /* Whole-hands-group fine-tuning: applied to .hero__hands itself (both
     hands together) via transform, so neither hand's own size changes.
     --hands-shift-* is a translate in plain screen px; --hands-scale
     shrinks the group from transform-origin: right bottom (so the right/
     bottom bleed edges stay anchored and only the top-left side pulls in),
     which is why the shift values below were increased to compensate. */
  --hands-shift-left: 150px;
  --hands-shift-up: 105px;
  --hands-shift-left-mobile: 30px;
  --hands-shift-up-mobile: 30px;
  --hands-scale: 0.92;
  --hands-scale-mobile: 0.94;

  /* Site-wide dark theme. Black is primary, white is secondary - every
     page picks these up automatically via the variables below. */
  --color-bg: #000000;
  /* page background */
  --color-surface: #0a0a0a;
  /* raised surfaces: cards, dropdown menus, form fields, panels */
  --color-text: #ffffff;
  /* primary text and headings */
  --color-text-muted: #a3a3a3;
  /* secondary text: subtext, descriptions, footer links */
  /* Muted text/small labels (copyright, disclaimer). Spec value #737373
     measures 4.43:1 on black - just under the 4.5:1 minimum - so this
     uses #8a8a8a (6.08:1) instead. */
  --color-text-subtle: #8a8a8a;
  --color-border: #262626;
  /* dividers and borders */
  --color-accent: #ffffff;
  /* focus outlines, and other small accents - buttons use their own glass fill (see .btn) */
  --color-accent-text: #000000;
  /* unused now that .btn sets its own near-black text colour directly */
  --color-error: #f87171;
  /* form validation errors (7.6:1 on black) */

  /* Nav bar fine-tuning - frosted-glass fixed bar, dark tint. */
  --nav-height: 68px;
  --nav-tint: 0.55;
  --nav-blur: 16px;
  --nav-border-color: var(--color-border);

  /* Dropdown panel (.nav__menu) glass - its own values, separate from
     the nav bar's --nav-tint/--nav-blur above, so darkening/blurring
     the panel never changes the nav bar itself. 0.825 = 0.55 x 1.5
     (~50% more opaque); 24px = 16px x 1.5 (~1.5x the blur). */
  --nav-menu-tint: 0.825;
  --nav-menu-blur: 24px;

  /* Footer fine-tuning. */
  --footer-bg: var(--color-bg);
  --footer-border-color: var(--color-border);
  --footer-gap: 2.5rem;
  --footer-padding-y: 4.5rem;
  --footer-heading-color: var(--color-text);
  --footer-link-color: var(--color-text-muted);
  --footer-link-hover-color: var(--color-text);
}

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  font-family: var(--font-sans);
  font-weight: var(--font-weight-regular);
  -webkit-font-smoothing: antialiased;
  background: var(--color-bg);
  color: var(--color-text);
}

h1,
h2,
h3,
h4,
h5,
h6 {
  font-weight: var(--font-weight-heading);
  letter-spacing: var(--font-letter-spacing-heading);
  text-wrap: balance;
}

a {
  color: inherit;
}

/* ---------- Hero ---------- */

.hero {
  position: relative;
  display: grid;
  /* Two stacked rows: the headline (auto-height), then .hero__row (the
     button/logo/paragraph/list content, now 1fr so it still fills the rest
     of the hero's height). The 3-column system that used to live here
     (grid-template-columns/align-items/gap) has moved onto .hero__row
     below - .hero itself no longer has columns of its own, just these two
     rows. .hero__highlights (the three-item list) lives inside .hero__row
     now too, as its 3rd column - .hero has no separate bottom strip any
     more. */
  grid-template-rows: auto 1fr;
  /* GUESS / DESIGN VALUE: space between the headline and .hero__row (the
     logo/button/paragraph row) now that the headline sits above it. */
  --hero-headline-gap: 48px;
  row-gap: var(--hero-headline-gap);
  max-width: var(--content-max-width);
  margin: 0 auto;
  /* svh (where supported) avoids mobile browsers' dynamic toolbar adding
     extra height on top of the vh fallback. */
  min-height: 100vh;
  min-height: 100svh;
  /* Extra top padding clears the fixed nav for hero__text only - the
     hands are a sibling in .hero-wrap, positioned independently, so they
     still reach the top edge and show through the bar as intended. */
  padding: calc(2rem + var(--nav-height)) 2rem 2rem;
  overflow: hidden;
}

/* The old .hero 3-column grid (button | logo | paragraph), relocated here
   unchanged so .hero itself can be just two stacked rows (headline, then
   this). Also now the positioning context for .hero__subhead's absolute
   placement below (replacing .hero in that role), so vertical centering
   there is scoped to just this row's own height - matching the logo's
   centered position - rather than .hero's full (now taller) height. */
.hero__row {
  position: relative;
  display: grid;
  grid-template-columns: 1fr 1fr;
  align-items: center;
  gap: 2rem;
  min-width: 0;
}

.hero__text {
  position: relative;
  z-index: 1;
  text-align: left;
  /* .hero__text is a CSS Grid item (grid defined on .hero__row). Grid items
     default to min-width: auto, meaning they refuse to shrink below
     their content's intrinsic min-content width - and the "See how it
     works" button inside uses white-space: nowrap, so its min-content
     width is its full, unwrapped width. On narrow phone widths that can
     exceed the space .hero__row actually has, forcing this column (and the
     text inside it) wider than the screen, which .hero's own overflow:
     hidden then crops mid-word instead of letting it wrap. Overriding
     back to 0 lets the column shrink to the space it's actually given,
     same as any normal block element. */
  min-width: 0;
}

.hero__eyebrow {
  margin: 0 0 1.1rem;
  font-size: 0.72rem;
  font-weight: var(--font-weight-label);
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--color-text-muted);
}

.hero__headline {
  /* Now a direct grid item of .hero itself (its own row, spanning the full
     width) rather than nested inside .hero__text - moved to the top of the
     hero and centered. margin:0 since spacing to .hero__row below now
     comes entirely from .hero's own row-gap (--hero-headline-gap) instead
     of this element's own margin, so the two don't stack/double up.
     z-index:1 (no position needed - z-index applies to grid items
     regardless of the position property) keeps it painted above the
     starfield (.hero-dots, z-index:0), the same way .hero__text already
     does for the button/paragraph below. */
  margin: 0;
  z-index: 1;
  text-align: center;
  /* GUESS / DESIGN VALUE: scales the existing clamp() by this one factor
     (not new hand-picked clamp bounds). Was 0.85 (chosen to fit one line at
     the old, smaller size); now ~1.5x that, so one line is no longer
     guaranteed at every desktop width - per spec, left to wrap onto two
     centered lines where it doesn't fit, not shrunk to force one line.
     Font weight and colour are the exact same as before, untouched. */
  --hero-headline-top-scale: 1.28;
  font-size: calc(clamp(2rem, 3.4vw, 3.1rem) * var(--hero-headline-top-scale));
  line-height: var(--line-height-headline-lg);
  font-weight: var(--font-weight-headline-lg);
  /* GUESS / DESIGN VALUE: a headline-specific override instead of changing
     the shared --font-letter-spacing-headline-lg (-0.03em), which another
     heading elsewhere on the page also uses - this only tightens this
     headline. letter-spacing is inherited, so .hero__headline-serif
     ("unite.") picks this up too without its own rule; I can't tell
     whether that reads as too tight on the serif italic without rendering
     it, so flagging that rather than guessing. */
  --hero-headline-letter-spacing: -0.02em;
  letter-spacing: var(--hero-headline-letter-spacing);
  text-wrap: balance;
  color: var(--color-text);
}

/* EXPERIMENT: "unite." only, in Instrument Serif Italic - everything else
   (size, colour, baseline) is inherited unchanged from .hero__headline;
   font-style:italic alone (independent of whether the custom font loads)
   is what gives the "serif" generic family fallback its italic too, so a
   failed font load still reads as a normal serif italic, never upright/
   sans, and never affects layout (still inline, still one line at desktop
   width - same text, same size). To remove this experiment: delete this
   rule, delete the @font-face block above it, delete the two font files
   in fonts/ (InstrumentSerif-Italic.woff2,
   InstrumentSerif-LICENSE.txt), and unwrap the <span> back to plain text
   in index.html. */
.hero__headline-serif {
  font-family: "Instrument Serif", serif;
  font-style: italic;
  font-size: 1.08em; /* GUESS / DESIGN VALUE: ~8% larger than the rest of the headline; em so it scales relative to .hero__headline's own font-size. */
}

/* Headline typing effect (built by the script right after the headline in
   index.html). .hero__headline-word carries over .hero__headline-serif's
   class for "unite.", so its italic/size still applies per word. */
.hero__headline-word {
  white-space: nowrap; /* keeps each word together when the headline wraps, per spec. */
}

.hero__headline-letter {
  opacity: 0; /* revealed one at a time by the script; full space is already reserved since the letter is present, just invisible. */
}

/* Visually hidden, but still read normally by screen readers - the
   .hero__headline-type version above is aria-hidden. Standard sr-only
   pattern (kept out of the document's visual flow without display:none,
   which assistive tech would also skip). */
.hero__headline-sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.hero__subhead {
  --hero-subhead-gap: 32px; /* GUESS / DESIGN VALUE: space between the paragraph and the button below it. */
  margin: 0 0 var(--hero-subhead-gap);
  max-width: 440px;
  /* GUESS / DESIGN VALUE: adds a constant amount on top of the original
     clamp(1rem, 1.1vw, 1.1rem) (which still does the same responsive
     scaling as before) rather than hand-picking new clamp bounds. Was 4px;
     dialed back ~1px (partway, not all the way, back toward the original). */
  --hero-subhead-size-increase: 3px;
  font-size: calc(clamp(1rem, 1.1vw, 1.1rem) + var(--hero-subhead-size-increase));
  line-height: 1.6;
  /* Belt-and-suspenders alongside .hero__text's min-width fix above: if
     a single word still can't fit the available width for any reason,
     it wraps onto its own line instead of overflowing past the edge. */
  overflow-wrap: break-word;
  color: #ffffff; /* pure white, one colour for the whole paragraph. */
  opacity: 0; /* fades in with "See how it works" and the right list - see index.html; now universal (every width), matching .hero__highlights below. */
}

/* Forces "less waiting, higher efficiency, more throughput." onto its own
   line after "platform:" - desktop only (see the min-width:861px override
   below); hidden here so tablet/phone get normal, unforced wrapping. */
.hero__subhead-break {
  display: none;
}

/* "See how it works" starts hidden so it can fade in alongside the left
   paragraph and the right list, synced to "Book an Assessment" - see the
   script in index.html. Scoped to this one button only, not the shared
   .btn class used by the nav/footer CTAs elsewhere on the site. */
.hero__text > .btn {
  opacity: 0;
}

/* ---------- Buttons: one shared "liquid glass" pill ---------- */
/* Every button on the site (.btn, always paired with .btn--primary - there
   is no other variant) uses this one look: a bright, nearly-opaque
   silver-white glass fill (opaque enough that the black page never reads
   through as grey), a single thin white rim, a soft white outer glow,
   two gentle diagonal light sweeps (::before) and a thin inner top
   highlight line, all built from gradients/shadows so the glass look
   holds even where backdrop-filter isn't supported - the blur just adds
   to it where it is. Opacity is only ever used on these decorative
   layers, never on the button box itself, so the text stays at full
   strength. .nav__cta/.footer__cta (smaller buttons) only scale the
   glow down further below; they never redefine the look itself. */
.btn {
  /* Liquid-glass tuning, all GUESSES - adjust these, not the properties
     below, to restyle the look. --btn-glass-angle is the direction of
     the base fill AND both diagonal sweep bands in .btn::before.
     --btn-fill-start/-mid/-end are the base glass colour itself - a
     mid-silver dip in the middle, not the near-white it was before,
     so the white sweep bands actually read against it. --btn-sweep-1-*
     is the wide, brighter band (upper-left third); --btn-sweep-2-* is
     the thinner, fainter band (lower-right). --btn-glow-size/-opacity
     is the soft outer box-shadow glow; --btn-edge-opacity is the thin
     bright inset top edge; --btn-inner-glow-opacity is the faint inset
     glow.
     Hover/press/focus, all GUESSES too:
     --btn-sweep-travel is how far .btn::before's gradient slides on
       hover/focus - that layer is 220% of the button's own width (set
       below), so a shift of "45% of the button's width" is expressed
       here as a percentage of the layer's OWN (220%) width:
       45 ÷ 220 × 100 ≈ 20.45%.
     --btn-hover-glow-grow/-boost: how much bigger/brighter the outer
       glow gets on hover or focus.
     --btn-hover-edge-opacity: the top edge highlight, brightened for
       hover/focus (replaces --btn-edge-opacity only in those states).
     --btn-hover-lift: the hover/focus lift (replaces the old
       hardcoded -1.5px).
     --btn-active-darken/-glow-dim: how much darker the fill and
       dimmer the glow get on :active (press). */
  --btn-glass-angle: 115deg;
  --btn-fill-start: #e6e8ee;
  --btn-fill-mid: #aeb3bf;
  --btn-fill-end: #dfe2e9;
  --btn-sweep-1-opacity: 0.75;
  --btn-sweep-2-opacity: 0.4;
  --btn-glow-size: 24px;
  --btn-glow-opacity: 0.18;
  --btn-edge-opacity: 0.7;
  --btn-inner-glow-opacity: 0.2;
  --btn-sweep-travel: 20.45%;
  --btn-hover-glow-grow: 8px;
  --btn-hover-glow-boost: 0.15;
  --btn-hover-edge-opacity: 0.95;
  --btn-hover-lift: -1px;
  --btn-active-darken: 0.94;
  --btn-active-glow-dim: 0.06;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  height: 50px;
  padding: 0 1.7rem;
  border-radius: var(--radius-btn);
  /* Clips .btn::before's sweep bands to this rounded shape - never the
     box-shadow below, which CSS always paints outside overflow:hidden
     regardless. */
  overflow: hidden;
  font-size: 0.95rem;
  font-weight: var(--font-weight-regular);
  color: #0b0d12;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  position: relative;
  border: 1px solid rgba(255, 255, 255, 0.9);
  background: linear-gradient(var(--btn-glass-angle), var(--btn-fill-start) 0%, var(--btn-fill-mid) 50%, var(--btn-fill-end) 100%);
  -webkit-backdrop-filter: blur(14px) saturate(140%);
  backdrop-filter: blur(14px) saturate(140%);
  box-shadow:
    0 0 var(--btn-glow-size) rgba(255, 255, 255, var(--btn-glow-opacity)),
    inset 0 1px 0 rgba(255, 255, 255, var(--btn-edge-opacity)),
    inset 0 0 12px rgba(255, 255, 255, var(--btn-inner-glow-opacity)),
    inset 0 -6px 10px rgba(130, 140, 158, 0.18);
  transition: border-color 200ms ease, box-shadow 250ms ease-out, transform 160ms var(--ease-out), filter 200ms ease;
}

/* Diagonal light sweeps - two soft linear bands at --btn-glass-angle,
   reading as light bending across curved glass rather than hard
   stripes. Negative z-index keeps this between the button's own fill
   (painted first) and its text/icon (plain flow content, painted
   after negative-z children), so it never needs a z-index of its own
   on the label. 220% width (left/right pulled out -60% each) gives
   the slide in/out of frame room to travel without running out of
   gradient at the edges; .btn's own overflow: hidden does the actual
   clipping to the pill shape, so the slide never reveals a gap at
   either edge. */
.btn::before {
  content: "";
  position: absolute;
  top: -8px;
  bottom: -8px;
  left: -60%;
  width: 220%;
  z-index: -1;
  background:
    linear-gradient(var(--btn-glass-angle),
      transparent 0%,
      rgba(255, 255, 255, var(--btn-sweep-1-opacity)) 28%,
      transparent 52%),
    linear-gradient(var(--btn-glass-angle),
      transparent 58%,
      rgba(255, 255, 255, var(--btn-sweep-2-opacity)) 72%,
      transparent 86%);
  transition: transform 600ms ease-out;
  pointer-events: none;
}

.btn--primary {
  /* Kept as the one button variant in use; the look itself lives on .btn. */
}

@media (hover: hover) and (pointer: fine) and (prefers-reduced-motion: no-preference) {
  .btn:hover::before {
    transform: translateX(var(--btn-sweep-travel));
  }
}

@media (prefers-reduced-motion: no-preference) {
  .btn:focus-visible::before {
    transform: translateX(var(--btn-sweep-travel));
  }
}

@media (hover: hover) and (pointer: fine) {
  .btn:hover {
    border-color: rgba(255, 255, 255, 1);
    box-shadow:
      0 0 calc(var(--btn-glow-size) + var(--btn-hover-glow-grow)) rgba(255, 255, 255, calc(var(--btn-glow-opacity) + var(--btn-hover-glow-boost))),
      inset 0 1px 0 rgba(255, 255, 255, var(--btn-hover-edge-opacity)),
      inset 0 0 12px rgba(255, 255, 255, var(--btn-inner-glow-opacity)),
      inset 0 -6px 10px rgba(130, 140, 158, 0.18);
  }
}

@media (hover: hover) and (pointer: fine) and (prefers-reduced-motion: no-preference) {
  .btn:hover {
    transform: translateY(var(--btn-hover-lift));
  }
}

/* :focus-visible gets the same brighter look as :hover (minus the
   hover/pointer gating, since a keyboard user may be on any device)
   plus the focus ring it already had, kept as-is below. */
.btn:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
  border-color: rgba(255, 255, 255, 1);
  box-shadow:
    0 0 calc(var(--btn-glow-size) + var(--btn-hover-glow-grow)) rgba(255, 255, 255, calc(var(--btn-glow-opacity) + var(--btn-hover-glow-boost))),
    inset 0 1px 0 rgba(255, 255, 255, var(--btn-hover-edge-opacity)),
    inset 0 0 12px rgba(255, 255, 255, var(--btn-inner-glow-opacity)),
    inset 0 -6px 10px rgba(130, 140, 158, 0.18);
}

@media (prefers-reduced-motion: no-preference) {
  .btn:focus-visible {
    transform: translateY(var(--btn-hover-lift));
  }
}

/* Press: lift removed, glow dimmed a touch, fill ~6% darker - its own
   faster (0.1s) transition so a click feels immediate/physical rather
   than drifting in at the same pace as hover. */
.btn:active {
  transition: border-color 100ms ease, box-shadow 100ms ease-out, transform 100ms ease-out, filter 100ms ease;
  transform: translateY(0);
  filter: brightness(var(--btn-active-darken));
  box-shadow:
    0 0 var(--btn-glow-size) rgba(255, 255, 255, calc(var(--btn-glow-opacity) - var(--btn-active-glow-dim))),
    inset 0 1px 0 rgba(255, 255, 255, var(--btn-edge-opacity)),
    inset 0 0 12px rgba(255, 255, 255, var(--btn-inner-glow-opacity)),
    inset 0 -6px 10px rgba(130, 140, 158, 0.18);
}

.btn:disabled {
  filter: brightness(0.82) saturate(0.85);
  cursor: not-allowed;
}

@media (hover: hover) and (pointer: fine) {
  .btn:disabled:hover {
    border-color: rgba(255, 255, 255, 0.9);
    box-shadow:
      0 0 10px rgba(255, 255, 255, 0.3),
      inset 0 1px 0 rgba(255, 255, 255, 0.7),
      inset 0 -6px 10px rgba(130, 140, 158, 0.18);
    transform: none;
  }
}

/* Smaller buttons (nav/footer) keep the same look but with the outer
   glow and inner glow scaled down (GUESSES, same as .btn's own) so it
   stays subtle at that size - overriding the variables .btn's box-
   shadow is built from, rather than redeclaring box-shadow itself, so
   any future tuning on .btn stays in sync here automatically. */
.nav__cta,
.footer__cta {
  --btn-glow-size: 12px;
  --btn-glow-opacity: 0.14;
  --btn-inner-glow-opacity: 0.12;
  --btn-hover-glow-grow: 4px;
  box-shadow:
    0 0 var(--btn-glow-size) rgba(255, 255, 255, var(--btn-glow-opacity)),
    inset 0 1px 0 rgba(255, 255, 255, var(--btn-edge-opacity)),
    inset 0 0 8px rgba(255, 255, 255, var(--btn-inner-glow-opacity)),
    inset 0 -4px 7px rgba(130, 140, 158, 0.18);
}

@media (hover: hover) and (pointer: fine) {

  .nav__cta:hover,
  .footer__cta:hover {
    box-shadow:
      0 0 calc(var(--btn-glow-size) + var(--btn-hover-glow-grow)) rgba(255, 255, 255, calc(var(--btn-glow-opacity) + var(--btn-hover-glow-boost))),
      inset 0 1px 0 rgba(255, 255, 255, var(--btn-hover-edge-opacity)),
      inset 0 0 8px rgba(255, 255, 255, var(--btn-inner-glow-opacity)),
      inset 0 -4px 7px rgba(130, 140, 158, 0.18);
  }
}

/* ---------- Hands illustration ---------- */

/* ---------- Homepage logo visual ----------
   All new numerical values in this block are GUESSES. The logo is never
   transformed; only the non-interactive glow layers move. */
.page-home .hero__visual {
  position: relative;
  display: grid;
  place-items: center;
  min-height: 100%;
  isolation: isolate;
  transform: translateY(-30px); /* GUESS: raises the unchanged logo within the existing hero. */
}

.page-home .hero__logo {
  position: relative;
  z-index: 1;
  display: block;
  width: 230px; /* GUESS: stable desktop emblem width in the centre zone. */
  height: auto;
  filter: drop-shadow(0 0 18px rgba(255, 255, 255, 0.16)); /* GUESS */
  overflow: visible; /* EXPERIMENT 1: .hero__logo is now the inlined logo-new.svg root (viewBox
     overridden in index.html to logo.svg's own "286 86 978 880" window, so size/position are
     unchanged) - SVG roots clip to their viewBox by default, so this is what lets the native
     glow-layer's blur bleed past that window instead of being cut off at it. No-op if this
     class is ever back on a plain <img>. */
}

/* logo-new.svg's own native glow-layer (the #glow-layer group inside the inlined SVG
   above - a <use> of the real emblem geometry run through its own SVG
   feGaussianBlur/feMorphology/feBlend filter) serves as the dim, uniform ambient base.
   The moving/cursor-reactive light (.hero__logo-glow below) is layered on top of this,
   so this is intentionally dialed down further than before (0.35 -> ~0.19, roughly 55%)
   to leave room for the moving light to read as the stronger, localized source.
   GUESS / DESIGN VALUE. */
#glow-layer {
  opacity: 0.19;
}

/* The hand-built ambient glow (SVG facet outlines + moving radialGradient, driven by
   hero-logo.js) supplies the moving/localized light on top of the dim native glow
   above. Re-enabled (was temporarily display:none while the native glow was evaluated
   alone). It never had its own positioning rule before (only its inner
   .hero__logo-light did), so as a plain visible block it became a second item in
   .hero__visual's `display: grid; place-items: center`, pushing .hero__logo into its
   own cell and displacing it. Taking it out of grid flow with absolute positioning
   restores .hero__logo as the grid's only in-flow item (its original centered
   placement), while this still overlays the logo exactly as before. */
.page-home .hero__logo-glow {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

/* The moving light itself is drawn by literally re-stroking the logo's own
   vector geometry (the same facet path data extracted from logo.svg: left
   panel, right panel, middle panel, the central interlocking loop, and the
   two back accents), not by masking a raster image or a synthetic circle.
   A radialGradient (#heroEdgeGrad / #heroBloomGrad) supplies the "light":
   its center (cx/cy, in the SVG's own user-space coordinates) is moved every
   frame by hero-logo.js, directly via element.setAttribute - no CSS custom
   properties, no mask-image. Wherever that gradient's bright zone currently
   overlaps one of the real facet outlines, THAT stretch of stroke lights up;
   everywhere else on the stroke falls back to the gradient's faint/
   transparent tail. That's what gives a narrow, localized "edge grazed by a
   light source" look instead of a whole panel or the whole logo going
   bright - the stroke is thin (7px, real screen pixels via
   vector-effect="non-scaling-stroke"), so only a small arc of the true
   silhouette can ever be lit at once.
   The bloom copy (.hero__logo-bloom-paths) re-strokes the same six facet
   outlines, much wider (60px) and run through an SVG <feGaussianBlur>
   filter with a generously oversized filter region (x/y/width/height set
   to -100%/300% of the stroke's own bounds) - that region is what lets the
   blur bleed outward past the true silhouette into the starfield, which a
   CSS mask-image's implicit box previously prevented.
   WHY THE EARLIER VERSIONS WERE WRONG: (1) the very first version masked a
   raster <img> with brightness(5.2)/contrast() - that amplified invisible
   raster/anti-aliasing residue at the image's own rectangular canvas edge
   into a visible rectangle. (2) The version after that masked by the
   logo's full alpha shape - correct silhouette, but the "light" was a ~70px
   circle, large enough relative to a single facet that the ENTIRE facet
   read as "lit" rather than just a graze along its edge - that's the
   "whole panel brightens" problem, not a narrow travelling edge light.
   Re-stroking the actual facet paths (thin line, not filled regions) is
   what makes only a short stretch of true edge light up at a time,
   matching the "narrow bright edge + soft bloom, not whole-shape
   illumination" reference. */
.page-home .hero__logo-light {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible; /* SVG root defaults to clipping at its own box - this is what lets the bloom's blur bleed past the logo. */
}

.page-home .hero__logo-edge-paths,
.page-home .hero__logo-bloom-paths {
  transition: none;
}

/* Click-to-glass: cursor hint, only active once the intro has finished and
   index.html's click-handler script has added this class. */
.page-home .hero__logo.is-clickable {
  cursor: pointer;
}

/* The static glass-image replacement for the logo, revealed by the same
   click-handler script. Sized/positioned to land exactly where .hero__logo
   sits (absolute + inset:0 + margin:auto centers it in the same box that
   .hero__logo's grid place-items:center centers .hero__logo in - same trick
   already used for .hero__logo-glow above). Starts invisible and non-
   interactive; the script crossfades it in on click and is one-way (no
   click-back), so it never needs pointer-events of its own. */
.page-home .hero__logo-glass {
  position: absolute;
  inset: 0;
  margin: auto;
  /* GUESS / DESIGN VALUE: the glass image's own empty margin makes it read
     smaller than the original logo at a 1:1 width match, so this is scaled up
     (~1.2x .hero__logo's 230px) until the three glass panels' outer edges
     roughly match the original logo's footprint. Tune this one value. */
  --hero-logo-glass-width: 288px;
  width: var(--hero-logo-glass-width);
  height: auto;
  /* GUESS / DESIGN VALUE: nudges the glass artwork within its otherwise-
     centered box so its panels line up with the original logo's footprint
     (measured against a screenshot). Negative X = left, negative Y = up. */
  --hero-logo-glass-offset-x: -32px;
  --hero-logo-glass-offset-y: -6px;
  transform: translate(var(--hero-logo-glass-offset-x), var(--hero-logo-glass-offset-y));
  z-index: 1; /* GUESS: matches .hero__logo's z-index so it paints at the same
    stacking level once .hero__logo's own content (the panels) has faded out. */
  opacity: 0;
  pointer-events: none;
  user-select: none;
}

@media (prefers-reduced-motion: reduce) {
  .page-home .hero__logo-bloom-paths,
  .page-home .hero__logo-edge-paths {
    display: none; /* hero-logo.js also bails out entirely under reduced motion; this keeps the static logo clean if the script hasn't run yet. */
  }
}

/* The three-item list, now the 3rd child of .hero__row (sibling of
   .hero__text and .hero__visual) in both markup and layout - not a
   separate bottom strip any more. This base, mobile-first rule is what
   narrow screens use as-is: normal document flow, after the logo and the
   paragraph/button block in .hero__row's single-column mobile stack (see
   its order:3 below), nothing hidden. At >=861px (media query below) it
   becomes the 3rd grid column instead, right-aligned within it and
   vertically centered by .hero__row's own align-items:center - the same
   way the paragraph/button block and the logo are. */
.page-home .hero__highlights {
  opacity: 0; /* fades in with "See how it works" and the left paragraph, synced to "Book an Assessment" - see the script in index.html. */
}

.page-home .hero__highlights-list {
  display: flex;
  flex-direction: column; /* stacked, left-aligned, at every width. */
  gap: 0;
  list-style: none;
  margin: 0;
  padding: 0;
  text-align: left;
}

/* Vertical line down the left of the whole list (items sit directly
   adjacent with no gap, so each one's own border-left lines up into a
   single continuous line) plus a horizontal divider between items -
   matching the look of the earlier right-side list. */
.page-home .hero__highlights-item {
  display: flex;
  align-items: center;
  gap: 0.65rem; /* GUESS / DESIGN VALUE: space between icon and text. */
  padding: 0.65rem 0 0.65rem 1rem; /* GUESS / DESIGN VALUE */
  border-left: 1px solid rgba(255, 255, 255, 0.16); /* GUESS / DESIGN VALUE: quiet vertical line. */
}

.page-home .hero__highlights-item + .hero__highlights-item {
  border-top: 1px solid rgba(255, 255, 255, 0.1); /* GUESS / DESIGN VALUE: quiet divider between items. */
}

.page-home .hero__highlights-icon {
  flex-shrink: 0;
  width: 20px; /* GUESS / DESIGN VALUE: icon size, shared by all three. */
  height: 20px;
  color: var(--color-text);
}

.page-home .hero__highlights-text {
  /* GUESS / DESIGN VALUE: was +2px on top of the original 0.9rem (14.4px);
     dialed back ~1px (partway, not all the way), still keeping it well
     under .hero__subhead's own (larger) size. */
  --hero-highlights-text-size-increase: 1px;
  font-size: calc(0.9rem + var(--hero-highlights-text-size-increase));
  line-height: 1.45;
  color: var(--color-text); /* white - a little brighter than .hero__subhead's muted grey. */
}

@media (min-width: 861px) {
  .page-home .hero {
    padding-left: 32px; /* GUESS / DESIGN VALUE: matches .nav__bar's 32px (2rem) padding so the headline/button/logo all share one left edge. */
    padding-right: 32px; /* GUESS / DESIGN VALUE: matches .nav__bar's 32px (2rem) padding so the right-side content shares one right edge with the nav button. */
  }

  .page-home .hero__row {
    grid-template-columns: minmax(0, 1fr) 230px minmax(0, 1fr); /* GUESS: left, centre emblem, and preserved right zone. */
    gap: 0;
    /* GUESS / DESIGN VALUE: the minimum gap either side block
       (.hero__subhead or .hero__highlights) must keep from the logo
       artwork at any desktop width - read by both of their width formulas
       below. Each side column's own rendered width is (100vw - .hero's
       64px padding - the logo's 230px) / 2 = 50vw - 147px (both side
       columns are equal 1fr tracks); each block's width formula below is
       that column width minus this clearance, so the gap can never drop
       under this number by construction - it isn't just a target, the
       maths guarantees it at every viewport this media query covers. */
    --hero-row-min-clearance: 80px;
    /* GUESS / DESIGN VALUE: .hero__row is a 1fr row inside .hero (unchanged,
       so .hero's own rows/headline position are never touched by this),
       so it stretches to fill .hero's full leftover height and its
       align-items:center then centers its content deep within that - far
       below the headline. This shifts the row's painted content up toward
       the headline without changing .hero__row's own computed size/
       position in .hero's grid (transform never affects layout), so
       nothing about .hero's rows or the headline moves. Measured from the
       ~200px visible headline-to-logo gap: 200 - 170 (the target gap) =
       30. Raise this number to shrink the gap further, lower it to grow
       the gap. */
    --hero-row-lift: 30px;
    transform: translateY(calc(-1 * var(--hero-row-lift)));
  }

  .page-home .hero__visual {
    grid-column: 2;
  }

  /* .hero__row's align-items:center puts .hero__text, .hero__visual and
     .hero__highlights all on the same shared center-line - but
     .hero__visual's own (separate, untouched) transform: translateY(-30px)
     then paints the logo 30px above that shared line, which is why the
     text/list previously read as sitting 30px below the logo's visible
     middle. --hero-side-content-offset is the GUESS / DESIGN VALUE for
     where they should actually sit (12px below the logo's visible middle,
     per spec); --hero-logo-visual-nudge-ref just documents/reads that
     existing -30px value here for the calc below - it does not change
     .hero__visual's own transform. */
  .page-home .hero__text,
  .page-home .hero__highlights {
    --hero-side-content-offset: 12px;
    --hero-logo-visual-nudge-ref: -30px;
    transform: translateY(calc(var(--hero-side-content-offset) + var(--hero-logo-visual-nudge-ref)));
  }

  /* Right column of .hero__row's 3-col grid (column 3, already otherwise
     empty) - vertically centered with the logo by the row's own
     align-items:center, same as the paragraph/button block in column 1.
     justify-self:end hugs the list's right edge against this column's own
     right edge, which is .hero__row's (and .hero's) right edge too - the
     same edge the "Book an Assessment" button's right edge already lines
     up with. */
  .page-home .hero__highlights {
    grid-column: 3;
    justify-self: end;
    /* GUESS / DESIGN VALUE: extra upward nudge for the right list only,
       layered on top of the shared --hero-side-content-offset/
       --hero-logo-visual-nudge-ref transform above (re-declaring the full
       transform here, since a later transform replaces rather than
       combines with an earlier one) - .hero__text and the shared
       vertical-centring value itself are untouched. */
    --hero-highlights-lift: 28px;
    transform: translateY(calc(var(--hero-side-content-offset) + var(--hero-logo-visual-nudge-ref) - var(--hero-highlights-lift)));
  }

  .page-home .hero__highlights-list {
    /* GUESS / DESIGN VALUE: was widened from 300px to 340px to fit the
       larger item text; this step's text-size reduction wasn't paired
       with an explicit new width number, so this dials the ceiling back
       ~halfway too (340 -> 320), matching the "halfway back" framing for
       both blocks. Still responsive (clamp), not fixed: the middle term is
       this column's own rendered width (50vw - 147px) minus
       --hero-row-min-clearance, so it only grows toward this ceiling where
       there's room, and always keeps at least 80px from the logo rather
       than risking an overlap at narrower desktop widths - see
       --hero-row-min-clearance's comment above. justify-self:end (below)
       keeps growing it to the LEFT, right edge unchanged. */
    --hero-highlights-width-max: 320px;
    width: clamp(200px, calc(50vw - 147px - var(--hero-row-min-clearance)), var(--hero-highlights-width-max));
  }

  /* The grey paragraph is back in normal flow here too now (no longer
     pulled out to the right with position:absolute) - it's already
     .hero__text's first child, directly above the button, in the markup.
     This just sets its width; the gap before the button
     (--hero-subhead-gap) and font/colour/line-height are all in the
     unprefixed base rule above, universal at every width. The .hero__text
     {position:static} escape-hatch this used to need (so the paragraph's
     absolute positioning could skip past it to .hero__row) is removed
     along with it - no longer needed now that nothing here is absolutely
     positioned. */
  .page-home .hero__subhead {
    /* GUESS / DESIGN VALUE: was widened from 420px to 460px; dialed back
       halfway (460 -> 440) alongside the ~1px font-size reduction, per
       spec. Responsive (clamp), not fixed - same reasoning as
       .hero__highlights-list above: the middle term is this column's own
       rendered width (50vw - 147px) minus --hero-row-min-clearance, so it
       never comes closer than 80px to the logo even at narrower desktop
       widths, at the cost of the paragraph possibly needing an extra
       wrapped line there instead - the two requirements (80px clearance
       and exactly three lines) can't both hold at every desktop width,
       this keeps the clearance guarantee. */
    --hero-subhead-width-max: 440px;
    width: clamp(200px, calc(50vw - 147px - var(--hero-row-min-clearance)), var(--hero-subhead-width-max));
  }

  .hero__subhead-break {
    display: block;
  }
}

/* Full-bleed wrapper: lets the hands panel reach the true right/top edges
   of the viewport, independent of .hero's centered 1320px max-width, with
   no horizontal scrollbar. Clips horizontally only (overflow-x) - the
   robot arm is now allowed to carry on below the hero's own bottom edge
   (overflow-y: visible) and fade out there instead of being clipped; see
   the mask on .hero__hands below. Since .hero__hands is absolutely
   positioned, it doesn't add to this box's own layout height, so letting
   it overflow downward doesn't push anything below the hero. */
.hero-wrap {
  position: relative;
  width: 100%;
  overflow-x: clip;
  overflow-y: visible;
  /* Hero-only black background (nav and footer keep the light theme).
     Covers the full hero-wrap box, including the breathing-room padding
     below, so the robot arm's mask-fade (see .hero__hand--robot) blends
     into black all the way down, never back into beige. */
  background: radial-gradient(ellipse at bottom, var(--hero-bg-glow) 0%, var(--hero-bg-black) 100%);
  /* Breathing room below the hands panel, before the overflow clip and
     the rounded divider on the next section - .hero__hands below is
     pinned to the hero's own height (not stretched to fill this
     padding), so this space doesn't shift the hand, only how far below
     it the clip sits. Was 7rem (112px); cut to bring the divider closer
     to the hero, per --hero-bottom-space below. */
  padding-bottom: var(--hero-bottom-space);
  --hero-bottom-space: 0px;
  /* Was 0px here (unused - the mobile media query below hardcoded its
     own 3rem instead of reading this variable). Set to match that same
     3rem so switching the mobile rule to use it, below, doesn't change
     the look. */
  --hero-bottom-space-mobile: 3rem;
  /* Phone only (under 700px): 0px, same as desktop's --hero-bottom-space
     above - closes the gap between the hands box and the hero's real
     bottom edge, so the robot's own bottom offset (already set via
     --robot-shift-down-mobile-phone, unchanged here) reaches past the
     hero's edge into the existing fade/cover zone instead of floating
     short of it. Neither hand's position/size variable is touched by
     this - only where the hero's own edge sits. */
  --hero-bottom-space-mobile-phone: 0px;
  /* How far past the hero's bottom edge the robot arm's own mask (below)
     is allowed to keep fading before reaching fully transparent.
     Requested as 220px/150px, but there's only --hero-divider-top-offset
     (48px) of room before the rounded divider line starts on desktop
     (0px --hero-bottom-space + 48px offset), and --hero-divider-top-
     offset plus this wrapper's own mobile padding-bottom (48px) = 96px
     on mobile - reduced both to fit inside that room with a small margin
     to spare, so the arm is always fully faded out before it reaches
     the line. */
  --hero-overhang: 36px;
  --hero-overhang-mobile: 80px;
  /* Fades the robot arm's overhang below the hero (see overflow-y:
     visible above) without changing anything's height/position, which
     would shift both hands. Put here, on .hero-wrap, rather than on
     .hero__hands - a mask the width of the narrower .hero__hands box
     (55% of this one) cut off everything either hand bleeds past that
     narrower box horizontally, which is most of both hands by design.
     .hero-wrap is the full-width box both hands already bleed within
     (overflow-x: clip only trims what's left over), so a mask sized to
     ITS width doesn't cut anything that wasn't already clipped there.
     mask-size is taller than the box by exactly --hero-overhang
     (anchored to the top via mask-position, mask-clip: no-clip so the
     overhanging content isn't clipped to the box to begin with) - so
     within that oversized mask, "100% minus the overhang" always lands
     exactly on this box's own real bottom edge (the hero's bottom
     edge), regardless of the box's actual height. Solid up to that
     point - no fade at all across the whole visible hero - then the
     overhang itself (the last --hero-overhang of the mask) eases
     through six evenly-spaced stops down to fully transparent. */
  -webkit-mask-image: linear-gradient(to bottom,
      #000 0%,
      #000 calc(100% - var(--hero-overhang)),
      rgba(0, 0, 0, 0.85) calc(100% - var(--hero-overhang) * 5 / 6),
      rgba(0, 0, 0, 0.6) calc(100% - var(--hero-overhang) * 4 / 6),
      rgba(0, 0, 0, 0.35) calc(100% - var(--hero-overhang) * 3 / 6),
      rgba(0, 0, 0, 0.15) calc(100% - var(--hero-overhang) * 2 / 6),
      rgba(0, 0, 0, 0.05) calc(100% - var(--hero-overhang) * 1 / 6),
      transparent 100%);
  mask-image: linear-gradient(to bottom,
      #000 0%,
      #000 calc(100% - var(--hero-overhang)),
      rgba(0, 0, 0, 0.85) calc(100% - var(--hero-overhang) * 5 / 6),
      rgba(0, 0, 0, 0.6) calc(100% - var(--hero-overhang) * 4 / 6),
      rgba(0, 0, 0, 0.35) calc(100% - var(--hero-overhang) * 3 / 6),
      rgba(0, 0, 0, 0.15) calc(100% - var(--hero-overhang) * 2 / 6),
      rgba(0, 0, 0, 0.05) calc(100% - var(--hero-overhang) * 1 / 6),
      transparent 100%);
  -webkit-mask-size: 100% calc(100% + var(--hero-overhang));
  mask-size: 100% calc(100% + var(--hero-overhang));
  -webkit-mask-position: top;
  mask-position: top;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-clip: no-clip;
  mask-clip: no-clip;
}

.hero__hands {
  position: absolute;
  top: 0;
  right: -50px;
  height: 100vh;
  height: 100svh;
  width: 55%;
  /* Pure visual shift + scale of the whole group - neither affects the
     hands' own widths (set as a percentage of this container's layout
     box, which transform never touches). Scale pivots from right/bottom
     so that corner (where the bleed lives) stays anchored; the translate
     then pulls the now-smaller group back left/up into position. */
  transform-origin: right bottom;
  transform: translate(calc(-1 * var(--hands-shift-left)), calc(-1 * var(--hands-shift-up))) scale(var(--hands-scale));
}

.hero__hand {
  position: absolute;
  height: auto;
  display: block;
  filter: drop-shadow(0 18px 30px rgba(21, 23, 26, 0.12));
}

/* Base left/bottom here (-32.7%/22.9%) are re-aimed for --robot-width
   AND --robot-rotate together: the fingertip stayed put through both the
   113%->106% shrink and the -32deg->-36deg tilt. Change those two
   variables above, not these two numbers, unless you want the fingertip
   to drift. */
.hero__hand--robot {
  width: var(--robot-width);
  left: calc(-32.7% + var(--robot-shift-right));
  bottom: calc(22.9% - var(--robot-shift-down));
  transform: rotate(var(--robot-rotate));
  transform-origin: center;
  /* Fades the image itself (not an overlay), diagonally toward the
     bottom-left corner of its own unrotated frame, where the cut-off
     wrist/arm sits - so the arm's tail dissolves there, not the fingers.
     Six evenly-spaced stops between --robot-fade-start and
     --robot-fade-end (100/85/60/35/15/5/0%) ease it out smoothly over
     that whole distance, instead of a flat two-stop fade with a visible
     start band - both stay in terms of the two variables, so tuning
     either one keeps every stop evenly spaced between them. Shared by
     mobile too: this is the only mask rule, nothing overrides it below. */
  --robot-fade-mid1: calc(var(--robot-fade-start) + (var(--robot-fade-end) - var(--robot-fade-start)) * 1 / 6);
  --robot-fade-mid2: calc(var(--robot-fade-start) + (var(--robot-fade-end) - var(--robot-fade-start)) * 2 / 6);
  --robot-fade-mid3: calc(var(--robot-fade-start) + (var(--robot-fade-end) - var(--robot-fade-start)) * 3 / 6);
  --robot-fade-mid4: calc(var(--robot-fade-start) + (var(--robot-fade-end) - var(--robot-fade-start)) * 4 / 6);
  --robot-fade-mid5: calc(var(--robot-fade-start) + (var(--robot-fade-end) - var(--robot-fade-start)) * 5 / 6);
  -webkit-mask-image: linear-gradient(var(--robot-fade-angle),
      #000 var(--robot-fade-start),
      rgba(0, 0, 0, 0.85) var(--robot-fade-mid1),
      rgba(0, 0, 0, 0.6) var(--robot-fade-mid2),
      rgba(0, 0, 0, 0.35) var(--robot-fade-mid3),
      rgba(0, 0, 0, 0.15) var(--robot-fade-mid4),
      rgba(0, 0, 0, 0.05) var(--robot-fade-mid5),
      transparent var(--robot-fade-end));
  mask-image: linear-gradient(var(--robot-fade-angle),
      #000 var(--robot-fade-start),
      rgba(0, 0, 0, 0.85) var(--robot-fade-mid1),
      rgba(0, 0, 0, 0.6) var(--robot-fade-mid2),
      rgba(0, 0, 0, 0.35) var(--robot-fade-mid3),
      rgba(0, 0, 0, 0.15) var(--robot-fade-mid4),
      rgba(0, 0, 0, 0.05) var(--robot-fade-mid5),
      transparent var(--robot-fade-end));
  animation: heroHandRobotIn var(--hand-anim-duration) var(--hand-anim-ease) both;
}

.hero__hand--human {
  width: 113%;
  right: calc(-50% + var(--human-shift-left));
  top: calc(18% + var(--human-shift-down));
  transform: rotate(-28deg);
  transform-origin: center;
  animation: heroHandHumanIn var(--hand-anim-duration) var(--hand-anim-ease) both;
}

/* Load-in animation: each hand starts offset along its own tilt (the
   rotate() angle is kept constant throughout, never reset) and glides to
   translate(0, 0) - its normal resting transform above - while fading in.
   Plays once on page load, not tied to scroll. */
@keyframes heroHandHumanIn {
  0% {
    opacity: 0;
    transform: translate(var(--human-anim-x), var(--human-anim-y)) rotate(-28deg);
  }

  30% {
    opacity: 1;
  }

  100% {
    opacity: 1;
    transform: translate(0, 0) rotate(-28deg);
  }
}

@keyframes heroHandRobotIn {
  0% {
    opacity: 0;
    transform: translate(var(--robot-anim-x), var(--robot-anim-y)) rotate(var(--robot-rotate));
  }

  30% {
    opacity: 1;
  }

  100% {
    opacity: 1;
    transform: translate(0, 0) rotate(var(--robot-rotate));
  }
}

@media (prefers-reduced-motion: reduce) {

  .hero__hand--human,
  .hero__hand--robot {
    animation: none;
  }
}

@media (max-width: 860px) {
  :root {
    --human-anim-x: var(--human-anim-x-mobile);
    --human-anim-y: var(--human-anim-y-mobile);
    --robot-anim-x: var(--robot-anim-x-mobile);
    --robot-anim-y: var(--robot-anim-y-mobile);
  }

  .hero {
    /* grid-template-columns removed here: .hero is row-based now (headline,
       then .hero__row), the old column-collapse for the button/logo/
       paragraph content lives on .hero__row below instead. */
    padding: calc(1.5rem + var(--nav-height)) 1.5rem 1.5rem;
    /* Re-valued (same --hero-headline-gap custom property) - this also
       governs the .hero__row-to-.hero__highlights gap (.hero's other
       row-gap), untouched here.
       GUESS / DESIGN VALUE: this is a row-gap, not the actual visible
       gap - .page-home .hero__visual (the logo's container, unconditional,
       not scoped to desktop) carries its own transform: translateY(-30px),
       which also applies at this width and paints the logo 30px above
       where this row-gap alone would place it. At the previous 1.5rem
       (24px) row-gap, that meant a visible gap of 24 - 30 = -6px - the
       logo was actually overlapping the headline slightly. Set to 70px so
       the VISIBLE gap comes out to the requested ~40px (70 - 30 = 40),
       not literally ~2.5rem/40px as a row-gap. */
    --hero-headline-gap: 70px;
    min-height: 0;
  }

  .hero__row {
    grid-template-columns: 1fr;
    gap: 1.5rem; /* same value .hero's own mobile gap used to be, now scoped to just this row's internal button/logo stack. */
  }

  /* Order at this width: headline (already first, outside .hero__row),
     then the logo, then the paragraph+button block - .hero__visual comes
     after .hero__text in the markup, so this reorders them visually
     without touching the DOM. */
  .hero__visual {
    order: 1;
  }

  .hero__text {
    order: 2;
    text-align: left;
    padding-top: 1rem;
  }

  .hero__highlights {
    order: 3; /* after the paragraph+button block, per spec. */
  }

  .hero__subhead {
    max-width: none;
  }

  .hero__hands {
    position: relative;
    width: 100%;
    height: auto;
    min-height: 320px;
    aspect-ratio: 1 / 1;
    transform-origin: right bottom;
    transform: translate(calc(-1 * var(--hands-shift-left-mobile)), calc(-1 * var(--hands-shift-up-mobile))) scale(var(--hands-scale-mobile));
  }

  .hero__hand {
    width: 90%;
  }

  .hero__hand--robot {
    left: calc(-8% + var(--robot-shift-right-mobile));
    bottom: calc(22% - var(--robot-shift-down-mobile));
  }

  .hero-wrap {
    padding-bottom: var(--hero-bottom-space-mobile);
    --hero-overhang: var(--hero-overhang-mobile);
  }

  .hero__hand--human {
    right: calc(-16% + var(--human-shift-left-mobile));
    top: calc(16% + var(--human-shift-down-mobile));
  }
}

@media (max-width: 480px) {
  /* Clips anything the hand images push past their own container's
     right edge at this width, without touching how they're positioned
     or sized - the wrapper that directly holds the two <img> elements,
     not .hero-wrap (which deliberately still allows vertical overflow
     below, for the robot arm's fade - untouched here). */
  .hero__hands {
    overflow-x: clip;
  }

  /* Safety net: whatever the cause, the page itself must never scroll
     sideways at phone widths. */
  html,
  body {
    overflow-x: clip;
  }
}

/* Phone only - everything from 700px up to 860px keeps the values from
   the block above unchanged; only this narrower range raises and
   enlarges the robot hand. Horizontal position (--robot-shift-right-
   mobile) and rotation (--robot-rotate) are untouched, so it stays on
   the left at the same angle - only how high it sits and how big it is
   change. */
@media (max-width: 700px) {
  :root {
    --robot-shift-down-mobile: var(--robot-shift-down-mobile-phone);
    --robot-shift-right-mobile: var(--robot-shift-right-mobile-phone);
    --hero-bottom-space-mobile: var(--hero-bottom-space-mobile-phone);
    --human-shift-left-mobile: var(--human-shift-left-mobile-phone);
  }

  .hero__hand--robot {
    transform: rotate(var(--robot-rotate)) scale(var(--robot-scale-mobile-phone));
  }

  /* Pulls the panel up over the hero's own black background by
     --the-problem-shift-up-mobile-phone (see the variable above).
     #the-problem already has z-index: 1 (from .section--hero-divider,
     untouched here), so it keeps painting over the hero regardless of
     this margin - this only changes where its box starts, not its
     stacking. The hero itself, the hands, and the shared divider
     offset are untouched by this rule. */
  #the-problem {
    margin-top: calc(-1 * var(--the-problem-shift-up-mobile-phone));
  }
}

/* ============================================================
   HERO DOTS BACKGROUND
   Decorative, non-interactive dots layer behind the hero content on
   index.html only. Dot counts, parallax strength/max/smoothing and the
   minimum-distance retry limit are JS-only numbers - see the SETTINGS
   block at the top of hero-dots.js. Everything that's expressible in CSS
   (sizes, opacities, loop durations, fade-in duration) is a custom
   property right here so it can be tuned without touching the rules
   below.
   ============================================================ */
:root {
  /* Hero-only background: a bottom-anchored glow fading to black. Scoped
     to the hero (via .hero-wrap's background below), not the global
     --color-bg, so the rest of the site stays on its light theme. */
  --hero-bg-glow: #0b0b0b;
  /* colour at the bottom edge of the radial gradient - set the glow value to #000000 for a completely flat black sky */
  --hero-bg-black: #000000;
  /* colour the gradient fades to */

  --hero-dots-color: #ffffff;
  /* dot colour on the hero's black background - independent of the site's (dark) --color-text */

  --hero-dots-size-small: 1px;
  /* layer 1 dot diameter */
  --hero-dots-size-medium: 2px;
  /* layer 2 dot diameter */
  --hero-dots-size-large: 3px;
  /* layer 3 dot diameter */

  --hero-dots-opacity-small: 0.6;
  /* layer 1 dot opacity */
  --hero-dots-opacity-medium: 0.5;
  /* layer 2 dot opacity */
  --hero-dots-opacity-large: 0.4;
  /* layer 3 dot opacity */

  --hero-dots-duration-small: 60s;
  /* layer 1 upward drift loop duration */
  --hero-dots-duration-medium: 100s;
  /* layer 2 upward drift loop duration */
  --hero-dots-duration-large: 150s;
  /* layer 3 upward drift loop duration */

  --hero-dots-fade-in-duration: 1.2s;
  /* opacity fade-in duration on page load */
}

/* Fills the hero's own box (matches .hero__hand's top+height technique
   above, not hero-wrap's bottom:0, so it never stretches into the
   breathing-room padding added below the hero for the hand fade). Sits
   behind everything else in .hero-wrap because it's inserted as the
   first child there and given the lowest explicit z-index - the hero
   section's own content (including .hero__hands) keeps painting after
   it with no other CSS changes needed. */
.hero-dots {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 100vh;
  height: 100svh;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
  opacity: 0;
  transition: opacity var(--hero-dots-fade-in-duration) linear;
}

.hero-dots--visible {
  opacity: 1;
}

/* Dedicated element for the JS mouse-parallax shift, kept separate from
   the drifting layers below so the two transforms never fight on the
   same element. */
.hero-dots__parallax {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
}

/* Each layer holds two stacked copies of the same dot pattern (.hero-dots__pattern--a
   at top:0, --b at top:100%) and drifts upward by exactly one pattern's
   height (translateY(-100%) of its own, pattern-height-tall box), so the
   loop never visibly jumps, gaps or resets. */
.hero-dots__layer {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  width: 100%;
  height: 100%;
  animation-name: heroDotsDrift;
  animation-timing-function: linear;
  animation-iteration-count: infinite;
}

.hero-dots__layer--small {
  animation-duration: var(--hero-dots-duration-small);
}

.hero-dots__layer--medium {
  animation-duration: var(--hero-dots-duration-medium);
}

.hero-dots__layer--large {
  animation-duration: var(--hero-dots-duration-large);
}

@keyframes heroDotsDrift {
  from {
    transform: translateY(0);
  }

  to {
    transform: translateY(-100%);
  }
}

.hero-dots__pattern {
  position: absolute;
  left: 0;
  width: 100%;
  height: 100%;
}

.hero-dots__pattern--a {
  top: 0;
}

.hero-dots__pattern--b {
  top: 100%;
}

.hero-dots__dot {
  position: absolute;
  border-radius: 50%;
  background: var(--hero-dots-color);
  /* Dot left/top are percentages of the pattern box (set inline by JS);
     this centers the dot on that point instead of the point being its
     corner. */
  transform: translate(-50%, -50%);
}

.hero-dots__layer--small .hero-dots__dot {
  width: var(--hero-dots-size-small);
  height: var(--hero-dots-size-small);
  opacity: var(--hero-dots-opacity-small);
}

.hero-dots__layer--medium .hero-dots__dot {
  width: var(--hero-dots-size-medium);
  height: var(--hero-dots-size-medium);
  opacity: var(--hero-dots-opacity-medium);
}

.hero-dots__layer--large .hero-dots__dot {
  width: var(--hero-dots-size-large);
  height: var(--hero-dots-size-large);
  opacity: var(--hero-dots-opacity-large);
}

/* Toggled by JS when the tab is hidden or the hero scrolls out of view. */
.hero-dots--paused .hero-dots__layer {
  animation-play-state: paused;
}

@media (prefers-reduced-motion: reduce) {
  .hero-dots {
    transition: none;
    opacity: 1;
  }

  .hero-dots__layer {
    animation: none;
  }
}

/* ---------- Navigation ---------- */

.nav {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 100;
  height: var(--nav-height);
  /* Frosted glass: semi-transparent black so content scrolling underneath
     (the hero, hand images, page sections) shows through blurred and
     darkened rather than a solid cut-off. .nav__menu (the dropdown
     panels) has its own, separate --nav-menu-tint/--nav-menu-blur
     glass values (darker/more blurred than this bar's, by request) -
     it's still a DOM descendant of this element, which also has
     backdrop-filter. Nested backdrop-filter like this has a real,
     inconsistently-implemented history across browsers (some render
     it correctly against the true page content behind both layers;
     others reportedly only let the inner one see this element's own
     backdrop). Not changed here: doing so reliably would mean moving
     .nav__menu out of .nav in the DOM, which was flagged as
     restructuring rather than done unasked. */
  background: rgba(0, 0, 0, var(--nav-tint));
  -webkit-backdrop-filter: blur(var(--nav-blur)) saturate(140%);
  backdrop-filter: blur(var(--nav-blur)) saturate(140%);
  border-bottom: 1px solid var(--nav-border-color);
}

.nav__bar {
  max-width: var(--content-max-width);
  height: 100%;
  margin: 0 auto;
  padding: 0 2rem;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.5rem;
}

.nav__wordmark {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  font-weight: var(--font-weight-heading);
  font-size: 1.1rem;
  letter-spacing: normal;
  text-transform: uppercase;
  color: var(--color-text);
  text-decoration: none;
  white-space: nowrap;
}

/* The logo symbol beside the nav/footer wordmark text - sized to a
   fixed height so it never changes the 68px nav bar height or pushes
   other nav items, with width left to auto so the image's own
   aspect ratio (set via its width/height attributes) is never
   stretched. Reuses the wordmark selectors themselves rather than a
   new class, since this is the only place either logo appears. */
.nav__wordmark img,
.footer__wordmark img,
.nav__wordmark svg {
  height: 24px;
  width: auto;
  display: block;
}

.nav__links {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 1.5rem;
  flex: 1;
  margin: 0;
  padding: 0;
}

.nav__link {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  font: inherit;
  font-size: 0.875rem;
  font-weight: var(--font-weight-regular);
  color: var(--color-text);
  text-decoration: none;
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
}

/* Chevron (two strokes forming a "v", no stem) replacing the old
   Unicode-triangle "dot" marker - an inline SVG, stroke="currentColor"
   so it always matches the surrounding text colour with no extra
   colour rule needed. Sizing/stroke width live on the <svg> itself in
   the markup, not here. */
.nav__chevron {
  display: inline-flex;
  align-items: center;
  line-height: 1;
  transition: transform 0.2s var(--ease-out);
}

.nav__dropdown {
  position: relative;
}

.nav__dropdown>summary {
  list-style: none;
}

.nav__dropdown>summary::-webkit-details-marker {
  display: none;
}

.nav__dropdown[open]>summary .nav__chevron {
  transform: rotate(180deg);
}

/* Overrides <details>'s own UA-stylesheet display:none on non-open
   content (ordinary specificity, so this - one class - wins): the
   panel is always laid out, and opacity/transform/visibility do the
   showing/hiding instead, so both can be transitioned. visibility
   switches to hidden only after the closing fade/slide finishes (its
   own 0s-duration transition carries the matching delay), so links
   can't be clicked or tabbed into while invisible, but become
   interactive again the instant the panel starts opening. Closing is
   quicker (0.15s) than opening (0.2s, ease-out) - two states, so one
   offset (6px) serves both directions; "about 4px" on the way back
   isn't distinguished from that without extra JS/keyframes. */
/* Glass: darker/more blurred than the nav bar's own glass on purpose -
   --nav-menu-tint/--nav-menu-blur (:root), separate from the nav bar's
   own --nav-tint/--nav-blur, so tuning the panel never touches the nav
   bar itself. Border colour was already var(--color-border), same
   variable the nav's own border uses, so no change needed there. See
   the known nested-backdrop-filter caveat noted where .nav itself is
   defined. */
.nav__menu {
  position: absolute;
  top: calc(100% + 0.9rem);
  left: 50%;
  min-width: 170px;
  margin: 0;
  background: rgba(0, 0, 0, var(--nav-menu-tint));
  -webkit-backdrop-filter: blur(var(--nav-menu-blur)) saturate(140%);
  backdrop-filter: blur(var(--nav-menu-blur)) saturate(140%);
  border: 1px solid var(--color-border);
  border-radius: 12px;
  padding: 0.4rem;
  box-shadow: 0 16px 40px rgba(21, 23, 26, 0.12);
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  opacity: 0;
  visibility: hidden;
  transform: translateX(-50%) translateY(-6px);
  transition: opacity 0.15s ease, transform 0.15s ease, visibility 0s linear 0.15s;
}

.nav__dropdown[open]>.nav__menu {
  opacity: 1;
  visibility: visible;
  transform: translateX(-50%) translateY(0);
  transition: opacity 0.2s var(--ease-out), transform 0.2s var(--ease-out), visibility 0s linear 0s;
}

.nav__menu a {
  display: block;
  padding: 0.55rem 0.75rem;
  border-radius: 8px;
  font-size: 0.92rem;
  color: var(--color-text);
  text-decoration: none;
  white-space: nowrap;
}

@media (hover: hover) and (pointer: fine) {
  .nav__menu a:hover {
    background: var(--color-bg);
  }
}

/* Small non-clickable heading inside a .nav__menu, for a labelled group
   of links below the dropdown's plain items (e.g. "Free Tools" under
   Resources' Blog/Safety & Trust). Reuses the menu item's own padding
   so it lines up with the links around it, plus a divider above and a
   muted, smaller label style so it doesn't read as another link. */
.nav__menu-label {
  margin: 0.3rem 0 0;
  padding: 0.5rem 0.75rem 0.3rem;
  border-top: 1px solid var(--color-border);
  font-size: 0.75rem;
  font-weight: var(--font-weight-label);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--color-text-muted);
}

.nav__actions {
  display: flex;
  align-items: center;
  gap: 1rem;
}

.nav__cta {
  height: 40px;
  padding: 0 1.15rem;
  font-size: 0.875rem;
}

.nav__hamburger {
  display: none;
  flex-direction: column;
  justify-content: center;
  align-items: stretch;
  gap: 4px;
  width: 36px;
  height: 36px;
  border: none;
  background: none;
  cursor: pointer;
  padding: 0;
  flex-shrink: 0;
}

.nav__hamburger span {
  display: block;
  height: 2px;
  width: 100%;
  background: var(--color-text);
  border-radius: 2px;
  transition: transform 160ms var(--ease-out), opacity 160ms ease;
}

.nav[data-menu-open="true"] .nav__hamburger span:nth-child(1) {
  transform: translateY(6px) rotate(45deg);
}

.nav[data-menu-open="true"] .nav__hamburger span:nth-child(2) {
  opacity: 0;
}

.nav[data-menu-open="true"] .nav__hamburger span:nth-child(3) {
  transform: translateY(-6px) rotate(-45deg);
}

@media (max-width: 860px) {
  .nav__bar {
    padding: 0 1.25rem;
  }

  .nav__hamburger {
    display: flex;
  }

  .nav__links {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    flex-direction: column;
    align-items: stretch;
    justify-content: flex-start;
    gap: 0;
    background: var(--color-surface);
    border-bottom: 1px solid var(--color-border);
    padding: 0 1.25rem;
    max-height: 0;
    overflow: hidden;
    opacity: 0;
    transition: max-height 220ms var(--ease-out), opacity 180ms ease;
  }

  .nav[data-menu-open="true"] .nav__links {
    max-height: 70vh;
    opacity: 1;
    overflow-y: auto;
    padding: 0.5rem 1.25rem 1rem;
  }

  .nav__link {
    width: 100%;
    padding: 0.85rem 0;
    border-bottom: 1px solid var(--color-border);
    justify-content: space-between;
  }

  .nav__dropdown {
    width: 100%;
  }

  .nav__dropdown>summary.nav__link {
    border-bottom: 1px solid var(--color-border);
  }

  /* Mobile: in flow (position: static), so the fade+slide above would
     just make it pop in at full height - animate height instead.
     Opacity/visibility are reset to always-shown here since max-height:
     0 + overflow: hidden already fully hides it when closed; the
     200px ceiling is comfortably past every dropdown's real content
     height today (a handful of short links), not a measured exact
     value - same caveat as the max-height technique .nav__links itself
     already uses for the hamburger panel. */
  .nav__menu {
    position: static;
    transform: none;
    width: 100%;
    box-shadow: none;
    border: none;
    background: transparent;
    /* No glass on mobile: this is now a plain, flattened, full-width
       list item (no floating panel edges to frame), so the blur would
       just smear whatever's behind that strip of the page for no
       reason. */
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
    padding: 0 0 0 0.75rem;
    opacity: 1;
    visibility: visible;
    max-height: 0;
    overflow: hidden;
    transition: max-height 0.15s ease, padding 0.15s ease;
  }

  .nav__dropdown[open]>.nav__menu {
    max-height: 200px;
    padding: 0.25rem 0 0.5rem 0.75rem;
    transition: max-height 0.2s var(--ease-out), padding 0.2s var(--ease-out);
  }
}

/* Reduced motion: instant open/close everywhere above - the chevron's
   rotation and both the desktop (opacity/transform) and mobile
   (max-height/padding) dropdown transitions. */
@media (prefers-reduced-motion: reduce) {
  .nav__chevron,
  .nav__menu,
  .nav__dropdown[open]>.nav__menu {
    transition: none !important;
  }
}

/* Phone only: the wordmark plus the "Book an Assessment" button and
   hamburger weren't fitting on one line within the ≤860px block's
   padding. Shrinks the logo/wordmark text/button to fixed small sizes
   and lets every nav__bar flex item shrink (min-width: 0, overriding
   the flex/grid default that otherwise floors an item at its content's
   full width) instead of relying on nowrap text to hold its full size
   - so the hamburger can never be pushed off-screen. 16px nav__bar
   padding either side, hamburger kept at a 40px tap target throughout. */
@media (max-width: 480px) {
  .nav__bar {
    padding: 0 16px;
    /* Was space-between: fine normally, but at this width it just eats
       whatever room is left between the two groups instead of holding
       a steady gap. flex-start + an explicit gap keeps the logo group
       and the button/hamburger pair a consistent distance apart, and
       clamp() scales that gap with the screen instead of one fixed
       size for every phone from 320 to 480px. */
    justify-content: flex-start;
    gap: clamp(16px, 5vw, 24px);
  }

  .nav__wordmark {
    min-width: 0;
    flex-shrink: 1;
    font-size: clamp(12px, 3.4vw, 15px);
  }

  .nav__wordmark img,
  .nav__wordmark svg {
    height: clamp(11.4px, 3.14vw, 14.8px);
    width: auto;
  }

  .nav__actions {
    min-width: 0;
    flex-shrink: 1;
    gap: 8px;
  }

  .nav__cta {
    min-width: 0;
    flex-shrink: 1;
    height: auto;
    font-size: clamp(11px, 3vw, 13px);
    padding: clamp(6px, 2vw, 8px) clamp(8px, 2.6vw, 12px);
    white-space: nowrap;
  }

  .nav__hamburger {
    flex-shrink: 0;
    width: 40px;
    height: 40px;
  }
}

/* 393px and below (iPhone 16 and narrower): fixed values instead of the
   clamp() scaling above, which this overrides. 16px side padding and
   the 40px hamburger tap target carry over unchanged from the ≤480px
   block above. */
@media (max-width: 393px) {
  .nav__bar {
    gap: 24px;
  }

  .nav__wordmark {
    font-size: 15px;
  }

  .nav__wordmark img,
  .nav__wordmark svg {
    height: 13.7px;
  }

  .nav__cta {
    font-size: 13px;
    padding: 8px 14px;
  }
}

/* Smallest phones: drop just the "Interoper" word (the logo mark
   stays) to free up the last bit of room - the button keeps its full
   label throughout, this never shortens it. .nav__wordmark-text is the
   span nav.js wraps the word in for the intro animation's text wipe;
   reused here as the hook to hide it, nothing else changes. */
@media (max-width: 340px) {
  .nav__wordmark-text {
    display: none;
  }
}

/* ---------- Placeholder pages ---------- */

.placeholder {
  min-height: 100vh;
  max-width: 640px;
  margin: 0 auto;
  padding: calc(2rem + var(--nav-height)) 2rem 4rem;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: center;
  text-align: left;
}

.placeholder__eyebrow {
  margin: 0 0 1rem;
  font-size: 0.72rem;
  font-weight: var(--font-weight-label);
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--color-text-muted);
}

.placeholder__title {
  margin: 0 0 1rem;
  font-size: clamp(1.75rem, 3vw, 2.5rem);
  font-weight: var(--font-weight-heading);
  color: var(--color-text);
}

.placeholder__text {
  margin: 0 0 2rem;
  max-width: 480px;
  font-size: 1.05rem;
  line-height: 1.6;
  color: var(--color-text-muted);
}

/* ---------- Demo request form ---------- */

.demo-form {
  width: 100%;
  max-width: 420px;
  margin: 0 0 1rem;
}

.demo-form__honeypot {
  position: absolute;
  left: -9999px;
}

.form-field {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  margin-bottom: 1.1rem;
}

.form-field label {
  font-size: 0.85rem;
  font-weight: var(--font-weight-regular);
  color: var(--color-text);
}

.form-field input,
.form-field select,
.form-field textarea {
  font: inherit;
  padding: 0.75rem 0.9rem;
  border-radius: 10px;
  border: 1px solid var(--color-border);
  background: var(--color-surface);
  color: var(--color-text);
}

.form-field input:focus,
.form-field select:focus,
.form-field textarea:focus {
  outline: 2px solid var(--color-accent);
  outline-offset: 1px;
}

.form-field textarea {
  min-height: 120px;
  resize: vertical;
}

.form-field__error {
  display: none;
  margin: 0;
  font-size: 0.8rem;
  color: var(--color-error);
}

.form-field.has-error input,
.form-field.has-error select {
  border-color: var(--color-error);
}

.form-field.has-error .form-field__error {
  display: block;
}

.demo-form__thanks {
  display: none;
  max-width: 420px;
  padding: 1rem 1.1rem;
  border-radius: 10px;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  color: var(--color-text);
}

.demo-form__thanks[data-visible="true"] {
  display: block;
}

/* ---------- Homepage sections ---------- */

.section {
  padding: 6rem 2rem;
  /* So a fixed-nav anchor jump (Platform, Safety & Trust, and the
     use-case cards below) doesn't tuck the heading under the nav bar. */
  scroll-margin-top: var(--nav-height);
}

.section--surface {
  background: var(--color-surface);
  border-top: 1px solid var(--color-border);
  border-bottom: 1px solid var(--color-border);
}

/* Homepage only: .section--surface is shared with platform.html (and its
   border is the only divider line between homepage sections - .section
   itself carries no border, and nothing else draws a line here), so the
   removal below is scoped to body.page-home rather than touching the
   shared rule above - platform.html keeps its own dividers exactly as
   they are. Doesn't touch .section--hero-divider (the rounded divider
   between the hero and #the-problem), which isn't .section--surface and
   has no border-top/bottom of its own to begin with. */
body.page-home .section--surface {
  border-top: none;
  border-bottom: none;
}

/* Hero -> first-section divider (index.html only, on #the-problem). There
   was no actual line here before - .hero-wrap's own radial-gradient
   background (it fades from the faint --hero-bg-glow near the bottom to
   flat --hero-bg-black) simply meets this section's plain, transparent
   background (inheriting body's flat --color-bg, the same black), so
   there was never a drawn border/divider to remove, just this seam with
   nothing marking it. A previous pass at this added a separate flat
   full-width line plus two separate corner pieces; both are gone,
   replaced below with one continuous outline.

   Built from two layers, both purely decorative (pointer-events: none):
   the outline (::before) and the glow (::after), both on the section
   itself - .section__inner and its content are never touched, so they
   never move. The outline is ONE pseudo-element: a 1px border with a
   large radius on its top two corners only and no bottom border, so the
   curve is a real border corner, not two things touching - then a
   single radial-gradient mask (not two separate gradients needing to be
   composited together) fades its brightness by straight-line distance
   from top-centre, which happens to vary it exactly how B/C/D below
   ask: along the flat top (where that distance is purely horizontal)
   centre-to-corner, and down each side (where the distance keeps
   growing past the corner) corner-to-nothing. Tune the variables below
   first. */
:root {
  --hero-divider-inset: clamp(12px, 2vw, 40px);
  --hero-divider-inset-mobile: 11px;
  --hero-divider-radius: 24px;
  --hero-divider-radius-mobile: 10.67px;
  --hero-divider-top-offset: 48px;
  /* pushes the whole outline down from the section's own top edge, clear of the hero */
  --hero-divider-line-centre-opacity: 0.5;
  /* outline at top-centre, its brightest point */
  --hero-divider-line-corner-opacity: 0.13;
  /* outline exactly at each top corner */
  --hero-divider-side-fade-length: 26px;
  /* how far past the corner the outline keeps fading, then stops entirely */
  --hero-divider-glow-opacity: 0.08;
  --hero-divider-glow-width: min(55%, 760px);
  --hero-divider-glow-height: 150px;

  /* Shared "edge glow" box treatment (.edge-glow below): same top-edge
     technique as the hero divider above (radial-gradient mask fading
     brightness by distance from top-centre, plus a soft glow just under
     it, plus side lines continuing down from the same top corners),
     reusing its values as starting points but kept in their own
     variables, in one shared rule, so every box that uses it (the
     Problem/Use-cases cards, the hub diagram panel, the FAQ rows, the
     booking form card) can be tuned from here at once. */
  --edge-radius: 16px;
  /* matches most boxes' own border-radius (16px) - overridden per box below where it differs (.hub uses 24px) */
  --edge-line-centre-opacity: 0.5;
  --edge-line-corner-opacity: 0.13;
  /* also the side lines' starting brightness - see .edge-glow::before below for why this one value serves both */
  --edge-glow-opacity: 0.08;
  --edge-glow-width: min(70%, 260px);
  --edge-glow-height: 80px;
  --edge-side-fade: 34%;
  /* the bottom slice of the box (as a % of its own current height) left with no line at all - so the side lines always fade to transparent by the remaining 66% down, however tall the box is or becomes (e.g. an opened FAQ row) */
}

.section--hero-divider {
  position: relative;
  /* Lifted above .hero-wrap (which has no z-index of its own, so this
     only needs to clear "auto") so this section's own background - see
     below - reliably paints over the robot arm's overhang, rather than
     relying on DOM order alone. Comfortably under the fixed nav's
     z-index: 100. */
  z-index: 1;
  /* Solid, flat and opaque - the same black as the rest of the page, so
     nothing of the arm shows through below the rounded line. clip-path
     (not a gradient) is what makes it start exactly at the line's own
     top edge instead of this section's own top edge: inset() pushes the
     top of the shape down by --hero-divider-top-offset, and its round
     corners reuse the exact same --hero-divider-radius as the outline
     itself, so the black's own curve matches the line's curve exactly
     instead of showing as a square corner outside it. Above the line
     (the section's own first --hero-divider-top-offset), this clip
     removes the background entirely, so the hero stays exactly as it
     is there - nothing added on top of it. The outline, glow and all of
     .section__inner's content (heading, cards) sit inside this same
     clipped shape, comfortably below where it starts, so none of them
     are cut by it. */
  background: var(--color-bg);
  -webkit-clip-path: inset(var(--hero-divider-top-offset) 0 0 0 round var(--hero-divider-radius) var(--hero-divider-radius) 0 0);
  clip-path: inset(var(--hero-divider-top-offset) 0 0 0 round var(--hero-divider-radius) var(--hero-divider-radius) 0 0);
  /* Clips the glow to the section's own box, and keeps this layer from
     ever contributing horizontal overflow/a scrollbar. Nothing above
     the rounded line has any background now (see the clip-path above),
     so there's nothing else here that could paint over the outline. */
  overflow: hidden;
}

/* The outline itself - inset from the section's sides, pushed down by
   --hero-divider-top-offset, box height = radius + side-fade-length so
   the two straight side-borders naturally stop exactly where the fade
   finishes (a border can't be told to stop partway down a side any
   other way - the box itself has to end there). The mask is a single
   ellipse centred at this box's own top-middle, sized so its edge (the
   gradient's 100% point) lands exactly on the top corners: at 0% it's
   full strength (top-centre), at 100% it's exactly at a top corner, and
   past that - down the sides - it keeps fading to fully transparent by
   the bottom of the box. */
.section--hero-divider::before {
  content: "";
  position: absolute;
  top: var(--hero-divider-top-offset);
  left: var(--hero-divider-inset);
  right: var(--hero-divider-inset);
  height: calc(var(--hero-divider-radius) + var(--hero-divider-side-fade-length));
  border: 1px solid #fff;
  border-bottom: none;
  border-top-left-radius: var(--hero-divider-radius);
  border-top-right-radius: var(--hero-divider-radius);
  -webkit-mask-image: radial-gradient(ellipse 50% 100% at 50% 0%,
      rgba(0, 0, 0, var(--hero-divider-line-centre-opacity)) 0%,
      rgba(0, 0, 0, var(--hero-divider-line-corner-opacity)) 100%,
      transparent 142%);
  mask-image: radial-gradient(ellipse 50% 100% at 50% 0%,
      rgba(0, 0, 0, var(--hero-divider-line-centre-opacity)) 0%,
      rgba(0, 0, 0, var(--hero-divider-line-corner-opacity)) 100%,
      transparent 142%);
  pointer-events: none;
}

/* The soft pool of light just beneath the line, centred, aligned to the
   outline's new (pushed-down) position. */
.section--hero-divider::after {
  content: "";
  position: absolute;
  top: var(--hero-divider-top-offset);
  left: 50%;
  width: var(--hero-divider-glow-width);
  height: var(--hero-divider-glow-height);
  transform: translateX(-50%);
  background: radial-gradient(ellipse at top, rgba(255, 255, 255, var(--hero-divider-glow-opacity)) 0%, transparent 70%);
  pointer-events: none;
}

@media (max-width: 700px) {
  .section--hero-divider {
    -webkit-clip-path: inset(var(--hero-divider-top-offset) 0 0 0 round var(--hero-divider-radius-mobile) var(--hero-divider-radius-mobile) 0 0);
    clip-path: inset(var(--hero-divider-top-offset) 0 0 0 round var(--hero-divider-radius-mobile) var(--hero-divider-radius-mobile) 0 0);
  }

  .section--hero-divider::before {
    left: var(--hero-divider-inset-mobile);
    right: var(--hero-divider-inset-mobile);
    height: calc(var(--hero-divider-radius-mobile) + var(--hero-divider-side-fade-length));
    border-top-left-radius: var(--hero-divider-radius-mobile);
    border-top-right-radius: var(--hero-divider-radius-mobile);
  }
}

.section__inner {
  max-width: 1320px;
  margin: 0 auto;
}

.section__inner--narrow {
  max-width: 640px;
}

/* index.html "Built around the people on your floor." only: this is
   the single longest .section__lede--intro paragraph on the site
   (206 characters) - at the shared 640px .section__inner--narrow
   width it wraps to 4 lines even with text-wrap: balance (balance
   only evens out a given line count, it can't reduce it - a narrower
   column would mean *more* lines, not fewer, so that's not the fix
   here despite the width being the thing to change). Widened to
   900px - .section__lede--intro's own existing max-width, which
   already applies everywhere a --intro lede sits outside a --narrow
   wrapper (e.g. #how-it-works) - just for this one section's wrapper,
   so this one lede reaches 3 balanced lines. Its heading just gets a
   little more breathing room as a side effect; still short enough to
   sit on 1-2 lines regardless. */
#humans-first .section__inner--narrow {
  max-width: 900px;
}

/* Platform page's and the Solutions pages' hero intro lede: same fix
   as #humans-first above, reusing the same 900px value (already
   .section__lede--intro's own max-width) rather than inventing a new
   one, so these wrap to 2 balanced lines on desktop instead of 3.
   Scoped to only the first section on each page (the one with the
   <h1>) via #platform-intro (Platform) or :first-of-type (the
   Solutions pages, which have no id there) so the later narrow
   sections - "What stays the same.", "Fits into...", every page's
   closing CTA - are untouched. Narrower viewports aren't forced to 2
   lines: this is only a cap, so the box (and the text wrapping inside
   it) still shrinks naturally with the viewport below 900px. */
body.page-platform #platform-intro .section__inner--narrow,
body.page-solutions main > section:first-of-type .section__inner--narrow {
  max-width: 900px;
}

.section__heading {
  margin: 0 0 1rem;
  font-size: clamp(1.75rem, 3vw, 2.5rem);
  font-weight: var(--font-weight-headline-lg);
  letter-spacing: var(--font-letter-spacing-headline-lg);
  line-height: var(--line-height-headline-lg);
  text-wrap: balance;
  color: var(--color-text);
}

.section__lede {
  margin: 0;
  font-size: 1.05rem;
  line-height: 1.7;
  color: var(--color-text-muted);
}

/* Bigger tier, for explanatory/intro ledes (added on top of the base
   .section__lede class above, never in place of it) - every closing
   "See it on your floor." lede and the scorecard intro deliberately
   stay off this class, at the base 1.05rem. No existing type-scale
   variable on this site is close to this size (the codebase has
   font-WEIGHT and letter-spacing variables, but no font-SIZE scale),
   so this is a literal clamp() rather than a variable reference, same
   as every other font-size on the site. clamp(18px, 1vw + 14px, 24px)
   reaches its 24px ceiling at any viewport >= 1000px, and eases down
   smoothly to 18px by narrow phone widths rather than jumping at a
   single breakpoint. max-width: 900px only actually binds on
   #how-it-works (the one place this sits outside a
   .section__inner--narrow wrapper, which otherwise already caps
   these paragraphs at 640px on every other page) - harmless,
   non-binding everywhere else. */
.section__lede--intro {
  max-width: 900px;
  margin-inline: auto;
  font-size: clamp(18px, 1vw + 14px, 24px);
  /* Stops a single short word being stranded alone on the last line
     (e.g. "over." / "UK GDPR."), same technique .section__heading
     already uses. */
  text-wrap: balance;
}

.section__lede+.btn {
  margin-top: 2rem;
}

/* Homepage only: centres every section heading below the hero, and any
   lede paragraph directly under one, as one unit - scoped to
   body.page-home since .section__heading/.section__lede are shared with
   other pages. Never matches card/step/timeline text (.card__title,
   .card__text, .step__title, .step__text, .timeline__label), which use
   their own, different classes, so those stay exactly as they are. The
   hero's own heading/paragraph use .hero__headline/.hero__subhead, not
   these classes, so the hero stays untouched too. */
body.page-home .section__heading {
  text-align: center;
}

body.page-home .section__lede {
  text-align: center;
  /* No-op today (neither class has its own max-width - they fill
     whatever container positions them), kept so centring still holds if
     either one ever gets a max-width of its own later. */
  margin-inline: auto;
}

/* Every other page (platform, warehouses, 3pls, factories, safety-trust,
   company): .section__inner--narrow is a centred block (max-width: 640px
   + margin: 0 auto from .section__inner), but .section__heading/
   .section__lede text inside it was still left-aligned by default, so
   the text looked off-centre inside an otherwise-centred box. Centres
   just the heading/lede text inside that specific block - scoped to
   .section__inner--narrow rather than .section__heading/.section__lede
   generally, so it never touches a heading or lede sitting in a plain,
   full-width .section__inner (e.g. the card-grid sections' own
   headings, or the FAQ heading, none of which have this mismatch since
   their block isn't narrower than the page). Checked: no card, step,
   FAQ, nav, footer, button or booking-form text uses .section__inner--
   narrow, so none of those are affected either way. */
.section__inner--narrow .section__heading,
.section__inner--narrow .section__lede {
  text-align: center;
}

/* Every page's closing CTA ("Become a design partner." / "See it on
   your floor.") has nothing but its heading, lede and button in this
   column - safe to centre the whole thing at once, which also centres
   the button (an inline-flex element, so text-align on its container
   centres it) under its heading, as asked. One shared class instead of
   a scoped id per page (was body.page-home #design-partner and
   #blog-cta, each repeating this same rule) - every closing-CTA
   .section__inner carries this class now, so there's one fix to tune
   instead of one per page. */
.cta-closing {
  text-align: center;
}

/* blog.html "Latest articles" heading: centred like other pages' narrow
   intro headings, scoped to this page's own section id so plain
   .section__inner card-grid headings elsewhere (e.g. platform.html's
   FAQ) stay exactly as they are. */
#blog-articles .section__heading {
  text-align: center;
}

/* ---- Blog article cards ----
   Own grid (not .cards) so its tablet/phone breakpoints (1000px/700px)
   don't change the 860px breakpoint every other .cards grid on the site
   shares. Equal-height rows come for free from CSS Grid's default
   stretch, so .blog-card just needs to be a column flex box to put that
   extra height to use (pinning the footer to the bottom via margin-top:
   auto). */
.blog-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1.5rem;
  margin-top: var(--heading-gap);
}

.blog-card {
  display: flex;
  flex-direction: column;
}

/* Linked cards only (card 1 today) - resets the anchor's own link
   styling, then a hover/focus state: a subtle lift, plus a stronger
   glow by overriding --edge-glow-opacity/--edge-line-centre-opacity
   locally (these are custom properties, so the override cascades down
   into .blog-card-link's own edge-glow AND its .cover-glow child,
   intensifying both at once without a second set of rules). */
.blog-card-link {
  text-decoration: none;
  color: inherit;
  transition: transform 180ms var(--ease-out);
}

.blog-card-link:hover,
.blog-card-link:focus-visible {
  transform: translateY(-4px);
  --edge-glow-opacity: 0.16;
  --edge-line-centre-opacity: 0.7;
}

.blog-card-link:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

/* Shared by .blog-card__cover and (blog/orchestration-layer-vs-point-
   to-point.html's) .article__cover: a cover needs its own solid fill (a
   "dark panel"), which .edge-glow alone can't give it (that class
   forces background: none !important - see its comment above). Reuses
   .edge-glow's exact ::before/::after technique and shared variables
   instead of its class, via this one shared class, so both covers stay
   identical and only get one copy of the technique to tune. */
.cover-glow {
  position: relative;
  border-radius: var(--edge-radius);
  background: var(--color-surface);
  overflow: hidden;
}

.cover-glow::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: var(--edge-side-fade);
  border: 1px solid #fff;
  border-bottom: none;
  border-top-left-radius: var(--edge-radius);
  border-top-right-radius: var(--edge-radius);
  -webkit-mask-image: radial-gradient(ellipse 50% 100% at 50% 0%,
      rgba(0, 0, 0, var(--edge-line-centre-opacity)) 0%,
      rgba(0, 0, 0, var(--edge-line-corner-opacity)) 100%,
      transparent 142%);
  mask-image: radial-gradient(ellipse 50% 100% at 50% 0%,
      rgba(0, 0, 0, var(--edge-line-centre-opacity)) 0%,
      rgba(0, 0, 0, var(--edge-line-corner-opacity)) 100%,
      transparent 142%);
  pointer-events: none;
}

.cover-glow::after {
  content: "";
  position: absolute;
  top: 0;
  left: 50%;
  width: var(--edge-glow-width);
  height: var(--edge-glow-height);
  transform: translateX(-50%);
  background: radial-gradient(ellipse at top, rgba(255, 255, 255, var(--edge-glow-opacity)) 0%, transparent 70%);
  pointer-events: none;
}

/* A cover photo (card 1's and the article page's) is normal in-flow
   content, so it paints before the position: absolute ::before/::after
   above regardless of DOM order - the glow line and soft light stay
   visible over it with no z-index needed. */
.cover-glow img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
}

.blog-card__cover {
  aspect-ratio: 16 / 10;
  margin-bottom: 1.25rem;
}

.blog-card__footer {
  margin-top: auto;
  padding-top: 1.1rem;
}

.blog-card__divider {
  border-top: 1px solid var(--color-border);
  margin-bottom: 0.9rem;
}

.blog-card__meta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
}

.blog-card__tag {
  display: inline-flex;
  align-items: center;
  padding: 0.3rem 0.75rem;
  border-radius: 999px;
  background: var(--color-border);
  color: var(--color-text-muted);
  font-size: 0.78rem;
  font-weight: var(--font-weight-label);
  white-space: nowrap;
}

.blog-card__status {
  font-size: 0.85rem;
  color: var(--color-text-muted);
  white-space: nowrap;
}

@media (max-width: 1000px) {
  .blog-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (max-width: 700px) {
  .blog-grid {
    grid-template-columns: 1fr;
    margin-top: var(--heading-gap-mobile);
  }
}

/* Heading gap: the space between a section's heading (or its intro
   paragraph, where one sits under the heading) and the first block
   underneath it - cards, the hub diagram, FAQ rows. Reference is
   index.html's "Every vendor brings its own system." heading and the
   three cards under it: was 2.5rem (40px, from .cards' own margin-top
   below colliding with .section__heading's 1rem/16px margin-bottom -
   adjoining block margins collapse to the larger of the two, so the
   smaller heading margin was always irrelevant here), now 2.75rem
   (44px, +10%). .cards and .faq-list both already used the old 2.5rem
   value - switched both to this variable so they stay matched to the
   reference. The hub diagram (.hub) is NOT switched in its own, shared
   rule: on platform.html .hub is the first thing in its own section
   (its 0 margin-top there is deliberate spacing, not a heading gap), so
   it only gets this treatment where it actually sits right under a
   heading in the same block - index.html's "One layer above every
   fleet." - via a rule scoped to that one section below. */
:root {
  --heading-gap: 2.75rem;
  --heading-gap-mobile: 1.75rem;
}

/* index.html only: the hub's own shared rule leaves it at margin-top: 0
   (right for platform.html, where it sits alone in its own section -
   see the --heading-gap comment above). Only here, under the lede
   paragraph in the same .section__inner, does it need the reference
   gap (it used to sit right under the heading itself, before the
   lede below was added - the same --heading-gap rule just now
   applies one element later). */
#how-it-works .hub {
  margin-top: var(--heading-gap);
}

/* index.html "See it in your operation." only: same headline-to-
   paragraph gap, applied one element later (lede -> button), scoped to
   this section's button wrapper only - .cta-closing itself is shared
   with other closing-CTA sections on other pages, which keep their
   current spacing. */
#design-partner .cta-closing__button {
  margin-top: var(--heading-gap);
}

@media (max-width: 860px) {
  .cards,
  .faq-list,
  #how-it-works .hub {
    margin-top: var(--heading-gap-mobile);
  }

  #design-partner .cta-closing__button {
    margin-top: var(--heading-gap-mobile);
  }
}

/* ---- Cards (The Problem, Use Cases) ---- */

.cards {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1.5rem;
  margin-top: var(--heading-gap);
}

.card {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: 16px;
  padding: 2rem;
  scroll-margin-top: var(--nav-height);
}

.section--surface .card {
  background: var(--color-bg);
}

/* ---- "Every vendor brings its own system." (#the-problem): framed
   to the same box model as #how-it-works' .hub diagram just below it
   (max-width: 1100px, centred, 40px horizontal inner padding - read
   from .hub's own "max-width: 1100px; padding: 36px 40px" above), so
   this section's content and its decorative top divider (further
   down) line up with the hub frame's edges. Cards stacked on the left,
   the four-fleet diagram on the right, inside that same framed width. */
/* Same surface as #how-it-works' section below it (.section--surface,
   background: var(--color-surface)) - applied by variable on the id
   rather than by adding that class, because .section--surface also
   has its own ".section--surface .card { background: var(--color-bg);
   }" rule further down, which would have silently changed these
   cards' background too (out of scope here). border-top/bottom from
   that class are moot either way - body.page-home removes them from
   every .section--surface section, and .section--hero-divider's own
   decorative border (unaffected by any of this) already does this
   section's framing. */
#the-problem {
  background: var(--color-surface);
}

/* #humans-first and #use-cases ("Handle more work with your current
   setup." and "Use cases.") previously inherited the plain black
   --color-bg. Set to the same --color-surface as the 3D model section
   and the other surface sections, by id rather than by adding
   .section--surface, for the same reason as #the-problem above:
   .section--surface .card would otherwise darken the use-case cards. */
#humans-first,
#use-cases {
  background: var(--color-surface);
}

#the-problem .section__inner {
  max-width: 1100px;
  padding: 0 40px;
}

.problem-layout {
  display: grid;
  grid-template-columns: 2fr 3fr;
  column-gap: 48px;
  align-items: stretch;
  margin-top: var(--heading-gap);
}

/* Cards stretch to the row's full height (set by the taller column -
   normally the visual) as three equal rows with a 14px gap, so no
   card is left with empty space at its own bottom. */
.problem-layout__cards {
  display: flex;
  flex-direction: column;
  gap: 14px;
  margin-top: 0;
}

#the-problem .problem-layout__cards .card {
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 22px 28px;
}

@media (max-width: 860px) {
  .problem-layout {
    grid-template-columns: 1fr;
    margin-top: var(--heading-gap-mobile);
    gap: 2rem;
  }
}

/* ---- The diagram itself: no outer panel - each vendor frame is its
   own small box sitting directly on the page background (only
   .pv-frame__box below draws anything), same vendor accent colours
   (--hub-accent-a/b/c/d), same robot icon (#icon-robot, referenced by
   id from the hub's shared <defs>). The SVG is width: 100% of its
   column; viewBox 564:388 sets its own height, and align-self: center
   (overriding the grid's default stretch) keeps it vertically centred
   in the row rather than stretched to match the cards column. Two
   complete <svg> variants - full (2x2, desktop) and mobile (the top
   row only) - are swapped with a plain media query rather than hiding
   part of one canvas, so neither version is left with dead space
   where the other's rows would have been. */
.problem-visual {
  position: relative;
  /* Stretches to the row's full height (the grid's default align-items:
     stretch, from .problem-layout) so its top lines up with the top of
     the first card - the SVG itself (a plain block child, no internal
     centring) then sits at this box's own top edge. At 80% width its
     rendered height now matches the card stack's height closely enough
     that this is effectively also its bottom edge. */
}

.problem-visual__svg {
  display: block;
  width: 100%;
  height: auto;
}

.problem-visual__svg--mobile {
  display: none;
}

@media (max-width: 700px) {
  .problem-visual__svg--full {
    display: none;
  }

  .problem-visual__svg--mobile {
    display: block;
  }
}

/* Desktop only (visual sits beside the cards, not stacked under them):
   the four-frame drawing runs at 80% of the column, centred
   horizontally via auto margins now that it's narrower than its
   container. 80% of the column, at the viewBox's 564:447 aspect
   ratio, renders at very close to the card stack's own height, so the
   drawing's top and bottom land close to the first/last card's edges
   (.problem-visual stretches to the row's height and the SVG sits at
   its own top - see .problem-visual above) without the aspect ratio
   being distorted. At <=860px the visual stacks full-width under the
   cards (same breakpoint as .problem-layout's own stacking query) and
   keeps the plain 100%-width sizing and the smaller label/icon from
   the base rules above - only this min-width query enlarges them, to
   stay readable once the drawing is scaled down to 80%. */
@media (min-width: 861px) {
  .problem-visual__svg--full {
    width: 80%;
    margin: 0 auto;
  }

  /* Converted from .hub__tile span's real on-screen size (0.72rem =
     11.52px) by this drawing's own 80% desktop scale (11.52 / 0.8 =
     14.4px), rather than copied as a raw number, so it renders at the
     same actual size as the lower tile labels despite living in a
     564-wide viewBox shown at 80% width. x stays 50px (unchanged -
     this round only changes size/weight/colour, not position). */
  .problem-visual__svg--full .pv-frame__label {
    font-size: 14.4px;
    x: 50px;
  }

  /* Converted the same way from .hub__tile > .hub__icon's real 20px
     (20 / 0.8 = 25px), re-centred on the same (29, 32) point the icon
     has always sat at (25/2 = 12.5, so x = 29 - 12.5, y = 32 - 12.5). */
  .problem-visual__svg--full .pv-icon {
    width: 25px;
    height: 25px;
    x: 16.5px;
    y: 19.5px;
  }
}

/* Fill, border and shadow all copied from .hub__tile's own resting
   "liquid glass" values (see the #pv-frame-fill/#pv-frame-border
   gradients' comment in the markup) - drop-shadow is the SVG
   equivalent of box-shadow for a shape (box-shadow itself doesn't
   apply to SVG geometry), same offset/blur/colour as .hub__tile's own
   "0 10px 24px rgba(0,0,0,.4)". .hub__tile's matching inset highlight
   and backdrop-filter blur aren't reproduced: SVG has no inset-shadow
   equivalent on a shape, and backdrop-filter has nothing to blur once
   #the-problem's own background (just above) is a flat colour rather
   than hero/other content showing through. */
.pv-frame__box {
  fill: url(#pv-frame-fill);
  stroke: url(#pv-frame-border);
  stroke-width: 1;
  filter: drop-shadow(0 10px 24px rgba(0, 0, 0, 0.4));
}

/* Hover glow ring, mouse/fine-pointer only (see problem-visual.js):
   an overlay on each frame's own border, stroked with that frame's
   #pv-glow-[a-d] radial gradient. Its near stop is transparent at
   rest (stop-opacity="0" presentation attribute in the markup -
   deliberately NOT set here in CSS: a stylesheet rule always beats a
   presentation attribute in the cascade, so the stop-opacity: 0 that
   used to live in this rule was silently overruling every value
   problem-visual.js's setAttribute("stop-opacity", ...) ever wrote,
   which is why the hover effect rendered nothing - this rule only
   sets stop-color, which JS never touches, so there's nothing left
   here to win that fight) and problem-visual.js raises it, and moves
   the gradient's centre, as the pointer nears that frame - mirroring
   #hub's own cursor-tracking rim glow (platform.js's
   initLiquidLight/.hub__tile::before) at the same 150px radius of
   influence, 90px glow size and 0.45 peak alpha, just drawn as an SVG
   stroke instead of a masked background layer. Fully inert until JS
   touches it, so the frame's resting look (drawn by .pv-frame__box
   above) is unchanged. */
.pv-frame__glow {
  fill: none;
  stroke-width: 1.5;
  pointer-events: none;
}

.pv-frame-glow__stop,
.pv-frame-glow__stop-edge {
  stop-color: #ffffff;
}

/* font-family/weight/colour copied from .hub__tile span: that rule
   sets no font-weight or letter-spacing of its own, so it inherits
   body's var(--font-weight-regular) (400) and the browser's normal
   letter-spacing - matched here explicitly rather than left to
   inherit, since this SVG text doesn't sit in the same inheritance
   chain. (--font-weight-label, used here previously, is a different,
   heavier value the hub tile labels don't actually use.) */
.pv-frame__label {
  font-family: var(--font-sans);
  font-weight: var(--font-weight-regular);
  letter-spacing: normal;
  font-size: 14px;
  fill: var(--color-text);
}

/* Mobile variant is scaled down a lot more (same 564-wide viewBox, far
   narrower rendered width than the desktop 2x2), so its label/icon are
   drawn larger in viewBox units to stay readable once scaled. */
.problem-visual__svg--mobile .pv-frame__label {
  font-size: 20px;
}

.pv-icon--a {
  color: var(--hub-accent-a);
}

.pv-icon--b {
  color: var(--hub-accent-b);
}

.pv-icon--c {
  color: var(--hub-accent-c);
}

.pv-icon--d {
  color: var(--hub-accent-d);
}

/* Each robot is a real photo/render (assets/robots/vendor-*.png),
   placed via <image> at its own natural proportions - sized by the
   width attribute in the markup, height left to each image's own
   aspect ratio (never stretched or cropped). */
.pv-robot image {
  display: block;
}

/* The one robot waiting: greyed out and dimmed, same idea as the
   previous solid-grey vector robot - grayscale removes its colour,
   opacity dims it, so it still reads as clearly different from the
   working robots around it. */
.pv-robot--idle image {
  filter: grayscale(1) opacity(45%);
}

/* Waiting-clock badge: a small filled light-grey circle (never green
   or an accent colour) sitting on the idle robot's top-right corner,
   with a dark clock face drawn on top of it so the hands read clearly
   against the light fill. */
.pv-clock__badge {
  fill: var(--color-text-muted);
  stroke: none;
}

.pv-clock__hands {
  fill: none;
  stroke: var(--color-surface);
  stroke-width: 1.3;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* Four "cut connections", each a pair of short dashed segments - one
   reaching in from each of the two frames it runs between - stopping
   short of each other with an "x" centred in the gap, so it reads as
   a link that's been cut in the middle rather than a stub pointing at
   empty space. Segments are grey (--color-text-muted, the same grey
   as the dimmed robot and its clock badge); the x marks are red
   (--color-error, the site's one existing red/error colour). Each
   connection's two segments and its x are wrapped in their own
   <g class="pv-frame pv-frame--top/--bottom"> in the markup, so they
   share the frames' own fade-up entrance animation below as one group
   (same class, same keyframe, same row timing) - there's nothing
   connection-specific to animate here beyond that shared wrapper, and
   the dashes themselves are just a plain, unanimated
   stroke-dasharray. */
.pv-cut {
  fill: none;
  stroke: var(--color-text-muted);
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-dasharray: 5 4;
}

.pv-cut-x {
  fill: none;
  stroke: var(--color-error);
  stroke-width: 1.5;
  stroke-linecap: round;
}

/* ---- Intro sequence, gated the same way as reveal.js: the hidden
   start state only applies once problem-visual.js has added
   js-pv-ready to <html> - no JS/blocked/disabled, and everything
   below stays in its plain, fully visible, static state. .pv-frame
   here matches both the four vendor frames (each also carries its own
   .pv-frame--a/b/c/d, for the hover glow - see further down) and the
   cut-connection wrapper <g>s in the markup, which only ever carry
   --top, --bottom or --mobile: the A-B connection is tagged --top
   (moves with Vendor A/B), C-D is --bottom (moves with Vendor C/D),
   and the two vertical connections B-D and C-A, sitting between the
   rows, are both tagged --top. Order: the bottom row (and C-D) fades
   up first, then the top row (and A-B, B-D, C-A) a beat later, then
   the waiting clock once both have settled - each stage only once,
   no loop. */
html.js-pv-ready #problem-visual .pv-frame {
  opacity: 0;
  transform: translateY(16px);
}

html.js-pv-ready #problem-visual.pv-visible .pv-frame {
  animation: pv-frame-in 0.55s var(--ease-out, ease-out) forwards;
}

html.js-pv-ready #problem-visual.pv-visible .pv-frame--bottom,
html.js-pv-ready #problem-visual.pv-visible .pv-frame--mobile {
  animation-delay: 0ms;
}

html.js-pv-ready #problem-visual.pv-visible .pv-frame--top {
  animation-delay: 150ms;
}

@keyframes pv-frame-in {
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

html.js-pv-ready #problem-visual .pv-clock {
  opacity: 0;
}

html.js-pv-ready #problem-visual.pv-visible .pv-clock {
  /* Frames settle by 150ms delay + 550ms duration = 700ms at the
     latest (the connections, sharing that same animation, settle by
     then too) - this starts 50ms after that, so the clock is always
     the last thing in, after both. */
  animation: pv-fade-in 0.25s ease-out 750ms forwards;
}

@keyframes pv-fade-in {
  to {
    opacity: 1;
  }
}

/* Belt-and-suspenders alongside problem-visual.js's own reduced-motion
   check (which skips adding js-pv-ready at all) - if that's ever
   missed, this still guarantees no motion and nothing stuck hidden. */
@media (prefers-reduced-motion: reduce) {
  html.js-pv-ready #problem-visual .pv-frame,
  html.js-pv-ready #problem-visual .pv-clock {
    opacity: 1;
    transform: none;
    animation: none;
  }
}

/* ---- Shared "edge glow" box treatment ----
   Added via class="edge-glow" (alongside each box's own class) to: the
   Problem-section cards and Use-cases cards (index.html), the hub
   diagram panel (index.html and platform.html), the FAQ rows
   (platform.html), and the booking form card (book.html). One rule set,
   one block of variables (above), instead of copying this per box.
   No visible box: no border, no fill - just the top-edge line, the glow
   under it, and side lines fading out before the bottom. */
.edge-glow {
  position: relative;
  /* !important: .card/.hub/.faq-item/.book__form-card each set their own
     background+border, and some of those rules are defined later in
     this file than this one, so without !important source order would
     let them win back the fill/outline this class exists to remove. */
  background: none !important;
  border: none !important;
  /* Clips ::before/::after below to the box's own curve, same role
     overflow: hidden plays on .section--hero-divider. Several of these
     boxes (.hub) already set their own overflow: hidden for unrelated
     reasons (clipping the diagram itself) - repeating it here is a
     no-op there, not an additional clip. */
  overflow: hidden;
}

/* Bright-at-centre, fading-to-the-edges top line, plus side lines that
   continue down from the same top corners and fade out smoothly, no
   bottom line - identical technique to .section--hero-divider::before
   (border on top+left+right only, one radial-gradient mask). The mask's
   ellipse is "50% 100%" of THIS box, so at the very top (y=0) only the
   horizontal term matters - the top edge's centre-to-corner fade is
   therefore the same shape no matter how tall the box below it is.
   Past the corner, going down each side (x fixed at the edge, y
   increasing), distance keeps growing from the corner's 100% to exactly
   142% (~sqrt2) right at THIS box's own bottom edge. The box itself
   runs from the top to --edge-side-fade short of the real element's
   bottom (via top: 0; bottom: var(--edge-side-fade) - a percentage of
   the real element's own current rendered height, not a fixed length),
   so the side fade's length automatically scales to each box: short on
   a closed FAQ row, long on the hub panel, and it re-measures itself if
   a box's height changes (an opened FAQ row). There is deliberately no
   separate "side brightness" variable: the side's starting value, at
   the corner, is the same single point as the top edge's corner value,
   so it reuses --edge-line-corner-opacity rather than introducing a
   second variable that would either duplicate it or fight it at that
   shared point. */
.edge-glow::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: var(--edge-side-fade);
  border: 1px solid #fff;
  border-bottom: none;
  border-top-left-radius: var(--edge-radius);
  border-top-right-radius: var(--edge-radius);
  -webkit-mask-image: radial-gradient(ellipse 50% 100% at 50% 0%,
      rgba(0, 0, 0, var(--edge-line-centre-opacity)) 0%,
      rgba(0, 0, 0, var(--edge-line-corner-opacity)) 100%,
      transparent 142%);
  mask-image: radial-gradient(ellipse 50% 100% at 50% 0%,
      rgba(0, 0, 0, var(--edge-line-centre-opacity)) 0%,
      rgba(0, 0, 0, var(--edge-line-corner-opacity)) 100%,
      transparent 142%);
  pointer-events: none;
}

/* The soft pool of light just under the centre of that top edge, same
   shape as .section--hero-divider::after. */
.edge-glow::after {
  content: "";
  position: absolute;
  top: 0;
  left: 50%;
  width: var(--edge-glow-width);
  height: var(--edge-glow-height);
  transform: translateX(-50%);
  background: radial-gradient(ellipse at top, rgba(255, 255, 255, var(--edge-glow-opacity)) 0%, transparent 70%);
  pointer-events: none;
}

/* Interactive glow (card-glow.js) - requires .edge-glow plus either
   .card or .faq-item, so the diagram box, the booking form card, the
   scorecard boxes and the article TOC (.edge-glow but never .card or
   .faq-item) keep the fixed, centred behaviour from the two rules
   above untouched. --card-glow-x/--card-glow-boost are only ever set
   by that script, on the elements it selects; the "50%"/1 fallbacks
   below mean a card or FAQ row looks exactly as it does today if the
   script never runs (blocked, fails, or JS off). An open/closing FAQ
   row's current size is read fresh every frame by the script (it
   calls getBoundingClientRect on each tick, not once), so the side
   fade and glow stay correct through the open/close animation exactly
   as the shared rules above already do. Only the horizontal mask/blob
   position and the two opacity values are overridden here -
   everything else (size, shape, radius, side fade) still comes from
   the shared rules above. */
.card.edge-glow::before,
.faq-item.edge-glow::before {
  -webkit-mask-image: radial-gradient(ellipse 50% 100% at var(--card-glow-x, 50%) 0%,
      rgba(0, 0, 0, calc(var(--edge-line-centre-opacity) * var(--card-glow-boost, 1))) 0%,
      rgba(0, 0, 0, calc(var(--edge-line-corner-opacity) * var(--card-glow-boost, 1))) 100%,
      transparent 142%);
  mask-image: radial-gradient(ellipse 50% 100% at var(--card-glow-x, 50%) 0%,
      rgba(0, 0, 0, calc(var(--edge-line-centre-opacity) * var(--card-glow-boost, 1))) 0%,
      rgba(0, 0, 0, calc(var(--edge-line-corner-opacity) * var(--card-glow-boost, 1))) 100%,
      transparent 142%);
}

.card.edge-glow::after,
.faq-item.edge-glow::after {
  left: var(--card-glow-x, 50%);
  background: radial-gradient(ellipse at top, rgba(255, 255, 255, calc(var(--edge-glow-opacity) * var(--card-glow-boost, 1))) 0%, transparent 70%);
}

/* The hub diagram panel's own corner radius (24px) differs from the
   16px every other edge-glow box uses - override just this variable,
   same rule otherwise. */
.hub.edge-glow {
  --edge-radius: 24px;
}

.card__title {
  margin: 0 0 0.6rem;
  font-size: 1.1rem;
  font-weight: var(--font-weight-headline-sm);
  letter-spacing: var(--font-letter-spacing-headline-sm);
  color: var(--color-text);
}

.card__text {
  margin: 0;
  font-size: 0.95rem;
  line-height: 1.6;
  color: var(--color-text-muted);
}

/* ---- Steps (How it works) ---- */

.steps {
  display: flex;
  align-items: flex-start;
  gap: 1.5rem;
  margin-top: 2.5rem;
}

.step {
  flex: 1;
}

.step__number {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.25rem;
  height: 2.25rem;
  margin-bottom: 1rem;
  border-radius: 50%;
  border: 1px solid var(--color-border);
  font-size: 0.9rem;
  font-weight: var(--font-weight-label);
  color: var(--color-text);
}

.step__title {
  margin: 0 0 0.6rem;
  font-size: 1.1rem;
  font-weight: var(--font-weight-headline-sm);
  letter-spacing: var(--font-letter-spacing-headline-sm);
  color: var(--color-text);
}

.step__text {
  margin: 0;
  font-size: 0.95rem;
  line-height: 1.6;
  color: var(--color-text-muted);
}

.step__arrow {
  flex-shrink: 0;
  margin-top: 0.5rem;
  color: var(--color-border);
  font-size: 1.25rem;
}

@media (max-width: 860px) {
  .section {
    padding: 3.5rem 1.5rem;
  }

  .cards {
    grid-template-columns: 1fr;
  }

  .steps {
    flex-direction: column;
  }

  .step__arrow {
    display: none;
  }
}

/* ---------- Book an assessment page ---------- */

.book {
  padding: calc(2rem + var(--nav-height)) 2rem 6rem;
}

.book__inner {
  max-width: 1320px;
  margin: 0 auto;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 4rem;
  align-items: start;
}

.book__title {
  margin: 0 0 1rem;
  font-size: clamp(1.75rem, 3vw, 2.5rem);
}

.checklist {
  list-style: none;
  margin: 2rem 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.checklist__item {
  display: flex;
  align-items: flex-start;
  gap: 0.75rem;
}

.checklist__icon {
  flex-shrink: 0;
  margin-top: 0.15rem;
  color: var(--color-text);
}

.checklist__text {
  margin: 0;
  font-size: 0.98rem;
  line-height: 1.6;
  color: var(--color-text-muted);
}

.book__form-card {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: 16px;
  padding: 2.5rem;
}

.book__consent {
  margin: 1rem 0 0;
  font-size: 0.8rem;
  color: var(--color-text-subtle);
}

.book__consent a {
  color: inherit;
  text-decoration: underline;
}

@media (max-width: 860px) {
  .book {
    padding: calc(1.5rem + var(--nav-height)) 1.5rem 3.5rem;
  }

  .book__inner {
    grid-template-columns: 1fr;
    gap: 2.5rem;
  }

  .book__form-card {
    padding: 1.75rem;
  }
}

/* ---------- Platform page ---------- */

/* The section wrapping the hub diagram is .section--surface, same as
   platform.html's other two .section--surface sections further down -
   scoped to this one specific section via its own id (not the shared
   class, and not body.page-home's removal trick, since that would also
   strip the line from those other two sections). Removes only the top
   line (between the intro text above and the diagram), not the bottom
   one, and only on platform.html. */
#hub-section {
  border-top: none;
}

/* The gap between the intro paragraph and the diagram panel is built
   from two paddings: the intro section's own bottom padding, and this
   section's top padding. Reusing --heading-gap/--heading-gap-mobile
   (the same reference used for every other heading-to-block gap on the
   site - see the big comment near .cards) for this section's top
   padding, and zeroing the intro section's bottom padding below, makes
   the total exactly match that reference: 44px desktop, 28px under
   860px (which also covers under 700px - there's no separate rule at
   that width for either section).
   Was: #hub-section padding-top 2rem (32px) desktop / 1.25rem (20px)
   under 860px, plus #platform-intro's unscoped 6rem (96px) / 3.5rem
   (56px) bottom padding from the shared .section rule - 128px / 76px
   total.
   Now: #hub-section padding-top 2.75rem (44px) / 1.75rem (28px, via the
   shared variables), plus #platform-intro padding-bottom: 0 - 44px /
   28px total. */
#hub-section {
  padding-top: var(--heading-gap);
}

#platform-intro {
  padding-bottom: 0;
}

@media (max-width: 860px) {
  #hub-section {
    padding-top: var(--heading-gap-mobile);
  }
}

/* Hub diagram: five fleet/people tiles feeding into a central Interoper
   tile, which feeds an output card. The tiles are laid out with ordinary
   flexbox/grid - platform.js measures their actual rendered positions on
   load and on resize, and draws the connector lines and travelling dots
   to match exactly, so there's no hand-picked coordinate system to keep
   in sync here. */
:root {
  --hub-accent-a: #a78bfa;
  /* Vendor A - purple */
  --hub-accent-b: #60a5fa;
  /* Vendor B - blue */
  --hub-accent-c: #2dd4bf;
  /* Vendor C - teal */
  --hub-accent-d: #fb923c;
  /* Vendor D - orange */
  --hub-accent-human: #e5e5e5;
  /* Human workforce - neutral */
  --hub-accent-output: #4ade80;
  /* Output card - green */
}

.hub {
  position: relative;
  width: 100%;
  max-width: 1100px;
  margin: 0 auto;
  padding: 36px 40px;
  border-radius: 24px;
  /* Flat near-black, very faint gradient - no colour cast. */
  background: linear-gradient(165deg, #0d0d0e 0%, #000000 100%);
  border: 1px solid var(--color-border);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 90px;
  overflow: hidden;
  /* perspective for the tile tilt effect (section 2) */
  perspective: 1200px;
}

.hub__lines {
  position: absolute;
  z-index: 1;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

/* ---- Shared liquid-glass tile treatment ---- */
.hub__output,
.hub__center,
.hub__tile {
  position: relative;
  z-index: 2;
  flex-shrink: 0;
  overflow: hidden;
  background: linear-gradient(155deg, rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0.015));
  -webkit-backdrop-filter: blur(16px) saturate(160%);
  backdrop-filter: blur(16px) saturate(160%);
  /* Subtle inset edge highlight, nudged toward the cursor by --edge-x/y
     (section 2), plus a plain neutral shadow for depth - no colour, no
     blur-glow. */
  box-shadow:
    inset var(--edge-x, 0px) var(--edge-y, 0px) 1px rgba(255, 255, 255, 0.08),
    0 10px 24px rgba(0, 0, 0, 0.4);
  /* Tilt; --tilt-x/--tilt-y are set by platform.js and eased back to
     0deg at rest, so with no JS/no pointer this is just 0. */
  transform: rotateX(var(--tilt-x, 0deg)) rotateY(var(--tilt-y, 0deg));
  transform-style: preserve-3d;
}

/* Liquid-glass edge: a 1px rim built with a mask (padding-box XOR
   border-box) so only the ring itself is visible, not a filled shape.
   At rest it's a faint white gradient, brighter along the top. Hovering
   adds a soft radial brightening at --mx/--my, scaled by --rim-glow
   (0-1, set by platform.js from distance to the cursor - tiles beyond
   ~150px get --rim-glow: 0, so this layer is simply invisible there).
   Excluded from Interoper, which keeps its own crisp border instead. */
.hub__output::before,
.hub__tile::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  border-radius: inherit;
  padding: 1px;
  background:
    radial-gradient(90px circle at var(--mx, 50%) var(--my, 50%), rgba(255, 255, 255, calc(0.45 * var(--rim-glow, 0))), transparent 100%),
    linear-gradient(180deg, rgba(255, 255, 255, 0.22), rgba(255, 255, 255, 0.12) 40%, rgba(255, 255, 255, 0.12));
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
  pointer-events: none;
}

.hub__badge {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border-radius: 10px;
  color: #0a0a0a;
}

.hub__badge .hub__icon {
  width: 18px;
  height: 18px;
  color: inherit;
}

/* Vendor/Human icons sit directly in the tile now (no badge) - thin
   outline icons, coloured by the tile's accent via currentColor. */
.hub__tile>.hub__icon {
  flex-shrink: 0;
  width: 20px;
  height: 20px;
}

/* ---- Output card: green, left-aligned ---- */
.hub__output {
  width: 200px;
  height: 140px;
  border-radius: 20px;
  padding: 1rem;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: flex-start;
  gap: 0.5rem;
  text-align: left;
}

.hub__badge--output {
  background: rgba(74, 222, 128, 0.85);
}

.hub__output-row {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  margin: 0;
  font-size: 0.78rem;
  color: var(--color-text-muted);
}

.hub__icon--sm {
  width: 14px;
  height: 14px;
  color: var(--hub-accent-output);
}

.hub__output-figure {
  margin: 0;
  font-size: 1.9rem;
  font-weight: var(--font-weight-heading);
  color: var(--color-text);
}

/* ---- Interoper: centred icon + word, glowing edge ---- */
.hub__center {
  width: 135px;
  height: 135px;
  border-radius: 20px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.6rem;
  text-align: center;
  font-weight: var(--font-weight-heading);
  font-size: 0.95rem;
  color: var(--color-text);
  /* Matches .nav__wordmark's own capitalisation technique exactly
     (text-transform, not typed-in-caps markup) - letter-spacing and
     font-weight already equal the nav's (normal / var(--font-weight-
     heading)) without needing to set them again here. */
  text-transform: uppercase;
  /* Crisp white-blue border, no blur/glow around it. */
  border: 1px solid rgba(191, 219, 254, 0.65);
  box-shadow:
    inset var(--edge-x, 0px) var(--edge-y, 0px) 1px rgba(255, 255, 255, 0.12),
    0 10px 24px rgba(0, 0, 0, 0.4);
}

/* The real logo mark (same /logo.svg the nav/footer wordmarks use),
   replacing the old generic layers icon - sized the same way the nav
   sizes it (a fixed height, width: auto) so its 978:880 proportions
   are never stretched, rather than the old icon's forced square
   width+height. */
.hub__icon--stack {
  width: auto;
  height: 34px;
}

/* ---- Right-hand tiles ---- */
.hub__tiles {
  position: relative;
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 8px;
}

.hub__tile {
  width: 170px;
  height: 50px;
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 0 0.875rem;
  border-radius: 14px;
}

.hub__tile span {
  margin: 0;
  padding: 0;
  font-size: 0.72rem;
  line-height: 1;
  color: var(--color-text);
  white-space: nowrap;
}

.hub__tile--a>.hub__icon {
  color: var(--hub-accent-a);
}

.hub__tile--b>.hub__icon {
  color: var(--hub-accent-b);
}

.hub__tile--c>.hub__icon {
  color: var(--hub-accent-c);
}

.hub__tile--d>.hub__icon {
  color: var(--hub-accent-d);
}

/* Human workforce: larger tile, soft white outline icon, same glass base. */
.hub__tile--human {
  width: 190px;
  height: 76px;
  border-radius: 16px;
}

.hub__tile--human>.hub__icon {
  color: rgba(255, 255, 255, 0.85);
}

/* Tight stair-step: column 1 (A, D) closest to Interoper, column 2
   (B, C) ~32px further right, column 3 (Human, furthest) another ~32px
   step out. Only an 8px row gap between tiles (set above). */
.hub__tile--b,
.hub__tile--c {
  margin-left: 32px;
}

.hub__tile--human {
  margin-left: 64px;
}

/* ---- Lines and dots ---- */
.hub__line {
  fill: none;
  stroke-width: 1.5;
  stroke-dasharray: 6 6;
  opacity: 0.75;
  animation: hub-dash-flow 1.8s linear infinite;
}

.hub__line--a {
  stroke: var(--hub-accent-a);
}

.hub__line--b {
  stroke: var(--hub-accent-b);
}

.hub__line--human {
  stroke: var(--hub-accent-human);
  opacity: 0.5;
}

.hub__line--c {
  stroke: var(--hub-accent-c);
}

.hub__line--d {
  stroke: var(--hub-accent-d);
}

.hub__line--out {
  stroke: var(--hub-accent-output);
}

@keyframes hub-dash-flow {
  to {
    stroke-dashoffset: -24;
  }
}

.hub__dot {
  fill: #ffffff;
}

@media (prefers-reduced-motion: reduce) {
  .hub__line {
    animation: none;
  }
}

@media (max-width: 700px) {
  .hub {
    flex-direction: column;
    align-items: stretch;
    gap: 28px;
    padding: 1.75rem;
    perspective: none;
  }

  .hub__output {
    order: 1;
    width: 100%;
    height: auto;
    align-items: flex-start;
    gap: 0.5rem;
    padding: 1.25rem;
  }

  .hub__output-figure {
    font-size: 2rem;
  }

  .hub__center {
    order: 0;
    align-self: center;
    width: 130px;
    height: 130px;
    font-size: 1.05rem;
  }

  .hub__icon--stack {
    width: auto;
    height: 32px;
  }

  /* The smaller icon size in section 2 is desktop-only. */
  .hub__tile>.hub__icon {
    width: 22px;
    height: 22px;
  }

  .hub__tiles {
    order: -1;
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 16px;
    width: 100%;
  }

  .hub__tile {
    width: 100%;
    height: auto;
    aspect-ratio: 1 / 1;
    margin-left: 0;
    flex-direction: column;
    justify-content: center;
    text-align: center;
    gap: 0.5rem;
    padding: 0.5rem;
    /* The smaller size/radius in section 2 is desktop-only. */
    border-radius: 16px;
  }

  .hub__tile span {
    white-space: normal;
    font-size: 0.78rem;
  }

  .hub__tile--human {
    grid-column: 1 / -1;
    order: -1;
    flex-direction: row;
    width: 100%;
    height: auto;
    aspect-ratio: auto;
    padding: 1rem;
    border-radius: 16px;
  }

}

.faq-list {
  display: flex;
  flex-direction: column;
  gap: 1rem;
  margin-top: var(--heading-gap);
  max-width: 760px;
}

.faq-item {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: 16px;
  padding: 1.25rem 1.5rem;
}

.faq-item summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  cursor: pointer;
  list-style: none;
  font-weight: var(--font-weight-heading);
  color: var(--color-text);
}

.faq-item summary::-webkit-details-marker {
  display: none;
}

.faq-item__icon {
  flex-shrink: 0;
  color: var(--color-text-muted);
  transition: transform 160ms var(--ease-out);
}

.faq-item[open] .faq-item__icon {
  transform: rotate(45deg);
}

.faq-item p {
  margin: 0.9rem 0 0;
  font-size: 0.95rem;
  line-height: 1.6;
  color: var(--color-text-muted);
}

/* ---------- Footer ---------- */

.footer {
  background: var(--footer-bg);
  border-top: 1px solid var(--footer-border-color);
  padding: var(--footer-padding-y) 2rem 2.5rem;
}

.footer__top {
  display: grid;
  /* Brand column plus two footer__col link columns (Solutions,
     Resources) - was 3 link columns (plus Company) until the Company
     column was removed sitewide; dropped from repeat(3, 1fr) to
     repeat(2, 1fr) so Solutions/Resources stay evenly spaced instead
     of leaving an empty 4th track where Company used to sit. */
  grid-template-columns: 1.4fr repeat(2, 1fr);
  gap: var(--footer-gap) 2rem;
  max-width: 1320px;
  margin: 0 auto;
}

.footer__brand {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 1rem;
}

.footer__wordmark {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  font-weight: var(--font-weight-heading);
  font-size: 1.1rem;
  letter-spacing: 0.08em;
  color: var(--footer-heading-color);
  text-decoration: none;
}

.footer__tagline {
  margin: 0;
  max-width: 320px;
  font-size: 0.92rem;
  line-height: 1.6;
  font-weight: var(--font-weight-label);
  color: var(--footer-heading-color);
}

.footer__cta {
  height: 44px;
  padding: 0 1.4rem;
  font-size: 0.85rem;
}

/* display: contents lets .footer__col children sit directly in
   .footer__top's grid, while <nav> still wraps them as one landmark. */
.footer__nav {
  display: contents;
}

.footer__col {
  display: flex;
  flex-direction: column;
  gap: 0.9rem;
}

.footer__heading {
  margin: 0;
  font-size: 0.78rem;
  font-weight: var(--font-weight-label);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--footer-heading-color);
}

.footer__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.65rem;
}

.footer__list a,
.footer__legal a,
.footer__top-link {
  color: var(--footer-link-color);
  text-decoration: none;
  font-size: 0.92rem;
}

@media (hover: hover) and (pointer: fine) {

  .footer__list a:hover,
  .footer__legal a:hover,
  .footer__top-link:hover {
    color: var(--footer-link-hover-color);
  }
}

.footer__list a:focus-visible,
.footer__legal a:focus-visible,
.footer__top-link:focus-visible,
.footer__wordmark:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

.footer__divider {
  max-width: 1320px;
  margin: calc(var(--footer-padding-y) * 0.6) auto 1.5rem;
  border-top: 1px solid var(--footer-border-color);
}

.footer__bottom {
  max-width: 1320px;
  margin: 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 1rem 2rem;
}

.footer__copyright {
  margin: 0;
  font-size: 0.85rem;
  color: var(--color-text-subtle);
}

.footer__legal {
  display: flex;
  flex-wrap: wrap;
  gap: 1.25rem;
}

.footer__disclaimer {
  max-width: 1320px;
  margin: 1.5rem auto 0;
  font-size: 0.78rem;
  line-height: 1.5;
  color: var(--color-text-subtle);
}

@media (max-width: 860px) {
  .footer {
    padding: 3rem 1.5rem 2rem;
  }

  .footer__top {
    grid-template-columns: repeat(2, 1fr);
    gap: 2.5rem 1.5rem;
  }

  .footer__brand {
    grid-column: 1 / -1;
  }

  .footer__list a,
  .footer__legal a,
  .footer__top-link,
  .footer__wordmark {
    min-height: 44px;
    display: inline-flex;
    align-items: center;
  }

  .footer__bottom {
    flex-direction: column;
    align-items: center;
    text-align: center;
  }

  .footer__legal {
    justify-content: center;
  }
}

/* Phone: three link columns at 860px's width would be too cramped for
   "Safety & Trust" to sit comfortably, so stack everything full-width. */
@media (max-width: 700px) {
  .footer__top {
    grid-template-columns: 1fr;
  }
}

/* ---------- Legal pages ---------- */

.legal-page {
  /* Was missing a min-height, so short pages (terms.html especially) left
     the footer floating right after the short content instead of at the
     bottom of the screen. */
  min-height: 100vh;
  min-height: 100svh;
  max-width: 720px;
  margin: 0 auto;
  padding: calc(2rem + var(--nav-height)) 2rem 4rem;
}

.legal-banner {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-left: 4px solid var(--color-accent);
  border-radius: 10px;
  padding: 1rem 1.25rem;
  margin: 0 0 2.5rem;
  font-size: 0.95rem;
  font-weight: var(--font-weight-regular);
  color: var(--color-text);
}

.legal-page h1 {
  margin: 0 0 1.5rem;
  font-size: clamp(1.75rem, 3vw, 2.5rem);
  font-weight: var(--font-weight-heading);
  color: var(--color-text);
}

.legal-page h2 {
  margin: 2rem 0 0.5rem;
  font-size: 1.1rem;
  font-weight: var(--font-weight-heading);
  color: var(--color-text);
}

.legal-page p {
  margin: 0 0 1rem;
  font-size: 0.98rem;
  line-height: 1.7;
  color: var(--color-text-muted);
}

.legal-fields {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.9rem;
}

.legal-fields li {
  font-size: 0.98rem;
  line-height: 1.6;
  color: var(--color-text-muted);
}

.legal-fields strong {
  color: var(--color-text);
}

/* ---------- Blog article page (blog/*.html) ---------- */

/* This page's own title+cover section only - zeroes its bottom padding
   (was the shared .section value: 96px desktop, 56px at <=860px, same
   as the gap above the title) so the divider line below sits directly
   under the image, without touching .section itself, which every
   other page's first section also relies on. */
.article-cover-section {
  padding-bottom: 0;
}

.article__back {
  display: inline-block;
  margin-bottom: 1.5rem;
  font-size: 0.9rem;
  color: var(--color-text-muted);
  text-decoration: none;
}

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

.article__title {
  margin: 0;
  max-width: 900px;
  font-size: clamp(2.25rem, 5vw, 3.75rem);
  line-height: 1.1;
  color: var(--color-text);
}

.article__meta {
  margin-top: 1.25rem;
}

/* Byline/date line under the "Guide" tag - small and muted so it
   reads as metadata, not as more body text. */
.article__byline {
  margin: 0.5rem 0 0;
  font-size: 0.85rem;
  color: var(--color-text-muted);
}

.article__cover {
  margin-top: var(--heading-gap);
  aspect-ratio: 16 / 9;
}

/* The cover's own .section__inner already spans the same content width
   as the title area and the footer - this just caps the image itself
   at a readable height (1100px wide at 16:9 is 618.75px tall, vs
   742.5px uncapped at the full 1320px content width) and centres it in
   that width, rather than left-aligning it at a narrower width (which
   left a lopsided gap on the right - see the earlier 680px version).
   Only applied above 1000px - below that, .article-layout (further
   down) drops to a single column, so the cover should fill the content
   width like everything else does there instead of staying capped. */
.article-cover-section .article__cover {
  max-width: 1100px;
  margin-left: auto;
  margin-right: auto;
}

@media (max-width: 1000px) {
  .article-cover-section .article__cover {
    max-width: none;
  }
}

.article-layout {
  display: grid;
  grid-template-columns: minmax(220px, 30%) 1fr;
  gap: 3rem;
  align-items: start;
}

.article-toc {
  position: sticky;
  top: calc(var(--nav-height) + 2rem);
  padding: 1.5rem;
}

.article-toc__heading {
  margin: 0 0 1rem;
  font-size: 0.72rem;
  font-weight: var(--font-weight-label);
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--color-text-muted);
}

.article-toc__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
}

.article-toc__link {
  display: block;
  padding: 0.5rem 0 0.5rem 0.9rem;
  border-left: 2px solid transparent;
  font-size: 0.92rem;
  line-height: 1.4;
  color: var(--color-text-muted);
  text-decoration: none;
  transition: color 160ms ease, border-color 160ms ease;
}

.article-toc__link:hover {
  color: var(--color-text);
}

.article-toc__link--active {
  border-left-color: var(--color-accent);
  color: var(--color-text);
  font-weight: var(--font-weight-label);
}

.article-content {
  max-width: 680px;
}

.article-section {
  margin-top: var(--heading-gap);
}

.article-section:first-child {
  margin-top: 0;
}

.article-section__heading {
  margin: 0 0 1rem;
  font-size: 1.4rem;
  color: var(--color-text);
}

.article-section__text {
  margin: 0 0 1rem;
  font-size: 1rem;
  line-height: 1.75;
  color: var(--color-text-muted);
}

.article-section__text:last-child {
  margin-bottom: 0;
}

.article-section__list {
  margin: 0;
  padding: 0 0 0 1.25rem;
  list-style: disc;
  font-size: 1rem;
  line-height: 1.75;
  color: var(--color-text-muted);
}

.article-section__list li {
  margin-bottom: 0.6rem;
}

.article-section__list li:last-child {
  margin-bottom: 0;
}

.article-section__text a {
  color: inherit;
  text-decoration: underline;
}

@media (max-width: 1000px) {
  .article-layout {
    grid-template-columns: 1fr;
    gap: var(--heading-gap-mobile);
  }

  .article-toc {
    position: static;
    top: auto;
  }

  .article-content {
    max-width: none;
  }
}

/* ---------- Scorecard page ---------- */

.scorecard {
  padding: calc(2rem + var(--nav-height)) 2rem 6rem;
}

/* Centre these two buttons under the text above/inside their card. .btn
   is shared and stays display: inline-flex everywhere else; here the
   outer box becomes a block-level flex item sized to its own content
   (width: fit-content), which auto margins can then centre - .btn's own
   internal layout (icon/label) is unaffected since flex and inline-flex
   use the same algorithm for a box's own children. */
#startBtn,
#sendBtn {
  display: flex;
  width: fit-content;
  margin-inline: auto;
}

/* Removes the browser's own default fieldset border/padding/margin
   (never a site rule - fieldset was never styled anywhere in this
   project) so no box is drawn around the question text and option
   list. Scoped to this one fieldset; doesn't touch .scorecard__opt's
   own :has(input:focus-visible) outline, which is unrelated and stays. */
#quizScreen fieldset {
  border: 0;
  margin: 0;
  padding: 0;
  min-width: 0;
}

/* Fade the whole stage out, swap content, fade back in - same timing as
   the prototype this page was rebuilt from. Reduced-motion users skip the
   transition entirely (handled in scorecard.js, not here). */
.scorecard__stage {
  min-height: 520px;
  transition: opacity 0.3s ease, transform 0.3s ease;
}

.scorecard__stage--fade-out {
  opacity: 0;
  transform: translateY(8px);
}

@media (prefers-reduced-motion: reduce) {
  .scorecard__stage {
    transition: none;
  }
}

.scorecard__eyebrow {
  font-size: 0.8rem;
  font-weight: var(--font-weight-label);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--color-text-muted);
  margin-bottom: 0.75rem;
  text-align: center;
}

.scorecard__note {
  margin: 0;
  font-size: 0.85rem;
  color: var(--color-text-muted);
}

.scorecard__grid2 {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1rem;
}

@media (max-width: 600px) {
  .scorecard__grid2 {
    grid-template-columns: 1fr;
  }
}

.scorecard__progress-row {
  display: flex;
  justify-content: space-between;
  font-size: 0.85rem;
  color: var(--color-text-muted);
  margin-bottom: 0.5rem;
}

.scorecard__pbar {
  height: 4px;
  background: var(--color-border);
  border-radius: 2px;
  overflow: hidden;
  margin-bottom: var(--heading-gap);
}

.scorecard__pbar i {
  display: block;
  height: 100%;
  width: 0;
  background: var(--color-text);
  transition: width 0.3s;
}

@media (prefers-reduced-motion: reduce) {
  .scorecard__pbar i {
    transition: none;
  }
}

.scorecard__opts {
  display: grid;
  gap: 0.6rem;
}

/* Reuses the shared edge-glow treatment (no fill, glowing top edge) so
   the option boxes match every other card on the site. The checked/focus
   states below are this page's own, scoped addition on top of it. */
.scorecard__opt {
  display: flex;
  gap: 0.75rem;
  align-items: flex-start;
  padding: 0.9rem 1rem;
  border-radius: var(--edge-radius, 16px);
  cursor: pointer;
  color: var(--color-text-muted);
  background: var(--color-surface);
  transition: color 0.15s;
}

.scorecard__opt input {
  margin-top: 0.3rem;
  accent-color: var(--color-text);
}

.scorecard__opt:has(input:checked) {
  color: var(--color-text);
  --edge-line-centre-opacity: 0.9;
  --edge-glow-opacity: 0.16;
}

.scorecard__opt:has(input:focus-visible) {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

.scorecard__nav-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 0.75rem;
  margin-top: var(--heading-gap-mobile);
}

.scorecard__hint {
  font-size: 0.8rem;
  color: var(--color-text-muted);
}

/* A plain text-style secondary action (Back, Start again) - not a second
   button variant, just an understated control that sits next to .btn
   without competing with it. */
.scorecard__ghost-btn {
  font: inherit;
  font-size: 0.9rem;
  color: var(--color-text-muted);
  background: none;
  border: none;
  padding: 0.5rem 0;
  cursor: pointer;
  text-decoration: underline;
}

.scorecard__ghost-btn:hover {
  color: var(--color-text);
}

.scorecard__ghost-btn:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

.scorecard__box {
  padding: 2rem;
  border-radius: 16px;
  margin-bottom: var(--heading-gap-mobile);
}

.scorecard__box-title {
  margin: 0 0 0.6rem;
  font-size: 1.1rem;
  font-weight: var(--font-weight-heading);
  color: var(--color-text);
}

.scorecard__readout {
  display: grid;
  gap: 0.9rem;
  text-align: center;
}

.scorecard__score {
  font-weight: var(--font-weight-heading);
  font-size: clamp(2.5rem, 6vw, 4.5rem);
  line-height: 0.95;
  letter-spacing: -0.03em;
  color: var(--color-text);
  font-variant-numeric: tabular-nums;
}

.scorecard__score small {
  font-size: 1.4rem;
  color: var(--color-text-muted);
  font-weight: var(--font-weight-regular);
}

.scorecard__band {
  display: inline-block;
  font-size: 0.8rem;
  font-weight: var(--font-weight-heading);
  padding: 0.25rem 0.75rem;
  border-radius: 999px;
  color: #0b0d12;
}

/* Muted, dark-theme-friendly versions of a red/amber/green scale - kept
   distinct (not just silver) since the colour itself carries meaning
   here (which readiness band you're in), scoped to this page only. */
:root {
  --scorecard-band-low: #e0715f;
  --scorecard-band-mid: #d9b25a;
  --scorecard-band-high: #6fcf97;
}

.scorecard__meter {
  height: 6px;
  background: var(--color-border);
  border-radius: 3px;
  overflow: hidden;
}

.scorecard__meter i {
  display: block;
  height: 100%;
  background: var(--color-text);
}

.scorecard__lbl {
  font-size: 0.75rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--color-text-muted);
}

.scorecard__cost {
  font-weight: var(--font-weight-heading);
  font-size: 2rem;
  line-height: 1.05;
  letter-spacing: -0.02em;
  color: var(--color-text);
}

.scorecard__recs {
  margin: 0;
  padding-left: 1.25rem;
  display: grid;
  gap: 0.5rem;
  color: var(--color-text-muted);
}

.scorecard__consent {
  display: flex;
  gap: 0.6rem;
  align-items: flex-start;
  font-size: 0.85rem;
  color: var(--color-text-muted);
  margin: 0.25rem 0 0.9rem;
}

.scorecard__consent input {
  margin-top: 0.25rem;
  accent-color: var(--color-text);
}

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

.scorecard__cta-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  align-items: center;
  margin-bottom: var(--heading-gap-mobile);
}

@media (max-width: 860px) {
  .scorecard {
    padding: calc(1.5rem + var(--nav-height)) 1.5rem 3.5rem;
  }

  .scorecard__box {
    padding: 1.5rem;
  }
}

/* ---------- Free Tools page ---------- */

/* free-tools.html now reuses .blog-grid/.blog-card/.blog-card__* and the
   single shared .section__inner directly, the same way /blog itself is
   built - no grid/card rule of its own is needed any more. The one gap
   reuse can't close: /blog's own heading-centring rule is scoped to
   #blog-articles (blog's own section id), not a generic class, so it
   never reaches this page's heading - this is the one page-scoped rule
   still needed, same property/value as blog's. */
#free-tools-grid .section__heading {
  text-align: center;
}

/* ---------- Scroll reveal (index.html's main content below the hero)
   ----------
   Numbers below are GUESSES - tune here, not in reveal.js. The hidden
   start state only ever applies once reveal.js has added the
   js-reveal-ready class to <html> (see that file): if the script
   fails, is blocked, or is disabled, .reveal never matches anything
   under this selector, and every element stays in its normal, fully
   visible, un-animated state. --reveal-delay is set per element by
   reveal.js (for the stagger); the ", 0s" fallback below only matters
   if that script somehow didn't run but js-reveal-ready still got
   added some other way, which it is never written to be - defensive
   only. */
:root {
  --reveal-distance: 24px;
  --reveal-duration: 0.6s;
  --reveal-ease: ease-out;
}

html.js-reveal-ready .reveal {
  opacity: 0;
  transform: translateY(var(--reveal-distance));
  transition: opacity var(--reveal-duration) var(--reveal-ease),
    transform var(--reveal-duration) var(--reveal-ease);
  transition-delay: var(--reveal-delay, 0s);
}

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

/* Belt-and-suspenders alongside reveal.js's own reduced-motion check
   (which skips adding js-reveal-ready at all, so this normally never
   has anything to do) - if that check is ever missed, this still
   guarantees no motion and no stuck-hidden content. */
@media (prefers-reduced-motion: reduce) {
  html.js-reveal-ready .reveal {
    opacity: 1;
    transform: none;
    transition: none;
  }
}

/* The 3D scroll section (index.html, copied from prototype-3d.html)
   sits inside <main>, flush with the next section - no entry/exit pad
   blocks. The 48px gap below the hero now lives on .proto3d-panel
   (its wrapper, just below) instead of here, since that's the
   section's actual outer edge now. */
.proto3d-scroll {
  margin-top: 0;
  margin-bottom: 0;
}

/* The wrapper (index.html) around .proto3d-scroll - the same lit
   rounded panel #the-problem used to have (.section--hero-divider,
   plus its #the-problem { background: var(--color-surface) }), found
   via `git show 896fddd:styles.css` (the last commit before that
   section was removed) and reused here by reference to the same
   --hero-divider-* custom properties, not copied as new literal
   numbers. The only deliberate departure from the original: the old
   --hero-divider-top-offset (48px) existed only because that section
   had no external top margin of its own and needed the rounded line
   to start partway down ITS OWN box, simulating emerging from the
   hero inside one shared background; here the lit top edge starts
   right at this box's own top (offset 0) AND margin-top is 0, so the
   panel sits flush against the hero's own bottom edge with no gap at
   all - every other contributor (.hero/.hero-wrap's own padding and
   margin-bottom, <main>'s padding/margin) was already 0 or internal
   to the hero's own starry background box, not external space.
   overflow: clip (not hidden/auto/scroll) clips the glow/corners
   without becoming a scroll container, so .proto3d-scroll__inner's
   position: sticky below keeps resolving against the page's real
   scrolling viewport, unaffected. */
.proto3d-panel {
  position: relative;
  margin-top: 0;
  margin-bottom: 0;
  margin-left: 0;
  margin-right: 0;
  padding-left: 4vw;
  padding-right: 4vw;
  background: var(--color-surface);
  overflow: clip;
  clip-path: inset(0 0 0 0 round var(--hero-divider-radius) var(--hero-divider-radius) 0 0);
}

.proto3d-panel::before {
  content: "";
  position: absolute;
  top: 0;
  left: var(--hero-divider-inset);
  right: var(--hero-divider-inset);
  height: calc(var(--hero-divider-radius) + var(--hero-divider-side-fade-length));
  border: 1px solid #fff;
  border-bottom: none;
  border-top-left-radius: var(--hero-divider-radius);
  border-top-right-radius: var(--hero-divider-radius);
  -webkit-mask-image: radial-gradient(ellipse 50% 100% at 50% 0%,
      rgba(0, 0, 0, var(--hero-divider-line-centre-opacity)) 0%,
      rgba(0, 0, 0, var(--hero-divider-line-corner-opacity)) 100%,
      transparent 142%);
  mask-image: radial-gradient(ellipse 50% 100% at 50% 0%,
      rgba(0, 0, 0, var(--hero-divider-line-centre-opacity)) 0%,
      rgba(0, 0, 0, var(--hero-divider-line-corner-opacity)) 100%,
      transparent 142%);
  pointer-events: none;
}

.proto3d-panel::after {
  content: "";
  position: absolute;
  top: 0;
  left: 50%;
  width: var(--hero-divider-glow-width);
  height: var(--hero-divider-glow-height);
  transform: translateX(-50%);
  background: radial-gradient(ellipse at top, rgba(255, 255, 255, var(--hero-divider-glow-opacity)) 0%, transparent 70%);
  pointer-events: none;
}

/* Buffer around the 3D map: shrinks the sticky stage inward from the
   panel's padded edges while keeping the sticky height calc intact
   (box-sizing: border-box means this padding is absorbed inside that
   same calc(100vh - var(--nav-height)), not added on top of it). This
   replaces .proto3d-scroll__inner's own padding (0 2rem, prototype-
   3d.css, unmodified) for every side, by design - not a new separate
   buffer stacked on top of it. */
.proto3d-panel .proto3d-scroll__inner {
  padding: 24px;
  box-sizing: border-box;
}

/* reveal.js's own generic tier-2/tier-3 selectors (.section__heading/
   .section__lede, then bare h2/h3/p/img) match this section's
   headings, card titles/text and logo images too, tagging them with
   .reveal - a second, IntersectionObserver-driven opacity system that
   would fight the section's own scroll-distance-driven fades
   (prototype-3d.js/prototype-3d.css, unmodified).

   Excluded from the opacity/transition override entirely: the hub
   logo, the problem heading and the 3 problem cards all have their
   opacity set on an inline style every frame by prototype-3d.js, so
   excluding them here just lets that inline value through (inline
   wins over any non-!important class rule regardless of specificity)
   - no risk of getting stuck. The drag hint sets no inline style at
   all (only a class toggle, .is-visible, against its own CSS); its
   own .proto3d-hint.is-visible rule is lower specificity than reveal.js's
   html.js-reveal-ready .reveal--visible rule, so once reveal.js's
   observer fires (likely early, since the hint sits inside the always-
   near-viewport sticky stage) it could end up stuck fully visible
   instead of gated to the Aligned phase - a real, confirmed risk,
   excluded anyway per instruction.

   transform is left alone except on the two elements whose OWN
   positioning depends on an inline transform prototype-3d.js sets
   every frame (the hub logo's screen-position translate, the drag
   hint's centring translate) - forcing transform: none on those
   would override that inline style the same way. Scoped to this
   section only, rather than editing reveal.js's own exclusion list
   (out of scope for this change) - every other page's/section's
   .reveal behaviour is untouched. */
.proto3d-scroll .reveal:not(#proto3dProblemHeading):not(#proto3dProblemCard0):not(#proto3dProblemCard1):not(#proto3dProblemCard2):not(.proto3d-hub-logo):not(.proto3d-hint) {
  opacity: 1 !important;
  transition: none !important;
}

.proto3d-scroll .reveal:not(.proto3d-hub-logo):not(.proto3d-hint) {
  transform: none !important;
}

/* index.html only: the footer stays fully visible always, with no
   scroll fade-in - scoped to body.page-home (rather than editing
   reveal.js's own logic, which every other page sharing this footer
   markup also uses) so only this page's footer is affected. Covers
   every .reveal element inside the footer regardless of whether it was
   hand-added in the markup or auto-tagged by reveal.js's own generic
   fallback tier (e.g. the bare <p> copyright line) - the selector
   matches on the resulting class either way. !important overrides
   html.js-reveal-ready .reveal's hidden start state unconditionally,
   so nothing here is ever dependent on reveal.js running at all. */
body.page-home .footer .reveal {
  opacity: 1 !important;
  transform: none !important;
  transition: none !important;
}

/* Text column recentred in the black area between the screen's left
   edge and the map's left edge (not just the panel's own padding).
   .proto3d-text has no explicit width - it's a grid item that
   stretches to fill its 40% track - so with margin-right untouched,
   this negative margin-left both shifts its left edge out to the
   screen edge AND widens it by the same amount to compensate,
   leaving its right edge (and the stage, untouched) exactly where it
   was. Every child's own centring (margin: auto, prototype-3d.css,
   unmodified) is relative to this element's current width, so it
   re-centres automatically with no further change. */
.proto3d-panel .proto3d-text {
  margin-left: calc(-1 * (4vw + 24px));
}

/* Heading position (prototype-3d.css sets .proto3d-block's
   padding-top: 95px) - overridden here instead, scoped to this
   section, since that file isn't touched. */
.proto3d-panel .proto3d-block {
  padding-top: 55px;
}

/* No margin/padding existed on .proto3d-stage-col in either
   prototype-3d.css or styles.css (only position: relative; min-width:
   0;) - this is a new, scoped margin-right, not an override of an
   existing value. */
.proto3d-panel .proto3d-stage-col {
  margin-right: 60px;
}

/* Centres the drag hint on .proto3d-stage's own midpoint via left:0/
   right:0/margin:auto instead of prototype-3d.css's left:50%/
   translateX(-50%) - a different technique for the same result
   relative to the same containing block (.proto3d-stage), per
   request. Fade (opacity/class toggle), font, colour, size and
   bottom offset all untouched. */
.proto3d-panel .proto3d-hint {
  left: 0 !important;
  right: 0 !important;
  margin-left: auto !important;
  margin-right: auto !important;
  width: fit-content !important;
  transform: none !important;
}

/* ---------- Solutions pages (warehouses/3pls/factories) ----------
   Scoped to body.page-solutions (added to just these 3 pages' <body>)
   so no other page sharing this stylesheet is affected. Reuses the
   landing page's own --hero-divider-* variables and its .proto3d-panel
   technique (the line sitting flush at the very top, --color-surface
   background) rather than the unused .section--hero-divider class
   (whose own --hero-divider-top-offset buffer and --color-bg
   background are a different, older variant) - same values, no new
   ones invented, none of the 3D section's own scroll/pin/JS behaviour. */
body.page-solutions main > section:first-of-type + section {
  position: relative;
  overflow: clip;
  clip-path: inset(0 0 0 0 round var(--hero-divider-radius) var(--hero-divider-radius) 0 0);
}

body.page-solutions main > section:first-of-type + section::before {
  content: "";
  position: absolute;
  top: 0;
  left: var(--hero-divider-inset);
  right: var(--hero-divider-inset);
  height: calc(var(--hero-divider-radius) + var(--hero-divider-side-fade-length));
  border: 1px solid #fff;
  border-bottom: none;
  border-top-left-radius: var(--hero-divider-radius);
  border-top-right-radius: var(--hero-divider-radius);
  -webkit-mask-image: radial-gradient(ellipse 50% 100% at 50% 0%,
      rgba(0, 0, 0, var(--hero-divider-line-centre-opacity)) 0%,
      rgba(0, 0, 0, var(--hero-divider-line-corner-opacity)) 100%,
      transparent 142%);
  mask-image: radial-gradient(ellipse 50% 100% at 50% 0%,
      rgba(0, 0, 0, var(--hero-divider-line-centre-opacity)) 0%,
      rgba(0, 0, 0, var(--hero-divider-line-corner-opacity)) 100%,
      transparent 142%);
  pointer-events: none;
}

body.page-solutions main > section:first-of-type + section::after {
  content: "";
  position: absolute;
  top: 0;
  left: 50%;
  width: var(--hero-divider-glow-width);
  height: var(--hero-divider-glow-height);
  transform: translateX(-50%);
  background: radial-gradient(ellipse at top, rgba(255, 255, 255, var(--hero-divider-glow-opacity)) 0%, transparent 70%);
  pointer-events: none;
}

/* Everything from the panel edge down to the footer shares the same
   --color-surface background as the landing page's surface sections -
   the hero-equivalent (first) section is untouched, staying on the
   page's plain black. */
body.page-solutions main > section:not(:first-of-type),
body.page-solutions .footer {
  background: var(--color-surface);
}

/* Removes every full-width section divider below the panel edge, so
   that edge is the only line left on the page - same technique as
   body.page-home's own removal of .section--surface's border-top/
   bottom (these pages have no body.page-home class, so that existing
   rule doesn't reach them on its own). .footer's own separate
   border-top (independent of .section--surface) is removed the same
   way. Does not touch .nav's own border-bottom, .card's own border,
   or .footer__divider (internal footer furniture, not a section
   divider). */
body.page-solutions .section--surface {
  border-top: none;
  border-bottom: none;
}

body.page-solutions .footer {
  border-top: none;
}

/* Same headline-to-paragraph gap as the landing page's own closing
   section (#design-partner .cta-closing__button), applied one element
   later here (lede -> button) since these pages' button has no
   wrapping span of its own to target. Specificity (3 classes) beats
   the sitewide .section__lede+.btn rule (2 classes) it would otherwise
   collide with, so this fully replaces that 2rem gap here rather than
   adding to it. */
body.page-solutions .cta-closing .btn {
  margin-top: var(--heading-gap);
}

@media (max-width: 860px) {
  body.page-solutions .cta-closing .btn {
    margin-top: var(--heading-gap-mobile);
  }
}

/* ---------- Platform page ----------
   Scoped to body.page-platform (added to platform.html's <body> only)
   so no other page sharing these classes - including book.html's own
   .checklist - is affected. #platform-intro (black) and #hub-section
   (the diagram, already --color-surface) are deliberately left out of
   this selector, by request - they keep their current backgrounds
   exactly as they are. No panel edge/clip-path/line/glow added here,
   unlike the Solutions pages - background and border changes only. */
body.page-platform #hub-section ~ section,
body.page-platform .footer {
  background: var(--color-surface);
}

/* Removes every full-width section divider on this page, including
   the one between section 1 and #hub-section (its own border-top) -
   so there are no horizontal divider lines between sections at all.
   .footer's own separate border-top is removed the same way. Does not
   touch .nav's own border-bottom, the card/FAQ-row outlines, or the
   diagram's own edge-glow line (none of those are .section--surface
   or .footer). */
body.page-platform .section--surface {
  border-top: none;
  border-bottom: none;
}

body.page-platform .footer {
  border-top: none;
}

/* Same headline-to-paragraph gap as the landing page's and the
   Solutions pages' own closing sections. */
body.page-platform .cta-closing .btn {
  margin-top: var(--heading-gap);
}

@media (max-width: 860px) {
  body.page-platform .cta-closing .btn {
    margin-top: var(--heading-gap-mobile);
  }
}

/* ---------- Page-to-page fade transition (sitewide, unscoped on purpose)
   ----------
   Opts every same-origin navigation (links, back/forward) into the
   browser's native cross-document view transitions - no JS, nothing
   intercepts link clicks or delays navigation. Unsupported browsers
   just ignore this rule and navigate normally, with no transition and
   no breakage. */
@view-transition {
  navigation: auto;
}

/* The default root crossfade (old page fading out, new page fading
   in), on the page content only - 0.4s, ease-in-out, easy to see. */
::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: 0.4s;
  animation-timing-function: ease-in-out;
}

/* Nav bar gets its own named group so it's excluded from the root
   crossfade above and stays visually still across the navigation -
   .nav is a single, always-present element on every page, so this
   name is safe (unique) at every capture. No other element on the
   site gets a view-transition-name, including anything in or around
   the 3D section, so its pinning/sticky behaviour is never touched by
   any of this. */
.nav {
  view-transition-name: site-nav;
}

::view-transition-old(site-nav),
::view-transition-new(site-nav) {
  animation: none;
  mix-blend-mode: normal;
}

/* Same background on <html> as <body> (body already has --color-bg)
   so no lighter/darker flash can show through behind the crossfade -
   the view-transition pseudo-element tree sits above <html>, not just
   above <body>. Identical on every page: one shared --color-bg
   variable, never overridden per page. */
html {
  background: var(--color-bg);
}

/* Safety net for the one real cause of the "nav replaying its load
   animation" bug: html.is-page-transition (set by the pagereveal
   listener in every page's <head>, before nav.js runs) makes nav.js's
   own intro IIFE return immediately, so none of this ever actually
   gets set during a transitioned navigation - this is just a backstop
   for exactly those same elements, scoped narrowly to the logo/
   wordmark pieces only. Deliberately excludes .nav__links/.nav__actions:
   those also have a legitimate closed/open mobile-menu state at
   <=860px (opacity/max-height toggled by [data-menu-open]) that an
   !important override here would permanently break; nav.js's own
   early-return already covers them completely, with no gap. */
html.is-page-transition .nav__wordmark-text {
  clip-path: none !important;
}

html.is-page-transition .nav__wordmark img,
html.is-page-transition .nav__wordmark svg,
html.is-page-transition .nav__wordmark .logo-side--left,
html.is-page-transition .nav__wordmark .logo-side--right,
html.is-page-transition .nav__wordmark .logo-wave {
  opacity: 1 !important;
  transform: none !important;
}

/* Reduced motion: instant switch, no crossfade on the root or the nav. */
@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation: none !important;
  }
}
