/* =============================================================================
   Page primitives — everything the inner pages need that the homepage did not.

   home-a.css is the signed-off contract and is regenerated from the mockup, so
   it is not edited. This file sits after it and adds only what is missing.

   Almost nothing here is new design. The homepage already established the
   idioms and they are global, so inner pages reuse .section .container .eyebrow
   .display .h1 .h2 .h3 .lead .muted .sm .btn .card .split .tag .dark .tiers
   .own-grid directly. What follows generalizes the three patterns that were
   written inline for one section each and are needed on every page:

     the asymmetric section header   (was .lj-hd / .who-hd / .work-hd)
     the ruled cell grid             (was .bento / .blt-grid / .aud)
     the closing conversion band     (was .final / .fin-grid)

   Same tokens throughout, so both themes work with no per-page dark variants.

   NAME COLLISIONS. Both stylesheets are global and unlayered, so a short class
   name added here can silently inherit rules from there. It has happened once
   already: `.hubcard .go` picked up the contract's `.go`, which is a filled
   accent button inside the product mockups, and every hub card rendered its
   "Read more" as a solid orange bar. Hence `.hub-go`.

   Before adding a short generic name, check it:
     grep -n '\.yourname\b' public/styles/home-a.css
   Reusing .section .eyebrow .lead .h2 .h3 .dark .btn .card .split .tag on
   purpose is fine and intended — that is the shared vocabulary. Colliding by
   accident on a two-letter name is not.
   ========================================================================== */

/* ---------- stacked section rhythm -----------------------------------------
   There is no rule here, on purpose, and this note exists so nobody adds one
   back.

   --sec-y is padding on BOTH edges, so two stacked .section elements sit two
   full gaps apart. That looks like a mistake and it is not. Measure the signed
   off homepage: every gap between consecutive sections is 159px, including the
   long run of plain paper-on-paper sections in the middle. The doubled gap IS
   the rhythm of this design.

   An earlier version collapsed the top padding when a plain section followed a
   plain section, which cut inner pages to 79px in the plain runs while the
   background-changing boundaries stayed at 158px. The result was a page whose
   spacing alternated, and the short gaps read as one section having lost its
   top margin. Inner pages now inherit .section untouched, so they measure the
   same as the homepage.

   If a specific pair of sections genuinely belongs tighter together, give that
   pair a class and set it there. Do not do it globally. */

/* `.tight` is that class, and #recognition is currently its only user. Both
   edges, so the gap it shares with each neighbour drops from 176px to 136px
   rather than only one side closing up — an asymmetric section reads as one
   that has lost a margin, which is the exact failure the note above records.
   This does depart from the homepage's uniform rhythm on purpose. */
.section.tight{padding-block:calc(var(--sec-y) * .55)}


/* ---------- header dropdowns -----------------------------------------------
   Three of the seven slots open a menu, with no JavaScript.

   `:hover` serves a mouse and `:focus-within` serves a keyboard. That pairing
   is the whole mechanism: tabbing onto the parent link puts focus inside the
   li, which matches :focus-within, which displays the panel, which makes the
   panel's links focusable — so the next Tab walks into the menu and Shift+Tab
   walks back out. Without the focus-within half the panel stays display:none
   and its links can never receive focus, which is the usual way a CSS-only
   dropdown quietly locks out keyboard users.

   visibility, not display:none, so the panel can transition. It must stay
   unfocusable while closed, which is what visibility:hidden buys over opacity
   alone — opacity:0 leaves the links in the tab order.

   Desktop only. Below 1000px `.nav ul` is display:none and the hamburger panel
   carries every link flat, so none of this applies. */
.hasmenu{position:relative}
.nav ul a{display:inline-flex;align-items:center;gap:5px}
/* the caret rotates so the open state is visible without colour alone */
.cav{transition:transform var(--duration) var(--ease);opacity:.6}
.hasmenu:hover .cav,.hasmenu:focus-within .cav{transform:rotate(180deg);opacity:1}

.submenu{position:absolute;inset-inline-start:-14px;top:100%;
  /* the header is 68px and the li is centred in it, so the panel would open
     flush against the bar. This lifts it clear of the bottom border. */
  padding-top:14px;
  visibility:hidden;opacity:0;transform:translateY(-4px);
  transition:opacity var(--duration) var(--ease),transform var(--duration) var(--ease),
    visibility 0s linear var(--duration)}
.hasmenu:hover .submenu,.hasmenu:focus-within .submenu{
  visibility:visible;opacity:1;transform:none;transition-delay:0s}

.submenu ul{display:grid;gap:0;margin:0;padding:7px;min-width:230px;
  background:var(--bg-raised);border:1px solid var(--border);
  border-radius:var(--radius-lg);box-shadow:0 14px 40px rgba(15,42,32,.13)}
:root[data-theme="dark"] .submenu ul{box-shadow:0 14px 40px rgba(0,0,0,.45)}
.submenu li{list-style:none}
.submenu a{display:block;padding:9px 13px;border-radius:var(--radius);
  font-size:.9375rem;color:var(--fg-muted);text-decoration:none;white-space:nowrap}
.submenu a:hover,.submenu a:focus-visible{background:var(--bg);color:var(--fg)}

@media (prefers-reduced-motion:reduce){
  .submenu{transition:none}
  .cav{transition:none}
}

/* the footer's legal row. `.f-bottom` is already a wrapping space-between flex,
   so this only has to keep its own three links from touching each other. */
.f-legal{display:flex;flex-wrap:wrap;gap:20px}

/* the mobile panel is flat, so a child is distinguished by indent alone */
.mobile-nav .mn-child{padding-inline-start:18px;font-size:.9375rem;
  color:var(--fg-muted)}

/* ---------- page hero ------------------------------------------------------
   The top of every inner page. Narrower than the homepage hero on purpose —
   that one carries a product carousel, this one carries a sentence. */
.page-hero{padding-block:clamp(3rem,5vw,5rem) clamp(2.5rem,4vw,4rem);
  border-bottom:1px solid var(--border)}
.page-hero-grid{display:grid;grid-template-columns:1.15fr .85fr;
  gap:clamp(32px,5vw,72px);align-items:end}
.page-hero h1{max-width:16ch}
.page-hero .lead{max-width:52ch;margin-top:20px}
.page-hero-cta{display:flex;flex-wrap:wrap;gap:12px;margin-top:28px}

/* The emphasised phrase in a hero heading is clay, not italic — the same rule
   the homepage has carried since it was signed off (`.hero-deck h1 em`).
   Every inner page already marks its key phrase with <em>; until now that
   produced italic Fraunces in the SAME colour as the rest of the line, which
   is a distinction you have to look for. */
.page-hero h1 em{font-style:normal;color:var(--accent)}

/* ---------- hero with a figure ---------------------------------------------
   For a hero whose right column carries a drawing instead of a short callout.

   Two changes, both measured rather than guessed. The base grid is 1.15fr/.85fr
   and `align-items:end`, which pinned the partners callout to the BOTTOM of a
   700px-tall row and left 615px of blank paper above it. And the base h1 is
   72px against a 16ch measure, which ran that page's heading to FIVE lines —
   the design spec caps a desktop headline at two or three.

   So: balance the columns, top-align them, and drop the display size while
   widening the measure. Smaller type alone would not have helped, because a
   16ch box shrinks with the font and the line count stays put. */
.page-hero.with-figure .page-hero-grid{grid-template-columns:1fr 1fr;
  align-items:start}
.page-hero.with-figure h1{max-width:26ch;
  font-size:clamp(1.75rem,1.05rem + 1.5vw,2.375rem);line-height:1.12}
.page-hero.with-figure .lead{max-width:46ch}
.hero-figure{display:grid;gap:20px}
@media (max-width:900px){
  .page-hero.with-figure .page-hero-grid{grid-template-columns:1fr}
}

/* ---------- the inline hero drawing ----------------------------------------
   `partner-network-visual.tsx`. NO plate: because this one is inlined rather
   than loaded through <img>, these rules actually reach the artwork, so it can
   take its colours from the theme instead of hard-coding pine on paper. That
   is the whole reason it is a component — the eight <img> drawings cannot do
   this, which is why they are plated.

   Everything below is a brand token. Nothing here is a hex value. */
.pnv{margin:0;display:grid;gap:14px}
.pnv svg{width:100%;height:auto;overflow:visible}
.pnv figcaption{font-size:.8125rem;color:var(--fg-muted);line-height:1.55}

.pnv-node{fill:var(--bg-raised);stroke:var(--border);stroke-width:1}
.pnv-core{fill:var(--brand);stroke:transparent}
.pnv-rail{fill:none;stroke:var(--border);stroke-width:1.4}
.pnv-line{fill:none;stroke:var(--accent);stroke-width:2;stroke-linecap:round;
  stroke-linejoin:round;stroke-dasharray:5 8;opacity:.55}
.pnv-pip{fill:var(--accent)}
/* SIZE IS MEASURED, NOT CHOSEN. The drawing renders 860 user units into a
   516px column, so every authored size arrives on screen at 0.6x — the mono
   labels were authored at 9px and were reaching the reader at 5.4px, which is
   texture rather than text. 17/14 is the largest pair that still fits: at 18px
   "Managed operations" has exactly 0px of slack left in its box, and at 15px
   "MANAGED BEHIND YOU" has 10. Both numbers come from getBBox, and
   `pnpm check:svgs` re-measures them on the twin file, which carries the same
   two sizes for exactly that reason. */
.pnv-label{fill:var(--fg);font-family:var(--font-display);font-size:17px;
  font-weight:600}
.pnv-small{fill:var(--fg-muted);font-family:var(--font-mono);font-size:14px;
  letter-spacing:.9px}
/* text sitting ON the pine core, which is dark in BOTH themes */
.pnv-on-core{fill:var(--brand-fg)}
.pnv-small.pnv-on-core{fill:color-mix(in srgb,var(--brand-fg) 88%,transparent)}

/* Dark theme: the core is a light pine on ink, so its label must flip to ink
   rather than staying paper. --brand/--brand-fg already swap for us; the node
   fill is the one thing that needs saying, because --bg-raised is pure white
   in light mode and a lifted ink in dark. */
:root[data-theme="dark"] .pnv-node{fill:var(--bg-raised);stroke:var(--border)}

.pnv-dot{fill:var(--accent);offset-distance:0%;
  animation:pnv-flow 4.2s cubic-bezier(.55,0,.45,1) infinite}
@keyframes pnv-flow{
  0%{offset-distance:0%;opacity:0}
  12%{opacity:1}
  88%{opacity:1}
  100%{offset-distance:100%;opacity:0}
}
/* The spec allows "very subtle connector flow" and nothing else continuous,
   and no interaction may depend on animation — the dots carry no information
   the lines do not already carry, so removing them removes nothing. */
@media (prefers-reduced-motion:reduce){
  .pnv-dot{animation:none;display:none}
}
/* ---------- the inline recognition drawing ---------------------------------
   `request-stream-visual.tsx`. Second drawing to come off the plate, for the
   same reason as the hero: an <img> is opaque to page CSS, so its colours have
   to be hard-coded, so it needs a paper plate in both themes — which is the
   white box that was sitting in the middle of the dark theme.

   Losing the plate also returns its padding to the drawing: 387px of a 445px
   column becomes the full 445, so the scale goes 0.84 -> 0.97 and the mono
   labels land at 13.5px instead of 7.6px. That is why these sizes are 17/14
   and the hero's are the same numbers doing much more work at 0.6.

   Everything below is a brand token. No hex values. */
.rqs{margin:0;display:grid;gap:14px}
.rqs svg{width:100%;height:auto;overflow:visible}
.rqs figcaption{font-size:.8125rem;color:var(--fg-muted);line-height:1.55;
  max-width:58ch}

.rqs-node{fill:var(--bg-raised);stroke:var(--border);stroke-width:1}
.rqs-rail{fill:none;stroke:var(--border);stroke-width:1.4;stroke-linecap:round}
/* dashed clay, the same connector language the hero uses, so the two drawings
   read as one system rather than two illustrations */
.rqs-line{fill:none;stroke:var(--accent);stroke-width:1.6;stroke-linecap:round;
  stroke-dasharray:4 7;opacity:.5}
.rqs-label{fill:var(--fg);font-family:var(--font-display);font-size:17px;
  font-weight:600}
.rqs-small{fill:var(--accent);font-family:var(--font-mono);font-size:14px;
  letter-spacing:.9px;opacity:.85}
/* Re-proportioning the drawing wider (460 -> 550 units) costs scale on a
   phone: 342px of container is 0.62, which put the mono at 8.7px. Safe to
   bump — "REQUEST 01" measures 111 units at 16px against 162 of room in the
   narrowest box — and being inline is the only reason a media query can reach
   it at all. The 14px base is what check:svgs measures the boxes against. */
@media (max-width:430px){
  .rqs-small{font-size:16px}
}

/* The dot runs the rail; each pip swells as it arrives. transform-box:fill-box
   makes the scale happen about the pip's own centre rather than the SVG's
   origin — without it every pip flies off toward the top-left corner. */
.rqs-pip{fill:var(--accent);transform-box:fill-box;transform-origin:center;
  animation:rqs-pip 5s linear infinite}
@keyframes rqs-pip{
  0%{transform:scale(1);opacity:.55}
  4%{transform:scale(1.7);opacity:1}
  16%{transform:scale(1);opacity:.55}
  100%{transform:scale(1);opacity:.55}
}
.rqs-dot{fill:var(--accent);offset-distance:0%;
  animation:rqs-flow 5s linear infinite}
@keyframes rqs-flow{
  0%{offset-distance:0%;opacity:0}
  6%{opacity:1}
  94%{opacity:1}
  100%{offset-distance:100%;opacity:0}
}
/* The spec allows a very subtle connector flow and nothing else continuous.
   The dots carry no information the drawing does not already carry, so
   removing them removes nothing. */
@media (prefers-reduced-motion:reduce){
  .rqs-dot{animation:none;display:none}
  .rqs-pip{animation:none;opacity:.85}
}

/* ---------- the other seven drawings, inlined ------------------------------
   `.pnv` and `.rqs` above each got their own block because each was inlined on
   its own. Seven more at once would be seven more near-identical copies, so
   these share one family and differ only where they genuinely differ.

   WHAT THEY SHARE is every colour. That is the whole reason for inlining: an
   <img> renders its SVG in an opaque subresource, page CSS cannot reach inside
   it, so the file has to hard-code pine-on-paper, so the drawing needs a paper
   plate in both themes to stay legible — and that plate is the white box in the
   middle of the dark theme. Inlined, the artwork reads the same brand tokens as
   the page and needs no plate at all. Nothing below is a hex value.

   WHAT DIFFERS IS TYPE SIZE, and it has to. Effective on-screen size is the
   authored size times (rendered width / viewBox width), and these nine render
   at scales from 0.34 to 1.23. One shared size would therefore land at nine
   different sizes on screen. Each drawing sets --t-label / --t-small for its
   own scale, measured, in the sizing block further down. */
.dgv{margin:0;display:grid;gap:14px}
.dgv svg{width:100%;height:auto;overflow:visible}
.dgv figcaption{font-size:.8125rem;color:var(--fg-muted);line-height:1.55;
  max-width:58ch}

/* a node box: a chip of raised surface on the page, in either theme */
.dgv-node{fill:var(--bg-raised);stroke:var(--border);stroke-width:1}
/* the pine core, and text sitting on it. Both flip with the theme together —
   --brand is pine on paper and a light pine on ink, and --brand-fg is its
   correct contrast partner in each case, which is why the pair is used rather
   than a fixed paper. This is the hero's .pnv-core/.pnv-on-core language. */
.dgv-core{fill:var(--brand);stroke:none}
/* a flat area rather than a node: the constellation and coverage drawings each
   painted a full-bleed pine rectangle to sit their light text on. That is the
   opposite of transparent, so the rectangle is gone and what remains of it is
   this — a quiet surface with an edge, following the theme. */
.dgv-panel{fill:var(--ui-bg);stroke:var(--border);stroke-width:1}
/* structural rule: a spine, a divider, a spoke. Carries no flow. */
.dgv-rail{fill:none;stroke:var(--border);stroke-width:1.4;stroke-linecap:round}
/* connector: dashed clay, the same language both drawings above use, so all
   nine read as one system rather than nine illustrations.

   The dashes themselves drift, which is the motion these seven mostly use. A
   travelling dot is the wrong instrument here: the hero and the recognition
   drawing each have one long rail, but these have connectors as short as 28
   units, and a dot crossing 28 units reads as a blink rather than as travel.
   Drifting the dash pattern moves at a fixed speed in user units, so every
   connector in every drawing flows at the same rate no matter its length —
   which is exactly what a dot cannot do without per-path duration arithmetic.
   13 = the 5+8 dash period, so the cycle is seamless. */
.dgv-line{fill:none;stroke:var(--accent);stroke-width:2;stroke-linecap:round;
  stroke-linejoin:round;stroke-dasharray:5 8;opacity:.5;
  animation:dgv-dash 1.7s linear infinite}
@keyframes dgv-dash{to{stroke-dashoffset:-13}}
.dgv-label{fill:var(--fg);font-family:var(--font-display);font-weight:600;
  font-size:var(--t-label)}
.dgv-small{fill:var(--fg-muted);font-family:var(--font-mono);letter-spacing:1px;
  font-size:var(--t-small)}
/* Text sitting ON the pine core. These come AFTER the two rules above and are
   written as compound selectors, and both of those things are load-bearing:
   `.dgv-label` and `.dgv-on-core` are one class each, so at equal specificity
   the later rule wins — declared first, `.dgv-on-core` lost to `.dgv-label`'s
   own fill, and the foundation band's label rendered page-foreground on pine, a
   luminance gap of 0.11. The dark theme scored 0.39 on the same bug and looked
   fine, which is why this was only visible from the light side. */
.dgv-label.dgv-on-core,.dgv-on-core{fill:var(--brand-fg)}
.dgv-small.dgv-on-core{fill:color-mix(in srgb,var(--brand-fg) 88%,transparent)}
.dgv-accent{fill:var(--accent)}
.dgv-stroke-accent{fill:none;stroke:var(--accent);stroke-width:2}

/* ---- motion. The spec allows a very subtle connector flow and nothing else
   continuous, and no interaction may depend on it: every dot travels a line
   that is already drawn, so removing them removes no information.

   Duration is set per element rather than here. A dot crossing a 500-unit
   connector and one crossing a 90-unit connector share a drawing, and giving
   them one duration makes the short one crawl — the eye reads speed, not
   duration. Each component sets its own from the path it actually runs. */
.dgv-dot{fill:var(--accent);offset-distance:0%;offset-rotate:0deg;
  animation-name:dgv-flow;animation-timing-function:linear;
  animation-iteration-count:infinite}
@keyframes dgv-flow{
  0%{offset-distance:0%;opacity:0}
  8%{opacity:1}
  92%{opacity:1}
  100%{offset-distance:100%;opacity:0}
}
/* transform-box:fill-box makes the swell happen about the pip's own centre.
   Without it every pip scales about the SVG origin and flies to the top-left. */
.dgv-pip{fill:var(--accent);transform-box:fill-box;transform-origin:center;
  animation:dgv-pip 4.8s linear infinite}
@keyframes dgv-pip{
  0%{transform:scale(1);opacity:.55}
  5%{transform:scale(1.65);opacity:1}
  18%{transform:scale(1);opacity:.55}
  100%{transform:scale(1);opacity:.55}
}
/* a halo that breathes rather than travels — used where the drawing has points
   of emphasis but no path to run along */
.dgv-ring{fill:none;stroke:var(--accent);stroke-width:1.5;
  transform-box:fill-box;transform-origin:center;
  animation:dgv-ring 4.8s ease-in-out infinite}
@keyframes dgv-ring{
  0%{transform:scale(.72);opacity:.5}
  45%{transform:scale(1.1);opacity:.12}
  70%{transform:scale(.72);opacity:.5}
  100%{transform:scale(.72);opacity:.5}
}
@media (prefers-reduced-motion:reduce){
  .dgv-line{animation:none}
  .dgv-dot{animation:none;display:none}
  .dgv-pip{animation:none;opacity:.85}
  .dgv-ring{animation:none;opacity:.3;transform:none}
}

/* ---- per-drawing type size ------------------------------------------------
   Every one of these is a CEILING, not a preference. A label grows until it
   runs out through its own box, and `scratchpad/maxtype.mjs` searched for where
   that happens on each drawing rather than guessing; `pnpm check:svgs` then
   holds the answer, measuring the twin file at these exact sizes.

   Four are AT their ceiling and cannot go higher without wider boxes — which
   means a wider canvas, which means a smaller scale, which gives most of the
   gain straight back. That is geometry work, not a colour change, and it is
   tracked separately. The comment after each is the ceiling the search found.

   No mobile overrides. The problem on a phone is scale, not size, and it is
   fixed as scale directly — see the width floor below. */
.dgv-dep{--t-label:17px;--t-small:10px}   /* dep-path   mono AT ceiling 10 */
.dgv-frag{--t-label:18px;--t-small:10px}  /* fragmented, ceiling 18/11    */
.dgv-cap{--t-label:22px;--t-small:17px}   /* constellation, ceiling 29/22 */
.dgv-found{--t-label:16px;--t-small:11px} /* layers   label AT ceiling 16 */
.dgv-path{--t-label:19px;--t-small:12px}  /* pathways, ceiling 19/26      */
.dgv-mkt{--t-label:16px;--t-small:17px}   /* coverage, bounded by canvas  */
.dgv-own{--t-label:20px;--t-small:13px}   /* ownership  mono AT ceiling 13 */

/* ---- the width floor ------------------------------------------------------
   A drawing rendered into a 342px phone column loses a third of the scale it
   has on a desktop, so type that is merely small at 1440 becomes texture at
   390 — that is where partner-pathways reached 3.1px. Bumping the font is the
   wrong lever, because the ceiling above is a fact about the boxes and does not
   move with the viewport. Bumping the SCALE is the right one: below 700px each
   figure keeps a floor width and scrolls, so a phone renders it no smaller than
   a desktop does, and the ceiling is the only limit left anywhere.

   tabindex on the scroller is not decoration. A scrollable region that is not
   focusable cannot be reached by keyboard at all, so a sighted keyboard user
   would be unable to see the right-hand half of any of these. */
.dgv-scroll{overflow-x:auto;overscroll-behavior-x:contain}
.dgv-scroll:focus-visible{outline:2px solid var(--ring);outline-offset:3px}
/* fragmented-journey's floor is NOT inside the 700px query, because it is the
   only landscape drawing in a full-width slot: at 1020 units it is already
   narrower than the container at 1440 and 1024, and it starts losing scale at
   around 880 rather than at 700. A min-width is inert while the container is
   wider than it, so declaring it unconditionally costs nothing above that and
   is the only thing that covers the 700-900 band, where the half-width
   drawings are still fine and this one is not. */
.dgv-frag .dgv-scroll svg{min-width:840px}
@media (max-width:700px){
  /* Per drawing, because "the desktop scale" is a different number for each:
     460 already beats desktop for the 560-unit drawings, dependency-path is the
     widest half at 640 units and needs 520 to match its desktop scale, and
     pathways is 900 units in a full-width slot, so it needs 720 — a 2.1x
     scroll, and still the cheapest of the options, since its ceiling caps the
     type at a size that renders 3.1px unscrolled. */
  .dgv-scroll svg{min-width:460px}
  .dgv-dep .dgv-scroll svg{min-width:520px}
  .dgv-path .dgv-scroll svg{min-width:720px}
}

/* the right column is optional; when a page omits it the heading must not
   stretch to 72rem, so the grid collapses rather than the column emptying. */
.page-hero-grid:has(> :only-child){grid-template-columns:1fr}
@media (max-width:900px){
  .page-hero-grid{grid-template-columns:1fr;gap:28px;align-items:start}
}

/* breadcrumb. Small, muted, never competing with the h1 above it. */
.crumb{font-family:var(--font-mono);font-size:.6875rem;letter-spacing:.12em;
  text-transform:uppercase;color:var(--fg-muted);margin-bottom:18px;
  display:flex;flex-wrap:wrap;gap:8px;align-items:center}
.crumb a{text-decoration:none;color:var(--fg-muted)}
.crumb a:hover{color:var(--accent)}
.crumb span[aria-hidden]{opacity:.45}

/* ---------- section header -------------------------------------------------
   Heading left, supporting sentence right. This is the rhythm the whole
   homepage is built on; it was written three times inline and is now one rule. */
.sec-head{display:grid;grid-template-columns:1.05fr .95fr;
  gap:clamp(32px,4.6vw,64px);align-items:end;margin-bottom:clamp(32px,4vw,52px)}
.sec-head h2{max-width:20ch}
.sec-head h2 em{font-style:normal;color:var(--accent)}
.sec-head .lead{max-width:50ch}
.sec-head.top{align-items:start}
@media (max-width:900px){
  .sec-head{grid-template-columns:1fr;gap:20px;align-items:start}
}

/* ---------- ruled cell grid ------------------------------------------------
   One object with internal hairlines rather than N floating cards, so a set
   reads as a set. Zero gap and border-inline-start on every cell except the
   first in a row is the whole trick. */
.ruled{display:grid;border:1px solid var(--border);border-radius:var(--radius-lg);
  background:var(--bg-raised);overflow:hidden}
.ruled-2{grid-template-columns:repeat(2,1fr)}
.ruled-3{grid-template-columns:repeat(3,1fr)}
.ruled-4{grid-template-columns:repeat(4,1fr)}
.rcell{padding:clamp(20px,2.4vw,30px);border-inline-start:1px solid var(--border);
  border-top:1px solid var(--border)}
/* the first row has the container's own border above it, and the first column
   has the container's own border beside it, so both would double up. */
.ruled-2 .rcell:nth-child(-n+2),
.ruled-3 .rcell:nth-child(-n+3),
.ruled-4 .rcell:nth-child(-n+4){border-top:0}
.ruled-2 .rcell:nth-child(2n+1),
.ruled-3 .rcell:nth-child(3n+1),
.ruled-4 .rcell:nth-child(4n+1){border-inline-start:0}
.rcell .k{font-family:var(--font-mono);font-size:.625rem;font-weight:500;
  letter-spacing:.16em;text-transform:uppercase;color:var(--accent);
  display:block;margin-bottom:18px}
.rcell h3{margin-bottom:10px}
.rcell p{color:var(--fg-muted);font-size:.9375rem}
.rcell ul{margin:14px 0 0;padding-inline-start:18px;display:grid;gap:7px;
  color:var(--fg-muted);font-size:.9375rem}
@media (max-width:900px){
  /* one column below the breakpoint, so every cell needs the top rule back and
     none needs a side rule. Rewritten wholesale rather than patched, because
     the nth-child sums above are wrong at a different column count. */
  .ruled-2,.ruled-3,.ruled-4{grid-template-columns:1fr}
  .ruled .rcell{border-inline-start:0;border-top:1px solid var(--border)}
  .ruled .rcell:first-child{border-top:0}
}
@media (min-width:901px) and (max-width:1100px){
  .ruled-4{grid-template-columns:repeat(2,1fr)}
  .ruled-4 .rcell{border-top:1px solid var(--border);
    border-inline-start:1px solid var(--border)}
  .ruled-4 .rcell:nth-child(-n+2){border-top:0}
  .ruled-4 .rcell:nth-child(2n+1){border-inline-start:0}
}

/* ---------- hub cards ------------------------------------------------------
   Used by /services and /who-we-serve to route onward. A whole-cell link, so
   the target is the card and not four words inside it. */
.hubgrid{display:grid;grid-template-columns:repeat(3,1fr);gap:16px;margin-top:8px}
@media (max-width:1000px){.hubgrid{grid-template-columns:repeat(2,1fr)}}
@media (max-width:640px){.hubgrid{grid-template-columns:1fr}}
.hubcard{display:flex;flex-direction:column;gap:10px;padding:26px;
  border:1px solid var(--border);border-radius:var(--radius-lg);
  background:var(--bg-raised);text-decoration:none;
  transition:border-color var(--duration) var(--ease),
             transform var(--duration) var(--ease)}
.hubcard:hover{border-color:var(--accent);transform:translateY(-2px)}
.hubcard .k{font-family:var(--font-mono);font-size:.625rem;letter-spacing:.16em;
  text-transform:uppercase;color:var(--accent)}
.hubcard h3{font-size:1.1875rem}
.hubcard p{color:var(--fg-muted);font-size:.9375rem;flex:1}
.hubcard .hub-go{font-size:.875rem;color:var(--accent);font-weight:500}

/* ---------- numbered steps on light ----------------------------------------
   .steps already exists but is scoped to the dark band (white hairlines, clay
   numerals). This is the same shape for a paper background. */
.numsteps{counter-reset:s;display:grid;gap:0;margin:0;padding-inline-start:0}
.numsteps li{list-style:none;counter-increment:s;display:grid;
  grid-template-columns:auto 1fr;gap:20px;padding:22px 0;
  border-top:1px solid var(--border);align-items:baseline}
.numsteps li:first-child{border-top:0;padding-top:0}
.numsteps li::before{content:counter(s,decimal-leading-zero);
  font-family:var(--font-display);font-size:.875rem;color:var(--accent);
  font-weight:600}
.numsteps b{display:block;font-size:1.0625rem;font-weight:600;margin-bottom:5px}
.numsteps span{color:var(--fg-muted);font-size:.9375rem}

/* ---------- callout --------------------------------------------------------
   An accent rule and nothing else. Used where a page has to say one thing
   plainly — a limit, an honesty note, a definition. */
.callout{padding:4px 0 4px 24px;border-inline-start:3px solid var(--accent);
  color:var(--fg-muted);max-width:62ch}
.callout strong{color:var(--fg);font-weight:600}
.callout.boxed{padding:22px 26px;border-inline-start-width:4px;
  background:var(--ui-bg);border-radius:0 var(--radius) var(--radius) 0;
  max-width:none}
/* .wide — the accent rule with no box, running the full container. 62ch is a
   reading measure and it is right for a callout sitting beside other content;
   it is wrong for one closing a section, where the short line reads as the
   block having failed to fill its row. */
/* text-wrap:pretty because a full-container measure is exactly what strands a
   single word on the last line — 62ch was doing that work implicitly. Longer
   copy fixes it at one width; this stops it coming back at the next. Ignored
   by browsers that do not support it, which costs nothing. */
.callout.wide{max-width:none;text-wrap:pretty}

/* ---------- two-panel contrast --------------------------------------------
   "A good fit" beside "Not a fit". Tinted rather than bordered, because the
   point is that the two halves are different, not that they are cells. */
.duo{display:grid;grid-template-columns:1fr 1fr;border:1px solid var(--border);
  border-radius:var(--radius-lg);overflow:hidden}
.duo > div{padding:clamp(24px,3.4vw,44px)}
.duo .yes{background:color-mix(in srgb,var(--pine-500) 9%,var(--bg-raised))}
.duo .no{background:color-mix(in srgb,var(--clay-600) 9%,var(--bg-raised))}
.duo h3{margin-bottom:16px}
.duo ul{margin:0;padding-inline-start:20px;display:grid;gap:11px;
  font-size:.9375rem}
/* the third variant: one half on pine. Used where the two sides are "what you
   do" and "what we do" rather than a judgement. */
.duo .deep{background:var(--pine-900);color:var(--paper-100)}
.duo .deep li{color:rgba(251,249,246,.78)}
.duo ol{margin:0;padding-inline-start:20px;display:grid;gap:11px;
  font-size:.9375rem}
@media (max-width:820px){.duo{grid-template-columns:1fr}
  .duo .no{border-top:1px solid var(--border)}}

/* ---------- question list --------------------------------------------------
   Native <details name> gives one-open-at-a-time with no JavaScript. */
.qa{border-top:1px solid var(--border)}
.qa details{border-bottom:1px solid var(--border)}
.qa summary{cursor:pointer;padding:18px 40px 18px 0;font-weight:500;
  position:relative;list-style:none}
.qa summary::-webkit-details-marker{display:none}
.qa summary::after{content:"";position:absolute;right:8px;top:26px;width:9px;
  height:9px;border-right:1.5px solid var(--fg-muted);
  border-bottom:1.5px solid var(--fg-muted);transform:rotate(45deg);
  transition:transform var(--duration) var(--ease)}
.qa details[open] summary::after{transform:rotate(-135deg);top:30px}
.qa details > p{padding:0 40px 22px 0;color:var(--fg-muted);max-width:70ch}

/* ---------- stat row -------------------------------------------------------
   Figures with a label under them. No invented outcome metrics — this is for
   facts about the service (price, turnaround, what is included). */
.metrics{display:grid;grid-template-columns:repeat(3,1fr);gap:clamp(16px,2vw,28px);
  margin-top:36px}
@media (max-width:700px){.metrics{grid-template-columns:1fr}}
.metric b{display:block;font-family:var(--font-display);font-size:2.25rem;
  font-weight:600;line-height:1;color:var(--brand)}
.metric span{display:block;font-size:.875rem;color:var(--fg-muted);margin-top:8px}
.dark .metric b{color:var(--clay-300)}

/* ---------- closing conversion band ---------------------------------------
   Generic version of the homepage's .final, for pages that end with an ask. */
.cta-band{background:var(--pine-800);color:var(--paper-100)}
.cta-band .inner{display:grid;grid-template-columns:1.15fr .85fr;
  gap:clamp(32px,5vw,64px);align-items:center}
.cta-band .h2{max-width:18ch}
.cta-band p{color:rgba(251,249,246,.78);max-width:54ch;margin-top:16px}
.cta-band .eyebrow{color:var(--clay-300)}
.cta-band .row{display:flex;flex-wrap:wrap;gap:12px}
@media (max-width:900px){
  .cta-band .inner{grid-template-columns:1fr;gap:28px;align-items:start}
}

/* ---------- long-form prose ------------------------------------------------
   Legal pages and the parts of /about that are genuinely just writing. */
.prose-page{max-width:70ch}
.prose-page h2{font-family:var(--font-display);font-size:var(--h2);
  font-weight:600;margin:44px 0 14px}
.prose-page h3{margin:30px 0 10px}
.prose-page p{margin-bottom:16px;color:var(--fg-muted)}
.prose-page ul,.prose-page ol{margin:0 0 18px;padding-inline-start:22px;
  display:grid;gap:9px;color:var(--fg-muted)}
.prose-page a{color:var(--accent);text-underline-offset:3px}
.prose-page > :first-child{margin-top:0}

/* ---------- forms ----------------------------------------------------------
   Shared by /contact and /migration. Labels above inputs, never placeholders
   as labels, and errors tied to the field with aria-describedby. */
.form{display:grid;gap:22px;max-width:640px}
.form-row{display:grid;grid-template-columns:1fr 1fr;gap:16px}
@media (max-width:640px){.form-row{grid-template-columns:1fr}}
/* align-content:start matters. Grid items stretch by default, so when one field
   in a row carries a hint line and its neighbour does not, the shorter field's
   INPUT grows to absorb the difference and you get a 70px-tall text box beside
   a 44px one. Sizing rows to content leaves the slack at the bottom instead. */
.field{display:grid;gap:7px;align-content:start}
.field > label{font-size:.875rem;font-weight:500}
.field .hint{font-size:.8125rem;color:var(--fg-muted)}
.field input,.field select,.field textarea{font:inherit;font-size:.9375rem;
  color:var(--fg);background:var(--bg-raised);border:1px solid var(--border);
  border-radius:var(--radius);padding:12px 14px;width:100%}
.field textarea{min-height:132px;resize:vertical}
.field input:focus-visible,.field select:focus-visible,
.field textarea:focus-visible{outline:2px solid var(--ring);outline-offset:1px}
.field .err{font-size:.8125rem;color:var(--clay-700)}
.field[data-invalid="true"] input,.field[data-invalid="true"] select,
.field[data-invalid="true"] textarea{border-color:var(--clay-700)}
.checks{display:grid;grid-template-columns:repeat(2,1fr);gap:10px}
@media (max-width:640px){.checks{grid-template-columns:1fr}}
.checks label{display:flex;gap:10px;align-items:flex-start;font-size:.9375rem;
  padding:11px 13px;border:1px solid var(--border);border-radius:var(--radius);
  background:var(--bg-raised);cursor:pointer}
.checks input{margin-top:3px;accent-color:var(--accent)}
/* honeypot. Off-screen rather than display:none, because some bots skip
   anything that is not rendered. aria-hidden and tabindex keep it away from
   assistive tech and the keyboard. */
.hp{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}

/* ---------- demonstration screens ------------------------------------------
   Stylised representations of a page, inside the same browser chrome the
   homepage hero uses (.card.site-preview / .site-bar / .site-url, all already
   defined in the contract).

   Deliberately abstract. These are demonstrations of structure, not screenshots
   of a client site, and they must not be mistakable for one. */
.dscreens{display:grid;grid-template-columns:repeat(3,1fr);gap:18px;margin-top:8px}
@media (max-width:1000px){.dscreens{grid-template-columns:1fr 1fr}}
@media (max-width:680px){.dscreens{grid-template-columns:1fr}}
.dscr .body{padding:16px}
.dscr .band{font-family:var(--font-display);font-size:1.0625rem;font-weight:600;
  padding-bottom:12px;margin-bottom:4px;border-bottom:1px solid var(--border)}
.dscr .row{display:grid;grid-template-columns:1fr auto;gap:12px;
  align-items:center;padding:11px 0;border-bottom:1px solid var(--ui-line);
  font-size:.875rem;color:var(--fg-muted)}
.dscr .row:last-child{border-bottom:0}
.dscr .row::after{content:"";width:26px;height:6px;border-radius:99px;
  background:var(--ui-bg)}
.dscr[data-kind="form"] .row::after{width:40px;height:22px;border-radius:6px;
  border:1px solid var(--border);background:var(--bg)}
.dscr[data-kind="list"] .row::after{width:34px;background:var(--pine-300)}
.dscr .foot{padding:12px 16px;border-top:1px solid var(--border);
  background:var(--ui-bg);font-family:var(--font-mono);font-size:.625rem;
  letter-spacing:.14em;text-transform:uppercase;color:var(--fg-muted)}

/* ---------- skip link ------------------------------------------------------
   Off-screen until focused, then a real, visible control. Not display:none,
   which would take it out of the tab order and defeat the point. */
.skip{position:absolute;left:16px;top:-100px;z-index:60;padding:12px 18px;
  background:var(--accent);color:var(--accent-fg);border-radius:var(--radius);
  text-decoration:none;font-size:.9375rem;font-weight:500;
  transition:top var(--duration) var(--ease)}
.skip:focus-visible{top:16px}
main:focus{outline:none}

/* ---------- pricing tiers: reserved hours and the note beneath ---------------
   `.tier p` is `flex:1`, which is what makes the description absorb the slack
   so the tick line and the button sit level across all three cards. A second
   plain <p> would take a share of that slack and knock them out of line, so the
   hours sentence opts out with flex:none and sits tight under the description.

   `.tier-note` is the note that stops three prices reading as a feature ladder.
   Two columns on a wide screen because they are two separate ideas — what the
   plans share, and how the time works — and one on a narrow one. */
.tier .hours{flex:none;font-size:.875rem;color:var(--fg);font-weight:500;
  margin-bottom:20px}
.tier-note{margin-top:24px;display:grid;grid-template-columns:1fr 1fr;
  gap:clamp(20px,3vw,40px)}
.tier-note p{font-size:.875rem;color:var(--fg-muted);line-height:1.65}
@media (max-width:760px){.tier-note{grid-template-columns:1fr;gap:14px}}

/* The capability list that replaced the per-tier feature ladder. Three columns
   so twelve items read as one set rather than a long thing to scroll, and it
   never resembles the three price cards above it. */
.caplist{list-style:none;margin:0;padding:0;display:grid;
  grid-template-columns:repeat(3,1fr);gap:0 clamp(24px,4vw,48px);
  border-top:1px solid var(--border)}
.caplist li{padding:14px 0;border-bottom:1px solid var(--ui-line);
  font-size:.9375rem;display:grid;grid-template-columns:auto 1fr;gap:10px;
  align-items:baseline}
.caplist li::before{content:"";width:5px;height:5px;border-radius:50%;
  background:var(--accent);transform:translateY(-3px)}
@media (max-width:900px){.caplist{grid-template-columns:1fr 1fr}}
@media (max-width:600px){.caplist{grid-template-columns:1fr}}

/* ---------- announcement bar hairline ---------------------------------------
   A clay line along the bottom edge of the announce bar, fading to nothing at
   both ends. Taken from home-gpt.html, which is the only place it exists;
   home-a ends the bar on a hard colour change into the header and this softens
   that join.

   It is a pseudo-element rather than a border-image because a border would run
   the full width at full strength, which is the thing being replaced. The bar
   itself only ever set position on its .container, so the offset parent has to
   be established here or the line would hang off whatever ancestor is
   positioned. */
.announce{position:relative}
.announce::after{content:"";position:absolute;inset:auto 0 0;height:1px;
  background:linear-gradient(90deg,transparent,var(--clay-400),transparent)}

/* ---------- misc -----------------------------------------------------------*/
.taglist{display:flex;flex-wrap:wrap;gap:8px;margin-top:18px}
.demo-badge{display:inline-flex;align-items:center;gap:7px;
  font-family:var(--font-mono);font-size:.625rem;letter-spacing:.14em;
  text-transform:uppercase;color:var(--accent-fg);background:var(--accent);
  padding:5px 11px;border-radius:99px}
.lede-cols{columns:2;column-gap:clamp(28px,4vw,56px);color:var(--fg-muted)}
.lede-cols p{margin-bottom:14px;break-inside:avoid}
@media (max-width:760px){.lede-cols{columns:1}}

/* ---------- inner-page vertical rhythm --------------------------------------
   Sections pad on BOTH edges, so two stacked ones produce twice `--sec-y`:
   79.2 + 79.2 = 158px between every pair. Measured, on both the homepage and
   the inner pages — they are identical, and 158px is the signed-off contract.

   It works on the homepage because that page alternates dark bands, tinted
   sections and full-width imagery, and the space between them is doing a job.
   It does not work on a page like /partners, which runs eighteen plain
   sections on one background; the same gap with nothing in it reads as the
   page having lost its place.

   So inner pages get their own rhythm, about 103px at desktop. NOT a return of
   the rule deleted in W24 — that one zeroed the top padding of every second
   section and produced an alternating 138/79/158/79 stagger down the page.
   This keeps every gap identical and only changes the number.

   `main:has(> .page-hero)` is the scope. Every inner page opens with a
   PageHero; the homepage opens with `.hero.hero-deck` and is therefore never
   matched, which is what protects the contract. A descendant selector rather
   than `.page-hero ~ .section`, because sections nested inside a wrapper (the
   partner brief lives in one) are not siblings of the hero and the sibling
   combinator would skip exactly the page that needed this most.

   Where :has() is unsupported the page keeps 158px — the current look, not a
   broken one. */
main:has(> .page-hero) .section{
  padding-block:clamp(2.5rem,3.6vw,3.5rem)
}

/* ===========================================================================
   PARTNER OPPORTUNITY BRIEF
   Primitives for the private partner material on /partners. They exist because
   that page does something no other page does: it puts sourced sector facts and
   CauseOps scenario arithmetic side by side, and a reader must never be able to
   mistake one for the other.
   ========================================================================= */

/* The persistent "you are reading private material" marker. Sticky, because the
   brief is long and that context must not scroll away.

   The site header is also sticky at z-index 40 and comes first in the DOM, so
   this sits below it rather than fighting it. On a phone it goes static: two
   stacked sticky bars eat a third of a short viewport. */
.priv-band{position:sticky;top:0;z-index:30;background:var(--pine-900);
  color:var(--paper-100);font-family:var(--font-mono);font-size:.6875rem;
  letter-spacing:.14em;text-transform:uppercase}
.priv-band .container{display:flex;align-items:center;gap:12px;
  padding-block:11px;flex-wrap:wrap}
.priv-band i{width:6px;height:6px;border-radius:50%;background:var(--clay-400);
  flex:none}
.priv-band em{font-style:normal;color:rgba(251,249,246,.6);letter-spacing:.1em}
@media (max-width:640px){.priv-band{position:static}}
/* the way out, pushed to the far end of the band */
.priv-out{margin-inline-start:auto}
.priv-out button{background:none;border:1px solid rgba(251,249,246,.28);
  color:var(--paper-100);font:inherit;letter-spacing:.1em;cursor:pointer;
  padding:5px 12px;border-radius:99px;
  transition:border-color var(--duration) var(--ease),
    background var(--duration) var(--ease)}
.priv-out button:hover{border-color:var(--clay-400);
  background:rgba(251,249,246,.06)}

/* ---------- the locked state of /partners -----------------------------------
   A modal over a dimmed page. The page behind is a placeholder, not the brief
   with a cover on it — see the note in partner-gate.tsx. */

/* what sits under the scrim: enough to fill the viewport, and nothing else */
.gate-behind{min-height:62vh;display:grid;align-items:center}
.gate-behind .display{margin-top:10px}

/* z-index 50 clears the sticky site header at 40. `position:fixed` rather than
   absolute so the dialog stays put if the placeholder is ever taller than the
   viewport. */
.gate-scrim{position:fixed;inset:0;z-index:50;display:grid;place-items:center;
  padding:24px;overflow-y:auto;
  background:color-mix(in srgb,var(--bg) 62%,transparent);
  backdrop-filter:blur(7px);-webkit-backdrop-filter:blur(7px)}

.gate-modal{width:min(100%,30rem);padding:clamp(26px,4vw,38px);
  background:var(--bg-raised);border:1px solid var(--border);
  border-radius:var(--radius-lg);box-shadow:0 26px 70px rgba(15,42,32,.22)}
.gate-modal .h3{margin-top:16px}
.gate-modal>p{margin-top:12px;font-size:.9375rem;color:var(--fg-muted)}

.gate-form{margin-top:26px}
.gate-form label{display:block;font-family:var(--font-mono);font-size:.6875rem;
  letter-spacing:.12em;text-transform:uppercase;color:var(--fg-muted);
  margin-bottom:9px}
.gate-form input{display:block;width:100%;padding:13px 16px;font:inherit;
  font-family:var(--font-mono);letter-spacing:.14em;color:var(--fg);
  background:var(--bg);border:1px solid var(--border);
  border-radius:var(--radius);
  transition:border-color var(--duration) var(--ease),
    box-shadow var(--duration) var(--ease)}
.gate-form input:focus-visible{outline:none;border-color:var(--accent);
  box-shadow:0 0 0 3px color-mix(in srgb,var(--ring) 26%,transparent)}
.gate-form input[aria-invalid="true"]{border-color:var(--clay-500)}
.gate-form .btn{width:100%;margin-top:18px;justify-content:center}

/* Not colour alone: the message carries a rule down its edge, so it reads as an
   error to someone who cannot see the red. */
.gate-error{margin-top:14px;padding:2px 0 2px 14px;font-size:.875rem;
  color:var(--fg);border-inline-start:3px solid var(--clay-500)}

/* The way out for a visitor with no code. Full width under the submit so it
   reads as the second option, not a cancel button beside the first. */
.gate-home{display:flex;width:100%;margin-top:10px;justify-content:center}

.gate-ask{margin-top:22px;padding-top:20px;border-top:1px solid var(--border);
  font-size:.875rem;color:var(--fg-muted)}

/* A blur over a full-page scrim is expensive on low-end phones, and the dim
   alone carries the meaning. */
@media (prefers-reduced-motion:reduce){
  .gate-scrim{backdrop-filter:none;-webkit-backdrop-filter:none;
    background:color-mix(in srgb,var(--bg) 88%,transparent)}
}

/* Claim provenance. Every figure on the brief wears one of these, and the two
   look different on purpose: a scenario must never read as a published fact. */
.claimtag{display:inline-flex;align-items:center;gap:7px;
  font-family:var(--font-mono);font-size:.625rem;letter-spacing:.12em;
  text-transform:uppercase;padding:5px 10px;border-radius:99px;
  border:1px solid var(--border)}
.claimtag::before{content:"";width:5px;height:5px;border-radius:50%}
.claimtag.fact{color:var(--brand);
  border-color:color-mix(in srgb,var(--brand) 34%,var(--border))}
.claimtag.fact::before{background:var(--brand)}
.claimtag.illus{color:var(--accent);
  border-color:color-mix(in srgb,var(--accent) 34%,var(--border))}
.claimtag.illus::before{background:var(--accent)}

/* A sourced figure: the number, what it counts, and where it came from. The
   source line is part of the component, not a footnote — a footnote is how an
   unsourced number ends up on a page and nobody notices. */
.srcstats{display:grid;grid-template-columns:repeat(3,1fr);
  gap:0 clamp(24px,4vw,52px);margin-top:32px;border-top:1px solid var(--border)}
@media (max-width:900px){.srcstats{grid-template-columns:1fr 1fr}}
@media (max-width:600px){.srcstats{grid-template-columns:1fr}}
.srcstat{padding:22px 0;border-bottom:1px solid var(--ui-line)}
.srcstat b{display:block;font-family:var(--font-display);font-size:1.875rem;
  font-weight:600;line-height:1;color:var(--brand)}
.srcstat .what{display:block;margin-top:10px;font-size:.9375rem;color:var(--fg)}
/* The source line was 10px uppercase mono at 72% opacity. It measured 11.9:1
   against the page, comfortably past WCAG, and was still the hardest thing here
   to read — the ratio was never the problem. Ten-pixel all-caps mono with
   letter-spacing is a wall of identical-height shapes, and this line carries
   the one thing on the page a sceptical reader most wants to check.

   So: the body typeface at 13px, sentence case, normal spacing. Same colour,
   same role, actually readable. The mono/caps treatment stays where it earns
   its keep — badges and table headers, which are two or three words. */
.srcstat .src{display:block;margin-top:9px;font-size:.8125rem;
  color:var(--fg-muted);line-height:1.55}
.srcstat .src a{color:inherit;text-decoration-color:color-mix(in srgb,currentColor 40%,transparent);
  text-underline-offset:2px}
.srcstat .src a:hover{text-decoration-color:currentColor}

/* Data tables. Wide content scrolls inside its own box; the page body must
   never scroll sideways, which shots.mjs enforces on every route. */
.dtable-wrap{overflow-x:auto;margin-top:26px;border:1px solid var(--border);
  border-radius:var(--radius-lg)}
.dtable{width:100%;min-width:540px;border-collapse:collapse;font-size:.9375rem}
.dtable th,.dtable td{padding:14px 18px;text-align:left;
  border-bottom:1px solid var(--ui-line)}
.dtable thead th{font-family:var(--font-mono);font-size:.625rem;
  letter-spacing:.12em;text-transform:uppercase;color:var(--fg-muted);
  background:var(--ui-bg);border-bottom:1px solid var(--border);
  white-space:nowrap}
.dtable tbody tr:last-child td,.dtable tbody tr:last-child th{border-bottom:0}
.dtable .num{text-align:right;font-variant-numeric:tabular-nums}
.dtable tbody th{font-weight:600}
.dtable caption{caption-side:bottom;padding:14px 18px;text-align:left;
  font-size:.8125rem;color:var(--fg-muted);border-top:1px solid var(--border)}

/* The national-to-local progression. A chain of stages, not a fake diagram. */
.flowchain{list-style:none;margin:0;padding:0;display:grid;gap:0}
.flowchain li{position:relative;padding:16px 0 16px 32px;
  border-bottom:1px solid var(--ui-line);font-size:1rem}
.flowchain li:last-child{border-bottom:0}
.flowchain li::before{content:"";position:absolute;left:5px;top:24px;
  width:7px;height:7px;border-radius:50%;background:var(--accent)}
/* the connector, drawn between dots rather than through them */
.flowchain li:not(:last-child)::after{content:"";position:absolute;left:8px;
  top:35px;bottom:-6px;width:1px;background:var(--border)}

/* ---------- the recognition list -------------------------------------------
   The seven requests an agency already fields, set as what they are: things a
   client says out loud. Quoted and italic rather than bulleted, because a
   bulleted list of them reads as a feature inventory, and the point of the
   section is that the reader hears their own inbox.

   Two columns on desktop so seven short lines do not become a tall thin
   column with a ragged right edge. */
.asklist{list-style:none;margin:36px 0 0;padding:0;
  display:grid;grid-template-columns:1fr 1fr;gap:0 clamp(24px,4vw,48px);
  border-top:1px solid var(--border)}
@media (max-width:700px){.asklist{grid-template-columns:1fr}}
.asklist li{padding:15px 0 15px 26px;position:relative;
  border-bottom:1px solid var(--ui-line);
  font-family:var(--font-display);font-size:1.0625rem;font-style:italic;
  color:var(--fg)}
/* an opening quote mark set in the gutter, so the text edge stays flush */
.asklist li::before{content:"\201C";position:absolute;left:0;top:14px;
  font-family:var(--font-display);font-size:1.5rem;line-height:1;
  color:var(--accent)}

/* ---------- .checks as a plain two-column list -----------------------------
   .checks began as a grid of form checkboxes and the partner brief reuses the
   grid for plain <li> items. Without this the browser defaults apply: a 40px
   list indent inside a grid cell, and a disc marker sitting where the column
   gap should be. */
ul.checks{margin:0;padding:0;list-style:none}
ul.checks > li{position:relative;padding:12px 0 12px 24px;font-size:.9375rem;
  color:var(--fg-muted);border-bottom:1px solid var(--ui-line)}
ul.checks > li::before{content:"";position:absolute;left:2px;top:19px;
  width:6px;height:6px;border-radius:50%;
  background:color-mix(in srgb,var(--brand) 55%,transparent)}

/* ---------- diagram layout -------------------------------------------------
   `.dgm` and its paper `.plate` used to live here, and both are gone with the
   last <img>. The plate existed only because an <img> is opaque to page CSS:
   the artwork had to hard-code pine-on-paper, so it needed a paper background
   in BOTH themes or its labels would have been near-black on ink. All nine
   drawings are inline React now and read brand tokens directly, which is what
   made the plate — the white box in the middle of the dark theme — removable
   rather than merely unfortunate. Their own rules are the `.dgv` block above.

   What survives here is layout: where a figure sits next to its prose.

   A diagram beside the sentences it illustrates. The spec asks for the visual
   summary to arrive before the detailed evidence, so this sits between the
   section header and the table or list that follows it.

   Below 900px it stacks and the figure lands immediately after the prose it
   explains, which is the reading order the spec requires. `.flip` only applies
   above that breakpoint — flipping order on a stacked column would put the
   drawing before its own explanation. */
.figrow{display:grid;grid-template-columns:1fr 1fr;
  gap:clamp(28px,4vw,56px);align-items:center;margin-top:clamp(24px,3vw,40px)}
/* `.align-ink` — the drawing's first mark level with the prose's first line.
   Centring aligns the figure BOX, and the box is not the drawing: the caption
   hangs below it, so a centred figure puts its ink high. Measured across the
   rows it started high in every one, from 19px on #wordpress upward.

   Opt-in rather than the default, because top-aligning also moves ALL the
   slack to the bottom, and where a drawing is much taller than its prose that
   is a worse problem than the one it fixes. Applied where the two columns are
   close in height. The remaining rows are a real finding, not a solved one.

   #platforms was the worst of them — a 244px column beside a 607px one, a
   363px hole — and it is not in this list any more because alignment was never
   its fix. A portrait drawing in a half-width column next to two paragraphs
   cannot be made to balance by moving it up or down; the hole is the height
   difference itself. That row is gone: the drawing turned landscape and took
   the full container, with the prose above it in `.lede-cols`. Where a
   drawing's height cannot be made to answer its prose's, that is the move,
   not an alignment keyword.

   This used to carry a padding compensation for the plate's own padding. With
   the plate gone the figure has none, so the boxes lining up IS the ink lining
   up, and the compensation would now be a 29px error rather than a fix. */
@media (min-width:901px){
  .figrow.align-ink{align-items:start}
}
.figrow .figtext{display:grid;gap:14px;align-content:center;color:var(--fg-muted)}
.figrow .figtext p{color:var(--fg-muted)}
.figrow .figtext .h3{color:var(--fg)}
@media (min-width:901px){
  .figrow.flip .dgv,.figrow.flip .rqs,.figrow.flip .pnv{order:-1}
  /* the tall portrait drawings read better given the narrower half */
  .figrow.tall{grid-template-columns:1.15fr .85fr}
  .figrow.tall.flip{grid-template-columns:.85fr 1.15fr}
}
@media (max-width:900px){
  .figrow{grid-template-columns:1fr;gap:24px}
}

/* The wide pathways drawing, which is a summary of the whole page and gets the
   full container rather than half of it. */
.dgv-full{margin-top:clamp(24px,3vw,40px)}
.dgv-full figcaption{max-width:none}

/* ---------- .taglist chips -------------------------------------------------
   .crumb is the BREADCRUMB component: display:flex, margin-bottom:18px,
   uppercase mono, no box. Used inside .taglist as a pill it inherited all of
   that and none of what a pill needs, so the capability list, the niche list
   and the client-owned accounts rendered as a run of uppercase phrases with
   87px of stray vertical margin and no chip at all. Measured, not guessed.

   Sentence case on purpose. These are multi-word phrases, not labels, and 12
   uppercase mono phrases in a row is the same wall-of-identical-shapes problem
   that made the source lines unreadable. */
.taglist .crumb{display:inline-flex;align-items:center;margin-bottom:0;
  font-family:var(--font-body);font-size:.875rem;letter-spacing:0;
  text-transform:none;color:var(--fg);
  padding:7px 14px;border-radius:var(--radius-full);
  background:var(--bg-raised);border:1px solid var(--border)}
