/* ============================================================================
   Informed Software Solutions — Home page styles
   ----------------------------------------------------------------------------
   Page-specific styles for index.html ONLY. Loaded AFTER the shared cascade
   (tokens.css → base.css → components.css → product.css → home.css).
   Shared CSS is FROZEN — do not edit it; home-only rules live here.

   Conventions (inherited): tokens only (no raw hex), mobile-first from 360px,
   measure rules respected, motion initial-states are JS-set (never opacity:0
   in CSS). Breakpoints used: 600px, 768px, 960px, 1200px.

   Section map:
     1. HERO            (#hero, .theme-operations)
     2. PRODUCTS        (#products — alternating editorial panels, pinnable)
     3. BESPOKE         (#bespoke — asymmetric editorial split)
     4. TRUST           (#trust — logo strip + statements)
     5. SECURITY        (#security — control-reference card cluster)
     6. ABOUT           (#about — leadership cards, duotone portraits)
     7. INSIGHTS        (#insights — coming-soon field-note cards)
     8. CONTACT         (#contact, .theme-operations — split + form)
   ========================================================================== */

/* ===========================================================================
   1. HERO
   Deepest navy void (.theme-operations → navy-950), full viewport. A cinematic
   "operations room": text column left, the COMPLETE fingerprint mark as a
   luminous ridge-line centrepiece right, floating over a cool navy-glow pool
   with a faint amber core glint and a soft drop glow. Scroll cue bottom-left.
   =========================================================================== */
.hero {
  position: relative;
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  /* leave room for the fixed header + the bottom scroll cue */
  padding-block: calc(var(--header-h) + var(--space-9)) var(--space-11);
  overflow: hidden;
  isolation: isolate;
  background-color: var(--navy-950);
}

/* Atmosphere pool — a cool navy-glow weighted to the right (behind the mark)
   plus a faint vignette that deepens the corners for cinematic falloff. Sits
   behind everything (z 0); content + visual lift above it. */
.hero::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background:
    radial-gradient(70% 70% at 72% 42%,
      rgba(44, 80, 120, 0.30) 0%,     /* navy-600 glow behind the mark */
      rgba(44, 80, 120, 0.10) 38%,
      transparent 72%),
    radial-gradient(120% 90% at 50% 120%,
      rgba(10, 20, 31, 0.0) 40%,
      rgba(5, 10, 18, 0.55) 100%);     /* bottom vignette */
}

/* Faint fingerprint-ridge texture field washing across the void. */
.hero::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background-image:
    repeating-radial-gradient(circle at 74% 42%,
      transparent 0 15px,
      rgba(86, 129, 171, 0.04) 15px 16px);
  -webkit-mask-image: radial-gradient(60% 60% at 74% 42%, #000 0%, transparent 78%);
          mask-image: radial-gradient(60% 60% at 74% 42%, #000 0%, transparent 78%);
}

.hero__grid {
  position: relative;
  z-index: var(--z-raised);
  display: grid;
  grid-template-columns: 1fr;
  align-items: center;
  gap: var(--space-8);
  width: 100%;
}

.hero__lede {
  position: relative;
  z-index: var(--z-raised);
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  max-width: 24ch;
}

/* Mobile hero scrim — on narrow viewports the particle canvas sits under the
   text column; a soft radial scrim behind the text settles the dense field so
   the muted lead copy keeps its contrast floor. Removed at >=768px (the mark
   resolves to the right of the text there). */
.hero__lede::before {
  content: '';
  position: absolute;
  inset: calc(var(--space-6) * -1) calc(var(--space-5) * -1);
  z-index: -1;
  pointer-events: none;
  background:
    radial-gradient(
      120% 100% at 30% 40%,
      color-mix(in srgb, var(--navy-950) 82%, transparent) 0%,
      color-mix(in srgb, var(--navy-950) 58%, transparent) 45%,
      transparent 78%
    );
}

.hero__kicker {
  margin-bottom: var(--space-1);
}

.hero__title {
  /* .display token sizing inherited (now clamps to ~7rem); tight measure */
  max-width: 15ch;
  color: var(--ink);
}

.hero__lead {
  max-width: 52ch;
  color: var(--text-soft);
}

.hero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4);
  margin-top: var(--space-3);
}

/* ---- Fingerprint centrepiece -------------------------------------------------
   The COMPLETE mark (all 54 ridge paths), composed as a designed object: a
   luminous ridge-line print with an amber core glint and a soft drop glow over
   the atmosphere pool. On mobile it sits in flow (below text); from 768px it
   anchors the right half. IMPORTANT: the particle bundle crossfades the static
   SVG by animating #fingerprint-mark's inline `style.opacity` 1→0 (and restores
   it on error / no-WebGL). So we must NOT set opacity on #fingerprint-mark
   itself — the glow is a `filter` (drop-shadow) which is independent of opacity,
   and per-path richness is on .fp-path. */
.hero__visual {
  position: relative;
  z-index: var(--z-raised);
  width: min(84%, 380px);
  margin-inline: auto;
  pointer-events: none;
}

/* Mobile (<768px): the single-column hero stacks the visual AFTER the text, which
   pushed the static fingerprint mark to ~950px down — entirely below the first
   viewport, so on a phone the hero read as text-on-void with no mark at all.
   Pull the mark out of flow and float it as a large, faint CENTRED backdrop
   behind the text instead: present and on-brand, but clearly subordinate to the
   copy (it sits below .hero__lede in the stack, and .hero__lede::before scrims
   the text for contrast). We only touch position/scale/filter here — never
   #fingerprint-mark's inline opacity (owned by the particle crossfade). */
@media (max-width: 767.98px) {
  .hero__visual {
    position: absolute;
    z-index: 1;                 /* behind .hero__lede (--z-raised) */
    top: 50%;
    left: 50%;
    width: min(118%, 560px);    /* oversized so the ridges read as a field */
    margin-inline: 0;
    transform: translate(-50%, -48%) scale(1);
    /* soften it back so the headline stays dominant: a gentle blur + the navy
       drop-glow filter already on #fingerprint-mark keep it atmospheric, not
       a competing focal object. */
    filter: blur(0.5px);
  }
}

/* Soft drop glow behind the mark — a blurred navy/amber pool that reads as the
   print "lifting" off the void. Decoupled from #fingerprint-mark's opacity. */
.hero__visual::before {
  content: '';
  position: absolute;
  inset: 6% 4%;
  z-index: -1;
  pointer-events: none;
  background:
    radial-gradient(closest-side at 52% 48%,
      rgba(86, 129, 171, 0.22) 0%,
      rgba(44, 80, 120, 0.10) 55%,
      transparent 78%);
  filter: blur(18px);
}

.hero__visual #fingerprint-mark {
  width: 100%;
  height: auto;
  /* drop glow (filter, NOT opacity — leaves the crossfade channel free) */
  filter:
    drop-shadow(0 0 22px rgba(86, 129, 171, 0.28))
    drop-shadow(0 0 4px rgba(224, 162, 61, 0.10));
}

/* Recolour the inlined fingerprint ridges to a luminous navy ramp. The SVG
   ships with a brand-navy inline <style> fill; we override per token here. The
   amber core glint lives on the innermost rings (data-ring 8/9/10) so the
   centre of the print catches a forensic light. No raw hex (tokens only). */
.hero__visual .fp-path {
  fill: var(--navy-400);
}
/* mid rings a touch brighter for ridge-line luminance */
.hero__visual .fp-path[data-ring="5"],
.hero__visual .fp-path[data-ring="0"],
.hero__visual .fp-path[data-ring="1"],
.hero__visual .fp-path[data-ring="7"] {
  fill: var(--navy-300);
}
/* amber core glint — the eye of the print */
.hero__visual .fp-path[data-ring="8"],
.hero__visual .fp-path[data-ring="9"],
.hero__visual .fp-path[data-ring="10"] {
  fill: var(--beacon-400);
}

/* Bottom cue + trustline share the hero's bottom edge */
.hero__scroll-cue {
  position: absolute;
  left: var(--container-pad);
  bottom: var(--space-6);
  z-index: var(--z-raised);
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  color: var(--text-soft);
  font-family: var(--font-mono);
  font-size: var(--fs-kicker);
  letter-spacing: var(--ls-tag);
  text-transform: uppercase;
}

.hero__scroll-cue:hover {
  color: var(--accent-text);
}

.hero__scroll-cue-track {
  position: relative;
  display: inline-block;
  width: 2px;
  height: 34px;
  border-radius: var(--radius-pill);
  background-color: var(--rule);
  overflow: hidden;
}

.hero__scroll-cue-dot {
  position: absolute;
  left: 50%;
  top: 0;
  width: 4px;
  height: 8px;
  margin-left: -2px;
  border-radius: var(--radius-pill);
  background-color: var(--accent);
  animation: hero-cue-fall 1.8s var(--ease-in-out) infinite;
}

@keyframes hero-cue-fall {
  0%   { transform: translateY(-8px); opacity: 0; }
  35%  { opacity: 1; }
  100% { transform: translateY(34px); opacity: 0; }
}

/* Reduced motion: the falling-dot keyframe ENDS at opacity:0, so a UA that
   forces a single iteration (the common reduced-motion behaviour) leaves the
   dot invisible — the scroll cue loses its mark. Pin the dot to a static,
   visible resting position (mid-track) and cancel the animation so the cue
   still reads as a deliberate indicator. */
@media (prefers-reduced-motion: reduce) {
  .hero__scroll-cue-dot {
    animation: none;
    transform: translateY(12px);
    opacity: 1;
  }
}

/* (The bottom-right trustline was removed — "trying too hard". Only the
   bottom-left scroll cue remains.) */

/* ≥768px — two-column hero, the mark anchors the right half and grows larger */
@media (min-width: 768px) {
  .hero__grid {
    grid-template-columns: 1.1fr 0.9fr;
    gap: var(--space-9);
  }
  .hero__lede {
    max-width: none;
  }
  /* particles resolve to the RIGHT of the text on two-column layouts, so the
     text no longer overlays the dense field — drop the mobile scrim entirely. */
  .hero__lede::before {
    display: none;
  }
  .hero__visual {
    width: min(100%, 500px);
    margin-inline: 0 auto;
  }
}

@media (min-width: 1200px) {
  .hero__visual {
    width: min(100%, 600px);
  }
}

/* ===========================================================================
   1b. PRELOADER  (non-critical polish — see <head> for the critical inline CSS)
   The overlay's layout, colours, per-stage build-up, and dismissal animation are
   defined inline in index.html's <head> so it paints before any external CSS
   arrives. THIS block only adds token-driven richness once the cascade has
   loaded: a cool navy-glow pool + a faint amber core glint behind the mark,
   matching the hero's atmosphere, and re-expresses the per-stage fills via
   tokens so the preloader mark and the hero mark stay the SAME object (the hero
   line-draws this same logo right after the fade — the continuity is the point).
   Everything here is additive — if it never loads, the inline CSS still renders
   a complete, on-brand, fully-staged overlay.
   Selectors match the inline rules' specificity so they reliably layer on top;
   they never fight the .plp-* build-up or the .is-dismissing / .is-done states.
   =========================================================================== */
#site-preloader {
  background-color: var(--navy-950);
  background-image:
    radial-gradient(60% 55% at 50% 46%,
      rgba(44, 80, 120, 0.26) 0%,    /* navy-600 cool glow pool */
      rgba(44, 80, 120, 0.08) 40%,
      transparent 72%),
    radial-gradient(30% 26% at 56% 56%,
      rgba(224, 162, 61, 0.10) 0%,   /* beacon-400 faint amber glint at the lens */
      transparent 70%);
  color: var(--ink);
}
/* Per-stage fills via tokens (match the hero ramp exactly). */
#site-preloader .plp-path { fill: var(--navy-400); }
#site-preloader .plp-path[data-plp-stage="cuff"]  { fill: var(--navy-300); }
#site-preloader .plp-path[data-plp-stage="glass"] { fill: var(--navy-300); }
#site-preloader .plp-path[data-plp-stage="bull"]  { fill: var(--beacon-400); }
#site-preloader .preloader__kicker { color: var(--ink-soft); }
#site-preloader .preloader__bar { background: var(--line); }
#site-preloader .preloader__bar::before {
  background: linear-gradient(90deg, var(--navy-400), var(--navy-300));
}

/* ===========================================================================
   2. PRODUCTS
   Section intro, then alternating editorial panels. Each panel is a 2-col
   grid (text + UI image) that swaps sides on even panels. Built as vertically
   stacked panels in .product-panels so they can be horizontally pinned later
   (Phase 3) without restructuring.
   =========================================================================== */
.products {
  position: relative;
  background-color: var(--navy-900);
  overflow: clip; /* contain the oversized section numeral */
}

/* cool glow pool weighted top-left behind the section head. z:0 + the section
   container is z:1 (set below) so the pinned-panels track is never disturbed. */
.products::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background:
    radial-gradient(48% 42% at 18% 8%,
      rgba(44, 80, 120, 0.16) 0%,
      transparent 70%);
}
/* lift the intro container above the glow without touching .product-panels
   (whose pin transform must not be perturbed by a stacking change) */
.products > .container { position: relative; z-index: var(--z-raised); }

.product-panels {
  display: flex;
  flex-direction: column;
  gap: var(--section-py-sm);
  margin-top: var(--space-9);
}

.product-panel {
  position: relative;
}

.product-panel__inner {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-7);
  align-items: center;
}

.product-panel__text {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-4);
  max-width: 56ch;
}

/* Per-panel accent token — drives tag, name underline, link hover.
   On the dark ground the raw IV/DF navies are too low-contrast, so each panel
   gets a brightened dark-ground accent. IMS keeps amber (already luminous). */
.product-panel--ims { --panel-accent: var(--beacon-400); }
.product-panel--iv  { --panel-accent: var(--navy-400); }
.product-panel--df  { --panel-accent: var(--navy-300); }

.product-panel__tag {
  /* override generic mono-tag colours with the per-product accent */
  color: var(--panel-accent);
  border-color: var(--panel-accent);
  background-color: transparent;
}

.product-panel__name {
  font-size: var(--fs-h2);
  color: var(--heading);
  position: relative;
  padding-bottom: var(--space-3);
}

.product-panel__name::after {
  content: '';
  position: absolute;
  left: 0;
  bottom: 0;
  width: 48px;
  height: 3px;
  border-radius: var(--radius-pill);
  background-color: var(--panel-accent);
}

.product-panel__blurb {
  font-size: var(--fs-body);
  color: var(--text); /* body-tier copy: brighter ink for >=7:1 on the ground */
  line-height: var(--lh-body);
}

.product-panel__links {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-6);
  margin-top: var(--space-2);
}

.product-panel__links .link-arrow:hover {
  color: var(--panel-accent);
}

/* UI composition image — framed, soft navy shadow, accent top rule */
.product-panel__visual {
  position: relative;
  margin: 0;
}

.product-panel__ui {
  width: 100%;
  height: auto;
  aspect-ratio: 1280 / 800;
  border-radius: var(--radius-lg);
  border: 1px solid var(--line-strong);
  /* deep cool shadow + a faint cool rim so the "screen" reads as glowing glass
     lifted off the void */
  box-shadow:
    var(--shadow-xl),
    0 0 0 1px rgba(86, 129, 171, 0.12);
  background-color: var(--navy-800);
}

/* Accent rule riding the top edge of the framed visual */
.product-panel__visual::before {
  content: '';
  position: absolute;
  inset: 0 0 auto 0;
  height: 4px;
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  background-color: var(--panel-accent);
  z-index: var(--z-raised);
}

/* ≥768px — side-by-side, alternate the visual to the left on even panels */
@media (min-width: 768px) {
  .product-panel__inner {
    grid-template-columns: 1fr 1fr;
    gap: var(--space-9);
  }

  /* Investigations (2nd panel) mirrors: visual left, text right */
  .product-panel--iv .product-panel__text {
    order: 2;
  }
  .product-panel--iv .product-panel__visual {
    order: 1;
  }
}

@media (min-width: 960px) {
  .product-panel__inner {
    grid-template-columns: 1.05fr 1.25fr;
    gap: var(--space-10);
  }
  .product-panel--iv .product-panel__inner {
    grid-template-columns: 1.25fr 1.05fr;
  }
}

/* ===========================================================================
   3. BESPOKE
   Asymmetric editorial: oversized statement headline (left / top) against a
   supporting body column with capability ticks + a 4-step process row.
   Lifted band (navy-880) with a glow pool weighted toward the statement.
   =========================================================================== */
.bespoke {
  background-color: var(--navy-880);
  overflow: clip;
}
.bespoke::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background:
    radial-gradient(46% 50% at 14% 30%,
      rgba(44, 80, 120, 0.18) 0%,
      transparent 70%);
}
.bespoke > .container { position: relative; z-index: var(--z-raised); }

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

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

.bespoke__statement {
  font-family: var(--font-display);
  font-size: var(--fs-h1);
  font-variation-settings: 'opsz' 96, 'wght' var(--fw-display);
  line-height: var(--lh-tight);
  letter-spacing: var(--ls-heading);
  color: var(--heading);
  max-width: 18ch;
  text-wrap: balance;
}

.bespoke__body {
  display: flex;
  flex-direction: column;
  gap: var(--space-7);
}

.bespoke__copy {
  font-size: var(--fs-lead);
  line-height: var(--lh-snug);
  color: var(--text); /* body-tier lead copy: brighter ink for >=7:1 */
  max-width: var(--measure);
}

/* Capability list — animated tick + label */
.bespoke__capabilities {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-3) var(--space-6);
}

.bespoke__capability {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  font-size: var(--fs-body);
  font-weight: var(--fw-medium);
  color: var(--text);
}

.bespoke__tick {
  flex-shrink: 0;
  width: 22px;
  height: 22px;
  color: var(--accent-text);
}

/* 4-step process row — numbered, connected by a hairline on wide screens */
.bespoke__process {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-4);
  padding-top: var(--space-6);
  border-top: var(--rule-light);
  counter-reset: none;
}

.bespoke__step {
  display: flex;
  align-items: baseline;
  gap: var(--space-3);
}

.bespoke__step-index {
  font-size: var(--fs-kicker);
  color: var(--accent-text);
  letter-spacing: var(--ls-tag);
}

.bespoke__step-label {
  font-family: var(--font-display);
  font-size: var(--fs-h3);
  font-variation-settings: 'opsz' 36, 'wght' var(--fw-display);
  color: var(--heading);
  line-height: 1.1;
  /* balance the wrap so "Stand behind it" breaks 2-up instead of stringing to
     three uneven lines against the single-line siblings. */
  text-wrap: balance;
}

@media (min-width: 600px) {
  /* In the 4-up process row the single-word labels (Listen/Design/Build) sit on
     one line while "Stand behind it" wraps, throwing the baselines off. Step the
     4-up labels down to the lead size so the longest balances cleanly to TWO
     lines in the narrow column, then reserve a consistent two-line min-height so
     every label box is the same height and the row's tops/baselines align. */
  .bespoke__step-label {
    font-size: var(--fs-lead);
    font-variation-settings: 'opsz' 28, 'wght' var(--fw-display);
    line-height: 1.15;
    min-height: calc(2 * 1.15em);
  }
}

@media (min-width: 600px) {
  .bespoke__capabilities {
    grid-template-columns: 1fr 1fr;
  }
  .bespoke__process {
    grid-template-columns: repeat(4, 1fr);
    gap: var(--space-6);
  }
  .bespoke__step {
    flex-direction: column;
    gap: var(--space-2);
  }
}

@media (min-width: 960px) {
  .bespoke__grid {
    grid-template-columns: 0.85fr 1.15fr;
    gap: var(--space-10);
    align-items: start;
  }
  .bespoke__statement {
    font-size: var(--fs-display);
    font-variation-settings: 'opsz' 144, 'wght' var(--fw-display);
    line-height: var(--lh-display);
    letter-spacing: var(--ls-display);
  }
  /* keep the statement aligned while the body scrolls past it */
  .bespoke__lede {
    position: sticky;
    top: calc(var(--header-h) + var(--space-7));
  }
}

/* ===========================================================================
   4. TRUST
   Centered logo strip (trust-strip component) + three short statements.
   Restrained — small section rhythm, hairline separators. Default ground
   (navy-900) so it recedes between the two lifted bands either side.
   =========================================================================== */
.trust {
  background-color: var(--navy-900);
}

/* Holding blurb shown in place of the client logos while permissions pend
   (server keeps either .trust__clients or .trust__blurb — see server.js). */
.trust__blurb {
  text-align: center;
}

.trust__blurb-text {
  max-width: 46ch;
  margin-inline: auto;
  font-size: var(--fs-lead);
  line-height: var(--lh-snug);
  color: var(--text-strong);
}

.trust__kicker {
  display: flex;
  justify-content: center;
  width: 100%;
  margin-bottom: var(--space-7);
  text-align: center;
}

.trust__strip {
  padding-block: var(--space-2);
}

/* tighten logo height a touch for a calmer strip */
.trust__strip .trust-logo {
  height: 40px;
}

/* Optical-weight normalisation. The detailed crests (Police, Customs, MSD)
   carry far more ink than the light Scope wordmark, so at an equal box height
   the wordmark reads smaller/lighter. The frozen `.trust-logo img { height:
   100% }` rule means the rendered image height follows the .trust-logo BOX
   height — so we nudge the light wordmark's box up ~12% (the image grows with
   it) to even out the optical weight. 4th (last) = Scope Investigations. */
.trust__strip .trust-logo:nth-child(4) {
  height: 45px; /* ~+12% */
}

.trust__statements {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-5);
  margin-top: var(--space-9);
  padding-top: var(--space-8);
  border-top: var(--rule-light);
}

.trust__statement {
  position: relative;
  padding-left: var(--space-5);
  font-size: var(--fs-body);
  line-height: var(--lh-snug);
  color: var(--text);
  max-width: 42ch;
}

.trust__statement::before {
  content: '';
  position: absolute;
  left: 0;
  top: 0.55em;
  width: 8px;
  height: 8px;
  border-radius: var(--radius-pill);
  background-color: var(--accent);
}

@media (min-width: 768px) {
  .trust__statements {
    grid-template-columns: repeat(3, 1fr);
    gap: var(--space-8);
  }
}

/* ===========================================================================
   5. SECURITY
   Control-reference card cluster. Each card reads like a forensic control:
   mono CTRL-NN + drawn tick, a short name, and a one-line body. Lifted band.
   =========================================================================== */
.security {
  background-color: var(--navy-880);
  overflow: clip;
  /* The shared .section-numeral sits at top:-0.18em, which lets its glyph's
     rounded top get shaved by this section's `overflow: clip` top edge — the
     curved '0' and '3' make that flat cut read as clipped where straighter
     numerals hide it. Give '03' a touch of headroom so the whole glyph sits
     inside the section. (base.css exposes --numeral-top precisely for this.) */
  --numeral-top: 0.04em;
}
.security::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background:
    radial-gradient(44% 44% at 82% 6%,
      rgba(44, 80, 120, 0.16) 0%,
      transparent 70%);
}
.security > .container { position: relative; z-index: var(--z-raised); }

.security__grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-5);
  margin-top: var(--space-2);
}

.security__card {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-6);
  /* navy-800 surface on the lifted band, subtle cool hairline, deep shadow */
  background-color: var(--navy-800);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  box-shadow: var(--shadow-md);
  transition:
    transform var(--dur-med) var(--ease-out),
    box-shadow var(--dur-med) var(--ease-out),
    border-color var(--dur-med) var(--ease-standard),
    background-color var(--dur-med) var(--ease-standard);
}

.security__card:hover {
  transform: translateY(-3px);
  box-shadow: var(--shadow-lg);
  border-color: color-mix(in srgb, var(--beacon-400) 35%, transparent);
  background-color: var(--navy-700);
}

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

.security__tick {
  flex-shrink: 0;
  color: var(--accent-text);
}

.security__ctrl {
  font-size: var(--fs-kicker);
  letter-spacing: var(--ls-tag);
  color: var(--text-soft);
}

.security__name {
  font-size: var(--fs-h3);
  font-variation-settings: 'opsz' 30, 'wght' var(--fw-display);
  color: var(--heading);
  line-height: 1.15;
  /* The control names are short, single-tier labels ("Sovereignty", "Secure by
     design", "Accountability", "Access"). They must NEVER break mid-word — an
     earlier hyphens:auto produced ugly "Accountabil-ity" / ragged "Secure / by
     design" and threw the 4-card baseline alignment off. So we forbid hyphens
     and keep every word whole; text-wrap:balance evens the one multi-word name
     ("Secure by design") across two tidy lines instead of one long + one orphan. */
  text-wrap: balance;
  hyphens: none;
  overflow-wrap: normal;
}

@media (min-width: 960px) {
  /* In the 4-column grid the per-card content box is only ~180px wide at 1440
     (narrower at 1024). At --fs-h3 (~31px) the longest single word,
     "Accountability", measures ~218px — it cannot fit one line and, with
     mid-word breaks forbidden, would overflow into the card padding. Step the
     4-up titles down to the lead size (~22px) so "Accountability" fits one line
     at BOTH 1440 and 1024 (verified ~155px and ~133px against the available
     width), while the other names stay single-line too. A reserved two-line
     min-height then aligns every card's title baseline — without it the
     single-line names and the two-line "Secure by design" would stagger the
     body copy below them. */
  .security__name {
    font-size: var(--fs-lead);
    font-variation-settings: 'opsz' 24, 'wght' var(--fw-display);
    min-height: calc(2 * 1.15em);
  }
}

.security__body {
  font-size: var(--fs-small);
  line-height: var(--lh-snug);
  color: var(--text); /* body-tier copy on navy-800 card: brighter ink (>=7:1) */
}

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

@media (min-width: 960px) {
  .security__grid {
    /* minmax(0,1fr) so a long control-name/body can't force a 4-col track wider
       than its share — repeat(4,1fr) overflowed the page ~23px at 960-1023px
       (same min-content trap as .insights__grid). */
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: var(--space-6);
  }
  .security__card {
    min-width: 0;
  }
}

/* ===========================================================================
   6. ABOUT
   Leadership cards with a duotone-navy portrait treatment. Default ground.
   =========================================================================== */
.about {
  background-color: var(--navy-900);
  overflow: clip;
}
.about > .container { position: relative; z-index: var(--z-raised); }

.about__team {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-8);
  margin-top: var(--space-2);
}

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

.about__portrait {
  position: relative;
  margin: 0;
  border-radius: var(--radius-lg);
  overflow: hidden;
  background-color: var(--navy-800);
  box-shadow: var(--shadow-lg);
  /* cool hairline so the framed portrait reads as a composed object on the void */
  outline: 1px solid var(--line-strong);
  outline-offset: -1px;
}

.about__portrait img {
  width: 100%;
  height: auto;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  /* Per-person vertical crop so every face shares a consistent eye-line near
     ~40% of the card height (the three source portraits frame the subject at
     different heights). Horizontal stays centred; only the Y is tuned below. */
  object-position: 50% 35%;
  /* Duotone base: desaturate, lift contrast, and pull the floor darker. The
     three source portraits have noticeably different background luminances;
     compressing the range with a firmer contrast + lower brightness lets the
     navy multiply wash below land them on a consistent tone. */
  filter: grayscale(1) contrast(1.12) brightness(0.82);
  transition:
    filter var(--dur-slow) var(--ease-standard),
    transform var(--dur-slow) var(--ease-out);
}

/* Per-person eye-line alignment. object-fit:cover maps the leftover vertical
   overflow linearly, so these were tuned by eye against the rendered square
   card (see _qa/shot-team-crop.png), not computed. Lower % pulls the crop
   window UP (shows more of the top); higher % pulls it DOWN. */
.about__portrait img.about__portrait-img--joel    { object-position: 50% 37%; }
.about__portrait img.about__portrait-img--kath    { object-position: 50% 20%; }
.about__portrait img.about__portrait-img--suzanne { object-position: 50% 34%; }

/* Navy duotone wash via a blended overlay (no raw hex — uses scrim token).
   A firmer multiply floor (navy-800 base + a stronger top-down scrim) clamps
   the three portraits' differing background tones to one consistent navy
   duotone. Raised opacity tightens the treatment so the lightest source no
   longer reads warmer than the others. */
.about__portrait::after {
  content: '';
  position: absolute;
  inset: 0;
  background:
    linear-gradient(180deg,
      color-mix(in srgb, var(--navy-900) 35%, transparent) 0%,
      var(--scrim-navy) 100%),
    var(--navy-800);
  mix-blend-mode: multiply;
  opacity: 0.66;
  transition: opacity var(--dur-slow) var(--ease-standard);
  pointer-events: none;
}

.about__card:hover .about__portrait img {
  /* keep a touch of cool desaturation on resolve so the team stays in the navy
     register rather than popping to full warm colour against the dark ground */
  filter: grayscale(0.3) contrast(1.04) brightness(0.98);
  transform: scale(1.02);
}

.about__card:hover .about__portrait::after {
  opacity: 0.22;
}

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

.about__name {
  font-size: var(--fs-h3);
  font-variation-settings: 'opsz' 36, 'wght' var(--fw-display);
  color: var(--heading);
  line-height: 1.1;
}

.about__role {
  font-size: var(--fs-kicker);
  letter-spacing: var(--ls-tag);
  text-transform: uppercase;
  color: var(--accent-text);
}

.about__bio {
  font-size: var(--fs-small);
  line-height: var(--lh-body);
  color: var(--text); /* body-tier copy: brighter ink for >=7:1 */
  margin-top: var(--space-2);
}

@media (min-width: 768px) {
  .about__team {
    grid-template-columns: repeat(3, 1fr);
    gap: var(--space-7);
  }
}

/* ===========================================================================
   7. INSIGHTS
   Placeholder field-note cards, clearly "Coming soon" (no dead links).
   Lifted band so it steps up before the deep contact void below.
   =========================================================================== */
.insights {
  background-color: var(--navy-880);
  overflow: clip;
}
.insights > .container { position: relative; z-index: var(--z-raised); }

.insights__grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-5);
  margin-top: var(--space-2);
}

.insights__card {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  padding: var(--space-6);
  min-height: 13rem;
  /* deliberately flatter than live cards — dashed cool hairline placeholder */
  background-color: color-mix(in srgb, var(--navy-800) 60%, transparent);
  border: 1px dashed var(--line-strong);
  border-radius: var(--radius);
}

.insights__tag {
  align-self: flex-start;
  font-size: var(--fs-kicker);
  letter-spacing: var(--ls-tag);
  text-transform: uppercase;
  color: var(--text-soft);
}

.insights__title {
  font-size: var(--fs-h3);
  font-variation-settings: 'opsz' 30, 'wght' var(--fw-display);
  color: var(--heading);
  line-height: 1.18;
  max-width: 24ch;
}

.insights__status {
  margin-top: auto;
}

/* the .coming-soon badge is injected via ::after (components.css); keep the
   host inline so the pill sits naturally */
.insights__status.coming-soon {
  display: inline-flex;
  align-items: center;
  align-self: flex-start;
  min-height: 1.4em;
}

@media (min-width: 768px) {
  .insights__grid {
    /* minmax(0,1fr) lets tracks shrink below their min-content width so a long
       card title can't force the grid wider than the container (24px page
       overflow at 768-1023px otherwise). */
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: var(--space-6);
  }
  .insights__grid > * {
    min-width: 0; /* belt-and-braces: items may also shrink past min-content */
  }
}

/* ===========================================================================
   8. CONTACT
   Deepest navy void (.theme-operations → navy-950). Split: intro + direct
   channels (left) / a raised form panel (right). Unified, cinematic close.
   =========================================================================== */
.contact {
  position: relative;
  overflow: clip;
}
/* cool glow pool weighted behind the headline, amber glint low-right */
.contact::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background:
    radial-gradient(46% 50% at 16% 14%,
      rgba(44, 80, 120, 0.22) 0%,
      transparent 70%),
    radial-gradient(40% 46% at 86% 92%,
      rgba(224, 162, 61, 0.08) 0%,
      transparent 70%);
}
.contact > .container { position: relative; z-index: var(--z-raised); }

.contact__grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-9);
  align-items: start;
}

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

.contact__title {
  font-size: var(--fs-h1);
  color: var(--heading);
}

.contact__reassurance {
  font-size: var(--fs-lead);
  line-height: var(--lh-snug);
  color: var(--text); /* body-tier copy: brighter ink for >=7:1 */
  max-width: 40ch;
}

.contact__channels {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  margin-top: var(--space-4);
}

.contact__phone,
.contact__email {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.contact__phone-label,
.contact__email-label {
  font-size: var(--fs-kicker);
  letter-spacing: var(--ls-tag);
  text-transform: uppercase;
  color: var(--text-soft);
}

/* Large click-to-call number */
.contact__phone-number {
  font-family: var(--font-display);
  font-size: clamp(1.85rem, 1.2rem + 3vw, 2.75rem);
  font-variation-settings: 'opsz' 72, 'wght' var(--fw-display);
  line-height: 1.05;
  letter-spacing: var(--ls-display);
  color: var(--heading);
  transition: color var(--dur-fast) var(--ease-standard);
}

.contact__phone:hover .contact__phone-number {
  color: var(--accent-text);
}

.contact__email-address {
  font-size: var(--fs-lead);
  color: var(--text);
  word-break: break-word;
  transition: color var(--dur-fast) var(--ease-standard);
}

.contact__email:hover .contact__email-address {
  color: var(--accent-text);
}

/* Form panel — a raised navy-800 surface lifted off the void with a deep cool
   shadow and a faint amber top-rule, so the form reads as a deliberate console. */
.contact__form-wrap {
  position: relative;
  background-color: var(--navy-800);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-lg);
  padding: clamp(var(--space-5), 4vw, var(--space-8));
  box-shadow: var(--shadow-xl);
}

.contact__form-wrap::before {
  content: '';
  position: absolute;
  inset: 0 0 auto 0;
  height: 3px;
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  background: linear-gradient(90deg,
    transparent 0%,
    var(--beacon-500) 30%,
    var(--beacon-400) 50%,
    var(--beacon-500) 70%,
    transparent 100%);
}

.contact__form {
  display: flex;
  flex-direction: column;
}

/* Product context chip — "Re: <Product>" with a remove control.
   The [hidden] guard MUST win over the inline-flex below: contact-form.js
   toggles the `hidden` attribute, but `display:inline-flex` would otherwise
   override the UA `[hidden]{display:none}` and leave an empty amber pill
   orphaned on a plain load (no ?product= token). */
.contact__chip[hidden] {
  display: none;
}

.contact__chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  align-self: flex-start;
  margin-bottom: var(--space-6);
  padding: var(--space-2) var(--space-2) var(--space-2) var(--space-4);
  border: 1px solid rgba(224, 162, 61, 0.4);
  background-color: rgba(224, 162, 61, 0.1);
  border-radius: var(--radius-pill);
  font-family: var(--font-mono);
  font-size: var(--fs-kicker);
  letter-spacing: var(--ls-tag);
  color: var(--beacon-400);
}

.contact__chip-remove {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  border-radius: var(--radius-pill);
  font-size: 1rem;
  line-height: 1;
  color: var(--beacon-400);
  transition:
    background-color var(--dur-fast) var(--ease-standard),
    color var(--dur-fast) var(--ease-standard);
}

.contact__chip-remove:hover {
  background-color: rgba(224, 162, 61, 0.18);
  color: var(--paper);
}

.contact__submit {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-4);
  margin-top: var(--space-2);
}

.contact__caption {
  font-size: var(--fs-kicker);
  letter-spacing: var(--ls-tag);
  color: var(--text-soft);
}

/* reCAPTCHA v3 attribution — required visible text when the floating badge is
   hidden (see .grecaptcha-badge below). Subtle small print, matching the form's
   ink-soft captions; links use the theme accent. */
.contact__recaptcha {
  margin-top: var(--space-4);
  font-size: var(--fs-kicker);
  line-height: var(--lh-snug);
  color: var(--text-soft);
  max-width: 46ch;
}
.contact__recaptcha a {
  color: var(--accent-text);
  text-decoration: underline;
  text-underline-offset: 0.15em;
}
.contact__recaptcha a:hover,
.contact__recaptcha a:focus-visible {
  color: var(--paper);
}

/* Hide the floating reCAPTCHA v3 badge globally. Permitted by Google ONLY when
   the attribution above is shown. Not theme-scoped: the badge is a fixed-position
   element Google injects at the <body> level. visibility:hidden (not display) so
   the widget still functions. */
.grecaptcha-badge {
  visibility: hidden;
}

/* Status region (success / error) — sits below the submit row */
.contact__status {
  margin-top: var(--space-6);
  align-items: flex-start;
  line-height: var(--lh-snug);
}

.form-status__check {
  flex-shrink: 0;
  width: 22px;
  height: 22px;
}

.form-status__text {
  /* allow the success/error sentence to wrap normally (mono base is tight) */
  font-family: var(--font-body);
  letter-spacing: var(--ls-body);
  font-size: var(--fs-small);
}

@media (min-width: 960px) {
  .contact__grid {
    grid-template-columns: 0.9fr 1.1fr;
    gap: var(--space-11);
  }
}

/* ===========================================================================
   MOTION CHOREOGRAPHY — page-specific additions (appended; shared CSS frozen)
   ----------------------------------------------------------------------------
   These rules are inert until JS adds the relevant hook class AFTER confirming
   motion is allowed. So no-JS and reduced-motion users keep the static layout
   above. (animations.js owns the class toggles.)
   =========================================================================== */

/* ---- Team duotone resolve -------------------------------------------------
   home.css already holds .about__portrait img desaturated and reveals it on
   :hover. The scroll-driven resolve mirrors that hover end-state via a class
   the duotone batch adds when each card enters — so the team "warms" into
   colour as you arrive, not only on hover. Identical end-state to :hover. */
.about__portrait.is-resolved img {
  filter: grayscale(0.3) contrast(1.04) brightness(0.98);
}
.about__portrait.is-resolved::after {
  opacity: 0.22;
}

/* ---- Pinned products horizontal sequence (desktop >=1024 + motion) --------
   JS adds .is-pinned to [data-product-panels] and .products--pinned to the
   section inside a gsap.matchMedia('(min-width:1024px)') block. The wrapper
   becomes a horizontal track of viewport-wide panels that ScrollTrigger pins
   and slides. Outside this exact state (mobile, reduced-motion, no-JS) the
   default vertical stack above is untouched. */
@media (min-width: 1024px) {
  .product-panels.is-pinned {
    flex-direction: row;
    flex-wrap: nowrap;
    gap: 0;
    margin-top: 0;
    /* track is N panels wide; each panel is one viewport */
    width: max-content;
    will-change: transform;
  }

  .product-panels.is-pinned .product-panel {
    flex: 0 0 100vw;
    width: 100vw;
    min-height: 100vh;
    display: flex;
    align-items: center;
    /* clear the fixed header so panel content never tucks under the nav */
    padding-block: calc(var(--header-h) + var(--space-7)) var(--space-9);
    /* Resting wash behind an INACTIVE panel — near-flat: a panel only blooms
       into its accent once it becomes active (crossfaded via the transition).
       Kept faint so the active lift below reads as a real change, not noise. */
    background:
      radial-gradient(
        120% 90% at 50% 0%,
        color-mix(in srgb, var(--panel-accent) 3%, transparent) 0%,
        transparent 70%
      );
    transition: background var(--dur-slow) var(--ease-standard);
  }

  .product-panels.is-pinned .product-panel__inner {
    width: 100%;
  }

  /* Active-panel accent bloom. JS sets data-active-panel="N" on the section;
     the matching panel washes into a perceptible (but restrained) accent tint
     that crossfades over --dur-slow as the active panel changes. A top-anchored
     radial keeps the colour weighted behind the heading, fading to clear toward
     the foot so body copy stays high-contrast. */
  .products--pinned[data-active-panel="0"] .product-panel--ims,
  .products--pinned[data-active-panel="1"] .product-panel--iv,
  .products--pinned[data-active-panel="2"] .product-panel--df {
    background:
      radial-gradient(
        120% 100% at 50% 0%,
        color-mix(in srgb, var(--panel-accent) 16%, transparent) 0%,
        color-mix(in srgb, var(--panel-accent) 7%, transparent) 45%,
        transparent 80%
      );
  }
}

/* Progress indicator — three ridge-segment dots, FIXED to the lower edge of
   the viewport while the sequence is pinned. The nav is appended to <body>
   (outside #smooth-content) by JS so position:fixed is not captured by the
   ScrollSmoother transform. Hidden until JS adds .is-visible, and never shown
   below 1024px. */
.products-progress {
  display: none;
}

@media (min-width: 1024px) {
  .products-progress.is-visible {
    position: fixed;
    left: 50%;
    bottom: var(--space-7);
    transform: translateX(-50%);
    z-index: var(--z-progress);
    display: flex;
    align-items: center;
    gap: var(--space-3);
    pointer-events: none;
    /* gentle fade-in as the sequence engages */
    animation: products-progress-in var(--dur-med) var(--ease-out) both;
  }

  @keyframes products-progress-in {
    from { opacity: 0; transform: translate(-50%, 8px); }
    to   { opacity: 1; transform: translate(-50%, 0); }
  }

  .products-progress__dot {
    /* ridge "segment": a short bar, not a circle — echoes the fingerprint */
    width: 28px;
    height: 3px;
    border-radius: var(--radius-pill);
    background-color: var(--navy-300);
    opacity: 0.55;
    transition:
      background-color var(--dur-med) var(--ease-standard),
      opacity var(--dur-med) var(--ease-standard),
      transform var(--dur-med) var(--ease-out);
  }

  .products-progress__dot.is-active {
    background-color: var(--beacon-500);
    opacity: 1;
    transform: scaleX(1.35);
    transform-origin: left center;
  }
}
