/* ==========================================================================
   AGILE Services Group — design system (site.css)
   Phase 1a — Plumbing. Fonts, tokens, type scale, spacing, grid, states,
   component inventory, motion policy. Written once, complete; later
   phases WIRE markup to these classes, they do not add new tokens.

   Load order: this file loads BEFORE each page's remaining legacy inline
   <style> block (see index.html / blog/*.html <head>). Its component
   classes (.nav, .nav--compact, .dl-row, .project-card, ...) are INERT during
   P1a — no markup references them yet. Only two rules are live on every
   page from P1a onward: `body` (typeface) and the reduced-motion block.
   ========================================================================== */

/* --------------------------------------------------------------------------
   Base reset — the UA stylesheet's default `body { margin: 8px }` produced
   an ~8px light frame around every full-bleed band on all six pages; there
   was no earlier global reset, only a box-sizing stopgap scoped to the
   article region (now removed — this is the single source). Minimal on
   purpose: margin/padding rhythm elsewhere stays explicit, not reset away.
   -------------------------------------------------------------------------- */
html,
body {
  margin: 0;
}

*,
*::before,
*::after {
  box-sizing: border-box;
}

/* Every heading/paragraph/list margin in this system is an explicit
   spacing-token utility (margin-bottom: var(--space-N) etc.) — the UA
   stylesheet's own default margins on these elements were stacking on
   top of that (e.g. h1's default ~59px top margin plus this file's own
   32px bottom margin), which is what pushed the hero's CTAs below the
   1440x900 fold. Zeroed once, here, rather than patched per component. */
h1, h2, h3, h4, h5, h6,
p, dl, dt, dd, figure, blockquote, ul, ol {
  margin: 0;
}

/* --------------------------------------------------------------------------
   Fonts — self-hosted latin-subset variable woff2. "Control Plane" art
   direction (2026-09-21 redesign): Fraunces is deleted. One family, two
   axes (wght 100-900, wdth 62-125 — Google's shipped latin range covers
   and exceeds the 400-800 x 100-125 the direction calls for). Root-
   absolute so blog/*.html resolves correctly. New versioned filename
   (immutable caching — a changed font file always gets a new name; see
   A9 in the campaign's verify.sh, updated to match).
   -------------------------------------------------------------------------- */
@font-face {
  font-family: 'Archivo';
  font-style: normal;
  font-weight: 100 900;
  font-stretch: 62% 125%;
  font-display: swap;
  src: url('/assets/fonts/archivo-var-wdth-latin-v2.woff2') format('woff2');
}

/* --------------------------------------------------------------------------
   Tokens — "Control Plane" palette (art-direction §3.1). Ratios computed
   against the documented pairing; --cyan is a DARK-BAND-ONLY accent (2.05:1
   -- 1.95:1 on --paper, both fail text/UI contrast) — never use it for
   text, borders or fills on --paper or --surface.
   -------------------------------------------------------------------------- */
:root {
  /* Colour */
  --field: #0f2146;                 /* primary dark canvas — hero, current work, tech, footer. white on it 15.83:1 */
  --field-raised: #17305b;          /* elevated surface on --field — diagram nodes, raised panels. white 13.04:1; --cyan 6.10:1 */
  --paper: #f2f5f9;                 /* light band canvas — cool near-white, not cream. --ink 14.48:1; --ink-muted 6.88:1 */
  --surface: #ffffff;               /* raised light band + card fill. --ink 15.83:1 */
  --ink: #0f2146;                   /* headings, body on light */
  --ink-muted: #4a5568;             /* secondary text on light — 6.88:1 on --paper */
  --cyan: #36c0e5;                  /* eyebrows, display numbers, CTA fill, diagram signal — DARK BANDS ONLY. 7.41:1 on --field, 6.10:1 on --field-raised, 1.95:1 on --paper (fails) */
  --on-field: #c3d2e8;              /* body copy on dark bands — 10.33:1 on --field */
  --on-field-dim: #9fb3d1;          /* labels, diagram captions on dark — 7.42:1 on --field */
  --hairline-dark: #2a4a7f;         /* decorative dividers/strokes on dark only — not a control edge */
  --edge-dark: #5578ad;             /* control borders on --field (ghost button) — 3.53:1, meets 1.4.11. Only 2.9:1 on --field-raised (fails) — use --edge-dark-raised there */
  --edge-dark-raised: #6483b3;      /* control borders on --field-raised (Contact form inputs, captcha) — 3.38:1, meets 1.4.11 */
  --blue: #0b5e91;                  /* links, rules, small accents on LIGHT bands — 6.35:1 on --paper */
  --blue-fill: #0a5e91;             /* CTA rest fill on LIGHT bands — P6b fix: was #0d79ba with --paper text (4.3:1, failed AA); --on-blue-fill (white) on this value is 6.95:1 */
  --blue-fill-hover: #073f5c;       /* CTA hover/focus, darkens further — white on it 11.2:1 */
  --on-blue-fill: #ffffff;          /* CTA text on --blue-fill / --blue-fill-hover — pure white, not --paper (see above) */
  --blue-bright: #36c0e5;           /* alias of --cyan kept for the swoosh gradient stop classes below */
  --tint: #eaf9ff;                  /* row hover, the one spec panel (light bands) */
  --rule: #e3e0da;                  /* row dividers ONLY on light — never an input/component edge (1.26:1) */
  --field-border: #8b8681;          /* input borders, OSS card edges — 3.46:1, meets WCAG 1.4.11 */
  --error: #b3261e;                 /* error text/border on light — 6.27:1, ALWAYS paired with a non-colour signal */
  --error-tint: #fbeceb;            /* error background wash on light bands only */
  --error-on-field: #ff9a91;        /* error text/border on DARK bands (Contact) — 7.75:1 on --field, 6.39:1 on --field-raised */
  --error-on-field-tint: rgba(255, 154, 145, .12); /* error background wash on DARK bands only */
  --lang-go: #00add8;                /* Open Source language dot — Go */
  --lang-ts: #3178c6;                /* Open Source language dot — TypeScript */
  --lang-shell: #4caf50;             /* Open Source language dot — Shell */
  --field-mark: #2a5a9e;            /* Graphic A watermark fill — 2.3:1 on --field. Deliberately visible-but-quiet: raised from --field-raised (1.4:1, read as a rendering glitch in review) per art-direction §5 "Brand watermark legibility" */

  /* Type scale — Archivo, one family, two axes. --d0 is hero-only; --d1/
     --d2/--d3/--num carry the rest of the redesign (P6b applies them to
     each section's own opener — see the art-direction build plan §4 step
     5). font-stretch below is expressed as the wdth axis via
     font-variation-settings because font-stretch keywords don't cover
     112-125% in every engine yet. */
  --d0: clamp(2.75rem, 1.4rem + 5.6vw, 5.5rem);
  --d0-line: .98;
  --d0-tracking: -.035em;
  --d0-weight: 800;
  --d0-wdth: 118;

  --d1: clamp(2rem, 1.2rem + 3.2vw, 3.5rem);
  --d1-line: 1.05;
  --d1-tracking: -.03em;
  --d1-weight: 700;
  --d1-wdth: 115;

  --d2: clamp(1.5rem, 1.15rem + 1.5vw, 2.125rem);
  --d2-line: 1.15;
  --d2-tracking: -.02em;
  --d2-weight: 700;
  --d2-wdth: 110;

  --d3: 1.3125rem;
  --d3-line: 1.25;
  --d3-tracking: -.01em;
  --d3-weight: 600;
  --d3-wdth: 100;

  --num: clamp(2rem, 1.4rem + 2.4vw, 3.25rem);
  --num-weight: 700;
  --num-wdth: 120;

  /* Type scale — Archivo (text) */
  --t-lead: 1.25rem;
  --t-lead-line: 1.55;

  --t-body: 1.0625rem;
  --t-body-line: 1.65;

  --t-sm: .9375rem;
  --t-sm-line: 1.5;

  --t-xs: .8125rem;
  --t-xs-line: 1.45;
  --t-xs-tracking: .02em;

  --t-eyebrow: .75rem;
  --t-eyebrow-line: 1.4;
  --t-eyebrow-tracking: .14em;
  --t-eyebrow-weight: 600;

  --t-mono: .875rem;
  --t-mono-line: 1.6;
  --font-mono: ui-monospace, SFMono-Regular, Menlo, monospace;

  /* Spacing scale — 4 8 12 16 24 32 48 64 96 128 */
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-6: 24px;
  --space-8: 32px;
  --space-12: 48px;
  --space-16: 64px;
  --space-24: 96px;
  --space-32: 128px;
  --section-padding-block: clamp(64px, 6vw, 128px);

  /* Grid — 12 columns, max-width 1240px (widened from 1120 — the empty
     right field tell), gap 32px */
  --grid-max: 1240px;
  --grid-gap: 32px;

  /* Motion */
  --dur: 120ms;
  --ease: cubic-bezier(.2, 0, 0, 1);

  /* Fixed-nav offset (ruby R-C1) — .nav's own rendered height, kept as a
     token so `main` and every section anchor can clear it. */
  --nav-h: 76px;
}

@media (max-width: 767px) {
  :root {
    --nav-h: 64px;
  }
}

/* --------------------------------------------------------------------------
   Base — body typography only. This is the ONE element-level rule this
   file applies unconditionally from P1a onward: legacy's own bare `body{}`
   rule is excised in the SAME step that links this file (1a.3), so without
   this rule the body font falls back to the browser default the moment
   Google Fonts is removed. Nothing else here touches legacy markup —
   headings, links, lists and images inside legacy sections keep whatever
   the surviving section-scoped legacy rules (and the browser default)
   give them until each section migrates in P1b/P2/P3.
   -------------------------------------------------------------------------- */
body {
  font-family: 'Archivo', -apple-system, BlinkMacSystemFont, sans-serif;
  font-size: var(--t-body);
  line-height: var(--t-body-line);
  color: var(--ink);
  background: var(--paper);
}

/* ruby R-C1 — the fixed nav (76px / 64px mobile) covers the first ~1 line
   of every page; without this the hero eyebrow sits under it at narrow
   desktop widths and every #anchor scrolls its target flush under the
   nav bar. */
main {
  padding-block-start: var(--nav-h);
}

[id] {
  scroll-margin-top: calc(var(--nav-h) + var(--space-6));
}

/* --------------------------------------------------------------------------
   Grid
   -------------------------------------------------------------------------- */
.grid {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  row-gap: 0;
  column-gap: var(--grid-gap);
  max-width: var(--grid-max);
  margin-inline: auto;
  padding-inline: var(--space-8);
}

/* ruby R-H6: row-gap on a 12-column grid also gapped every stacked block
   inside a single column span (hero's h1/lead/proof/actions), double-
   counting against their own margins and reading as floating fragments.
   Margins are now the single source of vertical rhythm inside a column;
   column-gap still separates side-by-side blocks (About prose vs <dl>). */

/* 11 column-gaps at the desktop --grid-gap (32px) is 352px alone — more
   than the entire content width on a narrow viewport, which collapses
   every 1fr track to 0 and lets content overflow the (zero-width) column
   it's placed in. The gap must shrink before the grid is asked to hold
   12 real tracks at a narrow width. ruby R-H5: the legacy `section{padding:
   6rem 2rem}` rule (still active until each section's own legacy block is
   stripped) ALSO applies 32px of inline padding on top of this — reducing
   this one doesn't zero the double-padding, but it stops it compounding to
   64px at 390 (326px of usable content) while the legacy rule still lives. */
@media (max-width: 767px) {
  .grid {
    column-gap: var(--space-4);
    padding-inline: var(--space-6);
  }
}

/* Grid items default to min-width:auto, which lets long unbroken content
   (mono fact values, nowrap proof text) blow the track past its column
   width instead of wrapping. Every direct grid child resets it. */
.grid > * {
  min-width: 0;
}

/* Column-span utilities named after the plan's own column ranges. Every
   content block starts at column 1 and ends at 9, 10 or 11 — never 12,
   never centred — except Open Source, the single deliberate full-width
   exception (D-A / 1a.2 asymmetry rule). The asymmetric split is a
   >=1024 concept (hero's own responsive rule: "below 1024 the hero spans
   columns 1-12"); every col-1-N utility collapses to full width below
   that breakpoint, same as col-9-12 already did for About's <dl>. */
.col-1-6  { grid-column: 1 / 7; } /* hero sub-paragraph only (1b.3) — completes the col-1-N set */
.col-1-7  { grid-column: 1 / 8; }
.col-1-8  { grid-column: 1 / 9; }
.col-1-9  { grid-column: 1 / 10; }
.col-1-11 { grid-column: 1 / 12; }
.col-1-12 { grid-column: 1 / 13; } /* Open Source only — the one exception */
.col-9-12 { grid-column: 9 / 13;  } /* About's mono <dl> */
.col-7-12 { grid-column: 7 / 13;  } /* Services manifesto, Contact form (P6b split headers) */
.col-1-5  { grid-column: 1 / 6;   } /* Services/Contact split-header left column (P6b) */
.col-1-4  { grid-column: 1 / 5;   } /* Leadership portrait column (P6b) */
.col-6-12 { grid-column: 6 / 13;  } /* Leadership content column, right-set (P6b) */

@media (max-width: 1023px) {
  .col-1-6,
  .col-1-7,
  .col-1-8,
  .col-1-9,
  .col-1-11,
  .col-9-12,
  .col-7-12,
  .col-1-5,
  .col-1-4,
  .col-6-12 {
    grid-column: 1 / 13;
  }
}

/* --------------------------------------------------------------------------
   Section rhythm
   -------------------------------------------------------------------------- */
.section {
  padding-block: var(--section-padding-block);
}

.section-intro {
  font-family: 'Archivo', sans-serif;
  font-size: var(--t-xs);
  line-height: var(--t-xs-line);
  letter-spacing: var(--t-xs-tracking);
  text-transform: uppercase;
  color: var(--ink-muted);
}

/* --------------------------------------------------------------------------
   Type-scale utility classes — every heading/paragraph composes from
   these, never a literal font-size/weight in markup CSS. "Control Plane":
   one family (Archivo) across a width axis, expressed with
   font-variation-settings since 'wdth' > 100 isn't reliably reachable via
   the font-stretch keyword set in every engine yet. --d0 is new (hero H1
   only); --d1/--d2/--d3 keep their names but carry the redesign's smaller,
   tighter-tracked values — sections whose own opener hasn't been rebuilt
   yet (P6b) render correctly, just at the new scale, until each section's
   heading treatment is revisited.
   -------------------------------------------------------------------------- */
.type-d0 {
  font-family: 'Archivo', sans-serif;
  font-size: var(--d0);
  line-height: var(--d0-line);
  letter-spacing: var(--d0-tracking);
  font-weight: var(--d0-weight);
  font-variation-settings: 'wdth' var(--d0-wdth);
}

.type-d1 {
  font-family: 'Archivo', sans-serif;
  font-size: var(--d1);
  line-height: var(--d1-line);
  letter-spacing: var(--d1-tracking);
  font-weight: var(--d1-weight);
  font-variation-settings: 'wdth' var(--d1-wdth);
}

.type-d2 {
  font-family: 'Archivo', sans-serif;
  font-size: var(--d2);
  line-height: var(--d2-line);
  letter-spacing: var(--d2-tracking);
  font-weight: var(--d2-weight);
  font-variation-settings: 'wdth' var(--d2-wdth);
}

.type-d3 {
  font-family: 'Archivo', sans-serif;
  font-size: var(--d3);
  line-height: var(--d3-line);
  letter-spacing: var(--d3-tracking);
  font-weight: var(--d3-weight);
  font-variation-settings: 'wdth' var(--d3-wdth);
}

.type-num {
  font-family: 'Archivo', sans-serif;
  font-size: var(--num);
  font-weight: var(--num-weight);
  font-variation-settings: 'wdth' var(--num-wdth);
  font-variant-numeric: tabular-nums;
  line-height: 1;
}

.type-lead {
  font-family: 'Archivo', sans-serif;
  font-size: var(--t-lead);
  line-height: var(--t-lead-line);
}

.type-sm {
  font-family: 'Archivo', sans-serif;
  font-size: var(--t-sm);
  line-height: var(--t-sm-line);
}

.type-xs {
  font-family: 'Archivo', sans-serif;
  font-size: var(--t-xs);
  line-height: var(--t-xs-line);
  letter-spacing: var(--t-xs-tracking);
  text-transform: uppercase;
}

.type-mono {
  font-family: var(--font-mono);
  font-size: var(--t-mono);
  line-height: var(--t-mono-line);
}

/* --------------------------------------------------------------------------
   Band utilities — full-bleed backgrounds, the 12-col .grid inside each
   still constrains content width (art-direction §3.4). Apply directly on
   the <section> element alongside its existing classes.
   -------------------------------------------------------------------------- */
.band--field {
  background: var(--field);
  color: var(--on-field);
}

.band--field-raised {
  background: var(--field-raised);
  color: var(--on-field);
}

.band--paper {
  background: var(--paper);
  color: var(--ink);
}

.band--surface {
  background: var(--surface);
  color: var(--ink);
}

/* --------------------------------------------------------------------------
   Prose block (About, Current Work)
   -------------------------------------------------------------------------- */
.prose {
  max-width: 60ch;
  color: var(--ink);
  font-size: var(--t-body);
  line-height: var(--t-body-line);
}

.prose p + p {
  margin-top: var(--space-4);
}

/* .prose defaults to --ink (light-band text). On a dark band (Work/Lab is
   --field) that's near-invisible against the same navy — override by
   context rather than adding a --prose--dark variant class everywhere
   .prose is used. */
.band--field .prose,
.band--field-raised .prose {
  color: var(--on-field);
}

/* --------------------------------------------------------------------------
   Skip link — first focusable element on every page (1b.2)
   -------------------------------------------------------------------------- */
.skip-link {
  position: absolute;
  top: -999px;
  left: var(--space-4);
  z-index: 1000;
  padding: var(--space-2) var(--space-4);
  background: var(--field);
  color: var(--on-field);
  font-family: 'Archivo', sans-serif;
  font-size: var(--t-sm);
  text-decoration: none;
  border-radius: 4px;
}

.skip-link:focus {
  top: var(--space-4);
}

/* --------------------------------------------------------------------------
   Eyebrow label — --t-eyebrow (art-direction §3.3). Colour is set by
   context (.eyebrow alone stays ink-muted for light bands already
   redesigned per-section in P6b; .hero/.band--field override it to
   --cyan below).
   -------------------------------------------------------------------------- */
.eyebrow {
  font-family: 'Archivo', sans-serif;
  font-weight: var(--t-eyebrow-weight);
  font-size: var(--t-eyebrow);
  line-height: var(--t-eyebrow-line);
  letter-spacing: var(--t-eyebrow-tracking);
  text-transform: uppercase;
  color: var(--ink-muted);
  display: block;
}

/* P6b: the comment above always intended this override; on a dark band
   --ink-muted is 2.1:1 on --field (fails AA and was visually near-invisible
   in the P6a-era Work/Tech bands) — --cyan is both the documented dark-band
   eyebrow colour and the fix. */
.band--field .eyebrow,
.band--field-raised .eyebrow {
  color: var(--cyan);
}

/* --------------------------------------------------------------------------
   Definition list row (Services, About facts)
   -------------------------------------------------------------------------- */
.dl-row {
  display: flex;
  justify-content: space-between;
  gap: var(--space-4);
  padding-block: var(--space-4);
  border-bottom: 1px solid var(--rule);
  transition: background var(--dur) var(--ease);
}

.dl-row:hover {
  background: var(--tint);
}

@media (max-width: 767px) {
  .dl-row {
    flex-direction: column;
  }
}

/* --------------------------------------------------------------------------
   Mono fact row / mono spec panel — About and the homelab block ONLY
   (imagery discipline, F11). The homelab spec block is the one bordered
   --tint panel treatment on the whole site.
   -------------------------------------------------------------------------- */
/* ruby R-H9: right-aligned mono values ragged-wrapped to 3-4 lines against
   a narrow <dl> column. Label above value, both left-aligned, at every
   width — this is no longer a row-then-stack responsive component, it's
   always stacked. ruby R-M14: --rule (1.26:1) was the only structure here
   and read as almost invisible; --field-border plus a touch more block
   padding gives the row division actual contrast. */
.mono-fact-row {
  font-family: var(--font-mono);
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-1);
  padding-block: var(--space-3);
  border-bottom: 1px solid var(--field-border);
}

.mono-fact-row dt,
.mono-fact-row dd {
  margin: 0;
  min-width: 0;
  overflow-wrap: break-word;
  text-align: left;
}

.mono-fact-row dt {
  font-size: var(--t-xs);
  line-height: var(--t-xs-line);
  letter-spacing: var(--t-xs-tracking);
  text-transform: uppercase;
  color: var(--ink-muted);
}

.mono-fact-row dd {
  font-size: var(--t-mono);
  line-height: var(--t-mono-line);
  color: var(--ink);
}

/* .mono-spec-panel is retired (P6b) — the homelab spec block it styled is
   now Graphic B (the Lab diagram); no markup references this class. */

/* --------------------------------------------------------------------------
   OSS card (Open Source — the one earned grid)
   -------------------------------------------------------------------------- */
/* P6b (Lighthouse a11y fix, WCAG 2.5.3): .project-card is now a
   non-interactive <article> — the aria-label ("<name> on GitHub") on the
   old wrapping <a> didn't contain its own visible text (the card's whole
   contents), which is a label-content-name-mismatch failure. The real
   link is now the project name inside <h3>, stretched to cover the whole
   card via .project-card-link's ::after (position:relative on the card is
   required for that). "View on GitHub" is decorative text (aria-hidden) —
   redundant with the link's own accessible name, not a second link. */
.project-card {
  position: relative;
  border: 1px solid var(--field-border);
  border-radius: 4px;
  padding: var(--space-6);
  transition: border-color var(--dur) var(--ease);
}

.project-card:hover {
  border-color: var(--blue);
}

.project-card:focus-within {
  border-color: var(--blue);
  outline: 2px solid var(--blue);
  outline-offset: 2px;
}

.project-card-link {
  color: inherit;
  text-decoration: underline;
  text-decoration-color: transparent;
  transition: text-decoration-color var(--dur) var(--ease);
}

.project-card-link:hover,
.project-card-link:focus-visible {
  text-decoration-color: currentColor;
}

/* The stretched link — makes the whole card clickable while the
   accessible name stays exactly the visible project name. Focus itself is
   suppressed here because .project-card:focus-within (above) draws the
   ring around the whole card instead of a tight box around the h3 text. */
.project-card-link::after {
  content: '';
  position: absolute;
  inset: 0;
}

.project-card-link:focus-visible {
  outline: none;
}

.project-card .type-mono {
  color: var(--ink-muted);
  flex: none;
}

/* --------------------------------------------------------------------------
   Writing item (dated index list)
   -------------------------------------------------------------------------- */
/* An <a> wrapping the whole row underlines everything inside it by
   default (title, dek, venue) — reset to none and let the title alone
   pick up the underline on hover/focus, the same affordance every other
   link in the system uses. */
.writing-item {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--space-4);
  padding-block: var(--space-4);
  border-bottom: 1px solid var(--rule);
  text-decoration: none;
  color: inherit;
}

.writing-item h3 {
  text-decoration: underline;
  text-decoration-color: transparent;
  transition: text-decoration-color var(--dur) var(--ease);
}

.writing-item:hover h3,
.writing-item:focus-visible h3 {
  text-decoration-color: currentColor;
}

.writing-item .date {
  font-family: var(--font-mono);
  font-size: var(--t-mono);
  color: var(--ink-muted);
  white-space: nowrap;
}

@media (max-width: 1023px) {
  .writing-item {
    grid-template-columns: 1fr;
  }
  .writing-item .date {
    order: -1;
  }
}

/* --------------------------------------------------------------------------
   CTA / text link — states (F11)
   -------------------------------------------------------------------------- */
.btn-cta {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  padding: var(--space-3) var(--space-6);
  background: var(--blue-fill);
  color: var(--on-blue-fill);
  font-family: 'Archivo', sans-serif;
  font-size: var(--t-sm);
  font-weight: 600;
  text-decoration: none;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  transition: background var(--dur) var(--ease);
}

.btn-cta:hover,
.btn-cta:focus-visible {
  background: var(--blue-fill-hover);
}

.text-link {
  color: var(--blue);
  text-decoration: underline;
  text-decoration-color: transparent;
  text-underline-offset: 3px;
  transition: text-decoration-color var(--dur) var(--ease);
}

.text-link:hover,
.text-link:focus-visible {
  text-decoration-color: currentColor;
}

/* Focus ring — every interactive element (F11) */
a:focus-visible,
button:focus-visible,
input:focus-visible,
textarea:focus-visible,
select:focus-visible,
[tabindex]:focus-visible {
  outline: 2px solid var(--blue);
  outline-offset: 2px;
}

/* --------------------------------------------------------------------------
   Form field — rest / focus / error / disabled (2.7)
   -------------------------------------------------------------------------- */
.form-field {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.form-field label {
  font-family: 'Archivo', sans-serif;
  font-size: var(--t-sm);
  color: var(--ink);
}

.form-field input,
.form-field textarea {
  font-family: 'Archivo', sans-serif;
  font-size: var(--t-body);
  color: var(--ink);
  background: var(--paper);
  border: 1px solid var(--field-border);
  border-radius: 4px;
  padding: var(--space-3);
  min-height: 44px;
}

.form-field input:focus-visible,
.form-field textarea:focus-visible {
  outline: 2px solid var(--blue);
  outline-offset: 2px;
}

.form-field.is-error input,
.form-field.is-error textarea {
  border-color: var(--error);
}

/* [hidden] carries display:none in the UA stylesheet at low priority — an
   unconditional `display: flex` here would beat it and show every error
   on first paint regardless of the attribute. Only apply flex once the
   attribute is actually removed (real bug, caught in the p2 captures:
   section-contact-1440 showed all three errors on a pristine page load). */
.form-field .field-error {
  color: var(--error);
  font-size: var(--t-sm);
  align-items: center;
  gap: var(--space-2);
}

.form-field .field-error:not([hidden]) {
  display: flex;
}

.form-field input:disabled,
.form-field textarea:disabled {
  opacity: 0.6;
  cursor: not-allowed;
}

.form-msg {
  font-family: 'Archivo', sans-serif;
  font-size: var(--t-sm);
  padding: var(--space-3);
  border-radius: 4px;
}

/* --------------------------------------------------------------------------
   Calendly facade — rest / pending / failed (2.6)
   -------------------------------------------------------------------------- */
.calendly-facade {
  border: 1px solid var(--field-border);
  background: var(--tint);
  border-radius: 4px;
  padding: var(--space-8);
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-4);
}

.calendly-facade.is-pending .btn-cta {
  opacity: 0.7;
  cursor: progress;
}

.calendly-facade.is-failed .btn-cta {
  display: none;
}

.calendly-facade .fallback-link {
  display: none;
}

.calendly-facade.is-failed .fallback-link {
  display: inline-flex;
}

/* --------------------------------------------------------------------------
   The swoosh — one brand gesture, used exactly twice (hero baseline, top
   edge of the footer). Not a divider style; a gesture.
   -------------------------------------------------------------------------- */
/* ruby R-M13 (design call, accepted): a tapering inline-SVG arc echoing the
   mark's own swoosh, not a flat gradient rule. Each of the two instances
   (hero baseline, footer top edge) carries its own <linearGradient> with a
   unique id, coloured via CSS on the <stop> elements below — SVG
   presentation attributes don't reliably resolve var() across browsers,
   CSS-on-stop does. preserveAspectRatio="none" + width:100% stretches the
   fixed viewBox to the container; aria-hidden, decorative only. */
.swoosh {
  display: block;
  width: 100%;
  height: 8px;
}

/* Dark-band instances (hero baseline, footer top edge): the signal trace
   runs --cyan -> a deeper blue -> transparent. Light-band instances (none
   yet redesigned in P6a) would use --blue per the "--cyan never on a
   light band" rule — not exercised here since both swoosh sites are dark. */
.swoosh-stop-a {
  stop-color: var(--cyan);
}

.swoosh-stop-b {
  stop-color: #1594c9;
}

.swoosh-stop-c {
  stop-color: #1594c9;
  stop-opacity: 0;
}

/* --------------------------------------------------------------------------
   Nav — two variants (F46), both now permanently on a dark surface
   ("Control Plane" §3.5/§3.7): `.nav` (index) is transparent over the
   navy hero and turns solid --field + a hairline-dark bottom border past
   40px scroll (existing JS toggles .is-scrolled — untouched). `.nav--compact`
   (the five blog pages, no scroll JS, D-N) is solid --field always, since
   its masthead band is still light until P6b — a permanent dark bar reads
   as deliberate chrome rather than an accident.
   -------------------------------------------------------------------------- */
.nav,
.nav--compact {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 100;
  padding: var(--space-4) 0;
  background: transparent;
  transition: background var(--dur) var(--ease), box-shadow var(--dur) var(--ease), padding var(--dur) var(--ease);
}

.nav.is-scrolled,
.nav--compact {
  background: var(--field);
  box-shadow: inset 0 -1px 0 var(--hairline-dark);
}

.nav.is-scrolled {
  padding-block: var(--space-3);
}

.nav .nav-content,
.nav--compact .nav-content {
  max-width: var(--grid-max);
  margin-inline: auto;
  padding-inline: var(--space-8);
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--space-4);
}

.nav .nav-links,
.nav--compact .nav-links {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  align-items: center;
  gap: var(--space-4);
}

@media (min-width: 1200px) {
  .nav .nav-links,
  .nav--compact .nav-links {
    gap: var(--space-6);
  }
}

.nav .nav-links a,
.nav--compact .nav-links a {
  font-family: 'Archivo', sans-serif;
  font-size: var(--t-sm);
  font-weight: 500;
  color: var(--on-field);
  text-decoration: none;
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  border-bottom: 2px solid transparent;
  transition: color var(--dur) var(--ease), border-color var(--dur) var(--ease);
}

.nav .nav-links a:hover,
.nav--compact .nav-links a:hover,
.nav .nav-links a:focus-visible,
.nav--compact .nav-links a:focus-visible {
  color: #ffffff;
  border-color: var(--cyan);
}

/* Mobile toggle — index.html only (.nav--compact carries no toggle, D-N).
   Hidden at/above the collapse breakpoint; P1b defect #1 was this rule's
   absence, which left the button visible in the desktop nav at 1440.
   ruby R-M15: 8 links crowd at 1024, so the collapse breakpoint moved
   from 767/768 to 1023/1024 — used consistently here and below. */
.nav .mobile-toggle {
  display: none;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  margin-inline-start: auto;
  padding: 0;
  background: none;
  border: none;
  border-radius: 4px;
  color: var(--on-field);
  cursor: pointer;
  transition: color var(--dur) var(--ease);
}

.nav .mobile-toggle:hover,
.nav .mobile-toggle:focus-visible {
  color: var(--cyan);
}

@media (max-width: 1023px) {
  .nav .mobile-toggle {
    display: inline-flex;
  }
}

/* index.html — collapses behind the toggle */
@media (max-width: 1023px) {
  .nav .nav-links { display: none; }
  .nav .nav-links.is-open {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    background: var(--field);
    padding: var(--space-4) var(--space-8) var(--space-6);
    gap: var(--space-2);
    border-bottom: 1px solid var(--hairline-dark);
    box-shadow: 0 12px 24px rgba(0, 0, 0, .35);
  }
  .nav .nav-links.is-open a {
    width: 100%;
  }
}

/* blog/*.html — three links, never collapses, wraps instead. No scroll JS
   on blog pages (D-N), so .is-scrolled never applies here — solid --field
   is the permanent state (ruby R-M18's hairline, now on dark). */
.nav--compact .nav-links { display: flex; }

@media (max-width: 767px) {
  .nav--compact .nav-content { flex-wrap: wrap; row-gap: 12px; }
  .nav--compact .nav-links   { width: 100%; gap: var(--space-6); }
  .nav--compact .nav-links a { min-height: 44px; display: inline-flex; align-items: center; }
}

/* --------------------------------------------------------------------------
   Footer — --field, the same dark canvas as the hero (art-direction §3.4
   row 10 / §3.7). The swoosh re-states the hero's signal trace along the
   top edge, closing the loop.
   -------------------------------------------------------------------------- */
.site-footer {
  background: var(--field);
  color: var(--on-field);
  padding-block: 0 var(--space-12);
}

/* ruby R-M12: the swoosh was sitting 48px inside the band instead of on
   its top edge — zero the footer's own top padding and push the content
   down from the swoosh's margin instead. */
.site-footer .swoosh {
  margin-bottom: var(--space-12);
}

.site-footer a {
  color: var(--on-field);
}

.site-footer a:hover,
.site-footer a:focus-visible {
  color: var(--cyan);
}

.site-footer .logo-chip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--paper);
  border-radius: 8px;
  padding: var(--space-2);
}

.site-footer .footer-content {
  max-width: var(--grid-max);
  margin-inline: auto;
  padding-inline: var(--space-8);
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--space-6);
}

.site-footer .footer-left {
  display: flex;
  align-items: center;
  gap: var(--space-4);
}

.site-footer .footer-left p {
  font-size: var(--t-sm);
  color: var(--on-field-dim);
}

.site-footer .footer-links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-6);
}

.site-footer .footer-links a {
  font-size: var(--t-sm);
  min-height: 44px;
  display: inline-flex;
  align-items: center;
}

@media (max-width: 767px) {
  .site-footer .footer-content {
    flex-direction: column;
    text-align: center;
    gap: var(--space-6);
  }
}

/* --------------------------------------------------------------------------
   Wordmark — real HTML text, per D-L (never the broken lockup SVGs — A13
   asserts they are never referenced). Single line, all caps, wide
   tracking, AGILE in --cyan via <b>. Both places this renders — the nav
   and the footer — sit on a dark canvas under "Control Plane" (§3.5/§3.7),
   so the wordmark is now permanently the on-dark treatment; no separate
   light-band variant exists.
   -------------------------------------------------------------------------- */
.logo {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  text-decoration: none;
}

.logo-wordmark {
  font-family: 'Archivo', sans-serif;
  font-weight: 500;
  font-size: var(--t-xs);
  line-height: 1.25;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--on-field);
}

.logo-wordmark b {
  font-weight: 600;
  color: var(--cyan);
}

/* --------------------------------------------------------------------------
   Hero — "Control Plane" (art-direction §3.5). Navy field, 88vh (not a
   full-viewport template tell), transparent nav overlaid on load. Graphic
   A (the swoosh field / brand watermark) is absolutely positioned behind
   the text column; the proof rail replaces the old monospace fact rows
   with display-scale numbers.
   -------------------------------------------------------------------------- */
.hero {
  position: relative;
  overflow: hidden;
  min-height: 88vh;
  display: flex;
  align-items: center;
  /* main's padding-block-start: var(--nav-h) pushes ALL content below the
     fixed nav so anchors/other sections never sit under it — correct
     everywhere except here, where the nav is meant to overlay a
     transparent strip of the hero itself. Pull the hero back up by that
     same offset and re-add it as extra top padding so the content
     position is unchanged but the --field canvas now extends under the
     nav instead of showing body's background through the gap. */
  margin-top: calc(-1 * var(--nav-h));
  /* Top padding trimmed by 80px off the clamped value (P6b fold fix);
     bottom padding is untouched. Desktop (>=1024px) trims further below
     — see the min-width:1024px override — to clear the fold on a 13"
     laptop (1440x760 after browser chrome). */
  padding-block: calc(var(--nav-h) + clamp(96px, 10vw, 160px) - 80px) clamp(96px, 10vw, 160px);
}

@media (min-width: 1024px) {
  .hero {
    /* Floored so the eyebrow can never rise above the nav's bottom edge
       (the -136px trim only clears the nav once 10vw passes ~136px, i.e.
       >=1352px). At 1440 both arms are --nav-h + 8px, so the fold tuning
       verified there is unchanged. */
    padding-block-start: max(
      calc(var(--nav-h) + 8px),
      calc(var(--nav-h) + clamp(96px, 10vw, 160px) - 136px)
    );
  }
}

.hero-eyebrow {
  color: var(--cyan);
  margin-bottom: var(--space-4);
  position: relative;
  z-index: 1;
}

.hero h1.type-d0 {
  color: #ffffff;
  max-width: 21ch;
  margin-bottom: var(--space-8);
  position: relative;
  z-index: 1;
}

.hero-lead {
  color: var(--on-field);
  max-width: 52ch;
  margin-bottom: var(--space-12);
  position: relative;
  z-index: 1;
}

/* Graphic A — "Swoosh Field" (§3.6). The brand's own A-counter-form and
   swoosh arc at architectural scale, translated/scaled from
   agile-services-group-mark.svg's own path data (not a freehand shape) so
   the watermark is provably the same mark, just large. Fill/stroke are
   CSS classes, not inline var() presentation attributes — SVG
   presentation attributes don't reliably resolve var() across browsers
   (see the .swoosh comment above). */
.hero-graphic {
  position: absolute;
  top: 50%;
  /* -140px is ~20% of this element's own 720px width (art-direction §3.6:
     "~20% bleeds off the right edge"). A percentage `right` here would
     resolve against the .hero container's width, not the graphic's own —
     a much larger, viewport-dependent bleed — so this is a fixed value. */
  right: -140px;
  transform: translateY(-50%);
  height: 620px;
  width: 720px;
  z-index: 0;
  pointer-events: none;
}

/* Watermark — raised to --field-mark (2.3:1) and given a hairline edge so
   the crop at the hero's own right boundary reads as a deliberate bleed
   rather than a stray, near-invisible shape (mozart's P6a review: "reads
   as a rendering glitch"). Still felt, not read — aria-hidden, decorative. */
.hero-graphic-a {
  fill: var(--field-mark);
  stroke: var(--hairline-dark);
  stroke-width: 1.5px;
}

.hero-graphic-swoosh {
  opacity: .55;
}

.hero-graphic-swoosh--thin {
  opacity: .8;
}

@media (max-width: 1279px) {
  .hero-graphic {
    height: 480px;
    width: 560px;
    opacity: .35;
  }
}

@media (max-width: 639px) {
  .hero-graphic {
    display: none;
  }
}

.hero-proof {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
  margin-bottom: var(--space-8);
}

.hero-proof dt {
  color: var(--cyan);
  margin: 0;
}

.hero-proof dd {
  margin: var(--space-1) 0 0;
  font-family: 'Archivo', sans-serif;
  font-size: var(--t-sm);
  color: var(--on-field-dim);
}

@media (min-width: 768px) {
  .hero-proof {
    flex-direction: row;
    flex-wrap: wrap;
    align-items: baseline;
  }
  .hero-proof > * + * {
    border-inline-start: 1px solid var(--hairline-dark);
    padding-inline-start: var(--space-6);
  }
}

.hero-actions {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  gap: var(--space-6);
  flex-wrap: wrap;
}

.btn-cta--field {
  background: var(--cyan);
  color: var(--field);
  border-radius: 6px;
  min-height: 52px;
}

.btn-cta--field:hover,
.btn-cta--field:focus-visible {
  background: #5ccdec;
}

.btn-ghost {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  padding: var(--space-3) var(--space-6);
  background: transparent;
  color: #ffffff;
  font-family: 'Archivo', sans-serif;
  font-size: var(--t-sm);
  font-weight: 600;
  text-decoration: none;
  border: 1.5px solid var(--edge-dark);
  border-radius: 6px;
  cursor: pointer;
  transition: border-color var(--dur) var(--ease), color var(--dur) var(--ease);
}

.btn-ghost:hover,
.btn-ghost:focus-visible {
  border-color: var(--cyan);
  color: var(--cyan);
}

@media (max-width: 767px) {
  .hero-actions {
    flex-direction: column;
    align-items: stretch;
  }
  .hero-actions .btn-cta,
  .hero-actions .btn-ghost {
    width: 100%;
  }
}

/* --------------------------------------------------------------------------
   About (§3.4 row 1) — numeral-first opener, no eyebrow. Asymmetric prose
   (1-7) + mono <dl> (9-12) unchanged in structure; --paper is now the cool
   near-white, not cream.
   -------------------------------------------------------------------------- */
.section-numeral {
  display: block;
  font-family: 'Archivo', sans-serif;
  font-size: clamp(2.5rem, 1.8rem + 3vw, 4.5rem);
  font-weight: 800;
  font-variation-settings: 'wdth' 118;
  line-height: 1;
  color: var(--field-border);
  opacity: .35;
}

@supports (-webkit-text-stroke: 1px black) {
  .section-numeral {
    color: transparent;
    opacity: 1;
    -webkit-text-stroke: 1.5px var(--field-border);
  }
}

/* "cyan -> transparent rule under the numeral — no cyan, use --blue" on
   light bands (art-direction §3.4 row 1). */
.section-numeral::after {
  content: '';
  display: block;
  width: 64px;
  height: 2px;
  margin-block: var(--space-3) var(--space-6);
  background: linear-gradient(90deg, var(--blue), transparent);
}

.about-prose > * + * {
  margin-top: var(--space-4);
}

/* ruby R-L21: "You work with me. There is no one else to hand it to." was
   the strongest line on the page and was buried in a mono <dl> slot.
   Promoted to a prose pull-line; dropped from the <dl> (R-H8 below). */
.about-pull {
  margin-top: var(--space-6);
  color: var(--ink);
}

.about-facts {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

@media (max-width: 1023px) {
  .about-facts {
    margin-top: var(--space-8);
  }
}

/* ruby R-H8: pin the fact list alongside the prose column on tall
   viewports instead of leaving ~1400px of empty space beside a short
   6-row list. align-self:start is required — a sticky grid item still
   stretches to the row's height by default, which defeats sticky. */
@media (min-width: 1024px) {
  .about-facts {
    position: sticky;
    top: calc(var(--nav-h) + var(--space-8));
    align-self: start;
  }
}

/* --------------------------------------------------------------------------
   Leadership (P6b) — image-first opener, the only right-set heading on the
   page (art-direction §3.4 row 5): portrait col-1-4, heading + prose +
   facts together in col-6-12. Portrait slot stays empty by default (5b).
   -------------------------------------------------------------------------- */
.leadership-portrait:empty {
  display: none;
}

/* Content defaults to col-1-8 (markup) so an empty portrait leaves no
   blank gap. Once the portrait is populated, shift content right to
   col-6-12 to sit beside it as designed. Scoped to the same >=1024px
   breakpoint as the col-1-N utilities above — without this guard the
   higher specificity here would beat their max-width:1023px collapse
   to grid-column:1/13 and break the stacked mobile layout. */
@media (min-width: 1024px) {
  .leadership-portrait:not(:empty) + .leadership-content {
    grid-column: 6 / 13;
  }
}

.leadership-portrait img {
  width: 100%;
  max-width: 280px;
  aspect-ratio: 4 / 5;
  object-fit: cover;
  display: block;
  border-radius: 4px;
  border: 1px solid var(--field-border);
}

.leadership-content .type-d3 {
  margin-top: var(--space-2);
}

.leadership-facts {
  display: flex;
  flex-direction: column;
  margin-top: var(--space-8);
}

@media (max-width: 1023px) {
  .leadership-portrait {
    margin-block-end: var(--space-24);
  }
  .leadership-portrait img {
    max-width: 200px;
  }
}

/* --------------------------------------------------------------------------
   Accessible-hidden utility (Current Work's spec-panel <dl> uses one
   visually-hidden <dt> plus multiple <dd> — valid HTML, keeps the term
   out of view without removing it from the accessibility tree).
   -------------------------------------------------------------------------- */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* --------------------------------------------------------------------------
   Section header — eyebrow + heading + lead intro, left-aligned at
   columns 1-7 (1a.2). Services through Contact all use this; About wrote
   its own header inline (1b.4) because it shares a column group with the
   prose block instead of sitting above a grid of its own.
   -------------------------------------------------------------------------- */
.section-header {
  margin-bottom: var(--space-12);
}

.section-header .type-d2 {
  margin-top: var(--space-2);
  margin-bottom: var(--space-4);
}

.section-header .type-lead {
  color: var(--ink-muted);
  max-width: 60ch;
}

/* Dark-band override — --ink-muted is 2.1:1 on --field (fails AA). Every
   .section-header used inside a dark band needs this; scoped generally
   rather than per-section so Work/Lab isn't missed the way it was in the
   first P6b pass. */
.band--field .section-header .type-lead,
.band--field-raised .section-header .type-lead {
  color: var(--on-field-dim);
}

/* --------------------------------------------------------------------------
   Shared strong-label style — dl terms (Services), card titles (Open
   Source), column subheads (Tech). Archivo 600, not Fraunces — Fraunces
   is reserved for h1/h2/h3-scale display type (type-d1/d2/d3).
   -------------------------------------------------------------------------- */
.term {
  font-family: 'Archivo', sans-serif;
  font-weight: 600;
  font-size: var(--t-body);
  color: var(--ink);
}

/* --------------------------------------------------------------------------
   Services (2.1) — "Control Plane" §3.4 row 2. Two-part header (H2 cols
   1-5, one-line manifesto cols 7-12) is the opener that differs from
   About's numeral and Work's visual-first below it. 8 cards replace the
   dense definition list — --surface white, 1px --field-border, 3px top
   rule in --blue, --d2 card heading. No shadow (art-direction §3.7: soft
   glows are an AI tell; the top rule does the work).
   -------------------------------------------------------------------------- */
.services-header {
  margin-bottom: var(--space-12);
  align-items: baseline;
}

.services-manifesto {
  color: var(--ink-muted);
}

@media (max-width: 1023px) {
  .services-manifesto {
    margin-top: var(--space-4);
  }
}

.service-cards {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-6);
}

@media (max-width: 1023px) {
  .service-cards {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (max-width: 639px) {
  .service-cards {
    grid-template-columns: 1fr;
  }
}

.service-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  background: var(--surface);
  border: 1px solid var(--field-border);
  border-top: 3px solid var(--blue);
  border-radius: 8px;
  padding: var(--space-6);
  transition: border-color var(--dur) var(--ease), transform var(--dur) var(--ease);
}

.service-card:hover {
  border-color: var(--blue);
  transform: translateY(-2px);
}

.service-card-title {
  margin: 0;
}

.service-card p {
  margin: 0;
  color: var(--ink-muted);
}

/* --------------------------------------------------------------------------
   Graphic C — "The Pipeline" (art-direction §3.6, rebuilt P6b per mozart's
   review C.1 — the first pass read as a thin hairline with near-illegible
   6-7px mono labels and a "Gate" tag colliding with its own marker). The
   firm's own open-sourced multi-agent delivery pipeline (Mozart
   Orchestration), drawn as a real sequence: a semantic <ol>, an SVG rail
   layer, numbered solid markers, and three NAMED gates. Its own intro row
   (eyebrow + lead + repo link) gives it the presence of a signature
   graphic rather than a footer flourish. Full-bleed strip closing out
   Services, --surface white — light band, so --blue/--ink only, no cyan.
   -------------------------------------------------------------------------- */
.pipeline-strip {
  padding-block: var(--space-24) var(--space-8);
}

.pipeline-intro {
  margin-bottom: var(--space-16);
}

.pipeline-intro .type-lead {
  color: var(--ink-muted);
  margin-top: var(--space-2);
}

.pipeline-wrap {
  position: relative;
}

.pipeline-rail {
  display: block;
  width: 100%;
  height: 2px;
  position: absolute;
  /* Centreline of the 40px marker: stages reserve --space-16 (64px) above
     the marker for the gate labels, so the marker centre is 64 + 20 = 84px. */
  top: calc(var(--space-16) + 20px);
  left: 0;
  z-index: 0;
}

.pipeline-rail-line {
  stroke: var(--rule);
}

.pipeline {
  position: relative;
  z-index: 1;
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: var(--space-4);
}

/* Every stage reserves the same top zone above its marker so all six
   markers land at the same y and the rail passes through every centre —
   gates no longer live inside this zone (they're separate, absolutely
   positioned list items; see .pipeline-gate below), but the reservation
   still keeps the marker row's vertical rhythm consistent. */
.pipeline-stage {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-2);
  padding-top: var(--space-16);
}

/* Numbered solid marker — replaces the old 18px hollow dot, which read as
   a hairline artefact rather than a step in a sequence. */
.pipeline-marker {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background: var(--ink);
  color: var(--surface);
  font-family: 'Archivo', sans-serif;
  font-size: 1.0625rem;
  font-weight: 700;
  flex-shrink: 0;
}

.pipeline-label {
  font-family: 'Archivo', sans-serif;
  font-size: 1.0625rem;
  font-weight: 600;
  color: var(--ink);
}

.pipeline-sub {
  font-family: 'Archivo', sans-serif;
  font-size: .8125rem;
  color: var(--ink-muted);
  max-width: 22ch;
}

/* Gate — a checkpoint BETWEEN two stages, not a label on either one. Each
   .pipeline-gate is its own aria-hidden <li> (kept out of the accessible
   list count; the gate names are already stated once in the visible lead
   sentence above), taken out of grid flow so it never competes for one of
   the six stage columns. Positioned at the exact midpoint between the two
   marker centres it separates: markers are laid out by
   `repeat(6, 1fr) + gap: var(--space-4)`, so for the gap after column index
   i (0-based) that midpoint is `(i + 0.5) * ((100% + gap) / 6) + half the
   40px marker` — i.e. `(i + 0.5) / 6 * (100% + gap) + 20px`. Review sits
   after column 1 (Plan→Review), Validation after column 3
   (Implement→Validate), Ship after column 4 (Validate→Ship). */
.pipeline-gate {
  position: absolute;
  top: 0;
  transform: translateX(-50%);
}

.pipeline-gate--review {
  left: calc((100% + var(--space-4)) * 0.25 + 20px);
}

.pipeline-gate--validation {
  left: calc((100% + var(--space-4)) * 0.583333 + 20px);
}

.pipeline-gate--ship {
  left: calc((100% + var(--space-4)) * 0.75 + 20px);
}

.pipeline-gate-bar {
  position: absolute;
  /* Centred on the rail: rail y = --space-16 + 20px; bar is 28px tall. */
  top: calc(var(--space-16) + 20px - 14px);
  left: 50%;
  transform: translateX(-50%);
  width: 12px;
  height: 28px;
  border-radius: 3px;
  background: var(--blue);
}

.pipeline-gate-label {
  position: absolute;
  /* Sits just above the bar, in the zone the stages reserve. */
  top: calc(var(--space-16) + 20px - 14px - var(--space-6));
  left: 50%;
  transform: translateX(-50%);
  font-family: 'Archivo', sans-serif;
  font-size: .75rem;
  font-weight: 700;
  letter-spacing: .02em;
  color: var(--blue);
  white-space: nowrap;
}

@media (max-width: 859px) {
  .pipeline-rail {
    display: none;
  }
  .pipeline {
    display: flex;
    flex-direction: column;
    gap: var(--space-8);
    border-left: 2px solid var(--rule);
    margin-left: 20px;
    padding-left: var(--space-6);
  }
  .pipeline-stage {
    padding-top: 0;
  }
  .pipeline-marker {
    margin-left: -40px;
  }
  /* The rail is the ol's own border-left; the bar (12px, first flex child)
     shifts left via `position: relative` — not a margin on the row — by
     enough to centre it on that rail (measured empirically at this layout:
     25px), while the label (the second flex child) keeps its normal
     reading position instead of shifting with it. */
  .pipeline-gate {
    position: static;
    transform: none;
    display: flex;
    align-items: center;
    gap: var(--space-3);
    margin-bottom: var(--space-3);
  }
  .pipeline-gate-bar {
    position: relative;
    left: -25px;
    width: 12px;
    height: 28px;
  }
  .pipeline-gate-label {
    position: static;
  }
}

/* --------------------------------------------------------------------------
   Current Work (2.2) — Work's own visual-first opener: the eyebrow/H2 is
   deliberately small (--d2, not --d1) so Graphic B (the Lab diagram) is
   the subject, not the heading (art-direction §3.4 row 3).
   -------------------------------------------------------------------------- */
.work-entry .eyebrow {
  margin-bottom: var(--space-2);
}

.work-entry .type-d3 {
  margin-bottom: var(--space-4);
}

/* --------------------------------------------------------------------------
   Graphic B — "The Lab" (art-direction §3.6). HTML + CSS grid node cards
   with one absolutely-positioned inline-SVG connector layer — not a
   monolithic SVG, so node labels stay legible at 390px (a 1120-wide
   viewBox scales text to ~4.5px; HTML text reflows instead).
   -------------------------------------------------------------------------- */
.lab-diagram {
  margin: var(--space-16) 0 0;
  padding: 0;
}

.lab-rail {
  display: block;
  width: 100%;
  height: 40px;
}

.lab-rail-label {
  font-family: var(--font-mono);
  font-size: 12px;
  fill: var(--on-field-dim);
}

.lab-rail-line {
  stroke: var(--hairline-dark);
}

.lab-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--space-6);
  margin-top: var(--space-2);
}

.lab-node {
  background: var(--field-raised);
  border: 1.5px solid var(--hairline-dark);
  border-radius: 10px;
  padding: var(--space-4);
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.lab-node-id {
  margin: 0;
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--on-field-dim);
}

.lab-node-spec {
  margin: 0;
  font-family: 'Archivo', sans-serif;
  font-size: 15px;
  font-weight: 600;
  color: #f2f5f9;
}

.lab-stack {
  grid-column: 1 / 5;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-6);
  margin-top: var(--space-2);
}

.lab-stack p {
  margin: 0;
  padding-left: var(--space-3);
  border-left: 2px solid var(--cyan);
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--on-field-dim);
}

@media (max-width: 899px) {
  .lab-rail {
    display: none;
  }
  .lab-grid {
    grid-template-columns: repeat(2, 1fr);
  }
  .lab-node {
    border-left: 2px solid var(--cyan);
  }
  .lab-stack {
    grid-column: 1 / 3;
  }
}

@media (max-width: 559px) {
  .lab-grid {
    grid-template-columns: 1fr;
  }
  .lab-stack {
    grid-column: 1;
    flex-direction: column;
  }
}

/* --------------------------------------------------------------------------
   Open Source sub-band (2.3) — paper, immediately below the Lab's navy
   field within the same #work section (the id stays on the outer
   <section>; open-source stays on the inline <h3>, per D-E). Heading +
   live count strip is the opener.
   -------------------------------------------------------------------------- */
.work-oss {
  padding-top: var(--space-16);
}

.oss-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--space-4);
  margin-bottom: var(--space-8);
}

.oss-header h3 {
  margin: 0;
}

.oss-count {
  color: var(--ink-muted);
}

.lang-dot {
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  margin-right: var(--space-2);
  vertical-align: middle;
  background: var(--blue);
}

.lang-dot--go { background: var(--lang-go); }
.lang-dot--ts { background: var(--lang-ts); }
.lang-dot--shell { background: var(--lang-shell); }

.project-card--wide {
  grid-column: span 2;
}

@media (max-width: 767px) {
  .project-card--wide {
    grid-column: span 1;
  }
}

.project-card-terminal {
  /* Sits above the stretched link (z-index:1) so its text stays selectable
     and it stays independently scrollable — clicking/dragging inside it
     must not trigger the card-wide GitHub navigation. */
  position: relative;
  z-index: 1;
  margin: 0;
  background: var(--field);
  color: var(--on-field);
  border-radius: 4px;
  padding: var(--space-4);
  font-family: var(--font-mono);
  font-size: 12px;
  line-height: 1.6;
  overflow-x: auto;
}

.project-card-terminal .t-dim { color: var(--on-field-dim); }
.project-card-terminal .t-cyan { color: var(--cyan); }

/* --------------------------------------------------------------------------
   Open Source (2.3) — the one earned grid, full-width. 3 cols >=1024,
   2 at 768-1023, 1 below. Cards reuse .project-card (border/hover
   already defined above); this adds the internal layout and drops the
   icon square (imagery discipline).
   -------------------------------------------------------------------------- */
.oss-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-6);
}

/* Without this, the wide card's unwrapped <pre> terminal excerpt sets the
   min-content width of its 1fr track (grid items default to min-width:auto,
   not 0) — every card in that column, and the page itself, overflowed
   horizontally at mobile widths as a result (measured: 501px card in a
   390px viewport). `.grid > *` already does this for the outer 12-col
   grid; `.oss-grid` is a separate nested grid and needs its own reset. */
.oss-grid > * {
  min-width: 0;
}

@media (max-width: 1023px) {
  .oss-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

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

.project-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.project-card p {
  color: var(--ink-muted);
  margin: 0;
  flex: 1;
}

/* Decorative echo of the link above — aria-hidden in markup, so this is
   presentation only, not a second (redundant) link. */
.project-card-cta {
  align-self: flex-start;
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  color: var(--blue);
}

/* --------------------------------------------------------------------------
   Writing (2.4, P6b) — feature-row-first opener (art-direction §3.4 row 6):
   the H2 is set small (--d3) above the most recent piece, which runs as a
   full-width feature row with a rule above it; the remaining five sit in a
   2-col list below (1-col under 1024). Reuses .writing-item (date column +
   content) for the list rows.
   -------------------------------------------------------------------------- */
.writing-header .type-d3 {
  margin-top: var(--space-2);
  margin-bottom: var(--space-4);
}

.writing-feature {
  display: block;
  padding-top: var(--space-8);
  margin-bottom: var(--space-12);
  border-top: 2px solid var(--ink);
  text-decoration: none;
  color: inherit;
}

.writing-feature .date {
  font-family: var(--font-mono);
  font-size: var(--t-xs);
  color: var(--ink-muted);
}

.writing-feature h2,
.writing-feature h3 {
  margin: var(--space-2) 0 var(--space-3);
  text-decoration: underline;
  text-decoration-color: transparent;
  transition: text-decoration-color var(--dur) var(--ease);
}

.writing-feature:hover h2,
.writing-feature:focus-visible h2,
.writing-feature:hover h3,
.writing-feature:focus-visible h3 {
  text-decoration-color: currentColor;
}

.writing-feature .dek {
  color: var(--ink-muted);
  max-width: 62ch;
  margin: 0 0 var(--space-2);
}

.writing-list {
  display: grid;
  grid-template-columns: 1fr;
  gap: 0 var(--space-16);
}

@media (min-width: 1024px) {
  .writing-list {
    grid-template-columns: 1fr 1fr;
  }
}

.writing-item h3 {
  margin: 0 0 var(--space-1);
}

.writing-item .dek {
  color: var(--ink-muted);
  margin: 0 0 var(--space-1);
  max-width: 62ch;
}

.writing-item .venue {
  font-family: var(--font-mono);
  font-size: var(--t-xs);
  color: var(--ink-muted);
}

.writing-item .venue-external {
  color: var(--blue);
  text-transform: none;
  letter-spacing: normal;
}

/* D15: was a plain .text-link ("View all on Medium") — the user wants the
   full archive link (more pieces on Medium than the six selected above)
   clearly visible, not a footnote. Bigger, semibold, permanent underline. */
.writing-view-all {
  margin-top: var(--space-8);
  padding-top: var(--space-6);
  border-top: 1px solid var(--rule);
}

.writing-view-all-link {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-family: 'Archivo', sans-serif;
  font-size: var(--t-lead);
  font-weight: 600;
  color: var(--blue);
  text-decoration: underline;
  text-decoration-color: currentColor;
  text-underline-offset: 3px;
}

.writing-view-all-link:hover,
.writing-view-all-link:focus-visible {
  color: var(--blue-fill-hover);
}

/* --------------------------------------------------------------------------
   Tech (2.5, P6b) — dense mono matrix on --field navy (art-direction §3.4
   row 7): the density itself is the graphic, ~60 tags reading as an
   instrument readout. Group labels in --cyan, values in --on-field-dim.
   3 cols >=1024, 2 at 768-1023, 1 below. No pills, no cards, no icons.
   -------------------------------------------------------------------------- */
.tech-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-8);
}

@media (max-width: 1023px) {
  .tech-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

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

.tech-column .term {
  display: block;
  margin-bottom: var(--space-3);
  color: var(--cyan);
}

.tech-column ul {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.tech-column li {
  font-family: var(--font-mono);
  font-size: var(--t-mono);
  color: var(--on-field-dim);
}

/* --------------------------------------------------------------------------
   Schedule (2.6, P6b) — the only centred band on the page. .grid still
   provides the column tracks; this just centres its own content within
   them and caps the measure at 640px (art-direction §3.4 row 8).
   -------------------------------------------------------------------------- */
.schedule-grid {
  justify-items: center;
  text-align: center;
}

.schedule-header,
.schedule-facade-col {
  max-width: 640px;
}

.schedule-header .type-lead {
  margin-inline: auto;
}

.schedule-facade-col .calendly-facade {
  align-items: center;
  text-align: center;
}

/* --------------------------------------------------------------------------
   Schedule facade (2.6) — wires the rest/pending/failed/loaded states onto
   .calendly-facade (states already defined above). Reserves height close
   to the loaded widget's own so nothing jumps after the click.
   -------------------------------------------------------------------------- */
.calendly-facade p {
  margin: 0;
}

.calendly-facade .type-sm {
  color: var(--ink-muted);
}

.calendly-facade.is-loaded .btn-cta {
  display: none;
}

#calendlyStatus {
  min-height: 1.3em;
  color: var(--ink-muted);
}

#calendlyContainer {
  display: none;
  width: 100%;
  min-height: 560px;
}

.calendly-facade.is-loaded #calendlyContainer {
  display: block;
}

/* --------------------------------------------------------------------------
   Contact (2.7) — restyled form; logic frozen, ARIA carved out. Field
   states (rest/focus/error/disabled) reuse .form-field, defined above.
   -------------------------------------------------------------------------- */
.contact-grid {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  max-width: 640px;
}

#contactForm {
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
}

#contactForm #message-hint {
  margin: 0;
  color: var(--ink-muted);
}

.form-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-4);
}

@media (max-width: 480px) {
  .form-row {
    grid-template-columns: 1fr;
  }
}

.hp-field {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.captcha-row {
  display: flex;
  align-items: flex-end;
  gap: var(--space-4);
  flex-wrap: wrap;
}

.captcha-row .form-field {
  flex: 1;
  min-width: 160px;
}

.captcha-question {
  font-family: var(--font-mono);
  font-size: var(--t-sm);
  color: var(--ink);
  background: var(--tint);
  border: 1px solid var(--field-border);
  border-radius: 4px;
  padding: 0 var(--space-4);
  min-height: 44px;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  white-space: nowrap;
}

.form-status {
  display: none;
  font-family: 'Archivo', sans-serif;
  font-size: var(--t-sm);
  padding: var(--space-3);
  border-radius: 4px;
}

.form-status.success {
  display: block;
  background: var(--tint);
  color: var(--ink);
  border: 1px solid var(--field-border);
}

.form-status.error {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  background: #fbeceb;
  color: var(--error);
  border: 1px solid var(--error);
}

.form-alt {
  color: var(--ink-muted);
  font-size: var(--t-sm);
}

.form-alt a {
  font-weight: 600;
}

/* --------------------------------------------------------------------------
   Contact — dark-band overrides (P6b, art-direction §3.7 "Forms (Contact,
   on dark)"). .cta now sits on --field-raised; every light-band default
   above (label/input/placeholder/border/status colours) is wrong on dark
   and is corrected here, scoped to .cta so no other .form-field consumer
   (there are none elsewhere on the page today) is affected.
   -------------------------------------------------------------------------- */
.cta .section-header .type-lead,
.cta .section-header .type-lead strong {
  color: var(--on-field);
}

.cta .form-field label {
  color: var(--on-field);
}

.cta #contactForm #message-hint {
  color: var(--on-field-dim);
}

.cta .form-field input,
.cta .form-field textarea {
  background: var(--field);
  color: var(--on-field);
  /* --edge-dark is only 2.9:1 against --field-raised (this section's own
     background) — fails 1.4.11's 3:1 non-text minimum. --edge-dark-raised
     clears both adjacent surfaces (4.1:1 on --field, 3.38:1 on
     --field-raised). Found during the P6b a11y fix pass, not previously
     flagged by Lighthouse's default ruleset but a real gap. */
  border-color: var(--edge-dark-raised);
}

.cta .form-field input::placeholder,
.cta .form-field textarea::placeholder {
  color: var(--on-field-dim);
}

.cta .form-field input:focus-visible,
.cta .form-field textarea:focus-visible {
  outline: 3px solid var(--cyan);
  outline-offset: 2px;
}

.cta .form-field.is-error input,
.cta .form-field.is-error textarea {
  border-color: var(--error-on-field);
}

.cta .form-field .field-error {
  color: var(--error-on-field);
}

.cta .captcha-question {
  color: var(--on-field);
  background: var(--field);
  border-color: var(--edge-dark-raised);
}

.cta .form-status.success {
  background: var(--field);
  color: var(--on-field);
  border-color: var(--edge-dark);
}

.cta .form-status.error {
  background: var(--error-on-field-tint);
  color: var(--error-on-field);
  border-color: var(--error-on-field);
}

.cta .form-alt {
  color: var(--on-field-dim);
}

.cta .form-alt .text-link {
  color: var(--cyan);
}

/* --------------------------------------------------------------------------
   Motion policy — unconditional, zeroes every animation/transition and
   scroll-behavior for users who ask for it (1a.2).
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ==========================================================================
   Article / blog pages (P3) — owned region: .article*, .author*, .prose,
   .nav--compact. Replaces the five per-article gradient heroes
   (amber/indigo/green/purple) and the CSS-initials avatar with the shared
   system. Everything between <article class="article-body"> and
   </article> is frozen prose markup (D-R) — styled below by descendant
   selector only, never by adding a class inside that element.
   ========================================================================== */

/* box-sizing is now set once, globally, in the Base reset at the top of
   this file (P6b) — the stopgap that used to live here is removed. */

/* --------------------------------------------------------------------------
   Nav-height override for the compact (blog) nav — ruby R-C1. P2 defines
   --nav-h globally plus the consuming `main { padding-block-start }` and
   `[id] { scroll-margin-top }` rules; this file only overrides the custom
   property's value on blog pages, where .nav--compact wraps to two rows
   below 768px (three links + logo) instead of collapsing behind a toggle.
   class="page-blog" is on all five <body> tags.
   -------------------------------------------------------------------------- */
.page-blog,
.page-secondary {
  --nav-h: 76px;
  /* Article body sits on --surface white, not --paper — the masthead
     (navy) and footer (navy) bracket it the same way the hero and footer
     bracket the home page (art-direction §3.9's dark -> light -> dark
     rhythm). */
  background: var(--surface);
}

@media (max-width: 767px) {
  .page-blog,
  .page-secondary {
    --nav-h: 112px;
  }
}

/* --------------------------------------------------------------------------
   Article header — the navy masthead band (art-direction §3.9). Mono meta
   line (date · category · reading time), title in Archivo (Fraunces is
   deleted system-wide), dek in the lead scale. Left-aligned, columns 1-8,
   never centred (asymmetry rule, F11). Gives every article the same
   dark -> light -> dark rhythm as the home page. The .nav--compact is
   already permanently solid --field (not an overlay like the hero), so no
   negative-margin nav offset trick is needed here.
   -------------------------------------------------------------------------- */
.article-header {
  background: var(--field);
  color: var(--on-field);
  padding-block: var(--space-16) var(--space-16);
}

.article-header h1,
.article-header p {
  margin: 0;
}

.article-meta {
  font-family: var(--font-mono);
  font-size: var(--t-mono);
  line-height: var(--t-mono-line);
  color: var(--on-field-dim);
  margin-bottom: var(--space-4);
}

.article-header h1 {
  font-family: 'Archivo', sans-serif;
  font-size: var(--d1);
  line-height: var(--d1-line);
  letter-spacing: var(--d1-tracking);
  font-weight: var(--d1-weight);
  font-variation-settings: 'wdth' var(--d1-wdth);
  color: #ffffff;
  margin-bottom: var(--space-6);
}

.article-subtitle {
  font-family: 'Archivo', sans-serif;
  font-size: var(--t-lead);
  line-height: var(--t-lead-line);
  color: var(--on-field);
}

/* --------------------------------------------------------------------------
   Article body — long-form prose. Measure capped independent of the grid
   column (68ch) so a wide viewport never over-lengthens the line; the
   column (col-1-8, applied in markup) still governs left alignment and
   the <1024 collapse. Every rule below targets the frozen content by
   descendant selector only — nothing inside <article class="article-body">
   ever gets a class added to it.
   -------------------------------------------------------------------------- */
.article-body {
  max-width: 68ch;
  padding-block: 0 var(--space-16);
  font-size: var(--t-body);
  line-height: var(--t-body-line);
  color: var(--ink);
}

.article-body p,
.article-body ul,
.article-body ol,
.article-body blockquote,
.article-body pre,
.article-body h2 {
  margin: 0;
}

.article-body p + p,
.article-body ul + p,
.article-body ol + p,
.article-body p + ul,
.article-body p + ol,
.article-body blockquote + p,
.article-body p + blockquote {
  margin-top: var(--space-4);
}

.article-body h2 {
  font-family: 'Archivo', sans-serif;
  font-size: var(--d3);
  line-height: var(--d3-line);
  letter-spacing: var(--d3-tracking);
  font-weight: var(--d3-weight);
  font-variation-settings: 'wdth' var(--d3-wdth);
  color: var(--ink);
  margin: var(--space-12) 0 var(--space-4);
}

.article-body strong {
  color: var(--ink);
}

.article-body ul,
.article-body ol {
  margin-left: var(--space-6);
}

.article-body li + li {
  margin-top: var(--space-2);
}

.article-body hr {
  border: none;
  border-top: 1px solid var(--rule);
  margin: var(--space-12) 0;
}

.article-body a {
  color: var(--blue);
  text-decoration: underline;
  text-decoration-color: transparent;
  text-underline-offset: 3px;
  transition: text-decoration-color var(--dur) var(--ease);
}

.article-body a:hover,
.article-body a:focus-visible {
  text-decoration-color: currentColor;
}

.article-body blockquote {
  border-left: 3px solid var(--blue);
  padding: var(--space-4) var(--space-6);
  background: var(--tint);
  border-radius: 0 4px 4px 0;
}

.article-body blockquote p {
  font-style: normal;
}

.article-body blockquote p + p {
  margin-top: var(--space-2);
}

.article-body code {
  font-family: var(--font-mono);
  font-size: .9em;
  background: var(--tint);
  border: 1px solid var(--field-border);
  border-radius: 4px;
  padding: .1em .4em;
}

.article-body pre {
  background: var(--field);
  color: var(--on-field);
  padding: var(--space-6);
  border-radius: 4px;
  overflow-x: auto;
}

.article-body pre code {
  background: none;
  border: none;
  padding: 0;
  color: inherit;
  font-size: var(--t-mono);
}

/* --------------------------------------------------------------------------
   Author card — mark.svg on --paper, never navy (D-L: the mark's gradient
   terminates at --navy, so on a navy band it's 1.0:1; --paper is safe with
   no chip needed). Heading promoted h4 -> h2 in all five files, closing
   the h1 -> h4 skip on mob-programming-ai and shipping-code-we-dont-
   understand (F12, heading-order).
   -------------------------------------------------------------------------- */
.article-author {
  padding-block: 0 var(--space-16);
}

.author-card {
  display: flex;
  align-items: center;
  gap: var(--space-6);
  padding: var(--space-6);
  border: 1px solid var(--field-border);
  border-radius: 4px;
}

.author-card img {
  flex-shrink: 0;
}

.author-info h2,
.author-info .term,
.author-info p {
  margin: 0;
}

.author-info h2,
.author-info .term {
  font-family: 'Archivo', sans-serif;
  font-size: var(--t-body);
  font-weight: 600;
  color: var(--ink);
  margin-bottom: var(--space-1);
}

/* .term now links to /#leadership (s1.3) — inherit the term's own weight/
   colour, add the standard underline-on-hover affordance. */
.author-info .term a {
  color: inherit;
  text-decoration: underline;
  text-decoration-color: transparent;
  transition: text-decoration-color var(--dur) var(--ease);
}

.author-info .term a:hover,
.author-info .term a:focus-visible {
  text-decoration-color: currentColor;
}

.author-info p {
  font-size: var(--t-sm);
  line-height: var(--t-sm-line);
  color: var(--ink-muted);
}

.author-card p {
  max-width: 62ch;
}

@media (max-width: 767px) {
  .author-card {
    flex-direction: column;
    align-items: flex-start;
  }
}

/* ==========================================================================
   Secondary-page template (§D-T, s1.1) — shell + components every service
   page, /dc-metro.html and blog/index.html build on. New rules and
   additions to existing selector lists only (D-14); no existing
   declaration is edited except the two named exceptions above
   (.author-info h2 / .page-blog). Where a new property is needed on an
   already-styled selector (.article-header, .service-card p), it lands as
   its own additional rule below rather than editing the original block.
   ========================================================================== */

/* --------------------------------------------------------------------------
   Dark-band focus ring (D-14, F42) — the pre-existing production a11y
   defect. Three shorthand `outline:` rules already carry var(--blue) at
   0,2,0 / 0,1,1 / 0,2,1 (site.css:580/705/738); an outline-COLOR-only
   override loses the cascade outright (r1's mistake). Every rule below
   uses the outline SHORTHAND and is specific enough to beat the rule it
   must override, appended after all three so source order can never be
   the deciding factor either. -> 7.41:1 on --field, 6.10:1 on
   --field-raised. Gated by A22 (rendered, not grep — a cascade loss is
   invisible to a static check).
   -------------------------------------------------------------------------- */
.band--field .btn-cta:focus-visible,
.band--field-raised .btn-cta:focus-visible,
.band--field .text-link:focus-visible,
.crumbs a:focus-visible {
  outline: 2px solid var(--cyan);
  outline-offset: 2px;
}

.band--field-raised .form-field input:focus-visible,
.band--field-raised .form-field textarea:focus-visible {
  outline: 2px solid var(--cyan);
  outline-offset: 2px;
}

.band--field :focus-visible,
.band--field-raised :focus-visible,
.nav--compact :focus-visible,
.site-footer :focus-visible,
.article-header :focus-visible {
  outline: 2px solid var(--cyan);
  outline-offset: 2px;
}

/* The skip link is a direct <body> child, positioned off-screen until
   focused — it sits outside every band selector above, so none of them
   reach it (measured: rgb(11,94,145) / --blue before this rule, ruby
   stage-8 s1 review). It renders over the dark nav on every page, so it
   gets the same cyan ring as everything else in that chrome. */
.skip-link:focus-visible {
  outline: 2px solid var(--cyan);
  outline-offset: 2px;
}

/* --------------------------------------------------------------------------
   Breadcrumb (§D-T.1) — visible on every secondary masthead, two items,
   Home -> page. Separator is generated content with empty alt text so a
   screen reader doesn't announce a bare slash. Links --on-field-dim
   (7.42:1 on --field); hover/focus --cyan (focus ring above).
   -------------------------------------------------------------------------- */
.crumbs {
  margin-bottom: var(--space-4);
}

.crumbs ol {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  font-family: 'Archivo', sans-serif;
  font-size: var(--t-sm);
}

.crumbs li {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

.crumbs li + li::before {
  content: "/" / "";
  color: var(--on-field-dim);
}

.crumbs a {
  color: var(--on-field-dim);
  text-decoration: none;
  display: inline-flex;
  align-items: center;
}

.band--field .crumbs a:hover {
  color: var(--cyan);
}

.crumbs li[aria-current="page"] {
  color: var(--on-field);
}

@media (max-width: 767px) {
  .crumbs a {
    min-height: 44px;
  }
}

/* --------------------------------------------------------------------------
   Masthead swoosh accent (§D-T.1) — the secondary masthead's right field
   is ~38% empty at 1440; reuses the hero's own gradient stop classes at a
   smaller scale, positioned behind the breadcrumb/H1/lead column. Each
   instance carries a per-file-unique gradient id (Pattern 9) so it never
   collides with the footer swoosh inside the same document.
   -------------------------------------------------------------------------- */
.article-header {
  position: relative;
  overflow: hidden;
}

.article-header .grid {
  position: relative;
  z-index: 1;
}

.masthead-swoosh {
  position: absolute;
  top: 42%;
  right: -60px;
  transform: translateY(-50%);
  height: 360px;
  width: 415px;
  opacity: .35;
  z-index: 0;
  pointer-events: none;
}

@media (max-width: 640px) {
  .masthead-swoosh {
    display: none;
  }
}

/* --------------------------------------------------------------------------
   Service card footer link (§D-T.5) — pins the card-more link to the
   card's baseline regardless of copy length. New rule, same selector as
   the existing .service-card p block (site.css ~1543) — additive, the
   original declaration is untouched.
   -------------------------------------------------------------------------- */
.service-card p {
  flex: 1 1 auto;
}

.card-more {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-3);
  padding-top: var(--space-3);
  border-top: 1px solid var(--rule);
  min-height: 44px;
  color: var(--blue);
  font-family: 'Archivo', sans-serif;
  font-size: var(--t-sm);
  font-weight: 600;
  text-decoration: none;
}

.card-more:hover,
.card-more:focus-visible {
  color: var(--blue-fill-hover);
}

/* --------------------------------------------------------------------------
   Service cards, 2-up ("What you get", §D-T.2) — distinct from the home
   page's 3-up .service-cards grid.
   -------------------------------------------------------------------------- */
.service-cards--2up {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-6);
}

@media (max-width: 767px) {
  .service-cards--2up {
    grid-template-columns: 1fr;
  }
}

/* --------------------------------------------------------------------------
   "How it runs" phase list (§D-T.2) — numbered <ol>, 2px --blue left rule
   per phase, distinguishing it from the article body's plain <ol>.
   -------------------------------------------------------------------------- */
.phase-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
}

.phase-list li {
  padding-left: var(--space-6);
  border-left: 2px solid var(--blue);
}

.phase-list .term {
  display: block;
  margin-bottom: var(--space-1);
}

/* ruby s2.1 template gate — the phase <ol> spans col-1-12 at desktop, and
   an unconstrained paragraph runs ~115 chars/line at that width. Cap the
   measure without narrowing the column. */
.phase-list li p {
  max-width: 68ch;
}

/* --------------------------------------------------------------------------
   FAQ band (§D-T.4, home page, s4) — CSS only in s1; no page renders it
   yet. band--paper split header, 2-col grid >=1024, single column below.
   No accordion — hidden-by-default content is the visibility defect D-3
   exists to fix.
   -------------------------------------------------------------------------- */
.faq-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-8) var(--space-16);
}

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

.faq-item {
  border-top: 2px solid var(--blue);
  padding-top: var(--space-4);
}

.faq-item .type-d3 {
  margin-bottom: var(--space-2);
}

.faq-item p {
  color: var(--ink-muted);
  max-width: 56ch;
}

/* --------------------------------------------------------------------------
   Related / prev-next strip (§D-T.6) — a band--paper strip at the end of
   <main>, before .article-author. s1 wires prev/next only; the related-
   service line is added at s4.0.
   -------------------------------------------------------------------------- */
.article-related {
  padding-block: var(--space-16);
}

.article-siblings {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-6);
  margin: 0;
  padding: 0;
  list-style: none;
}

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

.sibling-link {
  display: block;
  padding: var(--space-4);
  border: 1px solid var(--field-border);
  border-radius: 4px;
  text-decoration: none;
  color: inherit;
  transition: border-color var(--dur) var(--ease);
}

.sibling-link:hover,
.sibling-link:focus-visible {
  border-color: var(--blue);
}

.sibling-link .type-xs {
  display: block;
  color: var(--ink-muted);
  margin-bottom: var(--space-1);
}

.sibling-link .sibling-title {
  display: block;
  font-family: 'Archivo', sans-serif;
  font-weight: 600;
  color: var(--ink);
  text-decoration: underline;
  text-decoration-color: transparent;
  transition: text-decoration-color var(--dur) var(--ease);
}

.sibling-link:hover .sibling-title,
.sibling-link:focus-visible .sibling-title {
  text-decoration-color: currentColor;
}

.sibling-link--next {
  text-align: right;
}

@media (max-width: 767px) {
  .sibling-link--next {
    text-align: left;
  }
}

/* A lone "Next" card (no "Previous" sibling, e.g. the oldest article) would
   otherwise auto-place into column 1 of the 2-col grid and read on the
   left, where "Previous" belongs. Pin it to column 2 whenever the 2-col
   layout is active; a lone "Previous" card needs no fix — column 1 is
   already correct by auto-placement. */
@media (min-width: 768px) {
  .sibling-link--next {
    grid-column: 2;
  }
}

/* --------------------------------------------------------------------------
   Tight top padding (ruby stage-8 s1 review) — the blog index's masthead
   already supplies visual separation before the writing list; the default
   .section padding-block (clamp 64-128px on BOTH edges) reads as an extra,
   redundant gap on top of that. New utility, .section's own rule is
   untouched; bottom padding is inherited unchanged.
   -------------------------------------------------------------------------- */
.section--tight-top {
  padding-block-start: var(--space-16);
}

/* dc-metro corridor list (s3) */
#corridor .dl-row {
  justify-content: flex-start;
  column-gap: var(--space-8);
}

#corridor .dl-row dt {
  flex: 0 0 220px;
}

#corridor .dl-row dd {
  text-align: left;
}

@media (max-width: 767px) {
  #corridor .dl-row dt {
    flex-basis: auto;
  }
}

/* masthead rail (s2.2) */
.masthead-rail {
  list-style: none;
  margin: 0;
  padding: 0;
  align-self: center;
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.band--field .masthead-rail li {
  font-family: var(--font-mono);
  font-size: var(--t-mono);
  color: var(--on-field);
  padding-left: var(--space-4);
  border-left: 2px solid var(--cyan);
}

@media (max-width: 1023px) {
  .masthead-rail {
    flex-direction: row;
    flex-wrap: wrap;
    gap: var(--space-6);
    margin-top: var(--space-6);
  }
}

/* masthead pull (s2.3) */
.band--field .masthead-pull{align-self:center;margin:0;padding-top:var(--space-4);border-top:2px solid var(--cyan);color:var(--on-field);max-width:30ch}
@media (max-width:1023px){.masthead-pull{margin-top:var(--space-6);align-self:start}}
