/* ==========================================================================
   Components — Engineering Index, dark-only.
   Built to site/COMPONENT-SPECS.md. Every value here references a LAYER 3
   component token in tokens.css — never a primitive. Only the components
   the current home page uses are implemented.

   GLOW BUDGET (hard constraint, see spec sheet): exactly two glowing
   elements on the page — the h1, and the primary btn on hover. Plate cards
   carry real product photography now, not drawn linework, and no longer
   glow (a drop-shadow on a photo's alpha edge reads as a halo). Nothing
   else casts a shadow or bloom.
   ========================================================================== */

/* Shared layout shell — full-bleed header/section wrapper, centred to the
   grid max-width. Distinct from .container (base.css) only in name; kept
   separate so section-grid math below has an unambiguous parent to size
   against. */
.section-inner {
  max-width: var(--section-max-width);
  margin-inline: auto;
  padding-inline: var(--space-5);
}

@media (max-width: 640px) {
  .section-inner {
    padding-inline: var(--space-4);
  }
}

/* ===== 1. site-header ===== */
.site-header {
  background: var(--header-bg);
  border-bottom: 1px solid var(--header-border);
  color: var(--header-fg);
  position: sticky;
  top: 0;
  z-index: 150;
}

.header-inner {
  height: var(--header-height);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-5);
}

.brand {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  text-decoration: none;
  color: var(--header-fg);
  font-family: var(--type-heading-font);
}

.brand-word {
  font-weight: 700;
  font-size: var(--size-sm);
  letter-spacing: 0.02em;
  white-space: nowrap;
}

.primary-nav {
  display: flex;
  align-items: center;
  gap: var(--space-5);
}

.primary-nav ul {
  display: flex;
  align-items: center;
  gap: var(--space-6);
}

/* 44px minimum tap target (COMPONENT-SPECS.md §2). .btn--compact's padding
   + inherited line-height rendered ~37px tall. Scoped to header-cta, not
   btn--compact, because btn--compact is not used anywhere else in the site
   — it exists only as this CTA — so widening the shared class would be
   speculative. Flex-centering follows the same pattern already used for
   .nav-link below, so the label stays vertically centred at any font
   metrics rather than relying on a line-height hack. */
.header-cta {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
}

/* ===== 2. nav-link ===== */
.nav-link {
  font-family: var(--type-label-font);
  font-size: var(--type-label-size);
  letter-spacing: var(--type-label-tracking);
  text-transform: uppercase;
  text-decoration: none;
  color: var(--nav-link-fg);
}

.nav-link:hover {
  color: var(--nav-link-fg-hover);
}

/* current-section state — colour + underline carry the meaning together,
   aria-current="page" carries it for assistive tech, so this satisfies the
   colour-alone rule without a second glowing element. */
.nav-link--current {
  color: var(--nav-link-fg-current);
  text-decoration: underline;
  text-decoration-color: var(--nav-link-underline-current);
  text-decoration-thickness: var(--hairline);
  text-underline-offset: 4px;
}

/* Mobile header — two rows, no hamburger. Row 1: brand + compact CTA
   (space-between). Row 2: nav as one horizontally scrollable strip,
   hairline above, right-edge fade. See PAGE-SHELL.md §2.
   PAGE-SHELL.md specs this "below 760px"; the breakpoint is widened to
   1023px here because the single-row inline nav (5 links + brand + CTA at
   the system's mono/uppercase/tracked type) measures wider than that and
   overlaps the brand between ~760-1000px with no fix specified for that
   zone. Widening is a strict superset of the spec (760px is still well
   inside it) and is the minimal change that keeps every link reachable
   and the header free of overlapping text at every width down to 375px. */
@media (max-width: 1023px) {
  .header-inner {
    flex-wrap: wrap;
    height: auto;
    padding-block: var(--space-3);
  }

  .brand {
    order: 1;
  }

  .header-cta {
    order: 2;
  }

  .primary-nav {
    order: 3;
    flex-basis: 100%;
    width: 100%;
    margin-top: var(--space-3);
    padding-top: var(--space-3);
    border-top: 1px solid var(--header-border);
  }

  /* Wraps onto as many lines as five links need, rather than scrolling.
     A horizontally-scrolling strip was tried here first: its edge-fade
     affordance (linear-gradient to --header-bg, near-black) was invisible
     against a near-black page, and a real-buyer pass at 375px could not
     discover or reach the last two links or the header CTA by touch.
     Wrapping guarantees every link is on screen with no cue required. */
  .primary-nav ul {
    display: flex;
    flex-wrap: wrap;
    row-gap: var(--space-3);
    column-gap: var(--space-6);
    /* vertical room so a focused link's 2px outline + 2px offset never clips */
    padding-block: var(--space-2);
  }

  .primary-nav ul li {
    flex: 0 0 auto;
  }

  /* 44px minimum tap target (COMPONENT-SPECS.md §2), given with real
     vertical padding via flex-centering rather than a fixed line-height
     hack, so the label stays vertically centred at any font metrics. */
  .nav-link {
    display: flex;
    align-items: center;
    min-height: 44px;
    white-space: nowrap;
  }
}

/* ===== 3. btn ===== */
.btn {
  display: inline-block;
  font-family: var(--type-label-font);
  font-size: var(--type-label-size);
  letter-spacing: var(--type-label-tracking);
  text-transform: uppercase;
  text-decoration: none;
  padding: var(--btn-padding-y) var(--btn-padding-x);
  border-radius: var(--btn-radius);
  border: none;
  cursor: pointer;
  transition: background-color 120ms ease, box-shadow 120ms ease;
}

.btn--primary {
  background: var(--btn-bg);
  color: var(--btn-fg);
}

.btn--primary:hover {
  background: var(--btn-bg-hover);
  /* one of the two permitted glow elements */
  box-shadow: 0 0 28px var(--btn-bg-hover);
}

.btn--primary:active {
  background: var(--btn-bg-active);
  box-shadow: none;
}

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

.btn--compact {
  padding: var(--btn-compact-padding-y) var(--btn-compact-padding-x);
}

/* ===== 4/5. eyebrow + part-ref ===== */
.eyebrow-row {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
}

.part-ref {
  font-family: var(--type-data-font);
  font-size: var(--type-data-size);
  letter-spacing: var(--type-data-tracking);
  color: var(--part-ref-fg);
}

.part-ref--boxed {
  display: inline-block;
  border: 1px solid var(--part-ref-border);
  padding: 2px var(--space-2);
  border-radius: var(--radius-sm);
}

/* ===== 6. section / 12-col grid ===== */
.section {
  background: var(--section-bg);
}

.section-grid {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  column-gap: var(--section-gap);
  background-image: repeating-linear-gradient(
    to right,
    transparent 0,
    transparent calc(100% / 12 - 1px),
    var(--section-rule) calc(100% / 12 - 1px),
    var(--section-rule) calc(100% / 12)
  );
}

.section-content {
  grid-column: 1 / 9;
  /* grid/flex children default to min-width: auto, which refuses to shrink
     below content's min-content size — without this, a wide .data-table
     pushes this track past the viewport and defeats .table-scroll's
     overflow-x: auto. */
  min-width: 0;
  padding-block: var(--space-6) var(--space-5);
}

/* A section-grid with no spec-rail has no reason to leave columns 9–12
   empty — let the content span the full 12-col track instead of reading
   as an oversight beside a void. Spanning the track alone was not enough:
   the column-rule background below is painted on .section-grid itself,
   unconditional on what its children occupy, so it kept drawing ruled
   lines across the empty space to the right of narrower content (eg.
   body-copy's own 64ch max-width) even after this fix. Drop the rule
   background entirely on a rail-less section — there is nothing left for
   it to divide. */
.section-grid:not(:has(.spec-rail)) {
  background-image: none;
}

.section-grid:not(:has(.spec-rail)) .section-content {
  grid-column: 1 / 13;
}

@media (max-width: 640px) {
  .section-grid {
    grid-template-columns: 1fr;
    background-image: none;
  }

  .section-content {
    grid-column: 1;
  }
}

.section-divider {
  height: var(--hairline);
  background: var(--section-divider);
}

/* ===== 7. spec-rail ===== */
.spec-rail {
  grid-column: 9 / 13;
  border-left: 1px solid var(--rail-border);
  padding: var(--space-6) 0 var(--space-5) var(--space-5);
}

.spec-rail dl > div {
  margin-bottom: var(--space-4);
}

.spec-rail dt {
  font-family: var(--type-label-font);
  font-size: var(--type-label-size);
  letter-spacing: var(--type-label-tracking);
  text-transform: uppercase;
  color: var(--rail-fg);
  margin-bottom: var(--space-1);
}

.spec-rail dd {
  font-family: var(--type-data-font);
  font-size: var(--type-data-size);
  letter-spacing: var(--type-data-tracking);
  color: var(--text-primary);
}

.spec-rail dd a {
  color: var(--text-accent);
  text-decoration: none;
}

.spec-rail dd a:hover {
  color: var(--accent-hover);
}

.spec-rail dd a:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
}

@media (max-width: 640px) {
  .spec-rail {
    grid-column: 1;
    border-left: none;
    border-top: 1px solid var(--rail-border);
    padding: var(--space-5) 0 0 0;
    margin-top: var(--space-5);
  }
}

/* ===== 8. plate-card ===== */
.plate-grid {
  margin-top: var(--space-6);
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: var(--space-5);
}

.plate-card {
  position: relative;
  display: flex;
  flex-direction: column;
  height: 100%;
  background: var(--plate-bg);
  border-radius: var(--plate-radius);
  padding: var(--plate-padding);
  transition: background-color 120ms ease;
}

/* The heading link's ::after (below, z-index: 1) already extends the hit
   area over the whole card, which means the whole card already matches
   :hover on that link — :hover on a link fires while the pointer is over
   any of the link's boxes, including its generated content. This just
   makes that existing whole-card hit area visible: a background lift, no
   glow (plate cards no longer glow, see GLOW section). */
.plate-card:hover {
  background: var(--surface-raised);
}

.plate-tick {
  position: absolute;
  width: 8px;
  height: 8px;
  border: 1px solid var(--plate-tick);
}

.plate-tick--tl { top: 0; left: 0; border-width: 1px 0 0 1px; }
.plate-tick--tr { top: 0; right: 0; border-width: 1px 1px 0 0; }
.plate-tick--bl { bottom: 0; left: 0; border-width: 0 0 1px 1px; }
.plate-tick--br { bottom: 0; right: 0; border-width: 0 1px 1px 0; }

.plate-model {
  position: absolute;
  top: var(--space-4);
  right: var(--space-4);
}

.plate-dimension {
  font-size: var(--type-data-size);
  letter-spacing: var(--type-data-tracking);
  color: var(--plate-dimension-fg);
  margin-top: var(--space-6);
}

.plate-photo {
  height: 220px;
  margin-top: var(--space-4);
}

.plate-photo-img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
  object-position: center;
}

.plate-name {
  margin-top: var(--space-4);
}

/* Standard accessible card-link pattern: the heading is the only real
   anchor, and its ::after overlay extends the hit area to the whole card.
   No nested interactive elements — .plate-card itself is never a link. */
.plate-name a {
  color: inherit;
  text-decoration: none;
}

.plate-name a:hover {
  color: var(--text-accent);
}

.plate-name a::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
}

.plate-name a:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
}

.plate-spec-strip {
  margin-top: auto;
  padding-top: var(--space-3);
  border-top: 1px solid var(--border-hairline);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.plate-spec-strip > div {
  display: flex;
  justify-content: space-between;
  gap: var(--space-3);
}

.plate-spec-strip dt {
  font-family: var(--type-label-font);
  font-size: var(--type-label-size);
  letter-spacing: var(--type-label-tracking);
  text-transform: uppercase;
  color: var(--rail-fg);
}

.plate-spec-strip dd {
  font-family: var(--type-data-font);
  font-size: var(--type-data-size);
  letter-spacing: var(--type-data-tracking);
  color: var(--text-primary);
}

@media (max-width: 640px) {
  .plate-card {
    padding: var(--space-4);
  }

  .plate-dimension {
    display: none;
  }

  .plate-photo {
    height: 180px;
  }
}

/* ===== 9. stat-tile ===== */
.stat-grid {
  margin-top: var(--space-5);
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: var(--space-6);
}

.stat-tile {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.stat-figure {
  font-family: var(--type-data-font);
  font-size: var(--size-lg);
  letter-spacing: var(--type-data-tracking);
  color: var(--stat-figure-fg);
  font-variant-numeric: tabular-nums;
}

.stat-unit,
.stat-prefix {
  font-size: var(--type-data-size);
}

.stat-label {
  font-family: var(--type-label-font);
  font-size: var(--type-label-size);
  letter-spacing: var(--type-label-tracking);
  text-transform: uppercase;
  color: var(--stat-label-fg);
}

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

/* ===== 10. quick-link ===== */
.quick-link-grid {
  margin-top: var(--space-6);
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
}

.quick-link {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-4);
  border-top: 1px solid var(--quick-link-border);
  border-left: 1px solid var(--quick-link-border);
  text-decoration: none;
  color: var(--quick-link-fg);
  transition: background-color 120ms ease;
}

.quick-link:hover {
  background: var(--quick-link-bg-hover);
}

.quick-link-name {
  font-family: var(--type-heading-font);
  font-weight: 700;
  font-size: var(--size-md);
}

.quick-link-desc {
  font-size: var(--size-sm);
  line-height: var(--type-body-line);
  color: var(--text-secondary);
}

@media (max-width: 640px) {
  .quick-link-grid {
    grid-template-columns: 1fr;
  }
}

/* quick-link-grid--fill — home page six-services panel only. Fixed 3x2 so
   the six items fill the panel height cleanly instead of reflowing at
   auto-fit's whim. quick-link-media is the future per-service video slot:
   fixed 16:9, clipped, filled with the film ground colour so an empty box
   still reads as a media container, not a hole. Scoped to this modifier
   class (not the bare .quick-link-grid) because /solutions/ and /company/
   reuse the bare grid for unrelated link lists. */
.quick-link-grid--fill {
  grid-template-columns: repeat(3, 1fr);
}

.quick-link-media {
  display: block;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  background: var(--film-bg);
}

@media (max-width: 900px) {
  .quick-link-grid--fill {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (max-width: 640px) {
  .quick-link-grid--fill {
    grid-template-columns: 1fr;
  }
}

/* The six-services section is a standalone panel with no slack left after
   the panel-inner header-clearance padding and the 16:9 media boxes above —
   base.css's shared .section padding-block (space-10, 96px top/bottom) was
   built for sections that scroll past freely, not one pinned to exactly one
   viewport. Trimmed only here, only for this one section instance, so nine
   other .section uses on this page (and every other page) are untouched. */
.section--fill {
  padding-block: var(--space-8);
}

/* ===== chain (uses eyebrow/part-ref, own numbering) ===== */
.chain-list {
  margin-top: var(--space-6);
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.chain-list li {
  display: grid;
  grid-template-columns: 40px 1fr;
  gap: var(--space-4);
  padding-block: var(--space-3);
  border-top: 1px solid var(--border-hairline);
}

.chain-step-num {
  color: var(--text-accent);
}

.chain-step-name {
  font-weight: 700;
  color: var(--text-primary);
}

/* ===== industries list ===== */
.industry-list {
  margin-top: var(--space-6);
  display: flex;
  flex-direction: column;
}

.industry-item {
  display: grid;
  grid-template-columns: minmax(220px, 1fr) 2fr;
  gap: var(--space-5);
  padding-block: var(--space-4);
  border-top: 1px solid var(--border-hairline);
}

.industry-name {
  font-weight: 700;
  color: var(--text-primary);
}

.industry-desc {
  color: var(--text-secondary);
}

@media (max-width: 640px) {
  .industry-item {
    grid-template-columns: 1fr;
    gap: var(--space-1);
  }
}

/* ===== text-link (out-links, not a spec'd component — plain accent link) ===== */
.text-link {
  display: inline-block;
  margin-top: var(--space-5);
  color: var(--text-accent);
  font-family: var(--type-label-font);
  font-size: var(--type-label-size);
  letter-spacing: var(--type-label-tracking);
  text-transform: uppercase;
  text-decoration: none;
}

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

.body-copy {
  margin-top: var(--space-4);
  max-width: 64ch;
  color: var(--text-secondary);
}

/* ===== hero ===== */
.hero {
  position: relative;
  overflow: hidden;
  min-height: 100vh;
  display: flex;
}

.hero-media {
  position: absolute;
  inset: 0;
  z-index: 0;
}

.hero-media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center 58%;
  filter: brightness(0.98) contrast(1.06);
}

.hero-scrim {
  position: absolute;
  inset: 0;
}

/* darkens the top band, where the type sits, down onto solid ground */
.hero-scrim--top {
  background: linear-gradient(
    180deg,
    var(--surface-page) 0%,
    color-mix(in srgb, var(--surface-page) 78%, transparent) 34%,
    transparent 62%
  );
}

/* darkens the left edge, under the type block, so it never sits on raw
   linework with no ground beneath it */
.hero-scrim--left {
  background: linear-gradient(
    90deg,
    var(--surface-page) 0%,
    color-mix(in srgb, var(--surface-page) 55%, transparent) 34%,
    transparent 64%
  );
}

.hero-inner {
  position: relative;
  z-index: 1;
  width: 100%;
  display: flex;
  align-items: flex-start;
  padding-top: 16vh;
  padding-bottom: var(--space-9);
}

.hero-type {
  max-width: 760px;
}

.hero-h1 {
  margin-top: var(--space-3);
  max-width: 22ch;
  font-size: var(--hero-h1-size);
  line-height: 1.02;
  letter-spacing: -0.025em;
  /* one of the two permitted glow elements */
  filter: drop-shadow(0 0 32px var(--accent-muted));
}

.hero-sub {
  margin-top: var(--space-4);
  max-width: 52ch;
  font-size: var(--size-md);
  color: var(--text-primary);
}

.hero-type .btn {
  margin-top: var(--space-6);
}

@media (max-width: 640px) {
  .hero {
    flex-direction: column;
    min-height: 0;
  }

  .hero-media {
    position: relative;
    height: 40vh;
  }

  .hero-media img {
    object-position: center 40%;
  }

  /* .hero switches to flex-direction: column above this breakpoint, so
     .hero-inner stacks BELOW .hero-media instead of overlaying it — the
     type block sits on solid --surface-page, not on the photo. The
     desktop scrim strengths (tuned to keep text legible over a full-bleed
     underlay) are no longer protecting any text here, and were rendering
     as a near-solid black band across roughly the first fifth of the
     mobile viewport. Cut to a short top fade that only softens the seam
     under the header; drop the left fade entirely. */
  .hero-scrim--top {
    background: linear-gradient(180deg, var(--surface-page) 0%, transparent 18%);
  }

  .hero-scrim--left {
    background: none;
  }

  .hero-inner {
    align-items: stretch;
    padding-block: var(--space-6);
  }

  .hero-type {
    max-width: none;
  }
}

/* ===== CTA band ===== */
.cta-band-inner {
  text-align: center;
  max-width: 60ch;
  margin-inline: auto;
  padding-block: var(--space-6);
}

.cta-band-inner .eyebrow-row {
  justify-content: center;
}

.cta-sub {
  margin-top: var(--space-3);
  color: var(--text-secondary);
}

.cta-band-inner .btn {
  margin-top: var(--space-6);
}

/* ===== 13. draft-banner ===== */
.draft-banner {
  background: var(--draft-bg);
  border-bottom: 1px solid var(--draft-border);
  color: var(--draft-fg);
  font-family: var(--type-label-font);
  font-size: var(--type-label-size);
  letter-spacing: var(--type-label-tracking);
  text-transform: uppercase;
  text-align: center;
  padding: var(--space-2) var(--space-4);
}

/* ===== footer ===== */
.site-footer {
  background: var(--surface-void);
  border-top: 1px solid var(--border-hairline);
  padding-block: var(--space-9);
}

.footer-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: var(--space-6);
  font-size: var(--size-sm);
  color: var(--text-secondary);
}

.footer-grid dt {
  font-family: var(--type-label-font);
  font-size: var(--type-label-size);
  letter-spacing: var(--type-label-tracking);
  text-transform: uppercase;
  color: var(--text-secondary);
  margin-bottom: var(--space-2);
}

.footer-grid dd {
  margin-bottom: var(--space-1);
}

.footer-grid a {
  color: var(--text-accent);
  text-decoration: none;
}

.footer-grid a:hover {
  color: var(--accent-hover);
}

.footer-legal {
  margin-top: var(--space-7);
  padding-top: var(--space-5);
  border-top: 1px solid var(--border-hairline);
  font-size: var(--size-xs);
  color: var(--text-secondary);
}

/* ===== footer-credentials — trust signals, not performance figures.
   See PAGE-SHELL.md §7. ===== */
.footer-credentials {
  border-top: 1px solid var(--footer-credentials-border);
  margin-top: var(--space-6);
  padding-block: var(--space-6);
}

.footer-credentials dl {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-6);
}

.footer-credentials dl > div {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.footer-credentials dt {
  font-family: var(--type-label-font);
  font-size: var(--type-label-size);
  letter-spacing: var(--type-label-tracking);
  text-transform: uppercase;
  color: var(--footer-credentials-key-fg);
}

.footer-credentials dd {
  font-size: var(--size-sm);
  color: var(--footer-credentials-value-fg);
}

/* ===== 14. breadcrumb — every page except home ===== */
.breadcrumb {
  border-bottom: 1px solid var(--breadcrumb-border);
}

.breadcrumb ol {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  padding-block: var(--space-3);
}

.breadcrumb li {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-family: var(--type-label-font);
  font-size: var(--size-xs);
  letter-spacing: var(--type-label-tracking);
  color: var(--breadcrumb-fg);
}

/* separator is a pseudo-element, so it is invisible to assistive tech —
   this is the correct way to keep "/" out of the announced crumb trail. */
.breadcrumb li:not(:first-child)::before {
  content: "/";
  color: var(--breadcrumb-fg);
}

.breadcrumb a {
  color: var(--breadcrumb-fg);
  text-decoration: none;
}

.breadcrumb a:hover {
  color: var(--breadcrumb-fg-hover);
}

.breadcrumb [aria-current="page"] {
  color: var(--breadcrumb-fg-current);
}

/* ===== 15. page-hero — interior pages only. No bg image, no scrim, no
   glow: the home hero is the one place scale carries meaning. ===== */
.page-hero {
  padding-top: var(--space-10);
  padding-bottom: var(--space-9);
}

@media (max-width: 760px) {
  .page-hero {
    padding-top: var(--space-8);
    padding-bottom: var(--space-6);
  }
}

.page-hero-h1 {
  margin-top: var(--space-3);
  font-family: var(--type-display-font);
  font-size: clamp(var(--size-2xl), 6vw, var(--size-4xl));
  line-height: var(--type-display-line);
  letter-spacing: var(--type-display-tracking);
  font-weight: 700;
  color: var(--page-hero-fg);
}

.page-standfirst {
  margin-top: var(--space-4);
  font-size: var(--size-lg);
  color: var(--page-standfirst-fg);
  max-width: 62ch;
}

/* ===== 16. data-table — built to COMPONENT-SPECS.md §11 ===== */
.data-table {
  width: 100%;
  border-collapse: collapse;
}

.data-table caption {
  caption-side: top;
  text-align: left;
  font-family: var(--type-label-font);
  font-size: var(--type-label-size);
  letter-spacing: var(--type-label-tracking);
  text-transform: uppercase;
  color: var(--eyebrow-fg);
  margin-bottom: var(--space-3);
}

.data-table th,
.data-table td {
  padding: var(--table-cell-padding-y) var(--table-cell-padding-x);
  border-bottom: 1px solid var(--table-border);
  text-align: left;
}

.data-table thead th {
  font-family: var(--type-label-font);
  font-size: var(--type-label-size);
  letter-spacing: var(--type-label-tracking);
  text-transform: uppercase;
  font-weight: 500;
  color: var(--table-header-fg);
}

.data-table tbody td {
  font-family: var(--type-data-font);
  font-size: var(--type-data-size);
  letter-spacing: var(--type-data-tracking);
  color: var(--table-row-fg);
}

.data-table tbody tr:hover {
  background: var(--table-row-bg-hover);
}

.data-table .num {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/* Prevents the unit label breaking mid-word (e.g. "L/day" -> "L/da" + "y")
   in the narrow value column at 375px, where 2.24px letter-spacing
   (--type-data-tracking) on 14px mono type leaves too little width for a
   normal line break. Scoped to .data-table .num so it only affects the
   spec-table value column, not any other letter-spaced text. */
.data-table .num .unit {
  white-space: nowrap;
}

/* wrapper — overflow-x:auto so the page body never scrolls sideways
   because of a wide table, at every breakpoint (COMPONENT-SPECS.md §11).
   tabindex="0" is set in markup by the page author so keyboard users can
   scroll it; this rule only styles the resulting focus state. */
.table-scroll {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  position: relative;
  border: 1px solid var(--table-scroll-border);
  /* caps the table at a considered width instead of letting a short
     key/value table stretch to the full section column (COMPONENT-SPECS.md
     §11). A block element's auto width is min(available, max-width), so
     this only ever shrinks the box, never grows it past its container —
     375px inside .table-scroll is unaffected. */
  max-width: var(--table-max-width);
}

.table-scroll::after {
  content: "";
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  width: 32px;
  background: var(--table-scroll-fade);
  pointer-events: none;
}

.table-scroll:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
}

/* ===== 17. form — assessment page ===== */
.form {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
}

.form-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-5);
}

@media (max-width: 760px) {
  .form-row {
    grid-template-columns: 1fr;
  }
}

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

.form-label {
  font-family: var(--type-label-font);
  font-size: var(--type-label-size);
  letter-spacing: var(--type-label-tracking);
  text-transform: uppercase;
  color: var(--form-label-fg);
}

.form-required {
  color: var(--form-required-fg);
  margin-left: 2px;
}

.form-input,
.form-select,
.form-textarea {
  min-height: 44px;
  padding: var(--space-3) var(--space-4);
  background: var(--form-input-bg);
  border: 1px solid var(--form-input-border);
  border-radius: var(--radius-sm);
  color: var(--form-input-fg);
  font-family: var(--type-body-font);
  font-size: var(--type-body-size);
}

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

.form-input::placeholder,
.form-textarea::placeholder {
  color: var(--form-input-placeholder-fg);
}

.form-input:focus-visible,
.form-select:focus-visible,
.form-textarea:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
}

.form-hint {
  font-size: var(--size-sm);
  color: var(--form-hint-fg);
}

/* invalid state is border colour + this text together, never colour alone */
.form-error {
  font-size: var(--size-sm);
  color: var(--form-error-fg);
}

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

/* consent checkbox is not wrapped by .form-field (it has its own layout,
   see .form-consent below), so its invalid state gets its own hook rather
   than reusing .form-field--invalid. */
.form-consent--invalid input[type="checkbox"] {
  border-color: var(--form-error-border);
}

/* form-validate.js's submit-count summary. Empty by default (no errors
   yet, or the form is clean) and takes no visual space then; form-validate
   only ever writes text into it, never toggles a hidden attribute, so a
   screen reader's aria-live="polite" region announces the count the
   moment it changes. */
.form-summary {
  font-size: var(--size-sm);
  color: var(--form-error-fg);
}

.form-summary:empty {
  display: none;
}

.form-actions {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  margin-top: var(--space-3);
}

.form-consent {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
}

/* appearance: none, not accent-color — a native checkbox's border does not
   reliably take an author border-color across browsers (it fell back to
   currentColor when tested), which makes the WCAG 1.4.11 boundary
   unmeasurable/unguaranteed. Hand-drawing the check keeps the border color
   under the token system like every other form control. */
.form-consent input[type="checkbox"] {
  appearance: none;
  -webkit-appearance: none;
  width: 24px;
  height: 24px;
  min-width: 24px;
  margin: 0;
  border: 1px solid var(--form-checkbox-border);
  border-radius: var(--radius-sm);
  background: var(--form-input-bg);
  position: relative;
  cursor: pointer;
}

.form-consent input[type="checkbox"]:checked {
  background: var(--accent);
  border-color: var(--accent);
}

.form-consent input[type="checkbox"]:checked::after {
  content: "";
  position: absolute;
  left: 8px;
  top: 3px;
  width: 6px;
  height: 12px;
  border: solid var(--text-inverse);
  border-width: 0 2px 2px 0;
  transform: rotate(45deg);
}

.form-consent input[type="checkbox"]:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
}

/* Forced-colors mode (Windows high-contrast, Firefox forced-colors): the
   drawn checkmark is a plain ::after box with no borders of its own, and
   the checked background is a plain fill — the forced palette can flatten
   both to nothing, leaving an empty box with no checked/unchecked
   distinction. Handing the control back to the UA's native renderer here
   is the reliable fix: forced-colors mode always draws a real checked
   indicator (a system-colour tick or fill) for a native checkbox. */
@media (forced-colors: active) {
  .form-consent input[type="checkbox"] {
    appearance: auto;
    -webkit-appearance: auto;
    forced-color-adjust: auto;
    background: revert;
    border: revert;
  }

  .form-consent input[type="checkbox"]:checked::after {
    content: none;
  }
}

.form-consent label {
  font-size: var(--size-sm);
  color: var(--text-secondary);
}

/* ===== 18. callout ===== */
.callout {
  border: 1px solid var(--callout-border);
  background: var(--callout-bg);
  border-radius: var(--radius-sm);
  padding: var(--space-5);
  color: var(--callout-fg);
}

.callout-label {
  display: block;
  font-family: var(--type-label-font);
  font-size: var(--type-label-size);
  letter-spacing: var(--type-label-tracking);
  text-transform: uppercase;
  color: var(--callout-label-fg);
  margin-bottom: var(--space-2);
}

.callout p + p {
  margin-top: var(--space-3);
}

.callout--accent {
  border-color: var(--callout-border-accent);
}

/* ===== 19. media-plate — full-width image block, interior pages =====
   Width is capped to the largest derivative actually on disk, so the slot
   can never ask the browser to upscale a raster past its source pixels.
   Machine detail shots (plain .media-plate) top out at the 1200w
   derivative; industries stream photos (.media-plate--cover) top out at
   900w. This is a CSS-only fix — the HTML `sizes` attributes on the img
   tags still overstate the slot (1310px / 66vw) and should be corrected
   to match by whoever owns those files; the cap here is what stops the
   overstatement from ever reaching the DOM as a rendered pixel width. */
.media-plate {
  border: 1px solid var(--media-plate-border);
  background: var(--media-plate-bg);
  max-width: 1200px;
}

.media-plate--cover {
  max-width: 900px;
}

.media-plate img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.media-plate--cover img {
  object-fit: cover;
}

.media-plate-caption {
  font-family: var(--type-label-font);
  font-size: var(--size-xs);
  color: var(--media-plate-caption-fg);
  padding: var(--space-3) var(--space-4);
  border-top: 1px solid var(--media-plate-border);
}

/* ===== 20. quick-link-grid--three — related grid, foot of every page ===== */
.quick-link-grid--three {
  grid-template-columns: repeat(3, 1fr);
}

@media (max-width: 760px) {
  .quick-link-grid--three {
    grid-template-columns: 1fr;
  }
}

/* ===== 21. footnote-marker + footnote ===== */
.footnote-marker {
  font-size: 0.7em;
  margin-left: 2px;
}

.footnote-marker a {
  color: var(--footnote-marker-fg);
  text-decoration: none;
}

.footnote-marker a:hover {
  color: var(--accent-hover);
}

.footnote {
  margin-top: var(--space-4);
  padding-top: var(--space-3);
  border-top: 1px solid var(--footnote-border);
  font-size: var(--size-sm);
  color: var(--footnote-fg);
  max-width: 64ch;
}

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

/* ===== 22. film-row — video+copy media row, solution detail pages =====
   .film-video carries no border-radius: .media-plate (§19), the site's
   other full-bleed media component, uses square corners, so this matches
   rather than introducing a new shape. .film-toggle is a permanently
   visible pause/play control (WCAG 2.2.2), never a hover-reveal — it must
   stay reachable with JS blocked or on a keyboard-only visit. */
.film-row-inner {
  display: grid;
  grid-template-columns: 1.35fr 1fr;
  gap: var(--space-6);
  align-items: center;
}

@media (max-width: 768px) {
  .film-row-inner {
    grid-template-columns: 1fr;
  }
}

.film-figure {
  position: relative;
  margin: 0;
}

.film-video {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 960 / 536;
  background: var(--film-bg);
  border: var(--hairline) solid var(--media-plate-border);
}

.film-toggle {
  position: absolute;
  left: var(--space-3);
  bottom: var(--space-3);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
  padding: var(--btn-compact-padding-y) var(--btn-compact-padding-x);
  background: var(--film-caption-bg);
  color: var(--film-caption-fg);
  border: var(--hairline) solid var(--media-plate-border);
  border-radius: var(--radius-sm);
  font-family: var(--type-label-font);
  font-size: var(--type-label-size);
  letter-spacing: var(--type-label-tracking);
  text-transform: uppercase;
  cursor: pointer;
}

.film-toggle:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
  box-shadow: none;
  border-radius: var(--radius-sm);
}

.film-figure-caption {
  margin-top: var(--space-3);
  font-family: var(--type-label-font);
  font-size: var(--type-label-size);
  letter-spacing: var(--type-label-tracking);
  text-transform: uppercase;
  color: var(--media-plate-caption-fg);
}

/* ===== 23. quick-link--lead — lead card, six-services hub only =====
   Poster image only, no <video> — one video per page (the destination
   already carries the film). Collapses back to a standard single-column
   card at the same 768px breakpoint the film-row uses. */
.quick-link--lead {
  grid-column: span 1;
}

.quick-link--lead img {
  aspect-ratio: 960 / 536;
  width: 100%;
  height: auto;
  display: block;
  margin-bottom: var(--space-2);
}

@media (min-width: 769px) {
  .quick-link--lead {
    grid-column: span 2;
  }
}

/* ===== 24. filmscroll — sticky scroll-driven frame sequence, home page =====
   .filmscroll-track supplies the scroll distance (five cue states, one
   viewport each); .filmscroll-stage pins to the viewport while the track
   scrolls past it. Fail-open: .filmscroll-track ships [hidden] in markup
   and .filmscroll-fallback is the default-visible branch — filmscroll.js
   only flips the two once it has confirmed the canvas can paint. Below
   768px and under reduced motion, JS never touches either branch, so the
   markup's own hidden/visible split stands as the final state. */
.filmscroll-track {
  position: relative;
  height: 500vh;
}

.filmscroll-stage {
  position: sticky;
  top: 0;
  height: 100vh;
  overflow: hidden;
  background: var(--film-bg);
}

.filmscroll-canvas {
  /* Full-bleed, edge to edge across the stage — same cover treatment as
     the home hero image. The element just fills the box; the actual
     cover-fit scale/crop math happens in filmscroll.js's draw call against
     the canvas's real backing-store pixels, not here. */
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
}

.filmscroll-captions {
  position: absolute;
  inset: var(--space-6);
  display: grid;
  pointer-events: none;
}

.filmscroll-caption {
  grid-area: 1 / 1;
  align-self: end;
  max-width: 62ch;
  opacity: 0;
  transition: none;
  padding: var(--space-6) var(--space-5) var(--space-5);
  background: linear-gradient(to top, var(--film-bg) 0%, transparent 100%);
}

.filmscroll-kicker {
  font-family: var(--type-label-font);
  font-size: var(--type-label-size);
  letter-spacing: var(--type-label-tracking);
  text-transform: uppercase;
  color: var(--film-caption-fg);
}

.filmscroll-name {
  margin-top: var(--space-3);
  font-family: var(--type-display-font);
  font-size: clamp(var(--size-2xl), 6vw, var(--size-4xl));
  line-height: var(--type-display-line);
  letter-spacing: var(--type-display-tracking);
  font-weight: 700;
  color: var(--film-caption-fg);
}

.filmscroll-spec {
  margin-top: var(--space-3);
  font-family: var(--type-label-font);
  font-size: var(--type-label-size);
  letter-spacing: var(--type-label-tracking);
  text-transform: uppercase;
  color: var(--film-caption-fg);
}

.filmscroll-line {
  margin-top: var(--space-3);
  font-size: var(--size-lg);
  color: var(--film-caption-fg);
  max-width: 62ch;
}

/* Shared intro heading — sits above the sticky stage in normal flow, not
   inside either branch, so the page's one <h1> is always present whether
   or not filmscroll.js ever runs. Reuses .page-hero-h1 directly: it is a
   real static heading, not a cue-timed caption. */
.filmscroll-intro {
  padding-top: var(--space-10);
  padding-bottom: var(--space-7);
}

@media (max-width: 760px) {
  .filmscroll-intro {
    padding-top: var(--space-8);
    padding-bottom: var(--space-6);
  }
}

/* Fallback: static plates for sub-768px and prefers-reduced-motion visits. */
.filmscroll-fallback-grid {
  display: grid;
  gap: var(--space-7);
}

.filmscroll-fallback-grid figure {
  margin: 0;
}

.filmscroll-fallback-grid img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 960 / 540;
  object-fit: cover;
  background: var(--film-bg);
  border: var(--hairline) solid var(--media-plate-border);
}

.filmscroll-fallback-grid figcaption {
  margin-top: var(--space-3);
}

@media (min-width: 769px) {
  .filmscroll-fallback-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

/* ===== 25. panel-stack — sticky panel deck, home page =====
   Each .panel pins to the top of the viewport (position: sticky, top: 0)
   and stays glued there once its natural scroll position reaches that
   offset. Every panel shares one ancestor (.panel-stack) that is far
   taller than any single panel, so a panel does not "unstick" again until
   scrolling nears the very end of the document — meaning once two
   adjacent panels are both in their stuck state, they occupy the exact
   same viewport rectangle, and the later one in DOM order (given the
   higher --panel-z below) simply paints over the earlier one. That
   overlap alone is the stacking-deck effect: nothing here animates `top`
   or `translateY` to fake it.

   The film panel is the deliberate exception. It already owns a 500vh
   .filmscroll-track with its own sticky .filmscroll-stage; wrapping that
   in a second, outer sticky box breaks the inner one (a sticky descendant
   still resolves against the viewport, but the outer box's own "stuck"
   state freezes the on-screen position of everything inside it, so the
   track's getBoundingClientRect().top — what filmscroll.js reads to drive
   frame/caption progress — would stop changing while scrolled through).
   So the film section gets .panel--film instead of .panel: a plain,
   non-sticky, position: relative box with its own opaque background and a
   slot in the same z-index ladder. Because that box is opaque and far
   taller than one viewport, it still fully covers whatever panel is
   stuck behind it for as long as any part of it is on screen — no sticky
   trick required — and the panel after it covers it in turn exactly the
   normal way once the film's 500vh has scrolled past. */
.panel-stack {
  position: relative;
}

.panel {
  position: sticky;
  top: 0;
  min-height: 100vh;  /* fallback for browsers without svh support */
  min-height: 100svh;
  z-index: var(--panel-z, 1);
}

.panel-stage {
  min-height: 100vh;  /* fallback for browsers without svh support */
  min-height: 100svh;
  display: flex;
  align-items: center;
  overflow: hidden;
  background: var(--section-bg);
  transition: none;
  transform-origin: center;
  will-change: transform;
  /* Depth cue written by panelstack.js as --panel-covered (0..1): how far
     the next panel has advanced over this one. Kept subtle — 4% scale,
     35% dim at full coverage — and inert (no-op) until the property is
     set, since the default is 0. Never reaches the film panel: it has no
     .panel-stage element for this selector to match. */
  transform: scale(calc(1 - 0.04 * var(--panel-covered, 0)));
  filter: brightness(calc(1 - 0.35 * var(--panel-covered, 0)));
}

.panel-inner {
  width: 100%;
  padding-top: var(--header-height);
}

/* Film — see file banner above for why this is not .panel. */
.panel--film {
  position: relative;
  z-index: var(--panel-z, 1);
  background: var(--film-bg);
}

/* Final panel (CTA band + footer) — carries the top z-index so it covers
   panel 6, but is deliberately NOT sticky and NOT height-capped: it
   scrolls normally and ends the page, same as before this deck existed. */
.panel--end {
  position: relative;
  min-height: 0;
}

@media (max-width: 767px) {
  .panel,
  .panel--film,
  .panel--end {
    position: static;
    min-height: 0;
  }

  .panel-stage {
    min-height: 0;
    transform: none;
    filter: none;
  }

  .panel-inner {
    padding-top: 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  .panel,
  .panel--film,
  .panel--end {
    position: static;
    min-height: 0;
  }

  .panel-stage {
    min-height: 0;
    transform: none;
    filter: none;
  }

  .panel-inner {
    padding-top: 0;
  }
}
