/* ===========================================================================
   DUNICOT — "Field Dossier" design system
   Authored for dunicot.com. No framework, no utility soup.
   Palette: Dunicot navy / electric blue. Type: Inter Tight + IBM Plex Mono.
   =========================================================================== */

/* ---------------------------------------------------------------- 0. Fonts */
@font-face {
  font-family: 'Inter Tight';
  src: url('/fonts/inter-tight-latin.woff2') format('woff2');
  font-weight: 400 700; font-style: normal; font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Inter Tight';
  src: url('/fonts/inter-tight-latin-ext.woff2') format('woff2');
  font-weight: 400 700; font-style: normal; font-display: swap;
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Plex Mono';
  src: url('/fonts/plex-mono-400-latin.woff2') format('woff2');
  font-weight: 400; font-style: normal; font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Plex Mono';
  src: url('/fonts/plex-mono-500-latin.woff2') format('woff2');
  font-weight: 500; font-style: normal; font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

/* --------------------------------------------------------------- 1. Tokens
   Sampled from the live dunicot.com: navy bands (#021a36 / #01152e) with an
   electric-blue accent (#03a9f4) and white text. Dark is the default edition
   because the brand is dark; the light edition is a genuine alternate, not an
   afterthought. */
:root, :root[data-theme='dark'] {
  --bg:          #021a36;   /* base band */
  --bg-sunk:     #01152e;   /* alternating band */
  --bg-raised:   #052445;   /* cards and panels */
  --bg-deep:     #010f22;   /* masthead, footer, utility bar */

  --ink:         #ffffff;
  --ink-2:       #c3d4e6;
  --ink-3:       #8fa6bf;
  --ink-4:       #7e97b3;   /* 5.2:1 on --bg-raised */

  --rule:        rgba(255, 255, 255, 0.13);
  --rule-soft:   rgba(255, 255, 255, 0.07);
  --rule-strong: rgba(255, 255, 255, 0.30);

  --accent:      #03a9f4;   /* brand blue */
  --accent-hi:   #38bdf8;   /* hover / link-on-dark */
  --accent-deep: #0a92d1;
  --accent-wash: rgba(3, 169, 244, 0.12);
  --accent-rule: rgba(3, 169, 244, 0.34);
  --on-accent:   #021a36;

  --sev-crit:    #ff6b6b;
  --sev-high:    #ffa94d;
  --sev-med:     #ffd43b;

  /* type — sans throughout; mono only for small data labels */
  --sans: 'Inter Tight', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Arial, sans-serif;
  --serif: var(--sans);
  --mono: 'Plex Mono', ui-monospace, 'SF Mono', Menlo, Consolas, monospace;

  --fs-display: clamp(2.1rem, 4.4vw, 3.5rem);
  --fs-h2:      clamp(1.6rem, 2.7vw, 2.3rem);
  --fs-h3:      clamp(1.12rem, 1.5vw, 1.35rem);
  --fs-lede:    clamp(1.02rem, 1.25vw, 1.18rem);
  --fs-body:    1rem;
  --fs-small:   0.9375rem;
  --fs-label:   0.6875rem;

  --measure: 70ch;
  --gut: clamp(1.25rem, 4vw, 3.5rem);
  --max: 1240px;
  --max-narrow: 860px;
  --band: clamp(3.75rem, 7vw, 6rem);

  --radius: 6px;
  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);
  color-scheme: dark;
}

/* Light edition — same brand, inverted ground. */
:root[data-theme='light'] {
  --bg:          #ffffff;
  --bg-sunk:     #f4f7fb;
  --bg-raised:   #ffffff;
  --bg-deep:     #021a36;

  --ink:         #0b1b2f;
  --ink-2:       #3d5875;
  --ink-3:       #52708f;
  --ink-4:       #5c7086;   /* 5.1:1 on white, 4.7:1 on --bg-sunk */

  --rule:        rgba(11, 27, 47, 0.14);
  --rule-soft:   rgba(11, 27, 47, 0.07);
  --rule-strong: rgba(11, 27, 47, 0.34);

  --accent:      #0a6ea8;   /* 5.5:1 with white button text */
  --accent-hi:   #0369a1;
  --accent-deep: #0a92d1;
  --accent-wash: rgba(3, 169, 244, 0.09);
  --accent-rule: rgba(10, 127, 194, 0.30);
  --on-accent:   #ffffff;

  --sev-crit:    #c0392b;
  --sev-high:    #b4622a;
  --sev-med:     #8a7a2c;
  color-scheme: light;
}

/* ----------------------------------------------------------------- 2. Base */
*, *::before, *::after { box-sizing: border-box; }
html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: 6rem;
}
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    transition-duration: 0.001ms !important;
  }
}
body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--sans);
  font-size: var(--fs-body);
  line-height: 1.62;
  font-feature-settings: 'kern' 1, 'liga' 1, 'calt' 1;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
}
img, video { max-width: 100%; height: auto; display: block; }
svg { max-width: 100%; }
/* Inline icons must stay inline; only standalone SVGs become blocks. */
.svc__go svg, .themetoggle svg, .badge svg { display: block; }
::selection { background: var(--accent); color: var(--on-accent); }

h1, h2, h3, h4 {
  font-family: var(--sans);
  font-weight: 700;
  line-height: 1.15;
  letter-spacing: -0.018em;
  margin: 0;
  text-wrap: balance;
}
p { margin: 0 0 1.15em; }
a { color: inherit; }

:where(a):focus-visible,
:where(button):focus-visible,
:where(summary):focus-visible,
:where(input, textarea, select):focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 2px;
}

.skip {
  position: absolute; left: -9999px; top: 0; z-index: 200;
  background: var(--ink); color: var(--bg);
  padding: 0.75rem 1.25rem; font: 500 0.875rem/1 var(--mono);
}
.skip:focus { left: 0.5rem; top: 0.5rem; }

.vh {
  position: absolute !important; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0;
}

/* --------------------------------------------------------------- 3. Layout */
.wrap { width: 100%; max-width: var(--max); margin-inline: auto; padding-inline: var(--gut); }
.wrap-narrow { width: 100%; max-width: var(--max-narrow); margin-inline: auto; padding-inline: var(--gut); }
.band { padding-block: var(--band); position: relative; }
.band + .band { border-top: 1px solid var(--rule); }
.band--sunk { background: var(--bg-sunk); }
.band + .band { border-top: 1px solid var(--rule-soft); }

.cols {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  gap: clamp(1.5rem, 3vw, 2.75rem);
}
@media (max-width: 900px) { .cols { grid-template-columns: repeat(6, minmax(0, 1fr)); } }

/* ------------------------------------------------------------ 4. Typography */
.kicker {
  font: 500 var(--fs-label)/1.5 var(--mono);
  letter-spacing: 0.17em;
  text-transform: uppercase;
  color: var(--ink-3);
  display: flex; align-items: center; gap: 0.7rem;
  flex-wrap: wrap;
  margin: 0 0 1.6rem;
}
/* The rule needs a floor, or a long label squeezes it to 0px and the line
   reads as a scrambled two-row block. */
/* min-width:0 lets the label shrink and wrap internally instead of forcing
   the flex line wider than the measure. */
.kicker__t { min-width: 0; }
.kicker::after { min-width: 2.5rem; }
.kicker::after {
  content: ''; flex: 1 1 auto; height: 1px; background: var(--rule);
}
@media (max-width: 640px) {
  /* The trailing rule needs a line to sit on; below this width the label
     wraps instead, so the rule is dropped rather than left dangling. */
  .kicker { flex-wrap: wrap; row-gap: 0.35rem; }
  .kicker::after { display: none; }
}
.kicker--oxide { color: var(--accent); }
.kicker--plain::after { display: none; }
.kicker b { font-weight: 500; color: var(--accent); }

.display {
  font-size: var(--fs-display);
  font-weight: 800;
  letter-spacing: -0.025em;
  line-height: 1.1;
}
.h2 { font-size: var(--fs-h2); max-width: 20ch; margin-bottom: 0.7rem; }
.h3 { font-size: var(--fs-h3); letter-spacing: -0.014em; }
h1 em, h2 em, h3 em, .display em, .h2 em, .h3 em { font-style: normal; color: var(--accent); }

.lede {
  font-size: var(--fs-lede);
  line-height: 1.5;
  color: var(--ink-2);
  max-width: 54ch;
  text-wrap: pretty;
}
.prose { max-width: var(--measure); color: var(--ink-2); text-wrap: pretty; }
.prose > p + p { margin-top: 0; }
.prose strong { color: var(--ink); font-weight: 600; }
.prose h3 { color: var(--ink); margin: 2.4rem 0 0.8rem; }
.prose ul, .prose ol { margin: 0 0 1.3em; padding-left: 1.1rem; }
.prose li { margin-bottom: 0.45em; }
.prose li::marker { color: var(--accent); font-family: var(--mono); font-size: 0.85em; }


.mono { font-family: var(--mono); font-size: 0.8125rem; letter-spacing: 0.02em; }
.tag {
  display: inline-block;
  font: 400 0.6875rem/1.5 var(--mono);
  letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--ink-3);
  border: 1px solid var(--rule);
  padding: 0.28rem 0.55rem;
  /* These labels are full phrases, so they wrap at every width. `nowrap` made
     the text paint up to 320px outside its own 1px border and collide with
     the description column. */
  white-space: normal;
  max-width: 100%;
}
.tag--oxide { color: var(--accent); border-color: var(--accent-rule); background: var(--accent-wash); }

/* Inline editorial link: underline that sits below the baseline properly. */
.prose a, .lnk {
  color: var(--ink);
  text-decoration: underline;
  text-decoration-color: var(--accent-rule);
  text-decoration-thickness: 1px;
  text-underline-offset: 0.22em;
  transition: text-decoration-color 0.2s var(--ease), color 0.2s var(--ease);
}
.prose a:hover, .lnk:hover { color: var(--accent); text-decoration-color: var(--accent); }

/* ---------------------------------------------------------- 5. Rules & figs */
.rule { height: 1px; background: var(--rule); border: 0; margin: 0; }
.figrule {
  display: flex; align-items: baseline; gap: 1rem;
  font: 400 var(--fs-label)/1 var(--mono);
  letter-spacing: 0.15em; text-transform: uppercase; color: var(--ink-4);
  margin-bottom: 1.5rem;
}
.figrule::after { content: ''; flex: 1; height: 1px; background: var(--rule); transform: translateY(-0.2em); }
.figrule b { color: var(--accent); font-weight: 500; }

/* ------------------------------------------------------------- 6. Masthead */
.masthead {
  position: sticky; top: 0; z-index: 90;
  background: color-mix(in srgb, var(--bg-deep) 94%, transparent);
  backdrop-filter: saturate(1.4) blur(12px);
  -webkit-backdrop-filter: saturate(1.4) blur(12px);
  border-bottom: 1px solid transparent;
  transition: border-color 0.25s var(--ease), background 0.25s var(--ease);
}
.masthead[data-stuck='true'] { border-bottom-color: var(--rule); }
/* 5rem, not 4.5. The lockup is 52px tall, and at 72px that left five pixels
   top and bottom, which reads as the logo touching the edges of the bar. */
.masthead__in {
  display: flex; align-items: center; gap: 1.5rem;
  min-height: 5rem;
}
@media (max-width: 1100px) { .masthead__in { min-height: 4.25rem; } }
/* The lockup. The shield and the name are the company's own artwork, because
   the name is set in a slanted display face and a type reconstruction of it read
   as nearly-but-not-quite the logo. The line beneath it is live text, so it can
   be changed without re-cutting artwork and is read by search and screen readers
   as text rather than being locked inside a picture.

   The shield carries an empty alt and the name image carries the company name,
   so the pair announces itself once rather than twice. */
/* A two-row grid, not a row with a stacked column beside it. The descriptor
   runs the full width of the lockup and starts at the shield's left edge, so
   the mark sits above its own line rather than beside a block of text. */
.wordmark {
  display: grid;
  grid-template-columns: auto 1fr;
  column-gap: 0.65rem; row-gap: 0.45rem;
  margin-right: auto; text-decoration: none;
}
.wordmark__shield {
  grid-column: 1; grid-row: 1;
  display: block; height: 36px; width: auto;
  transition: opacity 0.2s var(--ease);
}
/* 31px against the shield's 36. At the earlier 30-against-44 the name read as
   the smaller of two objects rather than as the other half of one mark; at the
   same height it starts to outweigh the shield. Centred, not baseline-aligned:
   the shield ends in a point, so sitting the name on its lowest pixel dropped
   it below the shield's visual mass. */
.wordmark__word {
  grid-column: 2; grid-row: 1;
  display: block; height: 31px; width: auto;
  align-self: center;
  transition: opacity 0.2s var(--ease);
}
/* Larger than the mark it sits under would suggest, and lighter to compensate:
   the size makes it readable, the tone keeps it from competing with the name.
   0.85 of --ink-4 is as light as it goes and still clears 4.5:1 (4.84:1 on the
   dark footer ground); at 0.75 it falls to 4.05:1 and fails. Measure that ratio
   against the ground the text actually sits on, not against black — 0.75 over
   pure black comes out at exactly 4.20:1, which is how this comment first
   recorded a passing-looking number for a failing value. */
.wordmark__sub {
  grid-column: 1 / -1; grid-row: 2;
  font: 400 0.6875rem/1.3 var(--mono);
  letter-spacing: 0.04em; text-transform: uppercase;
  color: color-mix(in srgb, var(--ink-4) var(--wm-sub-a, 85%), transparent);
}
/* In the bar the descriptor has to hold one line — it sits beside the nav, and a
   second line would push the masthead taller on every page. */
.masthead .wordmark__sub { white-space: nowrap; }
/* In the footer the line has to fit a column that shares a 1128px shell with four
   link columns, and that column's width swings from 260px to 552px as the grid
   drops from five columns to one. At the bar's fixed 11px the line wants 342px, so
   it held one line at some widths and broke onto two at others.
   So it is sized FROM its column — but not by a smooth percentage. This is a
   monospace face, so the line's width is a STEP function of its size: its 46
   characters quantise together, jumping ~46px at each quarter-point. Measured
   painted widths at the two sizes used here: 246.56px at 9px, 294.41px at 10px
   (and 342.25px at the bar's 11px, which is what will not fit). A smooth cqw
   factor keeps landing just over a step boundary — 3.3cqw broke at 380px wide,
   3.1cqw at 764/768/1168/1172px. Tiers matched to the steps cannot: 10px from
   300px of column up, 9px below it.
   Worst case across the supported 320px+ range is 5.59px of slack, at vw=340
   where the cell is exactly 300px and takes the 10px tier. Below vw=287 the
   single-column cell is narrower than even the 9px line and it wraps — which is
   the intended degradation, not a failure, and it is below any width this site
   targets. Under an enlarged browser text preference it also wraps: the tiers are
   in rem but the threshold is in px, and no font-size that scales with the root
   can be guaranteed to fit a column that does not. Wrapping is the right answer
   there — the reader asked for bigger text.
   Hence deliberately no `nowrap` anywhere: when it does not fit it must wrap, not
   paint across the Services column, which is exactly what nowrap did here.
   The plain font-size is also the fallback where container queries are missing. */
.foot__grid > :first-child { container-type: inline-size; }
.foot .wordmark__sub { font-size: 0.5625rem; max-width: 100%; }
@container (min-width: 300px) { .foot .wordmark__sub { font-size: 0.625rem; } }
.wordmark:hover .wordmark__word, .wordmark:hover .wordmark__shield { opacity: 0.82; }

/* Below this the nav needs the room, so the descriptor leaves the BAR only — the
   footer has its own column and keeps it at every width. The bar comes down with
   it: an 80px bar holding only a 36px mark is 22px of empty chrome on a phone. */
@media (max-width: 1100px) {
  .masthead .wordmark__sub { display: none; }
  .wordmark__shield { height: 34px; }
  .wordmark__word { height: 29px; }
}
@media (max-width: 860px) {
  .wordmark__shield { height: 32px; }
  .wordmark__word { height: 27px; }
}

.nav { display: flex; align-items: center; gap: clamp(1rem, 2vw, 1.9rem); }
/* The bar shows labels. The descriptor and the panel tail belong to the mobile
   panel, where there is room for them. */
.nav__d, .nav__tail { display: none; }
.nav a {
  font: 400 0.875rem/1 var(--sans);
  text-decoration: none; color: var(--ink-2);
  padding: 0.4rem 0; position: relative;
  transition: color 0.2s var(--ease);
}
.nav a::after {
  content: ''; position: absolute; left: 0; right: 100%; bottom: 0;
  height: 1px; background: var(--accent);
  transition: right 0.28s var(--ease);
}
.nav a:hover, .nav a[aria-current='page'] { color: var(--ink); }
.nav a:hover::after, .nav a[aria-current='page']::after { right: 0; }

/* Holds the burger only, which is hidden above 960 — left in the flow it was an
   empty flex item still taking the bar's gap, pulling the nav 24px in from the
   gutter the lockup starts on. */
.mast-tools { display: none; align-items: center; gap: 0.75rem; }
@media (max-width: 960px) { .mast-tools { display: flex; } }
/* No theme switcher. The site is a single dark edition; the light palette
   below is kept but nothing sets data-theme any more, so it is unreachable
   until something does. */
.burger { display: none; width: 34px; height: 34px; background: none; border: 1px solid var(--rule); cursor: pointer; color: var(--ink); }
.burger span { display: block; width: 15px; height: 1px; background: currentColor; margin: 3px auto; transition: transform 0.25s var(--ease), opacity 0.2s; }
.burger[aria-expanded='true'] span:nth-child(1) { transform: translateY(4px) rotate(45deg); }
.burger[aria-expanded='true'] span:nth-child(2) { opacity: 0; }
.burger[aria-expanded='true'] span:nth-child(3) { transform: translateY(-4px) rotate(-45deg); }

/* The mobile panel. It used to be the bar's links stacked — a column of eight
   words with nothing to tell them apart and nothing to do at the end of it.
   Now each row is numbered and says what is behind it, the current section is
   marked, and the panel closes with the primary action and the two contact
   lines, so the menu is somewhere you can finish rather than only navigate. */
@media (max-width: 960px) {
  .burger { display: block; }
  .nav {
    position: fixed; inset: 4.5rem 0 auto 0;
    flex-direction: column; align-items: stretch; gap: 0;
    background: var(--bg-raised);
    border-block: 1px solid var(--rule);
    padding: 0.25rem var(--gut) 1.75rem;
    transform: translateY(-120%);
    transition: transform 0.35s var(--ease);
    max-height: calc(100dvh - 4.5rem); overflow-y: auto;
    counter-reset: nav;
  }
  .nav[data-open='true'] { transform: translateY(0); }

  .nav > a {
    display: grid;
    grid-template-columns: 2.4rem 1fr;
    align-items: baseline;
    row-gap: 0.2rem;
    padding: 0.95rem 0 0.9rem;
    border-bottom: 1px solid var(--rule-soft);
  }
  /* The number comes from a counter, so the markup stays a plain list of links.
     On the bar this pseudo-element is the invisible hit-area extension, declared
     further down the file — hence the `[data-open]`, which outranks it rather
     than relying on source order to win. */
  .masthead .nav[data-open] > a::before {
    content: counter(nav, decimal-leading-zero);
    counter-increment: nav;
    position: static; inset: auto;
    grid-column: 1; grid-row: 1;
    font: 400 0.75rem/1.6 var(--mono);
    letter-spacing: 0.08em; color: var(--ink-4);
  }
  .masthead .nav[data-open] > a::after { display: none; }
  .nav__l { grid-column: 2; grid-row: 1; font: 500 1.125rem/1.35 var(--sans); color: var(--ink); }
  .nav__d {
    display: block; grid-column: 2; grid-row: 2;
    font-size: 0.8125rem; line-height: 1.45; color: var(--ink-4);
  }
  /* Marks the section you are in — the bar's underline does not survive the
     switch to a stacked panel. */
  .masthead .nav[data-open] > a[aria-current='page']::before { color: var(--accent); }
  .nav > a[aria-current='page'] .nav__l { color: var(--accent-hi); }
  .nav > a[aria-current='page'] .nav__d { color: var(--ink-3); }

  .nav__tail { display: block; padding-top: 1.5rem; }
  .nav__tail .btn { display: flex; width: 100%; justify-content: center; }
  .nav__reach { margin: 1.25rem 0 0; display: grid; gap: 0.35rem; }
  .nav__reach a {
    padding: 0; border: 0; font-size: var(--fs-small); color: var(--ink-2);
    display: inline-block; justify-self: start;
  }
  /* 18px icons on a phone need a 44px target around them. The negative inline
     margin keeps the row visually where it was while the hit areas grow. */
  .nav__social {
    list-style: none; margin: 1rem -0.8125rem 0; padding: 0;
    display: flex; gap: 0.5rem;
  }
  .nav__social a {
    display: inline-flex; align-items: center; justify-content: center;
    min-width: 44px; min-height: 44px; padding: 0; border: 0; color: var(--ink-3);
  }
  .nav__social svg { width: 18px; height: 18px; display: block; }

  /* Rows arrive in order once the panel is open. Opacity only — no transform,
     so nothing shifts under a thumb already reaching for a row. */
  .nav[data-open='true'] > a { animation: navin 0.3s var(--ease) backwards; }
  .nav[data-open='true'] > a:nth-child(1) { animation-delay: 0.04s; }
  .nav[data-open='true'] > a:nth-child(2) { animation-delay: 0.08s; }
  .nav[data-open='true'] > a:nth-child(3) { animation-delay: 0.12s; }
  .nav[data-open='true'] > a:nth-child(4) { animation-delay: 0.16s; }
  .nav[data-open='true'] > a:nth-child(5) { animation-delay: 0.20s; }
  .nav[data-open='true'] > a:nth-child(6) { animation-delay: 0.24s; }
  .nav[data-open='true'] > a:nth-child(7) { animation-delay: 0.28s; }
  .nav[data-open='true'] > a:nth-child(8) { animation-delay: 0.32s; }
  .nav[data-open='true'] .nav__tail { animation: navin 0.3s var(--ease) 0.36s backwards; }
  @keyframes navin { from { opacity: 0; } to { opacity: 1; } }
  @media (prefers-reduced-motion: reduce) {
    .nav { transition: none; }
    .nav[data-open='true'] > a,
    .nav[data-open='true'] .nav__tail { animation: none; }
  }
}

/* ---------------------------------------------------------------- 7. Hero */
.hero { padding-block: clamp(3.5rem, 8vw, 7rem) clamp(3rem, 6vw, 5.5rem); }
.hero__head { grid-column: 1 / 8; }
.hero__card { grid-column: 8 / 13; align-self: end; }
@media (max-width: 900px) {
  .hero__head { grid-column: 1 / -1; }
  .hero__card { grid-column: 1 / -1; }
}
.hero .display { margin-bottom: 1.6rem; max-width: 15ch; }
.hero .lede { margin-bottom: 2.2rem; }

.actions { display: flex; flex-wrap: wrap; align-items: center; gap: 1.6rem; }
.btn {
  display: inline-flex; align-items: center; gap: 0.55rem;
  background: var(--accent); color: var(--on-accent);
  font: 600 0.9375rem/1 var(--sans);
  padding: 0.9rem 1.55rem; border-radius: var(--radius);
  text-decoration: none; border: 1px solid var(--accent);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.25);
  transition: background 0.2s var(--ease), border-color 0.2s var(--ease), transform 0.2s var(--ease);
}
.btn:hover { background: var(--accent-hi); border-color: var(--accent-hi); transform: translateY(-1px); }
.btn--ghost {
  background: transparent; color: var(--ink);
  border-color: var(--rule-strong); box-shadow: none;
}
.btn--ghost:hover { background: var(--accent-wash); border-color: var(--accent); color: var(--ink); }
.btn svg { width: 14px; height: 14px; transition: transform 0.22s var(--ease); }
.btn:hover svg { transform: translateX(3px); }

.arrowlink {
  display: inline-flex; align-items: center; gap: 0.5rem;
  font: 500 0.875rem/1 var(--mono); letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--ink); text-decoration: none;
  border-bottom: 1px solid var(--rule-strong);
  padding-bottom: 0.35rem;
  transition: color 0.2s var(--ease), border-color 0.2s var(--ease);
}
.arrowlink:hover { color: var(--accent); border-color: var(--accent); }
.arrowlink svg { width: 13px; height: 13px; transition: transform 0.22s var(--ease); }
.arrowlink:hover svg { transform: translateX(3px); }

/* Record card — the hero's "file card". Mono key/value rows, hairline box. */
.record {
  border: 1px solid var(--rule);
  background: var(--bg-raised);
  border-radius: var(--radius);
  overflow: hidden;
}
.record__hd {
  display: flex; justify-content: space-between; align-items: center; gap: 1rem;
  padding: 0.7rem 1rem;
  border-bottom: 1px solid var(--rule);
  font: 500 var(--fs-label)/1 var(--mono);
  letter-spacing: 0.15em; text-transform: uppercase; color: var(--ink-3);
}
.record__live { display: inline-flex; align-items: center; gap: 0.45rem; color: var(--accent); }
.record__live::before {
  content: ''; width: 6px; height: 6px; border-radius: 50%;
  background: var(--accent); animation: beat 2.6s ease-in-out infinite;
}
@keyframes beat { 0%, 100% { opacity: 1; } 50% { opacity: 0.25; } }
/* Dark is the default edition, so the base rule must match it. The multiply
   blend belongs to the light edition only — applied by default it crushed the
   portrait to a near-black smear. */
.record__portrait {
  border-bottom: 1px solid var(--rule);
  filter: grayscale(1) contrast(0.95) brightness(0.92);
  mix-blend-mode: normal;
  width: 100%; aspect-ratio: 4 / 3; object-fit: cover; object-position: center 22%;
}
:root[data-theme='light'] .record__portrait {
  filter: grayscale(1) contrast(1.06);
  mix-blend-mode: multiply;
}

/* Two founders, two identical cards. This section used to be one person's
   profile in a 7/5 split with a 4:3 portrait — a shape with nowhere to put a
   second name in it. Square portraits so the pair reads with the roster below.
   The card is a flex column with the body growing, so the fact rows and the
   links sit on the same line across both cards even though one founder has
   more to say than the other. */
.founders {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(1.5rem, 3vw, 2.25rem);
  list-style: none; padding: 0; margin: 0;
}
@media (max-width: 800px) { .founders { grid-template-columns: minmax(0, 1fr); } }
.founder { display: flex; flex-direction: column; }
/* Square, matching the source asset, so the card crops nothing. 4:3 was tried
   to keep the card shorter, but it threw away the top and bottom quarter of a
   portrait that is already framed tight, and the result read as over-zoomed
   beside the other founder. The extra card height is worth the honest framing. */
.founder__shot { flex: none; aspect-ratio: 1; }
.founder__body { flex: 1; padding: 1.25rem 1rem 1.35rem; border-bottom: 1px solid var(--rule); }
.founder__n {
  font: 600 1.375rem/1.2 var(--sans); letter-spacing: -0.015em;
  color: var(--ink); margin: 0 0 0.35rem;
}
.founder__r {
  font: 400 0.75rem/1.4 var(--mono); letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--accent-hi); margin: 0 0 0.9rem;
}
.founder__bio { font-size: var(--fs-small); color: var(--ink-2); line-height: 1.65; margin: 0; }
.founder__acts { display: flex; flex-wrap: wrap; gap: 1.25rem; padding: 1rem; }

/* --------------------------------------------------------- 8. Stat ledger */
/* Three to a row, not six sparse rows. The row form put the source hard right
   of a `1fr` middle column, so at 1440px a third of the band was empty and the
   block read as a table someone had not finished filling in. Grouping three to
   a row lets the figures run larger, fills the measure, and puts each source
   directly beneath the claim it supports instead of a hand-span away. */
.ledger {
  display: grid;
  /* Two up even on a phone. Stacked one to a row the block ran 1373px for six
     figures, which is a lot of thumb for very little information. */
  grid-template-columns: repeat(2, minmax(0, 1fr));
  column-gap: clamp(1.1rem, 3.5vw, 3.25rem);
}
@media (min-width: 980px) { .ledger { grid-template-columns: repeat(3, minmax(0, 1fr)); } }

.ledger__row {
  display: flex;
  flex-direction: column;
  gap: 0.55rem;
  padding: 1.5rem 0 1.75rem;
  border-top: 1px solid var(--rule-strong);
}
.ledger__fig {
  /* The figure must not break across lines ("Top 100", "$200K+"), so it is
     sized off the viewport rather than fixed: at 390px a two-column cell is
     about 160px, and 7.6vw keeps the longest value inside it. */
  white-space: nowrap;
  font-family: var(--serif);
  font-size: clamp(1.75rem, 7.6vw, 3.3rem);
  line-height: 0.88; letter-spacing: -0.035em;
  color: var(--ink);
  font-variant-numeric: lining-nums tabular-nums;
}
.ledger__label {
  font-size: var(--fs-small); color: var(--ink-2);
  text-wrap: pretty; max-width: 24ch;
}
/* `margin-top: auto` against the grid's default stretch puts every source on
   the same baseline across a row, so the hairlines line up and the evidence
   reads as a column rather than as a caption that ran on. */
.ledger__src {
  font: 400 0.6875rem/1.6 var(--mono);
  letter-spacing: 0.06em; color: var(--ink-4);
  margin-top: auto; padding-top: 0.95rem;
  border-top: 1px solid var(--rule-soft);
  text-wrap: pretty;
}
.ledger__src a { color: var(--ink-3); text-decoration-color: var(--rule); }
.ledger__src a:hover { color: var(--ink); text-decoration-color: currentColor; }

/* ------------------------------------------------------ 9. Entity roll-up */
.roll {
  /* 10rem, not 11rem: at 390px the available 350px fits two 160px columns but
     not two 176px ones, so the list collapsed to a single 37-row column. */
  columns: 4 10rem; column-gap: clamp(1.25rem, 3vw, 3rem);
  font: 400 var(--fs-small)/2.15 var(--sans);
  color: var(--ink-2);
}
.roll li {
  list-style: none;
  break-inside: avoid;
  border-bottom: 1px solid var(--rule-soft);
  padding-right: 0.5rem;
  transition: color 0.18s var(--ease);
}
.roll li:hover { color: var(--ink); }
.roll { padding: 0; margin: 0; }

/* -------------------------------------------------------- 10. Service rows */
.svc { border-top: 1px solid var(--rule-strong); }
.svc__row {
  display: grid;
  grid-template-columns: 4rem minmax(0, 1fr) minmax(0, 1.15fr) 2.5rem;
  gap: clamp(1rem, 2.5vw, 2.5rem);
  align-items: start;
  padding: clamp(1.5rem, 2.6vw, 2.2rem) 0;
  border-bottom: 1px solid var(--rule);
  text-decoration: none;
  position: relative;
  transition: color 0.25s var(--ease);
}
.svc__row::before {
  content: ''; position: absolute; left: calc(var(--gut) * -1); right: calc(var(--gut) * -1);
  top: 0; bottom: -1px; background: var(--accent-wash);
  opacity: 0; transition: opacity 0.25s var(--ease);
  /* z-index:-1 pushed this behind the section background, so the hover state
     was computed but never visible. */
  z-index: 0; pointer-events: none;
}
.svc__row > * { position: relative; z-index: 1; }
.svc__row:hover::before { opacity: 1; }
.svc__no {
  font: 400 0.8125rem/1.9 var(--mono); color: var(--ink-4); letter-spacing: 0.08em;
}
.svc__row:hover .svc__no { color: var(--accent); }
.svc__title { font-family: var(--serif); font-size: var(--fs-h3); color: var(--ink); margin-bottom: 0.55rem; }
.svc__meta { display: flex; flex-wrap: wrap; gap: 0.35rem; }
.svc__desc { font-size: var(--fs-small); color: var(--ink-2); }
.svc__go { justify-self: end; align-self: center; color: var(--ink-4); display: grid; place-items: center;
  transition: transform 0.25s var(--ease), color 0.25s var(--ease); }
.svc__go svg { width: 18px; height: 18px; }
.svc__row:hover .svc__go { color: var(--accent); transform: translateX(4px); }
@media (max-width: 860px) {
  .svc__row { grid-template-columns: 2.6rem minmax(0, 1fr); }
  .svc__desc { grid-column: 2; }
  .svc__go { display: none; }
}

/* -------------------------------------------------------- 11. Phase ladder */
.phases { counter-reset: ph; border-top: 1px solid var(--rule-strong); }
.phase {
  display: grid;
  grid-template-columns: 4rem minmax(0, 16rem) minmax(0, 1fr);
  gap: clamp(1rem, 3vw, 2.5rem);
  padding: clamp(1.5rem, 2.6vw, 2.1rem) 0;
  border-bottom: 1px solid var(--rule);
  align-items: start;
}
.phase__no {
  counter-increment: ph;
  font-family: var(--serif); font-size: 1.6rem; line-height: 1;
  color: var(--accent); font-variation-settings: 'opsz' 144, 'WONK' 1;
}
.phase__no::before { content: '0' counter(ph); }
.phase__t { font-family: var(--serif); font-size: var(--fs-h3); }
.phase__d { font-size: var(--fs-small); color: var(--ink-2); max-width: 68ch; }
.phase__d ul { margin: 0.7rem 0 0; padding-left: 1rem; }
@media (max-width: 860px) { .phase { grid-template-columns: 2.6rem minmax(0, 1fr); } .phase__d { grid-column: 2; } }

/* ------------------------------------------------------- 12. Deliverables */
.deliv {
  display: grid;
  /* Six items: three columns fill exactly, four leave two dead cells. */
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 0; border: 1px solid var(--rule); border-radius: var(--radius); overflow: hidden;
}
@media (min-width: 1100px) { .deliv { grid-template-columns: repeat(3, 1fr); } }
.deliv > div {
  padding: clamp(1.3rem, 2.2vw, 1.9rem);
  /* Negative-offset hairlines so adjacent cells share one 1px rule instead of
     stacking two. */
  box-shadow: 1px 0 0 var(--rule), 0 1px 0 var(--rule);
}
.deliv h3 { font-size: 1.0625rem; margin-bottom: 0.5rem; }
.deliv p { font-size: var(--fs-small); color: var(--ink-2); margin: 0; }
.deliv__no { font: 400 0.6875rem/1 var(--mono); color: var(--accent); letter-spacing: 0.1em; display: block; margin-bottom: 0.9rem; }

/* ------------------------------------------------------------ 13. KeyFacts
   Dense, extractable fact block. Answer engines lift these verbatim. */
.keyfacts {
  border: 1px solid var(--rule);
  border-left: 3px solid var(--accent);
  border-radius: var(--radius);
  background: var(--bg-raised);
  padding: clamp(1.3rem, 2.4vw, 1.9rem);
  margin: 2.5rem 0;
}
.keyfacts__t {
  font: 500 var(--fs-label)/1 var(--mono);
  letter-spacing: 0.15em; text-transform: uppercase; color: var(--accent);
  margin: 0 0 1rem;
}
.keyfacts dl { display: grid; grid-template-columns: minmax(8rem, 13rem) 1fr; gap: 0.55rem 1.4rem; margin: 0; font-size: var(--fs-small); }
.keyfacts dt { font: 400 0.6875rem/1.75 var(--mono); letter-spacing: 0.08em; text-transform: uppercase; color: var(--ink-4); }
.keyfacts dd { margin: 0; color: var(--ink); }
@media (max-width: 620px) { .keyfacts dl { grid-template-columns: 1fr; gap: 0.15rem 0; } .keyfacts dd { margin-bottom: 0.7rem; } }

/* ------------------------------------------------------------- 14. Tables */
.table { width: 100%; border-collapse: collapse; font-size: var(--fs-small); }
.table caption { text-align: left; font: 400 var(--fs-label)/1 var(--mono); letter-spacing: 0.15em; text-transform: uppercase; color: var(--ink-4); padding-bottom: 0.9rem; }
.table th, .table td { text-align: left; padding: 0.85rem 1rem 0.85rem 0; border-bottom: 1px solid var(--rule); vertical-align: top; }
.table thead th {
  font: 500 0.6875rem/1.6 var(--mono); letter-spacing: 0.11em; text-transform: uppercase;
  color: var(--ink-4); border-bottom-color: var(--rule-strong);
}
.table tbody tr:hover { background: var(--accent-wash); }
.table td:first-child { color: var(--ink); font-weight: 500; }
.tablescroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }

/* ---------------------------------------------------------------- 15. FAQ */
.faq { border-top: 1px solid var(--rule-strong); }
.faq details { border-bottom: 1px solid var(--rule); }
.faq summary {
  cursor: pointer; list-style: none;
  display: grid; grid-template-columns: 1fr 1.5rem; gap: 1.5rem; align-items: center;
  padding: 1.35rem 0;
  font-family: var(--serif); font-size: clamp(1.05rem, 1.5vw, 1.28rem);
  color: var(--ink);
  transition: color 0.2s var(--ease);
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary:hover { color: var(--accent); }
.faq summary::after {
  content: ''; width: 11px; height: 11px; justify-self: end;
  border-right: 1px solid currentColor; border-bottom: 1px solid currentColor;
  transform: rotate(45deg) translate(-2px, -2px);
  transition: transform 0.28s var(--ease);
}
.faq details[open] summary { color: var(--accent); }
.faq details[open] summary::after { transform: rotate(-135deg) translate(-3px, -3px); }
.faq__a { padding: 0 0 1.5rem; max-width: var(--measure); color: var(--ink-2); font-size: var(--fs-small); }
.faq__a > :last-child { margin-bottom: 0; }

/* --------------------------------------------------------- 16. Breadcrumb */
.crumbs {
  font: 400 0.75rem/1 var(--mono); letter-spacing: 0.06em;
  color: var(--ink-4); display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: center;
  padding-block: 1.5rem 0;
}
.crumbs a { color: var(--ink-3); text-decoration: none; border-bottom: 1px solid var(--rule); }
.crumbs a:hover { color: var(--accent); border-color: var(--accent); }
.crumbs span[aria-hidden] { color: var(--ink-4); }

/* ------------------------------------------------------------ 17. Page head */
.pagehead { padding-block: clamp(2.5rem, 5vw, 4rem) clamp(2rem, 4vw, 3.2rem); }
/* 1/8 so it stops exactly where the aside starts. At 1/9 the two tracks
   overlapped on column 8 and the grid put the card on a row of its own,
   stacking the header instead of setting it in two columns. */
.pagehead__t { grid-column: 1 / 8; }
/* Top-aligned, not bottom. The card used to be the taller of the two
   columns so the alignment never showed; once it lost a hundred pixels it
   dropped to the floor of the row and opened a void above itself while
   leaving another under the text. Aligned to the top it sits beside the
   heading, which is what it is there to answer. */
.pagehead__aside { grid-column: 8 / 13; align-self: start; }
@media (max-width: 900px) { .pagehead__t, .pagehead__aside { grid-column: 1 / -1; } }
.pagehead .display { max-width: 18ch; margin-bottom: 1.4rem; }

/* ---------------------------------------------------------------- 18. TOC */
.doc { display: grid; grid-template-columns: minmax(0, 15rem) minmax(0, 1fr); gap: clamp(2rem, 5vw, 4.5rem); }
@media (max-width: 960px) { .doc { grid-template-columns: minmax(0, 1fr); } .toc { display: none; } }
.toc { position: sticky; top: 6.5rem; align-self: start; }
.toc__t { font: 500 var(--fs-label)/1 var(--mono); letter-spacing: 0.15em; text-transform: uppercase; color: var(--ink-4); padding-bottom: 0.9rem; border-bottom: 1px solid var(--rule); margin-bottom: 0.9rem; }
.toc ol { list-style: none; margin: 0; padding: 0; counter-reset: toc; }
.toc li { counter-increment: toc; }
.toc a {
  display: grid; grid-template-columns: 1.9rem 1fr; gap: 0.3rem;
  padding: 0.42rem 0; text-decoration: none;
  font-size: 0.875rem; color: var(--ink-3);
  transition: color 0.2s var(--ease);
}
.toc a::before { content: '0' counter(toc); font: 400 0.6875rem/1.9 var(--mono); color: var(--ink-4); }
.toc a:hover, .toc a[data-active='true'] { color: var(--ink); }
.toc a[data-active='true']::before { color: var(--accent); }

/* --------------------------------------------------------- 19. Cards/grids */
.cardgrid { display: grid; grid-template-columns: repeat(auto-fit, minmax(clamp(240px, 30%, 330px), 1fr)); gap: 1rem; }
.card {
  padding: clamp(1.4rem, 2.2vw, 1.9rem);
  background: var(--bg-raised);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  text-decoration: none; display: block;
  transition: border-color 0.22s var(--ease), transform 0.22s var(--ease);
}
.card:hover { border-color: var(--accent); transform: translateY(-2px); }
.card h3 { margin-bottom: 0.6rem; }
.card p { font-size: var(--fs-small); color: var(--ink-2); margin: 0; }
.card__k { font: 400 0.6875rem/1 var(--mono); letter-spacing: 0.12em; text-transform: uppercase; color: var(--accent); display: block; margin-bottom: 0.9rem; }


/* ------------------------------------------------------------- 20. Quotes */
.pull {
  font-family: var(--serif); font-size: clamp(1.35rem, 2.4vw, 1.95rem);
  line-height: 1.24; letter-spacing: -0.018em; color: var(--ink);
  border-left: 2px solid var(--accent); padding-left: clamp(1.2rem, 2.5vw, 2rem);
  margin: 2.5rem 0; max-width: 34ch;
}
.pull cite { display: block; margin-top: 1rem; font: 400 0.75rem/1.6 var(--mono); font-style: normal; letter-spacing: 0.08em; text-transform: uppercase; color: var(--ink-4); }

/* --------------------------------------------------------- 21. Press/cites */
.cites { list-style: none; margin: 0; padding: 0; border-top: 1px solid var(--rule-strong); }
.cites li { border-bottom: 1px solid var(--rule); }
.cites a {
  display: grid; grid-template-columns: minmax(0, 11rem) minmax(0, 1fr) 6rem;
  gap: clamp(1rem, 3vw, 2.5rem); align-items: baseline;
  padding: 1.15rem 0; text-decoration: none;
  transition: color 0.2s var(--ease);
}
.cites__src { font: 500 0.75rem/1.7 var(--mono); letter-spacing: 0.09em; text-transform: uppercase; color: var(--accent); }
.cites__t { color: var(--ink); font-size: var(--fs-small); max-width: 74ch; }
.cites a:hover .cites__t { color: var(--accent); }
.cites__y { font: 400 0.75rem/1.7 var(--mono); color: var(--ink-4); text-align: right; }
/* Two columns where there is no trailing date, or the row keeps a 6rem gutter
   reserved for a column that is not rendered. */
.cites--nodate a { grid-template-columns: minmax(0, 11rem) minmax(0, 1fr); }
@media (max-width: 720px) {
  .cites a { grid-template-columns: minmax(0, 1fr); gap: 0.3rem; }
  .cites__y { text-align: left; }
}

/* ------------------------------------------------------------ 22. CTA band */
.cta {
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: clamp(2rem, 4.5vw, 3.6rem);
  display: grid; grid-template-columns: minmax(0, 1fr) auto;
  gap: clamp(1.5rem, 4vw, 3rem); align-items: center;
  background: var(--bg-raised);
  position: relative; overflow: hidden;
}
.cta::after {
  content: ''; position: absolute; right: -60px; top: -60px;
  width: 220px; height: 220px; border: 1px solid var(--accent-rule);
  border-radius: 50%; pointer-events: none;
}
.cta h2 { font-size: clamp(1.6rem, 3vw, 2.4rem); max-width: 18ch; margin-bottom: 0.8rem; }
.cta p { color: var(--ink-2); max-width: 48ch; margin: 0; font-size: var(--fs-small); }
@media (max-width: 800px) { .cta { grid-template-columns: minmax(0, 1fr); } }

/* ------------------------------------------------------------- 23. Footer */
.foot { background: var(--bg-deep); border-top: 1px solid var(--rule-soft); padding-block: clamp(3rem, 6vw, 4.5rem) 2rem; }
/* The brand column is sized by the descriptor under the mark, which has to hold
   one line, so it takes whatever the other four do not need. Columns 2-4 are
   bound by their widest label before it wraps — measured in place at 151/154/136px
   — and carry those as explicit minima. Column 5 is different: its widest label
   is only 103px, and what actually sets its floor is `columns: 112px 2` below,
   which needs 2 x 112 + 16 = 240px to keep two tracks. That leaves the brand
   column about 327px at the shared 1128px shell with the gap at 1.75rem. The fr
   ratios track what each column needs rather than splitting evenly. */
.foot__grid {
  display: grid;
  grid-template-columns:
    minmax(0, 2.3fr)
    minmax(153px, 1.15fr) minmax(156px, 1.18fr) minmax(138px, 1.05fr) minmax(240px, 1.95fr);
  gap: clamp(1.5rem, 2.4vw, 1.75rem);
}
/* The country list is long enough to tower over the other columns. Splitting it
   in two keeps the footer's baseline even. `columns: 112px 2` is self-limiting:
   two tracks where there is room for two, one where there is not, so the
   narrower breakpoints need no override. The 112px also sets this column's
   minimum track above, since it is wider than any label in the list. */
.foot ul.cols2 { columns: 112px 2; column-gap: 1rem; }
.foot ul.cols2 li { break-inside: avoid; }
/* Five columns need room; below that the link labels start wrapping to two lines,
   which reads worse than fewer, wider columns. Each step down also hands the brand
   column back some width, and the descriptor under the mark needs 247px of it. The
   two-column step stays at 860 because the band above it re-proportions instead
   (see below); the one-column step moved 520 -> 600, because at 521 two columns
   left the brand column 228px and the descriptor broke onto a second line. */
@media (max-width: 1180px) {
  /* Three equal thirds left the brand column at 248px at the bottom of this band,
     1.5px clear of the 9px line — too close to a font-metric shift to trust. The
     brand column takes a larger share instead; the other two still clear their
     widest label (154px) and the two-up country list (240px) at the band's
     narrowest, which is what lets the five-column step stay at 860. */
  .foot__grid { grid-template-columns: minmax(0, 1.3fr) repeat(2, minmax(156px, 1fr)); }
}
@media (max-width: 860px) { .foot__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 600px) { .foot__grid { grid-template-columns: minmax(0, 1fr); } }
.foot h4 { font: 500 var(--fs-label)/1 var(--mono); letter-spacing: 0.15em; text-transform: uppercase; color: var(--ink-4); margin-bottom: 1.1rem; }
.foot ul { list-style: none; margin: 0; padding: 0; }
.foot li { margin-bottom: 0.55rem; }
.foot a { font-size: var(--fs-small); color: var(--ink-2); text-decoration: none;
  transition: color 0.18s var(--ease); }
.foot a:hover { color: var(--accent); }
/* Each office is a block titled by its own entity name — no "Head office" /
   "US office" label above it. That puts the whole job of opening the block on the
   name, so it is set large, at the brightest ink, and carries a short accent rule
   where the label used to be. The rule is what separates the two offices and what
   makes the names read as titles rather than as the first line of the address. */
/* Both accent rules want the same clear space above them, and they do not get it
   from equal margins: the descriptor above rule 1 sets a 13px line box over a 7px
   glyph, the country line above rule 2 a 22.5px box over an 11px one, so the same
   margin leaves ~11px more air above rule 2. The lockup's margin carries that
   difference so the two blocks open identically. */
.foot .wordmark { margin-bottom: 1.9375rem; }
.office { font-style: normal; display: grid; gap: 0; max-width: 30ch; }
.office + .office { margin-top: 1.75rem; }
.office__name {
  font: 600 1.3125rem/1.25 var(--sans); color: var(--ink);
  letter-spacing: -0.015em; margin-bottom: 0.6rem;
}
.office__name::before {
  content: ''; display: block;
  width: 26px; height: 2px; background: var(--accent);
  margin-bottom: 0.85rem;
}
/* Tighter than the footer's link lists: these lines are one address, so they
   should read as a block rather than as four separate items. */
.office__line { font-size: var(--fs-small); color: var(--ink-3); line-height: 1.5; }
/* The country closes the block, so it steps back a tone from the lines above it
   — the same quiet weight the footer's column headings carry. */
.office__line:last-child { color: var(--ink-4); }
/* Sits below the two offices as a plain pair of lines — it needs no title of its
   own, and the gap above it matches the gap between the offices. */
.foot__reach { margin-top: 1.75rem; display: grid; gap: 0; }
.foot__reach a { font-size: var(--fs-small); line-height: 1.5; }
.foot__legal {
  margin-top: clamp(2.5rem, 5vw, 3.5rem); padding-top: 1.5rem; border-top: 1px solid var(--rule);
  display: flex; flex-wrap: wrap; gap: 0.75rem 2rem; justify-content: space-between;
  font: 400 0.75rem/1.7 var(--mono); color: var(--ink-4); letter-spacing: 0.04em;
}
/* Fine print, set in the body face rather than the mono of the line above it:
   this is prose to be read once, not a row of labels to be scanned. Measure is
   capped because a legal paragraph running the full 1128px shell is unreadable. */
.foot__fine {
  margin-top: 1.25rem;
  font: 400 0.75rem/1.75 var(--sans); color: var(--ink-4);
  max-width: 100ch;
}
.foot__fine p { margin: 0; }
.foot__fine p + p { margin-top: 0.5rem; }

/* ------------------------------------------------------------- 24. Reveal
   Scroll-driven and CSS-only. Content is visible by default and only ever
   animated where the browser supports view timelines — so a failed script,
   a disabled script or a crawler that does not run JavaScript still sees
   every word. Never hide content behind JavaScript. */
.rv { opacity: 1; }
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .rv {
      animation: rv-in linear both;
      animation-timeline: view();
      animation-range: entry 0% entry 50%;
    }
    /* Transform only — never opacity. Text below the fold must still be
       painted for anything that renders the page without scrolling it. */
    @keyframes rv-in {
      from { transform: translateY(12px); }
      to   { transform: none; }
    }
  }
}

/* --------------------------------------------------------------- 25. Print */
@media print {
  /* Reset the palette: printing the dark edition put 1.0:1 text on paper. */
  :root, :root[data-theme='dark'], :root[data-theme='light'] {
    --bg: #fff; --bg-sunk: #fff; --bg-raised: #fff; --bg-deep: #fff;
    --ink: #000; --ink-2: #222; --ink-3: #444; --ink-4: #555;
    --accent: #06c; --accent-hi: #06c; --accent-wash: transparent;
    --rule: #bbb; --rule-soft: #ddd; --rule-strong: #888;
  }
  .masthead, .cta, .themetoggle, .burger, .toc, .skip { display: none !important; }
  body { background: #fff; color: #000; font-size: 11pt; }
  .band--sunk, .card, .record, .keyfacts, .badge { background: #fff !important; }
  body::before { display: none; }
  .band { padding-block: 1.5rem; break-inside: avoid; }
  a[href^='http']::after { content: ' (' attr(href) ')'; font-size: 8pt; color: #555; }
  .display { font-size: 28pt; }
}

/* ------------------------------------------------------------ 26. Form
   Ruled rather than boxed, so it reads as part of the document. */
.form { display: grid; gap: 1.4rem; max-width: 46rem; }
.form__row { display: grid; grid-template-columns: 1fr 1fr; gap: 1.4rem; }
@media (max-width: 640px) { .form__row { grid-template-columns: minmax(0, 1fr); } }
.field label {
  display: block;
  font: 400 var(--fs-label)/1.8 var(--mono);
  letter-spacing: 0.11em; text-transform: uppercase; color: var(--ink-3);
  margin-bottom: 0.4rem;
}
.field label .req { color: var(--accent); }
.field input, .field textarea, .field select {
  width: 100%;
  background: transparent;
  color: var(--ink);
  border: 0; border-bottom: 1px solid var(--rule-strong);
  padding: 0.6rem 0;
  font: 400 1.0625rem/1.5 var(--sans);
  border-radius: 0;
  transition: border-color 0.2s var(--ease);
}
.field select {
  appearance: none;
  background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
                    linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position: right 0.75rem center, right 0.4rem center;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
  cursor: pointer;
}
.field textarea { resize: vertical; min-height: 7.5rem; }
.field input:focus, .field textarea:focus, .field select:focus {
  outline: none; border-bottom-color: var(--accent); border-bottom-width: 2px;
  margin-bottom: -1px;
}
.field input::placeholder, .field textarea::placeholder { color: var(--ink-4); }
.field[data-error='true'] input,
.field[data-error='true'] textarea { border-bottom-color: var(--accent); }
.field__err {
  display: none;
  font: 400 0.8125rem/1.5 var(--mono); color: var(--accent); margin-top: 0.35rem;
}
.field[data-error='true'] .field__err { display: block; }
/* Honeypot: present in the DOM, unreachable for a person. */
.hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }
.form__note { font-size: var(--fs-small); color: var(--ink-3); max-width: 46rem; }
.form__status {
  display: none; padding: 1rem 1.2rem;
  border-left: 2px solid var(--accent); background: var(--accent-wash);
  font-size: var(--fs-small); color: var(--ink);
}
.form__status[data-show='true'] { display: block; }
.form__status[data-kind='ok'] { border-left-color: var(--accent); background: var(--accent-wash); }

/* ------------------------------------------------------------ 27. Figures
   Article imagery sits full-measure with a hairline frame, so a screenshot
   reads as evidence rather than as decoration. */
.figure { margin: 2rem 0; }
.figure img {
  width: 100%; height: auto;
  border: 1px solid var(--rule);
  background: var(--bg-raised);
}
.figure figcaption {
  margin-top: 0.7rem;
  font: 400 0.8125rem/1.6 var(--mono);
  letter-spacing: 0.02em;
  color: var(--ink-4);
}
.figure figcaption::before { content: '↑ '; color: var(--accent); }

/* ------------------------------------------------ 28. Badges & logo wall */
.badges {
  list-style: none; margin: 0; padding: 0;
  display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  gap: 1rem;
}
.badge {
  display: flex; flex-direction: column; align-items: center; text-align: center;
  gap: 0.6rem; padding: 1.1rem 0.7rem;
  background: var(--bg-raised);
  border: 1px solid var(--rule); border-radius: var(--radius);
  transition: border-color 0.2s var(--ease);
}
/* Informational, not clickable — so it must not borrow the card's lift. */
.badge:hover { border-color: var(--accent-rule); }
.badge img { width: 72px; height: 72px; object-fit: contain; }
.badge__n {
  font: 600 0.75rem/1.35 var(--sans); color: var(--ink);
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden;
}
.badge__i { font: 400 0.625rem/1 var(--mono); letter-spacing: 0.09em; text-transform: uppercase; color: var(--ink-4); }

/* Hero variant. The credentials page carries the full grid with names and
   issuers; this is a trust cue, not a catalogue, so it stays readable at a
   glance instead of asking to be read.

   The marks are recognised without their names, and at this width a name
   clamps to an ellipsis anyway. The full name stays in each image's alt text,
   so nothing is lost to a screen reader. */
.herocreds {
  background: var(--bg-raised);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: clamp(1.1rem, 1.8vw, 1.5rem);
}
.herocreds__hd {
  margin: 0 0 1rem;
  font: 400 var(--fs-label)/1 var(--mono);
  letter-spacing: 0.15em; text-transform: uppercase; color: var(--ink-4);
}
.badges--hero {
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 0.5rem;
  /* The rule belongs to the grid's bottom edge so it spans the card, rather
     than to the link, where it stopped at the text and read as a stray dash. */
  padding-bottom: 1.1rem; margin-bottom: 1rem;
  border-bottom: 1px solid var(--rule);
}
.badges--hero .badge { padding: 0.55rem 0.3rem; gap: 0; }
.badges--hero .badge img { width: 48px; height: 48px; }
.badges--hero .badge__n, .badges--hero .badge__i { display: none; }

.logowall {
  list-style: none; margin: 0; padding: 2rem 0;
  display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 155px), 1fr));
  align-items: center; justify-items: center;
  gap: clamp(1.6rem, 2.6vw, 2.2rem) 1.25rem;
  border-block: 1px solid var(--rule-soft);
}
.logowall img {
  width: auto; max-width: 100%; object-fit: contain;
  /* Vendor marks sit on navy; normalise them to a single tone so the wall
     reads as one row rather than seven competing brand colours. */
  filter: brightness(0) invert(1); opacity: 0.75;
  transition: opacity 0.2s var(--ease);
}
.logowall li:hover img { opacity: 1; }
/* Wordmarks for the acknowledgements we hold no artwork for. Set at the same
   tone and opacity the images are flattened to, so the wall reads as one row
   rather than as logos plus leftovers. */
.logowall__wm {
  font: 600 1rem/1.3 var(--sans); letter-spacing: -0.012em;
  color: var(--ink); opacity: 0.75; text-align: center;
  transition: opacity 0.2s var(--ease);
}
.logowall li:hover .logowall__wm { opacity: 1; }
:root[data-theme='light'] .logowall__wm { color: var(--ink); opacity: 0.68; }
/* Several of these marks are white artwork. `grayscale()` leaves them white,
   so on the light edition they disappeared entirely — they have to be driven
   to black instead. */
:root[data-theme='light'] .logowall img { filter: brightness(0); opacity: 0.62; }
@media (max-width: 560px) {
  .logowall { grid-template-columns: repeat(3, 1fr); gap: 1.5rem 1rem; }
  .logowall img { transform: scale(0.82); }
  /* Three columns at this width leave about 105px a cell, so the longer
     wordmarks wrap and their rows grow taller than the ones beside them. */
  .logowall__wm { font-size: 0.875rem; }
}

.moreroll { margin-top: 2rem; }
.moreroll summary {
  cursor: pointer; list-style: none;
  font: 500 0.8125rem/1 var(--mono); letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--accent); padding: 0.6rem 0;
  display: inline-flex; align-items: center; gap: 0.5rem;
}
.moreroll summary::-webkit-details-marker { display: none; }
.moreroll summary::after {
  content: ''; width: 8px; height: 8px;
  border-right: 1px solid currentColor; border-bottom: 1px solid currentColor;
  transform: rotate(45deg) translate(-2px, -2px); transition: transform 0.25s var(--ease);
}
.moreroll[open] summary::after { transform: rotate(-135deg) translate(-2px, -2px); }


/* ---------------------------------------- 29. Always-dark shells
   Masthead and footer sit on --bg-deep in both editions. Without this the
   light edition's dark ink would be painted onto navy and fail contrast. */
:root[data-theme='light'] .masthead,
:root[data-theme='light'] .foot {
  --bg-raised: #052445;   /* the mobile nav panel lives on this token */
  --bg:        #021a36;
  --ink:   #ffffff;
  --ink-2: #c3d4e6;
  --ink-3: #8fa6bf;
  --ink-4: #7e97b3;
  --accent: #38bdf8;
  --accent-hi: #7dd3fc;
  --rule: rgba(255, 255, 255, 0.13);
  --rule-soft: rgba(255, 255, 255, 0.07);
  --rule-strong: rgba(255, 255, 255, 0.30);
  color: var(--ink);
}
:root[data-theme='light'] .masthead { background: color-mix(in srgb, var(--bg-deep) 94%, transparent); }

/* Light-edition chrome keeps the dark palette but on a lighter ground (#112742
   in the bar, #021a36 in the footer, against #010f24 dark), so the descriptor's
   85% tone lands at 4.02:1 and 4.47:1 there — both under the floor. 95% brings
   them back to 4.58:1 and 5.36:1. Both regions, not just the bar. */
:root[data-theme='light'] .masthead .wordmark__sub,
:root[data-theme='light'] .foot .wordmark__sub { --wm-sub-a: 95%; }

/* ------------------------------------------------ 30. States the QA found
   A disabled control has to look disabled; the enquiry form previously went
   inert after a successful send while still rendering as fully interactive. */
.form[data-sent='true'] { opacity: 0.55; }
.field input:disabled, .field textarea:disabled, .field select:disabled {
  color: var(--ink-4); border-bottom-style: dashed; cursor: not-allowed;
}
.btn:disabled, button:disabled { opacity: 0.5; cursor: not-allowed; transform: none; }

/* Pressed feedback — nothing on the site acknowledged a click. */
.btn:active, .arrowlink:active, .themetoggle:active, .burger:active { transform: translateY(1px); }
.card:active, .svc__row:active { transform: none; }

/* The burger had no hover or active affordance at all. */
.burger:hover { border-color: var(--accent); color: var(--accent); }

/* Focus rings were being sliced by the overflow on scrolling containers. */
.tablescroll { padding: 2px; margin: -2px; }

/* Mobile nav: off-screen panel must not be reachable or announced. */
.nav[inert] { visibility: hidden; }
@media (min-width: 961px) { .nav[inert] { visibility: visible; } }

/* ------------------------------------------- 31. Touch targets & affordance
   WCAG 2.5.8 asks for a 24px minimum; 44px is the comfortable mobile target.
   These controls were 30–38px tall, so their hit area is padded out without
   changing how they look. */
.themetoggle, .burger { width: 40px; height: 40px; }
@media (max-width: 960px) {
  .themetoggle, .burger { width: 44px; height: 44px; }
  .nav a { padding-block: 0.95rem; }          /* already ≥44px tall */
  .foot li { margin-bottom: 0; }
  .foot li a { display: inline-block; padding-block: 0.6rem; }
  .crumbs a { padding-block: 0.4rem; }
}
.skip { padding: 0.85rem 1.4rem; }
.masthead .nav a { position: relative; }
.masthead .nav a::before {                     /* invisible hit area extension */
  content: ''; position: absolute; inset: -12px -6px;
}
/* Below 960 this pseudo-element becomes the row number in the panel instead —
   see the mobile nav block. It used to be switched off here with `content:
   none`, which outranked the panel's own rule and left the numbers blank. */

/* A horizontally scrolling table needs to look scrollable. The fade is
   painted on the wrapper and disappears once the table is fully scrolled. */
.tablescroll {
  background:
    linear-gradient(to right, var(--bg) 30%, transparent) left / 32px 100% no-repeat,
    linear-gradient(to left,  var(--bg) 30%, transparent) right / 32px 100% no-repeat,
    radial-gradient(farthest-side at 0 50%, rgba(0,0,0,.28), transparent) left / 14px 100% no-repeat,
    radial-gradient(farthest-side at 100% 50%, rgba(0,0,0,.28), transparent) right / 14px 100% no-repeat;
  background-attachment: local, local, scroll, scroll;
}

/* --------------------------------------- 32. Badge tiles keep a navy ground
   Ten of the fifteen badge artworks are 20–68% white by pixel area, because
   they were drawn for a dark site. On the light edition's white card their
   shapes dissolve into the card. The tile therefore keeps the dark ground in
   both editions — a deliberate exception, not an oversight. */
.badges {
  --bg-raised: #052445;
  --ink:   #ffffff;
  --ink-3: #8fa6bf;
  --ink-4: #7e97b3;
  --rule:  rgba(255, 255, 255, 0.13);
  --accent-rule: rgba(3, 169, 244, 0.34);
}
.badge { color: var(--ink); }

/* The scroll fade has to match whichever band the table sits in. */
.tablescroll { --fade: var(--bg); }
.band--sunk .tablescroll, .band--sunk .tablescroll * { --fade: var(--bg-sunk); }
.tablescroll {
  background:
    linear-gradient(to right, var(--fade) 30%, transparent) left / 32px 100% no-repeat,
    linear-gradient(to left,  var(--fade) 30%, transparent) right / 32px 100% no-repeat,
    radial-gradient(farthest-side at 0 50%, rgba(0,0,0,.30), transparent) left / 14px 100% no-repeat,
    radial-gradient(farthest-side at 100% 50%, rgba(0,0,0,.30), transparent) right / 14px 100% no-repeat;
  background-attachment: local, local, scroll, scroll;
}

/* ------------------------------ 33. Standalone target sizes (WCAG 2.5.8)
   These controls are visually small by design — an arrow link's underline
   sits close to its text, a source credit is a footnote. Rather than inflate
   the type, the hit area is extended with a pseudo-element, so the target
   meets the size floor while the drawing stays exactly as intended. */
.arrowlink, .crumbs a, .ledger__src a, .cites__y a { position: relative; }
.arrowlink::before,
.crumbs a::before,
.ledger__src a::before {
  content: ''; position: absolute; left: 0; right: 0;
  top: 50%; height: 24px; transform: translateY(-50%);
}
@media (max-width: 960px) {
  .arrowlink::before,
  .crumbs a::before,
  .ledger__src a::before { height: 44px; }
}
/* The extension must not swallow clicks meant for neighbours. */
.crumbs a::before, .ledger__src a::before { z-index: 0; }
.crumbs span, .ledger__src { position: relative; z-index: 1; }
.ledger__src a { z-index: 2; }

/* ------------------------------------------- 36. Contact details (contact)
   Deliberately not a card. The previous version was an eight-row key/value
   table inside a bordered box with a pulsing "accepting work" pill, which is
   the shape of a status widget rather than of a company's contact details:
   every label carried the same weight as its value, eight dividers did the
   work hierarchy should do, and the addresses were wrapping mid-phrase
   because they had been forced into a narrow value column.

   Here the address is an address. The email is the thing you came for, so it
   is set largest; the offices are evidence that there is somewhere to send a
   contract; the hours are subordinate. No border, no background, no pill. */
.reach { display: grid; gap: 0.35rem; }
.reach .arrowlink { margin-top: 1.2rem; justify-self: start; }
.reach__email {
  font-size: clamp(1.25rem, 1.9vw, 1.5rem); line-height: 1.25;
  letter-spacing: -0.02em; font-weight: 600; color: var(--ink);
  text-decoration-color: var(--accent-rule); text-underline-offset: 0.18em;
  /* An email address has no spaces, so a narrow column would push it past the
     edge rather than break it. */
  overflow-wrap: anywhere;
}
.reach__email:hover { color: var(--accent-hi); }
.reach__phone {
  font: 400 0.9375rem/1.6 var(--mono); letter-spacing: 0.02em;
  color: var(--ink-2); text-decoration-color: var(--rule);
}
.reach__phone:hover { color: var(--ink); }

/* The gap only applies when something precedes it. On /contact/ the email and
   phone sit above; on /about/ the offices are the whole block. */
.reach__offices { display: grid; gap: 1.5rem; }
.reach__offices:not(:first-child) { margin-top: 1.9rem; }
.reach__offices strong a { color: inherit; text-decoration-color: var(--accent-rule); }
.reach__offices strong a:hover { color: var(--accent-hi); }
.reach__offices address {
  font-style: normal; font-size: var(--fs-small); line-height: 1.65;
  color: var(--ink-2);
  padding-top: 0.95rem; border-top: 1px solid var(--rule);
}
.reach__offices strong { display: block; color: var(--ink); font-weight: 600; }
.reach__place {
  display: block; margin-bottom: 0.45rem;
  font: 400 0.6875rem/1 var(--mono);
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--ink-4);
}
.reach__tz {
  display: block; margin-top: 0.35rem;
  font: 400 0.75rem/1.5 var(--mono); letter-spacing: 0.05em; color: var(--ink-4);
}
.reach__note {
  margin: 1.6rem 0 0; padding-top: 0.95rem; border-top: 1px solid var(--rule);
  font: 400 0.8125rem/1.6 var(--sans); color: var(--ink-3);
}

/* ------------------------------------------------------- 34. Named roster
   A people grid rather than a card grid: the portrait carries the weight, so
   the tile is the image plus two lines. Square crops keep the baseline of
   every name aligned across the row regardless of the source photograph. */
.people {
  display: grid; gap: clamp(1rem, 2vw, 1.5rem);
  /* Track floor scales with the viewport so the roster stays two-up on a
     phone instead of becoming fourteen full-width photographs to scroll past. */
  grid-template-columns: repeat(auto-fill, minmax(min(100%, clamp(140px, 22vw, 200px)), 1fr));
  list-style: none; padding: 0; margin: 0;
}
.person { min-width: 0; }
.person__shot {
  display: block; width: 100%; aspect-ratio: 1; object-fit: cover;
  border-radius: var(--radius); border: 1px solid var(--rule);
  background: var(--bg-raised);
  filter: grayscale(1) contrast(1.04);
  transition: filter 0.3s var(--ease), border-color 0.22s var(--ease);
}
.person:hover .person__shot,
.person:focus-within .person__shot { filter: grayscale(0); border-color: var(--accent); }
.person__n {
  display: block; margin-top: 0.85rem; color: var(--ink);
  font-size: var(--fs-small); font-weight: 600; line-height: 1.3;
}
.person__r {
  display: block; margin-top: 0.3rem; color: var(--accent);
  font: 400 0.6875rem/1.45 var(--mono); letter-spacing: 0.04em;
  /* Two lines reserved so a wrapping job title does not push this tile's
     description out of step with its neighbours across the row. */
  min-height: calc(0.6875rem * 1.45 * 2);
}
.person__f {
  display: block; margin-top: 0.5rem; color: var(--ink-3);
  font-size: 0.8125rem; line-height: 1.5;
}
/* The whole tile is the target where a member links onward. */
a.person { text-decoration: none; display: block; }
a.person:hover .person__n { color: var(--accent-hi); }

@media (prefers-reduced-motion: reduce) {
  .person__shot { transition: none; }
}

/* ------------------------------------------------ 35. Footer social row
   Brand glyphs are solid fills, so they need their own sizing rather than the
   stroked-icon rules. The 40px tile clears the WCAG 2.5.8 target floor without
   making the mark itself large. */
.social { display: flex; gap: 0.35rem; list-style: none; padding: 0; margin: 1.4rem 0 0; }
.social a {
  display: grid; place-items: center; width: 40px; height: 40px;
  border-radius: var(--radius); color: var(--ink-3);
  border: 1px solid transparent;
  transition: color 0.2s var(--ease), border-color 0.2s var(--ease), background 0.2s var(--ease);
}
.social a:hover, .social a:focus-visible {
  color: var(--accent-hi); border-color: var(--rule-strong); background: var(--bg-raised);
}
.social svg { width: 17px; height: 17px; display: block; }

/* Certification codes read as single tokens, so they must not break: "LPT
   Master" was splitting across two lines in the hero card. */

/* ----------------------------------------------- 37. Aggregate rating card
   Gold is the one place on the site that is not the brand blue, because a
   star that is not gold does not read as a star.

   Defined the way every other token here is: dark is the default and light is
   opt-in through the toggle, never through the OS preference. A
   prefers-color-scheme block made the gold follow the operating system while
   the rest of the page stayed dark, and the stars went muddy brown on navy. */
:root { --star: #f7bf3a; --star-off: rgba(255, 255, 255, 0.38); }
:root[data-theme='light'] { --star: #a86f00; --star-off: rgba(11, 27, 47, 0.50); }

.rating { margin: 0; display: grid; gap: 0; }
.rating__hd {
  margin: 0 0 1.1rem; padding-bottom: 0.85rem;
  border-bottom: 1px solid var(--rule);
  font: 400 var(--fs-label)/1 var(--mono);
  letter-spacing: 0.15em; text-transform: uppercase; color: var(--ink-4);
}
/* Score and stars share a baseline rather than stacking, so the pair reads as
   one statement and the card opens at full width instead of in a thin column. */
.rating__head { display: flex; align-items: baseline; gap: 1rem; flex-wrap: wrap; }
.rating__score {
  margin: 0; font-family: var(--serif);
  font-size: clamp(3.2rem, 5.4vw, 4.4rem); line-height: 0.86;
  letter-spacing: -0.04em; color: var(--ink);
  font-variant-numeric: lining-nums tabular-nums;
}
.rating__of { font-size: 0.34em; color: var(--ink-4); letter-spacing: 0; }

/* `width: max-content` and `justify-self: start`, or the row stretches to the
   grid column and the fill clips past the last star, painting all five gold
   regardless of score. */
.rating__stars {
  position: relative; display: block; width: max-content;
  --sw: 22px; --sg: 4px; line-height: 0;
}
.rating__track, .rating__fill { display: block; white-space: nowrap; }
/* The unfilled star is not decoration: a reader only knows the score is 4.8
   and not 5 by seeing the dim remainder of the fifth star, so it is a
   graphical object under WCAG 1.4.11 and needs 3:1. --rule-strong gave 2.65
   on dark and 2.15 on light. */
.rating__track { color: var(--star-off); }
.rating__fill {
  position: absolute; inset: 0 auto 0 0;
  overflow: hidden; color: var(--star);
  /* Whole stars span glyph+gap; the remainder spans glyph only, so a 0.8 lands
     four fifths across the fifth star rather than somewhere in the gap. */
  width: calc(var(--full) * (var(--sw) + var(--sg)) + var(--frac) * var(--sw));
}
.rating__stars svg {
  width: var(--sw); height: var(--sw); display: inline-block; margin-right: var(--sg);
}

.rating__count { margin: 1rem 0 0; font-size: var(--fs-small); color: var(--ink-2); }
.rating__count strong { color: var(--ink); font-weight: 600; }

.rating__q {
  margin: 1.6rem 0 0; padding: 0 0 0 1.1rem;
  border-left: 2px solid var(--accent-rule);
  font-size: 1.0625rem; line-height: 1.55; color: var(--ink);
  text-wrap: pretty;
}
.rating__by {
  margin: 0.7rem 0 0 1.1rem;
  font: 400 0.75rem/1.5 var(--mono); letter-spacing: 0.05em; color: var(--ink-4);
}
.rating__src {
  margin: 1.5rem 0 0; padding-top: 0.9rem; border-top: 1px solid var(--rule);
  font: 400 0.75rem/1.5 var(--mono); letter-spacing: 0.05em; color: var(--ink-4);
}
.rating__src a { color: var(--ink-3); }
.rating__src a:hover { color: var(--ink); }
.rating__src svg { vertical-align: -1px; margin-left: 2px; }

/* --------------------------------------------- 38. Attack scenario pages */
.sevline {
  display: flex; align-items: center; gap: 0.75rem; flex-wrap: wrap;
  margin: 0 0 1.5rem;
  font: 400 0.8125rem/1 var(--mono); letter-spacing: 0.04em; color: var(--ink-3);
}
.sev {
  display: inline-flex; align-items: center;
  padding: 0.3rem 0.6rem; border-radius: 3px;
  font: 500 0.6875rem/1 var(--mono);
  letter-spacing: 0.12em; text-transform: uppercase;
  /* Severity reads on a tinted ground rather than as coloured text, so it
     clears contrast without the hue having to carry the meaning alone. */
  color: var(--on-sev); background: var(--sev-bg); border: 1px solid var(--sev-bd);
}
.sev--critical { --sev-bg: color-mix(in srgb, var(--sev-crit) 18%, transparent);
                 --sev-bd: color-mix(in srgb, var(--sev-crit) 45%, transparent);
                 --on-sev: var(--sev-crit); }
.sev--high     { --sev-bg: color-mix(in srgb, var(--sev-high) 18%, transparent);
                 --sev-bd: color-mix(in srgb, var(--sev-high) 45%, transparent);
                 --on-sev: var(--sev-high); }
.sev--medium   { --sev-bg: color-mix(in srgb, var(--sev-med) 18%, transparent);
                 --sev-bd: color-mix(in srgb, var(--sev-med) 45%, transparent);
                 --on-sev: var(--sev-med); }

/* The light edition's severity hues are tuned to sit on white. On an 18% tint of
   themselves they fell to 3.3:1 and 2.7:1, under the 4.5:1 floor, so the pill text
   gets its own darker value there. Severity is still carried by the word, not only
   by the hue, which is what keeps it readable either way. */
:root[data-theme='light'] .sev--critical { --on-sev: #7a1a10; }
:root[data-theme='light'] .sev--high     { --on-sev: #73310d; }
:root[data-theme='light'] .sev--medium   { --on-sev: #4d430d; }

/* ------------------------------------------------- 26. Contact dock
   Two round actions bottom-right and a message panel above them. The panel
   animates from the buttons rather than fading in place, so the thing that
   opened it is visibly where it came from.

   Ships `hidden`; script reveals it. A reader without JavaScript never sees a
   toggle that cannot toggle — and the WhatsApp link is a plain anchor, so it
   would work for them anyway if it were shown. */
.dock {
  position: fixed; right: clamp(1rem, 3vw, 2rem); bottom: clamp(1rem, 3vw, 2rem);
  z-index: 60; display: flex; flex-direction: column; align-items: flex-end; gap: 0.6rem;
}
.dock[hidden] { display: none; }

.dock__b {
  width: 48px; height: 48px; flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 50%; cursor: pointer;
  background: var(--bg-raised); color: var(--ink);
  border: 1px solid var(--rule-strong);
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.32);
  transition: transform 0.22s var(--ease), border-color 0.22s var(--ease),
              background 0.22s var(--ease), color 0.22s var(--ease);
}
.dock__b svg { width: 21px; height: 21px; display: block; }
.dock__b:hover { transform: translateY(-2px); border-color: var(--accent); color: var(--accent-hi); }
.dock__b:active { transform: translateY(0); }
/* WhatsApp owns a colour people recognise; using it is the fastest way to say
   what the button is without a label beside it. */
.dock__b--wa:hover { background: #25d366; border-color: #25d366; color: #0b1b2f; }
.dock__b--chat[aria-expanded='true'] { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }

/* Entrance: the two buttons arrive one after the other on first paint. */
.dock__b { animation: dockin 0.32s var(--ease) backwards; }
.dock__b--wa { animation-delay: 0.05s; }
.dock__b--chat { animation-delay: 0.12s; }
@keyframes dockin { from { opacity: 0; transform: translateY(10px) scale(0.9); } to { opacity: 1; transform: none; } }

.dock__panel {
  width: min(21rem, calc(100vw - 2rem));
  background: var(--bg-raised); border: 1px solid var(--rule);
  border-radius: var(--radius); overflow: hidden;
  box-shadow: 0 18px 50px rgba(0, 0, 0, 0.45);
  transform-origin: bottom right;
  animation: panelin 0.26s var(--ease);
}
.dock__panel[hidden] { display: none; }
@keyframes panelin {
  from { opacity: 0; transform: translateY(12px) scale(0.96); }
  to   { opacity: 1; transform: none; }
}

.dock__head {
  display: flex; align-items: center; justify-content: space-between; gap: 1rem;
  padding: 0.75rem 0.5rem 0.75rem 1rem; border-bottom: 1px solid var(--rule);
  font: 500 var(--fs-label)/1 var(--mono);
  letter-spacing: 0.12em; text-transform: uppercase; color: var(--ink-3);
}
.dock__x {
  min-width: 34px; min-height: 34px; padding: 0; line-height: 1;
  background: none; border: 0; color: var(--ink-3); font-size: 1.25rem; cursor: pointer;
}
.dock__x:hover { color: var(--ink); }

.dock__form { display: grid; gap: 0.3rem; padding: 1rem; }
/* An explicit `display` beats the hidden attribute's UA `display:none`, so the
   form stayed on screen under the confirmation after a successful send. */
.dock__form[hidden] { display: none; }
.dock__l {
  font: 400 0.6875rem/1.6 var(--mono);
  letter-spacing: 0.1em; text-transform: uppercase; color: var(--ink-4);
}
.dock__l + .dock__i { margin-bottom: 0.55rem; }
.dock__i {
  width: 100%; min-height: 40px; padding: 0.5rem 0.7rem;
  background: var(--bg); color: var(--ink);
  border: 1px solid var(--rule); border-radius: var(--radius);
  font: 400 var(--fs-small)/1.5 var(--sans);
}
.dock__i:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.dock__ta { resize: vertical; min-height: 72px; }
.dock__err { margin: 0 0 0.6rem; font-size: 0.75rem; color: var(--sev-critical, #ff8d7a); }
.dock__err[hidden] { display: none; }
.dock__send { justify-content: center; width: 100%; }
.dock__send[disabled] { opacity: 0.6; cursor: default; }

.dock__done { padding: 1.25rem 1rem 1.4rem; }
.dock__done[hidden] { display: none; }
.dock__doneT { margin: 0 0 0.4rem; font: 600 1rem/1.35 var(--sans); color: var(--ink); }
.dock__doneB { margin: 0; font-size: var(--fs-small); line-height: 1.6; color: var(--ink-3); }

@media (max-width: 560px) {
  .dock { right: 1rem; bottom: max(1rem, env(safe-area-inset-bottom)); }
  .dock__panel { width: calc(100vw - 2rem); }
}

@media (prefers-reduced-motion: reduce) {
  .dock__b, .dock__panel { animation: none; }
  .dock__b { transition: border-color 0.22s var(--ease), background 0.22s var(--ease), color 0.22s var(--ease); }
  .dock__b:hover { transform: none; }
}
