/* ==========================================================================
   Site Support Group — light, editorial, precise.
   Porcelain surfaces, brand-navy ink, one cobalt accent. The Hub section is
   the page's single deliberate dark block: the product world, in contrast.
   Radius system: panels 14px, inputs 10px, buttons pill. No exceptions.
   ========================================================================== */

/* Design tokens (colour, type, radii, elevation, rhythm) now live in
   assets/brand.css, which every page loads BEFORE this file. It is the single
   source of truth shared by the homepage and the service pages — do not
   redeclare :root variables here. */

*, *::before, *::after { box-sizing: border-box; }

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

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

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: 1rem;
  line-height: 1.6;
  overflow-x: hidden;
}

img { max-width: 100%; height: auto; display: block; }

.svg-defs { position: absolute; width: 0; height: 0; overflow: hidden; }

.wrap {
  max-width: 1240px;
  margin-inline: auto;
  padding-inline: clamp(20px, 4vw, 48px);
}

section { padding-block: clamp(96px, 13vh, 170px); }
section[id] { scroll-margin-top: calc(var(--nav-h) + 8px); }

/* ---------- Type scale ---------- */

.h2, .h3, .h4, .hero__title {
  font-family: var(--font-display);
  margin: 0 0 0.5em;
  letter-spacing: -0.022em;
  text-wrap: balance;
}

.h2 { font-size: clamp(2.1rem, 4.2vw, 3.4rem); font-weight: 700; line-height: 1.04; }
.h3 { font-size: clamp(1.35rem, 2.4vw, 1.8rem); font-weight: 700; line-height: 1.14; }
.h4 { font-size: clamp(1.05rem, 1.5vw, 1.2rem); font-weight: 700; line-height: 1.3; letter-spacing: -0.01em; }

.lede {
  font-size: clamp(1.05rem, 1.5vw, 1.2rem);
  color: var(--ink-soft);
  max-width: 60ch;
  margin: 0 0 1.5em;
  line-height: 1.65;
}

p { margin: 0 0 1em; }
p:last-child { margin-bottom: 0; }

.eyebrow {
  font-size: 1.48rem;
  font-weight: 700;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--accent);
  margin: 0 0 1.2em;
}

::selection { background: rgba(8, 98, 244, 0.16); }

/* ---------- Focus & skip link ---------- */

:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 4px; }

.skip-link {
  position: fixed; top: -60px; left: 16px; z-index: 200;
  background: var(--ink); color: #fff; padding: 10px 18px;
  border-radius: 999px; text-decoration: none; transition: top 0.2s;
}
.skip-link:focus { top: 12px; }

/* ---------- Buttons ---------- */

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 10px;
  font-family: var(--font-body); font-weight: 600; font-size: 0.95rem;
  line-height: 1; white-space: nowrap; cursor: pointer;
  border-radius: 999px; border: 1px solid transparent;
  padding: 15px 30px; text-decoration: none;
  transition: transform 0.18s cubic-bezier(0.16, 1, 0.3, 1), background 0.18s, border-color 0.18s, color 0.18s, box-shadow 0.18s;
}
.btn:active { transform: translateY(1px) scale(0.99); }
.btn--lg { padding: 18px 36px; font-size: 1rem; }

.btn--primary {
  background: var(--accent); color: #fff;
  box-shadow: 0 10px 24px -10px rgba(8, 98, 244, 0.55);
}
.btn--primary:hover { background: var(--accent-deep); transform: translateY(-1px); }

.btn--ghost { background: transparent; color: var(--ink); border-color: var(--line-strong); }
.btn--ghost:hover { border-color: var(--ink); transform: translateY(-1px); }

/* Specular rim-light effect: a WebGL canvas bleeds past the button edge
   and draws a moving highlight that steers toward the cursor. */
.specular-btn { position: relative; isolation: isolate; }
.specular-btn__fx {
  position: absolute; inset: -20px;
  pointer-events: none; z-index: 0;
}
.specular-btn__fx canvas { display: block; }
.specular-btn__label { position: relative; z-index: 1; }

.btn--outline { background: transparent; color: var(--accent); border-color: rgba(8, 98, 244, 0.45); }
.btn--outline:hover { background: rgba(8, 98, 244, 0.07); transform: translateY(-1px); }

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

.nav {
  position: fixed; inset-inline: 0; top: 0; z-index: 100;
  transition: background 0.25s, border-color 0.25s;
  border-bottom: 1px solid transparent;
  /* White frosted glass at all times; the hero web renders behind it. */
  background: rgba(255, 255, 255, 0.62);
  backdrop-filter: blur(14px) saturate(1.5);
  -webkit-backdrop-filter: blur(14px) saturate(1.5);
}
.nav.scrolled {
  background: rgba(255, 255, 255, 0.78);
  border-bottom-color: var(--line);
}
/* While the mobile menu is open the nav goes solid (no backdrop-filter, so the
   fixed menu panel is never re-parented into a collapsed containing block). */
.nav.menu-open {
  background: rgba(250, 251, 253, 0.98);
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  border-bottom-color: var(--line);
}

.nav__inner {
  max-width: 1240px; margin-inline: auto; padding-inline: clamp(20px, 4vw, 48px);
  height: var(--nav-h);
  display: flex; align-items: center; gap: 32px;
}

.nav__brand {
  display: inline-flex; align-items: center; gap: 12px;
  text-decoration: none; color: var(--ink); flex-shrink: 0;
}
.nav__wordmark {
  /* Use the same local system stack for consistent, third-party-free loading. */
  font-family: var(--font-display); font-weight: 700; font-size: 1.07rem; letter-spacing: -0.01em;
}
.nav__wordmark em { font-style: normal; color: var(--accent); }

.nav__links { display: flex; align-items: center; gap: 28px; margin-left: auto; }
.nav__links > a, .nav__dropdown-trigger {
  color: var(--ink-soft); text-decoration: none; font-size: 0.92rem; font-weight: 500;
  transition: color 0.15s;
}
.nav__links > a:hover, .nav__dropdown-trigger:hover { color: var(--ink); }
/* The page you are on is marked in the nav and in the dropdown. */
.nav__links [aria-current="page"] { color: var(--ink); font-weight: 600; }

.nav__cta { padding: 11px 22px; font-size: 0.88rem; flex-shrink: 0; }

/* ---------- Services dropdown ----------
   The panel is never display:none — it is hidden with opacity/visibility so
   the six service links stay in the DOM, in reading order, for crawlers and
   for assistive tech. Opening is driven by :hover and :focus-within (so it
   works with CSS alone) and by .is-open (added by assets/site-nav.js for
   click and keyboard control). */

.nav__dropdown { position: relative; display: flex; align-items: center; }

.nav__dropdown-trigger {
  display: inline-flex; align-items: center; gap: 6px;
  background: none; border: 0; padding: 0; cursor: pointer;
  font-family: var(--font-body); line-height: inherit;
}
/* Caret. Rotates when open. */
.nav__dropdown-trigger::after {
  content: ""; width: 7px; height: 7px; margin-top: -3px;
  border-right: 1.6px solid currentColor; border-bottom: 1.6px solid currentColor;
  transform: rotate(45deg);
  transition: transform var(--t-base) var(--ease-settle);
}
.nav__dropdown:hover .nav__dropdown-trigger::after,
.nav__dropdown:focus-within .nav__dropdown-trigger::after,
.nav__dropdown.is-open .nav__dropdown-trigger::after {
  transform: rotate(225deg); margin-top: 2px;
}

.nav__dropdown-panel {
  position: absolute; top: calc(100% + 14px); left: 50%;
  transform: translate(-50%, -6px);
  min-width: 300px; padding: 10px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-panel);
  box-shadow: var(--shadow-panel);
  opacity: 0; visibility: hidden; pointer-events: none;
  transition: opacity var(--t-base), transform var(--t-base) var(--ease-settle), visibility var(--t-base);
  z-index: 10;
}
/* Invisible bridge across the gap, so the pointer can travel from the
   trigger to the panel without passing over a dead zone. */
.nav__dropdown-panel::before {
  content: ""; position: absolute; inset: -16px 0 100% 0;
}
.nav__dropdown:hover .nav__dropdown-panel,
.nav__dropdown:focus-within .nav__dropdown-panel,
.nav__dropdown.is-open .nav__dropdown-panel {
  opacity: 1; visibility: visible; pointer-events: auto;
  transform: translate(-50%, 0);
}

.nav__dropdown-panel a {
  display: flex; align-items: center; gap: 11px;
  padding: 9px 12px; border-radius: 9px;
  color: var(--ink); text-decoration: none;
  font-size: 0.9rem; font-weight: 600; line-height: 1.25;
  transition: background var(--t-fast), color var(--t-fast);
}
.nav__dropdown-panel a:hover { background: var(--bg-alt); }
/* Each item carries its service line's segment colour as a small pip, so the
   dropdown teaches the same colour mapping the mark and the cards use. */
.nav__dropdown-panel a::before {
  content: ""; flex: 0 0 auto; width: 8px; height: 8px; border-radius: 50%;
  background: var(--svc, var(--accent));
}
.nav__dropdown-panel hr {
  border: 0; border-top: 1px solid var(--line); margin: 8px 12px;
}
.nav__dropdown-panel .nav__dropdown-all {
  font-weight: 500; color: var(--ink-soft); font-size: 0.85rem;
}
.nav__dropdown-panel .nav__dropdown-all::before { background: transparent; }

/* Matches the width at which .nav__links (and therefore the trigger) is
   hidden — below this the mobile menu lists the services inline instead. */
@media (max-width: 1060px) {
  .nav__dropdown-panel { display: none; }
}

.nav__burger {
  display: none; margin-left: auto;
  background: none; border: 0; padding: 10px; cursor: pointer;
}
.nav__burger span {
  display: block; width: 22px; height: 2px; background: var(--ink);
  margin: 5px 0; border-radius: 2px; transition: transform 0.25s, opacity 0.25s;
}
.nav__burger[aria-expanded="true"] span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
.nav__burger[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
.nav__burger[aria-expanded="true"] span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }

/* "Explore ..." link at the foot of each service card, into its own page.
   Sits inside .svc-card__scene alongside the blurb and benefits, and inherits
   the card's --svc so it picks up that discipline's colour. */
/* .svc-card__link is gone. The "Explore …" line was the visible affordance
   back when only that text was clickable; now the whole card is the link and
   the label was just repeating the heading. The affordance moved to the card
   itself — see the hover lift below. */
/* The whole card is one link — .svc-card__hit wraps the header and the scene.
   A stretched ::after on this element could not work: it sits inside
   .svc-card__scene, which is position:relative, so the overlay resolved
   against the scene and left the card title unclickable. The "Explore …" text
   is now a <span> inside that anchor, kept purely as the visual affordance. */
.svc-card { position: relative; }
.svc-card__hit {
  flex: 1; min-height: 0;
  display: flex; flex-direction: column;
  color: inherit; text-decoration: none;
}
/* With the "Explore …" line removed, this lift is the only thing telling a
   pointer user the card is clickable, so it has to be perceptible — a shadow
   alone was not. Gated behind a fine pointer: on touch there is no hover, and
   the transform would only fire as a flicker on tap. */
@media (hover: hover) and (pointer: fine) {
  .svc-card {
    transition: transform var(--t-base) var(--ease-settle), box-shadow var(--t-base);
  }
  .svc-card:hover {
    transform: translateY(-3px);
    box-shadow: 0 1px 2px rgba(10, 37, 64, 0.05), 0 28px 60px -20px rgba(10, 37, 64, 0.3);
  }
}
@media (prefers-reduced-motion: reduce) {
  .svc-card, .svc-card:hover { transition: none; transform: none; }
}
.svc-card__hit:focus-visible { outline: 3px solid var(--accent); outline-offset: -3px; border-radius: var(--r-panel); }

.mobile-menu {
  position: fixed; inset: var(--nav-h) 0 0; z-index: 99;
  background: var(--bg);
  padding: 40px clamp(20px, 6vw, 48px);
  overflow-y: auto;
}
.mobile-menu nav { display: flex; flex-direction: column; gap: 8px; }
.mobile-menu a {
  color: var(--ink); text-decoration: none;
  font-family: var(--font-display); font-weight: 700; font-size: 1.6rem;
  padding: 12px 0;
}
.mobile-menu .btn { margin-top: 24px; font-family: var(--font-body); font-size: 1rem; align-self: flex-start; color: #fff; }

/* Services group: a disclosure row that expands the six service pages inline.
   Styled to match the sibling links exactly, so it reads as one list. */
.mobile-menu__toggle {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  width: 100%; padding: 12px 0;
  background: none; border: 0; cursor: pointer; text-align: left;
  color: var(--ink);
  font-family: var(--font-display); font-weight: 700; font-size: 1.6rem;
}
.mobile-menu__toggle::after {
  content: ""; flex: 0 0 auto; width: 10px; height: 10px; margin-right: 6px;
  border-right: 2px solid currentColor; border-bottom: 2px solid currentColor;
  transform: rotate(45deg) translate(-3px, -3px);
  transition: transform var(--t-base) var(--ease-settle);
}
.mobile-menu__toggle[aria-expanded="true"]::after { transform: rotate(225deg) translate(-3px, -3px); }

.mobile-menu__group {
  display: flex; flex-direction: column;
  margin: 0 0 8px; padding-left: 16px;
  border-left: 2px solid var(--line);
}
.mobile-menu__group a {
  font-family: var(--font-body); font-weight: 600; font-size: 1.05rem;
  color: var(--ink-soft); padding: 10px 0;
}
.mobile-menu__group a[aria-current="page"] { color: var(--accent); }

/* Must mirror the burger breakpoint above: if the mobile menu were still
   display:none here, pressing the burger in the 881-1060px band would toggle
   a panel that never appears. */
@media (min-width: 1061px) {
  .mobile-menu { display: none; }
}

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

.hero {
  position: relative;
  min-height: 100dvh;
  display: flex; align-items: center;
  padding-block: calc(var(--nav-h) + 24px) 64px;
  overflow: hidden;
  background: var(--bg);
}

.side-rays-container { position: absolute; inset: 0; width: 100%; height: 100%; z-index: 0; pointer-events: none; }
.side-rays-canvas { display: block; width: 100%; height: 100%; }

.hero__canvas { position: absolute; inset: 0; width: 100%; height: 100%; z-index: 1; }

/* Over the hero web the canvas draws the brand map pin as the cursor. */
@media (hover: hover) and (pointer: fine) and (prefers-reduced-motion: no-preference) {
  .hero { cursor: none; }
  .hero a, .hero button { cursor: pointer; }
  html.motion-paused .hero { cursor: auto; }
  html.no-js .hero { cursor: auto; }
}

.hero__grid { position: relative; z-index: 2; width: 100%; }

.hero__copy { max-width: 860px; }

.hero__title {
  font-size: clamp(2.7rem, 5vw, 4.3rem);
  font-weight: 700;
  line-height: 1.02;
  margin-bottom: 0.4em;
}
.hero__title .line { display: block; overflow: hidden; padding-bottom: 0.09em; margin-bottom: -0.09em; }
.hero__title .line > span { display: inline-block; }
.hero__title em { font-style: normal; color: var(--accent); }

.hero__sub {
  font-size: clamp(1.08rem, 1.6vw, 1.28rem);
  color: var(--ink);
  max-width: 54ch;
  margin-bottom: 1.2em;
  line-height: 1.6;
  margin-top: 0;
}

.hero__eyebrow {
  font-family: var(--font-body);
  text-transform: uppercase;
  letter-spacing: 0.14em;
  font-weight: 700;
  font-size: 0.8rem;
  color: var(--accent);
  margin: 0 0 0.9em;
}

/* Static, legible service chips (replaced the scrolling ticker so the list is
   readable at a glance and present in the HTML for crawlers). */
.hero__services {
  list-style: none;
  margin: 0 0 1.8em;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  /* Wider column gap than the pills needed: without a border, adjacent links
     need real space between them to read as separate items. */
  gap: 14px 28px;
  max-width: 62ch;
}
/* Bold text links, not chips.
   These were rounded glass pills, which made six navigable service lines look
   like inert tags — the pill shape reads as a filter or a dismissible label,
   so the one thing they did not look like was the links they now are. Set as
   bold text with a coloured rule underneath, each carrying its own segment
   colour from the mark, they read as a menu and they teach the segment/
   discipline mapping at the same time.

   The glass backing was there to guarantee contrast over the faint warehouse photo
   behind the hero. It is not needed: the h1 and the sub-paragraph already sit
   directly on that photo at --ink and read cleanly, and these are heavier. */
.hero__services li {
  font-family: var(--font-body);
  font-size: 0.95rem;
  line-height: 1.3;
}
.hero__services a {
  display: inline-block;
  font-weight: 700;
  color: var(--ink);
  text-decoration: none;
  padding-bottom: 2px;
  border-bottom: 2px solid var(--svc, var(--accent));
  transition: color var(--t-base), border-color var(--t-base);
}
.hero__services a:hover,
.hero__services a:focus-visible {
  color: var(--svc, var(--accent));
}

/* Faint automated-warehouse photo at the very back of the hero — beneath the
   glow rays (z-index 0) and the network-dot canvas (z-index 1), above the base
   gradient. First child of .hero so, at equal z-index, it paints under them. */
.hero__photo {
  position: absolute;
  inset: 0;
  z-index: 0;
  background: url('assets/photos/automated-warehouse.webp') center / cover no-repeat;
  opacity: 0.2;
  pointer-events: none;
}

.hero__closing {
  font-size: clamp(1.08rem, 1.6vw, 1.28rem);
  color: var(--ink);
  max-width: 54ch;
  line-height: 1.6;
  margin-top: 0;
  margin-bottom: 2.4em;
}

.hero__actions { display: flex; gap: 16px; flex-wrap: wrap; }

/* ---------- Model: how-it-works scroll story ----------
   CSS holds the FINISHED state (photo blurred back, every stage visible,
   arrows complete). GSAP rewinds from here, so no-JS and reduced-motion
   users simply see the completed diagram. */

/* The card is the only real content of this section now (the eyebrow
   lives inside .story, right above the card — see below), so the
   section's default top/bottom padding reads as far too much empty
   space on both sides — trimmed down specifically. The bottom padding
   is trimmed hard: the card is the visual end of this section, and the
   default padding-block bottom value stacked with .services's own top
   padding into a large dead gap between the two sections. */
.model { background: var(--surface); border-block: 1px solid var(--line); padding-top: clamp(24px, 3vh, 40px); padding-bottom: 0; }

/* margin-bottom only — .wrap's own margin-inline:auto (centring) must
   survive here, or the shorthand's implied margin-left/right:0 pins the
   whole block flush to the viewport edge instead of centring it like
   every other .wrap on the page. width:100% is required too now that
   this sits inside a flex column (.story): flex items with auto
   cross-axis margins shrink-to-content instead of stretching to fill
   the line, which left this centred as a narrow content-sized box
   instead of matching every other .wrap's "fill, then cap at 1240px"
   behaviour. */
.story__eyebrow-wrap { width: 100%; flex-shrink: 0; margin-top: 0; margin-bottom: 0; }

/* Pinned on desktop: one viewport tall. The eyebrow is the first flex
   child, sitting right in the nav-clearance padding that used to be
   blank space — .story's own top padding exists so nothing pinned sits
   under the fixed nav, and now that padding is exactly where "How it
   works" belongs, instead of the card being pushed an extra eyebrow's
   height further down. Since .story is what gets pinned (not a separate
   wrapper around the eyebrow + card), the eyebrow is on-screen for the
   whole pin duration by construction: it never scrolls away before the
   pin engages, because it IS part of what's pinned. */
.story {
  display: flex;
  flex-direction: column;
  gap: clamp(8px, 1.2vh, 14px);
  height: 100vh;
  height: 100svh;
  padding-top: calc(var(--nav-h) + 10px);
  padding-bottom: 16px;
  margin-top: clamp(8px, 1.2vh, 14px);
}

.story__card {
  position: relative;
  overflow: hidden;
  width: min(1520px, 100% - clamp(28px, 5vw, 88px));
  /* Anchored to the top of .story's box, not vertically centred in it:
     .story reserves a full 100vh for the desktop pin to work, and with
     the eyebrow now just one short line above it, centring the card left
     a large empty gap above it before any scrolling had happened. */
  margin: 0 auto;
  padding: clamp(24px, 2.8vw, 48px);
  background: var(--bg-alt);
  border: 1px solid var(--line);
  border-radius: var(--r-panel);
  box-shadow: var(--shadow-panel);
}

/* Desktop-pin-only: .story reserves a fixed 100vh for the pin to work,
   and the grid content's own natural height is shorter than that. Without
   this the leftover slack sits trapped below the card, inside the pinned
   box, showing up as extra dead space the instant the pin releases and
   the services section comes into view. Centring the (single,
   non-absolute) .story__grid child inside the now-taller card just means
   more of the photo shows above/below the flow content.
   Scoped to desktop only: on mobile .story has no fixed height to fill
   (height:auto, no pin), so this has no job to do there — and doing it
   anyway once broke the mobile grid, since making .story__card a flex
   container changed the sizing context .story__grid's "auto 1fr" row
   track resolves against. */
@media (min-width: 1061px) {
  .story__card {
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
    justify-content: center;
  }
}

.story__bg {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  object-position: center;
  /* Settled (post-intro-fade) resting state: kept lighter and less blurred
     than before so the photo stays clearly visible, not just a texture. */
  opacity: 0.7;
  filter: blur(8px);
  transform: scale(1.06); /* hides blur's soft edges */
  will-change: filter, opacity;
}
.story__veil {
  position: absolute; inset: 0;
  background: linear-gradient(180deg,
    rgba(250, 251, 253, 0.92) 0%,
    rgba(250, 251, 253, 0.55) 34%,
    rgba(250, 251, 253, 0.38) 100%);
  opacity: 0.85;
}

/* Overlays the photo at full strength while the card is first in view;
   fades out (in JS) to reveal the flow beneath it in the same screen
   space, rather than taking a separate block of vertical space above
   the card. */
.story__intro {
  position: absolute; inset: 0;
  z-index: 2;
  /* Top-anchored, not centred: text near the top of the card is visible
     the instant the card's top edge scrolls into view, well before a
     centred block (deep inside a tall card) would be. */
  display: flex; flex-direction: column; justify-content: flex-start;
  padding: clamp(24px, 2.8vw, 48px);
  pointer-events: none;
  /* The shared veil is tuned light, for the dark-ink text used everywhere
     else in the card; white intro text needs its own darker scrim behind
     it, layered on top of that veil, or it has nowhere near enough
     contrast against the photo. */
  background: linear-gradient(100deg, rgba(8, 20, 38, 0.62), rgba(8, 20, 38, 0.32) 65%, rgba(8, 20, 38, 0.18));
}
.story__intro .h2 { color: #fff; max-width: 16ch; }
.story__intro .lede { color: #fff; max-width: 52ch; margin-bottom: 0; opacity: 0.92; }

/* Reduced motion / no-JS: the card shows its finished state only — the
   intro overlay would otherwise sit frozen on top of the flow content
   it's meant to fade out and reveal. */
@media (prefers-reduced-motion: reduce) {
  .story__intro { display: none; }
}
html.no-js .story__intro { display: none; }

.story__lines {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  pointer-events: none;
}
.story__lines path { fill: none; stroke-linecap: round; }
.story__lines .story-arrow__head { stroke: none; }

/* Three columns, not four: site sits under step 1, hub sits under step 2,
   step 3 has no icon of its own (same as mobile) and shares its column
   with the services list it introduces. Site is pinned to the right edge
   of its column and hub is centred in the middle one, so the site->hub
   gap and the hub->services gap both resolve to exactly one column-gap,
   regardless of the ring and mark being different sizes. */
.story__grid {
  position: relative;
  display: grid;
  grid-template-columns: minmax(150px, 0.85fr) minmax(150px, 0.85fr) minmax(360px, 1.9fr);
  grid-template-rows: auto 1fr;
  grid-template-areas:
    "s1   s2   s3"
    "site hub  services";
  column-gap: clamp(18px, 2.6vw, 44px);
  row-gap: clamp(24px, 3.6vh, 44px);
}

.story__step--1 { grid-area: s1; }
.story__step--2 { grid-area: s2; }
.story__step--3 { grid-area: s3; }
.story__step .h4 { margin-bottom: 0.45em; }
.story__step p { color: var(--ink-soft); font-size: 0.92rem; line-height: 1.55; max-width: 44ch; }

.story__site {
  grid-area: site;
  /* Centred under "We Listen", same as hub under "We Assess" below. */
  align-self: center; justify-self: center;
  display: flex; flex-direction: column; align-items: center; gap: 12px;
}
.story__ring {
  width: clamp(56px, 5.2vw, 74px);
  aspect-ratio: 1;
  border-radius: 50%;
  overflow: hidden; /* clips the site photo to the circle */
  background: rgba(255, 255, 255, 0.88);
  border: 2.5px solid var(--accent);
  box-shadow: 0 12px 28px -14px rgba(10, 37, 64, 0.4);
}
.story__ring img { width: 100%; height: 100%; object-fit: cover; display: block; }
.story__node-label {
  font-weight: 600; font-size: 0.92rem; color: var(--ink);
  text-align: center; line-height: 1.3;
}

.story__hub {
  grid-area: hub;
  align-self: center; justify-self: center;
  display: flex; flex-direction: column; align-items: center; gap: 10px;
  text-align: center;
}
.story__hub-mark { width: clamp(92px, 8.6vw, 128px); height: auto; }

/* One vertical line of circles, title beside each rather than under it;
   the fan arrows all leave the hub from a single point and curve out to
   whichever row height each circle sits at. */
.story__services {
  grid-area: services;
  align-self: center;
  /* Shrinks to its widest row (longest title) and that block is centred
     under "We Act" as a whole — not each row individually, which is what
     was zig-zagging the circles left and right, since a title-only-length
     centred row shifts the circle with it. Circles all line up because
     every row now shares the same (shrink-wrapped) width and packs to
     its left edge. */
  justify-self: center;
  width: fit-content;
  /* Six rows stacked in one column is a lot of vertical real estate for a
     card pinned to a single viewport height: sized in vh, not a fixed px
     gap, so it compresses on shorter screens instead of pushing the card
     taller than the pin and getting clipped at the bottom. */
  list-style: none; margin: 0; padding: 0 0 clamp(12px, 1.6vh, 20px);
  display: flex; flex-direction: column;
  gap: clamp(8px, 1.5vh, 18px);
}
.story__svc {
  /* Markup order is title-then-photo (so screen readers hear the label
     first); row-reverse puts the circle on the left and the title on the
     right without needing to touch the DOM. flex-end (= left, reversed)
     packs every row to the same edge of the shared block width, so the
     circles align instead of drifting with each title's length. */
  display: flex; flex-direction: row-reverse;
  justify-content: flex-end;
  align-items: center;
  gap: clamp(14px, 1.6vw, 22px);
}
.story__svc-title {
  font-size: 0.92rem; font-weight: 600; line-height: 1.3;
  color: var(--ink); text-align: left; max-width: 22ch;
}
.story__svc-photo {
  flex-shrink: 0;
  width: clamp(42px, 4.4vh, 60px);
  height: auto; aspect-ratio: 1;
  border-radius: 50%;
  object-fit: cover;
  border: 3px solid var(--surface);
  box-shadow: 0 12px 26px -12px rgba(10, 37, 64, 0.4);
}

/* Short viewports: trim the card so the pinned frame always fits — the
   six-row services list is the tallest thing in it, so it gets the
   biggest cut. */
@media (min-width: 1061px) and (max-height: 840px) {
  .story__card { padding-block: 20px; }
  .story__grid { row-gap: 18px; }
  .story__step p { font-size: 0.86rem; max-width: 40ch; }
  .story__services { gap: 6px; padding-bottom: 10px; }
  .story__svc-photo { width: 40px; }
  .story__hub-mark { width: clamp(84px, 7.6vw, 108px); }
}

/* Below 1061px the story stacks and flows with the page (no pinning); the
   arrows snake down the card's side gutters. This flips earlier than the
   page's 880px collapse: between the two, the horizontal fan has no room. */
@media (max-width: 1060px) {
  .story { height: auto; padding-top: 0; padding-bottom: 0; }
  .story__card { width: min(680px, 100% - 40px); padding: 26px 20px 34px; }

  /* The intro is normal flow on mobile, not an overlay: it takes its own
     space at the top of the card, above the diagram grid, so the reader
     scrolls past it at their own pace — no scrub fading it away under
     them, and no possibility of it sitting on top of (or racing) the
     diagram reveals, because they no longer share any pixels. The
     negative margins bleed its scrim gradient out to the card edges,
     over the photo. relative + z-index, not static: the photo and veil
     layers are position:absolute, and an unpositioned block's background
     would paint underneath them. */
  .story__intro {
    position: relative;
    z-index: 2;
    inset: auto;
    margin: -26px -20px 18px;
    padding: 30px 20px 26px;
  }

  /* Nodes and their connecting line live in their own narrow column on the
     left; step text stays in a column of its own on the right, paired row
     for row: site beside "We Listen", hub beside "We Assess". "We Act" has
     no node of its own, so its row's left cell is left empty ("."), rather
     than let the text span into it — the connecting line can then run
     straight through that empty cell without any text in its way. */
  .story__grid {
    display: grid;
    grid-template-columns: clamp(84px, 24vw, 112px) 1fr;
    grid-template-areas:
      "site s1"
      "hub  s2"
      ".    s3"
      "svcs svcs";
    column-gap: clamp(14px, 4vw, 26px);
    row-gap: clamp(22px, 4vh, 34px);
  }
  .story__step--1 { grid-area: s1; align-self: center; }
  .story__step--2 { grid-area: s2; align-self: center; }
  .story__step--3 { grid-area: s3; }
  .story__site { grid-area: site; align-self: center; justify-self: center; }
  .story__hub  { grid-area: hub; align-self: center; justify-self: center; }
  .story__services { grid-area: svcs; }

  .story__step { width: 100%; padding-inline: 0; }
  .story__step p { max-width: 40ch; }
  .story__ring { width: clamp(48px, 13vw, 62px); }
  .story__hub-mark { width: clamp(70px, 20vw, 92px); }
  /* Full width and left-justified (not the desktop shrink-wrap-and-centre),
     with a reserved left gutter: the fan lines from the hub run down
     parallel "lanes" in that gutter (see storyBuild's mobile branch)
     before peeling off into their circles, so it must cover the
     outermost lane — LANE_BASE (18px) + 5 lane-gaps (7px each) + the
     6px line width — or that lane draws off the card edge. */
  .story__services {
    width: 100%; justify-self: stretch;
    padding: 0 0 56px clamp(52px, 15vw, 60px);
    gap: clamp(14px, 2.2vh, 22px);
  }
  .story__svc-photo { width: clamp(56px, 16vw, 70px); }
  .story__svc-title { font-size: 1.08rem; }
}

/* True phone widths: the card goes edge-to-edge, no side margin at all.
   Must come after the max-width:1060px block above so it wins the width
   cascade at the same specificity. */
@media (max-width: 640px) {
  .story__card { width: 100%; border-radius: 0; border-left: none; border-right: none; }
}

/* ---------- Services ---------- */

/* Top padding trimmed hard to match .model's own trimmed bottom padding —
   together they're the only gap between the how-it-works card and this
   section's eyebrow, replacing the ~200px+ dead zone the two sections'
   untouched default padding used to stack into. Bottom keeps the normal
   section rhythm. */
.services { background: var(--bg); padding-top: clamp(28px, 4vh, 48px); }

/* Own box model (not nested in a .wrap) so it can be a direct child of
   .services: on desktop it's sticky, and a sticky element only sticks for
   as long as its OWN containing block — its parent — stays on screen. If
   it were nested inside .services__head (a short block), it would unstick
   the moment that block scrolled past, long before the card stack below
   it finishes. As a direct child of the whole section it stays pinned,
   just under the nav, for the full scroll through .stack. */
.services__eyebrow {
  max-width: 1240px;
  margin-inline: auto;
  padding-inline: clamp(20px, 4vw, 48px);
}
@media (min-width: 1061px) {
  .services__eyebrow {
    position: sticky;
    top: var(--nav-h);
    z-index: 6;
    background: var(--bg);
    padding-block: 14px 6px;
  }
  /* Trailing margin is meant for spacing before an H2 that immediately
     follows in flow (every other section's eyebrow); here it would just
     be dead space padding out the sticky bar itself. Same element has
     both classes, so this overrides .eyebrow's margin directly. */
  .services__eyebrow { margin-bottom: 0; }
}

/* No max-width here: .lede already caps its own line-length (60ch), and
   constraining the wrapper itself re-centred the whole block narrower
   than the standard .wrap, misaligning it from every other section's
   left edge (including "How it works" directly above it). */
.services__head { margin-bottom: clamp(40px, 6vh, 72px); }

/* Two up once there is room for it. At full width a single column made each
   card enormous on a wide monitor — the photo scene alone ran most of the
   viewport height, so one service filled the screen and the set read as six
   separate pages rather than one menu. Two columns halves the card width and
   lets a pair be compared at a glance.

   1180px, not a round breakpoint: below it a half-width card is narrower than
   the scene's 380px min-height can carry gracefully and the blurb panel starts
   crowding the benefits panel opposite it. */
.stack { display: grid; gap: 22px; }
@media (min-width: 1180px) {
  .stack { grid-template-columns: repeat(2, minmax(0, 1fr)); align-items: start; }
}

.svc-card {
  --seg-dim: rgba(10, 37, 64, 0.1);
  --seg-tl: var(--seg-dim); --seg-tr: var(--seg-dim); --seg-r: var(--seg-dim);
  --seg-br: var(--seg-dim); --seg-bl: var(--seg-dim); --seg-l: var(--seg-dim);
  --mark-pin: rgba(10, 37, 64, 0.16);
  --mark-pin-dot: var(--svc);

  /* Static at every width — the old desktop sticky-stack (cards docking
     under the nav while the next one slid over) is gone in favour of the
     in-card scroll animations (stores laser, sourcing vehicles), which
     read best when the card scrolls past at normal speed like any other
     content. */
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-panel);
  display: flex;
  flex-direction: column;
  min-height: min(66vh, 600px);
  box-shadow: var(--shadow-panel);
  overflow: hidden;
}
.svc-card::before {
  content: ""; position: absolute; inset: 0 auto auto 0; width: 100%; height: 3px;
  background: linear-gradient(90deg, var(--svc), transparent 62%);
}

/* Per lit segment: the card colour, a gentle brightness/saturation lift
   (no spatial filters — drop-shadows clip to a visible rectangular filter
   region), a touch of translucency so the backlight shows through the
   "glass", and the backlight's centre position (--glow-x/y place the
   radial pool under that card's lit segment; the coordinates are the
   segment centroids mapped into the 116px ::before block). */
/* Per lit segment: colour, brightness/saturation lift, a slight scale-up
   (--lift, applied to the whole segment group about its own centroid) and
   a soft drop-shadow (--liftsh) so it sits raised ABOVE the other, flat
   segments — combined with the whole-mark tilt, the lit segment reads as
   the nearest, most-lifted facet. */
.svc-card[data-seg="tl"] { --seg-tl: var(--svc); --glow-tl: brightness(1.08) saturate(1.6); --glass-tl: 1; --gem-tl: 1; --glow-x: 39.5%; --glow-y: 32.5%; --lift-tl: scale(1.09); --liftsh-tl: drop-shadow(0 2px 2.5px rgba(6, 20, 45, 0.21)); }
.svc-card[data-seg="tr"] { --seg-tr: var(--svc); --glow-tr: brightness(1.08) saturate(1.6); --glass-tr: 1; --gem-tr: 1; --glow-x: 60.5%; --glow-y: 32.5%; --lift-tr: scale(1.09); --liftsh-tr: drop-shadow(0 2px 2.5px rgba(6, 20, 45, 0.21)); }
.svc-card[data-seg="r"]  { --seg-r: var(--svc); --glow-r: brightness(1.08) saturate(1.6); --glass-r: 1; --gem-r: 1; --glow-x: 69%; --glow-y: 50%; --lift-r: scale(1.09); --liftsh-r: drop-shadow(0 2px 2.5px rgba(6, 20, 45, 0.21)); }
.svc-card[data-seg="br"] { --seg-br: var(--svc); --glow-br: brightness(1.08) saturate(1.6); --glass-br: 1; --gem-br: 1; --glow-x: 60.5%; --glow-y: 67.5%; --lift-br: scale(1.09); --liftsh-br: drop-shadow(0 2px 2.5px rgba(6, 20, 45, 0.21)); }
.svc-card[data-seg="bl"] { --seg-bl: var(--svc); --glow-bl: brightness(1.08) saturate(1.6); --glass-bl: 1; --gem-bl: 1; --glow-x: 39.5%; --glow-y: 67.5%; --lift-bl: scale(1.09); --liftsh-bl: drop-shadow(0 2px 2.5px rgba(6, 20, 45, 0.21)); }
.svc-card[data-seg="l"]  { --seg-l: var(--svc); --glow-l: brightness(1.08) saturate(1.6); --glass-l: 1; --gem-l: 1; --glow-x: 31%; --glow-y: 50%; --lift-l: scale(1.09); --liftsh-l: drop-shadow(0 2px 2.5px rgba(6, 20, 45, 0.21)); }

/* White header strip: lit segment mark and title side by side. */
.svc-card__head {
  /* gap compensates for the mark's visual upscale (below) so the title
     keeps the same breathing room from the enlarged glyph. relative for
     the ::before backlight layer. */
  position: relative;
  display: flex; align-items: center; gap: 28px;
  padding: 20px clamp(22px, 3vw, 34px);
  background: #fff;
  border-bottom: 1px solid var(--line);
}
/* The "lit from behind" light source: a soft radial pool of the card's
   colour painted BEHIND the mark (generated first, so the svg paints on
   top of it), centred under the card's lit segment via --glow-x/--glow-y.
   Because the mark's segment gaps, corner notches and the well around the
   pin are genuinely transparent, this light shows through all of them —
   and through the lit segment itself, which is made slightly translucent
   (fill-opacity hook in the symbol) so it reads as glass with the light
   shining through rather than an opaque chip with a halo stamped on. */
.svc-card__head::before {
  content: "";
  position: absolute;
  left: calc(clamp(22px, 3vw, 34px) + 26px);
  top: 50%;
  width: 116px;
  height: 116px;
  transform: translate(-50%, -50%);
  pointer-events: none;
  /* Tight rim of light only — the lit segment is ~22px wide, so a 14px
     pool barely clears its edges: enough to leak through the transparent
     gaps immediately around it and no more. The gem look itself lives
     INSIDE the segment (drifting blobs + facet overlay in the symbol). */
  background: radial-gradient(11px circle at var(--glow-x, 50%) var(--glow-y, 50%),
    color-mix(in srgb, var(--svc) 40%, transparent),
    color-mix(in srgb, var(--svc) 14%, transparent) 62%,
    transparent 100%);
}
/* Scaled visually, not in layout: transform doesn't change the flex item's
   52x55 box, so the header keeps exactly the same height — the larger glyph
   just draws past its own box into the header's padding. overflow visible
   so the upscale isn't cropped at the svg viewport. Kept to a plain 2D
   scale (no 3D transform, no CSS filter on the <svg>): either of those
   forces the browser to rasterise the SVG to a bitmap and magnify it,
   softening every edge — a 2D scale re-renders the vectors crisply. The
   subtle whole-mark lift shadow is done inside the SVG (filter
   #mark-shadow) for the same reason. */
/* Wash of the card's own colour, sweeping in from the right and fading to
   plain white behind the mark.

   Deliberately not a pattern. A honeycomb texture was tried here and rejected:
   at this size any repeating motif reads as decoration stuck on top of the
   header rather than as part of it. Colour and depth do the same job of
   stopping the strip looking empty, and they do something a texture cannot —
   each of the six headers picks up its own segment colour, so the cards stop
   being six identical white bars.

   Held off the left so the mark keeps a clean field: its backlight (::before)
   works by leaking through the transparent gaps in the glyph, and a tint
   running underneath would dirty it. z-index 0 sits it below the mark and
   title, which take their own layer above. */
.svc-card__head::after {
  content: "";
  position: absolute; inset: 0; z-index: 0;
  pointer-events: none;
  background:
    radial-gradient(120% 180% at 100% 60%, color-mix(in srgb, var(--svc) 16%, transparent), transparent 62%),
    linear-gradient(100deg, transparent 34%, color-mix(in srgb, var(--svc) 9%, transparent) 100%);
}

.svc-card__hex {
  flex-shrink: 0;
  overflow: visible;
  transform-origin: center;
  transform: scale(1.44);
  position: relative; z-index: 1;
}
.svc-card__head .h3 { margin: 0; position: relative; z-index: 1; }
/* Each segment's art is wrapped in a .seg-lift group; view-box transform-box
   makes its inline transform-origin resolve in the mark's 0-80/0-84 viewBox
   space, so the lit segment scales up about its own centroid. */
.seg-lift { transform-box: view-box; }

/* Edge glint removed: the travelling blurred light streak read as shimmer
   and, on a ~75px mark at a distance, as pixellation. The lit segment now
   keeps only its STATIC glass treatment — the flat brand-colour facet, the
   backlight glowing through the transparent gaps around it, and the bevel
   rim below. The .seg-glint paths stay in the symbol markup but are never
   drawn. */
.seg-glint { display: none; }

/* Photorealistic scene fills the rest of the card. The photo layer sits on
   a brand-tinted gradient, so the card still works before the photo file
   for a card exists (a missing background-image layer paints nothing). */
.svc-card__scene {
  position: relative; flex: 1;
  display: flex; flex-direction: column;
  justify-content: space-between; align-items: flex-start;
  gap: 20px;
  padding: clamp(22px, 3.4vw, 40px);
  min-height: 380px;
  background-image:
    var(--photo, none),
    radial-gradient(900px 420px at 18% 0%, var(--svc-soft), transparent 62%),
    linear-gradient(155deg, #EFF4FA, #DFE9F4);
  background-size: cover, auto, auto;
  background-position: center, center, center;
}

/* Scroll-scrubbed "filmstrip" card (Maintenance): a <canvas> overlay draws
   the current frame from a 6x6 sprite sheet as the card scrolls (see
   main.js). Drawing on canvas — rather than a CSS background — lets JS
   apply true cover-fit maths, so each 16:9 frame keeps its ratio and crops
   to fill at any card width (wide desktop crops top/bottom, narrow mobile
   crops left/right) instead of stretching to the scene's box. The scene's
   own --photo (maintenance.jpg) stays underneath as the no-JS / reduced-
   motion fallback; the vignette and text panels layer above the canvas. */
.svc-card__film {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  display: block;
  z-index: 0;
  pointer-events: none;
}
.svc-card--scrub .svc-card__scene::before { z-index: 1; }
.svc-card--scrub .svc-card__points { position: relative; z-index: 2; }

/* Scrim, not a vignette. This used to be a faint darkening because the copy
   had its own glass panels to sit on; now the text sits directly on the
   photograph and the gradient is the only thing making it readable. Weighted
   to the top-left where the points are, and held off the bottom-right so the
   photographs still read as photographs. */
.svc-card__scene::before {
  content: ""; position: absolute; inset: 0; z-index: 1;
  background:
    linear-gradient(160deg, rgba(4, 12, 24, 0.74) 0%, rgba(4, 12, 24, 0.55) 38%, rgba(4, 12, 24, 0.18) 72%, rgba(4, 12, 24, 0.28) 100%),
    linear-gradient(180deg, rgba(4, 12, 24, 0.30), transparent 45%);
  pointer-events: none;
}

/* Compliance card: a soft green "audit sweep" band travels down the permit
   board as the card scrolls (main.js sets --audit-y 0..1 and fades it at the
   ends). Reads as an inspection/approval pass over the permits — eye-catching
   but subtle. One gradient element, GPU transform only, so it's very light;
   hidden by default (opacity 0) for no-JS / reduced-motion. z-index 1 sits it
   above the vignette (kept bright); the text panels layer above it. */
/* Audit sweep: a box clipped to the permit clipboards (positioned by main.js
   through the photo's cover-crop maths so it tracks the permits at any card
   width) with a soft vertical bar swept left->right across it. The permit
   region's top maps above the scene (the wide scene crops the photo top), so
   the box top goes negative — clip the scene so the green never bleeds up into
   the white header strip. */
.svc-card--auditscan .svc-card__scene { overflow: hidden; }
.svc-card__auditscan {
  position: absolute;
  left: 0; top: 0; width: 0; height: 0;   /* box set by main.js */
  z-index: 1;
  overflow: hidden;
  opacity: 0;
  pointer-events: none;
}
.svc-card__auditscan-bar {
  position: absolute;
  top: 0; bottom: 0; left: 0;
  width: 20%;                              /* px width set by main.js */
  transform: translateX(0);                /* px sweep set by main.js */
  background: linear-gradient(90deg,
    rgba(96, 244, 168, 0) 0%,
    rgba(110, 248, 176, 0.62) 50%,
    rgba(96, 244, 168, 0) 100%);
  mix-blend-mode: screen;
  will-change: transform;
}
.svc-card--auditscan .svc-card__points { position: relative; z-index: 2; }

/* Resourcing card: the photo rides its own layer that scroll gently pushes
   in — a slow "walk down the aisle" dolly toward the vanishing point (main.js
   sets --walk 0..1). Pure scale (always >= 1.03) so no edge is ever exposed;
   GPU transform only, so it's very light. background cover keeps the 16:9
   photo's ratio and crops to fill at any card width. */
.svc-card--parallax .svc-card__scene { overflow: hidden; }
.svc-card__parallax {
  position: absolute; inset: 0;
  z-index: 0;
  background-image: var(--photo);
  background-size: cover;
  background-position: center;
  transform: scale(calc(1.03 + var(--walk, 0) * 0.12));
  transform-origin: 56% 44%;
  will-change: transform;
}
.svc-card--parallax .svc-card__scene::before { z-index: 1; }
.svc-card--parallax .svc-card__points { position: relative; z-index: 2; }

/* Training card: the photo rides its own layer that scroll pans slowly
   sideways — a gentle lateral camera move across the training scene (main.js
   sets --pan 0..1). Held zoomed to 1.12 so the pan never exposes an edge, and
   centred at --pan 0.5 so the card sits neutral when it's mid-viewport. GPU
   transform only — as light as the Resourcing walk, just a different axis so
   the two cards read distinctly. */
.svc-card--drift .svc-card__scene { overflow: hidden; }
.svc-card__drift {
  position: absolute; inset: 0;
  z-index: 0;
  background-image: var(--photo);
  background-size: cover;
  background-position: center;
  transform: scale(1.14) translateX(calc((var(--pan, 0.5) - 0.5) * -9%));
  transform-origin: 50% 50%;
  will-change: transform;
}
.svc-card--drift .svc-card__scene::before { z-index: 1; }
.svc-card--drift .svc-card__points { position: relative; z-index: 2; }

/* Red laser scan line over the stores photo — a hot white-red core with a
   soft glow, blended so it reads as light on the photograph rather than a
   painted stripe. Its left/top/height are driven entirely from main.js
   (position scrubbed to scroll, mapped to the photo's barcode row through
   the background's own cover-crop maths). Hidden by default so no-JS and
   reduced-motion pages never show a frozen mid-scan line; sits before the
   text panels in the DOM so they keep painting above it. */
/* The sourcing map's vehicles: positioned/rotated/sized entirely from
   main.js, riding the printed dotted routes with scroll. Hidden until
   first positioned, so no-JS and reduced-motion pages just show the
   clean map, and there's no flash at (0,0) before the first apply. */
.svc-card__vehicle {
  position: absolute;
  top: 0; left: 0;
  height: auto;
  opacity: 0;
  pointer-events: none;
  will-change: transform;
}

.svc-card__laser {
  position: absolute;
  top: 0; left: 0;
  width: 3px; height: 40%;
  opacity: 0;
  transform: translateX(-50%);
  pointer-events: none;
  border-radius: 2px;
  background: linear-gradient(180deg,
    rgba(255, 70, 70, 0),
    rgba(255, 82, 82, 0.9) 14%,
    rgba(255, 214, 214, 1) 50%,
    rgba(255, 82, 82, 0.9) 86%,
    rgba(255, 70, 70, 0));
  box-shadow:
    0 0 6px 1px rgba(255, 46, 46, 0.8),
    0 0 22px 6px rgba(255, 0, 0, 0.35);
  mix-blend-mode: screen;
}

/* Floating glass text boxes over the photograph. */
/* .svc-card__blurb is gone — its selling points were folded into the four
   lines of .svc-card__points above. */

/* Four points, straight on the photograph.
   The card used to carry a paragraph in one glass panel and three bullets in
   another. Two floating boxes over an image is a lot of chrome for what is
   really a four-line claim, and the panels covered the photograph they were
   sitting on. The copy is now distilled to four short bold lines with nothing
   behind them but the picture and its scrim.

   Legibility comes from the scene scrim (see .svc-card__scene::before), which
   is a gradient over the whole image rather than a box behind the text — the
   photographs vary from pale warehouse racking to dark blue map, so the ramp
   has to be strong enough for the worst of them. */
.svc-card__points {
  position: relative; z-index: 2;
  list-style: none; margin: 0; padding: 0;
  align-self: flex-start;
  display: flex; flex-direction: column; gap: clamp(10px, 1.4vw, 15px);
  max-width: 30ch;
}
.svc-card__points li {
  font-family: var(--font-display);
  font-size: clamp(1.02rem, 1.35vw, 1.22rem);
  font-weight: 700; line-height: 1.25;
  letter-spacing: -0.012em;
  color: #fff;
  /* A tight shadow rather than a plate: keeps the type readable over a light
     patch without drawing a shape around it. */
  text-shadow: 0 1px 2px rgba(4, 12, 24, 0.55), 0 6px 22px rgba(4, 12, 24, 0.45);
}

/* ---------- Hub: the page's one deliberate dark block ---------- */

.hub {
  position: relative;
  background:
    radial-gradient(1100px 520px at 50% -8%, rgba(8, 98, 244, 0.22), transparent 65%),
    var(--hub-bg);
  color: var(--hub-text);
}

.hub .eyebrow { color: #7DAEFF; }
.hub .lede { color: var(--hub-dim); }

.marquee {
  margin-block: clamp(44px, 6vh, 72px);
  border-block: 1px solid rgba(234, 241, 250, 0.14);
  padding-block: 22px;
  overflow: hidden;
}
.marquee__track { display: flex; width: max-content; animation: marquee 46s linear infinite; }
.marquee__group { display: flex; align-items: center; gap: 34px; padding-right: 34px; }
.marquee__group span {
  font-family: var(--font-display); font-weight: 600;
  font-size: clamp(1.3rem, 2.6vw, 2rem);
  color: rgba(234, 241, 250, 0.9);
  white-space: nowrap;
}
.marquee__group i {
  width: 12px; height: 13px; flex-shrink: 0;
  clip-path: polygon(50% 0, 100% 25%, 100% 75%, 50% 100%, 0 75%, 0 25%);
  background: rgba(125, 174, 255, 0.7);
}
@keyframes marquee { to { transform: translateX(-50%); } }

.hub__collage {
  position: relative;
  height: clamp(430px, 46vw, 640px);
  margin-block: clamp(30px, 5vh, 60px);
}
.hub__shot {
  position: absolute; margin: 0;
  border-radius: var(--r-panel);
  border: 1px solid rgba(234, 241, 250, 0.16);
  overflow: hidden;
  box-shadow: 0 40px 90px -20px rgba(0, 0, 0, 0.65);
  background: #0B1626;
}
.hub__shot img { width: 100%; height: 100%; object-fit: cover; }
.hub__shot--map { width: 62%; left: 2%; top: 4%; z-index: 2; aspect-ratio: 2544 / 1277; }
.hub__shot--mobile { width: 21%; right: 6%; top: 10%; z-index: 3; aspect-ratio: 941 / 1672; }
.hub__shot--inventory { width: 47%; left: 30%; bottom: 2%; z-index: 1; aspect-ratio: 2500 / 1219; opacity: 0.94; }

/* "Coming soon" watermark across the whole collage. A light scrim reads the
   screenshots as a locked preview and lifts the white type off the busy,
   partly-light shots; the word is stamped full-width on the diagonal. Sits
   above every shot (their max z-index is 3) and never intercepts clicks. */
.hub__soon {
  position: absolute; inset: 0;
  z-index: 5;
  display: grid; place-items: center;
  overflow: hidden;
  pointer-events: none;
  background: rgba(5, 11, 22, 0.42);
  border-radius: var(--r-panel);
}
.hub__soon span {
  font-family: var(--font-display);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  white-space: nowrap;
  color: #fff;
  font-size: clamp(2.3rem, 10vw, 8rem);
  line-height: 1;
  transform: rotate(-15deg);
  text-shadow: 0 12px 44px rgba(0, 0, 0, 0.6), 0 3px 10px rgba(0, 0, 0, 0.55);
}

/* ===== Site Support Hub slide-in promo (see hub-promo.js) =====
   A small dismissible card in the bottom corner. Dark, to stand apart from the
   light page, and it links out in a new tab — no iframe, no window.open. */
.hub-promo {
  position: fixed;
  right: clamp(14px, 3vw, 28px);
  bottom: clamp(14px, 3vw, 28px);
  z-index: 1000;
  width: min(360px, calc(100vw - 28px));
  display: flex;
  gap: 14px;
  padding: 18px 20px;
  background: #0B1626;
  color: #e7eef8;
  border: 1px solid rgba(234, 241, 250, 0.14);
  border-radius: 16px;
  box-shadow: 0 24px 60px -18px rgba(6, 20, 45, 0.55);
  transform: translateY(150%);
  opacity: 0;
  transition: transform 0.5s cubic-bezier(.2, .9, .3, 1.1), opacity 0.5s ease;
}
.hub-promo.is-open { transform: none; opacity: 1; }
.hub-promo[hidden] { display: none; }
.hub-promo__mark { width: 44px; height: 48px; flex-shrink: 0; }
.hub-promo__body { min-width: 0; }
.hub-promo__title { margin: 0 0 4px; font-family: var(--font-display); font-weight: 700; font-size: 1.05rem; color: #fff; }
.hub-promo__text { margin: 0 0 10px; font-size: 0.9rem; line-height: 1.45; color: #b8c6db; }
.hub-promo__cta {
  display: inline-flex; align-items: center; gap: 4px;
  font-family: var(--font-body); font-weight: 700; font-size: 0.9rem;
  color: #7DAEFF; text-decoration: none;
}
.hub-promo__cta:hover { color: #fff; }
.hub-promo__close {
  position: absolute; top: 8px; right: 10px;
  width: 26px; height: 26px;
  display: grid; place-items: center;
  border: 0; border-radius: 7px;
  background: transparent; color: #8ea1bd;
  font-size: 1.3rem; line-height: 1; cursor: pointer;
}
.hub-promo__close:hover { background: rgba(255, 255, 255, 0.08); color: #fff; }
@media (prefers-reduced-motion: reduce) {
  .hub-promo { transform: none; transition: opacity 0.3s ease; }
}

.hub__features {
  display: grid; grid-template-columns: repeat(4, 1fr);
  gap: clamp(24px, 3vw, 40px);
  margin-block: clamp(30px, 5vh, 60px);
}
.hub__feature { border-top: 1px solid rgba(234, 241, 250, 0.2); padding-top: 20px; }
.hub__feature p { color: var(--hub-dim); font-size: 0.94rem; }

.hub__cta {
  display: flex; align-items: center; justify-content: space-between;
  gap: clamp(24px, 4vw, 64px);
  background: linear-gradient(130deg, rgba(8, 98, 244, 0.22), rgba(2, 178, 165, 0.12));
  border: 1px solid rgba(125, 174, 255, 0.3);
  border-radius: var(--r-panel);
  padding: clamp(30px, 4.5vw, 56px);
}
.hub__cta p { color: var(--hub-dim); max-width: 52ch; }
.hub__cta .btn { flex-shrink: 0; }

/* ---------- Track record ---------- */

.record { background: var(--surface); }

.record__grid {
  display: grid; grid-template-columns: 1.05fr 0.95fr;
  gap: clamp(40px, 6vw, 96px);
  align-items: start;
}

.record__proof { list-style: none; margin: 2em 0 0; padding: 0; display: grid; gap: 16px; }
.record__proof li { position: relative; padding-left: 30px; }
.record__proof li::before {
  content: ""; position: absolute; left: 0; top: 0.42em;
  width: 13px; height: 14px;
  clip-path: polygon(50% 0, 100% 25%, 100% 75%, 50% 100%, 0 75%, 0 25%);
  background: var(--accent);
}

.record__experience {
  background: var(--bg-alt);
  border: 1px solid var(--line);
  border-radius: var(--r-panel);
  padding: clamp(28px, 3vw, 40px);
}
.record__experience .h4 { margin-bottom: 1em; }
.record__experience .record__proof { margin-top: 0; }

.record__cta {
  max-width: 62ch;
  margin: clamp(32px, 5vh, 56px) 0 0;
  color: var(--ink-soft);
  font-size: 1.05rem;
}

/* ---------- Split CTA ---------- */

.split { padding-top: 0; background: var(--bg); }

.split__grid { display: grid; grid-template-columns: 1fr 1fr; gap: 22px; }

.split__panel {
  border-radius: var(--r-panel);
  padding: clamp(30px, 4vw, 56px);
  display: flex; flex-direction: column; align-items: flex-start; gap: 8px;
}
.split__panel p { margin-bottom: 1.4em; }
.split__panel--client { background: var(--ink); color: #fff; }
.split__panel--client p { color: rgba(255, 255, 255, 0.78); }
.split__panel--partner { background: var(--surface); border: 1px solid var(--line); box-shadow: var(--shadow-panel); }
.split__panel--partner p { color: var(--ink-soft); }
.split__panel .btn { margin-top: auto; }

/* ---------- Contact ---------- */

.contact { background: var(--bg-alt); border-top: 1px solid var(--line); }

.contact__grid {
  display: grid; grid-template-columns: 0.9fr 1.1fr;
  gap: clamp(40px, 6vw, 96px);
  align-items: start;
}

.contact__details { margin: 2.4em 0 0; display: grid; gap: 22px; }
.contact__details dt {
  font-size: 0.72rem; font-weight: 700; letter-spacing: 0.16em;
  text-transform: uppercase; color: var(--ink-soft); margin-bottom: 6px;
}
.contact__details dd { margin: 0; color: var(--ink); line-height: 1.55; }

.contact__form {
  display: grid; grid-template-columns: 1fr 1fr; gap: 20px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-panel);
  padding: clamp(26px, 3.4vw, 44px);
  box-shadow: var(--shadow-panel);
}

.field { grid-column: span 2; display: grid; gap: 8px; }
.field--half { grid-column: span 1; }

.field label { font-weight: 600; font-size: 0.92rem; }
.field__hint { color: var(--ink-soft); font-size: 0.82rem; font-weight: 400; margin: 0; }
.field__error { color: var(--danger); font-size: 0.85rem; margin: 0; }

.field input, .field select, .field textarea {
  width: 100%;
  background: var(--bg);
  border: 1px solid var(--input-border);
  border-radius: var(--r-input);
  color: var(--ink);
  font: inherit; font-size: 0.98rem;
  padding: 13px 15px;
  transition: border-color 0.15s, box-shadow 0.15s;
}
.field textarea { resize: vertical; min-height: 120px; }
.field input:focus, .field select:focus, .field textarea:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(8, 98, 244, 0.16);
}
.field.invalid input, .field.invalid textarea { border-color: var(--danger); }

.field select {
  appearance: none;
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8'%3E%3Cpath d='M1 1l5 5 5-5' stroke='%234A6076' stroke-width='1.8' fill='none' stroke-linecap='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 16px center;
}

.hp { position: absolute !important; left: -9999px; width: 1px; height: 1px; opacity: 0; }

.contact__form .btn { grid-column: span 2; justify-self: start; }

.form-status { grid-column: span 2; margin: 0; font-size: 0.95rem; min-height: 1.4em; }
.form-status.ok { color: var(--ok); }
.form-status.err { color: var(--danger); }

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

.footer { border-top: 1px solid var(--line); background: var(--bg); }

.footer__grid {
  display: grid; grid-template-columns: 1.2fr 0.8fr 1fr;
  gap: clamp(32px, 5vw, 80px);
  padding-block: clamp(48px, 7vh, 80px);
}
.footer__brand p { color: var(--ink-soft); font-size: 0.95rem; margin-top: 18px; max-width: 34ch; }

.footer__nav { display: grid; gap: 10px; align-content: start; }
.footer__nav a { color: var(--ink-soft); text-decoration: none; font-size: 0.95rem; }
.footer__nav a:hover { color: var(--ink); }

.footer__meta { color: var(--ink-soft); font-size: 0.95rem; }
.footer__meta a { color: var(--accent); text-decoration: none; }
.footer__meta a:hover { text-decoration: underline; }

.footer__legal {
  display: flex; justify-content: space-between; gap: 24px; flex-wrap: wrap;
  border-top: 1px solid var(--line);
  padding-block: 26px;
  color: var(--ink-soft); font-size: 0.84rem;
}
.footer__legal p { margin: 0; }
.footer__legal a { color: var(--ink-soft); }
.footer__legal a:hover { color: var(--ink); }

/* ---------- Responsive collapse ---------- */

/* The burger MUST appear at the same width the desktop links disappear.
   These were previously 1060px and 880px respectively, which left a dead band
   between 881px and 1060px — a very common laptop and tablet-landscape window
   — with no navigation on the page at all: no links, no burger. */
@media (max-width: 1060px) {
  .nav__links { display: none; }
  .nav__burger { display: block; }
  .hub__features { grid-template-columns: repeat(2, 1fr); }
}

@media (max-width: 880px) {
  .nav__cta { display: none; }

  .svc-card { min-height: 0; }
  .svc-card__head { flex-wrap: wrap; gap: 12px; }
  .svc-card__scene { min-height: 340px; }
  .svc-card__points { align-self: stretch; }

  .hub__collage { height: auto; display: grid; gap: 18px; }
  .hub__shot { position: static; }
  .hub__shot--map, .hub__shot--mobile, .hub__shot--inventory { width: 100%; }
  .hub__shot--mobile { width: 62%; justify-self: center; }

  .record__grid, .contact__grid { grid-template-columns: 1fr; }
  .split__grid { grid-template-columns: 1fr; }

  .footer__grid { grid-template-columns: 1fr; gap: 36px; }

  .field--half { grid-column: span 2; }
  .hub__cta { flex-direction: column; align-items: flex-start; }
}

@media (max-width: 560px) {
  .hero__actions .btn { width: 100%; }
  .hub__shot--mobile { width: 82%; }
}

/* ---------- Motion pause control (WCAG 2.2.2) ---------- */

.motion-toggle {
  position: fixed; left: 18px; bottom: 18px; z-index: 90;
  width: 40px; height: 40px;
  display: flex; align-items: center; justify-content: center;
  background: var(--surface);
  border: 1px solid var(--line-strong);
  border-radius: 999px;
  cursor: pointer;
  color: var(--ink);
  box-shadow: 0 6px 20px -6px rgba(10, 37, 64, 0.25);
  transition: transform 0.18s, border-color 0.18s;
}
.motion-toggle:hover { transform: translateY(-1px); border-color: var(--ink); }

.motion-toggle__pause {
  width: 10px; height: 12px;
  border-left: 3px solid currentColor;
  border-right: 3px solid currentColor;
}
.motion-toggle__play {
  display: none;
  width: 0; height: 0;
  border-top: 6px solid transparent;
  border-bottom: 6px solid transparent;
  border-left: 10px solid currentColor;
  margin-left: 2px;
}
html.motion-paused .motion-toggle__pause { display: none; }
html.motion-paused .motion-toggle__play { display: block; }
html.motion-paused .marquee__track { animation-play-state: paused; }

/* Without JS the button is dead and the marquee must not run. */
html.no-js .motion-toggle { display: none; }
html.no-js .marquee__track { animation: none; }

/* ---------- Motion guards ---------- */

/* Before JS initialises, and whenever JS is unavailable, everything is visible. */
html.no-js [data-reveal],
html.no-js .hero__title .line > span,
html.no-js .hero__sub,
html.no-js .hero__actions { opacity: 1 !important; transform: none !important; }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .marquee__track { animation: none; }
  .motion-toggle { display: none; }
  *, *::before, *::after { transition-duration: 0.01ms !important; animation-duration: 0.01ms !important; }
}
