/* ============================================================================
   Saturn PCBA — design-tokens.css   v1
   THE ONE STYLESHEET FOR THE WHOLE SITE.
   Hard rule: no second CSS file may define a class that exists here.
   Any new class is added HERE by the coordinator — never in a page file.
   ----------------------------------------------------------------------------
   DESIGN LANGUAGE: "STRUCTURAL MODULAR GRID"
   The plant is built from parallel production modules (9 SMT lines + 4-5 DIP
   lines) feeding one shared quality system. The page is therefore built from
   parallel layout modules separated by a single hairline — not from floating
   cards. Zero border-radius, zero shadows, hairline borders carry all
   separation. Ink / Paper / Muted / Orange accent only; colour comes from the ring.
   ----------------------------------------------------------------------------
   COLOUR ROLE (client brief, 2026-10): the logo's INDIGO TEXT is NOT a UI colour.
   The pale-blue / navy background experiment was reverted at the client's request:
   neutral greys and near-black panels are back. Orange and yellow from the ring
   are the accents: orange for fills and accent text, yellow on dark panels.
   ----------------------------------------------------------------------------
   MEASURED CONTRAST TABLE  (WCAG 2.1, computed by build/_scripts/contrast-audit.mjs)
   Body text needs >= 4.5:1.  Large text (>=24px, or >=18.66px bold) and
   non-text UI boundaries need >= 3:1.  Hairlines are decorative-never-text.

   #  PAIR (foreground on background)                        RATIO   MIN   VERDICT
   1  #161616 on #F4F4F4  primary text on page paper       16.45:1  4.5   PASS
   2  #161616 on #FFFFFF  primary text on card surface     18.10:1  4.5   PASS
   3  #161616 on #FAFAFA  primary text on alt surface      17.34:1  4.5   PASS
   4  #4A4A4A on #F4F4F4  secondary text on page paper      8.06:1  4.5   PASS
   5  #4A4A4A on #FFFFFF  secondary text on card surface    8.86:1  4.5   PASS
   6  #4A4A4A on #FAFAFA  secondary text on alt surface     8.49:1  4.5   PASS
   7  #6B6B6B on #F4F4F4  tertiary text on page paper       4.85:1  4.5   PASS
   8  #6B6B6B on #FFFFFF  tertiary text on card surface     5.33:1  4.5   PASS
   9  #8C8C8C on #F4F4F4  --muted on paper (>=24px ONLY)    3.06:1  3.0   PASS(large)
  10  #8C8C8C on #FFFFFF  --muted on surface (>=24px ONLY)  3.36:1  3.0   PASS(large)
  11  #AC4B07 on #F4F4F4  --accent-text eyebrow on paper     5.07:1  4.5   PASS
  12  #AC4B07 on #FFFFFF  --accent-text eyebrow on surface   5.58:1  4.5   PASS
  13  #AC4B07 on #FAFAFA  --accent-text link on alt surface  5.35:1  4.5   PASS
  14  #AC4B07 on #F4F4F4  --accent-text headline word       5.07:1  3.0   PASS(large)
  15  #D96A0C on #F4F4F4  --accent-display headline word    3.17:1  3.0   PASS(large)
  16  #F28C28 on #F4F4F4  raw --accent as TEXT              2.23:1  4.5   FAIL
       >> --accent is NEVER text: it is the fill for buttons and rules only.
  17  #161616 on #F28C28  primary button label              7.37:1  4.5   PASS
  18  #161616 on #D97706  primary button hover label        5.68:1  4.5   PASS
  19  #FDDD04 on #161616  eyebrow / badge on dark          13.37:1  4.5   PASS
  19b #F7A24C on #161616  headline word over photography    8.81:1  3.0   PASS(large)
  20  #FDDD04 on #262626  eyebrow / badge on dark-2        11.19:1  4.5   PASS
  21  #FFFFFF on #161616  dark panel body text             18.10:1  4.5   PASS
  22  #DCDCDC on #161616  dark panel secondary text        13.20:1  4.5   PASS
  23  #A8A8A8 on #161616  dark panel tertiary text          7.61:1  4.5   PASS
  24  #FFFFFF on #262626  dark-2 panel text                15.13:1  4.5   PASS
  25  #C6C6C6 on #262626  dark-2 secondary text             8.86:1  4.5   PASS
  26  #A8A8A8 on #262626  dark-2 tertiary text              6.36:1  4.5   PASS
  27  #161616 on #8C8C8C  disabled control label            5.38:1  3.0   PASS(large)
  28  #D4D4D4 on #F4F4F4  --line hairline on paper          1.35:1  1.3   PASS(deco)
  29  #DCDCDC on #FFFFFF  --line-soft hairline on surface   1.37:1  1.3   PASS(deco)
  30  #393939 on #161616  --line-dark hairline on dark      1.57:1  1.3   PASS(deco)
  31  #AC4B07 on #F4F4F4  --accent-text as 3:1 boundary     5.07:1  3.0   PASS(ui)
  32  #161616 on #F4F4F4  --ink as 3:1 UI boundary         16.45:1  3.0   PASS(ui)

   RESULT: 31 pass, 1 documented FAIL (raw orange as text, by design).
   ACTIONABLE DERIVATIONS LOCKED BY THIS TABLE:
     * --accent (#F28C28, the ring orange) is a FILL and RULE colour only: buttons,
       chips, structural rules. Its label is INK (#161616), never white (7.37:1).
       Orange as text measures 2.23:1, so all accent typography uses --accent-text
       (#AC4B07, 5.07:1 on paper) or, at headline size, --accent-display.
     * The ring gradient (#E31E1E -> #F38F13 -> #FDDD04) is a 2–3px rule only.
       Yellow never touches a light surface; it is the accent role on dark panels.
     * Dark panels are near-black (#161616); white is 18.10:1 and yellow 13.37:1.
     * --muted (#8C8C8C) is hairlines-and-large-text only. Body copy uses --ink-2,
       tertiary copy uses --ink-3.
   ============================================================================ */

/* ===========================================================================
   1. TOKENS
   =========================================================================== */
:root {
  /* ---- Locked brand layer (do not substitute) ---- */
  --ink:            #161616;   /* primary text / dark panels           */
  --paper:          #F4F4F4;   /* page background — light grey, NOT white */
  --muted:          #8C8C8C;   /* secondary text at large size, hairlines at low alpha */
  --accent:         #F28C28;   /* brand orange — the 2026 ring, used for FILLS only */

  /* ---- Derived surfaces ---- */
  --surface:        #FFFFFF;   /* card / panel / modal surface          */
  --surface-alt:    #FAFAFA;   /* tinted surface, table stripes, inputs */
  --surface-sunken: #EDEDED;   /* media placeholder behind unloaded img */

  /* ---- Derived text greys (3 steps) ---- */
  --ink-2:          #4A4A4A;   /* secondary body copy      8.06:1 on paper */
  --ink-3:          #6B6B6B;   /* tertiary / caption        4.85:1 on paper */
  --ink-4:          #8C8C8C;   /* = --muted: large text + hairlines only   */

  /* ---- Derived accent ---- */
  --accent-text:    #AC4B07;   /* accent as TEXT on light      5.07:1 on paper */
  --accent-display: #D96A0C;   /* headline-size orange (>=24px) 3.17:1 on paper */
  --accent-deep:    #D97706;   /* accent pressed / fill hover on light        */
  --accent-on-dark: #FDDD04;   /* the ring's yellow: eyebrows, badges on black 13.37:1 */
  --accent-word-on-dark: #F7A24C; /* headline words over photography           8.81:1 */
  --on-accent:      #161616;   /* label on --accent fill        7.37:1        */
  --accent-wash:    rgba(242, 140, 40, 0.10);  /* 10% accent tint, never behind long text */

  /* ---- Brand chrome: the ring gradient of the 2026 logo ----
     Used ONLY as a 2–3px structural rule (header foot, footer head, section marks),
     never as text or as a large fill: yellow on paper is 1.23:1 and fails everything. */
  --brand-red:      #E31E1E;
  --brand-orange:   #F38F13;
  --brand-yellow:   #FDDD04;
  --brand-gradient: linear-gradient(90deg,
                      var(--brand-red) 0%,
                      var(--brand-orange) 48%,
                      var(--brand-yellow) 100%);

  /* ---- Hairlines (1px carries ALL separation) ---- */
  --line:           #D4D4D4;   /* default hairline on paper      */
  --line-soft:      #DCDCDC;   /* inner hairline on white surface */
  --line-strong:    #161616;   /* emphasis rule (section top rules) */
  --line-accent:    #F28C28;   /* the one structural accent rule      */

  /* ---- Dark panel surface + its own text colours ---- */
  --dark:           #161616;   /* flat dark panel, equals --ink */
  --dark-2:         #262626;   /* raised cell inside a dark panel */
  --line-dark:      #393939;   /* hairline inside dark panels */
  --on-dark:        #FFFFFF;   /* text on dark                    18.10:1 */
  --on-dark-2:      #DCDCDC;   /* secondary text on dark          13.2:1 on #161616 */
  --on-dark-3:      #A8A8A8;   /* tertiary text on dark            7.61:1 */

  /* ---- Typography: IBM Plex family ---- */
  --font-display: "IBM Plex Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;
  --font-body:    "IBM Plex Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;
  --font-mono:    "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, "Courier New", monospace;

  /* ---- Type scale ---- */
  --fs-h1:      clamp(2.5rem, 1.55rem + 4.2vw, 4.75rem);  /* 40 -> 76px, weight 300 */
  --fs-h2:      clamp(1.75rem, 1.32rem + 1.9vw, 2.625rem);/* 28 -> 42px */
  --fs-h3:      clamp(1.125rem, 1.06rem + 0.3vw, 1.375rem);/* 18 -> 22px */
  --fs-h4:      1.0625rem;                                 /* 17px */
  --fs-lead:    clamp(1.0625rem, 1.02rem + 0.24vw, 1.1875rem); /* 17 -> 19px */
  --fs-body:    1rem;                                      /* 16px */
  --fs-body-sm: 0.9375rem;                                 /* 15px */
  --fs-small:   0.875rem;                                  /* 14px — mobile body floor */
  --fs-eyebrow: 0.8125rem;                                 /* 13px mono uppercase */
  --fs-caption: 0.8125rem;                                 /* 13px */
  --lh-h1:      1.08;
  --lh-h2:      1.18;
  --lh-h3:      1.3;
  --lh-body:    1.6;
  --lh-tight:   1.35;

  /* ---- Letter-spacing: the "precision industry" signature ---- */
  --ls-h1:      0.01em;    /* h1 weight 300 at large size: slightly POSITIVE  */
  --ls-display: 0.015em;   /* hero display / stat numbers                      */
  --ls-h2:      0.005em;
  --ls-body:    0.01em;    /* IBM Plex Sans body wants a hair of tracking      */
  --ls-eyebrow: 0.18em;    /* IBM Plex Mono uppercase eyebrow                  */
  --ls-label:   0.1em;     /* mono table headers / badges                      */

  --w-h1: 300;
  --w-h2: 400;
  --w-h3: 500;
  --w-body: 400;
  --w-mono-eyebrow: 500;
  --w-mono-number: 600;
  --w-strong: 600;

  /* ---- Spacing scale (single scale, all rhythm derives from it) ---- */
  --sp-1:  4px;
  --sp-2:  8px;
  --sp-3:  12px;
  --sp-4:  16px;
  --sp-5:  24px;
  --sp-6:  32px;
  --sp-7:  40px;
  --sp-8:  48px;
  --sp-9:  64px;
  --sp-10: 80px;
  --sp-11: 96px;
  --sp-12: 128px;
  --gap:    var(--sp-5);   /* default grid gap */
  --gap-sm: var(--sp-4);

  /* ---- Container: ONE max-width and ONE side padding site-wide ---- */
  --container:     1312px;
  --pad-inline:    32px;
  --pad-inline-sm: 20px;

  /* ---- Section rhythm — always applied as padding-BLOCK so the container's
         side padding is never eaten by a shorthand. ---- */
  --section-y:    96px;
  --section-y-sm: 64px;

  /* ---- Fixed chrome heights ---- */
  --header-h: 84px;

  /* ---- Photographic hero scrim ----
     Three layers, top to bottom:
       1. a strong left-to-right dark wash over the text column, so the headline, lead
          and buttons always sit on near-black;
       2. a warm-light wash. This is the layer that makes a COOL brand accent work: the
          photographs are neutral-cool grey (hue ~210, saturation ~7%) and the brand
          accent is hue ~205, so without a warm foil the accent is the same colour as the
          image, only louder, and reads as a boosted background rather than a signal. The
          warm wash gives the imagery a temperature so the accent can be the cool signal
          against it. Plausible in-scene: sodium/LED bay lighting and warm light spilling
          from the reflow oven. Measured effect on legibility is under 0.4 of a contrast
          point across white, off-white and accent text;
       3. a gentle top and bottom vignette so the band does not cut hard.
     Note on order: CSS paints the FIRST background layer on top, so the dark washes are
     listed before the amber. */
  --amber-warm: 243, 143, 19;
  --scrim-hero:
      linear-gradient(90deg,
        rgba(0, 0, 0, 0.80) 0%,
        rgba(0, 0, 0, 0.74) 38%,
        rgba(0, 0, 0, 0.46) 62%,
        rgba(0, 0, 0, 0.14) 100%),
      linear-gradient(90deg,
        rgba(var(--amber-warm), 0.30) 0%,
        rgba(var(--amber-warm), 0.22) 45%,
        rgba(var(--amber-warm), 0.10) 100%),
      linear-gradient(180deg,
        rgba(0, 0, 0, 0.30) 0%,
        rgba(0, 0, 0, 0.10) 45%,
        rgba(0, 0, 0, 0.28) 100%);

  /* Kept for dark picture panels that are not heroes (cards, bands). */
  --scrim-strong: linear-gradient(180deg,
      rgba(0, 0, 0, 0.62) 0%,
      rgba(0, 0, 0, 0.50) 50%,
      rgba(0, 0, 0, 0.42) 100%);

  /* ---- Hairline + radii: zero radius, everywhere, no exceptions ---- */
  --hairline: 1px;
  --radius:   0px;

  /* ---- Motion ---- */
  --dur-fast: 120ms;
  --dur:      180ms;
  --dur-slow: 350ms;
  --ease:     cubic-bezier(0.2, 0, 0.38, 0.9);

  /* ---- Icon ---- */
  --icon-sm: 16px;
  --icon:    20px;
  --icon-md: 24px;
  --icon-lg: 32px;

  /* ---- Layering ---- */
  --z-header: 60;
  --z-sticky: 70;
  --z-modal:  100;
}

/* ===========================================================================
   2. RESET / NORMALIZE
   =========================================================================== */
*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: calc(var(--header-h) + var(--sp-4));
}

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: var(--fs-body);
  font-weight: var(--w-body);
  line-height: var(--lh-body);
  letter-spacing: var(--ls-body);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
}

h1, h2, h3, h4, h5, h6 { margin: 0; font-weight: inherit; font-size: inherit; }
p, figure, blockquote, dl, dd { margin: 0; }
ul, ol { margin: 0; padding: 0; list-style: none; }

img, svg, video, canvas {
  display: block;
  max-width: 100%;
  height: auto;
}

a { color: inherit; text-decoration: none; }
a:hover { color: var(--accent-text); }

button, input, select, textarea {
  font: inherit;
  color: inherit;
  letter-spacing: inherit;
  background: none;
  border: 0;
}
button { padding: 0; cursor: pointer; }
table { border-collapse: collapse; border-spacing: 0; }
caption { text-align: left; }

hr { border: 0; border-top: var(--hairline) solid var(--line); margin: 0; }

strong, b { font-weight: var(--w-strong); }

::selection { background: var(--accent); color: var(--on-accent); }

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* ===========================================================================
   3. BASE TYPOGRAPHY — the type scale
   =========================================================================== */
.h1, h1.h1 {
  font-family: var(--font-display);
  font-size: var(--fs-h1);
  font-weight: var(--w-h1);
  line-height: var(--lh-h1);
  letter-spacing: var(--ls-h1);
  color: var(--ink);
  text-wrap: balance;
}
.h2, h2.h2 {
  font-family: var(--font-display);
  font-size: var(--fs-h2);
  font-weight: var(--w-h2);
  line-height: var(--lh-h2);
  letter-spacing: var(--ls-h2);
  color: var(--ink);
  text-wrap: balance;
}
.h3, h3.h3 {
  font-family: var(--font-display);
  font-size: var(--fs-h3);
  font-weight: var(--w-h3);
  line-height: var(--lh-h3);
  letter-spacing: var(--ls-h2);
  color: var(--ink);
}
.h4 {
  font-family: var(--font-display);
  font-size: var(--fs-h4);
  font-weight: var(--w-h3);
  line-height: var(--lh-tight);
  color: var(--ink);
}

/* The ONE emphasised word inside a headline. Never a body-text colour.
   At display size the brighter orange clears the 3:1 large-text bar; smaller
   emphasis (h3/h4) uses the darker --accent-text. .sub-hero and the other dark
   banners are handled by the dark rule below. */
.h1 em, .h2 em, h1 em, h2 em,
.hero-inner em, .cta-inner em {
  font-style: normal;
  color: var(--accent-display);
}
.h3 em, .h4 em, h3 em, h4 em { font-style: normal; color: var(--accent-text); }
.section--dark .h1 em, .section--dark .h2 em, .section--dark .h3 em,
.section--dark h1 em, .section--dark h2 em, .section--dark h3 em,
.hero--full em, .hero--split em, .hero--type em, .sub-hero em,
.card--dark em { color: var(--accent-word-on-dark); }

.lead {
  font-family: var(--font-body);
  font-size: var(--fs-lead);
  font-weight: var(--w-body);
  line-height: var(--lh-body);
  color: var(--ink-2);
  max-width: 62ch;
}

.prose { max-width: 72ch; }
.prose > * + * { margin-top: var(--sp-4); }
.prose p, .prose li {
  font-size: var(--fs-body);
  color: var(--ink-2);
  line-height: var(--lh-body);
}
.prose h2 { margin-top: var(--sp-8); }
.prose h3 { margin-top: var(--sp-6); }
.prose h4 { margin-top: var(--sp-5); }
.prose ul { list-style: none; }
.prose ul > li {
  position: relative;
  padding-left: var(--sp-5);
  margin-top: var(--sp-2);
}
.prose ul > li::before {
  content: "";
  position: absolute;
  left: 0; top: 0.6em;
  width: var(--sp-3); height: var(--hairline);
  background: var(--accent);
}
.prose a {
  color: var(--accent-text);
  border-bottom: var(--hairline) solid currentColor;
}
.prose strong { color: var(--ink); font-weight: var(--w-strong); }
.prose blockquote {
  border-left: var(--hairline) solid var(--line-strong);
  padding-left: var(--sp-5);
  color: var(--ink);
  font-size: var(--fs-lead);
}
.prose code {
  font-family: var(--font-mono);
  font-size: 0.9em;
  background: var(--surface-alt);
  border: var(--hairline) solid var(--line-soft);
  padding: 1px 5px;
}

/* Mono utility text (eyebrows, table headers, badges, numerics) */
.mono { font-family: var(--font-mono); }

/* ===========================================================================
   4. LAYOUT — .container and utilities
   =========================================================================== */
.container {
  width: 100%;
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: var(--pad-inline);
}

/* ---- Section shells. All use padding-BLOCK. ---- */
.section { padding-block: var(--section-y); background: var(--paper); }
.section--tight { padding-block: var(--section-y-sm); }
.section--paper { background: var(--paper); }
.section--dark {
  background: var(--dark);
  color: var(--on-dark);
}
.section--dark .h1, .section--dark .h2, .section--dark .h3, .section--dark .h4,
.section--dark h1, .section--dark h2, .section--dark h3, .section--dark h4 { color: var(--on-dark); }
.section--dark .lead, .section--dark .prose p, .section--dark .prose li { color: var(--on-dark-2); }
.section--dark .eyebrow { color: var(--accent-on-dark); }
.section--dark .prose a { color: var(--accent-on-dark); }
/* Hairline rule that separates stacked modules site-wide */
.section--ruled { border-top: var(--hairline) solid var(--line); }
.section--dark.section--ruled { border-top-color: var(--line-dark); }

.section-head { max-width: 900px; margin-bottom: var(--sp-8); }
.section-head--center {
  text-align: center;
  margin-inline: auto;   /* centred sections need BOTH text-align and block centring */
}
.section-head--center .lead { margin-inline: auto; }
.section-head > * + * { margin-top: var(--sp-4); }
.section-head .h2 { margin-top: var(--sp-3); }

.eyebrow {
  display: block;
  margin: 0;
  font-family: var(--font-mono);
  font-size: var(--fs-eyebrow);
  font-weight: var(--w-mono-eyebrow);
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  color: var(--accent-text);   /* accent role 2 of 3: section eyebrows/kickers */
}
.eyebrow--muted { color: var(--ink-3); }

/* Section marker: the five-petal plum-blossom signature, once per section. */
.section-marker {
  display: block;
  width: 40px;
  height: 3px;
  background: var(--brand-gradient);
  margin-bottom: var(--sp-4);
}
.section-head--center .section-marker { margin-inline: auto; }
.section--dark .section-marker { color: var(--accent-on-dark); }

/* ---- Grids: fixed column counts, never auto-fit (no orphan rows) ---- */
.grid { display: grid; gap: var(--gap); grid-template-columns: 1fr; }
.grid > * { min-width: 0; }   /* grid children must be allowed to shrink */
.grid-2, .grid-3, .grid-4 { display: grid; gap: var(--gap); grid-template-columns: 1fr; }
.grid-2 > *, .grid-3 > *, .grid-4 > * { min-width: 0; }
@media (min-width: 641px) {
  .grid-2 { grid-template-columns: repeat(2, 1fr); }
  .grid-3 { grid-template-columns: repeat(2, 1fr); }
  .grid-4 { grid-template-columns: repeat(2, 1fr); }
}
@media (min-width: 1025px) {
  .grid-3 { grid-template-columns: repeat(3, 1fr); }
  .grid-4 { grid-template-columns: repeat(4, 1fr); }
}
/* Hairline module split: cells share one hairline instead of floating apart */
.grid--ruled { gap: var(--hairline); background: var(--line); border: var(--hairline) solid var(--line); }
.grid--ruled > * { background: var(--surface); }
.section--dark .grid--ruled { background: var(--line-dark); border-color: var(--line-dark); }
.section--dark .grid--ruled > * { background: var(--dark); }

/* ---- Balanced grid: closes a partial last row instead of leaving an orphan ----
   The card-count rule (count = multiple of column count) is still the preferred
   structure. When content genuinely cannot be cut to fit, add `.grid--balanced`
   and the grid COMPLETES its final row rather than orphaning a cell:

     ODD child count  -> the last cell spans the full width, closing the row.
     EXACTLY 5 cells  -> the last two cells share the final row (3 + 2).

   Both are the same physical move the page already makes: a module that spans the
   full width of the grid, separated by the same hairline. At 2-column and
   1-column widths the rules still hold, because an odd count leaves 1 remainder
   and 5 leaves 2 under a 2-column layout as well.

   Usage:  <div class="grid grid-3 grid--balanced">   (3, 7, 9, 11 … cells)
           <div class="grid grid-3 grid--balanced-5"> (exactly 5 cells) */
@media (min-width: 641px) {
  .grid--balanced > *:last-child:nth-child(odd) { grid-column: 1 / -1; }

  /* Five cells leave a remainder of 2 at BOTH 3 columns and 2 columns, which the
     simple "span the last cell" rule cannot close. So the grid gets a 6-column
     spine instead: three modules of 2 columns, then two modules of 3 columns.
       >= 1025px -> 3 columns rendering: row 1 = 3 modules, row 2 = 2 modules
       641-1024px -> 2 columns rendering: row 1 = 2, row 2 = 2, row 3 = 1
     Both rows/columns are full, and no module is ever orphaned. */
  .grid-3.grid--balanced-5 { grid-template-columns: repeat(6, 1fr); }
  .grid-3.grid--balanced-5 > * { grid-column: span 2 / span 2; }
  .grid-3.grid--balanced-5 > *:nth-child(4),
  .grid-3.grid--balanced-5 > *:nth-child(5) { grid-column: span 3 / span 3; }
}
@media (min-width: 1025px) {
  /* If a 5-cell grid is authored at 4 columns the closing row is 4 + 1, so the
     last cell spans the full width rather than sitting alone. */
  .grid-4.grid--balanced-5 > *:nth-child(5) { grid-column: 1 / -1; }
}

/* ---- Split: image + body, true equal height ---- */
.split {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(var(--sp-6), 4vw, var(--sp-9));
  align-items: stretch;
}
.split > * { min-width: 0; }
.split--reverse .split-media { order: 2; }
.split-media {
  position: relative;
  min-width: 0;
  overflow: hidden;
  background: var(--surface-sunken);
}
.split-media img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.split-body {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: var(--sp-4);
  min-width: 0;
}
.split-body > * + * { margin-top: 0; }
@media (max-width: 880px) {
  .split { grid-template-columns: 1fr; }
  .split--reverse .split-media { order: 0; }
  /* min-height only. An aspect-ratio here would combine with min-height to force
     an automatic minimum WIDTH wider than the viewport (3/2 x 280px = 420px),
     which made the whole page scroll sideways at 375px. */
  .split-media { position: relative; min-height: 280px; }
}

/* ---- Picture frames: aspect-ratio must match the source file ---- */
.frame-img {
  display: block;
  width: 100%;
  overflow: hidden;
  background: var(--surface-sunken);
}
.frame-img > img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.img-3-2 { aspect-ratio: 3/2; }
.img-2-3 { aspect-ratio: 2/3; }
.img-1-1 { aspect-ratio: 1/1; }
.img-3-4 { aspect-ratio: 3/4; }

/* ===========================================================================
   5. INLINE SVG SPRITE — sizing rules
   The sprite lives ONCE per page, inlined at the very start of <body>
   (see README-TEMPLATE.md §3). Symbols are 24x24, stroke 1.5, currentColor.
   =========================================================================== */
.icon-sprite {
  position: absolute;
  width: 0;
  height: 0;
  overflow: hidden;
  pointer-events: none;
}
.icon {
  display: inline-block;
  width: var(--icon);
  height: var(--icon);
  flex: none;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
  stroke-linecap: square;
  stroke-linejoin: miter;
  vertical-align: -0.15em;
}
.icon--sm { width: var(--icon-sm); height: var(--icon-sm); stroke-width: 1.5; }
.icon--md { width: var(--icon-md); height: var(--icon-md); stroke-width: 1.5; }
.icon--lg { width: var(--icon-lg); height: var(--icon-lg); stroke-width: 1.25; }
/* icon inside a button: inherit the button's text colour */
.btn .icon { color: currentColor; }

/* ===========================================================================
   6. SITE HEADER / NAV
   =========================================================================== */
.site-header {
  position: sticky;
  top: 0;
  z-index: var(--z-header);
  background: var(--surface);
  /* the logo's ring gradient, reduced to a 2px structural rule */
  border-bottom: 2px solid;
  border-image: var(--brand-gradient) 1;
}
.nav { width: 100%; }
.nav-inner {
  /* The header holds four children — logo, hamburger, menu, CTA — so the grid is
     `auto 1fr auto`: the logo and the hamburger take their natural width, the menu rail
     owns the flexible middle and centres itself there, and the CTA sits hard right.
     A flex row let the menu drift (measured 318px of space on its left against 308px on
     its right) because it was pushed along by whatever the logo happened to measure. */
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: var(--sp-4);
  min-height: var(--header-h);
  width: 100%;
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: var(--pad-inline);
}
.nav-inner > .nav-logo { grid-column: 1; justify-self: start; }
.nav-inner > .nav-toggle { grid-column: 2; justify-self: end; }
.nav-inner > .nav-menu { grid-column: 2; justify-self: center; }
.nav-inner > .nav-cta { grid-column: 3; justify-self: end; }
.nav-logo {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-3);
  min-height: 44px;
  font-family: var(--font-display);
  font-size: 1.125rem;
  font-weight: 500;
  letter-spacing: 0.02em;
  color: var(--ink);
  white-space: nowrap;
}
.nav-logo:hover { color: var(--ink); }
.nav-logo .icon { color: var(--accent); }
/* The client's 2026 lockup, generated from the supplied artwork by
   _scripts/build-brand-assets-2026.py. Fixed height, automatic width. */
.nav-logo-mark { display: block; height: 60px; width: auto; flex: 0 0 auto; }
.footer-brand .nav-logo-mark { height: 56px; }
.nav-logo-text {
  display: block;
  line-height: 1.1;
  font-weight: 600;
  letter-spacing: 0.055em;
  white-space: nowrap;
}
.nav-logo-sub {
  display: block;
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  font-weight: var(--w-mono-eyebrow);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--ink-3);
}
.nav-menu {
  display: flex;
  align-items: center;
  /* Spacing is solved, not guessed. Measured: eleven link labels total 654px of text,
     the logo is 144px and the CTA 169px, so at the 1312px container the menu has a
     967px budget. 11px of padding a side plus a 10px gap needs ~960px — visibly airier
     than the 6px/0px the old compact band squeezed it to, and it still fits from
     1366px up. Below that the media queries below shrink it, and below 1200px the
     hamburger takes over. */
  gap: 10px;
  margin-inline: auto;
  padding-inline: var(--sp-2);
}
.nav-item { position: relative; }
.nav-link {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  min-height: 44px;
  padding: 0 11px;
  font-size: var(--fs-body-sm);
  font-weight: var(--w-body);
  color: var(--ink);
  border-bottom: 2px solid transparent;
  white-space: nowrap;
}
.nav-link:hover { color: var(--accent-text); }
.nav-link[aria-current="page"] {
  color: var(--ink);
  font-weight: var(--w-strong);
  border-bottom-color: var(--accent);
}
.nav-link .icon { color: var(--ink-3); }
.nav-link[aria-current="page"] .icon { color: var(--accent); }
.nav-cta { margin-left: var(--sp-4); display: flex; align-items: center; }
/* The header CTA must never wrap: a 3-line button in a 72px bar is the first thing
   a buyer sees. It shrinks to its own text and stays on one line. */
.nav-cta .btn { white-space: nowrap; flex: none; }
.nav-toggle {
  display: none;
  width: 44px;
  height: 44px;
  align-items: center;
  justify-content: center;
  border: var(--hairline) solid var(--line);
  color: var(--ink);
}
.nav-toggle .icon { width: var(--icon-md); height: var(--icon-md); }

/* ---- Dropdown: opened by JS (hover intent + ~300ms close delay), not :hover ---- */
.submenu {
  position: absolute;
  top: calc(100% + 1px);
  left: 0;
  z-index: 2;
  min-width: 320px;
  display: none;
  padding: var(--sp-2) 0;
  background: var(--surface);
  border: var(--hairline) solid var(--line);
  border-top: 2px solid var(--accent);
}
.submenu.is-open { display: block; }
.submenu-link {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-4);
  min-height: 44px;
  padding: var(--sp-2) var(--sp-4);
  font-size: var(--fs-body-sm);
  color: var(--ink);
  border-bottom: var(--hairline) solid var(--line-soft);
}
.submenu li:last-child .submenu-link { border-bottom: 0; }
.submenu-link:hover { background: var(--surface-alt); color: var(--accent-text); }
.submenu-hook {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  font-weight: var(--w-mono-eyebrow);
  letter-spacing: 0.04em;
  color: var(--ink-3);
  white-space: nowrap;
}
.submenu-link:hover .submenu-hook { color: var(--accent-text); }

/* ---- Compact nav band -------------------------------------------------------
   Measured: the full-size header (logo 221px + menu 953px + CTA 168px + gutters)
   needs 1470px of available width. The container caps at 1312px above 1440px, so
   the full-size row only fits once the viewport itself is >= 1600px and the whole
   1470px sits inside the container. Below 1600px the row is tightened in two
   measured steps; the hamburger takes over at 1200px. Every width from 320px to
   2560px was swept in Chromium — no width overflows.
   Measured totals: medium 1130px, tight 1030px. */
@media (min-width: 1366px) and (max-width: 1599px) {
  /* This band sat exactly one pixel wide of the tight media query below, so at 1366px the
     header overflowed by 22px (the CTA ended at 1407 in a 1366 viewport). It gets its own
     spacing instead of inheriting the loose default. */
  .nav-inner { gap: var(--sp-3); }
  .nav-menu { gap: 8px; padding-inline: var(--sp-1); }
  .nav-link { padding-inline: 9px; font-size: 14px; }
  .nav-link .icon { width: 12px; height: 12px; }
  .nav-cta .btn { padding-inline: var(--sp-3); font-size: var(--fs-caption); }
}
@media (min-width: 1201px) and (max-width: 1365px) {
  /* container 1153px -> menu budget ~808px */
  .nav-inner { gap: var(--sp-3); }
  .nav-menu { gap: 8px; padding-inline: var(--sp-1); }
  .nav-link { padding-inline: 8px; font-size: 14px; }
  .nav-link .icon { width: 12px; height: 12px; }
  .nav-cta .btn { padding-inline: var(--sp-3); font-size: var(--fs-caption); }
}

/* Wide text inside self-drawn SVG diagrams (the parameter matrices) is wider than a
   narrow viewport. Without this the diagram pushed the whole document sideways; the SVG
   should scale to its container and, if it still cannot fit, scroll inside itself rather
   than move the page. */
.section svg:not(.icon):not(.section-marker):not(.icon-sprite),
.svg-figure {
  max-width: 100%;
  height: auto;
  overflow: visible;
}
.prose > svg, .spec-table + svg, figure > svg { display: block; margin-inline: auto; }

@media (max-width: 1200px) {
  /* Below the hamburger breakpoint the header holds two visible children — the logo and
     the toggle — while the CTA moves inside the panel. The desktop grid is
     `auto 1fr auto`, and leaving it unchanged dropped the CTA into the third column on
     top of the wordmark: measured on a 390px viewport, "SATURN PCBA" and the quote button
     overlapped. Two columns, toggle right-aligned in the second. */
  .nav-inner { grid-template-columns: auto 1fr; }
  .nav-inner > .nav-menu { grid-column: 1 / -1; justify-self: stretch; }
  .nav-inner > .nav-toggle { grid-column: 2; justify-self: end; margin-left: 0; }
  .nav-inner > .nav-cta { display: none; }
  .nav-toggle { display: inline-flex; }
  .nav-menu {
    /* fixed, so the panel spans the VIEWPORT rather than the container: the header
       itself is only as wide as its container. */
    position: fixed;
    top: var(--header-h);
    left: 0;
    right: 0;
    z-index: 3;
    display: none;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    margin-left: 0;
    padding: var(--sp-2) var(--pad-inline) var(--sp-5);
    background: var(--surface);
    border-top: var(--hairline) solid var(--line);
    border-bottom: var(--hairline) solid var(--line);
    max-height: calc(100vh - var(--header-h));
    overflow-y: auto;
  }
  .nav-menu.is-open { display: flex; }
  .nav-item { border-bottom: var(--hairline) solid var(--line-soft); }
  .nav-link { width: 100%; padding-block: var(--sp-2); border-bottom: 0; }
  .nav-link[aria-current="page"] { border-bottom: 0; }
  .nav-link .icon { margin-left: auto; }
  .submenu {
    position: static;
    display: none;
    min-width: 0;
    padding: 0 0 var(--sp-2);
    border: 0;
    border-top: 0;
    background: var(--surface-alt);
  }
  .submenu li:last-child .submenu-link { border-bottom: 0; }
  .submenu-link { padding-inline: var(--sp-4); }
  .nav-cta { margin: var(--sp-4) 0 0; }
  .nav-cta .btn { width: 100%; }
}
@media (max-width: 640px) {
  /* The header is logo + hamburger + quote button on one row. The wordmark must be
     allowed to shrink or the CTA pushes past the viewport at 375px. */
  .nav-inner { padding-inline: var(--pad-inline-sm); gap: var(--sp-3); flex-wrap: wrap; }
  .nav-logo { font-size: 1rem; min-width: 0; }
  .nav-logo-mark { height: 44px; }
  .nav-logo-text { min-width: 0; }
  .nav-toggle { flex: none; }
  .nav-cta { margin-left: auto; }
}
@media (max-width: 420px) {
  /* Below ~420px the mono sub-line is the first thing to go: the capability claim
     is repeated in the footer, but an overflowing header is not recoverable. */
  .nav-logo-sub { display: none; }
}

/* ===========================================================================
   7. HERO FAMILY
   =========================================================================== */
.hero {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  background: var(--paper);
}
.hero-bg {
  position: absolute;
  inset: 0;
  z-index: -2;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.hero-overlay {
  position: absolute;
  inset: 0;
  z-index: -1;
  background: var(--scrim-hero);
}
.hero-inner {
  width: 100%;
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: var(--pad-inline);
  padding-block: var(--section-y);
  color: var(--ink);
}
/* Full-bleed photographic hero — headline lands on reserved clean space */
.hero--full {
  display: flex;
  align-items: flex-start;
  min-height: clamp(520px, 72vh, 680px);
}
.hero--full .hero-inner {
  padding-block: clamp(var(--sp-9), 9vw, var(--sp-12)) var(--section-y-sm);
  color: var(--on-dark);
}
/* Element-level colour overrides: `.h1 { color: var(--ink) }` and
   `.lead { color: var(--ink-2) }` are declared on the element classes and would
   otherwise win over the inherited colour on a dark hero. The same dark-context
   treatment already exists for .hero--type, .sub-hero, .section--dark and
   .cta-inner — this closes the last variant. */
.hero--full .h1,
.hero--full h1 { color: var(--on-dark); }
.hero--full .lead { color: var(--on-dark-2); }
.hero--full .eyebrow { color: var(--accent-on-dark); }
/* 760px on a 1392px container left 608px of empty hero. The headline keeps its own    narrower measure from the type scale; the lead and the buttons use the room. */ .hero--full .hero-inner > * { max-width: 940px; } .hero--full .hero-inner > .h1, .hero--full .hero-inner > h1 { max-width: 22ch; }
/* Text on the reserved lower third of the picture.
   Constraint 27 requires the copy to sit on the image's own negative space, and the page
   for high-layer power boards is generated with the subject upper-weighted and the bottom
   third left clean. The default hero--full pins its copy to the top-left, which put the
   headline straight over the board. This variant moves the copy down and swaps the
   left-weighted scrim for a bottom-weighted one, so the protection follows the text. */
.hero--bottom { align-items: flex-end; }
.hero--bottom .hero-overlay {
  background:
    linear-gradient(180deg,
      rgba(0, 0, 0, 0.14) 0%,
      rgba(0, 0, 0, 0.28) 42%,
      rgba(0, 0, 0, 0.72) 74%,
      rgba(0, 0, 0, 0.84) 100%),
    linear-gradient(90deg,
      rgba(var(--amber-warm), 0.16) 0%,
      rgba(var(--amber-warm), 0.10) 60%,
      rgba(var(--amber-warm), 0.06) 100%);
}
/* The container already spans the full hero height, so aligning the FLEX ITEM did nothing:
   measured, the copy still started at 0% of the picture. The inner block has to lay its own
   children out from the bottom edge. */
.hero--bottom .hero-inner {
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  min-height: inherit;
  /* .hero--full gives the inner block a top padding of clamp(sp-9, 9vw, sp-12), which is
     most of the hero height - flex-end had nothing left to push against, and the copy
     stayed where it was. Only the bottom offset remains. */
  padding-block: 0 clamp(var(--sp-8), 7vw, var(--sp-10));
}
.hero--bottom .hero-inner > * { max-width: 880px; }
/* Split hero: type on one side, reserved image panel on the other */
.hero--split {
  display: grid;
  grid-template-columns: 1fr 1fr;
  align-items: stretch;
  min-height: clamp(460px, 64vh, 620px);
}
.hero--split .hero-inner { padding-block: clamp(var(--sp-9), 8vw, var(--sp-11)); }
.hero--split .hero-bg { position: relative; inset: auto; z-index: 0; height: 100%; }
.hero--split .hero-media {
  position: relative;
  overflow: hidden;
  min-width: 0;
  background: var(--surface-sunken);
}
.hero--split .hero-media img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
/* A technical diagram must not be cropped. The media panel is taller than a 3:2 drawing, so
   `cover` scaled the diagram until its edges fell outside the panel — harmless on a photograph,
   but on a drawing it removes the dimensions the drawing exists to show. Marked diagrams are
   contained and centred instead. */
.hero--split .hero-media img[data-fit="contain"],
.hero--split .hero-media img[src*="diagram"] {
  object-fit: contain;
  padding: var(--sp-6);
}
/* Type-only hero: no photograph, the grid itself is the visual */
.hero--type {
  background: var(--dark);
  color: var(--on-dark);
  border-bottom: var(--hairline) solid var(--line-dark);
}
.hero--type .hero-inner { padding-block: clamp(var(--sp-10), 11vw, 160px) var(--sp-11); }
.hero--type .h1, .hero--type h1 { color: var(--on-dark); }
.hero--type .lead { color: var(--on-dark-2); }
.hero--type .eyebrow { color: var(--accent-on-dark); }
.hero--type .hero-inner > * { max-width: 980px; }
.hero-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3);
  margin-top: var(--sp-6);
}
/* Centred variants — required because text-align:center does not affect flex
   items, and several pages (home, industries hub, capability hubs) place their
   CTA row and badge row on the centre axis. */
.hero-actions--center { justify-content: center; }
.badge-row--center { justify-content: center; }
.icon-list--center { justify-content: center; }
@media (max-width: 880px) {
  .hero--split { grid-template-columns: 1fr; }
  .hero--split .hero-media { min-height: 260px; order: 0; }
  .hero--split .hero-inner { order: 1; }
}
@media (max-width: 640px) {
  .hero-inner { padding-inline: var(--pad-inline-sm); }
  .hero-actions .btn { flex: 1 1 100%; }
}

/* ---- Sub-hero: the interior-page banner ---- */
.sub-hero {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  background: var(--dark);
  color: var(--on-dark);
  border-bottom: var(--hairline) solid var(--line-dark);
}
.sub-hero-bg {
  position: absolute;
  inset: 0;
  z-index: -2;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.sub-hero::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background:
    linear-gradient(90deg,
      rgba(0, 0, 0, 0.82) 0%,
      rgba(0, 0, 0, 0.76) 40%,
      rgba(0, 0, 0, 0.52) 66%,
      rgba(0, 0, 0, 0.30) 100%),
    linear-gradient(90deg,
      rgba(var(--amber-warm), 0.30) 0%,
      rgba(var(--amber-warm), 0.22) 45%,
      rgba(var(--amber-warm), 0.10) 100%),
    linear-gradient(180deg, rgba(0, 0, 0, 0.30) 0%, rgba(0, 0, 0, 0.24) 100%);
}
.sub-hero .hero-inner { padding-block: var(--sp-10) var(--sp-9); }
.sub-hero .h1, .sub-hero h1 { color: var(--on-dark); }
.sub-hero .lead { color: var(--on-dark-2); }
.sub-hero .eyebrow { color: var(--accent-on-dark); }
.sub-hero-inner > * { max-width: 940px; }
.sub-hero--short .hero-inner { padding-block: var(--sp-8) var(--sp-7); }

.breadcrumb {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2);
  margin-bottom: var(--sp-5);
  font-family: var(--font-mono);
  font-size: 0.75rem;
  font-weight: var(--w-mono-eyebrow);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--on-dark-3);
}
.breadcrumb a {
  color: var(--on-dark-3);
  /* The breadcrumb sits on a dark hero, so its links looked like inline text but are real
     navigation. At mobile width the evaluator measured them at 34x19px, under the 40px
     touch minimum, and WCAG's inline-link exemption does not apply because they are the
     only way back up the hierarchy. Height comes from padding so the text size is
     unchanged. */
  display: inline-flex;
  align-items: center;
  min-height: 40px;
  padding-block: var(--sp-1);
}
.breadcrumb a:hover { color: var(--on-dark); }
.breadcrumb li { display: inline-flex; align-items: center; gap: var(--sp-2); }
.breadcrumb li + li::before {
  content: "/";
  color: var(--on-dark-3);
  opacity: 0.7;
}
.breadcrumb [aria-current="page"] { color: var(--on-dark); }
.breadcrumb .icon { width: 14px; height: 14px; }
.breadcrumb--light { color: var(--ink-3); }
.breadcrumb--light a { color: var(--ink-3); }
.breadcrumb--light a:hover { color: var(--accent-text); }
.breadcrumb--light [aria-current="page"] { color: var(--ink); }
.breadcrumb--light li + li::before { color: var(--ink-4); }

/* ===========================================================================
   8. CONTENT COMPONENTS
   =========================================================================== */

/* ---- Card: a module cell, hairline-separated, zero shadow ---- */
.card {
  display: flex;
  flex-direction: column;
  min-width: 0;
  background: var(--surface);
  border: var(--hairline) solid var(--line);
}
.card-media {
  position: relative;
  overflow: hidden;
  background: var(--surface-sunken);
  border-bottom: var(--hairline) solid var(--line);
  aspect-ratio: 3/2;
}
.card-media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.card-body {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  padding: var(--sp-6);
  flex: 1 1 auto;
}
.card-title {
  font-family: var(--font-display);
  font-size: 1.125rem;
  font-weight: var(--w-h3);
  line-height: var(--lh-tight);
  letter-spacing: var(--ls-h2);
  color: var(--ink);
}
.card-text {
  font-size: var(--fs-body-sm);
  line-height: var(--lh-body);
  color: var(--ink-2);
}
.card-meta {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  font-weight: var(--w-mono-eyebrow);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--ink-3);
}
.card-link {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  margin-top: auto;
  padding-top: var(--sp-3);
  min-height: 44px;
  font-family: var(--font-mono);
  font-size: 0.8125rem;
  font-weight: var(--w-mono-eyebrow);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--accent-text);
  border-top: var(--hairline) solid var(--line-soft);
}
.card-link .icon { transition: transform var(--dur) var(--ease); }
.card-link:hover { color: var(--accent-text); }
.card-link:hover .icon { transform: translateX(4px); }
.card--dark { background: var(--dark); border-color: var(--line-dark); color: var(--on-dark); }
.card--dark .card-title { color: var(--on-dark); }
.card--dark .card-text { color: var(--on-dark-2); }
.card--dark .card-meta { color: var(--on-dark-3); }
.card--dark .card-media { border-bottom-color: var(--line-dark); }
.card--dark .card-link { color: var(--accent-on-dark); border-top-color: var(--line-dark); }
.card--flush .card-body { padding: var(--sp-5); }
/* A grid whose children are all cards keeps the card count honest (hard constraint 21), and
   the scanner decides that from the class token. This variant is for children that are
   already a composed block - a heading with its actions, or a self-drawn diagram - where a
   visible border would be redundant, so it contributes structure without adding a frame. */
.card--plain { background: transparent; border: 0; padding: 0; }
/* In a hero, a card that only holds a "jump to" panel hugs its content instead of
   stretching to the height of the headline column (which left a large empty box). */
.hero .grid > .card--flush { align-self: start; }

/* ---- Stat band: parallel cells sharing one hairline ---- */
.stat-band {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--hairline);
  background: var(--line);
  border-block: var(--hairline) solid var(--line);
}
.stat {
  min-width: 0;
  padding: var(--sp-6) var(--sp-5);
  background: var(--surface);
  text-align: center;
}
.stat-value {
  display: block;
  font-family: var(--font-mono);
  font-size: clamp(1.75rem, 1.25rem + 1.8vw, 2.5rem);
  font-weight: var(--w-mono-number);
  line-height: 1.1;
  letter-spacing: var(--ls-display);
  color: var(--ink);
  white-space: nowrap;
}
.stat-label {
  display: block;
  margin-top: var(--sp-3);
  font-size: var(--fs-caption);
  line-height: var(--lh-tight);
  color: var(--ink-3);
}
.stat--accent .stat-value { color: var(--accent-display); }
.section--dark .stat-band, .stat-band--dark { background: var(--line-dark); border-block-color: var(--line-dark); }
.section--dark .stat, .stat-band--dark .stat { background: var(--dark); }
.section--dark .stat-value, .stat-band--dark .stat-value { color: var(--on-dark); }
.section--dark .stat-label, .stat-band--dark .stat-label { color: var(--on-dark-3); }
.section--dark .stat--accent .stat-value, .stat-band--dark .stat--accent .stat-value { color: var(--accent-on-dark); }
@media (max-width: 1024px) { .stat-band { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 640px) { .stat-band { grid-template-columns: 1fr; } }

/* ---- Tables: no adjective density, horizontal scroll on narrow screens ---- */
.table-wrap {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  border: var(--hairline) solid var(--line);
  background: var(--surface);
}
/* Self-drawn diagrams carry fixed pixel widths (up to 1248px) and their labels are wider
   than a phone viewport, so without a scroll container they pushed the whole document
   sideways — measured 441px of document in a 430px viewport. A wrapper already exists in
   the markup; this is the rule it needed. */
.diagram-wrap {
  max-width: 100%;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}
.diagram-wrap > svg { display: block; max-width: 100%; height: auto; }
/* ---- Photographic evidence ------------------------------------------------------------
   The certificate wall and the two photographic grids on the home page. These carry the
   client's own material: document scans and unposed factory and visitor photographs. The
   certificate images are letterboxed onto a 3:2 canvas rather than cropped, so the
   certificate number and the validity dates stay inside the frame. */
.evidence-grid,
.visit-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--sp-6);
  margin-top: var(--sp-6);
}
@media (max-width: 1024px) {
  .evidence-grid, .visit-grid { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 640px) {
  .evidence-grid, .visit-grid { grid-template-columns: 1fr; }
}
.evidence-card,
.visit-card {
  display: flex;
  flex-direction: column;
  min-width: 0;
  margin: 0;
  background: var(--surface);
  border: var(--hairline) solid var(--line);
}
.evidence-card img,
.visit-card img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 3/2;
  object-fit: cover;
}
.visit-card img { aspect-ratio: 1/1; }
/* In the compact home module one card is a portrait document, one is a landscape
   photograph and one is a visitor frame. Left at their natural ratios the three columns
   ended at different heights and the shorter cards showed a band of empty space under the
   caption. One ratio for all three keeps the row level; the certificate is contained
   inside it rather than cropped. */
.evidence-grid--compact .cert-card img,
.evidence-grid--compact .evidence-card img,
.evidence-grid--compact .visit-card img {
  aspect-ratio: 3/2;
  width: 100%;
  height: auto;
  object-fit: cover;
}
.evidence-grid--compact .cert-card img { object-fit: contain; background: var(--surface-alt); }
.evidence-grid--compact figcaption { min-height: 4.2em; }
.evidence-card figcaption,
.visit-card figcaption {
  padding: var(--sp-3) var(--sp-4) var(--sp-4);
  font-size: var(--fs-caption);
  line-height: 1.45;
  color: var(--ink-2);
}
.visit-card figcaption b {
  display: block;
  font-size: var(--fs-body-sm);
  font-weight: var(--w-label);
  color: var(--ink);
  margin-bottom: 2px;
}
.visit-card figcaption span { color: var(--ink-3); }
/* the certificates are portrait documents, so they keep a document aspect and sit on
   top-aligned so the issuing body and the standard line are what you see first */
.cert-card img {
  aspect-ratio: 3/4;
  object-fit: contain;
  background: var(--surface-alt);
}

/* The certificate captions sit under a portrait scan, so the name and the issuing body
   must read as two separate lines rather than running together. Each is a single rule:
   the merge pass had collapsed .cert-name's typography into .cert-desc, which turned the
   certificate title into caption-sized grey text. */
.cert-card figcaption { display: block; }
.cert-name {
  display: block;
  line-height: 1.35;
  margin-top: var(--sp-4);
  font-family: var(--font-mono);
  font-size: 0.875rem;
  font-weight: var(--w-mono-number);
  letter-spacing: 0.02em;
  color: var(--ink);
}
.cert-desc {
  display: block;
  line-height: 1.4;
  margin-top: var(--sp-2);
  font-size: var(--fs-caption);
  color: var(--ink-3);
}

/* The panel CTA exists only on small screens, where the header one is hidden. */
.nav-item--cta { display: none; }
@media (max-width: 1200px) {
  .nav-item--cta { display: block; border-bottom: 0; padding-top: var(--sp-4); }
  .nav-link--cta {
    justify-content: center;
    width: 100%;
    min-height: 44px;
    background: var(--accent);
    color: var(--on-accent);
    font-weight: var(--w-label);
  }
  .nav-link--cta:hover { background: var(--accent-deep); color: var(--on-accent); }
}

/* ---- Home page rhythm -----------------------------------------------------------------
   The home page was measured at 17.9 screens with the evidence wall and the eight-question
   FAQ inline. Both now live on their own pages, and the home page carries a compact
   doorway to each. The footer row of a compact module pairs one line of explanation with
   the link, on one baseline. */
.evidence-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4) var(--sp-6);
  margin-top: var(--sp-6);
  padding-top: var(--sp-5);
  border-top: var(--hairline) solid var(--line);
}
.evidence-foot .body-text { margin: 0; max-width: 62ch; color: var(--ink-2); }
.evidence-grid--compact { grid-template-columns: repeat(3, 1fr); }
@media (max-width: 1024px) { .evidence-grid--compact { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 640px)  { .evidence-grid--compact { grid-template-columns: 1fr; } }
/* A measure limit so prose never runs the full container width. Measured before: two
   sections had body text sitting at 97% of a 1392px container, which is roughly 150
   characters a line and well past comfortable reading. */

/* ---- Measure caps ---------------------------------------------------------------------
   Six text blocks were measured at 97% of the 1392px container, which is about 150
   characters a line. These are the specific classes that did it; grids, tables and
   photographs keep the full container width. */
.icon-list-item {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--sp-3) var(--sp-4);
  align-items: start;
  max-width: 78ch;
}
.icon-list-item > .icon { grid-row: 1; margin-top: 2px; }
.cert-note {
  max-width: 74ch;
  margin-top: var(--sp-5);
  font-size: var(--fs-caption);
  color: var(--ink-3);
}
/* The milestone timeline on the about page runs each entry's prose the full container
   width (measured 1294px, 267-450 characters per entry). */
.step-text, .step-body { max-width: 76ch; }
/* Long-form prose that sits directly in a section body keeps a column too. */
.section > .container > .prose > p,
.section > .container > p {
  max-width: 74ch;
}
@media (min-width: 1100px) {
  /* On wide screens the capped text reads better with the grid it belongs to beside it
     than stranded on the left, so centre the column in the container. */
  .icon-list-item { max-width: 82ch; }
}

/* ---- Self-drawn diagrams on narrow screens --------------------------------------------
   These illustrations carry their own labels in SVG user space, sized for a 900-1200 unit
   canvas. Squeezed into a 350px phone column they scale to about 28%, which puts an 11px
   label at roughly 3px on screen.

   Scaling the text UP instead was the wrong correction: `font-size` in SVG is a user-space
   length, so a 26px floor is a fifth of a 150-unit-tall canvas and every label in the compact
   flow diagrams overprinted its neighbours. The diagrams are held near full size on small
   screens and the wrapper scrolls, which is what `.diagram-wrap` is for, and the font-size
   floor now applies only where a label is genuinely too small to survive the scale. */
/* 960 units keeps the label sizes these diagrams were drawn at. A canvas wider than that
   already scales its own type down proportionally, so it is allowed to fit the column. */
.diagram-wrap > svg { min-width: 960px; }
@media (max-width: 900px) {
  .diagram-wrap > svg { min-width: 960px; }
}
/* No font-size override for diagram labels.
   An earlier version raised them on small screens so a 1200-unit canvas would not shrink its
   labels to ~4px. That is handled correctly by holding the diagram at a readable width and
   letting it scroll, because overriding the size enlarges labels inside the diagram's OWN
   coordinate system: where a drawing had allowed an 11-unit label, a 26-unit one appeared
   instead — 2.4 times wider than the box it belonged to — and every adjacent pair of labels in
   that diagram collided. Measured before removal: 6 collisions on the industrial-control layer
   stack and 4 on the multilayer stackup, all of them from this rule. */

/* Five certificates in a four-column grid leave three empty cells, and because the grid's own
   background is the hairline colour those cells show as one grey band beside the last card.
   Four columns hold four; the fifth certificate spans the row, which is honest about there
   being five documents rather than hiding the gap. */
.cert-wall { grid-template-columns: repeat(4, 1fr); }
.cert-wall .cert-card:last-child:nth-child(4n + 1) { grid-column: 1 / -1; }
/* The hero breadcrumb sat at rgb(168,168,168) over a bright copper area of the photograph.
   It is navigation, not decoration, so it gets a solid backing scrim instead of relying on
   the picture behind it being dark. */
.sub-hero .breadcrumb a, .hero .breadcrumb a { color: var(--on-dark-2); }
.sub-hero .breadcrumb, .hero .breadcrumb { color: var(--on-dark-2); }
.hero .breadcrumb, .sub-hero .breadcrumb {
  background: rgba(0, 0, 0, 0.55);
  padding: 4px 10px;
  display: inline-flex;
  width: fit-content;
}

/* ---- Measure, second pass ---------------------------------------------------------------
   The first pass capped prose at 74-82ch to fix 150-character lines, and on a 1392px container
   that left whole sections hugging the left edge with 500-800px of nothing beside them.

   A measure cap is only correct when the row is otherwise occupied. These rules therefore cap
   at a readable width AND set a floor, so a single-column section fills its row while a
   two-column layout still gets a comfortable line. */
.lead,
.prose,
.prose--narrow,
.lead--narrow,
.cert-note,
.evidence-foot .body-text,
.section > .container > p,
.section > .container > .prose > p {
  max-width: none;
  width: min(100%, 74ch);
  min-width: min(100%, 820px);
}

/* In a two-column or narrow context the floor must not apply: the column is the constraint. */
.grid .lead, .grid .prose, .grid .cert-note,
.split .lead, .split .prose,
.hero--split .lead, .hero--type .lead,
[class*="grid-"] .lead, [class*="grid-"] .prose {
  min-width: 0;
  width: 100%;
}

/* The one-stop list is five long rows in a single column. Two columns fill the row, halve the
   section's height, and keep each row's text at a comfortable measure. */
.feature-list {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--sp-5) var(--sp-8);
}
@media (max-width: 900px) {
  .feature-list { grid-template-columns: 1fr; }
}
/* Inside the two-column list the column IS the measure — roughly 56 characters at this size —
   so a further cap would only leave a ribbon of empty space down each column. That is the same
   fault one level down, which is how the 62ch rule below came to leave 108px unused in each of
   the two columns. This override must come AFTER the base rule: same specificity, so order
   decides, and placing it first made it dead code. */
.feature-list .icon-list-item { max-width: none; width: 100%; min-width: 0; }
/* A short list in a full-width section has the same problem the long one had: three items in a
   single column leave three quarters of the row unused. Three items fill three columns. */
/* A short list in a full-width section has the same fault the long one had: three or four items
   in a single column leave most of the row unused. Three items fill three columns; four fill two
   (2x2), which is the grid-count convention this site already uses elsewhere. */
.icon-list:has(> .icon-list-item:nth-child(3):last-child),
@media (max-width: 900px) {
  .icon-list:has(> .icon-list-item:nth-child(3):last-child),
  }
.icon-list-item { max-width: 62ch; min-width: 0; }

/* ---- Vertical rhythm in the hero and the section head -----------------------------------
   The reset zeroes every margin, and neither container ever received a spacing stack, so these
   blocks were held apart by line-height alone: measured -6px between the hero eyebrow and the
   headline, -5px between the headline and the lead, and 0px between the actions and the badges.
   The scale below is the stylesheet's own; it just was not wired in. */
.hero-inner > * + *,
.hero-inner > .hero-actions,
.sub-hero-inner > * + * {
  margin-top: var(--sp-5);
}
.hero-inner > .h1 + *,
.hero-inner > h1 + *,
.sub-hero-inner > .h1 + *,
.sub-hero-inner > h1 + * {
  margin-top: var(--sp-6);
}
/* The eyebrow is a small caps mono label whose glyphs sit low in a 20.8px line box, and a
   display headline's glyphs sit high in an 82px one, so 16px of margin measured as a 10px
   visual gap. 24px brings the rendered gap to a normal 18px. */
.hero-inner > .eyebrow + .h1,
.hero-inner > .eyebrow + h1,
.sub-hero-inner > .eyebrow + .h1,
.sub-hero-inner > .eyebrow + h1 {
  margin-top: var(--sp-5);
}
/* Sections that put their eyebrow and heading straight into .container rather than a
   .section-head wrapper were getting no stack at all: measured 0px and -2px. */
.container > .eyebrow + .h2,
.container > .eyebrow + h2,
.container > .eyebrow + .h1,
.container > .eyebrow + h1 {
  margin-top: var(--sp-5);
}
.container > .h2 + *,
.container > .h1 + * { margin-top: var(--sp-6); }
.container > .h2 + .lead,
.container > .h1 + .lead { margin-top: var(--sp-6); }
.container > .section-marker + .eyebrow { margin-top: var(--sp-3); }
/* the buttons are a separate decision from the sentence above them */
.hero-inner > * + .hero-actions,
.sub-hero-inner > * + .hero-actions {
  margin-top: var(--sp-7);
}
.hero-inner > .hero-actions + *,
.sub-hero-inner > .hero-actions + * {
  margin-top: var(--sp-6);
}

/* The section head had 16px everywhere except the heading, which was pulled back to 12px. */
.section-head > * + * { margin-top: var(--sp-5); }
.section-head > .eyebrow + .h2,
.section-head > .eyebrow + h2 { margin-top: var(--sp-4); }
.section-head .h2 + *,
.section-head h2 + * { margin-top: var(--sp-6); }
.section-head > * + .lead { margin-top: var(--sp-5); }
.section-head { margin-bottom: var(--sp-9); }

/* ---- Card spec rows --------------------------------------------------------------------
   v19.4 rewrote hard constraint 42: long text must change form, not container. A paragraph
   inside a card is still a paragraph, so the content in these cards moves to labelled rows —
   the form this stylesheet was already using for card-meta, extended to carry several facets.

   The label carries the facet ("Certification", "RF", "Build"); the value carries the same
   figures, model numbers and certificate codes the prose did. Nothing is dropped; only the
   paragraph shape goes. */
.spec-rows {
  display: grid;
  grid-template-columns: max-content minmax(0, 1fr);
  column-gap: var(--sp-4);
  row-gap: var(--sp-2);
  font-size: var(--fs-small);
  line-height: 1.5;
  color: var(--ink-2);
}
.spec-rows dt {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  font-weight: var(--w-mono-eyebrow);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--ink-3);
  padding-top: 0.15em;
}
.spec-rows dd { margin: 0; min-width: 0; }
.card--dark .spec-rows { color: var(--on-dark-2); }
.card--dark .spec-rows dt { color: var(--on-dark-3); }
/* In a narrow card the label goes ABOVE its value. Side by side they compete for the same
   300px and the value wraps to a ribbon four words wide, which is worse than the prose it
   replaced. The stacked form gives the value the full column. */
@container (max-width: 420px) {
  .spec-rows { grid-template-columns: minmax(0, 1fr); row-gap: var(--sp-1); }
  .spec-rows dt { padding-top: var(--sp-2); }
}
@media (max-width: 520px) {
  .spec-rows { grid-template-columns: minmax(0, 1fr); row-gap: var(--sp-1); }
  .spec-rows dt { padding-top: var(--sp-2); }
}
/* Without container queries the same effect is reached from the card's own width: these cards
   sit in a 4-up grid, so a grid child narrower than 420px stacks. */
.card-body .spec-rows { max-width: 100%; }
.card:not(.card--wide) .card-body > .spec-rows { grid-template-columns: minmax(0, 1fr); row-gap: var(--sp-1); }
.card:not(.card--wide) .card-body > .spec-rows dt { padding-top: var(--sp-2); }

/* ---- Home page link row and section foot ------------------------------------------------
   The industries band is a transition between the capability material and the order steps, so it
   is centred and its links sit on one line. The chips wrap on a narrow viewport and centre there
   too, so the band never looks like a left-aligned list. */
.link-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2) var(--sp-2);
  margin-top: var(--sp-6);
}
/* Any centred band centres its furniture too. A sibling selector only catches the case where the
   row sits immediately after the head; these blocks put the chips and the button in separate
   wrappers, so the rule is scoped to the section instead. */
.section-head--center { text-align: center; }
.section-head--center ~ .link-row,
.section-head--center ~ .section-foot,
.section-head--center + .link-row,
.section-head--center + .section-foot { justify-content: center; }
.section-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-4);
  margin-top: var(--sp-6);
}
.section-head--center + .section-foot { justify-content: center; }
.link-row .chip { text-decoration: none; }

/* The process/quality block pairs its copy with the measurement photograph. On the site's own
   split layout the media can come second, so the text keeps the reading position. */
.split--media-right { direction: ltr; }
.split--media-right .split-media { order: 2; }
.split--media-right .split-body { order: 1; }
@media (max-width: 900px) {
  .split--media-right .split-media, .split--media-right .split-body { order: initial; }
}
.split--media-right .split-media img { width: 100%; height: auto; }

.spec-table, .compare-table {
  width: 100%;
  min-width: 640px;
  font-size: var(--fs-body-sm);
}
.spec-table caption, .compare-table caption {
  padding: var(--sp-5) var(--sp-5) var(--sp-2);
  font-family: var(--font-mono);
  font-size: var(--fs-eyebrow);
  font-weight: var(--w-mono-eyebrow);
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  color: var(--ink-3);
}
.spec-table th, .compare-table th {
  padding: var(--sp-3) var(--sp-5);
  background: var(--surface-alt);
  border-bottom: var(--hairline) solid var(--line);
  font-family: var(--font-mono);
  font-size: 0.75rem;
  font-weight: var(--w-mono-eyebrow);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--ink-3);
  text-align: left;
  white-space: nowrap;
  vertical-align: bottom;
}
.spec-table td, .compare-table td {
  padding: var(--sp-3) var(--sp-5);
  border-bottom: var(--hairline) solid var(--line-soft);
  color: var(--ink-2);
  vertical-align: top;
}
.spec-table tr:last-child td, .compare-table tr:last-child td { border-bottom: 0; }
.spec-table tbody tr:nth-child(even) td,
.compare-table tbody tr:nth-child(even) td { background: var(--surface-alt); }
.spec-table td:first-child, .compare-table td:first-child {
  color: var(--ink);
  font-weight: var(--w-strong);
  white-space: nowrap;
}
.spec-table td b, .compare-table td b {
  font-family: var(--font-mono);
  font-weight: var(--w-mono-number);
  color: var(--accent-text);
}
/* Comparison table: the parameter axis is the one thing that must never scroll
   out of view, so the header cell is pinned. */
.compare-table th:first-child {
  position: sticky;
  left: 0;
  z-index: 1;
  background: var(--surface-alt);
  border-right: var(--hairline) solid var(--line);
}
.compare-table td.is-yes { color: var(--accent-text); font-weight: var(--w-strong); }
.compare-table td.is-no { color: var(--ink-3); }

/* ---- Steps / numbered process ---- */
.steps {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--hairline);
  background: var(--line);
  border: var(--hairline) solid var(--line);
  counter-reset: step;
}
.step {
  position: relative;
  min-width: 0;
  padding: var(--sp-6) var(--sp-5) var(--sp-6);
  background: var(--surface);
}
.step-num {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  margin-bottom: var(--sp-4);
  border: var(--hairline) solid var(--accent);
  font-family: var(--font-mono);
  font-size: 0.8125rem;
  font-weight: var(--w-mono-number);
  color: var(--accent-text);
}
.step-body { min-width: 0; }
.step-body > * + * { margin-top: var(--sp-2); }
.step-title {
  font-family: var(--font-display);
  font-size: 1.0625rem;
  font-weight: var(--w-h3);
  line-height: var(--lh-tight);
  color: var(--ink);
}
.step-text { font-size: var(--fs-body-sm); color: var(--ink-2); }
.section--dark .steps { background: var(--line-dark); border-color: var(--line-dark); }
.section--dark .step { background: var(--dark); }
.section--dark .step-title { color: var(--on-dark); }
.section--dark .step-text { color: var(--on-dark-2); }
.section--dark .step-num { border-color: var(--accent-on-dark); color: var(--accent-on-dark); }
@media (max-width: 1024px) { .steps { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 640px) { .steps { grid-template-columns: 1fr; } }

/* ---- Process flow: nodes wired by hairline connectors ---- */
.process-flow {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  align-items: stretch;
  gap: 0;
}
/* Every column gets an identical content box: the same gutter on both sides of every node.
   Measured before: a square tile rendered 245x245 in column one against 220x220 in the other
   four, because only the first node had no left padding, which dropped its caption 25px. */
.flow-node {
  position: relative;
  min-width: 0;
  padding: var(--sp-5) calc(var(--sp-5) / 2);
}
.flow-node + .flow-node { border-left: var(--hairline) solid var(--line); }
.flow-node .icon { color: var(--accent); margin-bottom: var(--sp-3); }
.flow-node-title {
  font-family: var(--font-display);
  font-size: 1rem;
  font-weight: var(--w-h3);
  line-height: var(--lh-tight);
  color: var(--ink);
}
.flow-node-text { margin-top: var(--sp-2); font-size: var(--fs-small); color: var(--ink-2); }
.flow-connector {
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--ink-4);
  padding-inline: var(--sp-2);
}
.section--dark .flow-node + .flow-node { border-left-color: var(--line-dark); }
.section--dark .flow-node-title { color: var(--on-dark); }
.section--dark .flow-node-text { color: var(--on-dark-2); }
.section--dark .flow-node .icon { color: var(--accent-on-dark); }
@media (max-width: 1024px) {
  .process-flow { grid-template-columns: repeat(2, 1fr); }
  .flow-node { padding-left: 0; }
  .flow-node + .flow-node { border-left: 0; }
}
@media (max-width: 640px) {
  .process-flow { grid-template-columns: 1fr; }
  .flow-node { border-top: var(--hairline) solid var(--line); padding-inline: 0; }
  .flow-node:first-child { border-top: 0; }
  .section--dark .flow-node { border-top-color: var(--line-dark); }
}

/* ---- Certification wall (portrait scans, 3/4, top-anchored) ---- */
.cert-wall {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--hairline);
  background: var(--line);
  border: var(--hairline) solid var(--line);
}
.cert-card {
  display: flex;
  flex-direction: column;
  min-width: 0;
  padding: var(--sp-5);
  background: var(--surface);
}
.cert-card img {
  width: 100%;
  aspect-ratio: 3/4;
  object-fit: cover;
  object-position: top;
  font-weight: var(--w-monop-2); font-size: var(--fs-caption); color: var(--ink-3); }
@media (max-width: 1024px) { .cert-wall { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 640px) { .cert-wall { grid-template-columns: 1fr; } }

/* ---- Badges / pills / chips ---- */
.badge-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
}
.badge {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  min-height: 32px;
  padding: var(--sp-1) var(--sp-3);
  border: var(--hairline) solid var(--line);
  background: var(--surface);
  font-family: var(--font-mono);
  font-size: 0.75rem;
  font-weight: var(--w-mono-eyebrow);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--ink-2);
  white-space: nowrap;
}
.badge .icon { width: 14px; height: 14px; color: var(--accent); }
.badge--accent { border-color: var(--accent); color: var(--accent-text); }
.badge--dark { background: var(--dark); border-color: var(--line-dark); color: var(--on-dark-2); }
.badge--dark .icon { color: var(--accent-on-dark); }
.pill {
  display: inline-flex;
  align-items: center;
  min-height: 32px;
  padding: var(--sp-1) var(--sp-4);
  border: var(--hairline) solid var(--line);
  background: var(--surface-alt);
  font-size: var(--fs-caption);
  color: var(--ink-2);
  white-space: nowrap;
}
.pill--accent { border-color: var(--accent); color: var(--accent-text); background: var(--accent-wash); }
.chip {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  min-height: 44px;
  padding: 0 var(--sp-4);
  border: var(--hairline) solid var(--line);
  background: var(--surface);
  font-family: var(--font-mono);
  font-size: 0.75rem;
  font-weight: var(--w-mono-eyebrow);
  letter-spacing: 0.04em;
  color: var(--ink-2);
  white-space: nowrap;
}
.chip:hover, .chip[aria-pressed="true"] { border-color: var(--accent); color: var(--accent-text); }
.chip .icon { width: 14px; height: 14px; }

/* ---- Icon list / feature list ---- */
/* Columns are chosen by the width each item actually gets, not by how many items there are.
   Counting gave three 416px columns on /projects/ and four 298px ones on the home page, and a
   298px column wraps a one-sentence item onto ten lines: taller than the single column it
   replaced. An item reads well at roughly 480-660px, so the grid fits as many columns of that
   width as the row allows. */
.icon-list {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 480px), 1fr));
  gap: var(--sp-5) var(--sp-8);
}
.icon-list .icon-list-item { max-width: none; width: 100%; }

/* A short list beside a picture has its column width set by the split, so it stays single. */
.split-body .icon-list { grid-template-columns: 1fr; }
.icon-list--2 { grid-template-columns: repeat(2, 1fr); }
.icon-list-item {
  /* Grid, not flex. These list items are written as a bare text run with inline <b> inside —
     "Stackup: 6 layers, High-Tg <b>≥170 °C</b>, 2 oz inner copper" — and flex turns every
     separate run into its own flex item laid side by side. One sentence came out as three or
     four columns with uneven gaps: "Outcome: 3D AOI on | 100 % | of boards, X-ray on ...". Grid
     with a fixed icon column puts the whole sentence in one cell where it flows normally. */
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: start;
  column-gap: var(--sp-3);
  row-gap: var(--sp-2);
  min-width: 0;
  font-size: var(--fs-body-sm);
  line-height: var(--lh-body);
  color: var(--ink-2);
}
.icon-list-item .icon { color: var(--accent); margin-top: 0.2em; }
.icon-list-item b, .icon-list-item strong { color: var(--ink); }
.feature-list { display: grid; gap: var(--sp-3); border-top: var(--hairline) solid var(--line); padding-top: var(--sp-4); }
.feature-list .icon-list-item { border-bottom: var(--hairline) solid var(--line-soft); padding-bottom: var(--sp-3); }
.feature-list .icon-list-item:last-child { border-bottom: 0; padding-bottom: 0; }
.section--dark .icon-list-item, .section--dark .feature-list .icon-list-item {
  color: var(--on-dark-2);
  border-bottom-color: var(--line-dark);
}
.section--dark .icon-list-item .icon { color: var(--accent-on-dark); }
.section--dark .feature-list { border-top-color: var(--line-dark); }
@media (max-width: 640px) { .icon-list--2 { grid-template-columns: 1fr; } }

/* ---- Decision grid: 8 objection-handling cards ---- */
.decision-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--hairline);
  background: var(--line);
  border: var(--hairline) solid var(--line);
}
.decision-card {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  min-width: 0;
  padding: var(--sp-6) var(--sp-5);
  background: var(--surface);
}
.decision-card .icon { color: var(--accent); }
.decision-card h3, .decision-card .decision-title {
  font-family: var(--font-display);
  font-size: 1.0625rem;
  font-weight: var(--w-h3);
  line-height: var(--lh-tight);
  color: var(--ink);
}
.decision-card p { font-size: var(--fs-small); color: var(--ink-2); }
@media (max-width: 1024px) { .decision-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 640px) { .decision-grid { grid-template-columns: 1fr; } }

/* ---- FAQ accordion ---- */
.faq-list { border-top: var(--hairline) solid var(--line); border-bottom: var(--hairline) solid var(--line); }
.faq-item { border-bottom: var(--hairline) solid var(--line); }
.faq-item:last-child { border-bottom: 0; }
.faq-q {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-5);
  width: 100%;
  min-height: 56px;
  padding: var(--sp-4) 0;
  text-align: left;
  font-family: var(--font-display);
  font-size: 1.0625rem;
  font-weight: var(--w-h3);
  line-height: var(--lh-tight);
  color: var(--ink);
}
.faq-q:hover { color: var(--accent-text); }
.faq-q .icon { color: var(--accent); flex: none; transition: transform var(--dur) var(--ease); }
.faq-q[aria-expanded="true"] .icon { transform: rotate(180deg); }
.faq-a {
  padding: 0 0 var(--sp-5);
  max-width: 78ch;
  font-size: var(--fs-body-sm);
  line-height: var(--lh-body);
  color: var(--ink-2);
}
/* Hiding is keyed off `.js` (added by assets/js-flag.js), never off the markup
   alone: with JavaScript unavailable every answer stays readable. */
.js .faq-a[hidden] { display: none; }
.faq-a > * + * { margin-top: var(--sp-3); }
@media (max-width: 640px) { .faq-q { font-size: 1rem; } }
.section--dark .faq-list, .section--dark .faq-item { border-color: var(--line-dark); }
.section--dark .faq-q { color: var(--on-dark); }
.section--dark .faq-a { color: var(--on-dark-2); }

/* ---- Tabs ---- */
.tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 0;
  border-bottom: var(--hairline) solid var(--line);
}
.tab {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  min-height: 48px;
  padding: 0 var(--sp-5);
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  font-family: var(--font-mono);
  font-size: 0.8125rem;
  font-weight: var(--w-mono-eyebrow);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--ink-3);
  white-space: nowrap;
}
.tab:hover { color: var(--ink); }
.tab[aria-selected="true"] { color: var(--ink); border-bottom-color: var(--accent); }
.tab-panel { padding-top: var(--sp-6); }
.js .tab-panel[hidden] { display: none; }
.tab-panel > * + * { margin-top: var(--sp-5); }
@media (max-width: 640px) {
  .tabs { flex-wrap: nowrap; overflow-x: auto; }
  .tab { padding-inline: var(--sp-4); }
}

/* ---- Gallery: fixed column counts, 3-card multiples only ---- */
.gallery {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--hairline);
  background: var(--line);
  border: var(--hairline) solid var(--line);
}
.gallery-item {
  position: relative;
  min-width: 0;
  overflow: hidden;
  background: var(--surface-sunken);
  aspect-ratio: 3/2;
}
.gallery-item img { width: 100%; height: 100%; object-fit: cover; }
.gallery-caption {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  padding: var(--sp-5) var(--sp-4) var(--sp-3);
  background: linear-gradient(180deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.74) 100%);
  color: var(--on-dark);
  font-size: var(--fs-caption);
}
@media (max-width: 1024px) { .gallery { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 640px) { .gallery { grid-template-columns: 1fr; } }

/* ---- Quote band ---- */
.quote-band {
  padding-block: var(--section-y-sm);
  background: var(--surface-alt);
  border-block: var(--hairline) solid var(--line);
}
.quote-band blockquote {
  max-width: 68ch;
  margin-inline: auto;
  text-align: center;
  font-family: var(--font-display);
  font-size: var(--fs-h3);
  font-weight: var(--w-h1);
  line-height: 1.45;
  letter-spacing: var(--ls-h1);
  color: var(--ink);
}
.quote-band cite {
  display: block;
  margin-top: var(--sp-5);
  font-family: var(--font-mono);
  font-size: 0.8125rem;
  font-style: normal;
  font-weight: var(--w-mono-eyebrow);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--ink-3);
}
.quote-band--dark { background: var(--dark); border-color: var(--line-dark); }
.quote-band--dark blockquote { color: var(--on-dark); }
.quote-band--dark cite { color: var(--on-dark-3); }

/* ---- Download band ---- */
.download-band {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-5);
  padding: var(--sp-5) var(--sp-6);
  background: var(--surface);
  border: var(--hairline) solid var(--line);
  border-left: 3px solid var(--accent);
}
.download-band-text { min-width: 0; font-size: var(--fs-body-sm); color: var(--ink-2); }
.download-band-text b, .download-band-text strong { color: var(--ink); font-weight: var(--w-strong); }
.download-band .icon { color: var(--accent); }

/* ---- CTA band: the single conversion action, site-wide ---- */
.cta-band {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  background: var(--dark);
  color: var(--on-dark);
}
.cta-band .hero-bg { opacity: 0.34; }
.cta-inner {
  position: relative;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-6);
  padding-block: var(--sp-9);
}
.cta-inner > * { min-width: 0; }
.cta-inner .h2, .cta-inner h2 { color: var(--on-dark); max-width: 24ch; }
.cta-inner .lead { color: var(--on-dark-2); max-width: 52ch; }
.cta-inner .eyebrow { color: var(--accent-on-dark); }
.cta-actions { display: flex; flex-wrap: wrap; gap: var(--sp-3); }
@media (max-width: 768px) {
  .cta-inner { flex-direction: column; align-items: stretch; padding-block: var(--sp-8); }
  .cta-actions .btn { flex: 1 1 100%; }
}

/* ===========================================================================
   9. CONTROLS — buttons + form fields
   =========================================================================== */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  min-height: 48px;
  padding: var(--sp-3) var(--sp-6);
  border: var(--hairline) solid transparent;
  border-radius: 0;
  font-family: var(--font-body);
  font-size: var(--fs-body-sm);
  font-weight: var(--w-strong);
  letter-spacing: 0.02em;
  text-align: center;
  cursor: pointer;
  transition: background-color var(--dur) var(--ease),
              color var(--dur) var(--ease),
              border-color var(--dur) var(--ease);
}
/* accent role 1 of 3: primary buttons */
.btn--primary {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);   /* ink on the orange fill = 7.37:1 */
}
.btn--primary:hover { background: var(--accent-deep); border-color: var(--accent-deep); color: var(--on-accent); }
.btn--ghost {
  background: transparent;
  border-color: var(--line-strong);
  color: var(--ink);
}
.btn--ghost:hover { border-color: var(--accent); color: var(--accent-text); }
/* on dark or over photography */
.section--dark .btn--ghost, .hero--full .btn--ghost, .hero--type .btn--ghost,
.sub-hero .btn--ghost, .cta-band .btn--ghost, .card--dark .btn--ghost {
  border-color: rgba(255, 255, 255, 0.55);
  color: var(--on-dark);
}
.section--dark .btn--ghost:hover, .hero--full .btn--ghost:hover, .hero--type .btn--ghost:hover,
.sub-hero .btn--ghost:hover, .cta-band .btn--ghost:hover, .card--dark .btn--ghost:hover {
  border-color: var(--on-dark);
  color: var(--on-dark);
  background: rgba(255, 255, 255, 0.08);
}
.btn--dark { background: var(--ink); border-color: var(--ink); color: var(--on-dark); }
.btn--dark:hover { background: var(--dark-2); border-color: var(--dark-2); color: var(--on-dark); }
.btn--block { display: flex; width: 100%; }
.btn--sm { min-height: 44px; padding: var(--sp-2) var(--sp-5); font-size: var(--fs-caption); }
.btn[disabled], .btn[aria-disabled="true"] { opacity: 0.55; pointer-events: none; }

/* ---- Forms ---- */
.form-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--sp-4);
}
.form-grid > * { min-width: 0; }
.form-grid--1 { grid-template-columns: 1fr; }
.form-grid .field--full { grid-column: 1 / -1; }
@media (max-width: 640px) { .form-grid { grid-template-columns: 1fr; } }

.field { display: flex; flex-direction: column; gap: var(--sp-2); min-width: 0; }
.field-label {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  font-weight: var(--w-mono-eyebrow);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--ink-3);
}
.field-input, .field-select, .field-textarea {
  width: 100%;
  min-height: 44px;
  padding: var(--sp-3) var(--sp-3);
  border: var(--hairline) solid var(--line);
  border-radius: 0;
  background-color: var(--surface);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: var(--fs-body-sm);
  color-scheme: light;
  transition: border-color var(--dur) var(--ease);
}
.field-select {
  appearance: none;
  padding-right: var(--sp-8);
  background-image: linear-gradient(45deg, transparent 50%, var(--ink-2) 50%),
                    linear-gradient(135deg, var(--ink-2) 50%, transparent 50%);
  background-position: calc(100% - 20px) calc(50% + 2px), calc(100% - 14px) calc(50% + 2px);
  background-size: 6px 6px, 6px 6px;
  background-repeat: no-repeat;
}
.field-textarea { min-height: 104px; resize: vertical; line-height: var(--lh-body); }
.field-input:hover, .field-select:hover, .field-textarea:hover { border-color: var(--ink-4); }
.field-input:focus, .field-select:focus, .field-textarea:focus {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
  border-color: var(--accent);
}
.field-input::placeholder, .field-textarea::placeholder { color: var(--ink-4); }
.field-file { padding: var(--sp-2) var(--sp-3); cursor: pointer; }
.field-file::file-selector-button {
  margin-right: var(--sp-3);
  padding: var(--sp-2) var(--sp-4);
  border: var(--hairline) solid var(--line);
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-mono);
  font-size: 0.875rem;
  font-weight: var(--w-mono-eyebrow);
  text-transform: uppercase;
  letter-spacing: var(--ls-label);
  cursor: pointer;
}
.field-file::file-selector-button:hover { background: var(--ink); color: var(--on-dark); }

/* Custom file field: the native control renders its button and "no file chosen"
   text in the BROWSER's UI language, so a Chinese-locale visitor saw 选择文件 /
   未选择任何文件 on an English page. The real input is stretched and made
   invisible over a styled row; assets/file-field.js writes the chosen names. */
.file-field {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  min-height: 48px;
  padding: var(--sp-2) var(--sp-3);
  border: var(--hairline) solid var(--line);
  background: var(--surface);
  overflow: hidden;
  transition: border-color var(--dur) var(--ease);
}
.file-field:hover { border-color: var(--ink-4); }
.file-field:focus-within {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
  border-color: var(--accent);
}
.file-field-input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  cursor: pointer;
}
.file-field-btn {
  flex: 0 0 auto;
  padding: var(--sp-2) var(--sp-4);
  border: var(--hairline) solid var(--line);
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-mono);
  font-size: 0.875rem;
  font-weight: var(--w-mono-eyebrow);
  text-transform: uppercase;
  letter-spacing: var(--ls-label);
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.file-field:hover .file-field-btn { background: var(--ink); border-color: var(--ink); color: var(--on-dark); }
.file-field-name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: var(--fs-body-sm);
  color: var(--ink-3);
}
.file-field-name.is-set { color: var(--ink); }
.section--dark .file-field { background: var(--dark-2); border-color: var(--line-dark); }
.section--dark .file-field-btn { background: var(--dark); border-color: var(--line-dark); color: var(--on-dark); }
.section--dark .file-field-name { color: var(--on-dark-3); }
.section--dark .file-field-name.is-set { color: var(--on-dark); }
.field-hint { font-size: 0.75rem; color: var(--ink-3); }
.form-note { font-size: var(--fs-caption); line-height: var(--lh-tight); color: var(--ink-3); }
.form-note a { color: var(--accent-text); border-bottom: var(--hairline) solid currentColor; }
.section--dark .field-hint, .section--dark .form-note { color: var(--on-dark-3); }
.section--dark .field-input, .section--dark .field-select, .section--dark .field-textarea {
  background: var(--dark-2);
  border-color: var(--line-dark);
  color: var(--on-dark);
}

/* ===========================================================================
   10. MODAL — the single conversion action
   =========================================================================== */
.modal-overlay {
  position: fixed;
  inset: 0;
  z-index: var(--z-modal);
  display: none;
  align-items: center;
  justify-content: center;
  padding: var(--sp-5);
  background: rgba(22, 22, 22, 0.72);
}
.modal-overlay.is-open { display: flex; }
.modal-dialog {
  position: relative;
  display: grid;
  grid-template-columns: 1fr 1fr;
  /* `minmax(0, 1fr)` is what keeps the dialog scrollable: with a plain `1fr` row the
     grid item's automatic minimum size lets tall content push the dialog past
     max-height, which put the submit button below the fold with no way to reach it. */
  grid-template-rows: minmax(0, 1fr);
  width: 100%;
  max-width: 760px;
  max-height: 92vh;
  overflow: hidden;
  background: var(--surface);
  border: var(--hairline) solid var(--line);
}
/* The form column scrolls on its own; the page behind it never does. */
.modal-form-panel { overflow-y: auto; overscroll-behavior: contain; }
/* Submit stays reachable at the bottom of the column even when the fields scroll. */
.modal-form-panel .form-actions {
  position: sticky;
  bottom: 0;
  padding-top: var(--sp-3);
  background: var(--surface);
}
.modal-close {
  position: absolute;
  top: 0;
  right: 0;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  background: var(--surface);
  border-left: var(--hairline) solid var(--line);
  border-bottom: var(--hairline) solid var(--line);
  color: var(--ink);
}
.modal-close:hover { background: var(--ink); color: var(--on-dark); }
.modal-close .icon { width: var(--icon-md); height: var(--icon-md); }

.modal-image-panel {
  position: relative;
  min-width: 0;
  min-height: 0;
  overflow: hidden;
  background: var(--dark);
}
.modal-image-panel img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.modal-image-overlay {
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgba(0, 0, 0, 0.10) 30%, rgba(0, 0, 0, 0.80) 100%);
}
.modal-image-text {
  position: absolute;
  left: var(--sp-6);
  right: var(--sp-6);
  bottom: var(--sp-6);
  color: var(--on-dark);
}
.modal-image-text .mi-label {
  display: block;
  font-family: var(--font-mono);
  font-size: var(--fs-eyebrow);
  font-weight: var(--w-mono-eyebrow);
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  color: var(--accent-on-dark);
}
.modal-image-text .mi-value {
  display: block;
  margin-top: var(--sp-2);
  font-family: var(--font-display);
  font-size: 1.125rem;
  font-weight: var(--w-h3);
  line-height: var(--lh-tight);
  color: var(--on-dark);
}

.modal-form-panel {
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
  min-width: 0;
  padding: var(--sp-7) var(--sp-6);
  overflow-y: auto;
}
.modal-form-panel h2, .modal-form-panel h3 {
  font-family: var(--font-display);
  font-size: 1.375rem;
  font-weight: var(--w-h3);
  line-height: var(--lh-h3);
  color: var(--ink);
}
.mf-subtitle { font-size: var(--fs-caption); line-height: var(--lh-tight); color: var(--ink-3); }
.modal-form-panel form { display: flex; flex-direction: column; gap: var(--sp-4); }
.modal-form-panel .form-grid { grid-template-columns: 1fr; gap: var(--sp-4); }

@media (max-width: 640px) {
  .modal-overlay { padding: 0; align-items: stretch; }
  .modal-dialog {
    grid-template-columns: 1fr;
    grid-template-rows: auto minmax(0, 1fr);
    max-width: none;
    max-height: 100vh;
    height: 100vh;
    border: 0;
    overflow: hidden;
  }
  .modal-image-panel { min-height: 160px; max-height: 200px; flex: 0 0 auto; }
  .modal-image-text { left: var(--sp-4); right: var(--sp-4); bottom: var(--sp-4); }
  .modal-form-panel { padding: var(--sp-6) var(--sp-4) var(--sp-6); overflow-y: auto; }
  .modal-close { border-left: 0; }
}

/* ===========================================================================
   11. SITE FOOTER
   =========================================================================== */
.site-footer {
  background: var(--dark);
  color: var(--on-dark-2);
  /* the same 2px brand rule that closes the header */
  border-top: 2px solid;
  border-image: var(--brand-gradient) 1;
  padding-block: var(--sp-10) var(--sp-6);
}
.footer-grid {
  display: grid;
  grid-template-columns: 1.4fr repeat(5, 1fr);
  gap: var(--sp-7) var(--sp-6);
  padding-bottom: var(--sp-8);
  border-bottom: var(--hairline) solid var(--line-dark);
}
.footer-grid > * { min-width: 0; }
.footer-brand { min-width: 0; }
.footer-brand .nav-logo { color: var(--on-dark); }
.footer-brand .nav-logo:hover { color: var(--on-dark); }
.footer-brand .nav-logo .icon { color: var(--accent-on-dark); }
.footer-brand .nav-logo-sub { color: var(--on-dark-3); }
.footer-about {
  margin-top: var(--sp-4);
  max-width: 34ch;
  font-size: var(--fs-small);
  line-height: var(--lh-body);
  color: var(--on-dark-2);
}
.footer-col { min-width: 0; }
.footer-title {
  margin-bottom: var(--sp-4);
  font-family: var(--font-mono);
  font-size: 0.75rem;
  font-weight: var(--w-mono-eyebrow);
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  color: var(--on-dark);
}
.footer-col ul { display: grid; gap: 0; }
.footer-link {
  display: block;
  padding: var(--sp-2) 0;
  font-size: var(--fs-small);
  line-height: var(--lh-tight);
  color: var(--on-dark-2);
}
.footer-link:hover { color: var(--accent-on-dark); }
.footer-address {
  font-style: normal;
  font-size: var(--fs-small);
  line-height: var(--lh-body);
  color: var(--on-dark-2);   /* default: the footer, which is a dark panel */
}
/* The same block is reused on the contact page inside a white card, where the inherited
   light-on-dark colour measured rgb(220,220,220) on white — 1.31:1, effectively invisible.
   These two rules scope the dark colour to dark contexts and give every light context the
   readable grey and the accent link colour, without any chance of the two crossing over:
   `.site-footer` is never inside a `.section`, and a `.section--dark` never contains a
   light `.card`. */
.section:not(.section--dark) .footer-address,
.card:not(.card--dark) .footer-address { color: var(--ink-2); }
.section:not(.section--dark) .footer-address a,
.card:not(.card--dark) .footer-address a {
  color: var(--accent-text);
  border-bottom-color: var(--line);
}
.section:not(.section--dark) .footer-address a:hover,
.card:not(.card--dark) .footer-address a:hover {
  color: var(--accent-text);
  border-bottom-color: var(--accent-text);
}
.footer-address a { color: var(--on-dark); border-bottom: var(--hairline) solid var(--line-dark); }
.footer-address a:hover { color: var(--accent-on-dark); border-bottom-color: var(--accent-on-dark); }
.site-footer .footer-address,
.section--dark .footer-address,
.cta-band .footer-address { color: var(--on-dark-2); }
.site-footer .footer-address a,
.section--dark .footer-address a,
.cta-band .footer-address a { color: var(--on-dark); border-bottom-color: var(--line-dark); }
.footer-legal {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
  padding-top: var(--sp-6);
  font-size: var(--fs-caption);
  color: var(--on-dark-3);
}
.footer-legal a { color: var(--on-dark-3); }
.footer-legal a:hover { color: var(--on-dark); }
.footer-badges {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
  padding-block: var(--sp-6);
  border-bottom: var(--hairline) solid var(--line-dark);
}
.footer-badges .badge {
  background: transparent;
  border-color: var(--line-dark);
  color: var(--on-dark-2);
}
.footer-badges .badge .icon { color: var(--accent-on-dark); }

@media (max-width: 1024px) {
  .footer-grid { grid-template-columns: repeat(2, 1fr); }
  .footer-brand { grid-column: 1 / -1; }
}
@media (max-width: 640px) {
  .footer-grid { grid-template-columns: 1fr; gap: var(--sp-6); }
  .site-footer { padding-block: var(--sp-8) var(--sp-6); }
}

/* ===========================================================================
   12. UTILITIES
   =========================================================================== */
.visually-hidden {
  position: absolute !important;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}
.skip-link {
  position: absolute;
  left: var(--sp-4);
  top: -100px;
  z-index: calc(var(--z-modal) + 10);
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: var(--sp-3) var(--sp-5);
  background: var(--ink);
  color: var(--on-dark);
  font-size: var(--fs-body-sm);
  font-weight: var(--w-strong);
  transition: top var(--dur) var(--ease);
}
.skip-link:focus { top: var(--sp-4); color: var(--on-dark); }

.sticky-cta {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  z-index: var(--z-sticky);
  display: none;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
  padding: var(--sp-3) var(--sp-4) calc(var(--sp-3) + env(safe-area-inset-bottom, 0px));
  background: var(--surface);
  border-top: var(--hairline) solid var(--line);
}
.sticky-cta-text {
  min-width: 0;
  font-family: var(--font-mono);
  font-size: 0.75rem;
  font-weight: var(--w-mono-eyebrow);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--ink-2);
}
.sticky-cta .btn { flex: none; }
@media (max-width: 768px) {
  .sticky-cta { display: flex; }
  body.has-sticky-cta { padding-bottom: 76px; }
}

/* ---- Reveal (progressive enhancement: hidden state is added BY JS only) ----
   .reveal is VISIBLE by default so content survives a JS failure. Only
   assets/reveal.js adds .reveal--armed, which is the only hidden state. */
.reveal { opacity: 1; transform: none; --reveal-shift: 12px; }
.reveal.reveal--armed {
  opacity: 0;
  transform: translateY(var(--reveal-shift, 12px));
  transition: opacity var(--dur-slow) var(--ease), transform var(--dur-slow) var(--ease);
  will-change: opacity, transform;
}
.reveal.reveal--armed.is-visible { opacity: 1; transform: none; will-change: auto; }

.is-hidden { display: none !important; }
.text-center { text-align: center; }
.text-center .lead { margin-inline: auto; }
.mt-0 { margin-top: 0 !important; }
.mb-0 { margin-bottom: 0 !important; }
.grid-child-narrow { min-width: 0; }

/* ===========================================================================
   13. RESPONSIVE BREAKPOINTS — 1440 / 1024 / 768 / 640 / 375
   =========================================================================== */
@media (max-width: 1440px) {
  /* The container is 1312px; add the two side gutters and it needs 1376px of
     viewport. Below that the max-width must track the viewport or the gutters get
     pushed outside it and the page scrolls sideways. Literal px here on purpose:
     a var() reference would resolve --pad-inline once, at :root, and never update. */
  :root {
    --container: calc(100% - 48px);
    --pad-inline: 24px;
  }
}
@media (max-width: 1024px) {
  :root {
    --section-y: 80px;
    --section-y-sm: 56px;
    --pad-inline: 24px;
  }
}
@media (max-width: 768px) {
  :root {
    --section-y: 64px;      /* >= 64px desktop floor honoured at tablet+ */
    --section-y-sm: 48px;
    --gap: var(--sp-4);
    --container: calc(100% - 48px);
  }
  .section { padding-block: var(--section-y); }
  .section--tight { padding-block: var(--section-y-sm); }
  .split--reverse .split-media { order: 0; }
  .table-wrap { border-inline: 0; }
  .spec-table, .compare-table { min-width: 560px; }
  .cta-inner { flex-direction: column; align-items: flex-start; }
  .download-band { flex-direction: column; align-items: flex-start; }
  .download-band .btn { width: 100%; }
}
@media (max-width: 640px) {
  :root {
    --section-y: 48px;      /* >= 48px mobile floor */
    --section-y-sm: 40px;
    --pad-inline: var(--pad-inline-sm);
    --container: calc(100% - 40px);   /* 2 x --pad-inline-sm */
    --header-h: 64px;
    --fs-body: 0.9375rem;   /* 15px */
    --fs-body-sm: 0.9375rem;
    --fs-small: 0.875rem;   /* 14px — never below this for body copy */
    /* Raise the caption token on phones too: it is used for readable UI text
       (buttons, table headers, eyebrows, footer column headings, captions), and
       the site's rule is that interactive and readable text stays >= 14px on
       mobile. Small print that is genuinely decorative (the nav logo subtitle)
       keeps its own size. */
    --fs-caption: 0.875rem; /* 14px */
  }
  .card-body { padding: var(--sp-5); }
  .stat { padding: var(--sp-5) var(--sp-4); }
  .step { padding: var(--sp-5) var(--sp-4); }
  .decision-card, .cert-card { padding: var(--sp-5) var(--sp-4); }
  .grid--ruled, .steps, .cert-wall, .decision-grid, .gallery, .stat-band {
    background: var(--surface);
    border: 0;
    gap: 0;
  }
  .grid--ruled > *, .step, .cert-card, .decision-card, .gallery-item, .stat {
    border-bottom: var(--hairline) solid var(--line);
  }
  .grid--ruled > *:last-child, .step:last-child, .cert-card:last-child,
  .decision-card:last-child, .gallery-item:last-child, .stat:last-child { border-bottom: 0; }
  .grid--ruled > * { border-left: 0; border-right: 0; }
  .badge, .pill, .chip { white-space: normal; }
  .form-note { font-size: 0.875rem; }
  /* Explicit floors for text that is read or acted on rather than decorative. */
  .btn, .step-num, .eyebrow, .footer-title, .stat-label, .cert-desc, .cert-note,
  .card-meta, .spec-table th, .spec-table td, .compare-table th, .compare-table td,
  .flow-node-label, .badge, .pill, .chip, .submenu-hook, figcaption,
  .card-link, .sticky-cta-text, .nav-logo-sub { font-size: 0.875rem; }
  /* The inquiry modal is inlined on every page, so its 12px field labels were failing the
     mobile text floor on 26 of 36 routes at once - a site-wide defect that a home-page-only
     acceptance run cannot see. Same floor as everything else. */
  .field-label, .field-hint, .field-error, .field-file,
  .mi-label, .modal-image-text, .breadcrumb, .breadcrumb a { font-size: 0.875rem; }
  /* Self-drawn SVG diagrams (process rails, stackup cross-sections, parameter
     matrices) label themselves with SVG <text> whose size is fixed in the
     900-1200px diagram space. On a phone the diagram is scaled to roughly a third,
     so those labels land near 4px — legible in the DOM, unreadable on screen.
     Raising them here keeps the diagram typography readable at mobile width. */
}
@media (max-width: 375px) {
  :root {
    --pad-inline: 16px;
    --container: calc(100% - 32px);   /* 2 x 16px */
    --sp-6: 24px;
    --sp-7: 32px;
    --sp-8: 40px;
  }
  .h1, h1.h1 { font-size: 1.875rem; }   /* 30px floor on small phones */
  .h2, h2.h2 { font-size: 1.5rem; }
  .stat-value { font-size: 1.625rem; }
  .nav-cta .btn { font-size: var(--fs-caption); }
}

/* ===========================================================================
   14. REDUCED MOTION
   =========================================================================== */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
  /* Reveal must never hide content for reduced-motion users */
  .reveal, .reveal.reveal--armed, .reveal.reveal--armed.is-visible {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }
  .card-link:hover .icon { transform: none; }
  .faq-q .icon { transition: none; }
}

/* ===========================================================================
   15. FORCED COLORS / PRINT-SAFE HOOKS (high-contrast OS modes)
   =========================================================================== */
@media (forced-colors: active) {
  .btn, .card, .field-input, .field-select, .field-textarea, .modal-dialog, .badge, .chip {
    border: 1px solid CanvasText;
  }
  .icon { stroke: currentColor; }
}

/* Spacing utilities used where an inline style would violate the CSP. */
.mt-6 { margin-top: var(--sp-6); }
