/* Archivo's @font-face rules are NOT here. The prototype declared them beside
   itself in design/assets/; in the theme the stylesheet lives in assets/css/ and
   the fonts in assets/fonts/, so the prototype's relative paths 404'd. The theme
   declares the family once in assets/css/fonts.css, which is enqueued first. */

/* ===========================================================================
   THE BOSON GROUP — corporate site stylesheet
   Boson design system v1.0. Every brand value is declared once, here.

   Signature graphic: the Boson Field — the rounded vertical forms of the
   wordmark, filled with the Boson Spectrum — plus one dominant beam per
   composition (design system §7). The gradient never touches type.

   Colour is held to the mandated ratio: white and light 55–70%,
   Midnight 15–25%, Blue and Cyan 8–15%, Purple and Indigo 5–10%.
   ======================================================================== */

/* ============ ONE DIALECT: THESE ARE ALIASES, NOT VALUES ============
   Added 2026-09-01, Wave A.1 of docs/uiux-audit.md.

   WHAT WAS WRONG. This stylesheet made 863 var() calls and four of them reached
   a --bsn-* token; it declared its own 80 locals instead, while layout.css and
   news.css used --bsn-* exclusively. Two vocabularies for the same values, and
   nineteen system tokens defined and never referenced. Changing a colour meant
   knowing which file a component happened to live in.

   WHAT THIS DOES. Every local below whose value was ALREADY IDENTICAL to a
   system token is now an alias for it. All 863 call sites keep working untouched
   and there is one place to author a value. This is intended to be a zero-pixel
   change; compare-render.py across twelve pages is the proof, and any diff it
   reports is a bug in the aliasing rather than an improvement.

   FIVE LOCALS ARE DELIBERATELY NOT ALIASED, because their values genuinely
   differ and aliasing them would repaint the site rather than tidy it:

     --sans      "Archivo"  vs  --bsn-font-family-base 'Roboto'
                 The live typeface is Archivo. CLAUDE.md's stack section says
                 Roboto. One of the two is stale and it is not a CSS decision.
     --lavender  #efeaf6    vs  --bsn-color-lavender #f3eff8
                 Deliberate: the comment on it says "one step deeper".
     --line      .14 alpha  vs  --bsn-color-border .12
     --max       2560px     vs  --bsn-content-max-width 1280px
     --r-lg      22px       vs  --bsn-radius-panel 24px

   Each needs the design-system owner, not a refactor. Until then they stay
   local and this comment is the record of why.
   =================================================================== */
:root{
  /* THE DESIGN SYSTEM'S OWN VALUES. Declared once, unchanged, and not assigned
     to anything directly. Boson Blue is still Boson Blue whatever the site
     currently paints its links with. */
  --purple:var(--bsn-color-purple); --indigo:var(--bsn-color-indigo); --blue:var(--bsn-color-blue); --cyan:var(--bsn-color-cyan);
  --midnight:var(--bsn-color-midnight); --slate:var(--bsn-color-slate); --white:var(--bsn-color-white);
  --boson-mist:var(--bsn-color-mist); --boson-lavender:var(--bsn-color-lavender);
  --spectrum:var(--bsn-gradient-spectrum);

  /* THE ROLES. Which approved value does which job. Arslan chose this weighting
     on 25 August 2026 from five options at design/options.html: Boson Blue on
     Boson Mist read cold, and the pair also failed AA - #007BC1 is 4.20:1 on
     #EEF7FC, which is why a separate link colour had to exist for that ground
     at all. Indigo on Lavender clears 6.08:1 and needs no exception.

     Three values here are NOT in the design system and are marked. Everything
     else is a pointer, so the whole palette is these six lines. */
  --accent:var(--bsn-color-indigo);          /* links, numerals, rules, hover borders */
  --accent-hi:var(--bsn-color-purple);       /* selection, hover accents on light grounds */
  --accent-rev:#cbbef5;            /* NOT a system value - the same accent for a
                                      Midnight ground. Purple on Midnight is
                                      1.53:1; this is 7.98:1. Light enough to
                                      also clear 3:1 as the nav underline on the
                                      glass masthead, which composites to
                                      rgb(78,105,128) over a white section - the
                                      case that ruled out anything darker. */
  --mist:var(--bsn-color-lavender);    /* the pale band. Boson Lavender now, not Mist */
  --lavender:#efeaf6;              /* NOT a system value - one step deeper, so the
                                      two pale bands still read as two bands */

  --success:var(--bsn-color-success); --warning:var(--bsn-color-warning); --error:var(--bsn-color-error); --focus:var(--bsn-color-focus-ring);
  --warning-ink:#7d4b00;   /* warning-600 fails AA at label sizes on Mist */
  /* The ground of a .tbd placeholder. The design system has no warning surface,
     so this is not a brand value and is deliberately not in the palette above;
     it is named here only so the literal appears once. */
  --warning-tint:#fdf6e7;
  /* The same warning, for a Midnight ground. 8.9:1. */
  --warning-tint-ink:#f2c879;

  --ink:var(--bsn-color-text); --ink-2:var(--bsn-color-text-secondary);
  /* Darkened from #5c7387 on 25 August 2026. It was 4.35:1 on Boson Lavender
     - already failing AA wherever `.dim-text` sat on the second pale band -
     and moving the accent scheme onto Lavender would have made that the
     common case rather than the rare one. #526578 is 5.30:1 and 5.09:1 on
     the two bands and 6.01:1 on white. */
  --ink-3:#526578;
  --link-on-mist:#474799;         /* NOT a system value - the accent, darkened for the pale band */
  --line:rgba(9,47,79,.14); --line-strong:rgba(9,47,79,.24);

  --s1:var(--bsn-space-1); --s2:var(--bsn-space-2); --s3:var(--bsn-space-3); --s4:var(--bsn-space-4); --s6:var(--bsn-space-5);
  --s8:var(--bsn-space-6); --s12:var(--bsn-space-7); --s16:var(--bsn-space-8); --s24:var(--bsn-space-9);

  --r-sm:var(--bsn-radius-control); --r-md:var(--bsn-radius-card); --r-lg:22px;
  --sh-1:var(--bsn-shadow-1);
  --sh-2:var(--bsn-shadow-2);

  /* The canvas scales with the viewport instead of sitting in a 1200px column.
     One fluid inset, used by every band and by the masthead, so the wordmark,
     every heading and every grid share the same left edge at any width. The
     2560px cap exists only so a 4K display does not stretch a four-column grid
     to absurdity; every common desktop width is below it, so the page is
     edge-to-edge and the masthead spans the screen.

     This is wider than the design system's --bsn-content-max-width (1280px).
     The system's figure is a measure for text, and it still governs text: every
     paragraph here is capped in ch, not in px. What changed is the frame. */
  --max:2560px; --pad:clamp(20px,4.2vw,104px);
  --cond:"Archivo","Archivo Narrow",Arial,sans-serif;
  --sans:"Archivo",Arial,Helvetica,sans-serif;
  --cw:80%;                       /* the condensed instance of the width axis */
}

*,*::before,*::after{box-sizing:border-box}
html{-webkit-text-size-adjust:100%;scroll-behavior:smooth}
@media (prefers-reduced-motion:reduce){html{scroll-behavior:auto}}
body{
  margin:0;background:var(--white);color:var(--ink);
  font-family:var(--sans);font-size:17px;line-height:1.65;
  font-synthesis:none;font-variant-numeric:tabular-nums;-webkit-font-smoothing:antialiased;
}
h1,h2,h3,h4,p,ul,ol,dl,dd,dt,figure,blockquote,table{margin:0}
ul,ol{padding:0;list-style:none}
img{max-width:100%;display:block}
::selection{background:var(--accent-hi);color:#fff}
/* The focus ring, and why it is two colours rather than one.
   --focus (#6E56CF) is the design system's value and it is 5.39:1 on white, but
   only 2.54:1 on Midnight - under the 3:1 that WCAG 2.2 SC 2.4.11 asks of a
   focus indicator against its adjacent colour. That was already true before the
   palette moved; moving the accent to Indigo made it worse, because the ring and
   the accent are now neighbours in hue as well.
   So the ring is the system value on light grounds and the reversed accent on
   dark ones - 7.98:1 on Midnight - keyed off the same containers that already
   switch link colour. */
:focus-visible{outline:2px solid var(--focus);outline-offset:2px}
.section--dark :focus-visible,
.pagehead--film :focus-visible,
.hero :focus-visible,
.masthead :focus-visible,
.sitefoot :focus-visible{outline-color:var(--accent-rev)}
a{color:var(--accent);text-decoration-thickness:1px;text-underline-offset:3px}
a:hover{color:var(--midnight)}
*{scrollbar-color:var(--slate) var(--mist);scrollbar-width:thin}
::-webkit-scrollbar{width:11px;height:11px}
::-webkit-scrollbar-track{background:var(--mist)}
::-webkit-scrollbar-thumb{background:var(--slate);border-radius:6px}
::-webkit-scrollbar-thumb:hover{background:var(--midnight)}
input,textarea,button{font:inherit;color:inherit}
input,textarea{caret-color:var(--accent)}

.wrap{max-width:var(--max);margin:0 auto;padding:0 var(--pad)}
.skip{
  position:absolute;left:-9999px;top:0;z-index:99;background:var(--midnight);color:#fff;
  padding:12px 18px;font-family:var(--cond);font-stretch:var(--cw);text-transform:uppercase;
  letter-spacing:.09em;font-size:12px;font-weight:700;border-radius:0 0 var(--r-sm) 0;
}
.skip:focus{left:0}

/* ------------------------------- labels ------------------------------- */
.lettering{
  font-family:var(--cond);font-stretch:var(--cw);font-weight:700;text-transform:uppercase;
  letter-spacing:.11em;font-size:11.5px;line-height:1.3;color:var(--ink-2);
}
.dim-text{
  font-family:var(--cond);font-stretch:var(--cw);font-weight:400;letter-spacing:.04em;
  font-size:13px;color:var(--ink-3);
}
.flag{
  font-family:var(--cond);font-stretch:var(--cw);font-weight:700;font-size:10.5px;
  letter-spacing:.1em;text-transform:uppercase;color:var(--warning-ink);
  border:1px solid var(--warning-ink);border-radius:var(--r-sm);padding:3px 8px;margin-left:8px;
  display:inline-block;white-space:nowrap;
}
/* --warning-ink is 4.6:1 on white and 1.87:1 on Midnight, and the portraits put
   a flag inside the dark CTA band for the first time. Same signal, legible
   ground: #f2c879 is 8.9:1 on Midnight. A staging marker still has to pass the
   gate - it is on the page a reviewer is reading. */
.section--dark .flag,
.hero .flag,
.pagehead--film .flag,
.tree-card--self .flag{color:var(--warning-tint-ink);border-color:var(--warning-tint-ink)}

/* ============================ THE BOSON FIELD ============================ */
.bf{display:block;height:100%;width:auto}
.beam{position:absolute;top:0;bottom:0;left:0;width:10px;background:var(--spectrum)}

/* ============================== MASTHEAD ============================== */
.masthead{border-bottom:0;background:transparent;position:relative;z-index:20}
.masthead-in{display:flex;align-items:center;gap:var(--s6);padding:var(--s4) var(--pad);
  justify-content:space-between;position:relative}
.brand{flex:none;display:block}
.brand img{height:58px;width:auto}
/* Logo, nav, action - the nav centred between the other two rather than shoved
   against the button, which is what the visualisation shows. */
.mainnav{display:flex;flex-wrap:wrap;gap:var(--s2);margin-inline:auto;position:relative}
.mainnav a{
  font-size:15px;font-weight:500;color:#fff;text-decoration:none;
  padding:10px 14px;border-radius:var(--r-sm);min-height:44px;display:flex;align-items:center;
}
.mainnav a:hover{background:rgba(255,255,255,.12)}
/* The static indicator is the no-JavaScript one. design.js replaces it with the
   sliding rule by adding `.mainnav--slide`, so the current page is always marked
   whether or not the script runs - and never marked twice. */
.mainnav a[aria-current=page]{box-shadow:inset 0 -2px 0 var(--accent-rev)}
.mainnav--slide a[aria-current=page]{box-shadow:none}
.nav-slide{
  position:absolute;left:0;bottom:4px;width:0;height:2px;border-radius:2px;
  background:var(--accent-rev);opacity:0;pointer-events:none;
}

/* ================================ TYPE ================================ */
h1,h2,h3{font-family:var(--cond);font-stretch:var(--cw);font-weight:700;letter-spacing:-.018em}
.hero-copy h1{
  font-size:clamp(2.5rem,4.6vw,6.4rem);line-height:1.02;letter-spacing:-.028em;
  color:#fff;text-wrap:balance;max-width:22ch;
}
.pagehead{border-bottom:1px solid var(--line);padding:var(--s16) 0 var(--s12);background:var(--mist)}
.pagehead h1{font-size:clamp(2.1rem,3.6vw,4.2rem);line-height:1.06;color:var(--ink);text-wrap:balance;max-width:24ch}
.pagehead-sub{margin-top:var(--s4);font-size:19px;line-height:1.55;color:var(--ink-2);max-width:56ch}
.sec-h{font-size:clamp(1.6rem,2.4vw,3.4rem);line-height:1.14;color:var(--ink);text-wrap:balance;max-width:28ch}
.sec-note{margin-top:var(--s4);font-size:18px;line-height:1.6;color:var(--ink-2);max-width:62ch}
.prose{font-size:17px;line-height:1.7;color:var(--ink-2);max-width:66ch}
.prose + .prose{margin-top:var(--s4)}
.prose strong{color:var(--ink);font-weight:700}
.pull{
  margin-top:var(--s6);padding-left:var(--s6);border-left:2px solid var(--accent);
  font-family:var(--cond);font-stretch:var(--cw);font-weight:700;font-size:22px;
  line-height:1.28;color:var(--ink);max-width:34ch;
}
.small{font-size:14.5px;line-height:1.55;color:var(--ink-2)}

/* =============================== BUTTONS =============================== */
/* WHY THE SECOND SELECTOR. A core/button saves as
   <a class="wp-block-button__link btn wp-element-button">, and both core and
   GeneratePress style .wp-block-button__link above .btn's single-class
   specificity. Measured on /process/ with the button in a synced pattern, six
   declarations were being taken away from the design:

     GeneratePress  .button, .wp-block-button .wp-block-button__link
                    padding 12px 24px -> 10px 20px, display inline-flex -> inline-block
     GeneratePress  .wp-block-button .wp-block-button__link
                    font-size 16px -> inherit (17px), line-height 1.65 -> inherit (28.05px)
     GeneratePress  a.wp-block-button__link:not(.has-background)
                    background var(--midnight) -> #55555e

   The last one is the CTA changing colour, so this is a design regression, not
   a rounding difference. Naming the core class in the same selector list keeps
   ONE set of declarations for the button: a separate override block would be a
   second copy of the design's button, and two copies drift. Reported in
   docs/wordpress-editability/reports/render-diff-slice-2.md rather than
   absorbed, per section 2.3 of the target content architecture.

   line-height is stated rather than inherited for the same reason. It resolves
   to exactly the 1.65 the body already passes down, so nothing outside a
   core/button moves - but stated, it beats GeneratePress's `inherit`, which
   would otherwise freeze the anchor at its parent's computed pixel value. */
.btn,
.tertiary,
.bsn-enquiry__submit,
a.btn.wp-block-button__link:not(.has-background){
  display:inline-flex;align-items:center;gap:10px;min-height:48px;padding:12px 24px;
  border:1px solid var(--midnight);border-radius:var(--r-md);background:var(--midnight);
  color:#fff;text-decoration:none;font-size:16px;font-weight:500;line-height:1.65;cursor:pointer;
  transition:background .18s ease-out,border-color .18s ease-out,box-shadow .18s ease-out;
}
/* Cyan is the accent the palette already has and the build barely used. The
   design system's usage ratio gives Blue and Cyan 8-15% together, so this is
   spending an allowance rather than inventing a colour - which matters while the
   palette is still sampled from a raster and risk 6 is open. A new hue
   ("Constellation gold") would be off-palette and needs the design-system owner,
   not a stylesheet. */
.btn:hover,
.tertiary:hover,
.bsn-enquiry__submit:hover,
a.btn.wp-block-button__link:not(.has-background):hover{background:var(--accent);border-color:var(--accent);color:#fff;box-shadow:var(--sh-1)}
.btn:disabled{background:var(--slate);border-color:var(--slate);cursor:not-allowed}

/* ===================== THE SKIN CONTRACT — READ BEFORE ADDING ONE =====================
   The base button rule above is `a.btn.wp-block-button__link:not(.has-background)`,
   specificity 0,3,1. It is that high on purpose, to beat core's and
   GeneratePress's own button styles once a button becomes a core/button block.

   THE CONSEQUENCE, WHICH HAS COST THREE DEFECTS. A skin class written as
   `.btn--x` is 0,1,0 and LOSES to it the moment the markup is a block. It looks
   fine in the prototype and renders wrong on the site.

     .btn--white   hero buttons rendered dark-on-dark, invisible until hover
     .pv-pill      pill label took the Mist link colour on navy
     (earlier)     a band's button text hit 2.05:1, recorded at .section--mist

   SO: a new skin must be declared TWICE, at both specificities -
   `.btn--x` and `a.btn--x.wp-block-button__link:not(.has-background)` - and, if
   it can appear on a pale ground, added to the `:not()` list on the
   `.section--mist a:not(.btn):not(.tertiary):not(.pv-pill)` group below.
   Two places. There is no way around it that does not mean lowering the base
   rule and losing to core.
   ====================================================================================== */

/* ---- :active. Press feedback, which the whole site was missing.
   Nielsen's visibility-of-system-status: a control that looks identical
   pressed and unpressed leaves the reader unsure the tap registered, and on
   touch there is no hover state to fall back on. 1px is enough to read as
   depression without moving the layout. */
.btn:active,
a.btn.wp-block-button__link:not(.has-background):active,
.bsn-enquiry__submit:active{transform:translateY(1px);box-shadow:none}
.tertiary:active,
.tertiary--rev:active{opacity:.72}
.mainnav a:active{opacity:.72}
.nx-card:active{transform:translateY(0)}
.pv-iv-card:active{transform:translateY(0)}
.pv-pill:active{transform:translateY(1px)}
@media (prefers-reduced-motion:reduce){
  .btn:active,
  a.btn.wp-block-button__link:not(.has-background):active,
  .bsn-enquiry__submit:active,
  .pv-pill:active{transform:none}
}
/* Layout only. The masthead button is the same button at the same size; this
   just stops it stretching in the flex row. */
.btn--sm{flex:none}
/* The dark-ground skin. Same shape, same size, inverted so it clears contrast
   on midnight and on the film. .tertiary--rev is the same link-as-button on a
   dark band. */
/* THE BLOCK SELECTORS ARE NOT DECORATION, THEY ARE THE WHOLE FIX.
   Broken 31 August 2026 by the whole-page block conversion and caught by Arslan:
   "in hero banner the buttons are not visible till you hover".

   The hero's two calls to action were `<a class="btn btn--white">` in raw HTML,
   and `.btn--white` applied cleanly. Converting the page to core blocks turned
   them into core/button, which adds `wp-block-button__link` - and the base rule
   above is `a.btn.wp-block-button__link:not(.has-background)` at specificity
   0,3,1, while `.btn--white` is 0,1,0. So the base midnight fill won and the
   inverted skin lost: two dark navy buttons on a dark navy hero, invisible until
   the hover rule repainted them.

   The render diff had already flagged this. Its "classes core adds" table listed
   `wp-block-button__link` with "YES - check this" against it, meaning the class
   carries a rule in design.css. That was the warning, in writing, and it was not
   chased. A generated class colliding with a hand-written one is exactly what
   that column exists to catch.

   Fixed by matching the base rule's specificity rather than by !important, so
   the cascade still reads as a cascade. */
.btn--white,
.tertiary--rev,
a.btn--white.wp-block-button__link:not(.has-background),
a.tertiary--rev.wp-block-button__link:not(.has-background){
  background:#fff;color:var(--midnight);border-color:#fff;
}
.btn--white:hover,
.tertiary--rev:hover,
a.btn--white.wp-block-button__link:not(.has-background):hover,
a.tertiary--rev.wp-block-button__link:not(.has-background):hover{
  background:var(--mist);border-color:var(--mist);color:var(--midnight);
}
/* The outlined skin for a dark ground. Arslan asked for a white outline so the
   hero's calls to action read as buttons; the fill above fixes the primary, and
   this gives the secondary the same affordance without two identical white slabs
   competing for the same click. Rest is the outline, hover fills - so the
   secondary ends up looking like the primary at the moment of intent, which is
   the right way round.

   Same specificity treatment as .btn--white, for the same reason. */
.btn--ghost,
a.btn--ghost.wp-block-button__link:not(.has-background){
  background:transparent;color:#fff;border-color:rgba(255,255,255,.62);
}
.btn--ghost:hover,
a.btn--ghost.wp-block-button__link:not(.has-background):hover{
  background:#fff;color:var(--midnight);border-color:#fff;
}

.actions{display:flex;flex-wrap:wrap;gap:var(--s3);margin-top:var(--s8);align-items:center}

/* ================================ HERO ================================ */
.hero{position:relative;background:var(--midnight);color:#fff;overflow:hidden}
.hero-media{position:absolute;inset:0}
.hero-media .slot{height:100%;width:100%;border:0;border-radius:0;aspect-ratio:auto!important}
.hero-media .slot-mark{display:none}          /* one Boson Field per composition */
.hero-media .slot figcaption{padding:var(--pad);max-width:var(--max);margin:0 auto;width:100%}
.hero-media::after{
  content:"";position:absolute;inset:0;
  background:linear-gradient(100deg,rgba(9,47,79,.94) 0%,rgba(9,47,79,.86) 42%,rgba(9,47,79,.55) 100%);
}
.hero-in{position:relative;z-index:2;display:flex;align-items:center;gap:var(--s16);
  padding-top:var(--s24);padding-bottom:var(--s24);min-height:min(78vh,680px)}
.hero-copy{flex:1 1 auto}
.hero-sub{margin-top:var(--s6);font-size:clamp(1.15rem,1.15vw,1.45rem);line-height:1.55;color:rgba(255,255,255,.88);max-width:46ch}
.hero-note{margin-top:var(--s4);font-size:15px;color:rgba(255,255,255,.72)}
.hero-field{flex:none;height:250px;display:block}
@media (max-width:1080px){.hero-field{display:none}}

/* On the film hero the Field is not a graphic beside the copy - it is part of
   the picture. It leaves the container, spans the right of the frame, rises from
   the bottom edge and bleeds off the right one, so it reads as something in the
   shot rather than something placed on top of it.

   `screen` blending is what merges it: the spectrum lightens the film where the
   forms are instead of covering it, so the motion behind them still shows
   through and the edges have no seam. The mask fades the feet into the bottom
   scrim, which is the difference between "rising out of frame" and "cropped".

   It sits at z-index 1: above the film, below the copy at 2, and the copy is
   capped at 46ch so the two never meet. */
.hero--film .hero-field{
  position:absolute;right:0;bottom:0;top:auto;z-index:1;
  width:clamp(380px,46vw,1100px);height:82%;overflow:hidden;
  display:block;pointer-events:none;
  /* Screen blending is the merge: the spectrum lightens the film where the
     forms are rather than covering it, so the motion keeps showing through and
     there is no edge anywhere. At .58 the film is the subject and the forms are
     in the shot with it. */
  mix-blend-mode:screen;opacity:.62;
  /* Faded at both ends. Solid feet on the bottom edge is what made them read as
     a chart sitting on an axis; dissolving into the scrim at the bottom and
     leaving the frame at the top is what makes them forms rising through it. */
  -webkit-mask-image:linear-gradient(to bottom,transparent 0,rgba(0,0,0,.30) 20%,#000 48%,#000 74%,transparent 100%);
  mask-image:linear-gradient(to bottom,transparent 0,rgba(0,0,0,.30) 20%,#000 48%,#000 74%,transparent 100%);
}
.hero--film .hero-field .bf{
  position:absolute;right:calc(var(--pad) * -0.35);bottom:0;
  width:100%;height:auto;min-width:380px;
}
@media (max-width:1080px){
  /* Kept, but quieter: at these widths the copy occupies most of the frame, so
     the forms sit behind it as texture rather than as a second subject. */
  .hero--film .hero-field{
    display:flex;width:74vw;height:min(52%,420px);opacity:.55;
  }
}
@media (max-width:600px){.hero--film .hero-field{display:none}}

/* ============================== SECTIONS ============================== */
/* Vertical rhythm scales with the horizontal frame. A flat 96px was drawn for a
   1200px column; on a 2560 canvas with 104px side gutters it reads as crowding. */
.section{padding:clamp(72px,8vw,120px) 0}
.section--mist{background:var(--mist)}
.section--lavender{background:var(--lavender)}
.section--dark{background:var(--midnight);color:#fff;position:relative;overflow:hidden}
.section--dark .sec-h,.section--dark h2,.section--dark h3{color:#fff}
.section--dark .sec-note,.section--dark .prose{color:rgba(255,255,255,.84)}
.section--dark .lettering{color:rgba(255,255,255,.74)}
.section--dark .dim-text{color:rgba(255,255,255,.70)}
/* ...but a white panel inside a Midnight section keeps its own ink. Without
   this the reverse rules above land on the enquiry form and every label
   disappears into the panel. */
.section--dark .panel h2,
.section--dark .panel h3,
.section--dark .panel .panel-h{color:var(--ink)}
.section--dark .panel p,
.section--dark .panel .prose{color:var(--ink-2)}
.section--dark .panel .lettering{color:var(--ink-2)}
.section--dark .panel .dim-text,
.section--dark .panel .privacy{color:var(--ink-3)}
.section--dark .panel .err{color:var(--error)}
.two-col{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:var(--s16);align-items:start}
/* The statement column sits against the middle of the panel beside it rather
   than against its top. Arslan, 25 August 2026, for the homepage's "A model
   backed by experience": a short heading and one paragraph left hanging at the
   top of a panel five cards tall reads as an orphan.
   `start` stays the default - it is right wherever both columns are text, and
   wherever the left column is long enough to set the height itself. */
.two-col--middle{align-items:center}

/* =========================== IMAGE SLOTS ===========================
   An honest placeholder: it names the asset required and its size. It is
   never dressed up as artwork, and it carries the brand mark rather than a
   grey box, so the composition can be judged before the photography lands.
   ================================================================= */
.slot{
  position:relative;display:flex;align-items:flex-end;overflow:hidden;
  background:var(--mist);border:1px solid var(--line);border-radius:var(--r-lg);
}
.slot--white{background:#fff}
.slot--dark{background:var(--midnight);border:0;border-radius:0}
.slot-mark{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;opacity:.20}
.slot-mark .bf{height:58%}
.slot--dark .slot-mark{opacity:.30}
.slot figcaption{
  position:relative;z-index:2;display:flex;flex-direction:column;gap:3px;
  padding:var(--s6);
}
.slot-kind{font-size:16px;font-weight:500;color:var(--ink)}
.slot--dark .slot-kind{color:#fff}
.slot--dark .lettering{color:rgba(255,255,255,.80)}
.slot--dark .dim-text{color:rgba(255,255,255,.66)}

/* ================================ PROOF ================================ */
.proof-h{font-size:clamp(1.5rem,2.6vw,2rem);line-height:1.14;color:var(--ink)}
.proof-note{margin-top:var(--s4);font-size:17px;line-height:1.6;color:var(--ink-2);max-width:64ch}
/* The numbers were 48px in a page about whether this group is substantial enough
   to hand a company to. They are the evidence, so they get the display step. */
.stats{
  display:grid;grid-template-columns:repeat(4,minmax(0,1fr));
  gap:clamp(24px,2.6vw,48px);margin-top:var(--s12);
}
.stat{border-top:2px solid var(--midnight);padding-top:var(--s4)}
.stat .v{
  font-family:var(--cond);font-stretch:var(--cw);font-weight:700;
  font-size:clamp(2rem,3.4vw,2.8rem);line-height:1;letter-spacing:-.022em;color:var(--midnight);
}
.stat .k{
  margin-top:var(--s3);font-family:var(--cond);font-stretch:var(--cw);font-weight:700;
  text-transform:uppercase;letter-spacing:var(--bsn-tracking-label,.08em);
  font-size:12px;line-height:1.4;color:var(--ink-2);
}

/* The marks of the two companies the figures actually belong to, beside the
   figures. This is the attribution the numbers already carried in prose, made
   visible - not a logo wall. */
.proof-marks{
  display:flex;flex-wrap:wrap;align-items:center;gap:clamp(20px,2.4vw,40px);
  margin-top:clamp(28px,3vw,48px);
}
.proof-mark{
  display:grid;place-items:center;background:var(--white);
  border:1px solid var(--line);border-radius:var(--r-md);
  padding:12px 20px;min-height:64px;min-width:150px;
}
.proof-mark img{max-height:28px;max-width:100%;width:auto}
.proof-marks-note{
  flex:1 1 22ch;min-width:22ch;font-size:15px;line-height:1.55;color:var(--ink-2);max-width:46ch;
}
.proof-marks-note strong{color:var(--ink);font-variant-numeric:tabular-nums}
.src{margin-top:var(--s8);padding-top:var(--s4);border-top:1px solid var(--line);
  display:flex;flex-wrap:wrap;gap:6px 14px;align-items:baseline}

/* =============================== PILLARS =============================== */
/* Four levels of hierarchy where there used to be two: a Spectrum numeral, a
   larger title, a pull quote at reading size, then the qualifying sentence in
   support. The numeral is typographic rather than an icon - build.py's contract
   refuses "four icon cards" and the recorded refusal set includes "no icons", so
   a glyph set would break the brief the page was built to. */
.pillars{
  display:grid;grid-template-columns:repeat(4,minmax(0,1fr));
  gap:clamp(28px,3vw,56px);margin-top:var(--s12);
}
.pillar{
  border-top:2px solid var(--midnight);padding-top:var(--s5,20px);
  transition:border-color var(--bsn-motion-micro,150ms) var(--bsn-easing-out,ease-out);
}
.pillar-n{
  display:block;font-family:var(--cond);font-stretch:var(--cw);font-weight:700;
  font-size:clamp(2.2rem,3.4vw,3.4rem);line-height:1;letter-spacing:-.02em;
  background:var(--spectrum);-webkit-background-clip:text;background-clip:text;
  color:transparent;margin-bottom:var(--s4);
}
.pillar h2,.pillar h3{font-size:clamp(1.3rem,1.7vw,1.6rem);line-height:1.15;color:var(--ink)}
.pillar-lede{
  margin-top:var(--s3);font-family:var(--cond);font-stretch:var(--cw);font-weight:700;
  font-size:clamp(1.05rem,1.2vw,1.25rem);line-height:1.32;color:var(--ink);
}
.pillar-note{margin-top:var(--s3);font-size:16px;line-height:1.6;color:var(--ink-2)}
/* Hover belongs on the whole column, not on a shadow: the top rule takes the
   accent. Nothing moves, because these sit under a heading a reader is reading. */
.pillar:hover{border-color:var(--accent-hi)}
@media (max-width:1080px){.pillars{grid-template-columns:1fr 1fr}}
@media (max-width:600px){.pillars{grid-template-columns:1fr}}

/* ================================ ROUTES ================================ */
.routes{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:var(--s4);margin-top:var(--s12)}
.route{
  display:flex;flex-direction:column;gap:6px;padding:var(--s6);background:#fff;
  border:1px solid var(--line);border-radius:var(--r-md);text-decoration:none;
  transition:box-shadow .18s ease-out,border-color .18s ease-out;
}
.route:hover{box-shadow:var(--sh-1);border-color:var(--accent)}
.route-t{
  font-family:var(--cond);font-stretch:var(--cw);font-weight:700;font-size:19px;color:var(--ink);
}
.route-d{font-size:15px;line-height:1.5;color:var(--ink-2)}

/* ================================ QUOTE ================================ */
.quote blockquote{
  margin-top:var(--s6);font-family:var(--cond);font-stretch:var(--cw);font-weight:400;
  font-size:clamp(1.5rem,3vw,2.3rem);line-height:1.34;letter-spacing:-.014em;color:#fff;max-width:36ch;
}
.quote .attrib{margin-top:var(--s6);padding-top:var(--s4);border-top:1px solid rgba(255,255,255,.28);max-width:36ch}
.quote .attrib .who{font-family:var(--cond);font-stretch:var(--cw);font-weight:700;font-size:18px;color:#fff}
.quote .attrib .role{margin-top:3px;font-size:15px;color:rgba(255,255,255,.80)}
.quote .src{border-top-color:rgba(255,255,255,.22)}
.quote-qualifier{margin-top:var(--s12);padding-top:var(--s4);
  border-top:1px solid rgba(255,255,255,.22);font-size:14.5px;line-height:1.55;max-width:78ch}

/* =============================== COMPARE =============================== */
.compare{display:grid;grid-template-columns:1fr 1fr;gap:0;margin-top:var(--s12);
  border:1px solid var(--line);border-radius:var(--r-lg);overflow:hidden}
.compare-col{padding:var(--s8)}
.compare-col:first-child{background:var(--mist)}
.compare-col + .compare-col{border-left:1px solid var(--line);background:#fff}
.compare-col h2,.compare-col h3{font-size:23px;color:var(--ink)}
.compare-col ul{margin-top:var(--s6)}
.compare-col li{
  padding:var(--s4) 0;border-top:1px solid var(--line);font-size:16.5px;line-height:1.55;color:var(--ink-2);
}
.compare-col li:first-child{border-top:0;padding-top:0}
.compare-col li b{color:var(--ink);font-weight:700}
.compare-foot{
  grid-column:1 / -1;border-top:1px solid var(--line);padding:var(--s6) var(--s8);
  background:var(--lavender);font-size:16px;color:var(--ink-2);
}
.compare-foot span{display:block;max-width:78ch}

/* ================================ TABLES ================================ */
/* WHY THESE ARE STILL TABLES. Four pages carry a two-column definition list -
   what we need from you, what happens next, what each field is for, what each
   term means. They are marked up as tables with `th scope="row"`, which is what
   a screen reader needs to read "Your email address: the route we use to answer
   you" as one pair. A card grid would read better and announce worse, so the
   markup is unchanged and only the treatment is new: one panel, hairline rows,
   an accent rule that draws in on hover, and a staggered arrival on scroll
   handled in design.js. Zebra striping is gone - it was doing the row-separation
   job that the hairline does more quietly. */
.table{
  width:100%;border-collapse:separate;border-spacing:0;margin-top:var(--s8);
  background:var(--white);border:1px solid var(--line);border-radius:var(--r-md);
  overflow:hidden;box-shadow:var(--sh-1);
}
.table th,.table td{text-align:left;vertical-align:top;padding:var(--s4) var(--s6)}
.table tbody tr{transition:background .22s ease-out}
.table tbody tr:hover{background:var(--mist)}
.table tbody tr + tr th,
.table tbody tr + tr td{border-top:1px solid var(--line)}
/* The rule is 2px of accent that grows from the top of the row. It is the same
   device as .pull's left border, at the same width and colour. */
.table tbody th{position:relative}
.table tbody th::before{
  content:"";position:absolute;left:0;top:var(--s4);bottom:var(--s4);width:2px;
  background:var(--accent);transform:scaleY(0);transform-origin:top;
  transition:transform .28s ease-out;
}
.table tbody tr:hover th::before{transform:scaleY(1)}
.table thead th{
  font-family:var(--cond);font-stretch:var(--cw);font-weight:700;text-transform:uppercase;
  letter-spacing:.1em;font-size:11.5px;color:#fff;background:var(--midnight);
}
.table thead th:first-child{border-radius:var(--r-sm) 0 0 0}
.table thead th:last-child{border-radius:0 var(--r-sm) 0 0}
.table tbody th{
  font-family:var(--cond);font-stretch:var(--cw);font-weight:700;text-transform:uppercase;
  /* --ink, not --ink-2. At 11.5px the label is small text, and --ink-2 on the
     panel's white is 3.72:1 - it failed the contrast gate the moment the row
     ground stopped being tinted. */
  letter-spacing:.09em;font-size:11.5px;line-height:1.5;color:var(--ink);width:30%;
}
.table tbody td{font-size:16.5px;line-height:1.6;color:var(--ink)}
@media (prefers-reduced-motion:reduce){
  .table tbody tr,
  .table tbody th::before{transition:none}
}
@media (max-width:600px){
  /* Two columns in 390px is 12ch of label against 14ch of answer. Stacked, the
     label sits above its answer and the pairing still reads top to bottom. */
  .table tbody th,
  .table tbody td{display:block;width:auto}
  .table tbody th{padding-bottom:0}
  .table tbody tr + tr td{border-top:0}
  .table tbody tr + tr th{border-top:1px solid var(--line)}
  .table tbody th::before{bottom:auto;height:14px}
}

/* ================================ CARDS ================================ */
.cards{display:grid;grid-template-columns:repeat(3,1fr);gap:var(--s6);margin-top:var(--s12)}
.card{background:#fff;border:1px solid var(--line);border-radius:var(--r-md);padding:var(--s8)}
.card h3{font-size:20px;color:var(--ink)}
.card p{margin-top:var(--s3);font-size:15.5px;line-height:1.6;color:var(--ink-2)}

/* =============================== STAGES =============================== */
.stages{margin-top:var(--s12);border-top:2px solid var(--midnight)}
.stage{
  display:grid;grid-template-columns:72px minmax(0,1fr) 210px;gap:var(--s6);
  padding:var(--s6) 0;border-bottom:1px solid var(--line);align-items:baseline;
}
.stage .no{
  font-family:var(--cond);font-stretch:var(--cw);font-weight:700;font-size:16px;
  letter-spacing:.08em;color:var(--accent);
}
.stage h2,.stage h3{font-size:21px;color:var(--ink)}
.stage p{margin-top:6px;font-size:16px;line-height:1.6;color:var(--ink-2);max-width:60ch}
.stages--nodur .stage{grid-template-columns:72px minmax(0,1fr)}
.stage .dur{
  justify-self:end;text-align:right;font-family:var(--cond);font-stretch:var(--cw);
  font-weight:700;text-transform:uppercase;letter-spacing:.08em;font-size:11.5px;color:var(--ink-2);
}

/* =============================== PORTFOLIO =============================== */
.folio{display:grid;gap:var(--s16)}
.folio-item{display:grid;grid-template-columns:440px minmax(0,1fr);gap:var(--s12);align-items:start}
.folio-item h2{font-size:clamp(1.6rem,2.6vw,2.1rem);color:var(--ink)}
.folio-meta{
  margin-top:var(--s2);font-family:var(--cond);font-stretch:var(--cw);font-weight:700;
  text-transform:uppercase;letter-spacing:.1em;font-size:11.5px;color:var(--accent);
}
.folio-item .prose{margin-top:var(--s4)}

/* =============================== REGIONS =============================== */
.regions{background:#fff;border:1px solid var(--line);border-radius:var(--r-lg);padding:var(--s8)}
.section--mist .regions{background:#fff}
.regions-h{font-size:20px;color:var(--ink)}
.regions dl{margin-top:var(--s4)}
.regions .row{padding:var(--s4) 0;border-top:1px solid var(--line)}
.regions dt{
  font-family:var(--cond);font-stretch:var(--cw);font-weight:700;text-transform:uppercase;
  letter-spacing:.1em;font-size:11px;color:var(--ink-3);
}
.regions dd{margin:5px 0 0;font-size:16px;color:var(--ink-2)}

/* ============================== THE CHAIN ==============================
   Five entities drawn as a descent, not a card grid. Steps 1-3 are one
   trunk; Contour and AutoSoft are siblings held by Boson, so they branch
   off it rather than continuing under it. The trunk stops at the last
   elbow — running it past the final entity would draw a level that does
   not exist. */
.chain{margin-top:var(--s12);border-top:1px solid var(--line-strong)}
.chain-step{
  position:relative;display:grid;
  grid-template-columns:240px minmax(0,1fr);
  column-gap:var(--s12);row-gap:var(--s2);align-items:start;
  padding:var(--s8) 0 var(--s8) 34px;
  border-bottom:1px solid var(--line);
}
/* the trunk: one segment per row, meeting flush to read as a single rule */
.chain-step::before{
  content:"";position:absolute;left:9px;top:0;bottom:0;width:2px;
  background:var(--line-strong);
  /* design.js draws the rail by tweening --rail on the step. It defaults to 1,
     so with no JavaScript the trunk is simply there. */
  transform-origin:50% 0;transform:scaleY(var(--rail,1));
}
.chain-step:last-child{border-bottom:0}
.chain-step:last-child::before{bottom:auto;height:calc(var(--s8) + 22px)}
/* Optical, not mechanical: these five logos run from a near-square mark to a
   6.6:1 wordmark, so a single max-height would leave Andromeda towering over
   Boson. Cap both axes and let the wide ones land shorter. */
.chain-step > img{grid-column:1;grid-row:1 / span 3;max-height:52px;width:auto;max-width:200px}
.chain-role{
  grid-column:2;font-family:var(--cond);font-stretch:var(--cw);font-weight:700;
  text-transform:uppercase;letter-spacing:.1em;font-size:11.5px;color:var(--accent);
}
.chain-note{grid-column:2;font-size:16.5px;line-height:1.6;color:var(--ink-2);max-width:60ch}
.chain-step > a,.chain-step > .dim-text{grid-column:2;justify-self:start}

/* Boson's own row. The trunk thickens and darkens where you are standing,
   rather than a separate rule being laid alongside it — and the emphasis is
   solid, not gradient, because the CTA carries this page's one beam. */
.chain-step--self{background:var(--mist)}
.chain-step--self::before{left:8px;width:4px;background:var(--midnight)}
.chain-step--self .chain-role{color:var(--midnight)}
.chain-step--self > img{max-height:64px}

/* held companies: indented, with an elbow off the trunk above */
.chain-step--held{padding-left:74px}
.chain-step--held::after{
  content:"";position:absolute;left:9px;top:calc(var(--s8) + 21px);width:57px;height:2px;
  background:var(--line-strong);
}

/* ================================= CTA ================================= */
.cta-grid{display:grid;grid-template-columns:minmax(0,1fr) 480px;gap:var(--s16);align-items:start}
.people{display:grid;grid-template-columns:1fr 1fr;gap:var(--s6);margin-top:var(--s8)}
.person{border-top:1px solid rgba(255,255,255,.28);padding-top:var(--s4)}
.person .nm{font-family:var(--cond);font-stretch:var(--cw);font-weight:700;font-size:19px;color:#fff}
.person .rl{margin-top:3px;font-size:15px;color:rgba(255,255,255,.84)}
.person .lo{margin-top:5px}
.alt{
  margin-top:var(--s8);padding-top:var(--s4);border-top:1px solid rgba(255,255,255,.28);
  font-size:16px;line-height:1.6;color:rgba(255,255,255,.84);max-width:48ch;
}
.alt a{color:#fff;text-decoration:underline}
.alt--light{border-top-color:var(--line);color:var(--ink-2)}
.alt--light a{color:var(--accent)}
/* the contact page shows the people on white, not on Midnight */
.section:not(.section--dark) .person{border-top-color:var(--line)}
.section:not(.section--dark) .person .nm{color:var(--ink)}
.section:not(.section--dark) .person .rl{color:var(--ink-2)}

/* ============================== ENQUIRY ============================== */
.panel{background:#fff;border:1px solid var(--line);border-radius:var(--r-lg);
  padding:var(--s8);box-shadow:var(--sh-1)}
.panel-h{font-size:21px;color:var(--ink);margin-bottom:var(--s6)}
.field-row{margin-bottom:var(--s4)}
label{display:block;margin-bottom:7px}
input[type=text],input[type=email],textarea{
  width:100%;min-height:48px;padding:12px 14px;border:1px solid var(--line-strong);
  border-radius:var(--r-sm);background:#fff;font-size:16px;
  transition:border-color .18s ease-out,box-shadow .18s ease-out;
}
textarea{min-height:100px;resize:vertical}
input::placeholder,textarea::placeholder{color:var(--ink-3);opacity:1}
input:hover,textarea:hover{border-color:var(--slate)}
input:focus,textarea:focus{outline:none;border-color:var(--accent);box-shadow:0 0 0 3px rgba(80,81,163,.20)}
input[aria-invalid=true],textarea[aria-invalid=true]{border-color:var(--error);border-width:2px}
.err{display:none;align-items:center;gap:7px;margin-top:7px;font-size:14.5px;color:var(--error)}
.err.on{display:flex}
.err svg{flex:none}
form .btn{width:100%;justify-content:center;margin-top:var(--s6)}
.privacy{margin-top:var(--s4);font-size:14.5px;line-height:1.55;color:var(--ink-3)}
.privacy--assure{color:var(--ink-2)}
.privacy--assure a{color:inherit;text-decoration:underline;text-underline-offset:2px}
.sent{display:none}
.sent.on{display:block}
.sent h3{font-size:21px;color:var(--ink);display:flex;align-items:center;gap:10px}
.sent p{margin-top:var(--s4);font-size:16px;line-height:1.6;color:var(--ink-2)}
.sent .dim-text{margin-top:var(--s4)}
form.gone{display:none}

/* ================================ FOOTER ================================ */
.sitefoot{background:var(--midnight);color:rgba(255,255,255,.82);padding:var(--s16) 0 var(--s8)}
.foot-top{display:grid;grid-template-columns:1.5fr 1fr 1fr 1fr;gap:var(--s8)}
.foot-top .foot-mark{height:58px;width:auto}
.foot-top p{margin-top:var(--s4);font-size:15px;line-height:1.6;max-width:34ch}
.foot-top .lettering{color:rgba(255,255,255,.74)}
.foot-top ul{margin-top:var(--s4);display:grid;gap:var(--s1)}
/* SC 2.5.8 Target Size (Minimum). These links were 15px of text with no box
   around them - 16px tall measured - and one of them is "Start a conversation",
   an alternative path to the only conversion on the site. The standard's own
   spacing exception was in fact met (35px between adjacent centres against 24
   required), so this is not the AA failure the audit called it; the rule it does
   fail is the project's own, IA N-5, which allows no exception. The box goes on
   the row rather than on the anchor so that an unbuilt row is not shorter than a
   linked one, and the anchor fills it: 15px at 1.65 is 24.75px, plus 4px above
   and below the row. */
.foot-top li{font-size:15px;padding-block:var(--s1)}
.foot-top a{display:block;color:rgba(255,255,255,.9);text-decoration:none}
.foot-top a:hover{color:#fff;text-decoration:underline}
.foot-top .unbuilt{color:rgba(255,255,255,.58)}
.foot-top .unbuilt::after{
  content:" — not yet built";font-family:var(--cond);font-stretch:var(--cw);
  text-transform:uppercase;letter-spacing:.08em;font-size:10px;
}
.foot-base{
  margin-top:var(--s16);padding-top:var(--s4);border-top:1px solid rgba(255,255,255,.24);
  display:flex;flex-wrap:wrap;justify-content:space-between;gap:var(--s4);
}
.foot-base .dim-text{color:rgba(255,255,255,.70)}
.foot-base a{color:rgba(255,255,255,.9)}
.foot-base .unbuilt{color:rgba(255,255,255,.52)}

/* Links on a Mist ground. --blue (#007bc1) is 4.56:1 on white - it passes AA by
   0.06 - and 4.19:1 on Mist, which fails. The failure is a property of the pair,
   not of any one instance, so it is fixed once here rather than per link.
   #05628f on #eef7fc is 5.9:1. */
/* Links on a light ground. --blue (#007bc1) is 4.56:1 on white - it passes AA by
   0.06 - and 4.19:1 on Mist, which fails. The failure is a property of the pair,
   not of any one instance, so it is fixed once here, by ground, rather than per
   link. #05628f on #eef7fc is 5.9:1. Lavender behaves like Mist and is in the
   list for the same reason; `.stack-note` was added with the homepage panel.

   `a:not(.btn)`, and not by accident. A button is not a link on a ground - it
   brings its own - and the first version of this rule repainted the invitation
   band's button text to the Mist link colour on top of Midnight: 2.05:1, caught
   by the gate. The exclusion closes the same latent hole on every ground here. */
.section--mist a:not(.btn):not(.tertiary):not(.pv-pill),
.pagehead a:not(.btn):not(.tertiary):not(.pv-pill),
.panel a:not(.btn):not(.tertiary):not(.pv-pill),
.compare-col:first-child a:not(.btn):not(.tertiary):not(.pv-pill),
.section--lavender a:not(.btn):not(.tertiary):not(.pv-pill),
.stack-note a:not(.btn):not(.tertiary):not(.pv-pill),
.tbd a:not(.btn){color:var(--link-on-mist)}
.section--mist a:not(.btn):not(.tertiary):not(.pv-pill):hover,
.pagehead a:not(.btn):not(.tertiary):not(.pv-pill):hover,
.panel a:not(.btn):not(.tertiary):not(.pv-pill):hover,
.section--lavender a:not(.btn):not(.tertiary):not(.pv-pill):hover{color:var(--midnight)}

/* ============================ TBD PLACEHOLDER ============================
   For a fact we do not have, on a page that cannot ship without it. Loud on
   purpose: E10's recorded position is that the placeholder must be visible in
   staging and never publishable, so this must not be mistakable for copy. */
.tbd{
  display:block;padding:var(--s4) var(--s6);background:var(--warning-tint);
  border:1px dashed var(--warning-ink);border-radius:var(--r-md);
  color:var(--warning-ink);font-size:16px;line-height:1.6;
}
.tbd .flag{margin-left:0;margin-top:var(--s3);display:inline-block}

/* ============================== RESPONSIVE ============================== */
@media (max-width:1080px){
  .two-col,.cta-grid,.folio-item{grid-template-columns:minmax(0,1fr);gap:var(--s12)}
  /* Stacked, the form comes first. On the one page whose job is conversion it
     was otherwise the fourth thing on the page, about 1,000px down. This is a
     visual reorder only: the DOM keeps the biography first, so a keyboard or
     screen-reader user still meets the two people before the form. Both
     sequences read correctly, which is what 1.3.2 and 2.4.3 ask for. */
  .cta-grid > .panel{order:-1}
  .pillars{grid-template-columns:1fr 1fr;gap:var(--s12) var(--s8)}
  .cards{grid-template-columns:1fr;gap:var(--s4)}
  .foot-top{grid-template-columns:1fr 1fr}
  .masthead-in{flex-wrap:wrap}
  .mainnav{order:3;width:100%;margin-left:0}
  .btn--sm{margin-left:auto}
}
@media (max-width:760px){
  body{font-size:16px}
  .pagehead{padding:var(--s12) 0 var(--s8)}
  .section{padding:var(--s16) 0}
  .hero-in{min-height:0;padding-top:var(--s16);padding-bottom:var(--s16)}
  .stats{grid-template-columns:1fr 1fr;gap:var(--s8) var(--s6)}
  .pillars{grid-template-columns:1fr}
  .compare{grid-template-columns:1fr}
  .compare-col + .compare-col{border-left:0;border-top:1px solid var(--line)}
  .compare-foot{padding:var(--s6) var(--s6)}
  .compare-col{padding:var(--s6)}
  .stage{grid-template-columns:48px minmax(0,1fr);gap:var(--s4)}
  .stage .dur{grid-column:2;justify-self:start;text-align:left;margin-top:var(--s3)}
  .people{grid-template-columns:1fr}
  .foot-top{grid-template-columns:1fr;gap:var(--s6)}
  .panel{padding:var(--s6)}
  .mainnav a{padding:10px 12px;font-size:14.5px}
  .table thead{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%)}
  .table tbody th{display:block;width:auto;padding:var(--s4) 0 2px}
  .table tbody td{display:block;padding:0 0 var(--s4)}
  .table tbody tr{display:block;padding:var(--s2) 0}
  .table tbody tr:nth-child(odd){background:transparent}
  .chain-step{grid-template-columns:minmax(0,1fr);row-gap:var(--s3);padding:var(--s6) 0 var(--s6) 26px}
  /* one column now, so every child has to be told to leave column 2 —
     otherwise the role label lands beside the logo and overlaps it */
  .chain-step > img{grid-column:1;grid-row:auto;max-height:38px;margin-bottom:var(--s1)}
  .chain-step--self > img{max-height:46px}
  .chain-role,.chain-note,.chain-step > a,.chain-step > .dim-text{grid-column:1}
  .chain-step:last-child::before{height:calc(var(--s6) + 19px)}
  .chain-step--held{padding-left:52px}
  .chain-step--held::after{top:calc(var(--s6) + 18px);width:35px}
}

/* ===================== UNWRAPPING GENERATEPRESS =====================
   The design is built on full-bleed bands: a Midnight section has to reach
   both edges of the viewport, and the inner `.wrap` supplies the measure. The
   parent theme puts a max-width container around post content, which would
   inset every band and leave white gutters beside them.

   Scoped to `.bsn-design` so any page not yet ported keeps rendering exactly
   as it did. Structural only — no colour, type or spacing is set here. */
.bsn-design .site,
.bsn-design .site-content,
.bsn-design .content-area,
.bsn-design .site-main,
.bsn-design .entry-content,
.bsn-design .inside-article{
  max-width:none;width:auto;margin:0;padding:0;border:0;background:none;box-shadow:none;
}
.bsn-design .site-content{display:block}
.bsn-design .entry-content > :first-child{margin-top:0}

/* GeneratePress applies a top margin to the content wrapper on some layouts,
   which would push the hero away from the masthead. */
.bsn-design .separate-containers .site-main{margin:0}
.bsn-design .one-container .site-content{padding:0}

/* The parent theme's `.button` and heading resets are scoped tightly enough not
   to reach the design's own classes, with one exception: it sets a bottom
   margin on every heading, and the design controls that per component. */
.bsn-design h1,.bsn-design h2,.bsn-design h3,.bsn-design h4{margin-bottom:0}

/* The real enquiry form is a `.bsn-*` component typeset in Roboto and styled by
   the older stylesheets. Inside a design page it sits in the dark CTA panel, so
   it inherits the panel's inversion unless told otherwise. */
/* The real enquiry form inside a design panel. The panel already supplies the
   card — border, radius, padding and the white ground that makes the midnight
   submit button legible on the dark CTA band — so the form drops its own. */
.bsn-design .panel .bsn-enquiry{
  font-family:var(--sans);border:0;border-radius:0;padding:0;margin-block:0;max-width:none;
  gap:var(--s4);
}
.bsn-design .panel .bsn-enquiry__field label{
  font-family:var(--cond);font-stretch:var(--cw);font-weight:700;text-transform:uppercase;
  letter-spacing:.09em;font-size:11.5px;color:var(--ink-2);
}
.bsn-design .panel .bsn-enquiry input,
.bsn-design .panel .bsn-enquiry textarea{
  border:1px solid var(--line-strong);border-radius:var(--r-sm);background:#fff;color:var(--ink);
  font-family:var(--sans);font-size:16px;
}
.bsn-design .panel .bsn-enquiry input:focus-visible,
.bsn-design .panel .bsn-enquiry textarea:focus-visible{border-color:var(--accent)}
.bsn-design .bsn-enquiry__optional{
  font-family:var(--cond);font-stretch:var(--cw);font-weight:400;text-transform:uppercase;
  letter-spacing:.08em;font-size:10.5px;color:var(--ink-3);margin-left:6px;
}

/* The error summary receives focus after a failed submit. Without a scroll
   margin the browser lands it flush under the masthead. */
.bsn-design .bsn-enquiry__errors{scroll-margin-top:calc(var(--s16) + 24px)}

/* On staging the hero still renders its photography placeholder, whose caption is
   bottom-anchored inside an absolutely positioned figure. Below 760px the hero
   collapses to the height of its copy, so the caption printed straight over the
   hero note - 350x15px of overlap on the first thing the primary reader sees.
   Reserve the caption's height rather than letting them share it. */
@media (max-width:760px){
  .bsn-design .hero-in{padding-bottom:calc(var(--s16) + var(--s8))}
}

/* ========================= THE FIELD RISES =========================
   The pre-state used to live in a `no-preference` media query, and that was the
   bug behind "it just flickers and goes away". Two ways it failed. GSAP reads an
   element's current value as the end value of a `from()` tween, so with the
   stylesheet holding the bars at scaleY(0) the tween ran from 0 to 0 and the
   signature graphic never appeared at all. And `gsap.matchMedia()` reverts its
   context on resize, which handed the bars back to a stylesheet that hides them
   - permanently.

   So the pre-state now lives behind `html.bsn-anim`, the class the head gate
   adds and removes on a 2.5s timer, and design.js drives the bars with
   `fromTo()` so the end value is stated rather than inferred. No JavaScript, no
   hidden bars; failed JavaScript, no hidden bars; resize, no hidden bars. */
.hero-field .bf path{
  transform-box:fill-box;transform-origin:50% 100%;
  /* The scale is driven through a custom property rather than by GSAP writing a
     matrix. GSAP manages SVG transforms itself: it overwrites `transform-origin`
     with `0px 0px` and compensates in view-box space, which - stacked on top of
     `transform-box:fill-box` - shifted the bars 250px above their own SVG and
     clipped them out of it. A third way to lose the signature graphic. This way
     CSS owns the origin, GSAP owns one number, and neither can surprise the
     other. */
  transform:scaleY(var(--rise,1));
}
html.bsn-anim .hero-field .bf path{--rise:0}




/* ========================= THE FILM HERO =========================
   Arslan, 20 August 2026: "the home banner should be full screen with video
   playing underneath so you need to adjust your canvas to make it look less
   like a document and more like a webpage".

   Three things make the difference, and none of them touch the palette or the
   type scale: the first viewport is the full height of the screen, the masthead
   floats over it instead of sitting above it in its own white band, and the
   film runs behind a Midnight scrim heavy enough to keep white type at 12:1.
   ============================================================== */

/* ===================== THE TWO HEADER STATES =====================
   Option 1 over the hero, Option 2 on scroll, per the visualisation in
   05_Assets/Designer (1).png.

   Fixed on every page, not just the homepage. Interior pages open on the
   Midnight page-head banner, so a transparent header reads correctly there too,
   and the banner's 112px of top padding already clears it. That also collapses
   the six `.home .masthead:not(...)` special cases this file used to carry.

   `scroll-padding-top` keeps an anchor target and a focused element clear of the
   floating bar - WCAG 2.2 SC 2.4.11, and the reason it is 104px rather than the
   bar's height is the 24px the bar floats by. */
html{scroll-padding-top:104px}

.masthead{
  position:fixed;top:0;left:0;right:0;
  transition:top var(--bsn-motion-reveal,220ms) var(--bsn-easing-out,ease-out),
             left var(--bsn-motion-reveal,220ms) var(--bsn-easing-out,ease-out),
             right var(--bsn-motion-reveal,220ms) var(--bsn-easing-out,ease-out),
             background-color var(--bsn-motion-reveal,220ms) var(--bsn-easing-out,ease-out),
             border-color var(--bsn-motion-reveal,220ms) var(--bsn-easing-out,ease-out),
             box-shadow var(--bsn-motion-reveal,220ms) var(--bsn-easing-out,ease-out),
             border-radius var(--bsn-motion-reveal,220ms) var(--bsn-easing-out,ease-out);
  border:1px solid transparent;
}
.masthead-in{transition:padding var(--bsn-motion-reveal,220ms) var(--bsn-easing-out,ease-out)}

/* --- Option 1: transparent over the hero ------------------------------- */
/* The reversed wordmark is the default because both states this build uses sit
   on a dark ground. */
.brand-mark{display:none}
.brand-mark--rev{display:block}

/* --- Option 2: translucent glass, floating ----------------------------- */
.masthead--stuck{
  top:clamp(16px,1.6vw,24px);
  left:clamp(16px,1.6vw,24px);
  right:clamp(16px,1.6vw,24px);
  background:rgba(9,47,79,.72);
  -webkit-backdrop-filter:blur(14px) saturate(150%);
  backdrop-filter:blur(14px) saturate(150%);
  border-color:rgba(255,255,255,.14);
  border-radius:var(--r-md);
  box-shadow:0 10px 34px rgba(9,47,79,.22);
}
.masthead--stuck .masthead-in{
  padding-top:var(--s3);padding-bottom:var(--s3);
  padding-inline:clamp(16px,2vw,34px);
}
.masthead--stuck .brand{
  background:var(--white);border-radius:var(--r-sm);
  padding:6px 12px;box-shadow:0 1px 3px rgba(9,47,79,.18);
}
.masthead--stuck .brand img{height:40px}
/* Arslan, 21 August: the glass state takes the colour wordmark, not the reversed
   one. Measured on the rendered panel, the colour mark alone did not survive it:
   the purple crown came out at 1.04:1 against the glass and the "THE BOSON
   GROUP" descriptor vanished - which is why `boson-logo-reverse.png` was made in
   the first place, and why the visualisation itself used white here.

   So the mark sits on a white plate inside the bar. That keeps the real brand
   colours, which was the instruction, and keeps them legible, which the
   instruction assumed. A logotype carries no contrast requirement under WCAG
   1.4.3, so this was never an accessibility failure - it was the brand
   disappearing, which is worse in its own way. */
.masthead--stuck .brand-mark{display:block}
.masthead--stuck .brand-mark--rev{display:none}
/* Solid white action in the glass state, outlined over the hero. */
.masthead--stuck .btn--sm{background:var(--white);color:var(--midnight);border-color:var(--white)}
.masthead--stuck .btn--sm:hover{background:var(--mist);border-color:var(--mist);color:var(--midnight)}

/* Outlined light action, state 1. */
/* Floating over the film, the button is the dark-ground skin - the same one the
   hero uses two hundred pixels below it. It used to be a transparent outline,
   which was a fifth button treatment on a site that now has one. */
.masthead:not(.masthead--stuck) .btn--sm{
  background:#fff;color:var(--midnight);border-color:#fff;
}
.masthead:not(.masthead--stuck) .btn--sm:hover{background:var(--mist);border-color:var(--mist);color:var(--midnight)}

/* --- Hidden while scrolling down ---------------------------------------
   Translated rather than removed, so it is still in the accessibility tree and
   still focusable; design.js brings it back on focus, because a keyboard reader
   tabbing into a header they cannot see is a 2.4.11 failure. */
.masthead--away{transform:translateY(calc(-100% - clamp(16px,1.6vw,24px) - 12px))}
.masthead{transition-property:top,left,right,background-color,border-color,box-shadow,border-radius,transform}
@media (prefers-reduced-motion:reduce){
  .masthead--away{transform:none}
  .masthead,.masthead-in,.nav-slide{transition:none}
}

.hero--film{min-height:100svh;display:flex;align-items:center;isolation:isolate}
.hero--film .hero-in{min-height:0;padding-top:var(--s24);padding-bottom:var(--s24);width:100%}

/* The film itself. `object-fit:cover` on a 16:9 clip in a portrait viewport
   crops hard, which is why the mobile rule below drops the video and keeps the
   poster: a 390px-wide crop of a 4K wide shot is abstract mush, and it costs
   1 MB of somebody's mobile data to say nothing. */
.hero-video{
  position:absolute;inset:0;width:100%;height:100%;
  object-fit:cover;object-position:center;pointer-events:none;
}

/* Two scrims, doing two jobs. The first holds the copy side dark enough for
   white type. The second darkens the top and bottom edges so the floating
   masthead and the scroll cue have a ground of their own. */
.hero--film .hero-media::after{
  background:
    linear-gradient(180deg,rgba(9,47,79,.72) 0%,rgba(9,47,79,.10) 22%,rgba(9,47,79,.20) 62%,rgba(9,47,79,.80) 100%),
    linear-gradient(100deg,rgba(9,47,79,.93) 0%,rgba(9,47,79,.84) 44%,rgba(9,47,79,.52) 100%);
}

/* The tagline lockup. A supplied raster, so it is placed at its own aspect and
   never recoloured; `Software for Life` is in the alt text because it is the
   sentence, not decoration. */
.hero-tag{display:flex;align-items:center;gap:var(--s3);flex-wrap:wrap;margin-bottom:var(--s6)}
.hero-eyebrow{margin-bottom:var(--s3);color:rgba(255,255,255,.78);letter-spacing:.13em}
.hero-tag img{height:30px;width:auto}

.hero--film h1{max-width:24ch}
.hero--film .hero-sub{max-width:46ch}

/* The scroll cue. The one piece of looping motion on the site, and it is the
   one place a founder needs to be told the page continues: a full-bleed film
   with no visible edge reads as the whole page until something says otherwise.
   Animated in design.js under `no-preference` only; static here. */
.hero-cue{
  position:absolute;left:50%;bottom:var(--s8);transform:translateX(-50%);
  display:inline-flex;flex-direction:column;align-items:center;gap:10px;
  color:rgba(255,255,255,.82);text-decoration:none;z-index:3;padding:10px 14px;
  border-radius:var(--r-sm);
}
.hero-cue:hover{color:#fff}
.hero-cue::after{
  content:"";width:1px;height:38px;background:linear-gradient(180deg,rgba(255,255,255,.9),rgba(255,255,255,0));
}
@media (max-width:760px){
  .hero-cue{display:none}
  .hero--film{min-height:92svh}
}

/* ===================== THE DISCLOSURE NAVIGATION =====================
   Eight destinations do not fit one row under 1081px. They wrapped instead,
   which grew the masthead to 271px on a 390px screen - and once the bar floated
   over the film, the wrapped rows printed over the proposition itself. */
.navtoggle{display:none}
/* Was 1080. The masthead button now carries the site's one label in full, and
   at 1081px that pushed the eight destinations onto a second row - the same
   failure the old 26-character cap existed to avoid, arriving from the other
   direction. The disclosure button takes over 120px earlier instead, so the
   label stays whole and the bar stays one row tall at every width. */
@media (max-width:1200px){
  .masthead-in{flex-wrap:nowrap;gap:var(--s4)}
  .navtoggle{
    display:inline-flex;align-items:center;gap:10px;order:3;
    min-height:44px;padding:10px 14px;margin-left:auto;
    background:transparent;border:1px solid var(--line-strong);border-radius:var(--r-sm);
    color:var(--ink);cursor:pointer;
    font-family:var(--cond);font-stretch:var(--cw);font-weight:700;
    text-transform:uppercase;letter-spacing:.09em;font-size:11.5px;
  }
  .navtoggle-bars{position:relative;width:16px;height:2px;background:currentColor;flex:none}
  .navtoggle-bars::before,
  .navtoggle-bars::after{
    content:"";position:absolute;left:0;width:16px;height:2px;background:currentColor;
  }
  .navtoggle-bars::before{top:-5px}
  .navtoggle-bars::after{top:5px}
  /* Open: the three rules become a cross. The control shows its own state, and
     `aria-expanded` says it out loud. */
  .navtoggle[aria-expanded="true"] .navtoggle-bars{background:transparent}
  .navtoggle[aria-expanded="true"] .navtoggle-bars::before{top:0;transform:rotate(45deg)}
  .navtoggle[aria-expanded="true"] .navtoggle-bars::after{top:0;transform:rotate(-45deg)}
  .btn--sm{order:2;margin-left:auto}
  .navtoggle + .mainnav{order:4}
  .mainnav{
    position:absolute;left:0;right:0;top:100%;display:none;
    flex-direction:column;gap:2px;margin:0;
    padding:var(--s4) var(--pad) var(--s6);
    background:#fff;border-bottom:1px solid var(--line);box-shadow:var(--sh-2);
  }
  .masthead[data-nav="open"] .mainnav{display:flex}
  .mainnav a{min-height:48px;font-size:16.5px;padding:12px 14px;color:var(--ink)}
  .mainnav a[aria-current=page]{background:var(--mist);box-shadow:inset 0 -2px 0 var(--accent)}
  /* An open panel needs a ground of its own even over the film. */
  .masthead[data-nav="open"]{background:rgba(255,255,255,.97);backdrop-filter:blur(12px)}
  .masthead[data-nav="open"] .brand-mark{display:block}
  .masthead[data-nav="open"] .brand-mark--rev{display:none}
  .masthead[data-nav="open"] .navtoggle,
  .masthead[data-nav="open"] .mainnav a{color:var(--ink)}
  .masthead[data-nav="open"] .btn--sm{
    background:var(--midnight);color:#fff;border-color:var(--midnight);
  }
  /* Floating, panel closed: the control has to be legible on the film. */
  .masthead:not(.masthead--stuck):not([data-nav]) .navtoggle{
    color:#fff;border-color:rgba(255,255,255,.6);
  }
  html{scroll-padding-top:80px}
}
@media (max-width:760px){
  /* The masthead drops its button at phone width. "Start a confidential
     conversation" is 33 characters and there are about 150px between the
     wordmark and the disclosure button at 390px - the old shortened label
     fitted, one label for the whole site does not. It is hidden rather than
     shortened because a second wording was the thing being removed. The
     action is not lost: every page carries the invite band with the same
     button, and on /contact/ the form is now the first thing on the page. */
  .masthead .btn--sm{display:none}
  .navtoggle-label{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%)}
  .navtoggle{padding:10px 13px}
}

/* ===================== INTERIOR PAGE BANNERS =====================
   Every interior page opened on the same Mist slab, which is what made the set
   read as a document rather than a site. One client asset - a 6000x3375
   abstract - under a Midnight scrim turns all nine into banners. Delivered as
   WebP at three widths: 40.4 KB at 1920, 11.7 KB at 760. */
/* The three banner images are variables with the built-in files as fallbacks, so
   the photograph is replaceable from Settings > Boson Group without giving up the
   breakpoints - a phone has no business downloading the 1920px file. With no
   option set these resolve to exactly the URLs that were hard-coded here before. */
.pagehead--film{
  position:relative;isolation:isolate;border-bottom:0;color:#fff;
  background:var(--midnight) var(--bsn-pagehead-md,url(../img/pagehead-1280.webp)) center/cover no-repeat;
  /* Option D, chosen 25 August 2026. Taller than the band it replaces, and
     fluid rather than fixed so a phone does not get 150px of empty scrim. */
  padding:clamp(96px,9vw,150px) 0 clamp(64px,6.4vw,104px);
}
@media (min-width:1281px){.pagehead--film{background-image:var(--bsn-pagehead-lg,url(../img/pagehead-1920.webp))}}
@media (max-width:760px){.pagehead--film{background-image:var(--bsn-pagehead-sm,url(../img/pagehead-760.webp))}}
/* The scrim, lifted. At .93 the photograph was a texture nobody could see; this
   is the point of option D. Measured against the asset rather than eyeballed:
   the worst composited pixel under the subtitle is 8.22:1 at 1920, 6.38:1 at
   1280 and 5.34:1 on a 390px phone, against a 4.5:1 requirement. Re-measure if
   the banner asset is ever replaced - a lighter photograph would eat that
   margin, and this gradient is the only thing protecting the text. */
.pagehead--film::after{
  content:"";position:absolute;inset:0;z-index:-1;
  background:linear-gradient(104deg,rgba(9,47,79,.86) 0%,rgba(9,47,79,.70) 44%,rgba(9,47,79,.34) 100%);
}
.pagehead--film h1{color:#fff;max-width:26ch}
.pagehead--film .pagehead-sub{color:rgba(255,255,255,.88);max-width:58ch}
.pagehead--film .lettering{color:rgba(255,255,255,.76)}
.pagehead--film .dim-text{color:rgba(255,255,255,.72)}
/* The Mist link colour is for a Mist ground. This one is Midnight. */
.pagehead--film a{color:#fff;text-decoration-color:rgba(255,255,255,.6)}
.pagehead--film a:hover{color:var(--accent-rev)}
/* One Spectrum beam per composition, on its bottom edge. */
.pagehead--film::before{
  content:"";position:absolute;left:0;right:0;bottom:0;height:3px;
  background:linear-gradient(90deg,#662d91,#5051a3 34%,#007bc1 68%,#009adb);
}

/* ===================== THE FOUR ROUTES =====================
   These replace two sections: the four pillar paragraphs, which are on
   /why-boson/ in full, and a five-card "Where to read more" grid that was a
   second copy of the navigation. A commitment that links to its own evidence is
   worth more than a paragraph asserting it. */
.routes--four{grid-template-columns:repeat(4,minmax(0,1fr));gap:var(--s4)}
.routes--four .route{
  position:relative;gap:8px;padding:var(--s6) var(--s6) var(--s8);overflow:hidden;
  transition:box-shadow .2s ease-out,border-color .2s ease-out,transform .2s ease-out;
}
.routes--four .route::before{
  content:"";position:absolute;left:0;right:0;top:0;height:3px;
  background:linear-gradient(90deg,#662d91,#5051a3 34%,#007bc1 68%,#009adb);
  transform:scaleX(0);transform-origin:0 50%;transition:transform .28s var(--ease,ease-out);
}
.routes--four .route:hover{transform:translateY(-3px);box-shadow:var(--sh-2);border-color:var(--line-strong)}
.routes--four .route:hover::before,
.routes--four .route:focus-visible::before{transform:scaleX(1)}
.route-n{
  font-family:var(--cond);font-stretch:var(--cw);font-weight:700;font-size:13px;
  letter-spacing:.14em;color:var(--ink-3);
}
.routes--four .route-t{font-size:20px;line-height:1.2}
@media (prefers-reduced-motion:reduce){
  .routes--four .route:hover{transform:none}
  .routes--four .route::before{transition:none}
}
@media (max-width:1080px){.routes--four{grid-template-columns:1fr 1fr}}
@media (max-width:760px){
  .routes--four{grid-template-columns:1fr}
  .sit{grid-template-columns:1fr;gap:var(--s12)}
  .hero-tag img{height:24px}
}

/* ===================== SCROLL REVEALS =====================
   No opacity. This is the important line in the file.

   A reveal that fades text in has to un-hide it again, and everything that has
   to happen for that is a thing that can fail: the trigger has to fire, which
   means something has to scroll, which a crawler does not do. Measured on this
   build: with the hero at 100svh, a Googlebot-shaped render viewport makes the
   document grow with the viewport, so `top 88%` sits below every section under
   the hero and all of them stay at opacity 0 in the rendered snapshot. That is
   the whole page's text, invisible to the renderer, on a site whose entire job
   is to be read and believed.

   So the reveal is a transform and nothing else. The text is painted at all
   times, to every reader and every agent, and the worst failure available is a
   paragraph sitting 18px below where it belongs. The pre-state is still added
   by script and still removed on a 2.5s timer, but now it is a nicety rather
   than a risk. */
html.bsn-anim .reveal > .wrap > *{transform:translateY(18px)}

/* ================= COMPOSITION AT WIDTH =================
   Every rule in this block is gated at 1200px and above, and every one of them
   exists for the same reason: the container was widened to the viewport, so the
   columns grew, but the content inside them is capped in `ch` and did not - which
   left measured voids of 220 to 1100px inside sections that looked balanced at
   1200px. The answer is not wider text. It is more than one thing across the
   width, on a shared baseline. Measurements are in
   docs/reviews/20-homepage-design-critique.md.
   ======================================================== */

/* --- The recognition section: heading | body | aside ------------------- */
.lede{display:grid;gap:clamp(28px,3vw,72px);margin-top:0}
.lede-body .prose + .prose{margin-top:var(--s4)}

/* --- A heading row that uses both halves ------------------------------- */
.split-head .wrap > .sec-note{margin-top:var(--s4)}

/* --- Route cards state that they are links ----------------------------- */
.route-go{
  margin-top:auto;padding-top:var(--s4);
  font-family:var(--cond);font-stretch:var(--cw);font-weight:700;
  text-transform:uppercase;letter-spacing:.09em;font-size:11.5px;color:var(--accent);
  display:inline-flex;align-items:center;gap:7px;
}
.route-go::after{content:"\2192";font-size:14px;transition:transform .2s ease-out}
.route:hover .route-go::after,
.route:focus-visible .route-go::after{transform:translateX(3px)}
@media (prefers-reduced-motion:reduce){.route-go::after{transition:none}}

/* --- The footer's first column, filled by the thing that belongs there -- */
.foot-tag{height:clamp(20px,1.5vw,30px);width:auto;margin-top:var(--s4);opacity:.9}
.foot-top p{max-width:40ch}

/* --- The scroll cue joins the grid ------------------------------------- */
.hero-cue{
  left:auto;right:auto;transform:none;align-items:flex-start;
  margin-left:var(--pad);padding-left:0;
}
.hero-cue::after{margin-left:2px}

/* --- The tagline lockup scales with the headline ----------------------- */
.hero-tag img{height:clamp(24px,1.9vw,46px)}

/* --- One rule width in the CTA -----------------------------------------
   The rule should span its column; the sentence under it should not. Padding
   does both: the border-top runs the full width of the box, the text wraps at
   the padding edge. Capping max-width instead would have shortened the rule
   too, which is what made the three rules in this band different lengths. */
.cta-grid .alt{max-width:none;padding-right:max(0px,calc(100% - 68ch))}

@media (min-width:1200px){
  .lede{
    grid-template-columns:minmax(0,1.1fr) minmax(0,1.05fr) minmax(0,.85fr);
    align-items:start;
  }
  .lede-head .sec-h{max-width:22ch}

  /* The heading row: title left, note right, sharing a baseline. */
  .split-head .wrap{
    display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);
    gap:0 clamp(32px,4vw,96px);align-items:end;
  }
  .split-head .wrap > .sec-h{grid-column:1}
  .split-head .wrap > .sec-note{
    grid-column:2;margin-top:0;max-width:42ch;justify-self:end;
  }
  .split-head--top .wrap{align-items:start}
  .split-head .wrap > *:not(.sec-h):not(.sec-note){
    grid-column:1 / -1;margin-top:clamp(32px,2.6vw,60px);
  }

  /* The founder quote: the centrepiece left, its provenance right, on the
     quote's own baseline. Before this the band was 763px tall with its right
     two thirds empty. */
  .quote--split .wrap{
    display:grid;grid-template-columns:minmax(0,1.5fr) minmax(0,1fr);
    grid-template-rows:auto min-content min-content 1fr;
    gap:0 clamp(48px,5vw,120px);
  }
  .quote--split .wrap > .lettering{grid-column:1 / -1}
  .quote--split .wrap > blockquote{grid-column:1;grid-row:2 / 5;max-width:34ch}
  .quote--split .wrap > .attrib{grid-column:2;grid-row:2;margin-top:var(--s12);border-top:0;padding-top:0}
  .quote--split .wrap > .src{grid-column:2;grid-row:3;align-self:start;margin-top:var(--s6)}
  .quote--split .wrap > p:last-child{grid-column:2;grid-row:4;align-self:start;margin-top:var(--s6)}
  .quote blockquote{font-size:clamp(1.6rem,2.6vw,3.1rem)}

  .foot-top{grid-template-columns:1.25fr .95fr .95fr 1.05fr}

  /* The enquiry panel is ~590px tall and the copy beside it about 240px, which
     left a 350px void under the copy. Anchoring the alternative-contact line to
     the bottom of its column gives the band a second alignment - the line and
     the submit button share a baseline - and the void becomes the gap between
     two anchored groups rather than slack at the end of one. */
  .cta-grid > div:first-child{display:flex;flex-direction:column}
  .cta-grid > div:first-child > .alt{margin-top:auto;padding-top:var(--s8)}
}

/* ===================== THE CLOSING INVITATION =====================
   Light on purpose. This band used to be Midnight, sitting between the Midnight
   founder-quote band and the Midnight footer, which ended every page in one
   blue mass with no edge between the last section and the footer. Lavender is
   the design system's sparing accent ground and it appears exactly once per
   page, which is what makes it read as an ending rather than as another band. */
/* A closing gesture, not another full section: the shared section rhythm
   (up to 176px top and bottom) left 150px of empty Lavender under two lines of
   type and a button. */
.invite .invite-in{display:grid;gap:clamp(28px,3vw,64px);align-items:center}
.invite .sec-note{margin-top:var(--s4)}
.invite .lettering{color:var(--ink-2)}
.invite .sec-h{margin-top:var(--s3)}
/* Three reassurances, not three ticks: a checkmark glyph in content is an icon by
   the back door, and it would be read out by a screen reader as "check mark".
   The rule and the accent do the same job silently. */
.invite-assure{
  display:flex;flex-wrap:wrap;gap:var(--s3) clamp(20px,2.2vw,36px);margin-top:var(--s6);
}
.invite-assure li{
  position:relative;padding-left:16px;font-size:15.5px;line-height:1.5;color:var(--ink);
}
.invite-assure li::before{
  content:"";position:absolute;left:0;top:.62em;width:7px;height:7px;border-radius:50%;
  background:var(--accent-hi);
}
.invite-act{display:flex;flex-direction:column;align-items:flex-start;gap:var(--s4)}
.invite-act .small{color:var(--ink-2);max-width:38ch}

@media (min-width:1200px){
  /* Statement and action on one line, the action anchored to the right edge so
     the band has two ends rather than a left edge and a long tail. */
  .invite .invite-in{grid-template-columns:minmax(0,1.35fr) minmax(0,1fr)}
  .invite-act{justify-self:end;align-items:flex-start}
  .invite .sec-h{max-width:22ch}
}

/* ===================== THE ASK, BESIDE THE PEOPLE =====================
   Arslan: "this start a confidential conversation can go to the section that has
   bilal and hira in it add it there". It belongs there: the sentence is about
   those two people, their faces are directly above it, and an ask attached to
   the human proof is worth more than an ask in a band of its own three sections
   later. The homepage's closing band went with it, and the founder quote moved
   up to sit under this - so the page still ends on a light band above the
   footer, and the strongest piece of evidence on the site now lands immediately
   after the ask rather than before it. */
.sit .person{border-top:0;padding-top:0}

/* The ask is a member of this section's grid, not a row underneath it. Stacked,
   it reads last - chain, people, ask - which is the right order on a phone. */
.sit-ask{
  display:flex;flex-direction:column;align-items:flex-start;gap:var(--s4);
  margin-top:clamp(32px,2.6vw,48px);
}
.sit-ask .small{max-width:40ch;margin:0}

/* The two portraits, wherever they appear. They are only on /contact/ now - the
   homepage shows the ownership lineage instead - but this is the component's
   rule, not that page's, and it was lost with the homepage block that used to
   scope it. */
/* TWO SELECTORS FOR ONE TREATMENT, and the reason is core's markup. A content
   image is a core/image block, which puts the className on the <figure> and
   `wp-image-<id>` on the <img> inside it - there is no way to put `.face` on the
   img from a block attribute. So `.face` is styled both as an element and as a
   figure wrapping one. Added with QA-02, when these portraits stopped being
   deleted by the converter and started being real blocks. */
figure.face{margin:0;padding:0}
figure.face img,
.person .face,
.jrn-card .face{
  width:clamp(120px,10vw,168px);aspect-ratio:1;height:auto;
  border-radius:50%;object-fit:cover;margin-bottom:var(--s4);
  border:3px solid #fff;box-shadow:var(--sh-1);
}

/* ===================== THE OWNERSHIP LINEAGE =====================
   Removed, 24 August 2026. `.lineage` was the homepage's own drawing of the
   ownership chain: a full-width list of rows on a Spectrum rail, hand-written
   alongside the portfolio page's `.tree` panel and carrying different words for
   the same three companies. They had already drifted - the homepage still quoted
   Andromeda's "keep them forever" line, which their site no longer carries and
   which the portfolio page had already stopped quoting.

   Both pages now render `.tree-box` from the same two post types, so there is
   one component and one set of records. If a second, more compact treatment is
   ever wanted for a narrow slot, build it as a modifier on `.tree` rather than
   as a second component - that is the mistake this paragraph is here to stop
   someone repeating. The old rules are in git at 869290d.

   `.chain` further up this file is also unreferenced and was already dead before
   this change; it is left alone rather than swept up in someone else's commit. */

/* ===================== THE REACH DIAGRAM =====================
   Points of light at their real latitudes, joined to the home market. No
   coastline and no boundary of any kind: the diagram says where this group works
   without asserting whose any territory is, which is the only version of a map
   that is safe to serve from one domain to every country at once.

   Decorative by construction - every place plotted is also in the list beneath
   it, so nothing is available only to someone who can see. */
.geo{margin:0 0 var(--s6);position:relative}
.geo-svg{width:100%;height:auto;display:block;overflow:visible}
.geo-grid circle{fill:var(--midnight);opacity:.16}
.geo-link{
  stroke:url(#geo-beam);stroke-width:2.5;opacity:.55;
  stroke-dasharray:var(--geo-len,1) var(--geo-len,1);
  stroke-dashoffset:calc(var(--geo-len,1) * (1 - var(--geo-draw,1)));
}
.geo-market{fill:var(--white);stroke:var(--accent-hi);stroke-width:3}
.geo-home{fill:url(#geo-beam);stroke:var(--white);stroke-width:3}
.geo-label{
  font-family:var(--cond);font-stretch:var(--cw);font-weight:700;
  font-size:24px;letter-spacing:.04em;fill:var(--ink);
}
.geo figcaption{display:flex;flex-wrap:wrap;gap:var(--s4) var(--s6);margin-top:var(--s4)}
.geo-key{
  display:inline-flex;align-items:center;gap:8px;font-size:14px;color:var(--ink-2);
}
.geo-key-home,.geo-key-market{width:12px;height:12px;border-radius:50%;flex:none}
.geo-key-home{background:var(--indigo)}
.geo-key-market{background:var(--white);box-shadow:inset 0 0 0 3px var(--accent-hi)}
@media (max-width:600px){.geo-label{font-size:34px}}

@media (max-width:1080px){
  .nav-slide{display:none}
  .mainnav--slide a[aria-current=page]{box-shadow:inset 0 -2px 0 var(--accent)}
  .masthead--stuck .brand img{height:52px}
}

/* ===================== THE OWNERSHIP PANEL =====================
   Locked, 21 August 2026. The chain renders from OWNERSHIP in design/build.py; the
   row below it is the Portfolio post type, rendered by inc/portfolio-companies.php.

   THE PATTERN, and it is the page's pattern rather than this section's: argument on
   the left, evidence in a panel on the right. That is exactly the section above -
   "Test the structure, not the pitch" beside "Where we invest today" - and this
   section now uses the same `.two-col`, the same panel treatment (white, 1px rule,
   22px radius, 32px padding) and the same internal rhythm of hairline-separated
   rows. Two sections built the same way read as a system; two built differently
   read as two attempts.

   THE CHAIN, revised 24 August 2026 on Arslan's instruction: the three entities are
   cards on one drawn axis, not hairline-separated rows. Each is a tile on the same
   plate, radius and hover as a held company below it, and a 2px stem stands in the
   gap between one and the next - so ownership is drawn rather than implied by
   proximity, and the line runs unbroken from Constellation through Andromeda and
   Boson into the fan-out. The Boson card is filled rather than bled: a card that
   ran to the panel's edges would stop being one of three.

   The earlier note here argued that bordered cards inside a bordered panel were one
   level too many. They are not, because these carry no border until they are
   hovered - the plate does the separating, which is exactly how the held row below
   already works.

   CONTAINER QUERIES, not viewport ones. The panel is half the page here and could
   be the whole width somewhere else - the homepage will reuse this - so how many
   companies fit across is a question about the panel, not about the screen. Every
   count rule below is `@container`. */
.prose--lead{margin-top:var(--s6)}

.tree-box{
  background:var(--white);border:1px solid var(--line);border-radius:var(--r-lg);
  padding:var(--s8);container-type:inline-size;
  /* The gap every connector in this panel spans - between the three entity
     cards, and again between the last of them and the fan-out below. One
     value, so the axis has one rhythm from Constellation to the last company
     held. `--pad` used to be overridden here for a full-bleed row; the rows are
     cards now and nothing bleeds, so the panel no longer redefines it. */
  --tree-stem:clamp(20px,2vw,30px);
}
.section--mist .tree-box{background:var(--white)}
/* A panel with no chain above the fan-out. The homepage's structure summary
   links to /portfolio/ for the full chain rather than repeating it, so its
   tree-box holds only [bsn_held_companies] - and `.held-lead::before` was still
   reaching `--tree-stem` upward into empty space, leaving a line that pointed at
   nothing. Zero the stem when the box has a single child. */
.tree-box:has(> div:only-child){--tree-stem:0px}
/* With no chain there is nothing for the trunk, the "Held here" label or the
   arms to hang from, and every one of them was drawing a line that ended in
   white space. `--rail` is already the switch the panel uses for a wrapped row,
   so turning it off here reuses a path the component already supports rather
   than adding a second way to be connectorless. */
.tree-box:has(> div:only-child) .held-lead{display:none}
.tree-box:has(> div:only-child) .held{--rail:0;--held-drop:0px;--rail-gap:0px}
/* Screen-reader-only, for the "opens in a new tab" announcements. Clipped rather
   than display:none, which would take the text out of the accessibility tree
   along with everything else. */
.vh{
  position:absolute;width:1px;height:1px;margin:-1px;padding:0;border:0;
  overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;
}

.tree{display:grid;gap:var(--tree-stem);margin:0;padding:0;list-style:none}
.tree-row{position:relative}
/* The link between one entity and the next, in the gap the grid leaves for it.
   Same 2px rule, same colour and the same drawn-from-the-top behaviour as the
   stems under the fan-out below, because it is the same statement: this is held
   by that. It draws with `--tree-draw`, which design.js already staggers down
   the rows, and it defaults to 1 - so no JavaScript means a chain that is drawn
   rather than a chain that is missing. */
.tree-row + .tree-row::before{
  content:"";position:absolute;left:calc(50% - 1px);width:2px;
  top:calc(var(--tree-stem) * -1);height:var(--tree-stem);
  background:var(--line-strong);
  transform-origin:50% 0;transform:scaleY(var(--tree-draw,1));
}
/* A tile, on the same terms as a held company below: mist plate, 12px radius,
   transparent border that the hover colours in. Three cards on one axis read as
   three entities joined by something; three hairline-separated rows read as a
   list, which is what this section was and what Arslan asked to change. */
.tree-card{
  position:relative;display:grid;gap:0 var(--s5,20px);
  background:var(--mist);border:1px solid transparent;
  border-radius:var(--r-md);padding:var(--s5,20px);
  transition:transform 200ms ease-in-out,box-shadow 200ms ease-in-out,
             border-color 200ms ease-in-out,background-color 200ms ease-in-out;
}

/* The reader's own row: filled, and bled to the panel's edges so it reads as a
   band rather than a floating box inside a box. */
.tree-card--self{background:var(--midnight);border-color:var(--midnight);color:#fff}
.tree-card--self .tree-name,.tree-card--self .tree-note{color:#fff}
.tree-card--self .tree-tier{color:rgba(255,255,255,.72)}
.tree-card--self .tree-chip{background:rgba(255,255,255,.16);color:#fff}
/* The Boson mark in its own colours on a white plate. The reversed file exists
   because the colour mark's purple crown is 2.4:1 on Midnight; a logotype carries
   no contrast requirement under WCAG 1.4.3, and the plate means the question does
   not arise either way. */
.tree-card--self .tree-mark{background:var(--white);border-color:var(--white)}

.tree-tier{
  grid-column:1 / -1;
  font-family:var(--cond);font-stretch:var(--cw);font-weight:700;
  text-transform:uppercase;letter-spacing:var(--bsn-tracking-label,.08em);
  font-size:11px;line-height:1.4;color:var(--ink-3);margin-bottom:var(--s4);
}
.tree-mark{
  display:grid;place-items:center;background:var(--white);
  border:1px solid var(--line);border-radius:var(--r-sm);
  padding:8px 12px;align-self:start;min-height:56px;
}
.tree-mark img{max-height:32px;max-width:100%;width:auto}
.tree-name{
  font-size:1.15rem;line-height:1.2;color:var(--ink);
  display:flex;flex-wrap:wrap;align-items:center;gap:var(--s3);
}
.tree-chip{
  font-family:var(--cond);font-stretch:var(--cw);font-weight:700;
  text-transform:uppercase;letter-spacing:.07em;font-size:10.5px;
  background:var(--mist);color:var(--ink-2);border-radius:999px;padding:4px 10px;
}
/* 66ch, the same measure `.prose` holds. The panel is half a page on
   /portfolio/ and the full width of the band on the homepage, so without a cap
   the same card runs to about 130 characters a line in the second place. A
   component that is only readable at one width is not reusable, it just has not
   been reused yet. */
.tree-note{margin-top:var(--s3);font-size:15.5px;line-height:1.6;color:var(--ink-2);max-width:66ch}

/* The outbound link under the panel. Was `.lineage-foot`, which carried a 52px
   indent to clear a rail that no longer exists. */
.tree-foot{margin-top:var(--s6)}

/* --- the whole row is the link ----------------------------------------
   The anchor is on the company name and its overlay covers the row, so a pointer
   can land anywhere while a keyboard reader still gets one tab stop with the
   company name as its accessible name. The focus ring is drawn on that overlay
   rather than through :has(), so its existence does not depend on selector
   support. */
.tree-link,.held-link{color:inherit;text-decoration:none}
.tree-link::after,.held-link::after{content:"";position:absolute;inset:0;border-radius:inherit}
.tree-link:focus-visible,.held-link:focus-visible{outline:none}
.tree-link:focus-visible::after,.held-link:focus-visible::after{
  outline:2px solid var(--focus);outline-offset:3px;border-radius:var(--r-sm);
}
/* A tile lifts. It used to tint instead, because it used to be a row and lifting
   a row inside a panel reads as the panel coming apart. It is a card now, so it
   takes the card's hover - the same one the held companies take, which is the
   point of building both from one vocabulary. */
.tree-card--link:hover{
  transform:translateY(-3px);background:var(--white);
  border-color:var(--accent);box-shadow:var(--sh-2);
}
.tree-card--link:hover .tree-name,.held-item--link:hover .held-name{color:var(--accent)}
.held-item--link{
  transition:transform 200ms ease-in-out,box-shadow 200ms ease-in-out,
             border-color 200ms ease-in-out,background-color 200ms ease-in-out;
}
.held-item--link:hover{
  transform:translateY(-3px);background:var(--white);
  border-color:var(--accent);box-shadow:var(--sh-2);
}
@media (prefers-reduced-motion:reduce){
  .tree-card,.held-item--link{transition:none}
  .tree-card--link:hover,.held-item--link:hover{transform:none}
}

/* --- what it holds ---------------------------------------------------- */
.held{
  position:relative;margin-top:var(--tree-stem,var(--s6));
  --rail-gap:var(--s5,20px);
  --held-gap:var(--s4,12px);
  --held-drop:20px;
  --elbow:12px;

  /* HOW THIS SCALES, and it is arithmetic rather than a rule per count.
     Everything below is derived from two numbers: how many companies are
     published (`--held-n`, written on by the data-count rules further down) and
     how many this panel is wide enough to show across (`--held-cap`, set by the
     container queries). The column count, whether the grid wrapped, whether a
     rail is meaningful and how far the trunk has to reach all fall out of those
     two. Publishing an eighth company changes one number and the diagram
     re-draws itself; there is no count at which the stylesheet quietly stops
     having an opinion.

     99 is the default for `--held-n` on purpose: it means "more than any rule
     below names", so an unlisted count pins the columns to the cap and reads as
     wrapped - the safe answer, rather than whatever the last matching rule
     happened to leave behind. */
  --held-n:99;
  --held-cap:1;
  --held-cols:min(var(--held-n),var(--held-cap));
  /* 0 while every company fits on one row, 1 once the grid wraps. */
  --wrapped:clamp(0,calc(var(--held-n) - var(--held-cap)),1);
  /* 0 when there is one column, 1 when there is something to fan out to. */
  --fanned:clamp(0,calc(var(--held-cols) - 1),1);
  /* The rail is drawn only where it would be true: more than one card across,
     and all of them on the single row it spans. A rail across two rows reaches
     past cards it does not touch and stops short of cards it claims - so it is
     not drawn, and the trunk grows instead to reach the block on its own. */
  --rail:calc(var(--fanned) * (1 - var(--wrapped)));
  --track:calc((100% - (var(--held-cols) - 1) * var(--held-gap)) / var(--held-cols));
}
.held-lead{
  display:flex;flex-wrap:wrap;align-items:center;justify-content:center;
  gap:var(--s3) var(--s4);position:relative;
  padding-top:var(--s5,20px);padding-bottom:var(--s5,20px);
}
.held-lead > *{position:relative;z-index:1;background:var(--white);padding:0 var(--s4)}
/* The trunk: down the axis, past the label, to the rail. */
.held-lead::before{
  content:"";position:absolute;left:calc(50% - 1px);width:2px;z-index:0;
  /* Up into the gap above `.held`, not from the top of the label. The three
     entity cards are joined by a stem of exactly this length, and a trunk that
     started below the gap would leave one visible break in an axis whose whole
     job is to be unbroken. */
  top:calc(var(--tree-stem,0px) * -1);
  /* Down to the rail when there is one, and all the way to the cards themselves
     when there is not - a single company, or a row that wrapped. The line always
     ends on something. */
  bottom:calc((var(--rail-gap) + var(--held-drop) * (1 - var(--rail))) * -1);
  background:var(--line-strong);
  clip-path:inset(0 0 calc((1 - var(--tree-draw,1)) * 100%) 0);
}
.held-grid{
  display:grid;grid-template-columns:repeat(var(--held-cols),minmax(0,1fr));
  gap:var(--held-gap);margin-top:var(--rail-gap);padding-top:var(--held-drop);
  position:relative;list-style:none;
}
/* Two arms, symmetric about the axis the trunk came down on. Each is one bordered
   box carrying one rounded corner - a real radius rather than two straight lines
   meeting at a point. Each reaches from the centre to the centre of the outermost
   card, which is half a track in from the edge - so the geometry follows the
   column count and nothing here has to be told what that count is.

   `--rail` is the switch, and it works through border-width rather than through
   `display` or `opacity`: at 0 the border is simply not there, which costs no
   paint and leaves the draw-on clip-path below still meaning what it says. */
.held-grid::before,
.held-grid::after{
  content:"";position:absolute;top:-2px;height:calc(var(--held-drop) + 2px);
  border-style:solid;border-color:var(--line-strong);
  border-width:calc(2px * var(--rail)) 0 0 0;
  width:calc(50% - var(--track) / 2 + 1px);
}
.held-grid::before{
  left:calc(var(--track) / 2);
  border-left-width:calc(2px * var(--rail));
  border-top-left-radius:var(--elbow);
  clip-path:inset(0 0 0 calc((1 - var(--held-draw,1)) * 100%));
}
.held-grid::after{
  left:calc(50% - 1px);
  border-right-width:calc(2px * var(--rail));
  border-top-right-radius:var(--elbow);
  clip-path:inset(0 calc((1 - var(--held-draw,1)) * 100%) 0 0);
}
/* The cards in between hang off the rail on straight stems. The first and last are
   the arms' own turns, so a stem there would draw the same line twice. */
.held-item:not(:first-child):not(:last-child)::before{
  content:"";position:absolute;left:calc(50% - 1px);top:calc(var(--held-drop) * -1);
  width:calc(2px * var(--rail));height:var(--held-drop);background:var(--line-strong);
  transform-origin:50% 0;transform:scaleY(var(--held-draw,1));
}
.held-item{
  position:relative;background:var(--mist);border:1px solid transparent;
  border-radius:var(--r-md);padding:var(--s5,20px);
  display:flex;flex-direction:column;gap:var(--s3);
}
.held-mark{display:flex;align-items:center;min-height:34px}
.held-mark img{max-height:28px;max-width:100%;width:auto;height:auto}
.held-name{font-size:16px;line-height:1.25;color:var(--ink)}
.held-note{font-size:14.5px;line-height:1.55;color:var(--ink-2);flex:1 1 auto;margin:0}

/* --- how wide is the panel? ------------------------------------------
   CONTAINER QUERIES, not viewport ones. This panel is half the page here and
   could be the whole width somewhere else - the homepage will reuse it - so how
   many companies fit across is a question about the panel, not about the screen.

   The thresholds are the width a row of N cards needs at a 200px minimum plus
   its gaps. Five is the ceiling Arslan set on 21 August and it is also the honest
   one: past five in half a page the cards stop being readable, and the rail that
   joins them is only ever correct across a single row.

   Two rules do all of it now. The cap says how many this panel can take, the
   count says how many there are, and `.held` above works out the rest. What used
   to be here was fifty-odd selectors enumerating every count against every
   container width - which had to be extended by hand for a sixth company and,
   because nobody had, left the sixth company's trunk hanging in mid-air. */
@container (min-width:440px){
  .tree-card{grid-template-columns:120px minmax(0,1fr)}
  .tree-mark{grid-column:1;grid-row:2}
  .tree-say{grid-column:2;grid-row:2}
}

@container (min-width:362px){.held{--held-cap:2}}
@container (min-width:549px){.held{--held-cap:3}}
@container (min-width:736px){.held{--held-cap:4}}
@container (min-width:923px){.held{--held-cap:5}}

/* The published count, from the attribute the renderer writes. Ten rules,
   because CSS cannot read a number out of an attribute; past ten the 99 default
   takes over and the row reads as wrapped, which by then it is. */
.held[data-count="1"]{--held-n:1}
.held[data-count="2"]{--held-n:2}
.held[data-count="3"]{--held-n:3}
.held[data-count="4"]{--held-n:4}
.held[data-count="5"]{--held-n:5}
.held[data-count="6"]{--held-n:6}
.held[data-count="7"]{--held-n:7}
.held[data-count="8"]{--held-n:8}
.held[data-count="9"]{--held-n:9}
.held[data-count="10"]{--held-n:10}

/* ========================= WHERE WE INVEST TODAY =========================
   The `where-we-invest-1a` design, on this site's tokens rather than its own.

   WHAT WAS TRANSLATED, AND WHY IT HAD TO BE. The prototype ships its own
   palette (#0c3157, #1a7fd4), its own three typefaces (Archivo, IBM Plex Sans,
   IBM Plex Mono from Google Fonts) and its own radii. None of that survives
   contact with CLAUDE.md: the design system is the operative standard for
   tokens, type, spacing and palette, and Roboto with an Arial fallback is the
   typeface. So the structure, the hierarchy and the interaction are the
   design's; every value below is the system's. The colours map almost exactly -
   its navy is our Midnight, its accent our Blue - which is a good sign about
   the design rather than a coincidence.

   The map is inline SVG rendered by inc/locations.php from paths projected at
   build time. No d3, no topojson, no world atlas over the wire: see that file's
   header for the two hard gates that ruled the CDN version out. */
.invest{
  background:var(--white);border:1px solid var(--line);border-radius:var(--r-lg);
  padding:var(--s6);
}
.section--mist .invest{background:var(--white)}

.invest-head{
  display:flex;align-items:baseline;justify-content:space-between;
  gap:var(--s4);margin-bottom:var(--s5,20px);
}
.invest-h{font-size:20px;line-height:1.25;color:var(--ink)}
.invest-count{
  font-family:var(--cond);font-stretch:var(--cw);font-weight:700;
  text-transform:uppercase;letter-spacing:.09em;font-size:11px;color:var(--ink-3);
  white-space:nowrap;
}

/* The plate.

   NO HEIGHT HERE, deliberately. It used to be a fixed height with the SVG set to
   `slice`, which crops whatever does not fit - so the window contained every
   market and the plate showed about two thirds of it, cutting Morocco off one
   edge and Java off the other. Arslan, 25 August: "the map is current zoomed in
   so zoom out to contain all countries."

   The SVG has a viewBox, which gives it an intrinsic aspect ratio. Letting it
   size itself from that means the whole window is always visible and the plate is
   always exactly as tall as the projection needs - no magic number here to be
   kept in step with the one in make-country-paths.py, which is the kind of pair
   that drifts the first time somebody changes the window. */
.invest-map{
  margin:0;border-radius:var(--r-sm);
  background:linear-gradient(180deg,#f6fafe,var(--mist));overflow:hidden;
}
.invest-svg{display:block;width:100%;height:auto}

.invest-land{fill:#dbe7f3;stroke:var(--white);stroke-width:.6}
.invest-shape{
  fill:#a8cbec;stroke:#7fb2e2;stroke-width:.9;
  transition:fill 200ms ease-in-out,opacity 200ms ease-in-out;
}
/* Purple for the offices and for every line that leaves one - Arslan, 25 August.
   `--purple` is the top stop of the Boson Spectrum, so this is the palette's own
   value rather than a colour picked to look like it. It also does a job here that
   Blue could not: the markets are Blue, so putting the offices and their arcs on
   the other end of the Spectrum separates what Boson *is* from where it *invests*
   at a glance, without adding a colour to the system. */
.invest-arc{
  fill:none;stroke:var(--purple);stroke-width:1;opacity:.55;
  transition:opacity 200ms ease-in-out;
}
.invest-dot{
  fill:var(--white);stroke:var(--accent);stroke-width:1.8;
  transition:opacity 200ms ease-in-out;
}
.invest-pin{fill:var(--purple)}
.invest-pin-label{
  font-family:var(--cond);font-stretch:var(--cw);font-weight:700;
  font-size:11.5px;fill:var(--purple);
  paint-order:stroke;stroke:#f4f9fe;stroke-width:3;stroke-linejoin:round;
}

/* Hovering a row lights its own region and steps the others back. design.js adds
   the class; nothing here needs to know what the regions are, which is what lets
   an editor invent one. */
.invest.is-focused .invest-shape.is-dim{opacity:.28}
.invest.is-focused .invest-shape:not(.is-dim){fill:#8fbde8}
.invest.is-focused .invest-arc.is-dim{opacity:.1}
.invest.is-focused .invest-arc:not(.is-dim){opacity:.95}
.invest.is-focused .invest-dot.is-dim{opacity:.2}

.invest-legend{
  display:flex;flex-wrap:wrap;gap:var(--s3) var(--s6);
  margin:var(--s4) 0 0;font-size:13.5px;color:var(--ink-2);
}
.invest-key{display:inline-flex;align-items:center;gap:8px}
.invest-key::before{content:"";width:10px;height:10px;border-radius:50%;flex:none}
.invest-key--office::before{background:var(--purple)}
.invest-key--market::before{background:var(--white);box-shadow:inset 0 0 0 2px var(--accent)}

.invest-rows{margin:var(--s5,20px) 0 0;padding:0;list-style:none;border-top:1px solid var(--line)}
.invest-row{
  display:grid;grid-template-columns:minmax(0,180px) minmax(0,1fr);
  gap:var(--s3) var(--s5,20px);padding:var(--s4) var(--s1);
  border-bottom:1px solid var(--line);
  transition:background-color 150ms ease-in-out;
}
.invest-row[data-region]:hover{background:var(--mist)}
.invest-label{
  font-family:var(--cond);font-stretch:var(--cw);font-weight:700;
  text-transform:uppercase;letter-spacing:.08em;font-size:11px;
  line-height:1.5;color:var(--ink-3);
}
.invest-value{font-size:15.5px;line-height:1.5;color:var(--ink)}
.invest-note{margin-top:var(--s4);font-size:12px;line-height:1.5;color:var(--ink-3);max-width:60ch}

@media (max-width:620px){
  .invest-row{grid-template-columns:minmax(0,1fr);gap:4px}
}
@media (prefers-reduced-motion:reduce){
  .invest-shape,.invest-arc,.invest-dot,.invest-row{transition:none}
}

/* =============================== THE FULL CARDS =============================
   The same companies again, at the top of the portfolio page, from the same post
   type - see `[bsn_portfolio_cards]`. A photograph and three paragraphs there, a
   logo and one sentence in the fan-out below. One record, so the two cannot
   disagree about how many companies there are or what they are called.

   The long description is written in the block editor, so it arrives as plain
   `<p>` rather than as `.prose`. These rules give it the page's prose settings
   without asking an editor to know what a class is. */
.folio-body{margin-top:var(--s4)}
.folio-body p{font-size:17px;line-height:1.7;color:var(--ink-2);max-width:66ch}
.folio-body p + p{margin-top:var(--s4)}
.folio-body strong{color:var(--ink);font-weight:700}
.folio-body ul,.folio-body ol{margin-top:var(--s4);padding-left:var(--s6);color:var(--ink-2)}
.folio-body li + li{margin-top:var(--s2)}

/* The photograph, once there is one. Same 4:3 frame the placeholder reserves, so
   setting one does not reflow the section. */
.folio-shot{margin:0}
.folio-shot-img{
  display:block;width:100%;height:auto;aspect-ratio:4 / 3;object-fit:cover;
  border-radius:var(--r-md);background:var(--mist);
}
/* The placeholder's ratio, as a class. It was an inline style in the prototype's
   generator; a rule that ships in the stylesheet is the house standard and it is
   also the only version a Content-Security-Policy will allow later. */
.slot--4x3{aspect-ratio:4 / 3}

/* ========================== THE QUESTIONS ========================== */
/* Nine questions in three-column runs, each under a hairline, each numbered.
   Cards became rules: a card puts a box around an answer, and nine boxes read as
   nine unrelated things rather than one list somebody worked through.

   The numerals are counters, as on /process/'s expectations - furniture, not
   content, so they never reach the FAQPage schema or a screen reader as an
   ordinal read twice. The second run continues the count rather than restarting,
   because the reader is working through one set of nine. */
.faqs{display:grid;grid-template-columns:repeat(3,1fr);gap:0 var(--s8);margin-top:var(--s12);counter-reset:faq}
.faqs--continued{counter-reset:faq 3}
.faq{
  display:flex;flex-direction:column;gap:var(--s3);
  padding:var(--s6) var(--s8) var(--s8) 0;border-top:1px solid var(--line);
}
.faq h3{
  display:flex;align-items:baseline;gap:var(--s3);margin:0;
  font-size:20px;line-height:1.3;letter-spacing:-.01em;color:var(--ink);
}
.faq h3::before{
  counter-increment:faq;content:counter(faq,decimal-leading-zero);
  flex:none;min-width:20px;
  font-family:var(--cond);font-stretch:var(--cw);font-weight:500;
  font-size:12px;line-height:1.4;letter-spacing:.06em;color:var(--ink-3);
}
/* The answer aligns to the question, not to the numeral. */
.faq p{margin:0 0 0 calc(20px + var(--s3));font-size:15px;line-height:1.7;color:var(--ink-2);max-width:42ch}

@media (max-width:1080px){
  .faqs{grid-template-columns:repeat(2,1fr)}
}
@media (max-width:760px){
  .faqs{grid-template-columns:minmax(0,1fr);gap:0}
  .faq{padding-right:0}
}

/* A ruled pair. The For Owners design opens with two columns each standing under
   its own 2px rule, which turns two paragraphs into two declared subjects rather
   than a block of text that happens to have two headings in it. Modifier on the
   existing .two-col rather than a new component, because the layout is
   unchanged - only the rule is new. */
.two-col--ruled > *{padding-top:var(--s6);border-top:2px solid var(--midnight)}

/* The permanence claim, at the size it earns.
   It was the clearest sentence on the site and it was living at 13px in the
   footer, which the marketing review called the least-read position available.
   Bilal approved the permanence claims on 31 August 2026, so it opens
   /why-boson/ above the four reasons that support it. */
.claim{
  margin:0 0 var(--s12);max-width:24ch;
  font-family:var(--cond);font-stretch:var(--cw);font-weight:700;
  font-size:clamp(1.5rem,2.6vw,2.6rem);line-height:1.1;letter-spacing:-.01em;color:var(--ink);
}

/* ==================== CREDIBILITY AT THREE LEVELS ==================== */
/* Three plain cards become three columns that get heavier as they descend the
   chain: a light rule over the listed parent, a heavier one over the operating
   group, and a filled navy panel for Boson itself - the level the reader is
   actually standing on. Same device as the "you are here" marker on the
   ownership tree, in a section that previously stated the relationship and
   showed nothing.

   THE LOGOS ARE DECORATIVE, THE NAMES ARE THE LINKS. Each mark carries alt=""
   and aria-hidden, because the company name sits right underneath it as a
   heading - a screen reader announcing "Constellation Software Inc." twice is
   worse than not seeing the logo at all. The outbound link is on the name, so it
   has a real accessible name rather than a bare image link, and Rank Math's
   new_window_external_links handles the target.

   USED TWICE, WITH AND WITHOUT MARKS. /for-owners/ builds "Who we speak with"
   from the same component and passes no logos, because an audience is not a
   company and has no mark to show. The image is optional by construction: no
   .level-mark, no space reserved for one.

   NOTE FOR THE PARENT-BRAND REVIEW. Using Constellation's and Andromeda's marks
   is a stronger form of the relationship claim than naming them in prose, and
   that wording is already gated under Q14. The precedent is the ownership tree,
   which already carries both marks and both outbound links on the homepage, so
   this is consistent with what ships rather than new - but it belongs in the
   same review, not outside it. */
.levels{display:grid;grid-template-columns:repeat(3,1fr);margin-top:var(--s12)}
.level{
  display:flex;flex-direction:column;gap:var(--s4);
  padding:var(--s8) var(--s8) var(--s12) 0;border-top:2px solid rgba(9,47,79,.25);
}
.level:nth-child(2){padding-left:var(--s8);border-top-color:rgba(9,47,79,.45);border-left:1px solid var(--line)}
.level--here{padding-left:var(--s8);background:var(--midnight);border-top-color:var(--midnight)}
/* Height-constrained with the intrinsic size still on the element, so three
   marks of three different aspect ratios sit on one optical line without any of
   them reserving the wrong box while the page loads. */
/* align-self is the load-bearing part. .level is a column flex container, so a
   child stretches across the cross axis by default: width:auto resolved to the
   full column and height:26px then squashed each mark to 400x26, which is not a
   logo, it is a smear. Three marks of three aspect ratios, each sized by its
   own height and left alone horizontally. */
.level-mark{display:block;align-self:flex-start;height:26px;width:auto;margin-bottom:var(--s2)}
.level h3{margin:0;font-size:22px;line-height:1.25;letter-spacing:-.01em;color:var(--ink)}
.levels .level h3 a{color:inherit;text-decoration:none}
.levels .level h3 a:hover{color:var(--accent);text-decoration:underline;text-underline-offset:3px}
.level p{margin:0;font-size:17px;line-height:1.65;color:var(--ink-2);max-width:42ch}
.level--here h3{color:var(--white)}
.levels .level--here h3 a{color:var(--white)}
.levels .level--here h3 a:hover{color:var(--white);text-decoration:underline}
.level--here p{color:rgba(255,255,255,.86)}

@media (max-width:1080px){
  .levels{grid-template-columns:minmax(0,1fr)}
  .level{padding:var(--s6) 0 var(--s8)}
  .level:nth-child(2){padding-left:0;border-left:0}
  .level--here{padding:var(--s8)}
}

/* ============ WHAT AN INITIAL CONVERSATION IS, AND IS NOT ============ */
/* Two rounded cards become one full-bleed split: the four things it is on the
   deeper lavender, the four things it is not on navy. No card, no radius, no
   shadow - the ground carries the distinction instead, which is the whole point
   of putting a reassurance and a disclaimer side by side.

   The handoff's #E4E1F2 against a #F2F0FA page is the same relationship this
   palette already has between --lavender and --mist, one step deeper, so both
   resolve to tokens rather than to a new value.

   ON THE BAND RULE. --lavender is also the invite band that follows this
   section, so the left column runs into it without a seam. That is fine here and
   only here: the right half is navy, so the boundary between this section and
   the invite is unmistakable across half the viewport. A single-tone tinted band
   above the invite would still be wrong. */
.conversation{display:grid;grid-template-columns:1fr 1fr}
.conversation-col{padding:var(--s16) var(--s24) var(--s24);display:flex;flex-direction:column;gap:var(--s8)}
.conversation-col--is{background:var(--lavender)}
.conversation-col--not{background:var(--midnight)}
.conversation-label{margin:0;color:var(--ink)}
.conversation-col--not .conversation-label{color:rgba(255,255,255,.74)}
.conversation-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:var(--s8)}
.conversation-list li{font-size:18px;line-height:1.5;color:var(--ink-2);max-width:44ch}
/* .86 rather than the handoff's #DCE7F1: the same near-white, expressed the way
   every other reverse body on this site is, so there is one way to write it. */
.conversation-col--not .conversation-list li{color:rgba(255,255,255,.86)}

/* The lead-in carries the subject of the sentence, so it is the only thing in
   the row that changes weight. Same device as .criteria-items strong. */
.conversation-list strong{font-weight:600;color:var(--ink)}
.conversation-col--not .conversation-list strong{color:var(--white)}

/* THE SAME COMPONENT, USED TWICE.
   /what-changes/ opens with the identical shape - the principles on the deeper
   lavender, what an acquisition actually changes on navy - so it is this
   component with a footnote rather than a second one that looks like it. The
   footnote spans both columns on the page ground below them, which is what
   makes it read as a caveat on the pair rather than on either half.

   This was the last page using .compare. The component and its
   `boson/compare` pattern both stay: a marketer can still insert it from the
   inserter, and deleting the CSS under a registered pattern would leave them a
   block that renders unstyled. */
.conversation--footed{background:var(--mist)}
.conversation-foot{
  grid-column:1/-1;margin:0;padding:var(--s12) var(--s24) var(--s16);
  max-width:76ch;font-size:17px;line-height:1.6;color:var(--ink-2);
}

@media (max-width:1080px){
  .conversation-col{padding:var(--s12) var(--s16) var(--s16)}
  .conversation-foot{padding:var(--s8) var(--s16) var(--s12)}
}
@media (max-width:760px){
  .conversation{grid-template-columns:minmax(0,1fr)}
  .conversation-col{padding:var(--s12) var(--pad)}
  .conversation-foot{padding:var(--s8) var(--pad) var(--s12)}
}

/* ============== WHAT OWNERS SHOULD EXPECT THROUGHOUT ============== */
/* Two rounded panels become two plain columns, each under a ruled label, with
   the six commitments numbered straight through from one column to the next.
   The heading row above is unchanged: it was already .split-head, which is this
   site's existing heading-beside-its-note row, so the handoff's top half needed
   no new component at all.

   THE NUMERALS ARE CSS COUNTERS, NOT TEXT. They are new - the panels had none -
   and they are furniture rather than content: the list is an <ol>, which already
   tells a screen reader these are ordered and how many there are, so printing
   "01" into the markup would only make it read the ordinal twice. It also keeps
   the section's word-for-word content exactly what it was, which is what the
   parity check is for.

   The second column continues the count rather than restarting, in the markup
   with start="4" and in the paint with a counter offset - the design numbers
   them 01 through 06 across the pair, because they are one list of six
   commitments presented in two halves. */
.expect-cols{display:grid;grid-template-columns:1fr 1fr;gap:0 var(--s24);margin-top:var(--s16)}
.expect-col{display:flex;flex-direction:column}
.expect-label{margin:0;color:var(--ink);padding-bottom:var(--s6);border-bottom:2px solid var(--midnight)}
.expect-list{list-style:none;margin:0;padding:0;counter-reset:expect}
.expect-list--continued{counter-reset:expect 3}
.expect-list li{
  display:flex;gap:var(--s6);padding:var(--s8) 0;border-bottom:1px solid var(--line);
  font-size:18px;line-height:1.5;color:var(--ink-2);
}
.expect-list li:last-child{border-bottom:0}
/* --ink-3 rather than the handoff's #8E8CAB, for the fourth time today and the
   same reason: #8E8CAB is about 2.9:1 on this band and 13px is small text. */
.expect-list li::before{
  counter-increment:expect;content:counter(expect,decimal-leading-zero);
  min-width:22px;flex:none;font-size:13px;line-height:1.6;font-weight:500;color:var(--ink-3);
}

@media (max-width:760px){
  .expect-cols{grid-template-columns:minmax(0,1fr);gap:var(--s12)}
  /* Stacked, the last row of the first column needs its rule back - the two
     columns have become one run and the join would otherwise be invisible. */
  .expect-col:first-child .expect-list li:last-child{border-bottom:1px solid var(--line)}
}

/* ========================= THE PROCESS ========================= */
/* Third section from the handoff bundle. The five stages stay a numbered list on
   the left; a navy panel at the right tracks whichever one is active, with a
   progress ring, a rail and a bar. The sequence advances itself every five
   seconds and stops for good the moment a pointer or the keyboard arrives -
   design.js owns that, and never starts it under reduced motion.

   TWO DEVIATIONS FROM THE HANDOFF, BOTH CONTRAST, BOTH LEARNED THE HARD WAY
   EARLIER TODAY when the gate caught a link measured mid-fade at 2.47:1:

   - The handoff dims an inactive stage's body to opacity 0.6. On this band that
     computes to roughly 3:1 against the ground and would fail 1.4.3. Inactive
     bodies stay at full strength; the active stage is marked by its ground and
     its rail instead, which is the louder signal anyway.
   - The inactive number is #8E8CAB in the handoff, about 2.9:1 on the lavender.
     It is var(--ink-3) here, the value this palette already carries for exactly
     this job at 5.09:1.

   The panel's own greys are unchanged in spirit but expressed as the site's
   reverse-text values rather than #8FAECB. */
.process{display:grid;grid-template-columns:1fr 1fr;gap:var(--s16);align-items:start}
.process .stages{margin-top:0}
/* Both halves of the section read the spectrum from the same place. The rail
   down the left of a highlighted stage is a slice of the same gradient its bar
   in the readout paints, sampled at the same position - so stage three's rail
   and stage three's bar are the same colour by construction rather than by two
   people picking a hex.
   
   It samples rather than naming a value because five stages do not divide four
   stops: naming them would have meant either inventing two intermediate hues,
   which is off-palette and needs the design-system owner, or repeating one,
   which is what this replaced. */
.process .stage{position:relative}
.process .stage::before{
  content:"";position:absolute;left:0;top:0;bottom:0;width:0;
  background-image:linear-gradient(90deg,var(--purple),var(--indigo),var(--blue),var(--cyan));
  background-size:500% 100%;
  transition:width .45s ease-out;
}
.process .stage:nth-child(1)::before{background-position-x:0%}
.process .stage:nth-child(2)::before{background-position-x:25%}
.process .stage:nth-child(3)::before{background-position-x:50%}
.process .stage:nth-child(4)::before{background-position-x:75%}
.process .stage:nth-child(5)::before{background-position-x:100%}

.process .stage{
  padding:var(--s8) var(--s6) var(--s8) 0;cursor:pointer;
  transition:background .45s ease-out;
}
.process .stage .no{color:var(--ink-3);transition:color .45s ease-out,padding-left .45s ease-out}
/* :hover as well as [data-active], in that order, so the highlight is there on
   the first pixel of the pointer arriving rather than after the JS has run - and
   so the section still responds with the script disabled. */
.process .stage:hover,
.process .stage[data-active]{background:rgba(255,255,255,.72)}
.process .stage:hover::before,
.process .stage[data-active]::before{width:3px}
.process .stage:hover .no,
.process .stage[data-active] .no{color:var(--ink);padding-left:var(--s3)}

/* The panel is a readout, not a chrome block: it sits on the same ground as the
   list, vertically centred against it, and says where in the sequence the
   emphasis is. Every stage carries a bar; the bars up to and including the
   active one are full, and the active one is the only solid fill. */
.process-panel{align-self:stretch;display:flex;align-items:center;padding-left:var(--s6)}
.process-panel-in{width:100%;display:flex;flex-direction:column;gap:var(--s8)}
.process-panel-head{
  display:flex;justify-content:space-between;align-items:baseline;
  padding-bottom:var(--s4);border-bottom:1px solid var(--line-strong);
}
.process-steps{display:flex;flex-direction:column;gap:var(--s8)}
.process-step{display:flex;flex-direction:column;gap:var(--s2)}
.process-step-head{display:flex;justify-content:space-between;align-items:baseline;gap:var(--s4)}
/* --ink-3, not the handoff's #9B9AB5 and #7A79A0. Both are around 2.5:1 on this
   band; --ink-3 is 5.09:1 and is what the palette already carries for a muted
   label. The hierarchy survives - reached stages go to --ink, which is darker
   still - and the third contrast failure of the day does not happen. */
.process-step-num{
  font-family:var(--cond);font-stretch:var(--cw);font-weight:600;font-size:11px;line-height:1;
  letter-spacing:.14em;color:var(--ink-3);transition:color .4s ease-out;
}
.process-step-title{font-size:15px;line-height:1.2;font-weight:500;color:var(--ink-3);transition:color .4s ease-out}
.process-step[data-done] .process-step-num,
.process-step[data-active] .process-step-num,
.process-step[data-done] .process-step-title,
.process-step[data-active] .process-step-title{color:var(--ink)}

.process-track{position:relative;height:10px;background:var(--line);transition:transform .35s ease-out}
/* THE SPECTRUM, SPENT WHERE IT ACTUALLY MEANS SOMETHING.
   The five bars carry one continuous Boson Spectrum sweep rather than five
   colours: each fill paints the same purple-to-cyan gradient at 500% width and
   shows its own fifth of it, so filling them in order sweeps the mark's own
   gradient left to right across the section. Purple at the first conversation,
   cyan at completion.

   This is the palette's Blue-and-Cyan allowance being spent, the same reasoning
   the button hover already records - not a new hue. Bars are not text, so the
   spectrum's mid-tones carry no contrast obligation here; every label above
   them is unchanged and still --ink or --ink-3. */
.process-fill{
  position:absolute;inset:0 auto 0 0;width:0;
  background-image:linear-gradient(90deg,var(--purple),var(--indigo),var(--blue),var(--cyan));
  background-size:500% 100%;opacity:.45;
  transition:width .7s cubic-bezier(.4,0,.2,1),opacity .4s ease-out,background-position .7s ease-out;
}
.process-step:nth-child(1) .process-fill{background-position-x:0%}
.process-step:nth-child(2) .process-fill{background-position-x:25%}
.process-step:nth-child(3) .process-fill{background-position-x:50%}
.process-step:nth-child(4) .process-fill{background-position-x:75%}
.process-step:nth-child(5) .process-fill{background-position-x:100%}
.process-step[data-done] .process-fill,
.process-step[data-active] .process-fill{width:100%}
/* Reached stays translucent, the one being read goes to full strength. That is
   the whole hierarchy: same colour, two weights. */
.process-step[data-active] .process-fill{opacity:1}

/* HOVERING A STAGE FILLS ITS BAR, FROM EITHER SIDE, WITHOUT WAITING FOR JS.
   design.js already does this - a pointer on a stage activates it and the bar
   fills - but only once the script has run and only while it is running. These
   rules make the link structural: hover any stage in the list, or its row in the
   readout, and the spectrum fills up to and including that stage, immediately
   and with the script disabled entirely.

   Cumulative on purpose, matching what the script does: `-n+N` fills every bar
   up to the hovered one, so the sweep always reads as progress through the
   sequence rather than one bar lighting up on its own. The hovered stage is the
   only one at full strength; the ones behind it stay at the reached weight.

   Written out per stage because :has() cannot count to a sibling's index. Five
   stages, five pairs, and the pairs are generated from the same numbers as the
   gradient positions above them. */
/* stage 01 */
.process:has(.stage:nth-child(1):hover) .process-step:nth-child(-n+1) .process-fill,
.process:has(.process-step:nth-child(1):hover) .process-step:nth-child(-n+1) .process-fill{width:100%}
.process:has(.stage:nth-child(1):hover) .process-step:nth-child(1) .process-fill,
.process:has(.process-step:nth-child(1):hover) .process-step:nth-child(1) .process-fill{opacity:1}
/* stage 02 */
.process:has(.stage:nth-child(2):hover) .process-step:nth-child(-n+2) .process-fill,
.process:has(.process-step:nth-child(2):hover) .process-step:nth-child(-n+2) .process-fill{width:100%}
.process:has(.stage:nth-child(2):hover) .process-step:nth-child(2) .process-fill,
.process:has(.process-step:nth-child(2):hover) .process-step:nth-child(2) .process-fill{opacity:1}
/* stage 03 */
.process:has(.stage:nth-child(3):hover) .process-step:nth-child(-n+3) .process-fill,
.process:has(.process-step:nth-child(3):hover) .process-step:nth-child(-n+3) .process-fill{width:100%}
.process:has(.stage:nth-child(3):hover) .process-step:nth-child(3) .process-fill,
.process:has(.process-step:nth-child(3):hover) .process-step:nth-child(3) .process-fill{opacity:1}
/* stage 04 */
.process:has(.stage:nth-child(4):hover) .process-step:nth-child(-n+4) .process-fill,
.process:has(.process-step:nth-child(4):hover) .process-step:nth-child(-n+4) .process-fill{width:100%}
.process:has(.stage:nth-child(4):hover) .process-step:nth-child(4) .process-fill,
.process:has(.process-step:nth-child(4):hover) .process-step:nth-child(4) .process-fill{opacity:1}
/* stage 05 */
.process:has(.stage:nth-child(5):hover) .process-step:nth-child(-n+5) .process-fill,
.process:has(.process-step:nth-child(5):hover) .process-step:nth-child(-n+5) .process-fill{width:100%}
.process:has(.stage:nth-child(5):hover) .process-step:nth-child(5) .process-fill,
.process:has(.process-step:nth-child(5):hover) .process-step:nth-child(5) .process-fill{opacity:1}

/* HOVER. The row lifts by a pixel and its track thickens - enough to say "this
   responds", small enough not to reflow anything beside it. The pointer already
   activates the stage through design.js; this is the part that happens before
   the JS does, and the part that still happens if the JS never loads. */
.process-step{cursor:pointer}
.process-step:hover{transform:translateY(-1px)}
.process-step:hover .process-track{transform:scaleY(1.3)}
.process-step:hover .process-fill{opacity:1}
.process-step:hover .process-step-num,
.process-step:hover .process-step-title{color:var(--ink)}

.process-hint{margin:0;font-size:12px;line-height:1.5;color:var(--ink-3)}

@media (prefers-reduced-motion:reduce){
  .process .stage,
  .process .stage::before,
  .process .stage .no,
  .process-step-num,
  .process-step-title,
  .process-track,
  .process-fill{transition:none}
  /* The lift is the only thing here that moves geometry. Under reduce it does
     not, and the colour change alone carries the hover. */
  .process-step:hover{transform:none}
  .process-step:hover .process-track{transform:none}
}
@media (max-width:1080px){
  /* The panel stops tracking and becomes a header for the list below it. There
     is nothing to be sticky against once the two are stacked. */
  .process{grid-template-columns:minmax(0,1fr);gap:var(--s12)}
  .process-panel{order:-1;padding-left:0}
  .process .stage{padding-left:0}
}
/* ====================== WHAT IS DOCUMENTED ====================== */
/* Second section from the same handoff: heading against its intro at the top, a
   three-by-three field of hairline-separated cells, the closing statement below.
   It replaces a two-column layout whose right half was a Section / What it
   settles table.

   WHY A dl AND NOT THE HANDOFF'S SPANS. This morning I argued these belonged in
   a table because `th scope="row"` is what makes a screen reader read "Your
   email address: the route we use to answer you" as one pair, and that a card
   grid would look better and announce worse. The handoff asks for the card grid.
   A description list settles it: dt/dd carries the pairing semantics a table
   gave, in a structure that lays out as a grid natively. That reasoning stands
   for the other three .table sections, which are unchanged.

   The table's Section / What it settles column headers are gone, because the new
   layout has no columns to head. That is the one content difference and it shows
   up in the parity diff as two removed strings.

   The cells are white, not the handoff's #F2F0FA. The hairline grid is drawn by
   a 1px gap over a painted parent, so cells have to match the band they sit on -
   and this band is white rather than tinted because the lavender invite band
   follows it, and two tinted bands do not touch on this site. */
.documented-head{display:grid;grid-template-columns:1fr 1fr;gap:var(--s16);align-items:start}
.documented-intro{display:flex;flex-direction:column;gap:var(--s6)}
.documented-grid{
  margin:var(--s16) 0 0;display:grid;grid-template-columns:repeat(3,1fr);gap:1px;
  background:var(--line);border-top:1px solid var(--line);border-bottom:1px solid var(--line);
}
.documented-cell{background:var(--white);padding:var(--s8);display:flex;flex-direction:column;gap:var(--s3)}
/* Flush to the column edges: the outer cells lose their outer padding so the
   type lines up with everything else in the wrap. */
.documented-cell:nth-child(3n+1){padding-left:0}
.documented-cell:nth-child(3n){padding-right:0}
/* Was dt/dd until 2026-08-31. The description list carried better pairing
   semantics, but core blocks have no dt/dd, so those eighteen sentences could
   only ever live in a locked HTML block a marketer cannot edit. Heading plus
   paragraph is the accessible, editable equivalent and the pairing is still
   programmatic through the heading structure. Recorded in
   docs/implementation-log.md - it is a real trade, not an oversight. */
.documented-cell h3{margin:0;color:var(--ink-3)}
.documented-cell p{margin:0;font-size:17px;line-height:1.5;color:var(--ink-2)}
.documented-pull{margin-top:var(--s16);max-width:38ch}

@media (max-width:1080px){
  .documented-head{grid-template-columns:minmax(0,1fr);gap:var(--s8)}
  .documented-grid{grid-template-columns:repeat(2,1fr)}
  .documented-cell:nth-child(3n+1),
  .documented-cell:nth-child(3n){padding-left:var(--s8);padding-right:var(--s8)}
  .documented-cell:nth-child(2n+1){padding-left:0}
  .documented-cell:nth-child(2n){padding-right:0}
}
@media (max-width:600px){
  .documented-grid{grid-template-columns:minmax(0,1fr)}
  .documented-cell,
  .documented-cell:nth-child(2n+1),
  .documented-cell:nth-child(2n){padding:var(--s6) 0}
}

/* ================== WHAT WE TYPICALLY LOOK FOR ================== */
/* An editorial split, from the design handoff in the "Website section design
   options" project (option 1c): a solid navy title panel running the full height
   at the left, the criteria in two labelled row groups at the right, separated by
   a hairline. It replaces a rounded white card split down the middle - the
   .compare component, which stays because /what-changes/ still uses it.

   MAPPED, NOT COPIED. The handoff carries its own palette and a Google-hosted
   Archivo pair; this site already has both, so every value below resolves to an
   existing token. The handoff's README asks for exactly that. Where the two
   disagree:

     handoff            here                     why
     #12385C navy       var(--midnight) #092f4f  the site's navy
     #F2F0FA ground     var(--mist)              which is Boson Lavender #f3eff8
     #41566E body       var(--ink-2)
     #7A79A0 label      var(--ink-3)
     rgba(18,56,92,.16) var(--line)
     20px item body     18px                     the site's scale has 17 and 18,
                                                 not 20. Flagged to design.
     12px/.14em label   .lettering               the site's one label role, at
                                                 11.5px/.11em. One label style,
                                                 not two.
     1200/900px         1080/760px               this site's breakpoints.

   The group labels are h3, not the handoff's span. They read identically and the
   heading outline survives - two h3s under the h2 is what /for-owners/ had
   before this change and what the a11y gate checks. */
.criteria{display:grid;grid-template-columns:440px 1fr;background:var(--mist)}
.criteria-panel{
  background:var(--midnight);padding:var(--s24) var(--s16);
  display:flex;flex-direction:column;justify-content:space-between;gap:var(--s16);
}
.criteria-h{color:var(--white);max-width:14ch}
.criteria-note{
  margin:0;font-size:16px;line-height:1.65;color:rgba(255,255,255,.74);
  border-top:1px solid rgba(255,255,255,.22);padding-top:var(--s8);max-width:46ch;
}
.criteria-body{padding:var(--s24);display:flex;flex-direction:column;gap:var(--s16)}
.criteria-group{display:grid;grid-template-columns:170px 1fr;gap:var(--s8);align-items:start}
/* padding-top optically aligns the label with the first line of body text. */
.criteria-label{margin:0;padding-top:var(--s2);color:var(--ink-3)}
.criteria-items{display:flex;flex-direction:column;gap:var(--s6);max-width:62ch}
.criteria-items p{margin:0;font-size:18px;line-height:1.5;color:var(--ink-2)}
.criteria-items strong{font-weight:600;color:var(--ink)}
.criteria-rule{height:1px;background:var(--line)}

@media (max-width:1080px){
  .criteria{grid-template-columns:340px 1fr}
  .criteria-panel{padding:var(--s16) var(--s8)}
  .criteria-body{padding:var(--s16) var(--s12)}
}
@media (max-width:760px){
  /* Stacked: the panel is a full-width header over the list it introduces. */
  .criteria{grid-template-columns:minmax(0,1fr)}
  .criteria-panel{padding:var(--s12) var(--pad);gap:var(--s8)}
  .criteria-body{padding:var(--s12) var(--pad);gap:var(--s12)}
  .criteria-group{grid-template-columns:minmax(0,1fr);gap:var(--s4)}
  .criteria-label{padding-top:0}
}

/* ========================= SPACING UTILITIES ========================= */
/* The bodies carried inline `style="margin-top:var(--s6)"` in 28 places, which
   CLAUDE.md forbids and which a marketer editing in the block editor cannot
   see. Three classes, on the spacing scale, replace all of them. */
.mt-s6{margin-top:var(--s6)}
.mt-s8{margin-top:var(--s8)}
.mt-s12{margin-top:var(--s12)}


/* ==================== EVIDENCE FROM THE WIDER GROUP ====================
   The interview cards, the executive-journey cards and the source lists added
   from Hira Zainab's 13 August 2026 email. Mapped in
   docs/decisions/014-wider-group-evidence.md.

   NO PORTRAIT IS CLEARED YET, so every face is `.face--ph`: the same circle at
   the same size, carrying initials. The dimensions are the real ones, so
   dropping a photograph in later cannot reflow the section around it. */

.face--ph{
  display:grid;place-items:center;
  font-family:var(--cond);font-stretch:var(--cw);font-weight:700;
  letter-spacing:.04em;
}

/* On the Midnight ground of the quote section. */
/* On the pale band. */
.jrn-card .face--ph{background:var(--lavender);color:var(--ink-2)}

/* --------------------------------- the executive journeys, on the pale band */

.jrn{
  display:grid;grid-template-columns:repeat(3,1fr);gap:var(--s8);
  margin-top:var(--s12);
}
.jrn-card{
  display:flex;flex-direction:column;
  border-top:1px solid var(--line);padding-top:var(--s6);
}
.jrn-card .face{width:88px;flex:0 0 88px;font-size:24px;margin-bottom:var(--s4)}
.jrn-nm{
  font-family:var(--cond);font-stretch:var(--cw);font-weight:700;
  font-size:21px;color:var(--ink);
}
.jrn-rl{margin-top:var(--s2);font-size:15px;line-height:1.5;color:var(--ink-2)}
.jrn-note{margin-top:var(--s3);font-size:16px;line-height:1.6;color:var(--ink-2)}
/* Pushed to the bottom so three cards of unequal prose still line their links
   up on one row. */
.jrn-links{margin-top:auto;padding-top:var(--s4);display:flex;flex-wrap:wrap;gap:var(--s2)}

.jrn-foot{margin-top:var(--s12)}
.jrn-qualifier{
  margin-top:var(--s8);padding-top:var(--s4);border-top:1px solid var(--line);
  font-size:14.5px;line-height:1.55;max-width:78ch;
}

/* --------------------------------- the source lists */

.evidence{margin-top:var(--s6)}
.ev-list{margin-top:var(--s4);list-style:none;padding:0;display:grid;gap:var(--s4)}
.ev-list li{
  display:flex;flex-direction:column;gap:2px;
  border-top:1px solid var(--line);padding-top:var(--s3);
}
.ev-link{
  /* Not a `.tertiary` pill. These are references inside body copy, and eleven
     buttons down one page would read as navigation rather than as sources. */
  font-size:16px;line-height:1.5;font-weight:600;color:var(--accent);
  text-decoration:underline;text-decoration-thickness:1px;text-underline-offset:.15em;
}
.ev-link:hover{color:var(--accent-hi)}
.ev-src{font-size:14px;line-height:1.45}
/* Boson Blue fails AA on the pale band; this is the ground's own link colour. */
.section--mist .ev-link{color:var(--link-on-mist)}

@media (max-width:1080px){
  .watch,
  .jrn{grid-template-columns:1fr 1fr}
}
@media (max-width:760px){
  .watch,
  .jrn{grid-template-columns:minmax(0,1fr);gap:var(--s6)}
  .jrn-links{margin-top:var(--s4)}
}

/* ================= READ NEXT — handoff option 3a =================
   Imported from Claude Design project 21b9100d, `design_handoff_read_next_nav`,
   31 August 2026. The handoff is marked high fidelity, so its colours, type,
   spacing, radii and motion are reproduced rather than approximated.

   TOKEN MAPPING. The handoff says to prefer the codebase's own tokens and use
   its values to pick the nearest match, so three of its colours resolve to
   tokens already here and the rest become `--nx-*`:

     handoff #4a3fa6 card-2 fill   -> var(--indigo)   #5051a3   (delta 3/17/3)
     handoff #eeecf7 page ground   -> var(--lavender) #efeaf6   (delta 1/0/1)
     handoff #fdfdff on-card text  -> var(--white)    #fff
     handoff 22px mobile radius    -> var(--r-lg)     22px      (exact)
     handoff 16px / 32px spacing   -> var(--s4) / var(--s8)     (exact)

   WHERE THE SCALES DISAGREE, THE HANDOFF WINS AND IT IS RECORDED. Its internal
   spacing runs 6/10/14/18/22/26/28/30 and Boson's scale is 4/8/12/16/24/32, so
   the card's own padding and gaps are literals from the handoff. That is a
   deliberate departure from "tokens come from the design system, not from
   taste" and it needs design-system sign-off; it is not a slip.
   ================================================================ */
.nx{
  /* TWO OF THE HANDOFF'S OWN COLOURS FAIL WCAG 2.2 AA AND ARE CHANGED HERE.
     Measured with axe against the rendered page, not by eye:

       section label  #6b6795 on #efeaf6  -> 4.16:1   needs 4.5   now #656090 (4.9:1)
       eyebrow card 2 #c0b8ee on #5051a3  -> 3.75:1   needs 4.5   now #d8d3f6 (4.6:1)

     Both are 11-12px, so they are normal text and the threshold is 4.5, not 3.0.
     The handoff's accessibility note checks the descriptions and the white
     titles and says they clear 4.5 - which they do - but it does not check the
     eyebrows or the section label, and those are the two that fail. Card 1's
     eyebrow (#9aa2d4 on #23306b) passes at 4.98 and is untouched.

     Reported back rather than absorbed silently: the handoff should carry the
     corrected values, because the next implementation of it will hit the same
     wall. CLAUDE.md makes zero axe violations at AA a hard gate, so this was
     never a judgement call. */
  --nx-navy:#23306b;              /* handoff card-1 fill; no near token exists */
  --nx-eyebrow-navy:#9aa2d4;
  --nx-eyebrow-indigo:#d8d3f6;   /* handoff #c0b8ee fails AA - see note */
  --nx-desc-navy:#c3c8e6;
  --nx-desc-indigo:#d5d0f3;
  --nx-rail-navy:#7f8ad4;
  --nx-rail-indigo:#d5d0f3;
  --nx-label:#656090;            /* handoff #6b6795 fails AA - see note */
  --nx-lift:220ms cubic-bezier(.2,.7,.3,1);
  --nx-tint:200ms ease;
  background:var(--lavender);
  padding-top:var(--s16);padding-bottom:var(--s16);
}
.nx .wrap{display:flex;flex-direction:column;gap:18px}

/* Section label. A real h2 rather than a <nav aria-label>: the handoff allows
   either, and `nav` is not a tagName core/group accepts - it would have cost a
   locked HTML block to gain nothing a visible heading does not already give. */
.nx-h{
  font-weight:700;font-size:12px;letter-spacing:.14em;text-transform:uppercase;
  color:var(--nx-label);
}

.nx-grid{display:grid;grid-template-columns:1fr 1fr;gap:22px}

.nx-card{
  position:relative;display:flex;gap:26px;
  padding:30px 32px 30px 28px;border-radius:28px;
  background:var(--nx-navy);
  transition:transform var(--nx-lift),box-shadow var(--nx-tint);
}
.nx-card:nth-child(even){background:var(--indigo)}

/* The rail. A ::before on a flex container is a flex item, so it lays out
   exactly where the handoff's <span> would and costs no markup. */
.nx-card::before{
  content:"";flex:none;width:3px;border-radius:3px;
  background:linear-gradient(var(--nx-rail-navy),rgba(127,138,212,.15));
}
.nx-card:nth-child(even)::before{
  background:linear-gradient(var(--nx-rail-indigo),rgba(213,208,243,.15));
}

.nx-body{display:flex;flex-direction:column;gap:var(--s4);min-width:0}

.nx-k{
  font-weight:700;font-size:11px;letter-spacing:.16em;text-transform:uppercase;
  color:var(--nx-eyebrow-navy);
}
.nx-card:nth-child(even) .nx-k{color:var(--nx-eyebrow-indigo)}

.nx-t{
  font-weight:700;font-size:29px;line-height:1.12;letter-spacing:-.01em;
  color:var(--white);
}
.nx-t a{color:inherit;text-decoration:none}
/* The whole panel is the click target while the anchor stays on the heading,
   so the card can remain core blocks. Same technique as the portfolio cards. */
.nx-t a::after{content:"";position:absolute;inset:0;border-radius:28px}

.nx-c{
  font-size:15px;line-height:1.6;text-wrap:pretty;color:var(--nx-desc-navy);
}
.nx-card:nth-child(even) .nx-c{color:var(--nx-desc-indigo)}

/* Pill. Visual only - the handoff is explicit that it must not be a nested
   link. The arrow is a ::after so the editable text is the label alone. */
.nx-pill{
  align-self:flex-start;display:inline-flex;align-items:center;
  margin-top:6px;padding:10px 18px;border-radius:999px;
  background:rgba(255,255,255,.10);
  font-weight:600;font-size:13px;letter-spacing:.03em;color:var(--white);
  transition:background var(--nx-tint),color var(--nx-tint);
}
.nx-card:nth-child(even) .nx-pill{background:rgba(255,255,255,.12)}
.nx-pill::after{content:"\2192";margin-left:10px}

/* Hover lifts the card and lights the pill - driven by the card, so anywhere
   on the panel does it. */
.nx-card:hover{transform:translateY(-2px)}
.nx-card:hover .nx-pill{background:var(--white);color:var(--nx-navy)}
.nx-card:nth-child(even):hover .nx-pill{background:var(--white);color:var(--indigo)}

/* Focus ring on the card, not the anchor: :has() puts it where the handoff
   wants it while the anchor keeps the accessible name. :focus-within is the
   fallback for engines without :has(). */
.nx-card:focus-within{box-shadow:0 0 0 3px var(--lavender),0 0 0 6px var(--indigo)}
.nx-card:has(.nx-t a:focus-visible){
  box-shadow:0 0 0 3px var(--lavender),0 0 0 6px var(--indigo);
}
.nx-card:has(.nx-t a:focus:not(:focus-visible)){box-shadow:none}

/* ---- The Boson Field: up on hover, down on hover-away ----
   Not from the handoff. Arslan, 31 August 2026: "when hover make the boson
   fingers come up and when hover away make the boson fingers go down animated".

   It is the real bosonfield(), the generator the hero uses, so the bar count,
   cap radius and Boson Spectrum stay in one place. The motion reuses the
   `--rise` custom property `.hero-field .bf path` already drives, and that is
   deliberate: the comment there records that GSAP rewriting SVG transform
   matrices once pushed the hero's bars 250px outside their own viewBox. CSS
   owns the origin, one number owns the scale.

   REST IS .34, NOT 0. Two passes to get this right. At scaleY(0) the rounded
   caps collapse into a row of lens shapes that reads as five stray dots, and at
   .16 they read as five dashes - both look like a rendering fault rather than
   the mark at rest. .34 on a 30px field reads as a low bar row, which is what
   "down" should look like. */
.nx-field{display:block;height:30px;margin-bottom:2px}
.nx-bf{height:100%;width:auto;opacity:.85}
.nx-bf path{
  transform-box:fill-box;transform-origin:50% 100%;
  transform:scaleY(var(--rise,.34));
  transition:transform .42s cubic-bezier(.22,1,.36,1);
}
/* Right to left coming down, so the gesture reverses rather than repeats. */
.nx-bf path:nth-child(1){transition-delay:.16s}
.nx-bf path:nth-child(2){transition-delay:.12s}
.nx-bf path:nth-child(3){transition-delay:.08s}
.nx-bf path:nth-child(4){transition-delay:.04s}
.nx-bf path:nth-child(5){transition-delay:0s}
/* Left to right going up. Keyboard focus counts: the only focusable thing in
   the card is the heading link, so a keyboard reader gets the mouse answer. */
.nx-card:hover .nx-bf path,
.nx-card:focus-within .nx-bf path{--rise:1}
.nx-card:hover .nx-bf path:nth-child(1),
.nx-card:focus-within .nx-bf path:nth-child(1){transition-delay:0s}
.nx-card:hover .nx-bf path:nth-child(2),
.nx-card:focus-within .nx-bf path:nth-child(2){transition-delay:.04s}
.nx-card:hover .nx-bf path:nth-child(3),
.nx-card:focus-within .nx-bf path:nth-child(3){transition-delay:.08s}
.nx-card:hover .nx-bf path:nth-child(4),
.nx-card:focus-within .nx-bf path:nth-child(4){transition-delay:.12s}
.nx-card:hover .nx-bf path:nth-child(5),
.nx-card:focus-within .nx-bf path:nth-child(5){transition-delay:.16s}

/* 720-900px: keep two columns, ease the title and the padding. */
@media (max-width:900px){
  .nx-t{font-size:25px}
  .nx-card{padding:26px 26px 26px 22px}
}
/* Below 720: single column. Pill padding keeps a 44px touch target. */
@media (max-width:720px){
  .nx-grid{grid-template-columns:minmax(0,1fr);gap:14px}
  .nx-card{padding:22px 22px 22px 18px;border-radius:var(--r-lg);gap:18px}
  .nx-t a::after{border-radius:var(--r-lg)}
  .nx-body{gap:12px}
  .nx-k{font-size:10.5px}
  .nx-t{font-size:23px;line-height:1.15}
  .nx-c{font-size:14.5px;line-height:1.55}
  .nx-pill{padding:12px 18px}
}

/* Reduced motion: the handoff says drop the translate and keep the colour
   transitions. The bars hold their resting height for the same reason. */
@media (prefers-reduced-motion:reduce){
  .nx-card{transition:box-shadow var(--nx-tint)}
  .nx-card:hover{transform:none}
  .nx-bf path{transition:none}
  .nx-card:hover .nx-bf path,
  .nx-card:focus-within .nx-bf path{--rise:.34}
}

/* ============== PERSPECTIVES — handoff as guideline ==============
   Composition from `design_handoff_perspectives_section`, Claude Design project
   21b9100d. Tokens from this design system, per Arslan on 31 August 2026:
   "ensure to stick to your own design system and use this as design guideline".

   So the handoff decides the SHAPE - a dark pull-quote band with an avatar
   attribution, then a light band carrying the supporting quote cards and an
   interviews grid - and this stylesheet decides every VALUE:

     handoff #0d2b4e dark ground   -> var(--midnight)  (section--dark already)
     handoff #f4f3f9 light ground  -> var(--mist)      (section--mist already)
     handoff #fdfdff               -> var(--white)
     handoff #16263d / #2b3d55     -> var(--ink) / var(--ink-2)
     handoff #6b7d95 / #5d789e     -> var(--ink-3)
     handoff #1c4a80 pill hover    -> var(--accent)
     handoff 16px / 14px radii     -> var(--r-md)
     handoff 92/84/72/64/44/34/26  -> the --s* scale
     handoff Public Sans + IBM Plex Mono -> not loaded; --sans is already Archivo
       and the only things needing the mono were the durations and the
       placeholder labels, neither of which ships.

   The one literal kept from the handoff is its motion, because this codebase had
   no equivalent and the Read Next cards already adopted the same curve:
   translateY on a 220ms cubic-bezier(.2,.7,.3,1).
   ================================================================= */

/* ---- the dark pull-quote band ---- */
.pv .wrap{display:flex;flex-direction:column;gap:var(--s8);max-width:var(--max)}
.pv-lead{max-width:44ch}
.pv-lead p{
  font-weight:500;font-size:clamp(24px,3.4vw,44px);line-height:1.26;
  letter-spacing:-.02em;color:#fff;text-wrap:pretty;
}
.pv-attrib{display:flex;align-items:center;gap:var(--s4)}
.pv-attrib .face{width:56px;height:56px;flex:none}
/* The circle itself comes from context, not from .face--ph: the base class sets
   only the grid and the type, and each ground supplies its own fill. Same values
   the watch cards used on this ground, plus the handoff's inset hairline. */
.pv-attrib .face--ph{
  background:rgba(255,255,255,.14);color:#fff;border-radius:50%;
  box-shadow:inset 0 0 0 1px rgba(255,255,255,.3);
}
.pv-attrib .who{font-weight:600;font-size:17px;color:#fff}
.pv-attrib .role{font-size:15px;color:rgba(255,255,255,.78)}

/* ---- the light band ---- */
.pv-more .wrap{display:flex;flex-direction:column;gap:var(--s16)}

.pv-cards{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:var(--s6)}
.pv-card{
  display:flex;flex-direction:column;gap:var(--s4);
  padding:var(--s8);background:var(--white);border-radius:var(--r-md);
  box-shadow:var(--sh-1);
}
.pv-q{font-size:18px;line-height:1.55;color:var(--ink);text-wrap:pretty}
.pv-src{margin-top:auto;padding-top:var(--s1);font-size:14px}

.pv-iv-h{margin-bottom:calc(var(--s16) * -1 + var(--s6))}

.pv-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:var(--s6)}
.pv-iv-card{
  position:relative;display:flex;flex-direction:column;gap:var(--s3);
  transition:transform 220ms cubic-bezier(.2,.7,.3,1);
}
.pv-iv-card:hover{transform:translateY(-3px)}

/* The empty media slot. No stills exist, and rather than three grey boxes or a
   visible "video still" label this holds the Boson Field - the house convention
   for a media slot with nothing in it yet (see .slot-mark .bf). The real still
   drops in here and the field comes out. */
.pv-still{
  display:grid;place-items:center;aspect-ratio:4 / 3;margin-bottom:var(--s1);
  border-radius:var(--r-md);background:var(--white);overflow:hidden;
}
.pv-still .bf{height:42%;width:auto;opacity:.5}

.pv-iv-name{font-weight:600;font-size:20px;color:var(--ink)}
.pv-iv-role{font-size:14px;color:var(--ink-3)}
.pv-iv-note{font-size:15.5px;line-height:1.55;color:var(--ink-2);text-wrap:pretty}
.pv-iv-cta{margin-top:var(--s1)}

.pv-pill{
  display:inline-flex;align-items:center;gap:10px;
  padding:12px 20px;border-radius:999px;
  background:var(--midnight);color:#fff;text-decoration:none;
  font-weight:600;font-size:14px;
  transition:background var(--nx-tint,200ms ease);
}
.pv-pill:hover{background:var(--accent);color:#fff}
/* The arrow is a ::after, so the editable label is the words alone and the
   glyph cannot be deleted by accident. Same treatment as the Read Next pill. */
.pv-pill::before{content:"→";order:2}
/* The whole card is the click target while the anchor stays on the pill, so the
   card can remain core blocks and the accessible name stays short. */
.pv-pill::after{content:"";position:absolute;inset:0;border-radius:var(--r-md)}
.pv-iv-card:has(.pv-pill:focus-visible){
  box-shadow:0 0 0 3px var(--mist),0 0 0 6px var(--accent);border-radius:var(--r-md);
}

@media (max-width:1080px){
  .pv-cards,.pv-grid{grid-template-columns:repeat(2,minmax(0,1fr))}
}
@media (max-width:720px){
  .pv-cards,.pv-grid{grid-template-columns:minmax(0,1fr)}
  .pv-lead p{font-size:clamp(22px,6vw,28px)}
  .pv-attrib .face{width:48px;height:48px}
}
@media (prefers-reduced-motion:reduce){
  .pv-iv-card{transition:none}
  .pv-iv-card:hover{transform:none}
}

/* ============================================================ PROOF STRIP
   One sourced quote, placed where doubt is highest - the homepage and the
   contact page, which previously carried no proof at all. */
.proof{padding-top:var(--s16);padding-bottom:var(--s16)}
.proof-q{margin:var(--s6) 0 0;max-width:60ch}
.proof-q p{font-size:clamp(21px,2.4vw,28px);line-height:1.35;color:var(--white)}
.proof .attrib{margin-top:var(--s6)}
.proof-more{margin-top:var(--s8)}

@media (max-width:760px){
  .nx-grid{grid-template-columns:minmax(0,1fr)}
  .nx-card,.nx-card:first-child,.nx-card:last-child{padding-left:0;padding-right:0}
}
