/* =============================================================================
   Austra Digital v12 — layout/type system measured from the licensed Framer
   reference, recoloured to Austra cobalt, plus the v12 motion/interaction layer
   specified in docs/ANIMATION-BRIEF-v12.md.

   Rules held throughout:
     · every pre-animation hidden state is gated behind .js-enabled, so with JS
       disabled the page renders fully visible
     · .js-fallback (set by the <head> net) force-unhides everything
     · prefers-reduced-motion collapses every sequence to its final visible state
     · no animation of width/height/top/left during scroll — transform+opacity only
   ========================================================================== */

/* Self-hosted Archivo (variable, latin subset). Single file covers 300–800;
   no Google Fonts dependency — fully offline. */
@font-face {
  font-family: 'Archivo';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('fonts/archivo.woff2') format('woff2');
}

:root {
  /* surfaces */
  --bg:        #fbfbfc;
  --card:      #ffffff;
  --band:      #f1f2f4;
  --ink:       #131519;   /* deep charcoal — CTA band */
  /* text */
  --text:      #24272d;
  --muted:     #4e535b;
  --tertiary:  #666c76;
  /* lines + accent
     v15 keeps v14's monochrome discipline — labels stay silver-grey — and gives
     ONE colour to the things that mean "act" or "active": CTAs, prices, ticks,
     the rail, active states.

     Emerald is applied as a GRADIENT on surfaces (buttons, badges, dots, rails)
     and as a SOLID on text, because a gradient on text makes contrast
     unverifiable. --accent is the solid; --grad-* are the surface stops. */
  --hairline:  #d6d9de;
  --accent:    #3d444e;   /* brushed steel — 9.5:1 on the page, 8.8:1 on band sections */
  --accent-dk: #2f353d;
  --accent-mid:#5a616c;   /* silver-grey, for labels — unchanged from v14 */

  /* SILVER FOLLOWS THE GOLD INVERSION, NOT THE COBALT RULE.
     Polished metal is a LIGHT surface, so it cannot carry white text — it
     carries a near-black label. And silver as TEXT is impossible: a darkened
     silver is just grey, so the solid becomes the brushed steel above.

     v17b lifts the whole ramp. The material still comes from --metal-inset,
     not from colour, but the face now sits close to the page at the lit corner
     and only separates at the shaded one. The SILHOUETTE is carried by the
     bottom stop plus the cast shadow — not by darkening the face.

     Stops vs --on-metal: #f8fafc = 17.3 · #edf1f6 = 16.0 · #cfd6e0 = 12.4.
     Composited under the label, worst case in any state: 13.0:1.
     Darkest composited face pixel vs the page: 1.63:1, down from 2.11:1 in
     v17a — lighter, but still a defined object. An earlier attempt that ENDED
     light (~1.06:1 everywhere) dissolved into the page; this one only STARTS
     light. That is the whole difference. */
  --on-metal:  #14161a;   /* the label colour on any metal surface */
  --gem-lit:   #ffffff;   /* brightest polish — decoration only (hero rail) */
  --grad-a:    #f8fafc;   /* lit corner    — 17.3:1 vs --on-metal */
  --grad-m:    #edf1f6;   /* body          — 16.0:1 */
  --grad-b:    #cfd6e0;   /* shaded corner — 12.4:1, and 1.42:1 vs the page */
  --grad:      linear-gradient(135deg, var(--grad-a) 0%, var(--grad-m) 48%, var(--grad-b) 100%);

  /* Hover brightening, painted on ::after and cross-faded with OPACITY —
     background-image does not interpolate, and opacity is the one property
     this build is happy to animate. Directional, so the shaded corner (and
     therefore the silhouette) survives the hover. */
  --metal-sheen: linear-gradient(135deg,
                   rgba(255, 255, 255, .62) 0%,
                   rgba(255, 255, 255, .22) 46%,
                   rgba(255, 255, 255, 0)  100%);

  --metal-edge:rgba(20, 22, 26, .16);   /* the cut edge — used by .pc-badge */
  --metal-back:#aab2be;   /* fallback body only; 8.5:1 vs --on-metal if ::before
                             ever fails to paint. No longer shows as the rim. */
  --metal-rim: rgba(72, 80, 92, .55);   /* the pressed rim. A rim reads as
                             pressed because it is darker THAN THE FACE, not
                             because it is near-black. Steel, not #3a414b. */

  /* THE MATERIAL. All in em so .btn-sm and .drawer-cta get a proportionally
     smaller bevel. Two dark layers pulled back (.22->.13, .20->.12) and two
     white layers pushed up (.30->.42, .28->.34) — the dome survives, the
     weight does not. */
  --metal-inset:
    -0.05em -0.05em 0.05em 0    inset rgba(20, 22, 26, .13),
     0      0      0.05em 0.2em inset rgba(255, 255, 255, .42),
     0.025em 0.05em 0.1em  0    inset rgba(255, 255, 255, 1),
     0.12em  0.12em 0.12em      inset rgba(255, 255, 255, .34),
    -0.075em -0.22em 0.22em 0.08em inset rgba(20, 22, 26, .12);

  /* PRESSED. Layer 1 is a spread-only inset ring — it draws the rim by
     REPAINTING, not by moving geometry. So `inset` never animates (the file's
     own header rule forbids animating top/left), border-radius draws and
     antialiases the curve itself, and the em unit still scales it. This
     replaces BOTH the reference's clip-path and v17a's inset:-1px->1px. */
  --metal-inset-in:
     0       0      0      0.075em inset var(--metal-rim),
     0.09em  0.13em 0.10em 0       inset rgba(20, 22, 26, .28),
    -0.03em -0.04em 0.07em 0.02em  inset rgba(20, 22, 26, .22),
     0.20em  0.20em 0.20em 0       inset rgba(20, 22, 26, .16),
     0       0      0.05em 0.42em  inset rgba(255, 255, 255, .22),
    -0.075em -0.12em 0.20em 0.10em inset rgba(20, 22, 26, .12);
  --metal-lite:#d8dee6;   /* silver on charcoal — 13.5:1 on the CTA band */
  /* facet colours for the .gem field — LIGHT-SURFACE set (must be darker than
     the page or they vanish). Flipped to a light set on the charcoal band. */
  --gf-a:      #c0c7d1;
  --gf-b:      #9aa3ae;
  --gf-c:      #6f7784;
  --silver:    #c3c9d1;
  --silver-lt: #e7eaee;

  /* radii */
  --r-card:  40px;
  --r-round: 99px;

  /* rhythm */
  --container: 1440px;
  --pad:       96px;
  --sec-gap:   72px;
  --grid-gap:  20px;
  --lift:      48px;      /* section overlap */

  /* INTERACTIVE BOUNDARIES — one ramp, three steps, mirroring the primary's
     rest -> hover -> press escalation. Every value clears 3:1 (WCAG 1.4.11) on
     all three light surfaces the page uses. --hairline (1.37:1 on the page,
     1.26:1 on --band) stays DECORATIVE and is never a control boundary again.

       --edge     3.36 page · 3.47 card · 3.10 band
       --edge-on  4.56 page · 4.72 card · 4.21 band
       --accent   9.51 page · 9.83 card · 8.78 band

     #8b919b (v14's value) was retired from control borders: it measures 2.83:1
     on --band, and .btn-ghost and .faq-ico both sit on --band. */
  --edge:    #848a94;
  --edge-on: #6d747e;

  /* The focus ring colour is a VARIABLE so dark surfaces can re-point it.
     --accent is 9.51:1 on the page but 1.86:1 on the footer ink — a focus
     indicator you cannot see is not a focus indicator. */
  --focus:   var(--accent);

  /* ---- SHADOW TEST (v17e) — REVERT BY SETTING THESE FOUR BACK ---------------
     was: --sh-a .30  --sh-b .34  --sh-hover .40  --sh-lift .26
     Deeper hover shadow on light sections, and the same shape inverted to WHITE
     on dark ones — on --ink a black shadow is invisible, so a raised object on
     the CTA band and the footer had no lift at all. Every consumer reads these
     tokens, so this whole experiment is four numbers wide. */
  --sh-a:      .42;
  --sh-b:      .48;
  --sh-hover:  .58;
  --sh-lift:   .36;
  --sh:        rgba(20, 30, 70, var(--sh-b));
  --sh-tight:  rgba(20, 30, 70, var(--sh-a));
  --sh-deep:   rgba(20, 30, 70, var(--sh-hover));
  --sh-ghost:  rgba(20, 30, 70, var(--sh-lift));

  /* motion */
  --e-out:  cubic-bezier(.22, 1, .36, 1);
  --e-back: cubic-bezier(.34, 1.4, .64, 1);

  /* ---- MARKER FAMILY -------------------------------------------------------
     Every circular marker on this site is one of two things: a STATE DOT with
     nothing inside it, or a MARK DISC that carries a glyph. Before this there
     were 8 diameters, 5 ring specs, 7 transition lists and 5 "active" scales
     across 14 markers. Sizes still differ by role — a 1px-rail dot and a 64px
     numbered token are not the same object — but the GRAMMAR is now shared:
     one radius, one ring rule, three states, one active increment.

     Tier A states:  idle = hairline ring, no fill
                     done = filled silver, silver ring
                     live = metal face + accent ring + halo (an ::after, so it
                            is revealed by OPACITY and can therefore animate) */
  --dot-xs:    8px;    /* inline with text: .status-pill .dot, .ts-dot        */
  --dot-sm:    9px;    /* on the hero rail: .fn-dot — 9 keeps top:3 centred
                          on the 1px rail at 7.5px. Do not change to 10.      */
  --dot-md:    14px;   /* sequence nodes: .sn-dot, .badge-core                */
  --mark-md:   22px;   /* glyph discs: .tick, .cmp-x                          */
  --dot-ring:      2px;     /* >= 12px */
  --dot-ring-thin: 1.5px;   /* <  12px */
  --dot-idle:  var(--hairline);
  --dot-done:  #aab2bd;
  --dot-mute:  #e3e6ea;     /* the recede, on decorative graphics only        */
  --dot-pop:   1.15;   /* tier A */
  --mark-pop:  1.08;   /* tier B — 15% of 22px shoves the baseline, 15% of 8px
                          does not. Equal visual increment, not equal factor. */
  --dot-t: box-shadow .3s linear, background-color .3s linear,
           transform .3s var(--e-back);

  /* The ONLY two hues on this site, and they exist for exactly one component:
     the x/tick comparison, where a red and a green are adjacent and opposed.
     Chosen for LIGHTNESS as much as hue — #a3453b sits at --tertiary's
     greyscale value and #1c5340 at --accent's, so desaturating the page
     preserves the existing "left column is lighter" hierarchy (1.48:1 grey).
     Measured: x 5.07:1 on its disc, tick 7.30:1 on its disc; both >= 3:1 for
     WCAG 1.4.11 and both >= 4.5:1 if ever used as text.
     NOTE: for a protanope these two are 1.11:1 — the SAME COLOUR. The glyph
     shape carries the meaning; colour is a bonus for those who can see it.
     Never reduce these marks to two coloured dots. */
  --mark-no:  #a3453b;
  --mark-yes: #1c5340;
}

*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: 'Archivo', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
  font-size: 18px;
  font-weight: 300;
  line-height: 1.5;
}

img, svg { max-width: 100%; }

h1, h2, h3, h4, h5 { margin: 0; font-weight: 600; letter-spacing: normal; }
/* h1-h5 set margin, weight and tracking but never line-height, so 33 heading
   elements were inheriting body's 1.5 — a 34px heading carrying 51px of leading.
   Specificity (0,0,1), so any component that already decided still wins. */
h3, h4, h5 { line-height: 1.2; }
p { margin: 0; }
ul, ol { margin: 0; padding: 0; list-style: none; }
a { color: inherit; text-decoration: none; }

/* The skip link was a metal surface with no material — background: var(--grad)
   and no shadow, so it read as a flat swatch rather than the same object as the
   button. The bevel is in em and body font-size is 18px, so it scales for free. */
.skip {
  position: absolute; left: -9999px; top: 0; z-index: 200;
  background-image: var(--grad);
  background-color: var(--metal-back);
  color: var(--on-metal);
  padding: 12px 20px; border-radius: 0 0 var(--r-round) 0;
  box-shadow: var(--metal-inset),
              0 0.40em 0.90em -0.55em var(--sh);
}
.skip:focus { left: 0; }   /* :focus, not :focus-visible — a skip link must show
                              for every focus path, and is never mouse-focused. */

/* Focus ring. Drawn 3px OUTSIDE the control, so on .btn-primary it lands on the
   page background, never on the metal face. outline follows border-radius, so
   pills get a pill ring with no clip-path. */
:focus-visible { outline: 2px solid var(--focus); outline-offset: 3px; }

/* Arriving at /#how from another page, the browser's native hash jump lands the
   section at 0 — underneath the 112px fixed header. Clicking the same link
   in-page, Lenis lands it at 112. Same anchor, two different results depending
   on how you got there. */
section[id], .cta-band[id], main[id] { scroll-margin-top: 112px; }

/* =============================================================================
   MARKER FAMILY — the shared recipe. Tokens live in :root.
   ========================================================================== */
.status-pill .dot, .ts-dot, .hero-node, .sn-dot, .badge-core {
  border-radius: var(--r-round);
  background: var(--dot-fill, transparent);
  box-shadow: inset 0 0 0 var(--dot-ring, 2px) var(--dot-line, var(--dot-idle));
  transition: var(--dot-t);
}
.status-pill .dot, .ts-dot { position: relative; display: inline-block; flex: none; }

/* THE LIVE OVERLAY. One rule, one property: "lit" is opacity 1, everywhere.
   A background-image cannot be interpolated, so the metal face has to live on
   a pseudo if any state is ever going to animate — and on the hero rail it is. */
.status-pill .dot::after, .ts-dot::after, .hero-node::after,
.sn-dot::after, .badge-core::after {
  content: ''; position: absolute; inset: 0;
  border-radius: inherit; pointer-events: none;
  background-image: var(--grad);
  box-shadow: inset 0 0 0 var(--dot-ring, 2px) var(--accent),
              0 0 0 3px rgba(61, 68, 78, .13);
  opacity: 0;
  transition: opacity .3s linear;
}
.status-pill .dot, .ts-dot { width: var(--dot-xs); height: var(--dot-xs); --dot-ring: var(--dot-ring-thin); }
.sn-dot, .badge-core       { width: var(--dot-md); height: var(--dot-md); }

/* Dark surfaces re-point the token. --metal-lite is 13.50:1 on the footer ink.
   .qform is a WHITE panel nested inside .cta-band, so it must reset to --accent
   (--metal-lite would be 1.35:1 on white). Inheritance handles the nesting. */
.footer, .cta-band, #system { --focus: var(--metal-lite); }
.qform, .form-done { --focus: var(--accent); }

/* ---------- layout primitives ---------- */
.container {
  max-width: var(--container);
  margin: 0 auto;
  padding-left: var(--pad);
  padding-right: var(--pad);
}

.section { padding: var(--sec-gap) 0; position: relative; }

/* Layered section transition: each section rises over the previous one with
   rounded top corners. Backgrounds alternate so the seam is visible. */
.sec-lift {
  border-radius: var(--r-card) var(--r-card) 0 0;
  margin-top: calc(var(--lift) * -1);
  padding-top: calc(var(--sec-gap) + var(--lift));
  background: var(--bg);
  z-index: 1;
}
#system, #process, #team, #faq { background: var(--band); }

/* ---------- type scale ---------- */
/* Was a flat 42px in an otherwise fluid file, which made the page's h1 SMALLER
   than its h2 from 621px to 1167px (ratio 0.81 at 621-900), with an 89% jump
   across the single pixel between 620 and 621. Fluid, so the ladder holds at
   every width. -0.015em tracking re-wraps nothing (verified at 10 viewports —
   negative tracking can only remove wraps, never add them). */
h2 { font-size: clamp(30px, 2.9vw, 42px); font-weight: 600; line-height: 1.05; letter-spacing: -.015em; text-transform: uppercase; }

.eyebrow {
  font-size: 18px; font-weight: 300; line-height: 1.5;
  color: var(--accent-mid); margin-bottom: 14px;
}
.lead { font-size: 18px; font-weight: 300; line-height: 1.5; color: var(--muted); }

.sec-head { max-width: 940px; margin-bottom: 44px; }
.sec-lead { margin-top: 18px; max-width: 68ch; }

/* ---------- buttons ---------- */
.btn {
  position: relative; z-index: 0;
  display: inline-flex; align-items: center; gap: 9px;
  font-size: 18px; font-weight: 400; line-height: 1;
  padding: 18px 30px;
  border-radius: var(--r-round);
  border: 1px solid transparent;
  cursor: pointer;
  transition: background-color .3s var(--e-out), color .3s var(--e-out),
              border-color .3s var(--e-out), transform .35s var(--e-out),
              box-shadow .35s var(--e-out);
}
.btn-primary {
  isolation: isolate;
  background: var(--metal-back);       /* fallback body only — never seen */
  color: var(--on-metal);
  border-color: transparent;
  font-weight: 500;
  /* the site's shadow family: blue-tinted, wide blur, heavy negative spread.
     In em so .btn-sm scales with it. This is .review's shadow, sized down. */
  box-shadow: 0 0.06em 0.10em -0.045em var(--sh-tight),
              0 0.40em 0.90em -0.55em  var(--sh);
  /* --e-back for the lift (matches .sn-dot / .review), --e-out for the shadow,
     linear for colour. No `ease` — it appears nowhere else in this file. */
  transition: transform  .35s var(--e-back),
              box-shadow .35s var(--e-out),
              color      .3s  linear;
}
/* THE FACE. -1px, not 0: an absolutely positioned pseudo resolves inset
   against the PADDING box, so at inset:0 the .btn 1px border leaks the body.
   inset is now CONSTANT in every state — the press is box-shadow only. */
.btn-primary::before {
  content: ""; position: absolute; inset: -1px; z-index: -1;
  border-radius: inherit;
  background-image: var(--grad);
  box-shadow: var(--metal-inset);
  transition: box-shadow .35s var(--e-out);
}
/* THE SHEEN. The third layer, free on .btn. Opacity only. */
.btn-primary::after {
  content: ""; position: absolute; inset: -1px; z-index: -1;
  border-radius: inherit; pointer-events: none;
  background-image: var(--metal-sheen);
  opacity: 0;
  transition: opacity .35s linear;
}
/* HOVER = lift and brighten, like every other hover on this site. The cast
   shadow GROWS (cards gain shadow on hover here; nothing loses it). Geometry
   does not change, so there is no jitter and no disagreement with .arw, which
   is also moving outward at .38s var(--e-out). */
.btn-primary:hover, .btn-primary:focus-visible {
  transform: translateY(-2px);
  box-shadow: 0 0.06em 0.10em -0.045em var(--sh-ghost),
              0 0.72em 1.45em -0.60em  var(--sh-deep);
}
.btn-primary:hover::after, .btn-primary:focus-visible::after { opacity: 1; }

/* .btn:active MUST stay ABOVE .btn-primary:active — identical specificity, so
   the later rule wins. Having it below silently killed the primary's press. */
.btn:active { transform: scale(.985); box-shadow: none; }

/* ACTIVE = the press, which is where the skeuomorphism belongs. The face takes
   the pressed stack including its rim ring, the lift is given back, and the
   button settles onto the page. .985 matches .btn — one press speed for both
   buttons sitting together in .hero-cta. .12s because a press under the finger
   is a state change, not a transition. */
.btn-primary:active {
  transform: translateY(0) scale(.985);
  box-shadow: 0 0.045em 0.09em -0.03em var(--sh);
  transition-duration: .12s;
}
.btn-primary:active::before { box-shadow: var(--metal-inset-in); transition-duration: .12s; }
.btn-primary:active::after  { opacity: .3;                       transition-duration: .12s; }
.btn-sm { font-size: 16px; padding: 12px 22px; }
.btn-block { width: 100%; justify-content: center; }

/* SECONDARY — the primary's gesture, one volume down.
   The old hover filled solid var(--text): a 14.5:1 slab standing next to a metal
   face whose darkest pixel is 1.63:1 against the page. The secondary action became
   the highest-contrast object on the screen, so the eye went to "See client work"
   instead of the primary CTA. And the old REST border was --hairline at 1.37:1 —
   below the 3:1 that identifies a control at all, which is WHY hover had to shout.
   Separation is now by MATERIAL, not gesture: the primary is the only filled
   object with a cast shadow, in every state. */
.btn-ghost {
  background: transparent;
  color: var(--text);
  border-color: var(--edge);      /* 3.36 page · 3.47 card · 3.10 band */
  overflow: hidden;
  transition: color .3s linear, border-color .3s linear,
              transform .35s var(--e-back), box-shadow .35s var(--e-out);
}
/* border-radius on the pseudo, not just overflow:hidden — the curve is then drawn
   and antialiased by border-radius (no clip-path, no rectangular overflow clip). */
.btn-ghost::before {
  content: ''; position: absolute; inset: 0; z-index: -1;
  border-radius: inherit;
  background: rgba(61, 68, 78, .09);
  transform: scaleX(0); transform-origin: left center;
  transition: transform .4s var(--e-out), box-shadow .35s var(--e-out);
}
/* label 12.54:1 on the composited fill · border 8.24:1 · fill vs page 1.15:1 */
.btn-ghost:hover, .btn-ghost:focus-visible {
  border-color: var(--accent);
  transform: translateY(-2px);
  box-shadow: 0 0.30em 0.60em -0.55em var(--sh-ghost);
}
.btn-ghost:hover::before, .btn-ghost:focus-visible::before { transform: scaleX(1); }

/* MUST declare transform explicitly and MUST stay below :hover. .btn:active is
   further up the file at identical specificity, so a :hover transform with no
   matching :active transform silently kills the press — the same trap that killed
   .btn-primary:active in v17a. */
.btn-ghost:active {
  transform: translateY(0) scale(.985);
  border-color: var(--accent-dk);
  box-shadow: none;
  transition-duration: .12s;
}
.btn-ghost:active::before {
  transform: scaleX(1);
  box-shadow: inset 0 .09em .16em -.02em rgba(20, 22, 26, .18);
  transition-duration: .12s;
}

/* DISABLED — main.js sets #formSubmit.disabled during submit, and there was NO
   disabled style at all: measured live, the button kept cursor:pointer, the full
   metal face, the full cast shadow and a 12.38:1 label — identical to armed.
   opacity is deliberately NOT used: this build's verification counts text below
   opacity 1, and dimming a label is what the five safety nets exist to prevent.
   The light goes out of the MATERIAL instead. Label 12.38 -> 6.54:1, still well
   over AA, and main.js swaps it to "Sending…" so it says so in words too. */
.btn:disabled, .btn[aria-disabled="true"] { cursor: default; }
.btn:disabled:hover, .btn:disabled:active,
.btn[aria-disabled="true"]:hover { transform: none; }

.btn-primary:disabled,
.btn-primary:disabled:hover,
.btn-primary:disabled:focus-visible,
.btn-primary:disabled:active {
  color: var(--muted);
  background: #e9ecf1;
  box-shadow: none;
  transform: none;
}
.btn-primary:disabled::before {
  background-image: none;
  background-color: #e9ecf1;
  box-shadow: 0 0 0 1px inset var(--silver);
}
.btn-primary:disabled::after,
.btn-primary:disabled:hover::after { opacity: 0; }
.btn-primary:disabled .arw::before { transform: none; opacity: 1; }
.btn-primary:disabled .arw::after  { opacity: 0; }

/* arrow swap: existing arrow exits upper-right, replacement enters lower-left */
.arw {
  position: relative; display: inline-block; flex: none;
  width: 1.05em; height: 1.25em; overflow: hidden; line-height: 1.25em;
  font-size: 1em;
}
.arw::before, .arw::after {
  content: '\2192';
  position: absolute; inset: 0;
  text-align: center; line-height: 1.25em;
  transition: transform .38s var(--e-out), opacity .3s linear;
}
.arw::after { transform: translate3d(-110%, 110%, 0); opacity: 0; }
.btn:hover .arw::before, .btn:focus-visible .arw::before { transform: translate3d(110%, -110%, 0); opacity: 0; }
.btn:hover .arw::after,  .btn:focus-visible .arw::after  { transform: none; opacity: 1; }
.arw-sm { font-size: .95em; }

/* =============================================================================
   NAV — transparent rounded shell → translucent blur, hides on scroll-down
   ========================================================================== */
.nav {
  position: fixed; top: 0; left: 0; right: 0; z-index: 100;
  padding-top: 20px; padding-bottom: 4px;
  transition: transform .35s cubic-bezier(.4, 0, .2, 1), padding-top .3s var(--e-out);
}
.nav-shell {
  display: flex; align-items: center; justify-content: space-between; gap: 24px;
  height: 66px; padding: 0 10px 0 22px;
  border: 1px solid transparent; border-radius: var(--r-round);
  transition: background-color .35s var(--e-out), border-color .35s var(--e-out),
              box-shadow .35s var(--e-out), backdrop-filter .35s var(--e-out);
}
.nav.is-stuck { padding-top: 12px; }
.nav.is-stuck .nav-shell {
  background: rgba(252, 252, 252, .84);
  -webkit-backdrop-filter: blur(16px);
          backdrop-filter: blur(16px);
  border-color: var(--hairline);
  box-shadow: 0 12px 34px -22px rgba(20, 30, 70, .38);
}
.nav.is-hidden { transform: translateY(-130%); }

.brand { display: inline-flex; align-items: center; gap: 10px; font-size: 18px; font-weight: 600; }
.brand-mark { width: 18px; height: 18px; border-radius: var(--r-round); background: var(--grad); display: inline-block; }

.nav-links { display: flex; gap: 32px; }
.nav-links a { font-size: 17px; font-weight: 300; color: var(--muted); transition: color .25s linear, transform .25s var(--e-out); display: inline-block; }
.nav-links a:hover, .nav-links a:focus-visible { color: var(--text); transform: translateY(-1px); }
/* a lifted thing must come back down when you press it — same .12s as the button */
.nav-links a:active { transform: translateY(0); transition-duration: .12s; }
.nav-links a.is-active { color: var(--text); }

.nav-right { display: flex; align-items: center; gap: 14px; }

/* Below 900px this is the primary navigation control and it had a 1.37:1
   boundary, no hover and no press. Press is scale(.94), not .985: the site's
   press is ~3px of travel on a 200-330px button, and .985 on a 44px square is
   0.7px — sub-pixel, i.e. no feedback. Constant displacement, not constant ratio. */
.burger {
  display: none;
  width: 44px; height: 44px; border-radius: var(--r-round);
  border: 1px solid var(--edge); background: var(--card);
  cursor: pointer; align-items: center; justify-content: center; flex-direction: column; gap: 5px;
  transition: border-color .25s linear, background-color .25s linear,
              transform .25s var(--e-back);
}
.burger span { display: block; width: 18px; height: 1px; background: var(--text); transition: transform .3s var(--e-out); }
.burger:hover { border-color: var(--edge-on); background: #f3f5f8; }
.burger:active { transform: scale(.94); transition-duration: .12s; }
.burger[aria-expanded="true"] { border-color: var(--accent); }
.burger[aria-expanded="true"] span:first-child { transform: translateY(3px) rotate(45deg); }
.burger[aria-expanded="true"] span:last-child  { transform: translateY(-3px) rotate(-45deg); }

.drawer {
  position: fixed; inset: 86px 0 0; z-index: 99;
  display: flex; flex-direction: column; align-items: stretch;
  padding: 0 var(--pad) calc(16px + env(safe-area-inset-bottom, 0px));
  /* the scrim IS the outside-click target — without a surface below the panel
     there is nothing to click and no signal that the page is suspended */
  background: rgba(19, 21, 25, .42);
}
.drawer[hidden] { display: none; }
.drawer-inner {
  background: rgba(252, 252, 252, .96);
  -webkit-backdrop-filter: blur(16px); backdrop-filter: blur(16px);
  border: 1px solid var(--hairline); border-radius: 28px;
  padding: 22px 24px 26px;
  display: flex; flex-direction: column; gap: 16px;
  flex: 0 1 auto; max-height: 100%;
  overflow-y: auto; overscroll-behavior: contain;
}
.drawer a { font-size: 20px; color: var(--muted); transition: color .25s linear; }
/* nav-links and foot-col both darken on hover AND focus; the drawer had neither. */
.drawer a:not(.btn):hover, .drawer a:not(.btn):focus-visible { color: var(--text); }
.drawer .drawer-cta { align-self: flex-start; color: var(--on-metal); }

/* =============================================================================
   1. HERO
   ========================================================================== */
/* 132, not 156: the nav shell ends at 86px, so this still leaves 46px of clear air
   above the pill, and it is the SAME value the <=900px breakpoint already uses —
   one number for the hero's top inset instead of two. Buys 24px of fold, which is
   what puts the whole rail plus its captions above a 900px viewport. */
.hero { position: relative; overflow: hidden; padding: 132px 0 var(--sec-gap); }
.hero-bg {
  position: absolute; left: -8%; right: -8%; top: -14%; height: 128%;
  background:
    radial-gradient(46% 42% at 72% 12%, rgba(61, 68, 78, .07), transparent 70%),
    radial-gradient(52% 46% at 12% 44%, rgba(120, 130, 155, .12), transparent 72%);
  z-index: 0; pointer-events: none;
}

/* --- faceted gem field ---------------------------------------------------
   HARD-stop conic gradients radiating from a point, like the cut of a stone —
   here reading as polished metal catching light rather than a gem. The drama in a gem comes from sharp luminance jumps
   between adjacent facets, so this is deliberately NOT heavily blurred — a
   soft wash would read as a smudge, not a stone. Kept at low opacity so it is
   a texture behind the layout rather than a picture in front of it.
   Decorative only: no text ever sits on the lit facets. --------------------- */
.gem {
  position: absolute; inset: -25% -12% auto; height: 150%;
  z-index: 0; pointer-events: none;
  opacity: .15;
  filter: blur(26px) saturate(106%);
  /* Masked out of the middle: the facets belong at the edges of the frame, not
     across the headline. Keeps the refraction as peripheral atmosphere. */
  -webkit-mask-image: radial-gradient(115% 95% at 46% 46%, transparent 0%, transparent 34%, #000 82%);
          mask-image: radial-gradient(115% 95% at 46% 46%, transparent 0%, transparent 34%, #000 82%);
  background:
    conic-gradient(from 198deg at 74% 8%,
      var(--gf-a) 0deg 9deg, transparent 9deg 21deg,
      var(--gf-b) 21deg 27deg, transparent 27deg 44deg,
      var(--gf-a) 44deg 51deg, transparent 51deg 68deg,
      var(--gf-c) 68deg 77deg, transparent 77deg 96deg,
      var(--gf-a) 96deg 103deg, transparent 103deg 124deg,
      var(--gf-b) 124deg 132deg, transparent 132deg 360deg),
    conic-gradient(from 22deg at 14% 66%,
      var(--gf-a) 0deg 7deg, transparent 7deg 26deg,
      var(--gf-b) 26deg 34deg, transparent 34deg 55deg,
      var(--gf-a) 55deg 61deg, transparent 61deg 84deg,
      var(--gf-c) 84deg 93deg, transparent 93deg 360deg);
}
.hero .gem { height: 138%; }
/* on the charcoal CTA panel the same cut reads as light trapped inside the stone */
.cta-band .gem {
  /* flipped: on charcoal the facets have to be LIGHTER than the surface */
  --gf-a: #ffffff; --gf-b: #d8dee6; --gf-c: #aab2bd;
  inset: 0 0 auto; height: 100%; opacity: .26; filter: blur(32px) saturate(112%);
  -webkit-mask-image: radial-gradient(110% 100% at 50% 50%, transparent 0%, transparent 26%, #000 76%);
          mask-image: radial-gradient(110% 100% at 50% 50%, transparent 0%, transparent 26%, #000 76%);
}
.hero > .container { position: relative; z-index: 1; }
.hero-copy { position: relative; }

/* THE META ROW. The pill and the eyebrow were stacked: 44 + 26 + 27 + 14 = 111px
   and two lines of text before the promise, both of which say "Melbourne". They are
   the same KIND of thing — orientation, not argument — so they are now one row and
   one glance. align-items: center because the pill is a 44px object and the eyebrow
   a 24px line; baseline would hang the pill. flex-wrap means that below ~1274px the
   eyebrow drops under the pill by itself, with no media query. */
.hero-meta {
  display: flex; align-items: center; flex-wrap: wrap;
  gap: 10px 20px;
  margin-bottom: 20px;
}
.hero-meta .status-pill { margin-bottom: 0; }

/* The eyebrow was 18px/300 — the same size AND weight as the lead beneath it and as
   body copy, separated only by 1.45 units of contrast (6.04 vs 7.49:1). That is not
   a label, that is a first sentence. At 16px it matches the pill it now sits beside,
   so the row reads as one piece of furniture. Colour untouched: still 6.04:1. The ch
   cap is removed because it wrapped 16px type to two lines and un-levelled the row. */
.hero .eyebrow { font-size: 16px; line-height: 24px; margin-bottom: 0; max-width: none; }

.status-pill {
  display: inline-flex; align-items: center; gap: 9px;
  border: 1px solid var(--hairline); border-radius: var(--r-round);
  padding: 9px 18px; font-size: 16px; font-weight: 300; color: var(--muted);
  background: var(--card); margin-bottom: 26px;
}
/* "we are open" IS the live state — said in the family's words */
.status-pill .dot::after { opacity: 1; }

/* 52px capped from a 1444px viewport up, against a 42px UPPERCASE h2, is a ratio of
   1.24 — and uppercase carries more apparent mass per line, so the headline was
   barely louder than the section headings below it. 60px takes the ratio to 1.43.
   4.4vw (not 3.6) keeps it growing to the cap instead of stalling early.
   line-height 1.04: 1.1 is body-copy leading and it opened a visible river between
   two 60px lines. max-width 34ch keeps the natural break long-over-short. */
.hero-title {
  font-size: clamp(38px, 4.4vw, 60px);
  font-weight: 600; line-height: 1.04;
  letter-spacing: -0.012em;
  max-width: 19ch; margin: 0 0 32px;
}

.hero-cta { display: flex; flex-wrap: wrap; gap: 14px; }

.trust-strip { display: flex; flex-wrap: wrap; gap: 10px; padding-top: 28px; margin-bottom: 44px; border-top: 1px solid var(--hairline); }
.trust-strip li {
  display: inline-flex; align-items: center; gap: 9px;
  font-size: 16px; font-weight: 300; color: var(--tertiary);
  border: 1px solid var(--hairline); border-radius: var(--r-round);
  padding: 8px 18px; background: var(--card);
  transition: color .28s linear, border-color .28s linear;
}
.trust-strip li:hover { color: var(--text); border-color: #aeb6c1; }
.trust-strip li:hover .ts-dot { transform: scale(var(--dot-pop)); }
.trust-strip li:hover .ts-dot::after { opacity: 1; }
/* These are <li>, not controls — the boundary deliberately stays --hairline; a
   3:1 edge would make static decoration look clickable. Only the mechanics change:
   the padding shift was NOT in the transition list, so the content snapped 2px
   while the colour eased over .28s. The transform transition below was already
   declared and never driven by anything — this wires it, which also takes the
   shift off the layout path. */
.trust-strip li > *:not(.ts-dot) { transition: transform .28s var(--e-out); }
.trust-strip li:hover > *:not(.ts-dot) { transform: translateX(2px); }

/* v19: the hero system preview (.flow-card / .flow-rail / .flow-node / .tag) is
   GONE — its argument was rotated 90 degrees and became THE THREAD. See the
   v19 HERO block at the end of this file. .trust-strip's base rules above stay:
   eight interior pages use them inside .page-head. */

/* =============================================================================
   3. RECOGNITION
   ========================================================================== */
.recog-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--grid-gap); }
.recog-item {
  position: relative; overflow: hidden;
  display: flex; gap: 14px; align-items: flex-start;
  background: var(--card); border: 1px solid var(--hairline);
  border-radius: var(--r-card); padding: 30px 32px;
  transition: transform .3s var(--e-back), border-color .3s linear, box-shadow .3s var(--e-out);
}
.recog-item::after {
  content: ''; position: absolute; inset: 0; pointer-events: none;
  background: radial-gradient(120% 90% at 0% 0%, rgba(61, 68, 78, .07), transparent 62%);
  opacity: 0; transition: opacity .3s linear;
}
.recog-item p { font-size: 18px; font-weight: 300; line-height: 1.5; transition: transform .3s var(--e-out); }
.recog-item:hover { transform: translateY(-6px); border-color: #aeb6c1; box-shadow: 0 16px 34px -20px rgba(20, 30, 70, .3); }
.recog-item:hover::after { opacity: 1; }
.recog-item:hover p { transform: translateY(-2px); }
.recog-item:hover .tick { transform: scale(1.12); }

/* Tier B — a mark disc: a 22px token that CARRIES A GLYPH. Shares the family's
   radius, transition and pop; the size differs because the job differs. */
.tick, .cmp-x {
  flex: none; position: relative;
  width: var(--mark-md); height: var(--mark-md);
  border-radius: var(--r-round);
  transition: transform .3s var(--e-back), background-color .3s linear;
}
.tick { margin-top: 3px; background: rgba(61, 68, 78, .12); }
/* 6x10 at 1.8px = 28.8 stroke-px, matching the cross's 2 x 9 at 1.6px = 28.8.
   Rendered in greyscale the old 5x9 at 1.6px carried 29% LESS ink than the x,
   so the "bad" mark visually outweighed the "good" one. Equalised — and stroke
   mass then becomes a second non-colour channel on top of the shape. */
.tick::after {
  content: ''; position: absolute; left: 6.5px; top: 4px;
  width: 6px; height: 10px;
  border: solid var(--accent); border-width: 0 1.8px 1.8px 0;
  transform: rotate(45deg);
}

.recog-close { margin-top: 34px; max-width: 76ch; font-size: 18px; font-weight: 300; color: var(--muted); }
.recog-close strong { font-weight: 600; color: var(--text); }

/* =============================================================================
   4. GROWTH SYSTEM — sticky, scroll-scrubbed five-stage sequence
   ========================================================================== */
/* v19: .sys-sec padding, .sys-scroll track and .sys-sticky pin are redefined in the
   v19 INK BAND block at the end of this file — the track is now a .js-enabled
   enhancement, so JS-off can no longer pin an overflowing box for 260vh. */
/* The rail column is sized to its content plus breathing room, not to a share of
   the grid. At 1.08fr/.92fr it was 541px wide holding ~130px of ink — 476px of
   void that read as a column that failed to load — while the h2 beside it broke
   to three lines. */
/* 208, not 292. The rail's labels only need 152.7px ("Follow up" + 38px pad);
   the 292 was set by .sys-done's single 274.1px line, which is opacity: 0 until
   the pin is 90% done — so for 90% of the scroll the reader saw 139.3px of void
   (47.7% of the column) beside every label. Measured after: 55.3px. */
/* v19: .sys-inner is redefined in the INK BAND block — one column by default,
   two only when .js-enabled + >=1024 + no-preference, matching isDesktopMotion(). */

/* The section heading rides in the sticky column beside the changing stage. */
.sys-head { margin-bottom: 40px; padding-bottom: 30px; }
.sys-head h2 { font-size: clamp(34px, 3.6vw, 52px); line-height: 1.06; max-width: 17em; }
.sys-lead { margin-top: 14px; max-width: 50ch; font-size: 19px; }

/* Tabular figures so "/ 05" cannot shift sideways when the stage number changes,
   and the live number in --text (13.4:1) rather than silver. */
.sys-count { font-size: 15px; font-weight: 400; letter-spacing: .1em; color: var(--accent-mid); margin-bottom: 14px; font-variant-numeric: tabular-nums; }
.sys-count #sysNum { color: var(--text); font-weight: 600; }
.sys-of { color: var(--tertiary); }

/* 268px was ~100px taller than any real pane, which lifted the whole block and
   left a hole under the tags. Tallest measured: 167px at 1600, 210px at 1024
   (where the tags wrap to two rows). 216px clears both. */
.sys-panes { position: relative; min-height: 210px; }
.sys-pane h3 { font-size: clamp(28px, 2.4vw, 34px); font-weight: 800; line-height: .95; text-transform: uppercase; letter-spacing: .06em; margin-bottom: 14px; }
/* the promise line and the tags were 20/300/--muted vs 17/300/--muted — two of
   three signals identical. Separated by colour alone: 6.91 -> 13.36:1. */
.sys-pane > p { font-size: 20px; font-weight: 300; color: var(--text); max-width: 46ch; margin-bottom: 26px; }
/* DETERMINISTIC WRAPPING. Convert needs 829.1px for one row and 1280px can never
   offer more than 785, so a single row is unreachable at laptop widths — the fix
   is to make every stage wrap the SAME way instead of only one of them. The safe
   window is 466.7px (below it Convert breaks to three rows) to 590.0px (above it
   Improve fits one row and becomes the odd one out); 528 is the midpoint, +/-11.7%,
   which survives font-metric drift. Above 1400 there is room for all five on one
   row, so the cap is lifted there instead. */
.sys-tags { display: flex; flex-wrap: wrap; gap: 10px; max-width: 528px; }
.sys-tags li {
  font-size: 17px; font-weight: 300; color: var(--muted);
  border: 1px solid var(--edge); border-radius: var(--r-round);
  padding: 10px 22px; background: var(--card);
  transition: transform .3s var(--e-out), border-color .3s linear, color .3s linear;
}
.sys-pane.is-active .sys-tags li:hover { transform: translateY(-3px); border-color: var(--accent); color: var(--accent); }

/* Panes stack only while JS drives them; with JS off they flow as five blocks. */
.js-enabled .sys-pane {
  position: absolute; inset: 0;
  opacity: 0; transform: translateY(14px); pointer-events: none;
  transition: opacity .45s var(--e-out), transform .45s var(--e-out);
}
.js-enabled .sys-pane.is-active {
  opacity: 1; transform: none; pointer-events: auto;
  transition: opacity .34s linear .1s, transform .5s var(--e-out) .06s;
}
.js-enabled .sys-pane.is-prev { transform: translateY(-14px); }

.sys-rail { position: relative; padding-left: 38px; }
.sys-line { position: absolute; left: 5px; top: 10px; bottom: 10px; width: 2px; background: var(--hairline); }
/* Was --grad-a -> --accent. --grad-a is #f8fafc: 1.07:1 on this section's
   #f1f2f4 band, so the COMPLETED part of the progress line was invisible and
   only its leading edge read as progress. Silver-grey -> steel: 5.58:1 at the
   top, 8.78:1 at the leading edge. */
.sys-line-fill { display: block; width: 100%; height: 0; background: linear-gradient(180deg, var(--accent-mid), var(--accent)); transition: height .18s linear; }
.sys-track { display: grid; gap: 26px; }
.sys-node { position: relative; display: flex; align-items: center; gap: 14px; }
.sn-dot {
  position: absolute; left: -38px; top: 50%; margin-top: -7px;
  /* opaque fill: the .sys-line rail passes UNDER this dot and must be masked */
  --dot-fill: var(--band);
  /* This rail sits on --band, not on white, and the family's "live = metal face"
     renders NEAR-WHITE at 14px there (measured 1.07:1) — it would make the
     active marker the palest thing on the rail while completed dots sat solid.
     So the recipe is kept and its VALUES are re-pointed: idle #868c96 = 3.02:1,
     done --accent-mid = 5.58:1, live --accent = 8.78:1. Same grammar, values
     that survive the surface. */
  --dot-line: #868c96;
}
.sn-lbl { font-size: clamp(20px, 2vw, 26px); font-weight: 600; color: var(--tertiary); transition: color .35s linear, transform .35s var(--e-out); }
.sys-node.is-done .sn-dot { --dot-fill: var(--accent-mid); --dot-line: var(--accent-mid); }
/* Completed stages read as "done, de-emphasised" via COLOUR, never opacity —
   text must always compute to opacity 1. */
.sys-node.is-done .sn-lbl { color: var(--muted); }
.sys-node.is-active .sn-dot {
  --dot-fill: var(--accent);
  transform: scale(1.18);
  /* the ring and the halo in one declaration — a repaint, no geometry moves */
  box-shadow: inset 0 0 0 var(--dot-ring) var(--accent), 0 0 0 6px rgba(61, 68, 78, .13);
}
.sys-node.is-active .sn-lbl { color: var(--text); }
.sys-node:hover .sn-lbl { color: var(--accent); transform: translateX(3px); }
.sys-node:hover .sn-dot { --dot-line: var(--accent); }

/* Tag cascade — TRANSFORM ONLY. The tags never get their own opacity, so this
   adds ZERO new hidden-text surface: their visibility is entirely the pane's.
   Desktop-gated because below 1024px the scroll engine never sets is-active. */
@media (min-width: 1024px) {
  .js-enabled .sys-tags li {
    transform: translateY(12px);
    transition: transform .46s var(--e-out), border-color .3s linear, color .3s linear;
  }
  .js-enabled .sys-pane.is-active .sys-tags li { transform: none; }
  .js-enabled .sys-pane.is-active .sys-tags li:nth-child(1) { transition-delay: .12s; }
  .js-enabled .sys-pane.is-active .sys-tags li:nth-child(2) { transition-delay: .18s; }
  .js-enabled .sys-pane.is-active .sys-tags li:nth-child(3) { transition-delay: .24s; }
  .js-enabled .sys-pane.is-active .sys-tags li:nth-child(4) { transition-delay: .30s; }
  /* (0,5,1) beats the rule above (0,4,1), so hover always wins */
  .js-enabled .sys-pane.is-active .sys-tags li:hover { transform: translateY(-3px); transition-delay: 0s; }
}

@media (min-width: 1400px) {
  .sys-tags  { max-width: none; }
  .sys-panes { min-height: 160px; }
}

.sys-done { margin-top: 34px; padding-top: 22px; border-top: 1px solid var(--hairline); font-size: 20px; font-weight: 600; color: var(--accent); }
.js-enabled .sys-done { opacity: 0; transform: translateY(8px); transition: opacity .5s var(--e-out), transform .5s var(--e-out); }
.js-enabled .sys-scroll.is-complete .sys-done { opacity: 1; transform: none; }

/* =============================================================================
   5. SELECTED CLIENT WORK
   ========================================================================== */
.case-lead { background: var(--card); border: 1px solid var(--hairline); border-radius: var(--r-card); padding: 44px; }
.case-lead-top { display: flex; flex-wrap: wrap; align-items: baseline; gap: 8px 18px; padding-bottom: 28px; margin-bottom: 30px; border-bottom: 1px solid var(--hairline); }
.case-lead-top h3 { font-size: 30px; font-weight: 600; }
.case-meta { font-size: 16px; font-weight: 300; color: var(--tertiary); }

.case-cols { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--grid-gap); }
.case-col .k { font-size: 16px; font-weight: 600; color: var(--accent-mid); margin-bottom: 10px; }
.case-col p { font-size: 17px; font-weight: 300; line-height: 1.5; color: var(--muted); }

.case-cta {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: 18px; margin: var(--grid-gap) 0; padding: 26px 34px;
  border: 1px dashed var(--hairline); border-radius: var(--r-card);
}
.case-cta p { font-size: 17px; font-weight: 300; color: var(--muted); }

.case-row { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--grid-gap); }
.case-mini { background: var(--card); border: 1px solid var(--hairline); border-radius: var(--r-card); padding: 40px; }
.case-sector { font-size: 16px; font-weight: 300; color: var(--accent-mid); margin-bottom: 8px; transition: color .3s linear; }
.case-mini h4 { font-size: 26px; font-weight: 600; margin-bottom: 24px; transition: transform .4s var(--e-out); }
.case-line { padding: 16px 0; border-top: 1px solid var(--hairline); }
.case-line .l { display: block; font-size: 15px; font-weight: 400; color: var(--tertiary); margin-bottom: 6px; }
.case-line .case-body { font-size: 17px; font-weight: 300; line-height: 1.5; color: var(--muted); }
.case-mini:hover h4 { transform: translateX(6px); }

/* =============================================================================
   7. FIX / REBUILD / GROW — sticky card stack
   ========================================================================== */
.cur-toggle {
  display: inline-flex; gap: 4px; margin-top: 22px; padding: 4px;
  border: 1px solid var(--edge); border-radius: var(--r-round); background: var(--card);
}
.cur-btn {
  font: inherit; font-size: 15px; font-weight: 400;
  padding: 8px 18px; border: 0; border-radius: var(--r-round);
  background: transparent; color: var(--tertiary); cursor: pointer;
  transition: background-color .25s linear, color .25s linear,
              transform .25s var(--e-back);
}
/* the unselected segment had no hover state at all — the one genuinely dead
   control on the page. Wash keeps the label at 13.35:1. */
.cur-btn:hover { background: rgba(61, 68, 78, .07); color: var(--text); }
/* scale(.96) for the same reason as .burger: .985 on a ~64px segment is 1px. */
.cur-btn:active { transform: scale(.96); transition-duration: .12s; }
/* The flat --accent fill STAYS. Metal would be the "consistent" answer
   (.pnum.is-on and .pc-badge both use var(--grad) for selected) and was rejected
   on measurement: a metal segment against the white track is 1.42:1, the flat
   fill is 9.83:1. On a segmented control the selected state IS the information. */
.cur-btn.is-on { background: var(--accent); color: #fff; }
.cur-btn.is-on:hover { background: var(--accent-dk); color: #fff; }

.price-stack { padding-bottom: 4vh; }
/* Holds the LAST card (Grow) pinned for a beat instead of letting it scroll
   straight past. Must be an in-flow box, not parent padding — sticky is
   constrained by the containing block's CONTENT box. */
.price-tail { height: 46vh; }
.price-slot { position: sticky; top: 132px; }
.price-slot + .price-slot { margin-top: 52vh; }

.price-card {
  position: relative;
  max-width: 1020px; margin: 0 auto;
  display: grid; grid-template-columns: 1.35fr .85fr; gap: 52px;
  background: var(--card); border: 1px solid var(--hairline);
  border-radius: var(--r-card); padding: 44px 48px;
  box-shadow: 0 24px 60px -40px rgba(20, 30, 70, .5);
  transform-origin: center top;
  transition: transform .45s var(--e-out), border-color .3s linear, box-shadow .4s var(--e-out);
}
.price-card.is-featured { border-color: var(--accent); }
/* ---------- cursor spotlight -------------------------------------------------
   A soft cobalt highlight that follows the pointer inside every card surface.
   Purely decorative: it is opacity on a pseudo-element, never on text, and it
   is skipped entirely on touch and under reduced motion. --------------------- */
.case-lead, .case-mini, .price-card, .review, .team-panel, .quote-block { position: relative; }
.case-lead::after, .case-mini::after,
.price-card::after, .review::after, .team-panel::after, .quote-block::after {
  content: ''; position: absolute; inset: 0; border-radius: inherit;
  pointer-events: none; opacity: 0;
  transition: opacity .4s linear;
  background: radial-gradient(280px circle at var(--mx, 50%) var(--my, 50%),
              rgba(61, 68, 78, .09), transparent 62%);
}
.case-lead:hover::after, .case-mini:hover::after,
.price-card:hover::after, .review:hover::after, .team-panel:hover::after,
.quote-block:hover::after { opacity: 1; }

/* Covered-card veil for the sticky stack. Darkens the card WITHOUT touching the
   opacity of any text node — text always computes to opacity 1. */
.price-card::before {
  content: ''; position: absolute; inset: 0; z-index: 3;
  border-radius: inherit; pointer-events: none;
  background: #0e1118; opacity: var(--cover, 0);
}
.price-card:hover { border-color: #aeb6c1; box-shadow: 0 28px 64px -34px rgba(20, 30, 70, .42); }
.price-card.is-featured:hover { border-color: var(--accent); }

.pc-top { display: flex; align-items: center; gap: 12px; margin-bottom: 10px; }
.pc-top h3 { font-size: 34px; font-weight: 700; text-transform: uppercase; }
.pc-badge { font-size: 14px; font-weight: 500; color: var(--on-metal); background: var(--grad); box-shadow: var(--metal-inset), 0 1px 3px rgba(20, 22, 26, .22); border: 1px solid var(--metal-edge); border-radius: var(--r-round); padding: 6px 14px; }
.pc-for { font-size: 17px; font-weight: 300; color: var(--muted); margin-bottom: 24px; }
.pc-list { display: grid; }
.pc-list li {
  font-size: 17px; font-weight: 300; color: var(--muted);
  padding: 12px 0 12px 20px; border-top: 1px solid var(--hairline);
  position: relative; transition: transform .3s var(--e-out), color .3s linear;
}
/* Chevron drawn with borders, not a glyph, so assistive tech has nothing to read. */
.pc-list li::before {
  content: ''; position: absolute; left: 1px; top: 21px;
  width: 5px; height: 5px;
  border: solid var(--accent); border-width: 1.5px 1.5px 0 0;
  transform: rotate(45deg);
}
.price-card:hover .pc-list li { transform: translateX(3px); color: var(--text); }

.pc-side { display: flex; flex-direction: column; justify-content: flex-end; gap: 22px; }
.pc-price { text-align: right; }
.pc-from { display: block; font-size: 15px; font-weight: 300; color: var(--tertiary); margin-bottom: 8px; }
.pc-amt { display: block; font-size: 44px; font-weight: 800; line-height: 1; color: var(--accent); transition: color .3s linear; }
.pc-per { font-size: 18px; font-weight: 300; color: var(--tertiary); }
.pc-alt { display: block; margin-top: 8px; font-size: 13px; font-weight: 300; color: var(--tertiary); }
.price-card:hover .pc-amt { color: var(--accent-dk); }

.price-note { margin-top: 34px; max-width: 82ch; font-size: 16px; font-weight: 300; color: var(--tertiary); }

/* =============================================================================
   8. ONE ACCOUNTABLE TEAM
   ========================================================================== */
.team-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 60px var(--grid-gap); align-items: start; }
.team-copy .lead { margin-top: 18px; max-width: 62ch; }
.team-cta { margin-top: 30px; }

.check-list { margin-top: 30px; display: grid; gap: 14px; }
.check-row { display: flex; gap: 14px; align-items: flex-start; transition: transform .3s var(--e-out); }
.check-row p { font-size: 18px; font-weight: 300; line-height: 1.5; color: var(--muted); transition: color .3s linear; }
.check-row:hover { transform: translateX(3px); }
.check-row:hover p { color: var(--text); }

.team-panel { background: var(--card); border: 1px solid var(--hairline); border-radius: var(--r-card); padding: 44px; position: relative; }
.tp-head { font-size: 16px; font-weight: 600; color: var(--accent-mid); text-transform: uppercase; margin-bottom: 24px; }

.tp-rows { position: relative; padding-left: 24px; }
.tp-line { position: absolute; left: 3px; top: 16px; bottom: 16px; width: 2px; background: var(--hairline); }
.tp-line-fill { display: block; width: 100%; height: 0; background: linear-gradient(180deg, var(--grad-a), var(--accent)); transition: height 1.1s var(--e-out) .25s; }
.tp-rows.is-on .tp-line-fill { height: 100%; }

.tp-row {
  position: relative; overflow: hidden;
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px 16px;
  padding: 20px 14px; border-top: 1px solid var(--hairline);
  transition: opacity .4s linear, padding .4s var(--e-out);
}
.tp-row::before {
  content: ''; position: absolute; inset: 0; z-index: 0;
  background: linear-gradient(90deg, rgba(61, 68, 78, .10), rgba(61, 68, 78, 0));
  transform: scaleX(0); transform-origin: left center;
  transition: transform .45s var(--e-out);
}
.tp-k { position: relative; z-index: 1; font-size: 22px; font-weight: 600; min-width: 150px; transition: transform .4s var(--e-out), color .3s linear; }
.tp-d { position: relative; z-index: 1; font-size: 17px; font-weight: 300; color: var(--muted); transition: transform .4s var(--e-out), color .3s linear; }
.tp-arw {
  position: relative; z-index: 1; margin-left: auto;
  width: 18px; height: 18px; flex: none;
  border: solid var(--accent); border-width: 1.6px 1.6px 0 0;
  transform: rotate(45deg) translate3d(-6px, 6px, 0); opacity: 0;
  transition: transform .4s var(--e-out), opacity .3s linear;
}
/* was `.tp-rows:hover .tp-row { opacity: .62 }` — the last dim-text hover on
   the site, and it dimmed .tp-k and .tp-d, both real text. The recede now runs
   on the row's hairline only; every label keeps opacity 1 and its own colour,
   and the hovered row's text still goes DARKER (see .tp-k / .tp-d below). */
.tp-rows:hover .tp-row { border-color: var(--dot-mute); }
.tp-rows .tp-row:hover { padding-left: 22px; padding-right: 6px; }
.tp-row:hover::before { transform: scaleX(1); }
.tp-row:hover .tp-k { transform: translateX(8px); color: var(--on-metal); }
.tp-row:hover .tp-d { transform: translateX(-3px); color: var(--text); }
.tp-row:hover .tp-arw { transform: rotate(45deg) translate3d(0, 0, 0); opacity: 1; }

/* rotating circular badge */
.badge-wrap { position: relative; width: 144px; height: 144px; margin-top: 38px; }
.rot-badge { display: block; width: 144px; height: 144px; }
/* letter-spacing tuned so the string measures 335px against the 339px circular
   path — any longer and the text truncates mid-word. Re-measure if edited. */
.badge-text { font-size: 11px; font-weight: 600; fill: var(--accent); letter-spacing: 0.75px; }
.badge-ring { fill: none; stroke: var(--hairline); stroke-width: 1; }
/* the badge core and an active stage node are now literally the same object */
.badge-core { position: absolute; left: 50%; top: 50%; transform: translate(-50%,-50%); }
.badge-core::after { opacity: 1; }

/* =============================================================================
   9. TESTIMONIALS — static 3-up grid (three real reviews; no autoplay carousel)
   ========================================================================== */
.review-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--grid-gap); }
.review {
  margin: 0; position: relative;
  background: var(--card); border: 1px solid var(--hairline);
  border-radius: var(--r-card); padding: 40px;
  display: flex; flex-direction: column; justify-content: space-between; gap: 24px;
  transition: transform .4s var(--e-back), border-color .3s linear, box-shadow .4s var(--e-out);
}
.rv-mark { font-size: 56px; line-height: .6; font-weight: 700; color: var(--hairline); transition: color .3s linear; }
/* flex: 1 1 auto — the cards are all exactly the same height, but
   space-between was centring the middle child, so the three quotes started at
   99 / 124 / 139px and their attributions at 268 / 290 / 290px. Nothing aligned
   across three identically-sized cards. */
.review blockquote { flex: 1 1 auto; margin: 0; font-size: 20px; font-weight: 300; line-height: 1.45; color: var(--text); }
.review figcaption { display: grid; gap: 4px; padding-top: 24px; border-top: 1px solid var(--hairline); }
.rv-who  { font-size: 17px; font-weight: 600; }
.rv-role { font-size: 15px; font-weight: 300; color: var(--tertiary); }
.rv-src  { font-size: 15px; font-weight: 300; color: var(--accent-mid); transition: color .3s linear; }
.review:hover { transform: translateY(-4px); border-color: #aeb6c1; box-shadow: 0 16px 36px -22px rgba(20, 30, 70, .3); }
.review:hover .rv-mark { color: var(--accent); }
.review:hover .rv-src { color: var(--accent-dk); }

/* =============================================================================
   10. FAQ
   ========================================================================== */
.faq-grid { display: grid; grid-template-columns: .8fr 1.2fr; gap: 72px; align-items: start; }
.faq-head { position: sticky; top: 140px; }
.faq-list { border-top: 1px solid var(--hairline); }
.faq-row { border-bottom: 1px solid var(--hairline); transition: border-color .3s linear; }
.faq-row.is-open { border-bottom-color: var(--accent); }
.faq-row h3 { margin: 0; font-weight: inherit; }
.faq-q {
  width: 100%; display: flex; align-items: center; justify-content: space-between; gap: 24px;
  padding: 26px 0; background: none; border: 0; text-align: left; cursor: pointer;
  font: inherit; font-size: 22px; font-weight: 600; color: var(--text);
  transition: padding-left .32s var(--e-out), color .3s linear;
}
.faq-q:hover, .faq-q:focus-visible { padding-left: 6px; color: var(--on-metal); }
/* the row is a <button> and had no press at all */
.faq-q:active { color: #000; transition-duration: .12s; }
.faq-ico {
  position: relative; flex: none; width: 40px; height: 40px;
  border-radius: var(--r-round); border: 1px solid var(--edge); background: transparent;
  transition: transform .4s var(--e-out), background-color .3s linear, border-color .3s linear;
}
.faq-ico::before, .faq-ico::after {
  content: ''; position: absolute; left: 50%; top: 50%; background: currentColor;
  transition: opacity .3s linear;
}
.faq-ico::before { width: 13px; height: 1.6px; margin: -.8px 0 0 -6.5px; }
.faq-ico::after  { width: 1.6px; height: 13px; margin: -6.5px 0 0 -.8px; }
/* keyboard got the row treatment but not the icon treatment — now both */
.faq-q:hover .faq-ico, .faq-q:focus-visible .faq-ico { background: rgba(61, 68, 78, .09); border-color: var(--accent); }
/* deepen rather than move: .faq-row.is-open already owns .faq-ico's transform */
.faq-q:active .faq-ico { background: rgba(61, 68, 78, .18); transition-duration: .12s; }
.faq-row.is-open .faq-ico { transform: rotate(45deg); border-color: var(--accent); color: var(--accent); }

.faq-a { overflow: hidden; }
.js-enabled .faq-a { height: 0; }
.faq-a-inner { padding: 0 60px 30px 0; }
.faq-a-inner p { font-size: 17px; font-weight: 300; line-height: 1.55; color: var(--muted); }

/* =============================================================================
   11. GROWTH REVIEW / CONTACT — dark panel that widens as it enters
   ========================================================================== */
.cta-band { position: relative; margin-top: 80px; padding: 110px 0 100px; color: #fff; }
.cta-panel-bg {
  position: absolute; inset: 0; z-index: 0;
  background: linear-gradient(160deg, #1b2030 0%, var(--ink) 55%, #101219 100%);
  border-radius: var(--r-card) var(--r-card) 0 0;
  /* Bottom origin: the panel's bottom edge is welded to the footer (its #101219
     stop against the footer's --ink #131519 — the seam is invisible), so scaling
     from the centre would open a light gap underneath. From the bottom the box
     grows UP out of the footer and only the top edge travels. */
  transform-origin: 50% 100%;
}
/* The closed state is JS-ONLY. It used to be an ungated `transform: scaleX(.92)`,
   which with JS disabled left the panel permanently 8% narrow — measured 3px
   clear of the white headline at 390px, i.e. one padding tweak from white text
   on a white page. */
.js-enabled .cta-panel-bg { transform: scale(.915, .938); }
/* Below 1024px the gutter (--pad 24/18px) is smaller than any useful inset, so
   the panel does not breathe at all — which removes that 3px near-miss. */
@media (max-width: 1023px) { .cta-panel-bg { transform: none !important; } }
.cta-band > .container { position: relative; z-index: 1; }
.cta-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 60px var(--grid-gap); align-items: start; }
.cta-copy .lead { margin-top: 18px; max-width: 60ch; color: rgba(255,255,255,.72); }
.cta-band h2 { color: #fff; }
.cta-band .eyebrow { color: var(--metal-lite); }
.cta-band .check-row p { color: rgba(255,255,255,.76); }
.cta-band .check-row:hover p { color: #fff; }
.cta-band .tick { background: rgba(216, 222, 230, .20); }
.cta-band .tick::after { border-color: var(--metal-lite); }

.form-wrap { position: relative; }
.qform { background: var(--card); border: 1px solid rgba(255,255,255,.14); border-radius: var(--r-card); padding: 40px; display: grid; gap: 18px; color: var(--text); }
.fstep { display: grid; gap: 18px; }
.js-enabled .qform[data-step="1"] .fstep-2 { display: none; }
.js-enabled .qform[data-step="2"] .fstep-next { display: none; }
.fstep-next { display: none; }
.js-enabled .fstep-next { display: inline-flex; }

.row2 { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; }
.field { display: grid; gap: 8px; }
.field label { font-size: 15px; font-weight: 400; color: var(--muted); transition: color .25s linear; }
.field .opt { color: var(--tertiary); font-weight: 300; }
.field input, .field textarea, .field select {
  font-family: inherit; font-size: 17px; font-weight: 300; color: var(--text);
  background: var(--bg);
  border: 1px solid var(--edge); border-radius: 18px;    /* 3.36 / 3.47 */
  padding: 14px 18px; width: 100%;
  transition: border-color .25s linear, box-shadow .25s linear;
}
.field textarea { min-height: 104px; resize: vertical; }
/* HOVER DARKENS. It used to LIGHTEN to #a9aeb6 — 2.16:1 — so pointing at a field
   pushed its boundary BELOW the 3:1 that identifies it as a control. Every other
   hover on this site adds definition; this one removed it. 3.36 -> 4.56. */
.field input:hover, .field textarea:hover, .field select:hover { border-color: var(--edge-on); }
.field input:focus, .field textarea:focus, .field select:focus {
  border-color: var(--accent); outline: none;
  box-shadow: 0 0 0 4px rgba(61, 68, 78, .16);
}
/* The quiet border+halo above is right for a mouse click, but `outline: none` on
   :focus also removed the global ring for KEYBOARD users (0-2-1 beats 0-1-0).
   Keyboard focus now gets the identical ring every other control has. */
.field input:focus-visible, .field textarea:focus-visible, .field select:focus-visible {
  outline: 2px solid var(--focus); outline-offset: 3px;
}
.field:focus-within label { color: var(--text); }
.field input::placeholder, .field textarea::placeholder { color: var(--tertiary); }
/* was #9aa2ad = 2.49:1 — a field became HARDER to see by validating. Now 4.56:1. */
.field.is-valid input, .field.is-valid textarea { border-color: var(--edge-on); }

.form-msg { font-size: 16px; font-weight: 300; color: var(--muted); min-height: 1px; }
.form-msg.ok  { color: var(--accent); }
.form-msg.err { color: #b3261e; }
.form-done { text-align: center; padding: 30px 10px; }
.form-done h3 { font-size: 24px; margin-bottom: 12px; }
.form-done p { font-size: 17px; font-weight: 300; color: var(--muted); }

/* =============================================================================
   FOOTER
   ========================================================================== */
.footer { background: var(--ink); color: rgba(255,255,255,.72); padding: 72px 0 40px; }
.footer .brand { color: #fff; }
.foot-grid { display: grid; grid-template-columns: 1.6fr 1fr 1fr 1.2fr; gap: var(--grid-gap); }
.foot-brand p { margin-top: 16px; max-width: 40ch; font-size: 16px; font-weight: 300; }
.foot-social { display: flex; gap: 10px; margin-top: 20px; }
.foot-social a {
  width: 40px; height: 40px; border-radius: var(--r-round);
  border: 1px solid rgba(255,255,255,.18); background: rgba(255,255,255,.04);
  display: flex; align-items: center; justify-content: center; color: rgba(255,255,255,.75);
  transition: color .25s linear, border-color .25s linear, transform .3s var(--e-out);
}
.foot-social a:hover, .foot-social a:focus-visible { color: #fff; border-color: rgba(255,255,255,.55); transform: translateY(-2px); }
.foot-social a:active { transform: translateY(0); transition-duration: .12s; }

.foot-col h5 { font-size: 16px; font-weight: 600; text-transform: uppercase; margin-bottom: 16px; color: #fff; }
.foot-col li { margin-bottom: 10px; font-size: 16px; font-weight: 300; }
.foot-col a { transition: transform .3s var(--e-out), color .25s linear; display: inline-block; }
.foot-col a:hover, .foot-col a:focus-visible { color: #fff; transform: translateX(3px); }
.foot-col a:active { transform: translateX(0); transition-duration: .12s; }

/* Registered so --glow can be TRANSITIONED. Only the fade uses it; the spot's
   position comes from --mx/--my and tracks the pointer with no lag, which is
   what "followed" means. Unsupported browsers just snap the fade on and off. */
@property --glow { syntax: '<number>'; inherits: false; initial-value: 0; }

.foot-word {
  margin: calc(64px - .085em) 0 calc(64px - .10em); overflow: hidden;
  /* CENTRED — the owner's instinct, and the measurement agrees. "AUSTRA DIGITAL"
     needs 8.48em of measure and the container gives at most 6.57em, so this has
     always been a TWO-LINE LOCKUP at every viewport from 390 to 2560, and the two
     lines are within 5% of each other in width. That is a near-square MARK, not a
     heading — and a mark centres. Left-aligned it left 438px (35%) of the footer
     empty on the right, under a CONTACT column that fills it.
     text-align centres the ADVANCE box, not the ink: -.02em tracking leaves
     non-ink trailing space and the sidebearings are unequal (A opens at 1px, D at
     13px), so the ink landed +2.03px / +6.71px right of centre — a 13.4px error on
     line two. box-sizing is border-box, so padding-right moves the centring axis
     left by HALF its value: .04em corrects to a 5.8px residual on a 1248px
     measure (0.47%). The em margins correct the vertical the same way — line-height
     .9 crops the leading, so a flat 56/40 read as 72/59; this measures optical
     64px above and below at every viewport.
     The glow still tracks: --mx/--my map to the padding box, which stays 1248px
     either way, and the no-pointer 50% 50% fallback now lands dead centre of the
     mark instead of inside "AUSTRA". */
  /* SPLIT TO BOTH EDGES — Austra left, Digital right. This is the answer to the
     centre-vs-fill argument: "AUSTRA DIGITAL" needs 8.48em against a 6.57em
     container, so it was always going to be two lines. Centred, it balanced the
     void but abandoned the x=169 rail every other section holds. Justified to the
     edges it FILLS the measure and anchors BOTH sides — and it stops being a
     heading that happens to wrap and becomes a designed lockup.
     No optical padding needed: each word is flushed to a real edge rather than
     centred on an advance box, so the sidebearing error has nowhere to land. */
  /* Two lines by necessity (8.48em of measure into a 6.57em container), so each
     word gets its own line and its own edge: AUSTRA flush left on the x=169 rail,
     DIGITAL flush right on the container edge. Flex + space-between does NOT do
     this — once the words wrap, each flex LINE justifies independently and both
     land left. Block lines with the second right-aligned is the only thing that
     actually reaches both edges. */
  display: block;
  font-size: clamp(46px, 12vw, 190px); line-height: .9;
  font-weight: 800; text-transform: uppercase; letter-spacing: -.02em;
  color: transparent;
  /* Two layers, both clipped to the glyphs. The base gradient is UNCHANGED, and
     the radial only ever ADDS white — at --glow: 0 its alpha is 0 and every
     pixel is byte-identical to the old rest state. The wordmark can never get
     darker than it was. */
  background-image:
    radial-gradient(420px circle at var(--mx, 50%) var(--my, 50%),
      rgba(255, 255, 255, calc(.40 * var(--glow, 0))) 0%,
      rgba(255, 255, 255, calc(.15 * var(--glow, 0))) 45%,
      rgba(255, 255, 255, 0) 74%),
    linear-gradient(100deg, rgba(255,255,255,.16), rgba(255,255,255,.07) 46%, rgba(255,255,255,.24));
  -webkit-background-clip: text; background-clip: text;
  /* Only the custom property animates. Transitioning `background` would smear
     the spot 600ms behind the pointer — the opposite of following it. */
  transition: --glow .5s linear;
}
.foot-word > span { display: block; }
.foot-word > span:last-child { text-align: right; }
.foot-word:hover { --glow: 1; }

.foot-bar {
  display: flex; flex-wrap: wrap; justify-content: space-between; gap: 12px;
  margin-top: 40px; padding-top: 26px; border-top: 1px solid rgba(255,255,255,.14);
  font-size: 15px; font-weight: 300; color: rgba(255,255,255,.55);
}

/* underline sweep on nav + footer links */
.nav-links a, .foot-col a, .drawer a:not(.btn) { position: relative; }
.nav-links a::after, .foot-col a::after, .drawer a:not(.btn)::after {
  content: ''; position: absolute; left: 0; right: 0; bottom: -3px; height: 1px;
  background: currentColor;
  transform: scaleX(0); transform-origin: left center;
  transition: transform .32s var(--e-out);
}
.nav-links a:hover::after, .nav-links a:focus-visible::after, .nav-links a.is-active::after,
.foot-col a:hover::after, .foot-col a:focus-visible::after,
.drawer a:not(.btn):hover::after,
.drawer a:not(.btn):focus-visible::after { transform: scaleX(1); }

/* =============================================================================
   MOTION LAYER — pre-animation hidden states (JS-gated)
   ========================================================================== */
.js-enabled [data-reveal]:not([data-revealed]) { opacity: 0; transform: translateY(24px); }
.js-enabled [data-reveal-lines]:not([data-revealed]) { opacity: 0; }
.js-enabled [data-hero-step]:not([data-revealed]):not([data-reveal-lines]) { opacity: 0; transform: translateY(14px); }

/* line-mask scaffolding for heading reveals — unwrapped again once revealed,
   so reflow on resize can never clip a line. */
.line-mask { display: block; overflow: hidden; padding-bottom: .09em; margin-bottom: -.09em; }
.line-inner { display: block; will-change: transform; }

/* hover: card lift */
.case-mini, .case-lead {
  transition: transform .45s var(--e-back), box-shadow .45s var(--e-out), border-color .25s linear;
}
.case-mini:hover, .case-lead:hover {
  transform: translateY(-5px);
  box-shadow: 0 18px 40px -22px rgba(20, 30, 70, .3);
  border-color: #aeb6c1;
}
.case-mini:hover .case-sector { color: var(--accent-dk); }

.hero-bg { will-change: transform; }

/* Net #5 — the head script un-gates everything if the module never signals ready. */
.js-fallback [data-reveal]:not([data-revealed]),
.js-fallback [data-reveal-lines]:not([data-revealed]),
.js-fallback [data-hero-step]:not([data-revealed]),
.js-fallback .sys-done { opacity: 1 !important; transform: none !important; }
.js-fallback .sys-pane { position: static !important; opacity: 1 !important; transform: none !important; pointer-events: auto !important; }
.js-fallback .sys-tags li { transform: none !important; }
.js-fallback .faq-a { height: auto !important; }
.js-fallback .cta-panel-bg { transform: none !important; }
.js-fallback body { cursor: auto !important; }


/* =============================================================================
   v13 ADDITIONS — before/after comparison, case links, radio choices,
   sticky mobile CTA. Placed BEFORE the responsive + reduced-motion blocks so
   those still win.
   ========================================================================== */

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* ---------- before / after comparison ---------- */
.compare { border-top: 1px solid var(--hairline); }
.cmp-head { display: grid; grid-template-columns: 1fr 1fr; gap: var(--grid-gap); padding: 18px 18px 14px; }
.cmp-h { font-size: 14px; font-weight: 600; text-transform: uppercase; letter-spacing: .06em; }
.cmp-h-old { color: var(--tertiary); }
.cmp-h-new { color: var(--text); }

.cmp-rows { display: grid; }
/* row-gap: 0 is load-bearing. `gap` was set on BOTH axes though it was only ever
   the column gutter; with one grid row it never showed. The label adds a second
   row, and the inherited 20px then costs 120px of page height across six rows —
   measured 122px per row with it, 102px without. */
.cmp-row {
  display: grid; grid-template-columns: 1fr 1fr; gap: var(--grid-gap);
  border-top: 1px solid var(--hairline);
  transition: background-color .3s linear;
}
.cmp-row:hover { background: rgba(61, 68, 78, .035); }
/* One line per cell, so the x and the tick share a baseline and the row reads
   ACROSS again. The left cell is now an h3 — it carries the symptom in the
   reader's words, which is both the heading and the content, so there is nothing
   left to duplicate underneath it. */
.cmp-old, .cmp-new {
  display: flex; gap: 12px; align-items: flex-start;
  padding: 18px; font-size: 17px; line-height: 1.5;
  transition: transform .35s var(--e-out), color .3s linear;
}
.cmp-old { font-weight: 600; font-size: 18px; }
.cmp-new { font-weight: 300; }
.cmp-old { color: var(--tertiary); }
.cmp-new { color: var(--text); background: var(--card); border-radius: 20px; }
/* Both marks respond; only the connected column moves. Before this, .tick had a
   hover pop and .cmp-x — its opposite number, 20px away in the same row — had
   no transition at all. */
.cmp-row:hover .cmp-new { transform: translateX(3px); }
.cmp-row:hover .cmp-new .tick { transform: scale(var(--mark-pop)); }
.cmp-row:hover .cmp-old .cmp-x { transform: scale(var(--mark-pop)); }

/* .cmp-label and .cmp-n retired. Once the label moved INTO the left cell the
   ordinal and the x were fighting for the same 24px gutter — and the x carries
   meaning (for a protanope the red and green measure 1.11:1, so the glyph IS the
   meaning) while the ordinal carried ceremony only. */

/* THE RED AND THE GREEN. Scoped to this component only: these are the only two
   hues on the site, and they exist where a red and a green are adjacent and
   opposed. The green is NOT applied to .tick globally — the tick is reused in
   the check-list and on the charcoal CTA band, where #1c5340 is 2.05:1 and
   would force a second green into existence. */
.cmp-x { margin-top: 2px; background: rgba(163, 69, 59, .10); }
.cmp-x::before, .cmp-x::after {
  content: ''; position: absolute; left: 50%; top: 50%;
  width: 9px; height: 1.6px; background: var(--mark-no); margin: -.8px 0 0 -4.5px;
}
.cmp-x::before { transform: rotate(45deg); }
.cmp-x::after  { transform: rotate(-45deg); }
.cmp-new .tick { background: rgba(28, 83, 64, .12); }
.cmp-new .tick::after { border-color: var(--mark-yes); }

/* ---------- case-study outbound links ---------- */
.case-link {
  display: inline-flex; align-items: center; gap: 7px;
  font-size: 15px; font-weight: 400; color: var(--accent);
  border-bottom: 1px solid transparent;
  transition: border-color .25s linear, color .25s linear;
}
.case-link:hover, .case-link:focus-visible { border-bottom-color: var(--accent); color: var(--accent-dk); }
.case-mini h4 { min-height: 2.4em; }
.case-slot { display: block; min-height: 24px; margin: -14px 0 18px; }
.case-mini .case-link { margin: 0; }

/* ---------- pricing scope note ---------- */
.pc-note {
  margin-top: 14px; text-align: right;
  font-size: 14px; font-weight: 300; line-height: 1.45; color: var(--tertiary);
}

/* ---------- form: step counter, radio choices, trust line ---------- */
.fstep-count {
  font-size: 12px; font-weight: 600; letter-spacing: .09em;
  text-transform: uppercase; color: var(--accent-mid);
}
.choices { border: 0; padding: 0; margin: 0; min-width: 0; }
.choices legend { font-size: 15px; font-weight: 400; color: var(--muted); padding: 0; margin-bottom: 10px; }
.choice {
  display: flex; align-items: flex-start; gap: 12px;
  padding: 13px 16px; margin-bottom: 8px;
  border: 1px solid var(--edge); border-radius: 16px;
  font-size: 16px; font-weight: 300; line-height: 1.4; cursor: pointer;
  transition: border-color .2s linear, background-color .2s linear,
              transform .2s var(--e-out);
}
.choice:last-child { margin-bottom: 0; }
/* same inversion as the inputs: hover was #a9aeb6 = 2.16:1. Now 3.36 -> 4.56. */
.choice:hover { border-color: var(--edge-on); }
.choice:active { transform: scale(.99); transition-duration: .12s; }
.choice input {
  appearance: none; -webkit-appearance: none;
  flex: none; width: 18px; height: 18px; margin: 1px 0 0;
  border: 1px solid var(--edge); border-radius: var(--r-round);
  background: var(--card);
  transition: border-color .2s linear, box-shadow .2s linear;
}
.choice input:checked { border-color: var(--accent); box-shadow: inset 0 0 0 4px var(--accent); }
.choice:hover input { border-color: var(--edge-on); }
.choice:has(input:checked) { border-color: var(--accent); background: rgba(61, 68, 78, .06); }
/* keyboard: the whole card shows where you are, not just the 18px dot — so
   arrowing through the group is legible at a glance. */
.choice:has(input:focus-visible) { border-color: var(--accent); background: rgba(61, 68, 78, .06); }
.form-trust {
  font-size: 14px; font-weight: 300; line-height: 1.5;
  color: var(--tertiary); text-align: center;
}

/* ---------- sticky mobile CTA ---------- */
.sticky-cta {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 95;
  display: none;
  padding: 10px 14px calc(10px + env(safe-area-inset-bottom, 0px));
  transform: translateY(130%);
  visibility: hidden;
  transition: transform .38s var(--e-out), visibility 0s linear .38s;
}
.sticky-cta.is-on { transform: none; transition: transform .38s var(--e-out), visibility 0s; }
.sticky-cta[hidden] { display: none !important; }
.sticky-cta-in {
  display: flex; align-items: center; justify-content: space-between; gap: 14px;
  padding: 10px 10px 10px 20px;
  background: rgba(252, 252, 252, .95);
  -webkit-backdrop-filter: blur(16px); backdrop-filter: blur(16px);
  border: 1px solid var(--edge); border-radius: var(--r-round);
  box-shadow: 0 14px 34px -18px rgba(20, 30, 70, .45);
}
.sticky-cta-txt { display: grid; line-height: 1.25; min-width: 0; }
.sticky-cta-txt strong { font-size: 15px; font-weight: 600; }
.sticky-cta-txt span { font-size: 12px; color: var(--tertiary); line-height: 1.35; }
.sticky-cta .btn-sm { flex: none; white-space: nowrap; }


/* ---------- cursor: dot + lagging ring -------------------------------------
   Ported from the v1 build. The dot uses mix-blend-mode: difference, so it
   inverts whatever is under it — dark on the warm-white sections, light on the
   charcoal CTA band and footer — without any JS colour switching. The native
   cursor is hidden on desktop pointer devices only, and restored over text
   inputs so the I-beam is never lost. ------------------------------------- */
.cursor-dot, .cursor-ring {
  position: fixed; left: 0; top: 0; z-index: 300;
  border-radius: var(--r-round);
  pointer-events: none; opacity: 0;
  transform: translate3d(-50%, -50%, 0);
  will-change: transform;
}
.cursor-dot {
  width: 12px; height: 12px;
  background: #fff; mix-blend-mode: difference;
  transition: width .28s var(--e-out), height .28s var(--e-out), opacity .3s linear;
}
.cursor-ring {
  width: 38px; height: 38px; z-index: 299;
  border: 1px solid rgba(61, 68, 78, .55);
  transition: width .3s var(--e-out), height .3s var(--e-out),
              border-color .3s linear, opacity .3s linear;
}
.cursor-dot.is-on { opacity: 1; }
.cursor-ring.is-on { opacity: .9; }
.cursor-dot.is-link { width: 46px; height: 46px; }
.cursor-ring.is-link { width: 12px; height: 12px; border-color: rgba(61, 68, 78, .95); }

/* MUST be JS-gated: with JS disabled there is no replacement dot, so hiding the
   native cursor would leave the page with no pointer at all. */
@media (min-width: 1024px) and (hover: hover) and (pointer: fine) {
  .js-enabled body { cursor: none; }
  /* never take the I-beam away from something you type into */
  input, textarea, select, [contenteditable] { cursor: auto; }
}
@media (max-width: 1023px), (hover: none) {
  .cursor-dot, .cursor-ring { display: none !important; }
}


/* =============================================================================
   INTERIOR PAGES — case studies, about, client-work index
   ========================================================================== */
.page-head { position: relative; overflow: hidden; padding: 156px 0 var(--sec-gap); }
.page-head > .container { position: relative; z-index: 1; }
.crumb { font-size: 15px; font-weight: 300; color: var(--tertiary); margin-bottom: 22px; }
.crumb a { color: var(--muted); border-bottom: 1px solid transparent; transition: border-color .25s linear, color .25s linear; }
.crumb a:hover, .crumb a:focus-visible { color: var(--accent); border-bottom-color: var(--accent); }
.crumb span { margin: 0 8px; color: var(--hairline); }

.page-title {
  font-size: clamp(32px, 3.4vw, 50px); font-weight: 600; line-height: 1.12;
  max-width: 20ch; margin: 4px 0 24px;
}
.page-lead { font-size: 19px; max-width: 62ch; }
.page-note { margin-top: 20px; max-width: 62ch; font-size: 16px; font-weight: 300; color: var(--tertiary); }
.page-link { margin-top: 24px; font-size: 16px; }
.page-head .trust-strip { margin-top: 34px; margin-bottom: 0; }

/* key facts */
.fact-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--grid-gap); margin-bottom: 56px; }
.fact {
  display: grid; gap: 8px; align-content: start;
  background: var(--card); border: 1px solid var(--hairline);
  border-radius: 24px; padding: 24px 26px;
  transition: transform .35s var(--e-back), border-color .25s linear;
}
.fact:hover { transform: translateY(-4px); border-color: #aeb6c1; }
.fact-k { font-size: 13px; font-weight: 600; letter-spacing: .07em; text-transform: uppercase; color: var(--accent-mid); }
.fact-v { font-size: 17px; font-weight: 300; line-height: 1.45; color: var(--text); }

/* article body */
.prose { max-width: 74ch; }
.prose h2 { font-size: clamp(26px, 2.4vw, 34px); line-height: 1.15; margin: 56px 0 18px; }
.prose h2:first-child { margin-top: 0; }
.prose p { font-size: 18px; font-weight: 300; line-height: 1.65; color: var(--muted); margin-bottom: 18px; }
.prose p strong, .prose li strong { color: var(--text); font-weight: 600; }
.prose em { font-style: italic; }
.prose-list { display: grid; gap: 14px; margin: 6px 0 20px; }
.prose-list li {
  position: relative; padding-left: 26px;
  font-size: 18px; font-weight: 300; line-height: 1.6; color: var(--muted);
}
.prose-list li::before {
  content: ''; position: absolute; left: 2px; top: 11px;
  width: 6px; height: 6px; border: solid var(--accent); border-width: 1.6px 1.6px 0 0;
  transform: rotate(45deg);
}
.prose-note {
  margin-top: 26px; padding: 22px 26px;
  background: var(--card); border: 1px solid var(--hairline); border-radius: 24px;
  font-size: 17px; color: var(--muted);
}

.quote-block {
  margin: 34px 0; padding: 32px 34px;
  background: var(--card); border: 1px solid var(--hairline); border-radius: var(--r-card);
  transition: transform .4s var(--e-back), border-color .25s linear;
}
.quote-block:hover { transform: translateY(-4px); border-color: #aeb6c1; }
.quote-block blockquote { margin: 0 0 18px; font-size: 20px; font-weight: 300; line-height: 1.5; color: var(--text); }
.quote-block figcaption { display: grid; gap: 4px; padding-top: 18px; border-top: 1px solid var(--hairline); }

/* client-work index cards */
.cs-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--grid-gap); }
.cs-card {
  position: relative; display: flex; flex-direction: column; gap: 14px;
  background: var(--card); border: 1px solid var(--hairline);
  border-radius: var(--r-card); padding: 40px;
  transition: transform .45s var(--e-back), border-color .25s linear, box-shadow .45s var(--e-out);
}
.cs-card::after {
  content: ''; position: absolute; inset: 0; border-radius: inherit; pointer-events: none; opacity: 0;
  transition: opacity .4s linear;
  background: radial-gradient(280px circle at var(--mx, 50%) var(--my, 50%), rgba(6, 95, 70, .08), transparent 62%);
}
.cs-card:hover { transform: translateY(-5px); border-color: #aeb6c1; box-shadow: 0 18px 40px -22px rgba(20, 30, 70, .3); }
.cs-card:hover::after { opacity: 1; }
.cs-sector { font-size: 15px; font-weight: 300; color: var(--accent-mid); }
.cs-card h2 { font-size: 28px; font-weight: 600; text-transform: none; line-height: 1.15; transition: transform .4s var(--e-out); }
.cs-card:hover h2 { transform: translateX(5px); }
.cs-body { font-size: 17px; font-weight: 300; line-height: 1.55; color: var(--muted); }
.cs-tags { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 4px; }
.cs-tags li { font-size: 14px; font-weight: 300; color: var(--tertiary); border: 1px solid var(--hairline); border-radius: var(--r-round); padding: 6px 14px; }
.cs-go { margin-top: auto; padding-top: 18px; display: inline-flex; align-items: center; gap: 8px; font-size: 16px; color: var(--accent); }

/* prev / next */
.page-next {
  display: flex; flex-wrap: wrap; justify-content: space-between; gap: 16px;
  margin-top: 48px; padding-top: 26px; border-top: 1px solid var(--hairline);
}
.page-next a {
  display: inline-flex; align-items: center; gap: 8px;
  font-size: 17px; color: var(--muted);
  transition: color .25s linear, transform .3s var(--e-out);
}
.page-next a:hover { color: var(--accent); transform: translateX(3px); }

.about-panel { margin: 24px 0 12px; }


/* =============================================================================
   SCREENSHOTS — real client sites in a light browser frame
   The frame is CSS, not baked into the JPEG, so it stays crisp and can be
   restyled without recapturing. Images are 1200x750 (2x the card slot).
   ========================================================================== */
.shot {
  margin: 0; position: relative;
  border: 1px solid var(--hairline); border-radius: 22px;
  background: var(--card); overflow: hidden;
  transition: border-color .3s linear, box-shadow .45s var(--e-out), transform .45s var(--e-back);
}
.shot-bar {
  display: flex; align-items: center; gap: 6px;
  padding: 11px 14px; border-bottom: 1px solid var(--hairline);
  background: linear-gradient(180deg, #fbfbfc, #f4f5f7);
}
.shot-bar i { width: 8px; height: 8px; border-radius: var(--r-round); background: #d8dade; display: block; }
.shot-bar em {
  margin-left: 10px; font-style: normal;
  font-size: 12px; font-weight: 300; color: var(--tertiary);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.shot-img { position: relative; overflow: hidden; line-height: 0; }
.shot-img img { display: block; width: 100%; height: auto; }

/* mask reveal — JS-gated, so with JS off the image is simply visible */
.js-enabled .shot-img img { transform: scale(1.06); clip-path: inset(100% 0 0 0); }
.js-enabled .shot.is-in .shot-img img {
  transform: none; clip-path: inset(0 0 0 0);
  transition: transform 1.15s var(--e-out), clip-path 1s var(--e-out);
}
.js-fallback .shot-img img { transform: none !important; clip-path: none !important; }

.shot:hover { border-color: #aeb6c1; box-shadow: 0 18px 40px -24px rgba(20, 30, 70, .35); }
.js-enabled .shot.is-in:hover .shot-img img { transform: scale(1.035); transition: transform .6s var(--e-out); }

/* placement */
.case-lead .shot, .case-mini .shot, .cs-card .shot { margin-bottom: 26px; }
.cs-card .shot { margin-bottom: 4px; }
.page-shot { margin: 0 0 56px; }
.page-shot .shot-img img { max-height: 620px; object-fit: cover; object-position: top center; }

.work-strip { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--grid-gap); margin: 22px 0 12px; }
.work-strip a { display: grid; gap: 10px; }
.work-strip span { font-size: 15px; font-weight: 400; color: var(--muted); transition: color .25s linear; }
.work-strip a:hover span { color: var(--accent); }

/* =============================================================================
   RESPONSIVE
   ========================================================================== */
@media (max-width: 1180px) {
  :root { --pad: 56px; }
  .recog-grid { grid-template-columns: repeat(2, 1fr); }
  .fact-grid { grid-template-columns: repeat(2, 1fr); }
  .case-cols { grid-template-columns: repeat(2, 1fr); }
  .review-grid { grid-template-columns: 1fr; }
  .foot-grid { grid-template-columns: 1fr 1fr; }
  .sys-inner { gap: 44px; }
  .price-card { grid-template-columns: 1fr; gap: 30px; padding: 38px 34px; }
  .pc-side { justify-content: flex-start; }
  .pc-price { text-align: left; }
  .pc-note { text-align: left; }
  .case-row { grid-template-columns: repeat(2, 1fr); }
}

/* Below 1024px every pinned/sticky sequence becomes ordinary vertical content. */
@media (max-width: 1023px) {
  .sys-scroll { height: auto; }
  .sys-sticky { position: static; height: auto; display: block; }
  .sys-inner { grid-template-columns: 1fr; gap: 0; }
  /* Hide the RAIL, not the whole right column — .sys-done ("One connected
     customer journey") lives inside .sys-right, so `.sys-right { display: none }`
     deleted the section's closing line from every phone AND from anyone with
     reduced motion on. With one column it simply becomes the last row. */
  .sys-rail { display: none; }
  .sys-done { margin-top: 8px; padding-top: 24px; }
  .sys-count { display: none; }
  .sys-panes { position: relative; min-height: 0; padding-left: 30px; }
  /* fade both ends rather than letting the line dangle past the first/last dot */
  .sys-panes::before {
    content: ''; position: absolute; left: 5px; top: 0; bottom: 0; width: 2px;
    background: linear-gradient(180deg, transparent 0,
                var(--hairline) 34px, var(--hairline) calc(100% - 34px), transparent 100%);
  }
  .js-enabled .sys-pane,
  .sys-pane { position: relative; inset: auto; opacity: 1; transform: none; pointer-events: auto; padding: 26px 0; }
  /* the mobile stand-in for an active .sn-dot — same object, same live look */
  .sys-pane::before {
    content: ''; position: absolute; left: -30px; top: 34px;
    width: var(--dot-md); height: var(--dot-md); border-radius: var(--r-round);
    background-image: var(--grad);
    box-shadow: inset 0 0 0 var(--dot-ring) var(--accent), 0 0 0 3px rgba(61, 68, 78, .13);
  }
  .sys-pane h3 { font-size: 34px; }
  .sys-pane > p { font-size: 18px; margin-bottom: 18px; }
  .js-enabled .sys-done { opacity: 1; transform: none; }

  .faq-grid { grid-template-columns: 1fr; gap: 36px; }
  .proc-head, .faq-head { position: static; }

  .price-slot { position: static; }
  .price-slot + .price-slot { margin-top: var(--grid-gap); }
  .price-tail { display: none; }
  .price-card { transform: none !important; max-width: none; }
}

@media (max-width: 900px) {
  :root { --pad: 24px; --sec-gap: 56px; --lift: 32px; }
  .nav-links { display: none; }
  .burger { display: flex; }
  .hero { padding-top: 132px; }
  .team-grid, .cta-grid, .case-row { grid-template-columns: 1fr; }
  .team-panel { order: -1; }
  .gem { display: none; }   /* large blurred conics are not worth the paint cost on a phone */
  .case-cta { flex-direction: column; align-items: flex-start; }
  .page-head { padding-top: 132px; }
  .cs-grid { grid-template-columns: 1fr; }
  .sticky-cta { display: block; }
  /* the bar covers the bottom of the page — keep the footer clear of it */
  .footer { padding-bottom: 108px; }
}

@media (max-width: 620px) {
  :root { --pad: 18px; }
  body { font-size: 17px; }
  h2 { font-size: 30px; }
  .recog-grid, .case-cols, .foot-grid, .row2 { grid-template-columns: 1fr; }
  .hero-title { font-size: clamp(32px, 9vw, 46px); }
  .case-lead, .case-mini, .price-card, .team-panel, .qform, .review, .recog-item, .case-cta {
    padding: 26px 22px; border-radius: 28px;
  }
  .faq-q { font-size: 19px; gap: 14px; }
  .fact-grid, .work-strip { grid-template-columns: 1fr; }
  .fact, .quote-block, .prose-note, .cs-card { padding: 24px 22px; border-radius: 24px; }
  .prose p, .prose-list li { font-size: 17px; }
  .page-next { flex-direction: column; }
  .cmp-head { display: none; }
  .cmp-row { grid-template-columns: 1fr; gap: 0; padding-bottom: 14px; }
  .cmp-old, .cmp-new { padding: 14px; font-size: 16px; }
  .cmp-old { font-size: 17px; }
  /* At this width the bar has one job: one tap. It STACKS — the sub-line is a
     promise, not decoration, and the appended conversion block re-lays it out
     rather than deleting it (see "v19 CONVERSION — AGENT E" at EOF). */
  .sticky-cta { padding: 8px 12px calc(8px + env(safe-area-inset-bottom, 0px)); }
  .sticky-cta .btn-sm { width: 100%; justify-content: center; font-size: 16px; padding: 14px 18px; }
  .faq-a-inner { padding-right: 10px; }
  .hero-cta .btn, .qform .btn, .drawer .drawer-cta { width: 100%; justify-content: center; }
  .hero-cta { width: 100%; }
  .brand { white-space: nowrap; }
  .nav-right .btn-sm { display: none; }   /* the CTA lives in the drawer at this width */
  .nav-shell { padding-left: 16px; }
  .drawer { padding: 0 18px; }
  .tp-row { padding: 18px 8px; }
  .tp-k { min-width: 0; }
}

/* Touch devices: hover-only affordances must be permanently visible. */
@media (hover: none) {
  .case-lead::after, .case-mini::after,
  .price-card::after, .review::after, .team-panel::after { display: none; }
  .tp-rows:hover .tp-row { opacity: 1; }
  .tp-arw { opacity: .55; transform: rotate(45deg); }
}

/* =============================================================================
   REDUCED MOTION — everything stays visible, nothing animates
   ========================================================================== */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation: none !important;
    transition: none !important;
    scroll-behavior: auto !important;
  }
  .rot-badge, .hero-bg, .cta-panel-bg { transform: none !important; }

  .js-enabled [data-reveal]:not([data-revealed]),
  .js-enabled [data-reveal-lines]:not([data-revealed]),
  .js-enabled [data-hero-step]:not([data-revealed]),
  .js-enabled .sys-done,
  .btn:active { opacity: 1 !important; transform: none !important; }

  /* sticky sequences collapse to plain vertical content */
  .sys-scroll { height: auto !important; }
  .sys-sticky { position: static !important; height: auto !important; display: block !important; }
  .sys-inner { grid-template-columns: 1fr !important; }
  .sys-rail { display: none !important; }
  .js-enabled .sys-tags li { transform: none !important; }
  .sys-count { display: none !important; }
  .js-enabled .sys-pane, .sys-pane {
    position: relative !important; inset: auto !important;
    opacity: 1 !important; transform: none !important; pointer-events: auto !important;
    padding: 22px 0 !important;
  }
  .price-slot { position: static !important; }
  .price-slot + .price-slot { margin-top: var(--grid-gap) !important; }
  .price-tail { display: none !important; }
  .price-card { transform: none !important; }
  .proc-head, .faq-head { position: static !important; }
  .faq-a { height: auto !important; }
  .sys-line-fill, .tp-line-fill { height: 100% !important; }
  .cursor-dot, .cursor-ring { display: none !important; }
  body { cursor: auto !important; }
  .gem { display: none !important; }
  /* the press is a transition, not an animation, but it still needs pinning */
  /* The press is a state change, not an animation, but it still needs pinning.
     Hover keeps its brightening (static, instant, visible — the same call this
     file already makes for nav underlines) and loses its lift. */
  .btn-primary::before, .btn-primary::after { transition: none !important; }
  .btn-primary:hover, .btn-primary:focus-visible { transform: none !important; }
  .btn-primary:hover::after, .btn-primary:focus-visible::after { opacity: 1; }
  .btn-primary:active { transform: none !important; }
  .js-enabled .shot-img img { transform: none !important; clip-path: none !important; }
  .shot:hover { box-shadow: none !important; }
  .case-lead::after, .case-mini::after,
  .price-card::after, .review::after, .team-panel::after { opacity: 0 !important; }

  /* hover lifts are removed, not left mid-transform */
  .case-mini:hover, .case-lead:hover, .review:hover, .recog-item:hover, .price-card:hover {
    transform: none !important; box-shadow: none !important;
  }
  /* every transform introduced by the control-system pass, pinned. The washes,
     borders and colour changes all survive — static and instant, the same call
     this file already makes for the primary's sheen and the nav underlines. */
  .btn-ghost:hover, .btn-ghost:focus-visible { transform: none !important; box-shadow: none !important; }
  .btn-ghost:active, .choice:active, .cur-btn:active, .burger:active,
  .nav-links a:active, .foot-col a:active, .foot-social a:active { transform: none !important; }
  .trust-strip li:hover > *:not(.ts-dot) { transform: none !important; }
  .arw::before, .arw::after { transition: none !important; }
  .btn:hover .arw::before, .btn:focus-visible .arw::before { transform: none !important; opacity: 1 !important; }
  .btn:hover .arw::after, .btn:focus-visible .arw::after { opacity: 0 !important; }
  .nav-links a::after, .foot-col a::after, .drawer a:not(.btn)::after { transform: scaleX(0) !important; }
  .nav-links a:hover::after, .nav-links a.is-active::after,
  .foot-col a:hover::after, .drawer a:not(.btn):hover::after { transform: scaleX(1) !important; }
}

/* =============================================================================
   INTERIOR-PAGE COMPONENTS (v17e migration — services, locations, contact)

   Everything here exists because a live ranking page needs it and no existing
   v17e component covered it. Nothing here is used by the home page.

   Contrast measured on --bg #fbfbfc:
     --muted   #4e535b   7.50:1  text
     --edge    #848a94   3.36:1  boundary  (WCAG 1.4.11 minimum is 3:1)
     --edge-on #6d747e   4.56:1  boundary
     --accent  #3d444e   9.51:1  text
   No opacity below 1 on text. Transform + opacity only. Reduced motion at the
   bottom of the block collapses every hover transform.
   ========================================================================== */

/* -- Inline links inside long-form prose. The base rule is
      `a { color: inherit; text-decoration: none }`, so a prose link is
      completely invisible without this. The underline is permanent — never a
      hover-only tell. */
.prose a {
  color: var(--accent);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
  transition: text-decoration-thickness .2s linear, color .2s linear;
}
.prose a:hover, .prose a:focus-visible { color: var(--accent-dk); text-decoration-thickness: 2px; }

/* -- h3 inside prose. Long-form pages need a third level; nothing on the home
      page did. Sentence case, unlike the global uppercase h2, so the hierarchy
      stays legible rather than shouting twice. */
.prose h3 {
  font-size: clamp(20px, 1.6vw, 23px); font-weight: 600; line-height: 1.25;
  text-transform: none; margin: 34px 0 12px;
}

/* -- Numbered variant of .prose-list. Replaces the tick glyph with a numeral. */
ol.prose-list { counter-reset: pl; }
ol.prose-list li::before {
  content: counter(pl); counter-increment: pl;
  border: 0; transform: none; width: auto; height: auto;
  left: 0; top: 0; font-size: 15px; font-weight: 600; color: var(--accent);
}

/* -- .cs-card carrying an h3 instead of an h2: service cards on a location page
      are subordinate to that page's section h2. Same sizing intent as
      `.cs-card h2`, one step down. */
.cs-card h3 {
  font-size: 24px; font-weight: 600; text-transform: none; line-height: 1.18;
  transition: transform .4s var(--e-out);
}
.cs-card:hover h3 { transform: translateX(5px); }

/* -- Chip links: adjacent suburbs, related services, cross-links. These are
      controls, so the boundary is --edge (3.36:1), not --hairline (1.37:1). */
.link-chips { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 6px; }
.link-chips a {
  display: inline-flex; align-items: center; gap: 8px;
  font-size: 16px; font-weight: 300; color: var(--muted);
  border: 1px solid var(--edge); border-radius: var(--r-round);
  padding: 9px 18px;
  transition: color .25s linear, border-color .25s linear, transform .3s var(--e-out);
}
.link-chips a:hover, .link-chips a:focus-visible {
  color: var(--accent); border-color: var(--edge-on); transform: translateY(-2px);
}

/* -- A link inside a .fact value (contact details). Without this the base
      `a { color: inherit; text-decoration: none }` makes a mailto: or tel:
      indistinguishable from the text beside it. */
.fact-v a {
  color: var(--accent); text-decoration: underline;
  text-decoration-thickness: 1px; text-underline-offset: 3px;
  transition: text-decoration-thickness .2s linear, color .2s linear;
}
.fact-v a:hover, .fact-v a:focus-visible { color: var(--accent-dk); text-decoration-thickness: 2px; }

/* -- Meta line under a page-head: postcode / region / pricing. --tertiary is
      5.9:1, above the 4.5:1 floor; the <strong> lifts to --muted at 7.5:1. */
.head-meta { margin-top: 18px; font-size: 16px; font-weight: 300; color: var(--tertiary); }
.head-meta strong { color: var(--muted); font-weight: 400; }

/* -- Spacing for the second and later blocks inside one open .section, on
      pages that stack several headed groups in a single container. A class,
      not a repeated inline style, so the rhythm is defined once. */
.sec-head-next { margin-top: 64px; }

@media (max-width: 620px) {
  .link-chips a { font-size: 15px; padding: 8px 15px; }
  .sec-head-next { margin-top: 48px; }
}

@media (prefers-reduced-motion: reduce) {
  .link-chips a:hover, .link-chips a:focus-visible { transform: none !important; }
  .cs-card:hover h3 { transform: none !important; }
  .prose a { transition: none !important; }
}

/* ---------------------------------------------------------------------------
   SHADOW TEST — the inverted half. On the charcoal CTA band and the footer a
   black shadow is literally invisible, so every raised object there has been
   sitting flat. Same geometry, white light. Scoped to dark surfaces only, so
   nothing on the light page is touched.
   REVERT: delete this block.
   ------------------------------------------------------------------------ */
.cta-band .btn-primary,
.footer .btn-primary {
  box-shadow: 0 0.06em 0.10em -0.045em rgba(255, 255, 255, .34),
              0 0.40em 0.90em -0.55em  rgba(255, 255, 255, .40);
}
.cta-band .btn-primary:hover,
.cta-band .btn-primary:focus-visible,
.footer .btn-primary:hover,
.footer .btn-primary:focus-visible {
  box-shadow: 0 0.06em 0.10em -0.045em rgba(255, 255, 255, .40),
              0 0.72em 1.45em -0.60em  rgba(255, 255, 255, .52);
}
.cta-band .qform,
.cta-band .form-done {
  box-shadow: 0 0.40em 1.10em -0.50em rgba(255, 255, 255, .22),
              0 24px 60px -40px rgba(0, 0, 0, .55);
}
.footer .foot-social a:hover {
  box-shadow: 0 0.30em 0.70em -0.45em rgba(255, 255, 255, .38);
}
@media (prefers-reduced-motion: reduce) {
  .cta-band .btn-primary:hover, .footer .btn-primary:hover { box-shadow: none !important; }
}


/* ===== v19 INK BAND — AGENT C ===== */

/* -----------------------------------------------------------------------------
   THE SURFACE. One scoped token re-point; no parallel rule set.

   --band is the only token the existing
     `#system, #process, #team, #faq { background: var(--band) }`
   rule reads, so re-pointing it here paints the band ink WITHOUT editing that
   rule — and .sn-dot masks the rail with `--dot-fill: var(--band)`, so the dot's
   opaque mask follows the surface for free.

   `color: #fff` is NOT redundant with --text. `body { color: var(--text) }`
   resolves --text ON BODY, so h2/h3 inherit a COMPUTED #24272d (1.21:1 on ink) —
   re-pointing --text here would never reach them. Biggest trap in the conversion.
   -------------------------------------------------------------------------- */
#system {
  --band: var(--ink);
  color: #fff;

  --text:       #ffffff;            /* 18.05:1  h3, promise line, #sysNum, active label */
  --muted:      #a2a9b3;            /*  7.62:1  lead, tags, completed rail label        */
  --tertiary:   #868d98;            /*  5.39:1  "/ 05", upcoming rail label  (AA)       */

  /* THE INVERSION. On white the metal ramp is the palest thing on the page, which
     is why this component had to abandon it. On ink the ramp is the LIT state. */
  --accent:     #ffffff;            /* 18.05:1  active, hover, .sys-done, fill tip      */
  --accent-mid: var(--metal-lite);  /* #d8dee6 — 13.33:1  eyebrow, travelled light      */
  --accent-dk:  var(--metal-lite);

  /* --hairline is 1.37:1 on the page but 12.76:1 on ink — an UNLIT groove that
     would out-shout the lit fill. Mandatory flip. */
  --hairline:   #343941;            /*  1.55:1  rail groove, .sys-done divider          */
  --edge:       #656c77;            /*  3.41:1  tag-chip boundary                       */
  --edge-on:    #7d8490;            /*  4.79:1                                          */
  --card:       var(--ink);         /* chips are outlined, not filled                   */

  /* Thread hooks (Agent B) — flip with the surface for free. */
  --thread-line: var(--hairline);
  --thread-lit:  var(--accent-mid);
  --thread-node: var(--accent);
}

/* THE RAIL ON INK — upcoming 3.03:1 ring / completed 4.98:1 disc / active metal
   face 12.34–17.60:1 (2.48:1 brighter than a completed disc). */
#system .sn-dot { --dot-line: #5d646f; }
#system .sys-node.is-done .sn-dot { --dot-fill: #7f8794; --dot-line: #7f8794; }
#system .sys-node.is-active .sn-dot { box-shadow: 0 0 0 6px rgba(255, 255, 255, .13); }
#system .sys-node.is-active .sn-dot::after { opacity: 1; box-shadow: none; }

/* THE PROGRESS LINE. Was --accent-mid -> --accent: on ink that is 2.94 -> 1.84:1,
   i.e. the completed line would be DARKER than the groove it replaces — the light
   build's old bug in mirror image. Reversed, brightening toward the leading edge. */
#system .sys-line-fill {
  background: linear-gradient(180deg, #7f8794 0%, var(--metal-lite) 55%, var(--gem-lit) 100%);
}

@media (max-width: 1023px) {
  #system .sys-pane::before { box-shadow: 0 0 0 3px rgba(255, 255, 255, .14); }
}

/* THE SEAMS. Existing .sec-lift geometry, unchanged — only the value changes, so
   the reader reads a colour decision, not a new shape. 120px of silent ink opens
   and closes the band instead of arriving at a 17.43:1 edge 26px from content. */
.sys-sec { padding-bottom: calc(var(--sec-gap) + var(--lift)); }

/* THE NO-TRACK SHAPE. At >=1024 with JS blocked the 260vh track and the 84vh
   centred sticky box BOTH applied while .sys-pane was un-stacked: ~1125px of
   content in an 806px box, overflowing ~160px UPWARD out of the section, pinned
   for 260vh. On ink that paints white text over the light section above.
   Fix: collapsed shape is the BASE; the track is a .js-enabled enhancement gated
   on exactly the condition isDesktopMotion() uses, so CSS and JS cannot disagree. */
.sys-scroll { position: relative; }
.sys-sticky { display: block; }
.sys-inner  { display: grid; grid-template-columns: 1fr; gap: 0; width: 100%; }
.sys-rail, .sys-count { display: none; }

@media (min-width: 1024px) {
  .js-enabled .sys-scroll { height: 260vh; }
  .js-enabled .sys-sticky { position: sticky; top: 11vh; height: 84vh; display: flex; align-items: center; }
  .js-enabled .sys-inner  { grid-template-columns: minmax(0, 1fr) minmax(180px, 208px); gap: 80px; align-items: center; }
  .js-enabled .sys-rail   { display: block; }
  .js-enabled .sys-count  { display: block; }
  .sys-pane + .sys-pane { border-top: 1px solid var(--hairline); margin-top: 26px; padding-top: 26px; }
}
@media (min-width: 1024px) and (max-width: 1180px) { .js-enabled .sys-inner { gap: 44px; } }

/* only the LIVE sticky version drops the dividers — reduced motion stacks too */
@media (min-width: 1024px) and (prefers-reduced-motion: no-preference) {
  .js-enabled .sys-pane + .sys-pane { border-top: 0; margin-top: 0; padding-top: 0; }
}

/* watchdog: the scrub never ran — collapse the track, kill the dead indicators */
.js-fallback .sys-scroll { height: auto !important; }
.js-fallback .sys-sticky { position: static !important; height: auto !important; display: block !important; }
.js-fallback .sys-inner  { grid-template-columns: 1fr !important; gap: 0 !important; }
.js-fallback .sys-rail, .js-fallback .sys-count { display: none !important; }
@media (min-width: 1024px) {
  .js-fallback .sys-pane + .sys-pane {
    border-top: 1px solid var(--hairline) !important; margin-top: 26px !important; padding-top: 26px !important;
  }
}

/* SHORT-VIEWPORT GUARD. The thesis h2 is now the largest heading in the page body,
   so the pinned frame is taller: ~584px inside a 645px box at 1024x768. Below
   ~700px of viewport height it would overflow the pin. */
@media (min-width: 1024px) and (max-height: 760px) {
  .sys-head h2 { font-size: clamp(30px, 2.6vw, 38px); }
  .sys-head    { margin-bottom: 28px; padding-bottom: 22px; }
}
/* ===== end ===== */

/* ===== v19 THREAD + TYPE — AGENT B ===== */
/* Everything here is scoped to main[data-thread] = the HOME PAGE ONLY. No
   interior page, no shared partial and no existing declaration changes. */

/* ---------- B1. THE THREAD — tokens ---------- */
[data-thread] {
  counter-reset: chapter;
  --thr-inset: 44px;
  /* `100%` resolves against the pseudo's containing block = the section's padding
     box = the viewport width, so this tracks --container and --pad with no JS. */
  --thr-x: calc(max(0px, (100% - var(--container)) / 2) + var(--pad) - var(--thr-inset));
  --thr-line: var(--silver);
  --thr-lit:  linear-gradient(180deg, var(--accent-mid), var(--accent));
  --thr-cap:  var(--accent);
}

/* FAIL-OPEN, AS THE DEFAULT, NOT A PATCH. .is-scrub is added by initThread() only
   after it has built the light layer. Until then — JS off, module blocked, a throw,
   reduced motion — the TRACK is painted in the LIT colour, so the thread renders
   fully lit and static with no !important anywhere and no gate to forget. */
[data-thread]:not(.is-scrub)              { --thr-line: var(--accent-mid); }
[data-thread]:not(.is-scrub) > .cta-band  { --thr-line: var(--metal-lite); }

/* Dark surfaces re-point the token — the same move this file makes for --focus. */
[data-thread] > .cta-band {
  --thr-line: rgba(216, 222, 230, .30);
  --thr-lit:  linear-gradient(180deg, var(--metal-lite), var(--gem-lit));
  --thr-cap:  var(--gem-lit);
  --thr-node-ring: var(--metal-lite);
  --thr-node-halo: rgba(216, 222, 230, .20);
  /* Neither is a magic number: 80px IS .cta-band's margin-top (the only real gap
     in the page), 100px IS its padding-bottom (thread stops level with the form). */
  --thr-lead: 80px;
  --thr-tail: 100px;
}

/* THE INK BAND — same contract (Agent B section 7.4 / Agent C). */
[data-thread] > #system {
  --thr-line: rgba(216, 222, 230, .30);
  --thr-lit:  linear-gradient(180deg, var(--metal-lite), var(--gem-lit));
  --thr-cap:  var(--gem-lit);
  --thr-node-ring: var(--metal-lite);
  --thr-node-halo: rgba(216, 222, 230, .20);
}
[data-thread]:not(.is-scrub) > #system { --thr-line: var(--metal-lite); }

/* The hero segment is NOT Agent B's. .hero has overflow:hidden, and the hero
   needs a struck light with a node on it — so Agent A owns that segment as real
   elements inside .container, on the SAME centre line (--thread-x below resolves
   to the identical x). Agent B's thread starts at #how. */

/* ---------- B2. THE THREAD — the segments ----------
   Zero layout cost: both boxes are position:absolute, out of flow, generate no
   line boxes, contribute nothing to their containing block's height. No section
   gains a positioning context — .section, .hero and .cta-band are ALREADY
   position:relative. No `left` is ever animated. */
[data-thread] > .section::before,
[data-thread] > .cta-band::before,
[data-thread] .thr-lit {
  content: '';
  position: absolute;
  z-index: 2;                       /* above .sec-lift (1), far below .nav (100) */
  /* -5.5px = half the 11px box. The LINE is painted at 50% of the box, so
     without this it lands at --thr-x + 5.5 while the hero centres its own line
     ON --thr-x — a measured 5.5px kink at the hero seam, on the one element the
     whole page is built around. */
  left: calc(var(--pad) - var(--thr-inset) - 5.5px);         /* fallback, no max() */
  left: calc(var(--thr-x) - 5.5px);
  width: 11px;                      /* the box; the LINE inside it is --thread-w */
  top: calc(var(--thr-lead, 0px) * -1);
  bottom: var(--thr-tail, 0px);
  pointer-events: none;
  background-size: var(--thread-w) 100%;
  background-position: 50% 0;
  background-repeat: no-repeat;
}

[data-thread] > .section::before,
[data-thread] > .cta-band::before {
  background-image: linear-gradient(var(--thr-line), var(--thr-line));
}

/* scaleY(1) is the RESTING value, so a throw at any point after the element
   exists leaves the thread fully lit rather than dark. */
[data-thread] .thr-lit {
  background-image: var(--thr-lit);
  transform-origin: 50% 0;
  transform: scaleY(1);
  will-change: transform;
}

/* THE TERMINUS — a second background layer on the track, so it costs no
   element and cannot be squashed by the light's scaleY. (The hero draws its own
   start cap; its segment is Agent A's, not this one.) */
[data-thread] > .cta-band::before {
  background-image: linear-gradient(var(--thr-line), var(--thr-line));
}

/* scaleY(1) is the RESTING value, so a throw at any point after the element
   exists leaves the thread fully lit rather than dark. */
[data-thread] .thr-lit {
  background-image: var(--thr-lit);
  transform-origin: 50% 0;
  transform: scaleY(1);
  will-change: transform;
}

/* THE TWO ENDPOINTS — a second background layer on the track, so they cost no
   element and cannot be squashed by the light's scaleY. */
[data-thread] > .cta-band::before {
  background-image: linear-gradient(var(--thr-line), var(--thr-line));
}

/* scaleY(1) is the RESTING value, so a throw at any point after the element
   exists leaves the thread fully lit rather than dark. */
[data-thread] .thr-lit {
  background-image: var(--thr-lit);
  transform-origin: 50% 0;
  transform: scaleY(1);
  will-change: transform;
}

/* THE TWO ENDPOINTS — a second background layer on the track, so they cost no
   element and cannot be squashed by the light's scaleY. */
[data-thread] > .hero::before {
  background-image:
    radial-gradient(circle 4.5px at 50% 50%, var(--thr-cap) 99%, transparent 100%),
    linear-gradient(var(--thr-line), var(--thr-line));
  background-size: 11px 9px, 1px 100%;
  background-position: 50% 0, 50% 0;
}
[data-thread] > .cta-band::before {
  background-image:
    radial-gradient(circle 5px at 50% 50%, var(--thr-cap) 99%, transparent 100%),
    linear-gradient(var(--thr-line), var(--thr-line));
  background-size: 11px 10px, var(--thread-w) 100%;
  background-position: 50% 100%, 50% 0;   /* the terminus sits AT the form's foot */
}

/* ---------- B3. CHAPTER MARKERS ---------- */
[data-thread] .eyebrow[data-chapter],
[data-thread] > .cta-band .eyebrow {
  position: relative;
  font-size: 13px;
  font-weight: 500;
  line-height: 1.4;
  letter-spacing: .12em;
  text-transform: uppercase;
  margin-bottom: 20px;
  max-width: none;
}
[data-thread] .eyebrow[data-chapter]    { color: var(--accent-mid); }
[data-thread] > .cta-band .eyebrow      { color: var(--metal-lite); }

[data-thread] .eyebrow[data-chapter] { counter-increment: chapter; }
[data-thread] .ch-n::before {
  content: counter(chapter, decimal-leading-zero) " \2014\00a0";
  color: var(--text);
  font-weight: 600;
}

/* THE NODE ON THE THREAD. The eyebrow's left edge IS the container's content edge
   in all eight sections — including the three in a grid and the one in a sticky
   column, because all four are the FIRST grid track and start on the same rail. */
[data-thread] .eyebrow[data-chapter]::before,
[data-thread] > .cta-band .eyebrow::before {
  content: '';
  position: absolute;
  left: calc(var(--thr-inset) * -1 - 4.5px);
  top: calc(.7em - 4.5px);
  width: 9px; height: 9px;
  border-radius: var(--r-round);
  background-image: var(--grad);
  box-shadow: inset 0 0 0 var(--dot-ring-thin) var(--thr-node-ring, var(--accent)),
              0 0 0 3px var(--thr-node-halo, rgba(61, 68, 78, .13));
  pointer-events: none;
}
/* the CTA node is the TERMINUS, so it reads one step heavier than a chapter */
[data-thread] > .cta-band .eyebrow::before {
  width: 11px; height: 11px;
  left: calc(var(--thr-inset) * -1 - 5.5px);
  top: calc(.7em - 5.5px);
}

/* ---------- B4. THE TYPE SCALE ----------
   h1 > h2 > h3 > lead at EVERY width, structurally: floor 40>30, slope 6.6vw>4.0vw,
   cap 84>58, and clamp() is monotone in each argument. The documented 621-1167px
   inversion needed a FLAT 42px in a fluid ladder; no flat value is left. */
[data-thread] .hero-title {
  font-size: clamp(40px, 6.6vw, 84px);
  font-weight: 600;
  line-height: 1.02;
  letter-spacing: -.032em;
  max-width: 19ch;
  margin: 0 0 28px;
}

/* SENTENCE CASE. Uppercase fills ~100% of its em box vs ~72% for mixed, so a 58px
   uppercase h2 has the presence of ~80px against an 84px h1 — ratio 1.05, i.e. the
   flat hierarchy again. These strings are sentences with verbs and full stops.
   Uppercase now means: small type, or one word. Never a sentence. */
[data-thread] h2 {
  font-size: clamp(30px, 4.0vw, 58px);
  font-weight: 600;
  line-height: 1.06;
  letter-spacing: -.022em;
  text-transform: none;
}
[data-thread] .sec-head h2 { max-width: 19ch; }

/* #system keeps a smaller h2 (it rides a sticky column beside the stage word) but
   is no longer SMALLER THAN ITS OWN h3, which it was: 34 vs 52. */
[data-thread] .sys-head h2 { font-size: clamp(30px, 3.1vw, 46px); line-height: 1.08; max-width: 18ch; }

/* THE DISPLAY h3 TIER. Uppercase 800 stays — single words, names, one shout per
   block. Capped so no h3 can out-shout the h2 above it. */
[data-thread] .sys-pane h3 { font-size: clamp(26px, 2.4vw, 34px); }
[data-thread] .pc-top h3   { font-size: clamp(26px, 2.6vw, 38px); }

[data-thread] .lead {
  font-size: clamp(18px, 1.05vw + 6.9px, 22px);
  line-height: 1.55;
}
[data-thread] .sec-head  { max-width: 1040px; margin-bottom: 52px; }
[data-thread] .sec-lead  { margin-top: 20px; max-width: 62ch; }
[data-thread] .sys-lead  { font-size: clamp(17px, .8vw + 8.5px, 19px); max-width: 46ch; }

/* ---------- B5. SECTION RHYTHM — three acts, expressed as air ----------
   The page paid exactly 72px between every one of ten sections, which is why a
   fast scroll read as one block. --sec-gap feeds .section's padding and
   .sec-lift's padding-top; --lift feeds .sec-lift's margin-top and padding-top,
   so one value moves BOTH halves of a seam and they cannot disagree. */
[data-thread]                { --sec-gap: 88px; }
[data-thread] > #system      { --sec-gap: 120px; --lift: 64px; }
[data-thread] > #results     { --sec-gap: 112px; }
[data-thread] > #reviews,
[data-thread] > #faq         { --sec-gap: 76px; }

/* ---------- B6. RESPONSIVE ---------- */
@media (max-width: 1180px) {
  [data-thread] { --thr-inset: 30px; }     /* --pad drops to 56; clearance stays >= 25px */
}

/* BELOW 1024 THE THREAD DOES NOT EXIST. --pad is 24px (18 below 620) — no gutter
   to run a line in without touching copy or hugging the viewport edge like a
   rendering artefact. It is also where the build already drops every pinned
   sequence and where isDesktopMotion() stops firing, so ONE number governs "this
   page has scroll choreography". The MARKERS survive at every width. */
@media (max-width: 1023px) {
  [data-thread] > .section::before,
  [data-thread] > .cta-band::before,
  [data-thread] .thr-lit,
  [data-thread] .eyebrow[data-chapter]::before,
  [data-thread] > .cta-band .eyebrow::before { display: none; }

  [data-thread] .sys-pane h3 { font-size: 26px; }   /* 34 beat the 30px h2 floor */
  [data-thread]              { --sec-gap: 72px; }
  [data-thread] > #system    { --sec-gap: 88px;  --lift: 40px; }
  [data-thread] > #results   { --sec-gap: 84px; }
  [data-thread] > #reviews,
  [data-thread] > #faq       { --sec-gap: 64px; }
}

@media (max-width: 900px) {
  [data-thread]              { --sec-gap: 60px; }
  [data-thread] > #system    { --sec-gap: 76px;  --lift: 32px; }
  [data-thread] > #results   { --sec-gap: 72px; }
  [data-thread] > #reviews,
  [data-thread] > #faq       { --sec-gap: 56px; }
}

/* ---------- B7. THE THREE FAIL-OPEN GATES ---------- */
.js-fallback [data-thread]             { --thr-line: var(--accent-mid); }
.js-fallback [data-thread] > .cta-band { --thr-line: var(--metal-lite); }
.js-fallback [data-thread] > #system   { --thr-line: var(--metal-lite); }
.js-fallback [data-thread] .thr-lit    { transform: scaleY(1) !important; }

@media (prefers-reduced-motion: reduce) {
  [data-thread]             { --thr-line: var(--accent-mid); }
  [data-thread] > .cta-band { --thr-line: var(--metal-lite); }
  [data-thread] > #system   { --thr-line: var(--metal-lite); }
  [data-thread] .thr-lit    { transform: scaleY(1) !important; }
}
/* ===== end ===== */

/* ===== v19 HERO — AGENT A ===== */

/* ---------------------------------------------------------------------------
   THE HERO OWNS THE THREAD'S FIRST SEGMENT.

   Agent B's page thread is a ::before per section. .hero cannot use it: the
   section carries `overflow: hidden` (it clips .gem and .hero-bg), and the hero
   needs three layers, not one — a track, a lit fill, and a travelling light that
   strikes the line into existence. So the hero draws its own segment as real
   elements inside .container, and B's thread starts at #how.

   The two are COLLINEAR by construction, not by coincidence:
       B:  section left + max(0,(100%-container)/2) + --pad - --thr-inset
       A:  .container left border            + --thread-x
   and .container's left border edge IS `max(0,(100vw-container)/2)`, so setting
   --thread-x to (--pad - --thr-inset) puts both lines on the same pixel at every
   width. Change --thr-inset and both move together.
   ------------------------------------------------------------------------ */
[data-thread] {
  --thread-w:    2px;
  --thread-x:    calc(var(--pad) - var(--thr-inset));
  --hero-indent: 0px;
  /* the same centre line, expressed relative to .hero-copy's CONTENT box, which
     is where .hero-node and the trust strip's rule reach it from. Negative. */
  --ht-offset:   calc(var(--thread-x) - var(--pad) - var(--hero-indent));
}

/* ---------- the copy column ---------- */
.hero-copy { position: relative; padding-left: var(--hero-indent); }

/* THE META ROW. The old rule was `flex-wrap: wrap` with NO media query, so it
   silently became two rows near 1274px — and because --pad steps 96 -> 56 at
   1180px the wrap was not even monotonic: it wrapped 1181-1221, UNwrapped
   1142-1180, and wrapped again below 1142. Three transitions in a 100px band.
   Above 900px the structure is now fixed — nowrap, and the eyebrow re-wraps
   inside the row. Text taking a second line is reflow; a component changing its
   box model is a flip, and there is now exactly one, at a declared width. */
.hero-meta { flex-wrap: nowrap; gap: 20px; margin-bottom: 32px; }
.hero-meta .status-pill { flex: none; }
.hero .eyebrow { flex: 1 1 auto; min-width: 0; }

/* the lead's rule moves off the deleted .hero-body wrapper. Measure unchanged. */
.hero .lead { max-width: 58ch; margin-bottom: 36px; }

.hero-cta { position: relative; margin-bottom: 44px; }

/* THE PLINTH. The strip's border-top is the only horizontal rule in the hero and
   it now SPRINGS FROM THE SPINE: the negative margin pulls its box left to the
   thread's centre line, the equal padding puts the pills back exactly where they
   were. Net layout change for the content: zero. Hero-scoped — eight other pages
   use .trust-strip inside .page-head and must not move. */
.hero .trust-strip {
  padding-top: 28px;
  margin: 0 0 0 var(--ht-offset);
  padding-left: calc(var(--ht-offset) * -1);
}

/* The one string kept from .flow-card, and the only explanation in the hero. The
   thread runs past it and off the hero's bottom edge — the second beat: the line
   does not stop here. 5.12:1 on --bg. */
.hero-thread-cap {
  margin-top: 28px;
  font-size: 15px; font-weight: 400; letter-spacing: .01em;
  color: var(--tertiary);
}

/* ---------- the thread ----------------------------------------------------
   Absolute against .container (already position: relative), from the container's
   top edge — 132px into the hero, 46px clear of the nav shell — to past the
   hero's bottom border edge, where `overflow: hidden` clips it. Both ends are
   bound to box edges and neither is ever animated: wrap-proof, resize-proof,
   zero CLS by construction.
   ------------------------------------------------------------------------ */
.hero-thread {
  position: absolute; z-index: 0; pointer-events: none;
  left: calc(var(--thread-x) - var(--thread-w) / 2);
  top: 0;
  bottom: calc(var(--sec-gap) * -1);
  width: var(--thread-w);
}
.ht-track, .ht-fill { position: absolute; inset: 0; border-radius: var(--thread-w); }
.ht-track { background: var(--hairline); }               /* 1.37:1 — decorative */
.ht-fill {
  background: var(--accent);                             /* 9.51:1 — the lit line */
  box-shadow: 0 0 6px rgba(61, 68, 78, .22);
  transform-origin: top center;
  transform: scaleY(1);          /* THE DEFAULT IS FULLY LIT — JS off, no motion */
  will-change: transform;
}
/* the unlit state is the ONLY thing behind the JS gate, and the gate releases
   itself the moment the strike completes */
.js-enabled .hero-thread:not([data-struck]) .ht-fill { transform: scaleY(0); }

/* THE STRIKE'S HEAD. A pure animation artefact, so with JS off it does not exist
   at all. font-size is set to its own diameter because --metal-inset is written
   in em — the same trick .mctl-ico uses to scale a bevel to a control-sized disc.
   The white core keeps --gem-lit alive; it is otherwise orphaned by the removal
   of the flow rail. */
.ht-light { display: none; }
.js-enabled .ht-light {
  display: block; position: absolute;
  top: 0; left: 50%;
  width: var(--dot-md); height: var(--dot-md); font-size: var(--dot-md);
  margin: calc(var(--dot-md) / -2) 0 0 calc(var(--dot-md) / -2);
  border-radius: var(--r-round);
  background-color: var(--metal-back);      /* fallback body only — never seen */
  background-image:
    radial-gradient(circle at 34% 30%, var(--gem-lit) 0 20%, transparent 58%),
    var(--grad);
  /* Halo extent is capped at spread + blur/2 = 19px, so the outermost pixel of
     the glow lands 74px from the container edge while the copy starts at 96px.
     It can never wash under text and can never lower a contrast ratio. */
  box-shadow: var(--metal-inset),
              0 0 0 1.5px var(--accent),
              0 0 12px 3px rgba(170, 178, 189, .8),
              0 0 26px 6px rgba(223, 228, 235, .5);
  opacity: 0;
  will-change: transform, opacity;
}

/* CONSTANT VELOCITY, one pass, then gone. Both halves are CSS animations driven
   by one custom property, so the strike runs on the compositor: no rAF frame, no
   Motion One API surface, transform and opacity only. */
.hero-thread.is-struck .ht-fill  { animation: htDraw 1.4s linear .35s forwards; }
.hero-thread.is-struck .ht-light { animation: htRun   1.4s linear .35s forwards,
                                              htFlash 1.4s linear .35s forwards; }
@keyframes htDraw  { from { transform: scaleY(0) }            to { transform: scaleY(1) } }
@keyframes htRun   { from { transform: translate3d(0, 0, 0) }
                     to   { transform: translate3d(0, var(--ht-travel, 640px), 0) } }
@keyframes htFlash { 0% { opacity: 0 } 6%, 90% { opacity: 1 } 100% { opacity: 0 } }

/* ---------- the node -------------------------------------------------------
   The only other metal in the hero, and the second of the direction's three
   sanctioned metal moments (CTA / travelling light / active marker). It joins the
   MARKER FAMILY by selector-list edit, so it inherits the radius, the ring recipe,
   the transition and the live overlay with no duplicated CSS. top: 28px is half a
   .btn (18 + 18 + 18 + 2px border = 56px), so it centres on the first button at
   every width, including the stacked layout below 620px.
   ------------------------------------------------------------------------ */
.hero-node {
  position: absolute; z-index: 1; pointer-events: none;
  width: var(--dot-md); height: var(--dot-md);
  left: var(--ht-offset); top: 28px;
  transform: translate(-50%, -50%);
  --dot-line: var(--dot-done);     /* the family's DONE ring while it waits */
}
.hero-node::after { opacity: 1; }                              /* LIT = the default */
.js-enabled .hero-node:not([data-lit])::after { opacity: 0; }

/* The copy now occupies the left ~60% at desktop, so the facet field's hole moves
   left with it: the facets bloom in the space .flow-card used to fill and the
   headline still sits on clean paper. Decorative; already display:none below
   900px and under reduced motion. */
.hero .gem {
  -webkit-mask-image: radial-gradient(112% 96% at 33% 44%, transparent 0%, transparent 32%, #000 80%);
          mask-image: radial-gradient(112% 96% at 33% 44%, transparent 0%, transparent 32%, #000 80%);
}

/* ---------- THE HERO'S ONE LAYOUT BREAKPOINT ------------------------------
   900px, because that is where this page ALREADY swaps the nav for a burger,
   --pad from 56 to 24, and four other components — so the whole page changes
   personality at one declared width rather than one component changing alone at
   a width nobody chose. Two things happen and nothing else:
     1. the meta row becomes an explicit stack
     2. the thread leaves the gutter (at --pad 18 a 2px line would sit 9px from
        the screen edge and read as a rendering artefact) and moves to the content
        edge; the copy indents 26px to clear it. At 390px: thread at x=18, node
        spanning 11-25px, copy at x=44.
   ------------------------------------------------------------------------ */
@media (max-width: 900px) {
  [data-thread] { --thread-x: var(--pad); --hero-indent: 26px; }
  .hero-meta { display: block; margin-bottom: 28px; }
  .hero-meta .status-pill { margin-bottom: 12px; }
  .hero .lead { margin-bottom: 32px; }
  .hero-cta   { margin-bottom: 36px; }
  /* the light is only 19px from the copy here, so the halo is trimmed to an 11px
     extent — it still cannot reach text, at any density */
  .js-enabled .ht-light {
    box-shadow: var(--metal-inset),
                0 0 0 1.5px var(--accent),
                0 0 10px 2px rgba(170, 178, 189, .75);
  }
}

/* ---------- the three fail-open gates ------------------------------------- */
.js-fallback .ht-fill        { transform: scaleY(1) !important; }
.js-fallback .ht-light       { display: none !important; }
.js-fallback .hero-node::after { opacity: 1 !important; }

@media (prefers-reduced-motion: reduce) {
  /* the thread renders fully lit and static — still a thread, no scrub */
  .ht-fill  { transform: scaleY(1) !important; }
  .ht-light { display: none !important; }
  .hero-node::after { opacity: 1 !important; }
  .hero-thread.is-struck .ht-fill,
  .hero-thread.is-struck .ht-light { animation: none !important; }
}
/* ===== end ===== */

/* v19: the facet wash is REMOVED from the hero. With the copy in one editorial
   column the right third is clean paper, and a masked conic field there did not
   read as polish — it read as a smudge on an otherwise empty page. The gem
   survives where it has something to sit behind: the CTA band. */
.hero .gem { display: none; }

/* ===== v19 PROOF + OFFER — AGENT D ===================================== */
/* Act III, six sections, six shapes. APPEND AT EOF: this block carries its own
   responsive and reduced-motion tails and they only work if nothing follows.
   Every selector is ID-scoped (#results / #process / #ways / #team / #reviews /
   #faq) for two reasons: .tp-*, .team-panel, .faq-row, .review::after and
   .shot are also shipped by /about/, /case-studies/ and /services/seo/, and ID
   scoping means a later agent block appended after this one cannot silently
   win on equal specificity.
   Nothing here animates width, height, top, left or inset. Nothing here puts
   meaning below opacity 1. No hover or focus state below lowers contrast.
   Nothing here is written on a top-level <section>: no overflow, no isolation,
   no filter, no contain, no transform, no opacity — the Thread's ::before seam
   overlap survives untouched, and section air is still --sec-gap / --lift. */

/* ---------------------------------------------------------------------------
   5. SELECTED CLIENT WORK — THE EXHIBIT WALL                      volume 4
   No clip, no control, no gate. The .js-enabled / .js-fallback pair that used
   to hold 13 paragraphs at max-height 0 has nothing left to hold: the fold is
   gone, not defended.
   ------------------------------------------------------------------------ */
#results .exhibits { display: grid; gap: 96px; }

#results .exhibit {
  display: grid;
  grid-template-columns: minmax(0, 1.06fr) minmax(0, .94fr);
  gap: 0 56px;
  align-items: start;
}
/* Explicit placement, not `order` — order on a grid item does not move it
   between columns, and the zig-zag is the point: the eye is thrown left, right,
   left, right down the section instead of running one straight gutter. */
#results .exhibit:nth-child(even) > .exh-shot { grid-column: 2; grid-row: 1; }
#results .exhibit:nth-child(even) > .exh-copy { grid-column: 1; grid-row: 1; }

/* The proof stays on screen for as long as you are reading about it. sticky is
   not motion, but .proc-head and .faq-head are already un-stuck under reduced
   motion, so this follows the same call (see the tail at the bottom). */
@media (min-width: 1024px) {
  #results .exh-shot { position: sticky; top: 128px; }
}

#results .exh-sector {
  font-size: 16px; font-weight: 300; color: var(--accent-mid);
  margin-bottom: 6px; transition: color .3s linear;
}
#results .exhibit:hover .exh-sector { color: var(--accent-dk); }
/* INTEGRATOR: floor lowered 26 -> 22. The live [data-thread] h2 scale bottoms
   out at 30px, and the house rule is h3 < 0.78 x h2 at EVERY width; 26/30 is
   0.87. 22/30 = 0.73, and the cap is untouched (34/58 = 0.59). */
#results .exh-copy h3 {
  font-size: clamp(22px, 2.4vw, 34px); font-weight: 600;
  line-height: 1.1; letter-spacing: -.015em;
}

/* THE LEDGER. The label becomes a rail rather than a caption: 12px, tracked,
   uppercase, in its own column, so four cases read as four spec sheets instead
   of four blocks of prose. text-transform is presentational — every label
   string is byte-identical in the DOM, in innerText and to a screen reader. */
#results .exh-lines { margin-top: 24px; }
#results .exh-lines .case-line {
  display: grid; grid-template-columns: 150px minmax(0, 1fr);
  gap: 2px 22px; padding: 18px 0;
  border-top: 1px solid var(--hairline);
}
#results .exh-lines .case-line .l {
  margin: 0; padding-top: 4px;
  font-size: 12px; font-weight: 600; letter-spacing: .08em;
  text-transform: uppercase; color: var(--accent-mid);
}
#results .exh-lines .case-line .case-body {
  font-size: 17px; font-weight: 300; line-height: 1.55; color: var(--muted);
}
#results .exh-copy .case-link { margin-top: 26px; }
#results .case-cta { margin-top: 76px; }

/* ---------------------------------------------------------------------------
   6. PROCESS — THE CONTENTS STRIP                                 volume 2
   The quietest thing between the exhibits and the price stack, and the only
   horizontal object in Act III. Four 64px discs that lit up in var(--grad) are
   now four 12px ordinals; the scrubbed rail is gone so the Thread is the only
   scroll-linked light on the page. No hover: a .pstep contains no link and no
   button, so a hover state on it was decoration pretending to be affordance.
   ------------------------------------------------------------------------ */
#process .proc-index { max-width: 1160px; }
#process .proc-head  { position: static; max-width: 780px; margin-bottom: 40px; }

#process .proc-steps {
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
  border-top: 1px solid var(--hairline);
}
#process .pstep {
  display: block; background: none; border-top: 0;
  border-left: 1px solid var(--hairline);
  padding: 26px 28px 0 26px;
}
#process .pstep:first-child { border-left: 0; padding-left: 0; }
#process .pnum {
  display: block; width: auto; height: auto;
  background: none; border-radius: 0; box-shadow: none; transform: none;
  font-size: 12px; font-weight: 600; letter-spacing: .1em;
  color: var(--accent-mid); margin-bottom: 12px;
}
#process .pbody h4 {
  font-size: 21px; font-weight: 600; color: var(--text);
  margin-bottom: 8px; transform: none;
}
#process .pbody p {
  font-size: 16px; font-weight: 300; line-height: 1.5;
  color: var(--muted); opacity: 1; transform: none;
}

/* ---------------------------------------------------------------------------
   7. FIX / REBUILD / GROW — THE QUOTE SHEETS         volume 5  (LOUDEST)
   Three levers, no new string, no new number:
     1. the amount is the largest type on the page after the h1
     2. the price sits in its own ruled column — a total on a quote
     3. "Most common", a string ALREADY IN THE DOM, is drawn instead of
        asserted: that one sheet inverts to --ink.
   It is an OBJECT, not a band: max-width 1080px, centred, never touching the
   viewport edge. DIRECTION-v19 §2's "one dark band in Act III" holds — #system
   is still the only dark band — and ink now marks the three places on the page
   that ask for a decision and nowhere else.
   ------------------------------------------------------------------------ */
#ways .price-card { max-width: 1080px; padding: 48px 52px; }
#ways .pc-amt     { font-size: clamp(50px, 4.8vw, 76px); letter-spacing: -.03em; }
#ways .pc-per     { font-size: 20px; }

@media (min-width: 1181px) {
  #ways .price-card { grid-template-columns: 1.4fr .8fr; gap: 0; }
  #ways .pc-side    { padding-left: 48px; border-left: 1px solid var(--hairline); }
}

/* THE INK SHEET — every token re-pointed. On --ink: #fff 18.28:1,
   --metal-lite 13.50:1, --silver 10.96:1. --accent would be 1.86:1, which is
   why not one of these inherits. */
#ways .price-card.is-featured {
  --focus: var(--metal-lite);
  background: var(--ink); border-color: var(--ink); color: #fff;
  box-shadow: 0 30px 72px -38px rgba(20, 30, 70, .78);
}
#ways .price-card.is-featured:hover {
  border-color: var(--ink);
  box-shadow: 0 34px 80px -32px rgba(20, 30, 70, .86);
}
#ways .price-card.is-featured .pc-for,
#ways .price-card.is-featured .pc-list li      { color: var(--metal-lite); }
#ways .price-card.is-featured:hover .pc-list li { color: #fff; }   /* 13.50 -> 18.28 */
#ways .price-card.is-featured .pc-list li      { border-top-color: rgba(255, 255, 255, .16); }
#ways .price-card.is-featured .pc-list li::before { border-color: var(--silver); }
#ways .price-card.is-featured .pc-from,
#ways .price-card.is-featured .pc-alt,
#ways .price-card.is-featured .pc-per,
#ways .price-card.is-featured .pc-note         { color: var(--silver); }
#ways .price-card.is-featured .pc-amt,
#ways .price-card.is-featured:hover .pc-amt    { color: #fff; }    /* hover holds, never drops */
#ways .price-card.is-featured .pc-side         { border-left-color: rgba(255, 255, 255, .16); }
/* The covered-card veil is a DARKEN — invisible on ink. Same --cover value,
   same rAF write, no JS change: this one card recedes by being LIGHTENED.
   #fbfbfc at .13 over --ink measures #313337; white on it is 12.66:1. */
#ways .price-card.is-featured::before { background: #fbfbfc; }
/* the cursor spotlight is a dark wash; on ink it does nothing */
#ways .price-card.is-featured::after {
  background: radial-gradient(280px circle at var(--mx, 50%) var(--my, 50%),
              rgba(255, 255, 255, .07), transparent 62%);
}

/* ---------------------------------------------------------------------------
   8. ONE ACCOUNTABLE TEAM — THE CERTIFICATE                       volume 3
   The only centred section on the site and the only 2x2 cross-ruled plate — a
   full stop between the two loudest sections. #team-scoped throughout because
   /about/ and /services/seo/ still ship .team-panel, .tp-line and .tp-rows.
   INTEGRATOR: the chapter marker is NOT centred and is NOT inside .team-seal.
   Agent B draws the Thread's node on the eyebrow's own left edge, so an eyebrow
   inside a 960px centred box would drag the node 144px off the line. The
   eyebrow stays on the container rail; the certificate under it is centred.
   ------------------------------------------------------------------------ */
#team .team-seal        { max-width: 960px; margin: 0 auto; text-align: center; }
#team .team-copy .lead  { margin: 18px auto 0; max-width: 60ch; }
#team .tp-head          { margin: 56px 0 16px; }

#team .tp-rows {
  display: grid; grid-template-columns: 1fr 1fr;
  padding-left: 0; text-align: left;
  border-top: 1px solid var(--hairline);
  border-bottom: 1px solid var(--hairline);
}
#team .tp-row                  { display: block; border-top: 0; }
#team .tp-row:nth-child(odd)   { padding: 26px 28px 26px 0; border-right: 1px solid var(--hairline); }
#team .tp-row:nth-child(even)  { padding: 26px 0 26px 28px; }
#team .tp-row:nth-child(-n+2)  { border-bottom: 1px solid var(--hairline); }
#team .tp-k   { display: block; min-width: 0; font-size: 21px; margin-bottom: 6px; }
#team .tp-d   { display: block; font-size: 16px; }
#team .tp-arw { display: none; }   /* aria-hidden decoration; an inline arrow in a
                                      grid cell points at nothing */
/* the base rule recedes un-hovered rows to --dot-mute; on a cross-ruled plate
   that reads as a rendering fault, not as a recede */
#team .tp-rows:hover .tp-row { border-color: var(--hairline); }

/* the four promises become a caption strip, not a fifth stacked list */
#team .check-list {
  margin-top: 40px; display: flex; flex-wrap: wrap;
  justify-content: center; gap: 12px 34px;
}
#team .check-row   { align-items: center; gap: 10px; }
#team .check-row p { font-size: 16px; }

#team .badge-wrap { margin: 48px auto 0; }
#team .team-cta   { margin-top: 26px; }

/* ---------------------------------------------------------------------------
   9. TESTIMONIALS — THE TESTIMONY                                 volume 4
   The page's only third-party proof was three identical 380px cards you had to
   lean in to read. It is now the largest body type in Act III at full measure,
   with the attribution and the five stars in a ruled right rail — three
   stacked ★★★★★ down one column is the mark the section was missing.
   The volume is TYPE. No card, no border, no shadow, no new asset.
   ------------------------------------------------------------------------ */
#reviews .review-grid {
  display: grid; grid-template-columns: 1fr; gap: 0;
  border-top: 1px solid var(--hairline);
}
#reviews .review {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(200px, 280px);
  grid-template-areas: "mark meta" "quote meta";
  gap: 0 56px; align-items: start;
  padding: 44px 0;
  background: none; border: 0; border-bottom: 1px solid var(--hairline);
  border-radius: 0; box-shadow: none; transform: none;
}
#reviews .review:hover { transform: none; box-shadow: none; border-color: var(--hairline); }
#reviews .review::after { display: none; }   /* spotlight needs a card to sit in */

#reviews .rv-mark {
  grid-area: mark; font-size: 46px; line-height: .55;
  color: var(--silver); margin-bottom: 14px;
}
#reviews .review:hover .rv-mark { color: var(--accent); }
#reviews .review blockquote {
  grid-area: quote; flex: none;
  font-size: clamp(22px, 2.1vw, 30px); font-weight: 300;
  line-height: 1.3; letter-spacing: -.012em; color: var(--text);
}
#reviews .review figcaption {
  grid-area: meta; align-content: start; gap: 6px;
  padding: 6px 0 0 40px;
  border-top: 0; border-left: 1px solid var(--hairline);
}
#reviews .rv-who { font-size: 18px; }
#reviews .rv-src { font-size: 16px; }

/* ---------------------------------------------------------------------------
   10. FAQ — THE REGISTER                                volume 1 (QUIETEST)
   The h2 is the FIRST ROW of the list, not a sticky column beside it — the
   only place on the page where the heading sits inside the component it
   introduces, which is what makes it unmistakable while also making it the
   smallest. The 40px bordered disc is gone: the +/x glyph is the indicator, at
   13.36:1 where WCAG 1.4.11 asks 3:1. #faq-scoped — /services/seo/ also ships
   .faq-row and must not move.
   ------------------------------------------------------------------------ */
#faq .faq-register { max-width: 900px; }
#faq .faq-head {
  position: static; padding-bottom: 24px;
  border-bottom: 1px solid var(--hairline);
}
#faq .faq-list  { border-top: 0; }
#faq .faq-q     { font-size: 19px; font-weight: 500; padding: 20px 0; gap: 20px; }
#faq .faq-ico   { width: 22px; height: 22px; border: 0; background: none; }
#faq .faq-q:hover .faq-ico,
#faq .faq-q:focus-visible .faq-ico { background: none; border-color: transparent; }
#faq .faq-q:active .faq-ico        { background: none; }
/* open state goes DARKER, not to --accent: rest 13.36 -> open 16.17 */
#faq .faq-row.is-open .faq-ico     { border-color: transparent; color: var(--on-metal); }
#faq .faq-a-inner   { padding: 0 0 26px; max-width: 74ch; }
#faq .faq-a-inner p { font-size: 16px; }

/* ---------------------------------------------------------------------------
   RESPONSIVE TAIL
   ------------------------------------------------------------------------ */
@media (max-width: 1180px) {
  #results .exhibits { gap: 72px; }
  #results .exhibit  { gap: 0 40px; }
  #reviews .review   { gap: 0 36px; grid-template-columns: minmax(0, 1fr) minmax(180px, 240px); }
  /* INTEGRATOR: the file already narrows .price-card here; an ID rule beats it,
     so the same value has to be restated or the card keeps desktop padding. */
  #ways .price-card  { padding: 38px 34px; }
}

@media (max-width: 1023px) {
  #results .exhibit { grid-template-columns: 1fr; gap: 26px 0; }
  #results .exhibit:nth-child(even) > .exh-shot,
  #results .exhibit:nth-child(even) > .exh-copy { grid-column: 1; grid-row: auto; }
  #results .exh-shot { position: static; }

  #process .proc-steps { grid-template-columns: 1fr 1fr; }
  #process .pstep,
  #process .pstep:first-child   { border-left: 0; padding: 24px 24px 24px 0; }
  #process .pstep:nth-child(even) { padding: 24px 0 24px 24px; border-left: 1px solid var(--hairline); }
  #process .pstep:nth-child(n+3)  { border-top: 1px solid var(--hairline); }
}

@media (max-width: 900px) {
  #results .exhibits { gap: 56px; }

  #reviews .review {
    grid-template-columns: 1fr;
    grid-template-areas: "mark" "quote" "meta";
    gap: 0; padding: 34px 0;
  }
  #reviews .review figcaption {
    padding: 22px 0 0; margin-top: 22px;
    border-left: 0; border-top: 1px solid var(--hairline);
  }

  #team .team-seal       { text-align: left; }
  #team .team-copy .lead { margin-left: 0; }
  #team .tp-rows         { grid-template-columns: 1fr; }
  #team .tp-row,
  #team .tp-row:nth-child(odd),
  #team .tp-row:nth-child(even)  { padding: 22px 0; border-right: 0; border-bottom: 1px solid var(--hairline); }
  #team .tp-row:last-child       { border-bottom: 0; }
  #team .check-list      { justify-content: flex-start; }
  #team .badge-wrap      { margin-left: 0; }
}

@media (max-width: 620px) {
  #results .exh-lines .case-line { grid-template-columns: 1fr; gap: 6px; padding: 16px 0; }

  #process .proc-steps { grid-template-columns: 1fr; }
  #process .pstep,
  #process .pstep:nth-child(even) { padding: 20px 0; border-left: 0; }
  #process .pstep:nth-child(n+2)  { border-top: 1px solid var(--hairline); }

  #ways .price-card { padding: 30px 24px; }
  #ways .pc-amt     { font-size: 44px; }

  #reviews .review blockquote { font-size: 21px; }
}

/* ---------------------------------------------------------------------------
   REDUCED MOTION
   Everything in this block is already static — no keyframes, no scrub, no
   scroll listener. The one call to make is the sticky screenshot, which
   follows .proc-head / .faq-head, both of which the file already un-sticks.
   ------------------------------------------------------------------------ */
@media (prefers-reduced-motion: reduce) {
  #results .exh-shot { position: static !important; }
}
/* ===== end ============================================================== */

/* ===== v19 CONVERSION — AGENT E ===== */
/* Appended after the client-work block. That block's "MUST BE LAST" note is about
   its OWN responsive / reduced-motion tails winning over earlier rules — nothing
   below touches #results / #process / #ways / #team / #reviews / #faq internals,
   and everything below needs to sit after the 1023/900/620px and reduced-motion
   blocks to win. */

/* ---------------------------------------------------------------------------
   1. DRAWER = a real dialog.
   Was a 100%-wide strip pinned at top:86px with no scrim, no scroll lock, no
   focus trap and no outside-click target — and the nav can slide away
   (.is-hidden) while it is open, taking the only close control with it.
   ------------------------------------------------------------------------ */
html.drawer-open, html.drawer-open body { overflow: hidden; }
/* the burger is the only way back out — it may never leave while the panel is up */
.drawer-open .nav.is-hidden { transform: none; }
/* and a keyboard user inside the header keeps their landmark, drawer or not */
.nav.is-hidden:focus-within { transform: none; }

.drawer-grp { display: flex; flex-direction: column; gap: 16px; }
.drawer-sep { display: block; height: 1px; background: var(--hairline); }

/* ---------------------------------------------------------------------------
   1b. NAV LEGIBILITY OVER THE INK BAND.        [INTEGRATOR ADDITION]
   The fixed shell now passes over #system (--ink). With JS running that is safe:
   .is-stuck is set at y > 80 and #system starts far below that, so the shell is
   always wearing its rgba(252,252,252,.84) plate there.
   But .is-stuck is a JS CLASS. With JS blocked, or after the .js-fallback
   watchdog fires, it is never added, the shell stays transparent, and --muted
   #4e535b over --ink #131519 measures 1.21:1 — the nav effectively disappears
   for two full viewports. So the plate becomes unconditional in exactly the two
   gates where the class cannot arrive. Same declarations as .nav.is-stuck
   .nav-shell, so there is one plate on the page, not two.
   ------------------------------------------------------------------------ */
html:not(.js-enabled) .nav-shell,
.js-fallback .nav-shell {
  background: rgba(252, 252, 252, .84);
  -webkit-backdrop-filter: blur(16px);
          backdrop-filter: blur(16px);
  border-color: var(--hairline);
  box-shadow: 0 12px 34px -22px rgba(20, 30, 70, .38);
}

/* ---------------------------------------------------------------------------
   2. STICKY CTA — parked = unfocusable.
   translateY(130%) leaves a fully tabbable link 130% below the fold. visibility
   is animatable as a discrete step, so the slide still plays on the way in and
   the control only becomes reachable once it is on screen. Nothing is
   display:none'd, so no copy leaves the DOM.
   ------------------------------------------------------------------------ */
.sticky-cta.is-on { visibility: visible; }

/* The drawer PRECEDES .sticky-cta in the body (nav.html vs tail.html), so a
   sibling combinator reaches it — no :has(), no JS. (0,2,1) beats
   `.sticky-cta.is-on` at (0,2,0). */
.drawer:not([hidden]) ~ .sticky-cta { transform: translateY(130%); visibility: hidden; }

/* ---------------------------------------------------------------------------
   3. FORM — error, prefill and submitting states.
   No new copy: the boundary and aria-invalid carry the error, the browser's own
   validation bubble carries the sentence.
   ------------------------------------------------------------------------ */
.field.is-err input, .field.is-err textarea, .field.is-err select { border-color: #b3261e; }
.field.is-err label { color: var(--text); }
.choices.is-err .choice { border-color: #b3261e; }
/* every hover on this site ADDS definition — 6.53:1 -> 8.94:1, never the reverse */
.field.is-err input:hover, .field.is-err textarea:hover, .field.is-err select:hover,
.choices.is-err .choice:hover { border-color: #8e1e18; }
/* must be (0,3,0): `.field.is-err input` (0,2,0) sits after `.field input:focus`
   in the cascade and would otherwise eat the focus treatment entirely */
.field.is-err input:focus, .field.is-err textarea:focus, .field.is-err select:focus {
  border-color: #8e1e18;
  box-shadow: 0 0 0 4px rgba(179, 38, 30, .16);
}

/* the answer carried in from a price card. --accent (9.83:1 on the white panel,
   well over the 3:1 control floor) — deliberately the loudest boundary in the
   form, because this is the field the visitor must SEE has been answered. */
.field.is-prefilled select { border-color: var(--accent); }

/* the failure line's address becomes reachable. Colour alone cannot distinguish
   a link from its surrounding text, so it is underlined. */
.form-msg a { text-decoration: underline; text-underline-offset: 2px; }
.form-msg a:hover, .form-msg a:focus-visible { color: #8e1e18; }

/* SUBMITTING. main.js now sets aria-disabled instead of disabled: `disabled`
   removes the button from the a11y tree mid-request and dumps focus on <body>.
   Same visual as the existing :disabled block — the light goes out of the
   MATERIAL, never out of the label (12.38 -> 6.54:1, still over AA), and the
   label says "Sending…" in words. */
.btn-primary[aria-disabled="true"],
.btn-primary[aria-disabled="true"]:hover,
.btn-primary[aria-disabled="true"]:focus-visible,
.btn-primary[aria-disabled="true"]:active {
  color: var(--muted);
  background: #e9ecf1;
  box-shadow: none;
  transform: none;
}
.btn-primary[aria-disabled="true"]::before {
  background-image: none;
  background-color: #e9ecf1;
  box-shadow: 0 0 0 1px inset var(--silver);
}
.btn-primary[aria-disabled="true"]::after,
.btn-primary[aria-disabled="true"]:hover::after { opacity: 0; }
.btn-primary[aria-disabled="true"] .arw::before { transform: none; opacity: 1; }
.btn-primary[aria-disabled="true"] .arw::after  { opacity: 0; }

/* ---------------------------------------------------------------------------
   4. THE THREAD'S TERMINUS.      [INTEGRATOR: ADAPTED TO THE LIVE GEOMETRY]
   Agent E specified its own line + node at x = max(9px, calc(--pad - 14px)).
   The Thread already runs INSIDE .cta-band (Agent B: [data-thread] > .cta-band
   ::before, top -80px / bottom 100px, on --thr-x), so E's own section 6 ask 2
   and Risk 6 apply: the ::before LINE is DROPPED — a second silver line ~30px
   away from the first would have kinked the page's signature element at its
   last seam — and only the NODE is kept, re-bound to the live tokens:

     line centre  = --thr-x + 5.5px   (an 11px track box painting a 1px line at 50%)
     cap centre y = --thr-tail + 5px  (Agent B's 11x10 radial pinned at 50% 100%)

   so a 14px node is `left: --thr-x - 1.5px` and `bottom: --thr-tail - 2px`,
   exactly concentric with the track's own 10px end-cap, which it covers. E's own
   numbers (bottom: 93px) came out of the same 100px padding and land within 5px;
   the concentric value is used so there is ONE object at the terminus.

   z-index 3, not E's 1: the track is z-index 2, so at 1 the cap would paint over
   the node instead of under it.

   Scoped to [data-thread] — cta.html is shared with 8 interior pages that have
   no Thread — and dropped below 1024px with the rest of the Thread.

   The node is the marker family's "live" recipe with the ring re-pointed to
   --metal-lite: --accent measures 1.86:1 on ink and would simply not be there.
   Static in every state: no animation, no JS, fully lit with JS off and under
   reduced motion. aria-invisible by construction (a pseudo-element).
   ------------------------------------------------------------------------ */
[data-thread] > .cta-band::after {
  content: ''; position: absolute; z-index: 3; pointer-events: none;
  left: calc(var(--pad) - var(--thr-inset) - 1.5px);   /* fallback, no max() */
  left: calc(var(--thr-x) - 1.5px);
  bottom: calc(var(--thr-tail, 100px) - 2px);
  width: var(--dot-md); height: var(--dot-md);
  border-radius: var(--r-round);
  background-image: var(--grad);
  box-shadow: inset 0 0 0 var(--dot-ring) var(--metal-lite),
              0 0 0 3px rgba(216, 222, 230, .16);
}
@media (max-width: 1023px) {
  [data-thread] > .cta-band::after { display: none; }
}

/* ---------------------------------------------------------------------------
   5. THE ARRIVAL — one quiet move.
   Metal is reserved for the primary CTA, the Thread's travelling light and the
   active marker (DIRECTION section 5), so the light coming to rest IN the submit
   button is not a new idea, it is the same object arriving. Opacity only, on a
   decorative sheen layer that can only ADD white — the --on-metal label's
   contrast rises with it (worst composited case 13.0:1, see :root), never falls.

   Fail-open: lit. JS off -> .55. Reduced motion -> .55, no transition.
   Ordering matters: hover/press are restated here at (0,3,0) because this block
   is appended last and would otherwise outrank .btn-primary:hover::after.
   ------------------------------------------------------------------------ */
.cta-band .cta-arrive::after { opacity: .55; }
.js-enabled .cta-band .cta-arrive:not(.is-arrived)::after { opacity: 0; }
.cta-band .cta-arrive:hover::after,
.cta-band .cta-arrive:focus-visible::after { opacity: 1; }
.cta-band .cta-arrive:active::after { opacity: .3; transition-duration: .12s; }

/* ---------------------------------------------------------------------------
   6. RESPONSIVE TAILS
   ------------------------------------------------------------------------ */
@media (max-width: 900px) {
  /* 108px of reserved space at the foot of every page was unconditional — with
     JS off the bar is [hidden] and never appears, so it was dead air. */
  .footer { padding-bottom: 72px; }
  .js-enabled .footer { padding-bottom: 108px; }
}

@media (max-width: 620px) {
  /* THE BAR STACKS instead of deleting its own reassurance. `display: none` on
     .sticky-cta-txt dropped "One business day · no automated pitch" from exactly
     the audience that needs it, and the ellipsis truncated it at 621–900px.
     Measured at 390px: 18 + 16 + 40 + 8 + 16 = 98px + safe-area — a standard
     mobile action bar, and the reassurance now rides ON the action. */
  .sticky-cta-txt { display: grid; }
  .sticky-cta-txt span { white-space: normal; overflow: visible; text-overflow: clip; }
  .sticky-cta-in {
    flex-direction: column; align-items: stretch; gap: 8px;
    padding: 10px 14px 12px;
    border-radius: 26px;
  }
  .sticky-cta .btn-sm { width: 100%; justify-content: center; }

  /* E's 620px terminus variant is dropped: the Thread — and with it the
     terminus — does not exist below 1024px (Agent B, B6). */
}

/* ---------------------------------------------------------------------------
   7. REDUCED MOTION — everything here is already static; these two pin the
   states that would otherwise depend on a JS class arriving.
   ------------------------------------------------------------------------ */
@media (prefers-reduced-motion: reduce) {
  .js-enabled .cta-band .cta-arrive::after { opacity: .55; }
  .sticky-cta, .drawer:not([hidden]) ~ .sticky-cta { transition: none !important; }
}
/* ===== end ===== */

/* ===== v19a HERO PROOF WALL ===== */
/* The owner's verdict on the first v19 hero: "it looks the same." Correct, and the
   reason was structural — the hero kept v17e's exact skeleton (pill row, headline,
   paragraph, two buttons, pill row) and everything genuinely new lived below the
   fold. It also showed ZERO evidence: a visitor whose site is underperforming had
   to scroll ~6,000px before seeing that we build websites.

   The wall is four real client sites, in the same order as #results, running past
   the right gutter. It is decoration in the accessibility tree (aria-hidden, alt="")
   because every image and every domain here already exists in the page with a full
   alt below — so this adds no strings, no announcements and no SEO surface. */

.hero-proof {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 20px;
  margin-top: 44px;
  /* bleed past the container's right edge so the row reads as "there is more of
     this" rather than as four items that happen to fit. .hero already clips. */
  width: calc(100% + var(--pad) + 40px);
  align-items: end;
}

.hp {
  margin: 0;
  border-radius: 14px;
  overflow: hidden;
  background: var(--card);
  border: 1px solid var(--hairline);
  /* the cards sit ON the page, not in it — one soft cast, no second border */
  box-shadow: 0 2px 4px -2px rgba(20, 22, 26, .10),
              0 22px 44px -20px rgba(20, 22, 26, .30);
  transition: transform .45s var(--e-out), box-shadow .45s var(--e-out);
}
/* a shallow stagger so the row has a rhythm instead of reading as a filmstrip.
   Transform only, and each card's own resting transform, so the hover below
   composes with it rather than fighting it. */
.hp:nth-child(1) { transform: translateY(16px); }
.hp:nth-child(2) { transform: translateY(0); }
.hp:nth-child(3) { transform: translateY(22px); }
.hp:nth-child(4) { transform: translateY(6px); }

.hp-bar {
  display: flex; align-items: center; gap: 5px;
  padding: 9px 12px;
  background: var(--band);
  border-bottom: 1px solid var(--hairline);
}
.hp-bar i {
  width: 6px; height: 6px; border-radius: var(--r-round);
  background: var(--hairline); flex: none;
}
.hp-bar em {
  font-style: normal;
  font-size: 11px; font-weight: 400; letter-spacing: .01em;
  color: var(--tertiary);                       /* 5.29:1 on --band */
  margin-left: 6px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* Fixed aspect so the four cards are the same height whatever the source crop,
   and so nothing reflows when a lazy image lands (the box is reserved). */
.hp-img { display: block; aspect-ratio: 1200 / 750; overflow: hidden; }
.hp-img img { display: block; width: 100%; height: 100%; object-fit: cover; object-position: 50% 0; }

/* every hover on this site ADDS definition — never removes it */
.hp:hover {
  box-shadow: 0 2px 4px -2px rgba(20, 22, 26, .12),
              0 30px 58px -22px rgba(20, 22, 26, .38);
}
.hp:nth-child(1):hover { transform: translateY(8px); }
.hp:nth-child(2):hover { transform: translateY(-8px); }
.hp:nth-child(3):hover { transform: translateY(14px); }
.hp:nth-child(4):hover { transform: translateY(-2px); }

/* The hero's vertical rhythm is retuned so the wall is ON the first screen at
   1600x900 rather than just below it — an evidence strip you have to scroll to
   find is the problem it was added to solve. */
.hero { padding-top: 112px; }
.hero .trust-strip { padding-top: 24px; }
.hero-thread-cap { margin-top: 20px; }

/* ---------- responsive ---------- */
@media (max-width: 1180px) {
  .hero-proof { gap: 16px; margin-top: 38px; }
}
/* Two up. Four 1200px-wide screenshots below ~1024 are stamps, not evidence. */
@media (max-width: 1023px) {
  .hero-proof {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    width: calc(100% + var(--pad) + 20px);
    margin-top: 34px;
  }
  .hp:nth-child(1), .hp:nth-child(2),
  .hp:nth-child(3), .hp:nth-child(4) { transform: none; }
}
/* On a phone the wall becomes two cards. Three and four are DECORATION ONLY —
   both sites are still fully present in #results with their real alt text — so
   dropping them costs no content and buys ~200KB and two screens of thumb. */
@media (max-width: 700px) {
  .hero-proof { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; width: calc(100% + var(--pad)); }
  .hp:nth-child(3), .hp:nth-child(4) { display: none; }
  .hp-bar { padding: 7px 9px; }
  .hp-bar em { font-size: 10px; }
}

/* ---------- the three fail-open gates ---------- */
/* The wall is [data-hero-step="7"], so the load choreography and the existing
   .js-fallback rule for [data-hero-step] already cover it. Only the resting
   transforms need pinning under reduced motion, and they are transforms, not
   opacity — nothing here can ever hide content. */
@media (prefers-reduced-motion: reduce) {
  .hp, .hp:hover { transition: none !important; }
}
/* ===== end ===== */

/* The wall was landing ~870px down: at 1600x900 only its top edge was on the
   first screen, which is the exact problem it exists to solve. Two levers, no
   type shrunk: the h1 measure goes 19ch -> 27ch so a 53-character headline sets
   in TWO lines instead of three (it also spans nearly the full container at
   1600, which is more editorial confidence, not less), and the stack between
   the lead and the wall gives back its slack. Net: ~150px, so the wall opens on
   the first screen at 900px of viewport. */
[data-thread] .hero-title { max-width: 27ch; margin: 0 0 26px; }
.hero { padding-top: 96px; }
.hero .lead { margin-bottom: 28px; }
.hero-cta { margin-bottom: 32px; }
.hero .trust-strip { padding-top: 20px; }
.hero-thread-cap { margin-top: 14px; }
.hero-proof { margin-top: 26px; }
