/* ==========================================================================
   bradlasater.com — design system
   Dark-only. Tokens → base → layout → components → utilities → print.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Tokens
   -------------------------------------------------------------------------- */

:root {
  color-scheme: dark;

  /* Color — OKLCH for perceptually even ramps.
     Neutrals carry a slight cool cast (hue 255) to read as technical. */
  --c-bg:             oklch(0.165 0.010 255);
  --c-bg-subtle:      oklch(0.195 0.012 255);
  --c-surface:        oklch(0.215 0.013 255);
  --c-surface-hover:  oklch(0.250 0.015 255);

  --c-border:         oklch(0.300 0.015 255);
  --c-border-strong:  oklch(0.520 0.018 255);

  /* Never pure white on dark — halation makes it harder to read, not easier. */
  --c-text:           oklch(0.955 0.004 255);
  --c-text-secondary: oklch(0.790 0.011 255);
  --c-text-muted:     oklch(0.680 0.013 255);

  --c-accent:         oklch(0.745 0.135 232);
  --c-accent-hover:   oklch(0.820 0.125 232);
  --c-accent-quiet:   oklch(0.310 0.055 232);
  --c-on-accent:      oklch(0.165 0.020 255);

  /* Semantic financial colors. Teal-green / warm-red rather than pure
     green/red so they stay separable for deuteranopic viewers; always
     pair with a sign or label, never color alone. */
  --c-pos:            oklch(0.780 0.140 165);
  --c-neg:            oklch(0.700 0.160 25);
  --c-warn:           oklch(0.800 0.130 85);

  /* Type */
  --font-sans:  "Inter", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
  --font-serif: "Newsreader", ui-serif, Georgia, "Times New Roman", serif;
  --font-mono:  "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  /* Fluid type scale, 320px → 1280px viewport. */
  --step--1: clamp(0.875rem, 0.85rem + 0.10vw, 0.9375rem);
  --step-0:  clamp(1rem,     0.98rem + 0.10vw, 1.0625rem);
  --step-1:  clamp(1.2rem,   1.13rem + 0.33vw, 1.4rem);
  --step-2:  clamp(1.44rem,  1.30rem + 0.68vw, 1.85rem);
  --step-3:  clamp(1.73rem,  1.49rem + 1.20vw, 2.45rem);
  --step-4:  clamp(2.07rem,  1.68rem + 1.97vw, 3.25rem);

  /* Spacing — 4pt base, geometric-ish. */
  --sp-1: 0.25rem;  --sp-2: 0.5rem;   --sp-3: 0.75rem;  --sp-4: 1rem;
  --sp-5: 1.5rem;   --sp-6: 2rem;     --sp-7: 3rem;     --sp-8: 4rem;
  --sp-9: 6rem;     --sp-10: 8rem;

  --radius-sm: 6px;
  --radius:    10px;
  --radius-lg: 16px;

  --measure: 68ch;
  --shell:   72rem;
  --shell-narrow: 48rem;

  --ring: 0 0 0 2px var(--c-bg), 0 0 0 4px var(--c-accent);
  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);
}

/* --------------------------------------------------------------------------
   2. Reset & base
   -------------------------------------------------------------------------- */

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

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}

body {
  background: var(--c-bg);
  color: var(--c-text);
  font-family: var(--font-sans);
  font-size: var(--step-0);
  line-height: 1.65;
  font-synthesis-weight: none;
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
}

img, svg, video { display: block; max-width: 100%; height: auto; }

h1, h2, h3, h4 {
  line-height: 1.15;
  font-weight: 600;
  letter-spacing: -0.02em;
  /* `pretty` fills the available width and only guards against orphans.
     `balance` equalises line lengths, which is right for short display
     headings but leaves narrow, ragged blocks once a heading wraps to three
     or more lines — so it is applied only once there is room. */
  text-wrap: pretty;
}
@media (min-width: 44rem) {
  h1, h2, h3, h4 { text-wrap: balance; }
}

p, li { text-wrap: pretty; }

a { color: var(--c-accent); text-decoration-thickness: 1px; text-underline-offset: 0.2em; }
a:hover { color: var(--c-accent-hover); }

/* Visible keyboard focus on every interactive element — the single most
   commonly skipped accessibility requirement. */
:focus-visible {
  outline: none;
  box-shadow: var(--ring);
}

/* box-shadow is not painted under forced-colors, and the rule above removes
   the outline — leaving keyboard users in Windows High Contrast with no focus
   indicator at all. */
@media (forced-colors: active) {
  :focus-visible {
    outline: 2px solid CanvasText;
    outline-offset: 2px;
  }
}

/* Tabular figures only where numbers stack vertically and must align —
   table rows and axis ticks. Large standalone values keep proportional
   figures, because equal-width digits make a number like 121 look loose
   at display sizes. */
table, time, .chart__tick {
  font-variant-numeric: tabular-nums lining-nums;
}

code, kbd, pre, samp { font-family: var(--font-mono); font-size: 0.9em; }

::selection { background: var(--c-accent-quiet); color: var(--c-text); }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

/* --------------------------------------------------------------------------
   3. Layout
   -------------------------------------------------------------------------- */

.shell {
  width: 100%;
  max-width: var(--shell);
  margin-inline: auto;
  padding-inline: clamp(1.25rem, 4vw, 2.5rem);
}
.shell--narrow { max-width: var(--shell-narrow); }

main { flex: 1; }

.section { padding-block: clamp(var(--sp-7), 7vw, var(--sp-9)); }
.section + .section { border-top: 1px solid var(--c-border); }

.section__head { margin-bottom: var(--sp-6); }
.section__eyebrow {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--c-accent);
  display: block;
  margin-bottom: var(--sp-2);
}
.section__title { font-size: var(--step-2); }
.section__lede {
  color: var(--c-text-secondary);
  max-width: var(--measure);
  margin-top: var(--sp-3);
}

.skip-link {
  position: absolute;
  left: var(--sp-4);
  top: var(--sp-4);
  z-index: 100;
  padding: var(--sp-2) var(--sp-4);
  background: var(--c-accent);
  color: var(--c-on-accent);
  font-weight: 600;
  border-radius: var(--radius-sm);
  transform: translateY(-200%);
  transition: transform 0.15s var(--ease);
}
.skip-link:focus { transform: translateY(0); }

/* Anchor targets clear the sticky header. */
[id] { scroll-margin-top: 6rem; }

/* --------------------------------------------------------------------------
   4. Site header / nav
   -------------------------------------------------------------------------- */

.site-header {
  position: sticky;
  top: 0;
  z-index: 50;
  background: color-mix(in oklch, var(--c-bg) 82%, transparent);
  backdrop-filter: blur(12px) saturate(1.4);
  border-bottom: 1px solid var(--c-border);
}
.site-header__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-5);
  min-height: 4rem;
}
.brand {
  font-weight: 650;
  font-size: var(--step-0);
  letter-spacing: -0.02em;
  color: var(--c-text);
  text-decoration: none;
  white-space: nowrap;
}
.brand:hover { color: var(--c-text); }
.brand__dot { color: var(--c-accent); }

.site-nav ul {
  display: flex;
  align-items: center;
  gap: clamp(var(--sp-3), 2vw, var(--sp-5));
  list-style: none;
  padding: 0;
  flex-wrap: wrap;
  justify-content: flex-end;
}
.site-nav a {
  color: var(--c-text-secondary);
  text-decoration: none;
  font-size: var(--step--1);
  font-weight: 500;
  /* 44px minimum touch target via padding, not height, to keep the bar tight. */
  padding: var(--sp-3) var(--sp-1);
  display: inline-block;
  transition: color 0.15s var(--ease);
}
.site-nav a:hover { color: var(--c-text); }

/* GitHub closes the bar but is not a section of this site, so a rule separates
   it from the page nav rather than letting it read as one more destination.
   It is in the header at all because a hiring manager looks for the code
   first, and the homepage bio is long enough to push a hero button row under
   the fold — this one is above it on every page. */
.site-nav__ext {
  border-left: 1px solid var(--c-border);
  padding-left: clamp(var(--sp-3), 2vw, var(--sp-5));
}
.site-nav__ext a { color: var(--c-text); }
.site-nav a[aria-current] { color: var(--c-text); }
.site-nav a[aria-current]::after {
  content: "";
  display: block;
  height: 2px;
  background: var(--c-accent);
  border-radius: 2px;
  margin-top: 2px;
}

/* Below this width the brand and a six-item nav cannot share a row without
   forcing the document wider than the viewport. Stack them, and drop the
   sticky behaviour so the header does not eat scarce vertical space.

   Measured, not guessed, and re-measured every time the bar changes. It was
   56rem for seven items (which fit at 880px and wrapped at 850px). The bar is
   now five items plus the brand, because Build Status, the evaluation protocol,
   the trading record and the handbook moved into the /vol/ section bar: it
   fits at 528px and wraps at 527px, so 46rem sits just above the threshold with
   room for a font that loads late with wider metrics. A wrapped bar puts the
   GitHub rule on its own line and reads as broken, so re-measure in the browser
   whenever an item is added or renamed. */
@media (max-width: 46rem) {
  .site-header { position: static; }
  .site-header__inner {
    flex-direction: column;
    align-items: flex-start;
    justify-content: flex-start;
    gap: var(--sp-1);
    min-height: 0;
    padding-block: var(--sp-4);
  }
  .site-nav { width: 100%; }
  .site-nav ul {
    justify-content: flex-start;
    gap: var(--sp-4);
    row-gap: 0;
  }
  .site-nav a { padding-block: var(--sp-2); }
  [id] { scroll-margin-top: var(--sp-4); }
}

/* --------------------------------------------------------------------------
   5. Hero
   -------------------------------------------------------------------------- */

.hero { padding-block: clamp(var(--sp-8), 10vw, var(--sp-10)) var(--sp-8); }
.hero__name {
  font-size: var(--step-4);
  letter-spacing: -0.035em;
  font-weight: 620;
}
.hero__role {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: 0.02em;
  color: var(--c-accent);
  margin-top: var(--sp-4);
  display: block;
}
.hero__thesis {
  font-family: var(--font-serif);
  font-size: var(--step-2);
  line-height: 1.4;
  font-weight: 400;
  color: var(--c-text);
  /* Display copy needs per-instance tuning to break at the right clause;
     `pretty` fills lines normally while avoiding orphans, and the measure
     is overridable per page via --thesis-measure. */
  max-width: var(--thesis-measure, 29ch);
  margin-top: var(--sp-3);
  letter-spacing: -0.01em;
  text-wrap: pretty;
}
/* The sharpest claim on the page — it earns full text colour so it is not
   mistaken for the body copy that follows it. */
.hero__stance {
  color: var(--c-text);
  max-width: var(--measure);
  margin-top: var(--sp-4);
}

/* Identity links sit here, directly under the opening, rather than only in the
   button row below the bio: two bio paragraphs push that row under the fold on
   a laptop, and the code is the first thing a hiring manager goes looking for.
   Set in mono at role size so they read as identity metadata, not as a third
   call to action competing with the buttons below. */
.hero__links {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--sp-2);
  margin-top: var(--sp-4);
  font-family: var(--font-mono);
  font-size: var(--step--1);
}
.hero__links a {
  color: var(--c-text-secondary);
  text-decoration: none;
}
.hero__links a:hover {
  color: var(--c-accent);
  text-decoration: underline;
}
.hero__links span { color: var(--c-text-muted); }

.hero__bio {
  color: var(--c-text-secondary);
  max-width: var(--measure);
  margin-top: var(--sp-5);
}

/* CV identity block — the portrait appears here and nowhere else.
   A face above the homepage thesis would trade a research signal for a
   portfolio one: across the peer survey, sites carrying a photo are academic
   homepages and badge-forward one-pagers, while the content-forward research
   sites have none. On a CV it is conventional and a recruiter mid-screen
   expects it, so this is the one page where it earns its place. */
.hero--cv { display: grid; gap: var(--sp-5); }

@media (min-width: 42rem) {
  .hero--cv {
    grid-template-columns: auto minmax(0, 1fr);
    gap: var(--sp-6);
    align-items: start;
  }
}

/* <picture> is inline by default, which would leave a descender gap under the
   portrait and make the grid track size against a text baseline. */
.hero--cv picture { display: block; }

.hero__portrait {
  width: clamp(6rem, 16vw, 8.5rem);
  aspect-ratio: 1;
  object-fit: cover;
  display: block;
  border-radius: var(--radius);
  border: 1px solid var(--c-border);
}

/* The role label carries a top margin so it can follow body copy elsewhere.
   Inside the identity column it is the first child, and that margin would
   drop the text a few pixels below the portrait's top edge. */
.hero--cv .hero__role { margin-top: 0; }

.actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3);
  margin-top: var(--sp-6);
}

/* --------------------------------------------------------------------------
   6. Buttons
   -------------------------------------------------------------------------- */

.btn {
  --btn-bg: transparent;
  --btn-fg: var(--c-text);
  --btn-bd: var(--c-border-strong);
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  min-height: 2.75rem;
  padding: var(--sp-2) var(--sp-5);
  background: var(--btn-bg);
  color: var(--btn-fg);
  border: 1px solid var(--btn-bd);
  border-radius: var(--radius);
  font: inherit;
  font-size: var(--step--1);
  font-weight: 550;
  text-decoration: none;
  cursor: pointer;
  transition: background 0.15s var(--ease), border-color 0.15s var(--ease), color 0.15s var(--ease);
}
.btn:hover { background: var(--c-surface); border-color: var(--c-border-strong); color: var(--c-text); }
.btn--primary {
  --btn-bg: var(--c-accent);
  --btn-fg: var(--c-on-accent);
  --btn-bd: var(--c-accent);
  font-weight: 600;
}
.btn--primary:hover { background: var(--c-accent-hover); border-color: var(--c-accent-hover); color: var(--c-on-accent); }

/* --------------------------------------------------------------------------
   7. Cards & grids
   -------------------------------------------------------------------------- */

.grid {
  display: grid;
  gap: var(--sp-4);
  grid-template-columns: repeat(auto-fit, minmax(min(18rem, 100%), 1fr));
}

.card {
  background: var(--c-bg-subtle);
  border: 1px solid var(--c-border);
  border-radius: var(--radius-lg);
  padding: clamp(var(--sp-5), 3vw, var(--sp-6));
  transition: border-color 0.15s var(--ease), background 0.15s var(--ease);
}
.card--link { text-decoration: none; color: inherit; display: block; }
.card--link:hover { border-color: var(--c-border-strong); background: var(--c-surface); color: inherit; }
.card__title {
  font-size: var(--step-1);
  margin-bottom: var(--sp-2);
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  flex-wrap: wrap;
}
/* Implementation status on each volatility-system stage card. Kept out of the
   heading so the accessible name stays the stage name alone. */
.card__status { margin: 0 0 var(--sp-4); }
.card__body { color: var(--c-text-secondary); font-size: var(--step--1); }
.card__body > * + * { margin-top: var(--sp-3); }

/* Stage cards carry four labelled fields, not a sentence, so they run far
   taller than an ordinary card. Wider columns make them shorter: three columns
   of 18rem produced a wall of text at desktop width. */
.grid--stages { grid-template-columns: repeat(auto-fit, minmax(min(26rem, 100%), 1fr)); }

/* Stage cards on /vol/, generated from data/components.json. Each carries the
   same four parts in the same order, so two stages can be compared by eye
   rather than by reading two different paragraph shapes. */
.stage__order {
  margin: 0 0 var(--sp-2);
  font-family: var(--font-mono);
  font-size: 0.7rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--c-text-muted);
}
.stage__num { color: var(--c-accent); }
.stage__summary { color: var(--c-text); }
.stage__fields { margin: var(--sp-4) 0 0; }
.stage__field + .stage__field { margin-top: var(--sp-3); }
.stage__field dt {
  font-family: var(--font-mono);
  font-size: 0.7rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--c-text-muted);
}
.stage__field dd { margin: var(--sp-1) 0 0; }
/* An entitlement gap is not the same kind of fact as unwritten code, and a
   reader deciding whether the work is credible needs to tell them apart. */
.stage__field--blocked dt { color: var(--c-warn); }
.stage__field--blocked dd { border-left: 2px solid color-mix(in oklch, var(--c-warn) 35%, transparent); padding-left: var(--sp-3); }
.stage__more { margin-top: var(--sp-4); font-size: var(--step--1); }

/* Build-status pipeline. An ordered list, because these stages are a sequence
   and the card grid on /vol/ reflows and cannot say so. This is also already
   the right shape on a phone, which a diagram is not. */
.pipeline { list-style: none; margin: var(--sp-5) 0 0; padding: 0; counter-reset: none; }
.pipeline__row {
  padding: var(--sp-5) 0;
  border-top: 1px solid var(--c-border);
}
.pipeline__row:last-child { border-bottom: 1px solid var(--c-border); }
.pipeline__head {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--sp-3);
}
.pipeline__num {
  font-family: var(--font-mono);
  font-size: var(--step-1);
  color: var(--c-accent);
}
.pipeline__name { margin: 0; font-size: var(--step-1); letter-spacing: -0.02em; }
.pipeline__layer {
  font-family: var(--font-mono);
  font-size: 0.7rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--c-text-muted);
}
.pipeline__summary {
  margin: var(--sp-3) 0 0;
  color: var(--c-text);
  max-width: var(--measure);
}
.pipeline .stage__fields { max-width: var(--measure); }
.pipeline__empty { margin-top: var(--sp-5); color: var(--c-text-muted); font-size: var(--step--1); }
/* The acceptance test is the operative line on this page: it is what would
   have to be true for the stage to move, so it gets the accent rule. */
.stage__field--next dt { color: var(--c-accent); }
.stage__field--next dd {
  border-left: 2px solid var(--c-accent-quiet);
  padding-left: var(--sp-3);
}

/* Running-today / target-system filter, CSS only. */
.viewswitch__set {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--sp-3);
  margin: 0;
  padding: 0;
  border: 0;
}
.viewswitch__legend {
  padding: 0 var(--sp-3) 0 0;
  font-family: var(--font-mono);
  font-size: 0.7rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--c-text-muted);
}
/* Visually hidden, not display:none — the radio must stay focusable and
   reachable by keyboard, and its label is the visible control. */
.viewswitch__input {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
.viewswitch__label {
  padding: 0.3rem var(--sp-4);
  border: 1px solid var(--c-border-strong);
  border-radius: 999px;
  font-size: var(--step--1);
  color: var(--c-text-secondary);
  cursor: pointer;
}
.viewswitch__label:hover { color: var(--c-text); background: var(--c-surface-hover); }
.viewswitch__input:checked + .viewswitch__label {
  color: var(--c-on-accent);
  background: var(--c-accent);
  border-color: var(--c-accent);
}
.viewswitch__input:focus-visible + .viewswitch__label { outline: var(--ring); outline-offset: 3px; }
/* :has() rather than a sibling combinator, because the radios sit inside a
   fieldset for grouping semantics and are therefore not siblings of the list.
   Support is broader than the color-mix(in oklch) this file already needs. */
.viewswitch:has(#view-running:checked) .pipeline__row:not(.is-running) { display: none; }

/* Homepage status strip — three facts, counted from the manifest. */
.statusstrip__wrap { padding-block: var(--sp-6); }
.statusstrip {
  display: grid;
  gap: var(--sp-4);
  grid-template-columns: repeat(auto-fit, minmax(min(16rem, 100%), 1fr));
  margin: 0;
  padding: var(--sp-5);
  background: var(--c-bg-subtle);
  border: 1px solid var(--c-border);
  border-radius: var(--radius);
}
.statusstrip__item dt {
  font-family: var(--font-mono);
  font-size: 0.7rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--c-accent);
}
.statusstrip__item dd {
  margin: var(--sp-2) 0 0;
  color: var(--c-text-secondary);
  font-size: var(--step--1);
}
.statusstrip__note {
  margin: var(--sp-3) 0 0;
  font-size: var(--step--1);
  color: var(--c-text-muted);
}

/* Section bar for /vol/. Sits under the primary header on every page inside
   the volatility system, so Build Status, the protocol, the record and the
   handbook are reachable without taking four top-level nav slots. */
.section-nav {
  border-bottom: 1px solid var(--c-border);
  background: var(--c-bg-subtle);
}
.section-nav__inner {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--sp-3) var(--sp-5);
  padding-block: var(--sp-3);
}
.section-nav__label {
  font-family: var(--font-mono);
  font-size: 0.7rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--c-text-muted);
}
.section-nav ul {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2) var(--sp-4);
  list-style: none;
  margin: 0;
  padding: 0;
}
.section-nav a {
  color: var(--c-text-secondary);
  font-size: var(--step--1);
  text-decoration: none;
}
.section-nav a:hover { color: var(--c-text); }
.section-nav a[aria-current] { color: var(--c-accent); }

/* Published diagnostic reports. Rendered generically from the job's own JSON,
   so the styling has to survive arbitrary nesting rather than a known shape. */
.report__table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--step--1);
}
.report__table th,
.report__table td {
  padding: var(--sp-2) var(--sp-3);
  text-align: left;
  vertical-align: top;
  border-bottom: 1px solid var(--c-border);
}
.report__table th {
  font-family: var(--font-mono);
  font-weight: 500;
  color: var(--c-text-secondary);
  white-space: nowrap;
  width: 1%;
}
.report__table td { font-family: var(--font-mono); color: var(--c-text); }
/* A nested object is a table inside a cell; strip its outer padding and rule so
   it reads as a sub-list rather than a second, misaligned grid. */
.report__table .report__table { margin: calc(var(--sp-2) * -1) 0; }
.report__table .report__table tr:last-child th,
.report__table .report__table tr:last-child td { border-bottom: 0; }
.report__list { margin: 0; padding-left: var(--sp-4); font-family: var(--font-mono); }
.report__none { color: var(--c-text-muted); font-style: italic; }
.report__meta { color: var(--c-text-muted); font-size: var(--step--1); }

/* Callout — used where a page has to qualify its own claim. Warm rule rather
   than a card, so it reads as an aside to the text it interrupts. */
.callout {
  margin-top: var(--sp-5);
  padding: var(--sp-4) var(--sp-5);
  border-left: 3px solid color-mix(in oklch, var(--c-warn) 45%, transparent);
  background: var(--c-bg-subtle);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  color: var(--c-text-secondary);
  font-size: var(--step--1);
}
.callout > * + * { margin-top: var(--sp-3); }
.callout strong { color: var(--c-text); }

/* Proof-point cards on the homepage: problem, contribution, outcome. */
.proof__kicker {
  margin: 0 0 var(--sp-2);
  font-family: var(--font-mono);
  font-size: 0.7rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--c-accent);
}
.proof .card__body strong { color: var(--c-text); }

/* Availability, promoted into the hero beside the actions. */
.hero__availability {
  margin-top: var(--sp-5);
  color: var(--c-text-secondary);
  font-size: var(--step--1);
}
.hero__availability strong { color: var(--c-text); }

/* Feature card — the volatility system slot. */
.feature {
  background:
    radial-gradient(120% 140% at 100% 0%, var(--c-accent-quiet) 0%, transparent 55%),
    var(--c-bg-subtle);
  border: 1px solid var(--c-border-strong);
  border-radius: var(--radius-lg);
  padding: clamp(var(--sp-5), 4vw, var(--sp-7));
}
.feature__title { font-size: var(--step-3); letter-spacing: -0.03em; }
.feature__body { color: var(--c-text-secondary); max-width: var(--measure); margin-top: var(--sp-4); }
.feature__body > * + * { margin-top: var(--sp-4); }

/* --------------------------------------------------------------------------
   8. Badges
   -------------------------------------------------------------------------- */

.badge {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  padding: 0.15rem var(--sp-3);
  border: 1px solid var(--c-border-strong);
  border-radius: 999px;
  font-family: var(--font-mono);
  font-size: 0.75rem;
  font-weight: 500;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--c-text-muted);
  white-space: nowrap;
}
.badge--active   { color: var(--c-warn);   border-color: color-mix(in oklch, var(--c-warn) 40%, transparent); }
.badge--live     { color: var(--c-pos);    border-color: color-mix(in oklch, var(--c-pos) 40%, transparent); }
.badge--progress { color: var(--c-accent); border-color: color-mix(in oklch, var(--c-accent) 40%, transparent); }
/* "Implemented — under validation" is the longest label of the four, and it
   wraps rather than overflowing its card on a narrow viewport. The nowrap
   above is right for the short states and wrong for this one. */
.badge--active { white-space: normal; }
.badge--dot::before {
  content: "";
  width: 6px; height: 6px;
  border-radius: 50%;
  background: currentColor;
  flex: none;
}

/* --------------------------------------------------------------------------
   9. Experience timeline
   -------------------------------------------------------------------------- */

.roles { display: grid; gap: var(--sp-6); }

.role { display: grid; gap: var(--sp-3); }
@media (min-width: 52rem) {
  .role {
    grid-template-columns: 12rem 1fr;
    gap: var(--sp-6);
  }
}

.role__when {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  color: var(--c-text-muted);
  font-variant-numeric: tabular-nums;
  padding-top: 0.2em;
}
.role__where { display: flex; flex-direction: column; gap: var(--sp-1); }
.role__title { font-size: var(--step-1); font-weight: 600; }
.role__org { color: var(--c-text-secondary); font-size: var(--step--1); }
.role__meta {
  display: flex;
  gap: var(--sp-2);
  flex-wrap: wrap;
  margin-top: var(--sp-1);
}
.role__points {
  list-style: none;
  padding: 0;
  margin-top: var(--sp-4);
  display: grid;
  gap: var(--sp-3);
}
.role__points li {
  position: relative;
  padding-left: var(--sp-5);
  color: var(--c-text-secondary);
  font-size: var(--step--1);
}
.role__points li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.62em;
  width: 10px;
  height: 1px;
  background: var(--c-border-strong);
}

/* --------------------------------------------------------------------------
   10. Tag lists
   -------------------------------------------------------------------------- */

.taggroup + .taggroup { margin-top: var(--sp-5); }
.taggroup__label {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--c-text-muted);
  display: block;
  margin-bottom: var(--sp-3);
}
.tags { display: flex; flex-wrap: wrap; gap: var(--sp-2); list-style: none; padding: 0; }
.tags li {
  border: 1px solid var(--c-border);
  border-radius: var(--radius-sm);
  padding: 0.2rem var(--sp-3);
  font-size: var(--step--1);
  color: var(--c-text-secondary);
  background: var(--c-bg-subtle);
}

/* --------------------------------------------------------------------------
   11. Definition rows (patents, education)
   -------------------------------------------------------------------------- */

.deflist { display: grid; gap: var(--sp-5); }
.deflist__item > dt { font-weight: 600; font-size: var(--step-0); }
.deflist__item > dd {
  margin: var(--sp-1) 0 0;
  color: var(--c-text-secondary);
  font-size: var(--step--1);
  max-width: var(--measure);
}

/* --------------------------------------------------------------------------
   12. Prose (writeups, log entries)
   -------------------------------------------------------------------------- */

.prose { max-width: var(--measure); }
.prose, .card__body, .deflist__item > dd { overflow-wrap: break-word; }
.prose > * + * { margin-top: var(--sp-4); }
.prose h2 { font-size: var(--step-2); margin-top: var(--sp-7); }
.prose h3 { font-size: var(--step-1); margin-top: var(--sp-6); }
.prose p, .prose li { color: var(--c-text-secondary); }
.prose ul, .prose ol { padding-left: var(--sp-5); display: grid; gap: var(--sp-2); }
.prose strong { color: var(--c-text); font-weight: 600; }
.prose blockquote {
  border-left: 2px solid var(--c-accent);
  padding-left: var(--sp-5);
  font-family: var(--font-serif);
  font-size: var(--step-1);
  line-height: 1.5;
  color: var(--c-text);
}
.prose blockquote cite {
  display: block;
  margin-top: var(--sp-2);
  font-family: var(--font-sans);
  font-size: var(--step--1);
  font-style: normal;
  color: var(--c-text-muted);
}
.prose code {
  background: var(--c-surface);
  border: 1px solid var(--c-border);
  border-radius: 4px;
  padding: 0.1em 0.35em;
  color: var(--c-text);
}

/* --------------------------------------------------------------------------
   13. Data tables
   -------------------------------------------------------------------------- */

.table-wrap { overflow-x: auto; border: 1px solid var(--c-border); border-radius: var(--radius); }
table { width: 100%; border-collapse: collapse; font-size: var(--step--1); }
th, td { padding: var(--sp-3) var(--sp-4); text-align: left; }
thead th {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--c-text-muted);
  font-weight: 500;
  background: var(--c-bg-subtle);
  border-bottom: 1px solid var(--c-border);
  white-space: nowrap;
}
tbody tr + tr td { border-top: 1px solid var(--c-border); }
td.numeric, th.numeric { text-align: right; font-family: var(--font-mono); }
.pos { color: var(--c-pos); }
.neg { color: var(--c-neg); }

/* --------------------------------------------------------------------------
   14. Empty / pending states
   -------------------------------------------------------------------------- */

.pending {
  border: 1px dashed var(--c-border-strong);
  border-radius: var(--radius);
  padding: var(--sp-6);
  color: var(--c-text-muted);
  font-size: var(--step--1);
  text-align: center;
}
.pending strong { color: var(--c-text-secondary); display: block; margin-bottom: var(--sp-2); }

/* --------------------------------------------------------------------------
   14b. Track record — hero figure, stat tiles, verdict, charts
   -------------------------------------------------------------------------- */

/* Hero figure: the elapsed-time number, which is the one thing a track record
   cannot fake. Same sans as everything else, proportional figures. */
.figure-hero { display: flex; align-items: baseline; gap: var(--sp-3); flex-wrap: wrap; }
.figure-hero__value {
  font-size: clamp(3rem, 2.2rem + 4vw, 5rem);
  font-weight: 620;
  letter-spacing: -0.04em;
  line-height: 1;
  color: var(--c-text);
}
.figure-hero__unit { font-size: var(--step-1); color: var(--c-text-secondary); font-weight: 500; }
.figure-hero__note { color: var(--c-text-muted); font-size: var(--step--1); margin-top: var(--sp-3); }

.stats {
  display: grid;
  gap: 1px;
  background: var(--c-border);
  border: 1px solid var(--c-border);
  border-radius: var(--radius);
  overflow: hidden;
  grid-template-columns: repeat(auto-fit, minmax(min(11rem, 100%), 1fr));
}
.stat { background: var(--c-bg-subtle); padding: var(--sp-4) var(--sp-5); }
.stat__label {
  display: block;
  font-family: var(--font-mono);
  font-size: 0.7rem;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--c-text-muted);
  margin-bottom: var(--sp-2);
}
.stat__value {
  font-size: var(--step-1);
  font-weight: 600;
  color: var(--c-text);
  line-height: 1.2;
}
.stat__hint { display: block; margin-top: var(--sp-1); font-size: 0.78rem; color: var(--c-text-muted); }

/* The verdict banner — whether the record is long enough to mean anything. */
.verdict {
  border: 1px solid var(--c-border-strong);
  border-left-width: 3px;
  border-radius: var(--radius);
  padding: var(--sp-5);
  background: var(--c-bg-subtle);
}
.verdict--pending { border-left-color: var(--c-warn); }
.verdict--ok { border-left-color: var(--c-pos); }
.verdict__title {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--c-text-muted);
  margin-bottom: var(--sp-2);
}
.verdict p { color: var(--c-text-secondary); font-size: var(--step--1); max-width: var(--measure); }

.chart { position: relative; margin-top: var(--sp-4); }
.chart__svg { width: 100%; height: auto; display: block; overflow: visible; touch-action: pan-y; }
.chart__caption {
  font-size: var(--step--1);
  color: var(--c-text-muted);
  margin-top: var(--sp-2);
}

.chart__grid { stroke: var(--c-border); stroke-width: 1; }
.chart__zero { stroke: var(--c-border-strong); stroke-width: 1; }
.chart__tick { fill: var(--c-text-muted); font-size: 11px; font-family: var(--font-mono); }

.chart__line { fill: none; stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.chart__line--equity { stroke: var(--c-accent); }
.chart__line--drawdown { stroke: var(--c-neg); }

.chart__area { stroke: none; }
.chart__area--equity { fill: var(--c-accent); opacity: 0.10; }
.chart__area--drawdown { fill: var(--c-neg); opacity: 0.10; }

/* 2px ring in the surface colour keeps the end-dot legible over the line. */
.chart__ring { fill: var(--c-bg); }
.chart__dot--equity { fill: var(--c-accent); }
.chart__dot--drawdown { fill: var(--c-neg); }

.chart__boundary { stroke: var(--c-warn); stroke-width: 1; opacity: 0.7; }
.chart__boundary-label {
  fill: var(--c-warn);
  font-size: 10px;
  font-family: var(--font-mono);
  letter-spacing: 0.04em;
}

.chart__hover { opacity: 0; transition: opacity 0.1s var(--ease); }
.chart__hover.is-on { opacity: 1; }
.chart__crosshair { stroke: var(--c-border-strong); stroke-width: 1; }

.chart__tip {
  position: absolute;
  top: 0;
  transform: translateX(-50%);
  background: var(--c-surface);
  border: 1px solid var(--c-border-strong);
  border-radius: var(--radius-sm);
  padding: var(--sp-2) var(--sp-3);
  font-size: 0.8rem;
  pointer-events: none;
  white-space: nowrap;
  display: flex;
  gap: var(--sp-3);
  box-shadow: 0 4px 16px rgb(0 0 0 / 0.4);
}
.chart__tip[hidden] { display: none; }
.chart__tip-date { color: var(--c-text-muted); font-variant-numeric: tabular-nums; }
.chart__tip-value { color: var(--c-text); font-weight: 600; font-variant-numeric: tabular-nums; }

details.tableview { margin-top: var(--sp-5); }
details.tableview > summary {
  cursor: pointer;
  color: var(--c-text-secondary);
  font-size: var(--step--1);
  padding: var(--sp-2) 0;
  list-style-position: inside;
}
details.tableview > summary:hover { color: var(--c-text); }
details.tableview > .table-wrap { margin-top: var(--sp-3); max-height: 26rem; overflow: auto; }

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

/* --------------------------------------------------------------------------
   15. Footer
   -------------------------------------------------------------------------- */

.site-footer {
  border-top: 1px solid var(--c-border);
  padding-block: var(--sp-6);
  margin-top: var(--sp-7);
  color: var(--c-text-muted);
  font-size: var(--step--1);
}
.site-footer__inner {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-4);
  justify-content: space-between;
  align-items: center;
}
.site-footer a { color: var(--c-text-secondary); text-decoration: none; }
.site-footer a:hover { color: var(--c-accent); }
.site-footer ul { display: flex; gap: var(--sp-5); list-style: none; padding: 0; flex-wrap: wrap; }

/* --------------------------------------------------------------------------
   16. Utilities
   -------------------------------------------------------------------------- */

.visually-hidden {
  position: absolute !important;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}
.measure { max-width: var(--measure); }
.muted { color: var(--c-text-muted); }

/* --------------------------------------------------------------------------
   17. Print — recruiters and hiring managers still print résumés.
   -------------------------------------------------------------------------- */

@media print {
  :root {
    --c-bg: #fff; --c-bg-subtle: #fff; --c-surface: #fff;
    --c-text: #000; --c-text-secondary: #222; --c-text-muted: #555;
    --c-border: #ccc; --c-border-strong: #999; --c-accent: #0b57d0;
    /* Semantic colours need resetting too — the dark-theme green, red and
       amber all fall below 2.9:1 on white paper. */
    --c-pos: #076b4e;
    --c-neg: #a5231b;
    --c-warn: #7a5600;
  }
  body { font-size: 10.5pt; line-height: 1.45; }
  @page { margin: 14mm; }

  .site-header, .skip-link, .pending { display: none !important; }

  /* The portrait is screen-only by default. A printed or PDF-exported CV is
     the copy that gets forwarded into an applicant-tracking system, and some
     US employers route photo-bearing résumés aside to avoid bias exposure.
     The screen page keeps the face; the artifact that travels stays neutral.
     To print it after all, delete this rule. */
  .hero--cv picture { display: none !important; }
  .hero--cv { grid-template-columns: 1fr; }

  /* The contact links live ONLY inside .actions and the footer. Hiding both
     printed a CV with no way to reach the candidate — the most expensive
     possible defect on a page recruiters are expected to print. Nav-style
     action rows are still suppressed; the contact row is kept. */
  .actions { display: none !important; }
  .hero .actions { display: flex !important; gap: 0 1rem; flex-wrap: wrap; }
  .hero .actions .btn {
    border: 0;
    padding: 0;
    min-height: 0;
    font-weight: 400;
    background: none !important;
    color: #000 !important;
  }
  .site-footer { border-top: 1px solid #ccc; padding-block: 0.5rem; }
  .site-footer ul { gap: 1rem; }

  /* break-inside on .section is unsatisfiable — a section is taller than a
     page, so browsers drop the rule. The per-item rules are what actually
     keep a role or card from splitting. */
  .section { padding-block: 0.6rem; }
  .role, .card, .deflist__item, .stat { break-inside: avoid; }
  .feature { background: none; border: 1px solid #ccc; }
  a { text-decoration: none; color: #000; }
  /* No mailto rule: the printed contact row's link text is already the
     address, so expanding the href would just repeat it. */
  a[href^="http"]::after { content: " (" attr(href) ")"; font-size: 8pt; color: #555; }
}

/* Build hook. scripts/build_site.py rewrites the datetime attribute and the
   text of every .page-updated__time from git history, so this class is how a
   date on the page stays tied to the commit that actually produced it. Styled
   here only so it never appears as an unstyled orphan class. */
.page-updated__time {
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
