/* ==========================================================================
   Hetnet Wireless Technologies — Components & Layout
   --------------------------------------------------------------------------
   Consumes tokens from brand.css exclusively. No literal colour values.
   Mobile-first. Breakpoints: 768px, 1024px, 1440px.

   CONTENTS
     00  Web fonts
     01  Reset & base
     02  Typography
     03  Layout primitives
     04  Section furniture
     05  Buttons
     06  Cards
     07  Icons
     08  Header & navigation
     09  Hero
     10  Capability strip
     11  Service / solution / feature grids
     12  Resource tiles
     13  News list
     14  CTA band
     15  Footer
     16  Forms
     17  Tags, pills, status
     18  Async states
     19  Inner page header
     20  Prose
     21  Process steps
     22  Spec table
     23  Resource listing
     24  Contact layout
     25  Motion
     26  Theme toggle
     27  Print
   ========================================================================== */


/* ==========================================================================
   00  WEB FONTS
   --------------------------------------------------------------------------
   Self-hosted, no third-party request. Both faces are variable, so one file
   per subset covers the whole weight range the site uses.

   unicode-range gates the download: an English page fetches only the two
   `latin` files (~78 KB combined). latin-ext is fetched only if a character
   in that range actually appears.

   font-display: swap renders the fallback immediately and swaps on load,
   so a slow font never blocks first paint. The fallback stacks in
   --font-body / --font-mono are the system stacks this site used before,
   which means a failed font load degrades to the previous design rather
   than to Times New Roman.
   ========================================================================== */

@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url("../fonts/inter-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6,
                 U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F,
                 U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF,
                 U+FFFD;
}

@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url("../fonts/inter-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7,
                 U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF,
                 U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0,
                 U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: "JetBrains Mono";
  font-style: normal;
  font-weight: 400 600;
  font-display: swap;
  src: url("../fonts/jetbrains-mono-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6,
                 U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F,
                 U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF,
                 U+FFFD;
}

@font-face {
  font-family: "JetBrains Mono";
  font-style: normal;
  font-weight: 400 600;
  font-display: swap;
  src: url("../fonts/jetbrains-mono-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7,
                 U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF,
                 U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0,
                 U+2113, U+2C60-2C7F, U+A720-A7FF;
}


/* ==========================================================================
   01  RESET & BASE
   ========================================================================== */

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

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

@media (prefers-reduced-motion: no-preference) {
  html { scroll-behavior: smooth; }
}

body {
  margin: 0;
  /* Guards against any single element forcing a horizontal scrollbar. */
  overflow-x: hidden;
  background: var(--c-surface);
  color: var(--c-text);
  font-family: var(--font-body);
  font-size: var(--fs-base);
  line-height: var(--lh-body);
  font-weight: var(--fw-regular);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

/* Inter ships contextual alternates and tabular figures. `cv11` gives the
   single-storey `l` that keeps 5G/LTE strings legible at small sizes. */
body {
  font-feature-settings: "cv11" 1, "ss01" 1;
  font-optical-sizing: auto;
}

/* Spec values, KPI numbers and file sizes must align in columns. */
.mono,
.hero__panel-val,
.spec-table td,
.resource-row__attrs { font-variant-numeric: tabular-nums; }

/* Theme change should read as a deliberate transition, not a flash. Scoped
   to the properties that actually change, and only while the user has opted
   into motion. The `.theme-ready` class is added by main.js AFTER the first
   paint, so the initial theme resolution is instant rather than animating
   from light to dark on every page load. */
@media (prefers-reduced-motion: no-preference) {
  .theme-ready body,
  .theme-ready .site-header,
  .theme-ready .card,
  .theme-ready .resource-tile,
  .theme-ready .hero__panel {
    transition: background-color var(--dur-slow) var(--ease-out),
                border-color var(--dur-slow) var(--ease-out),
                color var(--dur-slow) var(--ease-out);
  }
}

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

/* Media and long unbroken strings must never widen the viewport. */
pre, code, table { max-width: 100%; }

ul, ol { margin: 0; padding: 0; }
figure { margin: 0; }

:target { scroll-margin-top: 6rem; }

/* --- Focus -------------------------------------------------------------- */

/* Remove the ring only where a visible one is guaranteed by :focus-visible. */
:focus { outline: none; }

:focus-visible {
  outline: var(--bw-marker) solid var(--c-focus-ring);
  outline-offset: 2px;
  border-radius: var(--r-sm);
}

/* On the navy surfaces the primary blue ring has too little contrast. */
.surface-inverse :focus-visible,
.site-footer :focus-visible,
.cta-band :focus-visible {
  outline-color: var(--c-focus-ring-inv);
}

.skip-link {
  position: absolute;
  left: var(--sp-4);
  top: var(--sp-4);
  z-index: var(--z-skip);
  padding: var(--sp-3) var(--sp-5);
  background: var(--c-surface-inverse);
  color: var(--c-text-inverse);
  font-weight: var(--fw-semibold);
  text-decoration: none;
  border-radius: var(--r-md);
  transform: translateY(-200%);
}

.skip-link:focus-visible { transform: translateY(0); }

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}


/* ==========================================================================
   02  TYPOGRAPHY
   ========================================================================== */

h1, h2, h3, h4, h5, h6 {
  margin: 0;
  font-family: var(--font-head);
  font-weight: var(--fw-bold);
  line-height: var(--lh-snug);
  letter-spacing: var(--ls-tight);
  color: var(--c-text);
  text-wrap: balance;
}

h1 {
  font-size: var(--fs-4xl);
  line-height: var(--lh-tight);
  font-weight: var(--fw-black);
}
h2 {
  font-size: var(--fs-3xl);
  line-height: var(--lh-tight);
  font-weight: var(--fw-black);
}
h3 { font-size: var(--fs-xl); }

/* --- Gradient display type ---------------------------------------------
   The mark is a sphere of parallel bands sweeping light to dark. Clipping
   that same sweep to the display headings ties the page to the logo.

   Applied ONLY to h1 and the top-level section h2 — never to body copy,
   card titles or anything below --fs-2xl. Gradient text cannot be forced
   to a single contrast value, so it is restricted to large bold type where
   the 3:1 large-text threshold applies, and both gradient endpoints are
   chosen to clear it independently.

   `color` is set first and remains the rendered colour anywhere
   background-clip: text is unsupported, so this degrades to solid ink
   rather than to invisible text.

   Stated as "all h1/h2, except where it is wrong" rather than as a list of
   structural paths. The earlier version matched only headings inside a
   .section-head, which silently skipped the eight large headings that live
   in .prose (about, solutions) and in the contact page's two columns — they
   rendered solid while every other page's headings were gradient. An
   opt-out list stays correct when a page adds a heading somewhere new; an
   opt-in list of paths does not.

   The exclusions, and why each one is not negotiable:
     - .surface-inverse / .cta-band / .site-footer are dark slabs in BOTH
       themes. The light-theme gradient runs navy-to-blue and would be
       nearly invisible there, so those stay solid --c-text-inverse.
     - Card, tile and row headings are small (--fs-lg, ~22px). Below the
       24px/18.66px-bold threshold the 3:1 large-text allowance no longer
       applies, and a gradient cannot be pinned to a single contrast value.
     - .footer-col__title is an h2 in the markup despite rendering as a
       13px label; without an explicit reset it would be clipped to
       transparent and vanish.
   ------------------------------------------------------------------------ */
.grad-text,
h1,
h2 {
  color: var(--c-primary-900);
}

@supports ((-webkit-background-clip: text) or (background-clip: text)) {

  .grad-text,
  h1,
  h2 {
    background-image: var(--grad-heading);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    /* Descenders on `g`, `y`, `p` clip against the text box otherwise. */
    padding-bottom: 0.08em;
    /* Safari needs the fill explicitly cleared as well as `color`. */
    -webkit-text-fill-color: transparent;
  }

  /* Opt-outs. Class selectors outrank the bare h1/h2 above, so these win
     without !important. -webkit-text-fill-color must be reset too: clearing
     `color` alone leaves the fill transparent in WebKit. */
  .surface-inverse h1,
  .surface-inverse h2,
  .cta-band h2,
  .site-footer h2,
  .card h2,
  .resource-tile h2,
  .news-item h2,
  .resource-row h2,
  .callout h2,
  .footer-col__title,
  .h-sub {
    background-image: none;
    padding-bottom: 0;
    color: var(--c-text);
    -webkit-text-fill-color: currentColor;
  }

  .surface-inverse h1,
  .surface-inverse h2,
  .cta-band h2,
  .site-footer h2,
  .footer-col__title { color: var(--c-text-inverse); }

  .h-sub { color: var(--c-text-muted); }
}

/* Forced-colours mode discards background-image entirely; without this every
   heading on the site would render as transparent-on-transparent. */
@media (forced-colors: active) {
  .grad-text,
  h1,
  h2 {
    background-image: none;
    color: CanvasText;
    -webkit-text-fill-color: CanvasText;
  }
}
h4 { font-size: var(--fs-lg); letter-spacing: var(--ls-normal); }
h5, h6 { font-size: var(--fs-md); letter-spacing: var(--ls-normal); }

p {
  margin: 0 0 var(--sp-4);
  max-width: var(--measure);
}

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

a { color: var(--c-link); text-decoration-thickness: 1px; text-underline-offset: 0.18em; }
a:hover { color: var(--c-link-hover); }

strong { font-weight: var(--fw-semibold); }

/* The wide-tracked uppercase label echoes the logo wordmark. */
.eyebrow {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  margin: 0 0 var(--sp-4);
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--c-primary-700);
}

/* The short banded rule is the logo's parallel-band motif, reduced. */
.eyebrow::after {
  content: "";
  flex: 0 0 auto;
  width: 58px;
  height: 2px;
  background-image: var(--band-rule);
}

.surface-inverse .eyebrow { color: var(--c-accent); }
.surface-inverse .eyebrow::after { background-image: var(--band-rule-inverse); }

.lead {
  font-size: var(--fs-lg);
  line-height: var(--lh-body);
  color: var(--c-text-muted);
  max-width: var(--measure-tight);
}

.text-muted { color: var(--c-text-muted); }

.mono {
  font-family: var(--font-mono);
  font-size: 0.92em;
  letter-spacing: var(--ls-normal);
}


/* ==========================================================================
   03  LAYOUT PRIMITIVES
   ========================================================================== */

.container {
  width: 100%;
  max-width: var(--w-container);
  margin-inline: auto;
  padding-inline: var(--sp-gutter);
}

.container--narrow { max-width: var(--w-container-n); }

.grid { display: grid; gap: var(--sp-5); }

/* minmax(0, 1fr) rather than 1fr. A bare `1fr` is minmax(auto, 1fr), and
   `auto` floors the track at its min-content width — so a single long word
   such as "Documentation" widens its own column and pushes the whole grid
   past the viewport. minmax(0, ...) removes that floor. */
@media (min-width: 768px) {
  .grid { gap: var(--sp-6); }
  .grid--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .grid--3 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .grid--4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (min-width: 1024px) {
  .grid--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .grid--4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}

.stack > * + * { margin-top: var(--sp-4); }
.stack-lg > * + * { margin-top: var(--sp-6); }

/* Small utilities, so no HTML file needs an inline style attribute. */
.mt-6 { margin-top: var(--sp-6); }
.icon--brand { color: var(--c-primary-500); }
.surface-inverse .icon--brand { color: var(--c-accent); }


/* ==========================================================================
   04  SECTION FURNITURE
   ========================================================================== */

.section { padding-block: var(--sp-section); }
.section--lg { padding-block: var(--sp-section-l); }
.section--sunken { background: var(--c-surface-sunken); }

.surface-inverse {
  background: var(--c-surface-inverse);
  color: var(--c-text-inverse);
}

.surface-inverse h1,
.surface-inverse h2,
.surface-inverse h3,
.surface-inverse h4 { color: var(--c-text-inverse); }

.surface-inverse .text-muted,
.surface-inverse .lead { color: var(--c-text-inverse-mut); }

.section-head { margin-bottom: var(--sp-8); }
.section-head p { max-width: var(--measure-tight); }

@media (min-width: 1024px) {
  .section-head--split {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: var(--sp-8);
    align-items: end;
  }
  .section-head--split .section-head__aside { padding-bottom: var(--sp-1); }
}

/* Hairline divider carrying the band motif. */
.rule-band {
  height: 2px;
  border: 0;
  margin: 0;
  background-image: var(--band-rule);
  opacity: 0.5;
}


/* ==========================================================================
   05  BUTTONS
   ========================================================================== */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-3);
  /* 14px block padding + 1.5 line-height on 16px text clears 44px. */
  min-height: 44px;
  padding: var(--sp-3) var(--sp-6);
  border: var(--bw-hair) solid transparent;
  border-radius: var(--r-md);
  font-family: inherit;
  font-size: var(--fs-base);
  font-weight: var(--fw-semibold);
  line-height: var(--lh-normal);
  letter-spacing: var(--ls-wide);
  text-align: center;
  text-decoration: none;
  cursor: pointer;
  transition: background-color var(--dur-base) var(--ease-out),
              border-color var(--dur-base) var(--ease-out),
              color var(--dur-base) var(--ease-out),
              box-shadow var(--dur-base) var(--ease-out);
}

/* Gradient fill rather than flat brand blue. The sweep runs along the same
   axis as the mark's bands.

   background-image is used, not background — so `background-color` below it
   still shows through as the fallback if the gradient is dropped, and the
   colour transition on hover stays cheap (no gradient interpolation, which
   browsers cannot animate). Hover swaps the whole image instead. */
.btn--primary {
  background-color: var(--c-primary-500);
  background-image: var(--grad-btn);
  border-color: transparent;
  color: var(--c-text-on-primary);
  box-shadow: var(--sh-1);
}
.btn--primary:hover {
  background-image: var(--grad-btn-hov);
  color: var(--c-text-on-primary);
  box-shadow: var(--sh-glow-btn);
}

.btn--secondary {
  background: transparent;
  border-color: var(--c-border);
  /* Was --c-primary-900. On the dark canvas that token resolves to the
     structural navy #1B3A52, which is near-invisible on #070E15; --c-text
     is theme-correct in both. */
  color: var(--c-text);
}
.btn--secondary:hover {
  border-color: var(--c-primary-500);
  background: var(--c-primary-100);
  color: var(--c-text);
}

/* On the inverse surface: mint fill, carrying the darkest available ink.
   13.7:1 in dark, 9.6:1 in light. */
.btn--accent {
  background-color: var(--c-accent);
  background-image: none;
  border-color: transparent;
  color: var(--c-text-on-accent);
}
.btn--accent:hover {
  background-color: var(--c-primary-300);
  color: var(--c-text-on-accent);
  box-shadow: var(--sh-glow-btn);
}

.btn--ghost-inverse {
  background: transparent;
  border-color: var(--c-border-inverse);
  color: var(--c-text-inverse);
}
.btn--ghost-inverse:hover {
  border-color: var(--c-accent);
  background: transparent;
  color: var(--c-accent);
}

.btn--block { width: 100%; }

/* Text link that behaves as a call to action. */
.link-cta {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  min-height: 44px;
  font-weight: var(--fw-semibold);
  font-size: var(--fs-sm);
  letter-spacing: var(--ls-wide);
  text-decoration: none;
  color: var(--c-link);
  transition: color var(--dur-base) var(--ease-out),
              gap var(--dur-base) var(--ease-out);
}
.link-cta:hover { color: var(--c-link-hover); }
.link-cta .icon { flex: 0 0 auto; }

@media (prefers-reduced-motion: no-preference) {
  .link-cta:hover { gap: var(--sp-3); }
}


/* ==========================================================================
   06  CARDS
   ========================================================================== */

.card {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
  padding: var(--sp-6);
  background: var(--c-surface-raised);
  border: var(--bw-hair) solid var(--c-border);
  border-radius: var(--r-lg);
  /* overflow clips the ::before ribbon to the rounded corners. Safe here:
     nothing inside a card is positioned outside its box, and the focus ring
     is drawn on the card itself (see .card--link:has) rather than on a
     child, so it is not clipped. */
  overflow: hidden;
  box-shadow: var(--sh-1);
  transition: border-color var(--dur-base) var(--ease-out),
              box-shadow var(--dur-base) var(--ease-out),
              transform var(--dur-base) var(--ease-out);
}

/* A card's heading level is set by document outline, not by size. On a
   listing page the card heading is an h2; it must still look like a card
   title rather than a section title. */
.card h2,
.card h3 { font-size: var(--fs-lg); letter-spacing: var(--ls-normal); }

.card p { max-width: none; color: var(--c-text-muted); }
.card > :last-child { margin-top: auto; }

/* Sub-heading inside a card, above a spec list. */
.h-sub {
  font-size: var(--fs-xs) !important;
  font-weight: var(--fw-semibold);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--c-text-muted);
  margin-bottom: calc(var(--sp-3) * -1 + var(--sp-1));
}

/* Whole-card link: the heading anchor stretches over the card face. */
.card--link:hover {
  border-color: var(--c-border-strong);
  box-shadow: var(--sh-glow);
}

/* Lift is 2px and applied to transform only — compositor-only, no layout
   pass, and small enough that the pointer never falls out from under the
   card mid-hover (which would cause a hover/unhover flicker loop). */
@media (prefers-reduced-motion: no-preference) {
  .card--link:hover { transform: translateY(-2px); }
}

.card--link .card__title a { color: var(--c-text); text-decoration: none; }
.card--link .card__title a::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
}
.card--link:hover .card__title a { color: var(--c-primary-700); }

/* Keyboard focus lands on the inner anchor; lift the ring to the card edge. */
.card--link:has(a:focus-visible) {
  outline: var(--bw-marker) solid var(--c-focus-ring);
  outline-offset: 2px;
}
.card--link a:focus-visible { outline: none; }

/* --- Ribbon edge --------------------------------------------------------
   Replaces the former `.card--chamfer` cut corner, which was incompatible
   with the rounded surface language (its 45-degree notch reads as a
   rendering artefact against a 12px radius).

   The band motif survives as a single gradient ribbon along the card's top
   edge — the same light-to-dark sweep as the mark, reduced to one stroke.
   It fades out at both ends so it reads as a highlight catching the edge
   rather than as a border.

   Height is 2px, not 1px: a 1px line disappears on non-integer device pixel
   ratios, which is most Windows laptops at 125% or 150% scaling.
   ------------------------------------------------------------------------ */
.card--ribbon::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 2px;
  background-image: var(--grad-edge);
  opacity: 0.85;
  pointer-events: none;
  transition: opacity var(--dur-base) var(--ease-out);
}

.card--ribbon:hover::before { opacity: 1; }

.card__index {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  font-weight: var(--fw-medium);
  letter-spacing: var(--ls-label);
  color: var(--c-n-5);
}

.surface-inverse .card {
  background: rgba(255, 255, 255, 0.03);
  border-color: var(--c-border-inverse);
}
.surface-inverse .card p { color: var(--c-text-inverse-mut); }


/* ==========================================================================
   07  ICONS
   --------------------------------------------------------------------------
   Every inline SVG uses a 24x24 viewBox, currentColor stroke, butt caps and
   miter joins. Square terminals mirror the mark's chisel-cut band ends;
   rounded caps would soften geometry the logo does not have.
   ========================================================================== */

.icon {
  width: 24px;
  height: 24px;
  fill: none;
  stroke: currentColor;
  stroke-width: var(--icon-stroke);
  stroke-linecap: butt;
  stroke-linejoin: miter;
}

.icon--sm { width: 18px; height: 18px; }
.icon--lg { width: 32px; height: 32px; }

.icon-plate {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 52px;
  height: 52px;
  background: var(--c-plate-bg);
  color: var(--c-plate-fg);
  border: var(--bw-hair) solid var(--c-border-subtle);
  border-radius: var(--r-md);
  transition: border-color var(--dur-base) var(--ease-out),
              color var(--dur-base) var(--ease-out);
}

.card--link:hover .icon-plate,
.resource-tile:hover .icon-plate { border-color: var(--c-primary-500); }

.surface-inverse .icon-plate {
  background: rgba(255, 255, 255, 0.04);
  border-color: var(--c-border-inverse);
  color: var(--c-accent);
}


/* ==========================================================================
   08  HEADER & NAVIGATION
   ========================================================================== */

/* Translucent + blurred, so page content dissolves under the bar on scroll.

   The opaque `background` is declared first and is what actually renders
   for the ~5% of traffic without backdrop-filter support (and in Firefox
   when the user has disabled it). Only inside @supports does the bar become
   translucent — so it is never an unreadable transparent strip. */
.site-header {
  position: sticky;
  top: 0;
  z-index: var(--z-nav);
  background: var(--c-surface);
  border-bottom: var(--bw-hair) solid var(--c-border-subtle);
  box-shadow: var(--sh-header);
}

@supports ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .site-header {
    background: var(--c-surface-header);
    -webkit-backdrop-filter: blur(var(--blur-header)) saturate(180%);
    backdrop-filter: blur(var(--blur-header)) saturate(180%);
  }
}

/* A single `margin-right: auto` on the brand does all the alignment work
   here, at every breakpoint and for both header arrangements:

     mobile   brand | ....... | hamburger  theme
     desktop  brand | ....... | nav links  theme

   space-between was wrong once the header gained a third in-flow child —
   it would have stranded the hamburger in the middle of the bar. Two
   competing auto margins (one on the brand, one on the nav) would have
   split the free space instead of pushing everything right, so the nav
   does NOT get one. */
.site-header__inner {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  min-height: 68px;
}

.brand { margin-right: auto; }

/* --- Brand lockup ------------------------------------------------------- */

.brand {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-3);
  text-decoration: none;
  color: var(--c-text);
  padding-block: var(--sp-2);
}

.brand__mark { width: 38px; height: 38px; flex: 0 0 auto; }

.brand__text { display: flex; flex-direction: column; line-height: 1.1; }

.brand__name {
  font-size: var(--fs-sm);
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  /* --c-primary-900 resolves to a structural navy in dark mode and would be
     unreadable on the canvas; --c-text is correct in both themes. */
  color: var(--c-text);
  white-space: nowrap;
}

.brand__suffix {
  font-size: 0.6875rem;
  font-weight: var(--fw-medium);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--c-n-5);
  white-space: nowrap;
}

@media (min-width: 480px) {
  .brand__name { font-size: var(--fs-base); }
}

.site-footer .brand__name { color: var(--c-text-inverse); }
.site-footer .brand__suffix { color: var(--c-text-inverse-mut); }

/* --- Toggle ------------------------------------------------------------- */

.nav-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  width: 44px;
  height: 44px;
  padding: 0;
  background: transparent;
  border: var(--bw-hair) solid var(--c-border);
  border-radius: var(--r-md);
  color: var(--c-text);
  cursor: pointer;
  transition: border-color var(--dur-base) var(--ease-out),
              background-color var(--dur-base) var(--ease-out);
}

.nav-toggle:hover {
  border-color: var(--c-primary-500);
  background: var(--c-primary-100);
}

.nav-toggle__bar {
  display: block;
  width: 18px;
  height: 2px;
  background: currentColor;
  transition: transform var(--dur-base) var(--ease-out),
              opacity var(--dur-fast) var(--ease-out);
}

.nav-toggle__bars {
  display: flex;
  flex-direction: column;
  gap: 4px;
  align-items: center;
}

.nav-toggle[aria-expanded="true"] .nav-toggle__bar:nth-child(1) {
  transform: translateY(6px) rotate(45deg);
}
.nav-toggle[aria-expanded="true"] .nav-toggle__bar:nth-child(2) { opacity: 0; }
.nav-toggle[aria-expanded="true"] .nav-toggle__bar:nth-child(3) {
  transform: translateY(-6px) rotate(-45deg);
}

/* --- Nav panel ---------------------------------------------------------- */

.site-nav {
  position: absolute;
  left: 0;
  right: 0;
  top: 100%;
  /* A sheet floating over content needs to be opaque and raised — the blur
     on .site-header does not extend to this panel. */
  background: var(--c-surface-raised);
  border-bottom: var(--bw-hair) solid var(--c-border);
  box-shadow: var(--sh-3);
  max-height: calc(100dvh - 68px);
  overflow-y: auto;
}

/* Closed state. `hidden` keeps it out of the tab order entirely. */
.site-nav[hidden] { display: none; }

.site-nav__list {
  list-style: none;
  display: flex;
  flex-direction: column;
  padding: var(--sp-3) var(--sp-gutter) var(--sp-5);
}

.site-nav__link {
  display: flex;
  align-items: center;
  min-height: 48px;
  padding: var(--sp-2) 0;
  font-size: var(--fs-base);
  font-weight: var(--fw-medium);
  color: var(--c-text-muted);
  text-decoration: none;
  border-bottom: var(--bw-hair) solid var(--c-border-subtle);
  border-radius: var(--r-md);
  transition: color var(--dur-base) var(--ease-out),
              background-color var(--dur-base) var(--ease-out);
}

.site-nav__link:hover { color: var(--c-text); }

.site-nav__link[aria-current="page"] {
  color: var(--c-text);
  font-weight: var(--fw-semibold);
}

.site-nav__cta { margin-top: var(--sp-5); }
.site-nav__cta .btn { width: 100%; }

/* Desktop nav starts at 1200px, not 1024px.

   Measured at 1024: brand lockup 230px + theme toggle 52px + seven links
   ~520px + the Contact CTA 142px + container gutters 80px = ~1070px, which
   is 60px more than the 1009px available. Before `white-space: nowrap` that
   surplus showed up as the nav wrapping to two lines; with nowrap it became
   60-98px of horizontal overflow instead. Neither is acceptable, and no
   amount of padding trimming buys 60px without pushing the links under the
   44px touch target.

   So 1024-1199px (iPad landscape, small laptops) keeps the disclosure menu.
   The grid and layout breakpoints elsewhere in this file stay at 1024 —
   only the navigation moves. */
@media (min-width: 1200px) {
  .nav-toggle { display: none; }

  /* Static row on desktop; the hidden attribute must not hide it. */
  .site-nav,
  .site-nav[hidden] {
    display: block;
    position: static;
    max-height: none;
    overflow: visible;
    border: 0;
    box-shadow: none;
    background: transparent;
  }

  .site-nav__list {
    flex-direction: row;
    align-items: center;
    gap: var(--sp-1);
    padding: 0;
  }

  .site-nav__link {
    position: relative;
    min-height: 44px;
    padding-inline: var(--sp-2);
    font-size: var(--fs-sm);
    border-bottom: 0;
    /* Eight items plus a CTA and the theme toggle is a tight row at 1024px.
       Without this, "Resource Centre" and "Contact us" break onto a second
       line and the header grows by ~24px. */
    white-space: nowrap;
  }

  .site-nav__cta .btn { white-space: nowrap; }

  /* Pill hover rather than a colour-only change — gives the nav the same
     soft-cornered language as the cards and buttons. */
  .site-nav__link:hover { background: var(--c-primary-100); }

  /* Active marker uses the band motif rather than a plain underline.
     Colour alone does not mark the current page: the weight change on
     [aria-current] above carries it too. */
  .site-nav__link[aria-current="page"]::after {
    content: "";
    position: absolute;
    left: var(--sp-2);
    right: var(--sp-2);
    bottom: 2px;
    height: 2px;
    background-image: var(--band-rule);
  }

  /* The CTA is a filled button; it needs less horizontal padding than the
     default .btn so the row fits at exactly 1024px. */
  .site-nav__cta .btn { padding-inline: var(--sp-5); }

  .site-nav__cta { margin-top: 0; margin-left: var(--sp-4); }
  .site-nav__cta .btn { width: auto; }
}

/* Only the padding relaxes on wide screens. Raising the link font-size to
   --fs-base here (17px at this width) was what pushed "Resource Centre" and
   the CTA onto a second line at 1440 — the row has no slack left to spend
   on type size once the theme toggle is in it. */
@media (min-width: 1440px) {
  .site-nav__link { padding-inline: var(--sp-3); }
}


/* ==========================================================================
   09  HERO
   --------------------------------------------------------------------------
   Asymmetric two-column: copy left, technical spec panel right. No stock
   photograph, no centred text over a gradient wash.
   ========================================================================== */

.hero {
  position: relative;
  padding-block: var(--sp-9) var(--sp-section);
  background-color: var(--c-surface);
  /* Two radial washes, painted on the section itself so they sit UNDER the
     band field in ::before. Together they pull the eye diagonally from the
     headline toward the spec panel. */
  background-image: var(--grad-ambient), var(--grad-ambient-2);
  background-repeat: no-repeat;
  border-bottom: var(--bw-hair) solid var(--c-border-subtle);
  overflow: hidden;
}

/* Band field: the logo's parallel ribbons, flattened and set very low
   contrast so it reads as texture rather than decoration. Both the gradient
   and its opacity are themed — on the dark canvas a navy-tinted 0.55 field
   vanishes entirely, so the dark theme raises it to 0.9 over a brighter
   stroke colour. */
.hero::before {
  content: "";
  position: absolute;
  inset: 0;
  background-image: var(--band-field);
  opacity: var(--band-field-opacity);
  /* Fades the texture out toward the baseline so it never competes with the
     content sitting on top of it. */
  -webkit-mask-image: linear-gradient(to bottom, #000 0%, transparent 92%);
  mask-image: linear-gradient(to bottom, #000 0%, transparent 92%);
  pointer-events: none;
}

.hero__inner { position: relative; }

.hero__title { max-width: 16ch; }

.hero__lead {
  margin-top: var(--sp-5);
  font-size: var(--fs-lg);
  color: var(--c-text-muted);
  max-width: 46ch;
}

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

.hero__actions .btn { flex: 1 1 auto; }

@media (min-width: 480px) {
  .hero__actions .btn { flex: 0 0 auto; }
}

/* Technical panel — the credibility device in place of a photograph. */
.hero__panel {
  position: relative;
  margin-top: var(--sp-9);
  border: var(--bw-hair) solid var(--c-border);
  border-radius: var(--r-xl);
  background: var(--c-surface-raised);
  box-shadow: var(--sh-3);
  overflow: hidden;
}

/* The 3px solid top border became a gradient ribbon, matching the cards. */
.hero__panel::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 2px;
  background-image: var(--grad-edge);
  pointer-events: none;
  z-index: var(--z-raised);
}

.hero__panel-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
  padding: var(--sp-4) var(--sp-5);
  border-bottom: var(--bw-hair) solid var(--c-border-subtle);
  background: var(--c-surface-sunken);
}

.hero__panel-title {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--c-n-5);
}

.hero__panel-list { list-style: none; }

.hero__panel-item {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-4);
  padding: var(--sp-4) var(--sp-5);
  border-bottom: var(--bw-hair) solid var(--c-border-subtle);
}

.hero__panel-item:last-child { border-bottom: 0; }

.hero__panel-key {
  font-weight: var(--fw-semibold);
  font-size: var(--fs-sm);
  color: var(--c-text);
}

.hero__panel-val {
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
  color: var(--c-text-muted);
  text-align: right;
}

@media (min-width: 1024px) {
  .hero { padding-block: var(--sp-11) var(--sp-section-l); }
  .hero__inner {
    display: grid;
    grid-template-columns: minmax(0, 1.15fr) minmax(0, 0.85fr);
    gap: var(--sp-9);
    align-items: start;
  }
  .hero__panel { margin-top: var(--sp-2); }
}

@media (min-width: 1440px) {
  .hero__inner { gap: var(--sp-11); }
}


/* ==========================================================================
   10  CAPABILITY STRIP
   ========================================================================== */

.capability-strip {
  border-bottom: var(--bw-hair) solid var(--c-border-subtle);
  background: var(--c-surface-sunken);
  padding-block: var(--sp-7);
}

.capability-strip__list {
  list-style: none;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--sp-5) var(--sp-4);
}

.capability {
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
  padding-left: var(--sp-4);
  border-left: var(--bw-thick) solid var(--c-primary-500);
  border-radius: var(--r-sm);
}

.capability__label {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--c-text-muted);
}

.capability__value {
  font-size: var(--fs-md);
  font-weight: var(--fw-semibold);
  color: var(--c-text);
  line-height: var(--lh-snug);
}

@media (min-width: 768px) {
  .capability-strip__list { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}



/* Command Centre technology matrix — a compact evidence register rather than
   a decorative feature block. */
.hero--command .hero__panel { background: var(--c-surface-raised); }
.section--compact { padding-block: var(--sp-10); }
.technology-matrix { padding-block: var(--sp-10); background: var(--c-surface-sunken); border-block: var(--bw-hair) solid var(--c-border-subtle); }
.technology-matrix__inner { display: grid; gap: var(--sp-7); }
.technology-matrix__inner h2 { max-width: 20ch; }
.technology-matrix__inner .text-muted { max-width: 46ch; margin-top: var(--sp-4); }
.technology-matrix__table { overflow: hidden; border: var(--bw-hair) solid var(--c-border); border-radius: var(--r-lg); background: var(--c-surface-raised); box-shadow: var(--sh-1); }
.technology-matrix__row { display: grid; grid-template-columns: minmax(7rem, 1.6fr) repeat(4, minmax(2.6rem, 1fr)); align-items: center; border-bottom: var(--bw-hair) solid var(--c-border-subtle); }
.technology-matrix__row:last-child { border-bottom: 0; }
.technology-matrix__row > * { padding: var(--sp-3) var(--sp-2); text-align: center; font-size: var(--fs-xs); }
.technology-matrix__row > :first-child { padding-left: var(--sp-4); text-align: left; font-weight: var(--fw-semibold); color: var(--c-text); }
.technology-matrix__row--head { background: var(--c-surface-sunken); }
.technology-matrix__row--head > * { font-family: var(--font-mono); font-size: 0.68rem; font-weight: var(--fw-semibold); letter-spacing: var(--ls-label); text-transform: uppercase; color: var(--c-text-muted); }
.technology-matrix__row b { color: var(--c-primary-500); font-size: var(--fs-md); }
@media (min-width: 1024px) { .technology-matrix__inner { grid-template-columns: minmax(0, 0.8fr) minmax(30rem, 1.2fr); align-items: center; gap: var(--sp-9); } .technology-matrix__row > * { padding-block: var(--sp-4); } }
/* ===========================================================================
   11  SERVICE / SOLUTION / FEATURE GRIDS
   ========================================================================== */

.service-card__head {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-4);
}

.service-card h3 { margin-top: var(--sp-1); }

.spec-list {
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  font-size: var(--fs-sm);
  color: var(--c-text-muted);
}

.spec-list li {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
}

.spec-list .icon {
  flex: 0 0 auto;
  width: 16px;
  height: 16px;
  margin-top: 0.35em;
  color: var(--c-primary-500);
}

.surface-inverse .spec-list .icon { color: var(--c-accent); }

/* Sector rows — a table-like register rather than another card grid. */
.sector-list {
  list-style: none;
  border-top: var(--bw-hair) solid var(--c-border-inverse);
}

.sector {
  display: grid;
  gap: var(--sp-3);
  padding-block: var(--sp-6);
  border-bottom: var(--bw-hair) solid var(--c-border-inverse);
}

.sector__index {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  letter-spacing: var(--ls-label);
  color: var(--c-accent);
}

.sector h3 { font-size: var(--fs-xl); }
.sector p { color: var(--c-text-inverse-mut); max-width: var(--measure); }

@media (min-width: 768px) {
  .sector {
    grid-template-columns: 4rem minmax(0, 1fr);
    column-gap: var(--sp-6);
  }
  .sector__index { grid-row: span 2; padding-top: 0.5em; }
}

@media (min-width: 1024px) {
  .sector {
    grid-template-columns: 4rem minmax(0, 20rem) minmax(0, 1fr);
    align-items: baseline;
  }
  .sector__index { grid-row: auto; }
  .sector p { padding-top: 0.15em; }
}

/* Why-choose-us: numbered differentiators, no metrics.
   The flat 3px brand rule becomes the same fading gradient ribbon the cards
   and tiles carry, so every "block with a top edge" on the site reads as one
   family. */
.reason {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  padding-top: var(--sp-5);
}

.reason::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 2px;
  background-image: var(--grad-edge);
  pointer-events: none;
}

.reason h3 { font-size: var(--fs-lg); }
.reason p { color: var(--c-text-muted); max-width: none; }


/* ==========================================================================
   12  RESOURCE TILES
   ========================================================================== */

.resource-tile {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
  padding: var(--sp-6);
  background: var(--c-surface-raised);
  border: var(--bw-hair) solid var(--c-border);
  border-radius: var(--r-lg);
  overflow: hidden;
  box-shadow: var(--sh-1);
  transition: border-color var(--dur-base) var(--ease-out),
              box-shadow var(--dur-base) var(--ease-out),
              transform var(--dur-base) var(--ease-out);
}

/* The left marker rule becomes the same top ribbon the cards carry, so
   every tile-like surface on the site shares one edge treatment. */
.resource-tile::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 2px;
  background-image: var(--grad-edge);
  opacity: 0.85;
  pointer-events: none;
}

.resource-tile:hover {
  border-color: var(--c-border-strong);
  box-shadow: var(--sh-glow);
}

@media (prefers-reduced-motion: no-preference) {
  .resource-tile:hover { transform: translateY(-2px); }
}

.resource-tile h3 { font-size: var(--fs-lg); }
.resource-tile h3 a { color: var(--c-text); text-decoration: none; }
.resource-tile h3 a::after { content: ""; position: absolute; inset: 0; }
.resource-tile:hover h3 a { color: var(--c-primary-700); }

.resource-tile:has(a:focus-visible) {
  outline: var(--bw-marker) solid var(--c-focus-ring);
  outline-offset: 2px;
}
.resource-tile a:focus-visible { outline: none; }

.resource-tile p { color: var(--c-text-muted); max-width: none; font-size: var(--fs-sm); }

.resource-tile__meta {
  margin-top: auto;
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  letter-spacing: var(--ls-wide);
  color: var(--c-n-5);
}


/* ==========================================================================
   13  NEWS LIST
   ========================================================================== */

/* Separate rounded rows rather than one hairline-divided register. Gap
   replaces the shared borders, so a hover can light a single row without
   the neighbouring divider appearing to belong to it. */
.news-list {
  list-style: none;
  display: grid;
  gap: var(--sp-3);
}

.news-item {
  position: relative;
  display: grid;
  gap: var(--sp-2);
  padding: var(--sp-5) var(--sp-5);
  background: var(--c-surface-raised);
  border: var(--bw-hair) solid var(--c-border);
  border-radius: var(--r-lg);
  transition: background-color var(--dur-base) var(--ease-out),
              border-color var(--dur-base) var(--ease-out),
              box-shadow var(--dur-base) var(--ease-out);
}

.news-item:hover {
  border-color: var(--c-border-strong);
  box-shadow: var(--sh-glow);
}

.news-item__date {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  letter-spacing: var(--ls-wide);
  color: var(--c-text-muted);
  text-transform: uppercase;
}

.news-item h3 { font-size: var(--fs-lg); }
.news-item h3 a { color: var(--c-text); text-decoration: none; }
.news-item h3 a:hover { color: var(--c-primary-700); }

.news-item p {
  color: var(--c-text-muted);
  font-size: var(--fs-sm);
  max-width: var(--measure);
  margin: 0;
}

@media (min-width: 768px) {
  .news-item {
    grid-template-columns: 9rem minmax(0, 1fr);
    column-gap: var(--sp-6);
    align-items: baseline;
    padding: var(--sp-5) var(--sp-6);
  }
  .news-item__date { grid-row: span 2; }
  .news-item h3 { grid-column: 2; }
  .news-item p { grid-column: 2; }

  /* The 9rem track above was sized for a date. A Knowledge Centre row puts
     a discipline and a reading estimate there instead, which needs more
     room or it breaks mid-phrase. */
  .news-item--kb { grid-template-columns: 12rem minmax(0, 1fr); }
}


/* ==========================================================================
   14  CTA BAND
   ========================================================================== */

/* Inset rounded slab rather than a full-bleed band — the shape TanStack
   uses for its closing call to action. The outer element stays full-bleed
   so the page background shows in the gutters. */
/* The inner element carries `.container`, which supplies max-width and the
   horizontal gutter — and that gutter is INSIDE the painted slab. So the
   inset from the viewport edge has to come from the outer element, or the
   slab runs edge-to-edge on mobile with no visible rounding. */
.cta-band {
  padding-block: var(--sp-section);
  padding-inline: var(--sp-gutter);
}

.cta-band__inner {
  position: relative;
  padding-block: var(--sp-8);
  background-color: var(--c-surface-inverse);
  background-image: var(--grad-ambient);
  background-repeat: no-repeat;
  border: var(--bw-hair) solid var(--c-border-inverse);
  border-radius: var(--r-xl);
  color: var(--c-text-inverse);
  overflow: hidden;
}

/* Continuous gradient, not the dashed --band-rule: at 18px corner radius the
   dashes get clipped mid-stroke and read as a broken border rather than as
   the band motif. The motif still appears on this slab via the eyebrow rule. */
.cta-band__inner::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 2px;
  background-image: var(--grad-edge);
  pointer-events: none;
}

@media (min-width: 768px) {
  .cta-band__inner { padding-block: var(--sp-9); }
}

.cta-band h2 { color: var(--c-text-inverse); max-width: 20ch; }
.cta-band p { color: var(--c-text-inverse-mut); max-width: var(--measure-tight); }

.cta-band__inner { display: grid; gap: var(--sp-7); }

.cta-band__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-4);
  align-content: start;
}

.cta-band__actions .btn { flex: 1 1 auto; }

@media (min-width: 480px) {
  .cta-band__actions .btn { flex: 0 0 auto; }
}

@media (min-width: 1024px) {
  .cta-band__inner {
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: center;
    gap: var(--sp-9);
  }
}


/* ==========================================================================
   15  FOOTER
   ========================================================================== */

.site-footer {
  background: var(--c-surface-inverse);
  color: var(--c-text-inverse-mut);
  padding-block: var(--sp-9) var(--sp-6);
  border-top: var(--bw-hair) solid var(--c-border-inverse);
}

.site-footer a { color: var(--c-text-inverse-mut); text-decoration: none; }
.site-footer a:hover { color: var(--c-accent); text-decoration: underline; }

.site-footer__top { display: grid; gap: var(--sp-8); }

.site-footer__intro p {
  margin-top: var(--sp-4);
  font-size: var(--fs-sm);
  max-width: 40ch;
}

.site-footer__nav { display: grid; gap: var(--sp-7); }

.footer-col__title {
  margin-bottom: var(--sp-4);
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--c-text-inverse);
}

.footer-col__list { list-style: none; display: grid; gap: var(--sp-1); }

/* 44px, not 36px: these are the primary navigation on a phone once the
   header menu is closed, and 36px is below the 44x44 target guidance.
   Padding rather than pure min-height so the hit area extends across the
   full column width, not just the text run. */
.footer-col__list a {
  display: flex;
  align-items: center;
  min-height: 44px;
  font-size: var(--fs-sm);
}

.site-footer__bottom {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3) var(--sp-5);
  align-items: center;
  justify-content: space-between;
  margin-top: var(--sp-8);
  padding-top: var(--sp-5);
  border-top: var(--bw-hair) solid var(--c-border-inverse);
  font-size: var(--fs-xs);
}

/* Two, then four. The Knowledge Centre took the footer from three columns to
   four, and 4 does not divide by 3 — at the intermediate width a repeat(3)
   would leave one orphaned column on its own row. Two columns pair evenly
   there and split cleanly again at the wide breakpoint. */
@media (min-width: 640px) {
  .site-footer__nav { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (min-width: 1100px) {
  .site-footer__nav { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--sp-6); }
}

@media (min-width: 1024px) {
  .site-footer__top {
    grid-template-columns: minmax(0, 1fr) minmax(0, 2fr);
    gap: var(--sp-11);
  }
}


/* ==========================================================================
   16  FORMS
   ========================================================================== */

.field { display: grid; gap: var(--sp-2); margin-bottom: var(--sp-5); }

.field__label {
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  color: var(--c-text);
}

.field__req { color: var(--c-error); margin-left: 0.15em; }

.field__hint { font-size: var(--fs-xs); color: var(--c-text-muted); }

.field__control {
  width: 100%;
  min-height: 48px;
  padding: var(--sp-3) var(--sp-4);
  background: var(--c-surface-sunken);
  border: var(--bw-hair) solid var(--c-border);
  border-radius: var(--r-md);
  font-family: inherit;
  font-size: var(--fs-base);
  line-height: var(--lh-normal);
  color: var(--c-text);
  transition: border-color var(--dur-base) var(--ease-out),
              box-shadow var(--dur-base) var(--ease-out);
}

.field__control::placeholder { color: var(--c-n-5); }

.field__control:hover { border-color: var(--c-border-strong); }

.field__control:focus-visible {
  border-color: var(--c-primary-500);
  outline-offset: 0;
}

textarea.field__control { min-height: 9rem; resize: vertical; }

select.field__control { cursor: pointer; }

.field__error {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-2);
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  color: var(--c-error);
}

.field__error[hidden] { display: none; }

.field__error .icon { flex: 0 0 auto; width: 16px; height: 16px; margin-top: 0.25em; }

/* Invalid state signals through border weight and an icon, not colour alone. */
.field--invalid .field__control {
  border-color: var(--c-error);
  border-width: var(--bw-thick);
}

.form-status {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
  padding: var(--sp-4) var(--sp-5);
  margin-bottom: var(--sp-6);
  border-radius: var(--r-md);
  font-size: var(--fs-sm);
}

.form-status[hidden] { display: none; }
.form-status--error { background: var(--c-error-bg); color: var(--c-error); }
.form-status--success { background: var(--c-success-bg); color: var(--c-success); }

@media (min-width: 768px) {
  .field-row { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 var(--sp-5); }
}


/* ==========================================================================
   17  TAGS, PILLS, STATUS
   ========================================================================== */

.tag-list { list-style: none; display: flex; flex-wrap: wrap; gap: var(--sp-2); }

.tag {
  display: inline-flex;
  align-items: center;
  padding: var(--sp-1) var(--sp-3);
  /* Was --c-n-1, a fixed near-white from the light neutral scale — it stayed
     white on the dark canvas. --c-surface-sunken is theme-aware. */
  background: var(--c-surface-sunken);
  border: var(--bw-hair) solid var(--c-border-subtle);
  border-radius: var(--r-pill);
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  letter-spacing: var(--ls-wide);
  color: var(--c-text-muted);
}

.pill-example {
  display: inline-flex;
  align-items: center;
  padding: var(--sp-1) var(--sp-3);
  background: var(--c-warning-bg);
  border-radius: var(--r-sm);
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--c-warning);
}


/* ==========================================================================
   18  ASYNC STATES
   --------------------------------------------------------------------------
   Fetch-driven regions reserve height so a slow manifest does not shift the
   page, and always resolve to a visible message rather than an empty box.
   ========================================================================== */

.async-region { min-height: 8rem; }

.async-msg {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
  padding: var(--sp-5);
  border: var(--bw-hair) dashed var(--c-border);
  border-radius: var(--r-md);
  font-size: var(--fs-sm);
  color: var(--c-text-muted);
}

.async-msg[hidden] { display: none; }
.async-msg .icon { flex: 0 0 auto; color: var(--c-n-5); }

.async-msg--error {
  border-style: solid;
  border-color: var(--c-warning);
  background: var(--c-warning-bg);
  color: var(--c-warning);
}
.async-msg--error .icon { color: var(--c-warning); }

.surface-inverse .async-msg,
.cta-band .async-msg { border-color: var(--c-border-inverse); color: var(--c-text-inverse-mut); }


/* ==========================================================================
   19  INNER PAGE HEADER
   ========================================================================== */

.page-header {
  position: relative;
  padding-block: var(--sp-8) var(--sp-9);
  background-color: var(--c-surface-sunken);
  background-image: var(--grad-ambient);
  background-repeat: no-repeat;
  border-bottom: var(--bw-hair) solid var(--c-border);
  overflow: hidden;
}

.page-header::before {
  content: "";
  position: absolute;
  inset: 0;
  background-image: var(--band-field);
  opacity: var(--band-field-opacity);
  -webkit-mask-image: linear-gradient(to bottom, #000 0%, transparent 92%);
  mask-image: linear-gradient(to bottom, #000 0%, transparent 92%);
  pointer-events: none;
}

.page-header__inner { position: relative; }
.page-header h1 { font-size: var(--fs-3xl); max-width: 20ch; }
.page-header .lead { margin-top: var(--sp-5); }

.breadcrumb { margin-bottom: var(--sp-5); }

.breadcrumb__list {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2);
  font-size: var(--fs-xs);
  letter-spacing: var(--ls-wide);
  color: var(--c-text-muted);
}

.breadcrumb__list li { display: flex; align-items: center; gap: var(--sp-2); }

.breadcrumb__list li + li::before {
  content: "/";
  color: var(--c-n-5);
}

.breadcrumb a {
  color: var(--c-text-muted);
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  min-height: 44px;
}
.breadcrumb a:hover { color: var(--c-link); text-decoration: underline; }
.breadcrumb [aria-current="page"] { color: var(--c-text); font-weight: var(--fw-medium); }


/* ==========================================================================
   20  PROSE
   ========================================================================== */

/* SCOPING NOTE
   Every rule here is a direct-child selector, and the list rules additionally
   require :not([class]).

   The descendant form (`.prose h3`, `.prose li + li`) reached into any
   component nested inside prose. On about.html the <ol class="process"> is a
   direct child of .prose, so its four <li class="process__item"> were picking
   up `li + li { margin-top: 8px }` — which pushed three of the four grid
   cells down by 8px and visibly broke the row alignment — while their <h3>
   inherited a 40px prose margin inside a component that sets its own rhythm.

   A classed element is owned by its component; prose styles only unclassed
   content it is directly responsible for. */

.prose > * + * { margin-top: var(--sp-5); }
.prose > h2 { font-size: var(--fs-2xl); margin-top: var(--sp-9); }
.prose > h3 { font-size: var(--fs-lg); margin-top: var(--sp-7); }
.prose > h2:first-child, .prose > h3:first-child { margin-top: 0; }
.prose > p { max-width: var(--measure); }

.prose > ul:not([class]),
.prose > ol:not([class]) { padding-left: var(--sp-5); max-width: var(--measure); }

/* Descendant `li` here so nested lists inside a prose list stay styled; the
   :not([class]) on the parent is what keeps components out. */
.prose > ul:not([class]) li + li,
.prose > ol:not([class]) li + li { margin-top: var(--sp-2); }

.prose > ul:not([class]) { list-style: square; }
.prose > ol:not([class]) { list-style: decimal; }

.prose > ul:not([class]) li::marker,
.prose > ol:not([class]) li::marker { color: var(--c-primary-300); }

.callout {
  padding: var(--sp-5) var(--sp-6);
  background: var(--c-surface-sunken);
  border: var(--bw-hair) solid var(--c-border);
  /* The marker rule stays on the left here rather than becoming a top
     ribbon: a callout is an aside inside running prose, and a left rule is
     what signals "step out of the column" at reading scale. */
  border-left: var(--bw-marker) solid var(--c-primary-500);
  border-radius: var(--r-lg);
}
.callout p { max-width: var(--measure); }

/* A callout is a sidebar note, not a section — its heading should not
   compete with the surrounding page headings. */
.callout h3 { font-size: var(--fs-md); margin-bottom: var(--sp-3); }

/* Marks a value that still needs client confirmation. Deliberately loud
   so it cannot reach production unnoticed. */
.verify {
  display: inline-block;
  padding: 0 var(--sp-2);
  background: var(--c-warning-bg);
  border-bottom: var(--bw-thick) solid var(--c-warning);
  font-family: var(--font-mono);
  font-size: 0.9em;
  color: var(--c-warning);
}


/* ==========================================================================
   21  PROCESS STEPS
   ========================================================================== */

.process { list-style: none; display: grid; gap: var(--sp-6); counter-reset: step; }

.process__item {
  position: relative;
  display: grid;
  /* Grid's default align-content behaves as `stretch`, so each item was
     stretched to the tallest cell in the row and the surplus divided among
     its auto rows. Steps with shorter copy got taller heading boxes, which
     left the four <h3> sitting at four different heights. `start` keeps rows
     at their natural size so the headings align across the row. */
  align-content: start;
  gap: var(--sp-3);
  padding-top: var(--sp-5);
}

/* Same ribbon as .reason and the cards. Lower opacity because a process
   list shows four of these side by side and four full-strength ribbons
   read as a striped header band rather than as four separate steps. */
.process__item::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 2px;
  background-image: var(--grad-edge);
  opacity: 0.6;
  pointer-events: none;
}

.process__num {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--ls-label);
  color: var(--c-primary-700);
}

.process__item h3 { font-size: var(--fs-lg); }
.process__item p { color: var(--c-text-muted); max-width: var(--measure); }

@media (min-width: 768px) {
  .process { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--sp-6) var(--sp-7); }
}

@media (min-width: 1024px) {
  .process { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}


/* ==========================================================================
   22  SPEC TABLE  (products)
   ========================================================================== */

/* The scroll container carries the card surface, border and rounding — the
   table itself cannot, because border-radius does not clip a
   border-collapse: collapse table's own cells. */
.spec-table-wrap {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  background: var(--c-surface-raised);
  border: var(--bw-hair) solid var(--c-border);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-1);
}

.spec-table {
  width: 100%;
  min-width: 34rem;
  border-collapse: collapse;
  font-size: var(--fs-sm);
}

/* caption is a table child, so it scrolls with the table on narrow screens.
   Pulling it outside the rounded surface keeps it as a section label. */
.spec-table caption {
  text-align: left;
  padding: var(--sp-4) var(--sp-5) var(--sp-3);
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--c-text-muted);
}

.spec-table th,
.spec-table td {
  padding: var(--sp-4) var(--sp-5);
  text-align: left;
  border-bottom: var(--bw-hair) solid var(--c-border-subtle);
  vertical-align: top;
}

.spec-table thead th {
  background: var(--c-surface-sunken);
  border-bottom: var(--bw-hair) solid var(--c-border);
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--c-text-muted);
  /* Header stays put while a wide table is scrolled vertically past it. */
  position: sticky;
  top: 0;
  z-index: var(--z-raised);
}

/* No bottom rule on the final row: it would sit directly on the container's
   own border and read as a double line. */
.spec-table tbody tr:last-child th,
.spec-table tbody tr:last-child td { border-bottom: 0; }

.spec-table tbody tr { transition: background-color var(--dur-base) var(--ease-out); }
.spec-table tbody tr:hover { background: var(--c-surface-sunken); }

.spec-table tbody th { font-weight: var(--fw-semibold); white-space: nowrap; color: var(--c-text); }
.spec-table td { color: var(--c-text-muted); }


/* ==========================================================================
   23  RESOURCE LISTING  (filter toolbar + rows)
   ========================================================================== */

.toolbar {
  display: grid;
  gap: var(--sp-4);
  padding: var(--sp-5);
  margin-bottom: var(--sp-7);
  background: var(--c-surface-raised);
  border: var(--bw-hair) solid var(--c-border);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-1);
}

.toolbar__field { display: grid; gap: var(--sp-2); }

.toolbar__label {
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--c-text-muted);
}

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

.toolbar__search .icon {
  position: absolute;
  left: var(--sp-4);
  color: var(--c-n-5);
  pointer-events: none;
}

.toolbar__search .field__control { padding-left: calc(var(--sp-4) * 2 + 24px); }

.toolbar__status {
  font-size: var(--fs-sm);
  color: var(--c-text-muted);
  font-family: var(--font-mono);
}

@media (min-width: 768px) {
  .toolbar {
    grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
    align-items: end;
  }
  .toolbar__status { grid-column: 1 / -1; }
}

/* Matches .news-item: discrete rounded rows, not a divided register. */
.resource-list {
  list-style: none;
  display: grid;
  gap: var(--sp-3);
}

.resource-row {
  display: grid;
  gap: var(--sp-4);
  padding: var(--sp-5);
  background: var(--c-surface-raised);
  border: var(--bw-hair) solid var(--c-border);
  border-radius: var(--r-lg);
  transition: border-color var(--dur-base) var(--ease-out),
              box-shadow var(--dur-base) var(--ease-out);
}

.resource-row:hover {
  border-color: var(--c-border-strong);
  box-shadow: var(--sh-glow);
}

.resource-row__title { font-size: var(--fs-lg); }
.resource-row__title a { color: var(--c-text); text-decoration: none; }
.resource-row__title a:hover { color: var(--c-primary-700); text-decoration: underline; }

.resource-row__summary {
  color: var(--c-text-muted);
  font-size: var(--fs-sm);
  max-width: var(--measure);
  margin: 0;
}

.resource-row__attrs {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2) var(--sp-5);
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  color: var(--c-text-muted);
}

.resource-row__attrs .k {
  text-transform: uppercase;
  letter-spacing: var(--ls-wide);
  color: var(--c-n-5);
  margin-right: var(--sp-2);
}

.resource-row__aside {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--sp-3);
}

.resource-row__source {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  color: var(--c-n-5);
  text-transform: uppercase;
  letter-spacing: var(--ls-wide);
}

@media (min-width: 768px) {
  .resource-row {
    grid-template-columns: minmax(0, 1fr) auto;
    column-gap: var(--sp-7);
    align-items: start;
    padding: var(--sp-6);
  }
  .resource-row__aside { align-items: flex-end; text-align: right; }
}

/* Disabled action for seeded example rows that have no real target. */
.btn[aria-disabled="true"] {
  opacity: 0.55;
  cursor: not-allowed;
  pointer-events: none;
}

/* Highlighted search term. Underline carries the meaning so the match is
   not conveyed by colour alone. */
mark.hit {
  background: var(--c-warning-bg);
  color: var(--c-text);
  text-decoration: underline;
  text-underline-offset: 0.15em;
  padding: 0 1px;
}


/* ==========================================================================
   24  CONTACT LAYOUT
   ========================================================================== */

.contact-grid { display: grid; gap: var(--sp-9); }

.contact-aside > h2 { margin-bottom: var(--sp-6); }

.contact-aside__list { list-style: none; display: grid; gap: var(--sp-6); }

.contact-aside__item { display: grid; gap: var(--sp-2); }

.contact-aside__label {
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--c-text-muted);
}

/* <address> is the correct element for a postal address, but every UA
   stylesheet italicises it, which reads as emphasis the address does not
   carry. The element is kept for its semantics and the italic dropped. */
.contact-aside__address {
  font-style: normal;
  line-height: var(--lh-normal);
}

@media (min-width: 1024px) {
  .contact-grid {
    grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr);
    gap: var(--sp-11);
  }
}


/* ==========================================================================
   25  MOTION
   --------------------------------------------------------------------------
   Reveal styles live entirely inside the no-preference query. With reduced
   motion requested, .reveal never receives an initial transform or opacity,
   so content is visible even if JavaScript never runs.
   ========================================================================== */

@media (prefers-reduced-motion: no-preference) {

  .js .reveal {
    opacity: 0;
    transform: translateY(var(--reveal-shift));
    transition: opacity var(--dur-reveal) var(--ease-out),
                transform var(--dur-reveal) var(--ease-out);
  }

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

  .js .reveal--d1 { transition-delay: calc(var(--reveal-stagger) * 1); }
  .js .reveal--d2 { transition-delay: calc(var(--reveal-stagger) * 2); }
  .js .reveal--d3 { transition-delay: calc(var(--reveal-stagger) * 3); }
}


/* ==========================================================================
   26  THEME TOGGLE
   --------------------------------------------------------------------------
   A single button that reports the theme it will switch TO, not the one
   currently active — "Switch to dark theme" is unambiguous, whereas a
   labelled state ("Dark") reads as either a description or a destination
   depending on the user.

   Both icons are always in the DOM; CSS shows one. Swapping innerHTML from
   script would leave assistive tech announcing a stale node, and would flash
   an empty button during the swap.
   ========================================================================== */

.theme-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 44px;
  height: 44px;
  padding: 0;
  background: transparent;
  border: var(--bw-hair) solid var(--c-border);
  border-radius: var(--r-md);
  color: var(--c-text-muted);
  cursor: pointer;
  transition: color var(--dur-base) var(--ease-out),
              border-color var(--dur-base) var(--ease-out),
              background-color var(--dur-base) var(--ease-out);
}

.theme-toggle:hover {
  color: var(--c-text);
  border-color: var(--c-primary-500);
  background: var(--c-primary-100);
}

/* Default (light theme active) shows the moon — the destination. */
.theme-toggle__moon { display: block; }
.theme-toggle__sun  { display: none; }

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .theme-toggle__moon { display: none; }
  :root:not([data-theme="light"]) .theme-toggle__sun  { display: block; }
}

:root[data-theme="dark"] .theme-toggle__moon { display: none; }
:root[data-theme="dark"] .theme-toggle__sun  { display: block; }

/* Last element in the header row at every breakpoint. On mobile it sits
   beside the hamburger rather than inside the menu sheet, so the theme can
   be changed without first opening the navigation. */
.theme-toggle { margin-left: var(--sp-2); }

@media (min-width: 1200px) {
  .theme-toggle { margin-left: var(--sp-4); }
}


/* ==========================================================================
   27  PRINT
   ========================================================================== */

@media print {
  .site-header,
  .site-nav,
  .nav-toggle,
  .theme-toggle,
  .cta-band,
  .skip-link { display: none; }

  /* A visitor printing while the dark theme is active would otherwise send
     a near-black canvas to the printer. Force the light role values back for
     print regardless of which theme is on screen, and drop every decorative
     gradient — background-image is not printed by default anyway, but
     gradient-clipped headings WOULD render as transparent text. */
  :root,
  :root[data-theme="dark"] {
    color-scheme: light;
    --c-surface: #FFFFFF;
    --c-surface-sunken: #FFFFFF;
    --c-surface-raised: #FFFFFF;
    --c-surface-inverse: #FFFFFF;
    --c-text: #000000;
    --c-text-muted: #333333;
    --c-text-inverse: #000000;
    --c-text-inverse-mut: #333333;
    --c-border: #999999;
    --c-border-subtle: #CCCCCC;
    --c-border-inverse: #999999;
  }

  .grad-text {
    background-image: none !important;
    -webkit-text-fill-color: #000000;
    color: #000000 !important;
  }

  .hero::before,
  .page-header::before,
  .card--ribbon::before,
  .resource-tile::before,
  .hero__panel::before { display: none; }

  .card,
  .resource-tile,
  .hero__panel { box-shadow: none; }

  body { color: var(--c-text); background: #FFFFFF; }
  .section { padding-block: var(--sp-5); }
  a::after { content: " (" attr(href) ")"; font-size: 0.8em; }

  /* Scroll reveal never fires when printing — without this, every section
     below the fold prints blank. Same applies to any headless renderer
     that captures the full page without scrolling it. */
  .js .reveal {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }
}

/* Production Command Centre composition. This deliberately gives the
   homepage its own compressed operational rhythm without altering inner
   pages or the shared navigation system. */
.home-main .section { padding-block: var(--sp-10); }
.home-main .section--lg { padding-block: var(--sp-11); }

/* --------------------------------------------------------------------------
   HOMEPAGE HERO — the argument on the left, the measurement on the right.

   This hero is dark in BOTH themes: --c-surface-inverse resolves to the deep
   navy #003B64 under light and to the panel fill #0E1822 under dark. Every
   colour below is therefore picked against a dark ground once, and needs no
   theme fork. The one exception is the sub-threshold amber, which is why
   --c-scope-warn is declared outside the theme blocks in brand.css.
   -------------------------------------------------------------------------- */

.hero--command {
  /* --band-field is themed for the PAGE ground, not this one. Under the light
     theme it resolves to --c-primary-100 (#D6EAF3) at 0.55 opacity — near
     white — and .hero::before then lays those stripes straight across the
     eyebrow and the lead paragraph. Because this section is dark in both
     themes it needs one fixed treatment, pinned here and inherited by the
     pseudo-element. --c-accent is identical in both themes, so this holds. */
  --band-field: repeating-linear-gradient(170deg,
                  color-mix(in srgb, var(--c-accent) 14%, transparent) 0 2px,
                  transparent 2px 26px);
  --band-field-opacity: 0.85;

  /* Same reasoning: --c-border-inverse is #1A5580 under light, which all but
     disappears on the navy ground. A percentage of the inverse ink instead
     lands at the same weight against either ground. */
  --hero-hairline: color-mix(in srgb, var(--c-text-inverse) 26%, transparent);

  isolation: isolate;
  padding-block: var(--sp-8) var(--sp-10);
  background-color: var(--c-surface-inverse);
  background-image:
    linear-gradient(118deg,
      var(--c-surface-inverse) 0%,
      var(--c-surface-inverse) 46%,
      transparent 78%),
    var(--grad-ambient),
    var(--grad-ambient-2);
  color: var(--c-text-inverse);
  border-bottom: 0;
}

/* ---- Eyebrow -------------------------------------------------------------
   One label. The previous composition stacked a mono "Engineering Command
   Centre / Malaysia" strip directly above a mono "Radio frequency
   engineering" eyebrow — two devices competing for the same job. */

.hero__signal {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-3);
  margin-bottom: var(--sp-5);
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--c-primary-300);
}

.hero__signal-dot {
  flex: none;
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 50%;
  background: var(--c-accent);
  box-shadow: 0 0 0 0.28rem color-mix(in srgb, var(--c-accent) 15%, transparent);
}

.hero__signal-region {
  padding-left: var(--sp-3);
  border-left: var(--bw-hair) solid var(--hero-hairline);
  color: var(--c-text-inverse-mut);
}

/* ---- Headline ------------------------------------------------------------
   Was 13ch at up to 5.4rem, which pushed a 51-character sentence into five
   lines with an 8ch/18ch/8ch rag and consumed the whole viewport. 18ch at
   4.15rem lands it in three; text-wrap: balance evens what remains. */

.hero--command .hero__title {
  max-width: 18ch;
  font-size: clamp(2.3rem, 3.9vw, 4.15rem);
  line-height: 1.02;
  letter-spacing: -0.04em;
  text-wrap: balance;

  /* Opts out of the h1 gradient declared at the top of section 03. That
     gradient runs primary-500 → accent, which turned the whole headline into
     one blue mass and left no colour available for emphasis. Solid near-white
     here reads at 15:1 instead of ~9:1 AND frees the mint for one word.
     -webkit-text-fill-color must be cleared alongside `color`: clearing
     `color` alone leaves the fill transparent in WebKit. */
  background-image: none;
  padding-bottom: 0;
  color: var(--c-text-inverse);
  -webkit-text-fill-color: var(--c-text-inverse);
}

/* The only place the mint accent is spent in the copy column. "Measured" is
   the claim the whole page rests on; everything around it stays quiet. */
.hero--command .hero__title em {
  font-style: normal;
  color: var(--c-accent);
  -webkit-text-fill-color: var(--c-accent);
}

.hero--command .hero__lead {
  max-width: 42ch;
  color: var(--c-text-inverse-mut);
}

.hero--command .hero__actions { margin-top: var(--sp-7); }

.hero--command .btn--secondary {
  color: var(--c-text-inverse);
  border-color: var(--hero-hairline);
  background: transparent;
}
.hero--command .btn--secondary:hover {
  background: color-mix(in srgb, var(--c-text-inverse) 10%, transparent);
  border-color: color-mix(in srgb, var(--c-accent) 55%, transparent);
}


/* ==========================================================================
   HERO INSTRUMENT (.scope)
   --------------------------------------------------------------------------
   Replaces the five-row key/value panel. A spec table states the service; a
   plotted trace shows it, and the spec facts survive in the footer as the
   scope of that measurement rather than as a standalone list.
   ========================================================================== */

.scope {
  /* Declared once so the footer cells can repaint the identical ground and
     the 1px grid gaps below read as hairlines rather than seams. */
  --scope-ground: color-mix(in srgb, var(--c-surface-inverse) 74%, #000);
  --scope-rule:   color-mix(in srgb, var(--c-border-inverse) 68%, transparent);

  margin: var(--sp-8) 0 0;
  border: var(--bw-hair) solid color-mix(in srgb, var(--c-primary-300) 26%, var(--c-border-inverse));
  border-radius: var(--r-lg);
  background: var(--scope-ground);
  box-shadow: 0 1.5rem 3.5rem rgba(0, 0, 0, 0.45);
  overflow: hidden;
}

.scope__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
  padding: var(--sp-3) var(--sp-5);
  border-bottom: var(--bw-hair) solid var(--scope-rule);
  background: color-mix(in srgb, var(--c-primary-500) 12%, transparent);
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
}

.scope__title {
  font-weight: var(--fw-semibold);
  color: var(--c-primary-300);
}

.scope__unit {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  /* Opts out of the head row's uppercase: "dBm" is a unit symbol whose case
     is part of its meaning, and the transform rendered it "DBM". */
  text-transform: none;
  color: var(--c-text-inverse-mut);
}

.scope__unit-dot {
  width: 0.4rem;
  height: 0.4rem;
  border-radius: 50%;
  background: var(--c-accent);
}

.scope__plot { padding: var(--sp-5) var(--sp-4) var(--sp-3); }

.scope__svg {
  display: block;
  width: 100%;
  height: auto;
}

/* ---- Plot internals ------------------------------------------------------
   Font sizes here are viewBox units, not CSS pixels: the SVG scales to its
   container, so 11 units renders ~11px at the desktop column width and
   shrinks with it. The narrow-viewport query at the end compensates. */

.scope__grid line { stroke: color-mix(in srgb, var(--c-primary-300) 18%, transparent); }
.scope__axis      { stroke: color-mix(in srgb, var(--c-primary-300) 34%, transparent); }

.scope__tick {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.04em;
  fill: var(--c-text-inverse-mut);
}

/* Anything plotted inside this band failed acceptance. */
.scope__fail { fill: var(--c-scope-warn); opacity: 0.06; }

.scope__threshold {
  stroke: var(--c-scope-warn);
  stroke-width: 1;
  stroke-dasharray: 5 4;
  opacity: 0.55;
}

.scope__threshold-label {
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: 0.06em;
  fill: var(--c-scope-warn);
  opacity: 0.8;
}

.scope__area     { fill: url(#scope-fill); }
.scope__fill-top { stop-color: var(--c-accent); stop-opacity: 0.26; }
.scope__fill-bot { stop-color: var(--c-accent); stop-opacity: 0; }

/* Straight segments, not a smoothed curve: sampled field data has corners,
   and a bezier would read as an illustration of data rather than data. */
.scope__trace {
  stroke: var(--c-accent);
  stroke-width: 2;
  stroke-linejoin: round;
  stroke-linecap: round;
}

.scope__flag      { fill: var(--c-scope-warn); }
.scope__flag-halo { fill: var(--c-scope-warn); opacity: 0.18; }

.scope__flag-label {
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: var(--fw-semibold);
  fill: var(--c-scope-warn);
}

/* ---- Spec footer ---------------------------------------------------------
   A 1px grid gap over the rule colour draws the dividers, so no cell carries
   a border and there are no doubled edges at the intersections. */

.scope__specs {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--bw-hair);
  margin: 0;
  border-top: var(--bw-hair) solid var(--scope-rule);
  background: var(--scope-rule);
}

.scope__spec {
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
  padding: var(--sp-4) var(--sp-5);
  background: var(--scope-ground);
}

.scope__spec dt {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--c-text-inverse-mut);
}

.scope__spec dd {
  margin: 0;
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  color: var(--c-text-inverse);
}

/* ---- Draw-on ------------------------------------------------------------
   The hero's only motion, and it is one sequence rather than several
   effects: the trace draws, its fill rises behind it, then the failing
   sample is flagged. pathLength="1" on the path normalises the geometry so
   the dash values are fractions and the CSS never needs the real length.
   Gated on .js so a failed script leaves the finished chart visible, and on
   no-preference so reduced motion does the same. */

@media (prefers-reduced-motion: no-preference) {

  .js .scope__trace {
    stroke-dasharray: 1;
    stroke-dashoffset: 1;
    animation: scope-draw 1600ms var(--ease-out) 200ms forwards;
  }

  .js .scope__area {
    opacity: 0;
    animation: scope-fade 700ms var(--ease-out) 600ms forwards;
  }

  .js .scope__flag,
  .js .scope__flag-halo,
  .js .scope__flag-label {
    opacity: 0;
    animation: scope-fade 400ms var(--ease-out) 1500ms forwards;
  }

  /* Restores the halo's resting opacity rather than the default 1. */
  .js .scope__flag-halo { animation-name: scope-fade-halo; }
}

@keyframes scope-draw      { to { stroke-dashoffset: 0; } }
@keyframes scope-fade      { to { opacity: 1; } }
@keyframes scope-fade-halo { to { opacity: 0.18; } }

.hero--command + .capability-strip { margin-inline: auto; background: var(--c-surface-raised); box-shadow: var(--sh-2); position: relative; z-index: var(--z-raised); }
.command-services { background-image: linear-gradient(180deg, color-mix(in srgb, var(--c-primary-500) 5%, transparent), transparent 16rem); }
.command-services .section-head { max-width: 74rem; }
.command-services .grid { gap: var(--sp-4); }
.command-services .card { min-height: 0; }
.technology-matrix { position: relative; overflow: hidden; }
.technology-matrix::before { content: ""; position: absolute; width: 34rem; height: 34rem; right: -16rem; top: -18rem; border: 1px solid color-mix(in srgb, var(--c-primary-500) 28%, transparent); border-radius: 50%; box-shadow: 0 0 0 2.5rem color-mix(in srgb, var(--c-primary-500) 4%, transparent), 0 0 0 5rem color-mix(in srgb, var(--c-primary-500) 3%, transparent); pointer-events: none; }
.technology-matrix__inner { position: relative; }

/* Below the tablet breakpoint the SVG is scaled down hard by its container,
   so the in-viewBox type shrinks with it. Drop the two labels that are
   nice-to-have and enlarge the rest, rather than shipping 7px axis text. */
@media (max-width: 599px) {
  .scope__threshold-label { display: none; }
  .scope__tick            { font-size: 15px; }
  .scope__flag-label      { font-size: 15px; }
  .scope__specs           { grid-template-columns: minmax(0, 1fr); }

  /* The eyebrow wraps around 400px, and a vertical rule that was a divider
     between two items becomes a stray tick once the second one starts a
     line. The colour shift alone carries the split at this width. */
  .hero__signal-region { padding-left: 0; border-left: 0; }
}

@media (min-width: 768px) { .hero--command + .capability-strip { width: min(calc(100% - var(--sp-8)), 74rem); margin-top: calc(var(--sp-7) * -1); border: var(--bw-hair) solid var(--c-border); border-radius: var(--r-lg); } }

@media (min-width: 1024px) {
  .home-main .section { padding-block: var(--sp-11); }
  .hero--command { padding-block: var(--sp-10) var(--sp-11); }
  /* Near-equal column heights now that the headline is three lines, so the
     two columns can be optically centred against each other instead of the
     panel floating in the middle of an empty track. */
  .hero--command .hero__inner {
    grid-template-columns: minmax(0, 1.04fr) minmax(27rem, 0.96fr);
    align-items: center;
  }
  .scope { margin-top: 0; }
}
/* Resource overview: stay at two columns until there is enough room for
   long, descriptive labels such as Applications and Documentation. */
.resource-overview-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.resource-overview-grid .resource-tile { min-width: 0; }
.resource-overview-grid .resource-tile h3 { max-inline-size: 100%; font-size: clamp(1rem, 0.93rem + 0.2vw, 1.125rem); overflow-wrap: anywhere; }
@media (min-width: 1440px) { .resource-overview-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); } }

/* ==========================================================================
   28  KNOWLEDGE CENTRE
   --------------------------------------------------------------------------
   The Knowledge Centre is reference material, not marketing: an engineer
   arrives looking for one formula, one threshold or one counter name and
   should reach it without reading anything else. Every component below is
   built for scanning first and reading second.

   Nothing here duplicates an existing component. Tables reuse .spec-table,
   running text reuses .prose, asides reuse .callout, and the article index
   reuses the .resource-list toolbar, so a new entry in data/knowledge.json
   appears with search and category filtering already working.
   ========================================================================== */

/* ---- Pathways ------------------------------------------------------------
   Four entry points on the hub. These are disciplines, not a sequence, so
   they carry no numbering — the reader picks the one matching the problem
   in front of them rather than starting at step one. */

.kc-paths {
  list-style: none;
  display: grid;
  gap: var(--sp-4);
}

.kc-path {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  padding: var(--sp-6);
  background: var(--c-surface-raised);
  border: var(--bw-hair) solid var(--c-border);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-1);
  transition: border-color var(--dur-base) var(--ease-out),
              box-shadow var(--dur-base) var(--ease-out);
}

.kc-path::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 2px;
  background-image: var(--grad-edge);
  border-radius: var(--r-lg) var(--r-lg) 0 0;
  pointer-events: none;
}

.kc-path:hover { border-color: var(--c-border-strong); box-shadow: var(--sh-2); }

.kc-path__label {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--c-primary-700);
}

.kc-path h3 { font-size: var(--fs-lg); }
.kc-path > p { color: var(--c-text-muted); flex: 1 1 auto; }

/* This card contains several links, so it deliberately does NOT use the
   .card--link whole-card overlay pattern — that overlay would swallow every
   individual article link inside it. */
.kc-path__links { list-style: none; display: grid; gap: var(--sp-2); }

.kc-path__links a {
  display: flex;
  align-items: baseline;
  gap: var(--sp-2);
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  text-decoration: none;
  color: var(--c-link);
}
.kc-path__links a::before {
  content: "";
  flex: none;
  width: 0.42rem;
  height: 0.42rem;
  border-radius: 50%;
  background: var(--c-primary-300);
  transform: translateY(-0.15em);
}
.kc-path__links a:hover { text-decoration: underline; color: var(--c-link-hover); }

@media (min-width: 640px)  { .kc-paths { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 1200px) { .kc-paths { grid-template-columns: repeat(4, minmax(0, 1fr)); } }


/* ---- Article shell -------------------------------------------------------
   Contents rail left, article right. The rail is a <details> element: below
   1024px it is a real collapsed disclosure, and at 1024px and up it is
   forced open and made sticky, where a collapsed rail would just be an extra
   click on every article. */

/* minmax(0, 1fr) rather than a bare single-column grid. An implicit `auto`
   track is sized to its items' max-content, and .spec-table carries a
   min-width: 34rem — so on a phone the single column grew to 700px and took
   the whole page with it, instead of letting the table scroll inside its own
   .spec-table-wrap. The 0 minimum is what allows the track to shrink. */
.kc-layout { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--sp-7); }

.kc-toc {
  border: var(--bw-hair) solid var(--c-border);
  border-radius: var(--r-lg);
  background: var(--c-surface-sunken);
}

.kc-toc__summary {
  padding: var(--sp-4) var(--sp-5);
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--c-text-muted);
  cursor: pointer;
}
.kc-toc__summary::marker { color: var(--c-primary-500); }

.kc-toc__list {
  list-style: none;
  padding: 0 var(--sp-5) var(--sp-5);
  display: grid;
  gap: var(--sp-1);
}

.kc-toc__list a {
  display: block;
  padding: var(--sp-2) var(--sp-3);
  border-left: var(--bw-thick) solid var(--c-border);
  border-radius: 0 var(--r-sm) var(--r-sm) 0;
  font-size: var(--fs-sm);
  text-decoration: none;
  color: var(--c-text-muted);
  transition: border-color var(--dur-base) var(--ease-out),
              color var(--dur-base) var(--ease-out),
              background-color var(--dur-base) var(--ease-out);
}
.kc-toc__list a:hover {
  border-left-color: var(--c-primary-500);
  background: var(--c-surface-raised);
  color: var(--c-text);
}

/* Set by main.js as the reader scrolls past each heading. */
.kc-toc__list a[aria-current="true"] {
  border-left-color: var(--c-primary-500);
  background: var(--c-surface-raised);
  color: var(--c-link);
  font-weight: var(--fw-semibold);
}


/* ---- Article metadata bar ---------------------------------------------- */

.kc-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2) var(--sp-5);
  margin-top: var(--sp-5);
  padding-top: var(--sp-5);
  border-top: var(--bw-hair) solid var(--c-border-subtle);
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  letter-spacing: var(--ls-wide);
  color: var(--c-text-muted);
}
.kc-meta__item { display: inline-flex; align-items: center; gap: var(--sp-2); }
.kc-meta__item b { color: var(--c-text); font-weight: var(--fw-semibold); }


/* ---- Formula -------------------------------------------------------------
   Three parts: what the formula is called, the expression, and what the
   reader should take from it. The expression is the only element set in mono
   at reading size, so a page skimmed for exactly one equation still works. */

.formula {
  display: grid;
  gap: var(--sp-3);
  padding: var(--sp-5) var(--sp-6);
  background: var(--c-surface-sunken);
  border: var(--bw-hair) solid var(--c-border);
  border-left: var(--bw-marker) solid var(--c-primary-500);
  border-radius: var(--r-lg);
}

.formula__label {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--c-primary-700);
}

.formula__expr {
  margin: 0;
  font-family: var(--font-mono);
  font-size: var(--fs-md);
  font-weight: var(--fw-medium);
  line-height: var(--lh-snug);
  color: var(--c-text);
  /* Not overflow-x: auto. Setting an overflow on one axis makes the other
     compute to auto as well, which put a stray vertical scrollbar inside
     every formula box. The expression wraps on its own, so wrapping is all
     that is needed — `anywhere` covers a long unbroken term that would
     otherwise push the page sideways. */
  overflow-wrap: anywhere;
}

.formula__note {
  margin: 0;
  font-size: var(--fs-sm);
  color: var(--c-text-muted);
  max-width: var(--measure);
}

.formula-grid { display: grid; gap: var(--sp-4); }
@media (min-width: 900px) { .formula-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }


/* ---- Diagnostic matrix ---------------------------------------------------
   The RSRP x SINR read-out. A real 2x2 with row and column headers rather
   than four loose cards, because the diagnosis is only meaningful as the
   intersection of two axes — drop the axes and the method goes with them. */

.matrix-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }

.matrix {
  width: 100%;
  min-width: 30rem;
  border-collapse: separate;
  border-spacing: var(--bw-thick);
  font-size: var(--fs-sm);
}

.matrix th,
.matrix td {
  padding: var(--sp-4) var(--sp-5);
  text-align: left;
  vertical-align: top;
  border-radius: var(--r-md);
}

.matrix thead th,
.matrix tbody th {
  background: var(--c-surface-sunken);
  border: var(--bw-hair) solid var(--c-border);
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--c-text-muted);
}

.matrix td {
  background: var(--c-surface-raised);
  border: var(--bw-hair) solid var(--c-border);
}

.matrix td b { display: block; margin-bottom: var(--sp-1); color: var(--c-text); }
.matrix td span { color: var(--c-text-muted); }

/* Verdict colouring, keyed to the semantic tokens so both themes stay
   legible without a second set of values. */
.matrix td.is-good { border-color: color-mix(in srgb, var(--c-success) 45%, transparent); background: var(--c-success-bg); }
.matrix td.is-warn { border-color: color-mix(in srgb, var(--c-warning) 45%, transparent); background: var(--c-warning-bg); }
.matrix td.is-bad  { border-color: color-mix(in srgb, var(--c-error)   45%, transparent); background: var(--c-error-bg); }
.matrix td.is-good b { color: var(--c-success); }
.matrix td.is-warn b { color: var(--c-warning); }
.matrix td.is-bad  b { color: var(--c-error); }


/* ---- Verdict values inside .spec-table --------------------------------- */

.spec-table .v-good,
.spec-table .v-warn,
.spec-table .v-bad {
  font-family: var(--font-mono);
  font-weight: var(--fw-semibold);
  white-space: nowrap;
}
.spec-table .v-good { color: var(--c-success); }
.spec-table .v-warn { color: var(--c-warning); }
.spec-table .v-bad  { color: var(--c-error); }


/* ---- Pager --------------------------------------------------------------
   Previous / next within the Knowledge Centre, labelled with the destination
   title rather than a bare "Next", so each link still says where it goes
   when it is read out of context by a screen reader. */

.kc-pager {
  display: grid;
  gap: var(--sp-4);
  margin-top: var(--sp-9);
  padding-top: var(--sp-7);
  border-top: var(--bw-hair) solid var(--c-border);
}

.kc-pager__link {
  display: grid;
  gap: var(--sp-1);
  padding: var(--sp-5);
  border: var(--bw-hair) solid var(--c-border);
  border-radius: var(--r-lg);
  background: var(--c-surface-raised);
  text-decoration: none;
  transition: border-color var(--dur-base) var(--ease-out);
}
.kc-pager__link:hover { border-color: var(--c-border-strong); }
.kc-pager__link--next { text-align: right; }

.kc-pager__dir {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--c-text-muted);
}
.kc-pager__title { font-weight: var(--fw-semibold); color: var(--c-text); }
.kc-pager__link:hover .kc-pager__title { color: var(--c-link); }

@media (min-width: 640px) {
  .kc-pager { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .kc-pager__link--next { grid-column: 2; }
}


/* ---- Article body -------------------------------------------------------
   Not .prose: an article here is mostly components (tables, formulas,
   matrices) with prose between them, so the rhythm is set by the h2 rules
   rather than by a uniform stack margin. */

.kc-article > * + * { margin-top: var(--sp-6); }

.kc-article > h2 {
  margin-top: var(--sp-10);
  padding-top: var(--sp-6);
  border-top: var(--bw-hair) solid var(--c-border-subtle);
  font-size: var(--fs-2xl);
}
.kc-article > h2:first-child { margin-top: 0; padding-top: 0; border-top: 0; }
.kc-article > h3 { margin-top: var(--sp-7); font-size: var(--fs-lg); }
.kc-article > p { max-width: var(--measure); color: var(--c-text-muted); }
.kc-article > p strong { color: var(--c-text); }

.kc-article > ul:not([class]),
.kc-article > ol:not([class]) { padding-left: var(--sp-5); max-width: var(--measure); }
.kc-article > ul:not([class]) { list-style: square; }
.kc-article > ol:not([class]) { list-style: decimal; }
.kc-article > ul:not([class]) li,
.kc-article > ol:not([class]) li { color: var(--c-text-muted); }
.kc-article > ul:not([class]) li + li,
.kc-article > ol:not([class]) li + li { margin-top: var(--sp-2); }
.kc-article > ul:not([class]) li::marker,
.kc-article > ol:not([class]) li::marker { color: var(--c-primary-300); }
.kc-article > ul:not([class]) strong,
.kc-article > ol:not([class]) strong { color: var(--c-text); }

/* Every heading is an anchor target from the contents rail, so all of them
   need to clear the sticky header when jumped to. */
.kc-article [id] { scroll-margin-top: calc(var(--sp-9) + var(--sp-4)); }


/* ---- Desktop article layout -------------------------------------------- */

@media (min-width: 1024px) {
  .kc-layout { grid-template-columns: minmax(0, 16rem) minmax(0, 1fr); gap: var(--sp-9); }

  .kc-toc {
    position: sticky;
    /* Header height plus a gutter, matching the scroll-margin above. */
    top: calc(var(--sp-9) + var(--sp-4));
    align-self: start;
    max-height: calc(100vh - var(--sp-11));
    overflow-y: auto;
  }

  /* Forced open as a permanent sidebar. The marker is removed in both the
     standard and the WebKit form, and pointer-events are dropped so the
     summary cannot be clicked shut at a width where it should not collapse. */
  .kc-toc__summary { list-style: none; cursor: default; pointer-events: none; }
  .kc-toc__summary::-webkit-details-marker { display: none; }
}
