/* pages.css — page-level compositions only (kit §05). Every value traces to tokens.css. */

/* ============================================================
   5.1 — Home hero. --grad-hero with a subtle ink-and-gold grid
   motif, masked to the upper-right. No stock photography.
   ============================================================ */
.hero {
  position: relative; overflow: hidden;
  background: var(--grad-hero); color: #fff;
  padding-block: var(--space-24);
}
@media (min-width: 64em) { .hero { padding-block: var(--space-32); } }

/* --- Hero runs to the top of the viewport, behind the floating nav ---
   Requested 2026-08-17: the header must sit ON the hero, not above a band
   of page background. The header is in normal flow, so the hero used to
   start below it, leaving a 72px --neutral-50 strip across the top.

   Pulled up with a negative margin rather than switching the header to
   `position: fixed`. Going fixed would have meant a global content offset
   on every other page, re-deriving the tabbar and :target numbers, and a
   homepage escape hatch — all to solve a problem only the homepage has.
   The negative margin touches exactly one element.

   88px is the >=900px header band (components.css 4.1). It is added back
   as top padding so the hero copy does not slide up under the nav — the
   hero simply grows 88px taller and that extra 88px is the strip the pill
   floats on. BOTH VALUES MUST MOVE TOGETHER, and both must track the band
   height if the pill is ever resized again.

   Stacking works without a z-index here: .hero is position:relative with
   z-index:auto, which does NOT open a stacking context, so the header's
   z-index:50 still paints over it.

   >=900px only. Below that the header is an opaque full-bleed bar
   (components.css 4.1), so the hero would be hidden behind it and the
   result would look identical to not doing this at all. --- */
@media (min-width: 900px) {
  .hero { margin-top: -88px; padding-top: calc(var(--space-24) + 88px); }
}
/* Must follow the 64em padding-block rule above, which would otherwise
   reset the padding-top this restores. */
@media (min-width: 64em) {
  .hero { padding-top: calc(var(--space-32) + 88px); }
}
/* THE GRATICULE AND THE `.container` PROMOTION THAT WENT WITH IT NOW LIVE
   IN components.css §4.16, as `.graticule`, and index.njk carries that class
   on this element. Moved 2026-08-21 when the Our Story band became the
   motif's fourth caller. Nothing about the rendering changed — §4.16 is this
   rule, with the -webkit- mask prefix the brand-header copy always had and
   this one lacked, and with the mask position behind a --graticule-at
   default of the 78% 18% written here. Do not re-add a local ::before. */
.hero h1 { font: var(--type-display); letter-spacing: -0.022em; color: #fff; max-width: 18ch; }
.hero p { color: var(--neutral-200); }
.hero .eyebrow { color: var(--gold-300); }

/* --- Hero two-column split: copy left, decorative canvas right ---
   Single column below 64em, where the viz is not rendered at all. The
   columns are 1.05fr/1fr rather than even halves so the copy keeps close
   to the measure it had as a full-width block and the h1 does not pick up
   an extra wrap. `align-items: center` optically centers the canvas against
   the text rather than pinning it to the top of a taller column. */
@media (min-width: 64em) {
  .hero__inner {
    display: grid;
    grid-template-columns: 1.05fr 1fr;
    gap: var(--space-12);
    align-items: center;
  }
}
.hero__copy > :first-child { margin-top: 0; }

/* The canvas box. `display: none` below 64em is load-bearing, not just
   visual: js/hero-viz.js reads this box and refuses to run its rAF loop
   while it measures zero, so phones never animate an invisible canvas.
   Height comes from CSS, never from content — a failed script leaves an
   empty column instead of collapsing the hero. */
.hero__viz { display: none; }
@media (min-width: 64em) {
  .hero__viz { display: block; position: relative; height: clamp(340px, 34vw, 520px); }
}
.hero__viz-canvas { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }
/* Generic CTA row — used by the hero, the 404 error card, and Who We Are. */
.button-row { display: flex; flex-wrap: wrap; gap: var(--space-4); margin-top: var(--space-8); }
/* Centred variant. Reusing .button-row rather than adding a one-off rule
   also buys the --space-8 top margin, which a button needs and a text link
   did not — a solid button sitting 16px under a paragraph reads as attached
   to it.

   `max-width: none` is REQUIRED, not tidying. These rows are <p> elements,
   and base.css caps every <p> at --measure (~68ch). Without this the flex
   box itself is only ~640px of a 1152px container, so `justify-content:
   center` centres the button inside that narrow box — landing it around
   x=395 instead of 640, i.e. visibly left of centre. Same trap as the
   Our Brands lead paragraph in §5.3, opposite fix: there the box had to be
   centred, here it has to be allowed to fill. */
.button-row--center { justify-content: center; max-width: none; }
/* Right-aligned variant — Our Story's "Explore Our Story" CTA
   (maintainer, 2026-08-17). `max-width: none` is required here for exactly
   the reason spelled out above: the row is a <p>, base.css caps it at
   --measure (~68ch), and without the override `justify-content: flex-end`
   would pin the button to the right edge of that ~640px box rather than the
   container's, landing it near the middle of the page. */
.button-row--end { justify-content: flex-end; max-width: none; }
/* Outline button on a dark (ink-900) surface — the hero and the 404 error
   card: gold-300 text/border, not the light-surface .button--secondary
   treatment. */
.hero .button--secondary,
.error-card .button--secondary { color: var(--gold-300); border-color: var(--ink-700); }
.hero .button--secondary:hover,
.error-card .button--secondary:hover { background: var(--ink-800); border-color: var(--gold-500); }

/* ============================================================
   5.1a — Trust logo marquee. TWO CALLERS since 2026-08-25: the
   homepage hero and /contact/'s arc hero. The heading read
   "homepage hero only" until then; nothing about these rules
   changed when the second caller arrived, because none of them was
   ever scoped to `.hero` — they key off `.trust-marquee` alone.

   WHAT A THIRD CALLER MUST SUPPLY IS A DARK SURFACE.
   `.trust-marquee__logo` carries `filter: brightness(0) invert(1)`,
   which paints every logo flat white; on a light band the row would
   disappear. Both current callers are ink-900. That filter is the
   one thing here that is not surface-agnostic — the mask, the track
   arithmetic and the optical heights all travel anywhere.

   A pure-CSS infinite band — no JS, no rAF, no library. Read the header
   comment in partials/trust-marquee.njk for the mechanism; the two rules
   below that markup cannot express are:

   `width: max-content` on the track is REQUIRED. Without it the flex box is
   capped at the wrapper's width, every item is squeezed, and translateX(-50%)
   measures half of the wrong thing.

   `padding-inline-end: var(--marquee-gap)` is the seam fix, and it is exact
   rather than approximate. `gap` puts space only BETWEEN items, so the 12
   items (6 logos x 2 passes) give 11 gaps — translating to -50% would land
   half a gap short of the duplicate's first logo and hitch once per cycle.
   The trailing padding supplies the missing 12th gap, making the track
   12 logos + 12 gaps; half of that is 6 logos + 6 gaps, which is precisely
   item 7's left edge. THE GAP AND THE PADDING MUST STAY EQUAL — that is why
   both read from one --marquee-gap custom property instead of two literals
   that could drift. Changing the logo count does not affect this; changing
   only one of the pair does.

   The band is NOT full-bleed (maintainer, 2026-08-19): index.njk wraps it in a
   `.container`, so it loops inside the same measure as the hero copy and canvas
   and its edges align with the h1. Do not reproduce that width here with a
   `max-width` calc — `.container`'s inline padding is 24px but 32px at >=64em,
   and only the primitive itself tracks that. The wrapper also carries the
   `.graticule > .container { position: relative }` that lifts hero content
   above the absolutely-positioned grid motif (components.css §4.16), so the
   band needs no positioning of its own.
   ============================================================ */
.trust-marquee {
  --marquee-gap: var(--space-16);
  margin-top: var(--space-16);
  overflow: hidden;
  /* Fade both ends to transparent. A hard clip exposes the trick; the dissolve
     is what sells "infinite". 10% rather than the 12.5% a full-bleed band wants:
     inside the container the fade has ~1136px to work in instead of the whole
     viewport, and at 12.5% it ate ~142px a side — enough to leave a logo half
     dissolved at the exact edge the band is supposed to line up with.
     -webkit- prefix retained for Safari, which still needs it. */
  -webkit-mask-image: linear-gradient(to right, transparent 0%, #000 10%, #000 90%, transparent 100%);
          mask-image: linear-gradient(to right, transparent 0%, #000 10%, #000 90%, transparent 100%);
}
.trust-marquee__track {
  display: flex;
  align-items: center;
  list-style: none;
  margin: 0;
  padding: var(--space-2) var(--marquee-gap) var(--space-2) 0;
  gap: var(--marquee-gap);
  width: max-content;
  /* linear is non-negotiable — any easing makes the loop visibly pulse at the
     seam. ~4s per logo in one pass; re-time this if the logo count changes,
     since a fixed duration over a longer track scrolls faster. */
  animation: trust-marquee-scroll 24s linear infinite;
  will-change: transform;
}
.trust-marquee__item { flex-shrink: 0; display: flex; align-items: center; }
/* brightness(0) crushes every logo — grey, colour, whatever — to black while
   preserving alpha; invert(1) then lifts it to flat white. This is what makes
   six unrelated vendor marks read as one designed system, and it is why every
   source file must have a transparent background: an opaque one renders as a
   solid white rectangle. The 0.65 keeps them supporting evidence rather than
   competition for the h1. Both depend on the hero being dark. */
.trust-marquee__logo {
  height: 40px;
  width: auto;
  filter: brightness(0) invert(1);
  opacity: 0.65;
}
/* Optical sizing. These are NOT arbitrary and must not be collapsed into one
   uniform height: a 2.2:1 mark and a 4.1:1 wordmark set to the same pixel
   height do not carry the same visual weight. Judge by area and stroke
   thickness, not by the number. */
/* BBB is a square 1:1 outline mark among 2:1-4:1 wordmarks. It needs a
   noticeably taller box than any of them to carry equal visual weight —
   matching its height to theirs makes it read as a small stray icon. */
.trust-marquee__logo--bbb        { height: 64px; }
.trust-marquee__logo--jbt        { height: 40px; }
.trust-marquee__logo--jvc        { height: 37px; }
.trust-marquee__logo--fedex      { height: 31px; }
.trust-marquee__logo--trustpilot { height: 31px; }
.trust-marquee__logo--paypal     { height: 29px; }

@keyframes trust-marquee-scroll {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); }
}
/* Let a visitor stop the band to actually read a logo. */
.trust-marquee:hover .trust-marquee__track { animation-play-state: paused; }
/* base.css §(reduced motion) already forces animation-duration to 0.01ms
   !important globally, which would leave this looping tens of thousands of
   times a second at its end state. Killing the animation NAME stops it
   outright, and animation-name is not what that rule marks !important. */
@media (prefers-reduced-motion: reduce) {
  .trust-marquee__track { animation: none; }
}
/* Below 640px the desktop gap and heights make the band read as heavy
   furniture rather than a supporting detail. Scale both together; the seam
   holds because the padding reads the same custom property. */
@media (max-width: 40em) {
  .trust-marquee { --marquee-gap: var(--space-12); margin-top: var(--space-12); }
  .trust-marquee__logo             { height: 30px; }
  .trust-marquee__logo--bbb        { height: 46px; }
  .trust-marquee__logo--jbt        { height: 29px; }
  .trust-marquee__logo--jvc        { height: 27px; }
  .trust-marquee__logo--fedex      { height: 23px; }
  .trust-marquee__logo--trustpilot { height: 23px; }
  .trust-marquee__logo--paypal     { height: 21px; }
}

/* ============================================================
   5.2 — Two-column body copy (homepage only). Used twice: Who We
   Are's §2 paragraphs, and §5.3's Our Brands columns. One rule
   rather than two identical ones, so the pair cannot drift apart.
   Paragraphs sit side by side from 64em and stack below it,
   matching the breakpoint the hero splits on.

   The spacing is arranged so the stacked case is byte-for-byte the
   layout it had before the wrapper existed: base.css gives every <p>
   a --space-4 bottom margin, which used to separate these two and
   then separate the second from the "Learn More" link. Here the
   paragraph margins are zeroed and that same --space-4 is re-supplied
   as the grid gap and as the wrapper's own bottom margin. At 64em the
   gap widens to --space-8 because it is then a COLUMN gutter, not a
   paragraph break.

   Paragraphs keep base.css's --measure cap deliberately. It does not
   bind at the current 1200px container (each column is ~552px), but
   it is the site's line-length discipline and should survive a wider
   container rather than be overridden away here.
   ============================================================ */
/* `.brands__cols` half of these three rules is DORMANT since 2026-08-18: the
   two brand sentences it laid out moved into the brand modals, and the
   wrapper went with them. The `.who-we-are__cols` half is still live and is
   why the selectors stay — do not delete these rules on the strength of the
   brands half being unused. */
.who-we-are__cols,
.brands__cols { display: grid; gap: var(--space-4); margin-bottom: var(--space-4); }
.who-we-are__cols p,
.brands__cols p { margin-block: 0; }
@media (min-width: 64em) {
  .who-we-are__cols,
  .brands__cols { grid-template-columns: 1fr 1fr; gap: var(--space-8); }
}

/* ============================================================
   5.3 — "Our Brands" shell + centred intro (homepage only).
   .brands__shell encloses the headline, the intro paragraph and the
   .brand-panels grid in one white card on the muted band, so the
   section reads as a single contained object (maintainer,
   2026-08-18). .brands__intro centres the headline and the copy;
   the panel grid is its sibling inside the shell and stays
   left-aligned.

   `margin-inline: auto` is required, not cosmetic. base.css caps every
   <p> at --measure (~68ch), which really does bind on a full-width
   paragraph inside the 1200px container, so without it the box stays
   hard left and only its text centres inside that box, leaving the
   block visibly off-centre under the headline. The selector stays
   plural because it also covered the dormant .brands__cols paragraphs
   (§5.2), where a ~552px track never reaches the cap and the auto
   margins resolve to zero harmlessly.
   ============================================================ */
/* --- The arc field behind the Our Brands band (2026-08-20) -------------
   Markup and the geometry rationale live in partials/brands-arcs.njk; this
   is only placement and colour.

   SCOPED TO .section--arcs, NEVER TO .section--muted. The muted band class
   is used in 17 places across 9 templates (About, Expertise, Responsibility,
   Contact, Our Story, Leadership, and every odd brand-page section) — a rule
   on .section--muted would put this vortex behind all of them. The modifier
   is an opt-in and that is the point of it: THREE bands carry it, each by
   name — #brands here, .contact-hero (2026-08-19) and the homepage Our Story
   band (2026-08-25). It was #brands alone until those two, so read the count
   as "every caller is deliberate", not as "this is general to muted bands".

   ONLY .brands-arcs's PLACEMENT AND COLOUR ARE SHARED. Both dark callers
   also punch the convergence out of the band's middle, which #brands does
   not need — see the mask rule at §5.13. */
.section--arcs { position: relative; overflow: hidden; }
/* `overflow: hidden` above is load-bearing, not tidiness: the arcs originate
   on an ellipse WIDER and TALLER than the band (0.62W x 0.78H, see the
   partial), so without clipping they extend past the section into the
   neighbouring bands and, at narrow widths, push the document's scrollWidth
   out and produce horizontal overflow. Verified with tools/viewport.mjs. */
.section--arcs > .container { position: relative; }
/* No z-index on either of these. .section--arcs is position:relative with
   z-index:auto, so it does NOT open a stacking context; the arc div and the
   container are then simply painted in document order, and the container —
   being later — wins. Same technique .hero uses for its gold graticule
   (§5.1). Adding a z-index here would work too, and would be the first step
   toward a stacking context nobody needs. */
.brands-arcs {
  position: absolute; inset: 0; pointer-events: none;
  /* Fades the arcs where they meet the band's TOP AND BOTTOM edges, so they
     dissolve instead of ending as 28 cut stroke-ends against the sections
     above and below.

     VERTICAL ONLY, AND A RADIAL MASK IS THE WRONG TOOL HERE — this was tried
     twice before landing on a linear one. A radial gradient fades from the
     centre outward, but the centre is precisely the region .brands__shell
     already covers: the only place these arcs are ever visible is the outer
     band the shell does not reach. So every radial mask trades away exactly
     the pixels the motif needs, and tuning its radius only chooses between
     "invisible" and "no fade at all". Do not reintroduce one.

     The left and right edges are deliberately NOT faded. The section is
     full-bleed, so those cuts land at the viewport edge, where a clipped
     background reads as normal — the same way the `.graticule` grid
     (components.css §4.16) simply runs off the sides. It is only the horizontal boundaries, which
     abut another band, where a hard cut looks like a mistake.

     14% of a ~614px band is ~86px of ramp at each end, comfortably more than
     the ~48px of open space above and below the shell, so the falloff is
     already well under way before an arc reaches the boundary. */
  -webkit-mask-image: linear-gradient(to bottom, transparent 0%, #000 14%, #000 86%, transparent 100%);
          mask-image: linear-gradient(to bottom, transparent 0%, #000 14%, #000 86%, transparent 100%);
}
.brands-arcs svg { display: block; width: 100%; height: 100%; }
/* gold-500 is legal here for the same reason it is legal on the hero
   graticule and inside the hero canvas: this is non-text decoration, and
   tokens.css's "large/non-text only" note on gold-500 is a contrast rule
   about glyphs. Nothing is set on top of these strokes — .brands__shell is
   opaque and covers the field's dense centre entirely.

   The two opacities are low because the band is neutral-100, a far lighter
   ground than the hero's ink; the same stroke that reads as faint structure
   on near-black reads as a hard scratch on off-white. These were set by eye
   against the rendered band, not derived. */
.brands-arcs__paths path {
  fill: none;
  stroke: var(--gold-500);
  stroke-width: 1;
  opacity: 0.4;
}
/* hero-viz.js's BRIGHT_EVERY dial: every fourth arc is a permanent bright
   one, which is what stops the field reading as a flat screen of identical
   strokes. Kept at the same 1-in-4 ratio here. */
.brands-arcs__paths .brands-arcs__arc--bright { opacity: 0.62; stroke-width: 1.25; }
.brands-arcs__dots circle { fill: var(--gold-500); opacity: 0.7; }

/* --- The travelling pulses (hero-viz.js's comet pass, in CSS) ----------
   One pulse path per arc, sharing the arc's own `d`. Motion is a dash swept
   along the path; the per-arc animation-duration and animation-delay are
   inline on each element, generated from hero-viz.js's speed and phase
   dials (see the partial).

   THE COLOUR LOGIC IS INVERTED FROM THE HERO, ON PURPOSE. In the canvas the
   comet head is `champagne` (--gold-100) and its body --gold-300, because
   those are the BRIGHTEST tones available and the hero is near-black: the
   pulse reads by being lighter than its ground. This band is --neutral-100
   (#F3F1ED), where gold-100 (#F6ECD6) is very nearly the background colour
   and would be invisible. So the relationship is preserved by flipping it —
   the pulse reads by being DARKER than its ground, at --gold-700, against
   the --gold-500 resting arcs. Same figure/ground, opposite direction.
   This mirrors the decision already recorded in hero-viz.js, where the
   prototype's dimmest tone had to become --neutral-700 rather than the
   semantically obvious --ink-700, for the same class of reason: a tone is
   only "faint" or "bright" relative to what it sits on.

   THE DASH GEOMETRY. `pathLength="1"` on each path (see the partial) makes
   these numbers the hero's own `t` values:
     stroke-dasharray: 0.12 1  — 0.12 IS hero-viz.js's TAIL, unchanged. The
       gap of 1 is a full path length, so only ONE comet is ever on an arc;
       drop it and the arc fills with a repeating dotted train.
     stroke-dashoffset: 0.12 -> -1 — the sweep. Direction matters and is easy
       to get backwards: INCREASING the offset walks the dash toward the path
       START, so travelling origin -> destination means counting DOWN. It
       begins at 0.12 (dash sitting just off the origin end, invisible) and
       ends at -1 (just past the destination, invisible, and under the shell
       in any case). Total travel 1.12, which is why the comet is off-screen
       for a twelfth of each loop rather than popping in at full length.
   `linear` because the hero advances its pulses on raw elapsed time; an ease
   would make them lurch at the ends of every loop. */
.brands-arcs__pulses path {
  fill: none;
  stroke: var(--gold-700);
  stroke-width: 1.4;
  stroke-linecap: round;
  stroke-dasharray: 0.12 1;
  opacity: 0.62;
  animation-name: b79-arc-pulse;
  animation-timing-function: linear;
  animation-iteration-count: infinite;
}
/* hero-viz.js gives a boosted arc a thicker, brighter comet (lineWidth 2 vs
   1.4, alpha 0.95 vs 0.7). Same ratio here, read as darker rather than
   brighter for the reason above. */
.brands-arcs__pulses .brands-arcs__pulse--bright { stroke-width: 2; opacity: 0.85; }
@keyframes b79-arc-pulse {
  from { stroke-dashoffset: 0.12; }
  to   { stroke-dashoffset: -1; }
}
/* REDUCED MOTION: the pulses are removed outright and the static arc field
   remains, which is the whole motif minus the movement.

   `display: none` rather than leaning on base.css's global flattener, and
   that is deliberate. The flattener sets `animation-duration: 0.01ms
   !important` on everything, which is the right hammer for a one-shot
   transition but pathological against `animation-iteration-count: infinite`
   — it does not stop the animation, it asks for a loop every hundredth of a
   millisecond, on 28 elements. Removing the elements is unambiguous and
   costs nothing, and it is why this rule must not be "simplified" away as
   redundant with base.css. */
@media (prefers-reduced-motion: reduce) {
  .brands-arcs__pulses { display: none; }
}
/* NOT RENDERED BELOW 64em, matching .hero__viz (§5.1) — and for the same
   kind of reason, though this one is layout rather than battery. Below
   1024px .brands__shell fills nearly the full band width, leaving no rail
   for the arcs to be seen in; all that would render is a few strokes in the
   thin strip above and below the card, which reads as debris rather than as
   a motif. `display: none` and not `visibility` or `opacity: 0` so the SVG
   is not rasterised at all on phones. */
@media (max-width: 63.99em) { .brands-arcs { display: none; } }

.brands__intro p { margin-inline: auto; max-width: 84ch; }
/* ...and the cap itself is widened past base.css's 68ch (maintainer,
   2026-08-18: "lessen the side margins"). 68ch left ~250px of dead rail on
   each side of a 3-line centred paragraph inside the 1200px shell, so the
   copy read narrower than the panel grid beneath it. 84ch runs the intro
   to roughly the shell's interior at 1200px while still being a cap, so
   every width below that resolves to 100% and nothing overflows. This is
   deliberately looser than the reading measure: it applies ONLY to this
   one short, centred lead paragraph, not to running body copy. */
/* The shell's own surface — the same white/radius-lg/e1/neutral-200
   treatment .brand-panel and §5.7's closing cards use, so the three
   card surfaces on the homepage agree. No hover state: this is a
   container, not a control.

   Padding is deliberately TWO steps, not one. The panels inside are
   cards with --space-8 of their own padding, so the shell's padding is
   paid twice on every side; at 390px a flat --space-8 shell leaves a
   panel only ~214px of interior. --space-4 below 40em keeps the phone
   layout as wide as it was before the shell existed, then it opens up
   to --space-8 and stays there — see the comment on the 64em rule below
   for why it does not open further at desktop. */
/* The shell's own surface — the same white/radius-lg/e1/neutral-200
   treatment .brand-panel and §5.7's closing cards use, so the three
   card surfaces on the homepage agree. No hover state: this is a
   container, not a control.

   IT WAS BRIEFLY --grad-hero, ON 2026-08-20, AND THE MAINTAINER REVERSED IT
   THE SAME DAY ("I decided that I don't like it"). Reverted in full: the
   gradient, the copied .hero::before graticule, the `position: relative` the
   overlay needed on the shell and its children, and the #fff / neutral-200
   copy inversion that went with a dark ground. Do not reinstate any of it
   piecemeal on the theory that a part was worth keeping — the parts only
   made sense together, and the decision was against the result.

   The section's ARC FIELD is a separate, earlier change and is untouched by
   that reversal — it lives on the .section--arcs band around this shell, not
   on the shell, and it is still there.

   Padding is deliberately TWO steps, not one. The panels inside are
   cards with --space-8 of their own padding, so the shell's padding is
   paid twice on every side; at 390px a flat --space-8 shell leaves a
   panel only ~214px of interior. --space-4 below 40em keeps the phone
   layout as wide as it was before the shell existed, then it opens up
   to --space-8 and stays there — see the comment on the 64em rule below
   for why it does not open further at desktop. */
.brands__shell {
  background: #fff; border-radius: var(--radius-lg); box-shadow: var(--e1);
  border: 1px solid var(--neutral-200); padding: var(--space-4);
}
@media (min-width: 40em) { .brands__shell { padding: var(--space-8); } }
/* --space-8, not --space-12, since 2026-08-19 (maintainer, to cut vertical
   scroll). The shell holds two short cards and spent 499px at 1440px doing
   it — the least dense block on the page after the closing section. NOTE
   this breaks the "same step the section's own block padding takes in
   base.css" symmetry the comment above once claimed; that symmetry was the
   original reason for --space-12 and it is deliberately given up here. The
   40em step below is UNCHANGED and still --space-8, so this buys nothing on
   a phone — where the shell is --space-4 for the interior-width reason
   above — and nothing on a tablet. It is a desktop-only gain. */
@media (min-width: 64em) { .brands__shell { padding: var(--space-8); } }
/* Space between the intro block and the panel grid. base.css leaves the
   intro's last <p> only --space-4, which is a paragraph break, not the gap
   between two blocks inside a card. Set here rather than on the grid so
   .brand-panels stays reusable. */
.brands__intro { text-align: center; margin-bottom: var(--space-6); }
.brands__intro > :last-child { margin-bottom: 0; }
/* Card-inside-a-card: the shell took the white surface, so the panels
   cannot also be white — at rest they are the muted band's own
   neutral-100 with the hairline carrying the edge, and they resolve to
   white on hover. That fill change is the lift here; the e2 shadow, the
   gold hairline and the 1px translate are all still the component's
   (components.css §4.3) and are untouched. Scoped to .brands__shell so
   .brand-panel keeps its own white treatment anywhere it is reused.

   Contrast re-checked on the new fill: gold-700 (the role eyebrow and the
   "Explore" label) measures 4.89:1 on neutral-100, down from 5.52:1 on
   white. Still AA for normal text, but there is no headroom left — do not
   move any gold-700 text here to a lighter gold, and do not deepen this
   fill past neutral-100.

   `var(--brand-wash-image)` IS NOT OPTIONAL IN THESE TWO DECLARATIONS.
   components.css §4.3 puts the card's gold corner bloom in the `background`
   SHORTHAND, and a shorthand resets background-image to `none` — so writing
   `background: var(--neutral-100)` here, as this rule did before
   2026-08-20, silently strips the wash from the only place the component
   actually renders. The variable is declared on .brand-panel itself, so
   naming it costs nothing and there is no second copy of the gradient to
   keep in sync. The same applies to any future fill override on this
   element: carry the variable with it, or use `background-color` and leave
   the shorthand alone.

   The wash does NOT need a strength override for the neutral-100 fill. It
   is a gold-over-fill color-mix against transparent, so it composites onto
   whatever is beneath it; the 9%/16% pair reads near-identically on
   neutral-100 and on the white hover state, which was checked rather than
   assumed. */
.brands__shell .brand-panel { background: var(--neutral-100) var(--brand-wash-image) no-repeat; box-shadow: none; }
/* `box-shadow` has to be re-stated here, not just in the rest rule above:
   pages.css loads after components.css and `.brands__shell .brand-panel`
   ties `.brand-panel:hover` on specificity, so the flat rest shadow would
   otherwise win over the component's hover --e2 and the lift would lose
   its shadow. */
.brands__shell .brand-panel:hover,
.brands__shell .brand-panel:focus-visible { background: #fff var(--brand-wash-image) no-repeat; box-shadow: var(--e2); }

/* ============================================================
   Shared inner-page header (layouts/page.njk's .page__header) —
   a hairline separates the eyebrow/h1 block from body content on
   every page.njk-based page. No fill change from the page body
   (kit's own /about/ header sits on the same #FAF9F7 as the page).
   ============================================================ */
.page__header { padding-block: var(--space-16) var(--space-12); border-bottom: 1px solid var(--neutral-200); }

/* --split: the opt-in variant for a page that passes `headerNav`, i.e.
   /responsibility/ (2026-08-18) and /expertise/ (2026-08-20). Title
   left, chip row right (maintainer). Everything is inside the ≥64em query on
   purpose — below it the modifier does nothing at all and the three
   children stack in source order, eyebrow → h1 → chips, which is exactly
   the layout the page had when the bar was a separate full-bleed row. So
   phones see no change from this at all.

   `align-items: end` rather than `center`: the chips read as sitting ON
   the title's baseline rather than floating beside its optical middle,
   which is what makes them look attached to the h1 instead of parked in
   the whitespace. `justify-self: end` pins them to the container's right
   edge — the gap between title and chips is the point, since filling that
   dead space is what the move was for.

   minmax(0, 1fr) on the second column, not 1fr: the chip row is a flex
   line with `overflow-x: auto`, and a bare 1fr takes its min-content
   width as a floor, so the row would push the grid wider than the
   container and reintroduce horizontal overflow instead of scrolling.
   The auto/minmax pair is what lets the h1 keep its natural width while
   the chips absorb the remainder and scroll if they must. */
@media (min-width: 64em) {
  .page__header--split {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    column-gap: var(--space-8);
    align-items: end;
  }
  .page__header--split .eyebrow { grid-column: 1; grid-row: 1; }
  .page__header--split h1 { grid-column: 1; grid-row: 2; margin-bottom: 0; }
  /* The bar STRETCHES to its track (grid's default) and the chips are
     right-aligned INSIDE it. The obvious-looking `justify-self: end` on
     the bar itself is wrong and was measured wrong: justify-self opts the
     item out of `stretch`, so it sizes to max-content instead of the
     track, overflows leftward, and its `overflow-x: auto` never engages
     because the nav box is content-sized too. That produced a 0px gap to
     the title at 1280-1920 and a 176px OVERLAP of the title at 1024.
     Stretching is what hands the width constraint back to the grid. */
  .page__header--split .tabbar { grid-column: 2; grid-row: 2; min-width: 0; }
  /* Right-aligned, and ALLOWED TO WRAP. The wrap is not decoration — it is
     the only thing that makes flex-end safe. Measured: the four chips at
     body size are 857px wide and the second column is 826px at every
     viewport (the container caps at 1200px, so the track stops growing),
     i.e. they overflow by 31px at 1024 AND at 1920. Flex content that
     overflows a flex-end container spills past its START edge, where it
     cannot be scrolled to — which is exactly how the chips ended up
     printed across the title, 176px into it at 1024px. Wrapping removes
     the overflow condition instead of trying to scroll out of it. */
  .page__header--split .tabbar ul { justify-content: flex-end; flex-wrap: wrap; row-gap: var(--space-2); }
  /* …and with the chips at --type-small they fit on ONE line at every
     width, so the wrap above stays a safety net rather than the normal
     case. Dropping from the inherited 17px body size is also right on its
     own terms: beside a 48px title these are secondary navigation, and
     14.5px is the floor tokens.css allows. min-height stays 44px, so the
     tap target does not shrink with the type. */
  .page__header--split .tabbar a { font: var(--type-small); padding: 0 var(--space-3); }
}
/* Below the split, the bar is a plain block under the title and needs the
   space the old full-bleed row got from its own padding. */
@media (max-width: 63.99em) { .tabbar { margin-top: var(--space-6); } }

/* ============================================================
   Name/address/phone reset — used on the homepage closing section
   and /contact/. Browsers italicize <address> by default; the kit
   sets it in plain roman type alongside the rest of the page copy.
   ============================================================ */
.nap { font-style: normal; }
.nap p { margin: 0 0 var(--space-4); }

/* ============================================================
   5.7 — Homepage closing band. RESPONSIBILITY LEFT, LOOKING AHEAD
   RIGHT from 64em (maintainer, 2026-08-20). Until that date these
   were two stacked sections; index.njk's header comment for the
   band records the merge and why the surface is the muted one.

   Below 64em the two columns stack in source order — Responsibility,
   then Looking Ahead — which is the order the content doc reads and
   the order the page has always had. Responsibility sits left simply
   by being first in the markup; there is no `grid-column` swap.

   THE LEFT COLUMN IS NOT A CARD. That is a constraint, not a
   preference: `.reveal-list__item[open]` (components.css §4.14)
   marks the open row with a white background, which a white card
   behind it would cancel out. The Looking Ahead card keeps the
   shared surface rule below (white, radius-lg, e1, neutral-200
   hairline) because it has no such state to show.

   `align-items: start`, not `center` or `stretch`. The two columns
   are different kinds of thing — a heading plus a four-row
   disclosure list on the left, a short card plus a 320px ring on
   the right — and they are close but not equal in height (about
   620px against 600px at 1280px). Starting them together is what
   makes the band read as two columns rather than as a card floating
   beside a list. Stretching instead would run the white card to the
   list's full height and leave a slab of empty white under three
   lines of copy; centring would drop the whole right column ~20px
   and lose the shared top the two hang from.

   THE TWO EYEBROWS SIT ON ONE LINE, and the `padding-top` below is
   what puts them there (maintainer, 2026-08-21: "Looking Ahead seems
   to be slightly lower"). This REVERSES the 2026-08-20 note that read
   "the card's own padding then sets its eyebrow a little lower, and
   that is the card reading as a card" — do not restore the offset on
   that comment's authority. `align-items: start` levels the two
   COLUMN BOXES, but only the right one has a visible box: the card's
   24px padding and 1px border held its first line of text 25px below
   "RESPONSIBILITY", which is the gap that was reported.

   It is fixed by pushing the LEFT column's content down to the card's
   text, not by pulling the card up to the left column's. Pulling the
   card up would work geometrically and look wrong — the section pads
   48px, so a 25px lift leaves the white card floating 23px off the
   band's top edge. Padding the bare column costs nothing visually
   instead, because there is no left-hand box for the space to show
   up in; it simply merges into the section's own top padding.

   NOT the horizontal axis, and that is deliberate. The same 25px
   insets the card's text from its track (the text starts at 681.5px
   against the column's 656.5px at 1280). Cancelling it would mean
   either indenting the Responsibility column 25px — breaking the
   left rail every other section on the page shares at 64.5px — or
   bleeding the card 25px into the 48px gutter. The columns are
   already horizontally aligned where it counts: equal 544px tracks,
   both flush to the container rails.

   History, because this composition has moved several times: two
   matched white cards side by side (copy + contact, 2026-08-18),
   then one full-width copy card with the mark beneath it when the
   contact card was commented out, then the copy card left with the
   mark alone in the right column. Each is gone rather than left
   commented, because each was a different composition, not a
   variant to toggle.
   ============================================================ */
.closing__inner { display: grid; gap: var(--space-8); }
@media (min-width: 64em) {
  .closing__inner {
    grid-template-columns: 1fr 1fr;
    gap: var(--space-12);
    align-items: start;
  }
  /* Match the card's own inset so "RESPONSIBILITY" and "LOOKING AHEAD"
     start on the same line — see the eyebrow note above. It tracks the
     card rule below (--space-6 padding + a 1px border); the two must
     move together, so change this if that padding ever changes.

     Scoped to the two-column state on purpose: below 64em the columns
     stack and there is nothing across from the eyebrow to line up
     with, so the extra 25px would just be a dent in the band's top. */
  .closing__resp { padding-top: calc(var(--space-6) + 1px); }
}

/* Left column. No surface of its own — see the note above. The resets
   are the same pair .closing__copy carries: the section's own padding
   supplies the space the eyebrow's and the button row's margins would
   otherwise double. */
.closing__resp > :first-child { margin-top: 0; }
.closing__resp > :last-child { margin-bottom: 0; }

/* Right column. A wrapper, new with the 2026-08-20 merge: the copy card
   and the mark were two grid children of .closing__inner, one per column,
   and are now stacked in one. --space-8 between them, matching the gap
   .closing__inner uses when it stacks. `align-content: start` so the pair
   hangs from the top of the column with the left column's heading rather
   than centring against the taller list. */
.closing__ahead { display: grid; gap: var(--space-8); align-content: start; }

/* The mark sits under the Looking Ahead card, centred on it. Full 320px,
   unlike the Responsibility asides' 240px: the right column is ~542px wide
   and holds only the card and the ring, so the drawing runs at its authored
   size and --resp-ring-stroke stays at 1. THE TWO ALWAYS MOVE TOGETHER —
   the viewBox is 320 units, so any smaller --resp-ring-size renders every
   stroke at that fraction and the mark goes anaemic unless the stroke
   multiplier compensates (240px needs 320/240 = 1.3333). See §5.9.

   No optical offset. An earlier revision pulled the ring 7.5px left to drag
   its drawn edge onto the left edge of the card above it; the ring is
   centred under a full-column card now, so there is no edge to align to and
   the offset would only push it off-centre. */
.closing__mark { justify-self: center; width: 100%; }
.closing__mark .resp-ring {
  --resp-ring-size: 320px; --resp-ring-stroke: 1;
  margin-top: 0; margin-inline: auto;
}
/* VISIBLE ON MOBILE — the one exception to `.resp-ring`'s blanket
   below-64em hide (§5.9). Maintainer, 2026-08-24: "ensure that gold ingot
   svg on the looking ahead section is visible on mobile homepage."

   The hide exists for the `.band__aside` instances, where the ring stacks
   between an <h2> and that heading's own standfirst and wedges the two
   apart — /responsibility/, /expertise/, and /about/our-story/'s own
   Looking Ahead. `.closing__mark` is not that layout: it is centred UNDER
   the Looking Ahead card at the foot of the page, with nothing to come
   between, so the reason to hide it never applied here. The homepage
   instance was collateral from an unscoped rule.

   240px, not the 320px above: at 390px the column is 342px, so a 320px
   ring is very nearly full-bleed. 240px + 1.3333 is the size/stroke pair
   the aside marks already use — see §5.9's note that the two ALWAYS move
   together, since the viewBox is 320 units and any smaller box scales
   every stroke down with it (320/240 = 1.3333).

   SPECIFICITY vs SOURCE ORDER, both in play and worth knowing which is
   which. `display: block` beats §5.9's `.resp-ring { display: none }` on
   specificity — (0,2,0) against (0,1,0) — so that half is order-proof. The
   two custom properties only TIE with the `.closing__mark .resp-ring` rule
   directly above, and win on source order alone. That is why this block
   sits here, immediately after the rule it overrides, rather than beside
   the hide it exempts itself from: move it above that rule and the ring
   silently renders at 320px again. */
@media (max-width: 63.99em) {
  .closing__mark .resp-ring {
    display: block;
    --resp-ring-size: 240px; --resp-ring-stroke: 1.3333;
  }
}
.closing__copy,
.closing__card {
  background: #fff; border-radius: var(--radius-lg); box-shadow: var(--e1);
  border: 1px solid var(--neutral-200); padding: var(--space-6);
}
.closing__copy { display: grid; align-content: center; }
.closing__copy > :first-child { margin-top: 0; }
.closing__copy > :last-child { margin-bottom: 0; }
.closing__card h3 { margin-top: 0; }
/* The address's last row keeps .nap's --space-4 as the gap to the
   button; the button row itself sits flush with the card floor. */
.closing__cta { margin: 0; }

/* ============================================================
   5.8 — Stat-band drill-down. DORMANT since 2026-08-17: index.njk
   no longer passes `statViz`, so no .stat--viz button and no
   .statviz panel is ever emitted and every rule below matches
   nothing. Retained on purpose — the maintainer asked for the
   Flourish graphs to come out *temporarily*, and this is the tuned
   styling they need back (including the non-obvious grid fix on
   .stat--viz). Delete it only if the drill-down is abandoned for
   good; re-enable it from index.njk, not from here.

   (homepage only, so it lives here
   rather than beside the band in components.css §4.5 — the band
   itself is on two pages, this disclosure on one).

   .stat--viz is a <button> wearing .stat, so it needs the button
   UA styles stripped: buttons do not inherit font, and they bring
   their own background, border and padding that would override
   the band's. `width: 100%` keeps it filling its grid cell the
   way the <div> version does; without it the button shrink-wraps
   its text and the figures stop lining up with the non-
   interactive ones.

   The band already hints interactivity via .stat:hover's gold
   rules (components.css §4.5); the chevron is the second half of
   that affordance, since a hover border alone does not tell a
   visitor there is anything to open. It rotates on expand, the
   same language the nav chevron uses.
   ============================================================ */
.stat--viz {
  font: inherit; color: inherit; background: none; cursor: pointer;
  width: 100%; padding-inline: 0;
  border-inline: 0;
  /* `display: grid` + `align-content: start` is REQUIRED, not tidying, and
     `display: block` is NOT enough — that was tried first and failed.
     A <button> lays its children out in an anonymous box that centres them
     vertically, and the band stretches every cell to the tallest, so each
     button's content floated to its own vertical centre. Because the four
     stats carry different amounts of content (only two have a `note`), the
     ones with less were pushed DOWN — dropping "$175M+" and "18,000+" 12px
     below "225,000+" and breaking the single line the numerals sit on.
     Blockifying does not clear that centring in Chrome; giving the button a
     real grid formatting context and packing the tracks to the start does.
     .stat supplies padding-block, border-block, the transition and
     text-align; only the button-specific resets belong here. */
  display: grid;
  align-content: start;
}
.stat--viz .stat__label { display: block; font: var(--type-h4); margin: var(--space-3) 0 0; }
.stat--viz .stat__note { display: block; font: var(--type-small); color: var(--neutral-600); margin: 2px 0 0; }
.stat__chevron { display: block; margin: var(--space-2) auto 0; color: var(--gold-500); transition: transform .15s ease; }
.stat--viz[aria-expanded="true"] { border-block-color: var(--gold-500); }
.stat--viz[aria-expanded="true"] .stat__chevron { transform: rotate(180deg); }

/* The panel. Sits between the band and the "Who we are" eyebrow.
   `hidden` does the hiding; this only styles the open state — no
   `display` here, or it would fight the attribute. */
.statviz {
  border: 1px solid var(--neutral-200); border-radius: var(--radius-lg);
  background: #fff; box-shadow: var(--e1);
  padding: var(--space-6); margin-bottom: var(--space-8);
}
.statviz__head { display: flex; align-items: center; justify-content: space-between; gap: var(--space-4); }
.statviz__title { font: var(--type-h4); margin: 0; }
.statviz__close {
  display: inline-flex; align-items: center; justify-content: center;
  width: 44px; height: 44px; flex: none;
  background: none; border: 1px solid var(--neutral-200); border-radius: var(--radius-md);
  color: var(--neutral-600); cursor: pointer;
  transition: border-color .15s ease, color .15s ease;
}
.statviz__close:hover { border-color: var(--neutral-300); color: var(--ink-900); }
/* The close glyph is the shared "arrow" icon turned to point up-left, so
   the panel does not need a ninth icon in the shortcode's set. */
.statviz__close .icon { transform: rotate(-135deg); }
.statviz__body { margin-top: var(--space-4); }
/* Height is fixed in CSS, never derived from the embed: a cross-origin
   iframe cannot report its content height, so leaving it to shrink-wrap
   collapses it to zero. Flourish's own snippet does the same thing. */
.statviz__frame { width: 100%; height: clamp(320px, 42vw, 520px); border: 0; display: block; }

/* ============================================================
   5.5 — Expertise. Five `.band` sections (components.css §4.12) —
   sticky icon + heading aside, prose body at --measure — the same
   component /responsibility/ and /about/ use, alternating
   .section / .section--muted for rhythm. Replaces the old
   `.expertise-list` / `.expertise-item` hairline-row list, which
   ran one --measure column in a 1200px container and left the
   right ~45% of every screen empty.
   ============================================================ */

/* The inline icon treatment for this page's asides — the trailing icon, the
   `.heading-mark` nowrap pair, and the zeroed --aside-heading-offset — moved
   to components.css §4.12 on 2026-08-18 and is now the shared DEFAULT for
   every `.band`. /responsibility/ adopted the same treatment that day, which
   made all three rules identical on two pages and the old stacked defaults
   dead; per CLAUDE.md §6 they belong in components.css, and both scoped
   copies were deleted rather than left to drift apart. Nothing page-specific
   replaced them — this page needs no icon rule of its own. */

/* `.method-grid` / `.method` WERE HERE and were promoted to components.css
   §4.17 as `.spec-grid` / `.spec` on 2026-08-24, when /responsibility/'s
   four-areas index became the second caller and CLAUDE.md §6's two-page
   rule applied. The rename is what makes the shared name honest: a
   responsibility area is not a testing method. Nothing about this page's
   rendering changed in the move. */

/* Technology's customer-journey stepper — the page's one directional
   device, and the only place order is asserted on this page.

   HORIZONTAL AT EVERY WIDTH SINCE 2026-08-24 (maintainer request). It was
   a column below 48em and a row above it; the fork is gone and so is the
   `48em` entry that tokens.css listed under KNOWN OUTLIERS. Retired, not
   relocated — this file no longer has a 48em breakpoint.

   `.journey__step` CARRIES `min-width: 0` AND THAT IS REQUIRED, NOT
   DEFENSIVE BOILERPLATE. `1fr` is shorthand for `minmax(auto, 1fr)`, so
   without an explicit min-width each track's floor is its own item's
   min-content width, not zero. "Evaluation" alone contributes 68.2px of
   min-content — Chrome does not hyphenate during intrinsic sizing, and
   `overflow-wrap: break-word` is defined not to affect min-content sizing
   (only `anywhere` does) — so before `min-width: 0` was added that one
   step held its column open at 68.2px while its four neighbors sat at
   64.4-64.5px, and the connector under it ran 11% long (38.2px vs
   34.4-34.5px), visibly. `min-width: 0` drops the floor to zero so all
   five tracks share the `1fr` space evenly instead.

   IT FITS AT 390px, MEASURED, NOT ASSUMED (re-measured 2026-08-24 after
   `min-width: 0` landed). Against the 342px body column with five 1fr
   tracks, all uniform:
     gap            4px     8px     12px
     column       65.2px  62.0px  58.8px
     lines(*)        3       4       4
     overflow       0px     0px     0px
   confirmed uniform per-column (not just averaged) at three widths with
   the 4px gap: 390px 65.2px x5 (connectors 35.2px x4), 360px 59.2px x5
   (connectors 29.2px x4), 320px 51.2px x5 (connectors 21.2px x4).
   (*) at the full --type-small 14.5px with hyphens:auto. --space-1 (4px)
   is chosen for the 3-line result. WITHOUT hyphens:auto the same 4px gap
   still fits at 0 overflow — re-measured with hyphens forced off at
   390px: scrollWidth stays 390, no element's right edge passes 390px.

   THREE SEPARATE MECHANISMS, NOT ONE. Do not credit any one of them with
   what another does:
     1. The five 1fr tracks, WITH `min-width: 0` REMOVING their
        min-content floor (see above), fix the geometry: at 390px each is
        exactly (342 - 4x4) / 5 = 65.2px, independent of content. The row
        can never exceed its container — that is what makes document
        overflow structurally impossible here.
     2. `hyphens: auto` governs line count and therefore height only:
        90.5px at 3 lines vs 109.4px at 4. That is the whole of what it
        buys, and it is still wanted for the shorter result — it never
        guarded overflow.
     3. `overflow-wrap: break-word` is the last-resort per-item guard: if
        a single token still would not fit a 65.2px track, this is what
        stops it painting outside its own cell. It is a per-item
        concern, not what keeps the document from overflowing.
   DO NOT drop below 14px; tokens.css §type forbids it.

   THE 4px GUTTER IS ONLY VIABLE BECAUSE THE RAIL CARRIES THE SEQUENCE.
   Nothing is drawn between the labels: the connector sits at node height,
   spanning node-edge to node-edge across the gap, so the labels can run
   nearly edge to edge beneath it.

   NUMBERED, AND THIS IS THE ONE DEVICE ON THE PAGE THAT MAY BE. The four
   capability sections are parallel and carry no numbering (see the
   template's header comment, which records 01-04 being rejected); the
   customer journey genuinely is ordered. Numerals come from a CSS counter,
   so the markup stays five plain <li>s and re-ordering the sequence is a
   template edit, not a renumbering exercise.

   NO ARROWHEAD, 2026-08-24: `.journey__step` has two pseudo-elements and
   both are spent (node, connector). A terminating arrow would need a third
   element in the markup for decoration alone, and the numerals already
   encode direction. */
.journey {
  list-style: none; margin: 0 0 var(--space-6); padding: 0;
  display: grid; grid-template-columns: repeat(5, 1fr); gap: var(--space-1);
  counter-reset: journey-step;
}
.journey__step {
  position: relative;
  display: flex; flex-direction: column; align-items: center;
  text-align: center;
  font: var(--type-small); line-height: 1.3;
  color: var(--ink-900);
  hyphens: auto; -webkit-hyphens: auto;
  overflow-wrap: break-word;
  min-width: 0; /* required — see block comment above: 1fr's auto floor
                   otherwise lets "Evaluation" widen its own track */
}
/* The node. 26px is the smallest circle that holds a 12px numeral with
   even optical padding at this stroke weight. gold-700, not gold-500:
   this is TEXT on a light surface, and gold-500 is licensed for rules,
   numerals-as-display and icons only (tokens.css). */
.journey__step::before {
  counter-increment: journey-step;
  content: counter(journey-step);
  width: 26px; height: 26px;
  display: grid; place-items: center;
  border: 1px solid var(--gold-500);
  border-radius: 50%;
  font: 600 12px/1 var(--font-body);
  color: var(--gold-700);
  margin-bottom: var(--space-2);
}
/* The connector. A per-step segment spanning the gap between THIS node's
   right edge and the next node's left edge — deliberately NOT one rail
   drawn behind all five nodes, which would need the node to repaint the
   section's background and so would break the moment this section flipped
   between .section and .section--muted.
     left  = 50% + 13px (node radius) + 4px clearance
     width = one column + one gap - 34px (both radii + both clearances)
   At 390px that is 35.2px; at 1280px, 97px. */
.journey__step::after {
  content: "";
  position: absolute;
  top: 13px; height: 1px;
  left: calc(50% + 17px);
  width: calc(100% + var(--space-1) - 34px);
  background: var(--neutral-300);
}
.journey__step:last-child::after { content: none; }

/* Recycle Platinum's §1 opening sentence is the one strong-led paragraph
   across both brands (entity.json carries the <strong> inline). `.standfirst`
   sets weight 300 display serif, so an un-toned <strong> jumps to 700 mid-
   sentence and reads as a rendering fault. */
.standfirst strong { font-weight: 500; color: var(--ink-900); }

/* Logistics' callout. Stays in the body face on purpose — it must not
   compete with the standfirst above it, so the emphasis comes from the
   colour step and the margins, not from size.

   IT CARRIED A 3px gold-500 LEFT RULE AND A --space-6 INDENT UNTIL
   2026-08-25, when the maintainer asked for the line gone. The indent went
   with it: the padding existed only to clear the rule, and an indented
   paragraph with nothing in the gutter reads as a broken blockquote. What
   remains is the --ink-900 step out of the --neutral-700 body run
   (components.css §4.12) plus the doubled margins. Do not restore the rule
   — and note that `.band__summary`'s comment in components.css no longer
   has a left rule here to contrast itself against. */
.expertise-callout {
  color: var(--ink-900);
  margin-block: var(--space-6);
}

/* ============================================================
   5.4 — Brand detail header band. The page's single ink-900
   surface, sitting directly under the sticky header, so it takes
   its top padding from the same scale the hero does rather than
   .section's symmetric block padding. Breadcrumb links inherit
   gold-300 from .section--dark's `a` rule; the two non-link parts
   (the "/" separators and the aria-current page name) are re-set
   here because their light-surface neutrals disappear on ink.
   The kit's "How it works" step list that used to live in this
   slot (.steps / .steps__index / .steps__title / .steps__body)
   was removed with its markup — see brands.njk's comment and the
   README's "Retired with their markup".
   ============================================================ */
/* THE HERO'S OWN SURFACE, ON BOTH BRAND PAGES (maintainer, 2026-08-20).
   This band was flat --ink-900, inherited from the `section--dark` class it
   still carries in brands.njk; it now takes --grad-hero and the hero's
   graticule instead. The class stays on the element and is still doing work
   — every text colour in here comes from it (neutral-200 body, #fff
   headings, gold-300 eyebrow and breadcrumb links) and none of that changed,
   because the gradient's darkest stop IS ink-900. Only the fill is
   overridden. `.brand-header` and `.section--dark` are both one class, so
   this wins on source order alone: pages.css loads after base.css.

   --grad-hero IS DOCUMENTED "hero only" IN tokens.css AND IS DELIBERATELY
   USED ON A SECOND SURFACE HERE. That note predates this request. The
   departure is recorded the way CLAUDE.md §5 records the marine one — the
   kit is the spec, and where this site knowingly leaves it, the leaving is
   written down rather than found later. NOTE this is now the second and
   third element painting that gradient (hero, plus one brand header per
   brand), so it is no longer meaningfully "hero only"; if a fourth surface
   wants it, retire the note in tokens.css rather than keep appending
   exceptions to it.

   THE FIT WAS ALREADY MOSTLY BUILT. This band is pulled up under the
   floating nav by exactly the hero's technique (see the rule below), and
   partials/brand-motif.njk already describes its rings as echoing the
   homepage hero "so the two dark surfaces on the site read as one family".
   The gradient and the graticule finish that, rather than starting it.

   NO `overflow: hidden`, unlike .hero. The hero needs it for its canvas;
   here the only overlay is the ::before below, which is inset:0 on a
   square-cornered full-bleed band and so has nothing to escape. */
.brand-header {
  position: relative;
  background: var(--grad-hero);
  padding-block: var(--space-8) var(--space-12);
}
/* THE GRATICULE MOVED TO components.css §4.16 on 2026-08-21 — that comment
   asked for exactly this refactor once a fourth dark band wanted the motif,
   and the homepage Our Story band is it. brands.njk carries `.graticule` on
   this element now, which also supplies the `.container` promotion that used
   to be written out here.

   What is worth keeping from the old comment: the mask sits behind
   .brand-header__motif's rings at >=64em, which is the same overlap the hero
   runs — its arc canvas is over the grid in the same corner — so the two
   pages read the same way rather than one having a cleaner ornament than the
   other. That is why this band leaves --graticule-at at its default. */

/* --- The dark band runs to the top of the viewport, behind the floating
   nav. Identical treatment to the homepage hero (§5.1) and for the same
   reason: the header is in normal flow, so this band used to start below
   it and left an 88px strip of --neutral-50 across the top, with the white
   pill sitting on cream rather than on the band.

   88px is the >=900px header band (components.css 4.1), pulled off with a
   negative margin and added straight back as padding so the breadcrumb and
   h1 do not slide up under the pill. Both numbers move together, and both
   track the band height — see the hero rule, which carries the same pair.

   Net page height is unchanged: the block starts 88px higher and is 88px
   taller, so everything below it, including the footer, stays put.

   No z-index needed. .brand-header is a static element, and the header is
   position:sticky with z-index:50, so the pill paints over it either way.

   >=900px only, matching the hero: below that the header is an opaque
   full-bleed bar, so the band would just be hidden behind it. --- */
@media (min-width: 900px) {
  .brand-header { margin-top: -88px; padding-top: calc(var(--space-8) + 88px); }
}
/* 72px since 2026-08-19, up from 48px — this now matches .brand-panel__logo
   and .brand-dialog__logo (components.css §4.3/§4.3b) rather than sitting
   between them and the old 48px. Same fixed-height/auto-width reasoning as
   those two: the brand logos do not share an aspect ratio, so sizing by
   width would render them at different heights.

   THE HEIGHT IS UNCHANGED BY THE 2026-08-20 MOVE INTO THE MOTIF, on purpose
   and twice over. First, components.css's note on .brand-panel__logo is
   explicit that these three slots move together — "do not enlarge either one
   alone". Second, 72px is also where the retina budget sits: both PNGs are
   460px wide intrinsically, so the rendered width must stay under 230px, and
   Recycle Platinum is the wider ratio at 3.046 — at 72px it renders 219px,
   which is inside that ceiling with very little to spare. Making the logo
   bigger now that it has a 340px-wide column to sit in would blow the budget
   and soften both marks on any 2x display.

   `margin-top` is GONE rather than overridden: it spaced the logo under the
   eyebrow when this sat in the copy flow, and an absolutely positioned
   element takes its offset from the centring transform instead.

   THE 72px IS NOW WRITTEN AS ITS RATIO TO THE MOTIF, `--motif-w * 72/340`,
   so the two scale as one object (2026-08-24, when the motif started
   rendering on phones). At >=64em --motif-w is 340px and this resolves to
   72.0px — desktop is unchanged to the pixel, and everything the paragraphs
   above say about 72px still describes what renders there.

   It also keeps the retina budget safe by construction rather than by
   luck: both PNGs are 460px intrinsic and Recycle Platinum is the wider
   ratio at 3.046, so 72px/219px was already the ceiling. Every value below
   340px renders SMALLER than that, so the budget can only improve as the
   motif shrinks — at 390px the motif is ~118px, the logo 25px tall and
   ~76px wide, an 6x oversample. What this ratio protects instead is the
   INNER RING: the r=116 circle is 72.5% of the drawing's width, and a
   fixed 72px logo inside a 118px motif would have been 219px wide against
   an 86px ring — three times over it. Do not replace this with a fixed
   height at any width. */
.brand-header__logo {
  position: absolute; top: 50%; left: 50%;
  transform: translate(-50%, -50%);
  height: calc(var(--motif-w) * 72 / 340); width: auto; display: block;
}
/* The dormant hatch path — both brands carry a logo today, so this matches
   nothing. Centred the same way so a third brand added as a data entry gets
   a hatch in the right place rather than one pinned to the column's top-left.
   `max-width` keeps a long brand name from bursting the 340px motif. Still
   dormant, and still only checked at 340px: below 64em the motif is ~117px,
   where 88% is ~103px and "[LOGO SLOT — <brand>]" would set several lines
   tall and spill out of the rings. That is a problem for whoever adds a
   third brand with no logo, not one this site has today. */
.brand-header__logo-tbd {
  position: absolute; top: 50%; left: 50%;
  transform: translate(-50%, -50%);
  margin: 0; max-width: 88%; text-align: center;
}
.brand-header h1 { margin-top: var(--space-4); }
.brand-header .breadcrumb { padding-top: 0; }
.brand-header .breadcrumb li:not(:last-child)::after { color: var(--neutral-500); }
.brand-header .breadcrumb [aria-current="page"] { color: var(--neutral-200); }

/* Two-column header lockup, AT EVERY WIDTH SINCE 2026-08-24. 7fr/5fr at
   >=64em rather than .band's 5fr/7fr: here the wide column is the copy and
   the narrow one is ornament, which is the reverse of a band's
   sticky-heading-plus-prose.

   Below 64em the motif used to be `display: none` outright, on the reasoning
   that "a decorative element stacked above an h1 would push the page's actual
   subject below the fold on a phone". That reasoning was about STACKING, and
   it still holds — the motif is not stacked here. The maintainer asked
   (2026-08-24) to keep the animated mark and its logo on phones, so the two
   columns simply persist, with the ornament column shrunk to --motif-w and
   the copy column taking what is left. Nothing moves below the fold: the
   h1 is still the first thing under the breadcrumb, at the same y.

   --motif-w IS THE ONE NUMBER THAT DRIVES THE WHOLE ORNAMENT. It sets this
   grid track, .brand-motif's cap, and — via the 72/340 ratio the desktop
   logo was always sized at — .brand-header__logo's height. Retune it here
   and the SVG and the logo scale together; size any of the three
   independently and the logo will burst the rings. See both rules below.

   The clamp bottoms out at 104px (390px viewport lands at ~118px) and tops
   out at 200px, which it reaches at ~645px — comfortably before the 64em
   switch to the fr-based track, so there is no jump at the breakpoint.
   THE CEILING IS DELIBERATELY BELOW 340px: every px given to the ornament
   comes straight out of the copy column, and the copy column is what has to
   hold the CTA at a legible size. */
.brand-header__inner {
  --motif-w: clamp(104px, 30vw, 200px);
  display: grid;
  grid-template-columns: minmax(0, 1fr) var(--motif-w);
  column-gap: var(--space-3);
  align-items: center;
}
@media (min-width: 64em) {
  .brand-header__inner {
    --motif-w: 340px;    /* == .brand-motif's max-width; see that rule */
    grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
    column-gap: var(--space-12);
  }
}
.brand-header__cta { margin-top: var(--space-6); margin-bottom: 0; }
/* The CTA shrinks below 64em so "Visit Recycle Platinum" — the longer of the
   two labels — still fits the narrowed copy column on one line. 14.5px is
   --type-small's size, the floor CLAUDE.md/tokens.css set for body text, and
   the weight and family stay .button's --type-h4, so this is a size step and
   not a different type style. min-height stays base.css's 44px: the tap
   target does NOT shrink with the label. Measured at 390px: label + icon +
   padding lands at ~204px against a ~208px column. There is very little
   slack — if a third brand carries a longer CTA label than Recycle
   Platinum's, lower --motif-w's clamp floor rather than the font size.

   `padding-block` is the safety net for the widths where it does NOT fit:
   at 375px and below the column is narrower than the label and it wraps to
   two lines, which base.css's `padding: 0 var(--space-6)` would have packed
   into the 44px min-height with ~2px to spare. 8px of block padding lets a
   wrapped label grow the button instead. It cannot change the one-line case
   — the tallest thing on that line is the 22px icon, and 22 + 16 is 38px,
   still under the 44px floor.

   THE ICON HAS TO COME DOWN WITH THE LABEL, and not only for looks. The
   `svg` shortcode emits "arrow-external" at the 30px card grid
   (components.css §4.10), and 30px is what SETS this button's height once
   `padding-block` is non-zero: 30 + 16 + 2 is 48px, so a button asked to
   get smaller would have got 4px TALLER. 22px is the same step
   `.reveal-list__icon .icon` already takes at its own narrow width, and it
   also takes 8px off the button's WIDTH, which is the dimension actually
   under pressure here. Net at 390px: 44px tall exactly as on desktop — the
   tap target does not shrink — and ~196px wide against a 213px column.

   39.99em, NOT the 63.99em where the two-column lockup changes shape. The
   CTA only needs to shrink where the copy column is too narrow for it, and
   that stops well before 64em: at 640px the column is 388px against a
   243px full-size button. Between 40em and 64em the header is still two
   columns but the button and the chips are simply the desktop ones. */
@media (max-width: 39.99em) {
  .brand-header__cta .button {
    font-size: 14.5px;
    padding-inline: var(--space-3);
    padding-block: var(--space-2);
  }
  .brand-header__cta .button .icon { width: 22px; height: 22px; }
}

/* `position: relative` is what the centred logo inside resolves against —
   see .brand-header__logo above. It is safe to place here because .brand-motif
   is `margin-inline: auto` inside this box, so the box's centre and the SVG's
   centre are the same point horizontally, and the SVG is the box's only
   in-flow content so they share a centre vertically too. If anything else is
   ever added to this column, the logo needs its own wrapper instead. */
.brand-header__motif { display: block; position: relative; }
/* This column was `display: none` below 64em from 2026-08-19, and from
   2026-08-20 that took the brand logo with it. Both render at every width
   as of 2026-08-24 (maintainer: keep the animated mark and the logo on
   mobile). The column is sized by --motif-w on .brand-header__inner above,
   not here. */
/* Colour lives here, not on the SVG: custom properties do not resolve in
   SVG presentation attributes. See the partial's own comment.

   `max-width: var(--motif-w)` rather than a hard 340px since 2026-08-24 —
   340px is still what --motif-w resolves to at >=64em, so desktop is
   unchanged to the pixel; below it the cap tracks the grid track so the SVG
   never has to be sized twice. */
.brand-motif { display: block; width: 100%; height: auto; max-width: var(--motif-w); margin-inline: auto; }
/* `.brand-motif__spokes path` shared this rule until 2026-08-21, when the
   spokes were removed from the partial outright; the selector went with them
   rather than sitting here matching nothing. */
.brand-motif__rings circle { fill: none; stroke: var(--gold-300); stroke-width: 1.25; }
.brand-motif__nodes circle { fill: var(--gold-500); }
/* The milled edge and the orbiting tracer, geometry lifted from .resp-ring__mill
   and .resp-ring__tracer below so the brand headers strike from the same punch
   as /expertise/, /responsibility/ and the homepage closing mark. Two things
   deliberately differ, both because this frame sits on ink-900 rather than on a
   light surface: the stroke is --gold-300/--gold-500 instead of --neutral-300,
   and there is no --resp-ring-stroke multiplier. The 14s duration and the dash
   arithmetic are NOT free parameters: they are what makes the two tracers read
   as one mechanism.

   THE "NEVER SCALED DOWN" JUSTIFICATION FOR SKIPPING THE MULTIPLIER EXPIRED
   ON 2026-08-24, when --motif-w started resolving to ~117px at 390px instead
   of a flat 340px — every stroke here now renders at about 0.37x on a phone.
   The multiplier is still deliberately absent, for a reason that is specific
   to this drawing rather than inherited:

     THE MILL CANNOT TAKE ONE. Its 7-unit stroke at r=143 fills the gap
     between the r=136 and r=150 rims EXACTLY. .resp-ring gets away with
     multiplying its identical mill because its worst case is 1.3333 (9.33
     units, still inside the 14-unit gap); here the phone case is 320/117 =
     2.73, which is 19 units and bursts both rims. Multiplying only the two
     hairlines and the tracer and not the mill would thicken the frame's
     edges while its centre stayed put — a different drawing, not the same
     one scaled.

   What carries the coin edge at 117px is the milled band itself, which
   renders ~2.6px wide and reads clearly; the hairlines at stroke-opacity
   .16 are nearly gone at 1x and are back to a full device pixel on any 2x
   phone. Measured at 320, 360, 375 and 390px before this was accepted. If a
   future change makes the mark go anaemic anyway, the fix is a discrete
   --motif-w per breakpoint plus an exact 320/size multiplier on the
   hairlines, the tracer AND a re-derived mill/rim geometry — not a
   multiplier bolted onto these numbers. */
.brand-motif__mill {
  fill: none; stroke: var(--gold-300);
  stroke-width: 7; stroke-dasharray: 1.5 8.483;
}
.brand-motif__tracer {
  fill: none; stroke: var(--gold-500);
  stroke-width: 2; stroke-linecap: round;
  stroke-dasharray: 120 822.478;
  animation: resp-ring-orbit 14s linear infinite;
}
@media (prefers-reduced-motion: reduce) { .brand-motif__tracer { animation: none; } }

/* Fact-chip row — term+value pairs, used ONLY by the header's Founded/
   Acquired chips. The `buys` row ended up NOT sharing this: it renders with
   the pre-existing `.chip-list`/`.chip` from components.css instead (see
   brands.njk), so this block is single-purpose, not shared. Named
   `.fact-chip`, not `.chip`: components.css §4.1 already has a `.chip` (the
   article/newsroom category badge, and the buys list inside
   brand-dialog.njk and now the buys row here too) — a single-label badge, a
   genuinely different component from a term+value pair. The two idioms look
   like duplication but are not: `.chip` carries `background:
   var(--neutral-100)`, which would glare on this header's ink-900 band, so
   the header genuinely needed its own class rather than reusing `.chip`.
   Do not unify them. Scoped to pages.css for now: one use inside ONE
   template is not the "used on 2+ pages" test components.css exists for.
   Promote it there if a genuinely different page ever wants fact-chips. */
.fact-chips { list-style: none; display: flex; flex-wrap: wrap; gap: var(--space-2); margin: var(--space-6) 0 0; padding: 0; }
.fact-chip {
  display: inline-flex; align-items: baseline; gap: var(--space-2);
  border: 1px solid var(--neutral-300);
  border-radius: var(--radius-pill);
  padding: var(--space-2) var(--space-3);
  font: var(--type-small);
  color: var(--neutral-700);
}
/* STACKED below 64em (maintainer, 2026-08-24), because the copy column is
   now sharing the header with the motif and no longer has room for two
   pills abreast. They wrapped to two rows at 390px before this anyway —
   "Founded 2009" plus "Acquired by Beyond79 2014" measured 344px against a
   342px container — so this is not new height; it is the same two rows made
   deliberate, and now correct at every width up to 64em rather than only
   below the width where they happened to break.

   `stretch` + `space-between` rather than letting each pill shrink-wrap:
   equal-width pills with the term left and the value right read as a spec
   pair, and shrink-wrapped ones at these two very different label lengths
   read as a ragged accident. It is also what buys the fit — the value is
   pinned to the right edge instead of trailing a 20-character term.

   The horizontal padding drops to --space-2 for the same reason the CTA's
   does: "Acquired by Beyond79" is the longest string in this column and
   every px of chrome comes out of it. Vertical padding is untouched.

   39.99em, NOT 63.99em, and the ceiling is load-bearing in BOTH directions.
   Above it the two pills fit abreast again — 344px of chips against a 388px
   column at 640px — and a stretched pair there is actively worse: at 768px
   they became two 507px-wide rules with the value stranded at the far right.
   Below it they cannot fit abreast at any width, so the stack is the only
   layout, which is the whole justification for stretching them.

   `max-width` is what keeps the stretch from turning into the same problem
   one breakpoint lower. The stack runs all the way to 39.99em, and at 639px
   the copy column is 387px — pills that wide strand the value at the far
   right exactly the way the 768px ones did. 240px is a little over the
   natural width of the longer pill ("Acquired by Beyond79 2014" sets at
   ~212px), so it reads as a tidy pair at the top of the range and is inert
   at the bottom, where the column is narrower than the cap anyway. */
@media (max-width: 39.99em) {
  .fact-chips { flex-direction: column; align-items: stretch; max-width: 240px; }
  .fact-chip { justify-content: space-between; padding-inline: var(--space-2); }
}
.fact-chip__term { color: var(--neutral-500); }
/* On ink: neutral-200 body text and a gold-300 term, both legal as text on
   ink-900 per tokens.css. The hairline drops to ink-700, the token for dark
   hairlines — neutral-300 would glare against the band. No `background`
   override needed here: unlike the old shared-name version, nothing else
   sets a background on `.fact-chip`, so it is transparent by default and
   the ink-900 band shows through on its own. */
.section--dark .fact-chip { border-color: var(--ink-700); color: var(--neutral-200); }
.section--dark .fact-chip__term { color: var(--gold-300); }

/* Photograph slot in a band body (brands.njk `section.figure`). The hatch
   is a block that reserves real height, unlike base.css's inline-block
   `.tbd` — a photo slot that collapses to one text line does not read as a
   missing photograph. Added 2026-08-19 (Task 6). */
.brand-figure { margin: var(--space-8) 0 0; }
.brand-figure img { display: block; width: 100%; height: auto; border-radius: var(--radius-lg); }
.brand-figure--tbd {
  display: block;
  margin-top: var(--space-8);
  padding-block: var(--space-16);
  text-align: center;
}

/* ------------------------------------------------------------
   Brand-page closing card — the bookend lockup (2026-08-25).

   A MODIFIER ON components.css §4.13, WHICH IS UNTOUCHED. Bare
   `.closing-card` still serves /expertise/ and /responsibility/
   exactly as before; only brands.njk emits `--brand`. Those two
   pages carry no button and no brand assets, so the second column
   below would have nothing to hold there.

   The problem it solves is a wide-viewport problem only. At 1280px
   the card runs 1216px, base.css caps every `p` at --measure, and
   the copy filled roughly the left 60% — leaving ~480px of empty
   ink-900 to its right. At 390px there was no gap at all: the copy
   already filled the card and read well, which is why this stacks
   on phones rather than mirroring the header's persistent two
   columns. THE HEADER'S ARRANGEMENT IS NOT THE MODEL HERE and
   copying it would break the card: .brand-header__copy holds an
   eyebrow, an h1, two chips and a button, all of which survive a
   ~213px column at 390px. This holds two full paragraphs, which do
   not. The measured floor is at the 50em fork below.

   It lives in pages.css and not components.css deliberately. Both
   brand URLs come from ONE template, which is not the "used on 2+
   pages" test components.css exists for — and every rule this
   extends (--motif-w, .brand-motif, the logo centring technique)
   is already here, a few hundred lines up.
   ------------------------------------------------------------ */

/* --motif-w IS THE ONLY SIZE KNOB, exactly as it is on
   .brand-header__inner: it sets the grid track, .brand-motif's own
   max-width cap, and the logo width below. Retune it here and the
   whole lockup moves together; set a width on .brand-motif or the
   logo instead and they desynchronise.

   The values are deliberately SMALLER than the header's (340px at
   >=64em, clamp(104px, 30vw, 200px) below): this mark is the echo,
   not the statement, and it also has to sit above a footer that
   carries the same wordmark. The floor is 132px rather than the
   header's 104px because that is the ONE number this drawing
   cannot follow the header down on — pages.css's .brand-motif__mill
   note explains why the milled band takes no scale multiplier, and
   ~117px is the smallest width it was ever measured at. Do not
   lower this floor without re-measuring the mill at the new size. */
.closing-card--brand { --motif-w: clamp(132px, 34vw, 180px); }
@media (min-width: 50em) { .closing-card--brand { --motif-w: 200px; } }

/* Single column below 50em, so the mark follows the CTA in document
   order and lands centred beneath it. No `order` anywhere: the DOM
   sequence is already the stacked reading order.

   50em (800px), NOT 64em, AND THE NUMBER IS MEASURED. 64em was the
   first guess, being this file's dominant fork, and it is genuinely
   the HEIGHT-NEUTRAL point — but height-neutral is not the same as
   best, and the difference is worth writing down because it is not
   obvious:

     base.css caps every `p` at --measure, which resolves to 662px
     here. Above ~1023px the copy column is already wider than that,
     so narrowing it to a grid track changes no line breaks and the
     card's height is IDENTICAL with and without the lockup — 396px
     at 1023px and 428px at 1280px, before and after, to the pixel.
     Below ~1023px the column drops under --measure, the copy rewraps
     taller, and the lockup starts costing height.

   That makes 800-1023px look like a reason to stack. It is not, and
   the arithmetic runs the other way: at 800px the two-column card is
   521px tall, while STACKING the same mark under the CTA there would
   be 396 + 200 + 32 = 628px. The lockup is 107px shorter than the
   stacked alternative at that width. Stacking is only cheaper when
   the mark is absent altogether, which is not one of the options.

   The floor is the copy column itself: `inner - gap - --motif-w` is
   440px at 800px, but ~300px at 640px — the same too-narrow column
   that keeps the header's persistent-two-column arrangement from
   being copied here at all.

   50em is an EXISTING breakpoint in this file, deliberately: 48em
   (768px) is nearer the true floor and was rejected because
   CLAUDE.md §5 records that no rule in src/css keys off 48em any
   more, after the Expertise journey's row/column fork was deleted.
   Re-introducing it to gain 32px of range is not worth contradicting
   that. The next stop down, 40em, is below the floor.

   Both the grid fork and the --motif-w step run off this ONE
   breakpoint on purpose. They were on 50em and 64em respectively in
   an intermediate version, which meant the mark changed size at a
   width where nothing else about the card changed. */
.closing-card__inner { display: grid; gap: var(--space-8); }
@media (min-width: 50em) {
  .closing-card__inner {
    grid-template-columns: minmax(0, 1fr) var(--motif-w);
    gap: var(--space-12);
    align-items: center;
  }
}

/* components.css §4.13's `.closing-card > p:last-child` STOPS MATCHING
   once __inner exists — the CTA paragraph is no longer a direct child of
   the card — so its zeroed bottom margin is restored here, rather than by
   loosening the selector there. (§4.13 was edited later, on 2026-08-25, for
   the --grad-hero surface; that is unrelated and this rule still belongs
   here.) Unprefixed because `.closing-card__copy` is emitted by brands.njk
   alone; the other two pages have no such element. */
.closing-card__copy > p:last-child { margin-bottom: 0; }

/* The box is sized to --motif-w at EVERY width, not just where the grid
   track already is, so the absolutely centred logo below centres on the
   drawing rather than on a full-width cell that merely happens to share
   its midpoint. At >=64em this restates the track width and is inert. */
.closing-card__mark {
  position: relative;
  width: var(--motif-w);
  margin-inline: auto;
}

/* Centred the same way .brand-header__logo is, but sized by WIDTH, not
   height. That rule derives 72px from --motif-w * 72/340 because it serves
   the two brand PNGs (460x175 and 460x151, both roughly 3:1); this is the
   Beyond79 wordmark at 300x72, a 4.17:1 lockup, and a height-driven rule
   would run it wide and crowd the ring.

   0.58 puts the wordmark at 80% of the r=116 inner ring's width — the
   ring is 72.5% of the drawing, so 0.58/0.725 — which is the same
   proportion the header's brand logo occupies inside its own ring (189px
   in 246px at 340px). The two lockups therefore read as one object at
   two sizes. Retina is safe by construction: the PNG is 300px intrinsic
   and the widest this ever renders is 116px at >=64em, a 2.6x oversample. */
.closing-card__logo {
  position: absolute; top: 50%; left: 50%;
  transform: translate(-50%, -50%);
  width: calc(var(--motif-w) * 0.58); height: auto; display: block;
}

/* ============================================================
   5.5 — In-page tab bar. Four chips linking to that page's four
   section ids: #trust / #privacy / #compliance / #sustainability
   on /responsibility/, #valuation / #technology / #experience /
   #logistics on /expertise/ (added 2026-08-20, maintainer: "pill
   tab links for the Expertise page similar to Responsibility
   page"). Nothing here is page-scoped and nothing needed to
   change for the second consumer — the chips come from front
   matter through partials/tabbar.njk.

   EXPERTISE'S FOUR LABELS USED TO WRAP TO TWO ROWS at every width,
   where Responsibility's four fit on one. Its section headings ran
   ~119 characters against Responsibility's ~88 — ~965px of chips
   against an 845px track — and `flex-wrap: wrap` below caught the
   overflow. Shortening them to the id slugs was considered and NOT
   taken, because both pages label their chips with their section
   <h2>s.

   RESOLVED 2026-08-20 (maintainer: "I want those pills to be on one
   line") by trimming the ONE label that caused it: "Consumer
   Experience & Customer Acquisition" -> "Consumer Experience", in
   expertise.njk's front matter. ~96 characters now, ~778px, inside
   the 845px track. The other three are still their <h2>s, and the
   trimmed one is still that <h2>'s opening words, so the chips did
   not become a second naming scheme. The wrap below stays as the
   safety net it is on both pages — do not delete it.

   AMENDED 2026-08-24: the one-line guarantee is now DESKTOP-ONLY, and it now
   has TWO narrower tiers below it, not one. Below 40em the chips form a 2x2
   grid; from 40em to 63.99em they are a wrapping flex row instead — the
   grid's `repeat(2, 1fr)` was tried across the whole sub-64em range first
   and rejected on measurement, because at 1023px the ~975px container made
   each pill ~483px wide around a 14.5px label. See the two blocks at the
   end of this section. The 845px arithmetic above still describes >=64em
   and is still correct there.

   NOT STICKY, AND NOT FULL-BLEED, SINCE 2026-08-18 — maintainer:
   "move the anchor tags to the right of the page title rather than
   below". It now renders inside `.page__header` via that layout's
   `headerNav` slot and scrolls away with the title.

   WHAT THAT DELETED, so nobody puzzles over its absence: the bar
   used to be `position: sticky; top: 72px/80px; z-index: 40` on an
   opaque neutral-50 fill with a hairline under it, and its 69px
   height was a term in every anchor clearance on the page. All of
   that is gone. The `.band--resp` overrides that carried the 69px
   (a 181px sticky top and three :target tiers) went with it — the
   page now inherits the plain `.band` defaults below, which are the
   header-only numbers and were already correct for a page with
   nothing pinned under the header.

   THE TRADE, recorded because it was a real loss and not an
   oversight: the chips are no longer reachable once you scroll past
   the title. The sticky asides in §5.9 are what tell you where you
   are while reading; the chips are now a table of contents you use
   on arrival. If the bar is ever asked to pin again, it needs its
   sticky rules AND the 69px term back in every clearance below.
   ============================================================ */
.tabbar ul { list-style: none; display: flex; gap: var(--space-2); overflow-x: auto; margin: 0; padding: 0; }
.tabbar a {
  display: inline-flex; align-items: center; min-height: 44px; padding: 0 var(--space-4);
  border-radius: var(--radius-pill); border: 1px solid var(--neutral-300); text-decoration: none;
  color: var(--ink-900); white-space: nowrap;
}
.tabbar a[aria-current="true"] { background: var(--ink-900); color: var(--gold-300); border-color: var(--ink-900); }

/* GRID, NOT flex-wrap, BELOW 40em — 2026-08-24, part of the site-wide
   removal of in-page horizontal scrollers.

   The chips are a table of contents you use on arrival (see the trade
   recorded above), and one you have to swipe to read is not one. At 390px
   the track measured 964px on /expertise/ and 857px on /responsibility/
   against a 342px viewport.

   `flex-wrap: wrap` was tried first and rejected on measurement: with
   `white-space: nowrap` the chips run 141-243px against a 342px track, so
   no two labels pair and the row collapses to one chip per line — 4 rows
   on /expertise/, 3 on /responsibility/. A fixed 2-column grid with
   wrapping labels gives a clean 2x2 for roughly half the added height, and
   matches the 2-up treatment the homepage Expertise tiles take at the same
   width.

   `overflow-x: visible` is what actually disengages the scroller: the base
   rule's `auto` would otherwise keep this a scroll container even once its
   children stop overflowing.

   THE 44px min-height ON `.tabbar a` IS NOT TOUCHED — tap target, not
   decoration. The pills grow taller than 44px where a label wraps; that is
   the intended behaviour.

   CORRECTED 2026-08-24 (whole-branch review): this block originally ran to
   63.99em, not 39.99em. The 964px/857px measurements above are 390px-only
   and do not generalise upward — at 1023px the container is ~975px, so
   `repeat(2, 1fr)` plus `.tabbar a { width: 100% }` produced ~483px-wide
   pills around a 14.5px label, which nothing caught because only 390px and
   1280px were ever swept. Restricted to phone widths; the 40em-63.99em
   band below has its own rule now.

   This does NOT reopen the 2026-08-20 "I want those pills to be on one
   line" decision, which was about desktop, where the 845px track still
   holds all four on one row. Above 64em nothing here applies. */
@media (max-width: 39.99em) {
  .tabbar ul {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: var(--space-2);
    overflow-x: visible;
  }
  /* The <li> is the grid item; the <a> must fill it or the pills come out
     ragged at their intrinsic widths again. */
  .tabbar li { display: flex; }
  .tabbar a {
    width: 100%;
    justify-content: center;
    text-align: center;
    padding: var(--space-2) var(--space-3);
    font: var(--type-small);
    white-space: normal;
  }
}

/* 40em-63.99em: a wrapping flex row, not the phone grid. The 2-column grid
   is right at 390px and wrong here — at 1023px the container is ~975px, so
   `repeat(2, 1fr)` would make each pill ~483px wide around a 14.5px label.
   `flex-wrap` was rejected at 390px (chips are 141-243px against a 342px
   track, so nothing pairs and it collapses to one per row) but that does not
   generalise upward: at >=40em the track is >=592px and two or three chips
   pair comfortably. `overflow-x: visible` is still required to disengage the
   base rule's `auto` scroll container. Added 2026-08-24 after the whole-branch
   review measured the 900-1023px band, which the 390/1280 sweeps had missed. */
@media (min-width: 40em) and (max-width: 63.99em) {
  .tabbar ul { flex-wrap: wrap; overflow-x: visible; row-gap: var(--space-2); }
}

/* Same-page anchors (Responsibility's #trust etc.) must clear both the
   sticky header and the sticky tabbar beneath it. Measured against the
   real rendered heights via CDP getBoundingClientRect, never estimated —
   the brief's original 140px guess omitted both hairline borders and
   left anchored headings ~0.8px behind the tabbar. See task-12-report.md.

   <900px: header 72px min-height + 1px border-bottom = 73px; tabbar
   44px pill + 2 x 12px padding + 1px border-bottom = 69px. 73 + 69 = 142.

   >=900px: the header is a floating island with no bottom border, so the
   clearance is the island's bottom edge (80px), not the 88px band, and
   the tabbar is pinned to match above. 80 + 69 = 149.

   Replaces the old site's empty `.hash-link` offset-div pattern. */
:target { scroll-margin-top: 142px; }
@media (min-width: 900px) { :target { scroll-margin-top: 149px; } }

/* ANCHOR CLEARANCE — the four homepage Expertise cards deep-link into the
   ids on this page's aside <h2>s.

   These three tiers are now the ONLY clearance rules on the component.
   They were written to mirror `.band--resp`'s, minus the 69px tab bar
   that only Responsibility had; when that bar moved into the page header
   and stopped sticking (§5.5, 2026-08-18) the `.band--resp` overrides were
   deleted and Responsibility fell back to exactly these. Being UNSCOPED is
   what made that free: a plain `.band` is the default case, so every page
   on the component — /expertise/, /about/ and now /responsibility/ —
   inherits correct clearance without a rule of its own.

   THESE WERE `calc(<header> + var(--aside-heading-offset))` UNTIL
   2026-08-19. That variable measured how far a row's true top sat ABOVE its
   <h2>, back when the section icon was a block stacked over the heading: the
   id is on the h2, so landing the h2 flush with the header pushed the rest
   of the row up and hid the body column's first lines. Adding 46px fixed it.

   The icon moved INSIDE the h2 on 2026-08-18, on every page that has one, so
   the h2 became the top of its row again and the offset went to 0. It was
   briefly kept as a zero-valued knob. It is now gone, and the arithmetic is
   plain numbers, because a variable that is always zero bought nothing and
   cost something: it lived in components.css while these three declarations
   live here, so `var()` resolving to nothing would make all three INVALID AT
   COMPUTED-VALUE TIME — scroll-margin-top would fall back to 0 and every
   anchor on /expertise/ and /responsibility/ would land under the header,
   with nothing in either file pointing at the cause.

   IF AN ASIDE EVER STACKS SOMETHING ABOVE ITS HEADING AGAIN, add its height
   to all three numbers below and measure the result with CDP
   getBoundingClientRect — do not reintroduce a cross-file variable for it.

   Superseded `.expertise-item:target` (73/80px), which was correct only
   while the <h2> was the top of its section. Under the band it is not. */
.band__aside h2:target { scroll-margin-top: 73px; }
@media (min-width: 900px) {
  .band__aside h2:target { scroll-margin-top: 80px; }
}
@media (min-width: 64em) {
  /* ≥64em the aside is sticky, so the base becomes its own sticky top. */
  .band__aside h2:target { scroll-margin-top: var(--band-sticky-top); }
}

/* ============================================================
   5.9 — Responsibility page. The §4 hallmark ring, plus this
   page's own tuning of the shared editorial band. Added
   2026-08-18 (maintainer: overhaul for visual appeal); before it
   the page was one 68ch column in a 1200px container with four
   typographically identical sections, so the right ~45% of every
   screen was empty and nothing marked one area from the next.

   THE BAND ITSELF NOW LIVES IN components.css §4.12 as `.band` /
   `.band__aside` / `.band__body` / `.standfirst`. It was promoted
   there on 2026-08-18 when /about/ became its second consumer,
   which is exactly what the note that used to sit here asked for.
   What stayed behind was the geometry that depended on the tab
   bar, carried by `.band--resp`. That geometry is gone: the bar
   moved into the page header on 2026-08-18 and stopped sticking,
   so its 69px left every clearance and those rules were deleted.
   The modifier then carried nothing at all for part of that day.
   IT IS LOAD-BEARING AGAIN as of the inline-icon change below —
   the note that used to say "no rule in any file targets it" is
   no longer true, so do not delete `.band--resp` from the markup.
   ============================================================ */

/* This page's inline icon rules are GONE, not lost: they were identical to
   /expertise/'s and both were promoted to components.css §4.12 on 2026-08-18
   as the shared default. The comment that used to sit here argued for
   keeping them scoped until one session held both blocks — that happened,
   and this is the result. Do not re-add a scoped copy; change the default. */


/* Responsibility adds NO clearance rules of its own any more. It used to
   override all three tiers above by +69px for a sticky tab bar; that bar
   moved into the page header on 2026-08-18 and stopped sticking, so the
   unscoped `.band__aside h2:target` defaults are now exactly right for
   this page too. Verified by measurement, not by assuming the subtraction
   was clean — see the §5.5 note. */

/* Credentials sits below the band, full container width. */
/* `.resp-credentials` was the full-width wrapper around the credentials
   band on /responsibility/. Both the band and the wrapper were removed on
   2026-08-20 — see the note in responsibility.njk. */

/* ------------------------------------------------------------
   The four hallmark rings — one per section, markup in
   partials/resp-mark.njk. Struck-mark vocabulary: a shared frame
   of double hairline rim, milled edge and orbiting gold tracer,
   with a DIFFERENT DEVICE inside each, the way a hallmark set
   shares a punch shape and varies the mark within.

   Started as §4's alone (2026-08-18); the other three were added
   the same day at the maintainer's request. §4 keeps the four rim
   labels and four nodes because it is the only section describing
   a cycle — see the partial's header for why that is not an
   oversight in the other three.

   TWO COLOUR TIERS, and they are the whole reason the devices read
   as marks rather than as more frame: the FRAME is neutral-300,
   each DEVICE is neutral-400, one step darker. Both are decorative
   line work, so neither is bound by a text-contrast rule; gold-500
   is likewise licensed for rules and icons only, which is exactly
   what the tracer and the two gold accents are.

   THE MILLED EDGE IS ONE ELEMENT, NOT 90. A dash on a circular
   path is short along the circumference and as wide as the stroke
   ACROSS it, i.e. radial — so `stroke-width: 7` with a 1.5-long
   dash draws a milled/knurled tick, and one <circle> draws the
   whole bezel. r=143 → C = 2π(143) = 898.50; 90 ticks → period
   9.983 → dash 1.5 + gap 8.483. Dense and uniform on purpose:
   12 long + 48 short would read as a clock face.

   Tracer r=150 → C = 942.48. dasharray 120 + 822.48 = C exactly, so
   one arc travels a seamless loop, and the keyframe runs the offset
   to -942.48 = one full revolution. 120 units is ~13% of the ring —
   an arc you read as a sweep; the first pass used 70 and screenshotted
   as a speck on the rim. A <circle> path starts at
   3 o'clock and runs clockwise, so a negative offset advances the
   arc clockwise — matching the order of the four verbs. Any change
   to r means recomputing BOTH the dasharray and the keyframe.

   14s is deliberately slow: this is the only motion on the page and
   it should read as ambient, not as a spinner.

   `animation: none` explicitly, rather than leaning on base.css's
   blanket prefers-reduced-motion `animation-duration: 0.01ms`. That
   global is fine for finite transitions but this animation is
   `infinite`, and a 0.01ms infinite loop is a repaint every frame
   forever to render what is visually a static ring.
   ------------------------------------------------------------ */
.resp-ring {
  /* Two numbers per ring size, and the second one is not optional.
     --resp-ring-size is the rendered box; the drawing inside is a 320-unit
     viewBox, so any size below 320px scales every stroke down with it. At
     the intro's 240px that is 0.75x: a 1px rim renders at 0.75px and the
     device at 1.1px, which screenshotted as visibly anaemic beside the
     four full-size marks. --resp-ring-stroke pre-multiplies every
     stroke-width by the inverse of that scale so the RENDERED weight is
     identical at every size. Both numbers move together; 320/size is the
     value, always.

     Only stroke-widths are scaled. Every dasharray stays in user units on
     purpose — the mill's 90 ticks, the tracer's seamless loop and the
     quarters' four even arcs are all arithmetic against the circumference,
     and rescaling those would break the seams. One consequence, accepted:
     at the intro's size the mill's tick LENGTH is corrected (it is the
     stroke-width) but its tick WIDTH is not (it is the dash), so those
     ticks are a hair finer. Tick length and spacing carry that read; the
     width does not. */
  --resp-ring-size: 320px;
  --resp-ring-stroke: 1;
  position: relative;
  width: 100%; max-width: var(--resp-ring-size);
  aspect-ratio: 1;
  margin-top: var(--space-8);
}
/* Below 64em the aside stacks above its prose, where a 320px circle
   would wedge itself between a heading and that heading's own
   standfirst. Hidden rather than shrunk, the same call pages.css
   §5.1 makes for the homepage hero canvas at this exact breakpoint:
   it is decorative, it is aria-hidden, and the verbatim sentence it
   illustrates is on the page at every width. */
@media (max-width: 63.99em) { .resp-ring { display: none; } }
.resp-ring svg { display: block; width: 100%; height: 100%; overflow: visible; }
.resp-ring__rim { fill: none; stroke: var(--neutral-300); stroke-width: calc(1px * var(--resp-ring-stroke)); }
.resp-ring__mill {
  fill: none; stroke: var(--neutral-300);
  stroke-width: calc(7px * var(--resp-ring-stroke)); stroke-dasharray: 1.5 8.483;
}
.resp-ring__node { fill: var(--gold-500); stroke: none; }
.resp-ring__tracer {
  fill: none; stroke: var(--gold-500);
  stroke-width: calc(2px * var(--resp-ring-stroke)); stroke-linecap: round;
  stroke-dasharray: 120 822.478;
  animation: resp-ring-orbit 14s linear infinite;
}
@keyframes resp-ring-orbit { to { stroke-dashoffset: -942.478; } }
@media (prefers-reduced-motion: reduce) { .resp-ring__tracer { animation: none; } }

/* Labels are HTML spans absolutely positioned over the drawing, not
   SVG <text>. Inside a viewBox, `font-size: 11px` is 11 USER UNITS
   and scales with the box — at a 280px render the eyebrow would set
   at 9.6px, under the 14px floor tokens.css sets for body text and
   below even the 11px the eyebrow itself is licensed at. As HTML
   they stay at a real 11px however the drawing scales, and they
   inherit Archivo without an SVG font declaration.

   Percentages against a square (aspect-ratio: 1) box, so one set of
   numbers holds at every size. 13%/10% put each label inside the
   r=136 inner rim rather than on the milled band: at 320px the rim
   is 24px in from the edge, 10% is 32px. */
.resp-ring__label {
  position: absolute;
  font: var(--type-eyebrow);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--neutral-600);
  white-space: nowrap;
}
.resp-ring__label--n { left: 50%; top: 13%; transform: translateX(-50%); }
.resp-ring__label--e { right: 10%; top: 50%; transform: translateY(-50%); }
.resp-ring__label--s { left: 50%; bottom: 13%; transform: translateX(-50%); }
.resp-ring__label--w { left: 10%; top: 50%; transform: translateY(-50%); }

/* --- Device line work, one step darker than the frame ---------- */
.resp-ring__stand, .resp-ring__beam, .resp-ring__record,
.resp-ring__tumbler, .resp-ring__quarters,
.resp-ring__lock-shackle, .resp-ring__lock-body, .resp-ring__lock-keyhole,
.resp-ring__pivot,
.resp-ring__needle-n, .resp-ring__needle-s,
.resp-ring__fan-blade, .resp-ring__fan-pivot,
.resp-ring__gem, .resp-ring__gem-facet,
.resp-ring__spectrum, .resp-ring__spectrum-axis, .resp-ring__spectrum-tick,
.resp-ring__funnel, .resp-ring__level,
.resp-ring__route, .resp-ring__parcel-box, .resp-ring__parcel-tape {
  fill: none; stroke: var(--neutral-400);
  stroke-width: calc(1.5px * var(--resp-ring-stroke));
  stroke-linecap: round; stroke-linejoin: round;
}
/* `.resp-ring__core` was in this list until 2026-08-20 and is gone with the
   5px dot it styled — §2's centre is a padlock now (see the partial). The
   four lock parts and §1's needle pivot take its place here, so the lock is
   drawn in exactly the same line work as the balance, the record and the
   tumblers, which is what keeps the four devices one family. The needle
   itself is NOT in this list: it is filled, and is handled below. */

/* INTRO ICON — the compass needle swings and settles, then holds level
   for well over half the cycle.

   IT MUST NOT SPIN. A compass needle rotating continuously reads as an
   instrument that cannot find north — "searching", "lost" — which is the
   exact opposite of what this section claims. It settles and rests
   instead, and the rest is most of the animation.

   Amplitudes are larger than the §1 balance's (34° against 4°) because
   this is a 30px icon, not a 320px drawing: the needle tip sits ~8.5px
   from the pivot, so a 4° swing would move it a pixel and a half and read
   as nothing at all. Same damping curve, scaled to the object.

   This does duplicate the balance's MECHANIC — a damped settle — which
   the four ring devices deliberately avoid doing to each other. It is
   allowed here because this is an icon rather than a ring device, the two
   are a full band apart and never on screen together, and "instruments
   coming to rest" is a reasonable family trait for a page whose subject
   is assay. Do not read it as licence to give a second ring device a
   settle. */
.compass__needle {
  transform-origin: 0 0;
  animation: resp-compass 14s ease-in-out infinite;
}
@keyframes resp-compass {
  0%        { transform: rotate(-34deg); }
  12%       { transform: rotate(20deg); }
  22%       { transform: rotate(-12deg); }
  31%       { transform: rotate(7deg); }
  38%       { transform: rotate(-3deg); }
  44%, 100% { transform: rotate(0deg); }
}

/* INTRO — one ring built from four arcs, the gaps easing closed toward a
   whole and back open. The section's sentence drawn: "our approach to
   responsibility is built around four areas".

   dasharray arithmetic, all of it forced by r=104 → C = 653.451:
     period  C/4      = 163.363  (four arcs, no seam)
     rest    140      + 23.363 gap
     closed  158      +  5.363 gap
   Both pairs sum to the period, which is what keeps the four arcs even at
   every point in the animation. dashoffset -11.68 = half the resting gap,
   which is what lands the gaps ON the cardinals rather than 7° short.

   `stroke-dasharray` interpolates between two lists of equal length, so
   this is a plain CSS animation and needs no JS. 7s alternate = a 14s
   round trip, the same clock as every other mark on the page.

   THE GAPS NEVER FULLY CLOSE — 5.363 units of daylight remain at the
   tightest point. A ring that sealed itself would say "complete", and this
   page's copy is careful never to claim that; the four areas are ongoing
   practice, not a finished state. Do not animate this to 0. */
/* THE INTRO MARK IS 320px, i.e. it takes the `.resp-ring` default and needs
   no rule of its own. It was 240px (with --resp-ring-stroke: 1.3333 to hold
   the rendered line weight) from the day it shipped until 2026-08-20, when
   the maintainer asked for it to match the other four in "styling +
   positioning". A quarter smaller than its four siblings in the same
   left-aligned column is the positioning difference, so it went.

   THE TRADE THAT BUYS IS REAL AND WAS ACCEPTED, not overlooked. The intro's
   prose is three paragraphs against the four to five each section below it
   runs, so its aside was already the taller side of the band at 240px, by
   about 40px; at 320px it is taller by about 125px, and that extra ~85px is
   whitespace beside the lower third of the ring. If that dead space is ever
   judged the worse problem, the fix is this rule back exactly as it was —
   BOTH numbers, never the size alone, or every stroke in the mark thins
   with it (see --resp-ring-stroke at the top of this section). */

/* THE INGOT — the homepage closing mark's centrepiece (`device: "evolve"`,
   partials/resp-mark.njk). Deliberately the one FILLED element in the whole
   set: every other device is line work in neutral-400, so a solid gold bar
   at the centre is the thing the eye lands on, with the four arcs breathing
   around it.

   THREE TONES, ONE PER FACE, AND THE ORDER IS THE POINT. The bar was turned
   upside down on 2026-08-18, which means the receding face is now an
   UNDERSIDE rather than a lit top, and the tones were re-assigned to match
   rather than mirrored along with the geometry:

     end   gold-300  — the right end, turned toward the light
     face  gold-500  — the front, unchanged
     under gold-700  — the underside, in shadow

   Flipping the shape while leaving gold-300 on the receding face would have
   lit the bar from underneath, which reads as floating rather than resting.
   That descending step is the entire illusion of a solid: the geometry alone
   is just a hexagon, and two earlier two-tone versions read as a cheese
   wedge and then a flat plate. Do not "tidy" these to a single fill.

   All three are outlined in gold-700 so the shape still holds on the muted
   band behind it. The underside is filled gold-700 too, so its outline
   vanishes into it — correct, since that face is in shadow and wants no
   internal edge.

   The stroke takes the same `calc(1px * var(--resp-ring-stroke))` correction
   as the rest of the frame — the drawing is a 320-unit viewBox, so without
   it the outline thins out at any rendered size below 320px. */
.resp-ring__ingot-under,
.resp-ring__ingot-face,
.resp-ring__ingot-end {
  stroke: var(--gold-700);
  stroke-width: calc(1px * var(--resp-ring-stroke));
  stroke-linejoin: round;
}
.resp-ring__ingot-end   { fill: var(--gold-300); }
.resp-ring__ingot-face  { fill: var(--gold-500); }
.resp-ring__ingot-under { fill: var(--gold-700); }

.resp-ring__quarters {
  stroke-dasharray: 140 23.363;
  stroke-dashoffset: -11.68;
  animation: resp-quarters 7s ease-in-out infinite alternate;
}
@keyframes resp-quarters { to { stroke-dasharray: 158 5.363; } }

/* §1 TRUST — the balance settles and then RESTS. The swing occupies
   the first half of the cycle and the second half is dead level:
   a balance at rest is the message, so that stillness is content,
   not filler. Amplitude decays 4 → 2.6 → 1.6 → 0.9 → 0.4 → 0, which
   is a damped oscillation rather than five arbitrary numbers.

   `transform-origin: 0 0` with the pivot supplied by the parent
   group's translate — see the partial's ROTATION IDIOM note. */
.resp-ring__beam { transform-origin: 0 0; animation: resp-balance 14s ease-in-out infinite; }
@keyframes resp-balance {
  0%       { transform: rotate(-4deg); }
  14%      { transform: rotate(2.6deg); }
  26%      { transform: rotate(-1.6deg); }
  36%      { transform: rotate(0.9deg); }
  44%      { transform: rotate(-0.4deg); }
  50%, 100%{ transform: rotate(0deg); }
}

/* §2 PRIVACY — two tumblers counter-rotating about a core. The inner
   one is the mark's single gold accent. Periods are 26s and 34s:
   both slower than the 14s tracer, in opposite directions, and not
   multiples of each other, so the three never fall into a repeating
   pattern the eye can lock onto. Continuous by design — an
   alignment "click" would read as a security claim the copy does
   not make.

   dasharray shows one arc of each ring: outer r=104 → C=653.5, so
   261 on / 392.5 off is a 40% arc; inner r=72 → C=452.4, so 136 on
   / 316.4 off is a 30% arc. Change r and both numbers move. */
.resp-ring__tumbler { transform-origin: 0 0; }
.resp-ring__tumbler--outer {
  stroke-dasharray: 261 392.5;
  animation: resp-spin 26s linear infinite;
}
.resp-ring__tumbler--inner {
  stroke: var(--gold-500);
  stroke-dasharray: 136 316.4;
  animation: resp-spin 34s linear infinite reverse;
}
@keyframes resp-spin { to { transform: rotate(360deg); } }

/* §2's PADLOCK — no rules of its own beyond the shared line work above, and
   that is the whole point: it is static, so there is nothing to animate, and
   it takes the family stroke so it cannot drift from the other devices. Do
   not give the shackle a transition or a hover state; see the partial for
   why an opening or closing lock would overclaim what the copy says. */

/* INTRO MARK's COMPASS NEEDLE — hairline, two-tone, and motionless.

   IT IS IN THE DEVICE LINE-WORK LIST ABOVE, so fill, weight, caps and
   joins all come from there and only the north half's colour is set here.
   It was two SOLID triangles (gold-500 over neutral-400) for one day, and
   that made it the only filled, only saturated object in a set of hairline
   engravings — see the partial for the screenshot that killed it. If this
   is ever touched again, the rule to hold is that the five devices share
   one line treatment; a device that needs to shout is the wrong device.

   NORTH GOLD, SOUTH NEUTRAL survives, because the two halves have to
   differ or the needle is just a diamond. gold-500 is legal here for the
   reason it is on the nodes and the audit rule: a large decorative shape,
   not text, inside an aria-hidden figure. One gold accent inside an
   otherwise neutral drawing is the family habit, not an exception to it.

   NO ANIMATION, and do not add one by analogy with the 30px heading icon's
   `.compass__needle`, which settles and rests. That icon owns the movement
   for this section; this mark's movement is the quarters ring breathing
   around it, and the set allows one per mark. */
.resp-ring__needle-n { stroke: var(--gold-500); }

/* ============================================================
   /expertise/ DEVICES (2026-08-21). Five marks in the same frame
   as /responsibility/'s five — same milled edge, same double rim,
   same tracer (maintainer: match them). Every one of them takes
   the shared line work above and sets only its gold accent here,
   which is the rule that makes ten drawings one set.

   ONE MOVEMENT EACH, all on the 14s clock, and five different
   KINDS of movement so the page reads as one mechanism rather
   than five competing loops: the fan rotates, the gem changes
   colour and nothing else, the spectrum draws itself, the level
   translates-and-narrows, the parcel travels a path. That is the
   same discipline /responsibility/ applies to its four, and the
   reason two of these five may look adjacent (the level and the
   parcel both translate) is recorded honestly in the partial
   rather than contorted away — they are a falling rule and a
   moving object, and they are two bands apart.
   ------------------------------------------------------------ */

/* INTRO — the touch-needle fan splaying open and holding open.

   THE BASE TRANSFORM IS THE OPEN ANGLE, not the closed one, and
   that is the prefers-reduced-motion contract: with the animation
   off the fan reads as a displayed set of needles, which is the
   device. Parked closed it would read as one thick blade and the
   mark would say the opposite of what the section does.

   Four keyframe blocks rather than one, because the four blades
   rotate by four different amounts — a shared keyframe could only
   spin the whole fan, which is not an opening. The overshoot is
   ~4% past the open angle, the balance's damping scaled to a much
   shorter swing. */
.resp-ring__fan-blade {
  transform-origin: 0 0;
  animation: 14s ease-in-out infinite;
}
.resp-ring__fan-blade--a { transform: rotate(-45deg); animation-name: resp-fan-a; }
.resp-ring__fan-blade--b { transform: rotate(-15deg); animation-name: resp-fan-b; }
.resp-ring__fan-blade--c { transform: rotate(15deg);  animation-name: resp-fan-c; }
.resp-ring__fan-blade--d { transform: rotate(45deg);  animation-name: resp-fan-d; }
@keyframes resp-fan-a {
  0%        { transform: rotate(-15deg); }
  13%       { transform: rotate(-46.8deg); }
  22%, 88%  { transform: rotate(-45deg); }
  100%      { transform: rotate(-15deg); }
}
@keyframes resp-fan-b {
  0%        { transform: rotate(-5deg); }
  13%       { transform: rotate(-15.6deg); }
  22%, 88%  { transform: rotate(-15deg); }
  100%      { transform: rotate(-5deg); }
}
@keyframes resp-fan-c {
  0%        { transform: rotate(5deg); }
  13%       { transform: rotate(15.6deg); }
  22%, 88%  { transform: rotate(15deg); }
  100%      { transform: rotate(5deg); }
}
@keyframes resp-fan-d {
  0%        { transform: rotate(15deg); }
  13%       { transform: rotate(46.8deg); }
  22%, 88%  { transform: rotate(45deg); }
  100%      { transform: rotate(15deg); }
}
.resp-ring__fan-pivot { stroke: var(--gold-500); }

/* §1 VALUATION — the brilliant scintillating.

   THE ONLY DEVICE IN EITHER SET THAT MOVES NO GEOMETRY. Three
   facet outlines take gold in turn, a third of the clock apart,
   which is why one keyframe plus two delays does the whole job.

   THE THREE WINDOWS TILE THE CYCLE — 0-33.3%, 33.3-66.7%,
   66.7-100% — so exactly one facet is lit at every instant and
   the gleam WALKS the stone. It blinked in the first version: each
   facet was lit for 0.84s of the 14s and dark for the rest, which
   left the gem with no gold at all for more than half of every
   cycle and made the mark read as neutral line work that
   occasionally flickered. The 6% overlap at each edge is a real
   crossfade, not slop — remove it and the walk becomes a switch.

   `linear`, not the family's `ease-in-out`: easing a crossfade
   compresses it at both ends and reintroduces the blink.

   FACET 1 IS LIT AT REST (`opacity: 1` below) so the mark keeps
   its gold accent with animation off; 2 and 3 rest dark. Do not
   "tidy" all three to the same base — that is the reduced-motion
   state, not a redundancy. */
.resp-ring__gem-facet {
  stroke: var(--gold-500);
  opacity: 0;
  animation: resp-facet 14s linear infinite;
}
.resp-ring__gem-facet--1 { opacity: 1; }
.resp-ring__gem-facet--2 { animation-delay: -4.667s; }
.resp-ring__gem-facet--3 { animation-delay: -9.333s; }
@keyframes resp-facet {
  0%, 30%   { opacity: 1; }
  36%, 94%  { opacity: 0; }
  100%      { opacity: 1; }
}

/* §2 TECHNOLOGY — the spectrum reading in, holding, and clearing.

   `pathLength="1000"` on the path (see the partial) is what makes
   this one line of arithmetic instead of a measured length: the
   trace is a 17-segment polyline whose real length cannot be
   derived by hand the way the tracer's and the quarters ring's
   circumferences can. Normalising it means this dasharray never
   has to be recomputed when a peak moves.

   STROKE-DASHOFFSET RESTS AT 0 — fully drawn — so animation: none
   leaves a readable spectrum. The keyframes start it empty. Get
   this backwards and reduced motion renders a blank axis. */
.resp-ring__spectrum {
  stroke-dasharray: 1000;
  stroke-dashoffset: 0;
  animation: resp-spectrum 14s ease-in-out infinite;
}
@keyframes resp-spectrum {
  0%        { stroke-dashoffset: 1000; }
  45%, 85%  { stroke-dashoffset: 0; }
  100%      { stroke-dashoffset: 1000; }
}
.resp-ring__spectrum-axis { stroke: var(--gold-500); }

/* §3 CONSUMER EXPERIENCE — the level falling through the funnel.

   scaleX 0.259 IS NOT A TASTE VALUE. The funnel's walls taper
   0.7045 units of half-width per unit of depth (see the partial);
   the level starts 57.1 half-wide at y=132 and must be 14.8 half-
   wide at y=192, and 14.8/57.1 = 0.259. Move a wall and this
   number moves with it, or the level stops touching the glass.

   transform-origin: 0 0 puts the scale about the funnel's axis,
   which is what keeps it centred as it narrows — the path is
   drawn symmetrically about its own local origin for exactly
   this reason. */
.resp-ring__level {
  stroke: var(--gold-500);
  transform-origin: 0 0;
  animation: resp-level 14s ease-in-out infinite;
}
@keyframes resp-level {
  0%   { transform: translateY(0px) scaleX(1); opacity: 0; }
  6%   { transform: translateY(0px) scaleX(1); opacity: 1; }
  55%  { transform: translateY(60px) scaleX(.259); opacity: 1; }
  92%  { transform: translateY(60px) scaleX(.259); opacity: 1; }
  100% { transform: translateY(60px) scaleX(.259); opacity: 0; }
}

/* §4 SECURE OPERATIONS — the parcel travelling door to door.

   THE EASING IS IN THE POSITIONS, NOT THE TIMING FUNCTION. The
   seven stops are the route curve sampled at t = 0, .08, .25, .5,
   .75, .92, 1 — bunched at both ends — and then spread across
   equal slices of the clock. A uniform sample with ease-in-out
   would ease at EVERY keyframe and the parcel would stutter six
   times crossing the frame. y = -236·t(1-t) is the curve's whole
   vertical term, which is where these numbers come from; x is
   linear because the curve's three x values are evenly spaced.
   Both come off the `d` attribute in the partial — if that arc is
   ever redrawn, all six vertical offsets change with it.

   IT ARRIVES AND STAYS, for half the clock. That is the section
   (property arrives and is held), not dead time, and it is the
   same "the rest is most of the animation" habit the balance and
   the compass follow. The fade at 0/100% is what hides the loop:
   without it the parcel would streak back to the start. */
.resp-ring__parcel { animation: resp-parcel 14s linear infinite; }
@keyframes resp-parcel {
  0%     { transform: translate(0px, 0px);        opacity: 0; }
  4%     { transform: translate(0px, 0px);        opacity: 1; }
  10.7%  { transform: translate(12px, -17.37px);  opacity: 1; }
  17.3%  { transform: translate(37.5px, -44.25px); opacity: 1; }
  24%    { transform: translate(75px, -59px);     opacity: 1; }
  30.7%  { transform: translate(112.5px, -44.25px); opacity: 1; }
  37.3%  { transform: translate(138px, -17.37px); opacity: 1; }
  44%    { transform: translate(150px, 0px);      opacity: 1; }
  94%    { transform: translate(150px, 0px);      opacity: 1; }
  100%   { transform: translate(150px, 0px);      opacity: 0; }
}
.resp-ring__parcel-tape { stroke: var(--gold-500); }

/* §3 COMPLIANCE — a gold rule sweeping down the record and back.
   `alternate` is what makes it a sweep instead of a loop: 7s each
   way, so it never snaps back to the top.

   THE RULE RESTS OFF THE RECORD AT BOTH ENDS, and that is a fix, not
   a preference. `alternate` + `ease-in-out` is slowest at the
   extremes, so the rule DWELLS there — and the first version parked
   it at y=105, exactly on the first ruled line, where it sat long
   enough to read as "the top line is gold" instead of as a sweep.
   Screenshotted and corrected. It now starts 8px clear at y=97 and
   travels 126px to y=223, 8px past the last line at y=215, so both
   rest positions are visibly off the record and the movement
   through it is what you notice. Move the record lines and this
   travel distance moves with them. */
.resp-ring__audit {
  fill: none; stroke: var(--gold-500);
  stroke-width: calc(2px * var(--resp-ring-stroke)); stroke-linecap: round;
  animation: resp-audit 7s ease-in-out infinite alternate;
}
@keyframes resp-audit { to { transform: translateY(126px); } }

/* One stop for every device animation. Same reasoning as the
   tracer's rule above: these are all `infinite`, and base.css's
   blanket 0.01ms would leave them repainting forever to render
   something visually static. */
@media (prefers-reduced-motion: reduce) {
  .resp-ring__beam, .resp-ring__tumbler, .resp-ring__audit,
  .resp-ring__quarters, .compass__needle,
  .resp-ring__fan-blade, .resp-ring__gem-facet, .resp-ring__spectrum,
  .resp-ring__level, .resp-ring__parcel { animation: none; }
}

/* The closing "Our Commitment" card is now the shared `.closing-card`
   (components.css §4.13) — promoted 2026-08-18 when /expertise/ became
   the second page to use it. */

/* ============================================================
   404. One self-contained dark card — the page's only ink-900
   surface, well under the two-band ceiling.
   ============================================================ */
.error-card {
  background: var(--ink-900); color: var(--neutral-200);
  border-radius: var(--radius-xl); padding: var(--space-16) var(--space-8);
}
.error-card__code {
  font: 300 clamp(48px, 8vw, 72px)/1 var(--font-display);
  color: var(--gold-300); margin: 0 0 var(--space-4);
  font-variant-numeric: tabular-nums lining-nums;
}
.error-card h1 { color: #fff; }
.error-card p { color: var(--neutral-200); }
/* Plain inline links only — :not(.button) keeps this from beating
   .button--on-dark's ink-900 text (base.css) on the CTA row below,
   which would otherwise paint gold-300 text on a gold-300 button. */
.error-card a:not(.button) { color: var(--gold-300); }

/* ============================================================
   5.6 — Press-release dateline. The verbatim-migrated article body
   (guarded by tools/verify-verbatim.mjs — its markup cannot change)
   wraps the dateline in <em>, not <strong>; the kit wants it bold.
   Achieved here instead of in markup. This article's body is the
   only place `.article__body` currently emits an <em>.
   ============================================================ */
.article__body em { font-weight: 600; font-style: normal; }

/* ============================================================
   5.10 — Who We Are (/about/). Added 2026-08-18 (maintainer:
   overhaul for visual appeal). Only three things here are this
   page's own; everything else it renders is shared —
   `.band`/`.standfirst` (components.css §4.12), `.tile` in a
   `.grid--3` (§4.10) and the fact rows (§4.6). It also rendered the
   credentials card until that was removed on 2026-08-20.

   The page does NOT use `.band--resp`, and as of 2026-08-18
   neither does anything else — that modifier existed to clear
   Responsibility's sticky tab bar, which no longer sticks, so it
   now carries no rules at all. The component's own
   --band-sticky-top default is correct here, and on every page.
   ============================================================ */

/* ------------------------------------------------------------
   The company identity strip — /about/, at the foot of the Why
   Beyond79? band. Three shapes in one day, 2026-08-25, and the
   rejections are the useful part of the record:

     1. `.facts-card` — the six facts as a `.fact-rows` <dl> in a white
        card on its own band between the two prose bands. Rejected for
        POSITION ("not cutting off the flow of the page's sections")
        and for shape: `.fact-rows` is a ~22% term track against a 1fr
        value track (components.css §4.6), and four of these six values
        are short enough that the row's right half was empty at 1200px.
     2. An equal-track CSS grid, label stacked over value, directly
        under the page header. Rejected on sight — "The table looks
        terrible at the top like this" — for opening a page titled Who
        We Are on a slab of reference data before a word of prose.
     3. Label INLINE with value, items packed into a wrapping flex row,
        nested at the foot of the Why Beyond79? band. Rejected too —
        "It doesn't look good there either."
     4. This: the same inline rows, but lifted into the 5fr aside of
        band 1, under its heading.

   Do not reach back for 1, 2 or 3. `.fact-rows` ITSELF IS UNCHANGED AND
   STILL SHIPS — partials/brand-dialog.njk renders it, where the
   two-column form is right because the dialog is narrow. This is a
   second shape, not a replacement; see the note at the top of
   partials/fact-grid.njk for why it needs its own partial.

   WHY INLINE LABELS ARE THE WHOLE COMPACTION. Stacking the label over
   the value costs two lines per row; putting them on one baseline
   costs one. In the 453px aside that is ~90px saved over stacking,
   which is the difference between the strip fitting inside the space
   the band already reserves and pushing the aside past the body and
   growing the band.

   THE STRIP NOW COSTS THE PAGE NO HEIGHT. The 5fr column held one h2
   and then ~370px of nothing down to the band's floor. Every earlier
   shape occupied height of its own somewhere else on the page; this
   one is absorbed into space that was already being paid for.

   THE FLEX ROW STILL WRAPS, THOUGH NOTHING WRAPS HERE. In a 453px
   column one item fills a line and the wrapping never fires, so what
   the flex actually buys at this width is one-fact-per-row with rules
   that reach the full column (see `flex-grow` below). The wrapping is
   what it would do in a wide container, and is left in because it is
   the same three declarations either way — not because this instance
   needs it. If the strip is ever narrowed further, the fixed label
   column on `dt` is the first thing that will not fit.

   NO BREAKPOINTS IN THIS BLOCK. An earlier shape needed a 50em and a
   76em step, both derived from measured track widths, because an
   equal-track grid has to be told how many columns fit. Nothing here
   keys off a viewport width; the one media query below belongs to
   `.band--facts` and is about stacking order, not about this grid.
   ------------------------------------------------------------ */

/* The gap between the band heading and the strip's gold rule. The band
   itself closes the space underneath, so there is no bottom margin. */
.facts-strip { margin-top: var(--space-8); }

/* ------------------------------------------------------------
   `.band--facts` — band 1 of /about/ only, and the whole modifier
   is about ONE problem: DOM order and reading order disagree
   between the two layouts.

   At 64em and up `.band` is a 5fr/7fr grid, the strip sits in the
   aside under the h2, and that is the reading order too — heading,
   facts beneath it, prose alongside. Nothing to do.

   Below 64em the grid collapses to one column and the aside stacks
   ABOVE the body, which would put six rows of reference data between
   the heading and its own opening sentence. That is precisely the
   interruption this strip was moved out of band 2 to escape, and it
   would be worse here, mid-paragraph-block rather than between
   sections.

   `display: contents` dissolves the aside so its two children become
   grid items of `.band` in their own right, and the strip alone takes
   an order after the body's default 0. The h2 keeps order 0 and its
   DOM position, so the stack is h2 -> prose -> facts. The footer does
   the same thing for the same reason — see `.site-footer__col--identity`
   in components.css §4.11.

   WHY `display: contents` IS SAFE ON THIS BOX: it removes the element
   from the box tree, so any background, border or padding on it would
   vanish. `.band__aside` carries none — it is a bare grid cell whose
   only declarations are the sticky trio, and sticky applies at 64em
   and up, where this rule is not in force. If the aside ever gains a
   visible surface, this stops being free and the strip has to move out
   to being a third child of `.band` instead.

   NOT `order` ON THE ASIDE ITSELF, which would be the obvious fix and
   is wrong: it would carry the h2 down with the strip and leave the
   prose to open the band with no heading over it. -------------- */
@media (max-width: 63.99em) {
  .band--facts .band__aside { display: contents; }
  .band--facts .facts-strip { order: 1; margin-top: 0; }
}

.fact-grid {
  display: flex;
  flex-wrap: wrap;
  column-gap: var(--space-8);
  row-gap: 0;
  margin: 0;
  /* The block's top edge, and the same --gold-500 bar the tiles carry
     at their foot — deliberately the same signature, because this
     strip and the tile grid lower down are the page's two non-prose
     blocks. gold-500 is legal here for the reason tokens.css gives: a
     rule is non-text. */
  border-top: 2px solid var(--gold-500);
}

/* One fact = one flex item, so a term and its value are packed and
   wrapped as a unit and can never be split across lines.

   THE HAIRLINE IS ON THE BOTTOM, NOT THE TOP, AND THAT IS FORCED.
   With a top border the first row would sit a stray 1px grey rule
   directly under the 2px gold one, and suppressing it would mean
   selecting "every item on the first line" — which no selector can
   express here, because how many items fit line one depends on their
   content, not on a column count. (Shape 2 could only do it by
   spelling out `:nth-child(-n + 2/3)` per breakpoint.) On the bottom
   the question never arises: every row gets a rule beneath it, the
   last one included, and that closes the block.

   `align-items` is left at its `stretch` default on the flex parent
   above — that is what makes every item in a line the same height, so
   their bottom borders meet as one continuous rule even when one
   value has wrapped to two lines. */
.fact-grid__item {
  display: flex;
  align-items: baseline;
  gap: var(--space-3);
  min-width: 0;
  border-bottom: 1px solid var(--neutral-200);
  padding-block: var(--space-3);
  /* `flex-grow: 1` IS A RULE-LENGTH FIX, NOT SPACING. Without it each
     item is exactly as wide as its own text, so its bottom border stops
     where that text stops and every line ends on a different ragged x —
     three rules of three different lengths, which reads as unfinished
     rather than as a ruled block. Growing the items makes each line's
     items share its leftover width, so all three rules reach the
     container's right edge and stop together. Flex packs a line at the
     items' content widths BEFORE it grows them, so this cannot change
     which facts land on which line.

     The slack lands after each value (the inner flex is flex-start), so
     what widens is the space between one fact and the next label —
     which is where extra air belongs anyway. */
  flex: 1 1 auto;
}

.fact-grid dt {
  font: var(--type-eyebrow);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  /* NOT --gold-700, which is what `.eyebrow` uses. Six gold labels
     would read as six more section eyebrows, and gold-700 is also
     this site's link colour (see CLAUDE.md on the marine ramp) —
     wrong signal on a run of inert labels. --neutral-600 measures
     6.79:1 on --neutral-100; this is 11px text, so 4.5:1 is the
     threshold that applies. */
  color: var(--neutral-600);
  /* A FIXED COLUMN, NOT `flex: none`. At its natural width every label
     is a different length, so every value started at a different x and
     each wrapped value indented to its own private margin — six ragged
     left edges down a 453px column, which is far more visible than it
     was when the facts sat side by side across the full container.
     136px is the widest label, REGULATED UNDER, measured at 135.2px
     and rounded up; the rest pad out to it and every value lines up.
     `0 0` because this column must neither grow into the value's space
     nor shrink out of the way of it.

     If a longer term is ever added, this number is what has to move —
     a label wider than its column would be clipped or would push the
     value out. `nowrap` is what makes that failure visible rather than
     silent: the label stays on one line and overhangs instead of
     quietly re-wrapping into two. */
  flex: 0 0 136px;
  white-space: nowrap;
}

.fact-grid dd {
  margin: 0;              /* the UA's 40px inline start */
  /* --type-small, and unlike the earlier round this is a judgement, not
     a measurement — nothing breaks at --type-body. The strip is now a
     panel in the 5fr column BESIDE the prose rather than a block under
     it, so it is reference material next to the argument, and matching
     the body's own 17px would have the two reading as equal voices.
     It also keeps the aside close to the body's height instead of
     overshooting it, which is what lets the strip cost the page no
     height at all. Measured in the 305px value column: 10 lines at
     14.5px against 14 at 17px. */
  font: var(--type-small);
  color: var(--ink-900);
  /* The last-resort guard for a narrow phone, where an item can be
     wider than the whole container and has to shrink and wrap inside
     itself. `min-width: 0` on the item above is what lets it. */
  overflow-wrap: break-word;
}

/* ------------------------------------------------------------
   The ingot in the "Why Beyond79?" aside (2026-08-21, maintainer:
   "I'd like for src/images/Beyond79-Ingot.jpg to be implemented
   under Why Beyond79 on the about who we are page").

   240px is a RESOLUTION ceiling as much as a design one. The bar
   occupies 308x373 real pixels of the supplied file — that is all the
   detail there is — so the 360x425 asset renders at 1.33x on a 2x
   screen here. It survives that because the source is a smooth 3D
   render with no fine texture to alias; it would not survive much
   more. If a larger export ever arrives, this cap can rise. Do not
   raise it against the current file.

   `width: 100%` with the cap, not a bare width, because the aside is
   narrower than 240px on nothing at 64em+ but the band STACKS below
   that — there the aside is the full container and an uncapped image
   would run to 100% of it. The cap holds at every width; the
   percentage only matters in the stacked case.

   `height: auto` overrides the intrinsic height attribute the <img>
   carries, which is there to reserve the box before the file loads —
   without this the element would letterbox once the cap engages.

   No max-width change below 64em: 240px on a 342px stacked column is
   already the right proportion, and shrinking it further makes the
   engraved "196.967" unreadable.

   Nothing here sets a background, and that is deliberate — the PNG
   carries real alpha and the band is white, so the bar sits directly
   on the surface. See the note in about/index.njk about why the .jpg
   beside it must not be substituted back in.
   ------------------------------------------------------------ */
.why-ingot {
  display: block;
  width: 100%;
  max-width: 240px;
  height: auto;
  margin-top: var(--space-6);
}

/* ------------------------------------------------------------
   The onward pair that closes the page. A link card carrying a
   page name, one line about it, and the CTA arrow.

   NOT `.tile` with the icon left out: a tile's three-track subgrid,
   gold bottom rule and 52px disc are built around having an icon, and
   there is no glyph in the set for "Our Story" or "Leadership" that is
   not already spoken for by the grid higher up this page. This is the
   simpler card the pair actually needs. It borrows `.tile`'s surface
   and `.brand-panel__more`'s affordance so it still reads as the same
   family, and its hover is `a.tile`'s, to the transition timing.
   ------------------------------------------------------------ */
.next-card {
  display: grid; gap: var(--space-3); align-content: start;
  background: #fff;
  border: 1px solid var(--neutral-200);
  border-radius: var(--radius-lg);
  padding: var(--space-8);
  text-decoration: none; color: inherit;
  transition: box-shadow .15s ease, border-color .15s ease;
}
.next-card:hover { box-shadow: var(--e2); border-color: var(--neutral-300); }
/* An <h2> — these two are section-level on a page whose other sections
   are h2 — set at the card scale, matching `.tile__title`. */
.next-card__title { font: var(--type-h3); letter-spacing: -0.008em; margin: 0; }
.next-card p { margin: 0; max-width: none; font: var(--type-small); color: var(--neutral-600); }
/* `.brand-panel__more` verbatim apart from the align-self it does not
   need — this card has no tagline row to push past. gold-700 is the link
   colour since 2026-08-18 (tokens.css); the arrow is `icon--inline` and
   takes it from currentColor. */
.next-card__more {
  display: inline-flex; align-items: center; gap: var(--space-2);
  font: var(--type-h4); color: var(--gold-700);
}
.next-card:hover .next-card__more { color: var(--ink-900); }

/* ============================================================
   5.11 — Leadership (/about/leadership/). Added 2026-08-19
   (maintainer: overhaul for visual appeal). Everything the page
   renders is shared — `.band`/`.standfirst` (components.css
   §4.12), `.next-card` (§5.10) — except the portrait rail below,
   which is this page's own.

   THIS SECTION USED TO HOLD A SECOND THING: the figure row, which
   set the bio's fourth paragraph as three display numerals on
   `.stat-band`/`.stat` (§4.5). Removed 2026-08-19, later the same
   day as the overhaul that added it (maintainer: "remove the stat
   band from the Leadership page - unnecessary"), and with it
   `.bio-figures`, `.bio-figures__lead` and `.stat__verb` — this
   page was the only consumer of all three. The paragraph is plain
   body copy again; see leadership.njk and `_bioNote` in
   entity.json for what that cost on the content side. The page no
   longer renders §4.5 at all, so nothing here extends it.

   The page does NOT use `.band--resp`, and sets no
   --band-sticky-top: it has no tab bar, so the component's own
   default (the 80px header island + --space-8) is correct, the
   same as /about/.
   ============================================================ */

/* ------------------------------------------------------------
   The portrait rail — the band aside on a page with no headings
   to put there. /responsibility/ and /about/ promote a content-doc
   h2 into the aside; the Leadership copy has no subheadings, so
   inventing them would have been the only way to match those pages.
   The photo, name and title go there instead. All three were
   already on the page, and being sticky is what keeps the officer
   beside their own biography for its whole length.
   ------------------------------------------------------------ */

/* SQUARE SINCE 2026-08-21, WHEN THE PHOTOGRAPH ARRIVED. The slot was
   4:5 for as long as it was empty, on the reasoning recorded here at
   the time: a portrait ratio states what an empty slot is FOR, and
   that "matters more while it is empty than it will after the
   photograph lands." It landed — Josh Landy's headshot is a 1:1
   frame, cropped tight to the subject — so the ratio it was standing
   in for is now a fact rather than a guess, and 1:1 is what shows the
   supplied frame whole. 4:5 would have had `cover` cut a fifth of the
   width off a headshot that has no margin to spare. This is also the
   roster card's ratio (§4.8), so the two agree again.

   CAPPED AT 320px, NOT SET TO THE COLUMN — unchanged, and the note
   written when this was a hatch anticipated exactly this: 320px "is
   what a real headshot will want too — so this number does not change
   on the day the photo arrives." Filling the 5fr column would make it
   453px, the biggest thing on the page and larger than a portrait is
   ever printed.

   The box is shared by both branches of leadership.njk's photo test,
   which is the point of splitting it out: an officer with a portrait
   and an officer still waiting for one occupy the same slot, so the
   rail does not change shape between them. */
.person-rail__photo {
  aspect-ratio: 1 / 1;
  width: 100%;
  max-width: 320px;
  border-radius: var(--radius-lg);
}

/* The photograph. `height: auto` IS LOAD-BEARING, not boilerplate:
   the img carries `height="720"` so the box is reserved before the
   file loads, and a presentational height attribute is a specified
   height — which suppresses `aspect-ratio` entirely. Without this the
   slot renders 320×720, a full-height column of portrait with its
   sides cropped away by `cover`. Same reason `.why-ingot` carries it
   (§5.10). Remove one of the two and you must remove both.

   `object-fit` is then insurance, not a crop: the shipped file is
   square and so is the box, so it has nothing to trim today — it is
   here so a later portrait at some other ratio fills the slot instead
   of stretching in it. No background and no border; the frame is a
   photograph with its own ground and it defines its own edge against
   the white band, the same call `.why-ingot` makes. */
img.person-rail__photo {
  display: block;
  height: auto;
  object-fit: cover;
}

/* The empty slot, for a second officer with no portrait yet. `.tbd`
   (base.css) supplies the hatch, the dashed border and a radius the
   box above overrides; it is `display: inline-block` there, so this
   restates grid to centre the label and to make aspect-ratio apply.
   That is the same override `.person__photo` carried. The hatch STAYS
   VISIBLE — never swap it for a silhouette or a grey placeholder
   (CLAUDE.md §5). */
.person-rail__photo.tbd {
  display: grid;
  place-items: center;
  padding: var(--space-4);
}

/* BELOW THE BAND'S SPLIT THE RAIL IS A ROW, not a stack. Once the
   aside sits above the prose instead of beside it, a 320px portrait
   over the name is ~400px of placeholder before a phone reader
   reaches the first word of the biography — half the viewport, spent
   on the one thing on this page that is not finished yet. Photo
   beside name is the identity block a phone wants, and it costs
   ~250px of scroll. It reads the same way once the real headshot
   lands, so this is not hatch-specific staging.

   Written as the mobile-first default with the stack restored at
   64em, matching the min-width direction the rest of the file is
   authored in. 64em is the same breakpoint `.band` splits at
   (components.css §4.12) — the two must not drift, or the rail goes
   sideways in a column that is already narrow. */
.person-rail {
  display: grid;
  grid-template-columns: minmax(0, 150px) 1fr;
  column-gap: var(--space-6);
  align-items: center;
}
@media (min-width: 64em) {
  .person-rail { display: block; }
  .person-rail__photo { margin-bottom: var(--space-6); }
}
/* No `font:` here. This is the aside's <h2> and it takes the 34px
   display step from base.css, so the name sets at the same size as
   "Building Trusted Online Precious Metals Brands" does one page
   over — which is the point: the rail occupies the slot a section
   heading occupies, so it has to carry a section heading's weight.
   `.band__aside h2` already zeroes the bottom margin. */
.person-rail__id { line-height: 1.15; }
/* `.person__title`'s declaration (§4.8) plus the block display it
   needs here. Not a reuse of that class: it belongs to the dormant
   roster card, and a live page reaching into a component nothing
   renders is a dependency that breaks silently the day the card is
   deleted. Four declarations is the cheaper copy. */
.person-rail__title {
  display: block;
  margin-top: var(--space-4);
  font: var(--type-small);
  color: var(--neutral-600);
  text-transform: uppercase;
  letter-spacing: 0.16em;
}

/* ============================================================
   5.12 — Our Story (/about/our-story/). Added 2026-08-19
   (maintainer: overhaul for visual appeal). Almost everything the
   page renders is shared — `.band`/`.standfirst` (components.css
   §4.12), `.stat-band`/`.stat` (§4.5), `.next-card` (§5.10). Two
   things are this page's own, and both exist for the same reason:
   it is the first page to put either of those components on a
   DARK surface.

   WHY THESE LIVE HERE. One page uses them (CLAUDE.md §6) — the same
   rule that kept §5.11's `.stat__verb` out of §4.5 until that rule
   was deleted with its markup.
   They are named for the components they extend so the relationship
   is visible in both the template and the stylesheet. If a second
   page ever puts a band or a stat band on ink-900, promote the
   `.section--dark` block below to components.css §4.12/§4.5 — it is
   surface handling, not page styling, and nothing in it is specific
   to this page.
   ============================================================ */

/* The era year, leading its section's <h2> in the sticky aside. This
   page's section marker, standing in for the icon the other two
   `.band` pages put there — see the template's note on why no glyph
   in the set can honestly sit here.

   The type is NOT a new step on the display scale: it is
   `.timeline--horizontal .timeline__year` (components.css §4.7)
   verbatim — same clamp, same --type-h1 tracking, same tabular lining
   figures. Those are the four years of this same history rendered on
   the homepage, so the two treatments are deliberately one voice. The
   32px clamp floor is likewise load-bearing for the same string: it is
   what keeps "2014–Present" on one line at 390px.

   `display: block` because it sits INSIDE the <h2>, not before it —
   that is what keeps the year in the heading's text content for search,
   for AEO extraction and for a screen reader, while still stacking
   above the title visually. gold-700 is the only gold legal as text on
   white (tokens.css). */
.band__year {
  display: block;
  font: 300 clamp(32px, 3.4vw, 44px)/1.05 var(--font-display);
  letter-spacing: -0.018em;
  font-variant-numeric: tabular-nums lining-nums;
  color: var(--gold-700);
  margin-bottom: var(--space-3);
}

/* The stat band breaking out of the band grid, in its own container so
   three figures get the full 1200px rather than the 5fr/7fr body
   column's 635px — at which width `.stat-band`'s auto-fit track
   (components.css §4.5) drops to a single column and "$175 Million+"
   wraps. The margin is what separates it from the prose above, and it
   has to be stated here: `.stat-band`'s own --space-8 top margin is
   zeroed by its `:first-child` rule, which fires because the band IS
   the first child of this container. */
.story-figures { margin-top: var(--space-12); }
.story-figures > p:last-child { margin-bottom: 0; }

/* THE BAND'S DESKTOP TRACKS ARE CONTENT-SIZED HERE, NOT `1fr`.
   components.css §4.5 gives every band `repeat(auto-fit, minmax(220px,
   1fr))` above 64em. Across this container's full 1200px that resolves to
   three 378.7px tracks, but the three values render 254px, 310px and 200px
   wide — so columns 1 and 3 carry 124px and 178px of slack INSIDE the
   track, and the numerals read as scattered rather than as one group.

   Narrowing the band with a max-width does not fix it. "$175 Million+" needs
   ~310px at .stat__value's 4vw/56px display size, so three EQUAL tracks
   cannot go below 3x340 + 2x32 = 1116px before that value wraps — which is
   the failure the comment above was written to prevent, arriving from the
   other direction. Content-width tracks have no such floor: each stat takes
   exactly its own width and the column-gap becomes the real, chosen air
   between them. The group lands ~956px wide and `justify-content: center`
   centres it in the 1200px container.

   `repeat(3, auto)` is safe as a literal 3 because this rule is scoped to
   .story-figures, which passes exactly three statKeys (see our-story.njk).
   The homepage's four-stat band is untouched and keeps the auto-fit rule.
   A fourth stat added here would need this count changed with it.

   Scoped to 64em and up because below it components.css owns the layout:
   the 2-up grid and the :nth-child(3):last-child capstone that centres the
   orphaned third stat. Do not widen this query over them. */
@media (min-width: 64em) {
  .story-figures .stat-band {
    grid-template-columns: repeat(3, auto);
    justify-content: center;
    column-gap: var(--space-24);
  }
}

/* CENTRED ON PURPOSE, BY REQUEST (maintainer, 2026-08-25) — and a
   deliberate departure from base.css's reading-measure note, which reads
   "NO centered paragraph blocks - the old site centered everything and it
   fights scanning." That note still governs body copy everywhere else; this
   one sentence is a caption under a centred three-figure band, not prose to
   scan, and left-aligning it hung it off the bottom-left corner of an
   otherwise symmetric composition. Do NOT restore text-align: left on
   base.css's authority.

   No max-width: base.css's `p` rule already caps this at --measure (68ch,
   637px measured), which is what margin-inline: auto then centres. Adding a
   second cap here would only be a number to drift out of sync.

   THE LEFT HAIRLINE (maintainer, 2026-08-25) — 3px gold-500 over
   --space-6, which is `.quote`'s rule (components.css §4.x) to the pixel
   and the same 3px the era spines above now carry. gold-500 rather than
   the spines' lit gold-700 because this rule is static: gold-500 is what
   `.quote` and `.timeline` already use for a standing left rule, and
   gold-700 at full height beside a one-sentence caption reads as weight
   the sentence has not earned.

   THE CLASS IS NOT REUSED, ONLY ITS VALUES. `.quote` also sets
   `margin: var(--space-12) 0`, whose inline half is 0 — it would
   override the `margin-inline: auto` on the line below and drop this
   block hard against the container's left gutter, which is precisely the
   composition the centring note above was written to prevent.

   PADDING ON BOTH SIDES, and the right half is load-bearing rather than
   tidy. The text is CENTRED INSIDE this box, so any padding the left
   carries alone shifts every line 13.5px right of the three figures
   above it. Matching it on the right keeps the sentence's centre on the
   container's centre, which is the whole reason it is centred. The 3px
   is in the left figure because border-box sizing counts the border in.

   THE RULE IS STRAIGHT AND THE TEXT EDGE BESIDE IT IS NOT — that is the
   known cost of keeping both requests, put to the maintainer on
   2026-08-25 with the alternative (rule plus left-aligned text) shown
   beside it, and this is the option chosen. Do not "fix" the ragged edge
   by left-aligning the text; that reverses the centring request above. */
.story-figures__note {
  text-align: center;
  margin-inline: auto;
  border-left: 3px solid var(--gold-500);
  padding-left: var(--space-6);
  padding-right: calc(var(--space-6) + 3px);
}

/* ------------------------------------------------------------
   Dark-surface handling for the "Today" section. DORMANT since
   2026-08-21, when that section stopped being `.section--dark`
   (maintainer: "there doesn't need to be a full black background
   section for those stats. Instead, put that stat band"). Nothing on
   the site now renders a `.standfirst`, a `.band__year` or a `.stat`
   on ink-900. The five elements still carrying `.section--dark` are the
   homepage Our Story band, the brand-page header, and the three
   `.closing-card`s on /expertise/, /responsibility/ and the brand pages;
   none of them holds any of these three components, so every rule below
   currently matches nothing.

   RETAINED RATHER THAN DELETED, the same call §5.8 makes for the
   Flourish drill-down. Three of the four are not styling preferences
   but corrections that a dark surface REQUIRES from the first render,
   and the reasoning is worth more than the four lines it saves:

     `.standfirst` sets `color: var(--ink-900)` outright (§4.12), i.e.
     #0B0E10 on #0B0E10 — an invisible paragraph, not a low-contrast
     one. `.section--dark` cannot reach it, because that rule only
     repaints h1/h2/h3 and inline links.

     `.stat__note` sets `color: var(--neutral-600)` (§4.5), which is
     7.6:1 on white and about 2:1 on ink-900 — below any floor.
     --neutral-400 is the dark-surface equivalent at 7.8:1, and is what
     the footer already uses for secondary copy on ink.

     `.band__year` is --gold-700, the only gold legal as text on white
     (its own rule earlier in this section says why); --gold-300 is that
     token's opposite number on ink, at 9.4:1.

   `.stat__value` is the odd one out — a change of intent rather than a
   fix. It would inherit --neutral-200 and be perfectly legible; gold-300
   was chosen because it is what the homepage timeline's years already do
   on ink. `.stat__label` was left alone: it inherits --neutral-200 and
   needs nothing.

   The hover hairline stays --gold-500 and stays decorative; it carries
   no meaning on its own, so the 3:1 non-text floor did not apply there
   any more than it does on the light band the figures now sit on.
   ------------------------------------------------------------ */
.section--dark .standfirst { color: #fff; }
.section--dark .band__year { color: var(--gold-300); }
.section--dark .stat__value { color: var(--gold-300); }
.section--dark .stat__note { color: var(--neutral-400); }

/* ------------------------------------------------------------
   The era spines (2026-08-25, maintainer: "a scrolling timeline
   bar to the left side of the Our Story page", then "vertically
   down below the h2s - not the far left of the screen"). Four
   segments, one per era band, each hanging under its own heading
   in the aside column and filling gold as the read head crosses
   it. js/story-progress.js measures and drives them.

   THE FIRST BUILD WAS ONE FIXED HAIRLINE AT THE VIEWPORT EDGE,
   `position: fixed; left: 16px`, and was rejected on sight. Do
   not restore it. The line belongs in the h2 column; the browser
   margin is the one place the request rules out.

   THEY NAVIGATE NOTHING. No labels, no links, no focus, aria-
   hidden in the template, pointer events out of their own way.
   The sticky `.band__year` asides (components.css §4.12) are
   still this page's wayfinding — §4.12 calls them "the reason
   this needs no scroll-spy JS", and that stays true, because
   nothing here is a scroll target. Delete this block and its
   script and the page loses a flourish and nothing else.

   NOT SCROLL CONTAINERS, so the site-wide ban on in-page
   horizontal scrollers does not reach them, for the same reason
   `.trust-marquee__track` was never in scope: nothing here
   scrolls or can be scrolled. They are painted from a scroll
   position.

   LOOKING AHEAD HAS NO SEGMENT. Its aside carries the 320px
   `evolve` ring directly under the h2, which is precisely the
   space a segment would occupy — see that section's note in the
   template. Geometry, not an editorial view.
   ------------------------------------------------------------ */

/* SCOPED BY :has(), so no other `.band` gains a containing block.
   /about/ and /responsibility/ share this component and neither
   renders a spine, so neither matches. Same technique §5.13
   already relies on. */
.band:has(.story-spine) { position: relative; }

/* THE HEADING OCCLUDES THE LINE (maintainer, 2026-08-25: "ensure
   that you cannot see the line cutting behind the 2009, Sell Your
   Gold & Recycle Platinum Are Founded").

   `.band__aside` is sticky and painted with no background, so as it
   travelled down over the segment the rule showed through every gap
   between the heading's glyphs. It was worst on the descenders and
   the counters, where a 3px vertical stroke crossing a letterform at
   its narrowest point reads as a rendering fault rather than as
   depth. Centring the line in 2026-08-25's earlier pass is what
   exposed it — at the column's left edge the rule met only the first
   glyph of each line.

   AN OPAQUE PLATE, NOT A RECOMPUTED TOP. The alternative was to have
   the script push --story-spine-top down as the heading pins, so the
   segment always begins below it. That shrinks the segment, and the
   fill is a fraction OF the segment — the same scroll position would
   report a different fraction depending on how far the heading had
   travelled. Occlusion leaves every measured number alone.

   TWO SURFACES, BECAUSE THE ERAS ALTERNATE. 2009 and 2014-Present sit
   on `.section--muted` (--neutral-100) and 2014 on a plain `.section`,
   which paints nothing and shows the body's --neutral-50. A plate has
   to match whichever is behind it or it becomes a visible card. The
   `:has()` scope is the same one the containing block above uses, so
   /about/ and /responsibility/ — which share `.band` and render no
   spine — gain no background.

   THIS IS WHY THE SPINE PRECEDES THE ASIDE IN THE DOM, and the
   requirement is now harder than it was. Both are positioned at
   `z-index: auto`, so document order decides: ordered as they are,
   the plate paints over the line. Ordered the other way the line
   would paint over an opaque plate and be MORE visible than before
   this rule existed, not less. See the note in our-story.njk. */
.band:has(.story-spine) .band__aside { background: var(--neutral-50); }
.section--muted .band:has(.story-spine) .band__aside { background: var(--neutral-100); }

/* The plate runs --space-6 PAST the heading, so the line emerges the
   same distance below it at every scroll position. Without this the
   gap is --space-6 while the heading sits at the top of its band —
   the offset `.story-spine`'s own `top` adds — and zero the moment it
   pins, with the rule appearing flush against the descenders.

   `background: inherit` rather than the two colours again: a
   pseudo-element inherits from its originating element, so this picks
   up whichever of the pair above matched and cannot drift from it.

   Absolutely positioned against the aside, which is already a
   containing block because it is sticky. Nothing else lives in this
   column on the three banded eras — the one aside that carries more
   than a heading is Looking Ahead's `evolve` ring, and that band has
   no spine, so `:has()` excludes it and the ring is never covered. */
.band:has(.story-spine) .band__aside::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 100%;
  height: var(--space-6);
  background: inherit;
}

/* ABSOLUTE, THEREFORE NOT A GRID ITEM — the 5fr/7fr columns are
   untouched and no third track appears. It cannot live inside
   `.band__aside` where it visually belongs: that element is
   sticky, so a line parented to it would pin with the heading
   rather than drop through the section, and being positioned it
   would also trap any absolute child of its own.

   CENTRED IN THE ASIDE COLUMN since 2026-08-25 (maintainer:
   "centered below the h2s, not off to the left"). It ran at the
   column's left edge, flush with where the heading's own glyphs
   start, from the day it was built until that request.

   The centre is not a number a stylesheet can hold. `.band` is
   minmax(0, 5fr) / minmax(0, 7fr) with an --space-8 gap, so the
   aside's midpoint moves with the viewport — ~226px in from the
   padding edge at the full 1200px container, less at every width
   below it. The script measures the aside it is ALREADY measuring
   for --story-spine-top and writes --story-spine-left from the
   same read, so the two numbers can never disagree about which
   element they describe. Re-splitting the 5fr/7fr tracks moves
   the line with them and touches nothing here.

   NEGATIVE MARGIN, NOT `translateX(-50%)`, and that is not a
   preference. A transform makes this element a stacking context,
   and the paint-order argument above — the sticky heading slides
   OVER the line — holds only while both elements stay at
   `z-index: auto`. The margin sits the stroke ON the measured
   centre; `left` alone would start it there.

   The 32px fallback is `.container`'s own >=64em padding, i.e.
   the pre-2026-08-25 position. It is needed because `.band` and
   `.container` are the SAME element on this page
   (`<div class="container band">`), so absolute positioning
   resolves against the padding box and `left: 0` would put the
   line out in the gutter. Nothing paints down that path — the
   template ships every segment `hidden` and only a successful
   measure unhides it — but the gutter is the treatment that was
   rejected outright, so the fallback does not point at it.

   THE TAIL FADES rather than stopping, so a segment dissolves
   into the band's own bottom padding instead of squaring off on
   an edge that means nothing. Four segments cut flat is most of
   why they read as four rules rather than one chronology.

   IT IS A GRADIENT BACKGROUND, NOT `mask-image`, AND THAT IS THE
   SECOND ATTEMPT. A mask was tried first and silently deleted
   both dots: `mask-image` also sets the mask painting area, and
   the initial `mask-clip: border-box` clips the element's paint
   to that box — which here is the 3px stroke. The era node and
   the read head are 17px across including their halos, so every
   pixel of them fell outside it and neither ever drew. The fix is
   not `mask-clip: no-clip`; that is the right property but its
   support is too new to bet two visible affordances on. A
   background gradient clips nothing, so the dots are untouched.

   THE FILL CARRIES THE SAME RAMP, at the same stops, which is why
   it is clipped rather than scaled — see its own rule.

   The top is the one value CSS cannot derive: how tall this
   band's heading set. The script writes --story-spine-top; the
   air below the heading is added here so it stays on the token
   scale. The fallback keeps a scriptless segment at the top of
   the band rather than at a nonsense offset — though the
   template ships them `hidden`, so that path paints nothing.

   `min-height` is a floor for the short bands. "Today" is the
   shortest — a standfirst and one paragraph beside a two-line
   heading — and without it that segment would be a stub of a
   few pixels. Setting top, min-height and bottom together is
   deliberately over-constrained: per the absolute-positioning
   rules the resolved height wins and `bottom` is dropped, so a
   floored segment grows downward into the section's own padding,
   which is empty. */
.story-spine {
  --story-spine-w: 3px;
  position: absolute;
  left: var(--story-spine-left, 32px);
  margin-left: calc(var(--story-spine-w) / -2);
  top: calc(var(--story-spine-top, 0px) + var(--space-6));
  bottom: 0;
  width: var(--story-spine-w);
  min-height: 96px;
  background: linear-gradient(to bottom, var(--neutral-200) 0 72%, transparent);
  border-radius: var(--radius-pill);
  pointer-events: none;
}
/* The script unhides each segment only once it has measured it.
   Stated explicitly because the UA sheet's [hidden] rule is the
   only thing otherwise holding back an element this block gives a
   position and a size to. */
.story-spine[hidden] { display: none; }

/* NOT RENDERED BELOW 64em, matching .hero__viz (§5.1) and the
   default for .resp-ring (§5.9). Below that breakpoint `.band`
   stops being two columns: the aside stacks ON TOP of its prose
   and stops being sticky, so a segment hanging under the heading
   would be drawn straight down through the paragraphs it is
   supposed to sit beside. There is no aside column to put it in
   because there is no aside column. story-progress.js needs no
   companion breakpoint — a display:none segment returns no client
   rects and the update loop returns early on that alone. */
@media (max-width: 63.999em) {
  .story-spine { display: none; }
}

/* CLIPPED FROM THE BOTTOM, NOT SCALED — changed 2026-08-25 when
   the tail fade arrived, and the two are the same decision. The
   fill carries the identical 72% ramp the track above does, and a
   gradient painted on a scaled box scales with it: at scaleY(0.5)
   the ramp would land at 36% of the segment and the gold would
   dissolve halfway through an era the reader is still in. Under
   `clip-path` the element is never resized, so its gradient stays
   anchored to the segment's full height however little of it is
   revealed, and the two ramps line up at every fraction.

   THE COST, stated plainly because the note this replaces claimed
   the opposite: `transform: scaleY()` rode the compositor and a
   per-frame `clip-path` does not — it repaints. Four 3px-wide
   boxes, so it does not signify, but the file should not say
   "off the layout path" about something that is not.

   `inset(0 0 100% 0)` — clipped away entirely — is the unread
   state, and the script writes the bottom inset down toward 0 as
   the era fills. Top-anchored for the same reason
   `transform-origin: top` was: it has to mean "the first 40% of
   this era", not a bar growing from its middle. */
.story-spine__fill {
  position: absolute;
  inset: 0;
  background: linear-gradient(to bottom, var(--gold-700) 0 72%, transparent);
  border-radius: inherit;
  clip-path: inset(0 0 100% 0);
}

/* The era node, at the head of each segment. This is what makes
   four lines read as one timeline rather than four rules: a
   marked point per era, sitting under its own year. 9px against
   the 3px line, so it still reads as a node once the fill has
   passed it and both are gold-700 — extent carries it where
   colour cannot. --neutral-300 unlit is one step off the
   --neutral-200 track: present, not yet arrived at.

   THE HALO IS THE SAME DEVICE THE READ HEAD BELOW WEARS, which
   is the point: node and head are one object in two states, an
   era's start and the reader's position in it, rather than two
   unrelated dots. gold-300 is pale enough to read as a glow on
   both surfaces this page alternates between — it is drawn on
   white and on --neutral-100 and has to hold on each.

   The colour and the halo are the only motion here and base.css's
   reduced-motion block already clamps both. */
.story-spine::before {
  content: "";
  position: absolute;
  left: 50%;
  top: 0;
  width: 9px;
  height: 9px;
  margin-left: -4.5px;
  margin-top: -4px;
  border-radius: var(--radius-pill);
  background: var(--neutral-300);
  box-shadow: 0 0 0 0 var(--gold-300);
  transition: background-color 200ms ease, box-shadow 200ms ease;
}
.story-spine.is-active::before {
  background: var(--gold-700);
  box-shadow: 0 0 0 4px var(--gold-300);
}

/* THE READ HEAD, riding the leading edge of the fill — the thing
   that makes this an instrument rather than a bar that fills. The
   script writes --story-spine-y, the same fraction the fill is
   scaled by resolved against the segment's own pixel height, so
   the head and the top of the unfilled track are always the same
   line.

   `::after` RATHER THAN AN ELEMENT: `::before` is the era node and
   `::after` was free, so this cost no markup — the four segments
   in the template are still one empty span apiece.

   THE HONEST COST, against the fill's own note above: this is a
   transform driven by a custom property, so each frame is a style
   recalc and a repaint of the pseudo where the fill's inline
   `style.transform` takes the compositor fast path. Four pseudos
   at rAF is not measurable, but the file should not imply the
   whole mechanism stays off the layout path when a quarter of it
   does not.

   OPACITY IS WHAT TRANSITIONS, NEVER THE TRANSFORM. A transition
   on `transform` would smooth the per-frame translate and the head
   would trail the scroll by its own duration. Opacity carries the
   arrival; position is always immediate. */
.story-spine::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 0;
  width: 7px;
  height: 7px;
  margin-left: -3.5px;
  margin-top: -3.5px;
  border-radius: var(--radius-pill);
  background: var(--gold-700);
  box-shadow: 0 0 0 4px var(--gold-300);
  transform: translateY(var(--story-spine-y, 0px));
  opacity: 0;
  transition: opacity 200ms ease;
}
.story-spine.is-active::after { opacity: 1; }

/* ============================================================
   5.13 — Contact (/contact/). REBUILT 2026-08-25 (maintainer:
   revamp the page for visual appeal, and — asked explicitly —
   "spend freely, rebuild the page's proportions").

   THIS SECTION IS SHORTER THAN THE ONE IT REPLACES, and that is the
   headline result rather than a side effect. Gone with the rebuild:
   `.contact-shell`, `.contact-layout`, `.contact-layout__col`,
   `.contact-panel`, `.contact-row` and its four children,
   `.contact-brands`, the `.page__lede` block, two `:has()`-scoped
   padding trims, and ALL FIVE PAGE-SCOPED OVERRIDES ON `.brand-panel`.
   Roughly 700 lines became roughly 200.

   ------------------------------------------------------------
   WHY, because three earlier passes argued the other way and each was
   right about the page it was looking at.

   2026-08-19 fixed a genuinely flat page — one --measure column in a
   1200px container, five typographically flat headings, the right ~45%
   of the screen empty. 2026-08-20 gathered the result into one white
   shell on the muted band. 2026-08-21 squeezed that shell hard to
   "minimise vertical scroll", trimming row padding, section padding and
   header padding, and widening the route column to 400px so the h2 and
   the email could each hold one line.

   Every one of those steps worked. Together they produced a page whose
   entire content column ended at y=830 on a 1400px viewport, with the
   dark footer taking everything below it — one card floating above a
   much taller footer. The page read as a fragment.

   THE ROOT CAUSE WAS THE 400px ROUTE TRACK, NOT A LACK OF ORNAMENT, and
   this is the part worth carrying forward. Pinning three contact routes
   into a fixed 400px column left the brand cards 308px against the
   homepage's 519px. 308px is what forced the five overrides — fill,
   padding, logo height, head-row gap, tagline size — and the hollow
   middles those cards showed (tagline, gap, "Explore") were the visible
   symptom of the same squeeze. The old notes called the left column and
   the brand card "a zero-sum pair with no slack sitting idle in
   either", which was true, and the way out of a zero-sum pair is to
   stop making them share a row.

   So this pass widens rather than decorates. The routes get the whole
   container as a 3-up row; the brand pair gets the whole container as
   the component's own `.brand-panels` grid at ~544px a card; and the
   page gains a dark arc hero to anchor it.

   ------------------------------------------------------------
   THE PAGE IS STILL DELIBERATELY NOT `.band` (components.css §4.12),
   which is what /expertise/, /responsibility/ and both /about/ pages
   use. That 2026-08-19 decision is untouched here: a sticky left rail is
   wayfinding for a long prose scroll, and this page is a short directory
   of routes. If a later pass "restores consistency" by converting this
   to bands, it is reversing a decision, not tidying up.

   NO NEW TOKENS. No new breakpoints beyond the ones the shared
   primitives already carry — the route row rides `.grid--3` (base.css:
   3-up at 64em, 2-up at 40em, stacked below) and the brand pair rides
   `.brand-panels` (components.css §4.3: 2-up at 70em). The only media
   queries written here are the hero's own, and two of those exist purely
   to mirror `.hero`'s nav-overlap arithmetic (§5.1).
   ============================================================ */

/* ------------------------------------------------------------
   BEAT 1 — THE ARC HERO.

   The page's anchor. `.section--dark` supplies the ink-900 fill and the
   white/gold-300 text ramp (base.css); `.section--arcs` supplies the
   positioning and `overflow: hidden` (§5.3); partials/brands-arcs.njk
   supplies the motif. NOTHING HERE RESTATES ANY OF THAT — the rules
   below are padding and the two-column grid, and that is all this beat
   needed, which is why reusing the homepage's devices was the cheap
   answer as well as the right-looking one.

   PADDING SITS BETWEEN `.section` AND `.hero` ON PURPOSE. `.section` is
   32/48 and is proportioned for an ordinary band; `.hero` is 96/128 and
   is proportioned for the homepage's full-viewport opener. This is
   64/96 — a hero for an inner page. There is no --space-20 on the
   scale (tokens.css runs 12, 16, 24, 32), so 16 -> 24 is the step, not
   a rounding.
   ------------------------------------------------------------ */
.contact-hero { padding-block: var(--space-16); }
@media (min-width: 64em) { .contact-hero { padding-block: var(--space-24); } }

/* RUNS UP BEHIND THE FLOATING NAV, and this is required rather than
   decorative. The header is in normal flow, so without it the ink-900
   band would start 88px down the page and leave a --neutral-50 strip
   across the top with the nav pill floating on it — which is precisely
   the artefact §5.1 describes and solves for the homepage hero.

   The arithmetic is `.hero`'s, copied deliberately and not adapted: 88px
   is the >=900px header band (components.css §4.1), pulled up as a
   negative margin and added back as top padding so the copy does not
   slide under the pill. BOTH VALUES MUST MOVE TOGETHER if that band is
   ever resized.

   >=900px ONLY, for §5.1's reason: below that the header is an opaque
   full-bleed bar, so the band would simply hide behind it and the result
   would look identical to not doing this at all.

   THE SECOND 64em BLOCK IS NOT REDUNDANT AND MUST STAY LAST. The 64em
   `padding-block` rule above sets the top and bottom together, so it
   would overwrite the padding-top this restores. 64em (1024px) is above
   900px, so the two queries overlap and source order is what resolves
   them — exactly the ordering trap §5.1 documents on `.hero`. */
@media (min-width: 900px) {
  .contact-hero { margin-top: -88px; padding-top: calc(var(--space-16) + 88px); }
}
@media (min-width: 64em) {
  .contact-hero { padding-top: calc(var(--space-24) + 88px); }
}

/* THE CONVERGENCE IS MASKED OUT OF THE MIDDLE OF THE BAND, and this is
   the one rule the two DARK callers genuinely had to add to the motif.

   SHARED WITH THE HOMEPAGE OUR STORY BAND since 2026-08-25, which took
   the same `.section--arcs` + partial pairing (index.njk). It is one
   selector list rather than two rules because the problem is identical
   and the numbers below were re-measured against that band and held: at
   1440 the Our Story section is 1425x598, so the knot lands at (712,
   299) — inside `.story__lede`, which runs x 145->776 and y 123->414.
   A starburst behind the right-hand words of the lede, exactly the
   artefact described below. The mask is percentage-based, so it tracks
   the band when the timeline disclosure opens and the section grows.

   IT STAYS IN §5.13 rather than moving up to §5.3 beside the base
   `.brands-arcs` rule. §5.3 is where the motif's own placement and
   colour live and #brands must NOT take this; a modifier promoted to
   components.css would be worse still, since that file loads before
   this one and would lose the `mask-image` cascade to §5.3's base rule
   outright.

   partials/brands-arcs.njk draws 28 arcs that all sweep into a single
   destination at viewBox (720, 310) — dead centre. On the homepage that
   knot is never seen, because `.brands__shell` is painted over exactly
   that spot; §5.3's own mask note says so outright ("the centre is
   precisely the region .brands__shell already covers"). The motif was
   therefore drawn on the assumption that something covers its middle.

   NOTHING COVERS IT HERE. Measured at 1440x567 before this rule, the
   knot landed at roughly (705, 320) — inside the lede paragraph, a dense
   starburst behind "brands, please contact us directly." It stayed
   legible, but it pulled the eye to a point that means nothing and made
   the copy look like it had been dropped onto a busy plate.

   THIS IS NOT THE RADIAL MASK §5.3 REJECTED — it is its inverse, and the
   distinction is the whole reason it is allowed here. That one faded
   from the centre OUTWARD, which on the homepage destroyed the outer
   band that is the only visible part of the field there. This one fades
   the CENTRE and keeps the outer band, because here the visibility is
   the other way round. Read §5.3's note before touching either; they are
   opposite fixes to opposite problems and neither generalises.

   TWO MASK LAYERS, COMPOSITED WITH `intersect`. The linear layer is
   §5.3's own vertical fade, copied unchanged — the band abuts the light
   routes section below, and without it 28 arcs end as cut stroke-ends
   against that boundary. The radial layer punches out the middle. The
   composite must be `intersect`: the default is `add`, under which the
   radial's transparent centre is simply refilled by the linear's opaque
   middle and the rule does nothing. That is also the graceful
   degradation — a browser without mask-composite renders the field
   exactly as it did before this rule, rather than losing the motif.

   The -webkit- pair is retained for the same Safari versions §5.3's
   prefix serves; the legacy keyword for `intersect` is `source-in`. */
.contact-hero .brands-arcs,
.story-band .brands-arcs {
  -webkit-mask-image:
    radial-gradient(58% 62% at 50% 50%, transparent 0%, #000 78%),
    linear-gradient(to bottom, transparent 0%, #000 14%, #000 86%, transparent 100%);
  -webkit-mask-composite: source-in;
  mask-image:
    radial-gradient(58% 62% at 50% 50%, transparent 0%, #000 78%),
    linear-gradient(to bottom, transparent 0%, #000 14%, #000 86%, transparent 100%);
  mask-composite: intersect;
}

/* 5fr/7fr, which is `.band`'s ratio (components.css §4.12) and is used
   here for the same reason it works there: a short label-and-title block
   against a longer passage of lede. The title column needs to hold
   "Contact Beyond79" — a two-word h1 that fits 5fr at every width the
   split applies to — and the lede is three lines of `.standfirst`.

   THE SPLIT IS AT 64em, MATCHING partials/brands-arcs.njk's OWN
   BREAKPOINT, and the pairing is deliberate: below 64em the arc field is
   `display: none` (§5.3) and the hero is a single stacked column, so the
   band is plain ink-900 with copy on it and there is nothing to align
   against. The two must move together — a split without the motif leaves
   the right column looking empty, and the motif without the split puts
   arcs behind a full-width paragraph.

   `align-items: start`, not the default stretch: the two columns are
   unequal in height by design and the lede must begin level with the
   title's cap height rather than centring against it. This is the same
   correction the old `.page__header--split:has(.page__lede)` rule made
   for the same two blocks in their previous home. */
.contact-hero__inner { display: grid; gap: var(--space-6); }
@media (min-width: 64em) {
  .contact-hero__inner {
    grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
    column-gap: var(--space-12);
    align-items: start;
  }
}

/* Margin resets on both columns, so the grid gap is the only spacing
   between them and the h1's own block margins cannot open a hole at the
   top of the band. Written as `> :first-child` / `> :last-child` rather
   than naming the h1 and the p, so the pair keeps working if the eyebrow
   is ever dropped or a second paragraph added. */
.contact-hero__title > :first-child,
.contact-hero__lede > :first-child { margin-top: 0; }
.contact-hero__title > :last-child,
.contact-hero__lede > :last-child { margin-bottom: 0; }

/* THE LEDE'S h2 MUST NOT SET AT THE SECTION-HEADING STEP. It sits beside
   the h1 and would read as a competing title rather than as the label on
   a lede. --type-h4 is the step the route labels in beat 2 use, and the
   eyebrow above the h1 is smaller still, so the band reads
   title -> lede-label -> lede.

   CARRIED OVER VERBATIM from the deleted `.page__header--split
   .page__lede h2` rule — same font step, same margin — with only the
   colour changed, because `.section--dark h2` (base.css) paints headings
   #fff and neutral-600 would be unreadable on ink-900. gold-300 is the
   dark surface's own label colour, already used for `.eyebrow` there,
   and it measures 8.9:1 on ink-900. It must beat `.section--dark h2` on
   source order, which it does — base.css loads before pages.css. */
.contact-hero__lede h2 {
  font: var(--type-h4);
  color: var(--gold-300);
  margin: 0 0 var(--space-2);
}

/* ------------------------------------------------------------
   BEAT 2 — THE THREE ROUTES, 3-UP ON THE LIGHT SURFACE.

   THIS IS THE 2026-08-19 ARRANGEMENT RESTORED. The header comment on the
   version of this section that ran until today described the page as
   "a three-up row of contact-method cards on the light surface, and the
   two brands as a pair of cards on the muted one" — that is this. The
   2026-08-20 consolidation into a single shell is what had replaced it,
   and the note left behind then said the row form "was sized for a
   1136px container and this column is 453px". The container is 1136px
   again, so the form fits again.

   IT ADDS NO BREAKPOINT. `.grid--3` (base.css) is 3-up at 64em, 2-up at
   40em and stacked below, and the cards need nothing else — unlike the
   route panel it replaces, whose whole three-tier apparatus existed to
   stop one unwrappable email address from setting the height of a 400px
   column. A ~357px card at 1136px holds the 256px email on one line with
   69px to spare, so the problem that drove all of that is gone rather
   than solved.

   THE CARDS STACK THEIR CONTENTS — disc, then label, then value —
   instead of seating the icon beside the text. The old row arrangement
   spanned the icon across two grid rows specifically so its 52px became
   a floor the wrapped email could hide inside; that was a height trick
   for a narrow column and it buys nothing in a wide card. Stacked is
   also `.tile`'s arrangement (components.css §4.11), which is what these
   now sit closest to visually.
   ------------------------------------------------------------ */
.contact-routes__heading { margin-top: 0; margin-bottom: var(--space-8); }

/* ONE PANEL, NOT THREE CARDS (maintainer, 2026-08-25: "better contain
   the Address + Phone + Email rather than have them spanned out
   horizontally, which leaves a lot of white space"). This replaces a
   `.grid--3` row of three `.contact-card`s that shipped earlier the same
   day. Measured before the change: three 357px cards across the full
   1136px container, all three stretched to 232px by Address's three
   authored lines, with Phone and Email holding one line each in a box
   sized for four.

   THE COLUMNS ARE CONTENT-SIZED, AND THAT IS THE WHOLE REASON THIS
   PANEL STOPS SHORT OF THE CONTAINER. `repeat(3, max-content)` sizes
   each route to what it actually needs instead of giving all three an
   equal share of whatever is going.

   AN EQUAL 1fr SPLIT CANNOT BE CONTAINED — this was derived before
   choosing, not after. Every column of an equal split is as wide as the
   widest content, which is Address: 52px icon + 16px gap + 229px for
   "6401 Congress Ave, Suite 175" + 48px padding = 345px. Three of those
   is 1035px, which is the full container again. So "equal columns" and
   "contained" are mutually exclusive here, and the address's authored
   three lines are the constraint that makes them so. If a future change
   wants 1fr columns back, it is choosing to either span the container or
   wrap the address.

   `width: max-content` is what makes the grid shrink to the sum of its
   columns rather than stretching to its block container; `max-width:
   100%` is the guard that keeps it inside the container if content ever
   grows past it. Both are required — either alone is wrong.

   THE TRACKS ARE `minmax(0, max-content)`, NOT BARE `max-content`, AND
   THAT IS THE GUARD DOING REAL WORK. `max-width: 100%` clamps the grid
   BOX but not its TRACKS: with unshrinkable columns the box would obey
   the cap while the three tracks kept their full width and simply
   overflowed it, which is horizontal overflow — the exact failure
   tools/mobile-audit.mjs exists to catch. The floor of 0 lets a column
   give way instead, and the Email column is the one that does, breaking
   at the `<wbr>` after the @ (see contact.njk).

   THE FLOOR ACTUALLY FIRES AT 64em — this is not a theoretical guard.
   Measured: the panel's natural width is 954px, and the container
   interior at a 1024px viewport is 945px. The panel is clamped to 945
   there and the Email column gives up the 9px, breaking at its `<wbr>`;
   from 1040px up there is slack and the panel sits at its natural 954
   unchanged. So the range 1024-1039 is served ONLY by the shrinkable
   track, and with a bare `max-content` it would have overflowed instead.
   Re-measure this band, not just a wide desktop, if the padding, the
   icon size or the address copy ever changes.

   `list-style: none` + zeroed padding: this is a <ul> because three
   contact routes are a list, not three sibling sections, and the markers
   would otherwise sit outside the panel. */
.contact-panel {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  background: #fff;
  border: 1px solid var(--neutral-200);
  border-radius: var(--radius-lg);
  box-shadow: var(--e1);
}
@media (min-width: 64em) {
  .contact-panel {
    grid-template-columns: repeat(3, minmax(0, max-content));
    width: max-content;
    max-width: 100%;
  }
}

/* WHITE, NOT NEUTRAL-100, and the difference matters for the disc below.
   The panel sits on the page's light surface with no shell over it, so
   the card-inside-a-card problem that pushed the pre-rebuild panel to
   neutral-100 (and cost it two contrast repairs) does not arise. Same
   radius-lg / e1 / hairline treatment `.brand-panel` (§4.3) and
   `.closing__card` (§5.7) carry, so the card families on this site still
   read as one set. The fill and the frame are on `.contact-panel` above;
   the route below carries only its own padding, so a divider can run the
   full height of the panel rather than stopping short of it.

   THE ICON SITS BESIDE THE TEXT, NOT ABOVE IT, and this is half of the
   height saving the maintainer asked for. Stacked, every column paid
   52px for the disc plus a gap before its label even started. Beside,
   the column height is `max(52px disc, label + gap + value)` — the disc
   becomes a floor the text sits inside rather than a block it sits
   under, so Phone and Email cost the panel nothing beyond that floor.

   `align-items: start` so a label and a value sit at the TOP of their
   shared track rather than centred in it. Paired with the row subgrid
   below, that is what puts the first line of all three values on one
   baseline; centring each in a track sized by Address's three lines
   would drop the one-line Phone value ~20px below the other two. */
.contact-route {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  column-gap: var(--space-4);
  row-gap: var(--space-2);
  align-items: start;
  padding: var(--space-6);
}

/* EACH ROUTE IS A ROW SUBGRID OF THE PANEL, so the three labels share
   one track and the three values share another. Without it every route
   sizes its own two rows independently, and measured before this rule
   the "Address" label sat 10px above "Phone" and "Email" — the three
   columns each distributing their own slack differently inside a panel
   whose height Address had set. Three labels that nearly line up read
   as a mistake in a way that three that plainly do not never would.

   This is the mechanism `.contact-layout__col` used before the rebuild
   and the one components.css uses to level `.grid--4 > .tile`; it is
   the house answer to "make these siblings agree on a baseline".

   INSIDE THE 64em QUERY, because it only means anything when the routes
   are columns. Below it the panel is a single stacked column and each
   route is its own row of the panel, so there is nothing to share. */
@media (min-width: 64em) {
  .contact-panel { grid-template-rows: auto auto; }
  .contact-route { grid-row: span 2; grid-template-rows: subgrid; }
}

/* ALL THREE CHILDREN ARE PLACED EXPLICITLY, and the icon's span is the
   mechanism the whole arrangement rests on. With the disc occupying only
   the first row, the label and value stack BELOW it and the column costs
   `52 + gap + value` — which is the stacked card this replaced.

   `1 / span 2`, NOT `1 / -1`, and the difference is not stylistic. These
   rows are implicit — `.contact-route` sets grid-template-COLUMNS only —
   and `-1` addresses the end of the EXPLICIT grid, which here has no row
   tracks at all. `1 / -1` therefore resolves to a single-row span and
   silently does nothing. This trap was documented on the deleted
   `.contact-row`, which measured 16px TALLER when it was "tidied" that
   way; the note is carried forward because the hazard is identical. Do
   not collapse this to `-1` without adding explicit rows. */
.contact-route__icon { grid-column: 1; grid-row: 1 / span 2; align-self: center; }
.contact-route__label { grid-column: 2; grid-row: 1; }
.contact-route__value { grid-column: 2; grid-row: 2; }

/* THE DIVIDERS, written on the adjacent sibling and never on a route's
   own edge — an adjacent-sibling rule cannot draw a line before the
   first item or after the last, so the panel's own border stays the only
   thing at its edges and no doubled 2px line can appear at either end
   however many routes exist.

   THE AXIS FLIPS WITH THE LAYOUT. Stacked below 64em the panel is one
   column and the rule is a horizontal border-top; from 64em the routes
   are columns and it becomes a vertical border-left. Both are the same
   declaration on the same selector, so a third route added to the <ul>
   is divided correctly at both arrangements without touching this.

   neutral-200 is correct HERE and was not on the pre-rebuild panel,
   which needed neutral-300 — that card's fill was neutral-100, where
   neutral-200 measures 1.14:1 and reads as no line at all. This panel is
   white, where neutral-200 is 1.19:1, the same value `.contact-card`
   used for its own edges. If the fill ever deepens, the divider has to
   go with it. */
.contact-route + .contact-route { border-top: 1px solid var(--neutral-200); }
@media (min-width: 64em) {
  .contact-route + .contact-route {
    border-top: 0;
    border-left: 1px solid var(--neutral-200);
  }
}

/* The icon disc. Geometry AND fill are `.tile__icon`'s (§4.11) so the
   card families read as one, and the icon keeps the fixed gold-500 the
   30px card grid uses — see eleventy.config.js on why location/phone/
   email belong to that family and not to the inline set.

   NO RING. The gold-300 inset ring the old panel carried was a repair
   for a gold-100 disc on a neutral-100 fill, where it measured 1.04:1
   and vanished. On white the disc is 1.19:1 — the same value `.tile`
   has always run at, and visible — so the repair is not needed and is
   not carried over. If this card's fill is ever deepened, the ring comes
   back with it; do not deepen the fill without it.

   `justify-self: start` because the disc is a fixed 52px in a grid whose
   items would otherwise stretch to the card's full width, which would
   stretch the pill radius into a lozenge. */
.contact-route__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  justify-self: start;
  width: 52px;
  height: 52px;
  border-radius: var(--radius-pill);
  background: var(--gold-100);
}

/* The label is an <h3> and must not inherit the h3 display-serif step —
   at 24px it would compete with the "Beyond79 Corporate Office" h2
   directly above the row. --type-h4 is the same step `.fact-rows dt`
   uses for a label beside a value, which is exactly what these are, and
   the same step the hero lede's h2 takes above. neutral-600 measures
   7.6:1 on white. */
.contact-route__label { font: var(--type-h4); color: var(--neutral-600); margin: 0; }

/* `.nap`'s `p { margin: 0 0 var(--space-4) }` is shared with the
   homepage closing section (§5.7) and is not ours to change, so the card
   zeroes the trailing margin here rather than editing that rule. */
.contact-route__value > :last-child,
.contact-route__value .nap p { margin-bottom: 0; }

/* 44px tap target on the two linked values, per the footer NAP's own
   treatment. `inline-flex` + `align-items: center` rather than padding,
   so the hit area grows without the underline sliding away from the
   text. The address is not a link and gets none of this. */
.contact-route__value a { display: inline-flex; align-items: center; min-height: 44px; }

/* Belt and braces on the email address. At 3-up this never fires — the
   card is ~357px against the string's 256px — but the 2-up and stacked
   tiers on small phones can still run short, and an unbreakable
   28-character string one font-metric change away from not fitting is
   exactly what silently reintroduces horizontal overflow. `anywhere`
   rather than `break-word` so it also shrinks the element's min-content
   contribution, which is what a grid column actually measures.

   The `<wbr>` in contact.njk is the preferred break and fires first;
   this is the last resort behind it. */
.contact-route__value { overflow-wrap: anywhere; }

/* ------------------------------------------------------------
   BEAT 3 — OUR BRANDS.

   THERE IS ALMOST NOTHING HERE, AND THAT IS THE POINT. The pair uses
   `.brand-panels` (components.css §4.3) — the component's own wrapper,
   the one the homepage uses — at the full container width, so each card
   measures ~544px against the homepage's 519px (there is no
   `.brands__shell` padding to pay for on this page).

   THE OLD NOTE SAID `.brand-panels` COULD NOT BE USED HERE, and it was
   right at the time: its 70em breakpoint is derived from holding the
   longest tagline on one line in a 519px card, and a 308px card beside a
   400px route panel never offered that. The routes have left the row, so
   the card is wider than the number was derived for and the breakpoint
   measures something real again. `.contact-brands` and its local 64em
   are deleted.

   SO ALL FIVE PAGE-SCOPED OVERRIDES ON `.brand-panel` ARE GONE — the
   neutral-100 fill (with its `var(--brand-wash-image)` trap), the
   three-tier padding, the 34/40/34 logo, the --space-3 head gap, and the
   `font: var(--type-h3)` tagline that existed only to beat a `3.7cqw`
   clamp flooring out at 14px in a card too narrow for it. At ~544px the
   clamp resolves above its floor on its own and the component is correct
   unmodified. DO NOT REINTRODUCE ANY OF THEM without first re-measuring
   the card width — every one was a consequence of 308px.

   WHITE CARDS ON THE MUTED BAND IS §4.3's NATIVE ARRANGEMENT, not an
   override. The homepage repaints them neutral-100 only because
   `.brands__shell` took the white first; there is no shell here, so the
   component's own fill is the correct one and the gold corner wash comes
   with it untouched.
   ------------------------------------------------------------ */
.contact-brands__heading { margin-top: 0; margin-bottom: var(--space-8); }

/* ------------------------------------------------------------
   /contact/ MEETS THE FOOTER FLUSH — no 96px light band between them.

   KEPT FROM THE PREVIOUS LAYOUT, RESCOPED, AND DELIBERATELY NOT DROPPED
   WITH THE REST OF THE 2026-08-21 COMPACTION. It arrived in that pass
   alongside three padding trims and looks like a fourth, but it is not
   the same kind of rule: the trims bought scroll, and this one fixes a
   tonal sequence. Reversing the compaction is what 2026-08-25 was asked
   for; reintroducing an artefact the old notes correctly called "a
   mistake" was not.

   `.site-footer` carries a site-wide `margin-top: var(--space-24)`
   (components.css). On a page whose last section is the light body
   surface, that 96px reads as the footer's own breathing room. Here the
   last section is the muted brand band, so the margin strands a 96px
   stripe of --neutral-50 between two toned surfaces and breaks the
   page's sequence — dark hero, light routes, muted brands, dark footer —
   with an orphan strip.

   THIS IS THE SAME HAZARD components.css §4.13 NAMES for `.closing-card`
   ("the footer's site-wide 96px margin-top would otherwise strand light
   between two dark surfaces"). /expertise/, /responsibility/ and the
   brand pages all solve it by ending on a dark closing card. /contact/
   has no closing card — the maintainer declined one on 2026-08-19,
   wanting no content added — so it solves the same problem by meeting
   the footer instead.

   THE HOMEPAGE AND /about/ ALSO END ON `.section--muted` AND DO NOT
   CARRY THIS, which was measured on 2026-08-25 rather than assumed: both
   show the same 96px strip. That is a latent inconsistency on those two
   pages, NOT a reason to drop this one, and it is deliberately not
   "fixed" here — widening this rule to `main:has(.section--muted:
   last-child) + .site-footer` would change two pages nobody asked about.
   Raise it separately if it is wanted.

   Scoped through the footer's own previous sibling: `<main>` holds the
   page and `<footer>` follows it (layouts/base.njk). `.contact-panel` is
   this page's route row and exists nowhere else, so this cannot reach
   another page — it replaces the old `.contact-layout` hook, which was
   deleted with the two-column shell. The muted section keeps its own
   --space-12 bottom padding, which is what stops the brand cards butting
   the footer edge; only the gap OUTSIDE the band goes.
   ------------------------------------------------------------ */
main:has(.contact-panel) + .site-footer { margin-top: 0; }

/* ============================================================
   5.14a — Homepage Our Story surface. Added 2026-08-21
   (maintainer: "further improve the visual appeal of that homepage
   Our Story section - possibly add the same background image/
   coloring seen from the hero").

   THE HERO'S OWN SURFACE, exactly as the brand headers took it on
   2026-08-20 (§5.4) — --grad-hero plus the graticule, which is now
   the shared `.graticule` class in components.css §4.16. A THIRD
   LAYER JOINED THEM ON 2026-08-25: the arc field, via
   `.section--arcs` + partials/brands-arcs.njk (maintainer: "similar
   to the contact page hero, I'd like to add in that arc graphic onto
   the homepage's Our Story section"). The stack now paints gradient,
   grid, arcs, copy — the same four the /contact/ hero paints, and
   nothing in this rule changed to allow it. This band
   was flat ink-900 from the `section--dark` class it still carries
   in index.njk, and that class is still doing all of its other work:
   neutral-200 body, #fff headings, gold-300 eyebrow. None of those
   changed, because the gradient's darkest stop IS ink-900. Only the
   fill is overridden, and `.story-band` and `.section--dark` are
   both one class, so this wins on source order alone — pages.css
   loads after base.css.

   THE GRATICULE IS ALSO DOING COMPOSITIONAL WORK HERE, not just
   texture. The band's copy is capped at 58ch by `.story__lede`
   (§5.14) and the buttons stack under it since 2026-08-21, so above
   64em the right half of the band was empty ink. The bloom's default
   position — 78% 18%, the hero's — lands in exactly that void, which
   is why this caller does not set --graticule-at either. The band
   now opens the same way the hero does.

   `overflow: hidden` SINCE 2026-08-25, AND IT COMES FROM
   `.section--arcs` (§5.3) — do not add a second copy here. This band
   had none until that date, and the note that stood here explained
   why: the graticule's ::before is inset:0 on a square-cornered
   full-bleed band and has nothing to escape. The arc field added that
   day does. Its 28 arcs originate on an ellipse wider and taller than
   the section, so unclipped they run into the neighbouring bands and,
   at narrow widths, push the document's scrollWidth out. The clipping
   is load-bearing now; only the reason changed.

   --grad-hero's "hero only" note in tokens.css was retired in this
   change rather than annotated again — four surfaces paint it now.
   ============================================================ */
.story-band { background: var(--grad-hero); }

/* ============================================================
   5.14 — Homepage Our Story lede. Added 2026-08-20 alongside the
   milestone ladder (components.css §4.7), same maintainer request.

   The band's opening paragraph is one 620-character sentence group
   and NOT A WORD OF IT CHANGED — this is a measure-and-size rule
   only. At the base --type-body it ran the full 68ch measure, eight
   lines on a 1280px desktop and fourteen at 390px, and it is the
   first thing the eye meets in the section. 58ch and 19px/1.7 make
   it read as a lede: fewer words per line, more line height, and a
   size that separates it from the 17px body copy inside the
   milestone cards below rather than matching it.

   19px is not a token — the type scale steps 17px (--type-body) to
   21px (--type-h3), and 21px would compete with the card titles,
   which are exactly that. This is the one intermediate value, scoped
   to one element on one page, and it is why this rule lives in
   pages.css rather than components.css.

   No max-width below 40em: at 390px the container is already 342px,
   which is about 40 characters, so a 58ch cap would do nothing and
   the size steps down to keep the line count sane.
   ============================================================ */
.story__lede {
  font: 400 19px/1.7 var(--font-body);
  max-width: 58ch;
  color: var(--neutral-200);
}
@media (max-width: 39.99em) {
  .story__lede { font-size: 17px; }
}

/* ============================================================
   5.15 — Homepage Our Story disclosure. Added 2026-08-20:
   "the timeline takes up wayyy too much space ... I'd like the
   timeline hidden under a cta of which expands the timeline to be
   visible ... I want vertical scroll on desktop to be minimized."

   THE <summary> IS A BUTTON, NOT A COMPONENT OF ITS OWN. It carries
   `.button .button--on-dark` in index.njk — the same two classes as
   the "Explore Our Story" CTA at the foot of the band (maintainer,
   2026-08-20: "Make the view our timeline a similar button to the
   Explore Our Story"). It was a full-width outlined bar with its own
   border, padding, label colour and hover fill until that request;
   all four of those rules are gone because base.css now supplies
   them, and re-adding any of them here would fork the two buttons
   apart again. What is left below is only what a <details> needs on
   top of a button: the marker reset, the glyph, and the body gap.

   NOTE THE ABSENCE OF `display`. `.button` is `display: inline-flex`
   and that is what collapses the <summary>'s default block box down
   to the pill's own width. `.button` and `.story-reveal__summary` are
   both (0,1,0), so a `display` declared here would win on source
   order — pages.css loads after base.css — and stretch the button
   across the container again. Do not add one.

   There is also no `:focus-visible` rule. The earlier bar needed one
   because §4.14's caveat applied: <summary> was the focusable element
   while the visible affordance was drawn on it with a border the ring
   sat flush against. Now the summary IS the button, so base.css's
   global ring at 2px offset lands on it exactly as it lands on
   "Explore Our Story" — matching that button includes matching how it
   focuses.

   This is the page's SECOND <details> disclosure. The first is
   `.reveal-list` (components.css §4.14), the four Responsibility rows
   further down, and partials/reveal-list.njk carries the full
   argument for why a native <details> and not a div-and-class
   accordion: real expanded/collapsed state for assistive tech,
   keyboard-operable, works with JS off, findable by the browser's own
   in-page search when open. That reasoning is not restated here. The
   two still share the `expand` plus rotating 45 degrees into a close
   mark, which is the part a visitor carries between them. The glyph
   CANNOT be a chevron: eleventy.config.js's ICONS map has none and
   the shortcode throws on unknown names.

   It is NOT in components.css because one page uses it, which is the
   rule that file's header states. There is now very little here to
   promote if a second caller ever appears.
   ============================================================ */
/* The column that stacks both buttons (maintainer, 2026-08-21: "I'd
   like the View Our Timeline button to be ontop of the Explore Our
   Story button rather than side by side"). This REVERSES the
   2026-08-20 "I want the buttons to be side by side" request — the
   pairing lasted a day; do not restore the row from that comment's
   authority. --space-4 is `.button-row`'s own gap, so the two are
   spaced like any other button pair on the site, and the --space-8
   top margin is the one `.button-row` used to supply before the CTA
   lost that wrapper.

   `align-items: flex-start` is what keeps them pills. In a column the
   cross axis is WIDTH, so the default `stretch` would run both buttons
   the full width of the band — the full-width outlined bar the
   summary was explicitly moved away from on 2026-08-20. */
.story-actions {
  display: flex; flex-direction: column; align-items: flex-start;
  gap: var(--space-4);
  margin-top: var(--space-8);
}
/* THE OPEN STATE STILL RE-FLOWS, for the same reason it did as a row.
   `align-items: flex-start` above sizes the <details> to its summary,
   which is right closed (209px at 1280) and wrong open: the timeline
   would be squeezed into a button's width. `align-self: stretch`
   overrides the container for the open state only, giving it the full
   band. It replaced `flex: 1 0 100%`, which was a main-axis fix and
   in a column would set the element's HEIGHT, not its width.

   The margin is not decoration either: `gap` is the only separation
   between the CTA and the timeline above it, and --space-4 alone is
   too tight under a whole band. This takes it to 32px. It is on the
   [open] rule rather than the column so it costs nothing closed. */
.story-reveal[open] { align-self: stretch; margin-bottom: var(--space-4); }

/* BOTH marker resets are required, exactly as §4.14 documents:
   `list-style: none` covers Firefox and Chrome, and Safari only drops
   the triangle for the -webkit pseudo-element. */
.story-reveal__summary { list-style: none; }
.story-reveal__summary::-webkit-details-marker { display: none; }

/* 8px, against `.icon--arrow`'s 4px on the "Explore Our Story" arrow
   (components.css §4.9). The arrow gets less because it reads as
   punctuation trailing the label; this glyph is a control and wants
   to sit apart from the words. `inline-flex` is load-bearing — an
   inline box cannot be transformed, so the rotation below would do
   nothing without it. Colour comes from `.button--on-dark`'s ink-900
   label via `icon--inline`; do not set one here or the glyph stops
   tracking the button through its hover. base.css flattens this
   transition globally under prefers-reduced-motion. */
.story-reveal__toggle {
  display: inline-flex; margin-left: var(--space-2);
  transition: transform .15s ease;
}
.story-reveal[open] .story-reveal__toggle { transform: rotate(45deg); }

/* Only the gap the timeline's own variants do not supply. Above 64em
   `.timeline--horizontal` brings `margin-block: var(--space-8)` and
   below it `.timeline--grid` brings `padding-top: var(--space-6)`
   (components.css §4.7 / §4.13 — the same padding-top the now-dormant
   `.timeline--rail` used to supply), so 16px here lands at 48px and
   40px respectively — do not add more without checking both. */
.story-reveal__body { padding-top: var(--space-4); }

/* ============================================================
   5.16 — Homepage Expertise cards, compact. Added 2026-08-24
   (maintainer: "hide the p tag text on the homepage's our
   expertise cards" on mobile). Below 40em the four cards are
   icon + title only; the sentence each one carries is dropped.

   SCOPED TO #expertise, WHICH IS HOMEPAGE-ONLY. `.tile` is shared
   — the six differentiator cards on /about/ are `.tile`s too — so
   a bare `.tile p` rule would silently strip those as well, and
   there it would DELETE copy rather than relocate it (see §5.17).
   Since 2026-08-25 those cards come from partials/tile-reveal.njk
   rather than partials/tiles.njk, which narrows the shared surface
   to the class but does not remove it: the scope is still load-
   bearing and must stay. #expertise appears exactly once
   in src/ (index.njk's section wrapper) and is already the anchor
   target the nav and the stat band point at, so it is a stable
   hook rather than a styling hook invented for this.

   40em IS THE SAME BREAKPOINT .grid--compact USES (components.css
   §4.15a), on purpose and not by coincidence — that is precisely
   where this grid stops being a column of full-width cards and
   becomes a 2-up grid of 163px ones, which is the layout with no
   room for a sentence. Keep the two numbers together: a 163px card
   that still carried its paragraph would be the tall card the
   compact grid exists to avoid.

   (Until 2026-08-24 this paragraph named .grid--rail and a
   horizontal scroll-snap rail. The breakpoint and the reasoning
   are unchanged; only the mechanism it points at moved.)

   It is also below where the subgrid row template starts
   (`@media (min-width: 40em)`, components.css §4.10), so nothing
   here can leave an empty third track behind. Above 40em the
   cards are untouched and the body copy is back.

   `display: none`, NOT a .visually-hidden clip. The tiles are
   <a>s, so their accessible name is built from their text
   content; clipping the paragraph would leave every card
   announcing a full sentence after its title while showing only
   the title. Dropping it outright gives each link the short,
   scannable name the visible card actually has, and the full copy
   is one tap away at /expertise/ — which is where each card
   already points, and where it came from. Nothing is lost, only
   relocated.
   ============================================================ */
@media (max-width: 39.99em) {
  #expertise .tile p { display: none; }
}

/* ============================================================
   5.17 — The /about/ differentiator cards. Added 2026-08-25
   (maintainer: "more visual appeal, i.e. maybe a golden
   highlight upon hover" and "hide those larger p tag texts on
   mobile ... but still ensure that content is visible to
   crawlers").

   SCOPED TO `.differentiators`, a class carried by exactly one
   <section> — about/index.njk's "What Makes Beyond79 Different"
   band. `.tile` is shared with the homepage #expertise grid
   (partials/tiles.njk), so an unscoped rule here would repaint
   that grid too. This is the same hazard, and the same remedy,
   §5.16 above documents for `#expertise .tile p`. The two scopes
   are complements and neither can reach the other's grid.

   The markup is partials/tile-reveal.njk. Read its header first:
   it records why it is a fork of tiles.njk rather than a flag on
   it, and why the copy could not simply be dropped on mobile the
   way §5.16 drops the homepage's.
   ------------------------------------------------------------ */

/* --- The stretched backlink ------------------------------------------
   Added 2026-08-26 (maintainer: "these tile cards, I'd like them to be
   clickable backlinks to relevant sections of the site"). Five of the six
   land on the /expertise/ section whose id is the card's own icon name;
   the sixth is documented at its caller in about/index.njk.

   WHY THE CARD IS NOT ITSELF AN <a>. tiles.njk's answer to "make the card
   clickable" is `a.tile` — the whole card is the anchor. That is not
   available to these six: since 2026-08-25 each carries a <details>, and
   <summary> is an interactive element that may not be nested inside <a>.
   Wrapping anyway produces invalid markup and a disclosure the browser
   will not toggle.

   So the anchor is the <h3> title (partials/tile-reveal.njk) and this
   ::after stretches it over the whole card. The card is clickable
   edge-to-edge, but the link's accessible name is the title alone rather
   than title-plus-paragraph — which is the SAME outcome §5.16 above
   engineers for the homepage tiles by a different route, and for the same
   stated reason: "the short, scannable name the visible card actually
   has."

   `.tile` is already `position: relative` (components.css §4.10, where
   the gold ::before rule needs it), so the overlay has its containing
   block and this adds no new one. `overflow: hidden` there does not clip
   an `inset: 0` overlay.

   THE TWO Z-INDEXES ARE A PAIR AND ONLY MEAN ANYTHING TOGETHER. The
   overlay sits at 1 and the <summary> at 2, so below 40em the "Read more"
   control stays above the link and toggles instead of navigating. Neither
   `.tile` nor anything between them sets a z-index, so the two are
   compared in the same stacking context — do not introduce a z-index or
   a transform on `.tile`, `.tile__reveal` or the grid, either of which
   would create a context and trap the summary underneath the overlay.
   Above 40em the summary is `display: none` (see the force-open block
   below) and only the overlay is left, so the desktop card is clickable
   over its paragraph too, exactly like an `a.tile`.

   `color: inherit; text-decoration: none` because base.css paints every
   <a> gold-700 and the UA underlines it; without this the six card titles
   turn gold and gain rules. The pair outranks base.css's `a:hover` on
   specificity (0,2,0 against 0,1,1), so the title also holds its ink
   through hover and the card's own gold hover below is the only response.

   THE FOCUS RING GOES ON THE CARD, NOT THE TITLE. base.css's global
   `:focus-visible` would hug the anchor's inline box — a ring around a
   few words, on a control whose real hit area is the whole card. `:has()`
   moves it to the border box so the ring shows what the Enter key will
   actually activate. `:has()` is already load-bearing in this file
   (`.band:has(.story-spine)`) and in components.css §4.15's subgrid, so
   this adds no new support floor. `outline` is not clipped by the card's
   own `overflow: hidden` — that clips descendants, not the element's own
   outline. */
.differentiators .tile__link { color: inherit; text-decoration: none; }
.differentiators .tile__link::after {
  content: ""; position: absolute; inset: 0; z-index: 1;
}
.differentiators .tile__reveal-summary { position: relative; z-index: 2; }

.differentiators .tile__link:focus-visible { outline: none; }
.differentiators .tile:has(.tile__link:focus-visible) {
  outline: var(--focus-ring); outline-offset: 2px;
}

/* --- The gold hover ---------------------------------------------------
   These cards are inert <div>s, so `a.tile:hover` (components.css §4.10)
   has never matched them — until now they had no hover state at all.

   Four moves, all gold, all on the card's existing furniture rather than
   anything new: the hairline border warms to gold-500, the neutral
   resting shadow becomes the warm one, the gold rule at the card's foot
   thickens 2px -> 3px, and the icon disc gains a gold-300 ring. None of
   them changes the border box, so nothing reflows on hover — the ::before
   rule is `position: absolute` (§4.10 explains why that is load-bearing
   for the subgrid) and the disc ring is an inset shadow, not a border.

   --e-gold-1 IS DELIBERATE, AND IT IS A REACTIVATION. tokens.css scopes
   the gold-tinted elevations to "nav pill + its CTA only", and
   components.css §4.1 records --e-gold-1 as unused after the nav pill
   moved to the neutral --e2. But read why it moved: "A gold-tinted shadow
   is a warm halo around the bar, which is the same complaint that removed
   the warm fill; permanent gold would have been more visible than the
   hover-only gold ever was." The objection there is to PERMANENCE, on a
   surface that is on screen at all times. This is hover-only, on a card
   the pointer has to seek out, which is exactly the case that note treats
   as having been fine. Do not make it permanent.

   `@media (hover: hover)` so a touch device never latches the gold state
   on tap and holds it until the next tap elsewhere.

   gold-500 is legal on all four counts: a border, a shadow and a rule are
   non-text, and the ring is a non-text indicator at 1.46:1 against the
   gold-100 disc — the same disc-ring repair §5.13's contact panel already
   ships, and for the same reason (the disc has no edge of its own). */
@media (hover: hover) {
  .differentiators .tile {
    transition: border-color .15s ease, box-shadow .15s ease;
  }
  .differentiators .tile::before { transition: height .15s ease; }
  .differentiators .tile__icon { transition: box-shadow .15s ease; }

  .differentiators .tile:hover {
    border-color: var(--gold-500);
    box-shadow: var(--e-gold-1);
  }
  .differentiators .tile:hover::before { height: 3px; }
  .differentiators .tile:hover .tile__icon {
    box-shadow: inset 0 0 0 1px var(--gold-300);
  }
}

/* --- The disclosure, below 40em --------------------------------------
   The summary is the whole visible control: a two-word label and the
   `expand` plus, which rotates 45 degrees into a close mark under [open]
   — the same glyph, the same rotation and the same 44px floor
   `.reveal-list__summary` (components.css §4.14) already ships, so the
   two disclosures on this site behave identically.

   gold-700, not gold-500, for BOTH the label and the glyph. The label is
   text, so gold-500's 3.4:1 is not available to it at all; the glyph is
   held to the same standard for the reason §4.14 gives for its own
   toggle — at 18px on `icon--inline` it takes currentColor and reads as
   UI beside text, not as a decorative mark. Keeping them one colour also
   means the control reads as one object.

   BOTH marker resets are required: `list-style: none` covers Firefox and
   Chrome, and Safari only drops the triangle for the -webkit pseudo-
   element. Same pairing as §4.14.

   `min-height: 44px` is the WCAG 2.5.5/2.5.8 touch target, and this is
   the only tap target on the card — the card itself has no href.

   The focus ring is restated because <summary> is the focusable element
   and base.css's global rule would otherwise hug its content box. */
.differentiators .tile__reveal-summary {
  display: inline-flex; align-items: center; gap: var(--space-3);
  min-height: 44px;
  font: var(--type-small); color: var(--gold-700);
  cursor: pointer;
  list-style: none;
}
.differentiators .tile__reveal-summary::-webkit-details-marker { display: none; }
.differentiators .tile__reveal-summary:focus-visible {
  outline: var(--focus-ring); outline-offset: 2px;
}
.differentiators .tile__reveal-toggle {
  display: inline-flex; color: var(--gold-700);
  transition: transform .15s ease;
}
.differentiators .tile__reveal[open] .tile__reveal-toggle { transform: rotate(45deg); }
/* The open body clears the summary. `.tile p` (§4.10) already supplies the
   type and colour, so this sets spacing only and the card body reads
   exactly as it does on desktop. */
.differentiators .tile__reveal[open] > p { margin-top: var(--space-3); }

/* --- Force-open from 40em up -----------------------------------------
   Above 40em the summary disappears and the paragraph is simply the
   card's third element, so THE DESKTOP CARD IS PIXEL-IDENTICAL TO THE ONE
   THAT SHIPPED BEFORE THIS SECTION EXISTED. The <details> is a mobile
   affordance only; it is not a desktop accordion.

   40em IS NOT A FREE CHOICE. It is where `.grid--3` goes 1-up -> 2-up
   (base.css) AND where `.grid--3 > .tile`'s three-track subgrid starts
   (components.css §4.10) AND the breakpoint §5.16 uses for the homepage's
   equivalent. Collapse therefore only ever happens where the subgrid is
   off, so a closed card can never leave an empty third track behind. Move
   this number and all three follow.

   BOTH DECLARATIONS ARE REQUIRED, and this is the whole reason this block
   is not a one-liner. Engines split on how a closed <details> hides its
   contents:
     * older engines apply `display: none` to the non-summary children,
       which `display: block` overrides;
     * Chrome 131+, Safari 18.4+ and Firefox 139+ moved the closed state
       to `content-visibility: hidden` on the `::details-content` pseudo-
       element, which `display` cannot touch.
   Each declaration is inert on the engine the other one serves — an older
   engine ignores the unknown pseudo-element, and a newer one is unblocked
   by it — so the pair covers both with no UA sniffing and no script.

   VERIFIED, not assumed, in this repo's own Chrome 151 at 1280x900 and
   390x844: at 1280 the paragraph reports checkVisibility() true and the
   <details> measures its full 72px with the summary at 0px, while the
   `open` attribute is still absent; at 390 the same paragraph reports
   false and the <details> measures exactly its summary's 24px. The copy
   is present in the served HTML at both widths, which is the entire point
   of the exercise — see the partial's header. If you change either
   declaration, re-run that check rather than trusting the cascade. */
@media (min-width: 40em) {
  .differentiators .tile__reveal-summary { display: none; }
  .differentiators .tile__reveal > p { display: block; margin-top: 0; }
  .differentiators .tile__reveal::details-content {
    content-visibility: visible;
    block-size: auto;
  }
}

/* ============================================================
   5.18 — Legal pages (/privacy/, /terms/)

   Both pages are VERBATIM-LOCKED (CLAUDE.md §8). Everything here is
   presentation only; `tools/verify-verbatim.mjs` proves the copy still
   says exactly what the 2014 originals said, and it is narrowed to
   `.legal__body` so this file can dress the page without the check
   having to be loosened. Two rules below are load-bearing for that
   contract and are called out where they sit.

   The treatment is deliberately quiet — a legal document should read
   as a document, not as a landing page. The kit's own devices do the
   work: measure-bounded prose, a gold hairline, eyebrow markers.
   There is no new colour, no new radius and no motion.
   ------------------------------------------------------------ */

.legal { padding-block: var(--space-12) var(--space-16); }

/* ONE COLUMN EDGE FOR THE WHOLE DOCUMENT, set once here and inherited.

   --measure is 68**ch**, and `ch` resolves against the element's OWN font
   size — so putting `max-width: var(--measure)` on the 21px headings made
   their hairline rules measurably wider than the 17px paragraphs under
   them, and the column had two ragged right edges. Bounding the shared
   parent instead (which sits at the 17px body size, the size --measure was
   authored for) gives every child the same edge. Children below therefore
   set `max-width: none` to fill it rather than re-deriving a ch width at
   their own size. Do not put --measure back on an individual element here.
   -------------------------------------------------------------------- */
.legal__body { max-width: var(--measure); }

/* --neutral-700 is set HERE and inherited, not on a `.legal__body p`
   descendant rule — the same call components.css §4.12 makes for
   `.band__body`, and for the same reason: a descendant rule (0,1,1)
   would outrank every class inside this block and repaint them. Any
   new element in here declares its own colour. */
.legal__body { color: var(--neutral-700); }
.legal__body p { margin-bottom: var(--space-6); }

/* SPECIFICITY, NOT DECORATION. `.legal__body p` above is (0,1,1) and
   `.legal__meta` alone would be (0,1,0) — it would lose, and the
   effective date would take the 24px body gap. Qualifying it to
   (0,2,1) is what makes these declarations apply at all. This is the
   exact trap CLAUDE.md §5 documents for `.band__body p` against
   `.standfirst`. Do not "simplify" this selector back to one class. */
.legal__body .legal__meta {
  font: var(--type-small);
  letter-spacing: 0.06em;
  color: var(--neutral-600);
  margin-bottom: var(--space-8);
}

/* --- Section openers -------------------------------------------------

   The `§ 01` markers are CSS counters, NOT text in the document. That
   is the only reason they are allowed: generated content never enters
   the HTML, so it cannot reach verify-verbatim.mjs, and it cannot end
   up pasted into a copy of the policy. Never hard-code these numbers
   into the markup — a section added or removed would silently
   misnumber every section after it, and it would be locked copy.

   Numbering is honest here in the way CLAUDE.md §5 requires of the
   Expertise journey stepper: legal sections genuinely are enumerated
   and cross-referenced by number. It does not license numbering
   anywhere else on the site. */
.legal__body { counter-reset: legal-section; }

.legal .legal__h {
  counter-increment: legal-section;
  font: var(--type-h3);
  letter-spacing: -0.008em;
  color: var(--ink-900);
  max-width: none;                 /* fill .legal__body's edge — see above */
  /* Tightened from 48/24/16 on 2026-08-25 (maintainer: "decrease the
     vertical section paddings to minimize more vertical scroll"). 28px
     per section opener, over 11 and 14 sections. The PARAGRAPH gap below
     is deliberately NOT part of that cut — CLAUDE.md §5 settles it. */
  margin: var(--space-8) 0 var(--space-3);
  padding-top: var(--space-4);
  border-top: 1px solid var(--neutral-200);

  /* ANCHOR CLEARANCE — overrides the global `:target` rule at §5.9.
     That value is 73px header + 69px TAB BAR; these pages have no tab
     bar, so a TOC jump would land 69px below the heading it aimed at.
     Re-derive from the header numbers in components.css §1, not from
     the 142/149 pair, if the header is ever resized again. */
  scroll-margin-top: 73px;
}
@media (min-width: 900px) { .legal .legal__h { scroll-margin-top: 80px; } }

.legal .legal__h::before {
  content: "§ " counter(legal-section, decimal-leading-zero);
  display: block;
  font: var(--type-eyebrow);
  /* 0.10em, not the 0.16em the eyebrow ramp uses for words: at 11px that
     much tracking visibly pulls the two digits of "01" apart into "0 1".
     Tabular figures keep the marker column steady from §01 to §14. */
  letter-spacing: 0.10em;
  font-variant-numeric: tabular-nums;
  color: var(--gold-700);
  margin-bottom: var(--space-2);
}

/* --- Table of contents -----------------------------------------------

   LIVES OUTSIDE `.legal__body` IN THE MARKUP, and must stay there: it
   is excluded from the verbatim comparison structurally rather than by
   expectation (see the header comment in tools/verify-verbatim.mjs).
   Moving it inside that div would start counting its link text as
   locked legal copy and fail the build.

   Held to --measure so it shares both edges with the prose below it
   rather than floating at some width of its own. Long labels wrap
   inside their column; there is no rail and no scroller (CLAUDE.md §5). */
.legal__toc {
  max-width: var(--measure);
  background: var(--neutral-50);
  box-shadow: var(--e0);
  border-radius: var(--radius-lg);
  padding: var(--space-6);
  margin-bottom: var(--space-12);
}

.legal__toc-title {
  font: var(--type-eyebrow);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--neutral-600);
  margin-bottom: var(--space-4);
}

.legal__toc-list {
  list-style: none;
  counter-reset: legal-toc;
  display: grid;
  gap: var(--space-3) var(--space-8);
  margin: 0;
  padding: 0;
}
@media (min-width: 40em) {
  .legal__toc-list { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* Counter, not the <ol>'s own marker, so the TOC numbering is the same
   glyph sequence as the `§ nn` on the headings it points at. The <ol>
   stays an <ol> because the sections genuinely are ordered — the list
   semantics are for assistive tech, the numbers on screen are ours. */
.legal__toc-list li {
  counter-increment: legal-toc;
  display: flex;
  gap: var(--space-3);
  font: var(--type-small);
}
.legal__toc-list li::before {
  content: counter(legal-toc, decimal-leading-zero);
  flex: none;
  font-variant-numeric: tabular-nums;
  color: var(--neutral-400);
}

/* --- Closing contact block (privacy only) --------------------------- */
.legal__contact {
  list-style: none;
  margin: var(--space-6) 0 0;
  padding: var(--space-6);
  background: var(--neutral-50);
  box-shadow: var(--e0);
  border-radius: var(--radius-lg);
}
.legal__contact li { max-width: none; }
.legal__contact li:first-child {
  font-weight: 600;
  color: var(--ink-900);
}

/* --- Sticky right rail (>=64em) --------------------------------------

   Maintainer, 2026-08-25: "Put the table of contents off to the right
   side on the top - keep it visible on screen through scroll."

   The TOC KEEPS ITS DOM POSITION as the first child of `.legal`, before
   `.legal__body` — it is moved to the right visually with `grid-column`,
   NOT by reordering the markup. That is not a style preference: the
   verbatim check narrows the built page to `.legal__body` and relies on
   the TOC being outside it (see tools/verify-verbatim.mjs). Reordering
   the source to put the TOC second would also put it after the content
   for a screen reader, losing the skip-ahead the list exists to provide.

   Below 64em there is no room for a rail, so the TOC stays a full-width
   block above the prose and none of this applies.
   --------------------------------------------------------------------- */
@media (min-width: 64em) {
  .legal {
    display: grid;
    /* 260px holds the longest label — "Disclaimers and limitations of
       liability" — in two lines at --type-small, plus the numeral gutter.
       The prose column is 1fr but `.legal__body` stays capped at
       --measure, so the rail sits hard right and the slack falls between
       the two columns as air, not as over-long lines. */
    grid-template-columns: minmax(0, 1fr) 260px;
    gap: var(--space-16);
    /* REQUIRED for the sticky child: a stretched grid item is already as
       tall as its row, so it has no room to travel and `position: sticky`
       silently does nothing. */
    align-items: start;
  }
  /* BOTH items are pinned to row 1. Without `grid-row`, auto-placement
     walks forward in DOM order: the TOC declares column 2 and lands in
     row 1, the placement cursor is then past column 1 for that row, and
     `.legal__body` is pushed down to row 2 — leaving a column-1 void as
     tall as the TOC and making the page LONGER, not shorter. */
  .legal__body { grid-column: 1; grid-row: 1; }

  .legal__toc {
    grid-column: 2;
    grid-row: 1;
    position: sticky;
    /* 80px is the header island's BOTTOM edge at >=900px (components.css
       §1), the same number `.legal__h` clears for anchor jumps. Re-derive
       both from §1 together if the header is ever resized. */
    top: calc(80px + var(--space-6));
    max-height: calc(100vh - 80px - var(--space-12));
    overflow-y: auto;
    /* PINNED EXPLICITLY, not left to default. With `overflow-y: auto` and
       `overflow-x: visible`, CSS computes overflow-x to `auto` — which
       would make this a horizontal scroll container and trip
       tools/mobile-audit.mjs, the one thing CLAUDE.md §5 forbids
       site-wide. `hidden` keeps the computed pair legal. */
    overflow-x: hidden;
    margin-bottom: 0;
  }

  /* Back to one column: the 40em rule above splits the list 2-up for a
     full-width block, which does not fit a 260px rail. */
  .legal__toc-list { grid-template-columns: 1fr; }
}
