/* ============================================================
   Dixit — site styles
   ============================================================ */

/* MIRRORED IN errors.css — read before editing the two sections below.
   errors.css deliberately re-declares the six @font-face blocks, the two
   metric-adjusted fallback faces below them, and a 13-token subset of :root, so
   the error page still renders when this file is the thing that failed to load. That duplication is intentional (it is not a DRY defect
   to be fixed), but it means the two files drift silently.
   Changing an @font-face src/subset, or any of these tokens —
     --ink  --ink-3  --primary  --slate  --display  --body  --mono
     --maxw  --gutter  --radius-md  --radius-btn  --radius-round
   — means changing errors.css too. Currently in sync.
   The one token that must NOT be synced is --ink-4; see its line below. */

/* ===================== FONTS ===================== */
/* Self-hosted variable woff2 (latin + latin-ext subsets).
   One face per subset covers the full weight axis. */

/* Manrope 400–700 */
@font-face {
  font-family: "Manrope";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("/assets/static/fonts/manrope-latin.woff2") format("woff2");
  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: "Manrope";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("/assets/static/fonts/manrope-latin-ext.woff2") format("woff2");
  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;
}

/* Sora 400–700 */
@font-face {
  font-family: "Sora";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("/assets/static/fonts/sora-latin.woff2") format("woff2");
  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: "Sora";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("/assets/static/fonts/sora-latin-ext.woff2") format("woff2");
  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;
}

/* ---- Metric-adjusted fallbacks — these exist to hold CLS down ----

   font-display: swap means the fallback paints first and the webfont replaces
   it. Every layout difference between the two becomes a layout shift at swap
   time. Measured before this block existed: nine pages over 0.1 CLS on desktop,
   oncology at 0.357, and blocking **\/*.woff2 at the network layer took every
   one of them to exactly 0.0000. The mechanism was a single 85.5px line —
   .hero__title wrapping to one more line once Sora arrived, pushing the whole
   page down.

   The faces below re-declare Arial with Sora's and Manrope's metrics, so the
   pre-swap and post-swap text occupy the same space. They are NOT overrides on
   the real webfonts: putting size-adjust on Sora itself would distort the type
   we chose. Arial is named explicitly rather than relying on system-ui, because
   system-ui is SF on macOS, Segoe UI on Windows and Roboto on Android — three
   different widths, and one set of numbers cannot be right for all three. Arial
   ships on Windows and macOS; where it does not (Android, most Linux) the
   local() lookup fails, the face never loads, and the stack falls through to
   system-ui exactly as before. No regression, just no benefit there.

   size-adjust was MEASURED IN THE BROWSER, not computed from the font tables.
   The table arithmetic (weighted avg advance width, Sora 0.50439 em / Arial
   Bold 0.47325 em) says 106.6%, and 106.6% leaves oncology at 0.302. It is
   wrong because src: local("Arial") loads Arial Regular and the browser
   SYNTHESISES the bold, and synthetic bold is narrower than real Arial Bold.
   So the values come from rendering the same string in both families and
   taking the ratio of the measured widths — see tools-internal notes. Redo it
   that way if a webfont is ever replaced or re-subset; do not re-derive from
   the tables.

   ascent/descent are still table-derived: the webfont's OS/2 typo metrics
   (both fonts set USE_TYPO_METRICS, so browsers read those), divided by
   size-adjust, because the overrides are relative to the already-adjusted em.
     Sora    970/1000 ÷ 1.182 = 82.1%   |  290/1000 ÷ 1.182 = 24.5%
     Manrope 2132/2000 ÷ 1.044 = 102.1% |  600/2000 ÷ 1.044 = 28.7%
   line-gap is 0 in both webfonts, so 0% here.

   Result, desktop CLS, 26 pages, median of 3: nine pages over 0.1 became one.
   Worst page 0.357 -> 0.201. Twenty-three of twenty-four headlines now wrap to
   the same line count before and after the swap.

   KNOWN RESIDUE — /widen/data-transfer-qc, 0.201. Its headline is 2333 px of
   Sora but 2357 px of adjusted Arial in a 1212 px box: 1% wider, enough to
   need a third line. It is not tunable away. Sweeping size-adjust across
   113-120% shows data-transfer-qc needs <116% while central-review and
   reviewer-environment need >=117.5%, so no single value satisfies all three —
   the two fonts distribute width differently across specific letter
   combinations, which one uniform scale cannot correct. 118.2% is the value
   that leaves one page mismatched instead of two. Closing the last page means
   changing that headline's wording, not this number.

   Chrome and Firefox have honoured size-adjust since 92; Safari since 17, and
   older Safari simply gets unadjusted Arial, which is no worse than the
   system-ui it had. JetBrains Mono deliberately has no fallback face: it is
   used for small labels, none of which were a measured shift source, and its
   generic fallback (ui-monospace) is not a single font on any platform. ---- */
@font-face {
  font-family: "Sora Fallback";
  font-style: normal;
  font-weight: 400 700;
  src: local("Arial");
  size-adjust: 118.2%;
  ascent-override: 82.1%;
  descent-override: 24.5%;
  line-gap-override: 0%;
}
@font-face {
  font-family: "Manrope Fallback";
  font-style: normal;
  font-weight: 400 700;
  src: local("Arial");
  size-adjust: 104.4%;
  ascent-override: 102.1%;
  descent-override: 28.7%;
  line-gap-override: 0%;
}

/* JetBrains Mono 400–500 */
@font-face {
  font-family: "JetBrains Mono";
  font-style: normal;
  font-weight: 400 500;
  font-display: swap;
  src: url("/assets/static/fonts/jetbrains-mono-latin.woff2") format("woff2");
  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: "JetBrains Mono";
  font-style: normal;
  font-weight: 400 500;
  font-display: swap;
  src: url("/assets/static/fonts/jetbrains-mono-latin-ext.woff2") format("woff2");
  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;
}

/* ===================== TOKENS ===================== */
:root {
  /* Palette — extracted from trials.widen.it / Widen logo + Dixit logo */
  --ink: #0b1e3f;
  --ink-2: #14305c;
  --ink-3: #4a5a78;
  /* Do not sync this one to errors.css. There it is #b5c4de, because the error
     page puts the same labels on a dark backdrop (5.92:1 there). Same token,
     two contexts, two contrast-derived values — deliberately divergent. */
  --ink-4: #5b6c8c;      /* WCAG AA: 5.29:1 on white, 4.75:1 on #eef3fb */
  --primary: #3062b0;     /* WIDEN brand blue */
  --primary-2: #234c8e;
  --primary-3: #5a8bdc;
  --slate: #b5c4de;       /* Dixit logo slate */
  /* A lighter cousin of --slate, for the --hero-accent knob on the photographic
     heroes only: those pages lighten the overlay to .55/.75, and --slate does not
     lift enough against a photograph. Lived as a bare literal in 8 inline style
     attributes and in no :root until 2026-08-17, which meant a palette change
     would have missed it. Against the *default* composite it measures 3.69:1 vs
     --slate's 3.28:1 — but on the photo heroes, which is where it is actually
     used, neither figure applies and the backdrop has not been measured. */
  --hero-accent-photo: #b9d0f5;
  --mist: #eaf0f9;
  --bg: #f6f8fc;
  --paper: #ffffff;
  --rule: #d9e1ef;
  --rule-2: #ecf0f8;
  --rule-strong: #7f8fa8; /* functional borders: 3.28:1 on white, 3.09:1 on --bg */

  /* Status — form validation and inline feedback */
  --success: #1d5c34;
  --success-bg: #f0f8f2;
  --success-border: #bcdec7;
  --warn: #7a5410;
  --warn-bg: #fbf6ec;
  --warn-border: #ead9b8;
  --danger: #b3261e;
  --danger-bg: #fbf1f0;
  --danger-border: #ecc7c4;

  /* The *Fallback families are metric-adjusted Arial, declared in FONTS above.
     They must sit immediately after the webfont and before system-ui: that is
     the slot the browser paints from during the swap period. */
  --display: "Sora", "Sora Fallback", system-ui, sans-serif;
  --body: "Manrope", "Manrope Fallback", system-ui, sans-serif;
  --mono: "JetBrains Mono", ui-monospace, monospace;

  /* Type scale — seven steps; nothing sets a raw px font-size.
     See README: styles.css — Type scale. */
  --fs-display: clamp(44px, 6.6vw, 104px); /* 95px — page hero titles */
  --fs-h1: clamp(36px, 4.9vw, 68px);       /* 68px — dark CTA titles, headline stats */
  --fs-h2: clamp(30px, 4vw, 56px);         /* 56px — section titles */
  --fs-h3: clamp(20px, 1.9vw, 26px);       /* 26px — panel heads, editorial statements */
  --fs-h4: 20px;                           /* card titles, ledes, kickers */
  --fs-body: 16px;                         /* body copy, lists, inputs, buttons */
  --fs-micro: 12px;                        /* mono labels, meta, legal */

  --maxw: 1320px;
  --gutter: clamp(20px, 4vw, 56px);
  /* Corner radius scale. Every border-radius in the site references one of
     these; elements flush inside a padded rounded parent use
     calc(parent radius - padding) to stay concentric. */
  --radius-xs: 4px;    /* micro tags, map-pin labels */
  --radius-sm: 8px;    /* small chips, menu items, mock-UI elements */
  --radius-md: 12px;   /* inputs, buttons, KPI tiles, icons */
  --radius-lg: 16px;   /* cards, panels, list groups, media frames, grid shells */
  --radius-2xl: 28px;  /* hero/section-level dark cards */
  --radius-pill: 999px;
  --radius-round: 50%; /* circles: dots, checks, avatars, seals */
  --radius-btn: var(--radius-md); /* standard buttons/CTAs — matches form inputs */
  /* Card surface — one recipe for every card, panel and grid shell.
     See README: styles.css — Card system. */
  --card-radius: var(--radius-lg);
  --card-border: var(--rule);
  /* Card fill, picked by the band the card sits on, not by component. */
  --card-on-white: var(--mist);   /* card on a white section  — 1.15:1 */
  --card-on-muted: var(--paper);  /* card on a --muted section — 1.06:1 */
  /* Card padding — three steps, picked by the depth a card sits at. Horizontal
     values are overridden on phones by --card-pad-mobile at the end of this file. */
  --card-pad-panel: clamp(36px, 4.5vw, 64px); /* section-level panels: .ccard */
  --card-pad:       clamp(28px, 3vw, 40px);   /* content cards: .consult__card, .cform */
  --card-pad-note:  clamp(20px, 2.2vw, 28px); /* dense note strips: .assure__item */
  --section-padding: clamp(64px, 7vw, 112px);
  /* Standard horizontal inset between a card's edge and its text on mobile.
     Matches .discipline (the reference card); applied to every card/section
     in the mobile normalisation block at the end of this file. */
  --card-pad-mobile: 18px;
}

/* ===================== BASE ===================== */
* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; min-height: 100%; }
/* header.nav is sticky at 76px, so anything scrolled to the top of the viewport
   — an in-page anchor, or the element the browser scrolls to during sequential
   focus navigation — lands underneath it. Reserve the bar's height plus the
   16px the .ccard anchor already used. WCAG 2.2 SC 2.4.11. */
html { scroll-padding-top: 92px; }
body {
  font-family: var(--body);
  color: var(--ink);
  background: var(--paper);
  font-size: var(--fs-body);
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}
/* The reset is for the links that are not prose — nav items, buttons, whole-card
   anchors — where an underline would be noise. Every link that sits *inside a
   text block* must put it back: colour alone separates them from body copy at
   only 1.15:1, where SC 1.4.1 (Use of Colour) wants 3:1 for a colour-only cue.
   The one prose treatment is colour + underline at 3px offset, hover to
   --primary-2. Applied at .about__body a, .assure__text a, .cdone__urgent a,
   .cform__consent a, .section__kicker a and legal.css .legal a.
   Two links keep a different look, by role rather than by drift:
     .cform__alert a  — keeps color:inherit, because the alert's colour IS its
                        status (success/warning/danger); forcing --primary inside
                        a red alert would override the signal.
     .pub__title      — a card title, not a link in prose; its rule is painted
                        with background-image so it can sit under a 20px heading.
                        It is the only user of that recipe: the Capabilities
                        headings dropped it once the whole card became the
                        target. */
a { color: inherit; text-decoration: none; }
img, svg { display: block; max-width: 100%; }

/* Display-scale headings stop scaling at their clamp floor (36-44px) below a
   ~735px viewport, but their container keeps narrowing, so a long word can end
   up wider than the line box. The dark cards clip rather than spill
   (overflow: hidden), so the word silently loses its tail.
   break-word is the floor everywhere: it only takes effect when a word cannot
   fit on a line of its own, so it never changes a heading that already fits. */
.hero__title,
.contact__title,
.section__title,
.widen__card h2,
.about__title,
.statement__quote,
.turin__title {
  overflow-wrap: break-word;
}
/* Below 480px, prefer a proper hyphenated break to break-word's mid-word cut.
   Hyphenation is scoped rather than global because text-wrap: balance will use
   a break opportunity wherever it evens out the lines — on wider screens that
   rewraps headings that were never too long. 480px clears the widest word in
   the site's copy ("radiopharmaceutical", which fits from 430px up), and the
   12-char minimum keeps ordinary words ("oncology") whole. */
@media (max-width: 480px) {
  .hero__title,
  .contact__title,
  .section__title,
  .widen__card h2,
  .about__title,
  .statement__quote,
  .turin__title {
    -webkit-hyphens: auto;
    hyphens: auto;
    hyphenate-limit-chars: 12 4 4;
  }
}

/* Removed from the page but kept in the accessibility tree — for text that has
   to be announced but is carried visually by something else (a logo standing in
   for a page title, a heading whose grouping is already obvious on screen).
   Not display:none, which would drop it from the tree as well. */
.u-visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* Marker on links that leave the site. Inline-baseline rather than a flex item,
   so it sits with the last word and wraps with it; the base rule sets svg to
   display:block, which would otherwise put it on its own line. */
.ext-icon {
  display: inline-block;
  vertical-align: baseline;
  width: 10px;
  height: 10px;
  margin-left: 5px;
  flex-shrink: 0;
  opacity: 0.7;
}

/* Skip link — first tabbable thing on the page, off-screen until focused.
   z-index clears the sticky nav's 50. Its own focus ring has to be white:
   the global ring below is --primary, which is this element's background. */
.skip-link {
  position: absolute;
  top: 0;
  left: -9999px;
  z-index: 100;
  padding: 12px 20px;
  background: var(--primary);
  color: #fff;
  font-family: var(--body);
  font-size: var(--fs-body);
  font-weight: 600;
  border-radius: 0 0 var(--radius-md) 0;
}
.skip-link:focus { left: 0; }
.skip-link:focus-visible { outline-color: #fff; }
/* The skip target is programmatic only (tabindex="-1"); it must never draw a
   ring of its own when focus lands there. */
main:focus { outline: none; }

/* Keyboard focus. Form controls keep their own designed focus state (see
   .field input:focus), so the ring covers links, buttons and anything else
   tabbable. On dark surfaces the ring switches to white to stay visible. */
:where(a, button, summary, [tabindex]):focus-visible {
  outline: 2px solid var(--primary);
  outline-offset: 3px;
}
:where(.hero, .widen__card, .contact, .ccard, .lp) :where(a, button, [tabindex]):focus-visible {
  outline-color: #fff;
}
/* .cform is a white card nested inside the dark .ccard, so it inherits the
   white ring above and renders it invisible. Put the standard ring back. */
.cform :where(a, button, [tabindex]):focus-visible {
  outline-color: var(--primary);
}

/* ===================== SHARED ===================== */
.container {
  max-width: var(--maxw);
  margin: 0 auto;
  padding-left: var(--gutter);
  padding-right: var(--gutter);
}

.label {
  font-family: var(--mono);
  font-size: var(--fs-micro);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-4);
  font-weight: 500;
}

.eyebrow-num {
  font-family: var(--mono);
  font-size: var(--fs-micro);
  letter-spacing: 0.12em;
  color: var(--ink-4);
  text-transform: uppercase;
}

/* The accented word inside a heading. Was an <em>, which announced emphasis
   that was never meant — the treatment is decorative, and the heading reads the
   same without it. The italic lived on <em>'s UA default, so it has to be
   declared here; the per-heading rules that follow add only colour and weight.
   <em> is still used, correctly, in prose (.statement__quote and friends). */
.title-accent { font-style: italic; }

.brand {
  display: inline-flex;
  align-items: center;
  gap: 12px;
}
.brand__logo { height: 28px; width: auto; }

/* ===================== BUTTONS =====================
   Two variants across two surfaces; .widen__cta, .widen__link, .consult__cta
   and .cform__submit are aliases carrying placement only.
   See README: styles.css — Buttons. */
.btn,
.widen__cta,
.widen__link,
.consult__cta,
.cform__submit {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 15px 24px;
  border-radius: var(--radius-btn);
  border: 1px solid transparent;
  font-family: var(--body);
  font-weight: 600;
  font-size: var(--fs-body);
  line-height: 1.25;
  cursor: pointer;
  transition: background .15s, color .15s, border-color .15s;
}

/* Trailing arrow. The icon is a flex item next to the label, so it inherits
   flex-shrink: 1 and the label wins the space as the button narrows — on
   phones, where .hero__actions/.contact__actions give each CTA the full column
   and the label wraps, the arrow is what gets squeezed. The size is declared
   here rather than left to the svg width/height attributes, which any later
   rule would outrank, and in px so it never tracks the label's font-size. */
.btn > svg,
.widen__cta > svg,
.consult__cta > svg,
.cform__submit > svg {
  flex-shrink: 0;
  width: 14px;
  height: 14px;
}

/* primary — filled brand blue on light surfaces */
.btn--primary,
.cform__submit {
  background: var(--primary);
  color: #fff;
  border-color: var(--primary);
}
.btn--primary:hover,
.cform__submit:hover {
  background: var(--primary-2);
  border-color: var(--primary-2);
}

/* primary — filled white on dark surfaces */
.btn--primary.btn--on-dark,
.widen__cta {
  background: #fff;
  color: var(--ink);
  border-color: #fff;
}
.btn--primary.btn--on-dark:hover,
.widen__cta:hover {
  background: var(--slate);
  border-color: var(--slate);
}

/* secondary — outlined.
   .consult__cta is here rather than with the primaries: the two contact-page
   consultation cards jump to the form on the same page, so they must not carry
   the same filled treatment as .cform__submit, which is the only button on
   that page that actually sends anything.

   The border is --primary, not --rule-strong. Until 2026-08-18 every
   .btn--secondary on the site also carried .btn--on-dark, so this light-surface
   rule was reaching .consult__cta and nothing else, and its --rule-strong
   border had never been judged against a white band. It measures 3.28:1 on
   --paper and 3.09:1 on --bg: it clears SC 1.4.11 by 0.28 and 0.09, but it
   reads as a grey hairline next to the solid buttons this variant is supposed
   to be legible beside. --primary measures 6.00:1 and 5.64:1 on the same two
   surfaces and is already the label colour, so the outline and the text are
   one mark rather than two.

   Rank is carried by fill, not by border weight: a filled primary still
   outranks an outlined secondary at any border contrast. Making the outline
   visible does not promote the button — it only stops it reading as disabled.
   .consult__cta gains the same treatment, which is the intent: both are
   same-page jumps subordinate to the one CTA that converts.

   The fill is a tint of --primary, not transparent. Transparent means the
   button paints the band behind it, so its box had no edge of its own on any
   of the three surfaces this variant lands on: --bg inside .consult__card and
   inside the .about--muted approach sections, --paper in central-review's. The
   tint is the same move .btn--on-dark makes below — lay the button's own
   colour over the band at low alpha — mirrored for a light band, so the tint
   agent is --primary rather than white. Percentage, not a fixed hex, because
   the two bands differ: one value then separates from both by the same amount
   instead of matching one of them exactly. 10% measures 1.15:1 on --bg and
   1.15:1 on --paper, which is --card-on-white's separation (see TOKENS) — the
   figure the card system already treats as a visible surface change.
   Hover goes to 18%, the light-band equivalent of on-dark's 0 -> 0.08 step.

   Everything else is untouched: --primary border and label at rest,
   --primary-2 on hover, no fill on the primaries' terms. The label still
   clears SC 1.4.3 over the tint — --primary 4.92:1 on the 10% fill over --bg,
   5.21:1 over --paper, --primary-2 6.14:1 on the 18% hover fill — and the
   border still carries SC 1.4.11 against the band at 5.64:1 and 6.00:1,
   unchanged, since the tint sits inside the border rather than behind it.
   Where color-mix is unsupported the declaration drops and the background
   falls back to the initial transparent, which is what this rule did before. */
.btn--secondary,
.consult__cta {
  background: color-mix(in srgb, var(--primary) 10%, transparent);
  color: var(--primary);
  border-color: var(--primary);
}
.btn--secondary:hover,
.consult__cta:hover {
  background: color-mix(in srgb, var(--primary) 18%, transparent);
  color: var(--primary-2);
  border-color: var(--primary-2);
}
/* The outline is the only thing that makes this a button, so it carries SC
   1.4.11 on its own (3:1).
   It was 0.55, justified as "5.9:1 on --ink" — correct, and measured against
   the wrong surface. The dark cards are one of the two places this button is
   used; the other is every page hero, where the backdrop is not flat --ink but
   the overlay-over-wireframe composite (~#40698f, see the --hero-label note in
   HERO). There 0.55 measures 2.96:1 and fails.
   0.65 measures 3.46:1 on the composite and 7.58:1 on --ink — passing on both
   with margin, rather than passing on one by 0.04. */
.btn--secondary.btn--on-dark,
.widen__link {
  background: transparent;
  color: #fff;
  border-color: rgba(255,255,255,0.65);
}
.btn--secondary.btn--on-dark:hover,
.widen__link:hover {
  border-color: #fff;
  background: rgba(255,255,255,0.08);
}

/* ===================== NAV ===================== */
.nav {
  position: sticky;
  top: 0;
  z-index: 50;
  background: rgba(255,255,255,0.88);
  backdrop-filter: saturate(140%) blur(10px);
  -webkit-backdrop-filter: saturate(140%) blur(10px);
  border-bottom: 1px solid var(--rule-2);
}
.nav__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: 76px;
}
.nav__links {
  display: flex;
  align-items: center;
  gap: 40px;
}
.nav__link {
  font-size: var(--fs-body);
  font-weight: 500;
  color: var(--ink-2);
  transition: color .15s ease;
}
.nav__link:hover { color: var(--primary); }
.nav__link--active { color: var(--primary); }
/* The one compact button: same primary treatment, sized to the nav bar. */
.nav__cta {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  background: var(--primary);
  color: #fff;
  border: 1px solid var(--primary);
  padding: 10px 18px;
  border-radius: var(--radius-btn);
  font-size: var(--fs-body);
  line-height: 1.25;
  font-weight: 600;
  transition: background .15s ease, border-color .15s ease;
}

  /* dropdowns */
  .nav-item {
    position: relative;
    display: flex;
    align-items: center;
  }
  .nav-item > a {
    display: inline-flex;
    align-items: center;
    gap: 5px;
  }
  .nav-item > a .caret {
    transition: transform .2s ease;
    color: var(--ink-4);
  }
  .nav-item:hover > a .caret { transform: rotate(180deg); color: var(--primary); }
  .nav-menu {
    position: absolute;
    top: 100%;
    left: 50%;
    transform: translate(-50%, 6px);
    min-width: 232px;
    background: #fff;
    border: 1px solid var(--card-border);
    border-radius: var(--card-radius);
    box-shadow: 0 12px 34px rgba(11,30,63,0.12), 0 2px 8px rgba(11,30,63,0.06);
    padding: 8px;
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transition: opacity .16s ease, transform .16s ease;
    z-index: 60;
  }
  .nav-item:hover .nav-menu,
  .nav-item:focus-within .nav-menu {
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
    transform: translate(-50%, 0);
  }
  .nav-menu::before {
    content: "";
    position: absolute;
    top: -6px;
    left: 0;
    right: 0;
    height: 6px;
  }
  .nav-menu a {
    display: block;
    padding: 10px 12px;
    border-radius: calc(var(--card-radius) - 8px); /* concentric inside the 8px-padded .nav-menu */
    font-size: var(--fs-body);
    font-weight: 500;
    color: var(--ink-2);
    /* 1.25, matching the button family — at --fs-body, line-height here is
       setting a control's height, not a reading rhythm. */
    line-height: 1.25;
  }
  .nav-menu a .sub {
    display: block;
    font-size: var(--fs-micro);
    font-weight: 400;
    color: var(--ink-4);
    margin-top: 2px;
  }
  .nav-menu a:hover { background: var(--bg); color: var(--primary); }
  .nav-menu a:hover .sub { color: var(--ink-3); }
  .nav-menu a.nav-menu__link--active { background: var(--bg); color: var(--primary); }
  .nav-menu a.nav-menu__link--active .sub { color: var(--ink-3); }
  /* The parent link still navigates on desktop, so the overview entry would be
     a duplicate; it exists for mobile, where the parent only toggles.
     Qualified with `.nav-menu a` because the block rule above is (0,1,1) — a
     bare `.nav-menu__overview` is (0,1,0) and loses to it at every width. */
  .nav-menu a.nav-menu__overview { display: none; }
  /* Escape dismissal — WCAG 1.4.13, which wants hover/focus content to be
     dismissible without moving the pointer. The reveal above stays CSS-driven
     so the menu still opens with JS off; this class is all that JS adds, and
     nav.js clears it once the pointer or focus leaves the item. Specificity
     (0,3,0) ties with the :hover rule above and wins on source order. */
  .nav-item.nav-item--dismissed .nav-menu {
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
  }

.nav__cta:hover { background: var(--primary-2); border-color: var(--primary-2); }
/* Same non-shrinking, font-size-independent sizing as the page CTAs (see
   BUTTONS); the nav arrow is a 12px icon rather than 14px. */
.nav__arrow { transition: transform .2s ease; flex-shrink: 0; width: 12px; height: 12px; }
.nav__cta:hover .nav__arrow { transform: translateX(2px); }

/* hamburger toggle — mobile only */
.nav__toggle {
  display: none;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--ink-2);
  cursor: pointer;
}
.nav__toggle-close { display: none; }
.nav--open .nav__toggle-open { display: none; }
.nav--open .nav__toggle-close { display: block; }

/* Page scroll lock while the mobile menu is open. overscroll-behavior on the
   panel stops scroll chaining, but a drag that starts outside the panel — on
   the header bar, or the sliver of page beside it — still scrolls the page
   under the sticky menu, and it stays displaced after the menu closes.
   position: fixed rather than overflow: hidden because iOS Safari ignores the
   latter on body. Taking body out of flow discards the scroll position, so
   nav.js captures it into --nav-scroll-top (already negated) and scrolls back
   to it on close. Applied to <html> so the class is set before layout reads. */
.nav-locked body {
  position: fixed;
  top: var(--nav-scroll-top, 0px);
  left: 0;
  right: 0;
  overflow: hidden;
  /* Locking collapses the page's classic scrollbar; pad the gap back so the
     content and the sticky header don't shift sideways as it goes. Overlay
     scrollbars (touch devices, macOS default) measure 0 and this is a no-op. */
  padding-right: var(--nav-scrollbar-gap, 0px);
}

@media (max-width: 860px) {
  .nav__toggle { display: inline-flex; }
  .nav__links { display: none; }
  .nav--open .nav__links {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    background: #fff;
    border-bottom: 1px solid var(--rule-2);
    box-shadow: 0 12px 34px rgba(11,30,63,0.12);
    padding: 8px 0 16px;
    max-height: calc(100vh - 76px);
    overflow-y: auto;
    /* The panel is its own scroll container, so once it reaches its limit the
       browser chains the leftover delta to the page behind it. With an
       accordion open the panel is taller than a short landscape viewport, so
       that happens on an ordinary swipe. `contain` keeps the delta here.
       No touch-action: the default (pan-y plus pinch) is what a vertical
       scroller wants, and restricting it would cost pinch-zoom (SC 1.4.4). */
    overscroll-behavior: contain;
  }
  .nav--open .nav__links > .nav__link,
  .nav--open .nav-item > a {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 13px 20px;
    font-size: var(--fs-body);
  }
  .nav--open .nav-item {
    display: block;
    position: static;
  }
  /* dropdowns become inline accordions: JS toggles .nav-item--open */
  .nav--open .nav-menu {
    display: none;
    position: static;
    transform: none;
    min-width: 0;
    border: 0;
    border-radius: 0;
    box-shadow: none;
    background: var(--bg);
    padding: 4px 8px;
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
    transition: none;
  }
  .nav--open .nav-item--open .nav-menu { display: block; }
  /* The parent link toggles rather than navigates at this width, so the section
     landing page needs its own entry (see header.php). Carries the same
     `.nav-menu a` qualifier as the desktop rule it has to outrank. */
  .nav--open .nav-menu a.nav-menu__overview { display: block; }
  /* Dismissal is a desktop-only concern: the accordion is click-toggled, and
     Escape closes the whole panel. */
  .nav--open .nav-item.nav-item--dismissed .nav-menu {
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
  }
  /* the desktop :hover/:focus-within reveal (higher specificity) must not
     re-apply its translate(-50%) once the menu flows inline */
  .nav--open .nav-item:hover .nav-menu,
  .nav--open .nav-item:focus-within .nav-menu { transform: none; }
  .nav--open .nav-item--open > a .caret { transform: rotate(180deg); color: var(--primary); }
  .nav--open .nav-menu::before { display: none; }
  .nav--open .nav__cta {
    align-self: flex-start;
    margin: 12px 20px 0;
  }
}

/* ===================== HERO ===================== */
.hero {
  /* Per-page knobs — override these inline on the .hero element.
     See README: styles.css — Hero per-page knobs. */
  --hero-image: url("/assets/static/img/world-wireframe.svg");
  --hero-overlay-top: rgba(23, 74, 122, .8);
  --hero-overlay-bottom: rgba(9, 35, 59, .8);
  --hero-size: 100% 100%, 200% auto;
  --hero-pos: 0 0, left -17vw;
  /* Text colours are knobs too, because what the type sits on is a composite of
     the overlay and whatever the image happens to be — a photograph light enough
     can defeat a tint that works everywhere else. See --hero-scrim below.
     Both defaults are set against the *composited* backdrop, not the flat --ink
     token: world-wireframe.svg's landmasses are #e5e5e5, so the .8 overlay lands
     at ~#40698f rather than near --ink. --slate clears the 3:1 large-text
     threshold there; --primary-3 did not. The labels are 12px, so they need
     4.5:1, which --slate misses — white at .85 is the lightest step that
     passes while keeping them visibly quieter than .hero__meta-value (#fff).

     The measured ladder on that ~#40698f composite, which is the evidence base
     for every white-at-alpha choice in this file:
         white .55  2.96:1      white .72  3.88:1
         white .78  4.24:1      white .85  4.68-4.70:1  <- the vetted step
         white 1.0  5.78:1
     Anything at 16-20px is normal text and needs 4.5:1, so .85 is the floor, not
     a preference. Quieten hero text by SIZE, not by dropping below it — dimming
     a lede to look like a footnote is what put .hero__lede, .lp__tagline,
     .lp__foot and .err__lede below the threshold in the first place.
     (Moved here 2026-08-17 from .hero__provenance, a rule no template ever used;
     the reasoning outlived the markup and is worth more than the rule was.) */
  --hero-label: rgba(255, 255, 255, .85);
  --hero-accent: var(--slate);
  --hero-scrim: none;
  background-color: var(--ink);
  background-image:
    linear-gradient(var(--hero-overlay-top), var(--hero-overlay-bottom)),
    var(--hero-image);
  background-repeat: no-repeat;
  background-position: var(--hero-pos);
  background-size: var(--hero-size);
  background-attachment: scroll;
  position: relative;
  overflow: hidden;
  /* Deliberately NOT --section-padding, which every other section on the site
     uses. The band opens under the sticky nav rather than between two sections,
     and its bottom edge closes a page-title block rather than a content block —
     a tighter close there is the design, not drift. Reviewed against the
     site-wide spacing audit and kept as an exception; do not "correct" it to
     var(--section-padding) without changing the hero design with it. */
  padding: clamp(60px, 8vw, 110px) 0 clamp(40px, 6vw, 80px);
}
/* Below the .hero__top stack breakpoint the band grows tall (stacked meta,
   full-width CTAs, a title held at the 44px clamp floor) while the artwork
   shrinks with it: at `200% auto` a 2:1 image renders exactly one viewport-width
   tall, and --hero-pos lifts it a further 17vw, so the wireframe ends at 83vw —
   ~266px into a ~980px hero at 320px wide. The rest of the band was flat --ink
   below a hard horizontal seam.
   The artwork is deliberately NOT resized to close that gap. Anything that fills
   a content-driven height (cover, or a percentage tuned to the tallest hero)
   makes the wireframe's scale depend on how much copy a page carries, so the map
   would render at a different zoom on every page. Scale stays viewport-driven
   and therefore identical site-wide.
   Instead the seam is removed: the overlay layer is sized to the artwork band
   (83vw) and ends on opaque --ink, which is also the background-color, so the
   image resolves into the page and its bottom edge has nothing to give it away.
   Same treatment contact.php applies to hero_contact.webp, for the same reason.
   Pages that set --hero-size and --hero-overlay-bottom inline (about,
   imaging-core-lab) are untouched: inline styles beat any stylesheet rule. */
@media (max-width: 820px) {
  .hero {
    --hero-size: 100% 83vw, 200% auto;
    --hero-overlay-bottom: var(--ink);
  }
}
/* Optional scrim over the overlay, for heroes whose photograph is bright where
   the type sits. Deliberately a separate layer from --hero-overlay-*: raising
   the overlay enough to fix contrast flattens the whole photograph, whereas a
   scrim can be weighted to the top of the band — where the eyebrow, meta and
   title are — and fade out completely before the image's focal area. */
.hero::before {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--hero-scrim);
  pointer-events: none;
}
/* The scrim is a positioned pseudo-element, so the content needs to be
   positioned too in order to paint above it. */
.hero > .container { position: relative; }
.hero__top {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 40px;
  align-items: end;
  margin-bottom: clamp(40px, 5vw, 64px);
}
@media (max-width: 820px) {
  .hero__top { grid-template-columns: 1fr; align-items: start; }
}
.hero__eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  color: var(--hero-label);
}
.hero__dot {
  width: 8px; height: 8px;
  border-radius: var(--radius-round);
  background: var(--primary-3);
  box-shadow: 0 0 0 4px rgba(90,139,220,0.22);
}
.hero__eyebrow .label { color: var(--hero-label); }

/* No `color` here on purpose. It used to declare var(--ink-3) — a light-surface
   prose colour, which measures 1.20:1 on the hero composite. Nothing rendered it,
   because every child is either a .label or a .hero__meta-value and both override
   it, but it meant any bare text added directly to this block would inherit an
   effectively invisible colour and read as a missing element rather than as a
   contrast bug. Inheriting the hero's own colour is the safe default. */
.hero__meta {
  display: flex;
  gap: 28px;
  align-items: center;
}
.hero__meta-value { color: #fff; font-weight: 600; }
.hero__meta-item { display: flex; flex-direction: column; gap: 4px; }
.hero__meta-item .label { font-size: var(--fs-micro); }
.hero__meta .label { color: var(--hero-label); }
@media (max-width: 820px) {
  .hero__dot { flex-shrink: 0; }
  .hero__meta { align-items: flex-start; }
}

.hero__title {
  font-family: var(--display);
  font-weight: 600;
  font-size: var(--fs-display);
  line-height: 0.96;
  letter-spacing: -0.035em;
  margin: 0;
  color: #fff;
  text-wrap: balance;
}
.nowrap {
    white-space: nowrap;
}
.hero__accent {
  color: var(--hero-accent);
  font-style: italic;
  font-weight: 500;
}

/* The actions follow the lede they act on rather than sitting in a separate
   column across the hero; the right-hand space belongs to the artwork. */
.hero__bottom {
  margin-top: clamp(48px, 6vw, 80px);
  display: grid;
  gap: clamp(24px, 3vw, 36px);
  justify-items: start;
}
/* --hero-label, not a --mist alpha of its own. At 20px this is normal text and
   needs 4.5:1; rgba(234,240,249,0.78) measures 3.77:1 on the composited backdrop
   (see the --hero-label note above for why that backdrop, not flat --ink, is
   what the type sits on). --hero-label is the step already vetted at 4.68:1, and
   using the token rather than a literal means the pages that override it inline
   for a photograph carry the lede with them instead of leaving it behind.
   Hierarchy against .hero__title comes from size and weight, not from dimming
   this below the threshold. */
.hero__lede {
  font-size: var(--fs-h4);
  color: var(--hero-label);
  max-width: 46ch;
  line-height: 1.55;
  text-wrap: pretty;
}
.hero__actions {
  display: flex;
  gap: 12px;
  flex-wrap: wrap;
}

@media (max-width: 600px) {
  /* Three inline meta items are too narrow at phone widths to hold their
     values on one line, so they wrap raggedly. A two-up grid gives each item
     a full line and keeps the labels and values aligned. */
  .hero__meta {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 18px 20px;
  }
  /* Stacked CTAs share one width instead of sizing to their labels. */
  .hero__actions > *,
  .widen__actions > *,
  .contact__actions > * { flex: 1 1 100%; }
}

/* ===================== NETWORK MAP ===================== */
.network {
  position: relative;
  background: var(--paper);
  padding: var(--section-padding) 0;
  overflow: hidden;
}
.network__head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 24px;
  flex-wrap: wrap;
  margin-bottom: 28px;
}
.network__title {
  font-family: var(--display);
  font-weight: 500;
  font-size: var(--fs-h3);
  margin: 0;
  color: var(--ink);
  letter-spacing: -0.02em;
}
.network__title strong { color: var(--primary); font-weight: 600; }
.network__legend {
  display: flex; gap: 24px; flex-wrap: wrap;
  color: var(--ink-3);
  font-size: var(--fs-micro);
}
.network__lede {
  display: grid;
  /* min() so the 300px track floor cannot exceed the container. Below ~340px the
     content box is 280px, the bare 300px minimum won the sizing, and the block
     rendered 300px wide flush to the right screen edge — no clipping (.network
     is overflow:hidden) but no 20px gutter either, while every other section kept
     one. Above 340px this resolves to 300px exactly as before. */
  grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr));
  gap: 22px 56px;
  margin: 0 0 40px;
  padding-top: 26px;
  border-top: 1px solid var(--rule-2);
}
.network__lede .network__lead {
  grid-column: 1 / -1;
  margin: 0;
  max-width: 860px;
  font-family: var(--display);
  font-weight: 500;
  font-size: var(--fs-h3);
  line-height: 1.42;
  letter-spacing: -0.02em;
  color: var(--ink);
  text-wrap: pretty;
}
.network__lede p {
  margin: 0;
  color: var(--ink-2);
  font-size: var(--fs-body);
  line-height: 1.55;
  text-wrap: pretty;
}
.network__lede strong { color: var(--primary); font-weight: 600; }
.network__mapbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  margin-bottom: 12px;
}
.network__swatch {
  display: inline-block;
  width: 8px; height: 8px;
  border-radius: var(--radius-round);
  margin-right: 8px;
  transform: translateY(-1px);
}
.network__swatch--hq { background: var(--ink); }
.network__swatch--site { background: var(--primary); }
.network__canvas {
  position: relative;
  aspect-ratio: 16 / 7;
  border: 1px solid var(--card-border);
  border-radius: var(--card-radius);
  background: #fff;
  overflow: hidden;
  padding: 24px;
}
/* The world map, extracted to /assets/static/img/world-map.svg and referenced as
   an <img>. It was previously an inline <svg> with no rule of its own: an <svg>
   with no width/height attributes defaults to 100% x 100%, so it sized itself.
   An <img> has no such default, so the same box has to be stated here — this
   rule exists to keep the rendering identical, not to change it.
   object-fit is left at its default `fill`, which is load-bearing: the file
   carries preserveAspectRatio="none", and the two together stretch the 1500x700
   viewBox to whatever box the canvas gives it, exactly as the inline SVG did. */
.map-svg { width: 100%; height: 100%; }

.network__veil {
  position: absolute; inset: 0;
  background:
    radial-gradient(ellipse 60% 80% at 50% 50%, transparent 0%, rgba(255,255,255,0.5) 80%),
    linear-gradient(180deg, rgba(255,255,255,0.0) 50%, rgba(255,255,255,0.5) 100%);
  pointer-events: none;
}
.network__pins { position: absolute; inset: 0; pointer-events: none; }
.pin {
    position: absolute;
    transform: translate(-50%, -100%);
}
  .pin-marker {
    border-radius: var(--radius-round) var(--radius-round) var(--radius-round) 0;
    transform: rotate(-45deg);
    background: var(--primary);
    display: grid; place-items: center;
    border: 2px solid #fff;
    box-shadow: 0 7px 16px -5px rgba(48,98,176,0.55), 0 1px 2px rgba(11,30,63,0.18);
  }
  .pin-marker span {
    transform: rotate(45deg);
    color: #fff;
    font-family: var(--display);
    font-weight: 700;
    line-height: 1;
    letter-spacing: -0.02em;
  }
  .pin.hq .pin-marker {
    background: var(--ink);
    box-shadow: 0 7px 16px -5px rgba(11,30,63,0.6), 0 1px 2px rgba(11,30,63,0.25);
  }
  .pin.hq .pin-marker span {
    width: 7px; height: 7px; border-radius: var(--radius-round); background: #fff;
  }
  .pin-label {
    position: absolute;
    top: 100%; left: 50%;
    transform: translate(-50%, 4px);
    font-family: var(--mono);
    /* Was a raw 10px — the only raw font-size on live chrome (the other 13 are
       inside the aria-hidden mock-UI, which is deliberately off-scale).
       --fs-micro is the nearest step. NOTE: this is +2px on nowrap absolutely
       positioned labels over a dense map; check the European cluster on / for
       collisions before shipping. Revert to 10px here if they touch. */
    font-size: var(--fs-micro);
    letter-spacing: 0.07em;
    text-transform: uppercase;
    color: var(--ink-2);
    background: #fff;
    border: 1px solid var(--rule);
    padding: 3px 7px;
    border-radius: var(--radius-xs);
    white-space: nowrap;
  }
  .pin.hq .pin-label { color: var(--ink); border-color: var(--ink); font-weight: 600; }

.network__corner {
  position: absolute;
  font-family: var(--mono);
  font-size: var(--fs-micro);
  color: var(--ink-4);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  z-index: 2;
}
.network__corner--tl { top: 18px; left: 22px; }
.network__corner--tr { top: 18px; right: 22px; color: var(--primary); }
.network__corner--tr::before {
  content: ""; display: inline-block;
  width: 6px; height: 6px;
  border-radius: var(--radius-round);
  background: var(--primary);
  margin-right: 8px;
  transform: translateY(-1px);
  animation: pulse 2s ease-in-out infinite;
}
@keyframes pulse {
  0%, 100% { opacity: 1; transform: translateY(-1px) scale(1); }
  50% { opacity: 0.4; transform: translateY(-1px) scale(0.85); }
}
/* The dot is the one thing on the site that animates on its own, indefinitely
   and without a control (SC 2.2.2). It is decorative, so the honest fix is to
   stop it for anyone who has asked for less motion and leave it static. */
@media (prefers-reduced-motion: reduce) {
  .network__corner--tr::before { animation: none; }
}
.network__corner--bl { bottom: 18px; left: 22px; }
.network__corner--br { bottom: 18px; right: 22px; }

/* ===================== SECTION SHELL =====================
   --section-padding is the site's one section rhythm: the distance from a
   section's own edge to the first/last visible pixel of what it holds. Nothing
   nested between the two may add to it — a wrapper margin or a trailing
   paragraph margin makes a section that looks identical in its own CSS render a
   different gap. Where a component could contribute one, it is zeroed at the
   component (.about__body, .timeline-list, .legal in legal.css), not here.
   The one deliberate exception is .hero — see its own padding rule. */
.section { padding: var(--section-padding) 0; }
.section__head {
  display: grid;
  grid-template-columns: 1.35fr 0.65fr;
  gap: clamp(32px, 6vw, 80px);
  align-items: end;
  margin-bottom: clamp(48px, 7vw, 80px);
}
/* align-items: end above bottom-aligns the kicker against the title, which is
   the intent — but it aligns BOTH columns, so whichever is shorter gets pushed
   down by the height difference. The title column holds the eyebrow, i.e. the
   section's first visible content, so on any section whose kicker copy runs
   longer than its title the whole section appeared to start late: measured
   drops ran from 15px up to 143px, varying with nothing but how long that
   page's kicker sentence happened to be. Pinning the first column to the top
   fixes the start position while leaving the kicker end-aligned, so the
   intended look survives wherever the title column is the taller one. */
.section__head > :first-child { align-self: start; }
@media (max-width: 820px) {
  .section__head { grid-template-columns: 1fr; }
}
.section__title {
  font-family: var(--display);
  font-weight: 600;
  font-size: var(--fs-h2);
  line-height: 1.04;
  letter-spacing: -0.025em;
  margin: 14px 0 0;
  color: var(--ink);
  text-wrap: balance;
}
.section__kicker {
  /* A <p> that declared no margin at all, so it carried the UA's 1em top AND
     bottom — 20px at this font-size. The top margin made the kicker, not the
     eyebrow, the first inked pixel on every section where it out-ran the title
     column; the bottom margin silently grew .section__head. All spacing around
     the head belongs to .section__head itself. */
  margin: 0;
  font-size: var(--fs-h4);
  color: var(--ink-3);
  line-height: 1.55;
  text-wrap: pretty;
  max-width: 50ch;
}
/* The kicker is body copy, so its links take the prose treatment (see the note
   at the `a` reset). Every one of them is the /publications link that closes a
   "science behind this" section, and without this they inherited the reset —
   the same colour as the sentence around them, no underline, nothing to see. */
.section__kicker a { color: var(--primary); text-decoration: underline; text-underline-offset: 3px; }
.section__kicker a:hover { color: var(--primary-2); }
/* Single use (imaging-core-lab), where the kicker needs clearing from the
   heading above it. */
.section__kicker--spaced { margin-top: 24px; }

/* ===================== APPROACH ===================== */
.approach {
  background: var(--paper);
}
/* Hairline shell: the 1px gaps expose the grid's own background, so the cells
   read as separate cards without each needing a border that would double up
   between them. Same recipe as .certs__grid / .matters__list / .risk-list. */
.approach__grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1px;
  background: var(--rule);
  border: 1px solid var(--card-border);
  border-radius: var(--card-radius);
  overflow: hidden;
}
.approach__grid--two { grid-template-columns: 1fr 1fr; }
@media (max-width: 820px) {
  .approach__grid,
  .approach__grid--two { grid-template-columns: 1fr; }
}

/* Corner cells pick up the shell's curve. The shell owns the rounded corner and
   clips the cells to it, so square cells look right at rest. On hover
   .card-link paints `inset 0 0 0 1px` — a ring shaped to the cell's own square
   border-box — and the clip slices it at the curve, leaving a straight-cut
   hairline in the corner. Rounding the cells that sit at the shell's corners to
   its inner radius (outer minus the 1px border, concentric) puts the ring on the
   same curve. No-op at rest: the fill was already clipped to it.
   Scoped to .card-link because only those cells carry the hover ring, and only
   their grids (therapeutic-areas 3, solutions 6, resources 2) are full
   rectangles — the link-less grids close a short last row with
   .approach__cell--wide, where these corner picks would land mid-grid. */
.approach__grid > .card-link:first-child       { border-top-left-radius:     calc(var(--card-radius) - 1px); }
.approach__grid > .card-link:nth-child(3)      { border-top-right-radius:    calc(var(--card-radius) - 1px); }
.approach__grid > .card-link:nth-last-child(3) { border-bottom-left-radius:  calc(var(--card-radius) - 1px); }
.approach__grid > .card-link:last-child        { border-bottom-right-radius: calc(var(--card-radius) - 1px); }
/* Two columns: the outer corners move in one track, so the three-column picks
   above have to be cleared where they now land mid-grid. Reset first, set after. */
.approach__grid--two > .card-link:nth-child(3),
.approach__grid--two > .card-link:nth-last-child(3) { border-radius: 0; }
.approach__grid--two > .card-link:nth-child(2)      { border-top-right-radius:   calc(var(--card-radius) - 1px); }
.approach__grid--two > .card-link:nth-last-child(2) { border-bottom-left-radius: calc(var(--card-radius) - 1px); }
@media (max-width: 820px) {
  /* Single column — every outer corner sits on the first or the last cell. */
  .approach__grid > .card-link              { border-radius: 0; }
  .approach__grid > .card-link:first-child  { border-radius: calc(var(--card-radius) - 1px) calc(var(--card-radius) - 1px) 0 0; }
  .approach__grid > .card-link:last-child   { border-radius: 0 0 calc(var(--card-radius) - 1px) calc(var(--card-radius) - 1px); }
}
.approach__cell {
  background: var(--card-on-white);
  padding: 40px 36px 36px;
  display: flex;
  flex-direction: column;
  gap: 20px;
  min-height: 320px;
  position: relative;
}
.approach__step {
  position: absolute;
  top: 22px; right: 24px;
  font-family: var(--mono);
  font-size: var(--fs-micro);
  color: var(--ink-4);
  letter-spacing: 0.1em;
}
.approach__step--primary { color: var(--primary-2); }
.approach__cell h3 {
  font-family: var(--display);
  font-weight: 600;
  font-size: var(--fs-h4);
  margin: 0;
  letter-spacing: -0.01em;
  color: var(--ink);
  line-height: 1.15;
}
.approach__cell p {
  margin: 0;
  color: var(--ink-3);
  font-size: var(--fs-body);
  line-height: 1.55;
}
.approach__tag {
  margin-top: auto;
  font-family: var(--mono);
  font-size: var(--fs-micro);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--primary-2);
  padding-top: 18px;
  border-top: 1px solid var(--rule-2);
}

/* Muted approach variant (Capabilities) — grey section, white cells */
.approach--muted,
.widen {
  background: var(--bg);
}
.approach--muted .approach__cell { background: var(--card-on-muted); }
.approach--muted .section__title .title-accent { color: var(--primary); }

/* ===================== WIDEN ===================== */
.widen__card {
  background: var(--ink);
  color: #fff;
  border-radius: var(--radius-2xl);
  overflow: hidden;
  position: relative;
  padding: clamp(40px, 5vw, 72px);
  isolation: isolate;
}
.widen__card::before {
  content: "";
  position: absolute;
  inset: 0;
  background:
    radial-gradient(ellipse 70% 50% at 100% 0%, rgba(48,98,176,0.45), transparent 70%),
    radial-gradient(ellipse 60% 50% at 0% 100%, rgba(90,139,220,0.16), transparent 70%);
  z-index: -1;
}
.widen__grid {
  display: grid;
  grid-template-columns: 0.95fr 1.05fr;
  gap: clamp(40px, 6vw, 80px);
  align-items: center;
}
@media (max-width: 980px) {
  .widen__grid { grid-template-columns: 1fr; }
}
.widen__logo {
  height: 36px;
  width: auto;
  margin-bottom: 32px;
  filter: brightness(0) saturate(100%) invert(89%) sepia(11%) saturate(437%) hue-rotate(187deg) brightness(95%) contrast(86%);
}
.widen__card h2 {
  font-family: var(--display);
  font-weight: 600;
  font-size: var(--fs-h2);
  line-height: 1.04;
  letter-spacing: -0.025em;
  margin: 0;
  text-wrap: balance;
}
.widen__lede {
  color: rgba(234, 240, 249, 0.78);
  font-size: var(--fs-h4);
  margin-top: 24px;
  max-width: 50ch;
  line-height: 1.55;
}
.widen__feats {
  list-style: none;
  margin: 36px 0 0;
  padding: 0;
  display: grid;
  gap: 14px;
}
.widen__feats li {
  display: grid;
  grid-template-columns: 22px 1fr;
  gap: 14px;
  align-items: start;
  color: rgba(255,255,255,0.92);
  font-size: var(--fs-body);
  line-height: 1.55;
}
.widen__check {
  width: 22px; height: 22px;
  border-radius: var(--radius-round);
  background: var(--primary);
  color: #fff;
  display: grid; place-items: center;
  margin-top: 1px;
}
.widen__actions {
  margin-top: 44px;
  display: flex;
  gap: 12px;
  flex-wrap: wrap;
}
/* .widen__cta / .widen__link are the dark-surface primary and secondary —
   styled with the rest of the button system in the BUTTONS section. */

/* shared italic accent on dark cards */
.widen__card h2 em,
.contact__title .title-accent {
  font-style: italic;
  color: var(--slate);
  font-weight: 500;
}

/* ===================== WIDEN MOCK UI ===================== */
.widen__mock {
  background: #fff;
  border-radius: var(--card-radius);
  overflow: hidden;
  border: 1px solid rgba(255,255,255,0.1);
  box-shadow:
    0 30px 60px -20px rgba(0,0,0,0.4),
    0 12px 24px -10px rgba(0,0,0,0.25);
  color: var(--ink);
}
/* Caption under the mock. The interface above it is invented — a plausible trial
   name, a QA pass rate, review times — and nothing else on the page says so.
   It sits on the dark .widen__card, hence the white-derived colour.
   Do not move it inside .widen__mock: that element is aria-hidden, and the
   disclaimer is the one part of this illustration everyone needs. */
.mock__note {
  margin: 14px 0 0;
  font-family: var(--mono);
  font-size: var(--fs-micro);
  letter-spacing: 0.06em;
  /* 0.72 white on --ink measures 8.4:1 — this is 12px text, so it needs 4.5:1 */
  color: rgba(255, 255, 255, 0.72);
  text-align: right;
}
/* Mock is a desktop-app illustration — hide it on mobile viewports */
@media (max-width: 768px) {
  .widen__mock,
  /* The caption goes with it: a disclaimer for an illustration that is not
     rendered would be describing nothing. */
  .mock__note { display: none; }
}
.mock__bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 12px 16px;
  border-bottom: 1px solid var(--rule-2);
  background: var(--bg);
}
.mock__dots { display: flex; gap: 6px; }
.mock__dots span { width: 10px; height: 10px; border-radius: var(--radius-round); background: var(--rule); }
.mock__url {
  font-family: var(--mono);
  font-size: 11.5px;
  color: var(--ink-3);
  background: #fff;
  border: 1px solid var(--rule-2);
  padding: 4px 12px;
  border-radius: var(--radius-sm);
  flex: 1;
  margin: 0 14px;
  text-align: center;
}
.mock__badge {
  font-family: var(--mono);
  font-size: 10.5px;
  color: var(--primary);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  font-weight: 500;
}
.mock__body {
  display: grid;
  grid-template-columns: 180px 1fr;
  min-height: 380px;
}
.mock__side {
  background: var(--bg);
  border-right: 1px solid var(--rule-2);
  padding: 16px 14px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.mock__label {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-4);
  margin: 14px 8px 6px;
}
.mock__item {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 10px;
  font-size: 13px;
  color: var(--ink-2);
  border-radius: var(--radius-sm);
}
.mock__item--active {
  background: #fff;
  color: var(--ink);
  box-shadow: 0 1px 2px rgba(13,31,60,0.06);
  font-weight: 600;
}
.mock__bullet { width: 6px; height: 6px; border-radius: var(--radius-round); background: var(--ink-4); }
.mock__item--active .mock__bullet { background: var(--primary); }
.mock__main { padding: 18px 22px; display: flex; flex-direction: column; gap: 16px; }
.mock__header { display: flex; align-items: center; justify-content: space-between; }
.mock__title {
  font-family: var(--display);
  font-weight: 600;
  font-size: 16px;
  color: var(--ink);
}
.mock__pills { display: flex; gap: 6px; }
.pill {
  font-family: var(--mono);
  font-size: 10.5px;
  color: var(--ink-3);
  border: 1px solid var(--rule-2);
  background: #fff;
  padding: 4px 8px;
  border-radius: var(--radius-pill);
  letter-spacing: 0.04em;
}
.pill--live { color: var(--primary); border-color: rgba(48,98,176,0.3); background: var(--mist); }
.pill--live::before {
  content: ""; display: inline-block;
  width: 6px; height: 6px; background: var(--primary); border-radius: var(--radius-round);
  margin-right: 6px; transform: translateY(-1px);
}
.mock__kpis { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; }
.kpi {
  border: 1px solid var(--rule-2);
  border-radius: var(--radius-md);
  padding: 11px 13px;
}
.kpi__label {
  font-family: var(--mono);
  font-size: 9.5px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-4);
}
.kpi__value {
  font-family: var(--display);
  font-weight: 600;
  font-size: 20px;
  color: var(--ink);
  letter-spacing: -0.01em;
  margin-top: 2px;
}
.kpi__value small { color: var(--primary); font-size: 12px; font-weight: 500; margin-left: 4px; }
.mock__scans { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; }
.scan {
  aspect-ratio: 1 / 1;
  border-radius: var(--radius-sm);
  background:
    radial-gradient(circle at 50% 50%, var(--ink) 0%, var(--ink-2) 28%, #6e89b8 60%, #d4dcec 100%);
  border: 1px solid var(--rule-2);
  position: relative;
  overflow: hidden;
}
.scan::before {
  content: "";
  position: absolute;
  inset: 0;
  background:
    repeating-linear-gradient(0deg, rgba(255,255,255,0.04) 0px, rgba(255,255,255,0.04) 1px, transparent 1px, transparent 4px);
}
/* Solid fills, not tints: these are real DOM text at 10px, so white-on-fill has
   to clear 4.5:1 on its own — --primary gives 6.1:1, --warn 6.8:1. */
.scan__tag {
  position: absolute;
  top: 6px; left: 6px;
  font-family: var(--mono);
  font-size: 10px;
  color: #fff;
  background: var(--primary);
  padding: 1px 5px;
  border-radius: var(--radius-xs);
}
.scan__tag--warn { background: var(--warn); }
.timeline {
  border: 1px solid var(--rule-2);
  border-radius: var(--radius-md);
  padding: 12px;
}
.timeline__row {
  display: grid;
  grid-template-columns: 70px 1fr 60px;
  gap: 10px;
  align-items: center;
  font-size: 11.5px;
  color: var(--ink-3);
  padding: 5px 0;
}
.timeline__row + .timeline__row { border-top: 1px dashed var(--rule-2); }
.timeline__bar { height: 6px; background: var(--rule-2); border-radius: var(--radius-pill); overflow: hidden; }
.timeline__fill { display: block; height: 100%; background: var(--primary); border-radius: var(--radius-pill); }
.timeline__fill--w86 { width: 86%; }
.timeline__fill--w72 { width: 72%; }
.timeline__fill--w64 { width: 64%; }
.timeline__fill--w55 { width: 55%; }
.timeline__name { font-family: var(--mono); font-size: 10.5px; color: var(--ink-2); }
.timeline__pct { font-family: var(--mono); font-size: 10.5px; color: var(--ink); text-align: right; }

/* ===================== NUMBERS ===================== */
.numbers { background: #fff; }
.numbers__grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  border: 1px solid var(--card-border);
  border-radius: var(--card-radius);
  overflow: hidden;
}
@media (max-width: 820px) {
  .numbers__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 600px) {
  .numbers__grid { grid-template-columns: minmax(0, 1fr); }
}
.numbers__cell {
  padding: 40px 32px;
  border-right: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
  background: #fff;
  position: relative;
}
@media (min-width: 821px) {
  .numbers__cell:nth-child(4n) { border-right: 0; }
  .numbers__cell:nth-last-child(-n+4) { border-bottom: 0; }
}
@media (min-width: 601px) and (max-width: 820px) {
  .numbers__cell:nth-child(2n) { border-right: 0; }
  .numbers__cell:nth-last-child(-n+2) { border-bottom: 0; }
}
@media (max-width: 600px) {
  .numbers__cell { border-right: 0; }
  .numbers__cell:last-child { border-bottom: 0; }
}
.numbers__label {
  font-family: var(--mono);
  font-size: var(--fs-micro);
  color: var(--ink-4);
  letter-spacing: 0.1em;
  text-transform: uppercase;
}
.numbers__value {
  font-family: var(--display);
  font-weight: 600;
  font-size: var(--fs-h1);
  line-height: 1;
  letter-spacing: -0.035em;
  color: var(--ink);
  margin: 22px 0 12px;
}
.numbers__value span { color: var(--primary); }
.numbers__desc {
  font-size: var(--fs-body);
  color: var(--ink-3);
  line-height: 1.55;
}

/* ===================== ABOUT ===================== */
.about__grid {
  display: grid;
  grid-template-columns: 1fr 1.1fr;
  gap: clamp(40px, 6vw, 80px);
  align-items: start;
}
@media (max-width: 820px) {
  .about__grid { grid-template-columns: 1fr; }
}
.about__title {
  font-family: var(--display);
  font-weight: 600;
  font-size: var(--fs-h2);
  line-height: 1.04;
  letter-spacing: -0.025em;
  margin: 14px 0 0;
  color: var(--ink);
  text-wrap: balance;
}
.about__body p {
  color: var(--ink-3);
  font-size: var(--fs-body);
  line-height: 1.55;
  text-wrap: pretty;
  margin: 0 0 18px;
}
/* .about__body is the taller column of .about__grid, so whatever ends it sets
   the section's bottom edge. Most instances end on a .btn, a list or a note,
   all of which carry no bottom margin — but five end on a plain <p> and were
   handing the section 18px it never declared. Zeroed here rather than per page:
   the trailing margin is spacing BETWEEN paragraphs, and there is nothing after
   the last one to space it from. Specificity (0,2,0) clears .about__body p. */
.about__body > :last-child { margin-bottom: 0; }
.about__body .btn { margin-top: 32px; }
/* Inline prose links only — a .btn in this column keeps its own label colour. */
.about__body a:not(.btn) {
  color: var(--primary);
  text-decoration: underline;
  text-underline-offset: 3px;
  transition: color .15s;
}
/* Was border-bottom-color with no border-width or -style declared anywhere on
   this selector, so the computed width was 0px at rest, hover and focus alike —
   the hover underline never rendered, on the most-used prose-link context on the
   site. The underline is now permanent (see the note at the `a` reset) and hover
   moves the colour, which is what every other prose link here already does. */
.about__body a:not(.btn):hover { color: var(--primary-2); }
.about__disciplines {
  margin-top: 32px;
  display: grid;
  /* These cards sit in the narrower half of .about__grid, so the track count
     follows the space available rather than being fixed at three — at body
     size, three columns here leave five words per line. */
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 12px;
}
@media (max-width: 600px) {
  .about__disciplines { grid-template-columns: 1fr; }
}
.discipline {
  border: 1px solid var(--card-border);
  border-radius: var(--card-radius);
  padding: 18px;
  background: var(--card-on-white);
}
.about--muted .discipline { background: var(--card-on-muted); }
.discipline .label { color: var(--primary); margin-bottom: 8px; }
.discipline__title {
  font-family: var(--display);
  font-weight: 600;
  font-size: var(--fs-h4);
  color: var(--ink);
  letter-spacing: -0.01em;
}
.discipline__desc {
  margin-top: 4px;
  font-size: var(--fs-body);
  color: var(--ink-3);
  line-height: 1.55;
}

/* ===================== CONTACT ===================== */
.contact-section { padding-top: var(--section-padding); }
.contact {
  background: var(--ink);
  color: #fff;
  border-radius: var(--radius-2xl);
  /* Bottom margin is the section rhythm, so the footer rule sits the same
     distance below this card as every other section boundary. */
  margin: 0 var(--gutter) var(--section-padding);
  padding: clamp(56px, 7vw, 96px) 0;
  position: relative;
  overflow: hidden;
  isolation: isolate;
}
.contact__wrap {
  padding-left: max(32px, (100vw - var(--maxw)) / 2);
  padding-right: max(32px, (100vw - var(--maxw)) / 2);
  position: relative;
  z-index: 1;
}
.contact::before {
  content: "";
  position: absolute;
  inset: 0;
  background:
    radial-gradient(circle at 85% 30%, rgba(181,196,222,0.16), transparent 45%),
    radial-gradient(circle at 15% 90%, rgba(48,98,176,0.22), transparent 50%);
  z-index: -1;
}
.contact__globe {
  position: absolute;
  right: -10%;
  top: 50%;
  transform: translateY(-50%);
  /* The stroke lives here, not on the SVG. It used to be a stroke="#b5c4de"
     presentation attribute repeated on 20 pages; var() does not work in
     presentation attributes, so tokenising it means declaring it in CSS. The
     ellipses carry no stroke of their own and inherit this, exactly as they
     inherited the attribute. */
  stroke: var(--slate);
  /* 65% of the card, but never below 420px: the card narrows faster than the
     motif should, so below ~700px the globe holds its size and crops against
     the card's overflow: hidden instead of shrinking away. */
  width: max(65%, 420px);
  max-width: 760px;
  aspect-ratio: 1 / 1;
  opacity: 0.14;
  pointer-events: none;
  z-index: -1;
}
/* stroke-width is in viewBox units, so it thins to ~0.3px once the SVG is
   scaled down — at 0.14 opacity that antialiases to nothing. Pin it to 1px. */
.contact__globe > * { vector-effect: non-scaling-stroke; }
/* On phones the card is narrower than the globe either way, so anchor the
   globe to the card's left edge and let it crop off the right rather than
   hanging off the left. */
@media (max-width: 600px) {
  .contact__globe {
    right: auto;
    left: 10px;
    width: 500px;
  }
}
.contact__title {
  font-family: var(--display);
  font-weight: 600;
  font-size: var(--fs-h1);
  line-height: 1.02;
  letter-spacing: -0.03em;
  margin: 0;
  text-wrap: balance;
  max-width: 22ch;
}
.contact__lede {
  margin-top: 24px;
  color: rgba(234,240,249,0.72);
  font-size: var(--fs-h4);
  max-width: 56ch;
  line-height: 1.55;
}
.contact__actions {
  margin-top: 40px;
  display: flex;
  gap: 12px;
  flex-wrap: wrap;
}
.contact__meta {
  margin-top: 64px;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 24px;
  border-top: 1px solid rgba(255,255,255,0.12);
  padding-top: 36px;
}
@media (max-width: 720px) {
  .contact__meta { grid-template-columns: 1fr; }
}
/* 12px, so it needs 4.5:1 — and the backdrop is not flat --ink: .contact::before
   lays two decorative radial gradients over it, and the bottom-left one
   (rgba(48,98,176,.22) at 15% 90%) sits under this row. Measured against the
   composite at the worst position, .5 gave 4.55:1 — passing, but by 0.05, which
   any future change to those gradient alphas would spend. .62 measures 7.05:1 on
   flat --ink and 6.15:1 at the worst gradient position, and stays perceptibly
   quieter than .contact__meta-value (solid #fff) so the tonal hierarchy inside
   the block survives. Same recipe as --hero-label: white at reduced alpha. */
.contact__meta-label {
  font-family: var(--mono);
  font-size: var(--fs-micro);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: rgba(255,255,255,0.62);
}
.contact__meta-value {
  margin-top: 10px;
  font-size: var(--fs-body);
  color: #fff;
  line-height: 1.55;
}
.contact__meta-value a:hover { color: var(--slate); }

/* ===================== FOOTER ===================== */
.site-footer {
  border-top: 1px solid var(--rule-2);
  padding: 48px 0 56px;
  color: var(--ink-3);
  font-size: var(--fs-body);
}
.site-footer__row {
  display: grid;
  grid-template-columns: 2fr 1fr 1fr 1fr;
  gap: 40px;
  align-items: start;
}
/* The footer nav landmark wraps three of the four columns; display:contents
   keeps them as direct grid items of .site-footer__row, so the landmark costs
   nothing in layout terms. */
.site-footer__nav { display: contents; }
@media (max-width: 820px) {
  .site-footer__row { grid-template-columns: 1fr 1fr; }
  /* Targeted by class, not position: the columns no longer share one parent,
     so :first-child/:last-child would now match the wrong ones. */
  .site-footer__col--brand,
  .site-footer__col--contact { grid-column: 1 / -1; }
}
.site-footer__brand { margin-bottom: 16px; }
.site-footer__brand .brand__logo { height: 26px; }
.site-footer__tagline {
  margin: 16px 0 0;
  max-width: 36ch;
  color: var(--ink-3);
}
/* Column labels are <p>, not headings — they name the nav groups rather than
   sections of the document, so they no longer sit in the page heading outline.
   Every property a heading would have brought is set here, so the element is
   not carrying any of the appearance. */
.site-footer__col-title {
  font-family: var(--mono);
  font-size: var(--fs-micro);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-4);
  margin: 0 0 16px;
  font-weight: 500;
  line-height: 1.4;
}
.site-footer__col ul {
  margin: 0; padding: 0; list-style: none;
  display: flex; flex-direction: column; gap: 10px;
}
/* min-height carries the 24x24 minimum target size (SC 2.5.8). These were 22px
   tall: an inline anchor's target is its content box, which at 16px Manrope is
   the font's ascent+descent (~22px), not the 1.55 line box around it. inline-flex
   makes the box real so min-height applies; align-items keeps the label centred,
   so each row grows by 2px and nothing moves horizontally. The 10px gap between
   items is untouched, so centre-to-centre spacing stays ~34px.
   Covers all ten link types in the three nav columns, including the phone number
   and the email address. The postal address is a plain <li> and is unaffected. */
.site-footer__col a {
  display: inline-flex;
  align-items: center;
  min-height: 24px;
}
.site-footer__col a:hover { color: var(--primary); }
.site-footer__bottom {
  margin-top: 56px;
  border-top: 1px solid var(--rule-2);
  padding-top: 22px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: 12px;
  font-size: var(--fs-micro);
  color: var(--ink-4);
}
.iso {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-family: var(--mono);
  letter-spacing: 0.05em;
}
.iso__seal {
  height: 24px;
  display: grid; place-items: center;
  color: var(--ink-3);
  font-weight: 600;
}
.socials { display: flex; gap: 16px; }
.socials a {
  display: grid; place-items: center;
  color: var(--ink-3);
  transition: color .15s;
}
.socials a:hover { color: var(--primary); }

/* ============================================================
   PAGE COMPONENTS — shared by 2+ inner pages
   ============================================================ */

/* ---- Card link (solutions, therapeutic-areas) ---- */
.card-link { background: var(--paper); text-decoration: none; transition: background .15s ease, box-shadow .15s ease; }
.card-link:hover { background: var(--mist); box-shadow: inset 0 0 0 1px var(--rule); }
.card-link:hover h3 { color: var(--primary); }
.card-link h3 { transition: color .15s ease; }
.card-link__cta { display: inline-flex; align-items: center; gap: 8px; color: var(--primary-2); }
.card-link__cta svg { transition: transform .15s ease; flex-shrink: 0; width: 14px; height: 14px; }
.card-link:hover .card-link__cta svg { transform: translateX(3px); }
.sol-hero__second {
  color: rgba(234,240,249,0.72);
  font-size: var(--fs-h4);
  line-height: 1.55;
  max-width: 50ch;
  text-wrap: pretty;
}
.sol-hero__second strong { color: #fff; font-weight: 600; }

/* ---- Capabilities grid + editorial numerals (central-review, oncology,
        haematology, molecular-imaging-theranostics) ---- */
.caps-grid {
  display: grid;
  /* Every page using this grid holds five cards. A 280px track fits four
     across at desktop and strands the fifth alone on its own row; 330px
     resolves to three, so the set reads 3 + 2 and matches the three-column
     rhythm the other card grids use. */
  grid-template-columns: repeat(auto-fit, minmax(min(330px, 100%), 1fr));
  gap: 16px;
}
.caps-grid .approach__cell {
  background: var(--card-on-muted);
  border: 1px solid var(--card-border);
  border-radius: var(--card-radius);
  min-height: 280px;
}
/* On a white section, tint the card; on a grey (--muted) band it stays white. */
.approach:not(.approach--muted) .caps-grid .approach__cell {
  background: var(--card-on-white);
  border-color: var(--card-border);
}

/* A capability card whose whole area is clickable — same behaviour as
   .card-link on solutions / therapeutic-areas, reached differently. Those cards
   have no heading link, so the cell itself can be the <a>. Here the heading
   already is the link, and nesting a second anchor around it is invalid, so the
   existing anchor is stretched over the cell instead. One link per card either
   way; the cell keeps its <div>, and the position it stretches against is the
   position: relative .approach__cell already sets.
   The heading carries no link treatment of its own — no underline, no colour of
   its own at rest. It is not the target any more, the card is, and the arrow in
   .approach__tag is what says so. Same as every .card-link heading. */
.caps-cell--link { transition: background .15s ease, box-shadow .15s ease; }
.caps-cell--link h3 a { transition: color .15s ease; }
.caps-cell--link h3 a::after {
  content: "";
  position: absolute;
  inset: 0;
}
/* Two selectors, not one: .approach:not(.approach--muted) .caps-grid
   .approach__cell above sets the resting fill at four classes, so a three-class
   hover would never paint. The second selector matches it and adds the state. */
.caps-grid .caps-cell--link:hover,
.approach:not(.approach--muted) .caps-grid .caps-cell--link:hover {
  background: var(--mist);
  box-shadow: inset 0 0 0 1px var(--rule);
}
/* Hovering anywhere on the card lights the heading and nudges the arrow, so the
   whole cell reads as one target. .card-link__cta already styles the arrow; only
   the nudge is restated, because its own rule keys off .card-link. */
.caps-cell--link:hover h3 a { color: var(--primary); }
.caps-cell--link:hover .card-link__cta svg { transform: translateX(3px); }
/* The stretched anchor is an inline box, so its own ring would hug the heading
   text and understate what Enter activates. Move the standard ring to the cell. */
.caps-cell--link:has(h3 a:focus-visible) {
  outline: 2px solid var(--primary);
  outline-offset: 3px;
}
.caps-cell--link h3 a:focus-visible { outline: none; }
@media (prefers-reduced-motion: reduce) {
  .caps-cell--link,
  .caps-cell--link h3 a { transition: none; }
}

/* ---- Card numeral — one treatment for every numbered capability card,
        in both grids. It sits in the flow above the title rather than as an
        oversized watermark behind it, so it can never overlap the heading and
        needs no reserved padding. ---- */
.approach__num {
  font-family: var(--mono);
  font-size: clamp(38px, 3.4vw, 46px);
  font-weight: 300;
  line-height: 1;
  letter-spacing: -0.02em;
  color: color-mix(in srgb, var(--primary) 26%, transparent);
  user-select: none;
}

/* ---- Therapeutic-area detail pages (oncology, haematology, nuclear-medicine) ---- */
@media (min-width: 601px) {
  #endpoints .about__disciplines { grid-template-columns: 1fr 1fr; }
}

/* ---- Why-it-matters: checklist + closing statement (central-review group) ---- */
.matters__grid {
  display: grid;
  grid-template-columns: 0.95fr 1.05fr;
  gap: clamp(40px, 6vw, 80px);
  align-items: start;
}
@media (max-width: 820px) {
  .matters__grid { grid-template-columns: 1fr; }
}
/* Shorthand, not margin-top alone: this is a <p>, so leaving margin-bottom
   unset keeps the UA's 1em — 26px at this font-size. It sits last in the left
   column of .about__grid / .matters__grid, which is a grid item and therefore
   its own formatting context, so that margin cannot collapse out: it lands
   directly on the section's bottom edge. Same reason for every `margin: X 0 0`
   on a <p> in this file. */
.matters__statement {
  margin: 36px 0 0;
  padding-top: 28px;
  border-top: 1px solid var(--rule);
  font-family: var(--display);
  font-weight: 500;
  font-size: var(--fs-h3);
  line-height: 1.3;
  letter-spacing: -0.02em;
  color: var(--ink);
  text-wrap: balance;
}
.matters__statement em { color: var(--primary); font-style: italic; font-weight: 500; }
.matters__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 1px;
  background: var(--rule);
  border: 1px solid var(--card-border);
  border-radius: var(--card-radius);
  overflow: hidden;
}
.matters__list li {
  display: grid;
  grid-template-columns: 26px 1fr;
  gap: 16px;
  align-items: baseline;
  background: var(--paper);
  padding: 22px 26px;
}
.matters__list .matters__check {
  width: 24px; height: 24px;
  border-radius: var(--radius-round);
  background: var(--mist);
  color: var(--primary);
  display: grid; place-items: center;
  transform: translateY(4px);
}
.matters__list h3 {
  font-family: var(--display);
  font-weight: 600;
  font-size: var(--fs-h4);
  margin: 0;
  color: var(--ink);
  letter-spacing: -0.01em;
}
.matters__list p {
  margin: 4px 0 0;
  font-size: var(--fs-body);
  color: var(--ink-3);
  line-height: 1.55;
}

/* ============================================================
   PAGE-SPECIFIC
   ============================================================ */

/* ===================== PAGE: INDEX (HOME) ===================== */
/* Card height only. The fill these once set is now handled by --card-on-white /
   --card-on-muted, which pick from the band the card sits on; as an ID rule it
   outranked that and forced the tint onto grey bands across 20 pages. */
#capabilities .approach__cell,
#sponsors .approach__cell {
  min-height: 300px;
}
#sponsors .approach__step {
  position: static;
  top: auto;
  right: auto;
  font-size: var(--fs-micro);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--primary-2);
}
#sponsors .approach__step--primary { color: var(--primary); }

/* ===================== PAGE: ABOUT ===================== */

/* History */
.history__facts {
  margin-top: 36px;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 14px;
}
@media (max-width: 600px) { .history__facts { grid-template-columns: 1fr; } }
.fact {
  border: 1px solid var(--card-border);
  border-radius: var(--card-radius);
  padding: 20px;
  background: var(--card-on-white);
}
.fact__value {
  font-family: var(--display);
  font-weight: 600;
  font-size: var(--fs-h3);
  letter-spacing: -0.02em;
  color: var(--ink);
  line-height: 1;
}
.fact__value span { color: var(--primary); }
.fact__label {
  margin-top: 8px;
  font-family: var(--mono);
  font-size: var(--fs-micro);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-4);
}

/* Mission / Vision big statements */
.statement {
  display: grid;
  grid-template-columns: 0.42fr 1.58fr;
  gap: clamp(28px, 5vw, 72px);
  align-items: start;
}
@media (max-width: 820px) { .statement { grid-template-columns: 1fr; gap: 20px; } }
.statement__quote {
  font-family: var(--display);
  font-weight: 600;
  font-size: var(--fs-h2);
  line-height: 1.04;
  letter-spacing: -0.025em;
  color: var(--ink);
  margin: 0;
  text-wrap: balance;
}
.statement__quote em { font-style: italic; font-weight: 500; color: var(--primary); }
.statement__body {
  margin: 22px 0 0; /* shorthand: <p>, and it ends the section — see .matters__statement */
  font-size: var(--fs-body);
  line-height: 1.55;
  color: var(--ink-3);
  max-width: 56ch;
  text-wrap: pretty;
}
.statement__side {
  font-family: var(--mono);
  font-size: var(--fs-micro);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-4);
  padding-top: 8px;
  border-top: 2px solid var(--ink);
}

/* Team */
.team__grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 20px;
}
@media (max-width: 980px) { .team__grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 540px) { .team__grid { grid-template-columns: 1fr; } }
/* width/object-fit were repeated as an inline style on all four portraits;
   they are part of the component, not a per-person choice. */
.person__portrait {
  aspect-ratio: 4 / 5;
  border-radius: var(--card-radius);
  width: 100%;
  object-fit: cover;
}
/* One portrait is framed from the left edge rather than the centre. */
.person__portrait--left { object-position: left; }
.person__name {
  font-family: var(--display);
  font-weight: 600;
  font-size: var(--fs-h4);
  letter-spacing: -0.01em;
  color: var(--ink);
  margin: 18px 0 0;
}
.person__role {
  margin-top: 6px;
  font-family: var(--mono);
  font-size: var(--fs-micro);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--primary);
}
.person__desc {
  margin: 12px 0 0; /* shorthand: <p>, and it ends the section — see .matters__statement */
  font-size: var(--fs-body);
  line-height: 1.55;
  color: var(--ink-3);
  text-wrap: pretty;
}

/* Timeline */
.timeline-list {
  display: grid;
  gap: 0;
  border-top: 1px solid var(--rule);
}
.tl {
  display: grid;
  grid-template-columns: 200px 1fr;
  gap: clamp(20px, 4vw, 56px);
  padding: 30px 0;
  border-bottom: 1px solid var(--rule);
  align-items: baseline;
}
/* The rule under each row is what its 30px bottom padding spaces the text from.
   The last row has no rule, so that padding was spacing the text from nothing —
   and, as the last thing in the section, handing it 30px on top of
   --section-padding. Dropped with the border it belonged to. */
.tl:last-child {
  border-bottom: none;
  padding-bottom: 0;
}
@media (max-width: 700px) { .tl { grid-template-columns: 1fr; gap: 8px; } }
.tl__year {
  font-family: var(--display);
  font-weight: 600;
  font-size: var(--fs-h3);
  letter-spacing: -0.02em;
  color: var(--primary);
  line-height: 1;
}
.tl__title {
  font-family: var(--display);
  font-weight: 600;
  font-size: var(--fs-h4);
  letter-spacing: -0.01em;
  color: var(--ink);
  margin: 0;
}
.tl__desc {
  margin: 8px 0 0; /* shorthand: <p>, and it ends the section — see .matters__statement */
  font-size: var(--fs-body);
  line-height: 1.55;
  color: var(--ink-3);
  max-width: 64ch;
  text-wrap: pretty;
}

/* Certifications */
.certs__grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1px;
  background: var(--rule);
  border: 1px solid var(--card-border);
  border-radius: var(--card-radius);
  overflow: hidden;
}
@media (max-width: 820px) { .certs__grid { grid-template-columns: 1fr 1fr; } }
@media (max-width: 540px) { .certs__grid { grid-template-columns: 1fr; } }
.cert {
  background: var(--paper);
  padding: 32px 28px;
  display: flex;
  flex-direction: column;
  gap: 16px;
  min-height: 200px;
}
.cert__seal {
  width: 46px; height: 46px;
  border-radius: var(--radius-round);
  border: 1.5px solid var(--primary);
  color: var(--primary);
  display: grid; place-items: center;
  font-family: var(--mono);
  font-size: var(--fs-micro);
  font-weight: 500;
  letter-spacing: 0.04em;
  flex-shrink: 0;
}
.cert__name {
  font-family: var(--display);
  font-weight: 600;
  font-size: var(--fs-h4);
  letter-spacing: -0.01em;
  color: var(--ink);
}
.cert__desc {
  font-size: var(--fs-body);
  line-height: 1.55;
  color: var(--ink-3);
  margin-top: auto;
}

/* ===================== PAGE: CONTACT =====================
   .cpage is a deliberate light-hero wrapper, not a variant of .hero — do not
   "correct" it back. It is one design with the inline background on the
   <section> in contact.php; changing one means changing the other.
   See README: styles.css — Page: Contact. */
.cpage { padding: var(--section-padding) 0; }
/* Wide enough for the hero title at the --fs-display step, and still clear of
   the laptop in the background photograph. The lede keeps its own measure. */
.cpage__head { max-width: min(720px, 100%); margin-bottom: clamp(40px, 5vw, 64px); }
.cpage__eyebrow {
  display: inline-flex; align-items: center; gap: 12px;
  color: var(--ink-4); margin-bottom: 20px;
}
/* The eyebrow sits where the gradient over hero_contact.webp is still only ~10%
   white, so it reads against the photo rather than against a flat surface.
   --ink-4 measures 4.62:1 there on the photo's median and drops to 3.90:1 over
   its darker regions; --ink-3 holds 5.64:1 at the same position. Darkening the
   type rather than the gradient keeps the photograph visible. */
.cpage__eyebrow .label { color: var(--ink-3); }
.cpage__eyebrow .dot {
  width: 8px; height: 8px; border-radius: var(--radius-round);
  background: var(--primary); box-shadow: 0 0 0 4px rgba(48,98,176,0.18);
}
.cpage__title {
  font-family: var(--display); font-weight: 600;
  font-size: var(--fs-display); line-height: 0.96;
  letter-spacing: -0.035em; margin: 0; color: var(--ink); text-wrap: balance;
}
.cpage__title .title-accent { font-style: italic; font-weight: 500; color: var(--primary); }
.cpage__lede {
  margin-top: 22px; font-size: var(--fs-h4);
  color: var(--ink-3); line-height: 1.55; max-width: 52ch; text-wrap: pretty;
}

/* Scientific consultation intro */
.consult { margin-bottom: clamp(36px, 4.5vw, 56px); }
.consult__grid {
  display: grid; grid-template-columns: 1fr 1fr;
  gap: 16px;
}
@media (max-width: 820px) { .consult__grid { grid-template-columns: 1fr; } }
.consult__card {
  background: var(--bg); border: 1px solid var(--card-border);
  border-radius: var(--card-radius); padding: var(--card-pad);
  display: flex; flex-direction: column;
}
.consult__eyebrow {
  font-family: var(--mono); font-size: var(--fs-micro); letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--primary); margin-bottom: 16px;
}
.consult__lede {
  font-size: var(--fs-body); color: var(--ink-2); line-height: 1.55;
  margin: 0; text-wrap: pretty;
}
.consult__list {
  list-style: none; margin: 20px 0 0; padding: 0;
  display: grid; gap: 12px;
}
.consult__list li {
  display: grid; grid-template-columns: 22px 1fr; gap: 12px;
  align-items: start; color: var(--ink-3); font-size: var(--fs-body); line-height: 1.55;
}
/* The check circle in .consult__list li is .why__check (see PAGE: IMAGING CORE
   LAB); the 22px icon track above is sized to it. */
.consult__note {
  margin: 20px 0 0; font-size: var(--fs-body); color: var(--ink-3);
  line-height: 1.55; text-wrap: pretty;
}
/* Light-surface primary (see BUTTONS); only its placement in the card is
   specified here. margin-top:auto pins it to the bottom edge, which is what
   holds the CTAs on one line while the cards stretch to equal height in the
   two-column grid. Stacked, there is no free space left for auto to take, so
   the gap above the CTA would be whatever bottom margin the last content block
   happens to carry — 20px after .consult__note in one card, none after
   .consult__list in the other. Putting that gap on whichever element precedes
   the CTA spaces every card's button the same way, whatever it holds. */
.consult__card > :nth-last-child(2) { margin-bottom: 20px; }
.consult__cta { margin-top: auto; align-self: flex-start; }
.consult__cta svg { transition: transform .2s ease; }
.consult__cta:hover svg { transform: translateX(2px); }

/* CTA card holding form + details.
   This is the #form anchor target for the two .consult__cta buttons above it,
   so it keeps the sticky 76px nav clear of its top edge when jumped to. */
.ccard {
  background: var(--ink); color: #fff; border-radius: var(--radius-2xl);
  padding: var(--card-pad-panel); position: relative; overflow: hidden;
  isolation: isolate; scroll-margin-top: 92px;
}
.ccard::before {
  content: ""; position: absolute; inset: 0; z-index: -1;
  background:
    radial-gradient(circle at 85% 20%, rgba(181,196,222,0.16), transparent 45%),
    /* Same corner bloom as .contact::before, so the same alpha. It was 0.24
       here and 0.22 there — a 2% difference at this opacity is below the
       perceptual threshold, i.e. drift rather than intent. Collapsed toward
       0.22 because that is the value .contact__meta's contrast note measures
       against; moving the other way would have invalidated it. */
    radial-gradient(circle at 10% 95%, rgba(48,98,176,0.22), transparent 52%);
}
.ccard__grid {
  display: grid; grid-template-columns: 0.85fr 1.15fr;
  gap: clamp(36px, 5vw, 72px); align-items: start;
}
@media (max-width: 900px) { .ccard__grid { grid-template-columns: 1fr; } }

.cinfo__title {
  font-family: var(--display); font-weight: 600;
  font-size: var(--fs-h3); line-height: 1.08;
  letter-spacing: -0.02em; margin: 0; text-wrap: balance;
}
.cinfo__title .title-accent { font-style: italic; font-weight: 500; color: var(--slate); }
.cinfo__lede {
  margin-top: 18px; color: rgba(234,240,249,0.74);
  font-size: var(--fs-body); line-height: 1.55; max-width: 40ch;
}
.cinfo__rows { margin-top: 36px; display: grid; gap: 26px; }
.cinfo__label {
  font-family: var(--mono); font-size: var(--fs-micro); letter-spacing: 0.1em;
  text-transform: uppercase; color: rgba(255,255,255,0.5);
}
.cinfo__value { margin-top: 8px; font-size: var(--fs-body); color: #fff; line-height: 1.55; }
.cinfo__value a:hover { color: var(--slate); }
.cinfo__socials { margin-top: 34px; display: flex; gap: 16px; }
.cinfo__socials a {
  display: grid; place-items: center; color: rgba(255,255,255,0.8);
  transition: color .15s;
}
.cinfo__socials a:hover { color: #fff; }

/* Form */
.cform {
  background: #fff; border-radius: var(--card-radius); color: var(--ink);
  padding: var(--card-pad);
}
.cform__eyebrow {
  font-family: var(--mono); font-size: var(--fs-micro); letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--ink-4); margin-bottom: 22px;
}
/* Sits between the eyebrow and the field grid; the eyebrow's 22px bottom margin
   is the gap above it, so this only needs to space itself off the fields. */
.cform__required {
  margin: -8px 0 16px;
  font-size: var(--fs-micro);
  line-height: 1.4;
  color: var(--ink-4);
}
.cform__grid { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; }
@media (max-width: 540px) { .cform__grid { grid-template-columns: 1fr; } }
.field { display: flex; flex-direction: column; gap: 8px; }
.field--full { grid-column: 1 / -1; }
.field label {
  font-family: var(--mono); font-size: var(--fs-micro); letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--ink-3); font-weight: 500;
}
/* The `select` half of these two selectors matches nothing today — there is no
   <select> anywhere in the markup. Kept deliberately: it costs two selector
   fragments, and without it the first <select> anyone adds to a form renders
   unstyled against the inputs beside it. Audited and kept, not overlooked. */
.field input, .field textarea, .field select {
  font-family: var(--body); font-size: var(--fs-body); color: var(--ink);
  background: var(--bg); border: 1px solid var(--rule-strong);
  border-radius: var(--radius-md); padding: 13px 14px; width: 100%;
  transition: border-color .15s, background .15s, box-shadow .15s;
}
.field textarea { resize: vertical; min-height: 120px; }
.field input::placeholder, .field textarea::placeholder { color: var(--ink-4); }
.field input:focus, .field textarea:focus, .field select:focus {
  outline: none; border-color: var(--primary); background: #fff;
  box-shadow: 0 0 0 3px rgba(48,98,176,0.12);
}
.cform__foot {
  margin-top: 22px; display: flex; align-items: center;
  justify-content: space-between; gap: 18px; flex-wrap: wrap;
}
/* label.cform__consent outranks .field label, which would otherwise impose
   the mono uppercase field-label styling on the consent caption. */
label.cform__consent {
  display: flex; align-items: flex-start; gap: 10px;
  font-family: var(--body); font-weight: 400; text-transform: none;
  letter-spacing: normal; font-size: var(--fs-micro); color: var(--ink-4);
  line-height: 1.4; max-width: 34ch;
}
/* The base `a` rule strips colour and underline, which would leave the privacy
   policy link indistinguishable from the caption around it. Given consent to a
   document, the way to that document has to read as a link. */
.cform__consent a {
  color: var(--primary); font-weight: 600;
  text-decoration: underline; text-underline-offset: 3px;
}
.cform__consent a:hover { color: var(--primary-2); }
/* Custom box so the consent control matches the other fields' border, radius
   and invalid state instead of rendering per-OS. Still a native checkbox, so
   keyboard and label-click behaviour are unchanged — but appearance:none drops
   the UA focus ring, and a small box shows the .field input:focus ring poorly,
   so it gets an explicit outline back.

   24x24, not the 16x16 it was, and the reason is aim rather than conformance.
   SC 2.5.8 was already satisfied before this change and the audit was wrong to
   imply otherwise: the label wraps both the box and the caption, so the target
   is the whole label, measured at 248.9 x 33.6 on both 390px and 1350px. What
   the measurement does not capture is that nobody aims at a label — they aim at
   the square, and at 16px the square was under half the AA minimum in both
   dimensions. Missing it low or left still hit the label and ticked the box;
   missing it right, at the end of the caption, hit the privacy-policy link
   (77.7 x 17, inside the same label) and navigated away from a part-filled
   form. Making the square itself the 24px target removes the miss.

   This is the required field on the primary conversion path, so it is the one
   control where a fumbled tap costs an enquiry. */
.cform__consent input {
  appearance: none;
  -webkit-appearance: none;
  flex: 0 0 auto;
  width: 24px; height: 24px;
  margin-top: -2px;
  padding: 0;
  background: var(--bg);
  border: 1px solid var(--rule-strong);
  border-radius: var(--radius-xs);
  cursor: pointer;
  transition: background .15s, border-color .15s;
}
.cform__consent input:checked {
  background: var(--primary) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' fill='none'%3E%3Cpath d='M2.5 6.2l2.4 2.4 4.6-5' stroke='%23fff' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / 16px 16px no-repeat;
  border-color: var(--primary);
}
.cform__consent input:focus-visible {
  outline: 2px solid var(--primary);
  outline-offset: 2px;
}
/* Light-surface primary (see BUTTONS). */
.cform__submit:disabled { opacity: 0.6; cursor: default; }

/* Form validation + submission feedback */
.field__error { font-size: var(--fs-micro); line-height: 1.4; color: var(--danger); }
/* Standing guidance under an input, as opposed to .field__error's conditional
   validation message: same size, muted rather than red, and always shown. Used
   for the "no patient-identifying information" note on the message field, which
   has to be readable before someone types rather than after. */
.field__hint { font-size: var(--fs-micro); line-height: 1.4; color: var(--ink-4); }
/* The consent block takes the space left over by the button (flex-basis 0)
   instead of sizing to its content, so a shown error can never widen it and
   push the submit button to the next line. Below 540px the foot stacks. */
.cform__foot .field { flex: 1 1 0; min-width: 0; }
.cform__foot .field__error { max-width: 34ch; }
.cform__foot .cform__submit { flex-shrink: 0; align-self: flex-start; }
@media (max-width: 540px) {
  .cform__foot { flex-direction: column; align-items: flex-start; }
  .cform__foot .cform__submit { align-self: stretch; width: 100%; justify-content: center; }
}
.field--invalid input, .field--invalid textarea {
  border-color: var(--danger);
}
.field--invalid input:focus, .field--invalid textarea:focus {
  border-color: var(--danger); box-shadow: 0 0 0 3px color-mix(in srgb, var(--danger) 12%, transparent);
}
.cform__alert {
  margin-top: 22px; padding: 13px 16px; border-radius: var(--radius-md);
  font-size: var(--fs-body); line-height: 1.55; border: 1px solid;
}
/* Links inside an alert (the phone/email fallbacks on a send failure) keep the
   alert's own colour, so each variant stays legible on its own background. */
.cform__alert a { color: inherit; font-weight: 600; text-decoration: underline; text-underline-offset: 3px; }
.cform__alert--success { color: var(--success); background: var(--success-bg); border-color: var(--success-border); }
.cform__alert--danger  { color: var(--danger); background: var(--danger-bg); border-color: var(--danger-border); }
.cform__alert--warning { color: var(--warn); background: var(--warn-bg); border-color: var(--warn-border); }

/* Honeypot: visually removed, still in the submitted form data */
.cform__hp {
  position: absolute; left: -9999px; top: auto;
  width: 1px; height: 1px; overflow: hidden;
}

/* Post-submission confirmation. Carries .cform as well, so the surface,
   padding, mobile gutter and focus ring are the form's — only the parts the
   confirmation adds are declared here. Nothing sets `display` on .cdone: the
   panel is toggled with the hidden attribute, which a display declaration on a
   class selector would outrank. */
.cdone__icon {
  width: 46px; height: 46px; border-radius: var(--radius-round);
  background: var(--mist); color: var(--primary);
  display: grid; place-items: center; margin-bottom: 22px;
}
/* Tighter than in the form, where the eyebrow leads a field grid rather than
   a heading it belongs to. */
.cdone .cform__eyebrow { margin-bottom: 14px; }
.cdone__title {
  font-family: var(--display); font-weight: 600;
  font-size: var(--fs-h3); line-height: 1.08;
  letter-spacing: -0.02em; margin: 0; color: var(--ink); text-wrap: balance;
}
.cdone__lede {
  margin: 16px 0 0; font-size: var(--fs-body); color: var(--ink-3);
  line-height: 1.55; max-width: 46ch; text-wrap: pretty;
}
.cdone__lede strong { font-weight: 600; color: var(--ink-2); }
.cdone__label {
  font-family: var(--mono); font-size: var(--fs-micro); letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--ink-4); margin-top: 32px;
}
.cdone__steps {
  list-style: none; counter-reset: cdone-step;
  margin: 16px 0 0; padding: 0; display: grid; gap: 12px;
}
.cdone__steps li {
  counter-increment: cdone-step;
  display: grid; grid-template-columns: 22px 1fr; gap: 12px; align-items: start;
  color: var(--ink-3); font-size: var(--fs-body); line-height: 1.55;
}
.cdone__steps li::before {
  content: counter(cdone-step);
  width: 22px; height: 22px; border-radius: var(--radius-round);
  background: var(--mist); color: var(--primary);
  font-family: var(--mono); font-size: var(--fs-micro);
  display: grid; place-items: center; margin-top: 1px;
}
.cdone__urgent {
  margin: 32px 0 0; padding-top: 24px; border-top: 1px solid var(--rule-2);
  font-size: var(--fs-body); color: var(--ink-3); line-height: 1.55;
  text-wrap: pretty;
}
.cdone__urgent a { color: var(--primary); font-weight: 600; text-decoration: underline; text-underline-offset: 3px; }
.cdone__urgent a:hover { color: var(--primary-2); }
/* An action, not a destination, so it is a button dressed as a text link.
   min-height carries the 24x24 minimum target size (SC 2.5.8): at 12px with no
   padding the control was ~19px tall, the only target on the site under the
   floor. A button centres its content, so the label stays put and the box grows
   symmetrically around it; margin-top drops by the same ~5px the box gains so
   the gap above it is unchanged. */
.cdone__again {
  margin-top: 15px; padding: 0; border: 0; background: none; cursor: pointer;
  min-height: 24px;
  font-family: var(--body); font-size: var(--fs-micro); color: var(--ink-4);
  text-decoration: underline; text-underline-offset: 3px;
}
.cdone__again:hover { color: var(--primary); }

/* Response time + confidentiality, sitting directly under the form card */
.assure {
  margin-top: 16px;
  display: grid; grid-template-columns: 1fr 1fr; gap: 16px;
}
@media (max-width: 820px) { .assure { grid-template-columns: 1fr; } }
.assure__item {
  background: var(--bg); border: 1px solid var(--card-border);
  border-radius: var(--card-radius); padding: var(--card-pad-note);
  display: grid; grid-template-columns: 22px 1fr; gap: 12px; align-items: start;
}
.assure__icon {
  width: 22px; height: 22px; border-radius: var(--radius-round);
  background: var(--mist); color: var(--primary);
  display: grid; place-items: center; margin-top: 1px;
}
.assure__text {
  margin: 0; font-size: var(--fs-body); color: var(--ink-3);
  line-height: 1.55; text-wrap: pretty;
}
.assure__text strong { font-weight: 600; color: var(--ink-2); }
.assure__text a { color: var(--primary); text-decoration: underline; text-underline-offset: 3px; }
.assure__text a:hover { color: var(--primary-2); }

/* Find us in Turin */
/* Top padding only: as the last block inside .cpage, its bottom rhythm comes
   from the wrapper's padding, so the footer rule sits the same distance below
   the map as below the dark .contact card on every other page. */
.turin { padding-top: var(--section-padding); }
.turin__head {
  display: flex; justify-content: space-between; align-items: end;
  gap: 24px; flex-wrap: wrap; margin-bottom: clamp(28px, 4vw, 44px);
}
.turin__title {
  font-family: var(--display); font-weight: 600;
  font-size: var(--fs-h2); line-height: 1.04;
  letter-spacing: -0.025em; margin: 10px 0 0; color: var(--ink); text-wrap: balance;
}
.turin__note { font-size: var(--fs-body); color: var(--ink-3); max-width: 38ch; line-height: 1.55; }
@media (max-width: 760px) {
  .turin__head { gap: 12px; }
  .turin__note { max-width: none; }
}
.turin__grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; }
@media (max-width: 760px) { .turin__grid { grid-template-columns: 1fr; } }
.turin__map {
  margin-top: 16px; border: 1px solid var(--card-border); border-radius: var(--card-radius);
  overflow: hidden; background: var(--bg); aspect-ratio: 21 / 7;
}
@media (max-width: 760px) { .turin__map { aspect-ratio: 4 / 3; } }
.turin__map img { width: 100%; height: 100%; object-fit: cover; display: block; }
.turin__img {
  border: 1px solid var(--card-border); border-radius: var(--card-radius); overflow: hidden;
  background: var(--bg); aspect-ratio: 4 / 3;
}
.turin__img img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* ===================== PAGE: GLOBAL STUDIES ===================== */

/* compliance strip under presence numbers */
.gs-compliance {
  margin-top: 28px;
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}
.gs-chip {
  font-family: var(--mono);
  font-size: var(--fs-micro);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-3);
  background: var(--card-on-white);
  border: 1px solid var(--rule);
  border-radius: var(--radius-pill);
  padding: 8px 14px;
}
.gs-chip::before {
  content: "";
  display: inline-block;
  width: 6px; height: 6px;
  border-radius: var(--radius-round);
  background: var(--primary);
  margin-right: 8px;
  transform: translateY(-1px);
}

/* two-up presence stat cards */
.gs-presence {
  display: grid;
  grid-template-columns: 1fr 1fr;
  border: 1px solid var(--card-border);
  border-radius: var(--card-radius);
  overflow: hidden;
}
@media (max-width: 760px) { .gs-presence { grid-template-columns: 1fr; } }
.gs-presence__cell {
  background: #fff;
  padding: 40px 36px;
  border-right: 1px solid var(--rule);
}
.gs-presence__cell:last-child { border-right: 0; }
@media (max-width: 760px) {
  .gs-presence__cell { border-right: 0; border-bottom: 1px solid var(--rule); }
  .gs-presence__cell:last-child { border-bottom: 0; }
}
.gs-presence__value {
  font-family: var(--display);
  font-weight: 600;
  font-size: var(--fs-h1);
  line-height: 1;
  letter-spacing: -0.035em;
  color: var(--ink);
}
.gs-presence__value span { color: var(--primary); }
.gs-presence__head {
  margin: 18px 0 10px;
  font-family: var(--display);
  font-weight: 600;
  font-size: var(--fs-h4);
  letter-spacing: -0.01em;
  color: var(--ink);
}
.gs-presence__desc {
  font-size: var(--fs-body);
  line-height: 1.55;
  color: var(--ink-3);
  max-width: 44ch;
  text-wrap: pretty;
}
.gs-closing {
  margin: 28px 0 0; /* shorthand: <p>, and it ends the section — see .matters__statement */
  font-size: var(--fs-body);
  line-height: 1.55;
  color: var(--ink-3);
  max-width: 64ch;
  text-wrap: pretty;
}

/* collaboration cards — four independent cards */
.gs-collab {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 20px;
}
@media (max-width: 980px) { .gs-collab { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 540px) { .gs-collab { grid-template-columns: 1fr; } }
.gs-collab__card {
  background: var(--card-on-muted);
  border: 1px solid var(--card-border);
  border-radius: var(--card-radius);
  padding: 32px 28px;
  display: flex;
  flex-direction: column;
  gap: 14px;
  min-height: 280px;
}
.gs-collab__card h3 {
  font-family: var(--display);
  font-weight: 600;
  font-size: var(--fs-h4);
  letter-spacing: -0.01em;
  color: var(--ink);
  line-height: 1.15;
  margin: 0;
}
.gs-collab__card p {
  margin: 0;
  color: var(--ink-3);
  font-size: var(--fs-body);
  line-height: 1.55;
  text-wrap: pretty;
}
.gs-collab__tag {
  margin-top: auto;
  font-family: var(--mono);
  font-size: var(--fs-micro);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--primary);
  padding-top: 18px;
  border-top: 1px solid var(--rule);
}

/* ===================== PAGE: IMAGING CORE LAB ===================== */
/* Hero photographic background is set inline on .hero via --hero-image /
   --hero-size / --hero-overlay-* (see the HERO section above). */
.why__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 18px;
}
.why__list li {
  display: grid;
  grid-template-columns: 22px 1fr; /* tracks .why__check's width */
  gap: 16px;
  align-items: start;
  color: var(--ink-2);
  font-size: var(--fs-body);
  line-height: 1.55;
}
.why__list li strong { font-weight: 600; color: var(--ink); }
/* Also the check circle for .consult__list on the contact page, which had an
   identical rule under its own .consult__check name at this same 22px. */
.why__check {
  width: 22px; height: 22px;
  border-radius: var(--radius-round);
  background: var(--mist);
  color: var(--primary);
  display: grid; place-items: center;
  margin-top: 1px;
}
/* Closing sentence, not a sixth item: it lines up with the list's text column
   rather than its icon column, so it cannot read as a bullet that lost its
   check mark. Offset = .why__list li icon track (22px) + gap (16px). Scoped
   to .about__body because that ancestor's `p` rule would otherwise win on
   specificity and zero the top margin. */
.about__body .why__close {
  margin: 36px 0 0; /* twice the list's own 18px gap, so it detaches from it */
  padding-left: 38px;
  font-size: var(--fs-body);
  color: var(--ink-3);
  line-height: 1.55;
  text-wrap: pretty;
}

/* ===================== PAGE: WHAT IS WIDEN ===================== */
.risk-list {
  list-style: none;
  margin: 26px 0 0;
  padding: 0;
  display: grid;
  gap: 1px;
  background: var(--rule);
  border: 1px solid var(--card-border);
  border-radius: var(--card-radius);
  overflow: hidden;
}
.risk-list li {
  display: grid;
  grid-template-columns: 26px 1fr;
  gap: 14px;
  align-items: center;
  background: var(--card-on-white);
  padding: 16px 22px;
  font-size: var(--fs-body);
  color: var(--ink-2);
}
.risk-list .risk-x {
  width: 22px; height: 22px;
  border-radius: var(--radius-round);
  background: var(--danger-bg);
  color: var(--danger);
  display: grid; place-items: center;
}
.legacy-note {
  margin: 26px 0 0; /* shorthand: <p>, and it ends the section — see .matters__statement */
  padding-top: 22px;
  border-top: 1px solid var(--rule);
  font-family: var(--display);
  font-weight: 500;
  font-size: var(--fs-h3);
  line-height: 1.35;
  letter-spacing: -0.02em;
  color: var(--ink);
  text-wrap: balance;
}
.legacy-note em { color: var(--primary); font-style: italic; font-weight: 500; }

/* ===================== PAGE: LINKS ===================== */
/* Standalone "link hub" landing page (links.php). Chrome-less: no site
   header/footer; <body class="lp"> carries the full-viewport background. */
.lp {
  min-height: 100vh;
  background-color: var(--ink);
  background-image:
    linear-gradient(rgba(23, 74, 122, .8), rgba(9, 35, 59, .8)),
    url("/assets/static/img/world-wireframe.svg");
  background-repeat: no-repeat;
  background-position: 0 0, left -17vw;
  background-size: 100% 100%, 200% auto;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: clamp(32px, 6vw, 72px) var(--gutter);
}
.lp__inner {
  width: 100%;
  max-width: 460px;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
}

/* brand */
.lp__logo {
  height: clamp(40px, 6vw, 56px);
  width: auto;
  filter: brightness(0) invert(1);
  margin-bottom: 22px;
}
/* .lp carries the same overlay-over-wireframe background as .hero, so its text
   sits on the same composite and takes the same vetted step: white at .85,
   4.68:1. rgba(234,240,249,0.78) was 3.77:1 against a 4.5 requirement.
   Written out rather than referencing --hero-label, which is declared on .hero
   and does not reach this page. */
.lp__tagline {
  margin: 0 0 clamp(28px, 4vw, 40px);
  max-width: 38ch;
  color: rgba(255, 255, 255, 0.85);
  font-size: var(--fs-body);
  line-height: 1.55;
  text-wrap: pretty;
}

/* card */
.lp__card {
  width: 100%;
  background: #fff;
  border-radius: var(--card-radius);
  padding: clamp(22px, 3vw, 30px);
  box-shadow:
    0 30px 60px -24px rgba(5, 20, 40, 0.6),
    0 10px 24px -12px rgba(5, 20, 40, 0.45);
  text-align: left;
}
/* An <h2> — the group labels have to be headings for the page to have any
   structure at all. font-weight is pinned to 400 because that is the only
   heading default this rule was not already overriding: size, family, margin
   and colour were all set here when these were <div>s, so the element change
   is not a visual one. */
.lp__eyebrow {
  font-family: var(--mono);
  font-size: var(--fs-micro);
  font-weight: 400;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-4);
  margin: 0 4px 12px;
}
.lp__eyebrow--mt { margin-top: 26px; }

.lp__rows { display: flex; flex-direction: column; gap: 8px; }
.lp__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  padding: 14px 16px;
  border: 1px solid var(--card-border);
  border-radius: var(--card-radius);
  /* Nested inside the white .lp__card, so this recedes rather than tinting —
     --card-on-white is reserved for the hover state here. */
  background: var(--bg);
  transition: border-color .15s ease, background .15s ease, transform .15s ease;
}
.lp__row:hover {
  border-color: var(--primary);
  background: var(--mist);
  transform: translateY(-1px);
}
.lp__row-main { display: flex; align-items: center; gap: 13px; min-width: 0; }
.lp__ico {
  width: 38px; height: 38px;
  flex-shrink: 0;
  border-radius: var(--radius-md);
  background: var(--ink);
  color: #fff;
  display: grid; place-items: center;
  transition: background .15s ease;
}
.lp__row:hover .lp__ico { background: var(--primary); }
.lp__ico--brand { background: #fff; padding: 0; overflow: hidden; }
.lp__ico--brand svg { width: 100%; height: 100%; display: block; }
.lp__row:hover .lp__ico--brand { background: #fff; }
.lp__ico--li,
.lp__ico--fb,
.lp__ico--ig { background: #fff; padding: 0; overflow: hidden; border: none; box-shadow: none; }
.lp__ico--fb { background: transparent; }
.lp__ico--li svg,
.lp__ico--fb svg,
.lp__ico--ig svg { width: 100%; height: 100%; display: block; }
.lp__row:hover .lp__ico--li,
.lp__row:hover .lp__ico--ig { background: #fff; }
.lp__row:hover .lp__ico--fb { background: transparent; }
.lp__row-text { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.lp__row-title {
  font-family: var(--display);
  font-weight: 600;
  font-size: var(--fs-body);
  color: var(--ink);
  letter-spacing: -0.01em;
}
.lp__row-sub {
  font-family: var(--mono);
  font-size: var(--fs-micro);
  color: var(--ink-4);
  letter-spacing: 0.02em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.lp__arrow { color: var(--ink-4); flex-shrink: 0; width: 16px; height: 16px; transition: transform .2s ease, color .15s ease; }
.lp__row:hover .lp__arrow { color: var(--primary); transform: translateX(3px); }

/* Same step as .lp__tagline. At .55 this was 2.68:1 — the widest shortfall on
   the site — for 12px text needing 4.5:1. It now shares the tagline's colour and
   takes its quieter position from being 12px mono uppercase rather than 16px,
   which is the same size-not-opacity argument the hero uses. */
.lp__foot {
  margin-top: clamp(28px, 4vw, 40px);
  font-family: var(--mono);
  font-size: var(--fs-micro);
  letter-spacing: 0.06em;
  color: rgba(255, 255, 255, 0.85);
  text-transform: uppercase;
}
.lp__foot a:hover { color: #fff; }

/* ===================== PAGE: WORKFLOW & TRACEABILITY ===================== */
.why__grid {
  display: grid;
  grid-template-columns: 1fr 1.1fr;
  gap: clamp(40px, 6vw, 80px);
  align-items: start;
}
@media (max-width: 820px) { .why__grid { grid-template-columns: 1fr; } }
.why__feats { list-style: none; margin: 0; padding: 0; display: grid; gap: 14px; }
.why__feats li {
  display: grid;
  grid-template-columns: 24px 1fr;
  gap: 16px;
  align-items: start;
  color: var(--ink-2);
  font-size: var(--fs-body);
  line-height: 1.55;
}
.why__note { margin: 30px 0 0; color: var(--ink-3); font-size: var(--fs-body); line-height: 1.55; }
/* The line introducing a .why__feats list.
   These five paragraphs carried `class="about__body"` directly on the <p>. Every
   .about__body rule is a descendant selector (.about__body p, .about__body .btn,
   …), which needs an *ancestor* holding the class — a <p> that is .about__body is
   not inside one, so none of them ever matched and the paragraph fell back to
   body inheritance: --ink instead of --ink-3, 1.55 instead of 1.6, no text-wrap.
   It rendered darker than the prose above it and darker than the --ink-2 list
   below it. The properties .about__body p was meant to supply are declared here
   instead, so the line no longer depends on a wrapper that was never there.
   Bottom margin stays 26px — the spacing off the list was deliberate; 18px is
   .about__body p's generic value and would be a different decision. */
.why__intro {
  color: var(--ink-3);
  line-height: 1.55;
  text-wrap: pretty;
  margin: 0 0 26px;
}

.about--muted { background: var(--bg); }

.spec-list { list-style: none; margin: 8px 0 0; padding: 0; display: grid; gap: 2px; }
.spec-list li {
  display: grid;
  grid-template-columns: 30px 1fr;
  gap: 4px;
  align-items: baseline;
  padding: 14px 0;
  border-top: 1px solid var(--rule);
  color: var(--ink-2);
  font-size: var(--fs-body);
}
.spec-list li:last-child { border-bottom: 1px solid var(--rule); }
.spec-list .spec-num {
  font-family: var(--mono);
  font-size: var(--fs-micro);
  color: var(--primary);
  letter-spacing: 0.04em;
}
.spec-note {
  margin: 26px 0 0;
  color: var(--ink-3);
  font-size: var(--fs-body);
  line-height: 1.55;
  padding-left: 16px;
  border-left: 2px solid var(--primary);
}

.approach__cell--wide { grid-column: span 2; }
@media (max-width: 820px) { .approach__cell--wide { grid-column: span 1; } }

/* ============================================================
   EVIDENCE — logo wall, testimonials, publications, case studies
   See README: styles.css — Evidence module.
   ============================================================ */

/* ---- Logo wall ----
   Marks are normalised on HEIGHT, not width. Grayscale is house style, not
   information (SC 1.4.1). See README: styles.css — Logo wall. */
.logo-wall {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(180px, 100%), 1fr));
  gap: 1px;
  background: var(--rule);
  border: 1px solid var(--card-border);
  border-radius: var(--card-radius);
  overflow: hidden;
}
.logo-wall__item {
  /* --paper, NOT --card-on-white, and not inherited from the section: two of the
     six marks are opaque (logo_ielsg.webp is RGB, logo_anzup.png is a palette
     PNG with no tRNS chunk) and carry a white rectangle. A white cell
     hides it; any tint turns it into a visible box. This holds whatever band the
     wall sits on — tinting these cells means re-cutting those two assets first. */
  background: var(--paper);
  min-height: 132px;
}
/* The link fills the cell rather than wrapping only the logo: several marks
   scale well under the 24x24 minimum target size (SC 2.5.8), so the cell has to
   be the target. Also a <span> on any organisation without a 'url' — same box,
   no link semantics. */
.logo-wall__link {
  display: grid;
  place-items: center;
  width: 100%;
  height: 100%;
  padding: 30px 26px;
  text-decoration: none;
}
/* The global ring is outline-offset: 3px, drawn OUTSIDE the link box — and
   .logo-wall clips to its border-radius with overflow: hidden, so on the edge
   cells that ring would be cut off. Negative offset draws it just inside the
   cell instead, keeping the same colour and width. */
.logo-wall__link:focus-visible { outline-offset: -4px; }
.logo-wall__mark {
  max-width: 100%;
  max-height: 56px;
  width: auto;
  height: auto;
  object-fit: contain;
  filter: grayscale(1);
  /* 0.62 was too faint once greyscaled — three of the six marks carry fine
     detail (the GUMed crest, the SAKK and Peter Mac straplines) that dropped
     below legible. */
  opacity: 0.78;
  transition: filter .18s ease, opacity .18s ease;
}
/* Keyboard focus gets the same reveal as hover, so the wall does not behave
   differently for anyone navigating by tab. */
.logo-wall__link:hover .logo-wall__mark,
.logo-wall__link:focus-visible .logo-wall__mark {
  filter: grayscale(0);
  opacity: 1;
}
@media (prefers-reduced-motion: reduce) {
  .logo-wall__mark { transition: none; }
}
@media (max-width: 600px) {
  /* The gutter belongs on the link — that is the cell's padded box. Setting it
     on the item as well stacked the two (54px vertical, 44px each side) and
     squeezed the mark into the middle of a cell that got TALLER than the
     desktop one. min-height carries the height that stack used to produce, so
     the cells are the same size as before; only the mark inside them changes. */
  .logo-wall__item { min-height: 152px; }
  .logo-wall__link { padding: 30px var(--card-pad-mobile); }
  .logo-wall__mark { max-height: 44px; }
}
/* One column. auto-fit needs 361px of wall for two tracks (2x180px + the 1px
   gap) and the wall is the viewport less a 20px gutter each side, so the switch
   lands just above 400px — move this breakpoint if either of those changes.
   Cells are then ~330px wide, which leaves room for a much taller cap: at 80px
   the widest mark (Peter Mac, 746x226) is 264px, still clear of the gutter, so
   every mark stays height-normalised rather than one going width-bound. */
@media (max-width: 400px) {
  /* height, not max-height: logo_sakk.svg is intrinsically 110x51, so an auto
     height would pin it at 51px while the other five reach 80px and the row
     stops being normalised. It is vector, so scaling it past its intrinsic box
     costs nothing; the three raster marks are 380x380, 1066x612 and 950x950 and
     are still being scaled DOWN at 80px. On a viewport too narrow for the widest
     mark (below ~360px) max-width wins and object-fit: contain letterboxes it
     inside the 80px box — shorter, never stretched. max-height beats height, so
     the 44px cap set above has to be lifted for this to take effect at all. */
  .logo-wall__mark { max-height: none; height: 80px; }
}

/* ---- Testimonial ----
   A <figure> holding a <blockquote> and its attribution. */
.quote {
  margin: 0;
  background: var(--card-on-white);
  border: 1px solid var(--card-border);
  border-radius: var(--card-radius);
  padding: var(--card-pad);
}
/* :not(--pull) — the pull-quote is bare text on the section background, so it
   keeps the band's own colour rather than taking the card fill. */
.approach--muted .quote:not(.quote--pull),
.about--muted .quote:not(.quote--pull) { background: var(--card-on-muted); }
.quote__body { margin: 0; }
.quote__body p {
  margin: 0;
  color: var(--ink-2);
  font-size: var(--fs-body);
  line-height: 1.55;
  text-wrap: pretty;
}
.quote__attrib {
  display: flex;
  align-items: center;
  gap: 18px;
  margin-top: 26px;
  padding-top: 22px;
  border-top: 1px solid var(--rule);
}
.quote__logo {
  width: auto;
  height: auto;
  max-width: 62px;
  max-height: 46px;
  object-fit: contain;
  flex-shrink: 0;
}
.quote__who { display: flex; flex-direction: column; gap: 2px; }
.quote__author {
  font-family: var(--display);
  font-weight: 600;
  font-size: var(--fs-h4);
  color: var(--ink);
  letter-spacing: -0.01em;
  line-height: 1.2;
}
.quote__role,
.quote__org {
  font-size: var(--fs-body);
  color: var(--ink-4);
  line-height: 1.55;
  font-style: normal; /* <cite> italicises by default; the site does not */
}

/* Sub-heading introducing the wall where it follows other content in the same
   section (global-studies' collaboration cards). An h3 because it sits under
   that section's h2, not as a section of its own. */
.gs-collab__wall-head {
  font-family: var(--display);
  font-weight: 600;
  font-size: var(--fs-h4);
  color: var(--ink);
  letter-spacing: -0.01em;
  margin: clamp(40px, 5vw, 64px) 0 18px;
}

/* Several quotes together. Two of the three testimonials open with near-
   identical sentences, so they are stacked in one column rather than sat side
   by side, where the repetition would be read as a copy-paste error. */
.quote-stack {
  display: grid;
  gap: 16px;
  max-width: 78ch;
}

/* Pull-quote variant — the homepage evidence module. One sentence, set at
   statement scale, with the full quote a click away rather than disclosed
   in place. Shares .matters__statement's treatment. */
.quote--pull {
  background: none;
  border: 0;
  padding: 0;
}
.quote--pull .quote__body p {
  font-family: var(--display);
  font-weight: 500;
  font-size: var(--fs-h3);
  line-height: 1.3;
  letter-spacing: -0.02em;
  color: var(--ink);
  text-wrap: balance;
}
.quote--pull .quote__attrib { border-top: 0; padding-top: 0; margin-top: 24px; }

/* Homepage evidence module: pull-quote, then the wall, then the two links out.
   Spacing lives on the combinators so neither partial has to know what follows
   it — both are used standalone elsewhere. */
.quote + .logo-wall { margin-top: clamp(40px, 5vw, 64px); }
.evidence__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 16px 40px;
  margin-top: 36px;
}

@media (max-width: 600px) {
  .quote__attrib { gap: 14px; }
  .quote__logo { max-width: 50px; max-height: 38px; }
}

/* ---- Publications ----
   An editorial list, newest first. Scholarly metadata is itself the
   hierarchy, so this is a list with a reading order, not a card grid. */
.pubs__group + .pubs__group { margin-top: 44px; }
.pubs__theme {
  font-family: var(--display);
  font-weight: 600;
  font-size: var(--fs-h4);
  color: var(--ink);
  letter-spacing: -0.01em;
  margin: 0 0 18px;
}
.pubs {
  list-style: none;
  margin: 0;
  padding: 0;
  border-top: 1px solid var(--rule);
}
.pub {
  padding: 22px 0;
  border-bottom: 1px solid var(--rule);
}
.pub__title {
  font-family: var(--display);
  font-weight: 600;
  font-size: var(--fs-h4);
  line-height: 1.35;
  letter-spacing: -0.01em;
  color: var(--ink);
  text-decoration: none;
  text-wrap: pretty;
  display: inline;
  background-image: linear-gradient(var(--rule-strong), var(--rule-strong));
  background-size: 100% 1px;
  background-repeat: no-repeat;
  background-position: 0 100%;
  transition: color .15s ease, background-image .15s ease;
}
.pub__title:hover {
  color: var(--primary);
  background-image: linear-gradient(var(--primary), var(--primary));
}
@media (prefers-reduced-motion: reduce) {
  .pub__title { transition: none; }
}
.pub__meta {
  margin: 10px 0 0;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 6px 14px;
  font-size: var(--fs-body);
  color: var(--ink-4);
  line-height: 1.55;
}
.pub__journal { font-style: italic; }
.pub__year {
  font-family: var(--mono);
  font-size: var(--fs-micro);
  letter-spacing: 0.06em;
}
/* Named, already-public trial the paper reports on. Only set where the trial
   is public under its own name — most sponsors are confidential. */
.pub__trial {
  font-family: var(--mono);
  font-size: var(--fs-micro);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--primary-2);
  background: var(--mist);
  border-radius: var(--radius-xs);
  padding: 3px 8px;
}

/* ---- Publications jump-nav ----
   Links to the theme groups below, which includes/publication-list.php makes
   addressable whenever 'grouped' is set. Deliberately not .gs-chip: that class
   belongs to the Global Studies section of this file, and these are links,
   which need a hover state a static chip does not. Focus comes from the global
   :focus-visible rule. The bottom margin is this block's own — .section__head
   owns the space above it, and .pubs carries no top margin. */
.pubs-jump {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin: 0 0 clamp(32px, 4vw, 48px);
}
.pubs-jump__link {
  display: inline-flex;
  align-items: baseline;
  gap: 8px;
  font-size: var(--fs-micro);
  color: var(--ink-3);
  text-decoration: none;
  background: var(--card-on-white);
  border: 1px solid var(--rule);
  border-radius: var(--radius-pill);
  padding: 9px 14px;
  transition: color .15s ease, border-color .15s ease;
}
.pubs-jump__link:hover {
  color: var(--primary);
  border-color: rgba(48,98,176,0.3); /* unspaced, matching the other 8 in this file so they grep as one value */
}
.pubs-jump__count {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  color: var(--ink-4);
}
@media (prefers-reduced-motion: reduce) {
  .pubs-jump__link { transition: none; }
}

/* ---- Case study card ----
   Nothing renders while $evidence['case_studies'] is empty; this is here so
   the first real study needs one array entry and a PDF, nothing else. */
.cstudy__metric {
  font-family: var(--mono);
  font-size: clamp(26px, 2.4vw, 32px);
  font-weight: 300;
  line-height: 1;
  letter-spacing: -0.02em;
  color: var(--primary-2);
}

/* ============================================================
   MOBILE CARD GUTTER NORMALISATION
   Keep this block at the end of the file so it wins the cascade
   over the component declarations above.
   See README: styles.css — Mobile card gutter normalisation.
   ============================================================ */
@media (max-width: 600px) {
  /* Section-level dark cards (--radius-2xl's only consumers) drop to the
     standard card radius on phones. */
  :root { --radius-2xl: var(--radius-lg); }
  .approach__cell,
  .widen__card,
  .numbers__cell,
  .contact,
  .matters__list li,
  .cert,
  .fact,
  .consult__card,
  .ccard,
  .cform,
  .assure__item,
  .gs-presence__cell,
  .gs-collab__card,
  /* :not(--pull) — the pull-quote is bare text on the section background, not
     a card, so the shared card inset would indent it out of alignment. */
  .quote:not(.quote--pull),
  .risk-list li {
    padding-left: var(--card-pad-mobile);
    padding-right: var(--card-pad-mobile);
  }
  /* .contact insets its content with .contact__wrap rather than on the card
     itself, so once .contact takes the normalised padding above, the wrap adds
     a second inset on top of it and pushes the CTA text past every other
     section. Drop the wrap's inset on phones and let the card own it. */
  .contact__wrap {
    padding-left: 0;
    padding-right: 0;
  }
  /* concentric inside .ccard: inner = outer(--radius-2xl, 16px here) -
     .ccard side padding(--card-pad-mobile, 18px) = -2px, floored at
     --radius-sm so the inner card keeps a visible curve below the outer 16px */
  .cform {
    border-radius: max(var(--radius-sm), calc(var(--radius-2xl) - var(--card-pad-mobile)));
  }
}
