/* =============================================================================
   Bizilance Legal Consultants — static build
   Ported 1:1 from the Next.js/Tailwind source. Values are the measured
   reference values; nothing here is a re-interpretation of the design.
   ========================================================================== */

/* -----------------------------------------------------------------------------
   1. Reset (equivalent of the Tailwind preflight the original relied on)
   -------------------------------------------------------------------------- */
*,
::before,
::after,
::backdrop {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
  border: 0 solid;
}

html {
  line-height: 1.5;
  -webkit-text-size-adjust: 100%;
  tab-size: 4;
  -webkit-tap-highlight-color: transparent;
}

body { line-height: inherit; }

h1, h2, h3, h4, h5, h6 { font-size: inherit; font-weight: inherit; }

a { color: inherit; text-decoration: inherit; }

b, strong { font-weight: bolder; }

button, input, optgroup, select, textarea {
  font: inherit;
  letter-spacing: inherit;
  color: inherit;
  border-radius: 0;
  background-color: transparent;
  opacity: 1;
}

button,
input:where([type="button"], [type="reset"], [type="submit"]) {
  appearance: button;
  cursor: pointer;
}

img, svg, video, canvas, audio, iframe, embed, object {
  display: block;
  vertical-align: middle;
}

img, video { max-width: 100%; height: auto; }

[hidden] { display: none !important; }

/* The <symbol> sprite at the top of the document. display:none would stop
   Safari resolving <use>, so it is collapsed to a zero box instead. */
.sprite { position: absolute; width: 0; height: 0; overflow: hidden; }

/* -----------------------------------------------------------------------------
   2. Design tokens
   -------------------------------------------------------------------------- */
:root {
  /* ---- Brand -----------------------------------------------------------
     Single source of truth: #54C4D0 (cyan, ~186°). Everything below is
     derived from it so the whole palette moves together if the hue shifts.

       brand        accent — type, icons, dots, rules on dark
       brand-soft   hover tint for accent type
       brand-strong pressed / solid-fill hover
       brand-ink    text that sits *on* a brand fill
       deep / deep-2 large brand surfaces (panels, footer card, menus)
     ------------------------------------------------------------------- */
  --brand: #54c4d0;
  --brand-soft: #8ddce5;
  --brand-strong: #2fa8b6;
  --brand-ink: #04191c;
  --brand-deep: #0e2e33;
  --brand-deep-2: #123a41;

  /* brand at low alpha — rings, hairlines, glows */
  --brand-30: rgba(84, 196, 208, 0.30);
  --brand-20: rgba(84, 196, 208, 0.20);
  --brand-12: rgba(84, 196, 208, 0.12);
  --brand-06: rgba(84, 196, 208, 0.06);

  /* ---- Neutrals ---- */
  --color-ink: #0b0b0b;
  --color-brand: var(--brand-deep);
  --color-brand-50: rgba(14, 46, 51, 0.5);
  --color-muted: #8e8e8e;
  --color-cocoa: #0c0603;
  --color-w-70: rgba(255, 255, 255, 0.7);
  --color-w-50: rgba(255, 255, 255, 0.5);
  --color-w-12: rgba(255, 255, 255, 0.12);
  --color-w-10: rgba(255, 255, 255, 0.1);
  --color-w-07: rgba(255, 255, 255, 0.07);
  --color-b-85: rgba(0, 0, 0, 0.85);
  --color-b-65: rgba(0, 0, 0, 0.65);
  --color-b-18: rgba(0, 0, 0, 0.18);

  --font-geist: "Geist", "Geist Placeholder", sans-serif;
  /* alias, so rules that name the interface face keep reading clearly */
  --font-ui: var(--font-geist);

  /* ---- Corner radius ----------------------------------------------------
     Every photographic block used to sit at a 4px radius while the form and
     the footer sat at 16/24 — two unrelated languages on one page. One scale
     now governs all of it. ---------------------------------------------- */
  --r-xs: 6px;
  --r-sm: 10px;
  --r-md: 14px;
  --r-lg: 22px;
  --r-pill: 999px;

  /* ---- Photographic grade -----------------------------------------------
     The library is a mix of warm interiors, cool glass and neutral studio
     shots; ungraded, the page reads as a stock-photo contact sheet. Pulling
     saturation down and letting the brand-deep scrims supply the colour makes
     every image belong to the same world. ------------------------------- */
  --grade: saturate(0.45) contrast(1.06) brightness(0.94);
  --grade-hero: saturate(0.62) contrast(1.04) brightness(0.96);

  --ease: cubic-bezier(0.44, 0, 0.56, 1);
  --ease-io: cubic-bezier(0.4, 0, 0.2, 1);
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);

  /* ---- Gravity ----------------------------------------------------------
     The hero doesn't fade in, it is dropped. Two curves carry that: things
     accelerate downward on --ease-fall (a cubic approximation of s = ½gt²)
     and come to rest on --ease-settle, which overshoots once before it
     stops — the read of mass landing rather than a value easing out. ---- */
  --ease-fall: cubic-bezier(0.33, 0, 0.72, 0.15);
  --ease-settle: cubic-bezier(0.22, 1.42, 0.36, 1);

  --gutter: 80px;
  /* Kept tight: the bar is on screen for the whole page now, so every pixel of
     its height is a pixel of reading area permanently spent. */
  --header-h: 64px;

  /* ---- Measure ----------------------------------------------------------
     The reference was drawn at 1440. Past that the layout used to keep
     stretching, which strands the hero copy and the footer columns on wide
     displays; the shell now centres instead. --------------------------- */
  --maxw: 1600px;

  /* Three sections lead with a left rail (services, core values, why). They
     were set at 280 / 280 / 380, so the page's second vertical alignment moved
     twice on the way down. One measure now governs all of them. */
  --rail: 380px;

  /* ---- Spacing scale ----------------------------------------------------
     The port carried the reference's one-off paddings (158 / 105 / 96 / 75 /
     30 …), which read as noise rather than rhythm. Sections now sit on three
     fluid steps, so the page breathes consistently at every width.
     ------------------------------------------------------------------- */
  --step-1: 8px;
  --step-2: 16px;
  --step-3: 24px;
  --step-4: 32px;
  --step-5: 48px;
  --step-6: 64px;
  --step-7: 96px;

  /* section rhythm: tight / normal / loose */
  --section-sm: clamp(56px, 7vw, 80px);
  --section-md: clamp(80px, 10vw, 128px);
  --section-lg: clamp(96px, 13vw, 160px);
}

@media (max-width: 1279px) {
  :root { --gutter: 48px; }
}
@media (max-width: 809px) {
  :root { --gutter: 24px; }
}

* { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; }

html {
  scroll-behavior: smooth;
  /* Anchor targets would otherwise land underneath the fixed header. */
  scroll-padding-top: calc(var(--header-h) + var(--step-3));
}

/* Once the JS scroll engine is driving scrollTop (see js/main.js), the native
   smooth behaviour becomes a second animation competing for the same value:
   both would be easing the page toward a different position on the same frame.
   The class is set by the engine, so the native curve stays in place for
   touch, reduced-motion and no-JS. */
html.smooth-on { scroll-behavior: auto; }

body {
  /* Flat #0b0b0b is a colour, not an atmosphere — nine sections of it in a row
     is what made the page feel like a deck rather than a place. The ground is
     now a hair cooler, with two fixed brand glows sitting behind everything so
     the dark has depth and a direction of light. */
  background-color: #08090a;
  background-image:
    radial-gradient(58% 42% at 88% 4%, rgba(84, 196, 208, 0.055), transparent 62%),
    radial-gradient(52% 38% at 2% 48%, rgba(84, 196, 208, 0.045), transparent 64%),
    radial-gradient(60% 40% at 70% 96%, rgba(84, 196, 208, 0.035), transparent 62%);
  background-attachment: fixed;
  color: #fff;
  font-family: var(--font-ui);
  /* 300 was the reference weight for everything. Geist Light at 16–18px on a
     near-black ground goes thin and smeary; body copy now sits at 400 and the
     300 is reserved for display sizes, where it belongs. */
  font-weight: 400;
  font-size: 18px;
  line-height: 1.5;
  letter-spacing: -0.015em;
  accent-color: var(--brand);
}

/* A single film of grain over the whole page. On a dark ground it does the
   work a paper stock does in print: it stops the large flat fields from
   banding, and it keeps the brand glows from looking like a gradient tool. */
body::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  opacity: 0.038;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.82' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size: 160px 160px;
}

::selection { background: var(--brand); color: var(--brand-ink); }

/* Scrollbar picks up the brand on both engines. */
html { scrollbar-color: var(--brand-30) transparent; }
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: #0b0b0b; }
::-webkit-scrollbar-thumb {
  background: var(--brand-30);
  border-radius: var(--r-sm);
  border: 3px solid #0b0b0b;
}
::-webkit-scrollbar-thumb:hover { background: var(--brand); }

/* One consistent keyboard ring for every interactive element. */
:where(a, button, [tabindex]):focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 3px;
  border-radius: var(--r-xs);
}

/* -----------------------------------------------------------------------------
   3. Type scale: measured from the reference at 1440
   -------------------------------------------------------------------------- */
/* Sizes are fluid between the 809px and 1440px reference widths, so type
   scales with the viewport instead of snapping at two breakpoints. The upper
   bound of each clamp is the measured reference value.

   Tracking is expressed in em rather than px so it stays proportional as the
   size changes — the px values were only correct at exactly 1440. */
/* Headings balance their last line rather than leaving a single word hanging —
   the one type change kept from the serif experiment, because it is about
   line-breaking, not about the face. */
.t-h1, .t-h2, .t-h3, .t-h4 { text-wrap: balance; }

.t-h1 { font-size: clamp(40px, 1.61rem + 2.85vw, 70px); line-height: 1.06; letter-spacing: -0.022em; font-weight: 300; }
.t-h2 { font-size: clamp(36px, 1.71rem + 2.28vw, 60px); line-height: 1.08; letter-spacing: -0.028em; font-weight: 300; }
.t-h3 { font-size: clamp(30px, 1.62rem + 1.14vw, 42px); line-height: 1.18; letter-spacing: -0.028em; font-weight: 300; }
.t-h4 { font-size: clamp(24px, 1.41rem + 0.57vw, 30px); line-height: 1.22; letter-spacing: -0.026em; font-weight: 300; }
.t-h5 { font-size: clamp(20px, 1.18rem + 0.38vw, 24px); line-height: 1.25; letter-spacing: -0.024em; font-weight: 400; }
.t-body { font-size: clamp(16px, 0.94rem + 0.19vw, 18px); line-height: 1.55; letter-spacing: -0.015em; font-weight: 400; text-wrap: pretty; }
.t-sm { font-size: 16px; line-height: 1.5; letter-spacing: -0.015em; font-weight: 400; }
.t-eyebrow {
  /* Uppercase at 14px needs positive tracking to stay readable — the
     original inherited the display face's negative value, which jammed the
     caps together. */
  font-size: 13px; line-height: 1.2; letter-spacing: 0.08em;
  text-transform: uppercase; font-weight: 400;
}

.text-w-70 { color: var(--color-w-70); }
.text-muted { color: var(--color-muted); }
.text-brand { color: var(--brand); }

/* Inline emphasis inside a headline — brand colour, no italic. */
.hl { color: var(--brand); font-style: normal; }

/* -----------------------------------------------------------------------------
   4. Layout + shared primitives
   -------------------------------------------------------------------------- */
/* One shell governs every horizontal edge on the page. Anything that opts out
   of it (the services grid used to) reads as a misalignment. */
.shell {
  width: 100%;
  max-width: var(--maxw);
  margin-inline: auto;
  padding-inline: var(--gutter);
}
.shell-narrow { width: 100%; max-width: 920px; margin-inline: auto; padding-inline: var(--gutter); }

/* Screen-reader-only, used by the skip link before it takes focus. */
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.skip-link {
  position: fixed;
  top: 12px; left: 12px;
  z-index: 100;
  padding: 12px 20px;
  border-radius: var(--r-pill);
  background: var(--brand);
  color: var(--brand-ink);
  font-size: 14px;
  font-weight: 400;
  transform: translateY(-200%);
  transition: transform .25s var(--ease-out);
}
.skip-link:focus-visible { transform: none; }

/* Absolutely-positioned cover image (the `fill` behaviour of next/image).
   Every photograph on the page goes through the same grade, so a warm library
   interior and a cold glass atrium arrive as the same material. */
.img-fill {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  max-width: 100%;
  object-fit: cover;
  filter: var(--grade);
}

/* ---- Parallax plate ----
   A photograph that drifts against the scroll has to be larger than the frame
   that clips it, or the offset drags an empty edge into view. 128% with a -14%
   inset gives the crop 14% of the frame's height to travel in either
   direction — which is the ceiling js/main.js can ask for, since it drives an
   element through ±(data-parallax ÷ 2) of the frame's height and the largest
   value on the page is 0.28. Anything above that would need this oversize
   raised to match. Everything about the position is stated here; the script
   only ever writes a transform.

   The image is centred on its true crop when the frame is centred in the
   viewport — so the composition the photograph was chosen for is the one on
   screen at the moment the section is being read. */
.img-fill.parallax-img {
  inset: -14% 0;
  height: 128%;
  will-change: transform;
}

@media (prefers-reduced-motion: reduce) {
  .img-fill.parallax-img { inset: 0; height: 100%; will-change: auto; }
}

/* Section label. Still a flex row rather than a bare span: the gap is what
   keeps a second element (a rule, a count) from having to reintroduce the
   layout when one is added back. */
.eyebrow { display: flex; align-items: center; gap: 10px; }
.eyebrow .t-eyebrow { color: var(--brand); }

/* ---- Pill button ----
   The previous pill was a flat swatch of colour whose hover wiped a second
   colour up from the bottom: plenty of movement, but the button had no depth
   at rest and changed identity the moment you touched it. Both are what read
   as dated. A button is a physical object — it should be lit, seated on the
   page, and still be the same object after you engage it.

   Four layers, each with one job —

     ::before      the surface: a top-down gradient plus two inset hairlines,
                   light along the top edge and shade along the bottom, so the
                   shape catches light instead of being printed on the page
     ::after       the engaged glaze: a top-lit wash that fades up, carrying a
                   specular highlight that sweeps across once
     box-shadow    a stack — a 1px contact shadow that seats the pill, and a
                   brand-tinted ambient spill that widens as it lifts
     .pill-arrow   a round chip holding two arrows: the resting one leaves to
                   the right, its twin arrives from the left a beat behind

   The accent no longer arrives by repainting the button. It arrives in the
   glow and in the chip, which inverts on engage — so the brand stays the one
   interactive colour without any variant losing its identity. Colour is passed
   in per variant through --pill-* so the mechanics are written exactly once. */
.pill {
  --pill-h: 50px;
  --pill-surface: transparent;
  --pill-fg: #fff;
  --pill-edge: transparent;
  --pill-edge-hi: var(--pill-edge);
  --pill-hi: transparent;
  --pill-lo: transparent;
  --pill-glaze: transparent;
  --pill-sheen: rgba(255, 255, 255, 0.4);
  --pill-shadow: 0 1px 2px rgba(0, 0, 0, 0.24);
  --pill-shadow-hi: var(--pill-shadow);
  --pill-chip: rgba(255, 255, 255, 0.12);
  --pill-chip-hi: rgba(255, 255, 255, 0.9);
  --pill-chip-fg: #0b0b0b;

  position: relative;
  isolation: isolate;
  /* The glaze and its sweep run edge to edge, so the pill has to clip. */
  overflow: hidden;
  display: inline-flex; align-items: center; justify-content: center; gap: 12px;
  /* Set by height, not vertical padding: every pill on the page then agrees
     on one optical rhythm regardless of its label. Comfortably over the 44px
     touch floor. */
  height: var(--pill-h);
  padding-inline: 26px;
  border: 0;
  border-radius: var(--r-pill);
  color: var(--pill-fg);
  font-family: var(--font-ui);
  font-size: 14.5px; font-weight: 500; line-height: 1;
  letter-spacing: 0.08em; text-transform: uppercase;
  white-space: nowrap;
  box-shadow: var(--pill-shadow);
  transition:
    transform .45s var(--ease-out),
    box-shadow .45s var(--ease-out);
}

/* Resting surface. A pseudo-element rather than a background so the glaze can
   sit above it without either layer having to interpolate a gradient. */
.pill::before {
  content: "";
  position: absolute; inset: 0; z-index: -2;
  border-radius: inherit;
  background: var(--pill-surface);
  box-shadow:
    inset 0 0 0 1px var(--pill-edge),
    inset 0 1px 0 var(--pill-hi),
    inset 0 -1px 0 var(--pill-lo);
  transition: box-shadow .4s var(--ease-io);
}

/* Engaged glaze. Two backgrounds on one layer: a sweep parked off the right
   edge and the top-lit wash under it. Opacity carries the lift, the sweep's
   background-position carries the specular pass — neither needs a colour to
   animate, which is what keeps it smooth on a compositor. */
.pill::after {
  content: "";
  position: absolute; inset: 0; z-index: -1;
  border-radius: inherit;
  background-image:
    linear-gradient(100deg, transparent 30%, var(--pill-sheen) 50%, transparent 70%),
    var(--pill-glaze);
  background-size: 240% 100%, 100% 100%;
  background-position: 150% 0, 0 0;
  background-repeat: no-repeat;
  opacity: 0;
  transition:
    opacity .34s var(--ease-io),
    background-position .9s var(--ease-out);
}

/* ---- Engaged state ----
   Gated on a real pointer. A tap on touch leaves :hover latched on the element
   until you tap elsewhere, which would strand the glaze up and the arrow gone
   — so touch gets the resting state only, which is already a solid object.
   Keyboards keep the full state; the outline ring covers the touch case. */
@media (hover: hover) {
  .pill:is(:hover, :focus-visible) {
    /* A pill scaled up reads rubbery; a pill that rises 2px and throws a wider
       shadow reads as mass leaving the surface. */
    transform: translateY(-2px);
    box-shadow: var(--pill-shadow-hi);
  }
  .pill:is(:hover, :focus-visible)::after {
    opacity: 1;
    background-position: -50% 0, 0 0;
  }
  .pill:is(:hover, :focus-visible)::before {
    box-shadow:
      inset 0 0 0 1px var(--pill-edge-hi),
      inset 0 1px 0 var(--pill-hi),
      inset 0 -1px 0 var(--pill-lo);
  }
}

/* Press puts it back down onto the page and collapses the lift shadow to the
   contact shadow alone — the button takes the weight rather than shrinking. */
.pill:active {
  transform: translateY(0);
  box-shadow: var(--pill-shadow);
  transition-duration: .09s;
}

/* ---- Arrow ----
   A round chip rather than a bare glyph: it gives the label something to end
   against, and it is the one part of the button that inverts on engage, which
   is where the accent lands now that the surface no longer repaints. Inside,
   a clipped window with two arrows — the swap, not a slide, is what makes the
   button feel machined. */
.pill-arrow {
  position: relative;
  flex: none;
  display: block;
  width: 26px; height: 26px;
  /* pulls the chip toward the pill's end cap so the gap reads even */
  margin-right: -6px;
  border-radius: 50%;
  background: var(--pill-chip);
  overflow: hidden;
  transition:
    background-color .34s var(--ease-io),
    color .34s var(--ease-io);
}
.pill-arrow svg {
  position: absolute; inset: 0; margin: auto;
  width: 15px; height: 15px;
  display: block;
  transition: transform .5s var(--ease-out);
}
.pill-arrow svg:nth-child(2) { transform: translate3d(-160%, 0, 0); }

@media (hover: hover) {
  .pill:is(:hover, :focus-visible) .pill-arrow {
    background: var(--pill-chip-hi);
    color: var(--pill-chip-fg);
  }
  .pill:is(:hover, :focus-visible) .pill-arrow svg:nth-child(1) { transform: translate3d(160%, 0, 0); }
  .pill:is(:hover, :focus-visible) .pill-arrow svg:nth-child(2) { transform: translate3d(0, 0, 0); transition-delay: .07s; }
}

/* Arrows are decorative; the label carries the meaning. Pull the padding in on
   the chip side so the button stays optically centred. */
.pill:has(.pill-arrow) { padding-inline: 26px 20px; }

/* ---- Variants ----
   Each one is a material, not a colour: a gradient with a light top edge and a
   shaded bottom one, a matching contact shadow, and an ambient spill tinted to
   whatever the surface is made of. */

/* Primary. The only pill that spends the brand as a fill, so it stays the
   loudest target wherever it appears. */
.pill-brand {
  --pill-surface: linear-gradient(180deg, #79d8e2 0%, #54c4d0 52%, #35aab8 100%);
  --pill-fg: var(--brand-ink);
  --pill-edge: rgba(4, 25, 28, 0.18);
  --pill-edge-hi: rgba(4, 25, 28, 0.24);
  --pill-hi: rgba(255, 255, 255, 0.55);
  --pill-lo: rgba(4, 25, 28, 0.2);
  --pill-glaze: linear-gradient(180deg, rgba(255, 255, 255, 0.34), rgba(255, 255, 255, 0.02));
  --pill-sheen: rgba(255, 255, 255, 0.6);
  --pill-chip: rgba(4, 25, 28, 0.16);
  --pill-chip-hi: #04191c;
  --pill-chip-fg: #7ee2ec;
  --pill-shadow: 0 1px 2px rgba(4, 25, 28, 0.34), 0 10px 22px -14px rgba(84, 196, 208, 0.95);
  --pill-shadow-hi: 0 2px 4px rgba(4, 25, 28, 0.3), 0 22px 44px -18px rgba(84, 196, 208, 0.95);
}

/* White, for the two places that sit directly on the video: the bar and the
   mobile menu. Its accent arrives entirely through the chip. */
.pill-light {
  --pill-surface: linear-gradient(180deg, #ffffff 0%, #e9eef0 100%);
  --pill-fg: #0b0b0b;
  --pill-edge: rgba(11, 11, 11, 0.1);
  --pill-edge-hi: rgba(11, 11, 11, 0.12);
  --pill-hi: rgba(255, 255, 255, 0.9);
  --pill-lo: rgba(11, 11, 11, 0.12);
  --pill-glaze: linear-gradient(180deg, rgba(255, 255, 255, 0.95), rgba(255, 255, 255, 0.4));
  --pill-sheen: rgba(84, 196, 208, 0.4);
  --pill-chip: rgba(11, 11, 11, 0.08);
  --pill-chip-hi: var(--brand);
  --pill-chip-fg: var(--brand-ink);
  --pill-shadow: 0 1px 2px rgba(0, 0, 0, 0.32), 0 10px 24px -16px rgba(0, 0, 0, 0.9);
  --pill-shadow-hi: 0 2px 6px rgba(0, 0, 0, 0.34), 0 20px 40px -18px rgba(0, 0, 0, 0.95);
}

/* Deep brand surface, for pills that need weight without spending the accent. */
.pill-dark {
  --pill-surface: linear-gradient(180deg, #16434a 0%, #0e2e33 100%);
  --pill-fg: #fff;
  --pill-edge: rgba(255, 255, 255, 0.12);
  --pill-edge-hi: var(--brand-30);
  --pill-hi: rgba(255, 255, 255, 0.14);
  --pill-lo: rgba(0, 0, 0, 0.24);
  --pill-glaze: linear-gradient(180deg, rgba(84, 196, 208, 0.22), rgba(84, 196, 208, 0.04));
  --pill-sheen: rgba(255, 255, 255, 0.24);
  --pill-chip: rgba(255, 255, 255, 0.1);
  --pill-chip-hi: var(--brand);
  --pill-chip-fg: var(--brand-ink);
  --pill-shadow: 0 1px 2px rgba(0, 0, 0, 0.4), 0 10px 24px -16px rgba(0, 0, 0, 0.8);
  --pill-shadow-hi: 0 2px 6px rgba(0, 0, 0, 0.4), 0 20px 40px -18px rgba(84, 196, 208, 0.55);
}

/* Secondary. Glass rather than a hairline on near-nothing: it borrows the
   section behind it, so it has presence at rest without competing with the
   primary for colour. */
.pill-outline {
  --pill-surface: linear-gradient(180deg, rgba(255, 255, 255, 0.09), rgba(255, 255, 255, 0.03));
  --pill-fg: #fff;
  --pill-edge: rgba(255, 255, 255, 0.18);
  --pill-edge-hi: rgba(84, 196, 208, 0.5);
  --pill-hi: rgba(255, 255, 255, 0.14);
  --pill-lo: rgba(0, 0, 0, 0.14);
  --pill-glaze: linear-gradient(180deg, rgba(84, 196, 208, 0.16), rgba(84, 196, 208, 0.04));
  --pill-sheen: rgba(255, 255, 255, 0.22);
  --pill-chip: rgba(255, 255, 255, 0.1);
  --pill-chip-hi: var(--brand);
  --pill-chip-fg: var(--brand-ink);
  --pill-shadow: 0 1px 2px rgba(0, 0, 0, 0.22);
  --pill-shadow-hi: 0 2px 6px rgba(0, 0, 0, 0.22), 0 18px 36px -20px rgba(84, 196, 208, 0.85);
  /* On .pill itself, not ::before: `isolation` makes the pill a backdrop root,
     so a filter declared on a child would have nothing left to sample. */
  backdrop-filter: blur(12px) saturate(1.3);
  -webkit-backdrop-filter: blur(12px) saturate(1.3);
}

/* The global ring is drawn at the element box; on a pill it wants the pill
   shape and a little more air. */
.pill:focus-visible { outline-offset: 4px; border-radius: var(--r-pill); }

@media (prefers-reduced-motion: reduce) {
  .pill,
  .pill:is(:hover, :focus-visible),
  .pill:active { transform: none; }
  .pill::after { transition: opacity .2s linear; background-position: 0 0, 0 0; }
  .pill-arrow svg { transition: none; }
}

/* ---- Reveal-on-scroll ----
   The page is now read through an inertial scroll, and a 550ms reveal against
   a scroll that takes ~250ms to settle arrives as a flicker: the content has
   finished moving before the page has. Travel is longer and the curve runs
   over a second on a deep ease-out, so an element is still settling as the
   reader arrives at it — the cadence the observer's -12% margin is timed to.

   Two properties, two durations: the fade is deliberately shorter than the
   rise, so a block is legible for the last third of its travel instead of
   arriving and resolving at the same instant. */
.reveal {
  opacity: 0;
  transform: translate3d(0, 34px, 0);
  transition: opacity .85s var(--ease-out), transform 1.15s var(--ease-out);
  will-change: opacity, transform;
}
.reveal.in { opacity: 1; transform: none; }
/* Released once the element has landed, so the page isn't holding a
   compositor layer per reveal for the whole session. */
.reveal.done { will-change: auto; transition: none; }

/* Delays are back at roughly their authored values. They were cut to 60% when
   the reveal itself was quick, to stop the tail of a group arriving after the
   reader had scrolled past it; with the longer curve the stagger is what
   separates a group into a sequence rather than a single block dropping in. */
.d80  { transition-delay: 80ms; }
.d90  { transition-delay: 90ms; }
.d100 { transition-delay: 100ms; }
.d160 { transition-delay: 160ms; }
.d180 { transition-delay: 180ms; }
.d200 { transition-delay: 200ms; }
.d240 { transition-delay: 240ms; }
.d270 { transition-delay: 270ms; }
.d300 { transition-delay: 300ms; }
.d390 { transition-delay: 390ms; }
.d480 { transition-delay: 480ms; }

/* ---- Mask wipe ----
   The variant the section headings run. The block rises from behind its own
   bottom edge rather than simply fading up — the clip travels with the type,
   so the heading appears to be uncovered by the line it is sitting on.

   The clip is on the children, never on the .reveal itself. An element that
   clips itself to a zero-height rect also reports a zero intersection ratio,
   so it can never satisfy the observer's 0.1 threshold and would sit at
   opacity 0 for the life of the page. The wrapper stays unclipped and carries
   the fade and the travel; its children carry the wipe.

   The bottom inset resolves to -14% rather than 0: descenders, the accent rule
   under an emphasised word and the heading's own line-height all sit outside
   the content box, and clipping at exactly 0 shaves them. */
.reveal.mask {
  transform: translate3d(0, 46px, 0);
  transition:
    opacity .7s var(--ease-out),
    transform 1.25s var(--ease-out);
}
/* Stated explicitly, as `.drop` has to state it: this selector and `.reveal.in`
   carry the same specificity, so the later rule would win and the heading
   would come to rest 46px low — on top of the paragraph under it. */
.reveal.mask.in { transform: none; }
.reveal.mask > * {
  clip-path: inset(0 0 100% 0);
  /* The stagger classes set their delay on the wrapper; the wipe has to wait
     out the same one or the type would uncover before the block starts to
     move. */
  transition: clip-path 1.25s var(--ease-out);
  transition-delay: inherit;
}
.reveal.mask.in > * { clip-path: inset(0 0 -14% 0); }

/* ---- Group stagger ----
   For sets that reveal as a set — the jurisdiction chips. The delay comes from
   the child's index (assigned in js/main.js), so the row deals itself out
   left-to-right instead of appearing as one slab of sixteen. */
[data-stagger] > * {
  opacity: 0;
  transform: translate3d(0, 20px, 0);
  transition:
    opacity .7s var(--ease-out) var(--d, 0ms),
    transform .9s var(--ease-out) var(--d, 0ms);
}
[data-stagger].in > * { opacity: 1; transform: none; }

/* ---- Gravity drop ----
   A second reveal variant for the hero. `.drop` releases a block from just
   above its resting line and lets it settle with a single overshoot — enough
   weight to read as falling, short enough that running headline copy through
   it doesn't cost legibility. */
.reveal.drop {
  transform: translate3d(0, -20px, 0);
  transition: opacity .34s linear, transform .78s var(--ease-settle);
}
.reveal.drop.in { transform: none; }

/* `.fall` hands the motion to the child instead: the wrapper only carries the
   fade, so the pill inside can run the full impact-and-bounce timeline that a
   single transition can't express. Stagger comes from --fall-delay. */
.reveal.fall {
  transform: none;
  transition: opacity .01s linear;
}

@media (prefers-reduced-motion: reduce) {
  .reveal { opacity: 1; transform: none; transition: none; }
  .reveal.drop { transform: none; }
  .reveal.mask > * { clip-path: none; transition: none; }
  [data-stagger] > * { opacity: 1; transform: none; transition: none; }
  html { scroll-behavior: auto; }
}

/* ---- Marquee ---- */
@keyframes marquee { from { transform: translateX(0); } to { transform: translateX(-50%); } }
.marquee-track { display: flex; width: max-content; animation: marquee 30s linear infinite; }

/* ---- Utility ---- */
.hide-scrollbar::-webkit-scrollbar { display: none; }
.hide-scrollbar { -ms-overflow-style: none; scrollbar-width: none; }

/* -----------------------------------------------------------------------------
   5. Header
   -------------------------------------------------------------------------- */
/* The header used to be `absolute`, so it scrolled away at the top of a
   6,500px page and never came back — every navigation meant scrolling to the
   top first. It is now fixed and stays put for the whole page: transparent
   over the hero, picking up a surface once it leaves it. It is kept compact
   enough that a permanently visible bar costs the reader almost nothing. */
.header {
  /* One hairline value for all three rules — the bottom edge and the two
     vertical dividers — so the bar reads as a single ruled frame. Set at 0.18
     rather than the 0.12 used elsewhere: these sit over a moving video, where
     anything lighter disappears against a bright frame. */
  --rule: rgba(255, 255, 255, 0.18);

  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 50;
  transition:
    background-color .3s var(--ease-io),
    border-color .3s var(--ease-io),
    backdrop-filter .3s var(--ease-io);
  border-bottom: 1px solid var(--rule);
}

.header.is-stuck {
  background: rgba(9, 30, 34, 0.72);
  backdrop-filter: blur(14px) saturate(140%);
  -webkit-backdrop-filter: blur(14px) saturate(140%);
}

/* Reading progress, drawn along the bar's own bottom edge. A 6,500px single
   page gives no sense of how much is left; two pixels of brand answer that
   without adding an element to the page. */
.header::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: -1px;
  height: 2px;
  background: linear-gradient(90deg, var(--brand-strong), var(--brand));
  transform: scaleX(var(--progress, 0));
  transform-origin: left;
  opacity: 0;
  transition: opacity .35s var(--ease-io);
  pointer-events: none;
}
.header.is-stuck::after { opacity: 1; }

/* Logo hard left, everything else grouped on the right: the nav sits beside
   the CTA rather than floating in the middle of the bar, so the right edge
   reads as one navigation cluster and the wide gap after the lockup gives the
   mark room instead of splitting attention three ways. */
.header-bar {
  /* Named so the divider can be centred in the gap it sits in. */
  --header-gap: clamp(24px, 2.6vw, 44px);

  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: var(--header-gap);
  padding-block: 14px;
  height: var(--header-h);
}

/* One full-height rule, between the lockup and the nav — it separates identity
   from navigation, which is the only division in the bar worth marking. Drawn
   as a pseudo-element rather than a border so it runs the whole 64px and meets
   the bottom edge, and so it centres in the flex gap rather than hugging the
   nav. */
.nav::before {
  content: "";
  position: absolute;
  left: calc(var(--header-gap) / -2);
  top: 50%;
  transform: translateY(-50%);
  width: 1px;
  height: var(--header-h);
  background: var(--rule);
  pointer-events: none;
}

/* The lockup is a two-line mark (~3.15:1), so it is sized off its height —
   width-first sizing would overrun the 64px bar. It steps down on narrow
   screens to keep clearance from the CTA pill and the burger. */
.header-logo { flex-shrink: 0; margin-right: auto; }
.header-logo img { height: 28px; width: auto; }

@media (min-width: 640px) {
  .header-logo img { height: 32px; }
}

/* Six labels, one row. The gap is the only thing that flexes between 1200 and
   1600 — the type stays put, so the bar reads the same at every desktop width
   instead of shrinking toward the breakpoint. */
.nav { position: relative; display: none; align-items: center; gap: clamp(15px, 1.7vw, 28px); }

/* Sized down against the earlier 15px: with the bar now permanent, the labels
   sit over body copy for the whole page and read better as quiet wayfinding
   than as headline-weight type. Tracking stays pulled back from .t-eyebrow's
   0.08em so the caps read as words rather than rows of letters. */
.nav-link {
  position: relative;
  color: #fff;
  font-size: clamp(12px, 0.3vw + 8.4px, 13px);
  line-height: 1.2;
  letter-spacing: 0.045em;
  text-transform: uppercase;
  font-weight: 400;
  white-space: nowrap;
  padding-block: 5px;
  transition: color .3s var(--ease-io);
}
/* Nav links carry a brand underline that wipes in from the left on hover. */
.nav-link::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 1px;
  background: var(--brand);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform .3s var(--ease);
}
.nav-link:hover { color: var(--brand); }
.nav-link:hover::after { transform: scaleX(1); }

/* The nav gave no indication of where you were. */
.nav-link[aria-current="page"] { color: var(--brand); }
.nav-link[aria-current="page"]::after { transform: scaleX(1); }

.header-actions { display: flex; align-items: center; gap: 12px; }

/* The bar's one solid element: a white pill against the video. Trimmed to fit
   the shorter bar — it no longer needs extra size to hold the far edge, since
   nothing else in the bar competes with a solid white shape. */
.header-cta {
  --pill-h: 42px;
  padding-inline: 20px;
  font-size: 13.5px;
}
.header-cta:has(.pill-arrow) { padding-inline: 20px 16px; }
/* The chip scales with the bar's shorter pill so it doesn't crowd the label. */
.header-cta .pill-arrow { width: 22px; height: 22px; margin-right: -4px; }
.header-cta .pill-arrow svg { width: 13px; height: 13px; }

/* "BOOK CONSULTATION" is a long label; on a 375px bar it crashes into the
   lockup. The mobile menu carries the same CTA, so the bar drops it. */
@media (max-width: 767px) {
  .header-actions .pill { display: none; }
}

/* ---- Burger ---- */
.burger {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: flex-end;
  width: 32px;
  height: 32px;
  gap: 6px;
}

.burger-bar {
  display: block;
  height: 1.5px;
  background: #fff;
  transition: transform .3s var(--ease), background-color .3s var(--ease);
}
/* only the lower bar changes width, so only it animates one */
.burger-bar:nth-child(1) { width: 22px; }
.burger-bar:nth-child(2) { width: 16px; transition: transform .3s var(--ease), background-color .3s var(--ease), width .3s; }

.burger:hover .burger-bar { background: var(--brand); }
.burger.is-open .burger-bar { background: var(--brand); }
.burger.is-open .burger-bar:nth-child(1) { transform: translateY(3.75px) rotate(45deg); }
.burger.is-open .burger-bar:nth-child(2) { width: 22px; transform: translateY(-3.75px) rotate(-45deg); }

/* ---- Mobile menu ---- */
.mobile-menu {
  position: fixed;
  inset: 0;
  z-index: 40;
  background:
    radial-gradient(120% 70% at 100% 0%, var(--brand-12), transparent 60%),
    var(--brand-deep);
  padding-top: calc(var(--header-h) + var(--step-3));
  opacity: 0;
  visibility: hidden;
  transition: opacity .4s var(--ease), visibility .4s;
}
.mobile-menu.is-open { opacity: 1; visibility: visible; }

.mobile-nav {
  display: flex;
  flex-direction: column;
  overflow-y: auto;
  /* svh so mobile browser chrome can't push the last links out of reach. */
  max-height: calc(100svh - 120px);
  gap: 4px;
  padding-bottom: var(--step-5);
}

.mobile-link {
  color: rgba(255,255,255,0.8);
  text-transform: uppercase;
  letter-spacing: 0.01em;
  padding-block: 10px;
  border-bottom: 1px solid rgba(255,255,255,0.1);
  opacity: 0;
  transform: translateY(12px);
  /* Staggered per item; the source's inline transition also meant the hover
     colour swap here is instant, unlike the other nav links. */
  transition:
    opacity .4s calc(var(--i) * 0.05s + 0.1s),
    transform .4s calc(var(--i) * 0.05s + 0.1s);
}
.mobile-link:hover { color: var(--brand); }
.mobile-link[aria-current="page"] { color: var(--brand); }
.mobile-menu.is-open .mobile-link { opacity: 1; transform: none; }

.mobile-cta { margin-top: 24px; align-self: flex-start; }

/* 1024 was the handover point when the row held four labels and a submenu.
   Six full labels need ~1150px of bar before they stop crowding the lockup, so
   the burger now carries the tablet range too. */
@media (min-width: 1200px) {
  .nav { display: flex; }
  .burger { display: none; }
  .mobile-menu { display: none; }
}

/* -----------------------------------------------------------------------------
   6. Hero
   Full-bleed video stage: one frame edge to edge, copy and value pills
   bottom-anchored to a single shared floor.
   -------------------------------------------------------------------------- */
/* Was a flat `min-height: 900px`. On a 1440x900 laptop — after the 77px
   header — that pushed the primary CTA and the value pills below the fold.
   Now it fills the viewport but never exceeds the reference height, and it
   keeps a floor so short landscape windows stay usable. svh (not vh) so
   mobile browser chrome doesn't overshoot. */
.hero {
  position: relative;
  display: flex;
  overflow: hidden;
  min-height: clamp(620px, 100svh, 900px);
  /* Holds the brand surface for the frames before the video paints, so the
     hero never flashes white on a cold load. */
  background: var(--brand-deep);
  /* Keeps the media's negative z-index from escaping behind the page. */
  isolation: isolate;
}

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

/* The reset caps media at `max-width: 100%; height: auto`, which would letterbox
   a cover video — both are overridden here. */
.hero-video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  max-width: none;
  object-fit: cover;
  /* Same grade as the stills, held a little richer — this is the one moving
     image on the page and it can afford more colour than the cards. */
  filter: var(--grade-hero);
  /* Fades up on canplay (see js/main.js) so the first decoded frame doesn't
     pop against the brand panel underneath it. */
  opacity: 0;
  transition: opacity .9s var(--ease-out);
}
.hero-video.is-ready { opacity: 1; }

/* Four stacked layers, each doing one job: a flat floor tint so contrast never
   depends on what the footage happens to be showing, a bottom ramp for the copy
   band, a top ramp that keeps the fixed header legible over bright frames, and
   the brand light bleeding up from the left that the old panel used to carry. */
/* The left ramp is the fifth layer and the one doing the most work for the
   copy: contrast under a light 70px headline used to depend entirely on a flat
   30% tint plus a diffuse text-shadow, so a bright frame (open sky behind the
   bridge) thinned the strokes out. Ramping the left third down anchors the copy
   column specifically, instead of dimming the whole photograph to protect it. */
.hero-scrim {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background:
    linear-gradient(180deg, rgba(4, 25, 28, 0.60) 0%, rgba(4, 25, 28, 0) 32%),
    linear-gradient(90deg, rgba(4, 25, 28, 0.62) 0%, rgba(4, 25, 28, 0.26) 38%, rgba(4, 25, 28, 0) 66%),
    linear-gradient(0deg, rgba(4, 25, 28, 0.90) 0%, rgba(4, 25, 28, 0.55) 38%, rgba(4, 25, 28, 0.10) 72%),
    radial-gradient(75% 65% at 0% 100%, var(--brand-20), transparent 58%),
    rgba(4, 25, 28, 0.30);
}

/* The hero used to stop dead against the page ground — a hard horizontal seam
   right under the primary CTA. This dissolves the last 140px of footage into
   the body colour so the stage hands off to the page instead of ending. */
.hero::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 140px;
  pointer-events: none;
  background: linear-gradient(180deg, rgba(8, 9, 10, 0) 0%, rgba(8, 9, 10, 0.30) 62%, rgba(8, 9, 10, 0.88) 92%, #08090a 100%);
}

.hero-inner {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  gap: var(--step-5);
  /* Clears the fixed header, then bottom-anchors against a fluid floor. */
  padding-top: calc(var(--header-h) + var(--step-6));
  padding-bottom: clamp(48px, 7vh, 72px);
}

.hero-copy {
  display: flex;
  flex-direction: column;
  /* Held tighter than the old 30px bloom now that the scrim's left ramp carries
     the contrast — a wide soft shadow under light 70px type reads as haze. */
  text-shadow: 0 1px 18px rgba(4, 25, 28, 0.55);
}

/* Shrink-wraps to its text: `inline-flex` inside the block-level .reveal, so
   the chip is only as wide as the label rather than the whole copy column. */
.hero-kicker {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  /* Was asymmetric (13px left) to seat the brand mark optically; with the mark
     gone the label is the only content, so the padding is even again. */
  padding: 9px 18px;
  border-radius: var(--r-pill);
  /* Same material as the value pills opposite: inset ring, low-alpha brand
     fill, blur. The two ends of the hero now read as one set. */
  box-shadow: inset 0 0 0 1px var(--brand-30);
  background: rgba(4, 25, 28, 0.42);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  /* The chip is its own surface; the copy block's shadow would smear under it. */
  text-shadow: none;
}
.hero-kicker .t-eyebrow { color: var(--brand); }
.hero-kicker-rule {
  width: 1px;
  height: 12px;
  background: var(--brand-30);
  flex-shrink: 0;
}

/* 12ch was sized for the old 698px column, then 15ch for a 39-character
   headline. The current line names four practice areas and runs to 63, which
   at 15ch stacks five lines and pushes the lede off the fold.

   Two knobs bring it back to a two-line headline. 22ch is the first measure
   that balances to two lines while still stopping short of the value pills in
   the right column. And the display size steps down from the 70px .t-h1 cap:
   the hero is the only .t-h1 on the page, and 70px was set for a headline
   little more than half this length — at that size the longer line either
   breaks the accented "Legal Expertise" across two rows or eats the fold. */
.hero-title {
  margin-top: var(--step-3);
  max-width: 22ch;
  font-size: clamp(36px, 1.5rem + 2.25vw, 58px);
}

/* Three tiers, three treatments — 13px brand caps, 70px white display, and
   this. It was set at body size and full white, which put it level with the
   headline in brightness and left the block reading as two weights rather than
   a hierarchy. Larger than body (it is a sub-head, not a paragraph) and pulled
   back to 82% so the headline keeps the top of the page. */
.hero-text {
  margin-top: 26px;
  max-width: 44ch;
  font-size: clamp(17px, 1rem + 0.35vw, 20px);
  line-height: 1.6;
  color: rgba(255, 255, 255, 0.82);
}

.hero-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--step-2) var(--step-4);
  margin-top: 40px;
}

/* Deliberately not a second button — no surface, no ring, so the eye still
   lands on the CTA first. It was the last thing on the page still set in 13px
   caps at 0.08em: eyebrow styling standing next to a sentence-case button, and
   the single most dated shape in the hero. It now speaks the pill's type
   exactly, so the two read as one family at two levels of intent, and the rule
   under it is the only thing that says "link".

   The rule is two hairlines stacked in one box, not an opacity fade: a dim one
   holding the full width, and a brand one that wipes across it from the left.
   Same left-to-right read as the arrow, so the whole thing resolves one way. */
.hero-link {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 14px 2px;
  font-family: var(--font-ui);
  font-size: 14.5px;
  font-weight: 500;
  line-height: 1;
  letter-spacing: -0.011em;
  text-transform: none;
  color: #fff;
  transition: color .3s var(--ease);
}
.hero-link::before,
.hero-link::after {
  content: "";
  position: absolute;
  left: 2px; right: 2px; bottom: 10px;
  height: 1px;
  border-radius: 1px;
}
.hero-link::before { background: currentColor; opacity: 0.28; }
.hero-link::after {
  background: var(--brand);
  transform: scaleX(0);
  transform-origin: 0 50%;
  transition: transform .5s var(--ease-out);
}
.hero-link:is(:hover, :focus-visible) { color: var(--brand); }
.hero-link:is(:hover, :focus-visible)::after { transform: scaleX(1); }

/* The arrow gets the same clipped-window swap as the pill's, at the size a
   text link can carry — the two CTAs then behave alike on contact. */
.hero-link-arrow {
  position: relative;
  display: block;
  flex: none;
  width: 15px; height: 15px;
  overflow: hidden;
}
.hero-link-arrow svg {
  position: absolute; inset: 0; margin: auto;
  width: 15px; height: 15px;
  display: block;
  transition: transform .5s var(--ease-out);
}
.hero-link-arrow svg:nth-child(2) { transform: translate3d(-160%, 0, 0); }
.hero-link:is(:hover, :focus-visible) .hero-link-arrow svg:nth-child(1) { transform: translate3d(160%, 0, 0); }
.hero-link:is(:hover, :focus-visible) .hero-link-arrow svg:nth-child(2) { transform: translate3d(0, 0, 0); transition-delay: .07s; }

/* The chip is a one-line object. Below ~700px the label can't hold one line at
   any size worth reading, and a flex row that wraps splits it into two ragged
   columns with the divider stranded full-height between them. So the surface is
   dropped and the label reverts to plain text flow, the divider becoming a
   mid-line dot, which wraps the way a sentence does. The type stays the page's
   eyebrow either way; only the container is conditional. */
@media (max-width: 699px) {
  .hero-kicker {
    display: block;
    padding: 0;
    background: none;
    box-shadow: none;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    text-shadow: inherit;
    /* The line box came off the 18px paragraph default, so two lines of 13px
       caps sat 27px apart. Leading now tracks the caps it is setting. */
    font-size: 13px;
    line-height: 1.65;
  }
  .hero-kicker .t-eyebrow { display: inline; }
  .hero-kicker-rule {
    width: auto;
    height: auto;
    background: none;
    color: var(--brand-30);
  }
  .hero-kicker-rule::before { content: "\00b7"; margin: 0 4px; }
}

/* Stacked in a column beside the copy at desktop; below 1024 it folds into a
   wrapping row under the CTA rather than a tall stack that eats the frame. */
.hero-values {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  /* How far above its resting line each pill is released. Shorter on phones,
     where the pills sit directly under the copy and a long drop would sweep
     across the paragraph on its way down. */
  --fall-dist: 96px;
}

.value-pill {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--step-2);
  padding: 10px 22px;
  border-radius: var(--r-pill);
  /* inset ring rather than a border, so it doesn't add to height */
  box-shadow: inset 0 0 0 1px var(--brand-30);
  /* Moving footage is busier than the still it replaced — the fill is carried
     a little further so the ring and the blur still register on light frames. */
  background: rgba(4, 25, 28, 0.42);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  /* Bottom-centre, so the squash on impact compresses against the floor
     rather than around the middle of the pill. */
  transform-origin: 50% 100%;
  transition: transform .6s var(--ease-settle), box-shadow .3s var(--ease);
}
.value-pill .t-h4 { font-size: clamp(20px, 1.18rem + 0.38vw, 24px); }

/* Ring that fires outward on each impact — the only cue that says the pill hit
   something rather than stopped. Sized off the pill so it tracks the radius. */
.value-pill::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  opacity: 0;
}

.value-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--brand);
  box-shadow: 0 0 0 4px var(--brand-20);
  flex-shrink: 0;
}

/* Picked up again once it has landed: lift, then the same settle curve back
   down, so hover speaks the drop's language instead of a generic ease. */
.value-pill:hover {
  transform: translate3d(0, -4px, 0);
  box-shadow: inset 0 0 0 1px var(--brand), 0 14px 30px -18px var(--brand);
}

/* ---- The fall ----
   Release, accelerate, land, squash, and bounce twice with the amplitude
   decaying — the timeline a light object actually follows. The rotation is
   deliberately tiny: enough to stop three identical pills reading as one
   rigid block, not enough to look like they are tumbling.

   Fill is `backwards`, not `both`: the last keyframe is already the element's
   resting state, so letting the animation release its hold at the end costs
   nothing visually and hands transform back to the hover transition. */
.reveal.fall.in .value-pill {
  animation: value-fall 1.15s var(--ease-fall) var(--fall-delay, 0ms) backwards;
}
.reveal.fall.in .value-pill::after {
  animation: value-impact 1.15s linear var(--fall-delay, 0ms) backwards;
}
.reveal.fall.in .value-dot {
  animation: value-dot-hit 1.15s linear var(--fall-delay, 0ms) backwards;
}

/* Bottom of the group lands first, so the stack builds upward. The other way
   round, every later pill falls straight through one already at rest — true
   of the desktop column and of the wrapped row on phones, where the last pill
   sits on a second line directly under the first two.

   The gap between them tightened from 130ms to 110ms when the fourth pill was
   added, so the whole sequence still finishes inside 1.6s. */
.f1 { --fall-delay: 390ms; }
.f2 { --fall-delay: 280ms; }
.f3 { --fall-delay: 170ms; }
.f4 { --fall-delay: 60ms; }

@keyframes value-fall {
  0%   { opacity: 0; transform: translate3d(0, calc(var(--fall-dist) * -1), 0) rotate(-4.5deg) scale(1, 1); }
  9%   { opacity: 1; }
  /* contact */
  34%  { opacity: 1; transform: translate3d(0, 0, 0) rotate(0deg) scale(1, 1); animation-timing-function: cubic-bezier(0.3, 0.7, 0.4, 1); }
  40%  { transform: translate3d(0, 0, 0) rotate(0.4deg) scale(1.06, 0.87); animation-timing-function: cubic-bezier(0.15, 0.85, 0.35, 1); }
  /* Rebound is deliberately shorter than it wants to be: the pills sit 10px
     apart, and a bounce much past this height puts a landed pill inside its
     neighbour's lane while that one is still arriving. */
  54%  { transform: translate3d(0, -12px, 0) rotate(1.4deg) scale(0.98, 1.03); animation-timing-function: var(--ease-fall); }
  /* second contact — 15% of the first bounce is gone */
  68%  { transform: translate3d(0, 0, 0) rotate(0deg) scale(1.04, 0.94); animation-timing-function: cubic-bezier(0.15, 0.85, 0.35, 1); }
  77%  { transform: translate3d(0, -5px, 0) rotate(-0.5deg) scale(0.99, 1.01); animation-timing-function: var(--ease-fall); }
  87%  { transform: translate3d(0, 0, 0) rotate(0deg) scale(1.02, 0.98); animation-timing-function: cubic-bezier(0.2, 0.8, 0.4, 1); }
  94%  { transform: translate3d(0, -3px, 0) rotate(0deg) scale(1, 1); }
  100% { opacity: 1; transform: translate3d(0, 0, 0) rotate(0deg) scale(1, 1); }
}

@keyframes value-impact {
  0%, 33%   { opacity: 0; box-shadow: 0 0 0 0 var(--brand-30); }
  36%       { opacity: 1; box-shadow: 0 0 0 0 var(--brand-30); }
  53%       { opacity: 0; box-shadow: 0 0 0 18px rgba(84, 196, 208, 0); }
  67%       { opacity: 0.5; box-shadow: 0 0 0 0 var(--brand-20); }
  82%, 100% { opacity: 0; box-shadow: 0 0 0 11px rgba(84, 196, 208, 0); }
}

@keyframes value-dot-hit {
  0%, 33%   { box-shadow: 0 0 0 4px var(--brand-20); }
  38%       { box-shadow: 0 0 0 9px var(--brand-12); }
  52%, 66%  { box-shadow: 0 0 0 4px var(--brand-20); }
  70%       { box-shadow: 0 0 0 7px var(--brand-12); }
  84%, 100% { box-shadow: 0 0 0 4px var(--brand-20); }
}

@media (min-width: 1024px) {
  .hero-inner {
    flex-direction: row;
    align-items: flex-end;
    justify-content: space-between;
    gap: var(--step-7);
  }
  /* Right-aligned so the three unequal pills form a clean edge against the
     gutter rather than a ragged one. */
  .hero-values {
    flex-direction: column;
    align-items: flex-end;
    flex-shrink: 0;
    /* Clear air to the right of the copy at this width, so the pills can be
       dropped from higher up and the acceleration actually registers. */
    --fall-dist: 190px;
  }
}

@media (prefers-reduced-motion: reduce) {
  /* JS parks the video on its first frame; without the fade that frame would
     still animate in. */
  .hero-video { opacity: 1; transition: none; }

  .hero-link::after { transition: none; }
  .hero-link-arrow svg { transition: none; }

  /* Nothing falls: the pills are simply present, and hover stops moving them. */
  .reveal.fall.in .value-pill,
  .reveal.fall.in .value-pill::after,
  .reveal.fall.in .value-dot { animation: none; }
  .value-pill { transition: none; }
  .value-pill:hover { transform: none; }
}

/* -----------------------------------------------------------------------------
   7. About
   A single full-width statement. With the supporting paragraph removed there is
   nothing to balance the headline against, so it takes the extra width and a
   step up in size, and earns its motion from the scroll-linked word reveal
   rather than from a block-level wipe.
   -------------------------------------------------------------------------- */
.about { padding-top: var(--section-lg); padding-bottom: var(--section-md); }

.about-row {
  display: grid;
  gap: var(--step-6);
  margin-top: var(--step-5);
  align-items: center;
}

.about-copy {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--step-5);
}

/* The statement keeps the whole shell to itself until there is room for a
   second column beside it; below that the slideshow drops underneath, where a
   letterbox reads better than a portrait crop. */
@media (min-width: 1024px) {
  .about-row {
    grid-template-columns: minmax(0, 1fr) minmax(0, 340px);
    gap: var(--step-6);
  }
}

/* Its own step on the scale — between t-h3 (42) and t-h2 (60) — because it is
   now the only voice in the section. 26ch keeps it to three or four lines at
   desktop: long enough for the reveal to travel across, short enough that the
   eye doesn't lose the line. */
.about-title {
  font-size: clamp(30px, 1.42rem + 2.28vw, 54px);
  line-height: 1.16;
  letter-spacing: -0.028em;
  font-weight: 300;
  /* 26ch was set when the statement had the whole shell. Sharing the row with
     a narrow frame, it needs the extra measure or the left column reads as a
     column of stubs beside a lot of empty gutter. */
  max-width: 32ch;
  /* `balance` fights the word split — it re-measures per word span and can
     leave a ragged last line. `pretty` only guards the orphan. */
  text-wrap: pretty;
}

/* Past the reference width the 32ch cap stops governing the line and starts
   stranding a gutter: the shell keeps growing, the statement doesn't, and the
   row opens a third column of nothing between the last word and the frame.
   Above 1280 the grid column is the measure. */
@media (min-width: 1280px) {
  .about-title { max-width: 36ch; }
}

/* The pill closed the column on its own and left the statement standing over
   an empty half-row. The rule runs it out to the column edge — the same
   hairline the plate opposite opens with, so the two columns end on one
   language — and fades out rather than stopping, so it reads as the line
   running off rather than as a second element. */
.about-cta {
  display: flex;
  align-items: center;
  gap: var(--step-3);
  /* .about-copy aligns its children to the start, so the row has to ask for
     the width the rule needs. */
  align-self: stretch;
}

.about-cta-rule {
  flex: 1 1 auto;
  height: 1px;
  background: linear-gradient(90deg, var(--color-w-12), rgba(255, 255, 255, 0));
}

/* Stacked, the statement already runs the full shell and a rule chasing the
   pill across it is just a line. */
@media (max-width: 1023px) {
  .about-cta-rule { display: none; }
}

/* ---- Scroll-linked word reveal ----
   Each word is wrapped in js/main.js and its opacity is driven off the
   section's scroll depth: the line resolves from grey to white just ahead of
   the reader, a few words at a time. Opacity rather than colour, so the
   brand-coloured span lifts on the same curve as the white type — it rests on
   a higher floor, because #54c4d0 needs more alpha than white to hold the same
   contrast, but the curve above that floor is the one line.

   inline-block is what makes a word animatable on its own; the surrounding
   whitespace stays as real text nodes so the browser still line-breaks
   normally. */
.sr-word { display: inline-block; }
/* Promoted only while the statement is mid-reveal. `.reveal` already refuses
   to hold a layer per block for the life of the page; nineteen word-sized
   layers sitting there after the section has resolved would be worse. */
.is-live .sr-word { will-change: opacity; }
/* The base state only applies once the split has run. Without JS — or with
   reduced motion — the heading is simply full white, as authored. */
[data-scroll-text].is-split { color: #fff; }

@media (prefers-reduced-motion: reduce) {
  .sr-word { opacity: 1 !important; will-change: auto; }
}

/* ---- About slideshow ----
   Three frames on a short hold, cycled by js/main.js → initSlideshow. The
   change has to be quick without being a flicker, so two things happen at
   once: the outgoing frame fades under a crossfade shorter than the hold, and
   the incoming one keeps drifting (a slow scale) for as long as it is up. The
   drift is what stops a fast cut from reading as a broken carousel — the frame
   is always already in motion when the next one arrives.

   Source frames are 640px wide, so the column is capped at 460 rather than
   allowed to fill: past that the crops go soft on a 2× display. */
/* Stacked, it belongs on the page's left rail like everything else in the
   section; beside the statement it is pushed to the far edge so the frame ends
   where the shell does. */
/* A column now rather than a single frame: the picture, then the plate that
   names it. Two elements of unequal weight stacked tight enough to read as one
   object. */
.about-media-col {
  width: 100%;
  max-width: 440px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}

@media (min-width: 1024px) {
  .about-media-col { justify-self: end; max-width: 340px; }
}

/* A small landscape rectangle throughout — the sources are landscape, and at
   this size the frame is a note against the statement rather than a second
   headline. 340px wide also keeps the 640px files comfortably above 1:1. */
.about-media {
  position: relative;
  width: 100%;
  aspect-ratio: 4 / 3;
  overflow: hidden;
  border-radius: var(--r-md);
  background: var(--brand-deep);
  /* Same hairline the service cards hold their edge with, plus a brand bloom
     underneath so the block sits on the page ground rather than on top of it. */
  box-shadow:
    inset 0 0 0 1px var(--color-w-12),
    0 34px 80px -40px rgba(0, 0, 0, 0.95),
    0 0 60px -34px var(--brand-30);
  isolation: isolate;
}

.about-slide {
  position: absolute;
  inset: 0;
  opacity: 0;
  /* Shorter than the hold, so the frame is settled and still for a beat before
     the next change — that pause is what makes the cut read as deliberate. */
  transition: opacity .35s var(--ease-io);
}
.about-slide.is-active { opacity: 1; }

/* The drift runs only while the frame is up, and restarts each time it comes
   back around, because the class is removed in between. Direction alternates
   by index so consecutive frames never push the same way. */
/* Two of the three sources are already 3:2, so the frame keeps almost the
   whole photograph; only the 4:3 first frame gives up any height, and it gives
   it up from the top, where the blinds are. */
.about-img {
  transform: scale(1.045);
  transform-origin: 50% 50%;
}
.about-slide[data-slide="0"] .about-img { object-position: 50% 62%; }
.about-slide.is-active .about-img { animation: about-drift 7.5s var(--ease-io) forwards; }
.about-slide[data-slide="1"].is-active .about-img { transform-origin: 50% 30%; }
.about-slide[data-slide="2"].is-active .about-img { transform-origin: 35% 60%; }

@keyframes about-drift {
  from { transform: scale(1.045); }
  to   { transform: scale(1.11); }
}

/* Lighter than a card scrim, because nothing sits on top of this frame — no
   copy, no controls. Its only jobs are to tie the foot into brand-deep like
   every other surface on the page, and to soften the edge nearest the
   statement so the crop doesn't compete with it across the gutter. */
.about-scrim {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 2;
  background:
    linear-gradient(180deg, rgba(4, 25, 28, 0.26) 0%, rgba(4, 25, 28, 0) 38%, rgba(14, 46, 51, 0.55) 100%),
    radial-gradient(110% 90% at 0% 50%, rgba(8, 9, 10, 0.34), transparent 62%);
}

/* One brand-tinted band crosses the frame on each change. It is the only cue
   that the slide moved rather than simply being replaced, and at 350ms it is
   gone before the eye can go looking for it. */
.about-sweep {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 42%;
  z-index: 3;
  pointer-events: none;
  opacity: 0;
  background: linear-gradient(90deg, transparent, var(--brand-12) 44%, rgba(255, 255, 255, 0.16) 52%, var(--brand-12) 60%, transparent);
}
.about-media.is-changing .about-sweep { animation: about-sweep .35s var(--ease-out); }

@keyframes about-sweep {
  0%   { opacity: 0; transform: translate3d(-120%, 0, 0); }
  22%  { opacity: 1; }
  100% { opacity: 0; transform: translate3d(320%, 0, 0); }
}

/* ---- Plate ----
   Caption left, position right, on the hairline the frame sits over. The rule
   is the same weight as the one that closes the copy column, and it is what
   ties a small floating picture back onto the page's grid. */
.about-plate {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--step-3);
  padding-top: 12px;
  border-top: 1px solid var(--color-w-10);
}

/* The three captions are stacked on one another so the plate holds a fixed
   height and the row beneath it never moves; only one is ever at opacity 1. */
.about-caption {
  position: relative;
  flex: 1 1 auto;
  min-width: 0;
  height: 16px;
  color: var(--color-w-50);
}

.about-cap {
  position: absolute;
  left: 0;
  top: 0;
  white-space: nowrap;
  opacity: 0;
  transform: translate3d(0, 4px, 0);
  /* Slower in than the frame's crossfade: the picture has changed before the
     word does, so the word reads as a caption catching up rather than as a
     second thing changing at the same instant. */
  transition: opacity .5s var(--ease-io), transform .5s var(--ease-out);
}
.about-cap.is-active { opacity: 1; transform: none; }

/* Three marks, not dots: a dot says "there are three", a mark that fills says
   "and this one is halfway through". The fill is the only clock on the page
   the reader can actually see, which is what stops the next change from
   arriving as an interruption. */
.about-ticks { display: flex; align-items: center; gap: 6px; flex: 0 0 auto; }

.about-tick {
  position: relative;
  width: 20px;
  height: 2px;
  border-radius: var(--r-pill);
  background: var(--color-w-12);
  overflow: hidden;
}

.about-tick::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: var(--brand);
  transform: scaleX(0);
  transform-origin: left center;
}

/* --hold is written by js/main.js from the slideshow's own interval, so the
   fill and the change can never drift apart. */
.about-tick.is-active::after {
  animation: about-tick var(--hold, 3600ms) linear forwards;
}

/* Held whenever the timer is — off-screen, or on a backgrounded tab — so the
   mark and the change stay on one clock. */
.about-media-col.is-paused .about-tick.is-active::after { animation-play-state: paused; }

@keyframes about-tick {
  from { transform: scaleX(0); }
  to   { transform: scaleX(1); }
}

/* Nothing here is load-bearing: with motion off the first frame simply sits
   there, graded and framed, and the plate reports it without counting. */
@media (prefers-reduced-motion: reduce) {
  .about-slide { transition: none; }
  .about-slide.is-active .about-img { animation: none; transform: scale(1.02); }
  .about-media.is-changing .about-sweep { animation: none; }
  .about-cap { transition: none; }
  .about-tick.is-active::after { animation: none; transform: scaleX(1); }
}

/* -----------------------------------------------------------------------------
   8. Services
   Four cards, four disciplines, everything legible at once.

   The section previously ran an autoplaying coverflow. It looked expensive and
   it cost the reader the content: three of four practices were blurred out at
   any instant, the cards themselves held no type (title and description lived
   in a caption underneath, so the photograph never met its own label), and the
   only way to learn the fourth discipline was to wait ~16 seconds or work the
   arrows. The whole section spent 1300px of page height to show one sentence.

   The set is static now — no state, no timer, nothing that can desync — and
   each card is self-contained: plate, index, title, description, the
   capabilities inside the discipline, and one labelled way in. Motion is
   spent on arrival and on engage, where it costs the reader nothing.
   -------------------------------------------------------------------------- */
.services { padding-bottom: var(--section-md); }

.services-head { display: grid; gap: var(--step-4); }

/* The rail carries the label and the count. "Four — disciplines" does the job
   the old 01/04 counter did (tell the reader the list is short and finite)
   without being a position indicator for something that no longer moves. */
.services-head-rail { display: grid; gap: var(--step-3); justify-items: start; }

.services-tally {
  display: inline-flex;
  align-items: baseline;
  gap: 10px;
  color: var(--color-w-50);
}
.services-tally span:first-child { color: var(--brand); }
.services-tally i { font-style: normal; color: var(--color-w-50); }

.services-title { max-width: 18ch; }
.services-lede { max-width: 52ch; margin-top: var(--step-3); }

/* ---- The grid ----
   4-up on desktop, 2-up on tablet, stacked on phones. The cards are equal
   height by default (stretch), which is what lets every "Discuss this matter"
   in the row sit on one line regardless of how long the description above it
   runs. */
.services-grid {
  list-style: none;
  display: grid;
  gap: var(--step-3);
  margin-top: var(--section-sm);
}

.service-cell { display: flex; }

/* The card is a surface, not a photograph with words on it: it borrows the
   plaque treatment the stats block uses — a barely-there wash, one hairline
   ring, a brand bloom seeded in the top-left corner — so the two evidence
   blocks on the page are recognisably the same material. */
.service-card {
  position: relative;
  isolation: isolate;
  display: flex;
  flex-direction: column;
  width: 100%;
  padding: var(--step-3);
  gap: var(--step-3);
  border-radius: var(--r-lg);
  overflow: hidden;
  background:
    radial-gradient(90% 60% at 0% 0%, var(--brand-06), transparent 62%),
    rgba(255, 255, 255, 0.025);
  box-shadow: inset 0 0 0 1px var(--color-w-10);
  transition:
    transform .55s var(--ease-out),
    box-shadow .45s var(--ease-io),
    background-color .45s var(--ease-io);
}

/* Top edge rule. At rest it is the same hairline as the ring; on engage a
   brand gradient wipes across it left-to-right — the same gesture the Core
   Values rows use when a row is chosen, so "this one is live" reads the same
   way in both sections. */
.service-card::before {
  content: "";
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 1px;
  background: linear-gradient(90deg, var(--brand), var(--brand-20) 45%, transparent 80%);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform .6s var(--ease-out);
}

/* ---- Engaged state ----
   `:has()` is what lets a keyboard land on the title link and get the whole
   card's treatment, not just an outline around three words. It is additive:
   where it isn't supported the link still draws the page's standard focus
   ring, so the focus is never invisible — only less decorated. */
.service-card:hover,
.service-card:has(.service-link:focus-visible) {
  transform: translateY(-6px);
  box-shadow:
    inset 0 0 0 1px var(--brand-30),
    0 30px 60px -34px rgba(0, 0, 0, 0.9),
    0 0 44px -26px var(--brand-20);
}
.service-card:hover::before,
.service-card:has(.service-link:focus-visible)::before { transform: scaleX(1); }

/* ---- Plate ----
   Landscape, not the old 452:600 portrait: at a quarter of the shell a
   portrait crop would push the type below the fold of the card for no gain,
   and these are establishing shots rather than subjects.

   The ratio widens as the card widens. A 4:3 plate is well proportioned in a
   300px column; in the 450px column the 2-up layout gives it, the same ratio
   is a 340px-tall photograph sitting on top of 200px of type — the picture
   stops introducing the discipline and starts being the card. */
.service-media {
  position: relative;
  overflow: hidden;
  aspect-ratio: 16 / 10;
  border-radius: var(--r-md);
  box-shadow: inset 0 0 0 1px var(--color-w-10);
}

.service-img { transition: transform .9s var(--ease-io), filter .5s var(--ease-io); }
.service-card:hover .service-img,
.service-card:has(.service-link:focus-visible) .service-img { transform: scale(1.06); }

/* Bottom-weighted scrim so the plate sits down into the card rather than
   floating as a bright rectangle inside a dark one. */
.service-scrim {
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgba(4, 25, 28, 0.1) 0%, rgba(4, 25, 28, 0) 42%, rgba(4, 25, 28, 0.55) 100%);
}

/* ---- Body ---- */
/* No padding of its own: the type block shares the plate's left edge exactly,
   which is the whole point of the card padding being on the card. */
.service-body {
  display: flex;
  flex-direction: column;
  gap: var(--step-2);
  /* Fills the rest of the card so the CTA can be pushed to the floor. */
  flex: 1;
}

/* Index + trailing rule: the numeral tells the reader where they are in a set
   of four, and the rule carries that across the card as a baseline for the
   title to hang from. */
.service-index {
  display: flex;
  align-items: center;
  gap: 12px;
  color: var(--brand);
}
.service-rule {
  flex: 1;
  height: 1px;
  background: linear-gradient(90deg, var(--brand-30), transparent);
}

.service-title { color: #fff; }

/* ---- Stretched title link ----
   The card used to be one <a> wrapped around everything, which is convenient
   to write and poor to listen to: a screen reader announced the photograph,
   the paragraph, three tags and the CTA as one link name. The link is on the
   discipline now — "Anti-dumping & trade remedy" is exactly what belongs in a
   links list — and this pseudo-element restores the full-card hit area over
   it. Above the plate in z-order, so the whole surface stays clickable. */
.service-link { position: static; }
.service-link::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 2;
  border-radius: inherit;
}
.service-link:hover { color: var(--brand); }

/* .t-sm carries no wrap rule of its own (only .t-body does), and a four-line
   paragraph in a 300px column is exactly where a one-word last line shows. */
.service-desc { text-wrap: pretty; }

/* Capabilities inside the discipline. Same chip language as the Core Values
   tags — quieter here, because these are supporting detail rather than the
   claim itself. */
.service-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: var(--step-1);
  list-style: none;
}
.service-tags li {
  padding: 5px 11px;
  border-radius: var(--r-pill);
  font-size: 13px;
  line-height: 1.2;
  letter-spacing: -0.005em;
  color: var(--color-w-70);
  background: rgba(255, 255, 255, 0.03);
  box-shadow: inset 0 0 0 1px var(--color-w-10);
  transition: color .35s var(--ease-io), box-shadow .35s var(--ease-io);
}
.service-card:hover .service-tags li,
.service-card:has(.service-link:focus-visible) .service-tags li {
  color: var(--brand-soft);
  box-shadow: inset 0 0 0 1px var(--brand-20);
}

/* ---- Card CTA ----
   Every card links to the enquiry form, so it says so. The old label read
   "VIEW SERVICE" and went to #contact — an affordance that promised a page
   that does not exist. Pushed to the card floor by margin-top:auto, which is
   what keeps the four of them level across the row. */
.service-cta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--step-2);
  margin-top: auto;
  padding-top: var(--step-3);
  border-top: 1px solid var(--color-w-07);
  color: var(--color-w-50);
  transition: color .35s var(--ease-io);
}
.service-card:hover .service-cta,
.service-card:has(.service-link:focus-visible) .service-cta { color: var(--brand); }

/* The two-arrow chip from the pill: the resting arrow leaves right, its twin
   arrives from the left a beat behind. Written small and self-contained here
   rather than reusing .pill-arrow, which carries the pill's own sizing. */
.service-chip {
  position: relative;
  flex: none;
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  overflow: hidden;
  border-radius: var(--r-pill);
  background: rgba(255, 255, 255, 0.05);
  box-shadow: inset 0 0 0 1px var(--color-w-10);
  color: #fff;
  transition: background-color .4s var(--ease-io), box-shadow .4s var(--ease-io), color .4s var(--ease-io);
}
.service-chip svg { grid-area: 1 / 1; width: 15px; height: 15px; transition: transform .5s var(--ease-out); }
.service-chip svg:last-child { transform: translateX(-140%); }

.service-card:hover .service-chip,
.service-card:has(.service-link:focus-visible) .service-chip {
  background: var(--brand);
  box-shadow: inset 0 0 0 1px var(--brand);
  color: var(--brand-ink);
}
.service-card:hover .service-chip svg:first-child,
.service-card:has(.service-link:focus-visible) .service-chip svg:first-child { transform: translateX(140%); }
.service-card:hover .service-chip svg:last-child,
.service-card:has(.service-link:focus-visible) .service-chip svg:last-child { transform: translateX(0); }

/* Closing line for the section — the "one standard" claim plus the way in. */
.services-note {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--step-4);
  margin-top: var(--section-sm);
}
.services-note p { max-width: 52ch; }

@media (min-width: 640px) {
  .services-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (min-width: 1024px) {
  .services-head { grid-template-columns: minmax(0, var(--rail)) minmax(0, 1fr); gap: var(--step-7); align-items: start; }
  .services-note { flex-direction: row; align-items: center; justify-content: space-between; }
}

/* Held back to 1200 rather than 1024: at four across a 1024 shell each card is
   ~215px wide, which breaks "Anti-dumping & trade remedy" across four lines
   and leaves the tag row wrapping to three. */
@media (min-width: 1200px) {
  .services-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--step-3); }
  .service-card { padding: var(--step-3) var(--step-3) var(--step-4); }
  .service-media { aspect-ratio: 4 / 3; }
}

@media (prefers-reduced-motion: reduce) {
  .service-card,
  .service-card::before,
  .service-img,
  .service-chip svg { transition: none; }
  .service-card:hover,
  .service-card:has(.service-link:focus-visible) { transform: none; }
}

/* -----------------------------------------------------------------------------
   9. Values
   Three columns at 1440: eyebrow (x80), heading + accordion (x440),
   image (x920).
   -------------------------------------------------------------------------- */
.values { padding-block: var(--section-md); }

.values-layout { display: grid; gap: var(--section-sm); }

.values-tabs { display: grid; gap: var(--step-5); }

.values-list {
  display: flex;
  flex-direction: column;
}

.values-rows { display: flex; flex-direction: column; }

/* Was t-h4 (30px) — the same size as a service card title, so this section
   heading read as a caption next to the 42px headings elsewhere. It is now
   promoted to the page's section-heading size. */
.values-title { max-width: 18ch; margin-top: var(--step-3); }
.values-lede { max-width: 38ch; margin-top: var(--step-3); }

/* Title rows keep a fixed 106px pitch — they never shift as you switch. */
.values-row {
  position: relative;
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  text-align: left;
  gap: var(--step-3);
  /* Fixed 106px was most of a phone screen per row; the pitch is preserved at
     desktop but compresses gracefully below it. The desktop ceiling is set so
     four rows come out level with the image column beside them, instead of
     leaving 200px of dead ground under the last one. */
  min-height: clamp(76px, 9.5vw, 133px);
  padding-block: var(--step-2);
  border-top: 1px solid rgba(255,255,255,0.1);
}
.values-row:last-child { border-bottom: 1px solid rgba(255,255,255,0.1); }

/* Brand rule wipes across the top edge of the selected row. */
.values-row::before {
  content: "";
  position: absolute;
  top: -1px; left: 0; right: 0;
  height: 1px;
  background: var(--brand);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform .5s var(--ease);
}
.values-row.is-active::before { transform: scaleX(1); }

/* Legibility, not compliance: 0.45 on #0b0b0b already measured 4.51:1, but at
   Light weight it read as disabled rather than merely unselected. 0.55 takes
   it to 6.26:1 — present enough to invite a click, still clearly subordinate
   to the brand-coloured active row. */
.values-label {
  color: rgba(255,255,255,0.55);
  transition: color .3s var(--ease);
}
.values-row:hover .values-label { color: rgba(255,255,255,0.8); }
.values-row.is-active .values-label,
.values-row.is-active:hover .values-label { color: var(--brand); }

/* This used to be the brand mark — the same four-pointed glyph as every
   eyebrow on the page — rotated 45°, which turned the selected row's affordance
   into an ✕ and read as "close". It is now an actual arrow that slides right
   when its row is chosen. */
.values-arrow {
  display: flex;
  flex-shrink: 0;
  color: rgba(255, 255, 255, 0.3);
  transform: translateX(-6px);
  transition: transform .45s var(--ease-out), color .3s var(--ease-io);
}
.values-row:hover .values-arrow { color: rgba(255, 255, 255, 0.6); transform: translateX(0); }
.values-row.is-active .values-arrow,
.values-row.is-active:hover .values-arrow { color: var(--brand); transform: translateX(0); }

/* Portrait beside the rows at desktop; stacked below them it becomes a
   letterbox, because a 440:540 crop across a full tablet width would be a
   two-metre-tall photograph of someone's chin. */
.values-media {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 10;
  overflow: hidden;
  border-radius: var(--r-md);
  box-shadow: inset 0 0 0 1px var(--color-w-10);
}

@media (min-width: 1024px) {
  .values-media {
    aspect-ratio: 440 / 540;
    max-height: 560px;
    min-height: 380px;
  }
}

/* `visibility` (rather than the `hidden` attribute) keeps the crossfade while
   still taking inactive panels out of the accessibility tree and tab order. */
.values-panel {
  position: absolute;
  inset: 0;
  opacity: 0;
  visibility: hidden;
  transition: opacity .5s var(--ease), visibility .5s;
}
.values-panel.is-active { opacity: 1; visibility: visible; }

.values-scrim {
  position: absolute;
  inset: 0;
  /* The copy block now carries a tag row under it, so it reaches further up
     the image than the reference's single paragraph did — the scrim has to
     start earlier or the first line lands on bare photograph. */
  background: linear-gradient(180deg, rgba(4, 25, 28, 0) 18%, rgba(4, 25, 28, 0.55) 48%, rgba(4, 25, 28, 0.82) 72%, rgba(14, 46, 51, 0.95) 100%);
}

/* Copy + tags share one bottom-anchored block, so the tag row can never
   collide with a description that runs to three lines. */
.values-body {
  position: absolute;
  left: var(--step-4);
  right: var(--step-4);
  bottom: var(--step-4);
}

.values-copy { color: #fff; }

.values-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: var(--step-3);
  list-style: none;
}
.values-tags li {
  padding: 6px 12px;
  border-radius: var(--r-pill);
  color: var(--brand);
  box-shadow: inset 0 0 0 1px var(--brand-30);
  background: rgba(4, 25, 28, 0.4);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}

@media (min-width: 1024px) {
  .values-layout {
    grid-template-columns: minmax(0, var(--rail)) minmax(0, 1fr);
    gap: var(--step-7);
    align-items: start;
  }
  /* Pinned for the same reason the "Why" intro is: the four rows are the thing
     you scan, and the claim they answer to should still be on screen while you
     scan them. */
  .values-intro { position: sticky; top: calc(var(--header-h) + var(--step-5)); }
  /* The old `480px 440px` + 40px gap needed 960px inside a column that was
     only 920px wide at 1440, so the image hung 40px past the right gutter —
     invisible only because the body clipped overflow. Fractional columns
     make it fit at any width. */
  .values-tabs {
    grid-template-columns: minmax(0, 1fr) minmax(300px, 460px);
    gap: var(--step-6);
    align-items: start;
  }
}

/* -----------------------------------------------------------------------------
   10. Stats
   Plain dark row; four cells split evenly with a divider + 40px inset.
   -------------------------------------------------------------------------- */
.stats {
  /* One step tighter than the sections either side. The globe is a square that
     already carries a wide ring of its own dark around the sphere, so the band
     reads as generously padded at --section-sm and merely hollow at --md. */
  padding-block: var(--section-sm);
  /* Own stacking context so the ground can sit at z-index -1 and still land
     above the page background instead of behind it — same arrangement the
     globe's bloom already relies on. */
  position: relative;
  isolation: isolate;
}

/* ---- Section ground ------------------------------------------------------
   A photograph behind a band that already carries a lit globe and a paragraph
   of body copy has one job: give the section a room to stand in. So it is
   pushed most of the way down — desaturated by the page grade, then dimmed
   again — and covered by a scrim that is nearly opaque over the copy column,
   opens up across the middle where the photograph can actually be read, and
   fades to black at both edges so the band dissolves into the page rather than
   starting and stopping on a hard line. */
.stats-bg {
  position: absolute;
  inset: 0;
  z-index: -1;
  overflow: hidden;
}

.stats-bg .img-fill {
  /* Graded hard rather than merely faded. A bright daylit room at low opacity
     turns to grey mush; dropping the exposure and pushing contrast keeps the
     shapes — table, window, figures — legible in the dark instead. */
  filter: saturate(0.3) contrast(1.28) brightness(0.32);
  opacity: 0.56;
  /* The group sits low-centre in the frame; anchoring there keeps people in
     the band rather than cropping to empty ceiling. */
  object-position: 50% 55%;
}

.stats-scrim {
  position: absolute;
  inset: 0;
  background:
    /* Top and bottom feathers — the band has no border, so these are what
       join it to the sections above and below. */
    linear-gradient(180deg, #08090a 0%, rgba(8, 9, 10, 0.78) 28%, transparent 100%) top / 100% 200px no-repeat,
    linear-gradient(0deg, #08090a 0%, rgba(8, 9, 10, 0.78) 28%, transparent 100%) bottom / 100% 200px no-repeat,
    /* Dark well under the globe, so the sphere is lit by nothing but itself —
       carried out to the right gutter, since a lit window in the corner of the
       photograph is exactly what a globe should not have to compete with. */
    radial-gradient(56% 84% at 80% 50%, rgba(8, 9, 10, 0.94), rgba(8, 9, 10, 0.55) 60%, transparent 82%),
    linear-gradient(270deg, rgba(8, 9, 10, 0.8) 0%, rgba(8, 9, 10, 0.36) 14%, transparent 30%),
    /* And a softer one under the copy — an ellipse rather than a column, so the
       protection ends where the text does instead of flattening that whole
       half of the photograph. */
    radial-gradient(44% 54% at 19% 50%, rgba(6, 14, 16, 0.9), transparent 72%),
    /* Brand light, held to the left edge: a tint on the page's own ground, not
       a wash over the room. */
    linear-gradient(90deg, rgba(12, 42, 47, 0.6) 0%, rgba(12, 42, 47, 0.22) 18%, transparent 34%),
    linear-gradient(180deg, rgba(8, 9, 10, 0.5) 0%, rgba(8, 9, 10, 0.42) 46%, rgba(8, 9, 10, 0.72) 100%);
}

/* Stacked, the copy sits over the middle of the frame instead of a protected
   left column — so the scrim has to hold the whole width down. */
@media (max-width: 1023px) {
  .stats-bg .img-fill { opacity: 0.42; }
  .stats-scrim {
    background:
      linear-gradient(180deg, #08090a 0%, rgba(8, 9, 10, 0.75) 24%, transparent 100%) top / 100% 160px no-repeat,
      linear-gradient(0deg, #08090a 0%, rgba(8, 9, 10, 0.75) 24%, transparent 100%) bottom / 100% 160px no-repeat,
      /* The copy runs the full width here, so the protection is a band under
         the text rather than an ellipse beside it. */
      linear-gradient(180deg, rgba(8, 9, 10, 0.88) 0%, rgba(8, 9, 10, 0.86) 34%, rgba(8, 9, 10, 0.6) 58%, rgba(8, 9, 10, 0.72) 100%),
      radial-gradient(100% 46% at 24% 12%, rgba(12, 42, 47, 0.6), transparent 76%);
  }
}

/* The four figures are the section's evidence, and they were floating on bare
   ground with nothing holding them together. A single inset panel with a brand
   keyline reads as a plaque — the one place on the page where the firm states
   a number and stands behind it. */
.stats-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  /* The 2-up mobile layout had no row gap, so the two rows collided — and no
     column gap, so "Years of excellence" ran straight into the divider beside
     it. */
  row-gap: var(--section-sm);
  column-gap: var(--step-3);
  position: relative;
  padding: clamp(32px, 4vw, 56px) clamp(24px, 3vw, 44px);
  border-radius: var(--r-lg);
  background:
    radial-gradient(80% 140% at 0% 0%, var(--brand-06), transparent 60%),
    rgba(255, 255, 255, 0.025);
  box-shadow: inset 0 0 0 1px var(--color-w-10);
  overflow: hidden;
}

.stats-grid::before {
  content: "";
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 1px;
  background: linear-gradient(90deg, var(--brand), var(--brand-20) 40%, transparent 75%);
}

.stat {
  position: relative;
  display: flex;
  flex-direction: column;
  height: 100%;
  gap: var(--step-1);
  padding-left: clamp(20px, 2.8vw, 40px);
  border-left: 1px solid rgba(255,255,255,0.12);
}

/* The divider is a separator, so it belongs *between* cells — the first cell
   was drawing a rule hard against the page gutter with nothing to its left. */
.stats-grid > *:nth-child(2n + 1) .stat {
  border-left-color: transparent;
  padding-left: 0;
}

/* Top third of each divider picks up the brand — a small repeated accent
   that ties the row to the rest of the page. */
.stat::before {
  content: "";
  position: absolute;
  top: 0; left: -1px;
  width: 1px;
  height: 34%;
  background: var(--brand);
}
.stats-grid > *:nth-child(2n + 1) .stat::before { display: none; }

/* `tabular-nums` so the count-up doesn't reflow the label underneath it on
   every frame. */
.stat .t-h2 {
  color: var(--brand);
  font-variant-numeric: tabular-nums lining-nums;
}

@media (min-width: 1024px) {
  .stats-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); row-gap: 0; }

  /* At 4-up only the very first cell leads the row. */
  .stats-grid > *:nth-child(2n + 1) .stat {
    border-left-color: rgba(255,255,255,0.12);
    padding-left: clamp(20px, 2.8vw, 40px);
  }
  .stats-grid > *:nth-child(2n + 1) .stat::before { display: block; }

  .stats-grid > *:first-child .stat {
    border-left-color: transparent;
    padding-left: 0;
  }
  .stats-grid > *:first-child .stat::before { display: none; }
}

/* ---- Global network ------------------------------------------------------
   Claim left, world right — one row, the same reading order as every other
   two-column section on the page.

   The globe used to sit in a bordered well with a rail of sixteen names beside
   it. Both are gone: the names are on the sphere as flags, and the sphere now
   floats on the page's own ground with only its own light around it, which is
   what stops the section reading as a screen embedded in a document. */
.globe-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: clamp(40px, 5vw, 80px);
  align-items: center;
}

.globe-copy { display: grid; align-content: center; }
.global-title { max-width: 18ch; margin-top: var(--step-3); }
/* Narrower than the old full-bleed measure: in a column beside the globe, 60ch
   would run the paragraph out past the heading it belongs to. */
.global-text { max-width: 46ch; margin-top: var(--step-3); }

/* ---- Stage ----
   Square, because a globe is. The bloom is CSS rather than canvas so it can
   bleed past the square the canvas is confined to — the sphere reads as lit
   inside the page instead of pasted onto it. */
.globe-visual { min-width: 0; }

.globe-stage {
  position: relative;
  /* Own stacking context, so the bloom below can sit at z-index -1 and still
     land above the section's background rather than behind it. */
  z-index: 0;
  width: 100%;
  max-width: 620px;
  margin-inline: auto;
  /* Vertical drags have to stay with the page on touch; the canvas takes the
     horizontal axis for rotation and leaves the scroll alone. */
  touch-action: pan-y;
}

.globe-stage::before {
  content: "";
  position: absolute;
  inset: -16%;
  z-index: -1;
  pointer-events: none;
  background:
    radial-gradient(circle at 50% 50%, rgba(84, 196, 208, 0.09), transparent 58%),
    radial-gradient(circle at 38% 34%, rgba(255, 255, 255, 0.045), transparent 46%);
}

.globe-canvas {
  width: 100%;
  height: auto;
  /* Beats the intrinsic ratio of the width/height attributes, which are still
     the 300×150 default at the moment the script first measures the box. */
  aspect-ratio: 1;
  cursor: grab;
}
.globe-stage.is-dragging .globe-canvas { cursor: grabbing; }
.globe-stage.is-over-marker .globe-canvas { cursor: pointer; }

/* ---- Flag markers ----
   Positioned from the projected point every frame, so they are DOM only for
   the artwork and the type — the geometry is entirely the canvas's. */
.globe-labels {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

.globe-chip {
  position: absolute;
  top: 0; left: 0;
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 3px;
  border-radius: 5px;
  white-space: nowrap;
  background: rgba(9, 15, 18, 0.72);
  box-shadow: inset 0 0 0 1px var(--color-w-12);
  backdrop-filter: blur(7px);
  -webkit-backdrop-filter: blur(7px);
  /* Opacity is written per-frame from the marker's depth, so a flag fades with
     the point it belongs to as that point turns away. */
  transition: none;
}

.globe-chip img {
  width: 20px;
  height: 15px;
  border-radius: 2px;
  object-fit: cover;
  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.14);
}

/* Sixteen standing names would be the old rail again, wrapped around a ball:
   the name belongs only to the jurisdiction currently being held. */
.globe-chip-name {
  display: none;
  padding-right: 6px;
  font-size: 12px;
  line-height: 1;
  letter-spacing: -0.01em;
  color: #fff;
}

.globe-chip.is-lead {
  background: rgba(10, 28, 32, 0.88);
  box-shadow: inset 0 0 0 1px var(--brand-30), 0 6px 18px rgba(0, 0, 0, 0.45);
}
.globe-chip.is-lead .globe-chip-name { display: block; }

/* ---- Composition ----
   The stage is opt-in: it appears only once js/main.js has a 2D context in
   hand. Without JS, or without canvas, an empty square is worse than none —
   what stays is the claim, which is the content. */
.globe-visual { display: none; }
.is-live .globe-visual { display: block; }

@media (min-width: 1024px) {
  .globe-layout {
    grid-template-columns: minmax(0, 0.92fr) minmax(0, 1fr);
  }
}

/* Under 560 the sphere would eat the fold on its own. */
@media (max-width: 559px) {
  .globe-stage { max-width: 380px; }
}

/* -----------------------------------------------------------------------------
   11. Team
   One band. The photograph runs the height of the whole section rather than a
   header strip above it; the claim sits in a sticky left rail and the two
   Directors stand to its right. Two blocks that argued the same point in
   sequence — a headline on a picture, then a pair of cards on flat dark —
   now argue it once, side by side, on a single ground.
   -------------------------------------------------------------------------- */
.team {
  position: relative;
  /* The plate and the two edge fades sit at z-index -1; the isolate keeps that
     stack local, so nothing here can slide under the section above. */
  isolation: isolate;
  /* Matches every other content section (values, stats, why, mission, pubs) —
     --section-lg was sized for this band's old neighbours; between two
     --section-md sections it just stacks into a dead-feeling boundary,
     compounded by the sticky rail trailing on past where the cards end. */
  padding-block: var(--section-md);
}

/* Five layers, top of the stack down: two edge fades, then brand light behind
   the rail, a left-to-right ramp that holds the full width of the copy column
   before it opens up, and a downward ramp that puts the deepest dark under the
   second profile — the panels are translucent, so the ground beneath them is
   what their type is read against.

   The edge fades belong here rather than on the section itself: a pseudo of
   .team would sit at the same z-index as this plate and lose to it on DOM
   order, and anything painted above the plate from outside this element would
   also cover the copy. A band this tall reads as a pasted-in panel if it
   starts and ends on a hard line; both edges dissolve into the page's own
   ground instead. */
.team-bg {
  position: absolute;
  inset: 0;
  z-index: -1;
  overflow: hidden;
  background:
    linear-gradient(180deg, #08090a 0%, rgba(8, 9, 10, 0.72) 34%, transparent 100%) top / 100% 200px no-repeat,
    linear-gradient(0deg, #08090a 0%, rgba(8, 9, 10, 0.72) 34%, transparent 100%) bottom / 100% 240px no-repeat,
    radial-gradient(70% 62% at 0% 18%, rgba(14, 46, 51, 0.82), transparent 74%),
    linear-gradient(90deg, rgba(4, 25, 28, 0.88) 0%, rgba(4, 25, 28, 0.74) 44%, rgba(4, 25, 28, 0.5) 100%),
    linear-gradient(180deg, rgba(8, 9, 10, 0.12) 0%, rgba(8, 9, 10, 0.5) 48%, rgba(8, 9, 10, 0.9) 100%);
}

.team-layout {
  position: relative;
  display: grid;
  gap: var(--section-sm);
  align-items: start;
}

.team-title { margin-top: var(--step-3); max-width: 15ch; }
/* 58ch ran the lede past the scrim's usable width; 44 keeps it inside the rail. */
.team-text { margin-top: var(--step-3); max-width: 44ch; }

/* ---- Rail index ----
   Names the pair before either profile is read, and below the split breakpoint
   — where the panels stack under the rail rather than beside it — it is the
   only direct route to one Director. Same hairline rhythm as the "why" rows. */
.team-index {
  list-style: none;
  margin-top: var(--step-5);
  border-top: 1px solid var(--color-w-10);
}
.team-index li { border-bottom: 1px solid var(--color-w-10); }

.team-index a {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 20px;
  align-items: baseline;
  gap: 4px var(--step-2);
  padding-block: var(--step-3);
  transition: padding-left .5s var(--ease-out);
}
.team-index-name {
  grid-column: 1;
  font-size: 20px;
  line-height: 1.25;
  letter-spacing: -0.02em;
  transition: color .35s var(--ease-io);
}
.team-index-role { grid-column: 1; color: var(--color-w-50); }

.team-index-go {
  grid-column: 2;
  grid-row: span 2;
  align-self: center;
  color: var(--color-w-50);
  transition: transform .5s var(--ease-out), color .35s var(--ease-io);
}
.team-index-go svg { width: 16px; height: 16px; }

@media (hover: hover) {
  .team-index a:hover { padding-left: 8px; }
  .team-index a:hover .team-index-name { color: var(--brand); }
  .team-index a:hover .team-index-go { color: var(--brand); transform: translateX(4px); }
}

/* ---- Directors ----
   Two named Directors with real biographies are not a roster of thumbnails,
   and they are not two full-width stacked rows either — as rows they cost
   three screens and read as a list of two rather than as the partnership that
   runs the firm. Each is one panel: portrait and biography on a line, then the
   credentials across the panel's full width, where a label column and a
   four-term value actually fit on one row. */
.directors {
  display: grid;
  gap: var(--step-5);
}

/* The reveal wrapper is what the grid lays out, so it — not the article — is
   the cell that has to carry the stretch. */
.director-cell { display: flex; }

.director {
  position: relative;
  isolation: isolate;
  width: 100%;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  grid-template-areas:
    "head"
    "media"
    "body"
    "spec"
    "cta";
  padding: var(--step-4);
  border-radius: var(--r-lg);
  /* Glass, not a grey box: the panel sits on a photograph now, so it takes its
     colour from what is behind it and only supplies the density the type needs
     to be read. The blur is what stops the window frames in the plate from
     running through the biography. */
  background:
    linear-gradient(180deg, rgba(255, 255, 255, 0.07) 0%, rgba(255, 255, 255, 0.02) 42%, rgba(255, 255, 255, 0.012) 100%),
    rgba(6, 20, 23, 0.58);
  backdrop-filter: blur(22px) saturate(1.15);
  -webkit-backdrop-filter: blur(22px) saturate(1.15);
  box-shadow:
    inset 0 0 0 1px var(--color-w-10),
    0 40px 90px -60px rgba(0, 0, 0, 0.95);
  transition:
    box-shadow .5s var(--ease-io),
    transform .6s var(--ease-out),
    background-color .5s var(--ease-io);
}

/* Firefox before 103 and any engine without the filter would put the biography
   straight onto the photograph, so the fill goes nearly opaque instead. */
@supports not ((backdrop-filter: blur(2px)) or (-webkit-backdrop-filter: blur(2px))) {
  .director {
    background:
      linear-gradient(180deg, rgba(255, 255, 255, 0.06) 0%, rgba(255, 255, 255, 0.018) 42%, rgba(255, 255, 255, 0.01) 100%),
      rgba(6, 18, 21, 0.94);
  }
}

.director:hover {
  transform: translateY(-3px);
  background-color: rgba(8, 24, 27, 0.66);
  box-shadow:
    inset 0 0 0 1px var(--brand-30),
    0 48px 100px -58px rgba(0, 0, 0, 0.95);
}

/* Brand keyline riding the panel's top edge — the same device the mission
   panel and the "why" rows use, extending on hover so the whole card is one
   target rather than just the button in it. */
.director::before {
  content: "";
  position: absolute;
  top: 0;
  left: var(--step-4);
  width: 56px;
  height: 2px;
  border-radius: 0 0 2px 2px;
  background: var(--brand);
  transition: width .7s var(--ease-out);
}
.director:hover::before { width: 200px; }

/* ---- Card header ---- */
.director-head {
  grid-area: head;
  display: flex;
  align-items: center;
  gap: var(--step-3);
  padding-bottom: var(--step-4);
}

.director-kicker { color: var(--color-w-50); }

/* The rule carries the eye from the panel's left edge to the role label,
   which is what turns a loose caption into a masthead across the top. */
.director-rule {
  flex: 1;
  height: 1px;
  background: linear-gradient(90deg, var(--brand-30), var(--color-w-10));
}

/* ---- Portrait plate ----
   The Directors' photographs are studio cut-outs, not scenery: there is no
   background inside them to crop into, so the plate has to supply its own
   ground and let the figure stand on it rather than fill it. That is also why
   the image is contained and not covered further down — a crop on a cut-out
   takes an arm off, it does not reframe anything. */
.director-media {
  grid-area: media;
  position: relative;
  overflow: hidden;
  width: 100%;
  aspect-ratio: 1 / 1;
  border-radius: var(--r-md);
  background:
    radial-gradient(116% 74% at 50% 6%, rgba(84, 196, 208, 0.16), rgba(84, 196, 208, 0.035) 52%, transparent 76%),
    linear-gradient(180deg, rgba(255, 255, 255, 0.075) 0%, rgba(255, 255, 255, 0.02) 55%, rgba(255, 255, 255, 0.008) 100%);
  box-shadow: inset 0 0 0 1px var(--color-w-10), 0 24px 60px -34px rgba(0, 0, 0, 0.9);
}

/* A pool of shadow across the foot of the plate. Without it a cut-out reads as
   pasted on; with it the two figures share a floor and land as a pair. */
.director-media::before {
  content: "";
  position: absolute;
  z-index: 0;
  left: -10%;
  right: -10%;
  bottom: -14%;
  height: 42%;
  border-radius: 50%;
  background: radial-gradient(50% 50% at 50% 50%, rgba(0, 0, 0, 0.55), transparent 72%);
}

/* Brand light behind the figure, lifted on hover of the panel. It sits under
   the portrait, so the glow reaches around the shoulders instead of washing
   over the face. */
.director-media::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  background: radial-gradient(96% 62% at 50% 10%, rgba(84, 196, 208, 0.28), transparent 70%);
  opacity: 0;
  transition: opacity .5s var(--ease-io);
}
.director:hover .director-media::after { opacity: 1; }

/* No grade on the Directors — the page's own people are shot in a studio and
   should arrive as they are, not pushed towards the scenery's colour. */
.director-img {
  z-index: 1;
  object-fit: contain;
  object-position: 50% 100%;
  filter: none;
  /* The fade at the foot of the figure is baked into the file's alpha rather
     than masked here, so it stays on the chest at whatever height the frame
     beside the biography lands. */
  transition: transform .7s var(--ease-io);
}
.director:hover .director-img { transform: scale(1.03); }

.director-body {
  grid-area: body;
  padding-top: var(--step-4);
}

/* The "Director" label sits up in the panel's masthead, so the name is the
   first thing in the column and carries the hierarchy on its own. */
.director-role {
  margin-top: 10px;
  color: var(--brand);
}
.director-bio { margin-top: var(--step-3); }

/* ---- Spec list ----
   Keyword chips gave the least important content the most visual weight and
   said nothing about what each keyword *was*. A label/value list is how a
   practice states credentials: scannable, unambiguous, and it inherits the
   same hairline rhythm as the "why" rows. It spans the panel's full width
   rather than the text column's, which is what lets the label sit beside its
   value instead of above it. */
.director-spec {
  grid-area: spec;
  width: 100%;
  margin-top: var(--step-4);
}

.director-spec-row {
  display: grid;
  gap: 6px var(--step-4);
  padding-block: var(--step-2);
  border-top: 1px solid var(--color-w-10);
}
.director-spec-row:last-child { border-bottom: 1px solid var(--color-w-10); }

.director-spec-row dt { color: var(--color-w-50); padding-top: 2px; }

.director-spec-row dd {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 6px 12px;
  color: rgba(255, 255, 255, 0.88);
}
/* Brand bullet as the separator rather than a comma: it keeps multi-value rows
   parsing as a set at a glance, and ties the list to the accent. It trails its
   own value rather than leading the next one, so a row that wraps in the
   narrower panel column leaves the dot at the end of the line instead of
   starting the next line with a floating mark. */
.director-spec-row dd span { display: inline-flex; align-items: baseline; gap: 12px; }
.director-spec-row dd span:not(:last-child)::after {
  content: "";
  align-self: center;
  width: 3px;
  height: 3px;
  border-radius: 50%;
  background: var(--brand);
  opacity: 0.65;
}

.director-btn {
  grid-area: cta;
  justify-self: start;
  margin-top: var(--step-4);
}

/* On a phone the shell gutter is already 24px a side; a 32px panel inset on top
   of it would leave the bio around 260px of measure. */
@media (max-width: 559px) {
  .director { padding: var(--step-3) var(--step-3) var(--step-4); }
  .director::before { left: var(--step-3); }
}

/* Label and value share a line as soon as there is room for a fixed label
   column; below that the value wraps under its label. */
@media (min-width: 560px) {
  .director-spec-row {
    grid-template-columns: 124px minmax(0, 1fr);
    column-gap: var(--step-3);
    align-items: baseline;
  }
}

/* ---- The panel goes horizontal ----
   Portrait beside the biography rather than above it. Below 700 there is not
   enough width for both, and the portrait goes back to a landscape plate at
   the head of the card. */
@media (min-width: 700px) {
  .director {
    grid-template-columns: minmax(200px, 34%) minmax(0, 1fr);
    grid-template-areas:
      "head  head"
      "media body"
      "spec  spec"
      "cta   cta";
    column-gap: var(--step-4);
    padding: var(--step-4) var(--step-5) var(--step-5);
  }
  .director::before { left: var(--step-5); }
  /* The plate holds a portrait proportion instead of stretching to the height
     of the biography beside it. Stretched, this column runs to about 1:1.8,
     and a standing figure is nearer 1:1.2 — filling that would take the arms
     off, and containing the figure in it left the man small at the foot of a
     tall empty frame. At 4:5 the figure holds its own plate, and the short
     column that leaves reads as the space before the credentials below. */
  .director-media {
    aspect-ratio: 4 / 5;
    align-self: start;
    min-height: 240px;
  }
  .director-body { padding-top: 0; }
}

/* ---- The split ----
   Below 1240 the rail and a panel wide enough for a portrait plus a readable
   measure do not both fit, so the rail sits above the pair. Above it, the claim
   pins to the left of the profiles that back it. */
@media (min-width: 1240px) {
  .team-layout {
    grid-template-columns: minmax(0, 0.66fr) minmax(0, 1fr);
    gap: var(--step-7);
  }
  /* align-self: start pins the rail to the top of its track immediately, so
     "Our leadership" and the claim beneath it are on screen for the whole
     scroll rather than only reaching that position mid-way through. */
  .team-rail { position: sticky; top: calc(var(--header-h) + var(--step-5)); align-self: start; }
  .directors { gap: var(--step-6); }
  /* The portrait holds a wider column here than the old landscape plate did.
     A cut-out figure carries the panel, so starving it to 200px left a small
     man beside a long biography; the extra width also shortens the gap under
     the plate by taking a line or two out of the column beside it. */
  .director { grid-template-columns: minmax(240px, 34%) minmax(0, 1fr); }
}

@media (prefers-reduced-motion: reduce) {
  .director,
  .director::before,
  .director-img,
  .director-media::after,
  .team-index a,
  .team-index-go { transition: none; }
  .director:hover { transform: none; }
  .director:hover .director-img { transform: none; }
}

/* -----------------------------------------------------------------------------
   12. Why Bizilance
   Sticky intro on the left, a numbered list on the right — the same row
   rhythm as the values accordion, without the tab machinery.
   -------------------------------------------------------------------------- */
.why { padding-block: var(--section-md); }

.why-layout { display: grid; gap: var(--section-sm); align-items: start; }

.why-title { margin-top: var(--step-3); max-width: 18ch; }
.why-text { margin-top: var(--step-3); max-width: 42ch; }
.why-btn { margin-top: var(--step-5); }

.why-list { list-style: none; display: flex; flex-direction: column; }

.why-row {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--step-4);
  padding-block: var(--step-5);
  border-top: 1px solid rgba(255, 255, 255, 0.1);
}
.why-row:last-child { border-bottom: 1px solid rgba(255, 255, 255, 0.1); }

.why-num { color: var(--brand); padding-top: 0.6em; }

.why-name { margin-bottom: var(--step-2); }
.why-copy p { max-width: 56ch; }

@media (min-width: 1024px) {
  .why-layout { grid-template-columns: minmax(0, var(--rail)) minmax(0, 1fr); gap: var(--step-7); }
  /* The intro is short and the list is long — pinning it keeps the section's
     claim on screen while the five reasons scroll past it. */
  .why-intro { position: sticky; top: calc(var(--header-h) + var(--step-5)); }
  .why-row { grid-template-columns: 64px minmax(0, 1fr); }
}

/* -----------------------------------------------------------------------------
   13. Mission
   -------------------------------------------------------------------------- */
.mission { padding-bottom: var(--section-md); }

/* Nine sections of type on the same flat ground is a long read. The mission is
   the natural place to change surface: a lit panel that lets the page take a
   breath before the publications. */
.mission-inner {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  padding: clamp(48px, 6vw, 88px) clamp(24px, 4vw, 72px);
  border-radius: var(--r-lg);
  background:
    radial-gradient(85% 130% at 50% 0%, var(--brand-06), transparent 62%),
    rgba(255, 255, 255, 0.022);
  box-shadow: inset 0 0 0 1px var(--color-w-10);
  overflow: hidden;
}

/* Brand keyline centred on the panel's top edge — the same device as the
   footer card, mirrored. */
.mission-inner::before {
  content: "";
  position: absolute;
  top: 0; left: 50%;
  width: min(420px, 60%);
  height: 1px;
  transform: translateX(-50%);
  background: linear-gradient(90deg, transparent, var(--brand), transparent);
}

/* The 32px margin cleared the brand mark that used to head the panel. With the
   mark gone the quote is the first child, and keeping it would have added to
   the panel's own top padding — pushing the whole centred block off its
   vertical middle. */
.mission-quote {
  max-width: 26ch;
  text-wrap: balance;
}

.mission-attr {
  position: relative;
  margin-top: var(--step-5);
  padding-top: var(--step-5);
}
/* A short rule, rather than a gap, between the claim and who is making it. */
.mission-attr::before {
  content: "";
  position: absolute;
  top: 0; left: 50%;
  width: 40px;
  height: 1px;
  transform: translateX(-50%);
  background: var(--brand-30);
}

/* -----------------------------------------------------------------------------
   15. Contact band
   -------------------------------------------------------------------------- */
.cta {
  position: relative;
  overflow: hidden;
  min-height: clamp(420px, 45vw, 564px);
  display: flex;
  align-items: center;
}

.cta-scrim {
  position: absolute;
  inset: 0;
  background:
    radial-gradient(70% 90% at 50% 100%, var(--brand-20), transparent 65%),
    linear-gradient(180deg, rgba(4, 25, 28, 0.72) 0%, rgba(14, 46, 51, 0.86) 100%);
}

/* The reference centred a headline and a lone button here. The band now
   carries the consultation request itself, so the section that asks for the
   enquiry is also the one that can take it. */
.cta-inner {
  position: relative;
  display: grid;
  gap: var(--section-sm);
  align-items: center;
  padding-block: var(--section-lg);
  width: 100%;
}

.cta-title { max-width: 14ch; }
.cta-text { max-width: 46ch; margin-top: var(--step-3); }

.cta-details {
  display: grid;
  gap: var(--step-4);
  margin-top: var(--section-sm);
  padding-top: var(--step-5);
  border-top: 1px solid var(--color-w-12);
  list-style: none;
}
.cta-details p { margin-top: 6px; max-width: 34ch; }

.cta-link {
  color: #fff;
  transition: color .3s var(--ease-io);
}
.cta-link:hover { color: var(--brand); }

/* ---- Consultation form ---- */
.form-card {
  background: rgba(4, 25, 28, 0.72);
  backdrop-filter: blur(16px) saturate(140%);
  -webkit-backdrop-filter: blur(16px) saturate(140%);
  box-shadow: inset 0 0 0 1px var(--brand-20), 0 30px 70px -30px rgba(0, 0, 0, 0.9);
  border-radius: var(--r-lg);
  padding: clamp(24px, 3vw, 40px);
}

.form-sub { margin-top: var(--step-2); }

.form-grid {
  display: grid;
  gap: var(--step-3);
  margin-top: var(--step-5);
}

.field { display: flex; flex-direction: column; gap: 8px; }
.field label { color: var(--color-w-70); }

.field input,
.field select,
.field textarea {
  width: 100%;
  padding: 13px 16px;
  border-radius: var(--r-sm);
  background: rgba(255, 255, 255, 0.05);
  box-shadow: inset 0 0 0 1px var(--color-w-12);
  color: #fff;
  font-size: 16px; /* below 16px iOS zooms the whole page on focus */
  line-height: 1.4;
  letter-spacing: -0.015em;
  transition: box-shadow .3s var(--ease-io), background-color .3s var(--ease-io);
}
.field textarea { resize: vertical; min-height: 96px; }

.field input::placeholder,
.field textarea::placeholder { color: var(--color-w-50); }

.field input:focus,
.field select:focus,
.field textarea:focus {
  outline: none;
  background: rgba(255, 255, 255, 0.08);
  box-shadow: inset 0 0 0 1px var(--brand);
}

/* Native select chrome is white-on-white on this surface, so it is redrawn
   with the brand chevron. */
.field select {
  appearance: none;
  -webkit-appearance: none;
  padding-right: 44px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none'%3E%3Cpath d='M6 9l6 6 6-6' stroke='%2354c4d0' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 16px center;
}
.field select option { background: var(--brand-deep); color: #fff; }

.form-btn { margin-top: var(--step-5); width: 100%; }
.form-note { margin-top: var(--step-3); }

@media (min-width: 560px) {
  .form-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .field-wide { grid-column: 1 / -1; }
  .form-btn { width: auto; }
}

@media (min-width: 1024px) {
  .cta-inner { grid-template-columns: minmax(0, 1fr) minmax(0, 520px); gap: var(--step-7); }
  /* Three details across two columns left a hole under the address. The
     address — the longest of the three — takes the full row, and the two
     short reach blocks pair off beneath it. */
  .cta-details { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .cta-details > li:first-child { grid-column: 1 / -1; }
  .cta-details > li:first-child p { max-width: 52ch; }
}

/* -----------------------------------------------------------------------------
   16. Footer
   -------------------------------------------------------------------------- */
.footer { background: #0b0b0b; padding-top: var(--section-md); padding-bottom: var(--step-5); }

.footer-card {
  position: relative;
  overflow: hidden;
  background:
    radial-gradient(90% 120% at 100% 0%, var(--brand-12), transparent 55%),
    var(--brand-deep);
  border-radius: var(--r-lg);
  padding: clamp(32px, 4vw, 60px);
}

/* Brand keyline across the top of the card. */
.footer-card::before {
  content: "";
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 2px;
  background: linear-gradient(90deg, var(--brand) 0%, var(--brand-20) 45%, transparent 80%);
}

.footer-top {
  display: flex;
  flex-direction: column;
  gap: 60px;
}

.footer-brand {
  display: flex;
  flex-direction: column;
  max-width: 360px;
}

.footer-logo { width: 176px; height: auto; }

.footer-tagline { margin-top: 24px; }

.footer-links {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 32px 60px;
}

.footer-col { display: flex; flex-direction: column; align-items: flex-start; gap: 12px; }

/* Four bare link columns gave no clue what each one was; each now carries a
   label, so "Services" and "Pages" stop reading as one undifferentiated list. */
.footer-head { color: #fff; margin-bottom: 4px; }

.footer-link {
  color: var(--color-w-70);
  transition: color .3s var(--ease-io);
}
.footer-link:hover { color: var(--brand); }

/* 80px margin + 40px padding stacked to a 120px void above the rule. */
.footer-contact {
  display: flex;
  flex-direction: column;
  gap: var(--step-5);
  margin-top: var(--step-6);
  padding-top: var(--step-5);
  border-top: 1px solid rgba(255,255,255,0.12);
}

.footer-related p { margin-top: 8px; max-width: 52ch; }

.footer-address { line-height: 1.6; }

.footer-reach { display: flex; flex-direction: column; align-items: flex-start; gap: var(--step-3); }
/* Right-aligned and unconstrained, this line broke with "day." alone on the
   second row. A measure gives it a deliberate two-line shape instead. */
.footer-reach > .t-sm { max-width: 30ch; }

.footer-copy { margin-top: 32px; }

@media (min-width: 640px) {
  .footer-links { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (min-width: 1024px) {
  .footer-top { flex-direction: row; justify-content: space-between; gap: var(--step-6); }
  /* The contact column holds a two-line address and four reach links, so it
     gets more of the row than the two link lists beside it. */
  .footer-links { grid-template-columns: minmax(0, 1.15fr) minmax(0, 0.65fr) minmax(0, 1.15fr); }
  .footer-contact { flex-direction: row; align-items: flex-end; justify-content: space-between; }
  .footer-reach { align-items: flex-end; text-align: right; }
}

/* The footer card's padding is now a clamp, so the old `max-[809px]:`
   breakpoint override it inherited from Tailwind is no longer needed. */
