/* ==========================================================
   PLI — Phillips Light Industrial
   Brand: PLI Yellow #FEC202 · Accent Gold #E9AE00 · Deep Black #050505
          Charcoal #1B1B1B · Steel Gray #767676 · White #FFFFFF
   Type:  Roboto Condensed — 800/700 headlines, 500 subheads, 400 body
   Graphic language: 12–15° angled cuts, corner notches, high contrast
   Architecture: fixed WebGL canvas behind the page; sections are either
   transparent (world visible), scrimmed, or solid (world hidden).
   ========================================================== */

:root {
  --yellow: #FEC202;
  --gold: #E9AE00;
  --black: #050505;
  --charcoal: #1B1B1B;
  --gray: #767676;
  --light: #F4F4F4;
  --white: #FFFFFF;
  --cut: 13deg;
  --notch: 28px;
  --header-h: 78px;
  --ease: cubic-bezier(.22,.9,.24,1);
}

* { margin: 0; padding: 0; box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  /* Anchor offset. Lenis used to apply this as a -70px scroll offset in JS;
     scroll-padding-top is the native equivalent and, unlike the JS version, it
     also applies to a link followed from another page and to keyboard
     sequential-focus navigation. */
  scroll-padding-top: calc(var(--header-h) + 16px);
}

body {
  font-family: "Roboto Condensed", "Arial Narrow", sans-serif;
  font-weight: 400;
  /* Was body.no-webgl, applied only when the WebGL scene failed or was skipped.
     The scene is gone, so this is now the one background the site has. Fixed
     attachment keeps it still behind the scroll, which is what the canvas used
     to do. */
  background:
    radial-gradient(1200px 700px at 75% 20%, rgba(254,194,2,.14), transparent 60%),
    radial-gradient(900px 700px at 10% 80%, rgba(254,194,2,.06), transparent 55%),
    var(--black);
  background-attachment: fixed;
  color: var(--white);
  line-height: 1.55;
  /* Two declarations on purpose. `overflow-x: hidden` makes overflow-y compute
     to `auto`, which turns <body> into a scroll container — and a scroll
     container breaks `position: sticky` for everything inside it, because the
     sticky element then sticks to a scrollport that never scrolls. That is what
     the pinned process track lives on. `overflow-x: clip` clips exactly the
     same way without creating one. The `hidden` line stays underneath it as the
     fallback for anything too old to know `clip`. */
  overflow-x: hidden;
  overflow-x: clip;
  -webkit-font-smoothing: antialiased;
}
body.nav-open { overflow: hidden; }

/* width:100% is load-bearing — `margin: 0 auto` cancels cross-axis stretch
   inside a column flex parent, which would otherwise let this size to
   max-width (1240px) and overflow narrower viewports. */
.container { width: 100%; max-width: 1240px; margin: 0 auto; padding: 0 28px; }
.yellow { color: var(--yellow); }
.icon { display: inline-block; vertical-align: middle; stroke: currentColor; fill: none; }
.ic-y { stroke: var(--yellow) !important; }
.ic-yf { fill: var(--yellow); stroke: none; }

h1, h2, h3, h4 {
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: -.005em;
  line-height: 1.02;
}

/* Images are wrapped in <picture> to offer WebP ahead of the JPEG fallback.
   The wrapper must not become the img's containing block, or rules like
   .hero-photo img { height: 100% } would resolve against an auto-height
   inline box and collapse. display:contents removes the box entirely, so
   every existing `.parent img` rule keeps behaving as it did. */
picture { display: contents; }

section { position: relative; z-index: 1; }

/* Keyboard focus stays visible everywhere; mouse users never see it. */
:focus-visible {
  outline: 3px solid var(--yellow);
  outline-offset: 3px;
  border-radius: 2px;
}
/* clip-path clips everything an element paints — an outline included, and an
   outline sits outside the border box. Clipped controls would otherwise take
   focus with no visible ring at all, so they get an inset one instead. */
.btn:not(.btn-ghost):focus-visible,
.svc-card:focus-visible,
.problem-tile:focus-visible,
.industry-tile:focus-visible,
.solution-card:focus-visible {
  outline: none;
  box-shadow: inset 0 0 0 3px var(--yellow);
}
/* ...and where the surface is itself yellow or near-white, the ring flips to
   black so it still has something to contrast against. */
.btn.btn-yellow:focus-visible,
.industry-tile:focus-visible,
.solution-card:focus-visible {
  box-shadow: inset 0 0 0 3px var(--black);
}
.skip-link {
  position: absolute; left: 16px; top: -60px; z-index: 300;
  background: var(--yellow); color: var(--black);
  font-weight: 700; text-transform: uppercase; letter-spacing: .08em;
  padding: 12px 20px; text-decoration: none;
  transition: top .25s var(--ease);
}
.skip-link:focus { top: 16px; }

/* panel modes */
.panel-transparent { background: transparent; }
.panel-scrim { background: linear-gradient(180deg, rgba(5,5,5,.55), rgba(5,5,5,.82) 40%, rgba(5,5,5,.55)); backdrop-filter: blur(3px); }
.panel-solid { position: relative; z-index: 2; }

/* ==========================================================
   SCROLL PROGRESS
   ========================================================== */
.scroll-progress { position: fixed; top: 0; left: 0; right: 0; height: 2px; z-index: 200; background: rgba(255,255,255,.08); }
.scroll-progress span {
  display: block; height: 100%; background: var(--yellow);
  transform: scaleX(0); transform-origin: left;
}

/* ==========================================================
   TEXT SPLITTING
   ========================================================== */
/* Masks clip on both axes, so the padding must clear descenders (commas,
   parentheses) that hang below tight display line-heights. */
.line-mask { display: block; overflow: hidden; padding-bottom: .18em; margin-bottom: -.18em; }
/* will-change is a hint to promote a layer, and the layer is held for as long
   as the declaration applies — not just while something animates. Left on
   permanently these three selectors promoted 59 elements on the homepage.
   .is-animating is added for the duration of the reveal and removed after. */
.line-inner { display: inline-block; }
.is-animating .line-inner,
.is-animating .char,
.is-animating .word { will-change: transform; }
.char-word { display: inline-block; white-space: nowrap; }
.char { display: inline-block; }
.word-mask { display: inline-block; overflow: hidden; padding-bottom: .18em; margin-bottom: -.18em; }
.word { display: inline-block; }

/* ==========================================================
   BUTTONS
   ========================================================== */
/* The fill wipe is a background gradient rather than a ::before overlay.
   A button's label is a bare text node, so it cannot be lifted above an
   overlay with z-index — a positioned ::before would paint over it. */
.btn {
  --btn-bg: transparent;
  --btn-fill: transparent;
  position: relative;
  display: inline-flex; align-items: center; gap: 10px;
  font-family: inherit; font-weight: 700;
  text-transform: uppercase; letter-spacing: .07em; font-size: 15px;
  text-decoration: none; border: none; cursor: pointer;
  padding: 15px 32px 15px 24px;
  clip-path: polygon(0 0, 100% 0, calc(100% - 15px) 100%, 0 100%);
  background-color: var(--btn-bg);
  background-image: linear-gradient(to right, var(--btn-fill) 0 50%, transparent 50% 100%);
  background-size: 200% 100%;
  background-position: 100% 0;
  background-repeat: no-repeat;
  transition: background-position .45s var(--ease), color .3s var(--ease), box-shadow .3s;
}
.btn:hover { background-position: 0 0; }

.btn-yellow { --btn-bg: var(--yellow); --btn-fill: var(--black); color: var(--black); }
.btn-yellow:hover { color: var(--yellow); }

.btn-ghost {
  --btn-fill: var(--white); color: var(--white);
  box-shadow: inset 0 0 0 1.5px rgba(255,255,255,.55); clip-path: none;
}
.btn-ghost:hover { color: var(--black); box-shadow: inset 0 0 0 1.5px var(--white); }

.btn-outline {
  --btn-fill: var(--yellow); color: var(--white);
  /* clip-path:none for the same reason as .btn-ghost — the border here is an
     inset shadow, and the angled clip would slice it off down the right edge. */
  box-shadow: inset 0 0 0 1.5px var(--yellow); clip-path: none;
}
.btn-outline:hover { color: var(--black); }

.btn-lg { padding: 18px 38px 18px 28px; font-size: 16px; }
.btn-block { width: 100%; justify-content: center; }
/* Both of these promote a layer for effects that only exist with a pointer:
   the button hover sweep, and the magnetic pull driven by mousemove (which
   app.js gates behind !touch). On a phone they were 12+ layers held for
   nothing. */
@media (hover: hover) and (pointer: fine) {
  .btn { will-change: transform; }
  /* app.js writes the offset straight onto the element on pointermove and
     clears it on leave; this is what eases both. A tween per pointermove was
     re-interpolating a value that the next frame's move would replace. */
  .magnetic { will-change: transform; transition: transform .5s var(--ease); }
  .magnetic:not(:hover) { transition: transform .7s cubic-bezier(.18, 1.4, .4, 1); }
  .tilt { transition: transform .5s var(--ease); }
  .tilt:not(:hover) { transition: transform .8s var(--ease); }
}

/* ==========================================================
   SECTION HEADS
   ========================================================== */
.section-head { margin-bottom: 60px; }
.section-head h2 { font-size: clamp(34px, 5.4vw, 66px); }
/* These headings sit in a narrow grid column, so they need their own ceiling
   or long words overflow the mask and get clipped. */
.coverage .section-head h2,
.contact .section-head h2 { font-size: clamp(30px, 3.6vw, 50px); }

/* ==========================================================
   HEADER
   ========================================================== */
.site-header {
  position: fixed; inset: 2px 0 auto 0; z-index: 150;
  transition: background .4s var(--ease), backdrop-filter .4s, border-color .4s;
  border-bottom: 1px solid transparent;
}
.site-header.scrolled {
  background: rgba(5,5,5,.72);
  backdrop-filter: blur(18px);
  -webkit-backdrop-filter: blur(18px);
  border-bottom-color: rgba(255,255,255,.09);
}
.header-inner {
  max-width: 1400px; margin: 0 auto;
  height: var(--header-h); padding: 0 28px;
  display: flex; align-items: center; gap: 30px;
}

.brand { display: flex; align-items: center; gap: 12px; text-decoration: none; color: var(--white); }
.brand-mark {
  font-weight: 800; font-size: 26px; letter-spacing: .02em;
  color: var(--black); background: var(--white);
  border: 3px solid var(--black); outline: 2px solid var(--white);
  padding: 1px 8px; line-height: 1.25;
}
.brand-mark.sm { font-size: 20px; padding: 0 6px; }
.brand-name { font-weight: 700; font-size: 11px; letter-spacing: .16em; line-height: 1.3; }

.main-nav { display: flex; align-items: center; gap: 22px; margin-left: auto; }
/* The roll-up needs height and line-height locked to the same value, or the
   single visible line is clipped by its own overflow box. The yellow duplicate
   is taken out of flow so translateY(-100%) advances exactly one line. */
.main-nav a:not(.btn) {
  position: relative;
  color: var(--white); text-decoration: none;
  font-weight: 500; text-transform: uppercase;
  font-size: 12px; letter-spacing: .11em;
  white-space: nowrap;
  display: block;
  line-height: 1.5;
  height: 1.5em;
  overflow: hidden;
}
.main-nav a:not(.btn) span {
  display: block;
  transition: transform .45s var(--ease);
}
.main-nav a:not(.btn) span::after {
  content: attr(data-t);
  position: absolute; left: 0; top: 100%;
  color: var(--yellow);
}
.main-nav a:not(.btn):hover span { transform: translateY(-100%); }
.nav-cta { font-size: 12px; padding: 12px 24px 12px 17px; white-space: nowrap; }
/* The page you are on reads yellow — aria-current was being set with nothing
   rendering for sighted users. */
.main-nav a[aria-current="page"] { color: var(--yellow); }

.header-phone {
  display: flex; align-items: center; gap: 8px;
  color: var(--yellow); text-decoration: none;
  font-weight: 700; font-size: 16px; letter-spacing: .04em;
  white-space: nowrap;
}
.nav-toggle { display: none; }
.nav-foot { display: none; }   /* only surfaces inside the overlay menu */

/* ==========================================================
   SOLUTIONS MEGA MENU
   ========================================================== */
/* The panel anchors to .header-inner rather than to the nav item itself:
   four columns hung off a 70px-wide label would run straight past the
   right edge of the viewport. This keeps it on the page grid instead. */
.header-inner { position: relative; }

.nav-item { display: flex; align-items: center; gap: 5px; }

.mega-caret {
  display: inline-flex; align-items: center; justify-content: center;
  width: 22px; height: 22px; padding: 0;
  background: none; border: none; cursor: pointer;
  color: rgba(255,255,255,.65);
  transition: color .3s var(--ease), transform .35s var(--ease);
}
.mega-caret:hover { color: var(--yellow); }
.nav-item.is-open > .mega-caret { color: var(--yellow); transform: rotate(180deg); }

/* No gap between the header edge and the panel — a dead strip here is what
   makes menus feel like they snatch themselves away mid-approach. */
.mega {
  position: absolute;
  top: 100%; left: 28px; right: 28px;
  z-index: 5;
  opacity: 0; visibility: hidden;
  transform: translateY(-10px);
  /* visibility is discrete, so it is held until the fade finishes on close and
     flipped instantly on open — otherwise the panel is still computed hidden
     when ArrowDown tries to focus its first link, and focus silently fails. */
  transition: opacity .26s var(--ease), transform .26s var(--ease), visibility 0s linear .26s;
}
.nav-item.is-open .mega {
  opacity: 1; visibility: visible; transform: none;
  transition: opacity .26s var(--ease), transform .26s var(--ease), visibility 0s;
}

/* The shadow lives on the unclipped parent: an outer box-shadow on the clipped
   panel itself would be clipped away to nothing. */
.mega { filter: drop-shadow(0 26px 44px rgba(0,0,0,.55)); }
.mega-inner {
  background: rgba(11,11,11,.97);
  backdrop-filter: blur(22px);
  -webkit-backdrop-filter: blur(22px);
  border: 1px solid rgba(255,255,255,.12);
  border-top: 3px solid var(--yellow);
  clip-path: polygon(0 0, 100% 0, 100% calc(100% - var(--notch)), calc(100% - var(--notch)) 100%, 0 100%);
  padding: 24px 34px 34px;
}

.main-nav .mega .mega-all {
  display: inline-flex; align-items: center; gap: 10px;
  height: auto; overflow: visible;
  font-weight: 700; text-transform: uppercase;
  font-size: 12px; letter-spacing: .1em;
  color: var(--yellow); text-decoration: none;
  padding: 0 0 15px;
  margin-bottom: 22px;
  border-bottom: 1px solid rgba(255,255,255,.12);
  width: 100%;
  transition: gap .3s var(--ease);
}
.main-nav .mega .mega-all:hover { gap: 16px; }
.main-nav .mega .mega-all span { transition: none; }
.main-nav .mega .mega-all span::after { content: none; }
.main-nav .mega .mega-all:hover span { transform: none; }

.mega-cols { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px 34px; }

/* Industries has no sub-groups, so its six links are grid items directly rather
   than sitting inside .mega-col stacks. */

/* Above the mobile breakpoint that flat list becomes an ordinary vertical
   dropdown: one stacked column, sized to its own content and hung off the
   label, rather than the full-width sheet Solutions needs for four grouped
   columns. Narrow enough (~270px) to anchor to the nav item at every desktop
   width — a single horizontal row was ~820px and ran off the right of the
   viewport at 1280 and below, which is what ruled that layout out.
   Desktop only: under 1151px the nav is the stacked overlay, which already
   renders .mega-cols as one column and positions the panel in flow.
   The rows deliberately keep their inherited styling, so the dividers and the
   hover slide match the Solutions columns exactly. */
@media (min-width: 1151px) {
  .nav-item.nav-compact { position: relative; }
  /* Offsets only, never transform — .mega animates `transform` for its reveal,
     so a translate used for positioning would fight the open/close tween. */
  .nav-item.nav-compact > .mega { left: 0; right: auto; width: max-content; }
  .nav-compact .mega-cols { grid-template-columns: 1fr; gap: 0; }
}

.mega-head {
  font-weight: 700; text-transform: uppercase;
  font-size: 11px; letter-spacing: .2em;
  /* Not var(--gray): #767676 on this panel lands near 4.3:1, just under the
     4.5:1 AA floor for text this size. This sits around 6.8:1. */
  color: rgba(255,255,255,.58);
  padding-left: 26px; position: relative;
  margin-bottom: 10px;
}
.mega-head::before {
  content: ""; position: absolute; left: 0; top: 50%;
  width: 18px; height: 3px; background: var(--yellow);
  transform: translateY(-50%) skewX(calc(-1 * var(--cut)));
}

/* Undo the header link roll-up for panel links: it locks height to 1.5em
   and hides overflow, which would clip these two-line rows. */
.main-nav .mega-cols a {
  display: flex; align-items: center; gap: 11px;
  height: auto; overflow: visible;
  line-height: 1.3;
  font-size: 14px; font-weight: 500;
  text-transform: none; letter-spacing: .01em;
  color: rgba(255,255,255,.82);
  padding: 10px 6px 10px 0;
  border-bottom: 1px solid rgba(255,255,255,.07);
  transition: color .25s var(--ease), padding-left .25s var(--ease), border-color .25s;
}
.main-nav .mega-cols a span { transition: none; }
.main-nav .mega-cols a span::after { content: none; }
.main-nav .mega-cols a:hover span { transform: none; }
.main-nav .mega-cols a:hover,
.main-nav .mega-cols a:focus-visible {
  color: var(--yellow);
  padding-left: 9px;
  border-bottom-color: rgba(254,194,2,.35);
}
.mega-cols a .icon { width: 21px; height: 21px; flex-shrink: 0; }

/* ==========================================================
   HERO
   ========================================================== */
.hero {
  min-height: 100svh;
  display: flex; align-items: center;
}
/* Keeps the headline legible where it crosses the 3D geometry. The stops carry
   further right than a text-only hero needs: the gear cluster sits at x=+6/+14
   in world.js, directly behind the video panel, and at full brightness the two
   fight for the same eye. Holding the right side at ~.4 leaves the gears
   readable as background without competing with the footage. */
.hero::before {
  content: ""; position: absolute; inset: 0; z-index: 0; pointer-events: none;
  background: linear-gradient(100deg, rgba(5,5,5,.95) 0%, rgba(5,5,5,.87) 36%, rgba(5,5,5,.58) 62%, rgba(5,5,5,.42) 100%);
}
.hero-content { position: relative; z-index: 1; }
.hero-content {
  max-width: 1240px; margin: 0 auto;
  padding: calc(var(--header-h) + 52px) 28px 118px;
  width: 100%;
  display: grid;
  /* Two constraints fight over this row and both live in the copy column: the
     headline ("WHEN PRODUCTION" needs a fixed width per font size) and the CTA
     pair, which measures 588px and drops to two lines below that. 1.055 is the
     most the panel can take while the buttons still share a line at 1440. Push
     the panel wider and the CTAs stack — that is the trade, not a bug. */
  grid-template-columns: minmax(0, 1.055fr) minmax(0, .945fr);
  align-items: center;
  gap: clamp(28px, 3vw, 48px);
}

/* ---- hero video panel ----
   Decorative b-roll, so it is aria-hidden in the markup and takes no focus.
   The frame carries the house corner notch and the yellow top rule so it reads
   as part of the card system rather than a dropped-in embed. */
.hero-media { position: relative; }
.hero-video-frame {
  position: relative;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  background: var(--charcoal);
  /* house notch, bottom-right */
  clip-path: polygon(0 0, 100% 0, 100% calc(100% - var(--notch)), calc(100% - var(--notch)) 100%, 0 100%);
}
.hero-video {
  display: block;
  width: 100%; height: 100%;
  object-fit: cover;
  /* The footage is shot under bright plant lighting and would glare against a
     #050505 page. This is a knock-back, not the duotone treatment .hero-photo
     uses — the panel is the subject here, so it keeps its colour. */
  filter: saturate(.9) contrast(1.05) brightness(.9);
}
/* beds the lower edge into the page so the panel does not read as a sticker */
.hero-video-frame::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background:
    linear-gradient(180deg, transparent 62%, rgba(5,5,5,.42) 100%),
    linear-gradient(90deg, rgba(5,5,5,.34) 0%, transparent 22%);
}
/* thin yellow rule echoing the angled cut, sitting under the notch */
.hero-video-edge {
  position: absolute; right: 0; bottom: 0; z-index: 2;
  width: calc(var(--notch) * 2.4); height: 3px;
  background: var(--yellow);
  clip-path: polygon(0 0, 100% 0, 100% 100%, var(--notch) 100%);
  pointer-events: none;
}
.hero-eyebrow {
  display: inline-flex; align-items: center; gap: 11px;
  font-weight: 500; text-transform: uppercase;
  letter-spacing: .2em; font-size: 12px;
  color: rgba(255,255,255,.88);
  border: 1px solid rgba(255,255,255,.22);
  background: rgba(5,5,5,.35);
  backdrop-filter: blur(8px);
  padding: 10px 20px;
  clip-path: polygon(0 0, 100% 0, calc(100% - 11px) 100%, 0 100%);
  margin-bottom: 32px;
}
.pulse-dot {
  width: 9px; height: 9px; border-radius: 50%; background: var(--yellow);
  box-shadow: 0 0 0 0 rgba(254,194,2,.6);
  animation: pulse 2s infinite;
}
@keyframes pulse {
  0% { box-shadow: 0 0 0 0 rgba(254,194,2,.55); }
  70% { box-shadow: 0 0 0 13px rgba(254,194,2,0); }
  100% { box-shadow: 0 0 0 0 rgba(254,194,2,0); }
}

/* Sized to the text column, not the viewport: the copy now shares the hero with
   a 16:9 panel, so the ceiling is what "WHEN PRODUCTION" can occupy at ~575px
   without wrapping, and the vw term is tuned to hit that before the cap. */
.hero-title {
  font-size: clamp(34px, 4.45vw, 64px);
  font-weight: 800;
  margin-bottom: 24px;
  text-shadow: 0 8px 60px rgba(0,0,0,.55);
}
.hero-title .line { display: block; }

.hero-sub {
  font-size: clamp(16px, 1.35vw, 19px);
  color: rgba(255,255,255,.85);
  max-width: 540px; margin-bottom: 34px;
  text-shadow: 0 4px 30px rgba(0,0,0,.6);
}
.hero-ctas { display: flex; gap: 20px; flex-wrap: wrap; }

.hero-scroll {
  position: absolute; left: 50%; bottom: 34px;
  width: 26px; height: 44px;
  border: 2px solid rgba(255,255,255,.3); border-radius: 14px;
  transform: translateX(-50%);
}
.hero-scroll span {
  position: absolute; left: 50%; top: 8px;
  width: 4px; height: 9px; border-radius: 2px; background: var(--yellow);
  transform: translateX(-50%);
  animation: scrolldot 1.8s infinite;
}
@keyframes scrolldot {
  0% { top: 8px; opacity: 1; }
  70% { top: 25px; opacity: 0; }
  100% { top: 8px; opacity: 0; }
}

/* ==========================================================
   MARQUEE
   ========================================================== */
/* The tilted band is deliberately wider than the viewport; the outer section
   clips it so it never adds horizontal page scroll. */
.marquee-band {
  position: relative; z-index: 2;
  overflow: hidden;
  padding: 24px 0;
}
.marquee-tilt {
  background: var(--yellow); color: var(--black);
  padding: 16px 0;
  width: 108%; margin-left: -4%;
  transform: rotate(-1.2deg);
  border-top: 2px solid var(--black); border-bottom: 2px solid var(--black);
}
.marquee { overflow: hidden; }
.marquee-track {
  display: flex; align-items: center; gap: 30px;
  width: max-content; will-change: transform;
}
.marquee-track span {
  font-weight: 800; text-transform: uppercase;
  font-size: clamp(20px, 2.4vw, 32px); letter-spacing: .02em;
  white-space: nowrap;
}
.marquee-track i { font-style: normal; font-size: 13px; opacity: .5; }

/* ==========================================================
   TRUST + STATS
   ========================================================== */
.trustbar { padding: 90px 0; }
.trust-list {
  list-style: none;
  display: grid; grid-template-columns: repeat(5, 1fr); gap: 20px;
  padding-bottom: 64px;
  border-bottom: 1px solid rgba(255,255,255,.1);
}
.trust-list li {
  display: flex; flex-direction: column; align-items: center; gap: 14px;
  text-align: center;
  font-weight: 700; text-transform: uppercase;
  font-size: 13px; letter-spacing: .1em;
  color: rgba(255,255,255,.9);
}
.trust-list .icon { width: 44px; height: 44px; color: var(--white); }

.stats {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 24px;
  padding-top: 56px;
}
.stat b {
  display: block;
  font-size: clamp(42px, 5vw, 68px); font-weight: 800;
  color: var(--yellow); line-height: 1;
  font-variant-numeric: tabular-nums;
}
.stat span {
  display: block; margin-top: 10px;
  font-weight: 500; text-transform: uppercase;
  letter-spacing: .14em; font-size: 11px; color: var(--gray);
}

/* ==========================================================
   DIVISIONS
   ========================================================== */
.segments { padding: 130px 0 150px; }
.segment-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 30px; perspective: 1200px; }
.segment-card {
  position: relative;
  transition: transform .5s var(--ease);
  background: rgba(20,20,20,.55);
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
  border: 1px solid rgba(255,255,255,.12);
  border-top: 3px solid var(--yellow);
  padding: 40px 32px 36px;
  clip-path: polygon(0 0, calc(100% - var(--notch)) 0, 100% var(--notch), 100% 100%, 0 100%);
  display: flex; flex-direction: column;
  transform-style: preserve-3d;
  overflow: hidden;
}
.card-sheen {
  position: absolute; inset: -40%;
  background: radial-gradient(circle at center, rgba(254,194,2,.16), transparent 55%);
  opacity: 0; pointer-events: none;
  transition: opacity .4s var(--ease), transform .4s var(--ease);
}
/* Photo header. The card owns its padding and a clipped corner notch, so the
   shot bleeds back out to the card edge with negative margins rather than by
   restructuring the card. aspect-ratio holds the box before the file lands, so
   the four cards cannot shift height against each other while loading. */
.seg-shot {
  margin: -40px -32px 26px;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  border-bottom: 1px solid rgba(255,255,255,.1);
  background: rgba(255,255,255,.04);
}
.seg-shot img {
  width: 100%; height: 100%;
  object-fit: cover; display: block;
  /* Held back from full colour so four different stock shoots read as one set
     against the black page, and so the yellow stays the loudest thing on it. */
  filter: grayscale(.4) contrast(1.06) brightness(.92);
  transition: filter .5s var(--ease), transform .8s var(--ease);
}
.segment-card:hover .seg-shot img,
.segment-card:focus-within .seg-shot img {
  filter: none;
  transform: scale(1.045);
}

/* Sits over the photo now rather than over the card, so it needs to carry its
   own contrast instead of borrowing the card's flat background. */
.seg-index {
  /* z-index is load-bearing: .segment-card is transform-style: preserve-3d for
     the tilt, and inside a 3D rendering context children paint by depth rather
     than by the usual positioned-above-in-flow rule — so without it the photo
     paints over this number instead of under it. */
  position: absolute; z-index: 2; top: 14px; right: 26px;
  font-weight: 800; font-size: 40px; line-height: 1;
  color: rgba(255,255,255,.9);
  text-shadow: 0 2px 14px rgba(0,0,0,.8);
}
.seg-logo {
  display: flex; align-items: center; gap: 10px;
  font-weight: 800; font-size: 24px; margin-bottom: 10px;
}
.seg-logo .brand-mark { outline-color: transparent; }
.seg-tag { color: var(--gray); font-weight: 500; margin-bottom: 24px; min-height: 46px; }
.segment-card ul { list-style: none; margin-bottom: 32px; flex: 1; }
.segment-card li {
  padding: 10px 0 10px 28px;
  border-bottom: 1px solid rgba(255,255,255,.08);
  font-weight: 500; position: relative;
  transition: padding-left .3s var(--ease), color .3s;
}
.segment-card li::before {
  content: ""; position: absolute; left: 2px; top: 50%;
  width: 13px; height: 3px; background: var(--yellow);
  transform: translateY(-50%) skewX(calc(-1 * var(--cut)));
  transition: width .3s var(--ease);
}
.segment-card li:hover { padding-left: 34px; color: var(--yellow); }
.segment-card li:hover::before { width: 20px; }

/* ==========================================================
   MOTION
   Every from-state here is gated behind .will-reveal, and .will-reveal is only
   ever added by js/motion.js, per element, and only to elements that were
   already below the fold when it ran. Nothing in this stylesheet can hide
   content on its own — if the JS never runs, none of these rules match and the
   page is simply visible. That is the whole point of the arrangement: the GSAP
   version it replaces hid its targets the moment a tween was created, which
   meant any renderer that did not scroll saw a blank page below the fold.

   The transition lives on the .is-in rule rather than on .will-reveal. Adding
   a class whose after-change style carries a transition is what starts it, so
   this animates in and never animates out.
   ========================================================== */
.will-reveal { opacity: 0; will-change: opacity, transform; }
[data-anim="fade-up"].will-reveal { transform: translateY(40px); }
[data-anim="tile"].will-reveal    { transform: translateY(40px); }
[data-anim="card"].will-reveal    { transform: translateY(70px) scale(.96); }

.will-reveal.is-in {
  opacity: 1;
  transform: none;
  transition:
    opacity .8s var(--ease) var(--d, 0s),
    transform .9s var(--ease) var(--d, 0s);
}
[data-anim="card"].will-reveal.is-in { transition-duration: .9s, 1.1s; }

/* Masked line and word reveals. The heading carries .will-reveal; the pieces
   inside it are what actually move, staggered off --i, which JS stamps once
   per line when it splits the text. */
[data-split].will-reveal { opacity: 1; }          /* the mask does the hiding */
[data-split].will-reveal .line-inner,
[data-split].will-reveal .word { transform: translateY(110%); }
[data-split].will-reveal .word { opacity: 0; }

[data-split].will-reveal.is-in .line-inner {
  transform: none;
  transition: transform 1s var(--ease) calc(var(--i, 0) * 90ms);
}
[data-split].will-reveal.is-in .word {
  transform: none;
  opacity: 1;
  transition:
    transform .8s var(--ease) calc(var(--i, 0) * 25ms),
    opacity .5s linear calc(var(--i, 0) * 25ms);
}

/* ---- hero entrance ----
   body.loaded is added at DOMContentLoaded. Until then the header sits above
   the fold line and the hero copy is offset; after it, both settle. These are
   transitions on the loaded state, not from-states on the element, so a page
   that never gets the class still renders everything in place. */
.site-header { transition: transform .8s var(--ease); }
body:not(.loaded) .site-header { transform: translateY(-100%); }

@media (min-width: 821px) {
  body:not(.loaded) .hero-eyebrow,
  body:not(.loaded) .hero-sub,
  body:not(.loaded) .hero-ctas > *,
  body:not(.loaded) .hero-media { opacity: 0; transform: translateY(26px); }
  body:not(.loaded) .hero-scroll { opacity: 0; }

  .hero-eyebrow, .hero-sub, .hero-ctas > *, .hero-media, .hero-scroll {
    transition: opacity .7s var(--ease), transform .75s var(--ease);
  }
  .hero-eyebrow { transition-delay: .12s; }
  .hero-media   { transition-delay: .18s; }
  .hero-sub     { transition-delay: .26s; }
  .hero-ctas > *:nth-child(1) { transition-delay: .34s; }
  .hero-ctas > *:nth-child(2) { transition-delay: .42s; }
  .hero-scroll  { transition-delay: .6s; }
}

/* The headline is split into characters and each one rises out of its mask.
   body.hero-anim is only added once the split has actually happened, so the
   characters are never hidden before they exist. */
body.hero-anim .hero-title .char { display: inline-block; transform: translateY(115%); }
body.hero-anim.loaded .hero-title .char {
  transform: none;
  transition: transform .9s cubic-bezier(.16, 1, .3, 1) var(--d, 0s);
}

/* ---- process track: native sticky pin ----
   .is-pinned is added by JS on desktop only. --track is the horizontal travel
   plus a lead-out, so the section is that much taller than the viewport and
   there is scroll distance to spend on moving the track sideways. The old
   version used ScrollTrigger's pin, which wraps the element in a generated
   spacer and rewrites its position on every refresh; sticky does it in the
   compositor with no DOM surgery. */
/* .process-spacer, not padding-bottom on the section: a sticky box is clamped
   to its containing block's content box, and padding sits outside that, so the
   padding version gave the pin zero travel and it released immediately. The
   spacer is content, so the content box grows with it. --track is the
   horizontal travel plus a lead-out, written by app.js. */
.process-spacer { height: 0; }
.process.is-pinned .process-spacer { height: var(--track, 0px); }
.process.is-pinned .process-pin { position: sticky; top: 0; }


/* ---- one-off UI transitions ----
   Class-driven so nothing needs a tweening library to run them. */
@keyframes pli-shake {
  0%, 100% { transform: translateX(0); }
  20% { transform: translateX(-6px); }
  45% { transform: translateX(4px); }
  70% { transform: translateX(-2px); }
}
.is-shaking { animation: pli-shake .5s var(--ease); }

@keyframes pli-enter {
  from { opacity: 0; transform: translateY(14px); }
  to   { opacity: 1; transform: none; }
}
.is-entering { animation: pli-enter .5s var(--ease); }

@keyframes pli-swap {
  from { opacity: .35; transform: translateX(-8px); }
  to   { opacity: 1; transform: none; }
}
.is-swapped { animation: pli-swap .4s var(--ease); }

@media (prefers-reduced-motion: reduce) {
  .will-reveal,
  .will-reveal .line-inner,
  .will-reveal .word { opacity: 1 !important; transform: none !important; }
  .site-header,
  body:not(.loaded) .hero-eyebrow,
  body:not(.loaded) .hero-sub,
  body:not(.loaded) .hero-ctas > *,
  body:not(.loaded) .hero-media,
  body:not(.loaded) .hero-scroll { opacity: 1; transform: none; transition: none; }
  .is-shaking, .is-entering, .is-swapped { animation: none; }
}

/* ==========================================================
   CREDITS PAGE
   A licence record, so it is built to be read and checked rather than
   scrolled past: one column, no reveal animation, no photographs of its own.
   ========================================================== */
.credits-wrap { max-width: 860px; }
.credits-note { margin-bottom: 62px; }
.credits-note h2,
.credits-group h2 {
  font-size: clamp(24px, 3vw, 34px);
  margin-bottom: 18px;
}
.credits-note p { margin-bottom: 14px; }
.credits-note p:last-child { margin-bottom: 0; }
.credits-group { margin-bottom: 62px; }
.credits-lede { margin-bottom: 26px; max-width: 68ch; }

.credit-list { list-style: none; margin: 0; padding: 0; counter-reset: credit; }
.credit {
  counter-increment: credit;
  position: relative;
  padding: 20px 0 20px 46px;
  border-top: 1px solid rgba(0,0,0,.12);
}
.credit:last-child { border-bottom: 1px solid rgba(0,0,0,.12); }
.credit::before {
  content: counter(credit, decimal-leading-zero);
  position: absolute; left: 0; top: 22px;
  font-weight: 700; font-size: 13px; letter-spacing: .1em;
  color: var(--yellow);
}
.credit-shows { font-weight: 500; margin-bottom: 6px; }
.credit-orig { font-size: 14px; opacity: .72; margin-bottom: 6px; }
.credit-meta {
  display: flex; flex-wrap: wrap; gap: 6px 18px;
  font-size: 13px; letter-spacing: .04em;
  margin-bottom: 4px;
}
.credit-src { font-weight: 700; text-transform: uppercase; }
.credit-lic { opacity: .8; }
/* The licence links are the point of the page, so they are legible and on-brand
   rather than left at the browser default blue, which is the one colour that
   appears nowhere else on this site. */
.credits-wrap a {
  color: inherit;
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-color: var(--yellow);
  text-decoration-thickness: 2px;
}
.credits-wrap a:hover { color: var(--yellow); }
/* The filename is the join between this page and the file on disk, so it is
   set apart as a literal rather than styled as prose. */
.credit-file {
  font-family: ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace;
  font-size: 12px; opacity: .6;
  margin-left: auto;
}
.credit-used { font-size: 13px; opacity: .66; }
/* The "changed for this site" line is a licence condition on the CC BY images,
   not a footnote, so it is set apart rather than blended into the metadata. */
.credit-mod {
  font-size: 13px;
  margin-top: 8px;
  padding-left: 12px;
  border-left: 2px solid var(--yellow);
}

@media (max-width: 620px) {
  .credit { padding-left: 38px; }
  .credit-file { margin-left: 0; width: 100%; }
}

/* ==========================================================
   SECTION BAND
   A wide photograph between a section head and its content, to break up the
   long icon-tile runs. Borrowed from the pattern on mechfin.com, which uses it
   for the same reason: a page of tiles reads as a diagram, not as a plant.

   Built at 2.2:1 and dropped to 16:9 below 720px, which centre-crops the sides
   — so the subject has to sit inside the middle 80% of the frame. See
   build-bands.py, where the crops are generated.
   ========================================================== */
/* The figure is now just a block with a margin. Everything that used to make
   it a fixed, clipped picture box moved to .band-media, so the caption can sit
   under the photograph instead of being clipped away by the figure's own
   overflow: hidden. */
.band { position: relative; margin: 0 0 clamp(38px, 5vw, 62px); width: 100%; }
.band-media {
  position: relative;
  width: 100%;
  /* An explicit height, not aspect-ratio + max-height. Those two fight: once
     max-height clamps, aspect-ratio solves for width instead, and the band
     silently shrinks to 2.2 x its capped height (880px inside a 1240px
     container). A fixed height reserves its own box just as well for CLS. */
  height: clamp(190px, 26vw, 380px);
  overflow: hidden;
  border-left: 3px solid var(--yellow);
  clip-path: polygon(0 0, calc(100% - var(--notch)) 0, 100% var(--notch), 100% 100%, 0 100%);
  background: rgba(255,255,255,.04);
}
/* Held back from full colour so photographs from four different shoots read as
   one set, and so the yellow stays the loudest thing on the page. No scrim over
   the top: half these bands sit on the light panels, where a dark gradient on
   the edges reads as a vignette rather than as depth. */
.band img {
  width: 100%; height: 100%;
  object-fit: cover; display: block;
  filter: grayscale(.3) contrast(1.04);
}

/* ==========================================================
   PROBLEMS
   ========================================================== */
.problems { background: var(--charcoal); padding: 130px 0 150px; }
.problem-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 20px; }
.problem-tile {
  color: inherit; text-decoration: none;
  background: rgba(255,255,255,.04);
  border: 1px solid rgba(255,255,255,.09);
  padding: 34px 22px;
  display: flex; flex-direction: column; align-items: center; gap: 16px;
  text-align: center;
  font-weight: 700; text-transform: uppercase;
  font-size: 13px; letter-spacing: .08em;
  clip-path: polygon(0 0, calc(100% - 20px) 0, 100% 20px, 100% 100%, 0 100%);
  transition: background .35s, border-color .35s, transform .35s var(--ease);
}
.problem-tile:hover {
  background: rgba(254,194,2,.09);
  border-color: rgba(254,194,2,.5);
  transform: translateY(-7px);
}
.problem-tile .icon { width: 44px; height: 44px; color: var(--white); transition: transform .35s var(--ease); }
.problem-tile:hover .icon { transform: scale(1.12); }

/* ==========================================================
   ABOUT STATEMENT
   ========================================================== */
.about-band { padding: 150px 0; }
.about-wrap {
  display: grid;
  /* A 9:16 short is the opposite shape to the copy beside it: capped at 320px
     it stands ~570px tall, which is roughly phone-sized on a desktop and reads
     as the format it is. A free fr track would have blown it up past 900px. */
  grid-template-columns: minmax(0, 1fr) minmax(0, 320px);
  gap: clamp(36px, 4.5vw, 76px);
  align-items: center;
}
.about-statement {
  /* Was clamp(28px, 4.6vw, 58px) as a full-width statement. It now shares the
     row with the video panel, so the ceiling comes down. */
  font-size: clamp(24px, 2.9vw, 40px);
  font-weight: 800; text-transform: uppercase;
  line-height: 1.14;
  text-shadow: 0 8px 50px rgba(0,0,0,.6);
}
/* the requested break between the two sentences */
.about-statement + .about-statement { margin-top: .7em; }

/* ---- YouTube short ----
   No corner notch on this one, unlike the sibling media panels: the house cut
   takes a 28px bite out of the bottom-right, which is exactly where YouTube
   parks its fullscreen button. Brand shape is not worth eating a control. */
/* The cap lives on the element as well as the track, because once the grid
   collapses to one column the track is full width and a 9:16 box at 712px wide
   would stand over 1200px tall. */
.about-media { justify-self: end; width: 100%; max-width: 320px; }
.about-short {
  position: relative;
  width: 100%;
  aspect-ratio: 9 / 16;
  overflow: hidden;
  background: var(--charcoal);
}
.about-short iframe {
  position: absolute; inset: 0;
  display: block; width: 100%; height: 100%;
  border: 0;
}

/* ---- click-to-play video facade ----
   The poster and the play control are the whole component until someone presses
   it; the YouTube iframe replaces them in place, at the same size, so nothing
   below shifts when it loads. */
.video-facade {
  display: block;
  padding: 0;
  border: 0;
  cursor: pointer;
  color: var(--white);
  background: var(--charcoal);
  text-align: left;
  font: inherit;
}
.video-facade img {
  width: 100%; height: 100%;
  object-fit: cover;
  display: block;
  transition: transform .6s var(--ease), filter .4s var(--ease);
}
.video-facade::after {
  /* a light scrim so the play control and the caption hold against any frame */
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(180deg, rgba(5,5,5,.18) 0%, rgba(5,5,5,.05) 45%, rgba(5,5,5,.72) 100%);
}
.video-play {
  position: absolute; z-index: 2;
  top: 50%; left: 50%; transform: translate(-50%, -50%);
  width: 68px; height: 68px;
  display: grid; place-items: center;
  background: var(--yellow);
  color: var(--black);
  clip-path: polygon(0 0, calc(100% - 14px) 0, 100% 14px, 100% 100%, 0 100%);
  transition: transform .35s var(--ease), background .3s;
}
.video-play svg { width: 34px; height: 34px; }
.video-cap {
  position: absolute; z-index: 2; left: 0; right: 0; bottom: 0;
  padding: 16px 18px;
  font-weight: 700; text-transform: uppercase;
  font-size: 11px; letter-spacing: .1em; line-height: 1.45;
  color: rgba(255,255,255,.9);
}
.video-facade:hover img { transform: scale(1.04); }
.video-facade:hover .video-play { transform: translate(-50%, -50%) scale(1.08); }
.video-facade:focus-visible { outline: 3px solid var(--yellow); outline-offset: 3px; }

@media (prefers-reduced-motion: reduce) {
  .video-facade img, .video-play { transition: none; }
  .video-facade:hover img { transform: none; }
  .video-facade:hover .video-play { transform: translate(-50%, -50%); }
}

/* ---- band caption ----
   Sits under the photograph rather than over it. These bands carry licensed
   stock of other plants, so the caption says what the picture actually shows
   and how it relates to the section, instead of leaving the reader to assume
   it is a PLI facility. /credits/ carries the licensing. */
.band-cap {
  display: flex; align-items: baseline; gap: 12px;
  margin-top: 14px;
  font-size: 13px; line-height: 1.5;
  /* inherit rather than a fixed white: these bands sit in sections that set
     their own colour (.solutions is white-on-black-text, .industries is light,
     .problems is charcoal), and a hard-coded rgba(255,255,255,.6) was invisible
     on two of the three. opacity carries the same knock-back either way. */
  color: inherit;
  opacity: .62;
}
.band-cap::before {
  content: ""; flex: none; width: 18px; height: 3px;
  margin-top: .45em;
  background: var(--yellow);
  transform: skewX(calc(-1 * var(--cut)));
}
@media (max-width: 700px) { .band-cap { font-size: 12px; margin-top: 12px; } }

/* ---- section background photography ----
   One pattern, four sections. The shot is desaturated and knocked back, and a
   scrim sits over it, so type over the top is legible by construction rather
   than by luck with a given photograph. Each section sets its own scrim shape
   because the copy sits in a different place in each one.

   --bg-dim lifts a flat or mid-tone shot that would otherwise go to mud. It is
   safe to raise: the scrim is doing most of the work. Measure after changing it.

   Every one of these images is lazy and below the fold, so none of them is on
   the LCP path. */
.has-bg { position: relative; isolation: isolate; background: var(--black); }
.bg-photo { position: absolute; inset: 0; z-index: 0; overflow: hidden; }
.bg-photo img {
  width: 100%; height: 100%; object-fit: cover;
  object-position: var(--bg-pos, 50% 50%);
  filter: grayscale(1) contrast(1.05) brightness(var(--bg-dim, .42));
}
.bg-photo::after {
  content: ""; position: absolute; inset: 0;
  background: var(--bg-scrim, linear-gradient(180deg, rgba(5,5,5,.88), rgba(5,5,5,.8)));
}
/* everything the section actually renders sits above the photo layer */
.has-bg > .container,
.has-bg > .wrap,
.has-bg > *:not(.bg-photo) { position: relative; z-index: 1; }

/* the about statement is the largest type on the page and sits directly on the
   photograph, so its scrim is the heaviest and thins only toward the video */
.about-band.has-bg {
  --bg-pos: 50% 46%;
  --bg-scrim: linear-gradient(100deg,
    rgba(5,5,5,.93) 0%, rgba(5,5,5,.88) 42%, rgba(5,5,5,.62) 74%, rgba(5,5,5,.5) 100%);
}
.about-band.has-bg .about-statement { text-shadow: 0 6px 34px rgba(0,0,0,.85); }

/* The trust row and the stat figures run the full width, so this scrim is even.
   Both are large bold type on a dark ground with a lot of contrast headroom, so
   the photograph is allowed to read here rather than sitting as texture: at .38
   and a .9 scrim the hard hats were indistinguishable from black. */
.trustbar.has-bg {
  --bg-dim: .62;
  --bg-pos: 50% 42%;
  --bg-scrim: linear-gradient(180deg, rgba(5,5,5,.82) 0%, rgba(5,5,5,.72) 50%, rgba(5,5,5,.84) 100%);
}

/* the process cards already carry their own blurred panel, so the photograph
   behind them can sit a little brighter than the others */
.process-pin.has-bg {
  /* the pin is a flex column, so the absolutely-positioned photo layer is out
     of flow already; the container inside it still needs the z-index lift */
  --bg-dim: .46;
  --bg-scrim: linear-gradient(180deg, rgba(5,5,5,.86) 0%, rgba(5,5,5,.74) 45%, rgba(5,5,5,.86) 100%);
}

/* the contact form panel is translucent over this, so the left half where the
   copy sits is held heavier than the right half under the form */
.contact.has-bg {
  --bg-dim: .46;
  --bg-pos: 50% 44%;
  --bg-scrim: linear-gradient(95deg,
    rgba(5,5,5,.9) 0%, rgba(5,5,5,.85) 45%, rgba(5,5,5,.74) 100%);
}

@media (max-width: 900px) {
  /* stacked, every column runs full width, so the scrims stop thinning sideways */
  .about-band.has-bg, .contact.has-bg {
    --bg-scrim: linear-gradient(180deg, rgba(5,5,5,.9) 0%, rgba(5,5,5,.86) 55%, rgba(5,5,5,.82) 100%);
  }
}

/* ==========================================================
   SOLUTIONS
   ========================================================== */
.solutions { background: var(--white); color: var(--black); padding: 130px 0 150px; }
.solution-grid { display: grid; grid-template-columns: repeat(5, 1fr); gap: 18px; }
/* Eleven services, laid out 5 over 6 — the top row is the quality group, the
   bottom row is staffing, production and logistics, matching how the nav
   splits them. 30 columns is the lowest common multiple of 5 and 6, so a
   6-column card times five and a 5-column card times six both fill the row
   exactly; the gaps between tracks are absorbed into the spans, so the visible
   gutter stays 18px on both rows. Scoped above the first breakpoint — below it
   the grid drops to 4, 3 and 2 up and the spans must not apply. */
@media (min-width: 1151px) {
  .solution-grid { grid-template-columns: repeat(30, 1fr); }
  .solution-grid > * { grid-column: span 6; }
  .solution-grid > :nth-child(n + 6) { grid-column: span 5; }
}
.solution-card {
  position: relative;
  background: var(--white);
  border: 1.5px solid rgba(5,5,5,.12);
  color: var(--black); text-decoration: none;
  padding: 34px 18px 30px;
  display: flex; flex-direction: column; align-items: center; gap: 16px;
  text-align: center;
  font-weight: 700; text-transform: uppercase;
  font-size: 13px; letter-spacing: .06em;
  clip-path: polygon(0 0, calc(100% - 20px) 0, 100% 20px, 100% 100%, 0 100%);
  overflow: hidden;
  transition: transform .35s var(--ease), border-color .35s, color .35s;
}
.solution-card::before {
  content: ""; position: absolute; inset: 0; background: var(--black);
  transform: translateY(101%); transition: transform .45s var(--ease); z-index: 0;
}
.solution-card > * { position: relative; z-index: 1; }
.solution-card:hover { transform: translateY(-8px); border-color: var(--black); color: var(--white); }
.solution-card:hover::before { transform: translateY(0); }
.solution-card .icon { width: 46px; height: 46px; transition: color .35s; }
.solution-card:hover .icon { color: var(--yellow); }
.card-arrow { opacity: 0; transform: translateX(-8px); transition: all .35s var(--ease); color: var(--yellow); }
/* the arrow's <svg> is deliberately not .icon (that class sizes icons at 46px
   in this grid), so it needs the stroke that .icon would otherwise supply —
   without it the path has fill:none and no stroke, and renders as nothing */
.card-arrow svg { stroke: currentColor; fill: none; }
.solution-card:hover .card-arrow { opacity: 1; transform: translateX(0); }

/* ==========================================================
   PROCESS (pinned horizontal)
   ========================================================== */
.process { padding: 0; }
.process-pin {
  min-height: 100svh;
  display: flex; flex-direction: column; justify-content: center;
  padding: clamp(88px, 11vh, 120px) 0;
}
.process-viewport {
  --cols: 3;
  --gap: 30px;
  overflow: hidden;
}
/* Columns are sized off the viewport width, so exactly --cols cards fit and
   none is ever sliced at the edge. A flex track with fixed-px cards left a
   fractional card showing at most widths. */
.process-steps {
  list-style: none;
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: calc((100% - (var(--cols) - 1) * var(--gap)) / var(--cols));
  gap: var(--gap);
  width: 100%;
  will-change: transform;
}
.process-step {
  background: rgba(20,20,20,.5);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  border: 1px solid rgba(255,255,255,.12);
  border-left: 3px solid var(--yellow);
  padding: 34px 30px 36px;
  clip-path: polygon(0 0, calc(100% - 22px) 0, 100% 22px, 100% 100%, 0 100%);
}
.step-num {
  display: inline-flex; align-items: center; justify-content: center;
  width: 56px; height: 56px;
  background: rgba(254,194,2,.1);
  border: 1.5px solid rgba(254,194,2,.4);
  font-weight: 800; font-size: 19px; color: var(--yellow);
  clip-path: polygon(0 0, calc(100% - 13px) 0, 100% 13px, 100% 100%, 0 100%);
  margin-bottom: 22px;
}
.process-step .icon { display: block; width: 42px; height: 42px; margin-bottom: 16px; color: var(--white); }
.process-step h3 { font-size: 22px; margin-bottom: 10px; letter-spacing: .03em; }
.process-step p { font-size: 15px; color: rgba(255,255,255,.62); line-height: 1.55; }

.process-line { margin-top: 48px; height: 3px; background: rgba(255,255,255,.12); overflow: hidden; }
.process-line span { display: block; height: 100%; background: var(--yellow); transform: scaleX(0); transform-origin: left; }

/* Step the visible-card count down so cards never get too narrow to read. */
@media (max-width: 1240px) { .process-viewport { --gap: 24px; } }
@media (max-width: 1100px) { .process-viewport { --cols: 2; } }
@media (max-width: 1100px) and (orientation: landscape) and (max-height: 720px) {
  .process-pin { padding: 74px 0; }
  .process-step p { display: none; }   /* keep the pin inside short landscape viewports */
}

/* ==========================================================
   INDUSTRIES
   ========================================================== */
.industries { background: var(--light); color: var(--black); padding: 130px 0 150px; }

/* PLI Yellow measures ~1.5:1 on these light panels — far under the 3:1 floor
   for display text. On light sections the accent stops being the ink and
   becomes a highlight swash under black type, which keeps the brand cue. */
.solutions .yellow,
/* On a light panel the brand yellow is about 1.6:1 against white, so it cannot
   be used as text there. The old workaround set the word in black and drew a
   yellow highlighter bar under it — which read as an underline turning up at
   arbitrary points inside a heading rather than as emphasis, because the same
   markup rendered two completely different ways depending on the panel behind
   it. Light panels now inherit the heading colour: the accent appears where it
   has the contrast to work, and nowhere else. */
.industries .yellow { color: inherit; }
.industry-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 18px; }
.industry-tile {
  color: inherit; text-decoration: none;
  background: var(--white);
  display: flex; align-items: center; gap: 18px;
  padding: 24px 26px;
  font-weight: 700; text-transform: uppercase;
  font-size: 15px; letter-spacing: .05em;
  clip-path: polygon(0 0, calc(100% - 18px) 0, 100% 18px, 100% 100%, 0 100%);
  border-left: 4px solid var(--black);
  transition: border-color .35s, transform .35s var(--ease), box-shadow .35s;
}
.industry-tile:hover {
  border-left-color: var(--yellow);
  transform: translateX(8px);
  box-shadow: 0 16px 34px -16px rgba(5,5,5,.35);
}
.industry-tile .icon { width: 40px; height: 40px; flex: none; }

/* ---- photographed industry tiles ----
   The icon-row version stays in the stylesheet because the interior pages still
   use it. On the home page the tile becomes a card: photograph on top, the same
   icon and label underneath, so the row reads as six sectors rather than six
   pictures. */
.industry-tile.has-photo {
  display: block;
  padding: 0;
  overflow: hidden;
}
.industry-shot {
  display: block;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  background: rgba(5,5,5,.06);
}
.industry-shot img {
  width: 100%; height: 100%;
  object-fit: cover;
  display: block;
  filter: grayscale(.35) contrast(1.04);
  transition: transform .6s var(--ease), filter .4s var(--ease);
}
.industry-tile.has-photo:hover .industry-shot img { transform: scale(1.05); filter: grayscale(0) contrast(1.06); }
/* the card lifts rather than slides: a horizontal nudge on a photo card drags
   the whole picture sideways and reads as a glitch */
.industry-tile.has-photo:hover { transform: translateY(-6px); }
.industry-label {
  display: flex; align-items: center; gap: 16px;
  padding: 20px 22px 22px;
}
.industry-tile.has-photo .icon { width: 30px; height: 30px; }

.industry-more {
  margin-top: 26px;
  font-size: 15px;
  color: var(--gray);
}
.industry-more a {
  color: var(--black);
  font-weight: 700;
  text-decoration: none;
  border-bottom: 1px solid rgba(5,5,5,.28);
  padding-bottom: 1px;
  margin-left: 6px;
  white-space: nowrap;
}
.industry-more a:hover { border-bottom-color: var(--black); }
.industry-more .icon { width: 14px; height: 14px; margin-left: 4px; }

@media (max-width: 700px) {
  .industry-label { padding: 16px 18px 18px; gap: 13px; }
  .industry-more { font-size: 14px; }
}

/* ==========================================================
   COVERAGE
   ========================================================== */
.coverage { padding: 140px 0; }
.coverage-wrap { display: grid; grid-template-columns: minmax(0, 470px) minmax(0, 1fr); gap: 70px; align-items: center; }
.coverage-info > p { color: rgba(255,255,255,.72); margin-bottom: 30px; }
.state-readout {
  background: rgba(254,194,2,.08);
  border-left: 4px solid var(--yellow);
  padding: 18px 22px; margin-bottom: 28px; min-height: 90px;
}
.state-readout strong {
  display: block; text-transform: uppercase;
  font-size: 21px; letter-spacing: .06em; color: var(--yellow);
}
.state-readout span { color: rgba(255,255,255,.75); font-size: 15px; }
.state-list { list-style: none; display: flex; flex-wrap: wrap; gap: 10px; }
.state-list li {
  padding: 9px 20px;
  border: 1.5px solid rgba(255,255,255,.24);
  font-weight: 700; text-transform: uppercase;
  font-size: 12px; letter-spacing: .1em; cursor: pointer;
  clip-path: polygon(0 0, calc(100% - 11px) 0, 100% 100%, 0 100%);
  transition: background .3s, color .3s, border-color .3s;
}
.state-list li:hover, .state-list li.active { background: var(--yellow); color: var(--black); border-color: var(--yellow); }

.coverage-map svg { width: 100%; height: auto; display: block; filter: drop-shadow(0 20px 40px rgba(0,0,0,.5)); }
.state {
  fill: rgba(255,255,255,.06);
  stroke: rgba(255,255,255,.35); stroke-width: 2.1; stroke-linejoin: round;
  cursor: pointer;
  transition: fill .35s var(--ease), stroke .35s;
}
.state:hover, .state.active { fill: var(--yellow); stroke: var(--yellow); }
.map-label {
  font-family: "Roboto Condensed", sans-serif; font-weight: 700; font-size: 27px;
  fill: rgba(255,255,255,.85); letter-spacing: .06em; pointer-events: none;
}
.hq-dot { fill: var(--yellow); }
.hq-ring {
  fill: none; stroke: var(--yellow); stroke-width: 2;
  transform-box: fill-box; transform-origin: center;
  animation: hqping 2.2s infinite;
}
@keyframes hqping {
  0% { transform: scale(.6); opacity: 1; }
  80%, 100% { transform: scale(2.6); opacity: 0; }
}

/* ==========================================================
   CONTACT
   ========================================================== */
.contact { background: var(--black); padding: 140px 0; }
/* minmax(0,…) rather than a px minimum: the long support email is an
   unbreakable string and would otherwise set the column's min-content width. */
.contact-wrap { display: grid; grid-template-columns: minmax(0, 460px) minmax(0, 1fr); gap: 70px; align-items: start; }
.contact-info > p { color: rgba(255,255,255,.72); margin-bottom: 36px; }
.contact-line {
  display: flex; align-items: center; gap: 18px;
  color: var(--white); text-decoration: none;
  padding: 18px 0;
  border-top: 1px solid rgba(255,255,255,.12);
  transition: color .3s;
}
.contact-line:last-of-type { border-bottom: 1px solid rgba(255,255,255,.12); }
.contact-line:hover { color: var(--yellow); }
.contact-line .icon { width: 32px; height: 32px; flex: none; }
.contact-line small {
  display: block; color: var(--gray);
  text-transform: uppercase; letter-spacing: .16em; font-size: 11px;
}
.contact-line span {
  min-width: 0;
  font-weight: 700; font-size: clamp(15px, 1.4vw, 19px); letter-spacing: .02em;
  overflow-wrap: anywhere;
}

.contact-form {
  background: rgba(27,27,27,.75);
  backdrop-filter: blur(14px);
  border: 1px solid rgba(255,255,255,.1);
  border-top: 3px solid var(--yellow);
  padding: 44px 40px;
  clip-path: polygon(0 0, calc(100% - var(--notch)) 0, 100% var(--notch), 100% 100%, 0 100%);
}
.form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }
.contact-form label {
  display: block;
  font-weight: 500; text-transform: uppercase;
  font-size: 11px; letter-spacing: .14em;
  color: rgba(255,255,255,.6); margin-bottom: 20px;
}
.contact-form input,
.contact-form select,
.contact-form textarea {
  display: block; width: 100%; margin-top: 8px;
  background: rgba(255,255,255,.045);
  border: 1.5px solid rgba(255,255,255,.14);
  color: var(--white);
  font-family: inherit; font-size: 16px;
  padding: 13px 15px;
  transition: border-color .3s, background .3s;
}
.contact-form select option { color: var(--black); }
.contact-form input:focus,
.contact-form select:focus,
.contact-form textarea:focus {
  outline: none; border-color: var(--yellow); background: rgba(254,194,2,.06);
}
.contact-form input.invalid, .contact-form select.invalid { border-color: #e5484d; }
.file-label input { padding: 11px; }
.file-hint { display: block; margin-top: 7px; font-size: 12px; color: var(--gray); text-transform: none; letter-spacing: .02em; }
.form-success {
  margin-top: 20px;
  background: rgba(254,194,2,.1);
  border-left: 4px solid var(--yellow);
  padding: 16px 20px; font-size: 15px;
}
.form-success a { color: var(--yellow); }

/* ==========================================================
   FOOTER
   ========================================================== */
.site-footer { background: var(--charcoal); border-top: 3px solid var(--yellow); padding: 80px 0 0; }
/* Brand block plus one nav that carries the four link columns. The columns are
   nested rather than being direct grid children so the <nav aria-label="Footer">
   landmark can wrap exactly the navigation and nothing else — no display:contents
   needed, and the address and CTA stay outside it where they belong. */
.footer-grid { display: grid; grid-template-columns: 1.5fr 3.5fr; gap: 44px; padding-bottom: 60px; }
.footer-nav { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 32px; }
.footer-tag {
  margin-top: 20px; color: var(--gray);
  font-weight: 700; text-transform: uppercase; letter-spacing: .16em; font-size: 12px;
}
.site-footer h3 { font-size: 13px; letter-spacing: .16em; color: var(--yellow); margin-bottom: 18px; }

/* Scoped to .footer-col, not to every footer link: the generic rule this
   replaced also caught the CTA and would have forced display:block onto a
   .btn, collapsing its inline-flex row. */
.site-footer .footer-col a {
  display: block; color: rgba(255,255,255,.7);
  text-decoration: none; font-weight: 500; font-size: 15px;
  padding: 6px 0;
  transition: color .3s, transform .3s var(--ease);
}
.site-footer .footer-col a:hover { color: var(--yellow); transform: translateX(5px); }
/* the column heading doubles as the hub link, so it keeps the h3 colour */
.site-footer .footer-col h3 a {
  display: inline-block; color: inherit; font-size: inherit;
  font-weight: inherit; letter-spacing: inherit; padding: 0;
}
.site-footer .footer-col h3 a:hover { color: var(--white); transform: none; }
.site-footer .footer-col a.footer-all {
  display: inline-flex; align-items: center; gap: 8px;
  margin-top: 10px; font-weight: 700; color: var(--yellow);
}

/* ---- brand column: phone, email, address, CTA ---- */
.footer-contact { list-style: none; margin: 26px 0 26px; }
.footer-contact a,
.footer-contact address {
  display: grid; grid-template-columns: 17px minmax(0, 1fr);
  gap: 12px; align-items: start;
  color: rgba(255,255,255,.72); text-decoration: none; font-style: normal;
  font-size: 15px; font-weight: 500; line-height: 1.45;
  padding: 8px 0;
  transition: color .3s;
}
.footer-contact a:hover { color: var(--yellow); }
.footer-contact .icon { color: var(--yellow); margin-top: 2px; }
.footer-contact small {
  display: block; margin-bottom: 3px;
  font-size: 11px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .16em; color: var(--gray);
}
.footer-cta { align-self: start; }

.footer-bottom {
  border-top: 1px solid rgba(255,255,255,.1);
  max-width: 1240px; margin: 0 auto; padding: 24px 28px;
  display: flex; justify-content: space-between; align-items: center; gap: 16px; flex-wrap: wrap;
  color: var(--gray); font-size: 14px;
}
.footer-legal { display: flex; align-items: center; gap: 24px; flex-wrap: wrap; }
.footer-bottom a { color: var(--yellow); text-decoration: none; font-weight: 700; }

/* ==========================================================
   MOBILE CTA
   ========================================================== */
.mobile-cta {
  display: none;
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 140;
  background: rgba(5,5,5,.96);
  border-top: 1px solid rgba(255,255,255,.15);
  padding: 10px 14px calc(10px + env(safe-area-inset-bottom));
  gap: 10px;
}
.mobile-cta .btn { flex: 1; justify-content: center; font-size: 14px; padding: 14px 12px; }

/* ==========================================================
   INTERIOR PAGE — HERO
   ========================================================== */
/* Opaque on purpose. The WebGL world is a home-page signature; repeating it
   behind this hero would read as the same page twice. Everything down to the
   triage band is solid, so the world only surfaces in the lower half. */
.page-hero {
  position: relative; z-index: 2;
  padding: calc(var(--header-h) + 84px) 0 96px;
  background:
    radial-gradient(1100px 620px at 78% 12%, rgba(254,194,2,.09), transparent 62%),
    radial-gradient(760px 520px at 6% 88%, rgba(255,255,255,.045), transparent 60%),
    var(--black);
  overflow: hidden;
}
/* Drafting-table grid: brand geometry without another 3D scene. The mask keeps
   it from squaring off against the section edges. */
.hero-blueprint {
  position: absolute; inset: 0; z-index: 0; pointer-events: none;
  background-image:
    linear-gradient(rgba(255,255,255,.045) 1px, transparent 1px),
    linear-gradient(90deg, rgba(255,255,255,.045) 1px, transparent 1px);
  background-size: 78px 78px;
  -webkit-mask-image: radial-gradient(120% 90% at 70% 20%, #000 25%, transparent 78%);
  mask-image: radial-gradient(120% 90% at 70% 20%, #000 25%, transparent 78%);
}
/* The angled cut from the brand mark, blown up as a corner slab. */
.page-hero::after {
  content: ""; position: absolute; z-index: 0; pointer-events: none;
  top: 0; right: 0; width: 340px; height: 5px;
  background: var(--yellow);
  clip-path: polygon(0 0, 100% 0, 100% 100%, 40px 100%);
}
.page-hero .container { position: relative; z-index: 1; }

.page-hero-inner {
  display: grid; grid-template-columns: minmax(0, 1.15fr) minmax(0, .85fr);
  gap: 64px; align-items: center;
}

/* Entrance is CSS, not GSAP: no ScrollTrigger means nothing above the fold can
   ever be stranded at opacity 0 waiting for a trigger that already passed. */
@keyframes heroRise { from { opacity: 0; transform: translateY(18px); } to { opacity: 1; transform: none; } }
.page-hero-copy > *, .hero-index { animation: heroRise .7s var(--ease) both; }
.page-hero-copy > :nth-child(2) { animation-delay: .06s; }
.page-hero-copy > :nth-child(3) { animation-delay: .12s; }
.page-hero-copy > :nth-child(4) { animation-delay: .18s; }
.page-hero-copy > :nth-child(5) { animation-delay: .24s; }
/* The ladder used to stop at 5, which left the CTA row — and now the
   .page-answer standfirst on the pages that carry one — animating in at delay
   0, ahead of the copy above them. Carried on to cover the longest hero. */
.page-hero-copy > :nth-child(6) { animation-delay: .30s; }
.page-hero-copy > :nth-child(7) { animation-delay: .36s; }
.hero-index { animation-delay: .18s; }

/* ---- service index: the hero's right column earns its space by being a
   contents list for the page rather than decoration ---- */
.hero-index {
  background: rgba(255,255,255,.035);
  border: 1px solid rgba(255,255,255,.11);
  border-top: 3px solid var(--yellow);
  clip-path: polygon(0 0, calc(100% - var(--notch)) 0, 100% var(--notch), 100% 100%, 0 100%);
  padding: 22px 26px 26px;
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}
.hero-index-head {
  display: flex; justify-content: space-between; gap: 16px;
  font-weight: 700; text-transform: uppercase;
  font-size: 11px; letter-spacing: .18em;
  color: rgba(255,255,255,.55);
  padding-bottom: 15px; margin-bottom: 6px;
  border-bottom: 1px solid rgba(255,255,255,.12);
}
.hero-index-row {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center; gap: 16px;
  padding: 15px 4px 15px 0;
  color: var(--white); text-decoration: none;
  border-bottom: 1px solid rgba(255,255,255,.08);
  transition: padding-left .3s var(--ease), border-color .3s;
}
.hero-index-row:last-child { border-bottom: none; }
.hero-index-row:hover, .hero-index-row:focus-visible {
  padding-left: 10px;
  border-bottom-color: rgba(254,194,2,.4);
}
.hero-index-num {
  font-weight: 800; font-size: 13px; letter-spacing: .06em;
  color: rgba(255,255,255,.3);
  transition: color .3s;
}
.hero-index-row:hover .hero-index-num { color: var(--yellow); }
.hero-index-label {
  display: block; font-weight: 700; text-transform: uppercase;
  font-size: 14px; letter-spacing: .02em; line-height: 1.25;
}
.hero-index-label small {
  display: block; margin-top: 5px;
  font-weight: 500; text-transform: none; letter-spacing: 0;
  font-size: 12px; color: rgba(255,255,255,.5);
  overflow-wrap: anywhere;
}
.hero-index-count {
  font-weight: 800; font-size: 15px;
  min-width: 30px; text-align: center;
  color: var(--yellow);
  border-left: 2px solid rgba(254,194,2,.32);
  padding-left: 14px;
}

.breadcrumb { margin-bottom: 26px; }
.breadcrumb ol {
  list-style: none; display: flex; flex-wrap: wrap; align-items: center; gap: 10px;
  font-weight: 500; text-transform: uppercase;
  letter-spacing: .16em; font-size: 11px; color: var(--gray);
}
.breadcrumb a { color: var(--gray); text-decoration: none; transition: color .3s; }
.breadcrumb a:hover { color: var(--yellow); }
.breadcrumb li + li::before { content: "/"; margin-right: 10px; color: rgba(255,255,255,.28); }
.breadcrumb [aria-current="page"] { color: var(--white); }

.page-title { font-size: clamp(34px, 6.2vw, 84px); margin-bottom: 26px; text-shadow: 0 8px 60px rgba(0,0,0,.55); }
.page-lede {
  font-size: clamp(19px, 2.3vw, 27px);
  font-weight: 700; text-transform: uppercase; letter-spacing: .01em;
  line-height: 1.2; max-width: 780px; margin-bottom: 24px;
}
.page-intro {
  font-size: clamp(15px, 1.6vw, 18px);
  color: rgba(255,255,255,.76); max-width: 760px; margin-bottom: 40px;
  text-shadow: 0 4px 30px rgba(0,0,0,.6);
}
/* One self-contained sentence stating who we are, where, how fast and what
   number to call. It exists because an AI assistant answering "we have a
   quality spill in Ohio, who do we call today?" quotes a passage, not a page:
   the facts it needs were previously scattered across the lede, a stat strip
   and a CTA block, so no single extractable span carried all of them. Styled
   as a quiet standfirst rather than a callout — it is here to be read as
   normal opening copy, not to shout. */
.page-answer {
  font-size: clamp(15px, 1.6vw, 18px);
  line-height: 1.65;
  color: rgba(255,255,255,.9);
  max-width: 760px;
  margin-bottom: 34px;
  padding-left: 16px;
  border-left: 3px solid var(--yellow);
  text-shadow: 0 4px 30px rgba(0,0,0,.6);
}
.page-answer a { color: inherit; text-decoration: underline; text-underline-offset: 3px; }
.page-answer strong { font-weight: 800; }

.page-ctas { display: flex; gap: 20px; flex-wrap: wrap; }

/* ==========================================================
   SERVICE GROUPS
   ========================================================== */
.svc-section { padding: 120px 0 130px; }
/* Every service group is opaque so the world stays sealed behind the top half
   of the page; .alt just keeps the four bands from reading as one slab. */
.svc-section.panel-solid { background: var(--charcoal); }
.svc-section.panel-solid.alt { background: var(--black); }
.section-intro { max-width: 780px; margin-top: 22px; color: rgba(255,255,255,.72); font-weight: 500; }

/* Six tracks, cards spanning two, so a trailing pair can be centred instead of
   left-orphaned — the quality group has five cards. */
.svc-grid { display: grid; grid-template-columns: repeat(6, 1fr); gap: 22px; perspective: 1200px; }
.svc-grid > * { grid-column: span 2; }
.svc-grid > :nth-child(4):nth-last-child(2) { grid-column: 2 / span 2; }
.svc-grid-two > *, .svc-grid-staffing > * { grid-column: span 3; }

.svc-card {
  position: relative;
  display: flex; flex-direction: column;
  color: inherit; text-decoration: none;
  /* A light overlay, not the dark translucent recipe: these panels are opaque,
     and rgba(20,20,20,.55) would render the card DARKER than the surface it
     sits on. Matches .problem-tile / .why-tile. */
  background: rgba(255,255,255,.04);
  border: 1px solid rgba(255,255,255,.1);
  border-top: 3px solid var(--yellow);
  padding: 34px 28px 30px;
  clip-path: polygon(0 0, calc(100% - var(--notch)) 0, 100% var(--notch), 100% 100%, 0 100%);
  transform-style: preserve-3d;
  overflow: hidden;
  transition: background .35s var(--ease), border-color .35s;
}
/* border-color is a four-side shorthand — spelling the top out again keeps the
   signature yellow rail from washing out on hover. */
.svc-card:hover {
  background: rgba(255,255,255,.075);
  border-color: rgba(254,194,2,.45);
  border-top-color: var(--yellow);
}
.svc-icon { width: 44px; height: 44px; margin-bottom: 20px; }
.svc-card h3 { font-size: 20px; letter-spacing: .01em; margin-bottom: 11px; }
.svc-card p { color: rgba(255,255,255,.7); font-weight: 500; flex: 1; }
.svc-card .card-arrow { margin-top: 22px; align-self: flex-start; }
.svc-card:hover .card-arrow { opacity: 1; transform: translateX(0); }

.svc-list { list-style: none; margin-top: 22px; display: grid; grid-template-columns: repeat(2, 1fr); gap: 0 20px; }
.svc-list li {
  position: relative; font-weight: 500;
  padding: 9px 0 9px 26px;
  border-bottom: 1px solid rgba(255,255,255,.08);
}
.svc-list li::before {
  content: ""; position: absolute; left: 2px; top: 50%;
  width: 12px; height: 3px; background: var(--yellow);
  transform: translateY(-50%) skewX(calc(-1 * var(--cut)));
}

.svc-aside {
  display: flex; flex-direction: column; justify-content: center; gap: 18px;
  padding: 34px 30px;
  background: rgba(255,255,255,.03);
  /* solid, not dashed: a dashed edge is the web's drop-zone/empty-state idiom
     and it is the only one in this sheet */
  border: 1px solid rgba(255,255,255,.14);
  clip-path: polygon(0 0, calc(100% - var(--notch)) 0, 100% var(--notch), 100% 100%, 0 100%);
}
.svc-aside-kicker { font-weight: 800; text-transform: uppercase; letter-spacing: .13em; font-size: 13px; color: var(--yellow); }
.svc-aside p { color: rgba(255,255,255,.72); font-weight: 500; }
.svc-aside .btn { align-self: flex-start; }

.group-cta {
  display: inline-flex; align-items: center; gap: 12px;
  margin-top: 46px; padding-bottom: 8px;
  color: var(--yellow); text-decoration: none;
  font-weight: 700; text-transform: uppercase; letter-spacing: .09em; font-size: 14px;
  border-bottom: 2px solid rgba(254,194,2,.35);
  transition: gap .3s var(--ease), border-color .3s;
}
.group-cta:hover { gap: 19px; border-bottom-color: var(--yellow); }
/* The industries band is a light panel, so the link flips to black there. */
.industries .group-cta { color: var(--black); border-bottom-color: rgba(5,5,5,.28); }
.industries .group-cta:hover { border-bottom-color: var(--black); }

/* ==========================================================
   TRIAGE BAND
   ========================================================== */
/* No background of its own: this is where the world is revealed, so the
   .panel-scrim gradient is left to show through. */
.triage-band {
  padding: 110px 0;
  border-top: 1px solid rgba(254,194,2,.28);
  border-bottom: 1px solid rgba(255,255,255,.08);
}
.triage-inner { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, auto); gap: 54px; align-items: center; }
.triage-inner h2 { font-size: clamp(28px, 3.6vw, 50px); margin-bottom: 20px; }
.triage-inner p { color: rgba(255,255,255,.72); font-weight: 500; max-width: 580px; }
.triage-ctas { display: flex; flex-direction: column; align-items: flex-start; gap: 16px; }

/* ==========================================================
   WHY PLI
   ========================================================== */
.why { padding: 120px 0 130px; }
.why-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 22px; }
.why-tile {
  background: rgba(255,255,255,.04);
  border: 1px solid rgba(255,255,255,.09);
  border-left: 3px solid var(--yellow);
  padding: 30px 26px;
  transition: background .35s, transform .35s var(--ease);
}
.why-tile:hover { background: rgba(255,255,255,.07); transform: translateX(6px); }
.why-tile .icon { width: 38px; height: 38px; margin-bottom: 16px; }
.why-tile h3 { font-size: 17px; margin-bottom: 10px; }
.why-tile p { color: rgba(255,255,255,.7); font-weight: 500; font-size: 15px; }

/* ==========================================================
   FAQ
   ========================================================== */
.faq { padding: 120px 0 130px; }
.faq-list { max-width: 920px; }
.faq-item { border-bottom: 1px solid rgba(255,255,255,.12); }
.faq-item summary {
  list-style: none; cursor: pointer;
  display: flex; align-items: center; justify-content: space-between; gap: 26px;
  padding: 26px 0;
  font-weight: 700; text-transform: uppercase; letter-spacing: .03em;
  font-size: clamp(16px, 1.7vw, 20px); line-height: 1.25;
  transition: color .3s;
}
.faq-item summary::-webkit-details-marker { display: none; }
.faq-item summary:hover { color: var(--yellow); }
.faq-mark { position: relative; width: 18px; height: 18px; flex: none; }
.faq-mark::before, .faq-mark::after {
  content: ""; position: absolute; left: 50%; top: 50%; background: var(--yellow);
}
.faq-mark::before { width: 18px; height: 3px; transform: translate(-50%, -50%); }
.faq-mark::after {
  width: 3px; height: 18px; transform: translate(-50%, -50%);
  transition: transform .35s var(--ease), opacity .3s;
}
.faq-item[open] .faq-mark::after { transform: translate(-50%, -50%) rotate(90deg); opacity: 0; }
.faq-body { padding-bottom: 28px; max-width: 780px; color: rgba(255,255,255,.72); font-weight: 500; }

/* ==========================================================
   CLOSING CTA BAND
   ========================================================== */
.cta-band { padding: 150px 0 170px; text-align: center; }
.cta-band h2 { font-size: clamp(30px, 5vw, 68px); margin-bottom: 40px; text-shadow: 0 8px 50px rgba(0,0,0,.6); }
.cta-band .page-ctas { justify-content: center; }

/* ==========================================================
   INTERIOR DETAIL PAGES  (body.page-svc, body.page-ind)
   Shared hero chrome for both page types. Service pages then continue with
   the modules below; industry pages use their own set further down.
   Additive only — nothing above this block is redefined.
   ========================================================== */
/* Steel Gray is a brand colour but measures 4.49:1 on Deep Black, under the
   4.5:1 floor for label-sized text. Interior pages use a lighter tint (~7.8:1)
   and keep #767676 for rules and borders. */
.page-svc .page-hero-copy > :nth-child(6),
.page-ind .page-hero-copy > :nth-child(6) { animation-delay: .3s; }
/* Service and industry names are longer than the hub's, so the display size
   steps down to hold two lines and keep the primary CTA above the fold on a
   720px screen. */
.page-svc .page-hero, .page-ind .page-hero { padding-top: calc(var(--header-h) + 66px); }
.page-svc .page-title, .page-ind .page-title { font-size: clamp(32px, 4.8vw, 64px); }
/* Longer names (Sorting & Segregation, Quality Representation, Reverse
   Logistics, Automotive Manufacturing Support) need one more step down to hold
   two lines rather than three, which is what keeps the primary CTA above the
   fold on a 720px screen. */
.page-svc .page-title.is-long,
.page-ind .page-title.is-long { font-size: clamp(29px, 4.1vw, 55px); }

.prose p { color: rgba(255,255,255,.74); font-weight: 500; max-width: 68ch; margin-bottom: 18px; }
.prose p:last-child { margin-bottom: 0; }

.callout {
  border-left: 3px solid var(--yellow);
  padding: 4px 0 4px 24px;
  font-size: clamp(17px, 1.9vw, 21px);
  font-weight: 700; text-transform: uppercase; line-height: 1.3;
}

/* ---- hero scan panel: a 2D stand-in for the home page's WebGL, and a
        literal picture of what this service does — sweep a lot, mark what
        conforms, flag what does not ---- */
.scan-panel {
  position: relative;
  background: rgba(255,255,255,.035);
  border: 1px solid rgba(255,255,255,.11);
  border-top: 3px solid var(--yellow);
  clip-path: polygon(0 0, calc(100% - var(--notch)) 0, 100% var(--notch), 100% 100%, 0 100%);
  padding: 18px 22px 20px;
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}
.scan-head, .scan-foot {
  display: flex; align-items: center; justify-content: space-between; gap: 14px;
  font-weight: 700; text-transform: uppercase;
  font-size: 11px; letter-spacing: .18em;
  color: rgba(255,255,255,.55);
}
.scan-head { padding-bottom: 14px; border-bottom: 1px solid rgba(255,255,255,.12); }
.scan-foot { padding-top: 14px; border-top: 1px solid rgba(255,255,255,.12); }
.scan-live { display: inline-flex; align-items: center; gap: 8px; color: var(--yellow); }
.scan-foot b { color: var(--yellow); font-size: 13px; }
.scan-foot i { font-style: normal; color: rgba(255,255,255,.7); font-size: 13px; }
.scan-svg { display: block; width: 100%; height: auto; margin: 6px 0; }

.scan-cell { fill: none; stroke: rgba(255,255,255,.15); stroke-width: 1.5; }
.scan-mark {
  fill: none; stroke-width: 2.6; stroke-linecap: round; stroke-linejoin: round;
  transform-box: fill-box; transform-origin: center;
  opacity: 0;
  animation: markPop 4.5s ease-out infinite;
}
.scan-mark.ok { stroke: var(--yellow); }
.scan-mark.flag { stroke: rgba(255,255,255,.4); }
.scan-mark.r2 { animation-delay: .95s; }
.scan-mark.r3 { animation-delay: 1.9s; }
.scan-sweep { animation: scanSweep 4.5s linear infinite; }
.scan-bar { fill: var(--yellow); }

@keyframes scanSweep {
  from { transform: translateY(-4px); }
  to { transform: translateY(214px); }
}
@keyframes markPop {
  0% { opacity: 0; transform: scale(.4); }
  7% { opacity: 1; transform: scale(1); }
  84% { opacity: 1; transform: scale(1); }
  94%, 100% { opacity: 0; transform: scale(.92); }
}

/* ---- what's included ---- */
.check-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 0 48px; }
.check-item {
  display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 14px;
  padding: 17px 0;
  border-bottom: 1px solid rgba(255,255,255,.08);
}
.check-item .icon { width: 20px; height: 20px; margin-top: 3px; color: var(--yellow); }
.check-item h3 { font-size: 15px; letter-spacing: .02em; margin-bottom: 5px; }
.check-item p { color: rgba(255,255,255,.66); font-weight: 500; font-size: 14px; line-height: 1.5; }

/* ---- where we inspect ---- */
.where-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 20px; }
.where-card {
  background: rgba(255,255,255,.04);
  border: 1px solid rgba(255,255,255,.09);
  border-left: 3px solid var(--yellow);
  padding: 28px 24px;
  transition: background .35s, transform .35s var(--ease);
}
.where-card:hover { background: rgba(255,255,255,.07); transform: translateY(-5px); }
.where-card .icon { width: 36px; height: 36px; margin-bottom: 16px; }
.where-card h3 { font-size: 16px; margin-bottom: 9px; }
.where-card p { color: rgba(255,255,255,.68); font-weight: 500; font-size: 14px; line-height: 1.5; }

/* ---- Google reviews ----
   Verbatim quotes from the Google Business Profile, names as the reviewers
   published them, linked back to the source so anyone can check them. They are
   deliberately NOT marked up as Review/aggregateRating: Google treats reviews
   about a business, published on that business's own site, as self-serving and
   ineligible for rich results, and marking them up anyway risks a structured-
   data manual action. Plain readable content is the whole point here. */
.review-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 20px;
  margin-top: 40px;
}
/* auto-fit gives a lone card the whole row, and one quote ruled across 1200px
   reads as a gap in the page rather than as a quote. Cap the single-card case;
   from two upwards the grid is doing what it was written to do. */
.review-grid:has(> :only-child) { grid-template-columns: minmax(0, 620px); }
.review-card {
  background: rgba(255,255,255,.04);
  border: 1px solid rgba(255,255,255,.09);
  border-left: 3px solid var(--yellow);
  padding: 26px 24px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.review-stars {
  color: var(--yellow);
  font-size: 15px;
  letter-spacing: .18em;
  line-height: 1;
}
.review-card blockquote {
  margin: 0;
  color: rgba(255,255,255,.82);
  font-weight: 500;
  font-size: 15px;
  line-height: 1.55;
}
.review-cite {
  margin-top: auto;
  font-size: 12.5px;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: rgba(255,255,255,.55);
  font-style: normal;
}
.review-source { margin-top: 26px; font-size: 14px; color: rgba(255,255,255,.6); }
.review-source a { color: var(--yellow); text-decoration: underline; text-underline-offset: 3px; }

/* ---- who it's for ---- */
.who-inner { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, .82fr); gap: 56px; align-items: start; }
.who-chips { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 24px; }
.who-chip {
  display: inline-flex; align-items: center; gap: 10px;
  padding: 12px 20px;
  background: rgba(255,255,255,.05);
  border: 1px solid rgba(255,255,255,.13);
  clip-path: polygon(0 0, 100% 0, calc(100% - 12px) 100%, 0 100%);
  color: var(--white); text-decoration: none;
  font-weight: 700; text-transform: uppercase; letter-spacing: .07em; font-size: 12px;
  transition: background .3s var(--ease), color .3s, border-color .3s;
}
.who-chip .icon { width: 18px; height: 18px; }
.who-chip:hover { background: var(--yellow); color: var(--black); border-color: var(--yellow); }
/* clipped, so the global outline would be clipped away with it */
.who-chip:focus-visible { outline: none; box-shadow: inset 0 0 0 3px var(--yellow); }

.trigger-list { list-style: none; }
.trigger-list li {
  position: relative; padding: 13px 0 13px 28px;
  border-bottom: 1px solid rgba(255,255,255,.08);
  font-weight: 500; color: rgba(255,255,255,.75);
}
.trigger-list li::before {
  content: ""; position: absolute; left: 2px; top: 50%;
  width: 13px; height: 3px; background: var(--yellow);
  transform: translateY(-50%) skewX(calc(-1 * var(--cut)));
}

/* ---- proof strip ---- */
.trust-strip { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; }
.trust-item {
  display: flex; align-items: center; gap: 18px;
  padding: 26px 28px;
  background: rgba(255,255,255,.04);
  border: 1px solid rgba(255,255,255,.09);
  clip-path: polygon(0 0, calc(100% - 18px) 0, 100% 18px, 100% 100%, 0 100%);
}
.trust-item .icon { width: 34px; height: 34px; flex: none; }
.trust-item strong { display: block; font-weight: 800; text-transform: uppercase; font-size: 15px; letter-spacing: .03em; margin-bottom: 4px; }
.trust-item span { display: block; color: rgba(255,255,255,.64); font-weight: 500; font-size: 13.5px; line-height: 1.45; }
.trust-item b { color: var(--yellow); }

/* ---- related services: four across rather than the hub's three ---- */
.svc-grid-four { grid-template-columns: repeat(4, 1fr); }
.svc-grid-four > * { grid-column: span 1; }
.svc-grid-three { grid-template-columns: repeat(3, 1fr); }
.svc-grid-three > * { grid-column: span 1; }
.where-grid-three { grid-template-columns: repeat(3, 1fr); }
/* One location. The base track is 4-up, so a lone card would sit at a quarter
   width with three empty columns beside it and read as a rendering fault. */
.where-grid-one { grid-template-columns: minmax(0, 460px); }
.trust-strip-four { grid-template-columns: repeat(4, 1fr); }

.faq-body a { color: var(--yellow); text-decoration: underline; text-underline-offset: 3px; }
.cta-band .cta-line { margin-top: 26px; color: rgba(255,255,255,.66); font-weight: 500; }
.cta-band .cta-line a { color: var(--yellow); text-decoration: none; }
.cta-band .cta-line a:hover { text-decoration: underline; text-underline-offset: 3px; }

/* ---- containment: the firewall panel. Same panel chrome as the scan board so
        service pages read as one system, different diagram inside. ---- */
.guard-svg { display: block; width: 100%; height: auto; margin: 6px 0; }
.guard-lane { stroke: rgba(255,255,255,.16); stroke-width: 1.5; stroke-dasharray: 4 7; fill: none; }
.guard-bar { fill: var(--yellow); }
.guard-glow { animation: guardPulse 2.6s ease-in-out infinite; }
.guard-label {
  font-family: "Roboto Condensed", "Arial Narrow", sans-serif;
  font-weight: 700; font-size: 9px; letter-spacing: .16em;
  fill: rgba(255,255,255,.45);
}
.chip-box { fill: rgba(255,255,255,.05); stroke-width: 1.4; }
.chip-mark { fill: none; stroke-width: 2.4; stroke-linecap: round; stroke-linejoin: round; }
.chip.bad .chip-box { stroke: rgba(255,255,255,.3); }
.chip.bad .chip-mark { stroke: rgba(255,255,255,.5); }
.chip.good .chip-box { stroke: rgba(254,194,2,.55); }
.chip.good .chip-mark { stroke: var(--yellow); }
/* suspect stock never gets past the bar; certified product keeps moving */
.chip.bad { animation: guardBlock 4.2s ease-in-out infinite; }
.chip.good { animation: guardPass 4.2s linear infinite; }
.chip.d2 { animation-delay: 1.4s; }
.chip.d3 { animation-delay: 2.8s; }

@keyframes guardBlock {
  0% { transform: translateX(-34px); opacity: 0; }
  14% { opacity: 1; }
  56% { transform: translateX(112px); }
  64% { transform: translateX(102px); }
  86% { transform: translateX(102px); opacity: 1; }
  100% { transform: translateX(102px); opacity: 0; }
}
@keyframes guardPass {
  0% { transform: translateX(0); opacity: 0; }
  16% { opacity: 1; }
  84% { opacity: 1; }
  100% { transform: translateX(126px); opacity: 0; }
}
@keyframes guardPulse {
  0%, 100% { opacity: .3; }
  50% { opacity: .7; }
}

/* ==========================================================
   TECHNICAL PLATES
   Drawn artwork instead of photography: the brand guide rules out polished
   corporate stock, and a shop drawing says more about inspection work than a
   generic factory photo would. Pure SVG, so it carries no licensing and
   scales to any width.
   ========================================================== */
.figure-band { padding: 108px 0 118px; }
.figure-wrap {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, .46fr);
  gap: 56px; align-items: center;
}
.figure-plate {
  background: var(--white);
  border: 1px solid rgba(5,5,5,.14);
  clip-path: polygon(0 0, calc(100% - var(--notch)) 0, 100% var(--notch), 100% 100%, 0 100%);
  padding: 24px 26px;
  color: var(--black);
}
.figure-plate svg { display: block; width: 100%; height: auto; }
/* Second band on a page mirrors the first — copy left, photo right — so two
   bands do not read as the same module twice. */
.figure-band.flip .figure-wrap { grid-template-columns: minmax(0, .46fr) minmax(0, 1fr); }
.figure-band.flip .photo-figure { order: 2; }
.figure-band.flip .figure-cap { order: 1; }
.figure-band.on-dark .figure-plate {
  background: rgba(255,255,255,.04);
  border-color: rgba(255,255,255,.1);
  color: var(--white);
}
.figure-cap h2 { font-size: clamp(26px, 3vw, 42px); margin-bottom: 20px; }
.figure-cap p { color: rgba(255,255,255,.72); font-weight: 500; margin-bottom: 16px; }
.panel-light .figure-cap p { color: rgba(5,5,5,.72); }
/* ---- hero background photography ----
   Legibility is guaranteed by construction, not by luck with a given photo.
   The image is knocked back to half brightness AND sits under a scrim that is
   ~92% opaque across the copy column, so even a pure-white photo would leave
   the headline near 14:1 and the body copy above 8:1. The scrim thins toward
   the right so the panel side still reads as a photograph.

   --hero-dim lets a page lift a flat or mid-tone shot that would otherwise go
   to mud. It is safe to raise: at the copy column the scrim alone is ~94%
   opaque, so even a pure-white photo at --hero-dim: 1 leaves white headline
   type near 9.5:1. Measure after changing it; do not go above 1. */
.page-hero.has-photo { background: var(--black); }
/* Over a photograph the panel needs a real backdrop of its own — at 3.5%
   white the plant texture reads straight through its labels. */
.page-hero.has-photo .scan-panel {
  background: rgba(5,5,5,.62);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
}
.hero-photo {
  position: absolute; inset: 0; z-index: 0;
  overflow: hidden; isolation: isolate;
}
.hero-photo img {
  width: 100%; height: 100%; object-fit: cover;
  /* each page frames its own shot: set --hero-pos on the section */
  object-position: var(--hero-pos, 50% 50%);
  filter: grayscale(1) contrast(1.06) brightness(var(--hero-dim, .5));
}
.hero-photo::before {
  content: ""; position: absolute; inset: 0; z-index: 1;
  background: var(--yellow); mix-blend-mode: color; opacity: .3;
}
.hero-photo::after {
  content: ""; position: absolute; inset: 0; z-index: 2;
  background:
    linear-gradient(100deg, rgba(5,5,5,.97) 0%, rgba(5,5,5,.94) 30%, rgba(5,5,5,.72) 54%, rgba(5,5,5,.5) 78%, rgba(5,5,5,.42) 100%),
    linear-gradient(180deg, rgba(5,5,5,.55), transparent 26%, transparent 68%, rgba(5,5,5,.72));
}

/* ---- photography ----
   Plant photography is shot under bright white light and would sit on a
   #050505 page like a sticker. Desaturating and re-tinting toward PLI Yellow
   makes it part of the palette. isolation:isolate keeps the blend from
   reaching the page behind it. */
/* In-page photographs keep their original colour — no desaturation and no
   duotone. Only the caption gradient below remains, and that is there to keep
   white caption text legible over whatever the lower edge of the photo is
   doing, not to restyle the image. */
.photo-figure {
  position: relative;
  overflow: hidden;
  background: var(--charcoal);
  border-top: 3px solid var(--yellow);
  clip-path: polygon(0 0, calc(100% - var(--notch)) 0, 100% var(--notch), 100% 100%, 0 100%);
}
.photo-figure img {
  display: block; width: 100%; height: auto;
  aspect-ratio: 3 / 2; object-fit: cover;
  position: relative; z-index: 0;
}
.photo-figure::before {
  content: ""; position: absolute; inset: 0; z-index: 2; pointer-events: none;
  background: linear-gradient(180deg, transparent 46%, rgba(5,5,5,.62));
}
.photo-cap {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 3;
  display: flex; align-items: center; gap: 11px;
  padding: 16px 22px;
  font-weight: 700; text-transform: uppercase;
  font-size: 11px; letter-spacing: .14em;
  color: rgba(255,255,255,.82);
}
.photo-cap::before {
  content: ""; flex: none; width: 18px; height: 3px; background: var(--yellow);
  transform: skewX(calc(-1 * var(--cut)));
}

/* plate linework */
.pl-part { fill: none; stroke: currentColor; stroke-width: 2.2; stroke-linejoin: round; stroke-linecap: round; }
.pl-hole { fill: none; stroke: currentColor; stroke-width: 1.8; }
.pl-dim { fill: none; stroke: currentColor; stroke-width: 1.1; opacity: .55; }
.pl-thin { fill: none; stroke: currentColor; stroke-width: 1; opacity: .3; }
.pl-y { fill: none; stroke: var(--yellow); stroke-width: 2.2; stroke-linecap: round; stroke-linejoin: round; }
.pl-yf { fill: var(--yellow); }
.pl-fill { fill: currentColor; opacity: .05; }
.pl-txt {
  font-family: "Roboto Condensed", "Arial Narrow", sans-serif;
  font-weight: 700; font-size: 12px; letter-spacing: .07em;
  fill: currentColor;
}
.pl-txt-sm { font-size: 10px; letter-spacing: .15em; opacity: .55; }
.pl-txt-y { fill: #B98A00; }
.figure-band.on-dark .pl-txt-y { fill: var(--yellow); }

/* ---- sorting: the split panel. Third in the family — inspection scans a lot,
        containment holds a line, sorting divides one stream into two. ---- */
.sort-svg { display: block; width: 100%; height: auto; margin: 6px 0; }
.sort-node { fill: var(--yellow); }
.sort-svg .chip.up { animation: sortUp 4.4s ease-in-out infinite; }
.sort-svg .chip.down { animation: sortDown 4.4s ease-in-out infinite; }

@keyframes sortUp {
  0% { transform: translate(0, 0); opacity: 0; }
  10% { opacity: 1; }
  42% { transform: translate(130px, 0); }
  62% { transform: translate(182px, -37px); }
  90% { transform: translate(276px, -37px); opacity: 1; }
  100% { transform: translate(292px, -37px); opacity: 0; }
}
@keyframes sortDown {
  0% { transform: translate(0, 0); opacity: 0; }
  10% { opacity: 1; }
  42% { transform: translate(130px, 0); }
  62% { transform: translate(182px, 41px); }
  90% { transform: translate(276px, 41px); opacity: 1; }
  100% { transform: translate(292px, 41px); opacity: 0; }
}

/* ---- rework: the recovery panel. A flagged part enters the station and
        leaves it certified — the page's whole argument in one loop. ---- */
.fix-svg { display: block; width: 100%; height: auto; margin: 6px 0; }
.fix-station { fill: none; stroke: var(--yellow); stroke-width: 2; }
.fix-glow { fill: var(--yellow); animation: fixFlash 4.4s ease-in-out infinite; }
.fix-chip { animation: fixTravel 4.4s linear infinite; }
.fix-chip .chip-box { stroke: rgba(255,255,255,.34); }
.fix-chip .mark-was { stroke: rgba(255,255,255,.5); animation: fixWas 4.4s linear infinite; }
.fix-chip .mark-now { stroke: var(--yellow); animation: fixNow 4.4s linear infinite; }
.fix-chip.d2, .fix-chip.d2 .mark-was, .fix-chip.d2 .mark-now { animation-delay: 2.2s; }

@keyframes fixTravel {
  0% { transform: translateX(0); opacity: 0; }
  8% { opacity: 1; }
  92% { opacity: 1; }
  100% { transform: translateX(250px); opacity: 0; }
}
@keyframes fixWas { 0%, 42% { opacity: 1; } 52%, 100% { opacity: 0; } }
@keyframes fixNow { 0%, 48% { opacity: 0; } 58%, 100% { opacity: 1; } }
@keyframes fixFlash { 0%, 38% { opacity: .18; } 50% { opacity: .6; } 62%, 100% { opacity: .18; } }

/* ---- quality representation: the link panel. A PLI rep sits at the customer
        site and status flows back to you — the service in one loop. ---- */
.rep-svg { display: block; width: 100%; height: auto; margin: 6px 0; }
.rep-site { fill: rgba(255,255,255,.04); stroke: rgba(255,255,255,.22); stroke-width: 1.5; }
.rep-mark { fill: none; stroke: var(--yellow); stroke-width: 2; }
.rep-halo { fill: var(--yellow); animation: repPulse 2.8s ease-in-out infinite; }
.rep-dot { fill: var(--yellow); animation: repFlow 3.6s linear infinite; }
.rep-dot.d2 { animation-delay: 1.2s; }
.rep-dot.d3 { animation-delay: 2.4s; }

@keyframes repPulse { 0%, 100% { opacity: .16; } 50% { opacity: .42; } }
@keyframes repFlow {
  0% { transform: translateX(0); opacity: 0; }
  14% { opacity: 1; }
  86% { opacity: 1; }
  100% { transform: translateX(-96px); opacity: 0; }
}

/* ---- assembly: the build panel. Loose components feed three lanes into one
        cell and leave as a single checked unit — the service in one loop. ---- */
.asm-svg { display: block; width: 100%; height: auto; margin: 6px 0; }
.asm-cell { fill: none; stroke: var(--yellow); stroke-width: 2; }
.asm-glow { fill: var(--yellow); animation: asmFlash 4.4s ease-in-out infinite; }
.asm-part { animation: asmFeed 4.4s linear infinite; }
.asm-part .chip-box { stroke: rgba(255,255,255,.34); }
.asm-part.p2 { animation-delay: .16s; }
.asm-part.p3 { animation-delay: .32s; }
.asm-out { animation: asmBuilt 4.4s linear infinite; }
.asm-out .chip-box { stroke: rgba(254,194,2,.6); fill: rgba(254,194,2,.09); }
.asm-out .chip-mark { stroke: var(--yellow); }

@keyframes asmFeed {
  0% { transform: translateX(0); opacity: 0; }
  8% { opacity: 1; }
  38% { opacity: 1; }
  46%, 100% { transform: translateX(112px); opacity: 0; }
}
@keyframes asmFlash { 0%, 42% { opacity: .1; } 52% { opacity: .5; } 64%, 100% { opacity: .1; } }

/* ---- packaging: the pack panel. A carton runs the line, takes a label, and
        leaves sealed and counted — the service in one loop. ---- */
.pak-svg { display: block; width: 100%; height: auto; margin: 6px 0; }
.pak-head { fill: none; stroke: var(--yellow); stroke-width: 2; }
.pak-glow { fill: var(--yellow); animation: pakFlash 4.6s ease-in-out infinite; }
.pak-box { animation: pakRun 4.6s linear infinite; }
.pak-box .chip-box { stroke: rgba(255,255,255,.34); }
.pak-box .pak-label { fill: var(--yellow); animation: pakLabel 4.6s linear infinite; }
.pak-box .chip-mark { stroke: var(--yellow); animation: pakSealed 4.6s linear infinite; }
.pak-box.d2, .pak-box.d2 .pak-label, .pak-box.d2 .chip-mark { animation-delay: 2.3s; }

@keyframes pakRun {
  0% { transform: translateX(0); opacity: 0; }
  8% { opacity: 1; }
  92% { opacity: 1; }
  100% { transform: translateX(252px); opacity: 0; }
}
/* the box sits under the applicator between 50% and 67% of its run, so the
   label lands at 56% and the seal check follows once it is clear */
@keyframes pakLabel { 0%, 52% { opacity: 0; } 60%, 100% { opacity: 1; } }
@keyframes pakSealed { 0%, 72% { opacity: 0; } 80%, 100% { opacity: 1; } }
@keyframes pakFlash { 0%, 46% { opacity: .1; } 55% { opacity: .48; } 66%, 100% { opacity: .1; } }

/* ---- kitting: the kit panel. A tray runs past three component bins, fills a
        slot at each, and leaves counted — the service in one loop. ---- */
.kit-svg { display: block; width: 100%; height: auto; margin: 6px 0; }
.kit-bin { fill: rgba(255,255,255,.05); stroke: rgba(255,255,255,.28); stroke-width: 1.4; }
.kit-tray { fill: rgba(255,255,255,.04); stroke: rgba(254,194,2,.55); stroke-width: 1.8; }
.kit-run { animation: kitRun 5s linear infinite; }
.kit-slot { fill: var(--yellow); opacity: 0; animation: kitFill 5s linear infinite; }
.kit-slot.s2 { animation-delay: .5s; }
.kit-slot.s3 { animation-delay: 1s; }
.kit-check { fill: none; stroke: var(--yellow); stroke-width: 2.4; stroke-linecap: round; stroke-linejoin: round; opacity: 0; animation: kitDone 5s linear infinite; }

@keyframes kitRun {
  0% { transform: translateX(0); opacity: 0; }
  6% { opacity: 1; }
  92% { opacity: 1; }
  100% { transform: translateX(216px); opacity: 0; }
}
/* s1 lands at 30%, s2 at 40%, s3 at 50% via the delays above */
@keyframes kitFill { 0%, 22% { opacity: 0; } 30%, 100% { opacity: 1; } }
@keyframes kitDone { 0%, 58% { opacity: 0; } 66%, 100% { opacity: 1; } }

/* ---- industrial staffing: the roster panel. Empty shift slots fill one by
        one until the floor is covered — the service in one loop. ---- */
.stf-svg { display: block; width: 100%; height: auto; margin: 6px 0; }
.stf-person circle, .stf-person path { fill: none; stroke: rgba(255,255,255,.26); stroke-width: 1.6; }
.stf-on { opacity: 0; animation: stfFill 5.6s linear infinite; }
.stf-on circle { fill: var(--yellow); stroke: var(--yellow); }
.stf-on path { stroke: var(--yellow); stroke-width: 1.8; }
.stf-on.n2 { animation-delay: .3s; }
.stf-on.n3 { animation-delay: .6s; }
.stf-on.n4 { animation-delay: .9s; }
.stf-on.n5 { animation-delay: 1.2s; }
.stf-on.n6 { animation-delay: 1.5s; }
.stf-on.n7 { animation-delay: 1.8s; }
.stf-on.n8 { animation-delay: 2.1s; }

/* the last slot carries a 2.1s delay, so every slot is lit from ~48% to 86%
   before the roster clears and the loop restarts */
@keyframes stfFill { 0%, 4% { opacity: 0; } 10%, 86% { opacity: 1; } 94%, 100% { opacity: 0; } }

/* .step-num (the homepage process badge, above) is reused as-is inside
   .where-card on the staffing page — it wants a bare number, not a label. */
.where-card .step-num { margin-bottom: 18px; }

/* ---- warehouse support: the flow panel. Receiving keeps arriving while
        overflow holds the surge and shipping keeps going out. ---- */
.whs-svg { display: block; width: 100%; height: auto; margin: 6px 0; }
.whs-hold { fill: none; stroke: var(--yellow); stroke-width: 2; }
.whs-glow { fill: var(--yellow); animation: whsPulse 5.2s ease-in-out infinite; }
.whs-box .chip-box { stroke: rgba(255,255,255,.32); }
.whs-in { animation: whsFlow 5.2s linear infinite; }
.whs-in.d2 { animation-delay: 2.6s; }
/* outbound runs a half-cycle behind inbound, so the bay is never empty */
.whs-out { animation: whsFlow 5.2s linear infinite; animation-delay: 1.3s; }
.whs-out.d2 { animation-delay: 3.9s; }

@keyframes whsFlow {
  0% { transform: translateX(0); opacity: 0; }
  8% { opacity: 1; }
  92% { opacity: 1; }
  100% { transform: translateX(268px); opacity: 0; }
}
@keyframes whsPulse { 0%, 100% { opacity: .1; } 50% { opacity: .38; } }
@keyframes asmBuilt {
  0%, 50% { transform: translateX(0); opacity: 0; }
  58% { transform: translateX(10px); opacity: 1; }
  94% { transform: translateX(96px); opacity: 1; }
  100% { transform: translateX(104px); opacity: 0; }
}

/* ---- reverse logistics: the recovery loop. Returned product runs the wrong
        way down the top lane into recovery, then leaves again on the bottom
        lane carrying a tick. Two lanes travelling in opposite directions is
        the whole idea of the service, so they must stay mirrored. ---- */
.rev-svg { display: block; width: 100%; height: auto; margin: 6px 0; }
.rev-hold { fill: none; stroke: var(--yellow); stroke-width: 2; }
.rev-glow { fill: var(--yellow); animation: revPulse 5.4s ease-in-out infinite; }
.rev-box .chip-box { stroke: rgba(255,255,255,.32); }
.rev-tick { stroke: var(--yellow); }
.rev-back { animation: revBack 5.4s linear infinite; }
.rev-back.d2 { animation-delay: 2.7s; }
/* outbound runs a half-cycle behind inbound, so recovery is never idle */
.rev-out { animation: revOut 5.4s linear infinite; animation-delay: 1.35s; }
.rev-out.d2 { animation-delay: 4.05s; }

@keyframes revBack {
  0% { transform: translateX(0); opacity: 0; }
  8% { opacity: 1; }
  92% { opacity: 1; }
  100% { transform: translateX(-268px); opacity: 0; }
}
@keyframes revOut {
  0% { transform: translateX(0); opacity: 0; }
  8% { opacity: 1; }
  92% { opacity: 1; }
  100% { transform: translateX(268px); opacity: 0; }
}
@keyframes revPulse { 0%, 100% { opacity: .1; } 50% { opacity: .38; } }

/* ==========================================================
   LIGHT PANELS ON SERVICE PAGES
   The site already alternates into light bands (.solutions is white,
   .industries is --light). This is the same device made reusable, so a
   service page can break to a spec-sheet white without leaving the system.
   ========================================================== */
.panel-light {
  position: relative; z-index: 2;
  background: var(--light);
  color: var(--black);
}
/* Yellow is ~1.5:1 here, so on light bands it becomes a highlight under
   black type rather than the ink itself. */
.panel-light .yellow { color: inherit; }
.panel-light .section-intro,
.panel-light .prose p { color: rgba(5,5,5,.72); }

.panel-light .check-item { border-bottom-color: rgba(5,5,5,.13); }
/* black line icons are the guide's own default on light ground, and a yellow
   check would sit at 1.7:1 here */
.panel-light .check-item .icon { color: var(--black); }
.panel-light .check-item p { color: rgba(5,5,5,.68); }

.panel-light .trust-item {
  background: var(--white);
  border-color: rgba(5,5,5,.1);
}
.panel-light .trust-item span { color: rgba(5,5,5,.66); }
.panel-light .trust-item b { color: var(--black); }

.panel-light .where-card {
  background: var(--white);
  border-color: rgba(5,5,5,.1);
  border-left-color: var(--black);
}
.panel-light .where-card:hover { background: var(--white); border-left-color: var(--yellow); }
.panel-light .where-card p { color: rgba(5,5,5,.66); }

.panel-light .trigger-list li { border-bottom-color: rgba(5,5,5,.12); color: rgba(5,5,5,.75); }
.panel-light .trigger-list li::before { background: var(--black); }

.panel-light .who-chip {
  background: var(--white);
  border-color: rgba(5,5,5,.14);
  color: var(--black);
}
.panel-light .who-chip:hover { background: var(--yellow); border-color: var(--yellow); }
.panel-light .who-chip:focus-visible { box-shadow: inset 0 0 0 3px var(--black); }

.panel-light .group-cta { color: var(--black); border-bottom-color: rgba(5,5,5,.28); }
.panel-light .group-cta:hover { border-bottom-color: var(--black); }

/* .btn-ghost is white text on a white hairline — built for the dark panels and
   effectively invisible on --light. Inverting it keeps the same outline-button
   shape at full contrast; --btn-fill drives the hover wipe, so it flips too. */
.panel-light .btn-ghost {
  --btn-fill: var(--black); color: var(--black);
  box-shadow: inset 0 0 0 1.5px rgba(5,5,5,.35);
}
.panel-light .btn-ghost:hover { color: var(--white); box-shadow: inset 0 0 0 1.5px var(--black); }

/* The solution map is the one industry module that lives on a light band. */
.panel-light .sol-row { background: var(--white); border-color: rgba(5,5,5,.1); }
.panel-light .sol-row .icon { color: var(--black); }
.panel-light .sol-desc { color: rgba(5,5,5,.66); }
.panel-light .sol-arrow { color: rgba(5,5,5,.35); }
.panel-light .sol-row:hover { border-color: rgba(5,5,5,.2); }
.panel-light .sol-row:hover .sol-arrow { color: var(--black); }
/* Yellow on white is ~1.5:1, so the hover rail is black here and the yellow
   is kept for the icon wash only, where it is decoration rather than meaning. */
.panel-light .sol-row::before { background: var(--black); }

/* ==========================================================
   NATIONAL REACH  (homepage, sits under the coverage map)
   Pure --white rather than --light: it lands between two dark bands, and the
   extra step of contrast is what makes it read as a deliberate pause in the
   page instead of a third grey panel. Declared after .panel-light so the
   background override wins on source order.
   ========================================================== */
.reach { background: var(--white); padding: 130px 0 140px; }
.reach-wrap {
  display: grid;
  /* Even split now the left column holds 16:9 video: at .85fr the frame was
     only 266px tall against a 443px copy block. Equal columns give the video
     enough height to hold its own without cropping it. */
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: clamp(40px, 5vw, 88px);
  align-items: center;
}
/* Explicit placement rather than `order`, so the copy can stay first in the
   DOM while the figure renders on the left. */
.reach-figure { grid-column: 1; grid-row: 1; }
.reach-body   { grid-column: 2; grid-row: 1; }
/* The figure is now just the stack: media box plus its caption. The ratio and
   the clipping live on .reach-frame, so the caption sits outside the notch and
   outside overflow:hidden. */
.reach-figure { align-self: center; }
/* the heading now sits above the body copy, so it needs its spacing back */
.reach-head { margin-bottom: 30px; }

/* No dark edge bed here — this band is white, so the treatment used on
   .about-figure would read as a smudge rather than a blend. */
.reach-frame {
  position: relative;
  /* The footage is 1920x1080 and has to stay that shape, so the box carries the
     ratio and the copy is sized to meet it, not the other way round. */
  aspect-ratio: 16 / 9;
  overflow: hidden;
  /* The poster sits under the embed rather than the flat charcoal: the iframe
     is lazy-loaded, so without it the box is a black hole until YouTube
     answers, and a failed embed leaves it black for good. */
  background: url("../img/reach-poster.jpg") center / cover no-repeat var(--charcoal);
  /* Square corners, unlike every other media box in the system. The notch is a
     corner bite, and a bite out of a video is a bite out of the footage — there
     is no object-fit for an iframe to absorb it. Showing the whole frame won
     over the house shape here. Same reasoning on .office-frame. */
}
.reach-frame img,
.reach-frame video { display: block; width: 100%; height: 100%; object-fit: cover; }

/* ---- the two YouTube b-roll embeds (home .reach-frame, contact .office-frame)
   Absolute inset rather than width/height on a static iframe: an iframe with no
   size falls back to the 300x150 default and leaves the rest of the box as bare
   black, which is exactly what a stale stylesheet used to produce here.
   No overscan: the box is 16:9 and so is the footage, so 100%/100% is an exact
   fit and every pixel of the video survives. Earlier revisions scaled the
   iframe up to push YouTube's title bar and logo out of view, but that chrome
   only appears when muted autoplay is blocked, and paying for it with a
   permanent crop of the footage is the wrong trade. Any sub-pixel seam left by
   rounding lands on the poster behind, which is a frame of this same footage,
   not on black.
   Class selectors, not `... iframe`, so the reduced-motion rule further down
   can still hide them at equal specificity.
   pointer-events:none keeps the embed inert: no hover chrome, no
   click-to-pause, so YouTube's UI never gets a state that would draw it back. */
.reach-embed,
.office-embed {
  display: block;
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
  pointer-events: none;
}

/* ---- address caption ----
   Icon is black, not yellow: PLI Yellow measures ~1.5:1 on this white band, and
   the design system already sets black line icons as the light-ground default.
   The two lines carry a weight/colour step so the street reads first. */
.reach-caption {
  display: grid;
  grid-template-columns: 17px minmax(0, 1fr);
  gap: 12px;
  align-items: start;
  margin-top: 20px;
}
.reach-caption .icon { color: var(--black); margin-top: 2px; }
.reach-caption address { font-style: normal; }
.reach-addr-street,
.reach-addr-city { display: block; line-height: 1.45; }
.reach-addr-street {
  font-weight: 700; font-size: 15px;
  letter-spacing: .01em; color: var(--black);
}
.reach-addr-city {
  font-weight: 500; font-size: 14px;
  color: #525252;
}
/* Sized down from 50px so the copy block lands near the video's 16:9 height
   rather than towering over it. The binding line is still "READY WHEREVER YOU
   ARE." — it has to clear its column or "ARE." strands on a third line under
   its own swash. (The swash survives a wrap fine, Chrome slices the inline
   background across every fragment, so this is about the orphan, not the
   highlight.) */
.reach .section-head h2 { font-size: clamp(26px, 2.95vw, 42px); }

.reach-lead {
  font-size: clamp(15px, 1.12vw, 16px);
  font-weight: 500; line-height: 1.7;
  color: rgba(5,5,5,.72);
  max-width: 62ch;
}
/* Picks up the yellow rail from .state-readout in the section directly above,
   so the two bands read as one argument continuing across the colour change. */
.reach-close {
  margin-top: 22px; padding-left: 20px;
  border-left: 4px solid var(--yellow);
  font-size: clamp(15px, 1.22vw, 17.5px);
  font-weight: 700; line-height: 1.36;
  color: var(--black);
}
.reach-cta { margin-top: 28px; }

/* ==========================================================
   INDUSTRY PAGE MODULES  (body.page-ind)
   Industry pages answer a different question than service pages — "can you
   handle my sector's problems" rather than "what is this service" — so they
   run their own set of blocks instead of reusing the service modules.
   Additive only.
   ========================================================== */

/* ---- hero stat rail. Industry heroes drop the service pages' copy + diagram
        split for a single full-width column with the numbers underneath: the
        promise on these pages is response time, so it leads. ---- */
.ind-hero .page-hero-inner { grid-template-columns: minmax(0, 1fr); gap: 46px; }
/* No measure on the copy block itself — .page-lede and .page-intro already
   carry their own, and capping the shared parent squeezes the h1 into a third
   line, which pushes the stat rail and the primary CTA below the fold. */
.ind-stats {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 0; margin: 0;
  border-top: 1px solid rgba(255,255,255,.14);
  animation: heroRise .7s var(--ease) .3s both;
}
.ind-stat { padding: 26px 30px 0 0; }
.ind-stat + .ind-stat { padding-left: 34px; border-left: 1px solid rgba(255,255,255,.14); }
.ind-stat dt {
  font-size: 11px; font-weight: 700; letter-spacing: .2em; text-transform: uppercase;
  color: rgba(255,255,255,.62); margin-bottom: 10px;
}
.ind-stat dd {
  margin: 0 0 8px;
  font-weight: 900; line-height: 1;
  font-size: clamp(30px, 3.4vw, 44px);
  color: var(--white);
  /* Values are display type, so they set uppercase regardless of how the page
     types them. Numerals and "ISO" are unaffected; a worded value like
     "Same day" would otherwise sit in title case beside the uppercase unit. */
  text-transform: uppercase;
}
.ind-stat dd b { font-weight: 900; color: var(--yellow); }
.ind-stat dd i {
  font-style: normal; font-weight: 700;
  font-size: .42em; letter-spacing: .12em; text-transform: uppercase;
  color: rgba(255,255,255,.7);
}
.ind-stat p { font-size: 13.5px; font-weight: 500; color: rgba(255,255,255,.6); max-width: 30ch; }

/* ---- numbered problem cards. The service pages list what a service includes;
        an industry page opens on what is going wrong, so these lead with the
        index number and read as a numbered brief. ---- */
.prob-grid {
  list-style: none; margin: 0; padding: 0;
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 2px;
  background: rgba(255,255,255,.09);
  border: 1px solid rgba(255,255,255,.09);
}
.prob-card {
  background: var(--black);
  padding: 34px 30px 32px;
  transition: background .3s var(--ease);
}
.prob-card:hover { background: #0d0d0d; }
.prob-num {
  display: block;
  font-size: 13px; font-weight: 900; letter-spacing: .16em;
  color: var(--yellow); margin-bottom: 20px;
}
.prob-card h3 {
  font-size: 17px; font-weight: 800; text-transform: uppercase;
  letter-spacing: .01em; line-height: 1.22; margin-bottom: 10px;
}
.prob-card p { font-size: 14.5px; font-weight: 500; color: rgba(255,255,255,.66); line-height: 1.55; }

/* ---- solution map. The highest-value module on an industry page: it routes
        sector traffic into the service pages. Rows rather than cards so seven
        of them scan in one pass. ---- */
.sol-map { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; }
.sol-row {
  position: relative;
  display: grid; grid-template-columns: 26px minmax(0, 1fr) auto;
  align-items: center; gap: 4px 16px;
  padding: 20px 22px;
  background: rgba(255,255,255,.03);
  border: 1px solid rgba(255,255,255,.1);
  text-decoration: none; color: inherit;
  transition: border-color .3s var(--ease), background .3s var(--ease), transform .3s var(--ease);
}
/* the rail is the hover affordance; it grows from the left edge */
.sol-row::before {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 3px;
  background: var(--yellow);
  transform: scaleY(0); transform-origin: top;
  transition: transform .3s var(--ease);
}
.sol-row:hover, .sol-row:focus-visible { border-color: rgba(255,255,255,.24); transform: translateX(3px); }
.sol-row:hover::before, .sol-row:focus-visible::before { transform: scaleY(1); }
.sol-row .icon { width: 26px; height: 26px; grid-row: span 2; color: var(--yellow); }
.sol-name { font-size: 16px; font-weight: 800; text-transform: uppercase; letter-spacing: .02em; }
.sol-desc { grid-column: 2; font-size: 14px; font-weight: 500; color: rgba(255,255,255,.62); line-height: 1.5; }
.sol-arrow {
  grid-row: span 2; display: inline-flex; color: rgba(255,255,255,.3);
  transition: color .3s var(--ease), transform .3s var(--ease);
}
.sol-row:hover .sol-arrow { color: var(--yellow); transform: translateX(4px); }

/* ---- where-we-work list, inside the photo band rather than as its own
        section: the band already says "on site", so the list belongs to it. ---- */
.loc-list { list-style: none; margin: 22px 0 0; padding: 0; display: grid; gap: 0; }
.loc-list li {
  padding: 11px 0 11px 26px; position: relative;
  font-size: 15px; font-weight: 600;
  border-bottom: 1px solid rgba(255,255,255,.1);
}
.loc-list li::before {
  content: ""; position: absolute; left: 0; top: 50%; width: 12px; height: 2px;
  background: var(--yellow); transform: translateY(-50%);
}

/* ---- spec plates. Automotive buyers scan for the programme codes before they
        read anything else, so the code is the object and the prose supports
        it. Reused by any industry with named customer requirements. ---- */
.plate-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 18px; }
.plate {
  padding: 28px 26px 30px;
  background: linear-gradient(180deg, rgba(255,255,255,.05), rgba(255,255,255,.02));
  border: 1px solid rgba(255,255,255,.12);
  border-top: 3px solid var(--yellow);
  clip-path: polygon(0 0, 100% 0, 100% calc(100% - 18px), calc(100% - 18px) 100%, 0 100%);
}
/* Scoped to .plate for weight, not reach: the code is a <p>, so the
   `.plate p` description rule below outranks a bare `.plate-code` and the
   codes render as small grey body copy instead of the yellow they lead with. */
.plate .plate-code {
  font-size: 26px; font-weight: 900; letter-spacing: .04em;
  color: var(--yellow); line-height: 1; margin-bottom: 16px;
}
.plate h3 {
  font-size: 15px; font-weight: 800; text-transform: uppercase;
  letter-spacing: .02em; line-height: 1.25; margin-bottom: 10px;
}
.plate p { font-size: 14px; font-weight: 500; color: rgba(255,255,255,.64); line-height: 1.55; }

/* ---- reason rows. Same content shape as the service pages' why-tiles but
        laid on their side, which keeps this section short on a page that
        already carries a lot of stacked blocks. ---- */
.reason-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 44px; }
.reason {
  display: flex; align-items: flex-start; gap: 18px;
  padding: 22px 0;
  border-bottom: 1px solid rgba(255,255,255,.1);
}
.reason .icon { width: 30px; height: 30px; flex-shrink: 0; color: var(--yellow); }
.reason h3 {
  font-size: 16px; font-weight: 800; text-transform: uppercase;
  letter-spacing: .02em; margin-bottom: 6px;
}
.reason p { font-size: 14.5px; font-weight: 500; color: rgba(255,255,255,.64); line-height: 1.55; }

/* ---- industries hub: the routing grid, and the page's whole reason to exist.
        Each card previews the page it leads to by carrying that page's own hero
        image, so the hub and its children read as one set. Motion is 2D only —
        image scale, rail wipe, arrow slide — no tilt and no rotation. ---- */
.ind-cards { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 20px; }
.ind-card {
  position: relative; display: block; overflow: hidden;
  text-decoration: none; color: inherit;
  background: rgba(255,255,255,.03);
  border: 1px solid rgba(255,255,255,.1);
  clip-path: polygon(0 0, 100% 0, 100% calc(100% - var(--notch)), calc(100% - var(--notch)) 100%, 0 100%);
  transition: border-color .35s var(--ease), background .35s var(--ease), transform .35s var(--ease);
}
.ind-card:hover, .ind-card:focus-visible {
  border-color: rgba(255,255,255,.22);
  background: rgba(255,255,255,.06);
  transform: translateY(-4px);
}
/* the rail wipes down the left edge on hover, matching .sol-row */
.ind-card::before {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 3px; z-index: 4;
  background: var(--yellow);
  transform: scaleY(0); transform-origin: top;
  transition: transform .4s var(--ease);
}
.ind-card:hover::before, .ind-card:focus-visible::before { transform: scaleY(1); }

.ind-card-media { display: block; position: relative; aspect-ratio: 16 / 9; overflow: hidden; isolation: isolate; }
.ind-card-media img {
  width: 100%; height: 100%; object-fit: cover; display: block;
  /* the same desaturate-and-re-tint the heroes use, so hub art sits in the
     palette rather than dropping six full-colour photos onto a black page */
  filter: grayscale(1) contrast(1.05) brightness(.58);
  transform: scale(1.02);
  transition: transform .6s var(--ease), filter .5s var(--ease);
}
.ind-card:hover .ind-card-media img,
.ind-card:focus-visible .ind-card-media img {
  transform: scale(1.07);
  filter: grayscale(1) contrast(1.05) brightness(.78);
}
.ind-card-media::before {
  content: ""; position: absolute; inset: 0; z-index: 1;
  background: var(--yellow); mix-blend-mode: color; opacity: .28;
}
.ind-card-media::after {
  content: ""; position: absolute; inset: 0; z-index: 2;
  background: linear-gradient(180deg, rgba(5,5,5,.12) 40%, rgba(5,5,5,.88));
}
.ind-card-num {
  position: absolute; left: 26px; bottom: 14px; z-index: 3;
  font-size: 12px; font-weight: 900; letter-spacing: .18em; color: var(--yellow);
}

/* display is set explicitly rather than left to the element: these wrappers
   were spans, which stay inline, and inline padding never reaches block
   children — the heading and services line sat 1px off the card edge. */
.ind-card-body { display: block; padding: 26px 32px 30px; }
/* arrow shares the heading row rather than floating in a corner, so it can
   never collide with the services line below */
.ind-card-head { display: flex; align-items: center; justify-content: space-between; gap: 18px; margin-bottom: 12px; }
.ind-card-head h3 { font-size: clamp(20px, 2vw, 26px); }
.ind-card-go { display: inline-flex; flex-shrink: 0; color: rgba(255,255,255,.32); transition: color .3s var(--ease), transform .3s var(--ease); }
.ind-card:hover .ind-card-go, .ind-card:focus-visible .ind-card-go { color: var(--yellow); transform: translateX(5px); }
.ind-card-body > p { font-size: 15px; font-weight: 500; color: rgba(255,255,255,.68); line-height: 1.55; margin-bottom: 20px; }
.ind-card-svc {
  display: block; padding-top: 16px;
  /* the clip-path bites a --notch triangle out of the bottom-right corner, so
     the last line of this list is held clear of it */
  padding-right: var(--notch);
  border-top: 1px solid rgba(255,255,255,.1);
  font-size: 11.5px; font-weight: 700; letter-spacing: .12em; line-height: 1.75;
  text-transform: uppercase; color: rgba(255,255,255,.5);
}

/* ---- natural-colour hero. The default .has-photo treatment desaturates the
        photograph and re-tints it toward PLI Yellow; this variant leaves the
        colour alone. The scrim underneath stays and is pulled slightly darker,
        because a full-colour plant photo is busier behind white type than a
        duotone one — it needs the help more, not less. ---- */
.page-hero.is-natural .hero-photo img { filter: contrast(1.02) brightness(var(--hero-dim, .6)); }
.page-hero.is-natural .hero-photo::before { content: none; }
.page-hero.is-natural .hero-photo::after {
  background:
    linear-gradient(100deg, rgba(5,5,5,.97) 0%, rgba(5,5,5,.95) 32%, rgba(5,5,5,.78) 56%, rgba(5,5,5,.55) 80%, rgba(5,5,5,.45) 100%),
    linear-gradient(180deg, rgba(5,5,5,.6), transparent 26%, transparent 64%, rgba(5,5,5,.8));
}

/* ==========================================================
   LEGAL PAGES  (body.page-legal)
   Long-form reading, not marketing. Set on a light band because a policy is
   read start-to-finish and long body copy on near-black is tiring, with a
   sticky contents rail so a 13-section document stays navigable.
   ========================================================== */
.legal-wrap { display: grid; grid-template-columns: minmax(0, 250px) minmax(0, 1fr); gap: 64px; align-items: start; }

.legal-toc { position: sticky; top: calc(var(--header-h) + 26px); }
.legal-toc p {
  font-size: 11.5px; font-weight: 800; letter-spacing: .18em; text-transform: uppercase;
  color: #525252; padding-bottom: 12px; margin-bottom: 6px;
  border-bottom: 1px solid rgba(5,5,5,.14);
}
.legal-toc ol { list-style: none; margin: 0; padding: 0; counter-reset: toc; }
.legal-toc a {
  display: block; position: relative;
  padding: 8px 0 8px 30px;
  font-size: 13.5px; font-weight: 600; line-height: 1.35;
  color: rgba(5,5,5,.6); text-decoration: none;
  transition: color .25s var(--ease);
}
.legal-toc a::before {
  counter-increment: toc; content: counter(toc, decimal-leading-zero);
  position: absolute; left: 0; top: 8px;
  font-size: 11px; font-weight: 800; letter-spacing: .1em; color: rgba(5,5,5,.32);
  transition: color .25s var(--ease);
}
.legal-toc a:hover { color: var(--black); }
.legal-toc a.is-current { color: var(--black); font-weight: 800; }
.legal-toc a.is-current::before { color: #8a6800; }

/* A document that numbers its own sections (Terms) keeps those numbers, since
   legal sections get cited by number. The rail's counter is turned off there so
   the two numbering schemes do not stack up as "01  1. Use of the Site". */
.legal-toc.is-self-numbered a { padding-left: 0; }
.legal-toc.is-self-numbered a::before { content: none; }

.legal-body { max-width: 74ch; }
.legal-body > p, .legal-body li { font-size: 16px; font-weight: 500; color: rgba(5,5,5,.78); line-height: 1.72; }
.legal-body > p { margin-bottom: 20px; }
.legal-body strong { font-weight: 800; color: var(--black); }
.legal-body h2 {
  font-size: clamp(20px, 2vw, 26px); line-height: 1.15;
  margin: 52px 0 18px; padding-top: 26px;
  border-top: 1px solid rgba(5,5,5,.14);
  scroll-margin-top: calc(var(--header-h) + 26px);
}
.legal-body h2:first-of-type { margin-top: 44px; }
.legal-body ul { list-style: none; margin: 0 0 22px; padding: 0; }
.legal-body li { position: relative; padding: 9px 0 9px 26px; }
.legal-body li::before {
  content: ""; position: absolute; left: 0; top: 19px;
  width: 12px; height: 2px; background: var(--yellow);
}
.legal-body a { color: var(--black); text-decoration: underline; text-underline-offset: 3px; }
.legal-body a:hover { color: #6b5200; }

/* effective date — highlighted because it is the first thing anyone checks
   when they want to know whether a policy is current */
.legal-meta {
  display: inline-flex; align-items: baseline; gap: 10px;
  padding: 11px 18px; margin-top: 6px;
  background: rgba(254,194,2,.14); border-left: 3px solid var(--yellow);
  font-size: 14px; font-weight: 700; color: var(--black);
}
.legal-meta span { font-size: 11.5px; font-weight: 800; letter-spacing: .16em; text-transform: uppercase; color: #6b5200; }

.legal-contact {
  margin-top: 26px; padding: 28px 30px;
  background: var(--white); border: 1px solid rgba(5,5,5,.12);
  clip-path: polygon(0 0, calc(100% - var(--notch)) 0, 100% var(--notch), 100% 100%, 0 100%);
}
.legal-contact address { font-style: normal; font-size: 16px; font-weight: 600; color: var(--black); line-height: 1.7; }
.legal-contact address a { color: var(--black); }

/* ==========================================================
   CONFIRMATION MODAL
   Shown in place after a successful submit — no reload, no route change, which
   keeps the smooth-scroll feel of the rest of the site. Generic on purpose so
   any future form can reuse it.
   ========================================================== */
.modal {
  position: fixed; inset: 0; z-index: 200;
  display: grid; place-items: center;
  padding: 24px;
  opacity: 0; visibility: hidden;
  /* visibility is discrete, so it is held until the fade finishes on close and
     flipped instantly on open — otherwise the panel is still computed hidden
     when focus tries to move into it, and the move silently fails. */
  transition: opacity .28s var(--ease), visibility 0s linear .28s;
}
.modal.is-open { opacity: 1; visibility: visible; transition: opacity .28s var(--ease), visibility 0s; }
.modal-scrim { position: absolute; inset: 0; background: rgba(5,5,5,.82); backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px); }
.modal-panel {
  position: relative; z-index: 1;
  width: min(600px, 100%); max-height: calc(100vh - 48px); overflow-y: auto;
  padding: 44px 46px 40px;
  background: #0d0d0d;
  border: 1px solid rgba(255,255,255,.14);
  border-top: 4px solid var(--yellow);
  clip-path: polygon(0 0, 100% 0, 100% calc(100% - var(--notch)), calc(100% - var(--notch)) 100%, 0 100%);
  transform: translateY(14px) scale(.985);
  transition: transform .32s var(--ease);
}
.modal.is-open .modal-panel { transform: none; }
.modal-close {
  position: absolute; top: 16px; right: 16px;
  width: 42px; height: 42px; display: grid; place-items: center;
  background: none; border: 1px solid rgba(255,255,255,.16); color: rgba(255,255,255,.72);
  cursor: pointer; transition: color .25s, border-color .25s, background .25s;
}
.modal-close:hover, .modal-close:focus-visible { color: var(--black); background: var(--yellow); border-color: var(--yellow); }
.modal-mark {
  display: inline-flex; align-items: center; gap: 10px;
  font-size: 11.5px; font-weight: 800; letter-spacing: .18em; text-transform: uppercase;
  color: var(--yellow); margin-bottom: 16px;
}
.modal-panel h2 { font-size: clamp(26px, 3.2vw, 38px); line-height: 1.06; margin-bottom: 16px; }
.modal-panel > p { font-size: 15.5px; font-weight: 500; color: rgba(255,255,255,.74); line-height: 1.62; margin-bottom: 14px; }
.modal-urgent {
  margin-top: 26px; padding-top: 24px;
  border-top: 1px solid rgba(255,255,255,.13);
}
.modal-urgent p { font-size: 15px; font-weight: 600; color: var(--white); margin-bottom: 16px; }
.modal-fine { font-size: 13px; font-weight: 500; color: rgba(255,255,255,.55); margin-top: 14px; }

/* inline error, for a submission the server rejects — the modal is only ever
   for the success case */
.form-error {
  margin-top: 20px;
  background: rgba(229,72,77,.12);
  border-left: 4px solid #e5484d;
  padding: 16px 20px; font-size: 15px; color: #ffc9cb;
}
.form-error a { color: var(--yellow); }

/* ==========================================================
   CONTACT PAGE  (body.page-contact)
   Reuses the homepage form wholesale (.contact-wrap / .contact-form and the
   rest). Only the pieces that page did not need are added here. Additive only.
   ========================================================== */

/* ---- the two fastest routes, sat in the hero. Someone with a line down should
        not have to scroll to find the phone number, so it is the largest thing
        on the page after the h1. ---- */
.reach-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 18px; margin-top: 34px; }
.reach-card {
  display: block; padding: 26px 28px 28px;
  background: rgba(255,255,255,.05);
  border: 1px solid rgba(255,255,255,.14);
  border-left: 3px solid var(--yellow);
  text-decoration: none; color: inherit;
  transition: background .3s var(--ease), border-color .3s var(--ease), transform .3s var(--ease);
}
.reach-card:hover, .reach-card:focus-visible {
  background: rgba(255,255,255,.09);
  border-color: rgba(255,255,255,.26);
  transform: translateY(-3px);
}
.reach-label {
  display: flex; align-items: center; gap: 9px;
  font-size: 11.5px; font-weight: 800; letter-spacing: .17em; text-transform: uppercase;
  color: rgba(255,255,255,.6); margin-bottom: 12px;
}
.reach-label .icon { width: 17px; height: 17px; color: var(--yellow); }
.reach-value {
  display: block; font-weight: 900; line-height: 1.05;
  font-size: clamp(21px, 2.5vw, 32px);
  color: var(--white); margin-bottom: 8px;
  /* the email is one long unbreakable token and would otherwise set the
     column's min-content width and push the grid past its container */
  overflow-wrap: anywhere;
  transition: color .3s var(--ease);
}
.reach-card:hover .reach-value { color: var(--yellow); }
.reach-note { font-size: 13.5px; font-weight: 500; color: rgba(255,255,255,.62); line-height: 1.5; }

/* ---- form extras the homepage form does not carry ---- */
/* Honeypot. Positioned off-screen rather than display:none — some bots skip
   hidden fields but will fill a positioned one. Kept out of tab order and the
   accessibility tree by the markup. */
.hp-field {
  position: absolute; left: -9999px; width: 1px; height: 1px;
  overflow: hidden; opacity: 0; pointer-events: none;
}
.contact-form .form-consent {
  display: flex; align-items: flex-start; gap: 12px;
  margin: 4px 0 22px;
  font-size: 13.5px; font-weight: 500; color: rgba(255,255,255,.7); line-height: 1.5;
  /* This is a <label>, so it inherits .contact-form label — which is uppercase
     and letter-spaced for two-word field names. A consent sentence set that way
     is close to unreadable, and this one has to actually be read. */
  text-transform: none; letter-spacing: .01em;
}
.contact-form .form-consent input[type="checkbox"] {
  /* 24px is the WCAG 2.5.8 minimum target. The surrounding <label> already made
     the whole consent sentence tappable, but this is TCPA express written
     consent — the box itself should not be the fiddly part. */
  width: 24px; height: 24px; flex: none; margin-top: 0;
  accent-color: var(--yellow); cursor: pointer;
}
.contact-form .form-consent input.invalid { outline: 2px solid #e5484d; outline-offset: 2px; }

/* ---- address blocks on the light band ---- */
.addr-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 20px; }
/* single address: half a row of card and half a row of nothing reads as a
   missing second card, so the track shrinks to the one that exists */
.addr-grid-one { grid-template-columns: minmax(0, 520px); }

/* address card beside the aerial of the same address */
.offices-wrap {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: clamp(26px, 3.5vw, 52px);
  align-items: center;
}
.office-frame {
  position: relative;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  background: url("../img/reach-poster.jpg") center / cover no-repeat var(--charcoal);
  /* squared off for the same reason as .reach-frame: the notch would eat a
     corner of the footage, and an iframe has no object-fit to give it back */
}
.addr-card {
  padding: 30px 32px 32px;
  background: var(--white);
  border: 1px solid rgba(5,5,5,.12);
  clip-path: polygon(0 0, calc(100% - var(--notch)) 0, 100% var(--notch), 100% 100%, 0 100%);
}
.addr-card.is-hq { box-shadow: inset 4px 0 0 var(--yellow); }
.addr-label {
  font-size: 11.5px; font-weight: 800; letter-spacing: .17em; text-transform: uppercase;
  color: #8a6800; margin-bottom: 14px;
}
.addr-card address { font-style: normal; font-size: 16.5px; font-weight: 600; color: var(--black); line-height: 1.6; }
.addr-card p { font-size: 15px; font-weight: 500; color: rgba(5,5,5,.7); line-height: 1.6; }
.addr-card a { color: var(--black); text-decoration: underline; text-underline-offset: 3px; }

/* ==========================================================
   ABOUT PAGE  (body.page-about)
   The company page. It carries the same hard-edged system as the rest of the
   site but wants a warmer read, so it leans on natural-colour photography, more
   air inside the cards, and a slower reveal. Additive only.
   ========================================================== */

/* ---- an established-since mark. Same shape language as .rr-live but static:
        heritage is not a live status, so it does not pulse. ---- */
.est-mark {
  display: inline-flex; align-items: center; gap: 12px;
  padding: 9px 18px; margin-bottom: 22px;
  border: 1px solid rgba(254,194,2,.4); background: rgba(254,194,2,.08);
  clip-path: polygon(0 0, 100% 0, calc(100% - 11px) 100%, 0 100%);
  font-size: 11.5px; font-weight: 800; letter-spacing: .18em; text-transform: uppercase;
  color: var(--yellow);
}
.est-mark b { font-size: 15px; font-weight: 900; letter-spacing: .1em; }

/* ---- belief cards. The one place on the site where the copy is about the
        company rather than the work, so these get the most air of any card in
        the system and a numeral instead of an icon. ---- */
.val-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 20px; }
.val-card {
  position: relative; overflow: hidden;
  padding: 34px 36px 36px;
  background: var(--white);
  border: 1px solid rgba(5,5,5,.12);
  clip-path: polygon(0 0, calc(100% - var(--notch)) 0, 100% var(--notch), 100% 100%, 0 100%);
  transition: transform .4s var(--ease), box-shadow .4s var(--ease), border-color .4s var(--ease);
}
.val-card:hover { transform: translateY(-5px); border-color: rgba(5,5,5,.2); box-shadow: 0 18px 40px rgba(5,5,5,.1); }
/* the rule under the numeral grows on hover — the warm equivalent of the yellow
   rail the darker modules use */
.val-num {
  display: block; position: relative;
  font-size: 13px; font-weight: 900; letter-spacing: .2em;
  color: #8a6800; padding-bottom: 14px; margin-bottom: 18px;
}
.val-num::after {
  content: ""; position: absolute; left: 0; bottom: 0; height: 3px; width: 34px;
  background: var(--yellow);
  transition: width .45s var(--ease);
}
.val-card:hover .val-num::after { width: 76px; }
.val-card h3 { font-size: clamp(19px, 1.8vw, 23px); line-height: 1.2; margin-bottom: 12px; }
.val-card p { font-size: 15.5px; font-weight: 500; color: rgba(5,5,5,.72); line-height: 1.6; }

/* ==========================================================
   RAPID RESPONSE PAGE  (body.page-rr)
   The urgency page. Its one big idea is elapsed time, so the response track is
   the centrepiece and everything else supports it. Additive only.
   ========================================================== */

/* ---- live badge. The page's promise is that someone picks up, so the hero
        says so with a pulse rather than a claim. ---- */
.rr-live {
  display: inline-flex; align-items: center; gap: 10px;
  padding: 9px 16px; margin-bottom: 22px;
  border: 1px solid rgba(254,194,2,.4); background: rgba(254,194,2,.08);
  clip-path: polygon(0 0, 100% 0, calc(100% - 11px) 100%, 0 100%);
  font-size: 11.5px; font-weight: 800; letter-spacing: .18em; text-transform: uppercase;
  color: var(--yellow);
}

/* ---- the response track. Horizontal on desktop, vertical on phones. JS sets
        --p (0-1); the axis is chosen here so one handler drives both. ---- */
.track { position: relative; margin-top: 8px; }
.track-rail {
  position: absolute; top: 13px; height: 3px;
  background: rgba(5,5,5,.12);
  /* Runs dot-centre to dot-centre, not edge to edge, or the rail trails ~250px
     past the last dot and reads as unfinished. Dots sit at the start of each of
     four 1fr columns with a 28px gap: a column is (100% - 84px)/4, so the last
     dot centre lands at 75% + 35px, leaving 25% - 35px on the right. Left is
     half a 29px dot. */
  left: 14px; right: calc(25% - 35px);
}
.track-fill {
  display: block; height: 100%; background: var(--yellow);
  /* Defaults to full, not empty: if the script never runs the track still reads
     as a finished timeline rather than a dim, blank rail. JS adds .is-live and
     drives --p down from there. */
  transform: scaleX(var(--p, 1)); transform-origin: left;
}
.track-steps {
  position: relative; list-style: none; margin: 0; padding: 0;
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 28px;
}
.track-step { padding-right: 24px; }
.track-dot {
  display: block; width: 29px; height: 29px; margin-bottom: 20px;
  border-radius: 50%;
  background: var(--yellow); border: 3px solid var(--yellow);
  position: relative; z-index: 1;
  transition: border-color .4s var(--ease), background .4s var(--ease), transform .4s var(--ease);
}
.track-time {
  font-size: 12px; font-weight: 800; letter-spacing: .16em; text-transform: uppercase;
  color: #8a6800; margin-bottom: 8px;
  transition: color .4s var(--ease);
}
.track-step h3 { font-size: clamp(17px, 1.5vw, 20px); line-height: 1.2; margin-bottom: 10px; }
.track-step p { font-size: 14.5px; font-weight: 500; color: rgba(5,5,5,.68); line-height: 1.55; }
.track-step { transition: opacity .45s var(--ease); }

/* Everything above is the finished state, which is what renders with no JS and
   under prefers-reduced-motion. .is-live is added only when the scrub actually
   takes over, and only then do steps start out dimmed and light in sequence. */
.track.is-live .track-step { opacity: .45; }
.track.is-live .track-step.is-on { opacity: 1; }
.track.is-live .track-dot { background: var(--light); border-color: rgba(5,5,5,.16); }
.track.is-live .track-step.is-on .track-dot { background: var(--yellow); border-color: var(--yellow); transform: scale(1.12); }
.track.is-live .track-time { color: rgba(5,5,5,.42); }
.track.is-live .track-step.is-on .track-time { color: #8a6800; }

/* ---- what we respond to: compact icon cards, seven of them ---- */
.rr-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 16px; }
.rr-card {
  padding: 24px 22px 26px;
  background: rgba(255,255,255,.04);
  border: 1px solid rgba(255,255,255,.1);
  border-top: 3px solid rgba(254,194,2,.5);
  transition: background .35s var(--ease), border-top-color .35s var(--ease), transform .35s var(--ease);
}
.rr-card:hover { background: rgba(255,255,255,.07); border-top-color: var(--yellow); transform: translateY(-4px); }
.rr-card .icon { width: 30px; height: 30px; color: var(--yellow); margin-bottom: 16px; }
.rr-card h3 { font-size: 15.5px; line-height: 1.25; }

/* ---- how it works: a numbered stepper with the connector drawn between the
        numerals, so the sequence reads as one run rather than six boxes ---- */
.rr-steps { list-style: none; margin: 0; padding: 0; display: grid; gap: 0; }
.rr-step { position: relative; display: grid; grid-template-columns: 56px minmax(0, 1fr); gap: 22px; padding: 0 0 30px; }
.rr-step:last-child { padding-bottom: 0; }
/* connector runs from under one numeral to the next */
.rr-step:not(:last-child)::before {
  content: ""; position: absolute; left: 27px; top: 58px; bottom: 8px; width: 2px;
  background: linear-gradient(180deg, rgba(254,194,2,.5), rgba(255,255,255,.08));
}
.rr-step h3 { font-size: 17px; margin-bottom: 8px; }
.rr-step p { font-size: 15px; font-weight: 500; color: rgba(255,255,255,.7); line-height: 1.6; }
.rr-step .step-num { margin-bottom: 0; }

/* ---- weigh-up: the two ways this goes, side by side. Carries the section's
        argument better than prose can, and it is the page's closing pitch. ---- */
.weigh { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 20px; }
.weigh-col {
  padding: 30px 32px 32px;
  background: var(--white);
  border: 1px solid rgba(5,5,5,.12);
  clip-path: polygon(0 0, calc(100% - var(--notch)) 0, 100% var(--notch), 100% 100%, 0 100%);
}
.weigh-col.is-pli { border-color: rgba(5,5,5,.2); box-shadow: inset 4px 0 0 var(--yellow); }
.weigh-label {
  font-size: 11.5px; font-weight: 800; letter-spacing: .18em; text-transform: uppercase;
  color: rgba(5,5,5,.5); margin-bottom: 18px;
}
.weigh-col.is-pli .weigh-label { color: #8a6800; }
.weigh-col ul { list-style: none; margin: 0; padding: 0; }
.weigh-col li {
  position: relative; padding: 12px 0 12px 30px;
  border-bottom: 1px solid rgba(5,5,5,.1);
  font-size: 15px; font-weight: 600; color: rgba(5,5,5,.78); line-height: 1.45;
}
.weigh-col li:last-child { border-bottom: 0; }
/* a cross for the do-nothing column, a tick for ours — drawn, so no icon font */
.weigh-col li::before,
.weigh-col li::after {
  content: ""; position: absolute; left: 3px; top: 19px;
  width: 13px; height: 2px; background: rgba(5,5,5,.32);
}
.weigh-col li::before { transform: rotate(45deg); }
.weigh-col li::after { transform: rotate(-45deg); }
.weigh-col.is-pli li::before {
  left: 4px; top: 20px; width: 6px; height: 2px;
  background: var(--black); transform: rotate(45deg); transform-origin: left center;
}
.weigh-col.is-pli li::after {
  left: 8px; top: 22px; width: 12px; height: 2px;
  background: var(--black); transform: rotate(-52deg); transform-origin: left center;
}

/* ---- division split. Industry pages route to two division sites rather than
        the single .group-cta a service page carries, so each gets real estate
        and a reason rather than being a bare link. ---- */
.family-split { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 20px; }
/* Some industries draw on all three divisions rather than two. Collapses
   straight to one column at 920 with the base .family-split rule — a 2-up
   middle step would leave a ragged single card on the second row. */
.family-split-three { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.family-card {
  display: block; padding: 34px 32px 30px;
  background: rgba(255,255,255,.04);
  border: 1px solid rgba(255,255,255,.12);
  text-decoration: none; color: inherit;
  clip-path: polygon(0 0, 100% 0, 100% calc(100% - var(--notch)), calc(100% - var(--notch)) 100%, 0 100%);
  transition: background .3s var(--ease), border-color .3s var(--ease);
}
.family-card:hover, .family-card:focus-visible { background: rgba(255,255,255,.07); border-color: rgba(254,194,2,.45); }
.family-mark {
  display: inline-block; margin-bottom: 20px;
  font-size: 15px; font-weight: 900; line-height: 1.08;
  text-transform: uppercase; letter-spacing: .04em;
  color: var(--yellow);
  border-left: 3px solid var(--yellow); padding-left: 12px;
}
.family-card h3 {
  font-size: clamp(18px, 1.7vw, 22px); font-weight: 800;
  text-transform: uppercase; letter-spacing: .01em; line-height: 1.2; margin-bottom: 12px;
}
.family-card > p { font-size: 15px; font-weight: 500; color: rgba(255,255,255,.66); line-height: 1.6; margin-bottom: 20px; }
.family-go {
  display: inline-flex; align-items: center; gap: 10px;
  font-size: 13px; font-weight: 800; text-transform: uppercase; letter-spacing: .1em;
  color: var(--yellow);
  transition: gap .3s var(--ease);
}
.family-card:hover .family-go { gap: 16px; }

@media (prefers-reduced-motion: reduce) {
  /* the global rule collapses durations to .01ms, which would strand these
     mid-keyframe at opacity 0 — show the finished state instead */
  .scan-sweep { display: none; }
  .scan-mark { opacity: 1; animation: none; }
  .chip.bad, .chip.good, .guard-glow { animation: none; }
  .chip.bad { transform: translateX(102px); opacity: 1; }
  .chip.good { opacity: 1; }
  .sort-svg .chip.up { animation: none; opacity: 1; transform: translate(182px, -37px); }
  .sort-svg .chip.down { animation: none; opacity: 1; transform: translate(182px, 41px); }
  .fix-chip, .fix-glow { animation: none; }
  .fix-chip { opacity: 1; transform: translateX(190px); }
  .fix-chip .mark-was { animation: none; opacity: 0; }
  .fix-chip .mark-now { animation: none; opacity: 1; }
  .rep-dot, .rep-halo { animation: none; }
  .rep-dot { opacity: 1; transform: translateX(-48px); }
  .rep-halo { opacity: .3; }
  .asm-part, .asm-out, .asm-glow { animation: none; }
  .asm-part { opacity: 1; transform: none; }
  .asm-out { opacity: 1; transform: translateX(96px); }
  .asm-glow { opacity: .12; }
  .pak-box, .pak-glow { animation: none; }
  .pak-box { opacity: 1; transform: translateX(196px); }
  .pak-box .pak-label, .pak-box .chip-mark { animation: none; opacity: 1; }
  .pak-glow { opacity: .12; }
  .kit-run, .kit-slot, .kit-check { animation: none; }
  .kit-run { opacity: 1; transform: translateX(170px); }
  .kit-slot, .kit-check { opacity: 1; }
  .stf-on { animation: none; opacity: 1; }
  .whs-in, .whs-out, .whs-glow { animation: none; }
  .whs-in, .whs-out { opacity: 1; transform: translateX(150px); }
  .whs-glow { opacity: .12; }
  .rev-back, .rev-out, .rev-glow { animation: none; }
  /* mirrored, like the running animation: inbound has come back down the top
     lane, outbound has gone forward along the bottom one */
  .rev-back { opacity: 1; transform: translateX(-140px); }
  .rev-out { opacity: 1; transform: translateX(140px); }
  .rev-glow { opacity: .12; }
}

/* ==========================================================
   RESOURCES  (body.page-res, body.page-post)
   Two page types. The index routes; the posts are read start to finish. Both
   lean on what already exists — .legal-wrap for the article column, .faq for
   the questions, .page-hero for the banner — so the only genuinely new pieces
   here are the feed card, the empty download state, and the article furniture.
   ========================================================== */

/* ---- hero jump rail. The index carries three separate blocks; naming them in
        the hero means nobody has to scroll to find out what is on the page.
        Sits below the CTAs, in the same slot the industry pages use for their
        stat rail. ---- */
.res-jump { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 36px; }
.res-jump a {
  display: inline-flex; align-items: baseline; gap: 10px;
  padding: 11px 20px;
  background: rgba(255,255,255,.05);
  border: 1px solid rgba(255,255,255,.15);
  font-size: 12px; font-weight: 800; letter-spacing: .12em; text-transform: uppercase;
  color: rgba(255,255,255,.8); text-decoration: none;
  transition: color .3s var(--ease), background .3s var(--ease), border-color .3s var(--ease);
}
.res-jump a:hover, .res-jump a:focus-visible {
  color: var(--black); background: var(--yellow); border-color: var(--yellow);
}
.res-jump b { font-size: 11px; font-weight: 900; color: var(--yellow); }
.res-jump a:hover b, .res-jump a:focus-visible b { color: rgba(5,5,5,.5); }

/* ---- the insights feed. Same motion language as the industries hub — image
        scale, left-rail wipe, arrow slide — but built for a light band, so the
        card is white-on-grey with a shadow rather than a lifted tint. ---- */
.post-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 26px; }
.post-card {
  position: relative; display: flex; flex-direction: column; overflow: hidden;
  text-decoration: none; color: inherit;
  background: var(--white);
  border: 1px solid rgba(5,5,5,.12);
  clip-path: polygon(0 0, 100% 0, 100% calc(100% - var(--notch)), calc(100% - var(--notch)) 100%, 0 100%);
  transition: border-color .35s var(--ease), box-shadow .35s var(--ease), transform .35s var(--ease);
}
.post-card:hover, .post-card:focus-visible {
  border-color: rgba(5,5,5,.24);
  box-shadow: 0 18px 44px rgba(5,5,5,.13);
  transform: translateY(-4px);
}
.post-card::before {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 3px; z-index: 4;
  background: var(--yellow);
  transform: scaleY(0); transform-origin: top;
  transition: transform .4s var(--ease);
}
.post-card:hover::before, .post-card:focus-visible::before { transform: scaleY(1); }

.post-media { display: block; position: relative; aspect-ratio: 16 / 9; overflow: hidden; isolation: isolate; }
.post-media img {
  width: 100%; height: 100%; object-fit: cover; display: block;
  filter: grayscale(1) contrast(1.05) brightness(.6);
  transform: scale(1.02);
  transition: transform .6s var(--ease), filter .5s var(--ease);
}
.post-card:hover .post-media img,
.post-card:focus-visible .post-media img {
  transform: scale(1.07);
  filter: grayscale(1) contrast(1.05) brightness(.8);
}
.post-media::before {
  content: ""; position: absolute; inset: 0; z-index: 1;
  background: var(--yellow); mix-blend-mode: color; opacity: .28;
}
.post-media::after {
  content: ""; position: absolute; inset: 0; z-index: 2;
  background: linear-gradient(180deg, rgba(5,5,5,.1) 42%, rgba(5,5,5,.82));
}
/* category and length ride on the image, so the card answers "what kind of
   read is this, and how long" before the title has to */
.post-cat, .post-time {
  position: absolute; bottom: 16px; z-index: 3;
  font-weight: 900; text-transform: uppercase;
}
.post-cat { left: 26px; font-size: 11px; letter-spacing: .16em; color: var(--yellow); }
.post-time { right: 26px; font-size: 11px; font-weight: 800; letter-spacing: .12em; color: rgba(255,255,255,.68); }

.post-body { display: flex; flex-direction: column; flex: 1; padding: 28px 32px 32px; }
.post-body h3 {
  font-size: clamp(19px, 1.9vw, 25px); line-height: 1.16; margin-bottom: 14px;
  transition: color .3s var(--ease);
}
.post-card:hover h3, .post-card:focus-visible h3 { color: #6b5200; }
.post-body > p { font-size: 15px; font-weight: 500; color: rgba(5,5,5,.7); line-height: 1.6; margin-bottom: 22px; }
.post-more {
  display: inline-flex; align-items: center; gap: 10px;
  /* margin-top:auto pins this to the card floor so two cards with different
     excerpt lengths still line their read-more links up */
  margin-top: auto; padding-right: var(--notch);
  font-size: 12.5px; font-weight: 800; letter-spacing: .1em; text-transform: uppercase;
  color: var(--black);
  transition: gap .3s var(--ease);
}
.post-card:hover .post-more, .post-card:focus-visible .post-more { gap: 16px; }
.post-more svg { color: #8a6800; }

/* ---- downloads, empty on purpose. Dashed rather than solid so the state
        reads as "nothing here yet" by design instead of looking like a band
        that failed to load. ---- */
.dl-empty {
  max-width: 780px; margin: 0 auto; text-align: center;
  padding: 58px 48px 54px;
  background: rgba(255,255,255,.035);
  border: 1px dashed rgba(255,255,255,.22);
  clip-path: polygon(0 0, 100% 0, 100% calc(100% - var(--notch)), calc(100% - var(--notch)) 100%, 0 100%);
}
.dl-mark {
  display: inline-flex; align-items: center; justify-content: center;
  width: 74px; height: 74px; margin-bottom: 26px;
  border: 1px solid rgba(254,194,2,.4);
  background: rgba(254,194,2,.08);
  color: var(--yellow);
}
.dl-empty h3 { font-size: clamp(22px, 2.6vw, 34px); margin-bottom: 16px; }
.dl-empty > p {
  font-size: 16px; font-weight: 500; color: rgba(255,255,255,.7); line-height: 1.66;
  max-width: 58ch; margin: 0 auto 32px;
}
.dl-empty .page-ctas { justify-content: center; }

/* ---- article furniture. The body column itself is .legal-body, which already
        handles long-form measure, headings and the sticky rail. ---- */
.art-meta {
  display: flex; flex-wrap: wrap; align-items: center; gap: 14px;
  margin-top: 28px;
  font-size: 12px; font-weight: 800; letter-spacing: .12em; text-transform: uppercase;
  color: rgba(255,255,255,.62);
}
.art-meta b { color: rgba(255,255,255,.92); font-weight: 800; }
.art-dot { width: 4px; height: 4px; flex: none; background: var(--yellow); }

/* the opening paragraph is set larger — it is the standfirst, and stepping down
   from it into body size is what makes a long page feel like an article */
.legal-body .art-lead {
  font-size: 18.5px; font-weight: 500; color: rgba(5,5,5,.86);
  line-height: 1.62; margin-bottom: 26px;
}
/* closing note. Scoped under .legal-body because .legal-body > p is 0,1,1 and
   would otherwise win on size and colour. */
.legal-body .art-note {
  margin: 44px 0 0; padding: 26px 30px;
  background: rgba(254,194,2,.13); border-left: 3px solid var(--yellow);
  font-size: 16px; font-weight: 600; color: var(--black); line-height: 1.65;
}

/* read-next. Also scoped: .legal-body a underlines every link in the column,
   which would draw a line under the whole card. */
.legal-body .art-next {
  display: block; margin-top: 54px; padding: 28px 32px;
  background: var(--white); border: 1px solid rgba(5,5,5,.14);
  text-decoration: none; color: inherit;
  clip-path: polygon(0 0, 100% 0, 100% calc(100% - var(--notch)), calc(100% - var(--notch)) 100%, 0 100%);
  transition: border-color .35s var(--ease), box-shadow .35s var(--ease), transform .35s var(--ease);
}
.legal-body .art-next:hover, .legal-body .art-next:focus-visible {
  border-color: rgba(5,5,5,.26);
  box-shadow: 0 16px 38px rgba(5,5,5,.12);
  transform: translateY(-3px);
}
.art-next p {
  font-size: 11.5px; font-weight: 800; letter-spacing: .18em; text-transform: uppercase;
  color: #6b5200; margin-bottom: 10px;
}
.art-next h3 { font-size: clamp(18px, 1.8vw, 23px); line-height: 1.18; padding-right: var(--notch); }
.art-next span {
  display: inline-flex; align-items: center; gap: 10px; margin-top: 15px;
  font-size: 12.5px; font-weight: 800; letter-spacing: .1em; text-transform: uppercase;
  transition: gap .3s var(--ease);
}
.legal-body .art-next:hover span, .legal-body .art-next:focus-visible span { gap: 16px; }

/* ---- FAQ on a light band. The block was written for near-black only: the
        hairlines, the body copy and the hover colour all assume white type on
        dark, and PLI Yellow on #F4F4F4 is far too weak to carry either the
        hover state or the open/close mark. ---- */
.faq.panel-light .faq-item { border-bottom-color: rgba(5,5,5,.14); }
.faq.panel-light .faq-item summary:hover { color: #6b5200; }
.faq.panel-light .faq-mark::before,
.faq.panel-light .faq-mark::after { background: #8a6800; }
.faq.panel-light .faq-body { color: rgba(5,5,5,.74); }
.faq.panel-light .faq-body a { color: var(--black); }

/* careers line under the closing CTA — a different audience than the rest of
   the band, so it is separated by a rule rather than sitting in the CTA row */
.cta-careers {
  max-width: 560px; margin: 36px auto 0; padding-top: 26px;
  border-top: 1px solid rgba(255,255,255,.12);
  font-size: 14.5px; font-weight: 600; color: rgba(255,255,255,.6);
}
.cta-careers a {
  color: var(--yellow); font-weight: 800;
  text-decoration: underline; text-underline-offset: 4px;
  transition: color .3s var(--ease);
}
.cta-careers a:hover { color: var(--white); }

/* ==========================================================
   RESPONSIVE
   ========================================================== */
/* The phone number is the first thing to go when the header runs out of room,
   then the nav links collapse to the hamburger. */
@media (max-width: 1320px) {
  .header-phone { display: none; }
}

/* Below this the links move into a full-screen overlay behind the hamburger.
   The CTA stays in the header bar so it is always one tap away. */
@media (max-width: 1150px) {
  /* Height comes from viewport units, not a bottom offset: the header keeps a
     transform after the intro, which makes it the containing block for this
     fixed element and would collapse a top/bottom pair to zero. */
  .main-nav {
    position: fixed;
    top: var(--header-h); right: 0; left: 0;
    height: calc(100svh - var(--header-h));
    background: rgba(5,5,5,.98);
    backdrop-filter: blur(20px);
    flex-direction: column; align-items: flex-start;
    /* `safe` centres the menu while it fits, then flips to start-aligned once
       it outgrows the screen. Plain `center` overflows in both directions, so
       an expanded Solutions panel pushed the top of the list above the scroll
       origin where it could not be reached at all. */
    justify-content: center;
    justify-content: safe center;
    gap: 4px; padding: 24px clamp(28px, 8vw, 90px) 44px;
    transform: translateX(100%);
    /* A transform alone still leaves every link focusable and announced while
       the menu is shut, so visibility does the actual hiding — held until the
       slide finishes on close, flipped instantly on open. */
    visibility: hidden;
    transition: transform .5s var(--ease), visibility 0s linear .5s;
    overflow-y: auto;
  }
  .main-nav.open {
    transform: none;
    visibility: visible;
    transition: transform .5s var(--ease), visibility 0s;
  }
  .main-nav a:not(.btn) {
    font-size: clamp(28px, 5vw, 44px);
    letter-spacing: .02em; font-weight: 700;
    height: auto; line-height: 1.35; padding: 8px 0;
    flex-shrink: 0;   /* never let the label collapse to its padding */
  }
  .main-nav a:not(.btn) span::after { display: none; }
  .main-nav a:not(.btn):hover span { transform: none; color: var(--yellow); }

  .nav-toggle {
    display: flex; flex-direction: column; gap: 5px; margin-left: 8px;
    background: none; border: none; cursor: pointer; padding: 10px;
  }
  .nav-toggle span { width: 26px; height: 2.5px; background: var(--white); transition: transform .35s var(--ease), opacity .3s; }
  .nav-toggle.open span:nth-child(1) { transform: translateY(7.5px) rotate(45deg); }
  .nav-toggle.open span:nth-child(2) { opacity: 0; }
  .nav-toggle.open span:nth-child(3) { transform: translateY(-7.5px) rotate(-45deg); }

  .nav-cta { margin-left: auto; }

  .nav-foot {
    display: block; flex-shrink: 0;
    margin-top: 34px; padding-top: 26px;
    border-top: 1px solid rgba(255,255,255,.14);
    width: 100%; max-width: 460px;
  }
  .nav-phone {
    display: inline-flex; align-items: center; gap: 12px;
    color: var(--yellow); text-decoration: none;
    font-weight: 700; font-size: 26px; letter-spacing: .03em;
  }
  .nav-foot > span {
    display: block; margin-top: 8px;
    color: var(--gray); font-size: 13px;
    text-transform: uppercase; letter-spacing: .14em;
  }

  /* The mega menu stops floating and becomes an accordion in the overlay.
     These rules sit last in the block on purpose: the generic
     .main-nav a:not(.btn) sizing above would otherwise render the panel
     links at 44px alongside the top-level items. */
  .nav-item { flex-wrap: wrap; width: 100%; gap: 0; }
  .nav-item .nav-mega-link { flex: 1; }
  .mega-caret {
    width: 46px; height: 46px; margin-right: -10px;
    color: var(--yellow);
  }
  .mega {
    position: static; flex-basis: 100%;
    display: none;
    opacity: 1; visibility: visible; transform: none; transition: none;
  }
  .nav-item.is-open .mega { display: block; }
  .mega-inner {
    background: none; box-shadow: none; clip-path: none;
    backdrop-filter: none; -webkit-backdrop-filter: none;
    border: none; border-left: 2px solid rgba(254,194,2,.4);
    padding: 4px 0 16px 18px;
    margin-bottom: 8px;
  }
  .mega-cols { grid-template-columns: 1fr; gap: 2px 0; }
  /* was 10px: uppercase at .2em tracking is already hard to read small, and
     this is the mobile menu's only grouping cue. Matches the desktop size. */
  .mega-head { font-size: 11px; margin: 15px 0 2px; }
  .main-nav .mega .mega-all { font-size: 13px; padding-bottom: 12px; margin-bottom: 6px; }
  /* font-weight too: .main-nav a:not(.btn) above ties on specificity and would
     otherwise leak 700 onto these rows */
  .main-nav .mega-cols a { font-size: 17px; font-weight: 500; line-height: 1.3; padding: 12px 6px 12px 0; }
  .mega-cols a .icon { width: 19px; height: 19px; }

  .solution-grid { grid-template-columns: repeat(4, 1fr); }
  .trust-list { grid-template-columns: repeat(3, 1fr); row-gap: 34px; }
  .segment-grid { grid-template-columns: 1fr; max-width: 580px; margin: 0 auto; }
  /* 2.2:1 is too letterboxed to read on a phone; 16:9 centre-crops the sides,
     which is why the crops keep their subject in the middle 80%. */
  .band-media { height: clamp(160px, 46vw, 240px); }
}

@media (max-width: 920px) {
  .problem-grid { grid-template-columns: repeat(2, 1fr); }
  .solution-grid { grid-template-columns: repeat(3, 1fr); }
  .coverage-wrap, .contact-wrap { grid-template-columns: 1fr; }
  .about-wrap { grid-template-columns: 1fr; gap: 36px; }
  .about-band { padding: 110px 0; }
  /* stacked: the short sits under the copy, aligned to the same left edge */
  .about-media { justify-self: start; }
  .offices-wrap { grid-template-columns: 1fr; gap: 30px; }
  .reach-wrap { grid-template-columns: 1fr; gap: 34px; }
  /* drop the desktop placement so DOM order applies: copy first, image under it */
  .reach-figure, .reach-body { grid-column: auto; grid-row: auto; }
  /* Stacked there is no taller sibling to match, and the grid row no longer
     gives the figure a definite height — min-height would just open a charcoal
     gap under the image. Back to a plain 3:2. */
  /* ratio is already 16/9 on the base rule; nothing to override here */
  .reach-head { margin-bottom: 22px; }
  .reach { padding: 100px 0 110px; }
  /* brand block on its own row, the four link columns fold to 2x2 under it */
  .footer-grid { grid-template-columns: 1fr; }
  .footer-nav { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 28px 32px; }
  .industry-grid { grid-template-columns: repeat(2, 1fr); }
  .stats { grid-template-columns: repeat(2, 1fr); row-gap: 40px; }

  /* Two columns stop paying for themselves here: neither the headline nor a
     16:9 panel is legible at ~430px each. Stacking lets the type grow back. */
  .hero-content {
    grid-template-columns: 1fr;
    /* row-gap is 0 because the copy's children are promoted into this grid
       below, and each already carries its own margin-bottom. A grid gap here
       would stack on top of those and double the spacing. */
    row-gap: 0;
    padding-bottom: 96px;
  }

  /* Once the hero stacks, the video reads better directly under the headline
     than stranded below the CTAs. .hero-copy carries no styling of its own, so
     display:contents promotes its children into the hero grid at no cost and
     lets the video be ordered between the headline and the paragraph.
     Visual order only: the DOM is untouched, and the video is aria-hidden, so
     reading order and tab order are unaffected. */
  .hero-copy { display: contents; }
  .hero-eyebrow { order: 1; }
  .hero-title   { order: 2; }
  .hero-media   { order: 3; margin-bottom: 32px; }
  .hero-sub     { order: 4; }
  .hero-ctas    { order: 5; }
  /* 37px floor is the value the 320-1920 sweep was signed off against — below
     ~360px "WHEN PRODUCTION" takes two lines by design, and going larger here
     would push that wrap onto wider phones than it already affects. */
  .hero-title { font-size: clamp(37px, 7.2vw, 66px); }
  .hero-sub { max-width: 620px; }
  .hero-media { max-width: 640px; }

  /* the hero's index column stacks under the copy rather than being squeezed
     into a sliver that breaks its labels one letter per line */
  .page-hero-inner { grid-template-columns: 1fr; gap: 40px; }
  /* copy runs the full width here, so the scrim can no longer thin out to one
     side — it goes near-uniform instead */
  .hero-photo::after {
    background:
      linear-gradient(180deg, rgba(5,5,5,.9), rgba(5,5,5,.86) 55%, rgba(5,5,5,.93));
  }

  /* service cards drop to two up; the centred-orphan rule has to be undone
     or the fourth card would sit in a half-column */
  .svc-grid > *, .svc-grid > :nth-child(4):nth-last-child(2) { grid-column: span 3; }
  .svc-grid-staffing > * { grid-column: span 6; }
  .why-grid { grid-template-columns: repeat(2, 1fr); }
  .triage-inner { grid-template-columns: 1fr; gap: 34px; }
  .triage-ctas { flex-direction: row; flex-wrap: wrap; }

  /* service detail pages */
  .check-grid { grid-template-columns: 1fr; gap: 0; }
  .where-grid { grid-template-columns: repeat(2, 1fr); }
  .who-inner { grid-template-columns: 1fr; gap: 38px; }
  .figure-wrap { grid-template-columns: 1fr; gap: 36px; }
  /* .figure-band.flip .figure-wrap is 0,3,0 and would otherwise keep its
     two-column split at phone widths, squeezing the photo to ~94px. Reset the
     order too, so every band reads photo-then-copy on a phone. */
  .figure-band.flip .figure-wrap { grid-template-columns: 1fr; }
  .figure-band.flip .photo-figure { order: 1; }
  .figure-band.flip .figure-cap { order: 2; }
  .figure-band { padding: 84px 0 90px; }
  .trust-strip { grid-template-columns: 1fr; }
  .trust-strip-four { grid-template-columns: repeat(2, 1fr); }
  .svc-grid-four, .svc-grid-three { grid-template-columns: repeat(2, 1fr); }
  .where-grid-three { grid-template-columns: repeat(2, 1fr); }

  /* industry modules */
  .ind-cards { grid-template-columns: minmax(0, 1fr); }
  .post-grid { grid-template-columns: minmax(0, 1fr); }
  .prob-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }

  /* legal: the contents rail stops being sticky and sits above the text */
  .legal-wrap { grid-template-columns: minmax(0, 1fr); gap: 34px; }
  .legal-toc { position: static; }

  /* contact */
  .reach-grid, .addr-grid { grid-template-columns: minmax(0, 1fr); }

  /* about */
  .val-grid { grid-template-columns: minmax(0, 1fr); }

  /* rapid response */
  .rr-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .weigh { grid-template-columns: minmax(0, 1fr); }
  /* the track turns vertical: rail down the left, steps stacked beside it, and
     --p now drives scaleY */
  .track-steps { grid-template-columns: minmax(0, 1fr); gap: 0; }
  .track-rail { left: 13px; right: auto; top: 0; bottom: 0; width: 3px; height: auto; }
  .track-fill { width: 100%; height: 100%; transform: scaleY(var(--p, 0)); transform-origin: top; }
  .track-step { padding: 0 0 30px 52px; position: relative; }
  .track-step:last-child { padding-bottom: 0; }
  .track-dot { position: absolute; left: 0; top: 0; margin-bottom: 0; }
  .plate-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .sol-map, .reason-grid, .family-split { grid-template-columns: minmax(0, 1fr); }
  .reason-grid { gap: 0; }
  /* the rail stacks, so the divider moves from between columns to between rows */
  .ind-stats { grid-template-columns: minmax(0, 1fr); }
  .ind-stat { padding: 22px 0 0; }
  .ind-stat + .ind-stat {
    padding-left: 0; border-left: 0;
    border-top: 1px solid rgba(255,255,255,.14);
  }
  .ind-stat p { max-width: 48ch; }
}

@media (max-width: 760px) {
  :root { --header-h: 66px; }
  /* the sticky bottom bar carries the CTA from here down */
  .nav-cta { display: none; }
  .nav-toggle { margin-left: auto; }

  .trust-list { grid-template-columns: repeat(2, 1fr); }
  .trust-list li:last-child { grid-column: 1 / -1; }
  .solution-grid, .problem-grid { grid-template-columns: repeat(2, 1fr); }
  .industry-grid { grid-template-columns: 1fr; }
  .form-row { grid-template-columns: 1fr; gap: 0; }
  /* 2x2 is kept rather than a single 21-link stack — one column here would push
     the legal bar roughly a full screen further down. */
  .footer-grid { gap: 30px; }
  .mobile-cta { display: flex; }
  body { padding-bottom: 66px; }

  /* Accessible tap targets (WCAG 2.5.5 asks for ~44px) and no text under 12px. */
  .site-footer .footer-col a { padding: 12px 0; }
  .footer-contact a, .footer-contact address { padding: 11px 0; }
  .footer-cta { width: 100%; justify-content: center; }
  /* The Solutions and Industries headings are hub links, and a 13px cap-height
     line is not a tappable target. Padding brings them to ~45px; the heading
     margin comes off to keep the gap under the heading close to what it was. */
  .site-footer .footer-col h3 { margin-bottom: 4px; }
  .site-footer .footer-col h3 a { padding: 16px 0; }
  .nav-toggle { padding: 13px 10px; }
  .stat span, .contact-line small, .file-hint, .contact-form label { font-size: 12px; }
  .state-list li { padding: 12px 20px; }
  .footer-bottom { font-size: 13px; }
  .footer-bottom a { padding: 12px 0; display: inline-block; }

  /* narrow two-column cards: trim the gutters and let long service names break */
  .solution-card { padding: 26px 10px 24px; font-size: 12px; }
  .solution-card span, .problem-tile span { overflow-wrap: anywhere; hyphens: auto; }
  .problem-tile { padding: 28px 14px; }
  .hero-ctas .btn { width: 100%; justify-content: center; }
  .hero-scroll { display: none; }

  /* interior pages: everything to a single column */
  .page-hero { padding: calc(var(--header-h) + 60px) 0 76px; }
  .page-ctas .btn, .triage-ctas .btn, .reach-cta .btn { width: 100%; justify-content: center; }
  .svc-section, .why, .faq { padding: 84px 0 90px; }
  .svc-grid > *,
  .svc-grid > :nth-child(4):nth-last-child(2),
  .svc-grid-two > * { grid-column: span 6; }
  .why-grid { grid-template-columns: 1fr; }
  .svc-list { grid-template-columns: 1fr; }
  .triage-band { padding: 80px 0; }
  .cta-band { padding: 100px 0 110px; }

  /* Plate labels are SVG text: they scale with the drawing instead of
     reflowing, so at phone widths they fall under ~6px. The plate keeps its
     own scale and scrolls inside its frame rather than shrinking further. */
  .figure-plate { overflow-x: auto; clip-path: none; padding: 18px; }
  .figure-plate svg { min-width: 620px; }

  /* service detail pages */
  .where-grid, .where-grid-three, .svc-grid-four, .svc-grid-three, .trust-strip-four { grid-template-columns: 1fr; }
  /* these grids drop to a single explicit track, so their cards must span 1.
     Left at the .svc-grid default of span 6 they generate five zero-width
     implicit tracks and the gaps between them eat ~110px of card width. */
  .svc-grid-four > *, .svc-grid-three > * { grid-column: span 1; }
  .scan-panel { padding: 16px 16px 18px; }
  .callout { font-size: 17px; padding-left: 18px; }

  /* contact */
  .modal-panel { padding: 34px 22px 30px; }
  .modal-close { top: 10px; right: 10px; width: 38px; height: 38px; }
  .reach-card { padding: 22px 20px 24px; }
  .addr-card { padding: 24px 22px 26px; }

  /* about */
  .val-card { padding: 26px 24px 28px; }

  /* rapid response */
  .rr-grid { grid-template-columns: minmax(0, 1fr); }
  .weigh-col { padding: 24px 22px 26px; }
  .rr-step { grid-template-columns: 46px minmax(0, 1fr); gap: 16px; }
  /* .step-num is a fixed 56px square, which would overhang the narrower column */
  .rr-step .step-num { width: 46px; height: 46px; font-size: 17px; }
  .rr-step:not(:last-child)::before { left: 22px; top: 50px; }

  /* industry pages */
  .prob-grid, .plate-grid { grid-template-columns: minmax(0, 1fr); }
  .ind-card-body { padding: 22px 22px 24px; }
  .post-body { padding: 24px 22px 26px; }
  .post-cat { left: 22px; }
  .post-time { right: 22px; }
  .dl-empty { padding: 42px 24px 40px; }
  .legal-body .art-note { padding: 22px 22px; }
  .legal-body .art-next { padding: 24px 22px; }
  .ind-card-num { left: 22px; }
  .prob-card { padding: 26px 22px 24px; }
  .plate { padding: 24px 20px 26px; }
  .sol-row { padding: 18px 18px; gap: 4px 14px; }
  .family-card { padding: 28px 24px 26px; }

  /* native swipe carousel instead of the desktop pin; one whole card per
     snap, with a sliver of the next showing there is more to swipe */
  .process-pin { min-height: 0; padding: 90px 0 100px; }
  .process-viewport {
    --gap: 16px;
    overflow-x: auto; overflow-y: hidden;
    scroll-snap-type: x mandatory;
    scroll-padding-left: 28px;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    margin: 0 -28px; padding: 0 28px;
  }
  .process-viewport::-webkit-scrollbar { display: none; }
  .process-steps {
    transform: none !important;
    grid-auto-columns: calc(100% - 46px);   /* full card + a peek of the next */
  }
  .process-step { scroll-snap-align: start; }
  .process-line { margin-top: 28px; }
  .contact-form { padding: 32px 24px; }
  .segments, .problems, .solutions, .industries { padding: 90px 0 100px; }
  .about-band { padding: 110px 0; }
  .coverage, .contact { padding: 100px 0; }
}

/* ==========================================================
   REDUCED MOTION
   ========================================================== */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { animation-duration: .01ms !important; transition-duration: .01ms !important; }
  .marquee-tilt { transform: none; width: 100%; margin-left: 0; }
  /* app.js pauses the hero video so the poster frame is what stays on screen;
     this is the belt to that braces, for the case where JS never runs. The
     two YouTube embeds autoplay inside iframes where neither JS nor CSS can
     pause them, so they are hidden outright and the aerial still stands in. */
  .hero-video, .office-embed, .reach-embed { display: none; }
  .office-frame {
    background: url("../img/reach-poster.jpg") center / cover no-repeat var(--charcoal);
  }
  .hero-video-frame {
    background: url("../img/hero-poster.jpg") center / cover no-repeat var(--charcoal);
  }
  .reach-frame {
    background: url("../img/reach-poster.jpg") center / cover no-repeat var(--charcoal);
  }
}

/* ==========================================================
   MOBILE PAINT BUDGET
   Phones have no WebGL scene to compete with, but they do have far weaker
   fill rate. Everything below is an effect that costs a composited layer or
   a per-frame repaint while being close to imperceptible on a small screen
   held at arm's length. Desktop keeps all of it.
   ========================================================== */
@media (max-width: 819px) {
  /* inset:-50% makes this four times the viewport area, fixed, animating
     forever — for a grain texture at 2.8% opacity that no phone screen
     resolves. The layer alone is the cost, not the movement. */

  /* No loading screen on phones: there is no WebGL scene to mask, so the page
     is painted and readable before a curtain could finish. Hidden in CSS
     rather than by script so it never paints at all — otherwise it flashes
     between first paint and the point JS could remove it. The yellow wipe in
     intro() is gated on the same condition. */

  /* backdrop-filter forces the browser to re-blur everything behind the
     element. .hero-eyebrow is the worst case: it carries a blur AND is
     animated in during the opening sequence, so the blur is recomputed on
     every frame of the first thing anyone sees. The backgrounds below are
     nudged more opaque to hold the same contrast without it. */
  .hero-eyebrow {
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    background: rgba(5, 5, 5, .55);
  }
  .site-header.scrolled {
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    background: rgba(5, 5, 5, .92);
  }
  .panel-scrim {
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
  }
  .hero-index {
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    background: rgba(255, 255, 255, .055);
  }
}

/* The pulse ring used to animate box-shadow, which repaints the element on
   every frame for the life of the page. A pseudo-element scaling and fading
   is the same picture on the compositor only. Applied everywhere, not just
   mobile — it is strictly cheaper. */
.pulse-dot {
  position: relative;
  box-shadow: none;
  animation: none;
}
.pulse-dot::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;               /* behind the dot, so it reads as a ring leaving it */
  border-radius: 50%;
  background: rgba(254, 194, 2, .55);
  animation: pulseRing 2s ease-out infinite;
  will-change: transform, opacity;
}
@keyframes pulseRing {
  0%   { transform: scale(1);   opacity: .55; }
  70%  { transform: scale(3.9); opacity: 0; }
  100% { transform: scale(3.9); opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .pulse-dot::after { animation: none; opacity: 0; }
}

/* ==========================================================
   MOBILE HERO ENTRANCE
   The GSAP entrance was removed on phones because gsap.from() holds the text
   at opacity 0 until the timeline runs — after the preloader, after load,
   after GSAP initialises. The most important copy on the site was gated
   behind JavaScript.

   This is the same gesture in CSS. The stylesheet is already render-blocking,
   so these keyframes begin on the first painted frame: they cannot delay the
   text past the point it would have appeared anyway, and nothing about them
   depends on a script running. Transform and opacity only, so it stays on the
   compositor, and no splitting, masks or held will-change.

   To drop it again, delete this block — the hero paints correctly without it.
   ========================================================== */
@media (max-width: 819px) {
  .hero-copy > *,
  .hero-media {
    animation: heroEnter .45s cubic-bezier(.22, .9, .24, 1) both;
  }
  /* delays follow the reordered visual sequence, not DOM order */
  .hero-copy > .hero-title      { animation-delay: .05s; }
  .hero-media                   { animation-delay: .10s; }
  .hero-copy > .hero-sub        { animation-delay: .15s; }
  .hero-copy > .hero-ctas       { animation-delay: .20s; }

  @keyframes heroEnter {
    from { opacity: 0; transform: translateY(14px); }
    to   { opacity: 1; transform: none; }
  }

  /* Nothing should animate the copy out of view for someone who asked for
     less motion — paint it and leave it. */
  @media (prefers-reduced-motion: reduce) {
    .hero-copy > *,
    .hero-media { animation: none; }
  }
}

/* ==========================================================
   LEADERSHIP BAND
   Portrait plus biography. The image is a cut-out on the page's own black,
   so it sits in the panel rather than reading as a pasted-in photograph.
   ========================================================== */
.leader-band { padding: clamp(72px, 9vw, 128px) 0; }
.leader-wrap {
  display: grid;
  grid-template-columns: minmax(0, .8fr) minmax(0, 1.2fr);
  align-items: center;
  gap: clamp(28px, 4vw, 64px);
}
.leader-figure { margin: 0; }
/* No plate behind the portrait: the supplied image already carries a PLI
   Yellow ground, so anything under it would sit yellow on yellow. The corner
   notch is applied to the photograph itself instead, which is what ties it to
   the rest of the page. */
.leader-figure img {
  display: block;
  width: 100%; height: auto;
  clip-path: polygon(0 0, 100% 0, 100% calc(100% - var(--notch)), calc(100% - var(--notch)) 100%, 0 100%);
}
.leader-name {
  font-weight: 700; text-transform: uppercase;
  letter-spacing: .06em; font-size: 17px;
  margin: 4px 0 22px;
}
.leader-name span {
  display: block;
  font-weight: 500; text-transform: uppercase;
  letter-spacing: .16em; font-size: 12px;
  color: var(--yellow);
  margin-top: 5px;
}
.leader-quote {
  margin: 26px 0 0;
  padding: 0 0 0 20px;
  border-left: 3px solid var(--yellow);
  font-size: clamp(17px, 1.5vw, 21px);
  font-weight: 500;
  line-height: 1.45;
  color: var(--white);
}

@media (max-width: 920px) {
  .leader-wrap { grid-template-columns: 1fr; gap: 34px; }
  /* cap the portrait so it does not dominate a phone screen before the copy */
  .leader-figure { max-width: 340px; }
}

/* Who We Are: the right column now carries a photograph rather than a pull
   quote, so it needs the same framing the other figures on the page use. */
.who-figure { margin: 0; }
.who-figure img {
  display: block; width: 100%; height: auto;
  aspect-ratio: 3 / 2; object-fit: cover;
  clip-path: polygon(0 0, 100% 0, 100% calc(100% - var(--notch)), calc(100% - var(--notch)) 100%, 0 100%);
}

/* The name is a link now: it should read as the heading it already was, and
   only behave like a link on hover. */
.leader-name a {
  color: inherit;
  text-decoration: none;
  border-bottom: 2px solid rgba(254, 194, 2, .45);
  padding-bottom: 2px;
  transition: border-color .3s var(--ease), color .3s var(--ease);
}
.leader-name a:hover,
.leader-name a:focus-visible { color: var(--yellow); border-bottom-color: var(--yellow); }

@media (max-width: 920px) {
  .who-inner { grid-template-columns: 1fr; gap: 34px; }
}

/* The About page carries the same coverage map as the homepage, but inside a
   standard section rather than its own full-width band, so it needs its own
   top spacing and a slightly tighter column split. */
.about-coverage { margin-top: 44px; grid-template-columns: minmax(0, 400px) minmax(0, 1fr); gap: 56px; }

@media (max-width: 920px) {
  .about-coverage { grid-template-columns: 1fr; gap: 36px; margin-top: 34px; }
}

/* ==========================================================
   ARTICLE: AUTHOR AND SHORT ANSWER
   Two pieces of furniture added for the content programme.

   - .art-author puts a named, checkable human on the byline instead of
     "PLI Team". Authorship is an E-E-A-T signal, and an Organization called
     "PLI Team" is not an entity anything can corroborate.
   - .art-answer is a direct, self-contained answer to the question in the H1.
     Assistants and AI Overviews quote passages that answer a question cleanly;
     .art-lead sets the article up instead, which is a different job. There is
     no length to hit — it just has to make sense quoted away from the page.

   No contents list here: .legal-toc already provides one, wired to the
   [data-toc] scroll-spy in page.js.
   ========================================================== */

/* ---- byline. Sits in the dark hero, so colours are on the light-on-dark
        scale rather than the .legal-body one. ---- */
.art-author {
  display: flex; align-items: flex-start; gap: 16px;
  margin-top: 30px; padding-top: 26px;
  border-top: 1px solid rgba(255,255,255,.14);
  max-width: 62ch;
}
.art-author-img {
  width: 56px; height: 56px; flex: none;
  border-radius: 50%; object-fit: cover;
  border: 2px solid rgba(254,194,2,.55);
}
.art-author-text { min-width: 0; }
.art-author-name {
  margin: 0 0 4px;
  font-size: 15px; font-weight: 800; letter-spacing: .02em;
  color: rgba(255,255,255,.94);
}
.art-author-name a { color: inherit; text-decoration: none; border-bottom: 1px solid rgba(254,194,2,.5); }
.art-author-name a:hover, .art-author-name a:focus-visible { border-bottom-color: var(--yellow); }
.art-author-role {
  margin: 0 0 8px;
  font-size: 14px; font-weight: 500; line-height: 1.55;
  color: rgba(255,255,255,.66);
}
/* the dated meta line keeps the old .art-meta treatment, so both articles still
   read as the same template they did before */
.art-author .art-meta { margin-top: 0; }

/* ---- short answer. Modelled on .art-note (same yellow wash and rule) but
        heavier, because this is the first thing in the column and the passage
        most likely to be quoted elsewhere. ---- */
.legal-body .art-answer {
  margin: 0 0 34px; padding: 24px 28px;
  background: rgba(254,194,2,.13);
  border-left: 3px solid var(--yellow);
}
.legal-body .art-answer p {
  margin: 0;
  font-size: 17px; font-weight: 600; line-height: 1.62;
  color: rgba(5,5,5,.88);
}
.art-answer-label {
  display: block; margin-bottom: 10px;
  font-size: 11px; font-weight: 800; letter-spacing: .18em; text-transform: uppercase;
  color: #7a5c00;
}

@media (max-width: 640px) {
  .art-author { gap: 13px; }
  .art-author-img { width: 46px; height: 46px; }
  .legal-body .art-answer { padding: 20px 20px; }
}

/* ---- inline links in body copy ----
   The stylesheet has no base rule for <a>: every link is styled through its own
   class, so six links written inline in prose — all of them cross-links into
   the resources articles — fell back to the browser default #0000EE. On the
   charcoal panels that is about 2.3:1, well under AA for body text.

   Scoped to links carrying no class of their own, so nothing that already has
   .btn, .nav-link, .footer-all or similar is touched. The same rule is on the
   four division sites. */
main a:not([class]),
footer a:not([class]) {
  color: inherit;
  text-decoration: underline;
  text-decoration-color: var(--yellow);
  text-decoration-thickness: 2px;
  text-underline-offset: 3px;
  transition: color .25s var(--ease);
}
main a:not([class]):hover,
footer a:not([class]):hover { color: var(--yellow); }
.panel-light a:not([class]) { text-decoration-color: var(--yellow); }
