@charset "UTF-8";
/* =========================================================================
   finalert.com — next.css
   Built from SPEC-00-foundation.md + SPEC-01-hero.md.
   Grows section by section. Do not add a section without its approved spec.
   ========================================================================= */

/* ===== TOKENS  (SPEC-00 §1–§6) ===== */
:root {
  /* --- type families (SPEC-00 §1.1) --- */
  --font-display: 'Poppins', 'Century Gothic', 'Trebuchet MS', system-ui, sans-serif;
  --font-ui: 'DM Sans', -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;

  /* --- type scale (SPEC-00 §1.2) — shorthand: weight size/line family --- */
  /* d1 and d2 are the two heading steps the owner fixed by number: an h1 of
     exactly 50px and an h2 of exactly 35px from 1180 up, scaling down below.
     The slopes are solved for that, not eyeballed: 2.8vw + 16.96 is 50 at
     1180 and 1.6vw + 16.12 is 35 at 1180. Both are Poppins 700; every other
     display step below is Poppins 600, which is the only other weight the
     one font request loads. */
  --t-d1:      700 clamp(30px, 2.8vw + 16.96px, 50px)/1.08 var(--font-display);
  --t-d2:      700 clamp(24px, 1.6vw + 16.12px, 35px)/1.16 var(--font-display);
  --t-d3:      600 clamp(21px, 0.6vw + 18px, 26px)/1.26 var(--font-display);
  --t-d4:      600 19px/1.32 var(--font-display);
  --t-num:     600 clamp(28px, 1.4vw + 20px, 44px)/1 var(--font-display);
  --t-num-sm:  600 24px/1 var(--font-display);
  --t-lead:    400 clamp(17px, 0.4vw + 15px, 19px)/1.62 var(--font-ui);
  --t-body:    400 16px/1.65 var(--font-ui);
  --t-body-sm: 400 15px/1.58 var(--font-ui);
  --t-cap:     400 13px/1.45 var(--font-ui);
  --t-micro:   600 12px/1.4 var(--font-ui);
  --t-eyebrow: 600 12px/1.2 var(--font-display);
  --t-label:   600 13px/1.3 var(--font-ui);
  /* SPEC-06-R2 §3 | the one new step: the feature testimonial's pull quote.
     Mulish, not a display face, because it is 296 characters of reading. */
  --t-quote-lg: 400 clamp(20px, .55vw + 18px, 26px)/1.5 var(--font-ui);
  /* SPEC-09-R2 §4's --t-cta-h is RETIRED. The closer's headline is an <h2>
     and the owner set one h2 size for the whole site, so .cta__h now takes
     --t-d2 like every other band headline. */

  /* --- spacing (SPEC-00 §2.1) --- */
  --s-1: 4px;   --s-2: 8px;   --s-3: 12px;  --s-4: 16px;  --s-5: 20px;
  --s-6: 24px;  --s-7: 32px;  --s-8: 40px;  --s-9: 48px;  --s-10: 64px;
  --s-11: 80px; --s-12: 96px; --s-13: 120px; --s-14: 160px;

  /* --- container (SPEC-00 §2.2, widened on owner instruction) ---
     "To be honest I prefer the website to be full width." The measure is no
     longer a fixed 1360: it now FILLS the viewport minus the gutter and only
     stops at 1640, which at 1440 and 1680 means the page runs edge to edge
     with a real margin and at 1920 stops before the lines get unreadable.
     THE GUTTER IS ADDED, NOT SUBTRACTED, and that is the whole subtlety.
     .container is `max-width: var(--container); padding-inline: var(--gutter)`
     under border-box, so the token is a BOX width and the gutter is already
     inside it. Writing min(100% - 2*gutter, cap) therefore charges the gutter
     twice: at 390 the plate sat 48px off each edge instead of 24, and at 1440
     the first glyph landed on 128 instead of 64. min(100%, cap + 2*gutter) is
     the correct form: the box fills the window until the CONTENT reaches the
     cap, and the gutter is a real single gutter at every width.
     --container-narrow is DELIBERATELY unchanged. It is the prose measure
     (the legal body, the guide document column, the CTA-band copy) and a
     1640px paragraph is unreadable at any gutter. */
  --container: min(100%, 1640px + 2 * var(--gutter));
  --container-narrow: 840px;
  --container-wide: min(100%, 1800px + 2 * var(--gutter));
  --gutter: 24px;

  /* --- rhythm (SPEC-00 §2.3, rescaled | REFINE-01 §0.2) ---
     SPEC-00 wrote clamp(56px, 6.2vw, 112px) against its 1180 container. This
     build runs --container: 1360 | a 15% wider measure | and the vertical
     rhythm was never rescaled with it: at 1440 every section ran 89px of
     padding-block and the old clamp did not reach its 112px ceiling until a
     1806px viewport. The two clamps below put the vertical rhythm back in
     proportion to the horizontal measure. Costs ~150px of page height. */
  --section-y: clamp(64px, 7.4vw, 128px);
  --section-y-lg: clamp(80px, 9vw, 152px);
  /* SPEC-H-R3 §3.1. --header-h IS the resting bar height: html's
     scroll-padding-top and .hero's reserved padding both want that value. */
  --hdr-h: 64px;
  --hdr-h-stuck: 56px;
  --header-h: var(--hdr-h);

  /* --- radius (SPEC-00 §2.4) --- */
  --r-xs: 6px;  --r-sm: 10px; --r-md: 14px; --r-lg: 20px; --r-xl: 28px;
  --r-pill: 999px;

  /* --- z layers (SPEC-00 §2.5) --- */
  --z-pattern: 0;
  --z-media: 1;
  --z-overlay: 2;
  --z-glass: 3;
  --z-veil: 3;      /* SPEC-VH-R3 A6.5: the hero's masked grid, over the scrim */
  --z-content: 4;
  --z-rail: 20;
  /* SPEC-H-R3 §9. --z-mega/--z-panel are retired with the block that used
     them; the mega panels are children of <header> and stack inside it. */
  --z-hdr-scrim: 55;
  --z-hdr-panel: 58;
  --z-header: 60;
  --z-hdr-drawer-scrim: 78;
  --z-hdr-drawer: 80;
  --z-modal: 100;

  /* --- colour (SPEC-00 §3.1) --- */
  --navy: #022D5A;
  --gold: #FFCE1B;
  --navy-900: #011E3D;
  --navy-700: #022D5A;
  --navy-500: #0A4B8C;
  --navy-300: #6E8FB4;
  --navy-100: #D3E0EE;
  --navy-050: #EFF4FA;
  --gold-600: #E5B400;
  --gold-200: #FFE894;
  --gold-050: #FFF8DF;
  --ink:    #16212E;
  --ink-2:  #3E5061;
  --ink-3:  #5B6B7B;
  --paper:  #FFFFFF;
  --wash:   #F6F8FA;
  --line:   #E4E9EF;
  --line-2: #EEF2F6;
  --danger: #B3261E;
  --ok:     #17694A;

  /* --- patterns (SPEC-00 §3.3) --- */
  --pat-grid: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='48' height='48'%3E%3Cpath d='M48 0H0v48' fill='none' stroke='%23022D5A' stroke-opacity='.10' stroke-width='1'/%3E%3C/svg%3E");
  --pat-dot: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24'%3E%3Ccircle cx='2' cy='2' r='1.5' fill='%23022D5A' fill-opacity='.14'/%3E%3C/svg%3E");
  --pat-diag: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12'%3E%3Cpath d='M-1 1 1 -1M0 12 12 0M11 13 13 11' stroke='%23022D5A' stroke-opacity='.09' stroke-width='1'/%3E%3C/svg%3E");
  --pat-grid-inv: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='48' height='48'%3E%3Cpath d='M48 0H0v48' fill='none' stroke='%23FFFFFF' stroke-opacity='.09' stroke-width='1'/%3E%3C/svg%3E");

  /* --- elevation (SPEC-00 §5.1) --- */
  --sh-1: 0 1px 2px rgba(2, 45, 90, .06);
  --sh-2: 0 4px 12px -3px rgba(2, 45, 90, .09);
  --sh-3: 0 14px 30px -12px rgba(2, 45, 90, .14);
  --sh-4: 0 30px 64px -28px rgba(2, 45, 90, .18);
  --sh-inset-top: inset 0 1px 0 rgba(255, 255, 255, .9);

  /* --- motion (SPEC-VH-R3 §B1, which OVERRIDES SPEC-00 §6.1 and §6.2) ---
     Four verbs, one per kind of thing, applied consistently down every page:
     numerals count, rules draw, plates settle, copy rises. The distances and
     the two long durations are new; the old table's 14/20/26px over 520ms on
     elements already 60% into the viewport read as a page that had loaded
     slowly rather than a page that was doing something. */
  --ease-out:   cubic-bezier(.22, .61, .36, 1);   /* entrances, reveals */
  --ease-soft:  cubic-bezier(.33, .10, .20, 1);   /* line draws, parallax, long moves */
  --ease-io:    cubic-bezier(.65, .05, .36, 1);   /* two-way state changes */
  --ease-plate: cubic-bezier(.20, .80, .25, 1);   /* plates and bands settling */

  --dur-1: 150ms;   /* colour, border */
  --dur-2: 240ms;   /* transform micro, header state */
  --dur-3: 400ms;   /* small reveal, panel fade */
  --dur-4: 560ms;   /* small reveal */
  --dur-5: 640ms;   /* standard reveal */
  --dur-6: 760ms;   /* plate / media / band reveal */
  --dur-7: 1100ms;  /* rule draw, count-up, one-shot illustration */

  --rise-1: 24px;   /* small: labels, rows, list items */
  --rise-2: 32px;   /* default: cards, paragraphs, figures */
  --rise-3: 40px;   /* plates, media frames, band content */
  --slide-x: 28px;  /* rails and asides only, never body copy */

  --par-1: 24px;    /* parallax: foreground plate */
  --par-2: 44px;    /* parallax: media frame */
  --par-3: 68px;    /* parallax: background pattern / field */

  --stagger: 70ms;
  --stagger-1: 70ms;   /* tight: list rows, logo rail, nav */
  --stagger-2: 110ms;  /* loose: cards, tiles, plates */
  --stagger-cap: 6;    /* steps, then the delay stops growing */
}

/* The gutter ladder that goes with the widened container: 24 on a phone, 40
   from tablet up, 64 from 1440 up. It is the only thing holding the page off
   the viewport edge now that the measure fills the window, so it grows with
   the window instead of stopping at 32. */
@media (min-width: 768px)  { :root { --gutter: 40px; } }
@media (min-width: 1024px) { :root { --hdr-h: 76px; --hdr-h-stuck: 60px; } }
@media (min-width: 1440px) { :root { --gutter: 64px; } }
@media (min-width: 1180px) { :root { --hdr-h: 80px; --hdr-h-stuck: 64px; } }

/* ===== RESET  (self-contained) ===== */
*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: calc(var(--header-h) + 16px);
}

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink-3);
  font: var(--t-body);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-wrap: break-word;
}

h1, h2, h3, h4, p, figure, ul, ol, dl, blockquote { margin: 0; }
ul, ol { padding: 0; list-style: none; }

h1, h2, h3, h4 {
  color: var(--ink);
  font-weight: 600;
  text-wrap: balance;
}
p { text-wrap: pretty; }

a { color: var(--navy); text-decoration: none; }

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

button, input, select, textarea { font: inherit; color: inherit; }
button { background: none; border: 0; padding: 0; cursor: pointer; }

:focus-visible {
  outline: 2px solid var(--navy);
  outline-offset: 3px;
  border-radius: inherit;
}
/* .ct__rail is SPEC-08-R2 §11: a navy rail inside a light card, where the
   navy ring would vanish. */
.on-dark :focus-visible, .cta-band :focus-visible,
.ct__rail :focus-visible { outline-color: var(--gold); }

/* ===== UTILITIES  (SPEC-00 §2.2, §4, §6.2, §7, §10) ===== */

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

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

/* --- skip link (SPEC-00 §8.2) --- */
.skip {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: var(--z-modal);
  padding: 12px 20px;
  background: var(--paper);
  border: 1px solid var(--navy);
  border-radius: var(--r-sm);
  color: var(--navy);
  font: var(--t-label);
  box-shadow: var(--sh-2);
}
.skip:focus {
  left: var(--s-4);
  top: var(--s-4);
}

/* --- eyebrow + gold tick (SPEC-00 §10) --- */
.eyebrow {
  display: flex;
  align-items: center;
  gap: 10px;
  font: var(--t-eyebrow);
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--navy);
}
.tick {
  flex: 0 0 auto;
  width: 18px; height: 3px;
  border-radius: 1.5px;
  background: var(--gold);
}

/* --- buttons (SPEC-00 §7.1, §7.2, §7.4) --- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  min-height: 48px;
  padding: 15px 28px;
  font-family: var(--font-ui);
  font-weight: 600;
  font-size: 15px;
  letter-spacing: .005em;
  line-height: 1.2;
  text-align: center;
  white-space: nowrap;
  border: 1px solid transparent;
  border-radius: var(--r-sm);
  cursor: pointer;
  text-decoration: none;
  transition: background-color var(--dur-1) var(--ease-out),
              border-color   var(--dur-1) var(--ease-out),
              color          var(--dur-1) var(--ease-out),
              box-shadow     var(--dur-2) var(--ease-out),
              transform      var(--dur-2) var(--ease-out);
}
.btn--lg { min-height: 54px; padding: 17px 34px; font-size: 16px; }
.btn--sm { min-height: 40px; padding: 10px 18px; font-size: 14px; }

.btn--primary { background: var(--gold); color: var(--navy); box-shadow: var(--sh-2); }
.btn--primary:hover  { background: var(--gold-600); box-shadow: var(--sh-3); transform: translateY(-1px); }
.btn--primary:active { background: var(--gold-600); box-shadow: var(--sh-1); transform: translateY(0); }

.btn--secondary { background: transparent; color: var(--navy); border-color: rgba(2, 45, 90, .22); }
.btn--secondary:hover  { border-color: var(--navy); background: var(--navy-050); }
.btn--secondary:active { background: var(--navy-100); }

/* --- tertiary link-arrow (SPEC-00 §7.3) --- */
.link-arrow {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  min-height: 44px;
  font-family: var(--font-ui);
  font-weight: 600;
  font-size: 15px;
  color: var(--navy);
  transition: color var(--dur-1) var(--ease-out);
}
.link-arrow svg {
  width: 16px; height: 16px;
  flex: 0 0 auto;
  transition: transform var(--dur-1) var(--ease-out);
}
.link-arrow:hover {
  color: var(--navy-500);
  text-decoration: underline;
  text-underline-offset: 4px;
  text-decoration-thickness: 1.5px;
}
.link-arrow:hover svg { transform: translateX(4px); }

/* --- glass (SPEC-00 §4.1) --- */
.glass {
  background: rgba(255, 255, 255, .72);
  -webkit-backdrop-filter: blur(18px) saturate(1.18);
          backdrop-filter: blur(18px) saturate(1.18);
  border: 1px solid rgba(255, 255, 255, .78);
  border-radius: var(--r-lg);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .92),
    0 18px 40px -22px rgba(2, 45, 90, .26);
  color: var(--ink);
}

/* GLASS-NAVY (SPEC-00 §4.1) — over dark photography. First consumer is
   SPEC-05's .pf__panel; the radius is the consumer's, not the recipe's. */
.glass--navy {
  background: rgba(2, 45, 90, .52);
  -webkit-backdrop-filter: blur(16px) saturate(1.1);
          backdrop-filter: blur(16px) saturate(1.1);
  border: 1px solid rgba(255, 255, 255, .16);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .14),
    0 20px 44px -24px rgba(1, 30, 61, .5);
  color: #fff;
}

/* mandatory fallback (SPEC-00 §4.2) */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .glass { background: rgba(255, 255, 255, .95); }
  .glass--navy { background: rgba(2, 45, 90, .90); }
  .site-header.is-stuck { background: rgba(255, 255, 255, .97); }
}

/* --- scroll reveal (SPEC-VH-R3 §B1.1, which OVERRIDES SPEC-00 §6.2) -------
   Travel 24-40px on Y; X only on rails and asides, capped at --slide-x.
   Duration 400-760ms for a reveal, 1100ms for a draw or a count. --ease-out
   for copy, --ease-plate for anything with a surface. Scale never below .982,
   rotation never, stagger capped at 6 steps, and every reveal fires once:
   next.js unobserves the element the moment it lands. */
.js .r {
  opacity: 0;
  transform: translate3d(0, var(--rise-2), 0);
  will-change: opacity, transform;
}
.js .r--sm    { transform: translate3d(0, var(--rise-1), 0); }
.js .r--lg    { transform: translate3d(0, var(--rise-3), 0) scale(.986); }
.js .r--plate { transform: translate3d(0, var(--rise-3), 0) scale(.984); }
.js .r--left  { transform: translate3d(calc(var(--slide-x) * -1), 0, 0); }
/* a rule DRAWS: it is already at full opacity and only its length changes */
.js .r--rule  { transform: scaleX(0); transform-origin: left; opacity: 1; }
.js .r--rule-y { transform: scaleY(0); transform-origin: top; opacity: 1; }

.js .r.is-in {
  opacity: 1;
  transform: none;
  will-change: auto;
  transition:
    opacity   var(--dur-5) var(--ease-out) var(--rd, 0ms),
    transform var(--dur-5) var(--ease-out) var(--rd, 0ms);
}
.js .r--sm.is-in    { transition-duration: var(--dur-4); }
.js .r--lg.is-in,
.js .r--plate.is-in { transition-duration: var(--dur-6);
                      transition-timing-function: var(--ease-plate); }
.js .r--rule.is-in,
.js .r--rule-y.is-in { transition: transform var(--dur-5) var(--ease-out) var(--rd, 0ms); }

/* --- band arrival (SPEC-VH-R3 §B3.1) -------------------------------------
   A navy band scales the last 1.5% into place and lets its corners square off
   as it seats: small enough that nobody can name what happened, large enough
   that the section reads as arriving. `scale` and `border-radius` only. Never
   opacity on a full-bleed band, because a fading navy band flashes the paper
   behind it. There is deliberately NO JS fallback: scroll-driven decoration
   never earns a scroll listener to buy something nobody notices missing. */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) and (min-width: 768px) {
    .band-arrive {
      animation: band-in linear both;
      animation-timeline: view();
      animation-range: entry 0% entry 42%;
    }
  }
}
@keyframes band-in {
  from { scale: .985; border-radius: 24px; }
  to   { scale: 1;    border-radius: 0; }
}

/* --- parallax (SPEC-VH-R3 §B3.2) -----------------------------------------
   Ceiling of TWO parallax elements per page. `translate`, not `transform`, so
   it composes with the reveal's transform instead of clobbering it. A
   parallaxed image must be calc(100% + 2 * travel) tall inside its frame so
   the move can never expose an edge. */
@keyframes par1 { from { translate: 0 var(--par-1); } to { translate: 0 calc(var(--par-1) * -1); } }
@keyframes par2 { from { translate: 0 var(--par-2); } to { translate: 0 calc(var(--par-2) * -1); } }

@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) and (min-width: 1024px) {
    .par-1 { animation: par1 linear both; animation-timeline: view(); animation-range: entry 0% exit 100%; }
    .par-2 { animation: par2 linear both; animation-timeline: view(); animation-range: entry 0% exit 100%; }
  }
}

/* ===== HEADER & MEGA MENU  (SPEC-H-R3) =====================================
   One bar (brand / centred nav / phone + gold CTA), three full-bleed panels
   that are children of <header>, one page scrim, and one mobile dialog drawer
   that is a SIBLING of <header>. Everything the old .nav / .mega / .nav-panel
   / .nav-toggle block did is replaced here; that block and its checkbox are
   deleted.

   z-index (SPEC-H-R3 §9): the header owns --z-header (60) and is its own
   stacking context, so what matters inside it is the ORDER, scrim (55) under
   panel (58) under bar (59) -- the bar's hairline draws over the panel's top
   edge and the two read as one surface (D7). The spec printed the bar as 2,
   which would have put the panel on top of it and inverted D7; 59 keeps its
   three published numbers and their intended order. The drawer is a SIBLING
   of <header>, so its two tokens are real document-level layers.
   ========================================================================= */
.site-header {
  position: sticky;
  top: 0;
  z-index: var(--z-header);
  transition: background-color var(--dur-2) var(--ease-io),
              border-color     var(--dur-2) var(--ease-io),
              box-shadow       var(--dur-2) var(--ease-io);
}

/* --- the progress hairline (SPEC-VH-R3 §B3.3) ----------------------------
   A 2px gold rule across the bottom edge of the STUCK header, scaling from
   the left, showing progress through the document. Homepage only, one
   element, zero JavaScript. It is authored as .site-header::after rather than
   as the spec's separate .hdr__progress div because templates/partials/
   next-header.php is shared by every template and a homepage-only child would
   have to be printed on all of them; the pseudo-element is the same hairline
   with no markup change. It is a hairline, not a bar: 2px, gold, no shadow,
   no track behind it, and invisible while the header floats over the film so
   it never draws on the video. This spends one of SPEC-00 §3.5's gold
   instances; the page's census still lands inside the ceiling of 14. */
body.page-home-next .site-header::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: -1px;
  height: 2px;
  background: var(--gold);
  transform-origin: left;
  transform: scaleX(0);
  opacity: 0;
  pointer-events: none;
}
@supports (animation-timeline: scroll()) {
  @media (prefers-reduced-motion: no-preference) {
    body.page-home-next .site-header.is-stuck::after {
      opacity: 1;
      animation: prog linear both;
      animation-timeline: scroll(root block);
    }
  }
}
@keyframes prog { from { transform: scaleX(0); } to { transform: scaleX(1); } }
/* SPEC-01-R2 §3 puts the float header on layer 60 OVER the hero, and .hero
   reserves calc(var(--header-h) + ...) of padding-top for it. Sticky leaves
   the header in the flow, so a transparent bar sat on the paper ground ABOVE
   the navy hero and the over-dark white brand went white-on-white. Scoped to
   the R2 homepage: home-2026.php also ships --float but sits above its own
   hero, and every interior page is --solid. */
body.page-home-next .site-header--float {
  position: fixed;
  left: 0;
  right: 0;
}

.hdr__inner {
  position: relative;
  z-index: 59;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  column-gap: var(--s-6);
  min-height: var(--hdr-h);
  transition: min-height var(--dur-2) var(--ease-io);
}

/* --- appearance states (SPEC-H-R3 §3.2) --- */
.site-header--float {
  background: transparent;
  border-bottom: 1px solid transparent;
  box-shadow: none;
}
/* SPEC-IH-R3: interior pages now open on a paper or wash hero directly under
   the bar, so the solid bar carries a real --line rule and not a 8% navy
   wash. The transparent float state over the navy hero keeps no border. */
.site-header--solid,
.site-header.is-stuck {
  background: rgba(255, 255, 255, .86);
  -webkit-backdrop-filter: blur(14px) saturate(1.2);
          backdrop-filter: blur(14px) saturate(1.2);
  border-bottom: 1px solid var(--line);
  box-shadow: 0 4px 12px -6px rgba(2, 45, 90, .10);
}
.site-header.is-stuck .hdr__inner { min-height: var(--hdr-h-stuck); }
.site-header.is-stuck .brand__img { height: 28px; }

/* D7: a light 620px panel may not hang off a transparent bar over a navy
   hero. Opening one forces the solid appearance at full opacity and REMOVES
   the blur, so a panel never adds a backdrop-filter to the page. */
.site-header.is-panel-open {
  background: var(--paper);
  -webkit-backdrop-filter: none;
          backdrop-filter: none;
  /* The bar and the panel share one paper surface: no rule at the join. */
  border-bottom: 1px solid transparent;
  box-shadow: none;
}

/* --- brand --- */
.hdr__brand { display: flex; align-items: center; flex: 0 0 auto; }
.brand__img {
  width: auto;
  height: 30px;
  transition: height var(--dur-2) var(--ease-io);
}
@media (min-width: 1024px) { .brand__img { height: 32px; } }
@media (min-width: 1180px) { .brand__img { height: 34px; } }

/* --- nav ---------------------------------------------------------------- */
.hdr__nav { display: none; }
.hdr__nav-list {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 18px;
}
.hdr__nav-item {
  position: relative;
  display: flex;
  align-items: center;
  gap: 0;
}
.hdr__nav-link {
  position: relative;
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding-inline: 2px;
  font-family: var(--font-ui);
  font-weight: 600;
  font-size: 14.5px;
  line-height: 1.2;
  letter-spacing: .005em;
  color: var(--ink-2);
  white-space: nowrap;
  transition: color var(--dur-1) var(--ease-out);
}
/* the indicator sits 13px under the cap line, not glued to the bar floor */
.hdr__nav-link::after {
  content: "";
  position: absolute;
  left: 0; right: 0;
  bottom: calc(50% - 13px);
  height: 2px;
  border-radius: 1px;
  background: var(--gold);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 200ms var(--ease-out), background-color var(--dur-1) var(--ease-out);
}
.hdr__nav-item:hover  > .hdr__nav-link,
.hdr__nav-link:focus-visible,
.hdr__nav-item[data-hdr-open] > .hdr__nav-link { color: var(--navy); }
.hdr__nav-item:hover  > .hdr__nav-link::after,
.hdr__nav-link:focus-visible::after,
.hdr__nav-item[data-hdr-open] > .hdr__nav-link::after { transform: scaleX(1); }
/* "where I am" is navy, "where I'm pointing" is gold, so the two are
   distinguishable when they land on the same item */
.hdr__nav-item[data-hdr-current] > .hdr__nav-link::after {
  transform: scaleX(1);
  background: var(--navy-100);
}
.hdr__nav-item[data-hdr-current]:hover > .hdr__nav-link::after { background: var(--gold); }
/* closing scales from the right, per SPEC-H-R3 §8 */
.hdr__nav-item:not(:hover):not([data-hdr-open]) > .hdr__nav-link::after {
  transform-origin: right;
  transition-duration: 150ms;
}

.hdr__disc {
  display: none;
  align-items: center;
  justify-content: center;
  width: 28px;
  min-height: 44px;
  margin-left: -2px;
  color: var(--ink-2);
  transition: color var(--dur-1) var(--ease-out);
}
.js .hdr__disc { display: inline-flex; }
.hdr__nav-item:hover > .hdr__disc,
.hdr__nav-item[data-hdr-open] > .hdr__disc { color: var(--navy); }
.hdr__chev {
  width: 10px; height: 6px;
  transition: transform 180ms var(--ease-out);
}
[aria-expanded="true"] .hdr__chev { transform: rotate(180deg); }

/* --- phone + CTA + burger ------------------------------------------------ */
.hdr__actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--s-3);
  flex: 0 0 auto;
}
/* a secondary LINK, never a button: it must read as quieter than the gold
   CTA or it competes with the only conversion control in the bar */
.hdr__tel {
  display: none;
  align-items: center;
  gap: 8px;
  min-height: 44px;
  padding-inline: 10px;
  margin-right: var(--s-3);
  font-family: var(--font-ui);
  font-weight: 600;
  font-size: 14px;
  line-height: 1.2;
  color: var(--ink-2);
  white-space: nowrap;
  transition: color var(--dur-1) var(--ease-out);
}
.hdr__tel svg { width: 16px; height: 16px; flex: 0 0 auto; color: var(--navy-300); transition: color var(--dur-1) var(--ease-out); }
.hdr__tel:hover { color: var(--navy); }
.hdr__tel:hover svg { color: var(--gold-600); }

.hdr__cta { min-height: 42px; padding: 10px 16px; font-size: 14px; }
.hdr__cta-icon { display: none; width: 20px; height: 20px; flex: 0 0 auto; }
.hdr__burger {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 5px;
  width: 44px; height: 44px;
  padding: 0 10px;
  flex: 0 0 auto;
}
.hdr__burger span:not(.sr-only) {
  display: block;
  height: 2px;
  border-radius: 1px;
  background: var(--navy);
  transition: background-color var(--dur-2) var(--ease-out);
}
/* no JS means no drawer to open, so the control goes with it (D8) */
html:not(.js) .hdr__burger,
html:not(.js) .hdr__panel,
html:not(.js) .hdr__scrim,
html:not(.js) .hdr-drawer { display: none; }
/* five plain links, wrapped under the brand, always reachable */
html:not(.js) .hdr__nav { display: block; }
html:not(.js) .hdr__nav-list { flex-wrap: wrap; justify-content: flex-start; }
html:not(.js) .hdr__inner { grid-template-columns: auto minmax(0, 1fr) auto; }

/* below 560 the bar carries exactly three controls: 30 + 44 + 44 + gaps +
   gutters = 182px in a 375 box, so no overflow is possible (§3.1) */
@media (max-width: 560px) {
  .hdr__cta {
    width: 44px; height: 44px;
    min-height: 44px;
    padding: 0;
    border-radius: var(--r-pill);
  }
  .hdr__cta-full { display: none; }
  .hdr__cta-icon { display: block; }
  /* the stuck rule below re-adds text padding; on the icon-only pill that
     squeezed the glyph. Keep the 44px pill and no padding when stuck. */
  .site-header.is-stuck .hdr__cta { min-height: 44px; padding: 0; }
  .hdr__actions { gap: var(--s-3); }
}
/* the phone is in the bar at EVERY width; only its label comes and goes.
   Below 1120 the five nav labels, the brand and the CTA leave no room for
   ten more characters, and an overlapping bar is worse than an icon. */
@media (min-width: 768px) {
  .hdr__tel { display: inline-flex; width: 44px; justify-content: center; padding-inline: 0; margin-right: var(--s-2); }
  .hdr__tel-num { display: none; }
}
@media (min-width: 1024px) {
  .hdr__nav { display: block; }
  .hdr__burger { display: none; }
  .hdr__inner { column-gap: var(--s-6); }
  .hdr__cta { min-height: 44px; padding: 12px 18px; }
}
@media (min-width: 1120px) {
  .hdr__tel { width: auto; padding-inline: 10px; margin-right: var(--s-4); }
  .hdr__tel-num { display: inline; }
}
@media (min-width: 1180px) {
  .hdr__inner { column-gap: var(--s-8); }
  .hdr__nav-list { gap: 26px; }
  .hdr__nav-link { font-size: 15px; }
  .hdr__cta { min-height: 46px; padding: 13px 22px; font-size: 15px; }
}
@media (min-width: 1440px) { .hdr__nav-list { gap: 34px; } }
.site-header.is-stuck .hdr__cta { min-height: 42px; padding: 11px 18px; }
/* the icon-only pill below 560 keeps its 44px box when stuck; the text
   padding above squeezed the glyph to 6px (owner, 2026-09-20) */
@media (max-width: 560px) {
  .site-header.is-stuck .hdr__cta { min-height: 44px; padding: 0; }
  .site-header.is-stuck .hdr__cta-icon { width: 20px; height: 20px; flex: 0 0 20px; }
}

/* --- over-dark (homepage float) ----------------------------------------- */
.site-header--over-dark .hdr__brand { position: relative; }
.brand__light {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  opacity: 0;
  pointer-events: none;
  transition: opacity 220ms var(--ease-out);
}
/* below 560 the shared mark is the only file there is, so it is flipped to a
   white silhouette. brightness(0) invert(1) is a colour change, not a glow. */
.brand__light img { filter: brightness(0) invert(1); }
@media (min-width: 561px) { .brand__light img { filter: none; } }
.site-header--over-dark .hdr__brand > picture:first-child { transition: opacity 220ms var(--ease-out); }
.site-header--over-dark:not(.is-stuck):not(.is-panel-open) .hdr__brand > picture:first-child { opacity: 0; }
.site-header--over-dark:not(.is-stuck):not(.is-panel-open) .brand__light { opacity: 1; }
.site-header--over-dark:not(.is-stuck):not(.is-panel-open) :is(.hdr__nav-link, .hdr__disc, .hdr__tel) { color: rgba(255, 255, 255, .88); }
.site-header--over-dark:not(.is-stuck):not(.is-panel-open) .hdr__tel svg { color: rgba(255, 255, 255, .6); }
.site-header--over-dark:not(.is-stuck):not(.is-panel-open) .hdr__nav-item:hover > :is(.hdr__nav-link, .hdr__disc),
.site-header--over-dark:not(.is-stuck):not(.is-panel-open) .hdr__nav-link:focus-visible,
.site-header--over-dark:not(.is-stuck):not(.is-panel-open) .hdr__tel:hover { color: #fff; }
.site-header--over-dark:not(.is-stuck):not(.is-panel-open) .hdr__nav-item[data-hdr-current] > .hdr__nav-link::after { background: rgba(255, 255, 255, .5); }
.site-header--over-dark:not(.is-stuck):not(.is-panel-open) .hdr__burger span:not(.sr-only) { background: #fff; }
/* the navy ring would vanish on the navy field (SPEC-00 §7.5) */
.site-header--over-dark:not(.is-stuck):not(.is-panel-open) :focus-visible { outline-color: var(--gold); }

/* --- panel shell + scrim ------------------------------------------------- */
.hdr__panel {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  z-index: var(--z-hdr-panel);
  background: var(--paper);
  border-bottom: 1px solid var(--line);
  box-shadow: var(--sh-3);
  opacity: 0;
  transform: translateY(8px);
  transition: opacity 120ms var(--ease-out), transform 120ms var(--ease-out);
}
.hdr__panel.is-open {
  opacity: 1;
  transform: none;
  transition-duration: 180ms;
}
.hdr__scrim {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  height: 100vh;
  z-index: var(--z-hdr-scrim);
  background: rgba(1, 30, 61, .28);
  opacity: 0;
  transition: opacity 120ms var(--ease-out);
}
.hdr__scrim.is-on { opacity: 1; transition-duration: 180ms; }
/* panels are a desktop affordance; below 1024 the drawer is the menu */
@media (max-width: 1023px) { .hdr__panel, .hdr__scrim { display: none; } }

/* --- SERVICES panel (§4) ------------------------------------------------- */
.hdr-svc {
  display: grid;
  grid-template-columns: 220px minmax(0, 1fr);
  gap: 28px;
  align-items: start;
  padding-block: 14px 12px;
  max-height: calc(100vh - var(--hdr-h, 80px) - 16px);
  /* grows to content; scrolls only if the viewport is shorter than the sheet */
  overflow-y: auto;
  overscroll-behavior: contain;
}
/* below 1180 the feature column becomes a strip in the same track, so the
   rail sheds its descriptors to buy that strip its height back */
@media (max-width: 1179px) {
  .hdr-svc__tab-d { display: none; }
  /* the strip is a third row in this track, so the sheet needs the height */
}
/* The feature column is narrowed (280 -> 240 from 1180, 308 -> 250 from 1440)
   and at 1440 the panel takes --container-wide, the same per-panel container
   override .hdr__panel--res has always used. Both buy width for the ONE track
   whose height decides the sheet: the groups now carry all 67 menu services
   (owner ruling 2026-09-14, the hub is gone), and at the old 652px track the
   columns were 191px and nearly every service title wrapped to two lines. The
   track is 790px at 1440 now, ~237px per column, and most titles sit on one.
   The photograph simply follows its own aspect-ratio down; its column had
   ~310px of empty wash under the CTA card and still has room. */
/* 2026-09-20: two tracks, not three. The feature column is replaced by one
   low banner across the foot (.hdr-svc__banner, grid-column: 1 / -1), so the
   groups get every pixel beside the rail: 3 columns from 1180, 4 from 1440. */
@media (min-width: 1180px) { .hdr-svc { grid-template-columns: 240px minmax(0, 1fr); gap: 12px 28px; } }
@media (min-width: 1440px) {
  .hdr-svc { grid-template-columns: 264px minmax(0, 1fr); gap: 12px 36px; padding-block: 14px 12px; }
  .hdr__panel--svc .container { max-width: var(--container-wide); }
}

.hdr-svc__rail {
  display: grid;
  gap: 2px;
  background: var(--wash);
  border-radius: var(--r-md);
  padding: var(--s-3);
}
.hdr-svc__tab {
  display: grid;
  grid-template-columns: 40px 1fr;
  gap: 10px;
  align-items: center;
  padding: 10px 12px;
  border-radius: var(--r-sm);
  text-align: left;
  transition: background-color var(--dur-1) var(--ease-out), box-shadow var(--dur-1) var(--ease-out);
}
.hdr-svc__tab:hover { background: rgba(255, 255, 255, .6); }
/* the 3px gold edge is an inset shadow, not a border, so nothing reflows */
.hdr-svc__tab[aria-selected="true"] {
  background: var(--paper);
  box-shadow: var(--sh-2), inset 3px 0 0 var(--gold);
}
.hdr-svc__tile {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 40px; height: 40px;
  border-radius: var(--r-xs);
  background: var(--navy-050);
  overflow: hidden;
  color: var(--navy);
}
.hdr-svc__tile img { width: 40px; height: 40px; object-fit: cover; }
.hdr-svc__tile svg { width: 20px; height: 20px; }
.hdr-svc__tab-t { display: grid; gap: 2px; min-width: 0; }
.hdr-svc__tab-n { font: 600 17px/1.25 var(--font-display); color: var(--navy); }
.hdr-svc__tab-d { font: 400 13px/1.4 var(--font-ui); color: var(--ink-3); }
.hdr-svc__tab[aria-selected="true"] .hdr-svc__tab-d { color: var(--ink-2); }
.hdr-svc__tab-c { font: 600 12px/1.3 var(--font-ui); color: var(--navy-300); font-variant-numeric: tabular-nums; }

.hdr-svc__groups {
  min-width: 0;
  /* the panel grows to its content (owner: no inner scrollbar); the only
     guard is the sheet's own viewport cap below */
}
.js .hdr-svc__pane:not(.is-on) { display: none; }
/* SPEC-H-R3 §4.4's fixed 420px floor is GONE, explicitly rather than by
   omission. It existed so switching pillars never resized the panel under the
   pointer, and it could do that while every pane was three rows per group.
   The panes now carry 39, 8 and 20 services, so no floor can equalise them and
   a floor only adds wash under the short ones. The owner's rule governs
   instead: the panel grows to its content and scrolls only if it is taller
   than the viewport. */
.hdr-svc__pane { animation: hdrFade 120ms var(--ease-out); min-height: 0; }
@keyframes hdrFade { from { opacity: 0; } to { opacity: 1; } }
/* The panel lists EVERY published service (owner ruling 2026-09-14: the
   /services/ hub that used to hold the overflow is gone, so a service missing
   from this panel is a service with no route into it).

   2026-09-20: this is a plain grid of FOUR COLUMNS that PHP has already
   packed (fa_hdr_cols, first-fit decreasing). CSS multi-column balanced seven
   unbreakable groups so poorly that the Accounting pane scrolled at a 900px
   viewport while its fourth column sat half empty. `columns` and
   `break-inside` are gone with it. Two columns from 1024, four from 1180:
   the rows print the live menu's short labels now, so four fit beside the
   240px rail at 1180. */
.hdr-svc__grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0 32px;
  align-items: start;
}
@media (min-width: 1180px) { .hdr-svc__grid { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
@media (min-width: 1440px) { .hdr-svc__grid { gap: 0 40px; } }

/* 14px -> 10px between groups and 7px -> 6px inside a row (below): the two
   smallest levers that close the last 38px between the Accounting pane and a
   900px-tall viewport, taken instead of a fourth column (which would narrow
   the columns again and re-wrap the titles this change just unwrapped). */
.hdr-svc__grp { padding-bottom: 14px; }
/* a group with no lead service has no page, so its title is a heading rather
   than a link; it keeps the rule, the tick and the count and loses only the
   hover underline and the pointer */
.hdr-svc__gt--flat { cursor: default; }
.hdr-svc__gt {
  display: grid;
  grid-template-columns: 16px 1fr auto;
  align-items: center;
  gap: 10px;
  padding-block: 6px;
  font-size: 11px;
  border-bottom: 1px solid var(--line-2);
  font: var(--t-eyebrow);
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--navy);
}
.hdr-svc__gt:hover .hdr-svc__gl { text-decoration: underline; text-underline-offset: 3px; }
.hdr-svc__gn { font: 600 11px/1.2 var(--font-ui); letter-spacing: 0; color: var(--navy-300); font-variant-numeric: tabular-nums; }
/* 2026-09-20: the rows print the live menu's SHORT labels now, so almost none
   wraps, and 1.5/6px was leading set for two-line rows. 14px/1.35 with 4px of
   padding is the menu scale the client's own menu uses, and it is what brings
   the whole Accounting list plus the banner onto one 800px screen. */
.hdr-svc__rows { padding-top: 2px; }
.hdr-svc__grp--cont > .hdr-svc__rows { padding-top: 0; }
.hdr-svc__row {
  position: relative;
  display: block;
  padding-block: 3px;
  padding-left: 0;
  font: 400 13.5px/1.3 var(--font-ui);
  color: var(--ink-2);
  transition: color var(--dur-1) var(--ease-out), padding-left 150ms var(--ease-out);
}
.hdr-svc__row::before {
  content: "";
  position: absolute;
  left: -10px;
  top: 50%;
  width: 6px; height: 2px;
  border-radius: 1px;
  background: var(--gold);
  opacity: 0;
  transform: translateX(-4px);
  transition: opacity 150ms var(--ease-out), transform 150ms var(--ease-out);
}
.hdr-svc__row:hover, .hdr-svc__row:focus-visible { color: var(--navy); padding-left: 10px; }
.hdr-svc__row:hover::before, .hdr-svc__row:focus-visible::before { opacity: 1; transform: none; }
.hdr-svc__all { display: inline-block; padding-block: 6px; font: 600 13px/1.3 var(--font-ui); color: var(--navy); }
.hdr-svc__all:hover { text-decoration: underline; text-underline-offset: 3px; }

.hdr-svc__foot {
  display: block;
  margin-top: 4px;
  padding-top: 16px;
  border-top: 1px solid var(--line);
  font: 600 14px/1.3 var(--font-ui);
  color: var(--navy);
}
.hdr-svc__foot:hover { text-decoration: underline; text-underline-offset: 3px; }

/* the foot banner (2026-09-20). Replaces .hdr-svc__feat, .hdr-svc__photo,
   .hdr-svc__cap, .hdr-svc__card and its four text rules, all DELETED with the
   feature column. --navy-050 ground, one hairline on top, 80px of height. */
.hdr-svc__banner {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: 56px minmax(0, 1fr) auto auto;
  align-items: center;
  gap: 8px 16px;
  min-height: 56px;
  margin-top: 0;
  padding: 6px 14px;
  background: var(--navy-050);
  border-top: 1px solid var(--line);
  border-radius: var(--r-md);
}
.hdr-svc__bthumb {
  display: block;
  width: 56px; height: 46px;
  overflow: hidden;
  border-radius: var(--r-sm);
}
.hdr-svc__bthumb img { width: 100%; height: 100%; object-fit: cover; object-position: 52% 40%; }
.hdr-svc__btext { font: 400 13px/1.4 var(--font-ui); color: var(--ink-3); }
.hdr-svc__btext strong { font-weight: 600; color: var(--navy); }
.hdr-svc__bbtn { flex: 0 0 auto; }
.hdr-svc__tools {
  grid-column: 1 / -1;
  margin-top: 2px;
  padding-top: 4px;
  border-top: 1px solid rgba(1, 30, 61, .10);
}
.hdr-svc__tel { display: inline-flex; align-items: center; gap: 8px; min-height: 44px; font: 600 14px/1.2 var(--font-ui); color: var(--navy-500); }
.hdr-svc__tel svg { width: 16px; height: 16px; }

/* --- INDUSTRIES panel (§5) ----------------------------------------------
   Twelve tiles in a menu were a listing page, not a menu. The composition is
   now templates/industries-hub.php's own .indx index, condensed to two
   columns, with ONE 4/5 photograph in the feature column that follows the
   hovered or focused row through next.js module 8. Same tokens, same map,
   same states as the hub (fill, 3px gold edge, solid numeral); nothing here
   moves on transform, so the panel never reflows under the pointer. */
.hdr-ind {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 260px;
  gap: 28px;
  align-items: start;
  padding-block: 14px 12px;
  /* the guard, not the layout: eleven ruled rows over two columns fit well
     inside this, and nothing may paint outside the panel sheet */
  max-height: min(80vh, 680px);
  overflow: hidden;
}
@media (min-width: 1180px) { .hdr-ind { grid-template-columns: minmax(0, 1fr) 280px; gap: 32px; } }
@media (min-width: 1440px) { .hdr-ind { grid-template-columns: minmax(0, 1fr) 308px; gap: 36px; padding-block: 32px 28px; } }

.hdr-ind__index { min-width: 0; }
.hdr-ind__list {
  display: grid;
  grid-template-rows: repeat(var(--gr, 6), auto);
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
  column-gap: 32px;
}
@media (min-width: 1440px) { .hdr-ind__list { column-gap: 40px; } }
/* the rule is on the item, not on the list: both columns have to open with
   one, and the shorter column simply stops */
.hdr-ind__item { border-top: 1px solid var(--line-2); min-width: 0; }

.hdr-ind__row {
  position: relative;
  isolation: isolate;
  display: grid;
  grid-template-columns: 28px minmax(0, 1fr) 20px;
  column-gap: 12px;
  align-items: start;
  padding: 10px 12px;
  margin-inline: -12px;
}
.hdr-ind__row::before {
  content: "";
  position: absolute; inset: 0;
  z-index: -1;
  background: var(--navy-050);
  opacity: 0;
  transition: opacity var(--dur-2) var(--ease-out);
}
.hdr-ind__row::after {                   /* the 3px gold left edge */
  content: "";
  position: absolute; left: 0; top: 0; bottom: 0;
  z-index: -1;
  width: 3px;
  background: var(--gold);
  opacity: 0;
  transition: opacity var(--dur-2) var(--ease-out);
}
.hdr-ind__row:hover::before, .hdr-ind__row:focus-visible::before, .hdr-ind__row.is-on::before,
.hdr-ind__row:hover::after,  .hdr-ind__row:focus-visible::after,  .hdr-ind__row.is-on::after { opacity: 1; }
.hdr-ind__row:focus-visible { outline: 2px solid var(--navy); outline-offset: 2px; border-radius: var(--r-sm); }

.hdr-ind__num {
  padding-top: 3px;
  font: 600 14px/1.3 var(--font-display);
  font-variant-numeric: tabular-nums;
  letter-spacing: -.02em;
  color: rgba(2, 45, 90, .30);           /* aria-hidden, never the only carrier */
  transition: color var(--dur-2) var(--ease-out);
}
.hdr-ind__body { display: block; min-width: 0; }
.hdr-ind__name { display: block; font: 600 17px/1.3 var(--font-display); letter-spacing: -.012em; color: var(--navy); }
.hdr-ind__teaser { display: block; margin-top: 2px; font: 400 13px/1.45 var(--font-ui); color: var(--ink-3); }
.hdr-ind__go {
  width: 20px; height: 20px;
  align-self: center;
  color: var(--navy-100);
  transition: color var(--dur-1) var(--ease-out), transform var(--dur-1) var(--ease-out);
}
.hdr-ind__row:hover .hdr-ind__go, .hdr-ind__row:focus-visible .hdr-ind__go,
.hdr-ind__row.is-on .hdr-ind__go { color: var(--navy); transform: translateX(3px); }
.hdr-ind__row:hover .hdr-ind__num, .hdr-ind__row:focus-visible .hdr-ind__num,
.hdr-ind__row.is-on .hdr-ind__num { color: var(--navy); }

/* the feature column: one photograph, eleven stacked slides */
.hdr-ind__feat { display: grid; align-content: start; }
.hdr-ind__fig {
  position: relative;
  aspect-ratio: 4 / 5;
  border-radius: var(--r-md);
  overflow: clip;
  background: var(--navy-900);
}
.hdr-ind__slide {
  position: absolute; inset: 0;
  display: block;
  opacity: 0;
  transition: opacity var(--dur-3) var(--ease-soft);
}
.hdr-ind__slide.is-on { opacity: 1; }
.hdr-ind__slide picture { position: absolute; inset: 0; display: block; }
.hdr-ind__slide img { width: 100%; height: 100%; object-fit: cover; object-position: var(--po, 50% 42%); }
.hdr-ind__cap {
  position: absolute;
  inset: auto 0 0 0;
  display: block;
  padding: 34px 14px 12px;
  background: linear-gradient(180deg, transparent 0%, rgba(1, 30, 61, .78) 100%);
  color: #fff;
  font: 600 15px/1.35 var(--font-display);
}
.hdr-ind__all {
  display: block;
  padding-block: 14px 12px;
  border-bottom: 1px solid var(--line);
  font: 600 14px/1.3 var(--font-ui);
  color: var(--navy);
}
.hdr-ind__all:hover { text-decoration: underline; text-underline-offset: 3px; }
.hdr-ind__ask { margin-top: 12px; font: 400 13px/1.45 var(--font-ui); color: var(--ink-3); }
.hdr-ind__ask-link { display: inline-block; margin-top: 3px; font: 600 13px/1.3 var(--font-ui); color: var(--navy); }
.hdr-ind__ask-link:hover { text-decoration: underline; text-underline-offset: 3px; }

/* --- RESOURCES panel (SPEC-G-R3 §F, OVERRIDES SPEC-H-R3 §6) ---------------
   Index left, preview right, figure-follow — the same primitive the
   Industries panel above uses, so the two mega panels read as one system and
   a hover fix happens once. ~110 lines of the old three-column panel
   (.hdr-res__col, .hdr-res__h, .hdr-res__thumb, .hdr-res__img and the
   .hdr-res__list ruled rows) are DELETED with this block: nothing references
   them after the header change.

   ZERO IMAGES IN ANY PREVIEW. The 64x64 post thumbnails go with the old
   columns, which also removes the data-src promotion they needed and reduces
   the panel to text plus the logo strip.

   Index rows reuse .hdr-ind__row's geometry exactly — numeral / body /
   trailing element, is-on fill plus a 3px gold left edge, >=44px — by
   declaring the same values against .hdr-res__row. They are not aliased to
   the same selector because the two panels have different track widths and a
   shared selector would need an override anyway. */
.hdr__panel--res .container { max-width: 1040px; }
.hdr-res {
  display: grid;
  grid-template-columns: 340px minmax(0, 1fr);
  gap: 40px;
  align-items: start;
  padding-block: 14px 12px;
  /* the guard, not the layout. SPEC-H-R3's panel ceiling is 620px: the
     five-row index at ~72px is 360 and the tallest preview (01, three rows
     plus chips) is ~330. Both clear it. */
  max-height: min(80vh, 620px);
  overflow: hidden;
}
@media (min-width: 1180px) { .hdr-res { grid-template-columns: 420px minmax(0, 1fr); gap: 56px; } }

.hdr-res__index { min-width: 0; }
.hdr-res__list { list-style: none; }
.hdr-res__item { border-top: 1px solid var(--line-2); min-width: 0; }

.hdr-res__row {
  position: relative;
  isolation: isolate;
  display: grid;
  grid-template-columns: 28px minmax(0, 1fr) auto;
  column-gap: 12px;
  align-items: start;
  min-height: 44px;
  padding: 11px 12px;
  margin-inline: -12px;
  text-decoration: none;
}
.hdr-res__row::before {
  content: "";
  position: absolute; inset: 0;
  z-index: -1;
  background: var(--navy-050);
  opacity: 0;
  transition: opacity var(--dur-2) var(--ease-out);
}
.hdr-res__row::after {                   /* the 3px gold left edge */
  content: "";
  position: absolute; left: 0; top: 0; bottom: 0;
  z-index: -1;
  width: 3px;
  background: var(--gold);
  opacity: 0;
  transition: opacity var(--dur-2) var(--ease-out);
}
.hdr-res__row:hover::before, .hdr-res__row:focus-visible::before, .hdr-res__row.is-on::before,
.hdr-res__row:hover::after,  .hdr-res__row:focus-visible::after,  .hdr-res__row.is-on::after { opacity: 1; }
.hdr-res__row:focus-visible { outline: 2px solid var(--navy); outline-offset: 2px; border-radius: var(--r-sm); }

.hdr-res__num {
  padding-top: 3px;
  font: 600 14px/1.3 var(--font-display);
  font-variant-numeric: tabular-nums;
  letter-spacing: -.02em;
  color: rgba(2, 45, 90, .30);           /* aria-hidden, never the only carrier */
  transition: color var(--dur-2) var(--ease-out);
}
.hdr-res__body { display: block; min-width: 0; }
.hdr-res__name { display: block; font: 600 17px/1.3 var(--font-display); letter-spacing: -.012em; color: var(--navy); }
.hdr-res__desc { display: block; margin-top: 2px; font: 400 13px/1.45 var(--font-ui); color: var(--ink-3); }
/* the same element the drawer's .hdr-drawer__n already is */
.hdr-res__n {
  align-self: center;
  font: 600 13px/1.3 var(--font-ui);
  font-variant-numeric: tabular-nums;
  color: var(--navy-300);
  white-space: nowrap;
}
.hdr-res__row:hover .hdr-res__num, .hdr-res__row:focus-visible .hdr-res__num,
.hdr-res__row.is-on .hdr-res__num { color: var(--navy); }

/* the preview column: five stacked slides, one visible. Opacity only — no
   transform, so a slide swap cannot shift the panel's height or its scroll. */
.hdr-res__feat { position: relative; min-width: 0; min-height: 300px; }
.hdr-res__slide {
  position: absolute; inset: 0;
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--dur-3) var(--ease-soft), visibility 0s linear var(--dur-3);
  overflow: hidden;
}
.hdr-res__slide.is-on { opacity: 1; visibility: visible; transition-delay: 0s; }

.hdr-res__posts { list-style: none; }
.hdr-res__posts li + li { border-top: 1px solid var(--line-2); }
.hdr-res__post { display: grid; gap: 3px; padding-block: 12px; text-decoration: none; }
.hdr-res__meta { font: var(--t-micro); letter-spacing: .08em; text-transform: uppercase; color: var(--navy-300); }
.hdr-res__title {
  font: 600 15px/1.35 var(--font-display);
  color: var(--navy);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.hdr-res__post:hover .hdr-res__title { text-decoration: underline; text-underline-offset: 3px; }

.hdr-res__more { display: inline-block; margin-top: 10px; font: 600 13px/1.3 var(--font-ui); color: var(--navy); }
.hdr-res__more:hover { text-decoration: underline; text-underline-offset: 3px; }
.hdr-res__more--foot { margin-top: 12px; }
.hdr-res__desc2 { font: 400 14px/1.55 var(--font-ui); color: var(--ink-3); max-width: 46ch; }

.hdr-res__chips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 12px; list-style: none; }
.hdr-res__chip {
  display: inline-block;
  padding: 4px 10px;
  border-radius: var(--r-pill);
  background: var(--navy-050);
  font: 600 12px/1.4 var(--font-ui);
  color: var(--navy);
}
.hdr-res__chip:hover { background: var(--navy-100); }

/* 02 — every string in this card is verbatim from design/FACTS.md */
.hdr-res__cs { background: var(--navy-050); border-radius: var(--r-md); padding: 20px; }
.hdr-res__kicker { font: var(--t-micro); letter-spacing: .14em; text-transform: uppercase; color: var(--navy-300); }
.hdr-res__client { margin-top: 6px; font: 600 17px/1.25 var(--font-display); color: var(--navy); }
.hdr-res__sector { margin-top: 2px; font: var(--t-cap); color: var(--ink-3); }
.hdr-res__stat { display: flex; align-items: baseline; gap: 10px; margin-top: 12px; }
.hdr-res__num2 { font: 600 28px/1 var(--font-display); color: var(--navy); letter-spacing: -.01em; }
.hdr-res__statlbl { font: 400 13px/1.4 var(--font-ui); color: var(--ink-3); }
.hdr-res__metrics { margin-top: 10px; list-style: none; }
.hdr-res__metrics li {
  position: relative;
  padding-left: 16px;
  font: 400 13px/1.5 var(--font-ui);
  color: var(--ink-3);
}
.hdr-res__metrics li::before {
  content: "";
  position: absolute; left: 0; top: .68em;
  width: 8px; height: 1px;
  background: var(--navy-300);
}

/* 03 — the guides list */
.hdr-res__gd { list-style: none; counter-reset: none; }
.hdr-res__gd li + li { border-top: 1px solid var(--line-2); }
.hdr-res__gd a {
  display: grid;
  grid-template-columns: 28px minmax(0, 1fr);
  column-gap: 12px;
  padding-block: 11px;
  text-decoration: none;
}
.hdr-res__gdn {
  grid-row: 1 / 3;
  padding-top: 2px;
  font: 600 14px/1.3 var(--font-display);
  font-variant-numeric: tabular-nums;
  color: rgba(2, 45, 90, .30);
}
.hdr-res__gdt { font: 600 15px/1.35 var(--font-display); color: var(--navy); }
.hdr-res__gd a:hover .hdr-res__gdt { text-decoration: underline; text-underline-offset: 3px; }
.hdr-res__gdm { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 10px; margin-top: 4px; font: var(--t-cap); color: var(--ink-3); }
/* the pill is .gd-index__tag from resources.css where that file is loaded;
   the header ships on every page, so the two declarations it needs live here
   and are byte-identical to that file's. --gold-050 fill, --gold-200
   hairline, NAVY text: gold is never text on a light surface. */
.hdr-res__gdm .gd-index__tag {
  padding: 2px 8px;
  border-radius: var(--r-pill);
  background: var(--gold-050);
  border: 1px solid var(--gold-200);
  font: 600 11px/1.5 var(--font-display); letter-spacing: .06em; text-transform: uppercase;
  color: var(--navy);
}

/* 04 — the calculator teaser. Both figures are SPEC-G-R3 §0.2's published
   pricing and nothing here is rounded, averaged or estimated. */
.hdr-res__calc { background: var(--navy-050); border-radius: var(--r-md); padding: 20px; }
.hdr-res__price { margin-top: 8px; font: var(--t-num-sm); color: var(--navy); font-variant-numeric: tabular-nums; }
.hdr-res__priceline { margin-top: 6px; font: 400 13px/1.5 var(--font-ui); color: var(--ink-3); max-width: 34ch; }

/* 05 — the five firm-level questions */
.hdr-res__qs { list-style: none; }
.hdr-res__qs li + li { border-top: 1px solid var(--line-2); }
.hdr-res__qs a {
  display: block;
  padding-block: 11px;
  font: 500 15px/1.4 var(--font-ui);
  color: var(--navy);
  text-decoration: none;
}
.hdr-res__qs a:hover { text-decoration: underline; text-underline-offset: 3px; }

/* texture, not navigation. Unchanged. */
.hdr-res__logos { display: none; margin-top: 20px; padding: 20px 0 24px; border-top: 1px solid var(--line); }
.hdr-res__logorow { display: flex; align-items: center; justify-content: space-between; gap: var(--s-6); }
/* TOOLS-01: grayscale(1) + opacity .55 removed. This row shares the library
   with the trust strip and the service band, and the library is colour at
   rest everywhere. Marks only here, no names: the row is texture, and a chip
   tool has no glyph to draw, so next-header.php filters those out. */
/* TOOLS-03: 30px, was 22. Smaller than the page's marks on purpose — this row
   is texture under a nav panel, not a claim — but 22px was below the point
   where a wordmark is identifiable at all. width:auto so the wordmarks in the
   core set keep their ratio instead of letterboxing inside a square. */
.hdr-res__logorow img { height: 30px; width: auto; max-width: 118px; object-fit: contain; }
@media (min-width: 1180px) { .hdr-res__logos { display: block; } }

@media (prefers-reduced-motion: reduce) {
  .hdr-res__row::before, .hdr-res__row::after, .hdr-res__slide { transition-duration: 1ms; }
}

/* --- MOBILE DRAWER (§7) -------------------------------------------------- */
.hdr-drawer { position: fixed; inset: 0; z-index: var(--z-hdr-drawer); }
.hdr-drawer__scrim {
  position: absolute;
  inset: 0;
  z-index: var(--z-hdr-drawer-scrim);
  background: rgba(1, 30, 61, .44);
  opacity: 0;
  transition: opacity 160ms var(--ease-out);
}
.hdr-drawer.is-on .hdr-drawer__scrim { opacity: 1; transition-duration: 200ms; }
.hdr-drawer__sheet {
  position: absolute;
  inset: 0;
  z-index: calc(var(--z-hdr-drawer) + 1);
  display: grid;
  grid-template-rows: auto minmax(0, 1fr) auto;
  height: 100vh;
  height: 100dvh;
  background: var(--paper);
  opacity: 0;
  transition: opacity 200ms var(--ease-out);
}
.hdr-drawer.is-on .hdr-drawer__sheet { opacity: 1; }
@media (min-width: 560px) {
  .hdr-drawer__sheet {
    inset: 0 0 0 auto;
    width: min(420px, 92vw);
    opacity: 1;
    box-shadow: var(--sh-4);
    transform: translateX(100%);
    transition: transform 200ms var(--ease-out);
  }
  .hdr-drawer.is-on .hdr-drawer__sheet { transform: none; transition-duration: 260ms; }
}
@media (min-width: 1024px) { .hdr-drawer { display: none; } }

.hdr-drawer__bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  min-height: 64px;
  padding-inline: var(--gutter);
  border-bottom: 1px solid var(--line);
}
.hdr-drawer__brand img { width: auto; height: 28px; }
/* owner ruling 2026-09-20: no counts in the hamburger */
.hdr-drawer__n { display: none; }
.hdr-drawer__x { display: flex; align-items: center; justify-content: center; width: 44px; height: 44px; margin-right: -10px; color: var(--navy); }
.hdr-drawer__x svg { width: 20px; height: 20px; }

.hdr-drawer__body {
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  padding: 8px var(--gutter) 24px;
}
.hdr-drawer__lead {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 48px;
  font: 600 15px/1.3 var(--font-ui);
  color: var(--navy);
}
.hdr-drawer__sum, .hdr-drawer__row {
  display: flex;
  align-items: center;
  gap: 12px;
  min-height: 48px;
  border-top: 1px solid var(--line-2);
  font: 600 15px/1.35 var(--font-ui);
  color: var(--ink-2);
}
.hdr-drawer__sum { list-style: none; cursor: pointer; color: var(--navy); }
.hdr-drawer__sum::-webkit-details-marker { display: none; }
.hdr-drawer__sum::after {
  content: "";
  width: 8px; height: 8px;
  margin-left: auto;
  border-right: 1.6px solid var(--navy-300);
  border-bottom: 1.6px solid var(--navy-300);
  transform: rotate(45deg) translate(-2px, -2px);
  transition: transform 180ms var(--ease-out);
}
[open] > .hdr-drawer__sum::after { transform: rotate(225deg) translate(-2px, -2px); }
.hdr-drawer__sum .hdr-drawer__n { margin-left: auto; }
.hdr-drawer__n {
  font: 600 13px/1.2 var(--font-ui);
  color: var(--navy-300);
  font-variant-numeric: tabular-nums;
}
.hdr-drawer__row .hdr-drawer__n { margin-left: auto; }
.hdr-drawer__row--top { border-top: 1px solid var(--line-2); }
.hdr-drawer__row--post { font-weight: 400; color: var(--ink-3); }
/* Services, third level (owner ruling 2026-09-14). Each group is its own
   <details> so the accordion still opens as a list of groups; the services
   inside it are lighter, slightly shorter and indented one step, so the two
   levels never read as one flat list. 44px is still a full touch target. */
.hdr-drawer__acc--sub > .hdr-drawer__sum { color: var(--ink-2); }
.hdr-drawer__row--svc {
  min-height: 44px;
  padding-left: 16px;
  font-weight: 400;
  color: var(--ink-3);
}
.hdr-drawer__mini {
  margin-top: 16px;
  display: flex;
  align-items: center;
  gap: 10px;
  font: var(--t-eyebrow);
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--navy-300);
}
.hdr-drawer__mini .hdr-drawer__n { margin-left: auto; letter-spacing: 0; }
/* the drawer's industries rows carry the panel's numeral, not a thumbnail */
.hdr-drawer__num {
  flex: 0 0 auto;
  width: 24px;
  font: 600 13px/1.3 var(--font-display);
  font-variant-numeric: tabular-nums;
  letter-spacing: -.02em;
  color: rgba(2, 45, 90, .30);
}

.hdr-drawer__foot {
  border-top: 1px solid var(--line);
  background: var(--paper);
  padding: 16px var(--gutter) calc(16px + env(safe-area-inset-bottom));
}
.hdr-drawer__cta { display: flex; width: 100%; min-height: 52px; }
.hdr-drawer__tel {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 44px;
  font: 600 15px/1.2 var(--font-ui);
  color: var(--navy);
}
.hdr-drawer__tel svg { width: 16px; height: 16px; color: var(--navy-300); }
.hdr-drawer__note { font: 400 12px/1.4 var(--font-ui); color: var(--ink-3); }

/* --- reduced motion (SPEC-00 §6.3; !important permitted only here) ------- */
@media (prefers-reduced-motion: reduce) {
  .hdr__panel, .hdr__scrim, .hdr-drawer__sheet, .hdr-drawer__scrim,
  .hdr-svc__pane, .hdr__chev, .hdr-ind__slide, .hdr-ind__go, .hdr__nav-link::after,
  .brand__light, .hdr__inner {
    transition-duration: 1ms !important;
    animation-duration: 1ms !important;
    transform: none !important;
  }
  .hdr__nav-item:hover > .hdr__nav-link::after,
  .hdr__nav-item[data-hdr-open] > .hdr__nav-link::after,
  .hdr__nav-item[data-hdr-current] > .hdr__nav-link::after { transform: scaleX(1) !important; }
  [aria-expanded="true"] .hdr__chev { transform: rotate(180deg) !important; }
}

/* ===== HERO  (SPEC-VH-R3 Part A, "Video Hero") =============================
   One full-bleed navy-graded film under a four-layer scrim, with the copy
   column sized to sit inside the film's dim left 55%, a 96px brand grid masked
   into the top-left quadrant, and the glass ledger rail frosted across the
   foot. The whole frame is the image and the type lives on it: no split, no
   card, no photograph parked beside the type.

   DELETED with SPEC-01-R2's "Split Field": .hero__plane and its ::after
   dissolve, .hero__seam, .hero__arc and its circles, the --seam variable
   everything keyed off, the 1120 breakpoint that existed only to define the
   split, and the plane-in / plane-img / hp-plane / hp-arc / hp-pattern /
   hp-copy keyframes with the four-plane hero parallax block that used them.
   That parallax was scroll-driven decoration inside the hero, i.e. above the
   fold, so it moved where nobody was looking; SPEC-VH-R3 B3.2 spends the
   page's two parallax slots on .pf__frame and .tqc--lead instead. (SPEC-06-R3
   later withdrew the second of those with the navy lead card; .pf__frame is
   now the page's only parallax element.)

   RETAINED although the hero no longer renders them: .frame__gold and the
   .stats / .stat component, further down this file. assets/pages.css builds
   .ab-story__frame and .ab-stats on both for templates/page-about.php, and
   next.css is that template's stylesheet.

   GLASS CENSUS unchanged. .hero__rail is still the page's one hero glass
   element, and it now frosts real moving footage on its right half, so the
   document holds at SPEC-00 §8.6's ceiling of three backdrop-filter elements:
   .site-header.is-stuck, .hero__rail, .pf__panel.

   MIN-HEIGHT. SPEC-VH-R3 A4's table lists five widths; the three tiers below
   resolve to the table's values at every viewport height this page is
   verified at, so no sixth breakpoint is added to SPEC-00 §8.6's four. The
   1120 breakpoint the split needed is gone; 1120 survives only as the
   720p/1080p serving threshold in next.js, which is not a layout value. */
.hero {
  position: relative;
  isolation: isolate;
  overflow: clip;
  display: grid;
  align-content: end;                 /* <=1023: the copy sits on the dark floor */
  min-height: auto;                   /* <=767: content height, rail in flow */
  padding-top: calc(var(--header-h) + var(--s-8));
  padding-bottom: 0;
  color: #fff;
  background: #011E3D;                /* the ground the poster and film sit on */
}

/* --- the stage: film, scrim, grid (A6.1) ---------------------------------
   The poster is painted here as a background as well as on the <video>'s own
   poster attribute, so the crossfade from poster to first frame has nothing
   to flash through and a session that never promotes a source still has a
   fully painted hero. The URLs are relative to this stylesheet. */
.hero__stage {
  position: absolute;
  inset: 0;
  z-index: var(--z-media);
  pointer-events: none;
  background-color: #011E3D;
  background-image: url("img/hero-poster.jpg");
  background-image: image-set(url("img/hero-poster.webp") type("image/webp"),
                              url("img/hero-poster.jpg")  type("image/jpeg"));
  background-size: cover;
  background-position: 62% 50%;
  background-repeat: no-repeat;
}
.hero__video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 62% 50%;
  opacity: 0;                         /* A8: faded up on canplay, never before */
}
.hero__stage.is-live .hero__video {
  opacity: 1;
  transition: opacity 700ms var(--ease-soft);
}

/* --- the scrim (A6.2) -----------------------------------------------------
   Four layers in one element. <=1023 the film has no safe half, because the
   copy runs to 94% of the frame, so the composition rotates from
   left-weighted to bottom-weighted and the ramps rotate with it. */
.hero__scrim {
  position: absolute;
  inset: 0;
  z-index: var(--z-overlay);
  pointer-events: none;
  background:
    linear-gradient(180deg, rgba(1, 30, 61, .72) 0, rgba(1, 30, 61, .34) 14%, rgba(1, 30, 61, .20) 34%),
    linear-gradient(0deg,   rgba(1, 30, 61, .92) 0, rgba(1, 30, 61, .62) 30%, rgba(1, 30, 61, .20) 62%),
    linear-gradient(0deg, rgba(2, 45, 90, .34), rgba(2, 45, 90, .34));
}

/* --- the blue pattern, kept (A6.4) ---------------------------------------
   96px column grid, white, masked hard into the top-left quadrant where the
   scrim is heaviest, so it reads as architecture over the dimmed part of the
   film and is fully transparent by the time it reaches the lit part.
   Effective alpha across the visible field is 3-5%, inside SPEC-00 §3.3's 6%
   ceiling. Hidden below 1024, where it fights the footage. */
.hero__pattern {
  display: none;
  position: absolute;
  inset: 0;
  z-index: var(--z-veil);
  pointer-events: none;
  background-image:
    repeating-linear-gradient(90deg, rgba(255, 255, 255, .055) 0 1px, transparent 1px 96px),
    repeating-linear-gradient(0deg,  rgba(255, 255, 255, .034) 0 1px, transparent 1px 96px);
  -webkit-mask-image: radial-gradient(96% 84% at 4% 6%, #000 0 38%, transparent 76%);
          mask-image: radial-gradient(96% 84% at 4% 6%, #000 0 38%, transparent 76%);
  opacity: 0;
  animation: fade 700ms var(--ease-out) 0ms both;
}

/* --- header sentinel ---
   24px off the hero's top: the bar must go solid as soon as the reader
   scrolls, or the hero's own buttons and copy pass under a transparent bar
   (owner, 2026-09-15). The brand/nav cross-fade rides the same swap. */
.hero__sentinel {
  position: absolute;
  top: 24px; left: 0;
  width: 1px; height: 1px;
  pointer-events: none;
}

/* --- copy column (A4, A5) ------------------------------------------------ */
.hero__copy {
  position: relative;
  z-index: var(--z-content);
  width: 100%;
  max-width: 46rem;
}

.hero .eyebrow .tick {
  transform: scaleX(0);
  transform-origin: left;
  animation: grow-x 420ms var(--ease-out) 120ms both;
}

/* A3.1 sharpens the headline to a 15-character measure, so the h1 carries its
   own clamp rather than SPEC-00 §1.2's --t-d1 (which tops out at 62px against
   18ch).

   CLIENT AMENDMENT to A5's 88px. The 88px setting shipped and read as too
   large on the homepage, so the clamp is re-cut to resolve 60px at 1440 and
   cap at 64px, keeping the two-tone treatment and the tight leading exactly
   as A3.1 and A5 specify. The line is: clamp(30px, 2.82vw + 19.4px, 64px),
   which resolves 30px at 375, 41px at 768, 48px at 1024, 53px at 1180, 60px
   at 1440, and hits its 64px cap at 1578.

   Two consequences, both benign. A6.2's copy ramp was widened (26 -> 30 and
   52 -> 56) because "Decisions made." at 88px measured 695px and ended at
   53.3% of a 1440 frame; at 60px the same string measures ~473px and ends at
   x=545, i.e. 37.8%, so the gold phrase now sits deep inside the ramp's .84
   zone and the stop positions are conservative rather than load-bearing. And
   A4.1's vertical rhythm gains ~57px of slack in the copy column, which the
   hero's `align-content: center` absorbs without touching the rail. */
.hero h1 {
  margin-top: var(--s-6);
  font: var(--t-d1);
  letter-spacing: -.030em;
  color: #fff;
  /* 16ch, not 14: at the 30px floor "Decisions made." is 15 characters and a
     14ch box puts it a hair from wrapping to a third line at 375. */
  max-width: 16ch;
}
/* negative insets on three sides so descenders and the -.035em overhang are
   never clipped by the reveal */
/* no will-change: B8 item 5 requires nothing to be left promoted once the
   page has settled, and a 640ms clip-path on two spans does not need the
   hint. The only permanent will-change on the page is .trust__track, which is
   the one element that never stops animating. */
.hero h1 .ln {
  display: block;
  animation: ln-in 640ms var(--ease-soft) var(--ld, 0ms) both;
}
.hero h1 .ln:nth-child(1) { --ld: 220ms; }
.hero h1 .ln:nth-child(2) { --ld: 320ms; }
.hl-gold { color: var(--gold); }

.hero .lead {
  margin-top: var(--s-6);
  font: var(--t-lead);
  color: rgba(255, 255, 255, .82);
  max-width: 46ch;
}
.hero__actions {
  display: flex;
  flex-direction: column;             /* <=560 */
  align-items: stretch;
  gap: var(--s-3);
  margin-top: var(--s-7);
}
.hero__actions .btn { width: 100%; }
.hero .link-arrow {
  color: #fff;
  text-decoration: underline;
  text-decoration-color: rgba(255, 255, 255, .32);
  text-decoration-thickness: 1px;
  text-underline-offset: 6px;
}
.hero .link-arrow:hover { color: #fff; text-decoration-color: #fff; }
/* a gold ring on a gold button is invisible, so the primary CTA gets an opaque
   white inner ring inside the gold outer one. Focus rings are exempt from
   SPEC-00 §5.3 (§7.5): zero blur, zero offset, not a glow. */
.hero .btn--primary:focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px #fff, 0 0 0 5px var(--gold);
}

/* --- the ledger rail (A6.6) ----------------------------------------------
   <=767 it is static flow on flat navy with NO backdrop-filter: there is
   nothing behind it to frost at that width and the paint cost is real on a
   phone, which never downloads the film in the first place (A7). */
.hero__rail {
  position: relative;
  z-index: var(--z-rail);
  border-top: 1px solid rgba(255, 255, 255, .16);
  background: rgba(255, 255, 255, .07);
  --hy: 28px;
  animation: hero-up 560ms var(--ease-out) 840ms both;
}
.hero__rail-in { padding-block: var(--s-6) var(--s-7); }
.rail__cells {
  display: grid;
  grid-template-columns: 1fr 1fr;     /* <=560 */
  gap: var(--s-5) var(--s-6);
}
.rail__cell {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 6px;
}
/* 01/02/03 are index labels and pure decoration; below 561 the cells drop to
   two tiers so the numeral has room */
.rail__i { display: none; }
.rail__cell:nth-child(3) {
  grid-column: 1 / -1;
  padding-top: var(--s-5);
  border-top: 1px solid rgba(255, 255, 255, .14);
}
/* .rail__val, not .rail__num: SPEC-03's process rail already owns .rail__num
   (12px, --ink-3, further down this file), and the later rule was winning the
   cascade, so the hero's numerals rendered at 12px in grey. The two rails are
   unrelated components that only share a prefix. */
.rail__val {
  font: 600 26px/1 var(--font-display);   /* <=560 */
  font-variant-numeric: tabular-nums;
  letter-spacing: -.030em;
  color: #fff;
}
/* GOLD LEDGER. The suffix and the cue segment are both banked back to white:
   SPEC-00 §3.5's ceiling of 14 gold instances is tight and neither carries
   meaning the white numeral and the word "Scroll" beside them do not already
   carry. With the gold seam deleted the hero now spends three above the fold,
   not four: the header CTA, the eyebrow tick and the primary button. The h1's
   gold phrase is the fourth, and B3.3's progress hairline is the fifth. */
.rail__suffix {
  font-size: .62em;
  vertical-align: .28em;
  margin-left: 1px;
  color: rgba(255, 255, 255, .62);
}
.rail__cap {
  font: var(--t-cap);
  color: rgba(255, 255, 255, .72);
  max-width: 22ch;
}

/* --- the pause control (A9) ----------------------------------------------
   WCAG 2.2.2. 44x44 minimum hit area, keyboard-operable because it is a real
   <button>, and it lives in the rail's own grid column between the stats and
   the scroll cue. next.js drops `hidden` only once a source has actually been
   promoted; --pausecol keeps the column at 0 until then, so a poster-only
   session has nothing to press and no gap where the control would be. */
.hero__pause {
  display: none;
  width: 44px; height: 44px;
  align-items: center;
  justify-content: center;
  justify-self: center;
  border: 1px solid rgba(255, 255, 255, .42);
  border-radius: var(--r-pill);
  background: rgba(255, 255, 255, .14);
  color: #fff;
  transition: background-color var(--dur-1) var(--ease-out),
              border-color     var(--dur-1) var(--ease-out);
}
.hero.has-video .hero__pause:not([hidden]) { display: flex; }
.hero__pause:hover {
  background: rgba(255, 255, 255, .24);
  border-color: rgba(255, 255, 255, .70);
}
.hero__pause svg { width: 16px; height: 16px; }
/* one glyph swap, driven by the button's own state: bars while it is playing,
   a triangle once it has been pressed */
.hero__pause-play { display: none; }
.hero__pause[aria-pressed="true"] .hero__pause-bars { display: none; }
.hero__pause[aria-pressed="true"] .hero__pause-play { display: inline; }

/* the scroll cue: a real anchor with a visible label, never an icon-only div.
   Hidden below 1120, where the affordance is redundant on touch and the
   column does not fit. */
.rail__cue {
  display: none;
  flex-direction: column;
  align-items: flex-end;
  justify-content: center;
  gap: var(--s-3);
  align-self: stretch;
  padding-left: var(--s-7);
  border-left: 1px solid rgba(255, 255, 255, .14);
  font: var(--t-eyebrow);
  font-size: 11px;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, .64);
}
.rail__cue-rail {
  position: relative;
  width: 1px; height: 40px;
  background: rgba(255, 255, 255, .24);
  overflow: hidden;
}
/* the page's ONE looping element (SPEC-00 §6.6 B caps the page at three).
   Transform and nothing else. Its 28px travel is above §6.6 B's 8px amplitude
   ceiling, which is written for ambient decoration; a scroll indicator that
   moves 8px does not read as one. A8 starts the loop at 1240ms, after the
   deterministic sequence has ended. */
.rail__cue-seg {
  position: absolute;
  left: 0; top: 0;
  width: 1px; height: 12px;
  background: #fff;
  animation: cue-seg 1900ms var(--ease-io) 1240ms infinite;
}

/* --- HERO | 561px and up ------------------------------------------------- */
@media (min-width: 561px) {
  .hero h1 { margin-top: var(--s-7); }
  .hero__actions {
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--s-4) 28px;
  }
  .hero__actions .btn { width: auto; }
}

/* --- HERO | 768px and up (the film becomes a stage) ---------------------- */
@media (min-width: 768px) {
  .hero {
    min-height: min(max(620px, 100svh), 820px);
    padding-top: calc(var(--header-h) + var(--s-9));
    padding-bottom: 156px;            /* rail 92 + 64 clearance */
  }

  /* the rail overlays the film's bottom edge, which is what the scrim's
     bottom ramp is there to darken */
  .hero__rail {
    position: absolute;
    left: 0; right: 0; bottom: 0;
    height: 92px;
    background: rgba(255, 255, 255, .055);
    -webkit-backdrop-filter: blur(16px) saturate(118%);
            backdrop-filter: blur(16px) saturate(118%);
  }
  .hero__rail-in {
    height: 100%;
    padding-block: 0;
    display: grid;
    align-items: center;
    grid-template-columns: minmax(0, 1fr) var(--pausecol, 0px);
  }
  .hero.has-video { --pausecol: 56px; }
  /* stretch, not centre: the cell dividers are the rail's ledger rules and
     have to run its full height, the way a column rule does */
  .rail__cells { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 0; align-self: stretch; }
  .rail__i {
    display: block;
    font: 600 11px/1 var(--font-ui);
    font-variant-numeric: tabular-nums;
    letter-spacing: .20em;
    color: rgba(255, 255, 255, .40);
  }
  .rail__cell {
    padding-inline: var(--s-5);
    align-self: stretch;
    gap: 4px;
  }
  .rail__cell:first-child { padding-left: 0; }
  .rail__cell:nth-child(3) { grid-column: auto; padding-top: 0; border-top: 0; }
  /* the 1px dividers are pseudo-elements, not borders, so they can grow scaleY
     on the load timeline */
  .rail__cell + .rail__cell::before {
    content: "";
    position: absolute;
    left: 0; top: 0; bottom: 0;
    width: 1px;
    background: rgba(255, 255, 255, .14);
    transform: scaleY(0);
    transform-origin: top;
    animation: grow-y 380ms var(--ease-out) 1020ms both;
  }
  .rail__cell:nth-child(3)::before { animation-delay: 1080ms; }
  .rail__val { font-size: 28px; }
}

/* --- HERO | 1024px and up (the film gets a safe half) --------------------
   A6.3: at and above 1024 the copy column fits inside the film's dim left
   55%, so the composition rotates back from bottom-weighted to left-weighted
   and the scrim's ramps rotate with it. */
@media (min-width: 1024px) {
  .hero {
    align-content: center;
    min-height: min(max(660px, 100svh), 880px);
    padding-top: calc(var(--header-h) + 96px);
    padding-bottom: 160px;            /* rail 96 + 64 clearance */
  }
  .hero__copy { width: clamp(560px, 50vw, 720px); max-width: none; }
  .hero h1 { line-height: .98; letter-spacing: -.035em; max-width: none; }
  .hero .lead { margin-top: var(--s-8); }
  .hero__actions { margin-top: var(--s-8); gap: var(--s-5) var(--s-7); }

  /* A6.2's four layers, in paint order bottom-up: brand tint, copy ramp,
     bottom ramp, top ramp. The copy ramp is the one that guarantees the type:
     .90 navy behind the h1, still .58 at the copy column's right edge, and
     only .14 by the time it reaches the lit part of the frame.

     MEASURED AMENDMENT to A6.2's stops. A6.2 puts the .58 stop at 52% on the
     assumption that a 15-character line at 88px ends near x=520 in a 720px
     column. Measured in Poppins 600, "Decisions made." at 88.24px is 695px
     wide and ends at x=767, i.e. 53.3% of a 1440 frame. Rather than shrink
     the headline the client asked to be BIGGER, the ramp's own stops move out
     by four points: 26 -> 30 and 52 -> 56. The guarantee is unchanged in
     substance, because the scrim value at the gold phrase's right edge is now
     .607, above the .58 A6.2 specified at its own boundary. */
  .hero__scrim {
    background:
      linear-gradient(180deg,
        rgba(1, 30, 61, .74) 0,
        rgba(1, 30, 61, .30) 11%,
        rgba(1, 30, 61, 0)   26%),
      linear-gradient(0deg,
        rgba(1, 30, 61, .80) 0,
        rgba(1, 30, 61, .36) 13%,
        rgba(1, 30, 61, 0)   30%),
      linear-gradient(90deg,
        rgba(1, 30, 61, .90) 0%,
        rgba(1, 30, 61, .84) 30%,
        rgba(2, 45, 90, .58) 56%,
        rgba(2, 45, 90, .28) 74%,
        rgba(2, 45, 90, .14) 100%),
      linear-gradient(0deg, rgba(2, 45, 90, .22), rgba(2, 45, 90, .22));
  }
  .hero__pattern { display: block; }

  .hero__rail { height: 96px; }
}

/* --- HERO | 1120px and up (the cue column fits) -------------------------- */
@media (min-width: 1120px) {
  .hero__rail-in { grid-template-columns: minmax(0, 1fr) var(--pausecol, 0px) 152px; }
  .rail__cell { padding-inline: var(--s-7); }
  .rail__cue { display: flex; }
}

/* --- HERO | 1180px and up ------------------------------------------------ */
@media (min-width: 1180px) {
  .hero {
    min-height: min(max(720px, 100svh), 940px);
    padding-bottom: 168px;            /* rail 104 + 64 clearance */
  }
  .hero__rail { height: 104px; }
  .rail__val { font-size: 34px; }
}

/* A4.1: at short viewports the top padding drops and the h1 clamp's own
   shrink handles the rest, so nothing overlaps the rail down to 600px tall. */
@media (min-width: 1024px) and (max-height: 900px) {
  .hero { padding-top: calc(var(--header-h) + 56px); }
}

/* --- HERO | load choreography (A8). CSS keyframes, so the sequence runs even
       if next.js never executes. Only the film's own fade is JS-gated, and its
       absence leaves the poster, which is the finished state.
       Last deterministic motion ends at 2160ms. --- */
@keyframes hero-up  { from { opacity: 0; transform: translate3d(0, var(--hy, 18px), 0); } to { opacity: 1; transform: none; } }
@keyframes grow-y   { from { transform: scaleY(0); } to { transform: scaleY(1); } }
@keyframes grow-x   { from { transform: scaleX(0); } to { transform: scaleX(1); } }
@keyframes pop      { from { transform: scale(.5); opacity: 0; } to { transform: scale(1); opacity: 1; } }
@keyframes draw     { to { stroke-dashoffset: 0; } }
@keyframes fade     { to { opacity: 1; } }
@keyframes ln-in {
  from { clip-path: inset(-24% -8% 108% -8%); transform: translate3d(0, .16em, 0); }
  to   { clip-path: inset(-24% -8% -12% -8%); transform: none; }
}
@keyframes cue-seg   { from { transform: translateY(0); } to { transform: translateY(28px); } }

.hero .hl { animation: hero-up var(--hdur, 520ms) var(--ease-out) var(--hd, 0ms) both; }

.hero .eyebrow       { --hy: 10px; --hdur: 400ms; --hd: 160ms; }
.hero .lead          { --hy: 20px; --hdur: 520ms; --hd: 560ms; }
.hero .hero__actions { --hy: 16px; --hdur: 480ms; --hd: 700ms; }

/* --- HERO | fallbacks and preferences (A6.6, A10) --- */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .hero__rail { background: rgba(1, 30, 61, .72); }
}
@media (prefers-contrast: more) {
  .hero__scrim {
    background: linear-gradient(90deg, #011E3D 0, rgba(1, 30, 61, .90) 40%, rgba(1, 30, 61, .55) 100%);
  }
  .hero__pattern { display: none; }
  .hero__rail {
    background: rgba(1, 30, 61, .88);
    -webkit-backdrop-filter: none;
            backdrop-filter: none;
    border-top-color: rgba(255, 255, 255, .34);
  }
}

/* ===== SHARED, RETAINED FROM SPEC-01 =====================================
   The hero no longer renders either of these, but assets/pages.css builds on
   both for templates/page-about.php, which loads next.css:
     .frame__gold  -> .ab-story__frame .frame__gold (pages.css)
     .stats/.stat  -> .ab-stats .stats             (pages.css)
   Deleting them here would silently break that page, so they stay exactly as
   SPEC-01 left them. ========================================================= */
.frame__gold {
  position: absolute;
  left: -1px; top: 40px;        /* ≤560 */
  z-index: var(--z-overlay);
  width: 3px; height: 64px;     /* ≤560 */
  border-radius: 2px;
  background: var(--gold);
  transform: scaleY(0);
  transform-origin: top;
  animation: grow-y 420ms var(--ease-out) 720ms both;
}
.stats {
  position: relative;
  z-index: var(--z-content);
  display: grid;
  grid-template-columns: 1fr 1fr;   /* ≤560 */
  gap: var(--s-5) var(--s-6);
  margin-top: var(--s-8);
  padding-top: var(--s-6);
  border-top: 1px solid var(--line);
}
.stat { position: relative; }
.stat + .stat { padding-left: var(--s-6); }
.stat + .stat::before {
  content: "";
  position: absolute;
  left: 0; top: 0; bottom: 0;
  width: 1px;
  background: var(--line);
  transform: scaleY(0);
  transform-origin: top;
  animation: grow-y 420ms var(--ease-out) 560ms both;
}
.stat:nth-child(3) {
  grid-column: 1 / -1;          /* ≤560 */
  padding-left: 0;
  padding-top: var(--s-5);
  border-top: 1px solid var(--line);
}
.stat:nth-child(3)::before { display: none; }
.stat__num {
  display: block;
  font: var(--t-num);
  font-variant-numeric: tabular-nums;
  letter-spacing: -.032em;
  color: var(--navy);
}
.stat__plus {
  font-size: .62em;
  vertical-align: .28em;
  margin-left: 1px;
  color: var(--navy);
}
.stat__label {
  display: block;
  margin-top: var(--s-2);
  font: var(--t-cap);
  color: var(--ink-3);
  max-width: 20ch;
}
@media (min-width: 561px) {
  .frame__gold { left: -3px; top: 88px; width: 5px; height: 104px; }
  .stats { grid-template-columns: repeat(3, 1fr); gap: 0; }
  .stat:nth-child(3) {
    grid-column: auto;
    padding-left: var(--s-6);
    padding-top: 0;
    border-top: 0;
  }
  .stat:nth-child(3)::before { display: block; }
}

/* ===== SECTION HEAD  (shared component — SPEC-00 §10, defined at first use in SPEC-03 §5.2) ===== */
.sec__head { position: relative; z-index: var(--z-content); max-width: 68ch; }
.sec__head h2 {
  margin-top: var(--s-4);
  font: var(--t-d2);
  letter-spacing: -.022em;
  color: var(--ink);
  max-width: 22ch;
}
.sec__intro {
  margin-top: var(--s-5);
  font: var(--t-lead);
  color: var(--ink-3);
  max-width: 64ch;
}

/* --split modifier (SPEC-04 §5.2) */
.sec__head--split { max-width: none; }
.sec__head--split > .link-arrow { margin-top: var(--s-5); }
@media (min-width: 768px) {
  .sec__head--split {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: var(--s-8);
  }
  .sec__head--split > div { max-width: 62ch; }
  .sec__head--split > .link-arrow { flex: 0 0 auto; margin-top: 0; padding-bottom: 4px; }
}

/* =========================================================================
   TRUST STRIP  (SPEC-02)
   The hero's footnote, not a section: no eyebrow, no h2, no CTA, no cards,
   no gold, no glass, no pattern. Stays on --paper with a hairline top rule.
   ========================================================================= */
.trust {
  position: relative;
  isolation: isolate;
  padding-top: var(--s-7);      /* ≤560 → 32 */
  padding-bottom: var(--s-9);   /* ≤560 → 48 */
  background: var(--paper);
  border-top: 1px solid var(--line);
}
.trust__line {
  margin-inline: auto;
  max-width: 46ch;
  text-align: center;
  font: var(--t-lead);
  color: var(--ink-2);          /* one step below the hero in the hierarchy, still AAA */
}
.trust__rail {
  position: relative;
  overflow: hidden;             /* the only thing containing the track — do not remove */
  margin-top: var(--s-9);       /* REFINE-01 T4: 48, was 40 | let the wall own the block */
  --rail-gap: 36px;             /* ≤560 */
  --fade: 32px;                 /* ≤767 */
  -webkit-mask-image: linear-gradient(90deg,
      transparent 0, #000 var(--fade), #000 calc(100% - var(--fade)), transparent 100%);
          mask-image: linear-gradient(90deg,
      transparent 0, #000 var(--fade), #000 calc(100% - var(--fade)), transparent 100%);
}
.trust__track {
  display: flex;
  width: max-content;
  animation: rail 58s linear infinite;
  animation-play-state: paused;   /* starts only once the strip is revealed */
  /* permanently animating: the one element §6.2's "reset will-change" rule skips */
  will-change: transform;
}
.trust__rail.is-in .trust__track { animation-play-state: running; }
.trust__rail:hover .trust__track,
.trust__rail:focus-within .trust__track { animation-play-state: paused; }

/* -50% of a max-content track holding two identical sets is always exactly one
   set, whatever the real logo widths are. Never replace with a pixel distance. */
@keyframes rail { to { transform: translate3d(-50%, 0, 0); } }

.trust__set { display: flex; align-items: center; }
/* TOOLS-01 | a tool is a LOCKUP, not a wordmark.
   The fourteen navy #16212E wordmark PNGs are retired. The client asked for
   every mark in its own brand colour at rest, and there is no colour source
   for a bespoke navy wordmark; the library also had to grow from 14 to ~55 so
   the service singles can publish a real per-service list. So the unit is now
   the vendor's own mark (Simple Icons, CC0, recoloured to the brand's
   documented hex) at a fixed square, plus the product name in the site's
   display face. Height-normalised mark + one type size is what keeps a row of
   mixed brand colours reading as ONE row rather than as confetti.
   There is no grayscale state and no hover colour reveal: colour is the rest
   state everywhere the library is rendered. */
/* TOOLS-03 | the marks were too small to read as logos. Two heights now,
   because a square glyph and a 4:1 wordmark do not carry the same optical
   weight at the same pixel height: --lg-mark drives the squares, --lg-word
   the wordmarks, and --lg-word sits one step under so the two kinds balance
   in a row rather than the wordmarks dominating it. */
.lg {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: 10px;
  height: 42px;                 /* ≤560 */
  --lg-mark: 32px;
  --lg-word: 28px;
  --lg-wmax: 150px;
  --lg-type: 15px;
  padding-inline: calc(var(--rail-gap) / 2);
}
/* TOOLS-02: height is pinned and width follows the file's own ratio, because
   the library now mixes square glyphs with wordmarks 4-6x wider. ONE height
   across all three kinds is the whole reason a row of mixed brand colours and
   mixed logo shapes still reads as one row. */
.lg__mark {
  flex: 0 0 auto;
  height: var(--lg-word);
  width: auto;
  /* --lg-wmax is 150/170/190 rather than a flat 160: `contain` does not crop
     when a mark hits the cap, it letterboxes, which drops that one mark BELOW
     the row's common height. The cap is set just past the widest lockup in the
     library (Acumatica at 136 units wide per 24 of height) at each step, so it
     catches a runaway asset without ever shortening one that is in the set. */
  max-width: var(--lg-wmax);
  object-fit: contain;
}
/* a square mark keeps its square box, so the name sits at a fixed offset */
.lg--glyph .lg__mark { height: var(--lg-mark); width: var(--lg-mark); }
/* a wordmark says its own name, so there is no .lg__name to leave room for */
.lg--word { gap: 0; }
.lg__name {
  font: 600 var(--lg-type)/1 var(--font-display);
  letter-spacing: -.006em;
  color: var(--navy);
  white-space: nowrap;
}
/* A tool whose mark could not be sourced under a usable licence. The name is
   the mark. Same row, same height, so nothing about the list says "missing". */
.lg--chip {
  gap: 0;
  /* the pill IS this item's box, so the rail gap moves to margin: as padding
     it would push the hairline out to the wrong distance from the glyph. */
  padding-inline: 13px;
  margin-inline: calc(var(--rail-gap) / 2);
  border: 1px solid var(--line);
  border-radius: 999px;
}
.lg--chip .lg__name { font-weight: 500; color: var(--ink-2); }

/* --- TOOLS-04 | the shared logo rail -------------------------------------
   One moving row, used anywhere the library is rendered as a marquee. The
   homepage trust strip keeps its own .trust__rail selectors (a tuned edge
   fade, an .is-in gate and a reduced-motion unwind the homepage was measured
   against); this is that same mechanism, generalised.

   It lives in next.css, not pages.css, because the consumers sit in three
   different stylesheet stacks and a second copy would drift.

   NO GRID. The track is flex and every .lg is `flex: 0 0 auto` with its own
   padding-inline; spacing never comes from a fixed-width column. A
   `repeat(N, minmax(0,1fr))` grid is exactly what let a 200px wordmark spill
   into its neighbour, and it cannot happen here.

   It runs on load rather than waiting for a JS .is-in class, so the row reads
   the same with scripting off. Pausing is CSS-only for the same reason. */
.lgrail {
  position: relative;
  overflow: hidden;                      /* the only thing containing the track */
  --rail-gap: 32px;
  --fade: 28px;
  -webkit-mask-image: linear-gradient(90deg,
      transparent 0, #000 var(--fade), #000 calc(100% - var(--fade)), transparent 100%);
          mask-image: linear-gradient(90deg,
      transparent 0, #000 var(--fade), #000 calc(100% - var(--fade)), transparent 100%);
}
@media (min-width: 561px)  { .lgrail { --rail-gap: 40px; --fade: 48px; } }
/* the Services panel's foot strip: the shared rail at menu scale. Only the
   mark size and the gaps change; the track, the duplicate half and the
   reduced-motion unwind are the component's own. */
.lgrail--hdr { --rail-gap: 34px; --fade: 40px; }
.lgrail--hdr .lg { height: 28px; --lg-mark: 22px; --lg-word: 20px; --lg-wmax: 108px; --lg-type: 13px; }
@media (min-width: 1024px) { .lgrail { --rail-gap: 48px; --fade: 64px; } }
.lgrail__track {
  display: flex;
  width: max-content;
  animation: rail 68s linear infinite;
  will-change: transform;
}
/* Pause so a reader can look at a mark, and so keyboard focus never moves out
   from under itself. */
.lgrail:hover .lgrail__track,
.lgrail:focus-within .lgrail__track { animation-play-state: paused; }
.lgrail__set {
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
  list-style: none;
  margin: 0;
  padding: 0;
}
/* A 68s loop collapsed to 1ms would park the track at translate3d(-50%), i.e.
   half the marks off screen. Unwind it into a designed static state: one
   wrapped row, no mask, no clipping, the duplicate half hidden. */
/* owner ruling 2026-09-20: the rail keeps sliding under reduced motion (the
   wrapped grid read as broken on phones with Reduce Motion on). It slows to
   half speed instead; hover and focus still pause it. */
@media (prefers-reduced-motion: reduce) {
  .lgrail__track { animation-duration: 136s !important; animation-iteration-count: infinite !important; }
}
/* REFINE-01 T4: two centred paragraphs of the same weight sandwiching the rail
   made the block statically symmetric. The trademark note is the smallest,
   narrowest thing in it, so the wall reads as the subject. */
.trust__note {
  margin-top: var(--s-7);
  margin-inline: auto;
  max-width: 56ch;
  text-align: center;
  font: var(--t-cap);
  font-size: 11px;
  color: var(--ink-3);
}

@media (min-width: 561px) {
  .trust { padding-top: var(--s-8); padding-bottom: var(--s-10); }
  .trust__rail { --rail-gap: 44px; }
  .lg { height: 48px; --lg-mark: 36px; --lg-word: 32px; --lg-wmax: 170px; --lg-type: 16px; }
}
@media (min-width: 768px)  { .trust__rail { --fade: 72px; } }
@media (min-width: 1024px) {
  /* REFINE-01 T1: --fade was 72px against a 1296px rail, which cut the logos
     at both ends mid-glyph and read as clipping rather than as a fade.
     REFINE-01 T3: 48, was 44 | the two-line lockups drop to --lg-h 30 in
     inc/logos.php and the row gains the headroom that costs. */
  .trust__rail { --rail-gap: 56px; --fade: 120px; }
  .lg { height: 52px; --lg-mark: 40px; --lg-word: 36px; --lg-wmax: 190px; --lg-type: 16px; }
  /* REFINE-01 T2's opacity ramp (.68 then .78) is DELETED by TOOLS-01. It
     existed to stop a single-ink navy wordmark reading washed out against the
     body copy; the marks carry full brand colour now and any opacity below 1
     would desaturate exactly what the client asked to see. Size carries the
     step up at this breakpoint instead. */
}

/* =========================================================================
   HOMEPAGE HUB ANCHORS
   /services/ and /industries/ are removed (owner ruling 2026-09-14) and both
   301 to these two ids. Every "all services" / "all industries" link on the
   site now lands here, so the landing must clear the fixed header instead of
   putting the band's eyebrow underneath it. Same recipe the interior pages
   already use for their in-page jump targets, with a literal fallback because
   these two ids are also reached from a cold cross-document navigation.
   ========================================================================= */
#services,
#industries { scroll-margin-top: calc(var(--hdr-h, 80px) + 16px); }

/* =========================================================================
   SERVICES  (SPEC-03)
   Art on plates, text on open ground. No cards, no glass, no elevation:
   the structure is the plates plus the hairlines.
   ========================================================================= */
.svc {
  position: relative;
  isolation: isolate;
  overflow: clip;
  padding-block: var(--section-y);
  background: var(--wash);
}
.svc__pattern {
  position: absolute;
  inset: 0;
  z-index: var(--z-pattern);
  pointer-events: none;
  background-image: var(--pat-dot);
  /* tight on purpose: the mask must be fully transparent before it reaches
     .sec__intro (SPEC-00 §3.3 rule 5 — never a pattern behind body copy) */
  /* ≤1023 the head runs nearly the full container, so the only ground the
     pattern can have is a shallow band above the intro. Measured: the mask is
     fully transparent 30px+ before .sec__intro's top edge at 375/560/768. */
  --pat-x: 78%; --pat-y: 0%; --pat-w: 50%; --pat-h: 9%;
  -webkit-mask-image: radial-gradient(var(--pat-w) var(--pat-h) at var(--pat-x) var(--pat-y),
                        #000 0%, #000 42%, transparent 78%);
          mask-image: radial-gradient(var(--pat-w) var(--pat-h) at var(--pat-x) var(--pat-y),
                        #000 0%, #000 42%, transparent 78%);
}
/* ≥1024 the head is 62ch and the top-right quadrant opens up */
@media (min-width: 1024px) {
  .svc__pattern { --pat-x: 92%; --pat-y: 3%; --pat-w: 34%; --pat-h: 20%; }
}

/* --- pillars ---------------------------------------------------------------
   Round 3. The three columns were three stacks of text separated by a hairline
   in the gutter: no ground, no edge, and 40% of the band was empty wash. They
   are cards now, on --paper over the section's --wash, with the 20px radius
   the page already uses for its media frames.

   The card also does more than it did. It was one illustration, a name, a
   paragraph and a single link into one arbitrary service. It now previews the
   hub the way the hub itself is built: the numeral and the real service count
   at the top right, and a three-row ledger of lead services under the
   paragraph, all read from inc/services-map.php and WordPress rather than
   written here. The footer link goes to that pillar's hub anchor, so the band
   hands the reader to /services/ three times over instead of once.

   Gold appears nowhere: the eyebrow tick is the band's whole budget
   (SPEC-00 §3.5), and the numerals are --navy-300, a border/large-text tone.
   The illustration assets are untouched. */
.pillars { display: grid; gap: var(--s-6); margin-top: var(--s-10); }
.pillar {
  position: relative;
  z-index: var(--z-content);
  display: flex;
  flex-direction: column;
  padding: var(--s-6);
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-1);
  /* `translate`, not `transform`: .r--plate owns transform for the reveal and
     `.js .r.is-in { transform: none }` out-specifies :hover, so a hover
     transform here would simply never land. The two properties compose. */
  transition: translate var(--dur-2) var(--ease-out),
              box-shadow var(--dur-2) var(--ease-out),
              border-color var(--dur-1) var(--ease-out);
}
.pillar:hover,
.pillar:focus-within {
  translate: 0 -2px;
  border-color: var(--navy-100);
  box-shadow: var(--sh-3);
}

.pillar__top {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--s-4);
}

/* REFINE-01 S2 collapsed the nested frame: the art ships on a white ground,
   so the plate is the one frame, --paper with a 1px --line ring.
   Client round 2: the full-column 3/2 plate read as a hero image per column,
   "way too big". The plate is a compact square tile at icon scale. Round 3
   keeps that tile and sits it on --wash, because the card under it is now
   --paper and a white tile on white would lose its edge. The 880x880 masters
   are square, so `cover` shows the whole drawing; the per-pillar `scale`
   below only trims the white margin the two group scenes carry. */
.pillar__plate {
  --tile: 96px;
  display: block;
  width: var(--tile);
  height: var(--tile);
  flex: 0 0 auto;
  background: var(--wash);
  border-radius: var(--r-md);
  box-shadow: 0 0 0 1px var(--line);
  overflow: hidden;
}
.pillar__plate img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 50%;
  background: none;
  border-radius: 0;
  box-shadow: none;
}
/* `accounting` is a close-up that already fills its square. `advisory` and
   `analytics` are wider scenes with roughly 10% white margin on every side,
   so they get a small scale to match the accounting subject size. */
.pillar:nth-child(2) .pillar__plate img { scale: 1.12; }
.pillar:nth-child(3) .pillar__plate img { scale: 1.12; }

.pillar__meta {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: var(--s-1);
  text-align: right;
}
.pillar__num {
  font: var(--t-num-sm);
  letter-spacing: -.028em;
  color: var(--navy-300);
}
.pillar__count {
  font: var(--t-micro);
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ink-3);
  white-space: nowrap;
}

.pillar__title {
  margin-top: var(--s-6);
  font: var(--t-d3);
  letter-spacing: -.016em;
  color: var(--navy);
}
.pillar__text {
  margin-top: var(--s-3);
  font: var(--t-body-sm);
  color: var(--ink-3);
  max-width: 42ch;
}

/* the mini-ledger: the hub's row vocabulary at card scale. A rule under every
   row, the title left, a small arrow right. Titles are real post titles, so
   two lines is possible and the row is aligned to the first line, not centred. */
.pillar__ledger {
  margin-top: var(--s-5);
  border-top: 1px solid var(--line-2);
}
.pillar__ledger li { border-bottom: 1px solid var(--line-2); }
.pillar__row {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--s-3);
  padding-block: var(--s-3);
  font: var(--t-body-sm);
  font-weight: 500;
  color: var(--navy);
  transition: color var(--dur-1) var(--ease-out);
}
.pillar__row svg {
  width: 14px;
  height: 14px;
  flex: 0 0 auto;
  margin-top: 3px;          /* optical: the 14px arrow to the cap height of a 15px line */
  color: var(--navy-300);
  transition: transform var(--dur-2) var(--ease-out), color var(--dur-1) var(--ease-out);
}
.pillar__row:hover { color: var(--navy-500); }
.pillar__row:hover svg { color: var(--navy-500); transform: translateX(3px); }

/* pinned to the card foot so the three footers align whatever the copy does */
.pillar__all { margin-top: auto; padding-top: var(--s-4); }
.pillar__all:hover,
.pillar__all:focus-visible {
  color: var(--navy-500);
  text-decoration: underline;
  text-underline-offset: 4px;
  text-decoration-thickness: 1.5px;
}
.pillar__all:hover svg { transform: translateX(4px); }

/* the band foot carries the hub link the head used to hold */
.pillars__foot { margin-top: var(--s-6); }

@media (min-width: 768px) {
  /* two up, the third across the full row: three equal columns are too narrow
     for a 42ch paragraph plus a ledger until 1180. */
  .pillars {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--s-6);
  }
  .pillar:last-child { grid-column: 1 / -1; }
  /* the third card is twice as wide as the other two, so its ledger rows would
     be twice as long and its rules would run under 300px of nothing. It splits
     internally instead: copy left, ledger and hub link right, one row of tile
     and count across the top exactly as the narrow cards have. */
  .pillar:last-child {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    column-gap: var(--s-8);
    align-content: start;
  }
  .pillar:last-child .pillar__top    { grid-column: 1 / -1; grid-row: 1; }
  .pillar:last-child .pillar__title  { grid-column: 1; grid-row: 2; }
  .pillar:last-child .pillar__text   { grid-column: 1; grid-row: 3; }
  .pillar:last-child .pillar__ledger { grid-column: 2; grid-row: 2 / span 2; margin-top: var(--s-6); }
  .pillar:last-child .pillar__all    { grid-column: 2; grid-row: 4; margin-top: 0; }
  .pillar { padding: var(--s-7); }
  .pillar__plate { --tile: 104px; }
  .pillars__foot { margin-top: var(--s-7); }
}
@media (min-width: 1180px) {
  .pillars { grid-template-columns: repeat(3, minmax(0, 1fr)); column-gap: var(--s-6); }
  /* three equal columns again, so the third card drops the internal split */
  .pillar:last-child { grid-column: auto; display: flex; }
  .pillar:last-child .pillar__ledger { margin-top: var(--s-5); }
  .pillar:last-child .pillar__all    { margin-top: auto; }
  .pillar__plate { --tile: 112px; }
}

/* the head's facts cluster, right of the h2 from 768 up. Two lines, no rule
   below 768 where it sits under the intro rather than beside it. */
.svc__facts { margin-top: var(--s-6); max-width: 30ch; }
.svc__facts-n {
  display: block;
  font: var(--t-num);
  letter-spacing: -.028em;
  color: var(--navy);
}
.svc__facts-c {
  display: block;
  margin-top: var(--s-2);
  font: var(--t-cap);
  color: var(--ink-3);
}
@media (min-width: 768px) {
  .svc__facts {
    flex: 0 0 auto;
    max-width: 26ch;
    margin-top: 0;
    padding-left: var(--s-6);
    border-left: 1px solid var(--line);
  }
}

/* --- process rail: four independent cycles, not four steps ---------------
   No arrows, no "then", no gold terminal marker. The spine is a shared rule,
   not a path. 01-04 are index labels. */
.rail { position: relative; z-index: var(--z-content); margin-top: var(--s-11); }
.rail__label {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: var(--s-7);
  font: var(--t-eyebrow);
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--navy);
}
.rail__list {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  row-gap: var(--s-7);
  padding-left: var(--s-7);
}
/* the spine: a single hairline, drawn once when .rail reveals */
.rail__list::before {
  content: "";
  position: absolute;
  left: 5px;
  top: 6px;
  bottom: 6px;
  width: 1px;
  background: var(--line);
  transform: scaleY(0);
  transform-origin: top;
}
.rail.is-in .rail__list::before { animation: grow-y 900ms var(--ease-soft) 0ms both; }

.rail__item { position: relative; }
.rail__item::before {
  content: "";
  position: absolute;
  left: -32px;
  top: 6px;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--navy);
  transform: scale(.5);
  opacity: 0;
  transition: width var(--dur-2) var(--ease-out),
              height var(--dur-2) var(--ease-out),
              top var(--dur-2) var(--ease-out),
              left var(--dur-2) var(--ease-out),
              box-shadow var(--dur-2) var(--ease-out);
}
.rail.is-in .rail__item::before {
  animation: pop 240ms var(--ease-out) calc(260ms + var(--i) * 140ms) both;
}
.rail__item:hover::before,
.rail__item:focus-within::before {
  width: 14px;
  height: 14px;
  left: -34px;
  top: 4px;
  box-shadow: 0 0 0 3px var(--wash);   /* opaque ring, zero blur — not a glow */
}

.rail__link { display: block; }
.rail__num {
  display: inline;
  margin-right: var(--s-3);
  font: 600 12px/1 var(--font-display);
  font-variant-numeric: tabular-nums;
  letter-spacing: .08em;
  /* --ink-3, not --navy-300: --navy-300 on --wash is ≈3.2:1 and fails AA at 12px */
  color: var(--ink-3);
}
.rail__name {
  display: inline;
  font: var(--t-d4);
  letter-spacing: -.010em;
  color: var(--navy);
}
.rail__desc {
  display: block;
  margin-top: var(--s-2);
  font: var(--t-cap);
  color: var(--ink-3);
}
.rail__item:hover .rail__name,
.rail__item:focus-within .rail__name {
  color: var(--navy-500);
  text-decoration: underline;
  text-underline-offset: 4px;
  text-decoration-thickness: 1.5px;
}

@media (min-width: 1024px) {
  .rail { margin-top: var(--s-12); }
  .rail__list {
    grid-template-columns: repeat(4, minmax(0, 1fr));
    column-gap: var(--s-8);
    row-gap: 0;
    padding-top: var(--s-7);          /* clears the spine and the nodes */
    padding-left: 0;
  }
  .rail__list::before {
    left: 0;
    right: 0;
    top: 0;
    bottom: auto;
    width: auto;
    height: 1px;
    transform: scaleX(0);
    transform-origin: left;
  }
  .rail.is-in .rail__list::before { animation: grow-x 900ms var(--ease-soft) 0ms both; }
  /* the list's 32px padding-top is what clears the spine, so a node centred on
     the spine sits at -(32 + half the dot) from its item's top edge */
  .rail__item::before { left: 0; top: calc(var(--s-7) * -1 - 5px); }
  .rail__item:hover::before,
  .rail__item:focus-within::before { left: 0; top: calc(var(--s-7) * -1 - 7px); }
  .rail__num { display: block; margin-right: 0; margin-bottom: var(--s-3); }
  .rail__name { display: block; }
}

/* --- finance cycles band (SPEC-04-R2, "the ledger band") -----------------
   .rail above is unchanged and still serves templates/services-hub.php. The
   homepage renders .cyc instead: the same four cycles, the same approved
   one-liners, the same hrefs, and the same three prohibitions SPEC-03 §3.3 set
   out. No arrows between cycles, no "then", no gold terminal marker, because
   these are four independent cycles and not four steps of one. 01-04 stay
   index labels and the list stays unordered.

   What is new is the container. The four white rounded cards with 48px icon
   plates sat directly under three white pillar cards and read as a footnote to
   them; the dotted connector and its four navy dots carried no information,
   because there is no sequence to connect. Both are deleted. The band is a
   full-bleed --paper panel ruled into the --wash section, which is a surface
   no other part of the page has, and the weight comes from scale: 56px
   numerals, 44px plateless glyphs, 26px names, four ruled rows read as one
   ledger the firm keeps.

   Elevation is zero and there is no glass: rules and fills only. Gold appears
   exactly twice, as the 72x3 band mark and as the 3px active row edge, never
   as a fill and never behind text. */
.cyc {
  position: relative;
  z-index: var(--z-content);
  --cyc-pad: var(--s-10);              /* ≤1023 */
  margin-top: var(--s-10);             /* ≤767 */
  background: var(--paper);
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}
.cyc > .container { padding-block: var(--cyc-pad); }
.cyc__inner { position: relative; }
/* the gold mark sits ON the top rule, aligned to the container's text edge */
.cyc__inner::before {
  content: "";
  position: absolute;
  left: 0;
  top: calc(var(--cyc-pad) * -1 - 1px);
  width: 56px; height: 3px;            /* ≤767 */
  background: var(--gold);
  transform: scaleX(0);
  transform-origin: left;
}
.cyc.is-in .cyc__inner::before { animation: grow-x 520ms var(--ease-soft) both; }

.cyc__grid { display: grid; gap: var(--s-8); }
.cyc__aside { display: flex; flex-direction: column; }
.cyc__h {
  margin-top: var(--s-4);
  font: var(--t-d2);
  letter-spacing: -.022em;
  color: var(--navy);
  max-width: 16ch;
}

/* the figure: four slides stacked, the active one visible. Never rendered
   below 1024, so none of the four photographs is fetched on a phone. */
.cyc__fig {
  display: none;
  position: relative;
  margin-top: auto;
  aspect-ratio: 4 / 3;
  border-radius: var(--r-md);
  overflow: hidden;
  background: var(--navy-050);
}
.cyc__slide {
  position: absolute;
  inset: 0;
  opacity: 0;
  transition: opacity var(--dur-3) var(--ease-soft);
}
.cyc__slide.is-on { opacity: 1; }
.cyc__slide img {
  width: 100%; height: 100%;
  object-fit: cover;
  transform: scale(1.04);
  transition: transform var(--dur-5) var(--ease-soft);
}
.cyc__slide.is-on img { transform: none; }
.cyc__cap {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  padding: var(--s-5);
  font: var(--t-eyebrow);
  letter-spacing: .14em;
  text-transform: uppercase;
  color: #fff;
  background: linear-gradient(180deg, rgba(1, 30, 61, 0) 0%, rgba(1, 30, 61, .88) 68%);
}

/* --- the rows ------------------------------------------------------------
   Separators are the block's only structure: a rule above the first row and
   one under each. Nothing lifts, nothing shadows, nothing scales. That is card
   language and removing it is half the point. */
.cyc__list { border-top: 1px solid var(--line); }
.cyc__item { border-bottom: 1px solid var(--line); }

.cyc__link {
  position: relative;
  isolation: isolate;                  /* keeps the ::before fill behind the row's own text only */
  display: grid;
  grid-template-columns: minmax(0, 1fr) 32px;   /* ≤767 */
  column-gap: var(--s-4);
  align-items: center;
  padding-block: var(--s-6);           /* ≤767 → 24 */
}
/* the fill bleeds 20px past the text edge so it reads as a band, not a box */
.cyc__link::before {
  content: "";
  position: absolute;
  inset: 0 -20px;
  z-index: -1;
  background: var(--navy-050);
  border-radius: var(--r-sm);
  opacity: 0;
  transition: opacity var(--dur-2) var(--ease-out);
}
/* the gold edge is gated by OPACITY, not by transform, so forcing scaleY(1)
   under reduced motion cannot reveal all four edges at once */
.cyc__link::after {
  content: "";
  position: absolute;
  left: -20px; top: 0; bottom: 0;
  z-index: -1;
  width: 3px;
  background: var(--gold);
  opacity: 0;
  transform: scaleY(.4);
  transition: opacity var(--dur-2) var(--ease-out),
              transform var(--dur-2) var(--ease-out);
}
.cyc__link:focus-visible {
  outline: 2px solid var(--navy);
  outline-offset: 2px;
  border-radius: var(--r-sm);
}

.cyc__num {
  grid-column: 1; grid-row: 1;
  font: 600 34px/1 var(--font-display);          /* ≤767 */
  font-variant-numeric: tabular-nums;
  letter-spacing: -.03em;
  color: rgba(2, 45, 90, .16);
  transition: color var(--dur-2) var(--ease-out);
}
.cyc__glyph {
  grid-column: 2; grid-row: 1;
  justify-self: end;
  display: block;
  color: var(--ink-3);                 /* 5.6:1, not --navy-300's 3.04:1 */
  transition: color var(--dur-2) var(--ease-out);
}
.cyc__glyph svg { width: 32px; height: 32px; }  /* ≤767 */
.cyc__body { grid-column: 1; grid-row: 2; margin-top: var(--s-3); }
.cyc__tag {
  display: block;
  font: 600 13px/1.3 var(--font-ui);
  letter-spacing: .02em;
  color: var(--ink-3);
}
.cyc__name {
  display: block;
  margin-top: var(--s-1);
  font: 600 21px/1.24 var(--font-display);       /* ≤767 */
  letter-spacing: -.016em;
  color: var(--navy);
}
.cyc__desc {
  grid-column: 1 / -1; grid-row: 3;
  margin-top: var(--s-2);
  font: var(--t-body-sm);
  color: var(--ink-2);
  max-width: 22ch;
}
/* ≤767 the standalone arrow column is dropped and the arrow rides the name */
.cyc__arw {
  grid-column: 2; grid-row: 2;
  align-self: end;
  justify-self: end;
  display: block;
  color: var(--ink-3);
  transition: color var(--dur-2) var(--ease-out),
              transform var(--dur-1) var(--ease-out);
}
.cyc__arw svg { width: 14px; height: 14px; }    /* ≤767 */

/* hover and focus, repeatable. No lift, no shadow. */
.cyc__link:hover::before,
.cyc__link:focus-visible::before { opacity: 1; }
.cyc__link:hover::after,
.cyc__link:focus-visible::after  { opacity: 1; transform: scaleY(1); }
.cyc__link:hover .cyc__num,
.cyc__link:focus-visible .cyc__num { color: var(--navy); }
.cyc__link:hover .cyc__glyph,
.cyc__link:focus-visible .cyc__glyph,
.cyc__link:hover .cyc__arw,
.cyc__link:focus-visible .cyc__arw { color: var(--navy); }
.cyc__link:hover .cyc__arw,
.cyc__link:focus-visible .cyc__arw { transform: translateX(4px); }
.cyc__link:hover .cyc__name,
.cyc__link:focus-visible .cyc__name {
  text-decoration: underline;
  text-underline-offset: 5px;
  text-decoration-thickness: 1.5px;
  text-decoration-color: rgba(2, 45, 90, .35);
}

/* the reveal. --i is already printed per item in PHP. Resting opacity is 1
   without .js, matching the no-JS handling .rail already uses, so a reader
   with JavaScript off gets four rows and not a blank band. */
@keyframes cyc-row { from { opacity: 0; transform: translate3d(0, 12px, 0); }
                     to   { opacity: 1; transform: none; } }
.js .cyc__item { opacity: 0; }
.js .cyc.is-in .cyc__item {
  animation: cyc-row 400ms var(--ease-out) calc(160ms + var(--i) * 70ms) both;
}

@media (min-width: 768px) {
  .cyc { margin-top: var(--s-11); }
  .cyc__inner::before { width: 72px; }
  .cyc__grid { gap: var(--s-9); }
  .cyc__link {
    grid-template-columns: 56px 36px minmax(0, 1fr) 18px;
    column-gap: var(--s-5);
  }
  .cyc__num   { font-size: 40px; }
  .cyc__glyph { grid-column: 2; justify-self: start; }
  .cyc__glyph svg { width: 36px; height: 36px; }
  .cyc__body  { grid-column: 3; grid-row: 1; margin-top: 0; }
  .cyc__name  { font-size: 22px; }
  .cyc__desc  { grid-column: 3; grid-row: 2; }
  .cyc__arw   { grid-column: 4; grid-row: 1 / span 2; align-self: center; }
  .cyc__arw svg { width: 18px; height: 18px; }
  .cyc__num, .cyc__glyph { grid-row: 1 / span 2; align-self: center; }
  .cyc__link { padding-block: var(--s-6); }
}

@media (min-width: 1024px) {
  .cyc { --cyc-pad: var(--s-11); margin-top: var(--s-12); }
  .cyc__grid {
    grid-template-columns: 320px minmax(0, 1fr);
    column-gap: var(--s-9);
    align-items: stretch;
  }
  .cyc__fig { display: block; }
  .cyc__link {
    grid-template-columns: 64px 40px minmax(0, 1fr) 18px;
    column-gap: var(--s-5);
    padding-block: var(--s-6);
  }
  .cyc__num   { font-size: 44px; }
  .cyc__glyph svg { width: 40px; height: 40px; }
  /* a persistently highlighted row only makes sense where there is a figure
     for it to drive; :hover and :focus-visible apply at every width */
  .cyc__link.is-on::before { opacity: 1; }
  .cyc__link.is-on::after  { opacity: 1; transform: scaleY(1); }
  .cyc__link.is-on .cyc__num   { color: var(--navy); }
  .cyc__link.is-on .cyc__glyph,
  .cyc__link.is-on .cyc__arw   { color: var(--navy); }
}

@media (min-width: 1180px) {
  .cyc__grid { grid-template-columns: 380px minmax(0, 1fr); column-gap: var(--s-11); }
  .cyc__link {
    grid-template-columns: 78px 44px minmax(0, 1fr) 244px 20px;
    column-gap: var(--s-6);
    padding-block: var(--s-7);
  }
  .cyc__num   { font-size: 56px; }
  .cyc__glyph svg { width: 44px; height: 44px; }
  .cyc__name  { font-size: 26px; }
  /* the one-liner takes its own column, so each row reads as a ledger entry
     with a description and a value */
  .cyc__desc  { grid-column: 4; grid-row: 1 / span 2; align-self: center; margin-top: 0; }
  .cyc__arw   { grid-column: 5; }
  .cyc__arw svg { width: 20px; height: 20px; }
}

/* =========================================================================
   INDUSTRIES  (SPEC-04)
   Scroll-snapped rail on touch, 3-column grid on a desktop mouse. Same DOM.
   Eleven sectors plus a twelfth CTA card, so the set is always 12.
   ========================================================================= */
.ind {
  position: relative;
  isolation: isolate;
  padding-block: var(--section-y);
  background: var(--paper);          /* SPEC-00 §3.2 row 3 — no pattern */
}

/* ≤1023 — rail. The negative margin is exactly one gutter: any other value
   produces a page-level horizontal scrollbar on mobile. */
.ind__set {
  display: flex;
  gap: var(--s-4);                   /* ≤560 */
  margin-top: var(--s-8);            /* ≤560 */
  margin-inline: calc(var(--gutter) * -1);
  padding-inline: var(--gutter);
  overflow-x: auto;
  overscroll-behavior-x: contain;
  /* proximity, never mandatory: mandatory fights the browser's focus-scroll
     and produces a visible double-jump when Tab reaches an off-screen card */
  scroll-snap-type: x proximity;
  scroll-padding-inline-start: var(--gutter);
  scrollbar-width: thin;
  scrollbar-color: var(--navy-100) transparent;
}
.ind__set > .ind-card { flex: 0 0 min(78vw, 340px); scroll-snap-align: start; }
.ind__set::-webkit-scrollbar { height: 6px; }
.ind__set::-webkit-scrollbar-thumb { background: var(--navy-100); border-radius: 3px; }
.ind__set::-webkit-scrollbar-track { background: transparent; }

@media (min-width: 561px) { .ind__set { gap: var(--s-5); margin-top: var(--s-9); } }
@media (min-width: 1024px) {
  .ind__set {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: var(--s-6);
    margin-top: var(--s-10);
    margin-inline: 0;
    padding-inline: 0;
    overflow: visible;
    scroll-snap-type: none;
  }
  .ind__set > .ind-card { flex: initial; }
}

.ind-card {
  position: relative;
  display: block;
  border-radius: var(--r-md);
  overflow: hidden;
  background: var(--navy-900);       /* holds the shape before the lazy image lands */
  box-shadow: var(--sh-1);
  /* REFINE-01 I1: SPEC-00 §5.2 requires a 1px border on every card and says
     that is what stops a light layout reading flat. These tiles shipped with
     --sh-1 and nothing else, so on --paper their edges dissolved into the page.
     An outline at -1px offset, NOT a border: a border would eat into the 3/2
     media box. An inset box-shadow cannot do this job either | the <img> is a
     child and paints over it. */
  outline: 1px solid rgba(2, 45, 90, .10);
  outline-offset: -1px;
  transition: transform var(--dur-2) var(--ease-out),
              box-shadow var(--dur-2) var(--ease-out);
}
.ind-card:hover { outline-color: var(--navy); }
/* the resting outline above out-orders the global :focus-visible rule (equal
   specificity, later in the file), so the standard 2px/3px ring is restated
   here rather than silently degraded to a 1px inset one. */
.ind-card:focus-visible { outline: 2px solid var(--navy); outline-offset: 3px; }

.ind-card__media { display: block; position: relative; aspect-ratio: 3 / 2; }
.ind-card__img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* biased up so the subject clears the bottom scrim under the hover zoom.
     REFINE-01 I3: one crop for eleven different photographs is one crop too
     few, so the per-slug value in inc/industries-map.php arrives as --op and
     this stays the fallback. NOTE, and it is why I3 is a mechanism rather than
     a visible change today: every source in assets/img/ind/ is exactly 3:2 and
     so is this frame, so object-position currently has sub-pixel effect. The
     values are the calibration for the moment either side of that stops being
     true | a re-cropped source, or a frame at another ratio. */
  object-position: var(--op, 50% 42%);
  transition: transform 500ms var(--ease-soft);
}
/* REFINE-01 I2: the ramp was 0 at 30% / .46 at 62% / .86 at 100%, which dulled
   eleven photographs through their whole lower half and made the wall read as
   one dark repetitive texture instead of as eleven different places. Tightened
   to the bottom third; the 19px name keeps its permitted text-shadow and still
   clears 4.5:1 against the .88 foot. */
.ind-card__scrim {
  position: absolute;
  inset: 0;
  z-index: var(--z-overlay);
  pointer-events: none;
  background: linear-gradient(180deg,
      rgba(1, 30, 61, 0) 44%,
      rgba(1, 30, 61, .38) 68%,
      rgba(1, 30, 61, .88) 100%);
  transition: opacity var(--dur-2) var(--ease-out);
}
.ind-card__body {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;                         /* bottom-anchored: the teaser grows upward */
  z-index: var(--z-content);
  padding: var(--s-5);
  color: #fff;
}
.ind-card__name {
  display: block;
  font: var(--t-d4);
  letter-spacing: -.010em;
  color: #fff;
  text-shadow: 0 1px 2px rgba(2, 45, 90, .28);   /* the one SPEC-00 §5.3 permits */
}
/* DEFAULT IS VISIBLE. Collapsed only where a fine hover pointer exists, so a
   finger on a hybrid device never loses the teaser. Fail-safe direction only. */
.ind-card__teaser {
  display: grid;
  grid-template-rows: 1fr;
  opacity: 1;
  transition: grid-template-rows var(--dur-2) var(--ease-out),
              opacity var(--dur-2) var(--ease-out);
}
.ind-card__teaser > span {
  display: block;
  overflow: hidden;
  padding-top: var(--s-2);
  font: var(--t-cap);
  color: rgba(255, 255, 255, .88);
  text-shadow: 0 1px 2px rgba(2, 45, 90, .28);
}
/* SPEC-04 §5.4's prescribed remedy: at 1024-1179 the 304px card wraps four of
   the names to two lines, so the expanded overlay block runs tall. */
@media (min-width: 1024px) and (max-width: 1179px) {
  .ind-card__teaser > span { font-size: 12px; }
}

/* card 12 — the CTA */
.ind-card--cta { background: var(--navy); }
.ind-card__scrim--flat {
  background: linear-gradient(180deg, rgba(1, 30, 61, .30) 0%, rgba(1, 30, 61, .52) 100%);
}
.ind-cta {
  position: absolute;
  z-index: var(--z-glass);
  left: var(--s-4);
  right: var(--s-4);
  bottom: var(--s-4);
  padding: var(--s-5);
  /* SPEC-00 §2.4's nesting rule governs concentric radii; this panel is inset
     on three sides and floats, so --r-sm is the correct exception here. */
  border-radius: var(--r-sm);
  /* REFINE-01 I4: this panel floats over ind-cta.jpg, a near-flat dark navy
     frame, so an 18px blur bought nothing visually and cost one of SPEC-00
     §8.6's three backdrop-filter slots. Opaque white at .94 is visually
     indistinguishable here, and with the hero pill deleted (H1) the document
     is at exactly three. SPEC-01-R2 swapped the hero's slot from .hero__card
     to .hero__rail, so the three are now .site-header.is-stuck, .hero__rail
     and .pf__panel. */
  -webkit-backdrop-filter: none;
          backdrop-filter: none;
  background: rgba(255, 255, 255, .94);
}
.ind-cta__title {
  display: block;
  font: var(--t-d4);
  letter-spacing: -.010em;
  color: var(--navy);
}
.ind-cta__link {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  margin-top: var(--s-3);
  font-family: var(--font-ui);
  font-weight: 600;
  font-size: 15px;
  color: var(--navy);
  transition: color var(--dur-1) var(--ease-out);
}
.ind-cta__link svg {
  width: 16px;
  height: 16px;
  flex: 0 0 auto;
  transition: transform var(--dur-1) var(--ease-out);
}

/* :focus-visible carries the identical state to :hover on every rule — the two
   selectors are never separated, or the teaser becomes pointer-only. */
@media (hover: hover) and (pointer: fine) {
  .ind-card__teaser { grid-template-rows: 0fr; opacity: 0; }

  /* .ind__set is carried only to out-specify `.js .r.is-in { transform: none }`,
     which would otherwise cancel the card lift on every revealed card */
  .ind__set .ind-card:hover,
  .ind__set .ind-card:focus-visible { transform: translateY(-3px); box-shadow: var(--sh-3); }
  .ind-card:hover .ind-card__img,
  .ind-card:focus-visible .ind-card__img { transform: scale(1.045); }
  .ind-card:hover .ind-card__teaser,
  .ind-card:focus-visible .ind-card__teaser { grid-template-rows: 1fr; opacity: 1; }
  .ind-card:hover .ind-card__scrim,
  .ind-card:focus-visible .ind-card__scrim { opacity: 1; background-position: 0 -12%; }

  .ind-card--cta:hover .ind-cta__link,
  .ind-card--cta:focus-visible .ind-cta__link { color: var(--navy-500); }
  .ind-card--cta:hover .ind-cta__link svg,
  .ind-card--cta:focus-visible .ind-cta__link svg { transform: translateX(4px); }
  /* the CTA card does not zoom its photo: the glass panel would shear against it */
  .ind-card--cta:hover .ind-card__img,
  .ind-card--cta:focus-visible .ind-card__img { transform: none; }
}

/* --- no-JS: .is-in never lands, so the drawn elements would keep their
   initial states and simply disappear (SPEC-03 §7.6). --- */
html:not(.js) .rail__list::before { transform: none; }
html:not(.js) .rail__item::before { transform: scale(1); opacity: 1; }

/* =========================================================================
   PROOF BAND  (SPEC-05)
   Full-bleed --navy-900 with contained content, the hero mirrored:
   photograph left, copy right. One .glass--navy panel overhangs the frame's
   inner edge carrying the page's second and last count-up.
   No scrim, no sticky, no loop, no primary CTA, no box-shadow.

   CLIENT REVISION: §2.2 specified a flat ground and the client read it as
   plain. The band now carries the decorative pair the CTA band carries, and
   nothing else changed: an inverted grid tile masked to the top edge, and one
   hairline arc. Both only darken or hairline the ground, so every text pair
   in the section keeps the ratio §2.2 measured it at.

   REFINE-01 P2(b): the arc was --gold-200 and is now white at .16. The gold
   ledger was measured at 17 instances against SPEC-00 §3.5's ceiling of 14;
   this arc and the CTA band's twin were the two cheapest to bank, because
   neither was ever carrying meaning | they are ground, not accent. 17 -> 15.
   SPEC-09-R2 has since deleted the CTA band's arc outright, so this is the
   only one left on the page.
   ========================================================================= */
.pf {
  position: relative;
  isolation: isolate;
  overflow: clip;                    /* the arc overhangs the section edge */
  padding-block: var(--section-y);
  background-color: var(--navy-900); /* SPEC-05 §2.2 ladder row 3.5 */
  /* a vignette, not a bloom: every stop is --navy-900 at rising alpha over a
     --navy-900 ground, so the layer can only darken. SPEC-00 §5.3 item 8 bans
     a radial-gradient used as a halo BEHIND a button, icon or heading; this
     one sits at the section's rim and pulls the corners down. */
  background-image:
    radial-gradient(112% 74% at 50% 42%,
      rgba(1, 30, 61, 0) 0%, rgba(1, 30, 61, .32) 66%, rgba(1, 30, 61, .62) 100%);
  color: #fff;
}
.pf h2 { color: #fff; }

/* the inverted grid, masked to a shallow band across the top edge. SPEC-00
   §3.3 rule 5: a pattern never sits behind body copy, so the falloff is fully
   transparent well above .pf__lead. Rule 4: one tile only, and the arc below
   is a single inline <svg>, which rule 7 counts separately. */
.pf__pattern {
  position: absolute;
  inset: 0;
  z-index: var(--z-pattern);
  pointer-events: none;
  background-image: var(--pat-grid-inv);
  background-position: center top;
  /* REFINE-01 P3: at --pat-h 14% the mask showed only the vertical strokes of
     the 48px tile, so the layer read as faint banding rather than as a grid.
     26%, lifted 4% above the edge, shows the tile's corners. Still fully
     transparent well above .pf__lead, so SPEC-00 §3.3 rule 5 holds. */
  --pat-x: 50%; --pat-y: -4%; --pat-w: 130%; --pat-h: 26%;
  -webkit-mask-image: radial-gradient(var(--pat-w) var(--pat-h) at var(--pat-x) var(--pat-y),
                        #000 0%, #000 42%, transparent 78%);
          mask-image: radial-gradient(var(--pat-w) var(--pat-h) at var(--pat-x) var(--pat-y),
                        #000 0%, #000 42%, transparent 78%);
}

/* One hairline arc. No fill, no shadow, no blur: a drawn line, not a glow. It
   rests fully drawn so a no-JS reader sees it; .r on the element supplies the
   reveal.
   REFINE-01 P2(b): it was a 900px circle at left:-560px, so only a short
   tangent entered the band and it read as a stray scratch across the
   bottom-left corner. Reframed to 760px at -420px, a full quarter-sweep is
   inside the band. And the stroke went from --gold-200 to white, which is what
   banks the instance (see the section note above). */
.pf__arc {
  position: absolute;
  left: -420px;
  top: calc(50% - 380px);
  width: 760px;
  height: 760px;
  max-width: none;                   /* the reset's svg{max-width:100%} would crush it */
  z-index: var(--z-pattern);
  pointer-events: none;
  overflow: visible;
}
/* the alpha lives on the stroke, not on the element: .r owns element opacity.
   .16 puts the arc in the same white-alpha family as .pf__frame's outline (.10)
   and .pf__results' rules (.16), which is exactly the register it should sit
   in now that it is ground rather than accent. */
.pf__arc circle {
  fill: none;
  stroke: #fff;
  stroke-opacity: .16;
  /* the box scales the 400-unit viewBox to 760px, so .7 renders as ~1.3 CSS
     px. Anything authored at 1.5 here paints a 2.9px band. */
  stroke-width: .7;
  stroke-linecap: round;
  stroke-dasharray: var(--len);
  stroke-dashoffset: 0;
}
.js .pf__arc circle { stroke-dashoffset: var(--len); }
.js .pf__arc.is-in circle { animation: draw 1400ms var(--ease-soft) 120ms both; }
/* SPEC-00 §7.5's gold ring, applied by section root instead of by .on-dark.
   A focus ring is not a decorative gold instance (SPEC-05 §5.1). */
.pf :focus-visible { outline-color: var(--gold); }

.pf__grid { display: grid; gap: var(--s-8); }   /* ≤560 → 40 */

/* --- copy column (SPEC-05 §5.3) --- */
.pf__copy { position: relative; z-index: var(--z-content); }
.eyebrow--on-dark { color: #fff; }

.pf__h {
  margin-top: var(--s-4);
  font: var(--t-d2);
  letter-spacing: -.022em;
  max-width: 20ch;
}
.pf__lead {
  margin-top: var(--s-5);
  font: var(--t-lead);
  color: rgba(255, 255, 255, .78);   /* ≈11.0:1 on --navy-900 */
  max-width: 46ch;
}
/* hairline-ruled, not bulleted and not iconed: the same 1px device the hero's
   stats row uses, rotated. Three checkmarks would read as a pricing page.
   REFINE-01 P4: three 16px/600 white rows between white-16% rules read as a
   table, so each row opens on the page's own tick device in the ghost form
   .pf__panel-label already uses eight inches away. A 14px white rule, not a
   glyph and not gold: zero ledger cost, and still not a checkmark. */
.pf__results { margin-top: var(--s-7); max-width: 42ch; }
.pf__results li {
  display: flex;
  align-items: baseline;
  gap: var(--s-4);
  padding-block: var(--s-4);
  border-top: 1px solid rgba(255, 255, 255, .16);
  font-family: var(--font-ui);
  font-weight: 600;                  /* claims: second-heaviest after the H2 */
  font-size: 16px;
  line-height: 1.45;
  color: #fff;
}
.pf__results li::before {
  content: "";
  flex: 0 0 auto;
  width: 14px;
  height: 1px;
  background: rgba(255, 255, 255, .5);
  transform: translateY(-5px);       /* off the baseline, onto the x-height */
}
.pf__results li:last-child { border-bottom: 1px solid rgba(255, 255, 255, .16); }

.link-arrow--on-dark { margin-top: var(--s-7); color: #fff; }
/* no colour change on hover: --navy-500 means nothing on white, and the only
   "brighter than white" available is gold (SPEC-05 §5.3 note 3). */
.link-arrow--on-dark:hover {
  color: #fff;
  text-decoration: underline;
  text-underline-offset: 4px;
  text-decoration-thickness: 1.5px;
}

/* --- media frame (SPEC-05 §5.4) --- */
.pf__media { position: relative; }
.pf__frame {
  position: relative;
  z-index: var(--z-media);
  aspect-ratio: 4 / 5;               /* ≤560 */
  border-radius: var(--r-lg);        /* NOT --r-xl: the hero's 28px frame is the page's one */
  overflow: hidden;
  /* no box-shadow: SPEC-00 §5.1's scale is navy-tinted and renders as nothing
     on --navy-900. A 1px white-alpha outline does the separation instead. */
  outline: 1px solid rgba(255, 255, 255, .10);
  outline-offset: -1px;
}
/* the <picture> wrapper is a block of auto height, so a percentage height on
   the <img> would resolve to auto and object-fit would never govern the crop.
   Pinning the wrapper to the frame is what makes object-position real. */
.pf__frame picture { position: absolute; inset: 0; }
.pf__img { width: 100%; height: 100%; object-fit: cover; object-position: 50% 40%; }

/* --- the glass panel (SPEC-05 §5.5) --- */
.pf__panel {
  position: absolute;
  z-index: var(--z-glass);
  /* ≤560 — pinned inside the frame */
  left: var(--s-3); right: var(--s-3); bottom: var(--s-3);
  padding: var(--s-5);
  /* --r-md, not concentric with the frame's 20px: the panel floats over the
     frame's edge rather than nesting inside it (SPEC-00 §2.4 exception) */
  border-radius: var(--r-md);
}
/* a denser .glass--navy for the one place on the page where the number the
   whole section exists to deliver sits on glass over an uncontrolled photo */
.glass--dense { background: rgba(2, 45, 90, .66); }
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .glass--dense { background: rgba(2, 45, 90, .93); }
}

.pf__panel-label {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  font: var(--t-eyebrow);
  font-size: 11px;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: #fff;
}
/* a white bar at the .tick's geometry — keeps the hero card's label device
   without spending a gold instance. 14px matches the card tick, not the 18px
   section eyebrow tick. */
.tick--ghost { width: 14px; background: rgba(255, 255, 255, .55); }
.pf__panel-num {
  margin-top: var(--s-3);
  font: var(--t-num);
  font-variant-numeric: tabular-nums;
  letter-spacing: -.032em;
  color: #fff;
}
.pf__panel-unit { margin-top: var(--s-1); font: var(--t-cap); color: rgba(255, 255, 255, .82); }
.pf__panel-client {
  margin-top: var(--s-4);
  padding-top: var(--s-4);
  border-top: 1px solid rgba(255, 255, 255, .18);
  font: var(--t-label);
  color: #fff;
}

/* --- 561px and up: stacked, wide media, panel inside the frame --- */
@media (min-width: 561px) {
  .pf__grid  { gap: var(--s-9); }
  .pf__copy  { max-width: 620px; }
  .pf__frame { aspect-ratio: 16 / 10; }
  .pf__panel { left: auto; right: var(--s-6); bottom: var(--s-6); width: 268px; }
}

/* --- 1024px and up: two columns, media placed first via grid-column so DOM
       order stays copy → media (SPEC-00 §8.2) --- */
@media (min-width: 1024px) {
  .pf__grid {
    grid-template-columns: minmax(0, .9fr) minmax(0, 1fr);
    column-gap: var(--s-9);
    align-items: center;
  }
  .pf__copy  { grid-column: 2; grid-row: 1; max-width: none; }
  .pf__media { grid-column: 1; grid-row: 1; }
  .pf__frame { aspect-ratio: 5 / 6; }
  /* overhangs into the 48px column gap by 24 — never past .container */
  .pf__panel { right: -24px; bottom: var(--s-8); width: 244px; }
}

/* --- 1180px and up (re-measured for --container: 1360) ---------------------
   SPEC-05 §5.2's 496 + 64 + 556 was a 1116px measure. At 1296 the same track
   list hands the copy column 736px while .pf__results is capped at 42ch, so
   three 380px rules float in the middle of a 736px column with 350px of dead
   navy to their right: the "stranded" the client saw.

   The fix is to stop the copy column growing and give the width to the photo.
   The copy caps at 600px, the media takes the remainder (up to 632px), and
   the frame squares up so a 632px-wide card is not 790px tall. The three
   result rules widen with the column they sit in, to 46ch, so they resolve
   against its edge instead of stopping two thirds across. */
@media (min-width: 1180px) {
  .pf__grid  { grid-template-columns: minmax(0, 1fr) minmax(0, 600px); column-gap: var(--s-10); }
  .pf__lead  { max-width: 52ch; }
  /* the copy column is capped at 600px now, so the three rules resolve on its
     edge instead of stopping 46ch across it. A rule that ends where the column
     ends is the device; a rule that ends two thirds across is the stranding
     the client saw. */
  .pf__results { max-width: none; }
  .pf__frame { aspect-ratio: 1 / 1; }
  /* REFINE-01 P1: at 50% 40% in a 632px square frame the single figure was
     jammed against the left edge with roughly 55% of the frame given to blank
     beige wall. At 30% the figure moves onto the left third, the window and
     skyline carry the frame, and .pf__panel still lands on calm wall at
     bottom-right, which is what SPEC-00 §4.3 asks of glass over photography. */
  .pf__img  { object-position: 30% 42%; }
  .pf__panel { right: -32px; bottom: var(--s-9); width: 268px; }
}

/* =========================================================================
   TESTIMONIALS  (SPEC-06, rebuilt to SPEC-06-R2, re-cut to SPEC-06-R3)
   All four quotes visible at once on flat --navy-050: a 2x2 grid of four
   equal paper cards, each one closing on the client's own photograph.

   WHAT THIS REPLACES, and why the deletion is the point. SPEC-06 shipped four
   stacked quotes beside an index; a client revision turned that index into a
   tablist and the stack into a one-quote stage. Measured at 1440 the stage
   cell was 896px holding one 650px quote while the 320px aside ran ~560px
   against a ~380px stage: roughly 40% of the section was empty wash and three
   of four proofs sat behind a click. Gone with the stage: the rail, the
   01/04 counter and its progress fill, the is-on state machine, the
   role="tablist" promotion, 60-odd lines of JavaScript, and the --gold-600
   marker that existed only to say which tab was open.

   The section is now zero JavaScript. The no-JS page and the JS page are the
   same page, there is no tab order to manage, and the four ids still resolve.

   WHAT R3 REMOVES, and why. R2's navy lead card was five tracks wide, sticky
   from 1024, and set a 26px pull quote against three paper cards at 19px. The
   client read the result as bulky, and it was: a navy block that size is a
   second hero, the 26px setting made one of four clients louder than the rest
   for no reason present in the evidence, and the wall had "no avatars, no
   logos" written into it as a principle when the people who said these things
   were the strongest thing on offer. Gone with the navy card: the sticky
   position and its twelve-track re-cut, the 5/7 split, the 26px
   --t-quote-lg setting, the white-on-navy cite colours, the .tqc__tag
   industry eyebrow above each quote, and the second of the page's two
   parallax slots. In: the four portraits, at 48px, from the client's own
   site.

   Still no pattern, no glass, no gradient, no stars, no dots: nothing here
   moves except the existing .r reveal.

   GOLD LEDGER, net negative. Out: the --gold-600 rail marker (R2), and the
   58x40 mark on the navy card. In: one 26x18 quotation mark on .tqc--accent.
   With the eyebrow .tick that is two gold elements in the band, which is the
   ceiling, and neither is text.

   .tq / .tq__text / .tq__cite / .tq__name / .tq__role are LEFT INTACT below.
   Nothing renders them any more (case-study-next.php now prints .tqc like
   everything else) and they are not this pass's to delete.
   ========================================================================= */
.tst {
  position: relative;
  isolation: isolate;
  padding-block: var(--section-y);
  background: var(--navy-050);       /* SPEC-05 §2.2 ladder row 4 | no pattern */
}

/* --- head (SPEC-06-R2 §2.1) --- */
.tst__head-in { position: relative; z-index: var(--z-content); }
.tst__h {
  margin-top: var(--s-4);
  font: var(--t-d2);
  letter-spacing: -.022em;
  color: var(--ink);
  max-width: 16ch;
}
.tst__intro {
  margin-top: var(--s-4);
  font: var(--t-body-sm);
  color: var(--ink-3);
  max-width: 38ch;
}

/* --- the wall (SPEC-06-R3 §2.2) ---
   A 2x2 grid of equal cards, stacked below 768. align-items: stretch plus a
   column flex card with the attribution on margin-top:auto is what levels the
   two cards in a row: the shorter quote's card fills to the taller one and
   both attributions land on the same line, with no measured min-height to go
   stale when a quote is edited. The longest quote sets its row; nothing here
   is sticky, because no column is short any more. */
.tst__wall {
  display: grid;
  align-items: stretch;
  gap: var(--s-4);
  margin-top: var(--s-7);
}

.tqc {
  position: relative;
  display: flex;
  flex-direction: column;
  padding: var(--s-6) var(--s-5);
  border-radius: var(--r-lg);
  background: var(--paper);
  border: 1px solid var(--navy-100);
  box-shadow: var(--sh-1);
}
/* no hover response of any kind: nothing in this section is clickable and a
   lift would be a false affordance. */

/* .tqc__mark is aria-hidden and is authored first in the DOM, which is also
   where it belongs visually, so it needs no `order`. 26x18 rather than R2's
   34x24 on the paper cards and 58x40 on the navy one: at this size it reads
   as punctuation opening the quote, not as an ornament above it. */
.tqc__mark {
  display: block;
  width: 26px;
  height: 18px;
  margin-bottom: var(--s-4);
  fill: currentColor;
  color: var(--gold);
}

/* GOLD LEDGER for the band, and the whole of it: the eyebrow .tick, and this
   one quotation mark. Two marks, no third. The R2 navy-050 tint that was to
   break the grid is NOT available here | .tst's own ground is --navy-050
   (SPEC-05 §2.2 ladder row 4), so a navy-050 card would simply vanish. The
   card that breaks the four is the one that opens in gold instead, which
   costs no fill and no border weight. On --paper the mark is ~1.35:1 and
   would read as a smudge (SPEC-00 §3.5); it is only ever used on a card that
   carries the quote beneath it at full ink contrast, where it is punctuation
   and not text. */
.tqc--accent .tqc__mark { color: var(--gold); }

/* Mulish, not a display face: 459 characters of tracked Poppins is not a
   reading setting. 18px, not R2's 26px pull quote | no client here said
   anything that earns being twice the size of what the other three said. */
.tqc__text { margin: 0 0 var(--s-6); }
.tqc__text p {
  font: 400 clamp(17px, .14vw + 16px, 18px)/1.62 var(--font-ui);
  letter-spacing: -.002em;
  color: var(--ink);
}

/* the attribution row: portrait, then name over role, then the company mark
   pushed to the right edge. It is the reason this pass exists | the four
   people who said these things were not on the page at all. */
.tqc__cite {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  margin-top: auto;                  /* the row-levelling device, see above */
  padding-top: var(--s-5);
  border-top: 1px solid var(--navy-100);
}
.tqc__face { flex: 0 0 auto; display: block; }

/* the client's own photograph, centre-cropped square and shipped at 96 and
   192 so the 2x density is a srcset entry and not a second request. The 1px
   ring is a border drawn as a shadow so it cannot alter the 48px box. */
.tqc__avatar {
  display: block;
  width: 48px;
  height: 48px;
  border-radius: 50%;
  object-fit: cover;
  background: var(--navy-050);
  box-shadow: 0 0 0 1px rgba(2, 45, 90, .10);
}
/* stand-in for a sitter with no portrait: initials on navy, never a grey
   silhouette and never a generated face. */
.tqc__mono {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 48px;
  height: 48px;
  border-radius: 50%;
  background: var(--navy);
  color: #fff;
  font: 600 15px/1 var(--font-display);
  letter-spacing: .02em;
}
.tqc__who { flex: 1 1 auto; min-width: 0; }
.tqc__name {
  display: block;
  font: 600 15px/1.3 var(--font-display);
  letter-spacing: -.008em;
  color: var(--navy);
}
.tqc__role { display: block; margin-top: 2px; font: var(--t-cap); color: var(--ink-3); }
.tqc__brand { flex: 0 0 auto; margin-left: auto; padding-left: var(--s-3); }
.tqc__logo { display: block; height: 24px; width: auto; }

/* landed-here confirmation for a shared #tm-carter URL. Colour only, never
   geometry: anything that changes padding or border-width shifts the text at
   the exact moment the reader's eye lands on it. A 0-blur, 2px ring is the
   §7.6 field-ring device, not a shadow, so SPEC-00 §5.3 item 4 is not in
   play. */
.tqc:target { box-shadow: var(--sh-1), 0 0 0 2px var(--navy); }

@media (min-width: 768px) {
  /* two columns, and that is the whole layout. R2 cut this wall six ways at
     561 and twelve ways at 1024 to seat a sticky navy card; with four equal
     cards there is nothing to seat. */
  .tst__wall { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s-5); }
  .tqc { padding: var(--s-7) var(--s-6); }
}

@media (min-width: 1024px) {
  .tst__head {
    display: grid;
    grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
    column-gap: var(--s-9);
    align-items: end;
  }
  /* 6px so the intro's last baseline lands on the h2's, not its box edge.
     Optical alignment, SPEC-00 §2.1's stated exception. */
  .tst__intro { align-self: end; margin-top: 0; padding-bottom: 6px; }

  /* SHORT-COLUMN STICKY, WITHDRAWN. R2 pinned the navy lead card here and
     spent a twelve-track re-cut of the wall to give it 382px of travel. The
     card is gone and all four columns are now the same height, so there is no
     short column to stick and nothing to pin | the site-wide rule's own
     precondition is not met. The --par-1 parallax the lead gave up to stick
     stays given up: the band carries no parallax element at all. */
  .tst__wall { gap: var(--s-6); margin-top: var(--s-8); }
  .tqc { padding: var(--s-7); }
}

@media (min-width: 1180px) {
  /* two columns over a 24px gap at the 1296 measure is 636px a card, about
     568px of inner measure at 18px Mulish, roughly 66ch. Inside SPEC-00
     §1.3's 78ch ceiling with room to spare, and well clear of the 45ch floor
     that would make the longest of the four quotes a column of fragments. */
  .tqc { padding: var(--s-8) var(--s-7); }
}

/* --- .tq: the single-quote block, used ONLY by case-study-next.php ---------
   Kept verbatim. It is not rendered on the homepage any more, and the .tst
   overrides that used to sit here went with the stage. */
.tq { position: relative; max-width: 62ch; }
.tq + .tq { margin-top: var(--s-9); }

.tq__mark {
  display: block;
  margin-bottom: var(--s-3);
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 64px;
  line-height: .62;
  color: var(--navy-100);
  transition: color var(--dur-2) var(--ease-out);
}
.tq__text p { font: var(--t-lead); color: var(--ink); }
.tq__cite {
  margin-top: var(--s-6);
  padding-top: var(--s-5);
  border-top: 1px solid var(--navy-100);
  transition: border-color var(--dur-2) var(--ease-out);
}
.tq__name { display: block; font: var(--t-d4); letter-spacing: -.010em; color: var(--navy); }
.tq__role { display: block; margin-top: var(--s-1); font: var(--t-cap); color: var(--ink-3); }

.tq:target .tq__mark { color: var(--navy-300); }
.tq:target .tq__cite { border-top-color: var(--navy); }

@media (min-width: 561px) { .tq + .tq { margin-top: var(--s-10); } }

/* =========================================================================
   INSIGHTS  (SPEC-07)
   Two grammars now live here, and the split is deliberate.

   .ins-card is SPEC-07 §2 unchanged: typographic entries under 1px navy
   rules, no image, because the generator bakes the post's own headline, a
   contact bar and vendor brand colours into the covers and no text-free crop
   exists. templates/insights-archive.php (up to 22 pages of it),
   templates/post-next.php and assets/pages.css all render it. Do not touch it.

   .ins__journal is the homepage's three-item teaser, rebuilt to SPEC-07-R2.
   It renders NO post cover. The client asked for a picture because the
   section read as bare, and it still gets one, larger than any cover ever
   occupied: one art-directed photograph from the ph/ library beside three
   numbered rows under 1px rules. Taking the picture out of the per-post slot
   is what lets both findings stand at once, and it takes the hacks the
   covers forced with it: the height:116% top-crop, the 11/5 frame, the
   three-way sizes matrix, the 220px thumbnail sized so baked type would
   clear 6px, the branded navy fallback tile, and the n===3 composed-set
   branch. Roughly 270 lines out, 130 in.
   Still zero glass and zero pattern in this section.
   ========================================================================= */
.ins {
  position: relative;
  isolation: isolate;
  padding-block: var(--section-y);
  background: var(--paper);          /* SPEC-00 §3.2 row 5 — no pattern */
}

.ins__set { margin-top: var(--s-9); }          /* ≤560 → 48 */


.ins-card {
  position: relative;
  padding-block: var(--s-6);
  border-top: 1px solid var(--navy);
}
.ins-card:last-child { border-bottom: 1px solid var(--navy); }

.ins-card__date { margin-bottom: var(--s-3); font: var(--t-cap); color: var(--ink-3); }
.ins-card__title {
  font: var(--t-d4);
  letter-spacing: -.010em;
  overflow-wrap: anywhere;           /* SPEC-00 §1.3 — CMS strings */
}
.ins-card__link { color: var(--navy); transition: color var(--dur-1) var(--ease-out); }

/* whole-card target: one link in the DOM, one accessible name. The date sits
   outside the anchor so the card never announces its own date. */
.ins-card__link::after { content: ""; position: absolute; inset: 0; }

/* the rule thickens under the hovered card: a 2px navy bar drawn over the 1px
   one. No lift, no shadow, no background — these are ruled entries, not cards. */
.ins-card::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: -1px;
  height: 2px;
  background: var(--navy);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform var(--dur-2) var(--ease-out);
}
.ins-card:hover::before,
.ins-card:focus-within::before { transform: scaleX(1); }
.ins-card:hover .ins-card__link,
.ins-card:focus-within .ins-card__link {
  color: var(--navy-500);
  text-decoration: underline;
  text-underline-offset: 4px;
  text-decoration-thickness: 1.5px;
}

/* the ring belongs to the whole card. Without :has() the link keeps its own
   default ring — degraded, never absent. --r-xs because there is no card
   shape here for the ring to trace. */
@supports selector(:has(*)) {
  .ins-card:has(.ins-card__link:focus-visible) {
    outline: 2px solid var(--navy);
    outline-offset: 6px;
    border-radius: var(--r-xs);
  }
  .ins-card .ins-card__link:focus-visible { outline: none; }
}

/* --- 561-1023: rows, date in a left column --- */
@media (min-width: 561px) {
  .ins__set { margin-top: var(--s-10); }
  .ins-card {
    display: grid;
    grid-template-columns: 108px minmax(0, 1fr);
    column-gap: var(--s-6);
    align-items: baseline;
  }
}
@media (min-width: 561px) and (max-width: 1023px) {
  .ins-card__date  { margin-bottom: 0; }       /* it is a column, not a stack */
  /* 19px in a 588px measure reads as a caption, so it steps up the scale */
  .ins-card__title { font: var(--t-d3); letter-spacing: -.016em; }
}

/* --- 1024px and up: columns, the rule becomes a column head. align-items:
       start lets the columns run ragged from a shared top rule — that
       raggedness is the design; no min-height, no clamp, no ellipsis. --- */
@media (min-width: 1024px) {
  .ins__set {
    display: grid;
    grid-template-columns: repeat(var(--n, 3), minmax(0, 1fr));
    column-gap: var(--s-9);
    align-items: start;
  }
  .ins-card { display: block; padding-block: var(--s-6) 0; }
  .ins-card:last-child { border-bottom: 0; }
}

/* --- .ins__journal: the homepage teaser (SPEC-07-R2) ---------------------- */
.ins__journal { margin-top: var(--s-9); }

/* --- the art column --- */
.ins__art-rule {
  display: block;
  width: 32px;
  height: 3px;
  border-radius: 1.5px;
  margin-top: var(--s-7);
  /* the section's second and last gold instance. --gold-600, not --gold:
     #FFCE1B on --paper is 1.5:1 and SPEC-00 §3.4 forbids it outright. This is
     a 3px rule, never text, so §3.5's "accent bar" allowance applies. It
     replaces the deleted .ins-tile__fb-rule, so the section's gold count is
     unchanged at two. */
  background: var(--gold-600);
}
.ins__art-note {
  margin-top: var(--s-5);
  font: var(--t-body-sm);
  color: var(--ink-3);
  max-width: 34ch;
}
.ins__art .btn { margin-top: var(--s-6); }

/* --- the rows. Ruled entries, not cards: nothing lifts, nothing scales, no
       shadow appears anywhere in this column. --- */
.ins__rows { margin-top: var(--s-8); }

.ins-row {
  position: relative;
  display: grid;
  grid-template-columns: 36px minmax(0, 1fr);
  column-gap: var(--s-4);
  align-items: start;
  padding-block: var(--s-6);
  border-top: 1px solid var(--line);
}
.ins-row:last-child { border-bottom: 1px solid var(--line); }

/* the 2px navy bar drawn over the row's own 1px rule. Identical geometry and
   timing to .ins-card::before, so the homepage rows and the archive rows
   animate the same way. */
.ins-row::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: -1px;
  height: 2px;
  background: var(--navy);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform var(--dur-2) var(--ease-out);
}
.ins-row:hover::before,
.ins-row:focus-within::before { transform: scaleX(1); }

.ins-row__num {
  font: var(--t-num-sm);
  font-size: 20px;
  font-variant-numeric: tabular-nums;
  color: var(--navy-300);            /* aria-hidden decoration, carries nothing */
  transition: color var(--dur-2) var(--ease-out);
}
.ins-row:hover .ins-row__num,
.ins-row:focus-within .ins-row__num { color: var(--navy); }

.ins-row__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-2) var(--s-3);
  font: var(--t-cap);
  color: var(--ink-3);
}
/* a label, not a pill: a pill in a ruled row reads as UI, not as editorial */
.ins-row__cat {
  font: var(--t-micro);
  text-transform: uppercase;
  letter-spacing: .12em;
  color: var(--navy);
}
.ins-row__sep {
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: var(--navy-300);
}

.ins-row__title {
  margin-top: var(--s-3);
  /* SPEC-07-R2 §4: --t-d3 tops out at 26px, which does not read as a
     headline-led list across a 612px measure, and --t-d2 would fight the h2
     directly above. This tops out at 30px at 1360 and resolves to --t-d3's
     own value at 768, so the new step only exists on wide screens. */
  font: 600 clamp(22px, .9vw + 16px, 30px)/1.22 var(--font-display);
  letter-spacing: -.018em;
  overflow-wrap: anywhere;           /* SPEC-00 §1.3 | CMS strings */
}
.ins-row__link { color: var(--navy); transition: color var(--dur-1) var(--ease-out); }
/* whole-row target: one link in the DOM, one tab stop, one accessible name */
.ins-row__link::after { content: ""; position: absolute; inset: 0; }
.ins-row:hover .ins-row__link,
.ins-row:focus-within .ins-row__link {
  color: var(--navy-500);
  text-decoration: underline;
  text-underline-offset: 4px;
  text-decoration-thickness: 1.5px;
}

/* the chip is hidden below 768: the whole row is already the target and it is
   180px tall there. It never moves and the glyph never slides, because a
   glyph translating inside a fixed circle reads as a misalignment. */
.ins-row__chip { display: none; }

/* the ring belongs to the whole row. Without :has() the link keeps its own
   default ring | degraded, never absent. */
@supports selector(:has(*)) {
  .ins-row:has(.ins-row__link:focus-visible) {
    outline: 2px solid var(--navy);
    outline-offset: 6px;
    border-radius: var(--r-xs);
  }
  .ins-row .ins-row__link:focus-visible { outline: none; }
}

@media (min-width: 768px) {
  .ins__journal { margin-top: var(--s-9); }
  .ins__rows { margin-top: var(--s-9); }

  .ins-row {
    grid-template-columns: 60px minmax(0, 1fr) 44px;
    column-gap: var(--s-5);
    align-items: center;
    padding-block: var(--s-7);
  }
  .ins-row__chip {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    border-radius: 50%;
    border: 1px solid var(--line);
    color: var(--navy);
    transition: background-color var(--dur-2) var(--ease-out),
                border-color var(--dur-2) var(--ease-out),
                color var(--dur-2) var(--ease-out);
  }
  .ins-row__chip svg { width: 16px; height: 16px; }
  .ins-row:hover .ins-row__chip,
  .ins-row:focus-within .ins-row__chip {
    background: var(--navy);
    border-color: var(--navy);
    color: #fff;
  }
}

@media (min-width: 1024px) {
  .ins__journal {
    display: grid;
    grid-template-columns: minmax(0, 368px) minmax(0, 1fr);
    column-gap: var(--s-9);
    align-items: start;
    margin-top: var(--s-10);
  }
  .ins__rows { margin-top: 0; }
  .ins-row {
    grid-template-columns: 64px minmax(0, 1fr) 44px;
    column-gap: var(--s-6);
  }
  .ins-row__num { font: var(--t-num-sm); font-variant-numeric: tabular-nums; }
}

@media (min-width: 1180px) {
  .ins__journal { grid-template-columns: minmax(0, 440px) minmax(0, 1fr); column-gap: var(--s-11); }
  .ins-row { grid-template-columns: 72px minmax(0, 1fr) 44px; }
}

/* =========================================================================
   EDITORIAL PHOTO FRAME  (.ph-set / .phf)
   SECOND COPY. The first is assets/pages.css (the "EDITORIAL PHOTOGRAPHY"
   block), which nine interior templates load. home-next.php loads next.css
   only, and enqueuing 2000+ lines of pages.css for one frame is not a trade
   worth making, so the recipe is duplicated here verbatim.
   IF YOU EDIT ONE, EDIT THE OTHER. The markup itself is not duplicated: it
   lives once in inc/photos.php, which is where the srcset, the width/height
   pair and the alt rule live, and that is the part that matters.
   ========================================================================= */
.ph-set { position: relative; }
/* ≤560 the frame is the full column and a 14px offset would sit under the
   gutter, so the outline starts at 561. */
@media (min-width: 561px) {
  .ph-set::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: var(--z-pattern);
    translate: 14px 14px;
    border: 1.5px solid rgba(2, 45, 90, .16);
    border-radius: var(--r-lg);
    pointer-events: none;
  }
  .ph-set--flip::before { translate: -14px 14px; }
}

.phf {
  position: relative;
  z-index: var(--z-media);
  display: block;
  overflow: hidden;
  aspect-ratio: 3 / 2;                /* the masters are 3:2; the uncropped cut */
  border-radius: var(--r-lg);
  background: var(--navy-050);        /* the ground while the photograph decodes */
  outline: 1px solid rgba(2, 45, 90, .08);
  outline-offset: -1px;
  box-shadow: var(--sh-3);
}
/* the <picture> is a block of auto height, so a percentage height on the
   <img> would resolve to auto and object-fit would never govern the crop.
   Pinning the wrapper to the frame is what makes --po real. */
.phf picture { position: absolute; inset: 0; display: block; }
.phf img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: var(--po, 50% 44%);   /* SPEC-00 §8.5: never the default crop */
}

/* the insights art column's own cut, and it exists only in this file */
.phf--ins { aspect-ratio: 3 / 2; border-radius: var(--r-md); }
@media (min-width: 561px) { .phf--ins { border-radius: var(--r-lg); } }
@media (min-width: 768px) { .phf--ins { aspect-ratio: 16 / 9; } }
@media (min-width: 1024px) { .phf--ins { aspect-ratio: 4 / 3; } }

/* =========================================================================
   CONTACT  (SPEC-08)
   One --wash section, two bands on one ground. Band A is a 5/7 split: copy
   plus a direct-dial rail on the left, the page's single elevated form card
   on the right. Band B is both offices, always visible, each a large Google
   embed converted to a navy duotone that blooms to full colour on :hover and
   on :focus-within. No tabs, no glass, no pattern, no dark band, and zero
   JavaScript: validation is native, the note is a <details>, the duotone is
   two composited CSS layers. One gold instance, the submit button.
   ========================================================================= */
.ct {
  position: relative;
  isolation: isolate;
  padding-block: var(--section-y);
  background: var(--wash);           /* SPEC-08 §5.1 — flat, no pattern */
}

/* --- shared: navy eyebrow tick (SPEC-08 §11.2) ---------------------------
   Every section from SPEC-02 to SPEC-07 opens on a gold .tick. This one
   opens navy because the gold ledger is at its ceiling and this section's
   instance belongs on the button that converts. SPEC-08-R2 keeps the eyebrow
   OUTSIDE the console card, on --wash, precisely so this survives unchanged
   and the section still spends exactly one gold instance. --navy on --wash is 12.6:1
   as a non-text element, against SPEC-00 §8.1's 3:1 floor. */
.tick--navy { background: var(--navy); }

/* --- shared: external link-arrow (SPEC-08 §11.1) --- */
.link-arrow--ext:hover svg { transform: translate(3px, -3px); }

/* =========================================================================
   FORM FIELDS  (shared component — SPEC-00 §7.6, defined at first use here)
   Labels are always visible; a placeholder is never the only label. Errors
   are text + colour + aria-describedby, never colour alone.
   ========================================================================= */
.field__label {
  display: block;
  margin-bottom: var(--s-2);
  font: var(--t-label);
  letter-spacing: .02em;
  color: var(--ink);
}
.field__input, .field__select, .field__textarea {
  width: 100%;
  min-height: 48px;
  padding: 13px 16px;
  font: 400 15px/1.5 var(--font-ui);
  color: var(--ink);
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  box-shadow: var(--sh-1);
  transition: border-color var(--dur-1) var(--ease-out),
              box-shadow   var(--dur-1) var(--ease-out);
}
.field__input::placeholder, .field__textarea::placeholder { color: var(--ink-3); }
.field__input:hover, .field__select:hover, .field__textarea:hover { border-color: var(--navy-300); }
.field__input:focus, .field__select:focus, .field__textarea:focus {
  outline: none;
  border-color: var(--navy);
  /* a ring, not a glow: 0 offset, 3px spread, navy alpha (SPEC-00 §7.6) */
  box-shadow: 0 0 0 3px rgba(2, 45, 90, .16);
}
.field__textarea { min-height: 132px; padding-top: 14px; resize: vertical; }
.field__hint { margin-top: var(--s-2); font: var(--t-cap); color: var(--ink-3); }

/* appearance:none removes the native arrow, so the select draws its own */
.field__select {
  appearance: none;
  -webkit-appearance: none;
  padding-right: 44px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='none' stroke='%23022D5A' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m4 6 4 4 4-4'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 16px center;
}
.field__select:invalid { color: var(--ink-3); }   /* "Choose one" reads as a placeholder */

/* --- band A: the ask (SPEC-08 §5.2) --- */
.ct__top { display: grid; gap: var(--s-8); }

.ct__h {
  margin-top: var(--s-3);
  font: var(--t-d2);
  letter-spacing: -.022em;
  max-width: 22ch;
}
.ct__lead {
  margin-top: var(--s-5);
  font: var(--t-lead);
  color: var(--ink-3);
  max-width: 46ch;
}

/* the direct-dial rail.
   REFINE-01 X3: it used to be a card | --paper, --r-md, --sh-1 | which made
   the left column read as a smaller copy of the form beside it, against
   SPEC-08's "exactly one elevated object, the form". A 2px navy rule and a
   16px indent say the same thing without competing: a ruled block, not a
   second card. */
.ct__direct {
  margin-top: var(--s-8);
  padding: var(--s-1) 0 var(--s-1) var(--s-6);
  border-left: 2px solid var(--navy);
}
.ct__direct-label { font: var(--t-label); letter-spacing: .02em; color: var(--ink); }
.ct__tel {
  display: inline-block;
  margin-top: var(--s-2);
  font: var(--t-d3);
  letter-spacing: -.016em;
  color: var(--navy);
  transition: color var(--dur-1) var(--ease-out);
}
.ct__tel:hover {
  color: var(--navy-500);
  text-decoration: underline;
  text-underline-offset: 4px;
  text-decoration-thickness: 1.5px;
}
/* display:flex, not .link-arrow's inline-flex: the tel above is inline-block,
   so an inline-level mail link sits beside it on the same line at ≥1024 and
   collapses SPEC-08 §5.2's two-row rail into one. width:fit-content keeps the
   44px hit area and the hover underline on the text, not the column. */
.ct__mail { display: flex; width: fit-content; margin-top: var(--s-2); }
.ct__direct-hours { margin-top: var(--s-3); font: var(--t-cap); color: var(--ink-3); }

/* the form card — the section's single elevated object. --sh-2 rather than
   --sh-1 is deliberate (SPEC-08 §5.2): alpha .09 against §5.3's .18 ceiling,
   12px blur against the 70px ceiling, negative spread. Not a glow. */
.ct__form {
  position: relative;                /* honeypot containment */
  padding: var(--s-6);
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-2);
}
.ct__req { font: var(--t-micro); letter-spacing: 0; color: var(--ink-3); }

.ct__fields { display: grid; gap: var(--s-5); margin-top: var(--s-5); }

/* native validation, zero JS (SPEC-08 §7.2). :user-invalid, not :invalid —
   :invalid would paint every required field red on first paint. Unsupported
   engines paint nothing and the native block-plus-message still runs, so no
   @supports guard is needed and none should be written. */
.ct__form :is(.field__input, .field__select):user-invalid {
  border-color: var(--danger);
  box-shadow: 0 0 0 3px rgba(179, 38, 30, .14);
}
.ct__form :is(.field__input, .field__select):user-invalid ~ .field__hint { color: var(--danger); }

/* Chrome autofill would otherwise repaint the field pale yellow. A 0-blur
   inset white fill, not a glow — exempt from SPEC-00 §5.3 by inspection. */
.ct__form input:-webkit-autofill,
.ct__form input:-webkit-autofill:hover,
.ct__form input:-webkit-autofill:focus {
  -webkit-text-fill-color: var(--ink);
  box-shadow: 0 0 0 1000px var(--paper) inset, var(--sh-1);
  transition: background-color 9999s ease-in-out 0s;
}

/* honeypot: off-screen, out of the tab order, out of the AX tree, still posts */
.ct__hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

/* the optional note — native <details>: keyboard-operable, announced as a
   disclosure, no ARIA, no JS, and its content still submits when closed. */
.ct__note { margin-top: var(--s-5); border-top: 1px solid var(--line-2); }
.ct__note-sum {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  min-height: 44px;
  font: 600 15px/1.2 var(--font-ui);
  color: var(--navy);
  cursor: pointer;
  list-style: none;
  transition: color var(--dur-1) var(--ease-out);
}
.ct__note-sum::-webkit-details-marker { display: none; }
.ct__note-sum:hover { color: var(--navy-500); }
.ct__note-chev {
  width: 16px; height: 16px;
  flex: 0 0 auto;
  transition: transform var(--dur-2) var(--ease-io);
}
.ct__note[open] .ct__note-chev { transform: rotate(180deg); }
.ct__note-body { padding-bottom: var(--s-2); }
.ct__note-body .field__label { margin-top: var(--s-2); }

.ct__submit {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--s-4) var(--s-5);
  margin-top: var(--s-6);
  padding-top: var(--s-6);
  border-top: 1px solid var(--line-2);
}
.ct__assure { flex: 1 1 200px; max-width: 34ch; font: var(--t-cap); color: var(--ink-3); }

/* --- the contact console (SPEC-08-R2 §3) ---------------------------------
   Homepage only, and every rule below is scoped to .ct__console so that
   templates/page-contact.php | which renders .ct__top, .ct__intro, .ct__direct
   and .ct__form unscoped, and shares the whole .loc__* family | is not caught
   by any of it.

   One card, two grounds: a navy rail at 5/12 and the form on paper at 7/12.
   The rail is a flex column whose photograph is flex:1 1 auto, which is the
   entire height-matching mechanism: the card can never have a short column and
   swapping copy can never leave a gap. Do not give the desktop photo a fixed
   aspect-ratio.

   ELEVATION: this is the section's only elevated object. --sh-3 is alpha .14
   against SPEC-00 §5.3's .18 ceiling, 30px blur against the 70px ceiling, and
   a negative spread. .off__plate below gets a border and no shadow at all.
   GOLD LEDGER: unchanged. The eyebrow stays outside the card on --wash so
   .tick--navy survives, and the section still spends exactly one gold
   instance, the submit button. The gold on the rail's links is a hover state,
   not a resting instance. */
.ct__head .ct__h    { max-width: 24ch; }
.ct__head .ct__lead { max-width: 52ch; }

.ct__console {
  display: grid;
  margin-top: var(--s-8);
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-3);
  overflow: hidden;
}

.ct__rail {
  display: flex;
  flex-direction: column;
  background: var(--navy);       /* flat: the CTA band below owns the grid */
  color: #fff;
}
.ct__photo {
  position: relative;
  margin: 0;
  aspect-ratio: 16 / 9;          /* ≤767 */
  overflow: hidden;
  background: var(--navy);
}
.ct__photo picture { display: block; height: 100%; }
.ct__photo img {
  width: 100%; height: 100%;
  object-fit: cover;
  object-position: var(--po, 50% 50%);
  filter: grayscale(.35) contrast(1.03);
}
/* multiply only darkens, so neither of these two layers can produce a glow */
.ct__photo::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: var(--navy);
  opacity: .22;
  mix-blend-mode: multiply;
}
.ct__photo-veil {
  position: absolute;
  inset: 0;
  z-index: var(--z-overlay);
  pointer-events: none;
  background: linear-gradient(to bottom,
    rgba(2, 45, 90, 0) 55%, rgba(2, 45, 90, .55) 82%, #022D5A 100%);
}

/* the rail's copy block. .ct__direct's navy left rule is a light-ground
   device and is invisible here, so it is retired inside the rail only. */
.ct__console .ct__direct {
  flex: 0 0 auto;
  margin-top: 0;
  padding: var(--s-6);
  border-left: 0;
}
.ct__rail .ct__direct-label { color: #fff; }
.ct__rail .ct__tel  { color: #fff; }
.ct__rail .ct__mail { color: #fff; }
.ct__rail .ct__tel:hover,
.ct__rail .ct__mail:hover { color: var(--gold-200); }
.ct__rail .ct__direct-hours {
  margin-top: var(--s-4);
  padding-top: var(--s-4);
  border-top: 1px solid rgba(255, 255, 255, .16);
  color: var(--navy-100);
}

/* the form is a pane of the card now, not a card of its own */
.ct__console .ct__form {
  padding: var(--s-6);
  background: var(--paper);
  border: 0;
  border-radius: 0;
  box-shadow: none;
}

/* --- band B: the offices (SPEC-08 §5.3) ---
   Separated from band A by space, a change of axis (5/7 above, 1/1 here) and
   a demotion of heading level. No rule, no divider, no ground change. */
.ct__loc { margin-top: var(--s-10); }
.ct__loc-h { font: var(--t-d3); letter-spacing: -.016em; }
/* REFINE-01 X1: band B opened on a bare <h3> dropped onto the same --wash
   ground 96px below the form, so the offices read as an appendix to it rather
   than as the section's second movement. The page's own opening device, in
   navy, gives it a head with no new text and no gold. */
.ct__loc-h::before {
  content: "";
  display: block;
  width: 18px;
  height: 3px;
  border-radius: 1.5px;
  background: var(--navy);
  margin-bottom: var(--s-4);
}
.ct__loc-cap { margin-top: var(--s-3); font: var(--t-body-sm); color: var(--ink-3); max-width: 58ch; }

.ct__loc-grid { display: grid; gap: var(--s-9); margin-top: var(--s-8); }

/* .loc is NOT a card: no border, no shadow, no background. A bordered card
   wrapping a bordered map frame is a double frame, and the section already
   has exactly one elevated object — the form. */
.loc__map {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  aspect-ratio: 4 / 3;               /* <768 — a 21:9 strip at 375 shows one street */
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--navy-050);
  box-shadow: var(--sh-2);
}

/* the live embed */
.loc__frame {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  border: 0;
  z-index: var(--z-media);
  filter: grayscale(1) contrast(1.04) brightness(1.03);
  transition: filter var(--dur-3) var(--ease-out);
}

/* the navy duotone tint. `multiply` can only darken, so it cannot produce a
   glow; SPEC-00 §5.3 bans `screen` and `plus-lighter`, not this. Do not raise
   the opacity above .20: at .26 Google's minor-road labels fall below 4.5:1.
   pointer-events:none is mandatory or the tint eats every pan and zoom. */
.loc__tint {
  position: absolute; inset: 0;
  z-index: var(--z-overlay);
  pointer-events: none;
  background: var(--navy);
  opacity: .18;
  mix-blend-mode: multiply;
  transition: opacity var(--dur-3) var(--ease-out);
}
.loc__map:hover .loc__frame,
.loc__map:focus-within .loc__frame { filter: none; }
.loc__map:hover .loc__tint,
.loc__map:focus-within .loc__tint { opacity: 0; }

/* the fallback plate: BEHIND the frame at --z-pattern, so it is the loading
   state, the blocked state and the failure state at once. Never a white hole.
   aria-hidden because the address it would announce is in <address> below. */
.loc__fallback {
  position: absolute; inset: 0;
  z-index: var(--z-pattern);
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  gap: var(--s-2);
  padding: var(--s-6);
  background: var(--navy-050);
  color: var(--navy);
}
.loc__pin { width: 24px; height: 24px; margin-bottom: var(--s-1); }
.loc__fb-city { font: var(--t-label); text-transform: uppercase; letter-spacing: .06em; }
.loc__fb-note { font: var(--t-cap); color: var(--ink-2); }

.loc__body { margin-top: var(--s-6); }
.loc__flag {
  display: inline-block;
  padding: var(--s-1) var(--s-2);
  font: var(--t-label);
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--navy);                /* 12.0:1 on --navy-050 (SPEC-08 §2.2) */
  background: var(--navy-050);
  border-radius: var(--r-xs);
}
.loc__city { margin-top: var(--s-3); font: var(--t-d4); color: var(--navy); }
.loc__addr {
  margin-top: var(--s-2);
  font: var(--t-body-sm);
  line-height: 1.62;
  font-style: normal;                /* SPEC-00 §1.3 bans italics */
  color: var(--ink-3);
}
.loc__body .link-arrow { margin-top: var(--s-3); }

/* --- the office switcher and the office plate (SPEC-08-R2 §7, §9) --------
   .loc__* above is untouched and still serves templates/page-contact.php,
   which keeps the two-panel layout. Everything below is scoped to
   .off__plate / .off__tabs, which exist only on the homepage.

   The switcher's chrome changes and nothing else does. The REFINE-01 X2 note
   above records that two bordered white boxes above a form-heavy section read
   as two unfilled form controls; the segmented control fixed the doubling but
   kept the form vocabulary. An underline rail is navigation vocabulary, it
   sits on the heading baseline, and it removes a competing bordered surface
   from a section that is supposed to hold one object.

   No-JS contract, unchanged and load-bearing:
     - .off__tabs is display:none until .js is present. Dead buttons are worse
       than no buttons.
     - both panes render open. The .js rule below closes all but .is-on, and
       assets/next.js adds the `hidden` attribute so a closed pane's iframe is
       out of the tab order and out of in-page search.
   The ARIA, the roving tabindex and the data-src promotion for the New York
   map are all exactly as they were. */

/* 10px and 400ms, inside SPEC-00 §6.2's 16-26px / 400-600ms reveal envelope.
   Reduced motion collapses the duration and `both` holds the end state, so
   the pane still lands visible. */
@keyframes off-in { from { opacity: 0; transform: translate3d(0, 10px, 0); } }

.ct__loc-head--tabs .ct__loc-h { margin-bottom: 0; }

/* X1 revisited: the bare navy rule above this head read as an eyebrow with its
   text missing, because that is what it looked like. The rule now belongs to a
   labelled eyebrow and the h3's own ::before is switched off, so the head still
   carries exactly one mark. Navy, not the site's gold .tick: this band's gold
   is spent on the submit button. */
.ct__loc-head-t { min-width: 0; }
.ct__loc-eyebrow {
  display: flex;
  align-items: center;
  gap: 10px;
  font: var(--t-eyebrow);
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--navy);
  margin-bottom: var(--s-4);
}
.ct__loc-eyebrow::before {
  content: "";
  flex: 0 0 auto;
  width: 18px; height: 3px;
  border-radius: 1.5px;
  background: var(--navy);
}
.ct__loc-head-t .ct__loc-h::before { display: none; }

.off__tabs {
  display: none;
  position: relative;
  gap: 0;                            /* ≤767: two 50% tabs, full width */
  margin-top: var(--s-5);
  border-bottom: 1px solid var(--line);
}
.js .off__tabs { display: flex; }

.off__tab {
  display: flex;
  flex-direction: column;
  gap: 2px;
  flex: 1 1 50%;                     /* ≤767 */
  min-height: 48px;                  /* touch target */
  padding: var(--s-2) var(--s-3) var(--s-3);
  text-align: left;
  background: none;
  border: 0;
  border-radius: 0;
  box-shadow: none;
}
.off__tab-city,
.off__tab-sub { transition: color var(--dur-1) var(--ease-out); }
.off__tab-city { font: var(--t-label); color: var(--ink-3); }
.off__tab-sub  { font: var(--t-cap); font-size: 12px; color: var(--ink-3); }
.off__tab:hover .off__tab-city,
.off__tab.is-on .off__tab-city { color: var(--navy); }
.off__tab.is-on .off__tab-sub  { color: var(--ink-2); }

/* the indicator: one 2px navy bar that slides. Decorative, positioned by
   next.js §7; if that never runs the active tab is still identified by
   colour and by aria-selected, so this is a pure enhancement. */
.off__ind {
  position: absolute;
  left: 0; bottom: -1px;
  height: 2px;
  width: var(--iw, 0px);
  background: var(--navy);
  transform: translateX(var(--ix, 0px));
  transition: transform var(--dur-2) var(--ease-out),
              width     var(--dur-2) var(--ease-out);
  pointer-events: none;
}

/* the plate is a frame, the console is an object: 1px --line and NO shadow */
.off__plate {
  margin-top: var(--s-5);
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  overflow: hidden;
}
/* the map is bled to the card edge, so the plate is its frame now */
.off__plate .loc__map {
  aspect-ratio: 4 / 3;               /* ≤767 */
  border: 0;
  border-radius: 0;
  box-shadow: none;
}

.js .off__pane { display: none; }
.js .off__pane.is-on { display: block; animation: off-in var(--dur-3) var(--ease-out) both; }
/* no-JS: both panes visible, stacked inside the one plate, ruled apart */
.off__plate .off__pane + .off__pane { border-top: 1px solid var(--line); }
.js .off__plate .off__pane + .off__pane { border-top: 0; }

.off__plate .off__body { margin-top: 0; padding: var(--s-6); }
.off__hours {
  margin-top: var(--s-3);
  font: var(--t-body-sm);
  color: var(--ink-3);
}
.off__body .link-arrow { margin-top: var(--s-3); }

/* SEO-QA-2026-09-20 §4 | the city's own service pages inside the office pane.
   A ruled list under the address, not a new card: it is a secondary
   navigation aid and must not compete with "Get directions" above it. Only
   the Cleveland pane renders one; New York has no local pages and the block
   is driven by data, so nothing is emitted there. */
.off__local {
  margin-top: var(--s-5);
  padding-top: var(--s-4);
  border-top: 1px solid var(--line);
}
.off__local-h { font: var(--t-eyebrow); color: var(--ink-3); margin: 0 0 var(--s-2); }
.off__local-list {
  list-style: none; margin: 0; padding: 0;
  display: flex; flex-wrap: wrap; gap: var(--s-2) var(--s-4);
}
.off__local-list a {
  font: var(--t-body-sm); color: var(--ink-2);
  text-decoration: underline; text-underline-offset: .22em;
  text-decoration-thickness: 1px;
  text-decoration-color: color-mix(in oklab, currentColor 35%, transparent);
}
.off__local-list a:hover,
.off__local-list a:focus-visible { color: var(--ink); text-decoration-color: currentColor; }

/* --- CONTACT — 561px and up --- */
@media (min-width: 561px) {
  .ct__fields { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s-5) var(--s-4); }
}

/* --- CONTACT — 768px and up --- */
@media (min-width: 768px) {
  .ct__top  { gap: var(--s-9); }
  .ct__form { padding: var(--s-7); }
  .ct__loc  { margin-top: var(--s-11); }
  .loc__map { aspect-ratio: 21 / 9; }   /* a 16:10 at 720px would be 450px tall */

  .ct__console { margin-top: var(--s-9); }
  /* the console is still one column here, so the rail turns two-column
     internally rather than stacking a 720px-wide photograph */
  .ct__rail {
    display: grid;
    grid-template-columns: 40% minmax(0, 1fr);
    align-items: stretch;
  }
  .ct__photo { aspect-ratio: auto; min-height: 200px; }
  .ct__console .ct__direct { padding: var(--s-7); }
  .ct__console .ct__form   { padding: var(--s-7); }

  .off__tabs { gap: var(--s-6); }
  .off__tab  { flex: 0 0 auto; padding-inline: 0; }
  .off__plate .loc__map  { aspect-ratio: 21 / 9; }
  .off__plate .off__body { padding: var(--s-7); }
  /* address on the left, hours and actions on the right, so the detail block
     is not a tall column under a wide map */
  .off__plate .off__body {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    column-gap: var(--s-7);
    align-content: start;
  }
  .off__plate .loc__flag  { grid-column: 1; grid-row: 1; }
  .off__plate .loc__city  { grid-column: 1; grid-row: 2; }
  .off__plate .loc__addr  { grid-column: 1; grid-row: 3; }
  .off__plate .off__hours { grid-column: 2; grid-row: 1 / span 2; margin-top: 0; }
  .off__plate .off__body .link-arrow { grid-column: 2; grid-row: 3; margin-top: 0; }
}

/* --- CONTACT — 1024px and up (the 5/7 split) --- */
@media (min-width: 1024px) {
  .ct__top {
    grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
    gap: var(--s-9);
    align-items: start;
  }
  .ct__form { padding: var(--s-8); }
  .ct__loc  { margin-top: var(--s-12); }
  .ct__loc-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s-8); }
  .loc__map { aspect-ratio: 16 / 10; }

  /* the console's own 5/7, and the two panes touch: the seam is the navy edge
     itself, no border and no gap */
  .ct__console {
    grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
    border-radius: var(--r-xl);
  }
  .ct__rail { display: flex; flex-direction: column; }
  .ct__photo { flex: 1 1 auto; aspect-ratio: auto; min-height: 200px; }
  .ct__console .ct__direct { padding: var(--s-8); }
  .ct__console .ct__form   { padding: var(--s-9); }
  .ct__form .field__input,
  .ct__form .field__select { min-height: 52px; padding: 15px 16px; }

  .ct__loc-head--tabs {
    display: flex;
    justify-content: space-between;
    align-items: flex-end;
    gap: var(--s-8);
  }
  .off__tabs { margin-top: 0; }
  .off__plate {
    margin-top: var(--s-6);
    border-radius: var(--r-xl);
  }
  .off__plate .loc__map { aspect-ratio: 16 / 10; }
  .off__pane {
    display: grid;
    grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
    align-items: center;
  }
  .js .off__pane.is-on { display: grid; }
  .off__plate .off__body {
    display: block;
    align-self: center;
    padding: var(--s-9);
  }
  .off__plate .off__hours { margin-top: var(--s-3); }
  .off__plate .off__body .link-arrow { margin-top: var(--s-3); }
}

/* --- CONTACT — 1180px and up (reference layout) --- */
@media (min-width: 1180px) {
  .ct__top { gap: var(--s-10); }
}

/* =========================================================================
   THE SITE-WIDE CLOSING BAND (.svc-cta)
   Every template on the site ends on this object, rendered by the one partial
   templates/partials/cta-close.php. It lives in next.css and NOT in pages.css
   because the homepage loads next.css only.
   ========================================================================= */
/* --- 8. THE CLOSING BAND, rebuilt ---------------------------------------
   ONE component, one partial (templates/partials/cta-close.php), fourteen
   consumers. Every template used to carry its own copy of the markup, and
   every copy said the same six things: a heading, a sentence, two buttons,
   a photograph. The owner read it as plain, on every page, and that reading
   is correct: the band asks for the largest commitment on the page and used
   to offer less information than any band above it.

   WHAT IT CARRIES NOW. An eyebrow that names the page it closes, the
   heading, one sentence, a three-row ledger of what actually happens next,
   the gold button, the phone button, the opening hours and both cities with
   their marks, and on the right a photograph with a proof chip overlapping
   its lower corner over a low-contrast grid field and an arc.

   WHAT IS DELETED. The full-bleed .svc-cta__plane / .svc-cta__seam pair and
   the 1120px seam arithmetic that went with them. Nothing renders them any
   more: post-next.php, insights-archive.php and industries-hub.php were the
   last three consumers and all three now include the partial. The
   .svc-cta--contained MODIFIER is deleted too, for the same reason: with one
   markup there is nothing left to modify. Templates may keep the class in
   their markup harmlessly; the partial does not emit it.

   GROUND AND THE FOOTER. Unchanged and load-bearing: the section sits on
   --paper with real space under it and the plate resolves to #011E3D, while
   .ft is #05192F. Plate and footer are still two values with a hairline
   between them, which is the separation SPEC-09-R2 bought.

   GOLD, two instances and that is the budget: the primary button's fill and
   the proof chip's figure (gold on navy, 8.0:1, SPEC-00 §3.5 permits it as
   an accent on a navy surface). The eyebrow tick is WHITE, not the site's
   gold tick, specifically to hold the count at two. The two decorative
   layers are white at 8.5 and 15 per cent, both at or above the threshold
   the homepage band established when its 9%-on-a-gradient field failed to
   resolve, and both are masked away from the top-right corner so no line of
   type ever crosses either.

   NO GLASS. Zero backdrop-filter in this block. The chip is a solid fill,
   which is the only reason it can sit over a photograph. */
.svc-cta {
  background: var(--paper);
  padding-block: var(--section-y);
  color: inherit;
}
.svc-cta__plate {
  position: relative;
  isolation: isolate;
  overflow: clip;
  /* NO max-width. The plate used to cap itself at 1200 inside a 1296 container
     and the owner saw it: the closer was 96px narrower than the header bar and
     than every band above it. The plate now takes the full .container width, so
     its left and right edges are the header's edges exactly. */
  padding: var(--s-9) var(--s-7);
  border-radius: var(--r-lg);       /* 20px, unchanged */
  background: radial-gradient(150% 118% at 92% -6%, #063A70 0%, #022D5A 46%, #011E3D 100%);
  color: #fff;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  /* the stacked order is head, PHOTOGRAPH, ledger: at 390 the image arrives
     before the three steps rather than after the buttons, where it read as an
     afterthought and pushed the close 300px further down. */
  grid-template-areas:
    "head"
    "aside"
    "body";
  gap: var(--s-8);
}

/* --- the two decorative layers ----------------------------------------- */
.svc-cta__field {
  position: absolute;
  inset: 0;
  z-index: var(--z-pattern);
  pointer-events: none;
  border-radius: inherit;
  background-image:
    repeating-linear-gradient(90deg,  rgba(255, 255, 255, .085) 0 1px, transparent 1px 56px),
    repeating-linear-gradient(180deg, rgba(255, 255, 255, .085) 0 1px, transparent 1px 56px);
  background-position: 100% 0;
  /* anchored at the top-right corner and gone well before the measure: the
     grid frames the photograph and never runs under the heading. */
  -webkit-mask-image: radial-gradient(118% 92% at 100% 0, #000 0 36%, transparent 74%);
          mask-image: radial-gradient(118% 92% at 100% 0, #000 0 36%, transparent 74%);
}
.svc-cta__arc {
  position: absolute;
  z-index: var(--z-pattern);
  pointer-events: none;
  right: -190px;
  bottom: -230px;
  width: 560px;
  height: 560px;
  border-radius: 50%;
  border: 1.5px solid rgba(255, 255, 255, .15);
}

/* --- the head ----------------------------------------------------------- */
.svc-cta__head { grid-area: head; position: relative; z-index: var(--z-content); min-width: 0; }
.svc-cta__eyebrow {
  display: flex;
  align-items: center;
  gap: 10px;
  font: var(--t-eyebrow);
  letter-spacing: .14em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, .68);
}
.svc-cta__tick {
  flex: 0 0 auto;
  width: 18px; height: 3px;
  border-radius: 1.5px;
  background: rgba(255, 255, 255, .55);
}
.svc-cta__h {
  margin-top: var(--s-5);
  font: var(--t-d2);
  letter-spacing: -.022em;
  color: #fff;
  max-width: 18ch;
  text-wrap: balance;
}
.svc-cta__lead {
  margin-top: var(--s-4);
  font: 400 17px/1.6 var(--font-ui);
  color: rgba(255, 255, 255, .80);
  max-width: 48ch;
  text-wrap: pretty;
}

/* --- the ledger, the buttons and the line under them -------------------- */
.svc-cta__body { grid-area: body; position: relative; z-index: var(--z-content); min-width: 0; }
.svc-cta__nexth {
  font: var(--t-eyebrow);
  letter-spacing: .14em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, .56);
  margin-bottom: var(--s-4);
}
/* ruled rows, which is the register vocabulary the service, industry and
   About pages already use, rather than a second kind of list. */
.svc-cta__steps {
  display: grid;
  border-top: 1px solid rgba(255, 255, 255, .14);
  margin: 0;
  padding: 0;
  list-style: none;
}
.svc-cta__step {
  display: grid;
  grid-template-columns: 26px minmax(0, 1fr);
  column-gap: var(--s-5);
  align-items: start;
  padding-block: var(--s-5);
  border-bottom: 1px solid rgba(255, 255, 255, .14);
}
.svc-cta__step-ico {
  grid-column: 1;
  grid-row: 1 / span 3;
  display: block;
  width: 26px; height: 26px;
  margin-top: 3px;
  color: rgba(255, 255, 255, .60);
}
.svc-cta__step-ico svg { display: block; width: 100%; height: 100%; }
.svc-cta__step-n {
  grid-column: 2; grid-row: 1;
  font: 600 11px/1.2 var(--font-display);
  letter-spacing: .14em;
  font-variant-numeric: tabular-nums;
  color: rgba(255, 255, 255, .42);   /* aria-hidden; the <ol> carries the order */
}
.svc-cta__step-t {
  grid-column: 2; grid-row: 2;
  margin-top: var(--s-2);
  font: var(--t-d4);
  color: #fff;
}
.svc-cta__step-d {
  grid-column: 2; grid-row: 3;
  margin-top: var(--s-1);
  font: 400 14px/1.55 var(--font-ui);
  color: rgba(255, 255, 255, .68);   /* 6.2:1 on the plate's lightest stop */
}
.svc-cta__actions {
  display: flex; flex-wrap: wrap;
  gap: var(--s-3) var(--s-4);
  margin-top: var(--s-8);
}
.svc-cta__meta {
  display: flex; flex-wrap: wrap;
  gap: var(--s-2) var(--s-6);
  margin-top: var(--s-5);
  font: 400 14px/1.5 var(--font-ui);
  color: rgba(255, 255, 255, .72);
}
.svc-cta__meta-i { display: inline-flex; align-items: center; gap: var(--s-2); }
.svc-cta__meta-ico {
  display: block;
  flex: 0 0 auto;
  width: 16px; height: 16px;
  color: rgba(255, 255, 255, .54);
}
.svc-cta__meta-ico svg { display: block; width: 100%; height: 100%; }

/* --- the photograph and the proof chip ---------------------------------- */
.svc-cta__aside {
  grid-area: aside;
  position: relative;
  z-index: var(--z-content);
  min-width: 0;
}
.svc-cta__fig {
  margin: 0;
  border-radius: var(--r-md);
  overflow: clip;
  aspect-ratio: 3 / 2;
}
.svc-cta__fig picture,
.svc-cta__fig img {
  display: block;
  width: 100%; height: 100%;
  object-fit: cover;
  object-position: var(--po, 50% 44%);
}
.svc-cta__chip {
  position: absolute;
  z-index: var(--z-content);
  left: var(--s-5);
  bottom: var(--s-5);
  display: grid;
  gap: 2px;
  margin: 0;
  padding: var(--s-4) var(--s-5);
  border-radius: var(--r-md);
  background: #0A2545;               /* solid, not glass: it sits over a photo */
  border: 1px solid rgba(255, 255, 255, .16);
  box-shadow: var(--sh-3);
}
.svc-cta__chip-n {
  font: var(--t-num-sm);
  font-variant-numeric: tabular-nums;
  letter-spacing: -.02em;
  color: var(--gold);
}
.svc-cta__chip-l {
  font: 600 12px/1.3 var(--font-ui);
  letter-spacing: .02em;
  color: rgba(255, 255, 255, .74);
}

@media (min-width: 561px) {
  .svc-cta__plate { padding: var(--s-10) var(--s-9); }
}
@media (max-width: 560px) {
  .svc-cta__actions { flex-direction: column; gap: var(--s-3); }
  .svc-cta__actions .btn { width: 100%; }
  .svc-cta__meta { gap: var(--s-2) var(--s-5); font-size: 13px; }
  .svc-cta__chip { left: var(--s-4); bottom: var(--s-4); padding: var(--s-3) var(--s-4); }
}
@media (min-width: 1024px) {
  /* 7/5, the homepage closer's proportion. The photograph spans both text
     rows, so the column is one object rather than a stack of two. */
  .svc-cta__plate {
    grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
    grid-template-areas:
      "head aside"
      "body aside";
    column-gap: var(--s-10);
    row-gap: var(--s-8);
    padding: var(--s-11) var(--s-10);
    align-items: start;
  }
  .svc-cta__aside { align-self: center; }
  .svc-cta__fig { aspect-ratio: 4 / 5; }
  /* the chip crosses the photograph's lower-left corner and hangs 28px into
     the column gap, never past the plate's own padding, so the plate's
     overflow: clip cannot cut it. */
  .svc-cta__chip { left: 0; bottom: var(--s-8); transform: translateX(-28px); }
}
@media (min-width: 1180px) {
  .svc-cta__chip { transform: translateX(-36px); }
}
/* The 1120px seam arithmetic that used to live here is DELETED with the plane
   and the seam it positioned. Nothing computes a --seam any more. */

/* --- the closer's focus ring, its motion and its high-contrast entry ------
   All three used to live in assets/pages.css beside the component. They moved
   here with it: templates/home-next.php loads next.css ONLY, so anything the
   band needs has to be in this file or the homepage renders the markup bare.
   That is exactly what happened on the first deploy of this band. */

/* the plate is a navy field, so the gold primary inside it needs the
   white-then-gold pair rather than the page's navy ring. */
.svc-cta .btn--primary:focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px #fff, 0 0 0 5px var(--gold);
}

/* T5: the plate settles as ONE opacity rather than four staggered reveals, so
   the band can never be captured as an empty navy rectangle. */
.svc-cta__plate.r { will-change: opacity, transform; }
.svc-cta__plate.r.is-in { will-change: auto; }

@media (prefers-reduced-motion: reduce) {
  .svc-cta *, .svc-cta *::before, .svc-cta *::after { animation: none !important; }
  .svc-cta__plate.r { will-change: auto !important; }
  /* the chip's translateX is layout, not motion, and is deliberately kept. */
}

@media (prefers-contrast: more) {
  /* the two decorative layers and the three hairlines, raised. The grid and
     the arc are ornament and simply go; the ledger's rules are structure, so a
     reader who asks for more contrast gets a ledger that reads MORE like a
     ledger rather than less. */
  .svc-cta__field { background-image: none; }
  .svc-cta__arc { border-color: rgba(255, 255, 255, .34); }
  .svc-cta__steps { border-top-color: rgba(255, 255, 255, .34); }
  .svc-cta__step { border-bottom-color: rgba(255, 255, 255, .34); }
  .svc-cta__step-d,
  .svc-cta__meta { color: rgba(255, 255, 255, .92); }
  .svc-cta__chip { border-color: rgba(255, 255, 255, .40); }
}


/* =========================================================================
   NOTE, R4 closer pass. THE HOMEPAGE NO LONGER RENDERS .cta-band. home-next.php
   ends on the site-wide closer instead (templates/partials/cta-close.php,
   styled by .svc-cta in assets/pages.css), so the homepage and the other
   thirteen templates share one ending. The block below is NOT deleted:
   templates/services-hub.php and the two legacy archives are rollback
   documents on disk and still render .cta-band, and .container--narrow,
   .btn--secondary--dark and the whole .ft footer live inside it.
   ========================================================================= */

/* =========================================================================
   CTA BAND + FOOTER  (SPEC-09, band rebuilt to SPEC-09-R2)
   The page's second and last dark band: a full-bleed golden-hour skyline
   under a navy duotone, a left-weighted 7/5 grid, and a solid navy plate
   carrying what actually happens when you call. Its bottom edge resolves to
   flat --navy-900 and the footer continues that exact value, so the join is
   not a boundary and the page still crosses light -> dark exactly twice
   (SPEC-09 §5).

   WHAT THIS REPLACES. The band shipped centred, symmetrical and flat, and a
   client revision answered "plain" with two decorative layers: .cta__pattern
   at 9% alpha and .cta__arc at 16%. The client read it as plain a second
   time, which is the concrete evidence that a sub-10% mark on a gradient
   does not resolve. Both layers are deleted rather than thickened. Nothing
   in this section is now under 11% alpha and the largest element is a
   photograph.

   Zero glass and zero backdrop-filter, still: §8.6 caps the document at
   three and all three are spent on .site-header.is-stuck, .hero__rail and
   .pf__panel. The plate is a solid rgba fill, which is why it can sit over
   the photograph at all. Zero loop, zero sticky, zero new JavaScript.

   GOLD LEDGER, and this one goes UP by one, so it is recorded rather than
   slipped in: the <em> in the headline, --gold on navy at 8.0:1, permitted
   by SPEC-00 §3.5 as an accent on a navy surface. The deleted arc was white
   (REFINE-01 Y2 had already banked its gold), so nothing offsets it here.
   ========================================================================= */
.cta-band {
  position: relative;
  isolation: isolate;
  overflow: clip;
  padding-block: var(--section-y-lg);
  min-height: 720px;
  display: flex;
  align-items: center;
  color: #fff;
  background: var(--navy-900);       /* the ground the bottom fade lands on */
}
.container--narrow { max-width: var(--container-narrow); }

/* the section carries .r purely so the existing observer stamps .is-in on it
   and the media settle has a trigger. Its own reveal transform is cancelled:
   translating a 738px band would move the whole closer. */
.js .cta-band.r { opacity: 1; transform: none; }

/* --- layer 1: the photograph --- */
.cta__media {
  position: absolute;
  inset: 0;
  z-index: var(--z-media);
  overflow: hidden;
}
.cta__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 68% 46%;          /* sun and skyline sit in the right third */
  /* grayscale FIRST so the navy `color` blend below produces a true duotone
     instead of tinting an already-warm image toward mud. No blur, no colour
     drop-shadow, no saturation lift: SPEC-00 §5.3 is untouched. */
  filter: grayscale(1) contrast(1.06) brightness(.88);
}

/* --- layer 2: hue. `color` keeps the photograph's luminance and replaces its
   hue, which is the duotone. It is not a glow and cannot be: the blend mode
   is incapable of adding light anywhere, and §5.3 item 9 bans screen and
   plus-lighter specifically, neither of which is used. --- */
.cta__media::before {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--navy);
  mix-blend-mode: color;
}

/* --- layer 3: the scrim. Heavy under the copy, light where the sky reads.
   rgba(1,30,61,x) rather than `transparent` at the ends: `transparent`
   interpolates through rgba(0,0,0,0) in some engines and greys the ramp. --- */
.cta__media::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: var(--z-overlay);
  background-image:
    /* 3a horizontal: text zone -> photo zone. Text only ever sits where the
       alpha is >= .92, which caps the composite luminance at .065 and holds
       white above 9:1 even over the sun. */
    linear-gradient(96deg,
      rgba(1, 30, 61, .94) 0%,
      rgba(1, 30, 61, .93) 44%,
      rgba(2, 45, 90, .72) 66%,
      rgba(2, 45, 90, .55) 100%),
    /* 3b bottom resolve: the band's last pixel row and the footer's first are
       both #011E3D */
    linear-gradient(180deg, rgba(1, 30, 61, 0) 62%, var(--navy-900) 100%),
    /* 3c top edge: a 64px settle out of the light section above */
    linear-gradient(180deg, rgba(1, 30, 61, .42) 0%, rgba(1, 30, 61, 0) 64px);
}

/* --- the grid --- */
.cta__grid {
  position: relative;
  z-index: var(--z-content);
  display: grid;
  grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
  column-gap: var(--s-12);
  align-items: center;
}

.cta__h {
  font: var(--t-d2);
  letter-spacing: -.024em;
  color: #fff;
  max-width: 14ch;
  text-wrap: pretty;
}
/* no italics are loaded, so the <em> carries colour and nothing else */
.cta__h em { font-style: normal; color: var(--gold); }
/* REFINE-01 Y1: this is the only block on the page that starts cold, with no
   eyebrow and no tick above its heading. The tick's geometry in white gives
   it the same opening mark every other section has. Left-aligned now, not
   centred: nothing in this band is centred any more, and that is half of why
   the old one read flat. */
.cta__h::before {
  content: "";
  display: block;
  width: 26px;
  height: 3px;
  border-radius: 1.5px;
  background: rgba(255, 255, 255, .55);
  margin: 0 0 var(--s-5);
}
.cta__lead {
  margin-top: var(--s-6);
  font: var(--t-lead);
  color: rgba(255, 255, 255, .80);
  max-width: 44ch;
}
.cta__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-4);
  margin-top: var(--s-8);
}
/* the hours appear once, small, where they belong: the offices section 500px
   above and the footer both carry the two addresses already. */
.cta__hours {
  margin-top: var(--s-5);
  font: var(--t-body-sm);
  color: rgba(255, 255, 255, .66);
}

/* --- the plate --- */
.cta__next {
  position: relative;
  z-index: var(--z-content);
  padding: var(--s-8);
  border-radius: var(--r-lg);
  background: rgba(1, 30, 61, .92);
  border: 1px solid rgba(255, 255, 255, .14);
  /* --sh-3, not --sh-4: SPEC-00 §5.1 reserves --sh-4 for the hero media
     frame, one instance per page. */
  box-shadow: var(--sh-3);
}

/* the pattern that replaces the deleted 9% grid, and the reason it reads is
   that it sits on a controlled ground rather than on a gradient: 11% white on
   rgba(1,30,61,.92) resolves at 1x and at 2x. 76px pitch, masked off at the
   plate's own padding so no rule collides with the eyebrow. */
.cta__ledger {
  position: absolute;
  inset: 0;
  z-index: var(--z-pattern);
  pointer-events: none;
  border-radius: inherit;
  background-image: repeating-linear-gradient(
    180deg,
    rgba(255, 255, 255, .11) 0 1px,
    transparent 1px 76px
  );
  background-position: 0 96px;
  -webkit-mask-image: linear-gradient(180deg, transparent 0 72px, #000 96px 100%);
          mask-image: linear-gradient(180deg, transparent 0 72px, #000 96px 100%);
}

.cta__next-eyebrow {
  position: relative;
  z-index: var(--z-content);
  font: var(--t-eyebrow);
  text-transform: uppercase;
  letter-spacing: .14em;
  color: rgba(255, 255, 255, .62);
  margin-bottom: var(--s-7);
}
.cta__steps { position: relative; display: grid; gap: var(--s-5); }
/* the connecting spine between numerals: a real 1px line, not a texture */
.cta__steps::before {
  content: "";
  position: absolute;
  left: 15px;
  top: 28px;
  bottom: 28px;
  width: 1px;
  background: rgba(255, 255, 255, .20);
}
.cta__step {
  position: relative;
  z-index: var(--z-content);
  display: grid;
  grid-template-columns: 32px 1fr;
  gap: var(--s-5);
  align-items: center;
  min-height: 56px;
  font: var(--t-d4);
  color: rgba(255, 255, 255, .94);
}
.cta__step-n {
  font: var(--t-num-sm);
  font-size: 15px;
  letter-spacing: .04em;
  font-variant-numeric: tabular-nums;
  color: rgba(255, 255, 255, .30);   /* aria-hidden; the label carries meaning */
  background: rgba(1, 30, 61, .92);  /* punches the spine so it reads as ticks */
  padding-block: var(--s-1);
}

/* the photograph settles rather than appearing. Scale only, no opacity: an
   opacity ramp on a full-bleed layer flashes the flat navy ground underneath.
   Both selectors are .js-prefixed, so with no JavaScript the media simply
   paints at scale(1) and nothing is missing. */
.js .cta__media img { transform: scale(1.045); transition: transform 900ms var(--ease-soft); }
.js .cta-band.is-in .cta__media img { transform: none; }

@media (max-width: 900px) {
  .cta-band { min-height: 0; padding-block: var(--section-y); }
  /* the plate covers the right third here, so the horizontal ramp has nothing
     left to protect and is replaced by a flat scrim plus the bottom resolve. */
  .cta__media img { object-position: 78% 40%; }
  .cta__media::after {
    background-image:
      linear-gradient(180deg, rgba(1, 30, 61, .90) 0%, rgba(1, 30, 61, .93) 55%, var(--navy-900) 100%);
  }
  .cta__grid { grid-template-columns: minmax(0, 1fr); row-gap: var(--s-10); }
  .cta__next { padding: var(--s-9) var(--s-7) var(--s-8); }
}
@media (max-width: 560px) {
  .cta__grid { row-gap: var(--s-9); }
  .cta__h::before { margin-bottom: var(--s-4); }
  .cta__lead { margin-top: var(--s-5); }
  .cta__actions { margin-top: var(--s-7); gap: var(--s-3); }
  .cta__actions .btn { width: 100%; }
  .cta__next { padding: var(--s-7) var(--s-6) var(--s-7); border-radius: var(--r-md); }
  .cta__steps { gap: var(--s-4); }
  .cta__step { min-height: 52px; gap: var(--s-4); }
}

/* NOT SPEC-00 §7.2's glass secondary, which was never built and must not be:
   it carries backdrop-filter: blur(10px), and §8.6 caps the document at
   three. All three are spent, and after REFINE-01 H1 and I4 the census is
   .site-header.is-stuck, .hero__rail and .pf__panel | exactly three, where
   before this pass it was five. This is the same component restyled for a dark
   ground per §10, with the border and hover values lifted from that recipe and
   the blur removed.
   REFINE-01 Y4: the border was rgba(255,255,255,.42), 3.62:1 on --navy, which
   read thin beside a solid gold primary. .52 is ~4.8:1 and still nowhere near
   a second filled button. The white label is 13.8:1 either way. */
.btn--secondary--dark {
  background: transparent;
  color: #fff;
  border-color: rgba(255, 255, 255, .52);
}
.btn--secondary--dark:hover  { background: rgba(255, 255, 255, .10); border-color: rgba(255, 255, 255, .72); }
.btn--secondary--dark:active { background: rgba(255, 255, 255, .16); }

/* --- the footer (SPEC-09 §6) ---
   Ground is the value the band's terminal fade lands on. Zero gold: the focus
   rings come from .on-dark at the top of this file and a focus ring is not a
   decorative gold instance. Zero headings: the column heads are <p> elements
   serving as aria-labelledby targets, so footer chrome stays out of the
   heading map. Zero motion: a reveal on the last element of a document fires
   when the reader is already looking at it and reads as lag. */
/* GROUND, and this is the one thing in the block that is not a refinement.
   .ft was var(--navy-900), #011E3D, which is also exactly where the closing
   plate's gradient lands. Plate and footer therefore rendered as one
   undifferentiated dark mass with no edge between them, which is what the
   client saw. The footer now has its own value, one step deeper and cooler,
   plus a white hairline at the join. Three tones live in the terminal now:
     plate  #022D5A -> #011E3D   (assets/pages.css .svc-cta, NOT touched here:
                                  other templates depend on that value)
     footer #05192F              (--ft-ground below)
     panel  #0A2545              (--ft-panel, the call-back block)
   --ft-ground and --ft-panel are declared ON .ft rather than in :root: they
   are used nowhere else, and :root is shared ground this block has no reason
   to edit. */
.ft {
  --ft-ground: #05192F;
  --ft-panel:  #0A2545;
  --ft-line:   rgba(255, 255, 255, .10);
  --ft-txt:    rgba(255, 255, 255, .72);   /* 9.6:1 on --ft-ground */
  --ft-link:   400 14px/1.5 var(--font-ui);

  background: var(--ft-ground);
  border-top: 1px solid rgba(255, 255, 255, .08);
  padding-top: var(--s-12);
  padding-bottom: var(--s-9);
  color: var(--ft-txt);
}

/* --- band A: brand + call-back ------------------------------------------ */
.ft__top { display: grid; gap: var(--s-9); }

.ft__logo { width: 160px; }
.ft__desc {
  margin-top: var(--s-5);
  font: var(--t-body-sm);
  line-height: 1.66;
  color: var(--ft-txt);
  max-width: 54ch;
}
.ft__contact {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2) var(--s-6);
  margin-top: var(--s-6);
}
.ft__contact li { display: inline-flex; align-items: center; }
.ft__tel { font: 600 17px/1.3 var(--font-display) !important; color: #fff !important; }
.ft__hours { font: var(--t-body-sm); color: rgba(255, 255, 255, .62); }

/* the panel is the only lighter surface in the terminal, so it reads as an
   object on the footer rather than as more footer */
.ft__cta {
  background: #0A2545;
  border: 1px solid rgba(255, 255, 255, .10);
  border-radius: 20px;
  padding: 28px;
}
/* COMPACT, AND DESIGNED (owner, third revision 2026-09-20). The first pass
   made the card short by shrinking every part of it, which read as squeezed.
   This one keeps the height (~300px, against a ~220px brand block, and the
   two are centred on each other) but gives the parts a rhythm: 16px between
   head, lead, fields and the fallback line; a 20px head; 44px fields on a
   well one step lighter than the card with a visible hairline, the same
   shape as the console fields on /contact-us/; and the control on the field
   row itself, at field height and flush with the card's right edge, rather
   than orphaned on a row of its own. */
.ft__cta-h { font: 700 20px/1.3 var(--font-display); color: #fff; }
.ft__cta-lead {
  margin-top: 6px;
  font: 400 14px/1.5 var(--font-ui);
  color: var(--ft-txt);
  max-width: 46ch;
}
.ft__form { margin-top: var(--s-5); display: grid; gap: var(--s-4); }
.ft__label {
  display: block;
  margin-bottom: 6px;
  font: 500 13px/1.3 var(--font-ui);
  letter-spacing: .01em;
  color: rgba(255, 255, 255, .78);
}
.ft__input {
  width: 100%;
  min-height: 44px;
  padding: 10px 14px;
  font: 400 14px/1.4 var(--font-ui);
  color: #fff;
  background: #0F2C4F;
  border: 1px solid rgba(255, 255, 255, .14);
  border-radius: 8px;
  transition: border-color var(--dur-1) var(--ease-out), background var(--dur-1) var(--ease-out);
}
.ft__input::placeholder { color: rgba(255, 255, 255, .42); }
.ft__input:hover { border-color: rgba(255, 255, 255, .30); }
.ft__input:focus {
  outline: none;
  background: #123258;
  border-color: rgba(255, 255, 255, .58);
}
/* the honeypot: off-canvas, not display:none, so a bot that reads the CSS
   still fills it in */
.ft__hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }
/* the control shares the last field row and IS a cell of that row: same
   column as the Phone input above it, same left and right edge, same 44px
   height, sitting on the Work email input's baseline (owner, 2026-09-20). */
.ft__submit { display: flex; align-self: end; }
.ft__submit .btn { width: 100%; min-height: 44px; height: 44px; padding: 10px 22px; }
.ft__cta-alt {
  margin-top: var(--s-4);
  font: 400 13px/1.5 var(--font-ui);
  color: rgba(255, 255, 255, .62);
}
.ft__cta-alt a { color: #fff; text-decoration: underline; text-underline-offset: 3px; }

/* --- band B: the site index --------------------------------------------- */
.ft__grid {
  display: grid;
  gap: var(--s-8) var(--s-6);
  grid-template-columns: minmax(0, 1fr);
  margin-top: var(--s-11);
}

/* REFINE-01 F1: the head is 13px and the links beneath it are 14px, so the
   head was smaller than its own contents and the hierarchy read inverted. A
   hairline under it makes it a column head rather than a small label, without
   touching the type scale. 1.43:1, decorative, bounds no control. */
/* the sub-headings inside the one "Our Services" column (2026-09-20), which
   mirrors the live footer: two group sets under one head. */
/* "Company" is the Resources column's second heading, and the owner wants it
   to READ as one: same uppercase label type, same hairline rule, same colour
   as the column head above it. It therefore takes .ft__h itself rather than a
   smaller bold variant, and --n only adds the space that separates the two
   blocks. The old .ft__sh / .ft__sh--n pair had exactly one user, this
   heading, and is deleted with it. */
.ft__h--n { margin-top: var(--s-7); }
.ft__h {
  margin-bottom: var(--s-4);
  padding-bottom: var(--s-3);
  border-bottom: 1px solid var(--ft-line);
  font: var(--t-label);
  letter-spacing: .06em;
  text-transform: uppercase;
  color: #fff;                        /* 16.2:1 on --ft-ground */
}
.ft__h-link { color: inherit; transition: color var(--dur-1) var(--ease-out); }
.ft__h-link:hover { text-decoration: underline; text-underline-offset: 4px; }

.ft__links li + li { margin-top: var(--s-3); }
.ft__links a {
  font: var(--ft-link);
  color: var(--ft-txt);
  transition: color var(--dur-1) var(--ease-out);
}
.ft__links a:hover,
.ft__contact a:hover,
.ft__off-links a:hover {
  color: #fff;
  text-decoration: underline;
  text-underline-offset: 4px;
  text-decoration-thickness: 1.5px;
}
.ft__contact a { font: var(--t-body-sm); color: var(--ft-txt); transition: color var(--dur-1) var(--ease-out); }

/* the foot of a column: the link that says how much is behind it. The count
   is rendered from the rows the database returned, never from a constant. */
/* `inline`, not `inline-flex`: some of these labels are long enough to wrap
   in a 237px column, and a flex line box puts the arrow at the far edge of
   the second line instead of after the last word. */
.ft__more {
  display: inline;
  margin-top: var(--s-5);
  font: var(--t-micro);
  color: rgba(255, 255, 255, .82);
  transition: color var(--dur-1) var(--ease-out);
}
.ft__links + .ft__more { display: inline-block; }
.ft__more:hover { color: #fff; text-decoration: underline; text-underline-offset: 4px; }
.ft__arw {
  display: inline-block;
  width: 14px; height: 14px;
  margin-left: var(--s-2);
  vertical-align: -2px;
}

.ft__sub { margin-top: var(--s-8); }

/* --- band C: the offices strip, restored (owner, 2026-09-20) -------------
   A band of its own again, between the link grid and the legal rule. One
   row of cells: Cleveland, New York, and the "Talk to a human" card. The
   card keeps its own panel ground, so the strip reads as three panels. */
.ft__offices {
  margin-top: var(--s-10);
  padding-top: var(--s-8);
  border-top: 1px solid var(--ft-line);
}
.ft__offices-grid {
  display: grid;
  gap: var(--s-7);
  grid-template-columns: minmax(0, 1fr);
}
.ft__offices .ft__card { margin-top: 0; }
/* the strip's head labels the whole band, so it takes no column rule */
.ft__offices-h { border-bottom: 0; padding-bottom: 0; margin-bottom: var(--s-5); }

.ft__office--sm + .ft__office--sm { margin-top: var(--s-5); }
.ft__off-city { font: var(--t-d4); color: #fff; }
.ft__off-city--sm { font: 600 15px/1.3 var(--font-display); }

/* The mega panel's "Talk to a human" card, recovered (owner, 2026-09-20).
   These are the .hdr-svc__card rules deleted in d11a699, unchanged except
   the ground: #0A2545 is one step off the footer's ink so the card reads as
   a panel rather than disappearing into it. */
.ft__card {
  margin-top: var(--s-7);
  padding: 18px;
  background: #0A2545;
  border-radius: var(--r-md);
}
.ft__card-kicker { font: var(--t-micro); letter-spacing: .14em; text-transform: uppercase; color: var(--gold); }
.ft__card-head { margin-top: 6px; font: 600 18px/1.3 var(--font-display); color: #fff; }
.ft__card-body { margin-top: 6px; font: 400 14px/1.5 var(--font-ui); color: rgba(255, 255, 255, .78); }
.ft__card-btn { display: flex; width: 100%; margin-top: 14px; }
.ft__card-tel {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-height: 44px;
  margin-top: 4px;
  font: 600 14px/1.2 var(--font-ui);
  color: var(--gold);
}
.ft__card-tel svg { width: 16px; height: 16px; }
.ft__addr {
  margin-top: var(--s-2);
  font: var(--t-body-sm);
  line-height: 1.62;
  font-style: normal;
  color: var(--ft-txt);
}
.ft__off-hours {
  margin-top: var(--s-2);
  font: var(--t-cap);
  line-height: 1.6;
  color: rgba(255, 255, 255, .62);
}
.ft__off-links { display: flex; flex-wrap: wrap; gap: var(--s-2) var(--s-6); margin-top: var(--s-3); }
.ft__off-links a {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  font: var(--t-micro);
  color: rgba(255, 255, 255, .82);
  transition: color var(--dur-1) var(--ease-out);
}

/* --- band D: the legal rule ---------------------------------------------
   Container-width, not full-bleed: an edge-to-edge rule reads as a boundary
   BETWEEN two masses, an inset one as a division WITHIN one. At 1.43:1 it is
   declared decorative (SPEC-09 §6.4): it bounds no control, conveys no state. */
.ft__legal {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: center;
  gap: var(--s-3) var(--s-6);
  margin-top: var(--s-9);
  padding-top: var(--s-5);
  border-top: 1px solid rgba(255, 255, 255, .12);
}
.ft__legal, .ft__legal a { font: var(--t-micro); color: rgba(255, 255, 255, .62); }
.ft__legal-links { display: flex; flex-wrap: wrap; gap: var(--s-5); }
.ft__legal a { transition: color var(--dur-1) var(--ease-out); }
.ft__legal a:hover { color: #fff; text-decoration: underline; text-underline-offset: 3px; }

/* 44x44 hit area via the anchor's own box, not a transform (SPEC-00 §7.4) */
.ft__social { display: flex; gap: var(--s-1); margin-left: -10px; padding-right: 96px; } /* clearance for the HubSpot chat launcher that sits bottom-right */
.ft__social a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px; height: 44px;
  color: rgba(255, 255, 255, .62);   /* 6.8:1 on --ft-ground */
  transition: color var(--dur-1) var(--ease-out);
}
.ft__social a:hover { color: #fff; }
.ft__social svg { width: 19px; height: 19px; }

/* --- FOOTER — 561px and up --- */
@media (min-width: 561px) {
  .ft__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* name + phone on row one, email and the button on row two: two rows of
     two cells, no orphan control (owner, 2026-09-20) */
  .ft__form { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* --- FOOTER — 768px and up (three columns; Utility sits beside the pillars)
   The three pillar columns fill row one and Industries + Utility take row
   two, so the services block still opens the index at this width. */
@media (min-width: 768px) {
  .ft__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s-10) var(--s-8); }
}

/* --- FOOTER — 1024px and up (brand and the call-back panel side by side) --- */
@media (min-width: 1024px) {
  .ft {
    padding-top: var(--s-13);
    padding-bottom: var(--s-10);
  }
  .ft__top {
    grid-template-columns: minmax(0, 1fr) minmax(360px, .82fr);
    gap: var(--s-10);
    /* the card is centred on the brand block rather than hung from its top
       edge: the two are now close enough in height for that to read */
    align-items: center;
  }
}

/* --- FOOTER — 1180px and up (five columns, one row) ---------------------
   Three pillar columns at 1.12fr each against two utility columns at 1fr:
   the services are the widest thing in the footer at the width where the
   reader can see all five at once, which is the point of the rebuild. */
/* Five link columns (owner, 2026-09-20): one per pillar, then Industries and
   one utility column carrying Resources with Company under it. Four fit at
   1024-1179, where the utility column wraps. */
@media (min-width: 1024px) { .ft__grid { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
@media (min-width: 1180px) {
  .ft__grid {
    grid-template-columns: repeat(4, minmax(0, 1.08fr)) minmax(0, 1fr);
    gap: var(--s-7);
  }
}
/* the offices strip: the two offices pair up at 768 with the card beneath
   them, and the card joins the row as a third cell from 1024. */
@media (min-width: 768px) {
  .ft__offices-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s-8); }
  .ft__offices-grid .ft__card { grid-column: span 2; }
}
@media (min-width: 1024px) {
  .ft__offices-grid {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) minmax(260px, .85fr);
  }
  .ft__offices-grid .ft__card { grid-column: auto; }
}

/* =========================================================================
   REDUCED MOTION  (SPEC-00 §6.7 — strict)
   Everything visible, nothing moves. Sticky and the header's background
   state change are retained; only their transitions are dropped.
   ========================================================================= */
/* ===== SPEC-VH-R3 PART B | per-section choreography, homepage ==============
   Four verbs, one per kind of thing, applied down the page: numerals count,
   rules draw, plates settle, copy rises. This block carries only what the
   recipes in B4 need that the shared .r classes cannot express on their own.
   Placed last so it wins the cascade over the section blocks it amends. */

/* --- band 2, trust strip: the hairline above the logo rail draws ---------- */
.trust__rule {
  display: block;
  height: 1px;
  margin-top: var(--s-8);
  background: var(--line);
}

/* --- band 3b, cycles: the sticky aside, and its travel guard (B2, B2.1) ---
   MEASURED TRAVEL, widest breakpoint the rule would apply at (1440, real
   content, header 81 + 24 = 105):

       ledger .cyc__list   481px
       aside  .cyc__aside  456px  (eyebrow + h3 = 171, figure = 285)
       travel = 481 - 456 - 105 = -80px

   At 1180: 569 - 399 - 105 = 65px. At 1024: 512 - 388 - 77 - 24 = 23px.
   Every one of those is far below B2's hard threshold of 240px, so per B2's
   own instruction the aside is NOT sticky and the band is a normal two-column
   block. A 65px stick reads as a layout bug, and B2.1 is explicit: do not
   force it.

   B2.1 measured 536px of travel here because it assumed a SEVEN-cycle ledger
   (892px). This band renders FOUR cycles (SPEC-04-R2 §6), which is 481px, and
   the premise does not survive the difference. The guard below is therefore
   written against the row count that actually clears 240px on this band
   rather than B2's illustrative four: at seven rows the ledger is ~841px and
   travel at 1440 is ~280px. The rule is left in place, self-disabled, so the
   band becomes sticky the day a fifth, sixth and seventh cycle are added and
   never before. Where :has() is unavailable the aside is simply static, which
   is the same correct layout it has today. */
@media (min-width: 1024px) {
  .cyc__grid:has(.cyc__list > :nth-child(7)) { align-items: start; }
  .cyc__grid:has(.cyc__list > :nth-child(7)) .cyc__aside {
    position: sticky;
    top: calc(var(--header-h) + var(--s-6));
    z-index: var(--z-rail);
  }
}

/* each row's 1px rule draws with the row. The border moves to a pseudo so it
   can scale; the row's own gold index mark does not move (B6). */
.cyc__item { position: relative; border-bottom: 0; }
.cyc__item::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 1px;
  background: var(--line);
  transform: scaleX(0);
  transform-origin: left;
}
.js .cyc__item.is-in::after,
html:not(.js) .cyc__item::after {
  transform: scaleX(1);
  transition: transform var(--dur-5) var(--ease-out) var(--rd, 0ms);
}

/* --- band 5, proof: the parallaxed frame may never expose an edge --------
   The image is 88px taller than its frame, i.e. 2 x --par-2, so the 44px
   travel in each direction stays inside the crop. */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) and (min-width: 1024px) {
    .pf__frame.par-2 picture { height: calc(100% + 88px); top: -44px; }
    .pf__frame.par-2 .pf__img { height: 100%; }
  }
}

/* --- reduced motion, the additions B7 names by class ---------------------- */
@media (prefers-reduced-motion: reduce) {
  .cyc__link:hover { transform: none !important; }
  .ins-row img { transform: none !important; }
  /* the rules rest at scaleX(0), i.e. invisible, so they must be forced to
     their drawn state rather than merely un-transitioned */
  .cyc__item::after { transform: scaleX(1) !important; }
  .trust__rule { transform: none !important; }
}

@media (prefers-reduced-motion: reduce) {
  .js .r, .js .r.is-in { opacity: 1 !important; transform: none !important; }
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    animation-delay: 0ms !important;
    transition-duration: .01ms !important;
    transition-delay: 0ms !important;
    scroll-behavior: auto !important;
  }
  .btn:hover, .card:hover, .ind-card:hover, .cyc__link:hover { transform: none !important; }
  .card img, .ins-row img { transform: none !important; }

  /* SPEC-VH-R3 A10 / B7 | the hero's whole load choreography is CSS animation,
     and most of it rests INVISIBLE (the pattern at opacity 0, the rail
     dividers at scale 0, the lines clipped). Killing the animations therefore
     has to be paired with the final state for every one of them, or the field
     ships blank. The count-up is already skipped in next.js §4, and the three
     numerals are server-rendered at their approved values, so nothing there
     needs an override. The film is never promoted at all under reduced motion
     (next.js §10), so the poster is the whole hero and the pause control is
     never unhidden: there is nothing moving to pause. */
  .hero *, .hero *::before, .hero *::after { animation: none !important; }
  .hero__pattern            { opacity: 1 !important; }
  .hero h1 .ln              { clip-path: none !important; transform: none !important; }
  .rail__cell + .rail__cell::before { transform: none !important; }
  .hero .eyebrow, .hero .eyebrow .tick, .hero .lead,
  .hero__actions, .hero__rail { opacity: 1 !important; transform: none !important; }
  /* the cue segment parks at the foot of its rail rather than at the head */
  .rail__cue-seg { transform: translateY(28px) !important; }

  /* SPEC-02 §7.3 — a 58s loop collapsed to .01ms would freeze the track at
     translate3d(-50%), i.e. half the logos off-screen. Unwind it into a
     designed static state: all 14 in a centred wrap, no mask, no clipping. */
  /* owner ruling 2026-09-20: keep the slide, halve the speed (see .lgrail) */
  .trust__track { animation-duration: 116s !important; animation-iteration-count: infinite !important; }

  /* SPEC-03 §7.5 — reduced motion collapses the transition, not the state, so
     the drawn elements must be forced to their FINAL state or they vanish. */
  .rail__list::before       { transform: none !important; }
  .rail__item::before       { transform: scale(1) !important; opacity: 1 !important; }
  /* the pillar cards lift with `translate`, so that is what has to be pinned;
     .r owns their transform and the global .r rule above already lands it. */
  .pillar                   { translate: none !important; }

  /* SPEC-04-R2 §8. The connector and its four nodes went with the cards. What
     is left resting invisible is the four rows (opacity 0 under .js), the gold
     band mark (scaleX 0) and the gold row edge (scaleY .4). Every state change
     survives; only the movement is dropped. The row edge is gated by opacity,
     so forcing scaleY(1) here cannot reveal all four at once. */
  .js .cyc__item      { opacity: 1 !important; animation: none !important;
                        transform: none !important; }
  .cyc__inner::before { transform: none !important; }
  .cyc__link::after   { transform: scaleY(1) !important; }
  .cyc__slide img     { transform: none !important; }

  /* SPEC-04 §7.4 — the teaser rule is the critical one: without it eleven
     teasers stay at 0fr / opacity 0 for the users most likely to need them. */
  .ind-card__img    { transform: none !important; }
  .ind-card__teaser { grid-template-rows: 1fr !important; opacity: 1 !important; }
  .ind__set         { scroll-snap-type: none !important; }

  /* SPEC-05 §7.5 */
  .link-arrow--on-dark svg { transform: none !important; }

  /* the arc rests at a full dash offset, i.e. invisible, so it must be forced
     to its drawn state. The .r class already restores the element itself;
     this restores what is inside it. The CTA band's twin arc was deleted by
     SPEC-09-R2, so .pf__arc is the only one left. */
  .pf__arc circle { stroke-dashoffset: 0 !important; }

  /* SPEC-07-R2 §7: the hover STATES stay, only their interpolation is
     dropped. Nothing in this section rests invisible. */
  .ins-row::before,
  .ins-row__chip,
  .ins-row__num,
  .ins-row__link { transition: none !important; }

  /* SPEC-06-R2 §6 needs NO addition. The rail whose marker rested at
     scaleY(0) is gone with the stage, and the wall's four cards rest in their
     final state at every width: the global .r rule above lands them, and
     nothing else in the section moves at all. .tq below is case-study-next's
     copy of the old block and its two transitions are colour only. */
  .tq__mark, .tq__cite  { transition: none !important; }

  /* SPEC-09-R2 §9: the media settle is the section's only non-.r motion. */
  .js .cta__media img { transform: none !important; transition: none !important; }

  /* SPEC-07 §6.4 needs NO addition: .ins-card::before also rests at scaleX(0),
     but resting-invisible is its correct state — it is a hover response, not
     content, and the colour change plus underline carry the state alone.

     SPEC-08 §6 and SPEC-09 §7 need NO addition either, and the reason is
     worth recording. Every animated thing in the last two sections rests in
     its INFORMATIVE state, not its hidden one: the maps rest duotone and
     bloom to colour, so collapsing the transition makes the bloom instant
     rather than absent; the note chevron rests un-rotated and [open] still
     rotates it; the footer never moved. Nothing in either section is
     invisible, unreachable, or frozen mid-state under reduced motion.

     The office switcher inherits that property. off-in is an entrance with
     `both`, so the global animation-duration collapse above lands the pane
     on its end state instantly instead of removing it, and the switch is a
     hard cut. Nothing else in .off moves.

     SPEC-08-R2 §10 adds exactly two: the indicator slide is a transform, so
     it is stated explicitly rather than left to the global collapse, and the
     pane swap is pinned to a hard cut. */
  .off__ind { transition: none !important; }
  .js .off__pane.is-on { animation-duration: 1ms !important; }
}


/* =========================================================================
   MOBILE-QA-2026-09-20 — the phone pass.

   Appended as ONE block on purpose. Every rule below is a correction found
   by a Playwright sweep of nineteen templates at 360, 390, 414, 768 and 820
   with touch emulation and an iPhone UA; keeping them together means the
   whole mobile pass reads as one decision and reverts as one commit.

   Two things this block does NOT do. It does not redesign: no rule changes
   a grid, a colour or an order. And it does not touch the footer (.ft*),
   which was being rebuilt in parallel; the footer's own mobile findings are
   recorded in design/r4/MOBILE-QA-2026-09-20.md for a follow-up.
   ========================================================================= */
@media (max-width: 767px) {

  /* --- 1. the type floor ------------------------------------------------
     The audit found no body copy under 16px, but it found thirty-odd meta
     labels at 11 and 12px: eyebrows, chip labels, table headings, form
     hints. 12px uppercase at .14em tracking is legible on a 27" monitor and
     is not on a phone held at arm's length, and the owner's floor for meta
     text is 13px.

     Almost all of it flows from two tokens, so two declarations fix it
     everywhere at once rather than thirty selectors drifting apart later.
     The line-heights are raised with the sizes so the blocks keep the same
     optical density; the letter-spacing stays, because it is what makes
     these read as labels rather than as sentences. */
  :root {
    --t-micro:   600 13px/1.4 var(--font-ui);
    --t-eyebrow: 600 13px/1.25 var(--font-display);
  }
  /* the literal sub-13px values in this file that the tokens do not reach */
  .trust__note      { font-size: 13px; }
  .pf__panel-label  { font-size: 13px; }
  .svc-cta__chip-l  { font-size: 13px; }
  .svc-cta__nexth   { font-size: 13px; }

  /* --- 2. form fields: 16px, which is not a style choice -----------------
     Mobile Safari zooms the whole page when a field with a computed
     font-size under 16px takes focus, and it does not zoom back out. Every
     text input, select and textarea on the site sat at 15px, so tapping the
     contact form, the guide gate or a calculator select threw the reader
     into a zoomed viewport they then had to pinch out of. 16px is the
     threshold; the 48px min-height and the padding are unchanged, so the
     fields grow by a pixel of line box and nothing else moves. */
  .field__input,
  .field__select,
  .field__textarea { font-size: 16px; }

  /* --- 3. hit area, bought with padding --------------------------------
     Every target below was already in the right place and the right size
     visually. What it lacked was the 44x44 the platform guidelines ask for,
     so each one gains padding or a min-height and NOTHING gains a new
     position, margin or width. */

  /* the small button is the site's most-tapped control on a phone: it is
     the header's "Book a call". 40 -> 44 via 2px of padding each side. */
  .btn--sm { min-height: 44px; padding: 12px 18px; }
  .hdr__cta { min-height: 44px; }

  /* the brand mark is a 30px image in a 64px bar, so the anchor collapsed
     to 30x30. The padding opens the anchor to 44x44 and the equal negative
     margin puts the MARK back where it was, to the pixel: the logo does not
     move, the tappable box around it does. */
  .hdr__brand {
    padding: 7px;
    margin: -7px;
    min-width: 44px;
    min-height: 44px;
    justify-content: center;
  }

  /* the skip link is only ever seen by a keyboard or switch user, and for a
     switch user it is a target like any other. 43 -> 44. */
  .skip { display: inline-flex; align-items: center; min-height: 44px; }

  /* the contact rail's phone number: 26px of cap height, on its own line,
     so vertical padding is free and the negative top margin holds the gap
     to the label above at the value SPEC-08 set. */
  .ct__tel {
    padding-block: 9px;
    margin-top: calc(var(--s-2) - 9px);
  }
  .ct__mail { min-height: 44px; align-items: center; }

  /* the drawer repeats the brand mark in its own bar, at the same 30px and
     with the same collapsed anchor. Same correction, and the -10px margin
     mirrors .hdr-drawer__x on the other end of the bar so the two controls
     stay optically flush with the drawer's padding. */
  .hdr-drawer__brand {
    display: flex;
    align-items: center;
    min-width: 44px;
    min-height: 44px;
    padding: 7px;
    margin-left: -7px;
  }
}

/* --- the HubSpot widget vs the drawer -----------------------------------
   The chat container is injected at z-index 2147483647, which beats every
   layer this stylesheet defines, so the proactive greeting card sits ON TOP
   of the open navigation drawer: it covers three service rows and half the
   drawer's "Book a call" button on a 390px screen. The drawer is a modal
   surface and nothing may float over it.

   The container is a same-origin <div> (only the iframe inside it is
   cross-origin), so it can be hidden from here. :has() on <html> is the
   only way to reach it, because the widget is a sibling of <header>, not a
   descendant of anything the drawer marks. The rule is unscoped by width on
   purpose: .hdr-drawer.is-on only ever exists while the drawer is open, and
   the drawer only exists below 1180. */
html:has(.hdr-drawer.is-on) #hubspot-messages-iframe-container {
  display: none !important;
}


/* =========================================================================
   MOBILE-QA-2026-09-20, part two — the footer.

   Held back from the first pass because the footer was being rebuilt in
   parallel; these are the §5.2 findings of design/r4/MOBILE-QA-2026-09-20.md,
   applied now that it has landed.

   EVERY RULE IS INSIDE max-width: 767. The desktop footer -- the call-back
   card, the five-column index, the offices strip, the legal rule -- is
   byte-for-byte what it was, which is the condition this work was given.
   ========================================================================= */
@media (max-width: 767px) {

  /* --- 1. the call-back form zoomed mobile Safari ----------------------
     Same defect §3.1 fixed everywhere else, and the worst instance of it,
     because this form is on EVERY page of the site: three fields at 14px,
     each one zooming the viewport on focus with no way back.

     The height must not move -- the submit button shares the last field row
     and sits on the email input's baseline, and a taller input would break
     that alignment. 16px at 1.25 is a 20px line box; with 9px of padding
     and the 1px borders that is 40px of content, so the existing
     min-height: 44px still governs and the row is unchanged at 44. */
  .ft__input {
    font-size: 16px;
    line-height: 1.25;
    padding-block: 9px;
  }

  /* --- 2. hit area ------------------------------------------------------
     The rebuilt index carries fifty-odd links in five columns, all of them
     18px-tall inline anchors. On a phone that is a column of text you aim
     at rather than a list you tap.

     Each link becomes a 44px row: `display: block` so the target is the
     full column width and not just the glyphs, plus 13px of padding. The
     12px inter-row margin goes, because padding has replaced it -- without
     that the columns would gain 12px per row on top of the 26px and the
     footer would run away. Net effect per row: 30px -> 44px. */
  .ft__links a { display: block; padding-block: 13px; }
  .ft__links li + li { margin-top: 0; }

  /* the phone number and the email in the brand column, both already on
     their own line, so padding is free */
  .ft__contact li { display: block; }
  .ft__contact a { display: block; padding-block: 11px; }
  /* the tel is .ft__contact a too, and a bare .ft__tel loses to it on
     specificity, so it is qualified. 22.1px line box + 24 = 46. */
  .ft__contact .ft__tel { padding-block: 12px; }

  /* The column heading is a link ("Resources", "Company"). Padding alone
     would push the heading's underline rule away from the text and change
     the band's rhythm, so the padding is cancelled by an equal negative
     margin: the heading box, the rule and the spacing below are identical,
     and only the touch area grows. Same technique as .hdr__brand. */
  .ft__h-link {
    display: inline-block;
    padding-block: 14px;
    margin-block: -14px;
  }

  /* the offices strip's "Get directions" / "Call" pair, and the legal row */
  .ft__off-links a { min-height: 44px; }
  .ft__legal-links a { display: block; padding-block: 13px; }
  .ft__legal-links { gap: 0 var(--s-5); }

  /* .ft__more is the column foot ("All 111 services ->"); inline-block
     already, so it only needs the padding. */
  .ft__links + .ft__more { padding-block: 12px; }
}
