:root {
  color-scheme: dark;
  --dim: 1;
  --color-warn: #FF8F7A;
  --color-bg: #0B0E12;
  --color-surface: #161C24;
  --color-text: #F6F1E6;
  --color-accent: #A9D1EF;
  --color-accent-300: #D4EBFB;
  --color-accent-400: #BFDFF5;
  --color-accent-500: #A9D1EF;
  --color-accent-600: #7BAED6;
  --radius-md: 8px;
  --measure: 680px;
  --gutter: 24px;
  --font-body: 'Inter', system-ui, sans-serif;
  --font-mono: 'JetBrains Mono', ui-monospace, Menlo, monospace;
}

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

html { background: var(--color-bg); }

body {
  margin: 0;
  background: var(--color-bg);
  color: var(--color-text);
  font: 400 16px/1.6 var(--font-body);
  -webkit-font-smoothing: antialiased;
}

.page {
  min-height: 100vh;
  padding-bottom: 96px;
  background: var(--color-bg);
}

a { color: var(--color-accent); text-decoration: none;
    border-bottom: 1px solid color-mix(in srgb, var(--color-accent) 35%, transparent);
    text-underline-offset: 3px; }
a:hover { color: var(--color-accent-300); border-bottom-color: var(--color-accent-300); }
a:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 2px; border-radius: 2px; }

/* — hero — */
.hero { position: relative; width: 100%; height: clamp(240px, 34vw, 340px);
        overflow: hidden; }
/* The stage is the only picture. Catchin' Waves mounts one glyph canvas here
   and cruises in it. A passing touch can still scroll; play claims the gesture. */
.hero-stage { position: absolute; inset: 0; z-index: 1; }
/* Static frame for visitors without script. The game replaces it on mount. */
.hero-fallback { margin: 0; padding: 24px 0 0; font: 400 13px/1.15 var(--font-mono);
  color: color-mix(in srgb, var(--color-accent) 70%, transparent); white-space: pre;
  overflow: hidden; text-align: center; }
.hero-stage:has(.cw-root) > .hero-fallback { display: none; }
.hero-fade { position: absolute; inset: auto 0 0 0; height: 40px; z-index: 2; pointer-events: none;
             background: linear-gradient(to bottom, transparent, var(--color-bg)); }
/* The way in, in the hero's top-right corner, clear of the masthead that
   overlaps the bottom edge: a dim hint, [ ? ], and [ play ]. The buttons
   ship hidden; hero-game.js reveals them once the cruise canvas is up. */
.hero-bar { position: absolute; top: 12px; right: 66px; z-index: 3; display: flex;
  align-items: center; gap: 10px; pointer-events: none;
  font: 500 11px/1 var(--font-mono); letter-spacing: .08em; }
.hero-hint { white-space: nowrap; padding: 6px 0;
  /* 52% is the lowest mix that clears WCAG AA (4.78:1) on --color-bg at 11px. */
  color: color-mix(in srgb, var(--color-text) min(100%, calc(52% * var(--dim, 1))), transparent);
  text-shadow: 0 0 4px var(--color-bg), 0 0 2px var(--color-bg); }
.hero-hint:empty { display: none; }
.hero-play, .hero-how { pointer-events: auto; cursor: pointer; font: inherit; letter-spacing: inherit;
  padding: 6px 8px;
  color: color-mix(in srgb, var(--color-text) min(100%, calc(52% * var(--dim, 1))), transparent);
  background: color-mix(in srgb, var(--color-bg) 60%, transparent);
  border: 1px solid transparent; border-radius: 2px; }
.hero-play:hover, .hero-play:focus-visible,
.hero-how:hover, .hero-how:focus-visible { color: var(--color-accent);
  border-color: color-mix(in srgb, var(--color-accent) 55%, transparent); }
/* Keyboard focus gets a solid ring: the border above is too faint over a
   moving canvas to meet WCAG 2.4.7 and 1.4.11. */
.hero-play:focus-visible, .hero-how:focus-visible {
  outline: 2px solid var(--color-accent); outline-offset: 2px; background: var(--color-bg); }
.hero-play[hidden], .hero-how[hidden] { display: none; }
@media (pointer: coarse) { .hero-bar { top: 4px; right: 62px; } .hero-play, .hero-how { padding: 17px 12px; } }
/* Play grows the same hero. The manual grows a little more so the how-to
   fits. Phone fullscreen, when the browser allows it, covers the page;
   closing returns to this short cruise. */
.hero.hero-gaming { height: clamp(260px, 38vw, 440px); }
.hero.hero-howto { height: clamp(380px, 62vh, 560px); }
.hero.hero-gaming .hero-bar,
.hero.hero-howto .hero-bar { display: none; }
.hero.hero-gaming .hero-fade,
.hero.hero-howto .hero-fade { display: none; }
@media (max-width: 520px) { .hero-hint { display: none; } }

/* — masthead — */
.wrap { max-width: var(--measure); margin: 0 auto; padding: 0 var(--gutter);
        animation: rise .5s ease-out both; }
.masthead { margin-top: -14px; }
.masthead-kicker { display: flex; align-items: center; gap: 8px;
  font: 500 11px/1 var(--font-mono); letter-spacing: .12em;
  color: var(--color-accent); text-transform: uppercase; }
.rule-tick { width: 18px; height: 1px; background: currentColor; }

.masthead-main { display: flex; align-items: center; gap: 24px; margin-top: 14px; }
.portrait { width: 112px; height: 112px; border-radius: 50%; flex: none; object-fit: cover;
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--color-accent) 35%, transparent); }
/* No design equivalent — new wrapper introduced by the portrait layout so the
   name can shrink instead of overflowing the flex row. */
.masthead-text { min-width: 0; }
.masthead-text h1 { margin: 0; }
h1 { font: 500 clamp(30px, 8.5vw, 42px)/1.05 var(--font-body);
     letter-spacing: -.028em; margin: 0; }
.location { display: inline-flex; flex-wrap: wrap; align-items: center; gap: 6px 12px; margin: 6px 0 0;
  font: 400 13px/1 var(--font-mono);
  color: color-mix(in srgb, var(--color-text) min(100%, calc(62% * var(--dim, 1))), transparent); }
.needle { color: var(--color-accent); flex: none; }

.bio { margin: 18px 0 0; max-width: 60ch; text-wrap: pretty;
  color: color-mix(in srgb, var(--color-text) min(100%, calc(82% * var(--dim, 1))), transparent); }
.bio strong { font-weight: 500; color: var(--color-text); }

.tags { display: flex; flex-wrap: wrap; gap: 7px; margin-top: 18px;
  list-style: none; padding: 0; }
.tag { display: inline-flex; align-items: center; font-size: 11px; letter-spacing: .02em;
  padding: 3px 10px; border-radius: calc(var(--radius-md) * 0.75); font-family: var(--font-mono); }
.tag-outline { border: 1px solid var(--color-accent); color: var(--color-accent); }

.links { display: flex; flex-wrap: wrap; align-items: center; gap: 16px;
  margin-top: 22px; font: 400 13.5px/1 var(--font-mono); }
/* The social row sits on its own line directly under the location line,
   left-aligned with it: not centered, and not inline beside the text. */
.masthead-text .links { margin-top: 8px; gap: 12px; justify-content: flex-start; }
/* Icon links carry no text, so the shared underline has nothing to sit under. */
.links a { display: inline-flex; border-bottom: 0; padding: 2px; border-radius: 4px;
  position: relative; }
/* Invisible 44px hit area: the icon keeps its size, the finger gets room. */
.links a::after { content: ""; position: absolute; inset: -10px -8px; }
.links a:hover { color: var(--color-accent-300); }
.links svg { display: block; }

/* — sections — */
.section { margin-top: 58px; }
.section-head { display: flex; align-items: baseline; gap: 12px; }
.section-head h2 { font: 500 11px/1 var(--font-mono); letter-spacing: .16em;
  text-transform: uppercase; color: color-mix(in srgb, var(--color-text) min(100%, calc(58% * var(--dim, 1))), transparent);
  margin: 0; white-space: nowrap; }
.section-rule { flex: 1; height: 1px;
  background: linear-gradient(to right, color-mix(in srgb, var(--color-text) 16%, transparent), transparent); }

/* — talks / research lists — */
.numbered { list-style: none; margin: 22px 0 0; padding: 0;
  display: flex; flex-direction: column; gap: 22px; }
.numbered li { display: grid; grid-template-columns: 22px 1fr; gap: 14px; }
/* --color-accent-600 measured 3.95:1 against --color-bg — below WCAG AA.
   --color-accent-500 passes at 5.88:1. */
.num { font: 400 12px/1.7 var(--font-mono); color: var(--color-accent-500); }
.numbered h3 { font: 500 17.5px/1.35 var(--font-body); margin: 0; letter-spacing: -.012em; }
.numbered p { margin: 6px 0 0; font: 400 13.5px/1.6 var(--font-body);
  color: color-mix(in srgb, var(--color-text) min(100%, calc(62% * var(--dim, 1))), transparent); }
.meta { display: flex; gap: 16px; align-items: baseline; margin-top: 9px;
  font: 400 12.5px/1 var(--font-mono); }
/* 38% measured 3.16:1 against --color-bg at this size — below the 4.5:1
   WCAG AA floor. 52% passes at 4.78:1. */
.dim { color: color-mix(in srgb, var(--color-text) min(100%, calc(52% * var(--dim, 1))), transparent); }

/* — project cards — */
.cards { display: flex; flex-direction: column; gap: 10px; margin-top: 22px; }
.card { padding: 16px 18px; border-radius: var(--radius-md);
  background: linear-gradient(180deg, color-mix(in srgb, var(--color-surface) 88%, transparent), color-mix(in srgb, var(--color-surface) 55%, transparent));
  border: 1px solid color-mix(in srgb, var(--color-text) 8%, transparent);
  transition: border-color .18s ease, transform .18s ease; }
.card:hover { border-color: color-mix(in srgb, var(--color-accent) 42%, transparent);
  transform: translateY(-1px); }
.card-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
.card h3 { font: 500 16.5px/1.3 var(--font-body); margin: 0; }
.kicker { font: 400 10.5px/1 var(--font-mono); letter-spacing: .1em;
  text-transform: uppercase; color: var(--color-accent-500); }
.card p { margin: 7px 0 0; font: 400 13.5px/1.6 var(--font-body);
  color: color-mix(in srgb, var(--color-text) min(100%, calc(66% * var(--dim, 1))), transparent); }

/* — off-screen / terminal — */
.prose { margin: 22px 0 0; font: 400 15px/1.65 var(--font-body);
  color: color-mix(in srgb, var(--color-text) min(100%, calc(82% * var(--dim, 1))), transparent); max-width: 60ch;
  text-wrap: pretty; }
.terminal { margin-top: 20px; padding: 14px 16px; border-radius: var(--radius-md);
  border: 1px solid color-mix(in srgb, var(--color-text) 9%, transparent);
  background: color-mix(in srgb, var(--color-surface) 45%, transparent);
  font: 400 13px/1.9 var(--font-mono); }
/* 45% measured 3.91:1 against --color-bg — below WCAG AA. 55% passes at 5.19:1. */
.cmd { color: color-mix(in srgb, var(--color-text) min(100%, calc(55% * var(--dim, 1))), transparent); letter-spacing: .06em; }
.kv { display: grid; grid-template-columns: auto 1fr; gap: 4px 16px; margin: 4px 0 10px; }
.kv dt { color: var(--color-accent-400); }
.kv dd { margin: 0; }
.kv.results { grid-template-columns: auto auto; gap: 2px 22px; }
.kv.results dt a { color: var(--color-accent-300); justify-self: start; width: fit-content; }
.kv.results dd { color: color-mix(in srgb, var(--color-text) min(100%, calc(50% * var(--dim, 1))), transparent); }
.caret { display: inline-block; width: 8px; height: 14px; background: var(--color-accent);
  vertical-align: -2px; animation: caret 1.1s steps(1) infinite; }

/* — footer — */
/* Sits outside .wrap, so it needs its own containment or it runs to the
   viewport edges while everything above stays in the 680px column. */
/* 38% measured 3.16:1 against --color-bg — below WCAG AA. 52% passes at 4.78:1. */
.site-footer { margin-top: 64px; display: flex; align-items: center;
  justify-content: space-between; gap: 16px; font: 400 11.5px/1 var(--font-mono);
  color: color-mix(in srgb, var(--color-text) min(100%, calc(52% * var(--dim, 1))), transparent);
  max-width: var(--measure); margin-inline: auto; padding-inline: var(--gutter); }

.site-label { font: 500 11px/1 var(--font-mono); letter-spacing: .12em;
  text-transform: uppercase; color: var(--color-accent); }
/* As a link in the footer it inherits the footer's dimmer colour instead. */
.site-footer .site-label { color: inherit; }

/* Three items under space-between; wrapping keeps them off each other on a
   phone, where the location label alone eats most of the row. */
.site-footer { flex-wrap: wrap; row-gap: 10px; }
.footer-nav { display: flex; gap: 14px; }
.footer-nav a { position: relative; color: inherit; text-decoration: none;
  border-bottom: 1px solid color-mix(in srgb, var(--color-text) 22%, transparent); }
.footer-nav a::after, .theme-toggle::after { content: ""; position: absolute; inset: -14px -7px; }
.footer-nav a:hover, .footer-nav a:focus-visible { color: var(--color-accent);
  border-bottom-color: currentColor; }

@keyframes caret { 0%, 49% { opacity: 1; } 50%, 100% { opacity: 0; } }
/* Rise is transform-only on purpose: animating opacity from 0 hides the whole
   column on every load — and on every dev-server live reload — which reads as
   a flash. The 6px slide alone keeps the entrance motion without ever blanking
   the content, and transforms never move layout so there is no CLS. */
@keyframes rise  { from { transform: translateY(6px); } to { transform: none; } }

/* Unfilled placeholder links. Deliberately reads as unfinished rather than as a
   working link, and the strict build gate refuses to publish while any exist.
   38% measured 3.16:1 against --color-bg — below WCAG AA. 52% passes at 4.78:1. */
.todo { font-style: italic; color: color-mix(in srgb, var(--color-text) min(100%, calc(52% * var(--dim, 1))), transparent); }

.thought-list { list-style: none; margin: 22px 0 0; padding: 0;
  display: flex; flex-direction: column; gap: 18px; }
/* Same underlying 45%-on-bg failure as .cmd above (3.91:1); raised alongside
   it to the same 55% (5.19:1) even though the review's table didn't name
   this selector explicitly — it's the identical bug. */
.thought-teaser time { display: block; margin-top: 4px;
  font: 400 12px/1 var(--font-mono);
  color: color-mix(in srgb, var(--color-text) min(100%, calc(55% * var(--dim, 1))), transparent); }
.thought-teaser p { margin: 6px 0 0; font-size: 13.5px;
  color: color-mix(in srgb, var(--color-text) min(100%, calc(62% * var(--dim, 1))), transparent); }

/* — thought pages / prose — */
/* .thought is the <main class="wrap thought"> wrapper on thought pages and the thoughts
   index. Those pages have no .hero canvas above them (unlike home), so the
   content otherwise starts flush with the viewport edge. */
.thought { padding-top: 64px; }

.thought-head { margin: 0 0 8px; }
.thought-head .prose { margin-top: 14px; }
.thought-head .tags { margin-top: 16px; }

/* The `~/thoughts` backlink above the title. Dimmed like the rest of the mono
   micro-labels on the site, not full accent — it is a way back, not the
   primary action on the page. Present only on individual thought pages, not
   the index or 404, so the spacing lives on .eyebrow itself rather than on
   h1 (which must stay flush when there is no eyebrow above it). */
.eyebrow { margin: 0 0 10px; font: 500 11px/1 var(--font-mono); letter-spacing: .12em;
  text-transform: uppercase; }
.eyebrow a { color: color-mix(in srgb, var(--color-text) min(100%, calc(52% * var(--dim, 1))), transparent);
  border-bottom-color: transparent; }
.eyebrow a:hover { color: var(--color-accent-300); border-bottom-color: var(--color-accent-300); }

.thought-meta { margin: 10px 0 0; font: 400 12.5px/1 var(--font-mono);
  color: color-mix(in srgb, var(--color-text) min(100%, calc(62% * var(--dim, 1))), transparent); }

/* Heading scale for rendered markdown, one step below h1 at each level. */
.prose h2 { margin: 40px 0 0; font: 500 22px/1.3 var(--font-body); letter-spacing: -.015em; }
.prose h3 { margin: 32px 0 0; font: 500 18px/1.35 var(--font-body); letter-spacing: -.01em; }
.prose h4 { margin: 24px 0 0; font: 500 15px/1.4 var(--font-body); }
.prose h2:first-child, .prose h3:first-child, .prose h4:first-child { margin-top: 0; }

/* A readable measure independent of the container: 68ch rather than the
   60ch used by the single-paragraph .prose usage on the home page. */
.prose p, .prose ul, .prose ol { margin: 16px 0 0; max-width: 68ch; }
.prose p:first-child, .prose ul:first-child, .prose ol:first-child { margin-top: 0; }
.prose ul, .prose ol { padding-left: 22px; }
.prose li { margin-top: 6px; }
.prose li:first-child { margin-top: 0; }

.prose blockquote { margin: 20px 0 0; padding: 2px 0 2px 16px; max-width: 68ch;
  border-left: 2px solid color-mix(in srgb, var(--color-accent) 45%, transparent);
  color: color-mix(in srgb, var(--color-text) min(100%, calc(62% * var(--dim, 1))), transparent); }
.prose blockquote > :first-child { margin-top: 0; }

.prose code { background: color-mix(in srgb, var(--color-surface) 70%, transparent);
  padding: .15em .4em; border-radius: 4px; font: 400 0.88em var(--font-mono); }

/* overflow-x: auto is the load-bearing rule here — without it a long code
   line pushes the whole page wider than the viewport on a phone. */
.prose pre { margin: 20px 0 0; padding: 14px 16px; border-radius: var(--radius-md);
  background: color-mix(in srgb, var(--color-surface) 55%, transparent);
  overflow-x: auto; font: 400 13px/1.6 var(--font-mono); }
/* Nested inside .prose pre, so it must not stack a second background/padding
   on top of the block's own. */
.prose pre code { background: none; padding: 0; border-radius: 0; font-size: inherit; }

/* The Nocturne signature: a freestanding rule that fades to transparent at
   both ends over a 48px ramp, rather than the UA default inset border or a
   flat line that stops clean. */
.hr { margin: 40px 0 0; border: 0; height: 1px;
  background: linear-gradient(to right,
    transparent,
    color-mix(in srgb, var(--color-text) 16%, transparent) 48px,
    color-mix(in srgb, var(--color-text) 16%, transparent) calc(100% - 48px),
    transparent); }

/* Header regroups rather than shrinking: below 560px the bio would be squeezed
   into a ~270px column, so the portrait pairs with the h1 and the prose runs
   full width beneath both. */
@media (max-width: 560px) {
  .masthead-main { gap: 16px; }
  .portrait { width: 64px; height: 64px; }
}

/* The terminal block is a two-column grid pairing long slugs with meta text.
   Below 520px it stacks so no slug truncates mid-word. */
@media (max-width: 520px) {
  .kv { display: block; }
  .kv dt { margin-top: 8px; }
  .kv dd { margin: 2px 0 0 16px;
           color: color-mix(in srgb, var(--color-text) min(100%, calc(50% * var(--dim, 1))), transparent); }
}

@media (max-width: 640px) { :root { --gutter: 20px; } .section { margin-top: 44px; } }
@media (max-width: 420px) { :root { --gutter: 16px; } }

@media (prefers-reduced-motion: reduce) {
  .caret { animation: none; }
  /* Belt and suspenders next to the transform-only `rise`: with the animation
     killed, pin the resolved state explicitly so a reduced-motion user always
     lands fully opaque and unshifted, never mid-fade. */
  .wrap { animation: none; opacity: 1; transform: none; }
  * { animation-duration: .01ms !important; transition-duration: .01ms !important; }
}

/* Retro Cali high-contrast theme: endless summer poster. Toggled via
   <html data-theme="retro">, persisted in localStorage. */
[data-theme="retro"] {
  --color-bg: #14100A;
  --color-surface: #231A10;
  --color-text: #F4EAD2;
  --color-accent: #EF8B3A;
  --color-accent-300: #F8D3A5;
  --color-accent-400: #F0B46A;
  --color-accent-500: #EF8B3A;
  --color-accent-600: #A05E28;
}
.theme-toggle {
  font: inherit;
  background: none;
  border: 1px solid color-mix(in srgb, var(--color-text) 22%, transparent);
  color: var(--color-text);
  border-radius: 999px;
  padding: 2px 10px;
  cursor: pointer;
  position: relative;
}
.theme-toggle:hover { border-color: var(--color-accent); color: var(--color-accent); }

/* Light theme: low sun on wet sand. Foam-white ground, deep-water ink, a
   single ocean-blue accent. Flat tones, no gradients. Follows the OS setting
   on a first visit; the footer toggle cycles ocean, light and retro.
   --dim raises the strength of every dimmed text mix, because a dark mix on a
   light ground loses contrast faster than a light mix on a dark one. */
[data-theme="light"] {
  color-scheme: light;
  --dim: 1.36;
  --color-bg: #F3EFE6;
  --color-surface: #E6E0D2;
  --color-text: #0C2231;
  --color-accent: #0A5C85;
  --color-accent-300: #064766;
  --color-accent-400: #0A5278;
  --color-accent-500: #0A5C85;
  --color-accent-600: #4C86A8;
  --color-warn: #B03A24;
}
[data-theme="retro"] { color-scheme: dark; }

/* — accessibility — */
.sr-only { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
  overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0; }
.skip-link { position: absolute; left: 12px; top: -64px; z-index: 100; padding: 10px 14px;
  background: var(--color-bg); color: var(--color-accent); border: 2px solid var(--color-accent);
  border-radius: 4px; font: 500 14px/1 var(--font-mono); }
.skip-link:focus { top: 12px; }
button:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 2px; }
.section-head h2 a { color: inherit; border-bottom: 0; }
.section-head h2 a::after { content: " \2192"; opacity: .7; }
.section-head h2 a:hover, .section-head h2 a:focus-visible { color: var(--color-accent); }
::selection { background: var(--color-accent); color: var(--color-bg); }
@media (forced-colors: active) {
  .card, .terminal, .tag-outline, .theme-toggle, .hero-play, .hero-how { border: 1px solid CanvasText; }
  a:focus-visible, button:focus-visible { outline: 2px solid Highlight; }
}

/* Dark/light button: top right of every page, where people look for it. It
   shows the mode it will switch to. The game hides it while a run is open. */
.page { position: relative; }
.mode-toggle { position: absolute; top: 10px; right: 12px; z-index: 6;
  width: 44px; height: 44px; display: grid; place-items: center; padding: 0;
  color: var(--color-text); cursor: pointer; border-radius: 999px;
  background: color-mix(in srgb, var(--color-bg) 70%, transparent);
  border: 1px solid color-mix(in srgb, var(--color-text) 24%, transparent); }
.mode-toggle:hover { color: var(--color-accent); border-color: var(--color-accent); }
.mode-toggle svg { grid-area: 1 / 1; }
.mode-toggle .icon-moon { display: none; }
[data-theme="light"] .mode-toggle .icon-sun { display: none; }
[data-theme="light"] .mode-toggle .icon-moon { display: block; }
body:has(.hero-gaming) .mode-toggle { display: none; }
.sub-head { font: 500 12px/1 var(--font-mono); letter-spacing: .14em; text-transform: uppercase;
  color: color-mix(in srgb, var(--color-text) min(100%, calc(58% * var(--dim, 1))), transparent); margin: 0; }
.sub-head + .prose { margin-top: 10px; }

/* - lab figures - */
.fig-svg { display: block; width: 100%; height: auto; }
.fig-svg polygon, .fig-svg ellipse { stroke-linejoin: round; }
.f-top, .f-left, .f-right { stroke: color-mix(in srgb, var(--color-text) 45%, var(--color-bg));
  stroke-width: .6; stroke-linejoin: round; }
/* face mixes: text percent per tone and facing. Top lightest, right darkest. */
.fig-svg { --lo-r: 8%; --lo-l: 12%; --lo-t: 16%; --mid-r: 14%; --mid-l: 20%; --mid-t: 26%;
  --hi-r: 22%; --hi-l: 30%; --hi-t: 40%; }
[data-theme="light"] .fig-svg { --lo-r: 20%; --lo-l: 14%; --lo-t: 8%; --mid-r: 30%; --mid-l: 21%; --mid-t: 12%;
  --hi-r: 24%; --hi-l: 16%; --hi-t: 8%; }
.f-lo.f-right  { fill: color-mix(in srgb, var(--color-text) var(--lo-r), var(--color-bg)); }
.f-lo.f-left   { fill: color-mix(in srgb, var(--color-text) var(--lo-l), var(--color-bg)); }
.f-lo.f-top    { fill: color-mix(in srgb, var(--color-text) var(--lo-t), var(--color-bg)); }
.f-mid.f-right { fill: color-mix(in srgb, var(--color-text) var(--mid-r), var(--color-bg)); }
.f-mid.f-left  { fill: color-mix(in srgb, var(--color-text) var(--mid-l), var(--color-bg)); }
.f-mid.f-top   { fill: color-mix(in srgb, var(--color-text) var(--mid-t), var(--color-bg)); }
.f-hi.f-right  { fill: color-mix(in srgb, var(--color-text) var(--hi-r), var(--color-bg)); }
.f-hi.f-left   { fill: color-mix(in srgb, var(--color-text) var(--hi-l), var(--color-bg)); }
.f-hi.f-top    { fill: color-mix(in srgb, var(--color-text) var(--hi-t), var(--color-bg)); }
.fig-svg .f-accent { fill: color-mix(in srgb, var(--color-accent) 45%, var(--color-bg)); }
.d-slot { fill: color-mix(in srgb, var(--color-text) 3%, var(--color-bg));
  stroke: color-mix(in srgb, var(--color-text) 30%, var(--color-bg)); stroke-width: .4; }
.d-port { fill: color-mix(in srgb, var(--color-text) 6%, var(--color-bg));
  stroke: color-mix(in srgb, var(--color-text) 50%, var(--color-bg)); stroke-width: .5; }
.d-sled { fill: color-mix(in srgb, var(--color-text) 30%, var(--color-bg));
  stroke: color-mix(in srgb, var(--color-text) 50%, var(--color-bg)); stroke-width: .5; }
.d-btn { fill: color-mix(in srgb, var(--color-text) 42%, var(--color-bg));
  stroke: color-mix(in srgb, var(--color-text) 55%, var(--color-bg)); stroke-width: .4; }
.d-trim { fill: none; stroke: color-mix(in srgb, var(--color-text) 38%, var(--color-bg)); stroke-width: .5; }
.d-led { fill: color-mix(in srgb, var(--color-text) 55%, var(--color-bg)); }
.tick { stroke: color-mix(in srgb, var(--color-text) 45%, var(--color-bg)); stroke-width: .6; }
.cable { fill: none; stroke: color-mix(in srgb, var(--color-text) 62%, var(--color-bg));
  stroke-width: 1.8; stroke-linecap: round; }
.led { fill: var(--color-accent); stroke: none; }
.led-warn { fill: var(--color-warn); }
.halo { fill: var(--color-text); opacity: .12; stroke: none; }
.fig-ul { stroke: var(--color-text); stroke-width: 1; stroke-linecap: round; opacity: .7; }
.fig-label { fill: var(--color-text); font: 500 11px var(--font-mono);
  text-anchor: middle; dominant-baseline: central; }
/* Fig 2: the network */
.fig-net { max-width: 520px; margin-inline: auto; }
.band { fill: color-mix(in srgb, var(--color-text) 3%, var(--color-bg));
  stroke: color-mix(in srgb, var(--color-text) 28%, var(--color-bg)); stroke-width: 1; }
.chip { fill: color-mix(in srgb, var(--color-text) 9%, var(--color-bg));
  stroke: color-mix(in srgb, var(--color-text) 55%, var(--color-bg)); stroke-width: 1; }
.chip.planned { fill: none; }
.chip.planned, .net-arrow.planned { stroke-dasharray: 3 3; }
.fig-group { fill: var(--color-accent); font: 500 10px var(--font-mono);
  letter-spacing: .12em; text-transform: uppercase; }
.fig-node { text-anchor: start; }
.fig-end { text-anchor: end; }
.net-link { fill: none; stroke: color-mix(in srgb, var(--color-text) 55%, var(--color-bg));
  stroke-width: 1.2; stroke-linejoin: round; }
.net-arrow { fill: none; stroke: var(--color-accent); stroke-width: 1.6; stroke-linejoin: round; }
.net-head { fill: var(--color-accent); }
.layer { transition: transform .2s ease; }
.fig:has(a[href="#node-firewall"]:is(:hover, :focus-visible)) .layer-firewall,
.fig:has(a[href="#node-switch"]:is(:hover, :focus-visible)) .layer-switch,
.fig:has(a[href="#node-proxmox"]:is(:hover, :focus-visible)) .layer-proxmox,
.fig:has(a[href="#node-blue"]:is(:hover, :focus-visible)) .layer-blue,
.fig:has(a[href="#node-red"]:is(:hover, :focus-visible)) .layer-red,
.fig:has(a[href="#node-nas"]:is(:hover, :focus-visible)) .layer-nas { transform: translateY(-4px); }
.fig:has(a[href="#node-firewall"]:is(:hover, :focus-visible)) .layer-firewall .f-top,
.fig:has(a[href="#node-switch"]:is(:hover, :focus-visible)) .layer-switch .f-top,
.fig:has(a[href="#node-proxmox"]:is(:hover, :focus-visible)) .layer-proxmox .f-top,
.fig:has(a[href="#node-blue"]:is(:hover, :focus-visible)) .layer-blue .f-top,
.fig:has(a[href="#node-red"]:is(:hover, :focus-visible)) .layer-red .f-top,
.fig:has(a[href="#node-nas"]:is(:hover, :focus-visible)) .layer-nas .f-top { stroke: var(--color-accent); stroke-width: 1.4; }
@media (prefers-reduced-motion: reduce) {
  .layer { transition: none; }
  .fig .layer { transform: none !important; }
}
/* lab page layout */
.card-frame { border: 1px solid color-mix(in srgb, var(--color-text) 8%, transparent);
  border-radius: var(--radius-md); padding: 14px 16px; margin: 28px 0 0; }
.fig { margin-inline: 0; }
.fig-head { display: flex; justify-content: space-between; gap: 12px; margin-bottom: 12px;
  font: 400 11px/1 var(--font-mono);
  color: color-mix(in srgb, var(--color-text) min(100%, calc(58% * var(--dim, 1))), transparent); }
.fig-key { list-style: none; margin: 12px 0 0; padding: 0; display: flex; flex-wrap: wrap;
  gap: 6px 16px; font: 400 12.5px/1.4 var(--font-mono); }
.fig-key a { border-bottom: 0; }
.lab .section .card-frame { margin-top: 22px; }
.lab .numbered h3 .dim { font-weight: 400; }
.segments { display: grid; grid-template-columns: max-content 1fr; gap: 6px 16px;
  margin: 22px 0 0; font: 400 13.5px/1.6 var(--font-body); }
.segments dt { font: 400 12.5px/1.7 var(--font-mono); color: var(--color-accent-400); }
.segments dd { margin: 0;
  color: color-mix(in srgb, var(--color-text) min(100%, calc(62% * var(--dim, 1))), transparent); }
@media (max-width: 520px) {
  .segments { grid-template-columns: 1fr; gap: 0; }
  .segments dd { margin-bottom: 10px; }
}
