/* GENERATED by scripts/build-public-jobs-css.mjs — do not edit. */
/* The display face: Archivo instanced at width 112 (semi-expanded) and
   weights 500-800, Latin subset, self-hosted so every surface gets the
   width without a font-stretch beside each of its uses. An absolute path,
   so the brief and jobs stylesheets resolve it on gtmengineersearch.com. */
@font-face {
  font-family: "Archivo SemiExpanded";
  src: url("/fonts/archivo-semiexpanded.woff2") format("woff2");
  font-weight: 500 800;
  font-style: normal;
  font-display: swap;
}

/* Paper. Declared on the root. */
:root {
  /* Paper — bone, barely warm. The page has no accent colour (DESIGN.md
     "The inks"): ink and paper do the work, and colour appears only where
     something real lives — the hero, real logos, portraits, a found
     person. */
  --paper: #f5f2ec;
  /* The surface. --paper stays paper inside the night, where --bg turns
     over: a paper tile set on the night reaches for it. */
  --bg: var(--paper);
  /* Night — the landing hero's panel: a warm near-black. Every dark
     surface on the site is this one colour (.section--night below). */
  --stock-night: #16130f;
  --text: #141412; /* near-black — body and headings */
  --text-soft: #4b4943;
  /* Faint is still text, so it clears the 4.5:1 body floor on paper. */
  --text-faint: #5d5a53;

  /* Action — near-black. Primary buttons only: the heaviest solid ink on
     the page, so it outweighs the vivid person marks around it. Hover goes
     darker on paper, lighter on the night. */
  /* Interaction states (DESIGN.md "Interaction states") — one vocabulary
     for every control, whatever sheet it sits on:
     hover  = the lift: lighter paper inside the structure outline;
     chosen = the structure fill, type knocked out to paper;
     open   = the marker turns (a long open body also takes the lift). */
  --lift: color-mix(in srgb, #fff 45%, var(--bg));

  --action: #141412;
  --action-hover: #3a3631;
  /* Wash — the light the primary button turns to on hover: the colour the
     night hero is washing in right now. public/pixel-field.js sets it on
     <html> as each wash begins (DESIGN.md "Motion"); this is the value
     before the first wash, and on pages with no hero — the hero's coral.
     Every hero ink holds a dark label at 8:1. */
  --wash: #f67b67;

  /* Accent — no colour: the text ink at full strength. What a hover, a
     checked box or the live step answers in; the page signals by
     brightness and weight, never by hue. */
  --accent: #141412;
  --accent-hover: #000;

  /* Structure DRAWS: rules, axes, labels, frame annotations. A warm
     graphite, so it never reads as a second colour. */
  --structure: #57564f;

  /* Fill — areas that stand for the market (bands, bars, washes). Never a
     line and never type. */
  --fill: #57564f;

  /* Rule system — every line on the page is one of these two, at 1px. */
  --rule: rgba(20, 20, 18, 0.12); /* internal separation: rows, dividers */
  --rule-strong: rgba(20, 20, 18, 0.24); /* structural edges: plates, bands */

  /* Detail — a warm grey: the link underline at rest, the quote glyph,
     ghost-hover. Quieter than the text, so a hover can answer in it. */
  --detail: #a59d90;
  --detail-text: #4b4943; /* stat values: the soft text ink */

  /* Person inks — the only vivid colour on the site: the night hero's
     washes (pixel-field.js carries its own copy), the primary button's
     hover (through --wash), and a found person in a drawing, carried down
     from the wash that found them. Never links, rules, states or text. */
  /* On paper: each hue at one lightness (OKLCH L 0.55), so every found
     pixel holds ~4.5:1 on the paper. The night's are lighter (below). */
  --person-vermilion: #c13e2d;
  --person-cobalt: #3c6ad3;
  --person-marigold: #976704;
  --person-viridian: #07856c;
  --person-orchid: #b23e88;

  --font-display: "Archivo SemiExpanded", "Helvetica Neue", Arial, sans-serif;
  --font-body: "Libre Franklin", -apple-system, "Segoe UI", Roboto, sans-serif;
  /* Labels — JetBrains Mono, sentence case: the instrument's annotation.
     Frame tags, eyebrows, HUD labels, indices. */
  --font-label: "JetBrains Mono", ui-monospace, "SFMono-Regular", monospace;

  --radius: 2px;
  --ease-out: cubic-bezier(0.22, 1, 0.36, 1);

  /* Widths — the same discipline as the inks: two, each with one job.
     --shell    the page container. Every section, plate and band aligns
                to it; nothing on the page is wider.
     --measure  the reading measure for any run of prose — ledes, section
                bodies, checklist and FAQ rows, timeline phases. 35rem is
                ~74 characters at the 17px body size, measured against
                the real average advance of Libre Franklin rather than
                the `ch` unit: this face's zero is 1.5x its average
                character, so the 68ch this site used to set was a
                105-character line.
     Two widths are deliberate exceptions, each commented where it is
     set: the testimonial's set-type width (centered display type reads
     on its own measure) and the candidate form's field width. A new
     width asks the same question a new color does — which of the two am
     I? — before it earns a third. */
  --shell: 1040px;
  --measure: 35rem;
  /* The frame's insets (site.css "The frame"): the screen edge to the
     outer rail, then the 8px rail channel, then the gutter to the content.
     The shell narrows to keep them, so the rails never crowd the content
     or the screen's edge. */
  --frame-edge: 16px;
  --frame-gutter: 40px;
  --inset: calc(var(--frame-edge) + 8px + var(--frame-gutter));

  /* Motion knobs — the ink press (DESIGN.md "Motion"). Every duration,
     delay and easing the print sequence uses lives here, so the feel is
     tuned in one place without touching the mechanism. ink-press.js
     reads the same values for its coverage-build clocks. */
  /* Arrival — how every piece of content (text, a card, a drawing, a
     plate) comes in: from a slight blur and a few pixels low, settling
     once (DESIGN.md "Motion"). Pixel marks have their own turn. */
  --arrive: 480ms;
  --arrive-blur: 4px;
  --arrive-rise: 6px;
  /* The field on a phone (<768px) prints at this fixed scale instead of
     slicing to a tall hero's height: 42px cells, two rows above the plate
     (the landing hero). */
  --field-phone-scale: 0.75;
  --field-phone-cell: calc(56px * var(--field-phone-scale));
}

@media (max-width: 640px) {
  :root {
    --frame-edge: 6px;
    --frame-gutter: 18px;
  }
}

/* The night (DESIGN.md "Paper and night") — the one dark surface: the landing
   hero, the chapters a page sets dark (.section--night), the testimonial
   flood, the header and the footer. It is a scope, not a skin: every token
   a component reads turns over here, so a component drawn for paper prints
   on the night unchanged. Each ink clears 4.5:1 on --stock-night. The
   brief's chosen shortlist tab is part of the screen above it, so it
   prints on the night too. A brief ends on paper after its close, so its
   footer opts out (.site-footer--paper). */
.night,
.section--night,
.section--flood,
.site-header,
.site-footer:not(.site-footer--paper),
.shortlist.is-live .is-chosen > .shortlist__tab {
  --bg: var(--stock-night);
  --text: #efebe4;
  --text-soft: #c9c2b6;
  --text-faint: #aaa396;
  --lift: #241f19;
  --action: #efebe4;
  --action-hover: #fff;
  --accent: #fff;
  --accent-hover: #fff;
  --structure: #b5aa98;
  --fill: #6e6556;
  --rule: rgba(239, 235, 228, 0.12);
  --rule-strong: rgba(239, 235, 228, 0.24);
  --detail: #7d7466;
  --detail-text: #c9c2b6;
  /* The hero's own inks: every hue at one lightness (OKLCH L 0.72), so
     no wash glows brighter or sinks deeper than another on the night. */
  --person-vermilion: #f67b67;
  --person-cobalt: #75a2ff;
  --person-marigold: #d8960b;
  --person-viridian: #08c09d;
  --person-orchid: #e779bb;
  background-color: var(--stock-night);
  color: var(--text);
}

/* Tokens built from other tokens resolve where they are declared, so they
   are declared on paper and again on the night: each surface draws them
   in its own inks. The link underline is a crisp 1px rule in the text
   ink at 40% (rest) — visible enough to say "link" on paper and night —
   and the full text ink (hover) (site.css "Links"). */
:root,
.night,
.section--night,
.section--flood,
.site-header,
.site-footer:not(.site-footer--paper),
.shortlist.is-live .is-chosen > .shortlist__tab {
  --lift-outline: inset 0 0 0 1px
    color-mix(in srgb, var(--structure) 55%, transparent);
  --underline: linear-gradient(
    transparent calc(50% - 0.5px),
    color-mix(in srgb, var(--text) 40%, transparent) 0,
    color-mix(in srgb, var(--text) 40%, transparent) calc(50% + 0.5px),
    transparent 0
  );
  --underline-hover: linear-gradient(
    transparent calc(50% - 0.5px),
    var(--accent) 0,
    var(--accent) calc(50% + 0.5px),
    transparent 0
  );
}


* {
  box-sizing: border-box;
}

body {
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-body);
  font-size: 17px;
  line-height: 1.6;
  margin: 0;
}

h1,
h2,
h3 {
  font-family: var(--font-display);
  font-weight: 600;
  line-height: 1.15;
  margin: 0 0 0.5em;
  text-wrap: balance;
}

h1 {
  font-size: clamp(2.1rem, 4.6vw, 3.1rem);
  letter-spacing: -0.01em;
  line-height: 1.12;
}

h2 {
  font-size: clamp(1.55rem, 3vw, 2.05rem);
}

h3 {
  font-size: 1.1rem;
}

p {
  margin: 0 0 1em;
}

/* ── Links — one signature (DESIGN.md "Links") ──
   Content links are ink with a 1px warm-grey underline (the detail ink
   annotating) and answer hover by turning it to full ink. Chrome (header, footer,
   breadcrumb) and row links opt out of the underline where their own
   pattern carries the affordance; buttons and the example byline keep
   their own treatment entirely. */
/* The underline is a background, not text-decoration: a 1px rule
   (--underline, tokens.css) laid along the foot of the text and cloned
   onto every line a link wraps to. Which rule prints is --ul, so a hover
   only swaps the variable — a link that opts out (background-image: none,
   below) never grows a line on hover.

   Every measure is in em, so the gap to the letters scales with the type:
   the band is 0.36em tall (the rule runs through its middle) and sits on
   a 0.12em inline padding below the text box — whose foot is the font's
   descent, ~0.25em under the baseline — which puts the rule about 0.19em
   below the baseline at any size. Inline padding doesn't move the line
   box, so no layout shifts. */
a {
  --ul: var(--underline);
  -webkit-box-decoration-break: clone;
  box-decoration-break: clone;
  background-image: var(--ul);
  background-position: 0 100%;
  background-repeat: repeat-x;
  background-size: 2.4em 0.36em;
  padding-bottom: 0.12em;
}

a {
  color: var(--text);
  text-decoration: none;
}

a:not(.btn):not(.byline):hover,
a:not(.btn):not(.byline):active {
  --ul: var(--underline-hover);
  color: var(--accent);
}

/* Standalone links — a link on its own line, not inside a sentence ("See
   all 22 examples", "LinkedIn"): the text and an arrow, → for a page on
   this site, ↗ for one elsewhere. No line at rest (the arrow says it);
   on hover the line arrives and the arrow nudges toward where it goes.
   The site's affordance in one rule: an underline or an arrow always
   means clickable, and nothing that isn't ever carries either. */
.link-go,
.link-out {
  background-image: none;
  font-weight: 500;
}

.link-go::after,
.link-out::after {
  display: inline-block;
  margin-left: 0.4em;
  transition: transform 180ms var(--ease-out);
}

.link-go::after {
  content: "→";
}

.link-out::after {
  content: "↗";
}

.link-go:hover,
.link-out:hover {
  background-image: var(--ul);
}

.link-go:hover::after {
  transform: translateX(3px);
}

.link-out:hover::after {
  transform: translate(2px, -2px);
}

@media (prefers-reduced-motion: reduce) {
  .link-go::after,
  .link-out::after {
    transition: none;
  }
}

/* Links whose own pattern carries the affordance print no line. */
.site-header__mark,
.competitor-wordmark,
.site-header__link,
.site-footer__mark,
.site-footer__col a,
.catalog__row,
.logos__link,
.btn,
.byline {
  background-image: none;
  padding-bottom: 0;
}

/* Interaction states — one consistent focus-visible indicator (structure ink) for
   every interactive element: buttons, links, FAQ summary, form inputs. */
a:focus-visible,
button:focus-visible,
summary:focus-visible,
.strip__consent input:focus-visible {
  outline: 2px solid var(--structure);
  outline-offset: 2px;
}

/* ── Page heroes (DESIGN.md "Page heroes") — every content page opens,
   and closes, on paper: its copy beside the field (PageHero, PageClose),
   the landing hero's pixels at rest (public/pixel-patch.js). Paper, so the
   page reads from its first line; the pixels keep it the same site. ── */
.paper-hero {
  border-bottom: 1px solid var(--rule);
  padding: 4.5rem 0 4rem;
}

.paper-hero__grid {
  align-items: center;
  display: grid;
  gap: 4rem;
  grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr);
}

.paper-hero__copy h1 {
  margin: 1.25rem 0;
  max-width: 16ch;
}

.paper-hero__copy > :first-child {
  margin-top: 0;
}

.paper-hero__copy .lede {
  color: var(--text-soft);
  max-width: 34rem;
}

.paper-close {
  border-top: 1px solid var(--rule);
}

.paper-close__body > :first-child {
  margin-top: 0;
}

.paper-close__body h2 {
  max-width: 20ch;
}

/* The field: square pixels in the paper's ink at three faint levels, a
   person found now and then in a hero ink. Each turns over as the hero's
   do — squashing to its centre line and opening out — and the field
   fades out at its edges, so it has no box. */
.pixel-patch {
  display: grid;
  gap: 4px;
  grid-template-columns: repeat(var(--cols), minmax(0, 1fr));
  -webkit-mask-image: radial-gradient(
    ellipse 70% 70% at 50% 50%,
    #000 45%,
    transparent 100%
  );
  mask-image: radial-gradient(
    ellipse 70% 70% at 50% 50%,
    #000 45%,
    transparent 100%
  );
}

.pixel-patch i {
  animation: pixel-on 260ms var(--ease-out) both;
  animation-delay: var(--d);
  aspect-ratio: 1;
  background: var(--text);
  opacity: var(--o);
}

.pixel-patch i.is-found {
  background: var(--ink);
  opacity: 1;
}

.pixel-patch.is-on i,
.pixel-patch.is-still i {
  animation: none;
}

.pixel-patch.is-on i.is-turning {
  animation: pixel-turn 260ms var(--ease-out);
}

@keyframes pixel-turn {
  50% {
    transform: scaleY(0.06);
  }
}

@media (max-width: 860px) {
  .paper-hero__grid {
    gap: 2.5rem;
    grid-template-columns: minmax(0, 1fr);
  }

  /* On a phone the field is a band under the copy, not a square beside
     it. */
  .pixel-patch {
    max-height: 9rem;
    overflow: hidden;
  }
}

/* The way back: a small typewriter line inside the plate, above the
   title, led by an arrow so it reads as "back" rather than as a label.
   Its links carry the drawn underline like every link, so they read as
   links; the page itself is the title below, so the trail stops at its
   parent. */
.breadcrumb {
  color: var(--structure);
  display: flex;
  flex-wrap: wrap;
  font-family: var(--font-label);
  font-size: 0.85rem;
  gap: 0.2rem 0.5rem;
  margin: 0 0 1.1rem;
}

.breadcrumb::before {
  content: "←";
}

.breadcrumb a {
  color: var(--text-soft);
}

.breadcrumb a:hover {
  color: var(--accent);
}

/* ── Browser surfaces ──────────────────────────────────────────────────
   The parts of the page the browser draws are still part of the print.
   Left at their defaults they ship someone else's design system onto
   warm paper: a blue selection, a black caret, a grey scrollbar. Each
   takes the ink whose job already covers it — no new hue, no new role. */

/* Selection is the highlighter pass: the detail grey laid over the
   surface at the strength a mark needs, the ink still printing through. */
::selection {
  background: color-mix(in srgb, var(--detail) 32%, var(--bg));
  color: var(--text);
}

/* The caret is the one mark the visitor makes on this page, so it prints
   in the action ink rather than the browser's black. */
.form-field input {
  caret-color: var(--accent);
}

/* Placeholders are prompts, not content — the faint ink, at full alpha
   so the value is the token's and not the browser's dimming on top. */
::placeholder {
  color: var(--text-faint);
  opacity: 1;
}

/* A long page's scrollbar is its tallest single element, and grey is the
   one color the ink table does not contain. */
html {
  scrollbar-color: color-mix(in srgb, var(--structure) 38%, var(--bg)) var(--bg);
}

/* Byline — the builder of a GTM engineer example, as the testimonial
   names its speaker: a square photo on a hairline, the name in the display
   face, the role in mono, and LinkedIn as a standalone outbound link (the
   text and ↗). The whole byline is the link; hover takes the name to full
   ink and nudges the arrow. It replaced the tucked "pocket card", a print
   object (2026-10-28). */
.byline {
  align-items: center;
  color: var(--text);
  display: inline-flex;
  gap: 1rem;
  margin-top: 1.75rem;
}

.byline__photo {
  border: 1px solid var(--rule-strong);
  flex: none;
  height: 56px;
  object-fit: cover;
  width: 56px;
}

.byline__id {
  display: grid;
  gap: 0.15rem;
}

.byline__name {
  font-family: var(--font-display);
  font-size: 1.05rem;
  font-weight: 700;
  line-height: 1.2;
}

.byline__role {
  color: var(--text-soft);
  font-family: var(--font-label);
  font-size: 0.8rem;
}

.byline__go {
  color: var(--structure);
  font-size: 0.85rem;
  font-weight: 500;
  margin-top: 0.2rem;
}

.byline__go::after {
  content: "↗";
  display: inline-block;
  margin-left: 0.35em;
  transition: transform 180ms var(--ease-out);
}

.byline:hover .byline__go {
  color: var(--text);
}

.byline:hover .byline__go::after {
  transform: translate(2px, -2px);
}

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

.shell {
  margin: 0 auto;
  width: min(var(--shell), calc(100% - 2 * var(--inset)));
}

.eyebrow {
  color: var(--structure);
  font-family: var(--font-label);
  font-size: 14.1px;
  font-weight: 500;
  margin-bottom: 1rem;
}

.section {
  padding: 6rem 0;
}

@media (min-width: 960px) {
  .section {
    padding: 7.5rem 0;
  }
}

/* ── The frame (DESIGN.md "The frame") — one grid the page measures
   itself against, drawn faint, on paper and night alike, in the surface's
   own text ink at 7%:
   - the rails: a channel of two 1px verticals, 8px apart, just outside
     the shell, running the page's whole height;
   - the band: every section opens on a channel of two hairlines, 8px
     apart, ruled between them as graph paper — a middle line and a line
     every 4px across, two rows of 3px cells;
   - the pixel: where a band crosses a rail, the two channels make an
     8px square — lit a step stronger, the hero's unit at every crossing.
   The inner rail sits 56px outside the shell where the screen allows;
   narrower, the shell gives way first (tokens.css --inset), so the rails
   keep at least --frame-edge from the screen and --frame-gutter from the
   content. Background positions resolve 50%
   against the box less the image, so every layer centres on the same
   line whatever its width. The rail's distance from the centre is a whole
   number of 4px cells (rounded down on a narrow screen, so a rail may sit
   up to 3px further in), so the band's graph paper, counted from the
   centre, meets both rails on a cell line. ── */
.site-header,
.hero--night,
.section,
.site-footer {
  --rail: min(
    calc(var(--shell) / 2 + 56px),
    round(down, calc(50% - var(--frame-edge) - 8px), 4px)
  );
  --frame: color-mix(in srgb, var(--text) 7%, transparent);
  background-image:
    linear-gradient(var(--frame), var(--frame)),
    linear-gradient(var(--frame), var(--frame)),
    linear-gradient(var(--frame), var(--frame)),
    linear-gradient(var(--frame), var(--frame));
  background-position:
    calc(50% - var(--rail) - 8px) 0,
    calc(50% - var(--rail)) 0,
    calc(50% + var(--rail)) 0,
    calc(50% + var(--rail) + 8px) 0;
  background-repeat: no-repeat;
  background-size: 1px 100%;
}

.section {
  position: relative;
}

/* The band is graph paper: its two hairlines, a third along the middle,
   and a 1px line every 4px across, so it prints as two rows of 3px cells.
   The across lines repeat from the rails' centre line (each tile is 4px,
   so a background 50% resolves against the box less 4px: + 1.5px lands
   the tile on the rails' centre, which is 50% of the box less a 1px line).
   Both rails stand a whole number of cells from that line, so each
   crossing square, 9px, covers exactly two cells and their lines, on both
   sides at every width. The rails snap a half pixel up to the next whole
   one; a gradient tile does not, so its place is rounded the same way, or
   every line blurs across two. */
.section::after {
  --frame: color-mix(in srgb, var(--text) 7%, transparent);
  background-image:
    linear-gradient(var(--frame), var(--frame)),
    linear-gradient(var(--frame), var(--frame)),
    linear-gradient(var(--frame), var(--frame)),
    linear-gradient(90deg, var(--frame) 0 1px, transparent 1px);
  background-position:
    0 0,
    0 100%,
    0 4px,
    round(nearest, calc(50% + 1.5px), 1px) 0;
  background-repeat: no-repeat, no-repeat, no-repeat, repeat-x;
  background-size:
    100% 1px,
    100% 1px,
    100% 1px,
    4px 100%;
  content: "";
  height: 9px;
  left: 0;
  pointer-events: none;
  position: absolute;
  right: 0;
  top: 0;
}

/* As a section's top edge passes (public/frame.js marks it .is-passing),
   its crossing squares pulse brighter and settle back — a scanner moving
   down the frame. --node-mix is registered so the pulse can animate. */
@property --node-mix {
  syntax: "<percentage>";
  inherits: false;
  initial-value: 16%;
}

.section.is-passing::before {
  animation: node-pass 1400ms var(--ease-out);
}

@keyframes node-pass {
  15% {
    --node-mix: 70%;
  }
}

/* Where the page turns between paper and night, the change of surface is
   the edge: the band and its squares stand down and only the rails carry
   through. A band marks paper meeting paper, or night meeting night. */
:is(.section--night, .section--flood, .night)
  + .section:not(.section--night, .section--flood)::after,
:is(.section--night, .section--flood, .night)
  + .section:not(.section--night, .section--flood)::before,
.section:not(.section--night, .section--flood)
  + :is(.section--night, .section--flood)::after,
.section:not(.section--night, .section--flood)
  + :is(.section--night, .section--flood)::before {
  content: none;
}

/* The crossing squares, placed by offsets rather than background
   percentages, so the box runs outer rail to outer rail and each square
   sits flush at its end. The rails are 1px backgrounds, and a background's
   50% resolves against the box less the line — half a pixel short of an
   offset's 50%. So the box takes the rails' own arithmetic: their centre
   line is 50% less half a pixel, and --rail is recomputed from that line
   (its own 50% would be half a pixel out, and its rounding could then
   land a cell away). Measured with the offset's 50%, the square sat a
   pixel right of the rails whenever the section's width was odd
   (2026-10-28). */
.section::before {
  --rail: min(
    calc(var(--shell) / 2 + 56px),
    round(down, calc(50% - 0.5px - var(--frame-edge) - 8px), 4px)
  );
  --node: color-mix(in srgb, var(--text) var(--node-mix), transparent);
  background:
    linear-gradient(var(--node), var(--node)) left top / 9px 9px no-repeat,
    linear-gradient(var(--node), var(--node)) right top / 9px 9px no-repeat;
  content: "";
  height: 9px;
  left: calc(50% - 0.5px - var(--rail) - 8px);
  pointer-events: none;
  position: absolute;
  right: calc(50% - 0.5px - var(--rail) - 8px);
  top: 0;
  z-index: 1;
}

/* ── The flood — the testimonial's night (one per page, #archive-story):
   a night section by another name, which the briefs share. Its tokens turn
   over in tokens.css, so the quote prints in the night's text ink. ── */
.section--flood .testimonial__role {
  color: var(--text-soft);
}

.section__lede {
  color: var(--text-soft);
  max-width: var(--measure);
  text-wrap: pretty;
}

/* ── Hanging heading — the site's one composition for prose-led sections,
   on the landing page and the content pages alike: the heading hangs in
   the left margin and the prose runs in the measure column beside it, so
   the section composes to the full shell instead of leaving a dead half
   beside a 35rem column. Content that is already full-shell — a band, the
   logo wall, the pricing plate, the deliverables — sits BELOW the grid,
   not inside it; that width change then reads as a deliberate contrast
   rather than a ragged edge. Sections built on display type (the flood
   quote, the closing statement) are not prose-led and keep their own
   composition. Stacks at 960px, the same breakpoint everywhere. ── */
.hang {
  display: grid;
  /* Row gap only bites in the stacked state (one column, heading over
     body) — the two-column state is a single row. */
  gap: 1.5rem;
}

@media (min-width: 960px) {
  .hang {
    align-items: start;
    column-gap: 4rem;
    grid-template-columns: 20rem minmax(0, var(--measure));
  }
}

.hang h2 {
  margin: 0;
}

/* The grid owns the spacing between the two columns' contents, so the
   first and last child give up the block margins they carry elsewhere
   (.checklist's lead-in, .faq's, a paragraph's trailing margin). */
.hang__body > :first-child {
  margin-top: 0;
}

.hang__body > :last-child {
  margin-bottom: 0;
}

/* ── TechFrame — a readout's frame: a strong hairline border, an optional
   mono tag/coord on its edge. ── */
.tech-frame {
  position: relative;
  border: 1px solid var(--rule-strong);
  /* Plates are the surface: section washes print around a plate, never
     through it (same knockout as .band > * cells). */
  background: var(--bg);
}
.tech-frame__tag,
.tech-frame__coord {
  position: absolute;
  z-index: 2;
  padding: 0 8px;
  font-family: var(--font-label);
  font-size: 12.9px;
  color: var(--structure);
}
/* The label's paper knockout must stop at the plate edge: the half that
   protrudes outside stays transparent so section washes (tint/dots) show
   through; the inner half paints paper, which both matches the plate
   interior and erases the 1px frame line running behind the text. The
   7px stop is the -8px offset (measured from the padding box) minus the
   1px border the label must still cover. */
.tech-frame__tag {
  background: linear-gradient(to bottom, transparent 7px, var(--bg) 7px);
  top: -8px;
  left: 18px;
}
.tech-frame__coord {
  background: linear-gradient(to top, transparent 7px, var(--bg) 7px);
  bottom: -8px;
  right: 18px;
}

/* ── Band — the multi-item container: flush paper cells between strong
   top/bottom rules, hairline dividers via the gap-over-background trick.
   Used by the stats HUD and the offer deliverables (containment ladder,
   see DESIGN.md). ── */
.band {
  background: var(--rule);
  border-bottom: 1px solid var(--rule-strong);
  border-top: 1px solid var(--rule-strong);
  display: grid;
  gap: 1px;
  margin: 2.5rem 0 0;
}

.band > * {
  background: var(--bg);
  margin: 0;
}

/* ── TelemetryHUD — stat cells on the band pattern ── */
/* Ported from src/marketing/styles/motif.css .hud (live-dot variant skipped —
   the recruiting site has no "live" state to telemeter). Labels in
   --structure; values in --detail-text (the soft text ink), set in the
   label face. */
.hud {
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
}
.hud__cell {
  padding: 1rem 1.25rem;
}
.hud__label {
  display: block;
  margin-bottom: 0.4rem;
  font-family: var(--font-label);
  font-size: 12.9px;
  color: var(--structure);
}
.hud__value {
  display: block;
  font-family: var(--font-label);
  font-size: 1.4rem;
  font-weight: 600;
  color: var(--detail-text);
}

/* ── The ink press — every readout arrives as a screen draws it, row by
   row, then holds still (DESIGN.md "Motion"). ink-press.js adds .is-printing to
   a [data-print] root once it is in view (the hero also waits for the
   webfonts); the art's own CSS takes it from there. Without the class
   (no script, reduced motion, a thumbnail) the resting state is the
   fully printed page. Knobs: tokens.css. ── */

/* Armed by ink-press.js at load: art waits blank for its arrival, so
   nothing scrolls in printed and then un-prints. Only ever set by the
   script, so no-JS and reduced-motion pages keep the printed rest. */
.will-print:not(.is-printing) .spot-illustration__ink {
  opacity: 0;
}

/* Arrival — the one way content comes in (DESIGN.md "Motion"): from a
   slight blur and a few pixels low, settling once, as the checklist's
   tokens do. Pixel marks have their own motion (pixel-on). */
@keyframes arrive {
  from {
    filter: blur(var(--arrive-blur));
    opacity: 0;
    transform: translateY(var(--arrive-rise));
  }
}

@media (prefers-reduced-motion: reduce) {
  .spot-illustration.is-printing .spot-illustration__ink {
    animation: none;
  }
}

/* ── Spot illustrations — closed at
   three on the landing page (DESIGN.md "Concept"). Art, not a container:
   no box. They sit at the top of a section's heading column. ── */
.spot-illustration {
  margin-bottom: 1.25rem;
}

.spot-illustration svg {
  display: block;
  overflow: visible;
}

/* Process drawings (DESIGN.md "Process drawings"): fragments of the real
   deliverables — a paper card with a label header, labelled fields, tier
   pills, scores out of ten, ticks — shared by the spot illustrations and
   the timeline and assessment art (services/recruiting-site/
   fragment-art.ts). Set in the site's own faces; a person is a pixel
   (.person-mark), a judgment a tick in the text ink. */
:is(.spot-illustration, .timeline-art) .frag-card {
  fill: var(--bg);
  stroke: color-mix(in srgb, var(--structure) 55%, transparent);
  stroke-width: 1;
}

:is(.spot-illustration, .timeline-art) .frag-rule {
  stroke: var(--rule);
  stroke-width: 1;
}

:is(.spot-illustration, .timeline-art) .frag-rule--tick {
  stroke: var(--rule-strong);
}

:is(.spot-illustration, .timeline-art) .frag-head {
  fill: var(--structure);
  font: 700 10.5px var(--font-label);
}

:is(.spot-illustration, .timeline-art) .frag-tag {
  fill: var(--text-faint);
  font: 400 10px var(--font-label);
}

:is(.spot-illustration, .timeline-art) .frag-label {
  fill: var(--structure);
  font: 400 10.5px var(--font-label);
}

:is(.spot-illustration, .timeline-art) .frag-score {
  fill: var(--text);
  font: 600 12px var(--font-display);
}

:is(.spot-illustration, .timeline-art) .frag-bar,
:is(.spot-illustration, .timeline-art) .frag-track {
  fill: color-mix(in srgb, var(--structure) 28%, transparent);
}

/* Tier 1 prints solid, Tier 2 outlined, Tier 3 a faint outline. */
:is(.spot-illustration, .timeline-art) .frag-pill {
  fill: none;
  stroke: var(--text);
  stroke-width: 1;
}

:is(.spot-illustration, .timeline-art) .frag-pill--t1 {
  fill: var(--text);
}

:is(.spot-illustration, .timeline-art) .frag-pill--t3 {
  stroke: var(--rule-strong);
}

:is(.spot-illustration, .timeline-art) .frag-pill-text {
  fill: var(--text);
  font: 700 9.5px var(--font-label);
}

:is(.spot-illustration, .timeline-art) .frag-pill-text--t1 {
  fill: var(--bg);
}

:is(.spot-illustration, .timeline-art) .frag-pill-text--t3 {
  fill: var(--text-soft);
}

:is(.spot-illustration, .timeline-art) .frag-tick {
  fill: none;
  stroke: var(--text-soft);
  stroke-linecap: square;
  stroke-width: 2;
}

:is(.spot-illustration, .timeline-art) .frag-tick--gap {
  stroke: var(--text-faint);
}

.spot-illustration.is-printing .spot-illustration__ink {
  animation: arrive var(--arrive) var(--ease-out) var(--d, 0ms) both;
}

/* A glimpse of the field (field-patch.ts) where the copy is
   about the market of people (DESIGN.md "Marks"): the
   hero's marks at the hero's strength, static, fading out to the right as
   if the field runs on past the column. */
.field-patch {
  display: block;
  height: auto;
  overflow: visible;
  width: 100%;
}

.field-patch [filter] {
  mix-blend-mode: multiply;
}

.field-patch__field {
  opacity: 0.62;
}

/* ── Marks (DESIGN.md "Marks") — every mark below the hero prints in the
   surface's own ink: a person pixel, a tick, a node, a check. There is no
   accent colour; what is live (a checked box, the step being read) answers
   in full-strength ink. The only colour below the hero is a found person:
   one pixel per drawing, in a hero ink, as if carried down from the wash
   that found them — each drawing its own ink, so none repeats. ── */
.person-mark--found {
  color: var(--text);
}

.spot-illustration--shortlist .person-mark--found {
  color: var(--person-vermilion);
}

.timeline__phase:nth-child(2) .person-mark--found {
  color: var(--person-cobalt);
}

.timeline__phase:nth-child(3) .person-mark--found {
  color: var(--person-orchid);
}

.timeline__phase:nth-child(5) .person-mark--found {
  color: var(--person-viridian);
}

.assess__art .person-mark--found {
  color: var(--person-vermilion);
}

/* Marigold is the hero's faintest ink and falls under 3:1 on paper, so it
   stays in the hero. */
.field-patch .person-mark--found {
  color: var(--person-viridian);
}

/* ── Ambient motion rests while its root is off screen — ink-press.js
   toggles .is-offscreen on every print root. Each selector outranks the
   rule that starts the animation, since the animation shorthand resets
   play-state to running. ── */
.spot-illustration.is-offscreen .spot-illustration__ink {
  animation-play-state: paused;
}

/* ── Motion gate (public/motion-gate.js) — an endless animation runs only
   while someone can see it. Under an element the gate has paused (off
   screen, or reduced motion), and everywhere while the tab is hidden,
   every animation holds where it is and resumes from there. !important
   because each animation's shorthand resets play-state to running, and
   this must outrank all of them. ── */
[data-motion="paused"],
[data-motion="paused"] *,
[data-motion="paused"] *::before,
[data-motion="paused"] *::after,
[data-motion-hidden] *,
[data-motion-hidden] *::before,
[data-motion-hidden] *::after {
  animation-play-state: paused !important;
}

/* ── Hero — the landing's and the brief's opening; the night hero below
   sets its surface. ── */
.hero {
  padding: 3rem 0 4rem;
  position: relative;
}
/* Display width mirrors content pages' documented 24ch (DESIGN.md
   "Widths") so a long headline breaks into balanced lines. */
.hero h1 {
  max-width: 24ch;
}
/* On a phone the plate takes most of the field, so the hero opens a full
   row of marks above it. */
@media (max-width: 767px) {
  .hero {
    padding-top: calc(2 * var(--field-phone-cell));
  }
}

.hero__content {
  padding: 3.5rem 3rem 3.25rem;
  position: relative;
  z-index: 1;
}

@media (max-width: 640px) {
  /* The plate already carries the shell's gutters on a phone, so its
     content takes the plate's full width inside its own padding. */
  .hero__content {
    padding: 2.25rem 1.25rem 2rem;
    width: auto;
  }
}

.hero__sub {
  color: var(--text-soft);
  font-size: 1.2rem;
  margin: 1.25rem 0 2rem;
  max-width: var(--measure);
}

.hero__ctas {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
}

/* ── The night hero (DESIGN.md "The night hero") — the landing page opens
   on a dark panel of pixels (public/pixel-field.js) with the copy set
   straight on it, no plate. The night runs on under the logo wall below
   (.night wraps both), the panel fading out above it. The night's tokens
   turn over in tokens.css, so the copy and buttons need no inks of their
   own here. ── */
.night {
  position: relative;
}

.night > :not(.pixel-field) {
  position: relative;
}

.hero--night {
  display: flex;
  align-items: center;
  min-height: min(44rem, 88vh);
  padding: 4rem 0;
}

.hero--night .hero__content {
  padding: 0;
  pointer-events: none;
}

/* (0,2,0), to beat the plate hero's phone rule (width: auto), which the
   night hero has no plate to need: the copy keeps the shell's gutters. */
@media (max-width: 640px) {
  .hero--night .hero__content {
    width: calc(100% - 2 * var(--inset));
  }
}

.hero--night .hero__content > * {
  max-width: 36rem;
  pointer-events: auto;
}

.hero--night .hero__sub {
  color: var(--text-soft);
}

/* The panel fills the night — hero and logo wall — and fades out as it
   reaches the wall's copy (pixel-field.js). The glow is the panel's own
   low-resolution source, blurred in its canvas and scaled up over the
   dark. */
.pixel-field {
  inset: 0;
  overflow: hidden;
  position: absolute;
}

.pixel-field canvas {
  height: 100%;
  inset: 0;
  position: absolute;
  width: 100%;
}

/* The glow is blurred inside its own small canvas (pixel-field.js) and
   drawn here plainly: a CSS blur and a screen blend over the scaled canvas
   cost the compositor more than the whole panel. The pixels' gutters are
   cut in the panel's canvas too, for the same reason. */
.pixel-field__bloom {
  opacity: 0.28;
  transform: scale(1.02);
}

/* On a phone the copy takes the hero's width, so the panel gets a band of
   its own below the buttons, where its people rise. */
@media (max-width: 767px) {
  .hero--night {
    align-items: flex-start;
    min-height: 0;
    padding: 3rem 0 17rem;
  }
}

/* On a phone the two buttons stack at the plate's width, so neither can
   push the plate past the screen edge. */
@media (max-width: 480px) {
  .hero__ctas .btn {
    flex: 1 1 100%;
    text-align: center;
  }
}

:where(.cal-modal-trigger) {
  appearance: none;
  -webkit-appearance: none;
  cursor: pointer;
  font: inherit;
}

.btn {
  border-radius: var(--radius);
  display: inline-block;
  font-weight: 600;
  padding: 0.8rem 1.5rem;
  text-decoration: none;
}

/* Two faces per button (DESIGN.md "Motion"), each keeping its rank: the
   primary is a solid plate in the action ink and hovers to the hero's live
   wash colour (--wash, which re-washes in place if it changes mid-hover);
   the ghost is an outline and hovers to a faint fill inside a full-ink
   rule. A face is named once, here (--face-*), and the hover reads it — in
   CSS without the script, and in public/pixel-button.js, which turns the
   button over block by block (a canvas of plate blocks under two copies
   of the label, rest and hover, each shown only over its own blocks).
   Mid-turn the button's own plate, rule and label stand aside
   (.is-turning); turned, it wears the face (.is-hover). Under reduced
   motion the face simply switches. */
.btn--primary {
  --face-bg: var(--wash);
  --face-border: var(--wash);
  --face-fg: #16130f;
  background: var(--action);
  border: 1px solid var(--action);
  color: var(--bg);
}

.btn--ghost {
  --face-bg: color-mix(in srgb, var(--text) 10%, transparent);
  --face-border: var(--text);
  --face-fg: var(--text);
  background: transparent;
  border: 1px solid var(--rule-strong);
  color: var(--text);
}

.btn--primary,
.btn--ghost,
[data-pixel-turn] {
  isolation: isolate;
  position: relative;
}

/* :active too, so a tap (no hover on touch) still confirms. */
html:not(.pixel-buttons)
  :is(.btn--primary, .btn--ghost, [data-pixel-turn]):hover,
:is(.btn--primary, .btn--ghost, [data-pixel-turn]):active,
:is(.btn--primary, .btn--ghost, [data-pixel-turn]).is-hover {
  background: var(--face-bg);
  border-color: var(--face-border);
  color: var(--face-fg);
}

:is(.btn--primary, .btn--ghost, [data-pixel-turn]).is-turning {
  background: transparent;
  border-color: transparent;
  color: transparent;
}

/* A plate turn keeps its own content (pixel-button.js setup): only its
   plate and rule stand aside. */
[data-pixel-turn="plate"].is-turning {
  color: inherit;
}

.btn__pixels {
  height: calc(100% + 2px);
  inset: -1px;
  pointer-events: none;
  position: absolute;
  width: calc(100% + 2px);
  z-index: -1;
}

/* A copy of the label, laid exactly over the button's own (its border
   box, its padding), shown only over its blocks by a clip path. */
.btn__label {
  border: 1px solid transparent;
  border-radius: inherit;
  box-sizing: border-box;
  clip-path: inset(50%);
  display: block;
  inset: -1px;
  padding: inherit;
  pointer-events: none;
  position: absolute;
  text-align: inherit;
  white-space: inherit;
}

/* Disabled state — the form submit button while a request is in flight. */
.btn[disabled] {
  box-shadow: none;
  cursor: default;
  opacity: 0.6;
  pointer-events: none;
}

#candidates .btn {
  cursor: pointer;
}

#candidates .btn[disabled] {
  cursor: not-allowed;
  pointer-events: auto;
}

/* Offer deliverables — band cells, not floating boxes: the same flush
   hairline grammar as the stats HUD. */
/* One column, then three at once: three cards never sit two-up, which
   would leave an empty fourth cell. */
.deliverables {
  grid-template-columns: 1fr;
  list-style: none;
  padding: 0;
}

@media (min-width: 900px) {
  .deliverables {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

.deliverables li {
  padding: 1.5rem;
}

/* Each cell is headed by the illustration of its job; the drawings sit on
   one baseline so the three titles line up. */
.deliverables .spot-illustration {
  align-items: flex-end;
  display: flex;
  height: 150px;
  margin-bottom: 1rem;
}

.deliverables__index {
  color: var(--structure);
  font-family: var(--font-label);
  font-size: 0.85rem;
}

.deliverables p {
  color: var(--text-soft);
  margin: 0;
}

/* Why-us — two columns separated by a hairline rule centered in the gap
   (the HUD's divider grammar); stacks with a horizontal rule on mobile. */
.why {
  display: grid;
  gap: 2.5rem;
  grid-template-columns: 1fr;
  margin-top: 2.5rem;
}

.why > div + div {
  position: relative;
}

.why > div + div::before {
  background: var(--rule);
  content: "";
  height: 1px;
  inset: -1.25rem 0 auto 0;
  position: absolute;
}

@media (min-width: 768px) {
  .why {
    grid-template-columns: 1fr 1fr;
  }

  .why > div + div::before {
    height: auto;
    inset: 0 auto 0 -1.25rem;
    width: 1px;
  }
}

/* .strip carries no border-top: its tinted surface already marks the seam
   (one seam device per boundary). */

.strip form,
.strip .strip__success {
  margin-top: 1.5rem;
  /* Exception, deliberate: a field column, not a reading measure —
     inputs are sized to the answer they take, not to a line of prose. */
  max-width: 30rem;
}

/* The acknowledgement is the checklist's pixel check, lit once — the
   answer drawn in the page's own vocabulary rather than announced in a
   heading alone. */
.strip .strip__success {
  display: flex;
  gap: 0.9rem;
}

.strip__success h3 {
  margin-top: 0;
}

.strip__success-box {
  --tick-ink: var(--accent);
  margin-top: 0.3em;
}

/* The pixel turns on once, after the box lands: the mark being made, not
   a state that was always there. */
.strip__success-box::after {
  animation: pixel-on 220ms var(--ease-out) 120ms backwards;
}

@media (prefers-reduced-motion: reduce) {
  .strip__success-box::after {
    animation: none;
  }
}

.form-field {
  display: block;
  margin-bottom: 1rem;
}

/* A field is a blank to fill in on the sheet: a mono label
   over a lighter paper blank, outlined in a strong structure hairline with a
   heavier rule along its foot, so it reads as a place to write before
   anything is typed. The placeholder is a faint mono prompt, never mistaken
   for the value or for body copy. */
.form-field span {
  color: var(--structure);
  display: block;
  font-family: var(--font-label);
  font-size: 0.92rem;
  margin-bottom: 0.2rem;
}

/* A blank is the surface itself, a whisper lighter, on the strong rule —
   no tinted fill, no heavier foot. Hover firms the rule; focus draws it in
   full ink with a soft ring of the same ink. */
.form-field input {
  background: color-mix(in srgb, var(--text) 3%, transparent);
  color: var(--text);
  border: 1px solid var(--rule-strong);
  border-radius: var(--radius);
  font: inherit;
  padding: 0.75rem 0.9rem;
  transition:
    border-color 160ms var(--ease-out),
    box-shadow 160ms var(--ease-out);
  width: 100%;
}

.form-field input::placeholder {
  font-family: var(--font-label);
  font-size: 0.92rem;
}

.form-field input:hover {
  border-color: color-mix(in srgb, var(--text) 45%, transparent);
}

.form-field input:focus {
  border-color: var(--text);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--text) 12%, transparent);
  outline: none;
}

/* Ties the offending field to its .form-error message for screen-reader
   and sighted users alike; reuses the same red already used for the text
   below, not a new hue. */
.form-field input[aria-invalid="true"] {
  border-color: #a33a2a;
}

.form-error {
  color: #a33a2a;
  font-size: 0.9rem;
  margin: 0.5rem 0 0;
}

/* The consent notice carries the legal weight of what the visitor agreed
   to, so it has to read as text rather than as browser chrome: the same
   size as the field labels, secondary ink, and enough bottom margin that
   it stops butting against the submit button. Top-aligned to the box so a
   wrapped second line stays flush with the first. The box is drawn, not
   the browser's: the pixel check. */
.strip__consent {
  align-items: flex-start;
  color: var(--text-soft);
  display: flex;
  font-size: 0.85rem;
  gap: 0.55rem;
  line-height: 1.45;
  margin: 1rem 0 1.5rem;
}

.strip__consent input,
.jobs-check input {
  --tick-ink: var(--accent);
  appearance: none;
  cursor: pointer;
  margin: 0.1em 0 0;
}

.strip__consent input::after,
.jobs-check input::after {
  transform: scaleY(0);
  transition: transform 160ms var(--ease-out);
}

.strip__consent input:checked::after,
.jobs-check input:checked::after {
  transform: none;
  transition: transform 220ms var(--ease-out);
}

@media (prefers-reduced-motion: reduce) {
  .strip__consent input::after,
  .strip__consent input:checked::after,
  .jobs-check input::after,
  .jobs-check input:checked::after {
    transition: none;
  }
}

.honeypot {
  left: -9999px;
  position: absolute;
}

/* ── Page chrome — the header bar and the footer. Neither is a container
   from the ladder: they are the page's own frame, drawn with the same 1px
   rule tokens, mono labels and no box. ── */
/* The bar sticks to the top and tucks away while the reader scrolls down,
   back the moment they scroll up (public/site-header.js toggles
   .site-header--tucked). It is the night (tokens.css), so the page opens
   on one dark band from the bar through the hero; over everything else,
   its panels included. The tuck is an exit, so it runs faster than the
   return. */
.site-header {
  background-color: var(--bg);
  border-bottom: 1px solid var(--rule);
  position: sticky;
  top: 0;
  transition: translate 260ms var(--ease-out);
  z-index: 9;
}

/* iPhone Safari paints the page in the strip behind the status bar, above
   the viewport's top edge — where top: 0 pins the bar. So the bar's paper
   runs up behind the status bar (content scrolling under it never shows
   above the bar), and the tuck clears that strip as well as the bar. */
.site-header::before {
  background: inherit;
  bottom: 100%;
  content: "";
  height: 120px;
  left: 0;
  pointer-events: none;
  position: absolute;
  right: 0;
}

.site-header--tucked {
  transition-duration: 180ms;
  translate: 0 calc(-100% - 120px);
}

/* Whatever pins under the bar (the resources page's cluster tabs) steps
   down by its height while it shows and back to the top once it tucks,
   in step with its slide. The height comes from site-header.js; without
   the script the bar never tucks, and nothing else pins. */
:root {
  --site-header-offset: 0px;
}

html:has(.site-header:not(.site-header--tucked)) {
  --site-header-offset: var(--site-header-h, 0px);
}

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

/* One row from 861px: wordmark, the inline nav (Jobs when live, the
   pillars and Examples, content/nav.ts), the CTA. Below that the nav and
   CTA fold into the phone menu (.site-menu): with all five entries the
   row first fits at about 840px, and below it the CTA wrapped onto a
   second line. Adding a bar entry moves that point: re-measure it. */
.site-header__bar {
  align-items: center;
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem 1.25rem;
  padding: 0.9rem 0;
  /* The containing block the wrapped-nav dropdown hangs from — see
     .nav-drop__panel in SiteHeader.astro. */
  position: relative;
}

.site-header__mark {
  color: var(--text);
  font-size: 1.1rem;
  text-decoration: none;
}

/* ── The wordmark (DESIGN.md "Logo") — the mark, then "GTM Engineer"
   over "Search", one block. Every length is in em of the name's own size,
   so a placement sets only font-size. The mark runs from the top of the
   capitals of "GTM" to the baseline of "Search" (1.02em of line plus a
   0.72em cap height), and its line weight scales with the name, so it
   reads as heavy as the type's hairlines at every size. It strokes in
   currentColor: the surface's own ink, night or paper. ── */
.wordmark {
  align-items: center;
  display: inline-flex;
  font-family: var(--font-display);
  font-weight: 800;
  gap: 0.5em;
  letter-spacing: -0.015em;
  line-height: 1.02;
  white-space: nowrap;
}

.wordmark__name {
  display: inline-flex;
  flex-direction: column;
}

.wordmark__mark {
  flex: none;
  height: 1.74em;
  overflow: visible;
  translate: 0 var(--mark-shift, 0);
  width: auto;
}

.wordmark__mark path {
  fill: none;
  stroke: currentColor;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-width: 0.075em;
  vector-effect: non-scaling-stroke;
}

.site-header__nav {
  align-items: center;
  display: flex;
  flex-wrap: wrap;
  gap: 1.25rem;
  margin-left: auto;
}

.site-header__link {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  color: var(--structure);
  font-family: var(--font-label);
  font-size: 14.1px;
  text-decoration: none;
}

.site-header__cta {
  font-size: 0.85rem;
  padding: 0.55rem 1.1rem;
}

/* ── The phone menu (.site-menu) — below 860px the inline nav and the
   header CTA give way to one "Menu" disclosure: a <details>, so it opens
   without a script on every page, backend-rendered ones included. Its
   panel is a full sheet under the bar: the destinations as display rows,
   the clusters as a ledger under a "Resources" label, the CTA last. ── */
.site-menu {
  display: none;
}

@media (max-width: 860px) {
  .site-header__nav,
  .site-header__cta {
    display: none;
  }

  .site-menu {
    display: block;
    margin-left: auto;
  }
}

.site-menu__toggle {
  align-items: center;
  color: var(--structure);
  cursor: pointer;
  display: flex;
  font-family: var(--font-label);
  font-size: 0.92rem;
  gap: 0.55rem;
  list-style: none;
  min-height: 44px;
}

.site-menu__toggle::-webkit-details-marker {
  display: none;
}

/* Three short structure strokes; the outer two close into a cross when the
   menu is open. */
.site-menu__icon {
  display: block;
  height: 12px;
  position: relative;
  width: 18px;
}

.site-menu__icon::before,
.site-menu__icon::after,
.site-menu__icon span {
  background: var(--structure);
  content: "";
  height: 2px;
  left: 0;
  position: absolute;
  transition:
    opacity 160ms var(--ease-out),
    transform 200ms var(--ease-out);
  width: 100%;
}

.site-menu__icon::before {
  top: 0;
}

.site-menu__icon span {
  top: 5px;
}

.site-menu__icon::after {
  top: 10px;
}

.site-menu[open] .site-menu__icon::before {
  transform: translateY(5px) rotate(45deg);
}

.site-menu[open] .site-menu__icon span {
  opacity: 0;
}

.site-menu[open] .site-menu__icon::after {
  transform: translateY(-5px) rotate(-45deg);
}

/* The open menu is a sheet of the page's own paper laid over it, edge to
   edge and down to the bottom of the screen, so nothing behind it prints
   through and the page underneath stays put. Its height is the screen
   less the header bar, which is always at the top (it is sticky): padding
   0.9rem × 2 around the 44px toggle. The sheet scrolls itself if a short
   screen needs it. */
.site-menu__panel {
  align-content: start;
  background: var(--bg);
  border-top: 1px solid var(--rule);
  display: grid;
  height: calc(100dvh - (1.8rem + 44px));
  left: calc(50% - 50vw);
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 0.75rem 20px calc(2rem + env(safe-area-inset-bottom));
  position: absolute;
  top: 100%;
  width: 100vw;
}

/* The sheet pulls down off the bar — a short drop, no bounce. Closing
   is instant (a <details> can't animate shut), which keeps exits faster
   than entrances. */
@keyframes site-menu-pull {
  from {
    opacity: 0;
    transform: translateY(-8px);
  }
}

.site-menu[open] .site-menu__panel {
  animation: site-menu-pull 200ms var(--ease-out);
}

@media (max-width: 860px) {
  html:has(.site-menu[open]) {
    overflow: hidden;
  }
}

/* Rows: chrome links, so no underline, ever (DESIGN.md "Links"); the
   whole row is the target and answers with the lift, the page being read
   with the structure fill (DESIGN.md "Interaction states"). The row's lift
   runs 12px past the type on each side, so the type still sits on the
   shell's edge. */
.site-menu__panel .site-header__link {
  border-bottom: 1px solid var(--rule);
  color: var(--text);
  display: flex;
  font-family: var(--font-display);
  font-size: 1.5rem;
  font-weight: 600;
  margin: 0 -12px;
  min-height: 64px;
  padding: 0 12px;
  -webkit-tap-highlight-color: transparent;
}

.site-menu__panel .site-header__link:hover,
.site-menu__panel .site-header__link:active {
  background-color: var(--lift);
  box-shadow: var(--lift-outline);
  color: var(--accent);
}

.site-menu__panel .site-header__link[aria-current="page"] {
  background-color: var(--structure);
  box-shadow: none;
  color: var(--bg);
}

/* Resources: a mono label over a ledger of the clusters, set smaller in
   the body face, the way the footer lists them. */
.site-menu__label {
  border-bottom: 1px solid var(--rule);
  color: var(--structure);
  font-family: var(--font-label);
  font-size: 12.9px;
  margin: 2rem -12px 0;
  padding: 0 12px 0.6rem;
}

.site-menu__panel .site-menu__sub {
  font-family: var(--font-body);
  font-size: 1.05rem;
  font-weight: 400;
  min-height: 52px;
}

.site-menu__panel .btn {
  margin-top: 2rem;
  padding-block: 1rem;
  text-align: center;
}

@media (prefers-reduced-motion: reduce) {
  .site-menu__icon::before,
  .site-menu__icon::after,
  .site-menu__icon span {
    transition: none;
  }

  .site-menu[open] .site-menu__panel {
    animation: none;
  }
}

/* Shared resources dropdown: Astro and backend-rendered jobs. */
/* Hover opens for a mouse, :focus-within opens for the keyboard, and
   a tap just follows the link to /resources/ — no JS, nothing to
   break without it. */
.nav-drop {
  align-items: center;
  display: flex;
  position: relative;
}

/* The trigger flexes so the smaller caret glyph can't drag its line
   box (and the whole item) below the sibling links' baseline. */
.nav-drop > .site-header__link {
  align-items: center;
  display: inline-flex;
  gap: 0.25rem;
}

.nav-drop__caret {
  font-size: 8px;
  line-height: 1;
}

.nav-drop__panel {
  background: var(--bg);
  border: 1px solid var(--rule-strong);
  display: flex;
  flex-direction: column;
  min-width: 10rem;
  opacity: 0;
  padding: 0.5rem 1rem;
  pointer-events: none;
  position: absolute;
  /* Anchored to the trigger's RIGHT edge, so the panel grows inward
     rather than toward the viewport. Centring it (the old rule) aimed
     its growth at the right edge instead: the trigger is the last nav
     item before the CTA, so the panel overran the viewport by up to
     54px — and because a CLOSED panel still counts toward scroll
     width, every page scrolled sideways on a phone with nothing
     visible to explain it. */
  right: 0;
  top: calc(100% + 0.5rem);
  visibility: hidden;
  z-index: 20;
}

/* A hoverless bridge so the pointer can cross the gap between the
   link and the panel without closing it. */
.nav-drop::after {
  content: "";
  height: 0.6rem;
  inset: 100% 0 auto;
  position: absolute;
}

.nav-drop:hover .nav-drop__panel,
.nav-drop:focus-within .nav-drop__panel {
  opacity: 1;
  pointer-events: auto;
  visibility: visible;
}

.nav-drop__item--all {
  border-top: 1px solid var(--rule);
  margin-top: 0.4rem;
}

/* The dropdown is the cluster list, so a row names a cluster and says
   how deep it runs — the same count the index shelf prints. Mono for
   the count: it is an annotation, which is the mono face's job. */
.nav-drop__item {
  align-items: baseline;
  display: flex;
  gap: 1.5rem;
  justify-content: space-between;
  /* Every row takes the same padding rather than the header links' 44px
     floor, which padded a one-line row out below its words and left a
     wrapped one tight: the space between rows is now even. The panel
     opens for a pointer or the keyboard only — a phone gets the menu
     sheet — and each row still clears 40px. */
  min-height: 0;
  padding-block: 0.4rem;
}

.nav-drop__count {
  color: var(--text-soft);
  font-family: var(--font-label);
  font-size: 0.798rem;
  white-space: nowrap;
}

/* The footer is the page's base: the night (tokens.css), its type and
   rules in the night's inks. */
.site-footer {
  border-top: 1px solid var(--rule);
  color: var(--text-soft);
  font-size: 0.9rem;
  padding: 3.5rem 0 3rem;
}

.site-footer__mark {
  display: inline-block;
  font-size: 1.75rem;
  margin-bottom: 2.5rem;
  text-decoration: none;
}

.site-footer__cols {
  display: grid;
  gap: 2rem;
  grid-template-columns: 1fr;
}

@media (min-width: 640px) {
  .site-footer__cols {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

.site-footer__label {
  color: var(--structure);
  font-family: var(--font-label);
  font-size: 12.9px;
  margin: 0 0 0.9rem;
}

.site-footer__col ul {
  list-style: none;
  margin: 0;
  padding: 0;
}

.site-footer__col li {
  border-top: 1px solid var(--rule);
}

.site-footer__col li:last-child {
  border-bottom: 1px solid var(--rule);
}

.site-footer__col a {
  display: block;
  padding: 0.6rem 0;
  text-decoration: none;
}

/* ── Navigation links — the header's, its dropdown's and the footer's.
   They carry no underline (they read as navigation), so the hover must
   be unmistakable on its own: the text steps up from its resting ink to
   full ink, and a pixel turns on beside it — the site's mark for "this
   one" (DESIGN.md "Links"). The accent can't do it: with no accent
   colour, a hover that only changes to it barely moves. ── */
.site-header__nav .site-header__link,
.site-footer__col a {
  position: relative;
  transition: color 280ms var(--ease-out);
}

.site-footer__col a {
  color: var(--text-soft);
}

.site-header__nav .site-header__link::before,
.site-footer__col a::before {
  background: currentColor;
  content: "";
  height: 6px;
  left: -13px;
  margin-top: -3px;
  position: absolute;
  top: 50%;
  transform: scaleY(0);
  transition: transform 300ms var(--ease-out);
  width: 6px;
}

/* Inside the dropdown the pixel sits nearer, within the panel's padding,
   and level with the row's first line rather than the middle of the row,
   so a wrapped cluster's pixel doesn't sit between its lines. */
.site-header__nav .nav-drop__item::before {
  left: -11px;
  top: calc(0.4rem + 0.5lh);
}

.site-header__nav .site-header__link:is(:hover, :focus-visible),
.site-footer__col a:is(:hover, :focus-visible) {
  color: var(--text);
}

.site-header__nav .site-header__link:is(:hover, :focus-visible)::before,
.site-footer__col a:is(:hover, :focus-visible)::before {
  transform: none;
}

@media (prefers-reduced-motion: reduce) {
  .site-header__nav .site-header__link,
  .site-header__nav .site-header__link::before,
  .site-footer__col a,
  .site-footer__col a::before {
    transition: none;
  }
}

.site-footer__practice {
  margin: 2.5rem 0 0;
}

/* ── Page list — the ledger one page uses to point at several others: a
   pillar's hub list of its cluster members, and the landing page's route
   into the framework. Hairline rows, title in the display face. ── */
.page-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

/* Rows print straight on the section's stock — a stock is plain paper,
   so there is no pattern for a row to reserve its own paper against. */
.page-list li {
  border-top: 1px solid var(--rule);
  padding: 1rem;
  position: relative;
}

.page-list li:last-child {
  border-bottom: 1px solid var(--rule);
}

/* Every row is a link row, so the whole row is the target: the title's
   hit area stretches over it (one tab stop, the title as the accessible
   name) and the row answers on hover with the row-link signature (see
   "Row links"). The page being read is never a row. */
.page-list a {
  color: var(--text);
  font-family: var(--font-display);
  font-size: 1.1rem;
}

.page-list a::after {
  content: "";
  inset: 0;
  position: absolute;
}

.page-list a:hover {
  color: var(--accent);
}

.page-list p {
  color: var(--text-soft);
  font-size: 0.9rem;
  margin: 0.35rem 0 0;
  max-width: var(--measure);
}

/* The pillar's row says so, in the same words and the same mono
   annotation voice the index shelf uses for its lead guide. Scoped as
   `.page-list .page-list__start` (0,2,0) because it sits inside a
   `.page-list li` row and the row's own `.page-list p` rule (0,1,1) would
   otherwise win the cascade and overwrite these declarations. */
.page-list .page-list__start {
  color: var(--structure);
  font-family: var(--font-label);
  font-size: 14.1px;
  margin: 0 0 0.35rem;
}

/* ── Catalog — a Band variant for a set too long to print open: nine
   ledger rows on the left against one specification panel on the right,
   selection swapping the panel. Same edges as a Band (strong outer rule,
   hairline row dividers, paper cells); the only additions are the two
   states a selection needs, said the way every control says them
   (DESIGN.md "Interaction states"): hover is the lift, the chosen row is
   the structure fill. ── */
.catalog {
  border: 1px solid var(--rule-strong);
  display: grid;
  grid-template-columns: 1fr;
  margin-top: 2.5rem;
}

@media (min-width: 860px) {
  .catalog {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.2fr);
  }
}

.catalog__list {
  border-bottom: 1px solid var(--rule-strong);
  list-style: none;
  margin: 0;
  padding: 0;
}

@media (min-width: 860px) {
  .catalog__list {
    border-bottom: 0;
    border-right: 1px solid var(--rule-strong);
  }
}

/* A row turns over like the resources tabs and the ghost button
   (pixel-button.js, data-pixel-turn="plate"): on hover to a faint fill
   inside a full-ink rule, and when chosen the structure fill sweeps over it
   (pixelButton.reface) rather than jumping. Its content stays put through
   the turn and takes its new colours mid-sweep. The rule sits on
   all four sides of the row, transparent at rest, so the turn has an edge
   to draw; a row's top edge is the hairline between rows. */
.catalog__row {
  --face-bg: color-mix(in srgb, var(--text) 10%, transparent);
  --face-border: var(--text);
  --face-fg: var(--text);
  align-items: baseline;
  border: 1px solid transparent;
  color: inherit;
  display: grid;
  gap: 0.2rem 0.9rem;
  grid-template-columns: 1.6rem auto minmax(7rem, 1fr);
  padding: 0.78rem 1.1rem;
  text-decoration: none;
}

.catalog__list li + li .catalog__row {
  border-top-color: var(--rule);
}

/* The hairline between rows outranks the shared face rule, so the face's
   rule is restated above it: otherwise a hovered row kept its faint top
   edge, and the turn — which reads the face's rule from that edge — drew
   every edge faint until it settled. */
.catalog__list li .catalog__row.is-hover,
.catalog__list li .catalog__row:active,
html:not(.pixel-buttons) .catalog__list li .catalog__row:hover {
  border-color: var(--face-border);
}

/* The content takes its new colours at the middle of the 420ms sweep,
   when half the blocks have turned, so it stays readable on whichever
   ground is under it for most of the turn. */
.catalog__row :is(.catalog__index, .catalog__name, .catalog__scan) {
  transition: color 140ms linear 150ms;
}

@media (prefers-reduced-motion: reduce) {
  .catalog__row :is(.catalog__index, .catalog__name, .catalog__scan) {
    transition: none;
  }
}

.catalog__index {
  color: var(--structure);
  font-family: var(--font-label);
  font-size: 0.902rem;
}

.catalog__name {
  font-family: var(--font-display);
  font-size: 0.98rem;
  font-weight: 600;
}

/* The row anchor takes none of the base link hover: the turn is its
   answer, and the name is not underlined (a row, not a link in prose). */
.catalog__row:hover {
  color: inherit;
}

.catalog__scan {
  color: var(--text-soft);
  font-size: 0.88rem;
  line-height: 1.45;
  min-width: 0;
  white-space: normal;
}

/* Narrow rows stack the scan line under the name instead of sharing the
   row with the index column. */
@media (max-width: 859px) {
  .catalog__row {
    grid-template-columns: 1.6rem minmax(0, 1fr);
  }

  .catalog__name,
  .catalog__scan {
    grid-column: 2;
  }
}

/* The chosen row: the structure fill, type knocked out to paper, and a
   marker pointing at the panel it prints. It stays chosen under the
   pointer — a hover never repaints the choice. */
.catalog__row[aria-current] {
  --face-bg: var(--structure);
  --face-border: var(--structure);
  --face-fg: var(--bg);
  background-color: var(--structure);
  box-shadow: none;
  color: var(--bg);
}

/* Mid-turn the plate and rule stand aside for the turn's own, above the
   chosen row's fill (which outranks the shared .is-turning rule). */
.catalog__row.is-turning,
.catalog__row[aria-current].is-turning {
  background-color: transparent;
  border-color: transparent;
}

.catalog__list li + li .catalog__row.is-turning {
  border-top-color: transparent;
}

.catalog__row[aria-current] .catalog__index,
.catalog__row[aria-current] .catalog__scan {
  color: color-mix(in srgb, var(--bg) 78%, transparent);
}

.catalog__row[aria-current] .catalog__name {
  --ul: none;
  color: var(--bg);
}

.catalog__row[aria-current] .catalog__name::after {
  content: " ▸";
}

.catalog__panel {
  padding: 1.8rem 1.5rem;
}

/* Selecting a row swaps the specification beside it, and the swap used
   to be a hard cut — the panel simply became a different panel, which
   reads as a flicker rather than as an answer to the click. The new
   spec prints instead, in the timeline's vocabulary: ink landing, a few
   pixels of settle. It runs only on selection, never on load — before
   the script runs every panel is already visible, so panel 0 never
   changes display and never animates. */
@media (prefers-reduced-motion: no-preference) {
  .catalog__panel:not([hidden]) {
    animation: catalog-print 200ms var(--ease-out);
  }
}

/* Stillness was asked for, but the swap still has to be legible as a
   swap — the movement goes, the ink still lands. */
@media (prefers-reduced-motion: reduce) {
  .catalog__panel:not([hidden]) {
    animation: catalog-ink 140ms var(--ease-out);
  }
}

@keyframes catalog-print {
  from {
    opacity: 0;
    transform: translateY(4px);
  }
}

@keyframes catalog-ink {
  from {
    opacity: 0;
  }
}

@media (min-width: 860px) {
  .catalog__spec {
    align-self: start;
    position: sticky;
    top: 1.5rem;
  }

  .catalog__panel {
    padding: 1.8rem 2rem;
  }
}

.catalog__spec-label {
  color: var(--structure);
  font-family: var(--font-label);
  font-size: 0.876rem;
  margin: 0 0 0.5rem;
}

.catalog__panel h3 {
  font-size: 1.45rem;
  margin: 0 0 0.7rem;
}

.catalog__panel p:not(.catalog__spec-label) {
  margin: 0 0 0.75rem;
}

.catalog__panel p:last-child {
  margin-bottom: 0;
}

/* Only once the script has run does the panel column show one at a time;
   without JS every panel stays printed and every row is an anchor to its
   own. */
.catalog__panel + .catalog__panel {
  border-top: 1px solid var(--rule);
}

/* ── Question list — a register too long for the catalog ledger.
   Same grammar as the catalog (mono index, display title, scan line, lift
   hover, structure tint on the open row); the difference is that it runs
   full width and opens in place rather than printing into a side panel.
   No new hue and no new edge weight — the two rules and the three inks. */
.qlist {
  margin-top: 2.5rem;
}

.qlist__filters-label {
  color: var(--text-faint);
  font-family: var(--font-label);
  font-size: 0.798rem;
  margin: 0 0 0.7rem;
}

.qlist__chips {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
}

.qlist__chip {
  background: none;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  color: var(--text-soft);
  cursor: pointer;
  font-family: var(--font-label);
  font-size: 0.902rem;
  padding: 0.36rem 0.68rem;
  transition:
    background 120ms var(--ease-out),
    border-color 120ms var(--ease-out),
    color 120ms var(--ease-out);
}

.qlist__chip:hover {
  background: var(--lift);
  border-color: color-mix(in srgb, var(--structure) 55%, transparent);
  color: var(--text);
}

.qlist__chip[aria-pressed="true"] {
  background: var(--structure);
  border-color: var(--structure);
  color: var(--bg);
}

.qlist__chip-n {
  font-variant-numeric: tabular-nums;
  margin-left: 0.35rem;
  opacity: 0.62;
}

.qlist__state {
  border-bottom: 1px solid var(--rule-strong);
  color: var(--structure);
  font-family: var(--font-label);
  font-size: 0.902rem;
  font-variant-numeric: tabular-nums;
  margin: 1.3rem 0 0;
  padding-bottom: 0.5rem;
}

/* Without the script the filter bar is hidden, so the register needs its
   own top edge — the state line is not there to draw one. */
.qlist__filters[hidden] + .qlist__rows {
  border-top: 1px solid var(--rule-strong);
}

.qlist__rows {
  border-bottom: 1px solid var(--rule-strong);
  list-style: none;
  margin: 0;
  padding: 0;
}

.qlist__item + .qlist__item {
  border-top: 1px solid var(--rule);
}

.qlist__item[hidden] {
  display: none;
}

/* The scan line sits UNDER the question rather than beside it. Beside it,
   a question long enough to wrap squeezes the scan to a few words and an
   ellipsis on nearly every row — and a truncated essence is worse than no
   essence (the same call the catalog row makes at narrow widths, taken
   here at every width because these questions are sentences, not names). */
.qlist__row {
  cursor: pointer;
  display: grid;
  gap: 0.15rem 1rem;
  grid-template-columns: 2.1rem minmax(0, 1fr) 1.1rem;
  list-style: none;
  padding: 0.8rem 1.1rem 0.85rem;
  transition: background 120ms var(--ease-out);
}

.qlist__row::-webkit-details-marker {
  display: none;
}

/* Hover is the closed row's invitation: the lift. An open row has
   already answered it and sits on the lift whole — letting hover repaint
   just its header would split the open block in two. */
.qlist__details:not([open]) .qlist__row:hover {
  background: var(--lift);
  box-shadow: var(--lift-outline);
}

.qlist__index {
  color: var(--structure);
  font-family: var(--font-label);
  font-size: 0.902rem;
  font-variant-numeric: tabular-nums;
  /* The index sits on the question's first baseline, not the row's top. */
  padding-top: 0.2rem;
}

/* No underline. The underline is this system's standing signal for a
   LINK — something that takes the reader elsewhere (DESIGN.md, "Row
   links"). A question takes them nowhere; it opens underneath them.
   Carrying the link treatment here promised a navigation that never
   happened, so the row states its affordance with the marker below and
   with hover, and keeps the underline for things that really do leave. */
.qlist__q {
  font-family: var(--font-display);
  font-size: 1rem;
  font-weight: 600;
  line-height: 1.45;
  transition: color 120ms var(--ease-out);
}

.qlist__row:hover .qlist__q {
  color: var(--accent);
}

/* The disclosure tick: a drafting mark that turns a quarter-turn down when
   the row opens. It is the only moving part, and it moves once. */
/* Drawn, not typed: two 1px strokes meeting at a right angle, on the same
   hairline the rest of the page is ruled with. A glyph would carry a
   typeface's own metrics and weight, and would pivot around its em box
   rather than its own corner when the row opens. */
.qlist__marker {
  align-self: center;
  grid-column: 3;
  grid-row: 1 / -1;
  height: 0.44rem;
  justify-self: end;
  position: relative;
  transform: rotate(45deg);
  transition: transform 200ms var(--ease-out);
  width: 0.44rem;
}

.qlist__marker::before,
.qlist__marker::after {
  background: var(--structure);
  content: "";
  position: absolute;
  transition: background 120ms var(--ease-out);
}

/* The two strokes of the corner: one across the top, one down the right. */
.qlist__marker::before {
  height: 1px;
  left: 0;
  top: 0;
  width: 100%;
}

.qlist__marker::after {
  bottom: 0;
  height: 100%;
  right: 0;
  width: 1px;
}

.qlist__row:hover .qlist__marker::before,
.qlist__row:hover .qlist__marker::after {
  background: var(--accent);
}

.qlist__details[open] .qlist__marker {
  transform: rotate(135deg);
}

.qlist__scan {
  color: var(--text-faint);
  font-size: 0.84rem;
  grid-column: 2;
  line-height: 1.45;
  max-width: var(--measure);
}

/* Open is not chosen: the marker turns and the index steps to soft text, and the
   whole open block — question and answer — sits on the lift, so a long
   answer reads as one sheet pulled out of the register. */
.qlist__details[open] {
  background: var(--lift);
  box-shadow: var(--lift-outline);
}

.qlist__details[open] .qlist__index {
  color: var(--detail-text);
}

/* The open state is carried by the lift, the index and the turned
   marker. A second glyph on the question itself would say it a fourth
   time. */

/* The body arrives rather than appearing. One short move, and only for
   readers who have not asked for stillness. */
@media (prefers-reduced-motion: no-preference) {
  .qlist__details[open] .qlist__detail {
    animation: qlist-reveal 180ms var(--ease-out);
  }
}

@keyframes qlist-reveal {
  from {
    opacity: 0;
    transform: translateY(-3px);
  }
}

/* The body is a second surface for closing the row, so it says so with the
   pointer. A text cursor here reads as "nothing to click" and the
   affordance goes undiscovered. */
.qlist__detail {
  border-top: 1px solid var(--rule);
  cursor: pointer;
  /* Left and right match. The left inset hangs the body under the question
     rather than the index, and the rules inside it are only square with
     the row when the right inset answers it. */
  padding: 1.15rem 4.2rem 1.4rem;
}

@media (max-width: 719px) {
  .qlist__detail {
    padding-left: 1.1rem;
    padding-right: 1.1rem;
  }
}

/* Both labels take structure ink: a second ink on WEAK would read as a
   severity the palette does not own. The two cells are told apart by the
   rule between them and by the words. */
.qlist__label {
  color: var(--structure);
  font-family: var(--font-label);
  font-size: 0.798rem;
  margin: 0 0 0.45rem;
}

.qlist__block {
  margin: 0;
}

/* Follow-ups are the instrument, so they get the panel's opening position
   and its full measure. */
.qlist__follow {
  color: var(--text-soft);
  list-style: none;
  margin: 0;
  max-width: var(--measure);
  padding: 0;
}

.qlist__follow li {
  padding-left: 1.35rem;
  position: relative;
}

.qlist__follow li + li {
  margin-top: 0.4rem;
}

/* A leader dash on the same hairline as every other line on the page,
   sitting on the first line's baseline — a drafting mark, not a bullet. */
.qlist__follow li::before {
  background: var(--rule-strong);
  content: "";
  height: 1px;
  left: 0;
  position: absolute;
  top: 0.72em;
  width: 0.75rem;
}

.qlist__signal {
  color: var(--text-soft);
  margin: 0;
  max-width: var(--measure);
}

/* Strong and weak are read against each other — the pair is the rubric —
   so they sit side by side wherever there is room, as two cells of one
   spec block: a rule across the top of both, and a rule between them. */
.qlist__pair {
  border-top: 1px solid var(--rule);
  display: grid;
  gap: 1.15rem;
  margin-top: 1.4rem;
  padding-top: 1.1rem;
  position: relative;
}

@media (min-width: 820px) {
  .qlist__pair {
    column-gap: 4rem;
    grid-template-columns: 1fr 1fr;
  }

  /* The divider is drawn at the pair's true midpoint rather than hung off
     the weak cell's edge. Two equal `1fr` tracks either side of one gap
     put 50% exactly in the gap's centre, so both cells get the same
     measure — the previous version padded the rule off the weak cell and
     took that padding out of its width, leaving weak narrower than
     strong for no reason a reader could see. */
  .qlist__pair::before {
    background: var(--rule);
    bottom: 0;
    content: "";
    left: 50%;
    position: absolute;
    top: 0;
    width: 1px;
  }
}

.qlist__empty {
  color: var(--text-soft);
  padding: 1.6rem 1.1rem;
}

/* ── Band cells — the deliverables grammar (mono index, display title,
   soft body) carrying an enumerated set: the taxonomy's domains and
   approaches, and the landing page's framework links. No new box; .band
   supplies the rules and the paper knockout. Column counts are set per
   band, never auto-fit — an unoccupied grid cell prints as a block of
   band background. ── */
.band-cells {
  grid-template-columns: 1fr;
  list-style: none;
  padding: 0;
}

@media (min-width: 640px) {
  .band-cells--two {
    grid-template-columns: 1fr 1fr;
  }
}

@media (min-width: 860px) {
  .band-cells--three {
    grid-template-columns: repeat(3, 1fr);
  }
}

.band-cells li {
  padding: 1.5rem;
}

.band-cells__index {
  color: var(--structure);
  font-family: var(--font-label);
  font-size: 0.85rem;
  margin: 0 0 0.35rem;
}

/* Cell titles fill the cell rather than balancing. The global h1/h2/h3
   rule sets `text-wrap: balance`, which is right for a page headline
   sitting alone in a wide plate and wrong here: a cell is already narrow,
   so balancing pulls a two-line title well short of the right edge and
   leaves a column of dead space beside it. `pretty` still prevents an
   orphaned last word. */
.band-cells h3 {
  margin: 0 0 0.6rem;
  text-wrap: pretty;
}

.band-cells h3 a {
  color: var(--text);
}

.band-cells h3 a:hover {
  color: var(--accent);
}

/* A cell whose title is a link is a link card: the title's hit area
   stretches over the whole cell, so the click target matches what the
   cell looks like — and the cell says so on hover, with the row-link
   signature (see "Row links"). Cells with no link (the taxonomy's
   approaches) are untouched. */
.band-cells li:has(.band-cells__link) {
  position: relative;
}

.band-cells__link::after {
  content: "";
  inset: 0;
  position: absolute;
}

/* ── Row links — one signature for every row or cell that is a link
   (DESIGN.md "Links"): the framework pillars, the content pages'
   .page-list rows, the brief's .band-cells link cells.
   At rest: the title in plain ink and an arrow at the row's end — the
   arrow means "this takes you somewhere" (a disclosure row's plus means
   "this opens here").
   On hover the whole row answers, not just its title: it lifts inside a
   hairline, the title turns full ink, and the arrow slides toward where
   the row goes. No underline, at rest or on hover (2026-10-28): the arrow
   and the lift already say link, and an underline arriving under a
   wrapped title striped the card. ── */
.page-list li,
.band-cells li:has(.band-cells__link) {
  padding-right: 3rem;
}

.page-list li::before,
.band-cells li:has(.band-cells__link)::before {
  content: "→";
  font-family: var(--font-label);
  position: absolute;
  right: 1.1rem;
  top: 1.1rem;
}

.band-cells li:has(.band-cells__link)::before {
  top: 1.6rem;
}

.pillars__arrow,
.page-list li::before,
.band-cells li:has(.band-cells__link)::before {
  color: var(--structure);
  font-size: 1.1rem;
  line-height: 1.2;
  transition: transform 180ms var(--ease-out);
}

.pillars__row,
.page-list li,
.band-cells li:has(.band-cells__link) {
  transition:
    background-color 180ms var(--ease-out),
    box-shadow 180ms var(--ease-out);
}

.pillars__row:hover,
.page-list li:hover,
.band-cells li:has(.band-cells__link):hover {
  background-color: var(--lift);
  box-shadow: var(--lift-outline);
  cursor: pointer;
}

/* An object link — a card's or a row's title, whose hit area is the whole
   object — prints no underline: the arrow says where it goes, and a long
   title underlined line by line read as a striped block. */
.pillars__link,
.page-list a,
.band-cells__link {
  background-image: none;
}

.pillars__row:hover .pillars__link,
.page-list li:hover a,
.band-cells li:has(.band-cells__link):hover .band-cells__link {
  background-image: none;
  color: var(--accent);
}

.pillars__row:hover .pillars__arrow,
.page-list li:hover::before,
.band-cells li:has(.band-cells__link):hover::before {
  color: var(--accent);
  transform: translateX(4px);
}

@media (prefers-reduced-motion: reduce) {
  .pillars__row,
  .pillars__arrow,
  .page-list li,
  .page-list li::before,
  .band-cells li,
  .band-cells li::before {
    transition: none;
  }
}

.band-cells li p:not(.band-cells__index) {
  color: var(--text-soft);
  margin: 0 0 0.75rem;
}

.band-cells li p:last-child {
  margin-bottom: 0;
}

/* v2 sections */

/* Checklist — hairline-separated rows, Fragment Mono index leading each item.
   No border-left accent stripes (design ban). */
.checklist {
  list-style: none;
  margin: 2rem 0 0;
  max-width: var(--measure);
  padding: 0;
}

.checklist li {
  border-top: 1px solid var(--rule);
  color: var(--text-soft);
  display: flex;
  gap: 1rem;
  padding: 0.9rem 0;
}

.checklist li:last-child {
  border-bottom: 1px solid var(--rule);
}

/* ── The pixel check — every checkbox on the site, drawn or real: a 1px
   square in the strong rule, lit from inside by one pixel in the row's
   person ink (--tick-ink). Lighting is the night hero's pixel turning over
   (pixel-field.js FLIP, 220ms): the pixel opens from its centre line and
   closes back to it, never fades. ── */
.checklist__box,
.strip__success-box,
.strip__consent input,
.jobs-check input {
  border: 1px solid var(--rule-strong);
  border-radius: 0;
  flex-shrink: 0;
  height: 16px;
  position: relative;
  width: 16px;
}

.checklist__box::after,
.strip__success-box::after,
.strip__consent input::after,
.jobs-check input::after {
  background: var(--tick-ink);
  content: "";
  inset: 3px;
  position: absolute;
}

@keyframes pixel-on {
  from {
    transform: scaleY(0);
  }
}

.checklist__box {
  margin-top: 0.3em;
}

/* A checked row lights its pixel in the accent (DESIGN.md "Marks"). At
   rest every pixel is lit; with the script, the reader checks the list off
   as they scroll — one at a time. */
.checklist li {
  --tick-ink: var(--accent);
}

/* public/checklist-ticks.js takes over: pixels start dark and each row's
   turns on when the script marks it .is-ticked, as its line completes. */
.checklist--live .checklist__box::after {
  transform: scaleY(0);
  transition: transform 160ms var(--ease-out);
}

.checklist--live .is-ticked .checklist__box::after {
  transform: none;
  transition: transform 220ms var(--ease-out);
}

/* The stream: with the script, the list's words wait unprinted and arrive
   as one continuous answer once the list is in view — a token at a time,
   each softening in from a slight blur — each row's pixel lighting once
   its line is whole. */
.checklist--live .checklist__tok {
  display: inline-block;
  filter: blur(var(--arrive-blur));
  opacity: 0;
  transform: translateY(3px);
}

/* Long, overlapping fades on an even cadence: several tokens are always
   mid-arrival, so the words flow in rather than tick in. */
.checklist--live .checklist__tok.is-in {
  filter: none;
  opacity: 1;
  transform: none;
  transition:
    opacity var(--arrive) var(--ease-out),
    filter var(--arrive) var(--ease-out),
    transform var(--arrive) var(--ease-out);
}

/* A streamed passage (public/stream-in.js): the checklist's tokens, for
   any [data-stream] — unwritten until the reader reaches it, then a token
   at a time, softening in. */
.stream--live .stream__tok {
  display: inline-block;
  filter: blur(var(--arrive-blur));
  opacity: 0;
  transform: translateY(3px);
}

/* A button in a streamed passage waits until the passage's words are in,
   then arrives whole, as content does (the arrive keyframes), so no empty
   plate stands ahead of the words it follows. */
.stream--live:not(.is-streamed) .btn {
  visibility: hidden;
}

.stream--live.is-streamed .btn {
  animation: arrive var(--arrive) var(--ease-out);
}

/* A link's underline (Links, above) arrives with its words: until the
   passage is whole, each word draws its own piece of the rule on the
   link's metrics (the negative margin keeps the padding out of the line
   box), and the link's continuous rule returns once the last word lands. */
.stream--live:not(.is-streamed) a {
  background-image: none;
}

.stream--live:not(.is-streamed) a .stream__tok {
  background-image: var(--ul);
  background-position: 0 100%;
  background-repeat: repeat-x;
  background-size: 2.4em 0.36em;
  margin-bottom: -0.12em;
  padding-bottom: 0.12em;
}

/* Once the passage is written its words are text again. A browser may
   break a line between any two inline-blocks, so a comma straight after a
   link (its own token, outside the link) could start the next line; as
   text, the line-breaking rules keep it on the word before. */
.stream--live.is-streamed .stream__tok {
  display: inline;
}

.stream--live .stream__tok.is-in {
  filter: none;
  opacity: 1;
  transform: none;
  transition:
    opacity var(--arrive) var(--ease-out),
    filter var(--arrive) var(--ease-out),
    transform var(--arrive) var(--ease-out);
}

/* Screen-reader-only text: present for assistive tech, not printed. */
.sr-only {
  border: 0;
  clip: rect(0 0 0 0);
  height: 1px;
  margin: -1px;
  overflow: hidden;
  padding: 0;
  position: absolute;
  white-space: nowrap;
  width: 1px;
}

/* Mandate grid (MandateMatrix.astro) — a paper plate of ruled rows: the
   nine domains down the side, three roles across, one pixel mark a cell.
   Full shell; the table scrolls in its own box below its minimum width
   rather than squeezing the marks. */
.mandates {
  background: var(--bg);
  border: 1px solid var(--rule-strong);
  margin-top: 2.75rem;
  position: relative;
}

/* The grid's marks print in the text ink; the key's a step softer. Role
   columns are told apart by their heads, not by colour. */
.mandate-mark {
  color: var(--text);
}

.mandates__key .mandate-mark {
  color: var(--text-soft);
}

.mandates__scroll {
  overflow-x: auto;
}

.mandates__table {
  border-collapse: collapse;
  width: 100%;
}

@media (min-width: 640px) {
  .mandates__table {
    min-width: 36rem;
  }
}

/* The phone key: each role once, by its ink mark and number, above a grid
   narrow enough to sit on the screen without scrolling. */
.mandates__roles {
  display: none;
}

.mandates__num {
  display: none;
}

@media (max-width: 639px) {
  .mandates__roles {
    border-bottom: 1px solid var(--rule-strong);
    display: grid;
    gap: 0.9rem;
    list-style: none;
    margin: 0;
    padding: 1rem 1rem 1.1rem;
  }

  .mandates__roles li {
    column-gap: 0.7rem;
    display: grid;
    grid-template-columns: 1.4rem minmax(0, 1fr);
  }

  .mandates__roles .mandates__role {
    font-size: 1rem;
    margin: 0 0 0.15rem;
  }

  .mandates__table .mandates__label,
  .mandates__table .mandates__role,
  .mandates__table .mandates__context {
    display: none;
  }

  .mandates__num {
    align-items: center;
    display: flex;
    flex-direction: column;
    font-family: var(--font-label);
    font-size: 0.8rem;
    gap: 0.2rem;
  }

  .mandates__table thead th {
    padding: 0.6rem 0.4rem;
    text-align: center;
  }

  .mandates__table thead th:first-child {
    text-align: left;
    vertical-align: bottom;
  }

  /* Out-specifies the desktop row-header rule further down the sheet. */
  .mandates__table tbody th[scope="row"] {
    font-size: 0.8rem;
    padding: 0.4rem 0.6rem;
    white-space: normal;
  }

  /* Fixed layout: each role column takes 3.25rem and the domain names
     take what is left and wrap, so the grid never runs off the screen. */
  .mandates__table {
    table-layout: fixed;
  }

  .mandates__table thead th + th {
    width: 3.25rem;
  }
}

.mandates__table th,
.mandates__table td {
  border-bottom: 1px solid var(--rule);
  padding: 0;
}

.mandates__table thead th {
  border-bottom-color: var(--rule-strong);
  padding: 1.5rem 1.5rem 1.35rem;
  text-align: left;
  vertical-align: top;
}

.mandates__table thead th + th,
.mandates__table td {
  border-left: 1px solid var(--rule);
}

.mandates__label {
  color: var(--structure);
  display: block;
  font-family: var(--font-label);
  font-size: 0.86rem;
}

.mandates__role {
  color: var(--text);
  display: block;
  font-family: var(--font-display);
  font-size: 1.1rem;
  font-weight: 600;
  line-height: 1.25;
  margin: 0.35rem 0 0.4rem;
  text-wrap: balance;
}

.mandates__context {
  color: var(--text-soft);
  display: block;
  font-size: 0.88rem;
  font-weight: 400;
  line-height: 1.45;
}

.mandates__table tbody th {
  color: var(--structure);
  font-family: var(--font-label);
  font-size: 0.9rem;
  font-weight: 400;
  padding: 0 1.5rem;
  text-align: left;
  white-space: nowrap;
}

.mandates__table td {
  height: 3.6rem;
  text-align: center;
}

.mandates__table tbody tr:last-child th,
.mandates__table tbody tr:last-child td {
  border-bottom: 0;
}

.mandate-mark {
  display: inline-block;
  height: 28px;
  vertical-align: middle;
  width: 28px;
}

.mandate-mark--fill {
  fill: currentColor;
}

.mandate-mark--edge {
  fill: none;
  stroke: currentColor;
  stroke-width: 1;
}

.mandate-mark--hatch {
  stroke: currentColor;
  stroke-width: 1;
}

/* The grid fills in as it arrives (ink-press.js marks it .is-printing):
   each mark turns on the way the hero's pixels do, row by row and left to
   right, as if a reading is being taken (--d per cell, MandateMatrix). */
.mandates.will-print:not(.is-printing) td .mandate-mark > * {
  transform: scaleY(0);
}

.mandates td .mandate-mark > * {
  transform-box: fill-box;
  transform-origin: center;
}

.mandates.is-printing td .mandate-mark > * {
  animation: pixel-on 220ms var(--ease-out) var(--d, 0ms) both;
}

/* Pointing at a role — any of its cells or its heading — reads that
   mandate down the domains: the other roles step back. */
@media (hover: hover) {
  .mandates__table :is(td, thead th) {
    transition: opacity 160ms var(--ease-out);
  }

  .mandates__table:has(:is(td, th):nth-child(2):hover)
    :is(td, thead th):not(:nth-child(2), :first-child),
  .mandates__table:has(:is(td, th):nth-child(3):hover)
    :is(td, thead th):not(:nth-child(3), :first-child),
  .mandates__table:has(:is(td, th):nth-child(4):hover)
    :is(td, thead th):not(:nth-child(4), :first-child) {
    opacity: 0.3;
  }
}

.mandates__key {
  align-items: center;
  border-top: 1px solid var(--rule-strong);
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem 1.75rem;
  padding: 0.75rem 1.1rem;
}

.mandates__key-item {
  align-items: center;
  color: var(--text-soft);
  display: inline-flex;
  font-size: 0.9rem;
  gap: 0.5rem;
}

.mandates__key .mandate-mark {
  height: 22px;
  width: 22px;
}

.checklist__closer {
  font-family: var(--font-display);
  font-size: 1.2rem;
  margin-top: 2rem;
  max-width: var(--measure);
}

/* Pricing — a spec-sheet panel wrapped in a TechFrame; the frame is the sole
   enclosure. Terms are hairline-separated rows, first row ink-primary. */
.pricing-frame {
  margin-top: 2.5rem;
}

/* The price leads at a glance — one figure in display type — and the
   terms run beside it as a ledger with the action beneath them. */
.pricing {
  display: grid;
  gap: 1.5rem 3rem;
  grid-template-columns: 13rem minmax(0, 1fr);
  padding: 2.25rem 2rem 2rem;
}

@media (max-width: 760px) {
  .pricing {
    grid-template-columns: 1fr;
  }
}

.pricing__lead {
  align-content: start;
  display: grid;
  gap: 0.6rem;
  margin: 0;
}

.pricing__figure {
  color: var(--text);
  font-family: var(--font-display);
  font-size: clamp(3rem, 6vw, 4.25rem);
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  line-height: 0.95;
}

.pricing__figure-label {
  color: var(--structure);
  font-family: var(--font-label);
  font-size: 0.95rem;
  line-height: 1.4;
}

.pricing__terms {
  list-style: none;
  margin: 0 0 1.75rem;
  padding: 0;
}

.pricing__terms li:first-child {
  padding-top: 0;
}

.pricing__terms li {
  border-bottom: 1px solid var(--rule);
  color: var(--text-soft);
  padding: 0.85rem 0;
}

.pricing__terms li:first-child {
  color: var(--text);
  font-weight: 600;
}

/* Timeline — the search read step by step (DESIGN.md "Timeline steps").
   Each step is a row: its label right-aligned left of the spine, its
   drawing and text to the right. The spine is one clean 1px rule in the
   structure ink, a square node lit at each step; its tail fades past the
   last step — the 90-day guarantee runs beyond the measured window. One
   spine per page. */
.steps {
  margin-top: 3rem;
}

/* Stacked (below 960px, where .hang stacks too): the spine runs down the
   left edge; each step is its when, its text, then its drawing. */
.timeline {
  --spine-x: 5px;
  list-style: none;
  margin: 0;
  padding: 0;
  position: relative;
}

.timeline::before {
  background: var(--rule-strong);
  bottom: 0;
  content: "";
  left: var(--spine-x);
  -webkit-mask-image: linear-gradient(#000 80%, transparent 99%);
  mask-image: linear-gradient(#000 80%, transparent 99%);
  pointer-events: none;
  position: absolute;
  top: 0.5rem;
  width: 1px;
}

/* A step's node prints in the text ink; the step being read lights it in
   the accent (DESIGN.md "Marks"). */
.timeline__phase {
  --node-ink: var(--text);
}

.timeline__phase {
  margin: 0;
  padding: 0 0 3rem 2.25rem;
  position: relative;
}

.timeline__phase:last-child {
  padding-bottom: 0.5rem;
}

/* The step's node on the spine, level with its when: a square pixel,
   outlined until the reader reaches it, then lit (lit at rest). */
.timeline__phase::before {
  background: var(--node-ink);
  border: 1px solid var(--node-ink);
  box-shadow: 0 0 0 4px var(--bg);
  box-sizing: border-box;
  content: "";
  height: 11px;
  left: calc(var(--spine-x) - 5px);
  position: absolute;
  top: 0.55rem;
  width: 11px;
}

.timeline__label {
  color: var(--text);
  font-family: var(--font-display);
  font-size: 1.3rem;
  font-weight: 600;
  line-height: 1.2;
  margin: 0 0 0.6rem;
}

/* Text and drawing are one unit by proximity, not a box: the text leads
   (it sits under its when), the drawing hangs beside it as its picture. */
.timeline__content {
  display: grid;
  gap: 1.25rem;
  max-width: var(--measure);
}

.timeline__content p {
  color: var(--text);
  margin: 0;
  max-width: 24rem;
  text-wrap: pretty;
}

.timeline__art {
  max-width: 15rem;
}

@media (min-width: 560px) {
  .timeline__content {
    align-items: start;
    column-gap: 2rem;
    grid-template-columns: minmax(0, 1fr) 15rem;
  }

  .timeline__art {
    margin-top: -1.5rem;
  }
}

/* On the section's own grid (.hang: 20rem, a 4rem gutter, the measure):
   the when right-aligned under the heading, the spine and its node in the
   gutter, the text on the intro's line with the drawing beside it. */
@media (min-width: 960px) {
  .timeline {
    --spine-x: calc(20rem + 2rem);
  }

  .timeline__phase {
    align-items: baseline;
    column-gap: 4rem;
    display: grid;
    grid-template-columns: 20rem minmax(0, var(--measure));
    padding: 0 0 3.5rem;
  }

  .timeline__label {
    margin: 0;
    text-align: right;
  }
}

/* The step drawings (services/recruiting-site/timeline-art.ts). */
.timeline-art {
  display: block;
  height: auto;
  overflow: visible;
  width: 100%;
}

/* With public/timeline-steps.js: the whens stay as a roadmap, but a
   step's text and drawing wait unprinted until the reader reaches it, then
   arrive once — focusing in like everything else — and stay. Its node
   lights as it is reached. Nothing re-runs and nothing dims as the reader
   moves on. */
.steps--live .timeline__phase::before {
  background: var(--bg);
  transition: background-color 220ms var(--ease-out);
}

.steps--live .is-reached::before {
  background: var(--node-ink);
}

.steps--live .timeline__label {
  opacity: 0.4;
  transition: opacity var(--arrive) var(--ease-out);
}

.steps--live .is-reached .timeline__label {
  opacity: 1;
}

.steps--live .timeline__content {
  filter: blur(var(--arrive-blur));
  opacity: 0;
  transform: translateY(var(--arrive-rise));
  transition:
    opacity var(--arrive) var(--ease-out),
    filter var(--arrive) var(--ease-out),
    transform var(--arrive) var(--ease-out);
}

.steps--live .is-reached .timeline__content {
  filter: none;
  opacity: 1;
  transform: none;
}

/* ── The assessment spec ("A technical assessment designed for the job")
   — a short printed spec: a structure label, its content, a strong rule
   between rows. The scoring sheet (timeline-art.ts ASSESSMENT_ART) hangs
   under the heading. */
.assess__art {
  margin-top: 2rem;
  max-width: 15rem;
}

@media (min-width: 960px) {
  .assess__art {
    margin-top: 2.5rem;
    max-width: 15rem;
  }
}

.assess {
  border-bottom: 1px solid var(--rule-strong);
  margin: 2rem 0 0;
}

.assess__row {
  border-top: 1px solid var(--rule-strong);
  display: grid;
  gap: 0.5rem;
  padding: 1.25rem 0;
}

@media (min-width: 640px) {
  .assess__row {
    column-gap: 1.5rem;
    grid-template-columns: 10rem minmax(0, 1fr);
  }
}

.assess dt {
  color: var(--structure);
  font-family: var(--font-label);
  font-size: 0.95rem;
  line-height: 1.6;
}

.assess dd {
  margin: 0;
}

.assess dd p {
  color: var(--text);
  margin: 0 0 0.75rem;
  text-wrap: pretty;
}

.assess dd > :last-child {
  margin-bottom: 0;
}

.assess__formats {
  display: grid;
  gap: 0.15rem;
  list-style: none;
  margin: 0 0 0.6rem;
  padding: 0;
}

.assess__formats li,
.assess__questions li {
  color: var(--text);
}

.assess dd .assess__note {
  color: var(--text-soft);
}

/* The comparison (landing #compare) — the mandate grid's plate and ruled
   rows, answered in words: each claim down the side, one answer per model
   across, in the mono face. Our column is the lit one — a strip of the
   night through the paper, its answers bright and bold. No colour. */
.compare {
  background: var(--bg);
  border: 1px solid var(--rule-strong);
  margin-top: 2rem;
  overflow-x: auto;
}

.compare__table {
  border-collapse: collapse;
  table-layout: fixed;
  width: 100%;
}

.compare__table th,
.compare__table td {
  border-bottom: 1px solid var(--rule);
  line-height: 1.4;
  padding: 0.9rem 1.1rem;
  vertical-align: middle;
}

.compare__table tbody tr:last-child th,
.compare__table tbody tr:last-child td {
  border-bottom: 0;
}

.compare__table thead th {
  border-bottom-color: var(--rule-strong);
  color: var(--text-soft);
  font-family: var(--font-label);
  font-size: 0.82rem;
  font-weight: 400;
  text-align: center;
  vertical-align: bottom;
  width: 9.5rem;
}

.compare__table thead th:first-of-type {
  color: var(--structure);
}

.compare__table tbody th {
  color: var(--text);
  font-weight: 400;
  text-align: left;
}

.compare__table td {
  text-align: center;
}

/* Our column only: the head cell and every body cell under it (the
   corner above the claims is a <td>, so it stays bare). */
.compare__table thead th:first-of-type,
.compare__table tbody td:first-of-type {
  background: var(--stock-night);
  /* Its row lines are the night's own faint rule, so the column reads as
     one strip of the night, not a stack of tiles. */
  border-bottom-color: color-mix(in srgb, var(--stock-night) 88%, var(--bg));
  color: var(--bg);
}

.compare__wordmark {
  color: inherit;
  display: inline-block;
  font-size: 0.95rem;
  text-align: left;
}

/* A column head is narrow: the mark stands over the name rather than
   beside it, so the name keeps its size. */
.compare__wordmark .wordmark {
  align-items: flex-start;
  flex-direction: column;
  gap: 0.4em;
}

.compare__wordmark .wordmark__mark {
  height: 1.5em;
}

.compare__answer {
  color: var(--text-soft);
  font-family: var(--font-label);
  font-size: 0.86rem;
}

.compare__answer--no {
  color: var(--text-faint);
}

.compare__table tbody td.compare__answer:first-of-type {
  color: var(--bg);
  font-weight: 700;
}

.compare__figure {
  color: var(--text-soft);
  font-family: var(--font-display);
  font-size: 1.15rem;
  font-weight: 600;
}

.compare__table tbody td.compare__figure:first-of-type {
  color: var(--bg);
}

/* Below 800px four columns leave the claims too narrow for words
   like "onboarding": each claim instead takes its own full-width line,
   its three answers under it. Our column becomes a stack of the night's
   cells rather than one strip. The three answer columns are 9.5rem each,
   so the claims only get a readable column from 800px. */
@media (max-width: 799px) {
  .compare__table,
  .compare__table thead,
  .compare__table tbody {
    display: block;
  }

  .compare__table tr {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }

  /* The empty corner over the claims. */
  .compare__table thead td {
    display: none;
  }

  .compare__table thead th {
    font-size: 0.7rem;
    padding: 0.8rem 0.15rem;
    width: auto;
  }

  .compare__wordmark {
    font-size: 0.66rem;
  }

  .compare__table tbody th {
    border-bottom: 0;
    grid-column: 1 / -1;
    padding: 0.85rem 0.75rem 0.35rem;
  }

  .compare__table tbody td {
    padding: 0.55rem 0.2rem 0.85rem;
  }
}

@media (max-width: 359px) {
  .compare__wordmark {
    font-size: 0.6rem;
  }
}

.assess__questions {
  display: grid;
  gap: 0.3rem;
  list-style: none;
  margin: 0;
  padding: 0;
}

/* Each question hangs off a short structure rule, like a probe on a drawing. */
.assess__questions li {
  padding-left: 1.5rem;
  position: relative;
}

.assess__questions li::before {
  background: var(--structure);
  content: "";
  height: 2px;
  left: 0;
  position: absolute;
  top: 0.68em;
  width: 0.9rem;
}

.assess__close {
  color: var(--text);
  font-family: var(--font-display);
  font-size: 1.3rem;
  font-weight: 600;
  line-height: 1.3;
  margin: 1.75rem 0 0;
  text-wrap: balance;
}

/* Logo wall — bare client logos printed straight on the paper, no boxes:
   a single file of featured marks distributed across the full measure,
   reading as one line of type. Each mark is a CSS mask filled with the
   text ink, so mixed-color source assets all print in one ink. */
.logos {
  align-items: center;
  display: flex;
  flex-wrap: wrap;
  gap: 1.75rem 2.5rem;
  justify-content: space-between;
  list-style: none;
  margin: 2.75rem 0 0;
  padding: 0;
}

@media (max-width: 720px) {
  .logos {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    justify-items: start;
  }
}

/* Each mark links to its company. Hover (and keyboard focus) answers with
   the mark itself: it grows a little. No underline: the mark is the
   target. */
.logos__link {
  display: block;
  text-decoration: none;
  transition: transform 220ms var(--ease-out);
}

.logos__link:hover,
.logos__link:focus-visible {
  transform: scale(1.12);
}

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

  .logos__link:hover,
  .logos__link:focus-visible {
    transform: none;
  }
}

.logos__logo {
  background: var(--text);
  display: block;
  -webkit-mask: var(--mark) center / contain no-repeat;
  mask: var(--mark) center / contain no-repeat;
}

/* The total line: a full-width hairline rule (ledger grammar) sums the
   entries above it — the payoff lands in serif ink on the left, and the
   rest of the client list rides the same line as a mono annotation. */
.logos__total {
  align-items: baseline;
  border-top: 1px solid var(--rule);
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 2.5rem;
  justify-content: space-between;
  margin-top: 2.75rem;
  padding-top: 1.25rem;
}

.logos__closer {
  color: var(--text);
  font-family: var(--font-display);
  font-size: 1.3rem;
  margin: 0;
  text-wrap: balance;
}

.logos__also {
  color: var(--structure);
  font-family: var(--font-label);
  font-size: 12.9px;
  margin: 0;
}

/* Testimonial — a set pull quote printed straight on the paper, no box
   (same discipline as the logo wall). The display face carries the quote; the only
   ornament is one detail mark: an oversized opening quote glyph.
   Attribution is a structural annotation (mono, structure ink) plus the same
   ink-masked Archive mark the wall uses. */
.testimonial {
  padding-top: 3.5rem;
}

/* Exception, deliberate: centered display type on the flood reads on
   its own measure — wider than the body measure at a larger size, and
   centered rather than ranged left, because it is set as a quote. */
.testimonial__figure {
  margin: 0 auto;
  max-width: 44rem;
  text-align: center;
  position: relative;
}

.testimonial__quote {
  margin: 0;
}

.testimonial__quote::before {
  color: var(--detail);
  content: "\201C";
  display: block;
  font-family: var(--font-display);
  font-size: 4.5rem;
  line-height: 0.72;
  margin-bottom: 0.75rem;
}

.testimonial__quote p {
  color: var(--text);
  font-family: var(--font-display);
  font-size: clamp(1.2rem, 2.2vw, 1.45rem);
  line-height: 1.55;
  margin: 0 0 1em;
  text-wrap: pretty;
}

.testimonial__quote p:last-child {
  margin-bottom: 0;
}

.testimonial__attribution {
  align-items: center;
  display: flex;
  flex-wrap: wrap;
  gap: 0.9rem;
  justify-content: center;
  margin-top: 2rem;
}

/* A person prints as themselves: the photo in its own colour, untouched
   — every treatment tried (duotone, halftone, hatching, a sketch) read
   worse than the face — with edges that fade into the surface behind it
   instead of a hard crop. */
/* A portrait is a square crop on a hairline — a photograph, framed the way
   the page frames every readout. (The feathered round edge was a print
   vignette, retired 2026-10-27.) */
/* The speaker's photo arrives with their name: typed.js holds it unseen
   until the cursor reaches the name, then it shows as a line for a beat
   and opens out from its centre. */
.typed--live [data-typed-arrive]:not(.is-arrived) {
  visibility: hidden;
}

.typed--live [data-typed-arrive].is-arrived {
  animation: line-open 560ms cubic-bezier(0.65, 0, 0.35, 1);
}

/* The photo turns on the way the hero's pixels do: a horizontal line that
   opens out from its centre. */
@keyframes line-open {
  0%,
  22% {
    transform: scaleY(0.04);
  }
}

.testimonial__portrait {
  border: 1px solid var(--rule-strong);
  display: block;
  flex: none;
  height: 56px;
  position: relative;
  width: 56px;
}

.testimonial__photo {
  display: block;
  height: 100%;
  object-fit: cover;
  width: 100%;
}

/* Name set as type (the display face), role as the label annotation beneath it. */
/* The testimonial is typed (public/typed.js): one block cursor sets the
   quote quickly, pausing at its punctuation, then the name and role at a
   hand's slower pace. Every character holds its place unseen until it is
   typed (.typed-ch), so nothing reflows and a screen reader reads the
   whole from the start. */
/* Each character fades in rather than popping, so the typed edge is a
   short gradient flowing along the line, not a run of hard jumps. */
.typed-ch {
  opacity: 0;
  transition: opacity 140ms linear;
}

.typed-ch.is-typed {
  opacity: 1;
}

/* The cursor sits on the typed edge every frame and holds steady while
   typing; it only blinks once the text is done. It does not glide: a
   transition on left restarted every frame and never caught the edge at
   this pace — measured 40–170px behind it, and sweeping back across the
   text at every wrap (2026-10-28). The characters' own fade softens the
   edge instead. */
.typed-cursor {
  background: currentColor;
  pointer-events: none;
  position: absolute;
  transition: opacity 240ms var(--ease-out);
  width: 0.5em;
}

.typed-cursor.is-done {
  animation: typed-blink 1060ms steps(1, end) infinite;
}

.typed-cursor.is-gone {
  opacity: 0;
}

@keyframes typed-blink {
  50% {
    opacity: 0;
  }
}

.testimonial__who {
  display: grid;
  gap: 0.15rem;
  text-align: left;
}

.testimonial__name {
  color: var(--text);
  font-family: var(--font-display);
  font-size: 1.1rem;
  font-weight: 600;
  line-height: 1.2;
}

.testimonial__role {
  color: var(--structure);
  font-family: var(--font-label);
  font-size: 0.92rem;
}

/* ── "The person screening your hire" (.why--paired) — two columns of one
   shape: an identity row (a 96px picture — Joe's feathered portrait, or a
   3×3 square of the field — beside a title and a structure label), a lead
   line, then three ruled rows. From 768px the columns share a subgrid, so
   each row starts on the same line across both. ── */
.why--paired .why__col {
  display: grid;
  grid-template-rows: subgrid;
  grid-row: span 3;
  row-gap: 1.25rem;
}

.why--paired {
  row-gap: 2.5rem;
}

@media (min-width: 768px) {
  .why--paired {
    column-gap: 2.5rem;
    grid-template-rows: auto auto auto;
    row-gap: 0;
  }
}

/* Stacked, the first column's closing rule already marks the seam. */
@media (max-width: 767px) {
  .why--paired > .why__col + .why__col::before {
    display: none;
  }
}

.why__id {
  align-items: center;
  display: flex;
  gap: 1.1rem;
}

.why__picture {
  flex: none;
  height: 96px;
  width: 96px;
}

.testimonial__portrait.why__picture {
  height: 96px;
  width: 96px;
}

/* At 96px the field square prints a touch stronger than the hero field,
   so it holds its own beside a photograph. */
.why__market .field-patch__field {
  opacity: 0.85;
}

.why__who {
  display: grid;
  gap: 0.2rem;
}

.why__title {
  font-size: 1.2rem;
  margin: 0;
}

.why__label {
  color: var(--structure);
  font-family: var(--font-label);
  font-size: 0.92rem;
}

.why__link {
  font-family: var(--font-label);
  font-size: 0.88rem;
  justify-self: start;
}

.section__lede.why__lead {
  margin: 0;
}

.record {
  align-self: start;
  border-bottom: 1px solid var(--rule);
  margin: 0;
}

.record__row {
  border-top: 1px solid var(--rule);
  display: grid;
  gap: 0.15rem;
  padding: 0.75rem 0;
}

@media (min-width: 1100px) {
  .record__row {
    column-gap: 1.25rem;
    grid-template-columns: 10.5rem minmax(0, 1fr);
  }
}

.record dt {
  color: var(--text);
  font-weight: 500;
}

.record dd {
  color: var(--text-soft);
  margin: 0;
}

/* Below the hero the logo wall runs as a proof argument on one axis:
   claim, one-line setup, a single file of marks, then the ruled total
   line (serif payoff left, "also" annotation right). */
.logos-strip {
  padding: 6rem 0 6.5rem;
}

.logos-strip h2 {
  font-size: 1.55rem;
  margin-bottom: 0.5rem;
}

/* FAQ — serif question, Fragment Mono "+" indicator that rotates on open. */
/* ── FAQ — questions in two labelled groups (mono, like every
   label), each a hairline row: a display-face question at 500 and a drawn structure
   plus that turns to a cross when open. The answer slides open where the
   browser can animate <details> (::details-content); elsewhere it simply
   appears. ── */
/* ── The framework pillars — a ruled list in the reading column: a small
   solid field mark in the row's person ink, the title (the link, whose
   hit area covers the row) over its description, and an arrow that
   nudges on hover. ── */
.pillars {
  border-bottom: 1px solid var(--rule);
  list-style: none;
  margin: 2rem -0.85rem 0;
  padding: 0;
}

.pillars__row {
  border-top: 1px solid var(--rule);
  display: grid;
  gap: 0 1rem;
  grid-template-columns: minmax(0, 1fr) 1.25rem;
  padding: 1rem 0.85rem;
  position: relative;
}

.pillars__row:first-child {
  border-top-color: var(--rule-strong);
}

.pillars__title {
  font-size: 1.1rem;
  font-weight: 500;
  margin: 0 0 0.25rem;
}

.pillars__link {
  color: var(--text);
}

.pillars__link::after {
  content: "";
  inset: 0;
  position: absolute;
}

.pillars__row p {
  color: var(--text-soft);
  margin: 0;
  text-wrap: pretty;
}

.pillars__arrow {
  font-family: var(--font-label);
  padding-top: 0.1rem;
}

.faq {
  max-width: var(--measure);
}

.faq__group + .faq__group {
  margin-top: 3rem;
}

.faq__label {
  color: var(--structure);
  font-family: var(--font-label);
  font-size: 0.95rem;
  font-weight: 400;
  margin: 0 0 0.9rem;
}

/* The question row takes the whole height between the two rules, so its
   hover sheet runs rule to rule, and the answer carries its own space
   below. The rules reach 0.75rem into the gutter on both sides, the same
   as the sheet, so the sheet's corners meet them; the padding keeps the
   words in their column. */
.faq__item {
  border-bottom: 1px solid var(--rule);
  margin-inline: -0.75rem;
  padding-inline: 0.75rem;
}

.faq__group > .faq__label + .faq__item {
  border-top: 1px solid var(--rule-strong);
}

.faq__item summary {
  align-items: center;
  cursor: pointer;
  display: flex;
  font-family: var(--font-display);
  font-size: 1.08rem;
  font-weight: 500;
  list-style: none;
  min-height: 44px;
  padding-right: 2.5rem;
  position: relative;
}

.faq__item summary::-webkit-details-marker {
  display: none;
}

/* The marker: two short structure strokes. */
.faq__item summary::before,
.faq__item summary::after {
  background: var(--structure);
  content: "";
  height: 2px;
  position: absolute;
  right: 1rem;
  top: 50%;
  transition: transform 220ms var(--ease-out);
  width: 14px;
}

.faq__item summary::after {
  transform: rotate(90deg);
}

.faq__item[open] summary::before {
  transform: rotate(45deg);
}

.faq__item[open] summary::after {
  transform: rotate(135deg);
}

/* The question row hangs 0.75rem into the gutter on both sides, so the
   sheet has room around the words while the text keeps its column. */
.faq__item summary {
  margin-inline: -0.75rem;
  padding: 0.6rem 3.25rem 0.6rem 0.75rem;
}

/* Hover lifts the whole item, question and answer, open or shut — not the
   question row alone, which left an open answer, and the item's bottom
   edge, outside the sheet. The item takes the lift and a brighter edge at
   the rules' own weight on all four sides: its bottom rule turns that
   colour, a 1px shadow lies over the rule above it, and the sides are
   drawn inside. The keyboard gets the same sheet. An open item closes
   from a click anywhere on it (public/faq.js), so it says so with the
   pointer. */
details.faq__item {
  --edge: color-mix(in srgb, var(--structure) 75%, transparent);
  transition:
    background-color 180ms var(--ease-out),
    border-color 180ms var(--ease-out),
    box-shadow 180ms var(--ease-out);
}

details.faq__item:hover,
details.faq__item:has(> summary:focus-visible) {
  background-color: var(--lift);
  border-bottom-color: var(--edge);
  box-shadow:
    0 -1px 0 var(--edge),
    inset 1px 0 0 var(--edge),
    inset -1px 0 0 var(--edge);
}

/* An item that carries its list's top rule turns that rule instead. */
.faq__group
  > .faq__label
  + details.faq__item:is(:hover, :has(> summary:focus-visible)),
.brief-read
  > li:first-child
  > details.faq__item:is(:hover, :has(> summary:focus-visible)) {
  border-top-color: var(--edge);
  box-shadow:
    inset 1px 0 0 var(--edge),
    inset -1px 0 0 var(--edge);
}

details.faq__item:hover summary {
  color: var(--accent);
}

details.faq__item[open] {
  cursor: pointer;
}

.faq__item summary:focus-visible {
  outline: none;
}

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

/* The answer's space below is padding, not margin: a bottom margin
   collapses out of the opening box, so it arrived on its own after the
   slide had finished. */
.faq__item p {
  color: var(--text-soft);
  margin: 0;
  padding: 0.15rem 2.5rem 1rem 0;
  text-wrap: pretty;
}

/* The slide, where supported. */
@supports selector(::details-content) {
  .faq__item {
    interpolate-size: allow-keywords;
  }

  .faq__item::details-content {
    block-size: 0;
    overflow: clip;
    transition:
      block-size 280ms var(--ease-out),
      content-visibility 280ms allow-discrete;
  }

  .faq__item[open]::details-content {
    block-size: auto;
  }

  /* The answer fades up as the row opens, a beat behind the slide. */
  .faq__item p {
    filter: blur(var(--arrive-blur));
    opacity: 0;
    transform: translateY(-4px);
    transition:
      opacity 220ms var(--ease-out),
      filter 220ms var(--ease-out),
      transform 280ms var(--ease-out);
  }

  .faq__item[open] p {
    filter: none;
    opacity: 1;
    transform: none;
    transition:
      opacity var(--arrive) var(--ease-out) 60ms,
      filter var(--arrive) var(--ease-out) 60ms,
      transform var(--arrive) var(--ease-out) 40ms;
  }
}

@media (prefers-reduced-motion: reduce) {
  .faq__item summary::before,
  .faq__item summary::after,
  .faq__item::details-content,
  .faq__item p {
    transition: none;
  }
}

/* The close opens on the frame's band like every section, and the footer
   runs on from it below its own hairline. */
.final-cta {
  padding: 6rem 0;
  position: relative;
  text-align: left;
}

/* On a phone the close opens, like the hero, with two whole rows of the
   field above its plate. */
@media (max-width: 640px) {
  .final-cta {
    padding: calc(2 * var(--field-phone-cell)) 0 4.5rem;
  }
}

.final-cta .shell {
  position: relative;
  z-index: 1;
}

/* The landing close answers the hero (DESIGN.md "The close"): the pixel
   panel once more behind it (pixel-field.js, a second panel), and the call
   set straight on the night like the hero's copy — no plate. */
.final-cta--night {
  align-items: center;
  display: flex;
  min-height: min(42rem, 88vh);
  padding: 6rem 0;
}

.close__copy {
  pointer-events: none;
}

.close__copy > * {
  max-width: 34rem;
  pointer-events: auto;
}

.close__copy h2 {
  font-size: clamp(2rem, 4.2vw, 2.9rem);
  line-height: 1.12;
  margin-bottom: 1rem;
}

.close__copy .section__lede {
  margin-bottom: 2rem;
}

/* On a phone the copy takes the width, so the close adds a band below the
   button where the person rises, as the hero does. */
@media (max-width: 767px) {
  .final-cta--night {
    align-items: flex-start;
    min-height: 0;
    padding: 4rem 0 17rem;
  }
}

/* ── Arrivals below the fold (ink-press.js arms each [data-print] root
   .will-print and marks it .is-printing when it is looked at). Two
   families, each one motion: content arrives (focusing in from a slight
   blur, the `arrive` keyframes) and pixel marks turn on (opening from
   their centre line). Each runs once. None of it runs without the
   script or under reduced motion. ── */

/* The logo wall: each mark clears from a slight blur in turn, left to
   right. */
.logos-strip.will-print:not(.is-printing) .logos__logo {
  filter: blur(var(--arrive-blur));
  opacity: 0;
}

.logos-strip.is-printing .logos__logo {
  transition:
    opacity 420ms var(--ease-out) calc(var(--i, 0) * 70ms),
    filter 520ms var(--ease-out) calc(var(--i, 0) * 70ms);
}

/* The pricing plate arrives whole, like every other piece of content. */
.trace.will-print:not(.is-printing) {
  opacity: 0;
}

.trace.is-printing {
  animation: arrive var(--arrive) var(--ease-out) both;
}

/* The add-ons scoring sheet arrives like the offer's readouts. */
.assess__art.will-print:not(.is-printing) .timeline-art {
  opacity: 0;
}

.assess__art.is-printing .timeline-art {
  animation: arrive var(--arrive) var(--ease-out) both;
}

/* The comparison: our column's cells turn on in turn, top to bottom — a
   pixel opening from its centre line, per cell. */
.compare.will-print:not(.is-printing)
  .compare__table
  :is(thead th, tbody td):first-of-type {
  clip-path: inset(50% 0 50% 0);
}

.compare.is-printing .compare__table :is(thead th, tbody td):first-of-type {
  animation: cell-on 260ms var(--ease-out) calc(80ms + var(--r, 0) * 60ms) both;
}

.compare__table tbody tr:nth-child(1) td:first-of-type {
  --r: 1;
}
.compare__table tbody tr:nth-child(2) td:first-of-type {
  --r: 2;
}
.compare__table tbody tr:nth-child(3) td:first-of-type {
  --r: 3;
}
.compare__table tbody tr:nth-child(4) td:first-of-type {
  --r: 4;
}
.compare__table tbody tr:nth-child(5) td:first-of-type {
  --r: 5;
}
.compare__table tbody tr:nth-child(6) td:first-of-type {
  --r: 6;
}

@keyframes cell-on {
  from {
    clip-path: inset(50% 0 50% 0);
  }
  to {
    clip-path: inset(0);
  }
}

/* The sourcing square keeps finding people: one at a time, a dim pixel
   turns over to a hero ink — flattening to a line and opening out in its
   colour, as the hero's pixels do — holds two seconds, then turns back
   over to dim. Four take their turn on a 10s loop, never two at once. */
@media (prefers-reduced-motion: no-preference) {
  .why__market .field-patch__field .person-mark rect {
    transform-box: fill-box;
    transform-origin: center;
  }

  .why__market .field-patch__field .person-mark:nth-child(3) rect {
    --find: var(--person-viridian);
    animation: patch-find 10s linear 0.6s infinite;
  }

  .why__market .field-patch__field .person-mark:nth-child(7) rect {
    --find: var(--person-cobalt);
    animation: patch-find 10s linear 3.1s infinite;
  }

  .why__market .field-patch__field .person-mark:nth-child(5) rect {
    --find: var(--person-orchid);
    animation: patch-find 10s linear 5.6s infinite;
  }

  .why__market .field-patch__field .person-mark:nth-child(2) rect {
    --find: var(--person-vermilion);
    animation: patch-find 10s linear 8.1s infinite;
  }
}

/* One turn each way is 1.2% of the loop (120ms to the line, 120ms back
   out); the colour changes at the line, where the pixel has no height. */
@keyframes patch-find {
  0% {
    fill: currentColor;
    fill-opacity: 0.32;
    transform: scaleY(1);
  }
  1.2% {
    fill: currentColor;
    fill-opacity: 0.32;
    transform: scaleY(0);
  }
  1.21% {
    fill: var(--find);
    fill-opacity: 1;
    transform: scaleY(0);
  }
  2.4% {
    fill: var(--find);
    fill-opacity: 1;
    transform: scaleY(1);
  }
  20% {
    fill: var(--find);
    fill-opacity: 1;
    transform: scaleY(1);
  }
  21.2% {
    fill: var(--find);
    fill-opacity: 1;
    transform: scaleY(0);
  }
  21.21% {
    fill: currentColor;
    fill-opacity: 0.32;
    transform: scaleY(0);
  }
  22.4%,
  100% {
    fill: currentColor;
    fill-opacity: 0.32;
    transform: scaleY(1);
  }
}

/* Salary-page composition only (DESIGN.md "Public jobs", "The frame").
 * Fonts, paper, the frame (rails, band, crossing squares), links, header
 * and footer come from the real site.css. The hero is a paper section; the
 * instrument and the filters are two night sections, so night meets night
 * and the site's band marks the seam. Ported from the approved mockup
 * docs/superpowers/mocks/2026-11-03-salary-redesign (d-body.html, mock.css). */
.salary :where(button, input, select) {
  font-family: inherit;
  line-height: inherit;
  color: var(--text);
}
.salary button {
  cursor: pointer;
}
.salary :is(button, input, select, summary):focus-visible {
  outline: 2px solid var(--structure);
  outline-offset: 2px;
}
.jobs-skip:not(:focus) {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}
.jobs-skip:focus {
  position: fixed;
  z-index: 30;
  top: 12px;
  left: 12px;
  padding: 12px;
  background: var(--bg);
}

/* ── Hero: a paper section, copy only. ── */
.salary .salary-hero {
  padding-block: 72px 48px;
}
.salary-hero h1 {
  max-width: 16ch;
  margin: 0 0 20px;
  font-family: var(--font-display);
  font-size: clamp(2.4rem, 4.6vw, 3.6rem);
  line-height: 1.04;
  letter-spacing: -0.02em;
  text-wrap: balance;
}
.salary-lede {
  max-width: var(--measure);
  margin: 0;
  color: var(--text-soft);
  font-size: 1.0625rem;
  line-height: 1.55;
}

/* ── Night chapters: the site's .section--night draws the frame; the
   mockup also pinned its ground and ink. ── */
.salary .section--night {
  background-color: var(--bg);
  color: var(--text);
}
.salary .salary-chapter--field {
  padding-block: 32px 40px;
}
.salary .salary-chapter--filters {
  padding-block: 36px 64px;
}
.salary .salary-below {
  padding-block: 3rem 4rem;
}

/* ── Country (links to ?country=) and Clear all. ── */
.salary-top {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 20px;
  padding-bottom: 18px;
  margin-bottom: 6px;
  border-bottom: 1px solid var(--rule);
}
.salary-countries {
  display: inline-flex;
  border: 1px solid var(--rule-strong);
  font-family: var(--font-label);
  font-size: 12.5px;
  line-height: 1;
}
.salary-countries a {
  --ul: none;
  padding: 9px 12px;
  color: var(--text-soft);
  text-decoration: none;
}
.salary-countries a + a {
  border-left: 1px solid var(--rule-strong);
}
.salary-countries a:hover:not([aria-current="page"]) {
  background: var(--lift);
  color: var(--text);
}
.salary-countries a[aria-current="page"] {
  background: var(--text);
  color: var(--bg);
}
.salary-link {
  padding: 9px 0;
  border: 0;
  background: none;
  font: 500 12.5px/1 var(--font-label);
  text-decoration: underline;
  text-decoration-color: var(--detail);
  text-underline-offset: 4px;
}
.salary-link:hover {
  text-decoration-color: var(--accent);
}
.salary-link[hidden] {
  display: none;
}

/* ── The answer: one big median, one line under it. ── */
.salary-answer-row {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 32px;
  flex-wrap: wrap;
  margin: 26px 0 18px;
}
.salary-big {
  /* The suffix sits on the number's baseline and, on a phone, wraps flush
     left under it: the gap separates them only on one line. */
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  column-gap: 10px;
  margin: 0;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(3rem, 7vw, 5.25rem);
  line-height: 0.95;
  letter-spacing: -0.035em;
  font-variant-numeric: tabular-nums;
}
.salary-big__num {
  display: inline-block;
}
.salary-big small {
  font-size: 0.32em;
  font-weight: 600;
  letter-spacing: 0;
  color: var(--text-soft);
}
.salary-sub {
  margin: 12px 0 0;
  font: 500 13px/1.5 var(--font-label);
  color: var(--text-soft);
}
.salary-sub b {
  color: var(--text);
  font-weight: 700;
}
.salary-thin {
  max-width: 22ch;
  margin: 0;
  font-family: var(--font-display);
  font-size: clamp(1.6rem, 3vw, 2.2rem);
  line-height: 1.1;
}
/* The answer's status line: a 429's wait, or a failed update. */
.salary-status {
  margin: 0 0 18px;
  padding-left: 10px;
  border-left: 2px solid var(--accent);
  font: 500 12.5px/1.45 var(--font-label);
  color: var(--text);
}
.salary-status[hidden] {
  display: none;
}

/* ── Field stage: holds the canvas the pixel field draws into, and the
   hover card. ── */
.salary-stage {
  position: relative;
  min-width: 0;
}
.salary-stage[hidden] {
  display: none;
}
.salary-stage canvas {
  display: block;
  width: 100%;
}
/* The field's words over the canvas (salary-wash.ts): the median's label at
   the top, the pay axis under the base. Page text, so as sharp as the rest
   of the page; placed by the script in CSS px from the stage's corner. */
.salary-labels {
  position: absolute;
  inset: 0;
  pointer-events: none;
}
.salary-labels span {
  position: absolute;
  font-family: var(--font-label);
  font-weight: 500;
  line-height: 1;
  white-space: nowrap;
}
.salary-labels__median {
  top: 14px;
  font-size: 12px;
  color: var(--text);
}
.salary-labels__axis {
  top: var(--salary-axis-top);
  font-size: 11px;
  color: var(--text-faint);
}
.salary-labels--centre {
  transform: translateX(-50%);
}
.salary-labels--right {
  transform: translateX(-100%);
}
.salary-card {
  position: absolute;
  width: 268px;
  padding: 14px 14px 12px;
  border: 1px solid var(--rule-strong);
  background: color-mix(in srgb, var(--stock-night) 80%, black);
  box-shadow: 0 16px 40px
    color-mix(in srgb, var(--stock-night) 70%, transparent);
  pointer-events: none;
}
.salary-card[hidden] {
  display: none;
}
.salary-card h3 {
  margin: 0 0 4px;
  font-family: var(--font-display);
  font-size: 1.15rem;
  letter-spacing: -0.01em;
}
.salary-card .sub {
  margin: 0 0 10px;
  font: 500 11.5px/1.45 var(--font-label);
  color: var(--text-soft);
}
.salary-card .grp {
  margin: 10px 0 6px;
  font: 500 10.5px/1 var(--font-label);
  color: var(--structure);
}
.salary-card .bar {
  display: grid;
  grid-template-columns: 6.5rem 1fr 2rem;
  gap: 8px;
  align-items: center;
  font: 500 12px/1.5 var(--font-body);
}
.salary-card .bar i {
  display: block;
  height: 5px;
  background-image: linear-gradient(90deg, var(--ink) 5px, transparent 5px);
  background-size: 6px 5px;
}
.salary-card .bar b {
  text-align: right;
  font: 600 11.5px/1 var(--font-label);
}

/* ── Filters: three columns of chip groups, one column under 900px. ── */
.salary-filters {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  column-gap: 32px;
}
.salary-col {
  display: grid;
  align-content: start;
}
@media (max-width: 900px) {
  .salary-filters {
    grid-template-columns: 1fr;
  }
}
.salary-row {
  display: grid;
  grid-template-columns: 1fr;
  gap: 9px;
  align-items: start;
  padding: 12px 0;
  border-bottom: 1px solid var(--rule);
}
.salary-row__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}
.salary-label {
  margin: 0;
  font: 500 11px/1 var(--font-label);
  color: var(--structure);
}
.salary-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
.salary-chip {
  display: inline-grid;
  grid-template-columns: auto auto;
  align-items: baseline;
  gap: 8px;
  padding: 8px 9px;
  border: 1px solid var(--rule-strong);
  background: transparent;
  font: 500 12.5px/1 var(--font-body);
}
.salary-chip__m {
  font: 600 12px/1 var(--font-label);
  color: var(--text-soft);
  font-variant-numeric: tabular-nums;
}
.salary-chip:hover:not(:disabled):not([aria-pressed="true"]) {
  background: var(--lift);
  border-color: var(--text-soft);
}
.salary-chip[aria-pressed="true"] {
  background: var(--text);
  color: var(--bg);
  border-color: var(--text);
}
.salary-chip[aria-pressed="true"] .salary-chip__m {
  color: var(--bg);
  opacity: 0.75;
}
/* Too thin to stand on: muted fill, no border, never clickable. */
.salary .salary-chip:disabled {
  background: color-mix(in srgb, var(--text) 4%, transparent);
  border-color: transparent;
  color: var(--text-faint);
  cursor: not-allowed;
  opacity: 0.62;
}
.salary-chip:disabled .salary-chip__m {
  font-weight: 500;
  color: var(--text-faint);
}
/* A pick is in flight: the figures are the last selection's until the
   answer lands, so they recede rather than read as this one's. */
[data-salary-filters][aria-busy="true"] .salary-chip__m,
[data-salary-filters][aria-busy="true"] .salary-fact__m {
  opacity: 0.35;
  transition: opacity 120ms ease-out 80ms;
}
/* Tools read "All of" by default: picked chips lead with a +. */
.salary-chip--all {
  grid-template-columns: auto auto auto;
}
.salary-chip--all[aria-pressed="true"]::before {
  content: "+";
  margin-right: -2px;
  font: 700 12px/1 var(--font-label);
}
.salary-match {
  display: inline-flex;
  border: 1px solid var(--rule-strong);
}
.salary-match button {
  padding: 5px 8px;
  border: 0;
  background: none;
  font: 500 11px/1 var(--font-label);
  color: var(--text-soft);
}
.salary-match button + button {
  border-left: 1px solid var(--rule-strong);
}
.salary-match button[aria-pressed="true"] {
  background: var(--text);
  color: var(--bg);
}
.salary-rule {
  margin: 2px 0 0;
  font: 500 11.5px/1.45 var(--font-label);
  color: var(--text-soft);
}
.salary-rule[hidden] {
  display: none;
}
.salary-fact {
  margin: 0;
  padding: 8px 10px;
  border: 1px solid var(--rule);
  font: 500 13px/1.45 var(--font-body);
  color: var(--text-soft);
}
.salary-fact b {
  color: var(--text);
  font-weight: 600;
}
.salary-empty {
  margin: 0;
  padding-top: 9px;
  font: 500 12px/1.4 var(--font-label);
  color: var(--text-faint);
}

/* ── Table view: the same data for readers and assistive tech. ── */
.salary-table {
  width: 100%;
  margin-top: 1.5rem;
  border-collapse: collapse;
  font-size: 0.9rem;
}
.salary-table :is(th, td) {
  padding: 0.55rem 0.6rem;
  border-bottom: 1px solid var(--rule);
  text-align: left;
}
.salary-table th {
  color: var(--text-faint);
  font-family: var(--font-label);
  font-size: 0.75rem;
  font-weight: 500;
}
.salary-table td:not(:first-child) {
  font-family: var(--font-label);
  font-variant-numeric: tabular-nums;
}

/* ── Server summary: medians by level and by years, side by side. ── */
.salary-summary {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(18rem, 100%), 1fr));
  gap: 2rem;
}

/* ── Note. ── */
.salary-note {
  max-width: var(--measure);
  margin-top: 1.5rem;
  color: var(--text-soft);
  font-size: 0.875rem;
}

/* ── Cite this data: one copyable line under the note. ── */
.salary-cite {
  max-width: var(--measure);
  margin-top: 2rem;
  padding-top: 1rem;
  border-top: 1px solid var(--rule);
}
.salary-cite h2 {
  margin: 0 0 0.5rem;
  color: var(--text-faint);
  font-family: var(--font-label);
  font-size: 0.75rem;
  font-weight: 500;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.salary-cite p {
  margin: 0;
  font-family: var(--font-label);
  font-size: 0.8125rem;
  overflow-wrap: anywhere;
  user-select: all;
}

/* ══ The reading pages: the monthly report and its index, the citable
   pages and the hub (render-shell.ts; design brief
   docs/superpowers/specs/2026-11-03-gtmes-salary-pages-design-brief.md).
   Paper like a content page: the site's paper hero and close beside the
   field, sections in the frame; the report's figures are one night
   chapter. ══ */
.salary-page-hero h1 {
  max-width: 24ch;
  overflow-wrap: anywhere;
}
.salary-page-hero .breadcrumb {
  align-items: center;
}
/* Night meets the paper hero on a straight edge: no band, no rule. */
.salary-page-hero:has(+ .section--night) {
  border-bottom: 0;
}
.salary-page-hero + .section--night::before,
.salary-page-hero + .section--night::after {
  content: none;
}
.salary .salary-section {
  padding-block: 3.5rem 4rem;
}
@media (min-width: 960px) {
  .salary .salary-section {
    padding-block: 4.5rem 5rem;
  }
}

/* ── The readout in the hero: the main page's answer language. ── */
.salary-readout {
  margin-top: 1.75rem;
}
.salary-readout__label {
  margin: 0 0 0.6rem;
  font: 500 12.5px/1.4 var(--font-label);
  color: var(--structure);
}
.salary-readout .salary-big {
  font-size: clamp(2.75rem, 6vw, 4.5rem);
}

/* ── Mono annotation lines: what the figures are, as of when. ── */
.salary-scope {
  margin: 0.5rem 0 0;
  font: 500 12px/1.45 var(--font-label);
  color: var(--structure);
}
.salary-colophon {
  max-width: var(--measure);
  margin: 1.25rem 0 0;
  font: 500 12px/1.6 var(--font-label);
  color: var(--text-soft);
}
.paper-hero__copy .salary-colophon {
  margin-top: 1rem;
}
.salary-links {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1.5rem;
  margin: 1.5rem 0 0;
}
.salary-links--stack {
  flex-direction: column;
  align-items: flex-start;
}
.salary-api {
  max-width: var(--measure);
  margin: 1.5rem 0 0;
  font: 500 11.5px/1.6 var(--font-label);
  color: var(--text-faint);
  overflow-wrap: anywhere;
}
.salary-explainer {
  max-width: var(--measure);
  margin: 0 0 2rem;
}
.salary-more,
.salary-thin-note {
  max-width: var(--measure);
}
.salary-thin-note {
  margin: 0;
  color: var(--text-soft);
}

/* ── The range strip (DESIGN.md "Charts print in the text ink"): the
   middle half a flat 14% tint of the text ink, the median a solid 2px ink
   tick. Geometry is set inline from the server's shared axis; a label is
   shifted by its own share of the plot (--at), so it always contains its
   point and never leaves the plot. ── */
.salary-range {
  position: relative;
}
.salary-range__plot {
  position: relative;
  height: 12px;
}
.salary-range__band {
  position: absolute;
  top: 0;
  bottom: 0;
  background: color-mix(in srgb, var(--text) 14%, transparent);
}
/* On the night a 14% tint sinks into the ground: the band takes 24%. */
.section--night .salary-range__band {
  background: color-mix(in srgb, var(--text) 24%, transparent);
}
.salary-range__tick {
  position: absolute;
  top: -3px;
  bottom: -3px;
  width: 2px;
  margin-left: -1px;
  background: var(--text);
}
/* As wide as the facts under it, so the two read as one block. */
.salary-range--large {
  max-width: var(--measure);
  margin: 0 0 2rem;
  padding-block: 1.75rem;
}
.salary-range--large .salary-range__plot {
  height: 28px;
}
.salary-range__label {
  position: absolute;
  transform: translateX(calc(var(--at) * -100%));
  font: 500 12.5px/1 var(--font-label);
  color: var(--text-soft);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.salary-range__label--median {
  top: 0;
  color: var(--text);
  font-weight: 700;
}
/* P25 and P75 hang from the band's ends: P25 starts where the band
   starts, P75 ends where it ends, so neither can leave the plot. */
.salary-range__label--p25,
.salary-range__label--p75 {
  bottom: 0;
}
.salary-range__label--p25 {
  transform: none;
}
.salary-range__label--p75 {
  transform: translateX(-100%);
}
/* A narrow band: P25 and P75 take a row each. */
.salary-range--tight {
  padding-bottom: 3.25rem;
}
.salary-range--tight .salary-range__label--p25 {
  bottom: 1.5rem;
}
/* The pay axis under each row's strip, in mono structure ink. */
.salary-axis {
  position: relative;
  height: 1.75rem;
  margin-top: 0.75rem;
}
.salary-axis__tick {
  position: absolute;
  top: 7px;
  transform: translateX(calc(var(--at) * -100%));
  font: 500 11px/1 var(--font-label);
  color: var(--structure);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.salary-axis__tick::before {
  position: absolute;
  top: -7px;
  left: calc(var(--at) * 100%);
  width: 1px;
  height: 4px;
  background: var(--structure);
  content: "";
}

/* ── A cut's facts: an unboxed Ledger, label and figure on one row. ── */
.salary-ledger {
  max-width: var(--measure);
  margin: 0 0 1.25rem;
  border-top: 1px solid var(--rule);
}
.salary-ledger > div {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 0.25rem 1rem;
  padding: 0.6rem 0;
  border-bottom: 1px solid var(--rule);
}
.salary-ledger dt {
  color: var(--text-soft);
}
.salary-ledger dd {
  margin: 0;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  text-align: right;
}

/* ── Rows of cuts (the hub's groups, the report's figures): a Ledger, each
   row the cut's name, its postings, its strip over its own axis, and its
   median. The chart carries the row; the type around it stays quiet. A
   passing row is an object link: the name carries the site's underline,
   its arrow sits in a column of its own at the row's end, and the whole
   row lifts on hover. A thin row is muted, with no line and no arrow. ── */
.salary-cuts {
  margin: 0;
  padding: 0;
  list-style: none;
  border-top: 1px solid var(--rule);
}
.salary-cut {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto 1rem;
  grid-template-areas:
    "name median go"
    "meta meta ."
    "chart chart .";
  column-gap: 1rem;
  align-items: baseline;
  padding: 0.8rem 0.5rem 0.6rem;
  border-bottom: 1px solid var(--rule);
  transition:
    background-color 180ms var(--ease-out),
    box-shadow 180ms var(--ease-out);
}
/* The report's wide figures (short labels: a title level, a years
   band): one line per cut, the chart between the name and the median,
   the arrow centred on the row. */
@media (min-width: 640px) {
  .salary-cuts--inline .salary-cut:not(.salary-cut--thin) {
    grid-template-columns: 8.5rem minmax(0, 1fr) 5.75rem 1rem;
    grid-template-areas:
      "name chart median go"
      "meta chart median go";
    align-items: start;
  }
  .salary-cuts--inline .salary-cut__chart {
    margin-top: 0.3rem;
  }
  .salary-cuts--inline .salary-cut::after {
    align-self: center;
  }
}
.salary-cut__name {
  grid-area: name;
  font-family: var(--font-display);
  font-size: 0.95rem;
  font-weight: 500;
  line-height: 1.3;
}
a.salary-cut__name {
  justify-self: start;
  color: var(--text);
}
a.salary-cut__name::before {
  position: absolute;
  inset: 0;
  content: "";
}
.salary-cut:has(> a)::after {
  grid-area: go;
  justify-self: end;
  color: var(--structure);
  font-family: var(--font-label);
  content: "→";
  transition: transform 180ms var(--ease-out);
}
.salary-cut:has(> a):hover {
  background-color: var(--lift);
  box-shadow: var(--lift-outline);
}
.salary-cut:has(> a):hover a.salary-cut__name {
  --ul: var(--underline-hover);
  color: var(--accent);
}
.salary-cut:has(> a):hover::after {
  color: var(--accent);
  transform: translateX(3px);
}
.salary-cut__median {
  grid-area: median;
  font-family: var(--font-display);
  font-size: 1rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  text-align: right;
}
.salary-cut__meta {
  grid-area: meta;
  margin-top: 0.2rem;
  font: 500 11.5px/1.4 var(--font-label);
  color: var(--text-faint);
  font-variant-numeric: tabular-nums;
}
.salary-cut__chart {
  grid-area: chart;
  margin-top: 0.6rem;
}
.salary-cut .salary-axis {
  height: 1.1rem;
  margin-top: 0.35rem;
}
.salary-cut .salary-axis__tick {
  top: 6px;
  font-size: 10px;
}
.salary-cut .salary-axis__tick::before {
  top: -6px;
  height: 3px;
}
.salary-cut--thin .salary-cut__name {
  color: var(--text-faint);
}
@media (prefers-reduced-motion: reduce) {
  .salary-cut,
  .salary-cut::after {
    transition: none;
  }
}

/* ── Related cuts and the months: page-list rows, the figure under the
   name in mono (beside it, a long name and its figure wrapped unevenly). ── */
.salary-related li p,
.salary-report-list li p {
  margin: 0.3rem 0 0;
  font: 500 12.5px/1.45 var(--font-label);
  font-variant-numeric: tabular-nums;
}

/* ── The report's figures: one night chapter. Level, then years, on one
   USD axis, each group full width so every row's chart has room for its
   axis; then the countries as a HUD band. ── */
.salary .salary-figures {
  padding-block: 4rem 4.5rem;
}
.salary-figures h2 {
  margin: 0 0 2rem;
}
.salary-figures h3 {
  margin: 0;
  font-size: 1.25rem;
}
.salary-figures__groups {
  display: grid;
  gap: 3rem;
  margin-bottom: 3rem;
}
.salary-figures__group .salary-cuts {
  margin-top: 1rem;
}
.salary-country-hud {
  grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
  margin-top: 1rem;
}
.salary-country-hud .hud__cell {
  display: grid;
  gap: 0.35rem;
}
.salary-country-hud .hud__label {
  margin: 0;
}
.salary-hud__median {
  font-family: var(--font-display);
  font-size: 1.6rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.salary-hud__meta {
  font: 500 12px/1.4 var(--font-label);
  color: var(--text-soft);
}

/* ── How we count: the headline counts as a HUD, the cleaning as compact
   tables, the sources as stacked bars in the text ink. ── */
.salary-hud {
  grid-template-columns: repeat(2, minmax(0, 1fr));
  margin: 0 0 1.75rem;
}
.salary-hud .hud__value {
  margin: 0;
  font-variant-numeric: tabular-nums;
}
.salary-method__title {
  margin: 0 0 2.5rem;
}
.salary-method__row + .salary-method__row {
  margin-top: 2.5rem;
  padding-top: 2.5rem;
  border-top: 1px solid var(--rule);
}
.salary-method__row h3 {
  margin: 0;
  font-size: 1.25rem;
}
.salary-clean {
  width: 100%;
  margin: 0.75rem 0 1.25rem;
  border-collapse: collapse;
  font-size: 0.9rem;
}
.salary-clean caption {
  padding-bottom: 0.4rem;
  font: 500 12px/1 var(--font-label);
  color: var(--structure);
  text-align: left;
}
.salary-clean :is(th, td) {
  padding: 0.5rem 0;
  border-top: 1px solid var(--rule);
  vertical-align: top;
}
.salary-clean th {
  padding-right: 1rem;
  font-weight: 400;
  text-align: left;
}
.salary-clean td {
  font: 500 12.5px/1.5 var(--font-label);
  font-variant-numeric: tabular-nums;
  text-align: right;
  white-space: nowrap;
}
.salary-clean-none,
.salary-sources__none {
  font: 500 12.5px/1.5 var(--font-label);
  color: var(--text-soft);
}
.salary-sources {
  max-width: var(--measure);
  margin: 1.25rem 0 1.5rem;
}
.salary-sources figcaption {
  margin-bottom: 0.5rem;
  font-size: 0.95rem;
}
.salary-sources figcaption b {
  font-weight: 600;
}
.salary-sources__rows {
  display: grid;
  grid-template-columns: 10rem minmax(0, 1fr) 5.5rem;
  gap: 0.5rem 1rem;
  align-items: center;
}
.salary-sources__label {
  font-size: 0.9rem;
  color: var(--text-soft);
}
.salary-sources__track {
  position: relative;
  height: 10px;
  background: color-mix(in srgb, var(--text) 8%, transparent);
}
.salary-sources__fill {
  position: absolute;
  inset: 0 auto 0 0;
  min-width: 2px;
  background: var(--text);
}
.salary-sources__value {
  font: 500 12px/1 var(--font-label);
  font-variant-numeric: tabular-nums;
  text-align: right;
}
@media (max-width: 480px) {
  .salary-sources__rows {
    grid-template-columns: minmax(0, 1fr) auto;
  }
  .salary-sources__track {
    grid-column: 1 / -1;
    order: 1;
  }
}
/* Every exclusion stays public, folded under its scope paragraph. */
.salary-clean-all {
  margin-top: 0.75rem;
}
.salary-clean-all > summary {
  width: fit-content;
  font-weight: 500;
  cursor: pointer;
}
.salary-clean-all > summary::marker {
  color: var(--structure);
}

/* ── The close's button sits under its note. ── */
.salary-close .btn {
  margin-top: 1.5rem;
}

/* A short row label's full cut name, for assistive tech and crawlers. */
.salary-sr {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}
