/* Brand palette "Navy Trust", ported from the spencerinsight design system so
   the two properties read as one brand. It is a single navy family - no second
   hue - and the accent is a blue drawn out of the same navy rather than the
   green this site used to carry.

   Two accents rather than one, for the same reason the old scheme needed two
   greens: --signal is the mid blue the brand is drawn in, and it clears 4.5:1
   only on the dark bands. --signal-strong is that blue pulled down until it
   clears AA against white, which is what the buttons (white label on blue) and
   the icons sitting on --paper need. --signal-light is the on-dark variant, for
   accents over --ink and --deep.

   Two darks rather than one: --ink is the brand navy, used for text, the footer
   and the darkest bands; --deep is one step up, so the CTA band and the about
   band separate from the sections either side of them. */
:root {
  --ink: #0f1b3d;
  --deep: #1e3a5f;
  --ink-soft: #3d4a60;
  --paper: #f0f4f7;
  --white: #fff;
  --mist: #e8edf3;
  --line: #d8dfe6;
  --signal: #3b6fa0;
  --signal-strong: #27567d;
  --signal-light: #83b4de;
  --success: #177c52;
  --muted: #5f6b80;
  --max: 1240px;
  --pad: clamp(22px, 4vw, 56px);

  /* The button plate. Blue on the light sections; the dark bands below reverse
     it to paper-on-navy, because --signal-strong on --deep is 1.5:1 and on --ink
     2.2:1 - a plate that dark on a ground that dark stops reading as a button at
     all, let alone clearing the 3:1 a control's boundary needs. Reversed it is
     9.8:1, and the label on it 14.3:1. */
  --button-bg: var(--signal-strong);
  --button-fg: var(--white);
  --button-bg-hover: #1d4361;
}

* { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
  margin: 0;
  color: var(--ink);
  background: var(--paper);
  font-family: "Manrope", Arial, sans-serif;
  font-size: 16px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}
body.menu-open { overflow: hidden; }
img { display: block; max-width: 100%; }
a { color: inherit; text-decoration: none; }
button, input, textarea { font: inherit; }
button { color: inherit; }
svg { stroke-width: 1.8; }
.container { width: min(var(--max), calc(100% - (var(--pad) * 2))); margin: 0 auto; }
.section-pad { padding: clamp(88px, 10vw, 144px) 0; }
.skip-link { position: fixed; z-index: 1000; top: 8px; left: 8px; padding: 10px 16px; color: var(--white); background: var(--ink); transform: translateY(-150%); }
.skip-link:focus { transform: translateY(0); }

h1, h2, h3, p { margin-top: 0; }
h1, h2, h3 { font-family: "Sora", "Manrope", Arial, sans-serif; text-wrap: balance; }
.button, .eyebrow, .prose th { font-family: "Manrope", Arial, sans-serif; }
p { text-wrap: pretty; }
h1, h2 { letter-spacing: -.025em; line-height: .98; }
h3 { letter-spacing: -.015em; }
h1 { margin-bottom: 28px; font-size: clamp(48px, 6.4vw, 96px); font-weight: 700; }
h2 { margin-bottom: 0; font-size: clamp(40px, 4.6vw, 68px); font-weight: 700; }
h1 em, h2 em { color: var(--signal); font-style: normal; }
.eyebrow {
  display: flex;
  align-items: center;
  gap: 11px;
  margin-bottom: 26px;
  color: var(--ink-soft);
  font-family: "Manrope", Arial, sans-serif;
  font-size: 12px;
  font-weight: 800;
  letter-spacing: .13em;
}
.eyebrow span { width: 24px; height: 2px; background: var(--signal-strong); }
.eyebrow-light { color: var(--mist); }
.eyebrow-light span { background: var(--signal); }

.site-header {
  position: fixed;
  z-index: 100;
  top: 0;
  left: 0;
  width: 100%;
  color: var(--white);
  border-bottom: 1px solid rgba(255,255,255,.17);
  transition: background .25s, color .25s, border-color .25s;
}
/* The bar starts transparent over a dark hero and turns white on scroll, so the
   accent in the lock-up's bar-E and the button plate both have to change with it.
   Over the hero the header takes the on-dark values from the shared dark-band rule
   further down this file; the rule below is the swap back for once there is white
   behind it. The letterforms need no rule of their own - they are currentColor, so
   the colour transition on .site-header carries them. */
.site-header.scrolled, .site-header.menu-active { color: var(--ink); background: rgba(255,255,255,.97); border-color: var(--line); }
.site-header.scrolled, .site-header.menu-active, .site-header.is-solid {
  --signal: var(--signal-strong);
  --button-bg: var(--signal-strong);
  --button-fg: var(--white);
  --button-bg-hover: #1d4361;
}
.header-inner { width: min(1400px, calc(100% - 40px)); height: 82px; margin: auto; display: flex; align-items: center; justify-content: space-between; }
.brand { display: inline-flex; align-items: center; min-height: 44px; flex-shrink: 0; }
/* The wordmark. Its own viewBox is 604x182, so a width is all it needs - height
   follows. The two faces are set here rather than as SVG attributes because the
   CSP allows no style="..." attribute, and font-family is not one of the
   presentation attributes SVG offers.

   The width is what sets the tagline's rendered size: it is 23 units of a 604-unit
   box, so 196px puts it at 7.5px on screen. Taking the lock-up much below that
   stops the tagline reading as words and turns it into a grey rule. */
.brand-wordmark { width: 196px; height: auto; display: block; }
.brand-wordmark .wm-display { font-family: "Sora", "Manrope", Arial, sans-serif; }
.brand-wordmark .wm-sans { font-family: "Manrope", Arial, sans-serif; letter-spacing: .04em; }
/* The three bars of the mark spread apart and close on hover - a small motion
   the eye catches without the logo changing shape in a way that would break
   the lock-up. The bars are the only SVG geometry that moves, so the letters
   keep their exact positions. */
.brand-wordmark .wm-bar { transition: transform .3s ease; }
.brand:hover .wm-bar-top { transform: translateX(6px); }
.brand:hover .wm-bar-bottom { transform: translateX(-6px); }
/* Stretched to the full 82px so a .nav-group inside it can hang its panel off
   the bottom edge of the bar. Anchoring the panel to the text box instead would
   leave a strip of hero between label and menu, and a hover gap is a menu that
   closes while you are travelling to it. */
.desktop-nav { align-self: stretch; display: flex; align-items: center; gap: clamp(24px, 3vw, 46px); }
.desktop-nav > a, .nav-group > a { position: relative; font-size: 14px; font-weight: 600; }
.desktop-nav > a::after, .nav-group > a::after { content: ""; position: absolute; left: 0; bottom: -8px; width: 100%; height: 2px; background: var(--signal); transform: scaleX(0); transform-origin: left; transition: transform .2s; }
.desktop-nav > a:hover::after, .desktop-nav > a:focus-visible::after,
.nav-group > a:hover::after, .nav-group > a:focus-visible::after { transform: scaleX(1); }

/* Services and Sectors as split controls: the label is still a link to the hub,
   the chevron beside it opens the deep pages. :hover opens it on its own, so a
   mouse still gets the menu with script.js in flight; .is-open is what a click,
   a tap or the keyboard latches it with.

   Deliberately no :focus-within here. Escape closes the panel and puts focus
   back on the chevron - which is inside the group, so focus-within would
   reopen what Escape just shut. */
.nav-group { position: relative; align-self: stretch; display: flex; align-items: center; gap: 2px; }
.nav-group-toggle { width: 24px; height: 44px; padding: 0; border: 0; background: transparent; color: inherit; display: inline-flex; align-items: center; justify-content: center; cursor: pointer; }
.nav-group-toggle svg { width: 15px; transition: transform .2s; }
.nav-group:hover .nav-group-toggle svg,
.nav-group.is-open .nav-group-toggle svg { transform: rotate(180deg); }

/* The panel is always paper-on-white, whatever the bar above it is doing - it
   hangs below the header, over the page rather than over the hero, so it takes
   the page's colours and not the header's on-dark set. */
.nav-menu {
  position: absolute;
  top: 100%;
  left: 0;
  margin: -1px 0 0;
  padding: 8px 0;
  min-width: 236px;
  list-style: none;
  color: var(--ink);
  background: var(--white);
  border: 1px solid var(--line);
  border-radius: 3px;
  box-shadow: 0 20px 44px rgba(15, 27, 61, .18);
  opacity: 0;
  visibility: hidden;
  transform: translateY(-6px);
  transition: opacity .18s, transform .18s, visibility .18s;
}
.nav-group:hover .nav-menu,
.nav-group.is-open .nav-menu { opacity: 1; visibility: visible; transform: none; }
.nav-menu a { display: block; padding: 11px 20px; font-size: 14px; font-weight: 600; white-space: nowrap; transition: background .15s, color .15s; }
.nav-menu a:hover, .nav-menu a:focus-visible { color: var(--signal-strong); background: var(--paper); }
.nav-menu a[aria-current] { color: var(--signal-strong); }
.header-actions { display: flex; align-items: center; gap: 20px; }
.header-phone { display: flex; gap: 8px; align-items: center; font-size: 13px; font-weight: 600; }
.header-phone svg { width: 15px; }
/* The Support Request icon. It carries its label from the width where the
   full strip fits: measured with the label in place, the nav starts wrapping
   below ~1150px, so the plate falls back to the icon alone under 1160. There
   the label is taken off screen rather than removed, which keeps the link's
   accessible name the same at every width; the labelled link is also in the
   mobile panel and both footer rows. The plate matches the phone link's
   weight rather than the button's - it is a route, not the page's primary
   call to action. */
.header-support { display: inline-flex; align-items: center; justify-content: center; gap: 8px; min-width: 42px; height: 42px; padding: 0 12px; border: 1px solid currentColor; border-radius: 3px; opacity: .75; transition: opacity .2s, background .2s; }
.header-support span { font-size: 13px; font-weight: 600; white-space: nowrap; }
.header-support:hover, .header-support:focus-visible { opacity: 1; background: rgba(255,255,255,.1); }
.site-header.scrolled .header-support:hover, .site-header.scrolled .header-support:focus-visible,
.site-header.menu-active .header-support:hover, .site-header.menu-active .header-support:focus-visible,
.site-header.is-solid .header-support:hover, .site-header.is-solid .header-support:focus-visible { background: var(--paper); }
.header-support svg { width: 18px; }
@media (max-width: 1160px) {
  .header-support { width: 42px; padding: 0; gap: 0; }
  /* Off-screen, not display:none, so the accessible name survives. */
  .header-support span { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }
}
/* The language switch. On desktop it is one compact control in the header
   strip - the current language's native name and a chevron - opening the other
   six as the same panel the Services and Sectors chevrons open. It used to be
   a flat row of all seven names, which at narrow laptop widths wrapped to two
   lines and competed with the phone number, the support icon and the call
   button for the 82px strip. It inherits the header's colour - white over the
   hero, ink once scrolled - and needs none of its own.
   The panel is right-aligned: the control sits near the right edge of the bar,
   so a panel hanging off its left edge would leave the viewport.
   :hover opens the panel on its own, so a mouse gets it before script.js
   arrives; :focus-within gives the keyboard the same route while there is no
   script to toggle the button. script.js adds .is-ready once it owns the
   trigger, which drops the :focus-within rule - Escape puts focus back on the
   trigger, and focus-within would reopen what it just closed. */
.lang-group-toggle {
  display: inline-flex; align-items: center; gap: 6px; padding: 4px 0;
  min-height: 44px;
  border: 0; background: transparent; color: inherit; cursor: pointer;
  font-size: 12px; font-weight: 700; letter-spacing: .04em; white-space: nowrap;
  opacity: .85; transition: opacity .2s;
}
.lang-group-toggle:hover, .lang-group-toggle:focus-visible { opacity: 1; }
.lang-group-toggle svg { width: 14px; transition: transform .2s; }
.lang-group:hover .lang-group-toggle svg,
.lang-group.is-open .lang-group-toggle svg { transform: rotate(180deg); }
.lang-group .nav-menu { left: auto; right: 0; }
.lang-group:not(.is-ready):focus-within .nav-menu { opacity: 1; visibility: visible; transform: none; }
/* The current language is a span, not a link, so it takes the row's metrics
   from a rule of its own. .nav-menu a[aria-current] above covers the links. */
.lang-menu li > span { display: block; padding: 11px 20px; font-size: 14px; font-weight: 600; white-space: nowrap; color: var(--signal-strong); }
/* The mobile panel's copy of the switch, flat: it has the room there, and a
   disclosure inside a panel that already scrolls is one tap too many. Language
   names sit in their own language and the one the visitor is reading is marked
   by the underline in --signal. */
.lang-switch { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; font-size: 12px; font-weight: 700; letter-spacing: .04em; white-space: nowrap; }
.lang-switch a { padding: 4px 0; opacity: .72; transition: opacity .2s; }
.lang-switch a:hover, .lang-switch a:focus-visible { opacity: 1; text-decoration: underline; text-underline-offset: 6px; text-decoration-color: var(--signal); }
.lang-switch [aria-current] { text-decoration: underline; text-underline-offset: 6px; text-decoration-color: var(--signal); text-decoration-thickness: 2px; }
/* From the tablet width down the switch lives in the mobile panel rather than
   the header strip - the same trade the phone number makes just above. The
   is-mobile copy needs no display:none on desktop: it sits inside .mobile-nav,
   which is hidden there. */
@media (max-width: 1040px) {
  .header-actions .lang-group { display: none; }
  /* The mobile panel styles every <a> as a full-width row, which is right for
     nav links and wrong for two small language names side by side. */
  .lang-switch.is-mobile { padding: 26px 0 6px; }
  .mobile-nav .lang-switch.is-mobile a, .mobile-nav .lang-switch.is-mobile [aria-current] {
    padding: 0; border-bottom: 0; font-family: "Manrope", Arial, sans-serif;
    font-size: 14px; letter-spacing: .04em;
  }
  .mobile-nav .lang-switch.is-mobile [aria-current] { color: var(--signal-strong); }
}
.button {
  min-height: 54px;
  padding: 0 24px;
  border: 0;
  border-radius: 3px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 18px;
  color: var(--button-fg);
  background: var(--button-bg);
  font-size: 14px;
  font-weight: 800;
  cursor: pointer;
  transition: background .2s, transform .2s;
}
.button:hover { background: var(--button-bg-hover); transform: translateY(-2px); }
.button svg { width: 18px; transition: transform .2s; }
.button:hover svg, .button:focus-visible svg { transform: translate(2px, -2px); }
.button-small { min-height: 42px; padding: 0 18px; font-size: 12px; }
.menu-toggle { display: none; width: 42px; height: 42px; padding: 0; border: 0; background: transparent; align-items: center; justify-content: center; }
.mobile-nav { display: none; }

/* Every dark band, in one rule: the accent opens up so it lifts off the navy, and
   the button plate reverses to paper-on-navy - see the note on --button-bg. */
.hero, .page-hero, .solutions, .about, .cta-band, .site-footer, .consent, .site-header {
  --signal: var(--signal-light);
  --button-bg: var(--mist);
  --button-fg: var(--ink);
  --button-bg-hover: var(--white);
}

.hero { position: relative; min-height: 780px; height: min(920px, 100svh); color: var(--white); overflow: hidden; }
.hero-image, .hero-shade { position: absolute; inset: 0; width: 100%; height: 100%; }
.hero-image { object-fit: cover; object-position: center; }
.hero-shade { background: linear-gradient(90deg, rgba(6,14,32,.94) 0%, rgba(6,14,32,.75) 31%, rgba(6,14,32,.15) 61%, rgba(6,14,32,.05) 100%); }

/* Hero network mesh. Drawn over the photograph and *under* .hero-shade, so the
   fade that protects the headline dims the mesh with it. The colours mirror the
   values the mesh had when it was baked into the hero PNG.

   Every animation below is opacity-in-from-zero, so the resting state - the one a
   prefers-reduced-motion visitor sees, since the global rule at the end of this
   file collapses animations - is the quiet static mesh, not a frozen mid-pulse. */
.hero-net { position: absolute; inset: 0; z-index: 1; width: 100%; height: 100%; pointer-events: none; }
/* script.js adds .is-idle when the hero scrolls away - nothing is watching the
   animation then, and it costs a repaint a frame to keep it going. Stated this
   way round rather than as an .is-live opt-in, so a mesh no script ever reaches
   still runs. */
.hero-net.is-idle * { animation-play-state: paused; }
.hero-net line { fill: none; }
.net-spokes line { stroke: #35516f; stroke-opacity: .12; stroke-width: 2; }
.net-links line { stroke: #44618a; stroke-opacity: .3; stroke-width: 2; }
.net-core { fill: #93aac4; fill-opacity: .65; }
.is-lit .net-core { fill: var(--signal-light); fill-opacity: .88; }

/* Traffic. One dash, 6% of the link, running the length of it and then resting -
   the --d delays at the end of this block are spread across the cycle so the
   mesh never flashes in unison. The glow is per-line on purpose: on the group it
   would re-filter the whole hero-sized bounding box every frame. */
.net-pulse {
  stroke: var(--signal-light); stroke-width: 3; stroke-linecap: round;
  filter: drop-shadow(0 0 9px rgba(131, 180, 222, .55));
  stroke-dasharray: 6 100; stroke-dashoffset: 6; opacity: 0;
  animation: net-travel 5s linear infinite var(--d, 0s);
}
@keyframes net-travel {
  0% { stroke-dashoffset: 6; opacity: 0; }
  6%, 46% { opacity: .95; }
  52%, 100% { stroke-dashoffset: -100; opacity: 0; }
}

/* Sites breathing. transform-box keeps the scale centred on the circle instead
   of the SVG's own origin. */
.net-halo {
  fill: var(--signal-light); opacity: 0;
  transform-box: fill-box; transform-origin: center;
  animation: net-breathe 4s ease-in-out infinite var(--d, 0s);
}
.is-lit .net-halo { animation-name: net-breathe-lit; }
@keyframes net-breathe {
  0%, 100% { opacity: 0; transform: scale(.5); }
  50% { opacity: .16; transform: scale(1); }
}
@keyframes net-breathe-lit {
  0%, 100% { opacity: .06; transform: scale(.6); }
  50% { opacity: .32; transform: scale(1.15); }
}

/* The hub, pinging out across the estate. */
.net-hub-ring { fill: none; stroke: var(--signal-light); stroke-opacity: .22; stroke-width: 3; }
.net-hub-ring-outer { stroke-opacity: .09; }
.net-hub-core { fill: var(--signal-light); }
.net-ping {
  fill: none; stroke: var(--signal-light); stroke-width: 3; opacity: 0;
  transform-box: fill-box; transform-origin: center;
  animation: net-ping 4.8s ease-out infinite var(--d, 0s);
}
@keyframes net-ping {
  0% { opacity: .5; transform: scale(.55); }
  70% { opacity: 0; transform: scale(2.6); }
  100% { opacity: 0; transform: scale(2.6); }
}

/* The mesh's per-element delays. These used to be style="--d:…" attributes in
   the markup, which is how they survived on `make serve` while being dropped in
   production: style-src in the CSP has no 'unsafe-inline', so the browser
   discards the whole attribute there and every pulse fired in unison behind the
   fade. In a stylesheet the same values are CSP-safe, and they stay in the one
   file that owns how the site looks. The numbers keep the old de-synchronised
   spread - they are deliberately uneven so the traffic never marches. */
.net-pulses line:nth-child(1) { --d: 0s; }
.net-pulses line:nth-child(2) { --d: .83s; }
.net-pulses line:nth-child(3) { --d: 1.66s; }
.net-pulses line:nth-child(4) { --d: 2.49s; }
.net-pulses line:nth-child(5) { --d: 3.32s; }
.net-pulses line:nth-child(6) { --d: 4.15s; }
.net-pulses line:nth-child(7) { --d: 4.98s; }
.net-pulses line:nth-child(8) { --d: .81s; }
.net-pulses line:nth-child(9) { --d: 1.64s; }
.net-pulses line:nth-child(10) { --d: 2.47s; }
.net-pulses line:nth-child(11) { --d: 3.3s; }
.net-pulses line:nth-child(12) { --d: 4.13s; }
.net-pulses line:nth-child(13) { --d: 4.96s; }
.net-pulses line:nth-child(14) { --d: .79s; }
.net-nodes .net-node:nth-child(1) { --d: 0s; }
.net-nodes .net-node:nth-child(2) { --d: .61s; }
.net-nodes .net-node:nth-child(3) { --d: 1.22s; }
.net-nodes .net-node:nth-child(4) { --d: 1.83s; }
.net-nodes .net-node:nth-child(5) { --d: 2.44s; }
.net-nodes .net-node:nth-child(6) { --d: 3.05s; }
.net-nodes .net-node:nth-child(7) { --d: 3.66s; }
.net-nodes .net-node:nth-child(8) { --d: .27s; }
.net-nodes .net-node:nth-child(9) { --d: .88s; }
.net-nodes .net-node:nth-child(10) { --d: 1.49s; }
.net-nodes .net-node:nth-child(11) { --d: 2.1s; }
.net-nodes .net-node:nth-child(12) { --d: 2.71s; }
.net-nodes .net-node:nth-child(13) { --d: 3.32s; }
.net-nodes .net-node:nth-child(14) { --d: 3.93s; }
.net-nodes .net-node:nth-child(15) { --d: .54s; }
.net-nodes .net-node:nth-child(16) { --d: 1.15s; }
.net-nodes .net-node:nth-child(17) { --d: 1.76s; }
.net-nodes .net-node:nth-child(18) { --d: 2.37s; }
.net-nodes .net-node:nth-child(19) { --d: 2.98s; }
.net-nodes .net-node:nth-child(20) { --d: 3.59s; }
.net-nodes .net-node:nth-child(21) { --d: .2s; }
.net-nodes .net-node:nth-child(22) { --d: .81s; }
.net-hub .net-ping:nth-of-type(2) { --d: 1.6s; }
.net-hub .net-ping:nth-of-type(3) { --d: 3.2s; }

/* One shared entrance: fade up into place, no delay drift of its own. Applied
   to the hero and page-hero copy, to the chips and to the staggered reveals
   below - one keyframe for everything that arrives rather than rests. The
   movement lives in `translate`, not `transform`, so an element that also
   lifts on :hover (the cards) can carry both without them fighting. */
@keyframes rise-in {
  from { opacity: 0; translate: 0 22px; }
  to { opacity: 1; translate: 0 0; }
}

.hero-content > * { animation: rise-in .7s ease-out both; }
.hero-content > :nth-child(2) { animation-delay: .12s; }
.hero-content > :nth-child(3) { animation-delay: .24s; }
.hero-content > :nth-child(4) { animation-delay: .36s; }
.hero-proof { animation: rise-in .7s ease-out .5s both; }
/* The photograph breathes - a slow 5% push-in that reads as movement without
   reading as a gimmick. Both ends of the keyframe are the resting scale, so a
   visitor who prefers reduced motion lands on the still image the animation
   would have returned to anyway. */
.hero-image { animation: hero-drift 26s ease-in-out infinite; }
@keyframes hero-drift {
  0%, 100% { scale: 1; }
  50% { scale: 1.05; }
}

.hero-content { position: relative; z-index: 2; width: min(var(--max), calc(100% - (var(--pad) * 2))); margin: auto; padding-top: clamp(170px, 22vh, 230px); }
.hero-content h1 { max-width: 870px; }
.hero-copy { max-width: 615px; margin-bottom: 38px; color: var(--mist); font-size: clamp(17px, 1.6vw, 21px); line-height: 1.55; }
.hero-actions { display: flex; align-items: center; gap: 32px; }
.text-link { display: inline-flex; align-items: center; gap: 13px; padding: 8px 0; border-bottom: 1px solid currentColor; font-size: 14px; font-weight: 700; }
.text-link svg { width: 17px; transition: transform .2s; }
.text-link:hover svg, .text-link:focus-visible svg { transform: translateX(4px); }
.text-link-light { color: var(--white); }
/* The hero's second link points down into the page, so its arrow travels the
   same way the sentence says to go. */
.text-link-down:hover svg, .text-link-down:focus-visible svg { transform: translateY(4px); }
.hero-proof {
  position: absolute;
  z-index: 2;
  bottom: 0;
  right: 0;
  width: min(760px, 55vw);
  min-height: 108px;
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  color: var(--ink);
  background: rgba(255,255,255,.96);
}
.hero-proof div { padding: 25px 22px; border-left: 1px solid var(--line); display: flex; flex-direction: column; justify-content: center; }
.hero-proof strong { font-family: "Sora", "Manrope", Arial, sans-serif; font-size: 20px; line-height: 1.1; }
.hero-proof span { margin-top: 6px; color: var(--muted); font-size: 11px; }

.split-heading { display: grid; grid-template-columns: 1.35fr .65fr; gap: clamp(50px, 10vw, 140px); align-items: end; margin-bottom: 66px; }
.service-grid { display: grid; grid-template-columns: repeat(4, 1fr); border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); }
.service-card { position: relative; min-height: 380px; padding: 38px 28px 34px; border-left: 1px solid var(--line); transition: background .25s, transform .25s; }
.service-card:first-child { border-left: 0; }
.service-card:hover { z-index: 1; background: var(--white); transform: translateY(-6px); box-shadow: 0 16px 36px rgba(12,27,51,.1); }
.service-card > svg { width: 36px; height: 36px; margin: 58px 0 26px; color: var(--signal-strong); transition: transform .25s; }
/* The card lifts and the icon answers a beat by lifting too - one gesture,
   read at the corner of the eye rather than watched. */
.service-card:hover > svg, .service-card:focus-visible > svg { transform: translateY(-5px); }
.service-number { color: var(--muted); font-family: "Manrope", Arial, sans-serif; font-size: 12px; font-weight: 700; transition: color .25s; }
.service-card:hover .service-number, .service-card:focus-visible .service-number { color: var(--signal-strong); }
.service-card h3 { margin-bottom: 14px; font-size: 20px; line-height: 1.25; }
.service-card p { color: var(--muted); font-size: 14px; line-height: 1.65; }
.service-list { position: absolute; bottom: 31px; left: 28px; color: var(--ink-soft); font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: .06em; }

.solutions { padding: clamp(90px, 10vw, 140px) 0; color: var(--white); background: var(--ink); }
.solutions-inner { display: grid; grid-template-columns: .85fr 1.15fr; gap: clamp(50px, 10vw, 150px); align-items: start; }
.solutions-copy { position: sticky; top: 140px; }
.solutions-copy > p:not(.eyebrow) { max-width: 470px; margin: 30px 0; color: var(--mist); font-size: 18px; }
.solution-list { border-top: 1px solid rgba(255,255,255,.16); }
.solution-list > div { height: 92px; display: grid; grid-template-columns: 54px 1fr 40px; align-items: center; border-bottom: 1px solid rgba(255,255,255,.16); transition: padding .2s, color .2s; }
.solution-list > div:hover { padding-left: 12px; color: var(--signal); }
.solution-list span { color: #9fadc2; font-size: 11px; }
.solution-list strong { font-family: "Sora", "Manrope", Arial, sans-serif; font-size: clamp(18px, 2vw, 25px); }
.solution-list svg { width: 24px; color: var(--signal); transition: transform .2s, opacity .2s; }
.solution-list > div:hover svg { transform: translateX(5px); opacity: .85; }

.approach-grid { display: grid; grid-template-columns: 1.05fr .95fr; gap: clamp(50px, 8vw, 110px); align-items: center; }
.approach-visual { position: relative; }
/* An offset keyline frame behind the image - a print-mount that says "this is
   a picture we placed", and gives the band some depth in a brand blue the
   photograph does not have to carry. The image is positioned so it paints on
   top of the frame's overlapping edge while the frame still shows down the
   right and along the bottom. */
.approach-visual::before { content: ""; position: absolute; inset: 16px -16px -16px 16px; border: 2px solid rgba(39, 86, 125, .32); }
.approach-visual img { position: relative; width: 100%; aspect-ratio: 4/4.5; object-fit: cover; object-position: 40% center; }
.approach-copy .lead { margin: 30px 0 32px; color: var(--muted); font-size: 18px; }
.steps { margin: 0; padding: 0; list-style: none; border-top: 1px solid var(--line); }
.steps li { padding: 20px 0; display: grid; grid-template-columns: 42px 1fr; border-bottom: 1px solid var(--line); }
.steps li > span { padding-top: 3px; color: var(--signal-strong); font-family: "Manrope", Arial, sans-serif; font-size: 11px; font-weight: 700; }
.steps strong { font-family: "Sora", "Manrope", Arial, sans-serif; font-size: 16px; }
.steps p { margin: 4px 0 0; color: var(--muted); font-size: 13px; }

.sectors { background: var(--white); }
.sector-grid { display: grid; grid-template-columns: repeat(3, 1fr); border-top: 1px solid var(--line); border-left: 1px solid var(--line); }
.sector-grid article, .sector-grid > a { position: relative; min-height: 218px; padding: 26px; border-right: 1px solid var(--line); border-bottom: 1px solid var(--line); }
/* The only link in either grid, so the only card that should answer a hover:
   the cell brightens and the icon turns a few degrees toward it, the way a
   placard does when you walk up to it. */
.sector-grid > a { transition: background .25s; }
.sector-grid > a:hover, .sector-grid > a:focus-visible { background: var(--paper); }
.sector-grid article > span, .sector-grid > a > span { color: var(--muted); font-size: 10px; }
.sector-grid h3 { margin: 42px 0 8px; font-size: 19px; }
.sector-grid p { max-width: 250px; margin: 0; color: var(--muted); font-size: 13px; }
.sector-grid svg { position: absolute; top: 24px; right: 24px; width: 26px; color: var(--signal-strong); transition: transform .25s; }
.sector-grid > a:hover svg, .sector-grid > a:focus-visible svg { transform: rotate(-8deg) scale(1.14); }

/* Seven sectors in a three-up grid leaves a row with one card in it and two
   empty cells beside it, which reads as a page that failed to finish loading.
   When the count lands one past a full row - which seven does, and which the
   selector will keep catching, since it counts rather than names - that odd
   card takes the whole row instead.

   The icon stays where it is on the wide card. Bringing it in off the right
   edge to sit near the words looks like the fix and is not: at 26px from the
   left it lands under the paragraph. Left alone it holds the same right-hand
   icon column as the six cards above it, which is what makes the row read as
   part of the grid rather than as a banner stuck on the end. */
.sector-grid > a:last-child:nth-child(3n + 1) { grid-column: 1 / -1; }

.about { position: relative; overflow: hidden; padding: clamp(88px, 10vw, 140px) 0; color: var(--white); background: var(--deep); }
.about-inner { display: grid; grid-template-columns: 1fr 1fr; gap: clamp(60px, 11vw, 160px); }
.about-copy .lead { margin-bottom: 26px; color: var(--white); font-size: clamp(20px, 2vw, 27px); line-height: 1.45; }
.about-copy > p:not(.lead) { color: var(--mist); line-height: 1.8; }

.contact-grid { display: grid; grid-template-columns: .9fr 1.1fr; gap: clamp(60px, 10vw, 140px); }
.contact-copy > p:not(.eyebrow) { max-width: 520px; margin: 30px 0 40px; color: var(--muted); font-size: 17px; }
.contact-details { display: flex; flex-direction: column; gap: 18px; }
.contact-details a, .contact-details > div { display: flex; align-items: center; gap: 15px; }
.contact-details svg { width: 19px; color: var(--signal-strong); }
.contact-details span { display: flex; flex-direction: column; font-size: 14px; font-weight: 600; overflow-wrap: anywhere; }
.contact-details small { color: var(--muted); font-size: 10px; font-weight: 500; text-transform: uppercase; letter-spacing: .08em; }
.contact-form { padding: clamp(28px, 4vw, 50px); background: var(--white); box-shadow: 0 20px 60px rgba(12,27,51,.1); }
.form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 22px; }
.contact-form label { display: flex; flex-direction: column; gap: 8px; margin-bottom: 20px; color: var(--ink-soft); font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .08em; }
.contact-form input, .contact-form textarea { width: 100%; padding: 12px 0; color: var(--ink); border: 0; border-bottom: 1px solid var(--line); border-radius: 0; outline: 0; background: transparent; text-transform: none; letter-spacing: 0; resize: vertical; transition: border-color .2s; }
.contact-form input:focus, .contact-form textarea:focus { border-color: var(--signal-strong); }
.form-submit { width: 100%; margin-top: 8px; }
.form-status { min-height: 24px; margin: 12px 0 0; color: var(--muted); font-size: 12px; text-align: center; }
.form-status.is-error { color: #a4291f; }
.form-status.is-sent { color: var(--success); font-weight: 700; }
.form-note { margin: 4px 0 0; color: var(--muted); font-size: 12px; text-align: center; }
.form-note a { color: var(--ink-soft); text-decoration: underline; }
.form-submit[disabled] { opacity: .55; cursor: progress; }
/* Off-screen rather than display:none - a bot reading the DOM sees a normal
   field, which is the point of a honeypot. */
.form-honeypot { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

/* ------------------------------------------------------- support request ---
   Process copy on the left, ticket form on the right. The form reuses
   .contact-form wholesale, so these rules add only what the enquiry form
   never needed: a select, a checkbox row, a file input, the conditional
   parts block and the panel that replaces the form after a send. */
.support-layout { display: grid; grid-template-columns: .9fr 1.1fr; gap: clamp(50px, 8vw, 120px); align-items: start; }
.support-intro { position: sticky; top: 130px; }
.support-lead { max-width: 480px; margin: 30px 0 36px; color: var(--muted); font-size: 17px; line-height: 1.7; }
.support-notices { margin: 0; padding: 0; list-style: none; border-top: 1px solid var(--line); }
.support-notices li { padding: 18px 0; display: grid; grid-template-columns: 34px 1fr; gap: 14px; border-bottom: 1px solid var(--line); color: var(--ink-soft); font-size: 14px; line-height: 1.65; }
.support-notices svg { width: 19px; margin-top: 3px; color: var(--signal-strong); }
.support-alt { margin: 28px 0 0; color: var(--muted); font-size: 13px; }
.support-alt a { color: var(--ink); text-decoration: underline; }
.support-form .form-section { margin: 8px 0 22px; padding-top: 26px; border-top: 1px solid var(--line); color: var(--signal-strong); font-family: "Manrope", Arial, sans-serif; font-size: 11px; font-weight: 800; letter-spacing: .1em; text-transform: uppercase; }
.support-form .form-section:first-child,
.support-form .form-work-order + .form-section { margin-top: 0; padding-top: 0; border-top: 0; }
.support-form select { width: 100%; padding: 12px 0; color: var(--ink); border: 0; border-bottom: 1px solid var(--line); border-radius: 0; outline: 0; background: transparent; transition: border-color .2s; }
.support-form select:focus { border-color: var(--signal-strong); }
.support-form input[type="file"] { padding: 12px 0 8px; font-size: 13px; cursor: pointer; }
.support-form input[type="file"]::file-selector-button { margin-right: 14px; padding: 9px 16px; border: 1px solid var(--line); border-radius: 3px; color: var(--ink); background: var(--paper); font: inherit; font-size: 12px; font-weight: 700; cursor: pointer; transition: background .2s, border-color .2s; }
.support-form input[type="file"]::file-selector-button:hover { background: var(--white); border-color: var(--signal-strong); }
/* The checkbox row undoes the form's uppercase-label treatment - its label is
   a sentence, not a field name. */
.form-check { flex-direction: row; align-items: center; gap: 12px; text-transform: none; letter-spacing: 0; font-size: 13px; font-weight: 600; }
.form-check input { width: auto; accent-color: var(--signal-strong); }
/* Work-order-only mode: the toggle's own file input, and the notification
   email block stretching across the row its hidden companion leaves behind. */
.form-work-order { margin: 4px 0 20px; }
.form-work-order .form-hint { margin-top: -12px; margin-bottom: 4px; }
.support-form.is-work-order .form-emails { grid-column: 1 / -1; }
/* The mode takes whole blocks off the screen with the hidden attribute, but the
   user-agent [hidden] rule loses to any author display: a .form-row is grid and
   a label is flex, so those fields stayed visible until this rule put the
   display back under the attribute's control. */
.support-form [hidden] { display: none; }
.form-parts { margin: 4px 0 20px; padding: 22px 22px 4px; background: var(--paper); }
.form-hint { margin: -8px 0 20px; color: var(--muted); font-size: 12px; }
/* The notification email rows: "Add another email" clones the template row and
   each added row carries its own Remove, so the count never drops below the
   one required field. script.js joins the values into the single ';' field
   the endpoint receives. */
.form-emails { display: flex; flex-direction: column; gap: 8px; margin-bottom: 20px; }
.form-emails > label { margin-bottom: 0; }
.form-email-row { display: flex; align-items: center; gap: 12px; }
.form-email-row input { flex: 1 1 auto; width: auto; min-width: 0; }
.form-email-add, .form-email-remove { flex-shrink: 0; padding: 0; border: 0; background: transparent; color: var(--signal-strong); font-family: "Manrope", Arial, sans-serif; font-size: 12px; font-weight: 700; cursor: pointer; }
.form-email-add { align-self: flex-start; }
.form-email-add:hover, .form-email-remove:hover { text-decoration: underline; }
.support-success { padding: clamp(28px, 4vw, 50px); background: var(--white); box-shadow: 0 20px 60px rgba(12,27,51,.1); scroll-margin-top: 130px; }
.support-success h2 { margin-bottom: 20px; font-size: clamp(28px, 3vw, 40px); }
.support-success > p:not(.eyebrow) { margin-bottom: 28px; color: var(--muted); font-size: 15px; line-height: 1.7; }
.support-success .support-notices { border-top: 0; }

.site-footer { padding: 52px 0 28px; color: var(--white); background: var(--ink); }
.footer-top { padding-bottom: 40px; display: flex; align-items: center; justify-content: space-between; border-bottom: 1px solid rgba(255,255,255,.14); }
.footer-top > p { margin: 0; color: var(--mist); font-size: 13px; }
.back-top { width: 44px; height: 44px; display: grid; place-items: center; border: 1px solid rgba(255,255,255,.24); transition: background .25s, border-color .25s; }
.back-top svg { width: 18px; transition: transform .25s; }
.back-top:hover { background: rgba(255,255,255,.08); border-color: var(--signal); }
.back-top:hover svg { transform: translateY(-3px); }
.footer-bottom { padding-top: 24px; display: flex; justify-content: space-between; color: #9fadc2; font-size: 11px; }
.footer-bottom div { display: flex; gap: 30px; }

/* Long-form copy - privacy.html. Classes rather than the inline style="..."
   attributes 404.html uses, because style-src in the CSP has no 'unsafe-inline'
   either, so those attributes are dropped in production. */
.prose { max-width: 68ch; }
.prose h1 { margin-top: 48px; font-size: clamp(38px, 4.4vw, 62px); }
.prose h2 { margin: 52px 0 14px; font-size: clamp(22px, 2.2vw, 28px); }
.prose h2:first-of-type { margin-top: 40px; }
.prose p, .prose li { color: var(--ink-soft); font-size: 15px; }
.prose a { color: var(--ink); text-decoration: underline; }
/* .prose a is a class-plus-element selector and .button is one class, so the
   underlined ink link above wins inside long-form copy - which is fine for the
   inline links it was written for and wrong for the CTA at the foot of 404 and
   privacy: it put ink text on the button plate, at 3.4:1. */
.prose a.button { color: var(--button-fg); text-decoration: none; }
.prose ul { padding-left: 20px; }
.prose li { margin-bottom: 8px; }
.prose table { width: 100%; margin: 18px 0 0; border-collapse: collapse; font-size: 14px; }
.prose th, .prose td { padding: 12px 14px 12px 0; text-align: left; vertical-align: top; border-bottom: 1px solid var(--line); }
.prose th { font-family: "Manrope", Arial, sans-serif; font-size: 12px; font-weight: 800; letter-spacing: .06em; text-transform: uppercase; }
.prose-updated { color: var(--muted); font-size: 12px; }
.prose-back { margin-top: 56px; }
.consent-reset { margin-top: 8px; }

/* ---------------------------------------------------------------- interior --
   The service and sector pages. They reuse the homepage's grids and bands
   wherever the shape is the same; only what those pages needed and the
   homepage did not have is below. */

/* The header is white-on-transparent so it can sit over a dark hero. Privacy
   and 404 have no hero, so there it starts opaque instead of being white text
   on a white page until the first scroll. */
.site-header.is-solid { color: var(--ink); background: rgba(255,255,255,.97); border-color: var(--line); }
main.section-pad { padding-top: clamp(138px, 14vw, 196px); }

/* The twelve interior page headers were a flat navy slab - the one band every
   generated page shares, and the one with no visual at all. It gets the same
   language the homepage hero uses, drawn in CSS rather than in markup so all
   twelve pages change at once: a fine dot grid like a planning print, two soft
   glows drifting behind it, and the copy arriving in sequence. */
.page-hero { position: relative; overflow: hidden; padding: clamp(150px, 16vw, 210px) 0 clamp(60px, 7vw, 92px); color: var(--white); background: var(--ink); box-shadow: inset 0 -1px 0 rgba(131, 180, 222, .28); }
.page-hero::before, .cta-band::before, .about::before {
  content: "";
  position: absolute;
  inset: -30% -15%;
  z-index: 0;
  background:
    radial-gradient(closest-side, rgba(59, 111, 160, .55), transparent) 22% 24% / 55% 75% no-repeat,
    radial-gradient(closest-side, rgba(131, 180, 222, .16), transparent) 82% 72% / 48% 64% no-repeat;
  animation: band-glow 18s ease-in-out infinite;
}
.page-hero::after, .cta-band::after, .about::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  background-image: radial-gradient(rgba(255, 255, 255, .055) 1px, transparent 1.6px);
  background-size: 27px 27px;
  mask-image: linear-gradient(105deg, transparent 8%, #000 34%, #000 62%, transparent 94%);
}
.page-hero > .container, .cta-band > .container, .about > .container { position: relative; z-index: 1; }
/* 0% and 100% are the resting frame, so the reduced-motion collapse of this
   infinite drift leaves the glows exactly where they started. */
@keyframes band-glow {
  0%, 100% { transform: translate3d(0, 0, 0); }
  50% { transform: translate3d(-3.5%, 2.5%, 0); }
}
.page-hero .container > * { animation: rise-in .65s ease-out both; }
.page-hero .container > :nth-child(2) { animation-delay: .08s; }
.page-hero .container > :nth-child(3) { animation-delay: .18s; }
.page-hero .container > :nth-child(4) { animation-delay: .3s; }
.page-hero .container > :nth-child(5) { animation-delay: .42s; }
.breadcrumb { margin-bottom: 34px; display: flex; flex-wrap: wrap; gap: 10px; color: #9fadc2; font-size: 12px; font-weight: 600; }
.breadcrumb a { border-bottom: 1px solid transparent; }
.breadcrumb a:hover { color: var(--signal); border-color: currentColor; }
.breadcrumb [aria-current] { color: var(--white); }
.page-hero h1 { max-width: 17ch; }
.page-hero-lead { max-width: 62ch; margin: 0; color: var(--mist); font-size: clamp(17px, 1.7vw, 21px); line-height: 1.55; }
.chips { margin: 36px 0 0; padding: 0; display: flex; flex-wrap: wrap; gap: 10px; list-style: none; }
.chips li { padding: 8px 15px; border: 1px solid rgba(255,255,255,.22); border-radius: 999px; color: var(--mist); font-size: 12px; font-weight: 600; animation: rise-in .5s ease-out both; }
/* Each chip lands a beat after the sentence above it, so the row spells itself
   out rather than appearing all at once. */
.chips li:nth-child(1) { animation-delay: .5s; }
.chips li:nth-child(2) { animation-delay: .58s; }
.chips li:nth-child(3) { animation-delay: .66s; }
.chips li:nth-child(4) { animation-delay: .74s; }
.chips li:nth-child(5) { animation-delay: .82s; }
.chips li:nth-child(6) { animation-delay: .9s; }

/* The homepage's two summary headings now carry a link out as well as the
   paragraph, so the right-hand column of .split-heading is a div rather than
   the bare <p> that .split-heading > p was written for. */
.split-heading > div > p:not(.eyebrow) { margin: 0 0 18px; color: var(--muted); font-size: 17px; line-height: 1.7; }
.split-heading > div > p:not(.eyebrow):last-child { margin-bottom: 0; }
.split-heading .text-link { margin-top: 18px; }
.section-heading > h2 { margin-bottom: 52px; }

.point-grid { display: grid; grid-template-columns: repeat(3, 1fr); border-top: 1px solid var(--line); border-left: 1px solid var(--line); }
.point-card { padding: 34px 28px 36px; border-right: 1px solid var(--line); border-bottom: 1px solid var(--line); transition: background .25s; }
.point-card:hover { background: var(--white); }
.point-card > svg { width: 30px; height: 30px; margin-bottom: 24px; color: var(--signal-strong); transition: transform .25s; }
.point-card:hover > svg { transform: translateY(-4px); }
.point-card h3 { margin-bottom: 12px; font-size: 18px; line-height: 1.3; }
.point-card p { margin: 0; color: var(--muted); font-size: 14px; line-height: 1.65; }

.deliver-list { margin: 0; padding: 0; list-style: none; border-top: 1px solid rgba(255,255,255,.16); }
.deliver-list li { padding: 19px 0; display: flex; gap: 16px; align-items: flex-start; border-bottom: 1px solid rgba(255,255,255,.16); font-size: clamp(15px, 1.5vw, 18px); }
.deliver-list svg { flex: 0 0 auto; width: 20px; margin-top: 3px; color: var(--signal); }

.process-inner { display: grid; grid-template-columns: .85fr 1.15fr; gap: clamp(45px, 8vw, 120px); align-items: start; }

.related { background: var(--white); }
.related-block + .related-block { margin-top: 56px; }
.related-title { margin-bottom: 26px; font-size: clamp(22px, 2.4vw, 30px); }
.related-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1px; border: 1px solid var(--line); background: var(--line); }
.related-card { position: relative; padding: 28px 26px 30px; display: block; background: var(--paper); transition: background .2s, transform .2s; }
.related-card:hover { background: var(--white); transform: translateY(-3px); }
.related-card > svg:first-child { width: 26px; height: 26px; margin-bottom: 22px; color: var(--signal-strong); }
.related-card strong { display: block; margin-bottom: 8px; font-family: "Sora", "Manrope", Arial, sans-serif; font-size: 17px; }
.related-card span { display: block; max-width: 32ch; color: var(--muted); font-size: 13px; line-height: 1.6; }
.related-arrow { position: absolute; top: 26px; right: 24px; width: 20px; color: var(--ink-soft); }

/* The hub pages put the card grid straight under the intro, so it does not
   need a second helping of section padding above it. */
.hub-grid { padding-top: 0; }

.cta-band { position: relative; overflow: hidden; padding: clamp(70px, 8vw, 110px) 0; color: var(--white); background: var(--deep); }
.cta-inner { display: flex; flex-wrap: wrap; gap: 40px; align-items: flex-end; justify-content: space-between; }
.cta-actions { display: flex; flex-wrap: wrap; gap: 28px; align-items: center; }

.footer-nav { padding: 40px 0; display: grid; grid-template-columns: repeat(4, 1fr); gap: 34px; border-bottom: 1px solid rgba(255,255,255,.14); }
.footer-nav h2 { margin-bottom: 16px; color: var(--signal); font-size: 11px; font-weight: 800; letter-spacing: .12em; text-transform: uppercase; }
.footer-nav ul { margin: 0; padding: 0; list-style: none; }
.footer-nav li { margin-bottom: 10px; color: var(--mist); font-size: 13px; line-height: 1.5; }
.footer-nav a:hover { color: var(--white); }

/* Cookie banner. Injected by analytics.js rather than living in index.html, so
   one copy serves every page - see the comment on showConsentBanner().
   Deliberately styled like the footer rather than as an alarm: it is a question,
   not a warning. Reject and Accept get the same weight and the same hit area;
   burying reject is treated by the ICO as not having asked. */
.consent {
  position: fixed;
  z-index: 200;
  right: var(--pad);
  bottom: var(--pad);
  left: var(--pad);
  max-width: 640px;
  margin: 0 auto;
  padding: 22px 24px;
  display: flex;
  align-items: center;
  gap: 24px;
  border-radius: 3px;
  color: var(--white);
  background: var(--ink);
  box-shadow: 0 18px 44px rgba(6, 14, 32, .38);
  animation: consent-in .4s ease both;
}
.consent p { margin: 0; color: var(--mist); font-size: 13px; }
.consent a { color: var(--signal); text-decoration: underline; }
.consent-actions { display: flex; flex-shrink: 0; gap: 10px; }
.consent-actions button {
  min-height: 42px;
  padding: 0 20px;
  border: 1px solid rgba(255,255,255,.24);
  border-radius: 3px;
  color: var(--white);
  background: transparent;
  font-family: "Manrope", Arial, sans-serif;
  font-size: 12px;
  font-weight: 800;
  letter-spacing: .04em;
  cursor: pointer;
}
.consent-accept { color: var(--button-fg); background: var(--button-bg); border-color: var(--button-bg); }
.consent-reject:hover { border-color: var(--white); }
.consent-accept:hover { background: var(--button-bg-hover); border-color: var(--button-bg-hover); }
@keyframes consent-in { from { opacity: 0; transform: translateY(14px); } }


/* Scroll reveals run off the same rise-in as the hero copy. Animation rather
   than transition on purpose: a transition on `transform` here sat on top of
   every card's own hover transform - same shorthand, later in the cascade -
   so a card that had revealed lifted to its hover position over .65s and
   dragged its background change with it. An animation on `opacity` and
   `translate` leaves `transform` and `transition` alone for the cards to use.
   Fill mode both keeps the from-state during a stagger delay and the
   to-state after it, so the resting values are the visible ones. */
.reveal { opacity: 0; }
.reveal.is-visible { animation: rise-in .65s ease-out both; }
/* Cards in a grid arrive across it rather than all at once - a third of a
   card-width apart, read as one surface settling. Column position, not index:
   the point grid wraps three to a line on the interior pages. */
.service-grid > .reveal:nth-child(2), .point-grid > .reveal:nth-child(3n+2) { animation-delay: .09s; }
.service-grid > .reveal:nth-child(3), .point-grid > .reveal:nth-child(3n) { animation-delay: .18s; }
.service-grid > .reveal:nth-child(4) { animation-delay: .27s; }
:focus-visible { outline: 3px solid var(--signal); outline-offset: 4px; }

@media (max-width: 1040px) {
  .desktop-nav, .header-phone { display: none; }
  .menu-toggle { display: flex; }
  .mobile-nav {
    position: fixed;
    inset: 82px 0 auto;
    max-height: calc(100dvh - 82px);
    overflow-y: auto;
    padding: 32px var(--pad) 45px;
    display: flex;
    flex-direction: column;
    gap: 0;
    color: var(--ink);
    background: var(--white);
    border-bottom: 1px solid var(--line);
    transform: translateY(-120%);
    opacity: 0;
    pointer-events: none;
    transition: transform .25s, opacity .25s;
  }
  .mobile-nav.open { transform: translateY(0); opacity: 1; pointer-events: auto; }
  .mobile-nav a { padding: 15px 0; border-bottom: 1px solid var(--line); font-family: "Sora", "Manrope", Arial, sans-serif; font-size: 20px; font-weight: 700; }
  /* The deep pages, flat rather than behind a disclosure - a tap that only
     reveals more taps is worse than a list you can flick through. Which is why
     the panel now has to scroll: fourteen entries is taller than a phone. */
  .mobile-subnav { margin: 0; padding: 0; list-style: none; display: flex; flex-direction: column; }
  .mobile-subnav a { display: block; padding: 11px 0 11px 18px; font-family: "Manrope", Arial, sans-serif; font-size: 15px; font-weight: 600; color: var(--muted); }
  .mobile-subnav a[aria-current] { color: var(--signal-strong); }
  .service-grid { grid-template-columns: repeat(2, 1fr); }
  .service-card:nth-child(3) { border-left: 0; border-top: 1px solid var(--line); }
  .service-card:nth-child(4) { border-top: 1px solid var(--line); }
  .hero-proof { width: 70vw; }
  .approach-grid { gap: 60px; }
  .sector-grid { grid-template-columns: repeat(2, 1fr); }
  .sector-grid > a:last-child:nth-child(3n + 1) { grid-column: auto; }
  .sector-grid > a:last-child:nth-child(2n + 1) { grid-column: 1 / -1; }
  .point-grid, .related-grid, .footer-nav { grid-template-columns: repeat(2, 1fr); }
  .process-inner { grid-template-columns: 1fr; gap: 44px; }
}

@media (max-width: 760px) {
  .header-inner { width: calc(100% - 32px); height: 72px; }
  .brand-wordmark { width: 168px; }
  .header-actions .button { display: none; }
  .mobile-nav { top: 72px; max-height: calc(100dvh - 72px); }
  .hero { min-height: 720px; height: 100svh; max-height: 850px; }
  .hero-image { object-position: 61% center; }
  /* The mesh is centre-cropped and cannot follow the shift above, and the
     heavier mobile shade all but hides it anyway. */
  .hero-net { display: none; }
  .hero-shade { background: linear-gradient(90deg, rgba(6,14,32,.9), rgba(6,14,32,.38)), linear-gradient(0deg, rgba(6,14,32,.8), transparent 62%); }
  .hero-content { padding-top: 144px; }
  .hero-content h1 { font-size: clamp(44px, 13vw, 62px); }
  .hero-copy { max-width: 90%; font-size: 16px; }
  .hero-actions { align-items: flex-start; flex-direction: column; gap: 16px; }
  .hero-proof { left: 0; right: 0; width: 100%; min-height: 88px; grid-template-columns: repeat(2, 1fr); }
  .hero-proof div { padding: 13px var(--pad); border-top: 1px solid var(--line); }
  .hero-proof div:nth-child(3), .hero-proof div:nth-child(4) { display: none; }
  .split-heading, .solutions-inner, .approach-grid, .about-inner, .contact-grid { grid-template-columns: 1fr; }
  .split-heading { gap: 28px; margin-bottom: 45px; }
  .split-heading > div > p:not(.eyebrow) { font-size: 16px; }
  .service-grid { grid-template-columns: 1fr; }
  .service-card { min-height: 325px; border-top: 1px solid var(--line); border-left: 0; }
  .service-card:first-child { border-top: 0; }
  .service-card > svg { margin-top: 40px; }
  .solutions-inner { gap: 55px; }
  .solutions-copy { position: static; }
  .solution-list > div { height: 78px; }
  .approach-visual { margin-right: 14px; }
  .approach-visual img { aspect-ratio: 4/4.8; }
  .approach-copy { padding-top: 8px; }
  .sector-grid { grid-template-columns: 1fr; }
  .sector-grid article, .sector-grid > a { min-height: 190px; }
  .about-inner, .contact-grid { gap: 48px; }
  .support-layout { grid-template-columns: 1fr; gap: 48px; }
  .support-intro { position: static; }
  .form-row { grid-template-columns: 1fr; gap: 0; }
  .footer-top { align-items: flex-start; flex-wrap: wrap; gap: 30px; }
  .footer-top > p { order: 3; width: 100%; }
  .footer-bottom { flex-direction: column; gap: 16px; }
  /* Stacked, and the buttons go full width so neither is the easier target. */
  .consent { flex-direction: column; align-items: stretch; gap: 18px; }
  .consent-actions button { flex: 1; }
  .point-grid, .related-grid, .footer-nav { grid-template-columns: 1fr; }
  .page-hero h1 { font-size: clamp(38px, 11vw, 54px); }
  .page-hero-lead { font-size: 16px; }
  .cta-inner { flex-direction: column; align-items: flex-start; gap: 30px; }
  .cta-actions { flex-direction: column; align-items: flex-start; gap: 20px; }
  .related-block + .related-block { margin-top: 42px; }
}

/* Everything that moves on this site is arranged so its resting frame is also
   its visible frame - entrances end at opacity 1, the infinite drifts begin
   and end on the same value, and the mesh pulses fade to nothing rather than
   freeze mid-pulse. Collapsing durations and delays is therefore enough: this
   page becomes exactly the still site it would have been without any of it. */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { transition-duration: .01ms !important; animation-duration: .01ms !important; animation-delay: 0s !important; animation-iteration-count: 1 !important; }
  .reveal { opacity: 1; transform: none; }
}
