@import url("../fonts/fonts.css");

/* ============================================================
   naushad.co.uk — design system
   Carried over from the original AWSM theme: #dd3333 accent,
   Merriweather headings, PT Sans body. Everything else is new.
   ============================================================ */

/* ---------- tokens ---------- */
:root {
  /* brand — unchanged from the original site */
  --red: #dd3333;
  --red-hot: #ff4040;
  --red-deep: #a81f1f;

  /* type */
  --font-display: "Merriweather", Georgia, "Times New Roman", serif;
  --font-body: "PT Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;

  /* fluid type scale */
  --step--1: clamp(0.83rem, 0.79rem + 0.18vw, 0.94rem);
  --step-0: clamp(1rem, 0.95rem + 0.24vw, 1.13rem);
  --step-1: clamp(1.2rem, 1.11rem + 0.44vw, 1.5rem);
  --step-2: clamp(1.44rem, 1.29rem + 0.75vw, 2rem);
  --step-3: clamp(1.73rem, 1.48rem + 1.23vw, 2.66rem);
  --step-4: clamp(2.07rem, 1.68rem + 1.95vw, 3.55rem);
  --step-5: clamp(2.49rem, 1.88rem + 3.05vw, 4.73rem);

  /* space */
  --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;

  --measure: 68ch;
  --wrap: 1180px;
  --wrap-narrow: 780px;
  --radius: 4px;
  --radius-lg: 8px;
  --border-hair: 1px;

  --ease: cubic-bezier(0.4, 0, 0.2, 1);
  --dur: 200ms;
}

/* Dark is the site's default; the markup ships data-theme="dark" and the
   toggle only ever moves between the two explicit blocks below. These base
   values remain as the no-attribute fallback. */
:root { color-scheme: dark; }
:root[data-theme="light"] { color-scheme: light; }

/* light */
:root {
  --bg: #ffffff;
  --bg-alt: #f5f6f8;
  --surface: #ffffff;
  --surface-raised: #ffffff;
  --ink: #12161c;
  --ink-soft: #444d59;
  --ink-mute: #6b7683;
  --line: #e2e6ea;
  --line-strong: #cbd2d9;
  --accent: var(--red);
  --accent-ink: #ffffff;
  --grid-line: rgba(18, 22, 28, 0.045);
  --shadow: 0 1px 2px rgba(18, 22, 28, 0.06), 0 8px 24px rgba(18, 22, 28, 0.06);
  --shadow-lift: 0 2px 4px rgba(18, 22, 28, 0.08), 0 16px 40px rgba(18, 22, 28, 0.1);
  --glow: none;
}

/* dark */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: #0a0d12;
    --bg-alt: #0f141b;
    --surface: #111821;
    --surface-raised: #161e29;
    --ink: #e8eef5;
    --ink-soft: #b3bfcc;
    --ink-mute: #7d8b9c;
    --line: #1e2733;
    --line-strong: #2c3746;
    --accent: var(--red-hot);
    --accent-ink: #ffffff;
    --grid-line: rgba(255, 255, 255, 0.032);
    --shadow: 0 1px 2px rgba(0, 0, 0, 0.5), 0 8px 24px rgba(0, 0, 0, 0.4);
    --shadow-lift: 0 2px 4px rgba(0, 0, 0, 0.6), 0 16px 44px rgba(0, 0, 0, 0.55);
    --glow: 0 0 0 1px rgba(255, 64, 64, 0.18), 0 0 28px rgba(255, 64, 64, 0.1);
  }
}

/* explicit toggle wins in both directions */
:root[data-theme="dark"] {
  --bg: #0a0d12;
  --bg-alt: #0f141b;
  --surface: #111821;
  --surface-raised: #161e29;
  --ink: #e8eef5;
  --ink-soft: #b3bfcc;
  --ink-mute: #7d8b9c;
  --line: #1e2733;
  --line-strong: #2c3746;
  --accent: var(--red-hot);
  --accent-ink: #ffffff;
  --grid-line: rgba(255, 255, 255, 0.032);
  --shadow: 0 1px 2px rgba(0, 0, 0, 0.5), 0 8px 24px rgba(0, 0, 0, 0.4);
  --shadow-lift: 0 2px 4px rgba(0, 0, 0, 0.6), 0 16px 44px rgba(0, 0, 0, 0.55);
  --glow: 0 0 0 1px rgba(255, 64, 64, 0.18), 0 0 28px rgba(255, 64, 64, 0.1);
}

:root[data-theme="light"] {
  --bg: #ffffff;
  --bg-alt: #f5f6f8;
  --surface: #ffffff;
  --surface-raised: #ffffff;
  --ink: #12161c;
  --ink-soft: #444d59;
  --ink-mute: #6b7683;
  --line: #e2e6ea;
  --line-strong: #cbd2d9;
  --accent: var(--red);
  --grid-line: rgba(18, 22, 28, 0.045);
  --glow: none;
}

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

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: 5rem;
}

@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;
  }
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: var(--step-0);
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
}

h1, h2, h3, h4, h5, h6 {
  font-family: var(--font-display);
  line-height: 1.18;
  letter-spacing: -0.015em;
  margin: 0 0 var(--sp-4);
  text-wrap: balance;
}

h1 { font-size: var(--step-5); font-weight: 700; }
h2 { font-size: var(--step-3); font-weight: 700; }
h3 { font-size: var(--step-1); font-weight: 700; }
h4 { font-size: var(--step-0); font-weight: 700; }

p { margin: 0 0 var(--sp-4); max-width: var(--measure); text-wrap: pretty; }

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

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

/* A two-tone ring, not a single outline. A lone accent hairline disappears
   against the red band on #contact and against the dark globe hero, because
   there it is accent-on-accent. The inner --bg halo re-establishes contrast
   on whatever the ring happens to land on, so one rule covers every surface.
   box-shadow rather than outline: it follows border-radius, so it hugs a
   pill chip instead of boxing it. */
:root { --ring: 0 0 0 2px var(--bg), 0 0 0 4px var(--accent); }
:root[data-theme="dark"] { --ring: 0 0 0 2px #0a0d12, 0 0 0 4px var(--accent); }

:focus-visible {
  outline: none;
  box-shadow: var(--ring);
  border-radius: 3px;
}
/* Anything that already owns box-shadow (cards, buttons that lift on hover)
   would have its ring overwritten by the more specific rule. outline is the
   fallback there — it cannot be clobbered by a shadow. */
.btn:focus-visible, .tile:focus-visible, .card:focus-visible {
  box-shadow: none;
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}

/* Cards are <article>, so focus lands on the small link inside and the eye
   has to hunt for a 90px target inside a 400px box. Promote the ring to the
   whole card: the visitor sees what they are about to open, not the six
   words they happen to be on. */
.venture:focus-within, .lib-item:focus-within, .tool:focus-within,
.env:focus-within, .cert-card:focus-within, .note:focus-within,
.svc:focus-within, .client:focus-within {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}
/* ...and drop the inner ring, or the card reads as two nested boxes. */
.venture a:focus-visible, .lib-item a:focus-visible, .tool a:focus-visible,
.env a:focus-visible, .cert-card a:focus-visible, .note a:focus-visible,
.svc a:focus-visible, .client a:focus-visible {
  outline: none; box-shadow: none; text-decoration: underline;
}

::selection { background: var(--accent); color: #fff; }

.skip-link {
  position: absolute; left: -9999px; top: 0; z-index: 999;
  background: var(--accent); color: #fff; padding: var(--sp-3) var(--sp-5);
  font-weight: 700;
}
.skip-link:focus { left: 0; }

/* ---------- layout ---------- */
.wrap { width: min(100% - 2.5rem, var(--wrap)); margin-inline: auto; }
.wrap-narrow { width: min(100% - 2.5rem, var(--wrap-narrow)); margin-inline: auto; }

.section { padding-block: var(--sp-9); position: relative; }
.section--alt { background: var(--bg-alt); }
/* The header is sticky at 4.25rem. Without this, clicking a nav anchor parks
   the section heading underneath it and the visitor lands on body copy with
   no idea what they jumped to. */
.section[id] { scroll-margin-top: 5.5rem; }
.section--tight { padding-block: var(--sp-8); }

/* hairline grid backdrop — the "cyber" texture, kept very low contrast */
.gridlines::before {
  content: "";
  position: absolute; inset: 0;
  background-image:
    linear-gradient(var(--grid-line) 1px, transparent 1px),
    linear-gradient(90deg, var(--grid-line) 1px, transparent 1px);
  background-size: 72px 72px;
  pointer-events: none;
  mask-image: radial-gradient(ellipse 90% 70% at 50% 30%, #000 20%, transparent 78%);
  -webkit-mask-image: radial-gradient(ellipse 90% 70% at 50% 30%, #000 20%, transparent 78%);
}

/* ---------- eyebrow / mono labels ---------- */
.eyebrow {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-mute);
  display: flex; align-items: center; gap: var(--sp-3);
  margin-bottom: var(--sp-4);
}
.eyebrow::before {
  content: "";
  width: 1.75rem; height: 1px;
  background: var(--accent);
  flex: none;
}

.section-head { margin-bottom: var(--sp-7); max-width: var(--measure); }
/* balance, not pretty. These ledes are one or two sentences and several land
   within a character or two of the 68ch cap — the research lede is exactly 68
   — so the last word drops to a line of its own. `pretty` only nudges the
   final lines and Firefox ignores it entirely; `balance` evens every line and
   is supported where it matters. */
.section-head p {
  color: var(--ink-soft); font-size: var(--step-1); margin-bottom: 0;
  text-wrap: balance;
}

/* ---------- header ---------- */
.site-header {
  position: sticky; top: 0; z-index: 100;
  background: color-mix(in srgb, var(--bg) 82%, transparent);
  backdrop-filter: saturate(1.6) blur(14px);
  -webkit-backdrop-filter: saturate(1.6) blur(14px);
  border-bottom: var(--border-hair) solid transparent;
  transition: border-color var(--dur) var(--ease), background var(--dur) var(--ease);
}
.site-header[data-stuck="true"] { border-bottom-color: var(--line); }

.site-header__inner {
  display: flex; align-items: center; gap: var(--sp-5);
  min-height: 4.25rem;
}

.brand {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1.05rem;
  letter-spacing: -0.02em;
  color: var(--ink);
  text-decoration: none;
  display: flex; align-items: baseline; gap: 0.4rem;
  margin-right: auto;
}
.brand__dot { color: var(--accent); }
.brand__suffix {
  font-family: var(--font-mono);
  font-size: 0.68rem;
  letter-spacing: 0.12em;
  color: var(--ink-mute);
  text-transform: uppercase;
}

.nav { display: flex; align-items: center; gap: var(--sp-1); }
.nav a {
  font-size: 0.9rem;
  color: var(--ink-soft);
  text-decoration: none;
  padding: var(--sp-2) var(--sp-3);
  border-radius: var(--radius);
  position: relative;
  transition: color var(--dur) var(--ease);
}
.nav a:hover { color: var(--ink); }
/* "Where I am" has to outrank "where my cursor is", or the two states are
   indistinguishable and the spy tells the visitor nothing. Current gets the
   accent colour and a solid rule; hover only gets the ink shift and a faded
   one. Deliberately NOT a weight change — bolding a nav link mid-scroll
   reflows the whole bar every time the marker moves. */
.nav a[aria-current="true"] { color: var(--accent); }
.nav a::after {
  content: "";
  position: absolute; left: var(--sp-3); right: var(--sp-3); bottom: 0.25rem;
  height: 1.5px; background: var(--accent);
  transform: scaleX(0); transform-origin: left;
  transition: transform var(--dur) var(--ease);
}
.nav a:hover::after { transform: scaleX(1); opacity: 0.55; }
.nav a[aria-current="true"]::after { transform: scaleX(1); height: 2px; opacity: 1; }

.theme-toggle {
  background: transparent;
  border: var(--border-hair) solid var(--line);
  color: var(--ink-soft);
  width: 2.25rem; height: 2.25rem;
  border-radius: var(--radius);
  display: grid; place-items: center;
  cursor: pointer; flex: none;
  transition: border-color var(--dur) var(--ease), color var(--dur) var(--ease);
}
.theme-toggle:hover { border-color: var(--line-strong); color: var(--ink); }
.theme-toggle svg { width: 1.05rem; height: 1.05rem; }
/* data-theme is always set now — dark from the markup, light only if the
   reader chose it — so the icon rules no longer need a system-preference
   fallback. The icon shows what a click will DO: a sun while dark. */
.theme-toggle .icon-sun { display: block; }
.theme-toggle .icon-moon { display: none; }
:root[data-theme="light"] .theme-toggle .icon-sun { display: none; }
:root[data-theme="light"] .theme-toggle .icon-moon { display: block; }

.nav-toggle { display: none; }

@media (max-width: 860px) {
  .nav-toggle {
    display: grid; place-items: center;
    width: 2.25rem; height: 2.25rem;
    background: transparent; border: var(--border-hair) solid var(--line);
    border-radius: var(--radius); color: var(--ink); cursor: pointer;
  }
  .nav {
    position: fixed; inset: 4.25rem 0 auto;
    flex-direction: column; align-items: stretch;
    background: var(--bg);
    border-bottom: var(--border-hair) solid var(--line);
    padding: var(--sp-4) 1.25rem var(--sp-6);
    gap: 0;
    transform: translateY(-8px);
    opacity: 0; visibility: hidden;
    transition: opacity var(--dur) var(--ease), transform var(--dur) var(--ease), visibility var(--dur);
  }
  .nav[data-open="true"] { opacity: 1; visibility: visible; transform: translateY(0); }
  .nav a { padding: var(--sp-4) 0; border-bottom: var(--border-hair) solid var(--line); font-size: 1rem; }
  .nav a::after { display: none; }
}

/* ---------- hero ---------- */
.hero {
  position: relative;
  padding-block: clamp(3.5rem, 9vh, 7rem) var(--sp-9);
  overflow: hidden;
}

/* --- globe hero ---------------------------------------------------------
   The hero is deliberately dark in BOTH themes: the submarine-cable globe
   only reads against deep navy, and it matches the AiiDIS #04091f brand.
   Everything below the hero still follows light/dark normally. */
.hero--globe {
  --hero-bg: #04091f;
  background: var(--hero-bg);
  min-height: 100svh;
  display: flex;
  align-items: center;
  isolation: isolate;
}

/* globe_cables.js sizes its renderer to window.innerWidth/innerHeight, so the
   hero must match the viewport exactly or the globe sits off-centre. */
@media (min-width: 1024px) {
  .hero--globe { height: 100vh; min-height: 640px; }
}

/* Everything in the hero stacks POSITIVELY. A negative z-index canvas behind a
   parent that paints its own background is unreliable to hit-test, which made
   the globe impossible to grab. Layer order: glow 0, canvas 1, scrim 2,
   text 3, hint 4 — and only the canvas accepts the pointer. */

/* Placeholder glow, visible instantly. Replaced by the live globe if loaded. */
.hero--globe::before {
  content: "";
  position: absolute;
  inset: 0;
  background:
    radial-gradient(circle at 72% 48%, rgba(58, 214, 255, 0.16), transparent 42%),
    radial-gradient(circle at 68% 52%, rgba(255, 64, 64, 0.1), transparent 55%),
    radial-gradient(ellipse at 50% 120%, rgba(58, 214, 255, 0.07), transparent 60%);
  z-index: 0;
  pointer-events: none;
  transition: opacity 900ms var(--ease);
}

.hero__canvas {
  position: absolute;
  inset: 0;
  z-index: 1;
  opacity: 0;
  transition: opacity 1200ms var(--ease);
  pointer-events: none;
}
.hero__canvas canvas { display: block; }

/* Grab affordance, so it reads as something you can throw around. */
.hero--globe[data-globe="ready"] .hero__canvas { cursor: grab; }
.hero--globe[data-globe="ready"] .hero__canvas:active { cursor: grabbing; }

/* Once the globe reports ready, fade it in and let the pointer reach it. */
.hero--globe[data-globe="ready"] .hero__canvas { opacity: 1; pointer-events: auto; }
.hero--globe[data-globe="ready"]::before { opacity: 0; }

/* Keeps headline contrast without hiding the globe. The left column needs
   enough cover for text; the right stays almost clear so the cables read. */
.hero__scrim {
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  background:
    linear-gradient(100deg, rgba(4, 9, 31, 0.82) 0%, rgba(4, 9, 31, 0.6) 30%,
                    rgba(4, 9, 31, 0.18) 55%, rgba(4, 9, 31, 0) 78%),
    linear-gradient(to bottom, rgba(4, 9, 31, 0.55) 0%, rgba(4, 9, 31, 0) 18%,
                    rgba(4, 9, 31, 0) 78%, var(--hero-bg) 100%);
}

/* Only dim behind the text once the globe is actually up. */
.hero--globe:not([data-globe="ready"]) .hero__scrim { opacity: 0.45; }

/* The hero text floats over a draggable globe. Letting it swallow pointer
   events made the globe impossible to rotate, and drags selected text instead.
   So the text layer is transparent to the pointer and only the genuinely
   interactive bits opt back in. */
.hero__content { position: relative; z-index: 3; pointer-events: none; }
.hero__content a,
.hero__content button { pointer-events: auto; }

/* Nothing to select means no accidental text highlight mid-drag. */
.hero--globe[data-globe="ready"] .hero__content { user-select: none; -webkit-user-select: none; }

/* Hint that the globe is interactive — fades once the visitor grabs it. */
.hero__hint {
  position: absolute;
  right: clamp(1rem, 4vw, 3rem);
  bottom: clamp(1rem, 4vh, 2.5rem);
  z-index: 4;
  display: none;
  align-items: center;
  gap: var(--sp-3);
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.5);
  pointer-events: none;
  transition: opacity 500ms var(--ease);
}
.hero--globe[data-globe="ready"] .hero__hint { display: flex; }
.hero--globe[data-globe-engaged="true"] .hero__hint { opacity: 0; }
.hero__hint svg { width: 1rem; height: 1rem; }

/* Hero type is always on dark, regardless of the active theme.
   Text shadows keep it readable where the globe passes behind it. */
.hero--globe h1,
.hero--globe .stat__num { color: #f2f6fb; text-shadow: 0 2px 24px rgba(4, 9, 31, 0.85); }
.hero--globe .hero__lede { color: #c3cede; text-shadow: 0 1px 14px rgba(4, 9, 31, 0.9); }
.hero--globe .hero__roles,
.hero--globe .stat__label { color: #8fa0b6; }
.hero--globe .hero__status {
  color: #a9b8cc;
  background: rgba(255, 255, 255, 0.05);
  border-color: rgba(255, 255, 255, 0.14);
  backdrop-filter: blur(6px);
}
.hero--globe .hero__roles span + span::before { background: rgba(255, 255, 255, 0.28); }
.hero--globe .stats { border-top-color: rgba(255, 255, 255, 0.14); }
.hero--globe h1 .accent { color: var(--red-hot); }
.hero--globe .btn--ghost { border-color: rgba(255, 255, 255, 0.32); color: #eaf1f8; }
.hero--globe .btn--ghost:hover { border-color: var(--red-hot); color: var(--red-hot); }

@media (max-width: 1023px) {
  .hero--globe { min-height: 0; }
}

.hero__status {
  display: inline-flex; align-items: center; gap: var(--sp-3);
  font-family: var(--font-mono);
  font-size: var(--step--1);
  color: var(--ink-mute);
  border: var(--border-hair) solid var(--line);
  border-radius: 999px;
  padding: 0.3rem 0.85rem 0.3rem 0.6rem;
  margin-bottom: var(--sp-5);
  background: var(--surface);
}
.pulse {
  width: 7px; height: 7px; border-radius: 50%;
  background: #24c07a;
  box-shadow: 0 0 0 0 rgba(36, 192, 122, 0.6);
  animation: pulse 2.4s var(--ease) infinite;
  flex: none;
}
@keyframes pulse {
  0% { box-shadow: 0 0 0 0 rgba(36, 192, 122, 0.55); }
  70% { box-shadow: 0 0 0 7px rgba(36, 192, 122, 0); }
  100% { box-shadow: 0 0 0 0 rgba(36, 192, 122, 0); }
}

/* The name is now the eyebrow. It keeps display type and the accent dot so it
   still reads as the wordmark, but at a size that hands the stage to the
   headline underneath rather than competing with it. */
.hero__name {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--step-1);
  letter-spacing: -0.02em;
  line-height: 1;
  color: var(--ink);
  margin: 0 0 var(--sp-3);
}
.hero__name .accent { color: var(--accent); }

/* --step-5 tops out at 4.73rem, which suits a one-word h1 and buries a
   twelve-word one. The headline is a sentence, so it takes a step down and a
   tight measure: three balanced lines on desktop, never a runaway column. */
.hero__headline {
  font-size: var(--step-4);
  line-height: 1.1;
  max-width: 22ch;
  margin: 0 0 var(--sp-5);
  text-wrap: balance;
}

/* Demoted below the lede: these are credentials, not a second headline. */
.hero__roles {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: 0.04em;
  color: var(--ink-mute);
  margin-bottom: var(--sp-6);
  display: flex; flex-wrap: wrap; gap: var(--sp-2) var(--sp-4);
}
.hero__roles span { display: inline-flex; align-items: center; gap: var(--sp-3); }
.hero__roles span + span::before {
  content: ""; width: 4px; height: 4px; border-radius: 50%;
  background: var(--line-strong); margin-right: var(--sp-1);
}

.hero__lede {
  font-size: var(--step-1);
  color: var(--ink-soft);
  max-width: 60ch;
  margin-bottom: var(--sp-6);
}

.hero__actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-5); }

/* The secondary action, as a text link. Same target, a fraction of the weight,
   so the primary button is unambiguously the primary. */
.hero__link {
  display: inline-flex; align-items: center; gap: var(--sp-2);
  font-size: 0.95rem; font-weight: 700;
  color: var(--ink-soft);
  text-decoration: none;
  transition: color var(--dur) var(--ease), gap var(--dur) var(--ease);
}
.hero__link:hover { color: var(--accent); gap: var(--sp-3); }
.hero__link svg { width: 1rem; height: 1rem; flex: none; }

/* ---------- buttons ---------- */
.btn {
  display: inline-flex; align-items: center; gap: var(--sp-3);
  font-family: var(--font-body);
  font-size: 0.95rem; font-weight: 700;
  padding: 0.7rem 1.3rem;
  border-radius: var(--radius);
  border: var(--border-hair) solid transparent;
  text-decoration: none;
  cursor: pointer;
  transition: transform var(--dur) var(--ease), background var(--dur) var(--ease),
              border-color var(--dur) var(--ease), color var(--dur) var(--ease);
}
.btn:hover { transform: translateY(-1px); }
.btn:active { transform: translateY(0); }

.btn--primary { background: var(--accent); color: var(--accent-ink); }
.btn--primary:hover { background: var(--red-deep); color: #fff; }
:root[data-theme="dark"] .btn--primary:hover { background: var(--red); }

.btn--ghost { border-color: var(--line-strong); color: var(--ink); background: transparent; }
.btn--ghost:hover { border-color: var(--accent); color: var(--accent); }

.btn svg { width: 1rem; height: 1rem; flex: none; }

/* ---------- stat strip ---------- */
/* Its own band now, between the ventures and the live testbeds — a thin rule
   of evidence under the claim it supports. Deliberately shallow padding: this
   is a caesura between two heavy sections, not a section in its own right. */
.stat-band { padding-block: var(--sp-7); }
.stat-band .stats { margin-top: 0; }

.stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: var(--sp-5);
  margin-top: var(--sp-8);
  padding-block: var(--sp-6);
  border-block: var(--border-hair) solid var(--line);
}
.stat__num {
  font-family: var(--font-display);
  font-size: var(--step-3);
  font-weight: 700;
  line-height: 1;
  color: var(--ink);
  display: block;
}
.stat__num .unit { color: var(--accent); }
.stat__label {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-mute);
  margin-top: var(--sp-2);
  display: block;
}

/* ---------- cards ---------- */
.grid { display: grid; gap: var(--sp-5); }
.grid--2 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 340px), 1fr)); }
.grid--3 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 280px), 1fr)); }

.card {
  background: var(--surface);
  border: var(--border-hair) solid var(--line);
  border-radius: var(--radius-lg);
  padding: var(--sp-6);
  display: flex; flex-direction: column;
  position: relative;
  transition: border-color var(--dur) var(--ease), transform var(--dur) var(--ease),
              box-shadow var(--dur) var(--ease);
}
.card:hover { border-color: var(--line-strong); transform: translateY(-2px); box-shadow: var(--shadow); }
.card h3 { margin-bottom: var(--sp-3); }
.card p { color: var(--ink-soft); font-size: 0.95rem; }
.card > :last-child { margin-bottom: 0; }

/* venture card — the lead content */
.venture {
  --edge: var(--accent);
  background: var(--surface);
  border: var(--border-hair) solid var(--line);
  border-radius: var(--radius-lg);
  padding: var(--sp-7) var(--sp-6) var(--sp-6);
  position: relative;
  overflow: hidden;
  display: flex; flex-direction: column;
  transition: border-color var(--dur) var(--ease), transform var(--dur) var(--ease),
              box-shadow var(--dur) var(--ease);
}
.venture::before {
  content: "";
  position: absolute; top: 0; left: 0; right: 0; height: 2px;
  background: linear-gradient(90deg, var(--edge), color-mix(in srgb, var(--edge) 15%, transparent));
}
.venture:hover {
  border-color: color-mix(in srgb, var(--edge) 45%, var(--line));
  transform: translateY(-3px);
  box-shadow: var(--shadow-lift);
}
.venture__tag {
  font-family: var(--font-mono);
  font-size: 0.68rem; letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--edge);
  border: var(--border-hair) solid color-mix(in srgb, var(--edge) 35%, transparent);
  border-radius: 999px;
  padding: 0.2rem 0.6rem;
  align-self: flex-start;
  margin-bottom: var(--sp-4);
}
.venture__name {
  font-size: var(--step-2);
  margin-bottom: var(--sp-2);
  letter-spacing: -0.02em;
}
.venture__name .tm { font-size: 0.5em; vertical-align: super; color: var(--ink-mute); }
.venture__tagline {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  color: var(--edge);
  margin-bottom: var(--sp-4);
}
.venture__body { color: var(--ink-soft); font-size: 0.95rem; flex: 1; }
.venture__meta {
  display: flex; flex-wrap: wrap; gap: var(--sp-2);
  margin: var(--sp-5) 0 var(--sp-5);
  padding-top: var(--sp-5);
  border-top: var(--border-hair) solid var(--line);
}
.chip {
  font-family: var(--font-mono);
  font-size: 0.7rem;
  color: var(--ink-mute);
  background: var(--bg-alt);
  border: var(--border-hair) solid var(--line);
  border-radius: var(--radius);
  padding: 0.22rem 0.55rem;
}
.venture__link {
  font-weight: 700; font-size: 0.95rem;
  text-decoration: none;
  display: inline-flex; align-items: center; gap: var(--sp-2);
  color: var(--edge);
  align-self: flex-start;
}
.venture__link svg { width: 0.9rem; height: 0.9rem; transition: transform var(--dur) var(--ease); }
.venture:hover .venture__link svg { transform: translate(2px, -2px); }

/* ---------- article list ---------- */
.article-list { display: flex; flex-direction: column; }
.article {
  display: grid;
  grid-template-columns: 8.5rem 1fr auto;
  gap: var(--sp-5);
  align-items: baseline;
  padding: var(--sp-5) 0;
  border-bottom: var(--border-hair) solid var(--line);
  text-decoration: none;
  color: inherit;
  transition: background var(--dur) var(--ease);
}
.article:first-child { border-top: var(--border-hair) solid var(--line); }
.article:hover { background: var(--bg-alt); }
.article__date {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  color: var(--ink-mute);
  white-space: nowrap;
}
.article__title {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--step-0);
  line-height: 1.35;
  margin: 0;
  transition: color var(--dur) var(--ease);
}
.article:hover .article__title { color: var(--accent); }
.article__read {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  color: var(--ink-mute);
  white-space: nowrap;
}
@media (max-width: 720px) {
  .article { grid-template-columns: 1fr; gap: var(--sp-2); }
  .article__read { display: none; }
}

/* ---------- timeline ---------- */
.timeline { position: relative; padding-left: 2.25rem; }
.timeline::before {
  content: "";
  position: absolute; left: 0.42rem; top: 0.4rem; bottom: 0.4rem;
  width: 1px; background: var(--line);
}
.tl-item { position: relative; padding-bottom: var(--sp-6); }
.tl-item::before {
  content: "";
  position: absolute; left: -2.25rem; top: 0.42rem;
  width: 9px; height: 9px; border-radius: 50%;
  background: var(--bg);
  border: 2px solid var(--line-strong);
  transition: border-color var(--dur) var(--ease), background var(--dur) var(--ease);
}
.tl-item:hover::before { border-color: var(--accent); background: var(--accent); }
.tl-item__year {
  font-family: var(--font-mono);
  font-size: 0.72rem; letter-spacing: 0.1em;
  color: var(--accent);
  display: block; margin-bottom: var(--sp-1);
}
.tl-item__title { font-size: var(--step-0); font-weight: 700; margin-bottom: var(--sp-1); }
.tl-item__sub { color: var(--ink-mute); font-size: 0.9rem; margin: 0; }

/* ---------- contact ---------- */
.contact-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 220px), 1fr));
  gap: var(--sp-5);
}
.contact-item {
  border: var(--border-hair) solid var(--line);
  border-radius: var(--radius-lg);
  padding: var(--sp-5);
  text-decoration: none;
  color: inherit;
  display: block;
  transition: border-color var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.contact-item:hover { border-color: var(--accent); transform: translateY(-2px); }
.contact-item__label {
  font-family: var(--font-mono);
  font-size: 0.7rem; letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--ink-mute); display: block; margin-bottom: var(--sp-2);
}
.contact-item__value { font-weight: 700; font-size: 0.98rem; word-break: break-word; }

/* ---------- footer ---------- */
.site-footer {
  border-top: var(--border-hair) solid var(--line);
  padding-block: var(--sp-7);
  background: var(--bg-alt);
}
.site-footer__inner {
  display: flex; flex-wrap: wrap; gap: var(--sp-5);
  align-items: center; justify-content: space-between;
}
.site-footer p { margin: 0; font-size: 0.88rem; color: var(--ink-mute); }
.social { display: flex; gap: var(--sp-2); flex-wrap: wrap; }
.social a {
  width: 2.25rem; height: 2.25rem;
  display: grid; place-items: center;
  border: var(--border-hair) solid var(--line);
  border-radius: var(--radius);
  color: var(--ink-mute);
  transition: color var(--dur) var(--ease), border-color var(--dur) var(--ease),
              transform var(--dur) var(--ease);
}
.social a:hover { color: var(--accent); border-color: var(--accent); transform: translateY(-2px); }
.social svg { width: 1.05rem; height: 1.05rem; }

/* ---------- consent banner ---------- */
.consent {
  position: fixed;
  left: 1rem; right: 1rem; bottom: 1rem;
  z-index: 200;
  max-width: 30rem;
  margin-left: auto;
  background: var(--surface-raised);
  border: var(--border-hair) solid var(--line-strong);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lift);
  padding: var(--sp-5);
}
.consent[hidden] { display: none; }
.consent p { font-size: 0.9rem; color: var(--ink-soft); margin-bottom: var(--sp-4); }
.consent__actions { display: flex; gap: var(--sp-3); justify-content: flex-end; }
.consent .btn { padding: 0.5rem 1rem; font-size: 0.88rem; }

/* ---------- reveal on scroll ---------- */
.reveal { opacity: 0; transform: translateY(14px); }
.reveal.is-visible {
  opacity: 1; transform: none;
  transition: opacity 600ms var(--ease), transform 600ms var(--ease);
}
@media (prefers-reduced-motion: reduce) {
  .reveal { opacity: 1; transform: none; }
}

/* ---------- about ----------
   Rebuilt: the right column used to be a boxed list of eight certification
   names. It was a third hand-kept copy of that list (which is how a stale
   "CISM certified" survived here after both other pages were corrected), it
   gave equal weight to credentials with independently-marked scores and ones
   without, and it went nowhere. Now it shows only what an examiner marked,
   and ends in routes to the pages holding the rest. */
.about-grid {
  display: grid;
  gap: var(--sp-7);
  grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr);
  align-items: start;
}
@media (max-width: 860px) { .about-grid { grid-template-columns: 1fr; } }

.about-main p { color: var(--ink-soft); font-size: var(--step-0); }

.about-side {
  border: var(--border-hair) solid var(--line);
  border-radius: var(--radius-lg);
  padding: var(--sp-5);
  background: var(--surface);
}

/* A score someone else awarded is the only self-reported credential that is
   not self-reported. Mono, right-aligned, so the numbers form a column the
   eye can compare down rather than a ragged run of text. */
.about-scores { list-style: none; margin: 0 0 var(--sp-5); padding: 0; }
.about-score {
  display: flex; align-items: baseline; gap: var(--sp-4);
  padding: var(--sp-3) 0;
  border-bottom: var(--border-hair) solid var(--line);
}
.about-score:last-child { border-bottom: 0; }
.about-score__name { flex: 1; font-size: 0.88rem; color: var(--ink-soft); }
.about-score__val {
  font-family: var(--font-mono);
  font-size: 0.82rem; font-weight: 700;
  color: var(--accent);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

/* The way out of the panel. Three counts, each a door to the evidence. */
.about-routes {
  display: grid; gap: var(--sp-1);
  border-top: var(--border-hair) solid var(--line-strong);
  padding-top: var(--sp-4);
}
.about-route {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  gap: 0 var(--sp-3);
  padding: var(--sp-3) var(--sp-3) var(--sp-3) 0;
  border-radius: var(--radius);
  text-decoration: none; color: inherit;
  transition: background var(--dur) var(--ease), padding-inline-start var(--dur) var(--ease);
}
.about-route:hover {
  background: var(--bg-alt);
  padding-inline-start: var(--sp-3);
}
.about-route__n {
  font-family: var(--font-display); font-weight: 700;
  font-size: 0.98rem; color: var(--ink);
}
.about-route__sub {
  grid-column: 1;
  font-size: 0.78rem; color: var(--ink-mute); line-height: 1.4;
}
.about-route svg {
  grid-column: 2; grid-row: 1 / span 2;
  width: 1rem; height: 1rem; color: var(--accent);
  transition: transform var(--dur) var(--ease);
}
.about-route:hover svg { transform: translateX(3px); }

/* The hex string is a nod from the original site. It used to sit as a loose
   line between the prose and the chips, where it read like a rendering fault.
   Framed as a shell command with its own output it reads as what it is: a
   joke for the people who would actually run it. */
.about-strip {
  margin-top: var(--sp-7);
  padding-top: var(--sp-6);
  border-top: var(--border-hair) solid var(--line);
}
.about-hex {
  font-family: var(--font-mono);
  font-size: 0.76rem;
  letter-spacing: 0.06em;
  line-height: 1.9;
  color: var(--ink-mute);
  margin: 0;
  max-width: none;
  word-break: break-word;
}
.about-hex__prompt { color: var(--accent); margin-inline-end: 0.5ch; }
.about-hex__val { color: var(--ink-soft); }
.about-hex__pipe { color: var(--ink-mute); margin-inline-start: 0.5ch; }
.about-hex__out {
  font-family: var(--font-mono);
  font-size: 0.76rem;
  letter-spacing: 0.06em;
  color: var(--accent);
  margin: 0 0 var(--sp-5);
}
.about-hex__out::before { content: "\2192  "; opacity: 0.6; }

.about-roles { display: flex; flex-wrap: wrap; gap: var(--sp-2); }

.sr-only {
  position: absolute; width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap;
}

/* ---------- origin ----------
   Dark band that bookends the page against the globe hero. The 1997 photo is
   the only piece of evidence on an otherwise claim-based page, so it gets
   room rather than being reduced to a timeline dot. */
.origin {
  background: #04091f;
  color: #e8eef5;
  position: relative;
  overflow: hidden;
}
.origin::before {
  content: "";
  position: absolute; inset: 0;
  background:
    radial-gradient(ellipse 70% 60% at 18% 15%, rgba(58, 214, 255, 0.10), transparent 60%),
    radial-gradient(ellipse 60% 50% at 85% 90%, rgba(255, 64, 64, 0.08), transparent 60%);
  pointer-events: none;
}
.origin > .wrap { position: relative; z-index: 1; }


/* Two chips side by side: the solid date stamp, then the fact it marks.
   The photo already shows the burn-in, so the caption doesn't narrate it. */

/* Solid amber, picking up the camera's own burn-in colour. Text is the
   section's navy rather than white: white on #ffb020 is a 1.8:1 contrast
   ratio and fails WCAG outright, where navy lands at ~10.7:1. */
.origin-stamp {
  font-family: var(--font-mono);
  font-size: 1rem;
  font-weight: 700;
  letter-spacing: 0.18em;
  color: #04091f;
  background: #ffb020;
  border: 1px solid #ffb020;
  border-radius: var(--radius);
  padding: 0.45rem 0.8rem;
  flex: none;
  display: inline-flex;
  align-items: center;
}


.origin-lede {
  font-size: var(--step-1);
  color: #c3cede;
  border-left: 2px solid var(--red-hot);
  padding-left: var(--sp-5);
  margin-bottom: var(--sp-5);
}



/* ---------- client wall ----------
   Third-party marks: mixed formats, baked-in dark text, assorted backgrounds.
   A filter-only treatment breaks half of them in dark mode, so each sits on a
   light tile in BOTH themes. Desaturated at rest so the wall reads as one
   system; true colour on hover. */
.client-wall {
  list-style: none;
  margin: 0;
  padding: 0;
  /* Flex rather than grid, purely so the final row centres. With 33 marks a
     grid leaves the last five hard against the left edge with a wide gap
     beside them, which reads as a mistake rather than a wall. */
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--sp-3);
}
.client {
  /* Grows to share the row, capped so a sparse last row does not stretch into
     letterboxes. */
  flex: 1 1 152px;
  max-width: 205px;
  background: #fff;
  border: var(--border-hair) solid var(--line);
  border-radius: var(--radius);
  aspect-ratio: 5 / 3;
  display: grid;
  place-items: center;
  /* Zero, deliberately. The builder sizes each logo as a percentage of this
     box, so the box has to keep the tile's 5:3 shape — padding here made it
     1.93:1 and object-fit shrank every mark a second time. The breathing room
     is in LOGO_MAX_W / LOGO_MAX_H instead. */
  padding: 0;
  transition: border-color var(--dur) var(--ease), transform var(--dur) var(--ease),
              box-shadow var(--dur) var(--ease);
}
.client img {
  /* Width and height are set per logo by the builder (see logo_size) so every
     mark covers the same ink AREA. They are deliberately NOT set here.

     What was here before — max-height:100% with an auto width — is why the
     tiles were ragged: a percentage max-height cannot resolve against a box
     whose own height comes from aspect-ratio, so it behaved as `none` and the
     near-square marks (APS, ICE, Moonpig at ~1:1) pushed their tiles taller
     than the rest of the row. */
  object-fit: contain;
  filter: grayscale(1);
  opacity: 0.62;
  transition: filter var(--dur) var(--ease), opacity var(--dur) var(--ease);
}
.client:hover {
  border-color: var(--line-strong);
  transform: translateY(-2px);
  box-shadow: var(--shadow);
}
.client:hover img { filter: none; opacity: 1; }

/* Tiles stay white in dark mode too — pulled back so the wall doesn't glare
   against a dark page. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .client { opacity: 0.9; }
  :root:not([data-theme="light"]) .client:hover { opacity: 1; }
}
:root[data-theme="dark"] .client { opacity: 0.9; }
:root[data-theme="dark"] .client:hover { opacity: 1; }

/* ---------- back to top ----------
   The ring doubles as a read-progress indicator, which earns its keep on the
   long article pages. --p is written by site.js as a 0-1 fraction. */
.to-top {
  --p: 0;
  position: fixed;
  right: clamp(1rem, 3vw, 2rem);
  bottom: clamp(1rem, 3vw, 2rem);
  z-index: 90;
  width: 2.9rem;
  height: 2.9rem;
  display: grid;
  place-items: center;
  padding: 0;
  cursor: pointer;
  border: 0;
  border-radius: 50%;
  color: var(--ink);
  background: color-mix(in srgb, var(--surface-raised) 88%, transparent);
  backdrop-filter: saturate(1.5) blur(10px);
  -webkit-backdrop-filter: saturate(1.5) blur(10px);
  box-shadow: var(--shadow);
  opacity: 0;
  transform: translateY(8px) scale(0.94);
  transition: opacity var(--dur) var(--ease), transform var(--dur) var(--ease),
              color var(--dur) var(--ease);
}
.to-top[hidden] { display: none; }
.to-top[data-visible="false"] { pointer-events: none; }
.to-top[data-visible="true"] { opacity: 1; transform: none; }
.to-top:hover { color: var(--accent); }
.to-top:hover .to-top__ring { border-color: transparent; }
.to-top svg { width: 1.05rem; height: 1.05rem; position: relative; z-index: 1; }

/* Progress ring: conic fill masked to a 2px band so it reads as a stroke. */
.to-top__ring {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  border: 1px solid var(--line-strong);
  background: conic-gradient(var(--accent) calc(var(--p) * 360deg), transparent 0);
  -webkit-mask: radial-gradient(circle, transparent calc(50% - 2px), #000 calc(50% - 2px));
  mask: radial-gradient(circle, transparent calc(50% - 2px), #000 calc(50% - 2px));
  transition: border-color var(--dur) var(--ease);
}

/* Sit above the consent banner while it is open. */
body:has(#consent:not([hidden])) .to-top { bottom: calc(clamp(1rem, 3vw, 2rem) + 9rem); }

@media (prefers-reduced-motion: reduce) {
  .to-top { transition: opacity 1ms; transform: none; }
}

/* ---------- article pages ---------- */
.post { padding-block: var(--sp-8) var(--sp-9); }
.post__header { margin-bottom: var(--sp-7); }
.post__header h1 { font-size: var(--step-4); }
.post__header .eyebrow { margin-bottom: var(--sp-4); }

.post__body { font-size: 1.05rem; line-height: 1.75; }
.post__body > * { max-width: none; }
.post__body p { color: var(--ink-soft); }
.post__body h2 { font-size: var(--step-2); margin-top: var(--sp-8); }
.post__body h3 { font-size: var(--step-1); margin-top: var(--sp-7); }
.post__body h4 { margin-top: var(--sp-6); }
/* Migrated bodies contain <h1>s mid-article; demote visually so the page
   keeps one visual top level without rewriting author markup. */
.post__body h1 { font-size: var(--step-2); margin-top: var(--sp-8); }

.post__body img {
  border-radius: var(--radius);
  border: var(--border-hair) solid var(--line);
  margin-block: var(--sp-5);
}
.post__body figure { margin: var(--sp-6) 0; }
.post__body figcaption {
  font-size: 0.85rem; color: var(--ink-mute);
  margin-top: var(--sp-2); font-family: var(--font-mono);
}

.post__body ul, .post__body ol { padding-left: 1.25rem; color: var(--ink-soft); }
.post__body li { margin-bottom: var(--sp-2); }

.post__body blockquote {
  margin: var(--sp-6) 0;
  padding: var(--sp-2) 0 var(--sp-2) var(--sp-5);
  border-left: 3px solid var(--accent);
  color: var(--ink);
  font-size: var(--step-1);
}

.post__body code {
  font-family: var(--font-mono);
  font-size: 0.88em;
  background: var(--bg-alt);
  border: var(--border-hair) solid var(--line);
  border-radius: 3px;
  padding: 0.1em 0.35em;
}
.post__body pre {
  background: var(--bg-alt);
  border: var(--border-hair) solid var(--line);
  border-radius: var(--radius);
  padding: var(--sp-5);
  overflow-x: auto;
  font-size: 0.88rem;
  line-height: 1.6;
}
.post__body pre code { background: none; border: 0; padding: 0; font-size: inherit; }

/* Wide content scrolls inside its own box — never the page body. */
.table-scroll { overflow-x: auto; margin: var(--sp-6) 0; }
.post__body table { border-collapse: collapse; width: 100%; font-size: 0.92rem; }
.post__body th, .post__body td {
  border: var(--border-hair) solid var(--line);
  padding: var(--sp-3);
  text-align: left;
}
.post__body th { background: var(--bg-alt); font-weight: 700; }

.embed {
  position: relative;
  aspect-ratio: 16 / 9;
  margin: var(--sp-6) 0;
  border-radius: var(--radius);
  overflow: hidden;
  border: var(--border-hair) solid var(--line);
}
.embed iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }

.post__body hr { border: 0; border-top: var(--border-hair) solid var(--line); margin: var(--sp-7) 0; }

/* ---------- pager ---------- */
.pager {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr));
  gap: var(--sp-4);
  margin-top: var(--sp-8);
  padding-top: var(--sp-6);
  border-top: var(--border-hair) solid var(--line);
}
.pager__link {
  display: block;
  padding: var(--sp-4) var(--sp-5);
  border: var(--border-hair) solid var(--line);
  border-radius: var(--radius-lg);
  text-decoration: none;
  color: var(--ink);
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 0.95rem;
  line-height: 1.4;
  transition: border-color var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.pager__link:hover { border-color: var(--accent); transform: translateY(-2px); }
.pager__link span {
  display: block;
  font-family: var(--font-mono);
  font-size: 0.68rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-mute);
  margin-bottom: var(--sp-2);
  font-weight: 400;
}
.pager__link--next { text-align: right; }

.post__back { margin-top: var(--sp-6); font-family: var(--font-mono); font-size: 0.85rem; }

/* ---------- archive ---------- */
.year-block { margin-bottom: var(--sp-8); }
.year-block__year {
  font-family: var(--font-mono);
  font-size: 0.8rem;
  letter-spacing: 0.16em;
  color: var(--accent);
  margin-bottom: var(--sp-3);
}

/* ---------- portfolio tiles ---------- */
.tiles {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 260px), 1fr));
  gap: var(--sp-5);
}
.tile {
  display: flex; flex-direction: column;
  background: var(--surface);
  border: var(--border-hair) solid var(--line);
  border-radius: var(--radius-lg);
  overflow: hidden;
  text-decoration: none;
  color: inherit;
  transition: border-color var(--dur) var(--ease), transform var(--dur) var(--ease),
              box-shadow var(--dur) var(--ease);
}
.tile:hover { border-color: var(--accent); transform: translateY(-2px); box-shadow: var(--shadow); }
.tile__media {
  position: relative;
  display: block;
  aspect-ratio: 16 / 10;
  background: var(--surface-raised);
  overflow: hidden;
}
.tile__media img {
  width: 100%; height: 100%;
  object-fit: cover;
  display: block;
  transition: transform 480ms var(--ease);
}
.tile:hover .tile__media img { transform: scale(1.04); }
.tile__badge {
  position: absolute; inset-block-start: var(--sp-2); inset-inline-start: var(--sp-2);
  font-family: var(--font-mono);
  font-size: 0.62rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  padding: 2px 7px;
  border-radius: var(--radius);
  background: rgba(4, 9, 31, 0.78);
  color: #fff;
}
.tile__body { display: block; padding: var(--sp-4) var(--sp-4) var(--sp-5); }
.tile__title {
  display: block;
  font-family: var(--font-display);
  font-size: 0.98rem;
  line-height: 1.35;
  margin-bottom: var(--sp-2);
}
.tile__desc { display: block; font-size: 0.85rem; color: var(--ink-soft); line-height: 1.5; }
.tile__still img { width: 100%; height: auto; border-radius: var(--radius-lg); }

.section-more { margin-top: var(--sp-6); }

/* ---------- click-to-load video ----------
   No third-party request is made until the play button is pressed, so the page
   sets no YouTube/Vimeo cookies for a visitor who never watches. */
.vidbox {
  position: relative;
  aspect-ratio: 16 / 9;
  border-radius: var(--radius-lg);
  overflow: hidden;
  background: #04091f;
  margin-bottom: var(--sp-6);
}
.vidbox img, .vidbox iframe {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  border: 0;
  display: block;
}
.vidbox__play {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  display: grid; place-items: center;
  background: rgba(4, 9, 31, 0.34);
  border: 0;
  cursor: pointer;
  transition: background var(--dur) var(--ease);
}
.vidbox__play:hover { background: rgba(4, 9, 31, 0.5); }
.vidbox__play span {
  width: 68px; height: 68px;
  border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 6px 28px rgba(0, 0, 0, 0.45);
  position: relative;
  transition: transform var(--dur) var(--ease);
}
.vidbox__play:hover span { transform: scale(1.08); }
.vidbox__play span::after {
  content: "";
  position: absolute; inset-block-start: 50%; inset-inline-start: 54%;
  translate: -50% -50%;
  border-inline-start: 20px solid #fff;
  border-block-start: 12px solid transparent;
  border-block-end: 12px solid transparent;
}
.vidbox__host {
  position: absolute; inset-block-end: var(--sp-2); inset-inline-end: var(--sp-3);
  font-family: var(--font-mono);
  font-size: 0.62rem;
  letter-spacing: 0.1em;
  color: rgba(255, 255, 255, 0.72);
  pointer-events: none;
}
.vidbox[data-loaded="true"] .vidbox__play,
.vidbox[data-loaded="true"] .vidbox__host,
.vidbox[data-loaded="true"] > img { display: none; }

.post__lede { font-size: var(--step-1); color: var(--ink-soft); margin-top: var(--sp-3); }

/* ---------- career detail ---------- */
.cv-year { margin-bottom: var(--sp-8); }
.cv-year__year {
  font-family: var(--font-mono);
  font-size: 0.8rem;
  letter-spacing: 0.16em;
  color: var(--accent);
  margin-bottom: var(--sp-4);
  padding-bottom: var(--sp-2);
  border-bottom: var(--border-hair) solid var(--line);
}
.cv-year__items {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 320px), 1fr));
  gap: var(--sp-5);
}
.cv-item {
  background: var(--surface);
  border: var(--border-hair) solid var(--line);
  border-radius: var(--radius-lg);
  padding: var(--sp-5);
  scroll-margin-top: 96px;
}
.cv-item:target { border-color: var(--accent); box-shadow: var(--shadow); }
.cv-item__title { font-size: 1.02rem; margin-bottom: var(--sp-2); }
.cv-item__sub {
  font-family: var(--font-mono);
  font-size: 0.68rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--accent);
  margin-bottom: var(--sp-3);
}
.cv-item__body { font-size: 0.9rem; color: var(--ink-soft); line-height: 1.65; }
.cv-item__body > :last-child { margin-bottom: 0; }
.cv-item__body img { max-width: 100%; height: auto; border-radius: var(--radius); }

/* ---------- footer index ---------- */
.footer-nav {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3) var(--sp-5);
  padding-bottom: var(--sp-5);
  margin-bottom: var(--sp-5);
  border-bottom: var(--border-hair) solid var(--line);
}
.footer-nav a {
  font-family: var(--font-mono);
  font-size: 0.78rem;
  letter-spacing: 0.04em;
  color: var(--ink-soft);
  text-decoration: none;
}
.footer-nav a:hover { color: var(--accent); }

/* ---------- services ---------- */
.svc-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 300px), 1fr));
  gap: var(--sp-4);
}
.svc {
  display: flex; flex-direction: column;
  background: var(--surface);
  border: var(--border-hair) solid var(--line);
  border-radius: var(--radius-lg);
  padding: var(--sp-5);
  text-decoration: none;
  color: inherit;
  position: relative;
  overflow: hidden;
  transition: border-color var(--dur) var(--ease), transform var(--dur) var(--ease),
              box-shadow var(--dur) var(--ease);
}
.svc::before {
  content: "";
  position: absolute; inset-block-start: 0; inset-inline: 0;
  height: 2px;
  background: var(--accent);
  scale: 0 1;
  transform-origin: left;
  transition: scale var(--dur) var(--ease);
}
.svc:hover { border-color: var(--line-strong); transform: translateY(-2px); box-shadow: var(--shadow); }
.svc:hover::before { scale: 1 1; }
.svc__name { font-size: 1rem; margin-bottom: var(--sp-2); }
.svc__short {
  font-size: 0.88rem;
  color: var(--ink-soft);
  line-height: 1.6;
  margin-bottom: var(--sp-4);
  flex: 1;
}
.svc__more {
  font-family: var(--font-mono);
  font-size: 0.7rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--accent);
  display: inline-flex; align-items: center; gap: 6px;
}
.svc__more svg { width: 12px; height: 12px; }

.svc-toc {
  display: flex; flex-wrap: wrap;
  gap: var(--sp-2) var(--sp-3);
  padding-bottom: var(--sp-6);
  margin-bottom: var(--sp-7);
  border-bottom: var(--border-hair) solid var(--line);
}
.svc-toc a {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.04em;
  padding: 5px 10px;
  border: var(--border-hair) solid var(--line);
  border-radius: 100px;
  color: var(--ink-soft);
  text-decoration: none;
  white-space: nowrap;
}
.svc-toc a:hover { border-color: var(--accent); color: var(--accent); }

.svc-details { display: grid; gap: var(--sp-8); }
.svc-detail { scroll-margin-top: 96px; max-width: 70ch; }
.svc-detail h2 { font-size: var(--step-2); margin-bottom: var(--sp-3); }
.svc-detail__lede {
  font-size: var(--step-0);
  color: var(--accent);
  margin-bottom: var(--sp-4);
}
.svc-detail p { color: var(--ink-soft); line-height: 1.75; margin-bottom: var(--sp-4); }
.svc-detail__items {
  list-style: none;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 260px), 1fr));
  gap: var(--sp-2) var(--sp-5);
}
.svc-detail__items li {
  position: relative;
  padding-inline-start: 18px;
  font-size: 0.9rem;
  color: var(--ink-soft);
  line-height: 1.55;
}
.svc-detail__items li::before {
  content: "";
  position: absolute;
  inset-inline-start: 0; inset-block-start: 0.55em;
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--accent);
}

/* ---------- career roles ---------- */
.roles { display: grid; gap: var(--sp-5); }
.role {
  background: var(--surface);
  border: var(--border-hair) solid var(--line);
  border-inline-start: 3px solid var(--accent);
  border-radius: var(--radius-lg);
  padding: var(--sp-5) var(--sp-6);
}
.role__period {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-mute);
  margin-bottom: var(--sp-2);
}
.role__title { font-size: 1.08rem; margin-bottom: var(--sp-1); }
.role__org {
  font-size: 0.95rem;
  color: var(--accent);
  margin-bottom: var(--sp-3);
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2);
}
.role__tag {
  font-family: var(--font-mono);
  font-size: 0.6rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  padding: 2px 7px;
  border-radius: 100px;
  border: var(--border-hair) solid var(--line-strong);
  color: var(--ink-mute);
}
.role__tag--nda { border-color: var(--accent); color: var(--accent); }
.role__scope, .role__note {
  font-size: 0.85rem;
  color: var(--ink-mute);
  font-style: italic;
  margin-bottom: var(--sp-3);
}
.role__also {
  list-style: none;
  font-size: 0.85rem;
  color: var(--ink-mute);
  margin-bottom: var(--sp-3);
}
.role__also li::before { content: "\2937\00a0"; }
.role__points { list-style: none; display: grid; gap: var(--sp-2); }
.role__points li {
  position: relative;
  padding-inline-start: 18px;
  font-size: 0.9rem;
  color: var(--ink-soft);
  line-height: 1.6;
}
.role__points li::before {
  content: "";
  position: absolute;
  inset-inline-start: 0; inset-block-start: 0.6em;
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--line-strong);
}

/* ---------- cv page ---------- */
.pullquote {
  border-inline-start: 3px solid var(--accent);
  padding: var(--sp-4) 0 var(--sp-4) var(--sp-5);
  margin-bottom: var(--sp-8);
  font-family: var(--font-display);
  font-size: var(--step-1);
  line-height: 1.5;
  color: var(--ink);
  max-width: 62ch;
}
.cv-block { margin-bottom: var(--sp-8); }
.cv-block > h2 {
  font-size: var(--step-1);
  padding-bottom: var(--sp-2);
  margin-bottom: var(--sp-5);
  border-bottom: var(--border-hair) solid var(--line);
}
.cv-subhead { font-size: 0.95rem; margin: var(--sp-6) 0 var(--sp-3); }
.cv-lede { color: var(--ink-mute); font-size: 0.9rem; margin-bottom: var(--sp-4); }
.cv-prose { max-width: 70ch; }
.cv-prose p { color: var(--ink-soft); line-height: 1.75; margin-bottom: var(--sp-4); }
.cv-list { list-style: none; display: grid; gap: var(--sp-2); max-width: 80ch; }
.cv-list li {
  position: relative;
  padding-inline-start: 18px;
  color: var(--ink-soft);
  font-size: 0.92rem;
  line-height: 1.65;
}
.cv-list li::before {
  content: "";
  position: absolute;
  inset-inline-start: 0; inset-block-start: 0.6em;
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--accent);
}

.cert-grid {
  list-style: none;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 280px), 1fr));
  gap: var(--sp-3);
}
.cert {
  background: var(--surface);
  border: var(--border-hair) solid var(--line);
  border-radius: var(--radius);
  padding: var(--sp-3) var(--sp-4);
  display: flex; flex-direction: column; gap: 4px;
}
.cert__name { font-size: 0.9rem; line-height: 1.4; }
.cert__meta {
  font-family: var(--font-mono);
  font-size: 0.68rem;
  letter-spacing: 0.06em;
  color: var(--ink-mute);
}

.stack { display: grid; gap: var(--sp-5); }
.stack__group h3 {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--accent);
  margin-bottom: var(--sp-2);
}
.stack__group p { font-size: 0.88rem; color: var(--ink-soft); line-height: 1.9; }

/* ---------- clients named without a logo ---------- */
.client-also {
  display: flex; flex-wrap: wrap; align-items: baseline;
  gap: var(--sp-2) var(--sp-4);
  margin-top: var(--sp-5);
  padding-top: var(--sp-5);
  border-top: var(--border-hair) solid var(--line);
}
.client-also > span:first-child {
  font-family: var(--font-mono);
  font-size: 0.68rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-mute);
}
.client-also__name {
  font-family: var(--font-display);
  font-size: 0.95rem;
  color: var(--ink-soft);
}

/* ---------- library ---------- */
.lib-group { scroll-margin-top: 96px; margin-bottom: var(--sp-9); }
.lib-group__head {
  display: flex; flex-wrap: wrap; align-items: baseline;
  gap: var(--sp-2) var(--sp-4);
  padding-bottom: var(--sp-4);
  margin-bottom: var(--sp-6);
  border-bottom: var(--border-hair) solid var(--line);
}
.lib-group__head h2 { font-size: var(--step-2); }
.lib-group__head p { font-size: 0.9rem; color: var(--ink-mute); margin: 0; }

.lib-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 260px), 1fr));
  gap: var(--sp-5);
}

/* Card and non-card share every rule; only .lib-item--flat drops the
   affordances, so an item with no published file cannot look clickable. */
.lib-item {
  display: flex;
  flex-direction: column;
  background: var(--surface-raised);
  border: var(--border-hair) solid var(--line);
  border-radius: var(--radius-lg);
  overflow: hidden;
  text-decoration: none;
  color: inherit;
  transition: transform var(--dur) var(--ease),
              border-color var(--dur) var(--ease),
              box-shadow var(--dur) var(--ease);
}
a.lib-item:hover {
  transform: translateY(-3px);
  border-color: var(--accent);
  box-shadow: var(--shadow);
}
.lib-item--flat { opacity: 0.92; }

/* Covers are scans of real documents, 0.71 to 0.77 wide-to-tall and none of
   them the same. `contain` inside a fixed window keeps every title readable —
   cropping to a common shape would cut the words off the covers, which on a
   document is the only thing identifying it.
   The window is PORTRAIT for the same reason. In a 4:3 landscape box a 0.71
   cover is limited by height, so it rendered 116px wide inside a 260px card
   with dead air either side. At 3:4 the same cover fills the box and reads as
   a book rather than a thumbnail. */
.lib-cover {
  display: grid;
  place-items: center;
  aspect-ratio: 3 / 4;
  padding: var(--sp-2);
  background:
    linear-gradient(160deg, rgba(221, 51, 51, 0.05), transparent 65%),
    var(--bg-alt);
  border-bottom: var(--border-hair) solid var(--line);
  overflow: hidden;
}
.lib-cover img {
  /* 100% rather than max-width, deliberately. These are 2013-2016 WordPress
     uploads and the originals really are this small — 300x422 for most, 150x211
     for three of them; there is no larger file on the server to point at. With
     `width:auto` the browser never scales an image UP, so a 300px cover sat at
     300 CSS px inside its box and read as a postage stamp on a retina display.
     Filling the box upscales ~1.6x, which is soft but legible; a cover you
     cannot see is worse than one that is slightly soft. */
  width: 100%;
  height: 100%;
  object-fit: contain;
  border-radius: 2px;
  box-shadow: 0 2px 6px rgba(18, 22, 28, 0.18), 0 10px 24px rgba(18, 22, 28, 0.12);
  transition: transform var(--dur) var(--ease);
}
a.lib-item:hover .lib-cover img { transform: scale(1.035); }

.lib-mono {
  font-family: var(--font-display);
  font-size: 2.4rem;
  letter-spacing: 0.06em;
  color: var(--accent);
  opacity: 0.55;
}

.lib-text {
  display: flex;
  flex-direction: column;
  flex: 1;
  gap: var(--sp-2);
  padding: var(--sp-4) var(--sp-4) var(--sp-5);
}
.lib-meta { display: flex; flex-wrap: wrap; gap: var(--sp-2); align-items: center; }
.lib-kind, .lib-mine {
  font-family: var(--font-mono);
  font-size: 0.62rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  padding: 3px 7px;
  border-radius: 100px;
  white-space: nowrap;
}
.lib-kind { color: var(--ink-mute); border: var(--border-hair) solid var(--line-strong); }
.lib-mine { color: var(--accent-ink); background: var(--accent); }
.lib-title {
  font-family: var(--font-display);
  font-size: 0.98rem;
  font-weight: 700;
  line-height: 1.35;
}
.lib-desc {
  font-size: 0.84rem;
  color: var(--ink-soft);
  line-height: 1.6;
  flex: 1;
}
.lib-cta {
  display: inline-block;
  font-family: var(--font-mono);
  font-size: 0.7rem;
  letter-spacing: 0.06em;
  color: var(--accent);
  margin-top: var(--sp-1);
  text-decoration: none;
}
/* Nested inside .lib-item--flat, which is a <div> — a link here is legal and
   is the only clickable thing on a card whose document is not published. */
.lib-cta--ask { color: var(--ink-mute); align-self: flex-start; }
.lib-cta--ask:hover { color: var(--accent); }

@media (max-width: 520px) {
  /* Two up rather than one: at this width a single column of 4:3 covers
     turns the page back into the scroll it was rebuilt to replace. */
  .lib-grid { grid-template-columns: repeat(2, 1fr); gap: var(--sp-3); }
  .lib-cover { padding: var(--sp-2); }
  .lib-text { padding: var(--sp-3); gap: var(--sp-1); }
  .lib-desc { font-size: 0.78rem; }
}

/* ---------- toolkit ---------- */
.tool-warning {
  max-width: var(--measure);
  margin-bottom: var(--sp-6);
  padding: var(--sp-4) var(--sp-5);
  border-inline-start: 3px solid var(--accent);
  border-radius: 0 var(--radius) var(--radius) 0;
  background: var(--bg-alt);
  font-size: 0.88rem;
  color: var(--ink-soft);
  line-height: 1.7;
}

.tool-group { scroll-margin-top: 96px; margin-bottom: var(--sp-8); }
.tool-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 280px), 1fr));
  gap: var(--sp-4);
}

/* No cover art on this page. There never was any — every one of the thirteen
   original entries reused the same unrelated placeholder — so the card is
   typographic and the name does the identifying. */
.tool {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  padding: var(--sp-4) var(--sp-5);
  background: var(--surface-raised);
  border: var(--border-hair) solid var(--line);
  border-inline-start: 3px solid var(--line-strong);
  border-radius: var(--radius);
  text-decoration: none;
  color: inherit;
  transition: transform var(--dur) var(--ease),
              border-color var(--dur) var(--ease),
              box-shadow var(--dur) var(--ease);
}
a.tool:hover {
  transform: translateY(-2px);
  border-color: var(--accent);
  border-inline-start-color: var(--accent);
  box-shadow: var(--shadow);
}
.tool--mine { border-inline-start-color: var(--accent); }
.tool--retired { opacity: 0.55; }
.tool--flat { cursor: default; }

.tool-head {
  display: flex; flex-wrap: wrap; align-items: baseline;
  gap: var(--sp-2) var(--sp-3);
}
.tool-name {
  font-family: var(--font-display);
  font-size: 0.98rem;
  font-weight: 700;
  line-height: 1.3;
}
a.tool:hover .tool-name { color: var(--accent); }
.tool-tag {
  font-family: var(--font-mono);
  font-size: 0.6rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-mute);
  padding: 2px 6px;
  border: var(--border-hair) solid var(--line-strong);
  border-radius: 100px;
  white-space: nowrap;
}
.tool--mine .tool-tag { color: var(--accent-ink); background: var(--accent); border-color: var(--accent); }
.tool-desc { font-size: 0.84rem; color: var(--ink-soft); line-height: 1.6; }

/* ---------- skills ---------- */
.skill-split {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 380px), 1fr));
  gap: var(--sp-8);
  margin-bottom: var(--sp-9);
}
.skill-col > h2 {
  font-size: var(--step-1);
  padding-bottom: var(--sp-2);
  margin-bottom: var(--sp-5);
  border-bottom: var(--border-hair) solid var(--line);
}
.skill-note { font-size: 0.88rem; color: var(--ink-mute); margin-bottom: var(--sp-4); }

.cert-grid { list-style: none; display: grid; gap: var(--sp-3); }
.cert-card {
  display: grid;
  /* The score sits in its own column so every chip lines up down the right
     edge — the whole point of keeping the scores is that they are comparable
     at a glance, which a chip floating after variable-length text is not. */
  grid-template-columns: 1fr auto;
  align-items: center;
  gap: var(--sp-1) var(--sp-4);
  padding: var(--sp-3) var(--sp-4);
  background: var(--surface-raised);
  border: var(--border-hair) solid var(--line);
  border-inline-start: 3px solid var(--accent);
  border-radius: var(--radius);
}
.cert-name { font-weight: 700; font-size: 0.92rem; line-height: 1.35; }
.cert-meta {
  grid-column: 1;
  font-family: var(--font-mono);
  font-size: 0.68rem;
  letter-spacing: 0.04em;
  color: var(--ink-mute);
}
.cert-score {
  grid-column: 2; grid-row: 1 / span 2;
  font-family: var(--font-mono);
  font-size: 0.78rem;
  font-weight: 700;
  color: var(--accent);
  white-space: nowrap;
}

/* An expired credential is still evidence of the work, so it stays — but it
   loses the accent spine and says "expired" on its face, so no one has to
   read the date to know. */
.cert-card--expired { border-inline-start-color: var(--line-strong); opacity: 0.82; }
.cert-state {
  display: inline-block;
  margin-inline-start: var(--sp-3);
  font-family: var(--font-mono);
  font-size: 0.6rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-mute);
  border: var(--border-hair) solid var(--line-strong);
  border-radius: 999px;
  padding: 0.1rem 0.45rem;
  vertical-align: 0.15em;
}

/* A link to a third-party badge, sitting where a self-reported score would
   have been. It occupies the same cell, so the column still aligns. */
.cert-verify {
  grid-column: 2; grid-row: 1 / span 2;
  display: inline-flex; align-items: center; gap: var(--sp-2);
  font-family: var(--font-mono);
  font-size: 0.66rem;
  letter-spacing: 0.04em;
  color: var(--ink-mute);
  text-decoration: none;
  white-space: nowrap;
  transition: color var(--dur) var(--ease);
}
.cert-verify:hover { color: var(--accent); }
.cert-verify svg { width: 0.8rem; height: 0.8rem; flex: none; }

.xp-list { list-style: none; display: grid; gap: var(--sp-2); }
.xp-row {
  display: flex; align-items: baseline; gap: var(--sp-3);
  padding: var(--sp-3) 0;
  border-bottom: var(--border-hair) solid var(--line);
}
.xp-name { flex: 1; font-size: 0.92rem; line-height: 1.45; }
.xp-name a { color: inherit; text-decoration: none; }
.xp-name a:hover { color: var(--accent); text-decoration: underline; }
.xp-level {
  font-family: var(--font-mono);
  font-size: 0.62rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  padding: 3px 8px;
  border-radius: 100px;
  white-space: nowrap;
}
.xp-level--expert { color: var(--accent-ink); background: var(--accent); }
.xp-level--advanced { color: var(--ink-mute); border: var(--border-hair) solid var(--line-strong); }

.skill-breadth > h2 {
  font-size: var(--step-1);
  padding-bottom: var(--sp-2);
  margin-bottom: var(--sp-6);
  border-bottom: var(--border-hair) solid var(--line);
}
.skill-group { margin-bottom: var(--sp-7); scroll-margin-top: 96px; }
.skill-group h3 { font-size: 1rem; }

/* Tags, not bars. The WordPress original scored each of these out of 100
   against no stated scale; a flat list says the same thing without inviting
   a reader to compare numbers that were never measured. */
.tag-cloud {
  list-style: none;
  display: flex; flex-wrap: wrap;
  gap: var(--sp-2);
}
.tag-cloud li {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.02em;
  padding: 5px 10px;
  color: var(--ink-soft);
  background: var(--bg-alt);
  border: var(--border-hair) solid var(--line);
  border-radius: 100px;
}

/* ---------- career field notes ---------- */
.note-year { margin-bottom: var(--sp-8); scroll-margin-top: 96px; }
.note-year__year {
  font-family: var(--font-mono);
  font-size: 0.78rem;
  letter-spacing: 0.2em;
  color: var(--accent);
  padding-bottom: var(--sp-2);
  margin-bottom: var(--sp-5);
  border-bottom: var(--border-hair) solid var(--line);
}
.note-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 320px), 1fr));
  /* Rows size to their own content rather than the tallest in the row, so a
     three-line note does not inherit the height of a note with a video. */
  align-items: start;
  gap: var(--sp-5);
}
.note {
  display: flex;
  flex-direction: column;
  background: var(--surface-raised);
  border: var(--border-hair) solid var(--line);
  border-radius: var(--radius-lg);
  overflow: hidden;
  scroll-margin-top: 96px;
}
.note__media { display: block; line-height: 0; }
.note__media img {
  width: 100%; height: auto;
  aspect-ratio: 16 / 9;
  object-fit: cover;
}
/* The shared .vidbox carries a bottom margin for use inside prose; inside a
   card it sits flush against the text block instead. */
.note .vidbox { margin-bottom: 0; border-radius: 0; }
.note__text { padding: var(--sp-4) var(--sp-5) var(--sp-5); }
.note__title { font-size: 1rem; line-height: 1.35; margin-bottom: var(--sp-1); }
.note__sub {
  font-family: var(--font-mono);
  font-size: 0.68rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-mute);
  margin-bottom: var(--sp-3);
}
.note__body { font-size: 0.86rem; color: var(--ink-soft); line-height: 1.65; }
.note__body p { margin-bottom: var(--sp-3); }
.note__body p:last-child { margin-bottom: 0; }
.note__body img { max-width: 100%; height: auto; border-radius: var(--radius); }
.note__body a { color: var(--accent); }
.note__source {
  margin-top: var(--sp-3);
  font-family: var(--font-mono);
  font-size: 0.68rem;
  letter-spacing: 0.06em;
}
.note__source a { color: var(--ink-mute); text-decoration: none; }
.note__source a:hover { color: var(--accent); }

/* ---------- topic chips on an article ---------- */
.topics {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--sp-2);
  margin-block: var(--sp-7) 0;
  padding-top: var(--sp-5);
  border-top: var(--border-hair) solid var(--line);
}
.topics > span {
  font-family: var(--font-mono);
  font-size: 0.66rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-mute);
  margin-inline-end: var(--sp-1);
}
.topics a {
  font-family: var(--font-mono);
  font-size: 0.7rem;
  padding: 4px 10px;
  border: var(--border-hair) solid var(--line);
  border-radius: 100px;
  color: var(--ink-soft);
  text-decoration: none;
}
.topics a:hover { border-color: var(--accent); color: var(--accent); }

/* ---------- links to a shipped document ---------- */
.doc-link {
  display: inline-flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-4);
  border: var(--border-hair) solid var(--line);
  border-radius: var(--radius);
  background: var(--bg-alt);
  text-decoration: none;
  color: var(--ink);
  font-size: 0.9rem;
}
.doc-link:hover { border-color: var(--accent); color: var(--accent); }
.doc-link__type {
  font-family: var(--font-mono);
  font-size: 0.6rem;
  letter-spacing: 0.1em;
  padding: 3px 6px;
  border-radius: 3px;
  color: var(--accent-ink);
  background: var(--accent);
}

/* ---------- career: the two tracks ---------- */
.roles__label {
  font-family: var(--font-mono);
  font-size: 0.68rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-mute);
  margin: var(--sp-8) 0 var(--sp-5);
  padding-bottom: var(--sp-2);
  border-bottom: var(--border-hair) solid var(--line);
}
.roles__label:first-child { margin-top: 0; }

/* The constant carries the accent rail; the engagements beside it do not, so
   the two tracks are distinguishable before a word is read. */
.role--constant {
  border-inline-start: 3px solid var(--accent);
  padding-inline-start: var(--sp-5);
  background: var(--bg-alt);
  border-radius: 0 var(--radius-lg) var(--radius-lg) 0;
  padding-block: var(--sp-5);
  padding-inline-end: var(--sp-5);
}
.role__tag--now { color: var(--accent-ink); background: var(--accent); }
.role__orgnote {
  font-size: 0.85rem;
  color: var(--ink-mute);
  margin-top: var(--sp-1);
}
.role__service {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  line-height: 1.6;
  color: var(--ink-mute);
  margin-top: var(--sp-2);
  padding-inline-start: var(--sp-3);
  border-inline-start: 2px solid var(--line-strong);
}

/* ---------- long-form articles ---------- */
.reading-bar {
  position: fixed; inset-block-start: 0; inset-inline: 0;
  height: 2px; z-index: 60;
  pointer-events: none;
}
.reading-bar span {
  display: block; height: 100%;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: 0 50%;
}

.longform {
  display: grid;
  /* Rail first in the source only on desktop — on a phone the contents list
     sits above the article, where it works as a summary rather than a rail. */
  grid-template-columns: minmax(0, 1fr);
  gap: var(--sp-6);
}
.longform__body { max-width: 72ch; }

@media (min-width: 1024px) {
  .longform {
    grid-template-columns: 230px minmax(0, 1fr);
    gap: var(--sp-8);
    align-items: start;
  }
  .longform__rail { position: sticky; top: 92px; }
  .toc__list {
    max-height: calc(100vh - 180px);
    overflow-y: auto;
    scrollbar-width: thin;
  }
}

.toc {
  border-inline-start: 2px solid var(--line);
  padding-inline-start: var(--sp-4);
}
.toc__label {
  font-family: var(--font-mono);
  font-size: 0.64rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-mute);
  margin-bottom: var(--sp-3);
}
.toc__list { list-style: none; counter-reset: toc; }
.toc__item a {
  display: block;
  padding: 4px 0;
  font-size: 0.8rem;
  line-height: 1.4;
  color: var(--ink-mute);
  text-decoration: none;
  border-inline-start: 2px solid transparent;
  margin-inline-start: -18px;
  padding-inline-start: 16px;
}
.toc__item a:hover { color: var(--ink); }
.toc__item--3 a { padding-inline-start: 28px; font-size: 0.76rem; }
.toc__item a[aria-current] {
  color: var(--accent);
  border-inline-start-color: var(--accent);
}

/* The anchor is present for everyone but only inked on hover, so a heading
   reads as a heading and still offers a link when someone wants one. */
.head-anchor {
  margin-inline-start: 0.4em;
  color: var(--accent);
  opacity: 0;
  text-decoration: none;
  font-weight: 400;
  transition: opacity var(--dur) var(--ease);
}
h2:hover .head-anchor, h3:hover .head-anchor,
.head-anchor:focus-visible { opacity: 0.7; }

.post--long h2, .post--long h3 { scroll-margin-top: 92px; }

/* ---------- code blocks ---------- */
pre.code {
  position: relative;
  overflow-x: auto;
  padding: var(--sp-4) var(--sp-5);
  margin-block: var(--sp-5);
  background: var(--bg-alt);
  border: var(--border-hair) solid var(--line);
  border-inline-start: 3px solid var(--accent);
  border-radius: var(--radius);
  font-family: var(--font-mono);
  font-size: 0.78rem;
  line-height: 1.7;
  tab-size: 4;
}
pre.code code {
  font: inherit;
  background: none;
  padding: 0;
  white-space: pre;
}
/* A listing is data to scan, not a program to read — tighter and taller, and
   it scrolls in its own box rather than growing the page to a thousand rows. */
pre.code--listing {
  max-height: 26rem;
  overflow-y: auto;
  line-height: 1.55;
}
pre.code[data-lang]::before {
  content: attr(data-lang);
  position: absolute; inset-block-start: 0; inset-inline-start: 0;
  font-size: 0.58rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--accent-ink);
  background: var(--accent);
  padding: 2px 8px;
  border-end-end-radius: var(--radius);
}
.code__copy {
  position: absolute; inset-block-start: 6px; inset-inline-end: 6px;
  font-family: var(--font-mono);
  font-size: 0.62rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 4px 9px;
  border: var(--border-hair) solid var(--line-strong);
  border-radius: 100px;
  background: var(--surface-raised);
  color: var(--ink-mute);
  cursor: pointer;
  opacity: 0;
  transition: opacity var(--dur) var(--ease), color var(--dur) var(--ease);
}
pre.code:hover .code__copy, .code__copy:focus-visible { opacity: 1; }
.code__copy:hover { color: var(--accent); border-color: var(--accent); }
.code__copy.is-done { opacity: 1; color: var(--accent); border-color: var(--accent); }

/* ---------- research & tooling ----------
   Four equal cards in a three-column grid left an orphan on row two. The PhD
   project is also plainly not peer to a retired side project, so it leads as a
   wide exhibit with its video and the other three form one clean row. The
   hierarchy solves the layout problem and tells the truth at the same time. */
/* The title block spans both columns rather than sitting in the copy column.
   With the heading inside it, the copy ran ~490px against a 16:9 video at
   ~350px, and the difference showed as a slab of dead surface under the
   video — the same fault as the origin section. Lifting the header out takes
   ~140px off the copy side and the two columns land within a line of each
   other. */
.rsrch--lead {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  grid-template-areas:
    "head  head"
    "media copy";
  gap: var(--sp-5) var(--sp-7);
  align-items: start;
  padding: var(--sp-6);
  margin-bottom: var(--sp-6);
  background: var(--surface);
  border: var(--border-hair) solid var(--line);
  border-inline-start: 3px solid var(--accent);
  border-radius: var(--radius-lg);
}
.rsrch__lead-head { grid-area: head; }
.rsrch__media { grid-area: media; }
.rsrch__lead-copy { grid-area: copy; }
@media (max-width: 860px) {
  .rsrch--lead {
    grid-template-columns: 1fr;
    grid-template-areas: "head" "media" "copy";
    gap: var(--sp-5);
  }
}
/* .vidbox carries a 2rem bottom margin for use in article bodies. Inside a
   grid cell that is just a gap the layout has already allowed for. */
.rsrch__media .vidbox { margin-bottom: 0; }
.rsrch__lead-copy > p { max-width: none; }
.rsrch__lead-copy > p:last-of-type { margin-bottom: 0; }

.rsrch-grid {
  display: grid;
  gap: var(--sp-5);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 290px), 1fr));
}
.rsrch {
  display: flex; flex-direction: column;
  padding: var(--sp-6);
  background: var(--surface);
  border: var(--border-hair) solid var(--line);
  border-radius: var(--radius-lg);
  transition: border-color var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.rsrch-grid .rsrch:hover { border-color: var(--line-strong); transform: translateY(-2px); }

/* The eyebrow used to be a 45-character subtitle chopped at 38, mid-word.
   Now it is a fixed category plus a state, which is the pair of facts a
   reader actually needs: what kind of thing is this, and is it alive. */
.rsrch__kind {
  display: flex; align-items: center; gap: var(--sp-3);
  margin: 0 0 var(--sp-3);
  font-family: var(--font-mono);
  font-size: 0.68rem; letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--ink-mute);
}
/* In the row of three, the chip is pushed to the card's right edge so the
   states line up as a column. NOT in the lead card — that header spans the
   full width, so an auto margin would fling the chip a thousand pixels from
   the words it belongs to. */
.rsrch-grid .rsrch__state { margin-inline-start: auto; }
.rsrch__state {
  font-size: 0.6rem; letter-spacing: 0.1em;
  padding: 0.12rem 0.5rem;
  border-radius: 999px;
  border: var(--border-hair) solid var(--line-strong);
}
.rsrch__state--live { color: #24c07a; border-color: rgba(36, 192, 122, 0.5); }
.rsrch__state--research { color: #ffb020; border-color: rgba(255, 176, 32, 0.5); }
.rsrch__state--private,
.rsrch__state--archived { color: var(--ink-mute); }

.rsrch__name {
  margin: 0 0 var(--sp-3);
  font-size: var(--step-1); line-height: 1.25;
}
.rsrch__name--lead { font-size: var(--step-2); }
.rsrch__tagline {
  margin: 0 0 var(--sp-4);
  color: var(--ink); font-size: 0.95rem; line-height: 1.5; font-weight: 700;
  max-width: 62ch; text-wrap: balance;
}
/* The lead tagline sits under a full-width header, so without a measure it
   would run the whole 1116px card and read as a banner rather than a line. */
.rsrch--lead .rsrch__tagline { font-size: var(--step-0); max-width: 70ch; }
.rsrch__body {
  margin: 0; color: var(--ink-soft); font-size: 0.9rem; line-height: 1.7;
  max-width: none;
}

.rsrch__caps, .rsrch__modes {
  list-style: none; margin: var(--sp-4) 0 0; padding: 0;
  display: grid; gap: var(--sp-2);
}
.rsrch__caps li, .rsrch__modes li {
  position: relative;
  padding-inline-start: var(--sp-5);
  font-size: 0.86rem; color: var(--ink-soft); line-height: 1.5;
}
.rsrch__caps li::before, .rsrch__modes li::before {
  content: ""; position: absolute;
  inset-inline-start: 0; inset-block-start: 0.55em;
  width: 5px; height: 5px; border-radius: 50%;
  background: var(--accent);
}

/* Two of these three domains no longer resolve. A card that links a reader
   into a dead site is worse than one that says the site is gone, so the
   state line replaces the call to action rather than sitting beside it. */
.rsrch__cta, .rsrch__note { margin: var(--sp-5) 0 0; padding-top: var(--sp-4);
  border-top: var(--border-hair) solid var(--line); }
.rsrch__cta {
  display: inline-flex; align-items: center; gap: var(--sp-2);
  font-size: 0.88rem; font-weight: 700;
  color: var(--accent); text-decoration: none;
  transition: gap var(--dur) var(--ease);
}
.rsrch__cta:hover { gap: var(--sp-3); }
.rsrch__cta svg { width: 0.9rem; height: 0.9rem; }
.rsrch__note, .rsrch__site {
  font-family: var(--font-mono);
  font-size: 0.7rem; letter-spacing: 0.04em; line-height: 1.6;
  color: var(--ink-mute); max-width: none;
}
.rsrch__site { margin: var(--sp-5) 0 0; }
.rsrch__site-name { color: var(--ink-soft); }
/* The footer line sits on the floor of every card in the row, so three cards
   of different body lengths still align along their bottom rule. */
.rsrch-grid .rsrch > :last-child { margin-top: auto; }

/* ---------- recommendations ----------
   Quote first, attribution second, proof third. The pull line is the one
   sentence worth reading if you read nothing else; the full text sits under
   it; the original screenshot is one disclosure away. A transcription with no
   original is only a claim, so the proof is always present — just not
   competing with the words for attention. */
.rec-list {
  display: grid;
  gap: var(--sp-6);
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 420px), 1fr));
  align-items: start;
}
.rec {
  background: var(--surface);
  border: var(--border-hair) solid var(--line);
  border-inline-start: 3px solid var(--accent);
  border-radius: var(--radius-lg);
  padding: var(--sp-6);
  scroll-margin-top: 5.5rem;
}
.rec:target { box-shadow: var(--glow); border-color: var(--accent); }

/* A signed, countersigned letter is stronger evidence than a LinkedIn post,
   so it gets the wider column and a warmer spine. */
.rec--letter { grid-column: 1 / -1; border-inline-start-color: #ffb020; }
.rec--announcement { border-inline-start-color: var(--line-strong); }

.rec__pull {
  margin: 0 0 var(--sp-5);
  font-family: var(--font-display);
  font-size: var(--step-1);
  line-height: 1.4;
  color: var(--ink);
  text-wrap: pretty;
}
.rec__pull::before { content: "\201C"; color: var(--accent); margin-inline-end: 0.1em; }
.rec__pull::after  { content: "\201D"; color: var(--accent); }

.rec__body { color: var(--ink-soft); font-size: 0.94rem; line-height: 1.7; }
.rec__body p { margin-bottom: var(--sp-4); max-width: none; }
.rec__body p:last-child { margin-bottom: 0; }

.rec__who {
  margin-top: var(--sp-5);
  padding-top: var(--sp-4);
  border-top: var(--border-hair) solid var(--line);
}
.rec__name { margin: 0; font-weight: 700; font-size: 0.95rem; color: var(--ink); }
.rec__role { margin: 0.15rem 0 0; font-size: 0.86rem; color: var(--ink-soft); }
.rec__meta {
  margin: var(--sp-2) 0 0;
  font-family: var(--font-mono);
  font-size: 0.66rem; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--ink-mute);
}

.rec-proof { margin-top: var(--sp-4); }
.rec-proof summary {
  cursor: pointer;
  font-family: var(--font-mono);
  font-size: 0.68rem; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--ink-mute);
  padding: var(--sp-2) 0;
  transition: color var(--dur) var(--ease);
}
.rec-proof summary:hover { color: var(--accent); }
.rec-proof[open] summary { color: var(--accent); margin-bottom: var(--sp-3); }
.rec-proof img {
  width: 100%; height: auto;
  border: var(--border-hair) solid var(--line);
  border-radius: var(--radius);
  /* The originals are screenshots of white UI. On the dark theme an unbounded
     white rectangle is a flashbang, so it is inset and slightly dimmed until
     hovered. */
  background: #fff;
}
:root[data-theme="dark"] .rec-proof img {
  opacity: 0.86; transition: opacity var(--dur) var(--ease);
}
:root[data-theme="dark"] .rec-proof img:hover { opacity: 1; }
.rec-doc {
  display: inline-flex; align-items: center; gap: var(--sp-2);
  margin-top: var(--sp-3);
  font-family: var(--font-mono); font-size: 0.7rem; letter-spacing: 0.06em;
  color: var(--ink-mute); text-decoration: none;
}
.rec-doc:hover { color: var(--accent); }
.rec-doc svg { width: 0.8rem; height: 0.8rem; }

.rec-note {
  margin-top: var(--sp-7);
  font-size: 0.84rem; color: var(--ink-mute); max-width: 60ch;
}

/* ---------- the way in, since this page is not in the nav ---------- */
.rec-cta-band { padding-block: var(--sp-7); }
.rec-cta {
  display: flex; align-items: center; gap: var(--sp-5);
  padding: var(--sp-6);
  background: var(--surface);
  border: var(--border-hair) solid var(--line);
  border-inline-start: 3px solid var(--accent);
  border-radius: var(--radius-lg);
  text-decoration: none; color: inherit;
  transition: border-color var(--dur) var(--ease), transform var(--dur) var(--ease),
              box-shadow var(--dur) var(--ease);
}
.rec-cta:hover {
  border-color: var(--accent);
  transform: translateY(-2px);
  box-shadow: var(--shadow-lift);
}
.rec-cta__text { display: block; }
.rec-cta__label {
  display: block;
  font-family: var(--font-mono);
  font-size: 0.66rem; letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--accent);
  margin-bottom: var(--sp-2);
}
.rec-cta__head {
  display: block;
  font-family: var(--font-display); font-weight: 700;
  font-size: var(--step-1); line-height: 1.25; color: var(--ink);
}
.rec-cta__sub {
  display: block;
  margin-top: var(--sp-2);
  font-size: 0.88rem; line-height: 1.6; color: var(--ink-soft);
  max-width: 62ch;
}
.rec-cta__go { flex: none; color: var(--accent); }
.rec-cta__go svg { width: 1.5rem; height: 1.5rem; }
.rec-cta:hover .rec-cta__go { transform: translateX(3px); }
@media (max-width: 600px) {
  .rec-cta { flex-direction: column; align-items: flex-start; gap: var(--sp-4); }
}

/* ---------- portfolio gallery ----------
   The twenty items that were a screenshot and a sentence. Denser than .tile
   because there is no page to click through to — the card IS the item, so it
   shows the whole caption rather than a truncated teaser. */
.gal-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 320px), 1fr));
  gap: var(--sp-6);
}
.gal {
  margin: 0;
  background: var(--surface);
  border: var(--border-hair) solid var(--line);
  border-radius: var(--radius-lg);
  overflow: hidden;
  display: flex; flex-direction: column;
  /* Old /portfolio/<slug>/ URLs now redirect to #<slug> on this page, so the
     card has to clear the sticky header when it is the jump target. */
  scroll-margin-top: 5.5rem;
}
.gal:target { border-color: var(--accent); box-shadow: var(--glow); }
.gal__media {
  position: relative; display: block;
  aspect-ratio: 16 / 10;
  background: var(--surface-raised);
  overflow: hidden;
}
.gal__media img { width: 100%; height: 100%; object-fit: cover; }
.gal .embed, .gal .video-facade { margin: 0; border: 0; border-radius: 0; }
.gal__cap {
  padding: var(--sp-4) var(--sp-5) var(--sp-5);
  display: flex; flex-direction: column; gap: var(--sp-2);
}
.gal__title {
  font-family: var(--font-display);
  font-weight: 700; font-size: 0.98rem; line-height: 1.35;
  color: var(--ink);
}
.gal__desc { font-size: 0.88rem; line-height: 1.6; color: var(--ink-soft); }
.gal__date {
  font-family: var(--font-mono);
  font-size: 0.66rem; letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--ink-mute);
  margin-top: auto; padding-top: var(--sp-2);
}

/* ---------- 404 ---------- */
.page-404 { min-height: 60svh; display: flex; align-items: center; }
.page-404 h1 { font-size: var(--step-4); }
.page-404__lede { font-size: var(--step-1); color: var(--ink-soft); }
.page-404__actions { margin-top: var(--sp-6); }

/* ---------- replacements for WordPress inline styles ----------
   The old bodies carried style="" attributes from the visual editor. The only
   ones expressing real intent were a percentage width on a screenshot and the
   odd centred caption; wpclean rounds those to the nearest quarter and emits
   one of these classes instead, so the intent survives in the stylesheet
   where it belongs. Everything else was theme residue and is simply gone. */
.post__body .w25 { width: 25%; height: auto; }
.post__body .w50 { width: 50%; height: auto; }
.post__body .w75 { width: 75%; height: auto; }
.post__body .ta-center { text-align: center; }
@media (max-width: 600px) {
  /* A quarter-width screenshot is unreadable on a phone. Below the fold of a
     narrow column the width hint stops helping and starts hiding detail. */
  .post__body .w25, .post__body .w50, .post__body .w75 { width: 100%; }
}

/* ---------- a run of link-only paragraphs ---------- */
.linklist {
  list-style: none;
  columns: 2;
  column-gap: var(--sp-6);
  margin-block: var(--sp-5);
}
@media (min-width: 900px) { .linklist { columns: 3; } }
.linklist li {
  break-inside: avoid;
  padding: 4px 0;
  font-size: 0.84rem;
  line-height: 1.45;
}
.linklist a { color: var(--ink-soft); text-decoration: none; }
.linklist a:hover { color: var(--accent); text-decoration: underline; }

/* ---------- live environments ---------- */
.env-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 290px), 1fr));
  gap: var(--sp-4);
}
.env {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  padding: var(--sp-4) var(--sp-5) var(--sp-4);
  background: var(--surface-raised);
  border: var(--border-hair) solid var(--line);
  border-radius: var(--radius-lg);
  text-decoration: none;
  color: inherit;
  transition: transform var(--dur) var(--ease),
              border-color var(--dur) var(--ease),
              box-shadow var(--dur) var(--ease);
}
.env:hover {
  transform: translateY(-3px);
  border-color: var(--accent);
  box-shadow: var(--shadow);
}
.env__head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--sp-3); }
.env__name { font-family: var(--font-display); font-weight: 700; font-size: 0.98rem; }
.env__state {
  font-family: var(--font-mono);
  font-size: 0.58rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  padding: 3px 7px;
  border-radius: 100px;
  white-space: nowrap;
}
.env__state--near { color: var(--accent-ink); background: var(--accent); }
.env__state--dev { color: var(--ink-mute); border: var(--border-hair) solid var(--line-strong); }
.env__sector {
  font-family: var(--font-mono);
  font-size: 0.66rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-mute);
}
.env__desc { font-size: 0.83rem; color: var(--ink-soft); line-height: 1.6; flex: 1; }
/* A bar, not a spinner: these are honest partial figures and a 10% environment
   should look like one rather than hide behind a label. */
.env__meter {
  display: block;
  height: 3px;
  border-radius: 3px;
  background: var(--line);
  overflow: hidden;
  margin-top: var(--sp-2);
}
.env__meter > span { display: block; height: 100%; background: var(--accent); }
.env__foot { display: flex; align-items: baseline; justify-content: space-between; }
.env__host, .env__pct { font-family: var(--font-mono); font-size: 0.7rem; }
.env__host { color: var(--accent); }
.env__pct { color: var(--ink-mute); }

/* ---------- section index numerals ---------- */
/* Ink low enough to read as texture rather than content. Two values because a
   red at 5% over white is nearly invisible while the same red at 5% over
   #0a0d12 is not — the light theme needs the stronger one. */
:root { --index-ink: rgba(221, 51, 51, 0.075); }
:root[data-theme="dark"] { --index-ink: rgba(255, 64, 64, 0.055); }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) { --index-ink: rgba(255, 64, 64, 0.055); }
}

.section-index {
  position: absolute;
  inset-block-start: var(--sp-6);
  inset-inline-end: var(--sp-6);
  font-family: var(--font-display);
  font-size: clamp(3rem, 7vw, 6.5rem);
  font-weight: 700;
  line-height: 0.8;
  letter-spacing: -0.05em;
  color: var(--index-ink);
  pointer-events: none;
  user-select: none;
  z-index: 0;
}
/* Group-level numerals sit inside a band that already has one, so they are
   smaller and pulled tight to their own heading rule. */
.lib-group, .tool-group, .skill-group, .note-year, .svc-detail { position: relative; }
.lib-group > .section-index,
.tool-group > .section-index,
.skill-group > .section-index,
.note-year > .section-index,
.svc-detail > .section-index {
  inset-block-start: -0.15em;
  inset-inline-end: 0;
  font-size: clamp(2.2rem, 5vw, 3.6rem);
}
/* Everything in a band must sit above the numeral, or a wide card can end up
   behind it and lose a corner of its text. */
.section > .wrap, .section > .wrap-narrow { position: relative; z-index: 1; }

@media (max-width: 600px) {
  /* At this width the numeral collides with the heading it is meant to sit
     beside, and there is no room for decoration. */
  .section-index { display: none; }
}

/* ---------- origin: the exhibit ---------- */
.origin-file {
  font-family: var(--font-mono);
  font-size: 0.68rem;
  letter-spacing: 0.22em;
  color: #7f93ab;
  padding-bottom: var(--sp-3);
  margin-bottom: var(--sp-7);
  border-bottom: 1px solid rgba(255, 255, 255, 0.14);
}
.origin-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--sp-8);
  align-items: start;
}
/* Corner marks, the way an exhibit gets registered on a light table. Drawn on
   the wrapper rather than the image so they sit outside the print's edge. */
.origin-print {
  margin: 0;
  position: relative;
  padding: var(--sp-3);
  border: 1px solid rgba(255, 255, 255, 0.14);
}
.origin-print::before, .origin-print::after {
  content: "";
  position: absolute;
  inline-size: 14px; block-size: 14px;
  border: 2px solid var(--red-hot);
}
.origin-print::before {
  inset-block-start: -1px; inset-inline-start: -1px;
  border-inline-end: 0; border-block-end: 0;
}
.origin-print::after {
  inset-block-end: -1px; inset-inline-end: -1px;
  border-inline-start: 0; border-block-start: 0;
}
.origin-print img { width: 100%; display: block; filter: contrast(1.04); }


.origin-copy .eyebrow { color: #8fa0b6; }
.origin-copy h2 { color: #f2f6fb; }
.origin-copy p { color: #9daec4; }
.origin-log {
  list-style: none;
  margin: var(--sp-6) 0 0;
  padding: 0;
  counter-reset: log;
}
.origin-log li {
  display: grid;
  grid-template-columns: 6.5rem 1fr;
  gap: var(--sp-4);
  padding: var(--sp-3) 0 var(--sp-3) var(--sp-4);
  border-inline-start: 2px solid rgba(255, 255, 255, 0.14);
  align-items: baseline;
}
.origin-log li:hover { border-inline-start-color: var(--red-hot); }
.origin-y { font-family: var(--font-mono); font-size: 0.76rem; color: var(--red-hot); letter-spacing: 0.06em; }
.origin-t { font-size: 0.9rem; color: #9daec4; line-height: 1.6; }

.origin-sheet {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: var(--sp-4);
  margin-top: var(--sp-8);
  padding-top: var(--sp-6);
  border-top: 1px solid rgba(255, 255, 255, 0.14);
}
.origin-sheet figure { margin: 0; position: relative; }
.origin-sheet img {
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  border: 1px solid rgba(255, 255, 255, 0.12);
  filter: grayscale(0.4) contrast(1.05);
  transition: filter var(--dur) var(--ease);
}
.origin-sheet figure:hover img { filter: none; }
.origin-sheet figcaption {
  position: absolute;
  inset-block-start: 6px; inset-inline-start: 6px;
  font-family: var(--font-mono);
  font-size: 0.6rem;
  letter-spacing: 0.1em;
  color: #04091f;
  background: #ffb020;
  padding: 1px 6px;
}
@media (max-width: 860px) { .origin-grid { grid-template-columns: 1fr; gap: var(--sp-6); } }

/* Both exhibits share the left column, stacked. The copy beside them runs to
   roughly twice the height of one photograph, so a single print left a column
   of dead navy under it — the gap is exactly where the second one belongs. */
.origin-exhibits { display: grid; gap: var(--sp-5); align-content: start; }
.origin-print--wide { margin: 0; }
/* 16:10, not 16:9. The photograph is 976x608 — 1.605 — so a 16:9 window would
   crop about a tenth off the top and bottom, which is the ceiling-mounted
   screens and the desk. Matching its own ratio crops nothing. */
/* The photograph's own ratio, so nothing is cropped. The 2026 master is
   1649x954 (1.729) — a 16:10 frame would have shaved the sides and a 16:9 one
   the top and bottom, and both would have cut into the wall of screens on the
   right, which is the half of the room worth seeing. */
.origin-print--wide img { aspect-ratio: 1649 / 954; object-fit: cover; }
.origin-print figcaption {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--sp-3);
  margin-top: var(--sp-3);
}
.origin-cap { font-size: 0.9rem; color: #c3cede; }
.origin-now {
  margin: 0;
  color: #9daec4;
  font-size: 0.92rem;
  line-height: 1.75;
}

@media (max-width: 520px) {
  .origin-log li { grid-template-columns: 1fr; gap: var(--sp-1); }
  .origin-print { padding: var(--sp-2); }
}


/* ---------- generated by build.py ----------
   Per-item values the build computes: one rule per distinct
   logo size and per distinct meter fill. Edit build.py, not
   this block — it is regenerated on every build. */
.meter-00 { width:95% }
.meter-01 { width:90% }
.meter-02 { width:20% }
.meter-03 { width:10% }
.logo-04 { width:71.5%;height:53.1% }
.logo-05 { width:68.5%;height:55.5% }
.logo-06 { width:62.3%;height:61.0% }
.logo-07 { width:64.9%;height:58.5% }
.logo-08 { width:62.6%;height:60.7% }
.logo-09 { width:82.7%;height:45.9% }
.logo-10 { width:75.2%;height:50.5% }
.logo-11 { width:81.1%;height:46.9% }
.logo-12 { width:88.0%;height:28.8% }
.logo-13 { width:85.9%;height:44.2% }
.logo-14 { width:58.0%;height:65.5% }
.logo-15 { width:47.7%;height:79.6% }
.logo-16 { width:72.5%;height:52.4% }
.logo-17 { width:88.0%;height:30.8% }
.logo-18 { width:88.0%;height:42.0% }
.logo-19 { width:88.0%;height:36.2% }
.logo-20 { width:88.0%;height:40.6% }
.logo-21 { width:88.0%;height:22.5% }
.logo-22 { width:70.4%;height:54.0% }
.logo-23 { width:63.6%;height:59.7% }
.logo-24 { width:68.2%;height:55.7% }
.logo-25 { width:78.1%;height:48.6% }
.logo-26 { width:84.9%;height:44.8% }
.logo-27 { width:86.0%;height:44.2% }
.logo-28 { width:63.7%;height:59.7% }
.logo-29 { width:48.0%;height:79.1% }
.logo-30 { width:67.5%;height:56.3% }
.logo-31 { width:88.0%;height:33.8% }
