/* ============================================================================
   Structure:
     1. Fonts (IBM Plex superfamily)        — self-host target + CDN fallback
     2. The Invariant Spine  (:root)        — NEVER changes between modes
     3. The Mode Dial  (html[data-mode=…])  — swaps a handful of tokens
     4. Base element styles                  — type, links, rules, crop marks
     5. Artifact vocabulary (utility classes)— crop marks, blueprint frames,
                                               color blocks, dot motif, etc.

   Set a mode with ONE attribute on <html>:
       <html data-mode="doc">     ← default if omitted
       <html data-mode="lab">
       <html data-mode="studio">
       <html data-mode="demo">
   ========================================================================== */

/* ---------------------------------------------------------------------------
   1. FONTS
   PRODUCTION: self-host WOFF2 (uncomment the @font-face block, drop files in
   /fonts, keep font-display:swap). The CDN @import below is for prototyping
   and keeps payload off your repo. IBM Plex Sans / Serif / Mono — one
   superfamily so the system stays coherent even when it goes "slightly wrong."
--------------------------------------------------------------------------- */
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:ital,wght@0,400;0,500;0,600;1,400&family=IBM+Plex+Sans:ital,wght@0,400;0,500;0,600;1,400&family=IBM+Plex+Serif:ital,wght@0,400;0,500;0,600;1,400&display=swap');

/*  --- production self-host scaffold (preferred) -----------------------------
@font-face{ font-family:"IBM Plex Sans";  font-weight:400 600; font-display:swap;
  src:url("/fonts/IBMPlexSans-var.woff2") format("woff2"); }
@font-face{ font-family:"IBM Plex Serif"; font-weight:400 600; font-display:swap;
  src:url("/fonts/IBMPlexSerif-var.woff2") format("woff2"); }
@font-face{ font-family:"IBM Plex Mono";  font-weight:400 600; font-display:swap;
  src:url("/fonts/IBMPlexMono-var.woff2") format("woff2"); }
----------------------------------------------------------------------------- */

/* ===========================================================================
   2. THE INVARIANT SPINE
   These tokens are IDENTICAL in every mode and every project. Locked.
   =========================================================================== */
:root{

  /* — Type superfamily (THE spine: one family, sans/serif/mono coherent) — */
  --font-sans:  "IBM Plex Sans", ui-sans-serif, system-ui, -apple-system, "Segoe UI", Arial, sans-serif;
  --font-serif: "IBM Plex Serif", ui-serif, Georgia, "Times New Roman", serif;
  --font-mono:  "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;

  /* — Type scale (fluid, deliberately restrained hierarchy) — */
  --step--1: clamp(0.82rem, 0.78rem + 0.20vw, 0.90rem);  /* meta, dates, labels */
  --step--2: clamp(0.74rem, 0.72rem + 0.10vw, 0.80rem);  /* tiny labels */
  --step-0:  clamp(1.00rem, 0.96rem + 0.20vw, 1.10rem);  /* body */
  --step-1:  clamp(1.20rem, 1.10rem + 0.50vw, 1.45rem);  /* h3 / lead */
  --step-2:  clamp(1.50rem, 1.35rem + 0.75vw, 1.90rem);  /* h2 */
  --step-3:  clamp(1.90rem, 1.60rem + 1.50vw, 2.80rem);  /* h1 */
  --step-4:  clamp(2.60rem, 1.90rem + 3.20vw, 4.60rem);  /* poster (studio/demo) */

  --lh-body:  1.62;
  --lh-tight: 1.12;
  --tracking-tight: -0.018em;

  /* — Measure & rhythm — */
  --measure: 68ch;          /* main reading column */
  --measure-wide: 92ch;
  --measure-compact: 42rem;
  --space: 1rem;
  --gutter: 2.5rem;
  --gutter-tight: 1rem;

  /* — Geometry: LOCKED. No rounded corners. Ever. — */
  --radius: 0px;

  /* — Hairline rule weight (blueprint / crop-mark only, never decorative box) */
  --hair: 1px;

  /* — Motion: quiet. Snap-ish; no bounce, no reveal, no delayed paint. — */
  --ease: cubic-bezier(0.2, 0, 0, 1);
  --dur: 120ms;

  /* — Signature warm accent family (the "dot motif" — color is RARE) — */
  --warm-vermilion: #d3402a;
  --warm-orange:    #e3760e;
  --warm-coral:     #ef6a4a;
  --warm-rust:      #a8301b;

  /* The spine ships in DOC mode by default (see §3). */
}

/* ===========================================================================
   3. THE MODE DIAL  — the variable layer
   Each mode swaps: paper/ink pair, the single accent slot, the leading type
   face, layout density, and the "disorder budget" (how much grid-violation is
   sanctioned). Switching mode is a ONE-LINE change on <html>.
   =========================================================================== */

/* ---- DOC (default) — austere, archival, text-first. The personal site. ----
   Black on near-white. Color is a guest, not a host. Disorder: minimal,
   confined to the edges (crop marks, one misaligned underlay).            */
:root,
html[data-mode="doc"]{
  --paper:    #faf9f6;   /* very light warm gray */
  --paper-2:  #f1efe9;
  --ink:      #161514;   /* very dark warm gray, not pure black */
  --ink-2:    #4b4742;
  --ink-3:    #8b8782;
  --hairline: color-mix(in oklab, var(--ink) 14%, transparent);
  --accent:   var(--warm-vermilion);
  --font-lead: var(--font-sans);   /* body & headings: sans */
  --density: 1;          /* line-height & padding multiplier */
  --disorder: 0.7;       /* 0–3 LIVE dial (see §3e). doc = visible but quiet */
}

/* ---- LAB — notebook / dashboard. Denser. Blueprint hairlines ALLOWED. -----
   Mono-forward labels, hard black boxes, dashed group frames, registration
   marks. The Agent Protocol Tech Tree & Homelab live here.                */
html[data-mode="lab"]{
  --paper:    #f6f5f1;
  --paper-2:  #ebe9e3;
  --ink:      #121316;
  --ink-2:    #44474d;
  --ink-3:    #83868d;
  --hairline: color-mix(in oklab, var(--ink) 20%, transparent);
  --accent:   var(--warm-vermilion);
  --font-lead: var(--font-mono);   /* labels & structure lean mono */
  --density: 0.82;       /* tighter */
  --disorder: 1.15;
}

/* ---- STUDIO — most expressive. Serif-led, warm paper, poster lettering, ----
   big color blocks with hairline borders, grid violations turned up.
   The Augmented Classroom essay lives here.                               */
html[data-mode="studio"]{
  --paper:    #f3ecdd;   /* cream paper */
  --paper-2:  #e9e0cb;
  --ink:      #20180f;   /* warm near-black ink */
  --ink-2:    #5a4d3c;
  --ink-3:    #938876;
  --hairline: color-mix(in oklab, var(--ink) 16%, transparent);
  --accent:   var(--warm-rust);
  --font-lead: var(--font-serif);  /* body & display: serif */
  --density: 1.08;       /* roomier, editorial */
  --disorder: 1.9;
}

/* ---- DEMO — loud, rough, quick one-pagers. The system's dark surface. -----
   Ink ground, paper text, vermilion that earns its keep. Dirtied fades OK,
   smooth gradients still banned. The LLM Topography Explorer lives here.  */
html[data-mode="demo"]{
  --paper:    #131312;   /* ink ground (dark surface, tokenized — not "dark mode") */
  --paper-2:  #1d1d1b;
  --ink:      #f3f1ea;   /* paper text */
  --ink-2:    #b0ada3;
  --ink-3:    #76736b;
  --hairline: color-mix(in oklab, var(--ink) 22%, transparent);
  --accent:   #f0512f;   /* hotter vermilion for the dark ground */
  --font-lead: var(--font-mono);
  --density: 0.9;
  --disorder: 2.45;
}

/* ===========================================================================
   3b. SEMANTIC COLOR ROLES  — stop the accent from doing five jobs.
   The single --accent means exactly ONE thing: identity / active / emphasis.
   STATUS is its own vocabulary, constant across modes & palettes, so a
   "warning" never borrows the brand color.
       --accent  → brand mark, selected/active state, primary emphasis
       --ok      → success / healthy / running
       --warn    → caution / attention (amber, deliberately NOT the accent)
       --alert   → error / destructive / down (red)
   =========================================================================== */
:root{ --ok:#2f7d4f; --warn:#b9791a; --alert:#bf3520; }
html[data-mode="studio"]{ --ok:#3a6e4c; --warn:#9a6a16; --alert:#a8301b; }
html[data-mode="demo"]{ --ok:#4cb37a; --warn:#e0a92e; --alert:#f0512f; } /* lifted for the dark ground */

/* ===========================================================================
   3c. PALETTE KNOB  — pick a project's identity. ORTHOGONAL to mode:
       <html data-mode="lab" data-accent="sky">
   REWORKED (review 2): a palette is no longer one color. It is a BRIGHT accent
   + a backing set of three DUSTY EARTH-TONE PLATES (--p1/--p2/--p3). The accent
   does identity/active/emphasis; the plates are the "ink separations" that bloom
   through as misregistration (see §3e) and tint neutral chrome as --disorder
   rises. Overall scheme: earthy/dusty jewel tones anchored on MAROON + BLUE
   SLATE, backed by slate / greige; lighter on yellow, green, purple. Never a
   smooth-gradient/marketing hue. The warm DOT MOTIF (.dots) stays its own warm
   signature and does NOT follow this knob.
       --accent      bright identity / active / emphasis
       --p1 --p2 --p3  dusty plates — bloom in misregistration, tint chrome
   =========================================================================== */
/* ember — bright red-orange, maroon/clay/slate backing  (DEFAULT) */
:root,
html[data-accent="ember"]{ --accent:#d8482b; --p1:#9c3f2f; --p2:#6e2a31; --p3:#5d6a6e; }
/* maroon — jewel maroon, slate + dusty gold backing */
html[data-accent="maroon"]{ --accent:#9e2b39; --p1:#5c2a30; --p2:#4c5a6a; --p3:#8a6f3a; }
/* sky — bright sky blue, slate / teal / greige backing */
html[data-accent="sky"]{ --accent:#3f86ad; --p1:#3a5a6a; --p2:#4f5e6e; --p3:#7d7a6a; }
/* moss — leaf green (less common), olive / sage / gold backing */
html[data-accent="moss"]{ --accent:#5f8a3f; --p1:#4a5a36; --p2:#6b7358; --p3:#7a6a3a; }
/* clay — burnt orange (less common), clay / maroon / gold backing */
html[data-accent="clay"]{ --accent:#cf7a2c; --p1:#9c5a2e; --p2:#6e3a2c; --p3:#7a6a3a; }
/* slate — restrained blue-slate, greige / dusty-purple backing */
html[data-accent="slate"]{ --accent:#4a5e72; --p1:#44586a; --p2:#6f6a5e; --p3:#5a5560; }

/* lifted accents so each palette stays legible on the DEMO dark ground */
html[data-mode="demo"][data-accent="ember"] { --accent:#f0512f; }
html[data-mode="demo"][data-accent="maroon"]{ --accent:#cf4655; }
html[data-mode="demo"][data-accent="sky"]   { --accent:#5aa6cc; }
html[data-mode="demo"][data-accent="moss"]  { --accent:#86b85a; }
html[data-mode="demo"][data-accent="clay"]  { --accent:#e0992e; }
html[data-mode="demo"][data-accent="slate"] { --accent:#8290a4; }

/* Backward-compatible aliases for older small sites using the pre-system
   personal-site tokens. They resolve dynamically, so mode changes still work. */
:root{
  --font-text: var(--font-lead);
  --text: var(--ink);
  --text-secondary: var(--ink-2);
  --text-tertiary: var(--ink-3);
  --bg: var(--paper);
  --rule: var(--hairline);
  --content-width: var(--measure-compact);
}

/* ===========================================================================
   3d. PITCH / HIERARCHY  — the antidote to "every component is an outlined box
   at the same volume." Three tiers. The rule: a region gets ONE t1, a few t2,
   and everything else is t3. Borders & fills are earned, not default.
       .t1  loud   — solid accent fill        (one primary action / selection)
       .t2  medium — hairline outline          (secondary, optional)
       .t3  quiet  — text only, no box         (the default for most chrome)
   =========================================================================== */
.t1,.t2,.t3{ font: inherit; cursor:pointer; padding:.45rem .9rem; line-height:1;
  border:2px solid transparent; background:transparent; color:var(--ink);
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease), border-color var(--dur) var(--ease); }
.t1{ background:var(--accent); border-color:var(--accent); color:var(--paper); font-weight:600; }
.t1:hover{ filter:brightness(1.06); }
.t2{ border-color:var(--hairline); color:var(--ink); }
.t2:hover{ border-color:var(--ink); }
.t3{ color:var(--ink-2); padding-left:0; padding-right:0; }
.t3:hover{ color:var(--ink); }
.t1:active,.t2:active,.t3:active{ transform:translateY(1px); }

/* ===========================================================================
   3e. DISORDER  — the misprint engine. --disorder (0–3) is now a LIVE dial,
   not just a documented budget. The model: our black-on-paper baseline is the
   "key plate"; the dusty palette plates sit in perfect registration beneath it
   and are invisible at rest. As --disorder rises, REGISTRATION DRIFTS — the
   plates fan out from behind the key and colored fringes bloom; whole blocks
   nudge off-grid; neutral chrome (rules, marks, labels) picks up plate color.
   So turning disorder up literally turns COLOR up — from a near-monochrome
   baseline toward a mis-registered, posterized print.

   Drive it per-mode via the --disorder default, or override live:
       document.documentElement.style.setProperty('--disorder', 1.8)

   Offsets scale in em, so the fringe stays proportional at any size.
   NOTE: the offset calc is inlined in `transform` (not stored in an
   intermediate --mis-* var) because a custom property that references another
   var is substituted at its DECLARING element — an intermediate would bake in
   the root's --disorder and ignore per-element overrides.

   TWO INDEPENDENT KNOBS (both cascade — set inline or in a class rule):
     --mis-spread   : separation MULTIPLIER (default 1). Scales every
                      displacement — plate fan-out, .shift, .nudge, .ghost-shift,
                      the .rule--mis line. Bump it to throw things further apart,
                      drop it to tighten, all WITHOUT touching the global dial.
     --mis-opacity  : plate translucency MULTIPLIER (default 1). Scales BOTH the
                      .misreg fade ramp and its cap together, so 2 ≈ fully opaque
                      plates, 0.5 ≈ half as present. Separate from --disorder and
                      from --mis-spread, so you can move plates apart while keeping
                      them faint, or stack them tight but bold.
   They read at the declaring element (same mechanism as --disorder above), so a
   per-element/per-class override cascades correctly to the pseudo-plates.
   =========================================================================== */

/* — .misreg — key text built from offset plates. Needs data-text="<same text>"
   (set it in markup, or let JS mirror textContent). Apply to HEADINGS / big
   numbers / key labels — never body copy. Two plates fan opposite the key. — */
.misreg{ position: relative; isolation: isolate; }
.misreg::before, .misreg::after{
  content: attr(data-text);
  position: absolute; inset: 0; z-index: -1; pointer-events: none;
  padding: inherit;                         /* honor the host's padding so the plate
     text lands on the SAME baseline as the key text — without this a heading that
     carries a top rule / padding-top (e.g. a section heading) prints its plates
     up in the padding, floating high. inset:0 already sits inside the border, so
     only padding needs compensating; inheriting it does that generically. */
  mix-blend-mode: multiply;                 /* plates darken paper → colored fringe */
  /* Plates fade IN as disorder rises and stay TRANSLUCENT so the solid key text
     on top remains legible — opacity is capped well under 1 and the fringe reads
     as a soft tint, not a second opaque word. At disorder 0 it is fully
     transparent (perfect register / monochrome). */
  opacity: clamp(0, calc(var(--disorder) * 0.32 * var(--mis-opacity, 1)), calc(0.5 * var(--mis-opacity, 1)));
  /* NB: do NOT transition `transform`/`opacity` here — both are calc(var(--disorder)*…),
     and a transition on a property whose specified string is unchanged freezes the
     value in Chromium when only the custom property updates. The slider gives
     continuous feedback on its own; the offset must recompute instantly. */
  transition: color var(--dur) var(--ease);
}
.misreg::before{ color: var(--p2); transform: translate(calc(var(--disorder) * 0.07em * var(--mis-spread, 1)),  calc(var(--disorder) * 0.045em * var(--mis-spread, 1))); }
.misreg::after { color: var(--p3); transform: translate(calc(var(--disorder) * -0.07em * var(--mis-spread, 1)), calc(var(--disorder) * -0.045em * var(--mis-spread, 1))); }
/* on the demo dark ground the plates must ADD light, not subtract it */
html[data-mode="demo"] .misreg::before,
html[data-mode="demo"] .misreg::after{ mix-blend-mode: screen; }

/* — .shift — nudge a whole block off-grid as disorder rises (mis-registration
   at the layout scale). Alternate direction with .shift--alt. — */
.shift{ transform: translate(calc(var(--disorder) * -2px * var(--mis-spread, 1)), calc(var(--disorder) * 1.2px * var(--mis-spread, 1))) rotate(calc(var(--disorder) * -0.14deg)); }
.shift--alt{ transform: translate(calc(var(--disorder) * 2px * var(--mis-spread, 1)), calc(var(--disorder) * -1px * var(--mis-spread, 1))) rotate(calc(var(--disorder) * 0.16deg)); }

/* — .nudge — hand-set EMPHASIS. A small relative shift + micro-rotation for an
   emphasized term, a callout, a label — finer than .shift (which is for whole
   layout blocks). Reads as "placed by hand," not snapped to the grid. Stays
   inline-block so it can wrap a word mid-sentence. Alternate with .nudge--alt. — */
.nudge{ position: relative; display: inline-block;
  transform: translate(calc(var(--disorder) * 0.6px * var(--mis-spread, 1)), calc(var(--disorder) * -0.7px * var(--mis-spread, 1))) rotate(calc(var(--disorder) * 0.25deg)); }
.nudge--alt{ position: relative; display: inline-block;
  transform: translate(calc(var(--disorder) * -0.6px * var(--mis-spread, 1)), calc(var(--disorder) * 0.7px * var(--mis-spread, 1))) rotate(calc(var(--disorder) * -0.25deg)); }

/* — .ghost-shift — a box slides off its footprint as disorder rises, leaving a
   hairline "ghost" of where it should have sat: the gap shows behind it. The
   ghost ::before counter-translates by the negative offset so it stays pinned to
   the original position while the box drifts; z-index:-1 keeps it behind any
   fill. At disorder 0 box and ghost coincide (clean). Pair with .hardbox /
   .block / .panel — anything with a defined edge. — */
.ghost-shift{ position: relative;
  transform: translate(calc(var(--disorder) * 3px * var(--mis-spread, 1)), calc(var(--disorder) * -2px * var(--mis-spread, 1))); }
.ghost-shift::before{ content:""; position:absolute; inset:0; z-index:-1; pointer-events:none;
  border: var(--hair) solid color-mix(in oklab, var(--p3) 55%, var(--hairline));
  transform: translate(calc(var(--disorder) * -3px * var(--mis-spread, 1)), calc(var(--disorder) * 2px * var(--mis-spread, 1))); }

/* — .rule--mis — a misregistered hairline: a second, plate-tinted line drifts
   off the main rule as disorder rises (the line equivalent of plate fan-out).
   Extends .rule. Drawn with box-shadow, NOT ::after, because .rule is usually an
   <hr> — a replaced element that generates no pseudo-element boxes. The shadow
   replicates the rule's border box (a hairline) offset down + tinted, so it
   works on <hr> AND on any bordered element. Honors --mis-spread. — */
.rule--mis{
  box-shadow: 0 calc(var(--disorder) * 1.6px * var(--mis-spread, 1)) 0 0
    color-mix(in oklab, var(--p2) calc(40% + var(--disorder) * 12%), transparent);
}

/* — .poster — posterized (stepped, banded) accent fill instead of a flat one;
   reads as a coarse print screen. Use on meters / blocks. — */
.poster{ background-image: repeating-linear-gradient(90deg,
    var(--accent) 0, var(--accent) 3px,
    color-mix(in oklab, var(--accent) 62%, var(--p2)) 3px,
    color-mix(in oklab, var(--accent) 62%, var(--p2)) 6px); }

/* — disorder tints NEUTRAL CHROME toward the palette as it rises (bring color
   in). At --disorder 0 these are pure hairline/ink; by 3 they carry plate. — */
.rule{ border-top-color: color-mix(in oklab, var(--p2) calc(var(--disorder) * 20%), var(--hairline)); }
.crop::before{
  border-color: color-mix(in oklab, var(--p3) calc(var(--disorder) * 26%), var(--hairline));
}

/* ===========================================================================
   4. BASE ELEMENT STYLES
   =========================================================================== */
*,*::before,*::after{ box-sizing:border-box; }

html{
  background: var(--paper);
  color: var(--ink);
  -webkit-text-size-adjust: 100%;
}

body{
  margin: 0;
  font-family: var(--font-lead);
  font-size: var(--step-0);
  line-height: calc(var(--lh-body) * var(--density));
  text-rendering: optimizeLegibility;
  font-kerning: normal;
  font-feature-settings: "kern" 1, "liga" 1;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* Headings — restrained weights, tight leading, never "designed". */
h1,h2,h3,h4{
  font-family: var(--font-lead);
  font-weight: 600;
  line-height: var(--lh-tight);
  letter-spacing: var(--tracking-tight);
  margin: 1.4em 0 0.5em;
  text-wrap: balance;
}
h1{ font-size: var(--step-3); margin-top: 0; }
h2{ font-size: var(--step-2); }
h3{ font-size: var(--step-1); letter-spacing: 0; }
h4{ font-size: var(--step-0); letter-spacing: 0.02em; }

/* Default heading disorder: a light two-plate registration fringe that works
   on plain semantic h1/h2 text. `.misreg` remains the stronger opt-in version
   when markup can provide `data-text`. */
h1,h2{
  text-shadow:
    calc(var(--disorder) * 0.028em * var(--mis-spread, 1))
    calc(var(--disorder) * 0.018em * var(--mis-spread, 1))
    0 color-mix(in oklab, var(--p2) clamp(0%, calc(var(--disorder) * 12% * var(--mis-opacity, 1)), 42%), transparent),
    calc(var(--disorder) * -0.024em * var(--mis-spread, 1))
    calc(var(--disorder) * -0.016em * var(--mis-spread, 1))
    0 color-mix(in oklab, var(--p3) clamp(0%, calc(var(--disorder) * 10% * var(--mis-opacity, 1)), 36%), transparent);
}
html[data-mode="demo"] h1,
html[data-mode="demo"] h2{
  text-shadow:
    calc(var(--disorder) * 0.028em * var(--mis-spread, 1))
    calc(var(--disorder) * 0.018em * var(--mis-spread, 1))
    0 color-mix(in oklab, var(--p2) clamp(0%, calc(var(--disorder) * 18% * var(--mis-opacity, 1)), 56%), transparent),
    calc(var(--disorder) * -0.024em * var(--mis-spread, 1))
    calc(var(--disorder) * -0.016em * var(--mis-spread, 1))
    0 color-mix(in oklab, var(--p3) clamp(0%, calc(var(--disorder) * 16% * var(--mis-opacity, 1)), 50%), transparent);
}

p{ margin: 0 0 1em; text-wrap: pretty; }
p:last-child{ margin-bottom: 0; }

.lead{ font-size: var(--step-1); line-height: 1.45; }
small, .meta{ font-size: var(--step--1); color: var(--ink-2); line-height: 1.45; }

/* The body lead face varies by mode, but mono is ALWAYS mono. */
code, pre, kbd, samp, .mono{
  font-family: var(--font-mono);
  font-size: 0.92em;
}

/* — Links: underline only. NO color shift. Hover changes weight/offset. — */
a{
  color: inherit;
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.14em;
  transition: text-decoration-thickness var(--dur) var(--ease),
              text-underline-offset var(--dur) var(--ease);
}
a:hover{ text-decoration-thickness: 2px; text-underline-offset: 0.17em; }
a:focus-visible{ outline: 2px solid var(--accent); outline-offset: 2px; }

.skip-link{
  position: absolute;
  top: 0.75rem;
  left: 0.75rem;
  z-index: 1000;
  transform: translateY(calc(-100% - 1rem));
  background: var(--ink);
  color: var(--paper);
  padding: 0.45rem 0.65rem;
  font-family: var(--font-mono);
  font-size: var(--step--1);
  text-decoration: none;
}
.skip-link:focus-visible{ transform: translateY(0); outline-color: var(--paper); }

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

/* Selection picks up the warm accent, lightly. */
::selection{ background: color-mix(in oklab, var(--accent) 26%, transparent); }

/* Mono label — the system's workhorse caption (uppercase, tracked). */
.label{
  font-family: var(--font-mono);
  font-size: var(--step--1);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--ink-2);
}

/* Reading column. */
.measure{ max-width: var(--measure); }
.measure-wide{ max-width: var(--measure-wide); }

/* ===========================================================================
   4a. COMPOSITION PRIMITIVES
   Reusable page structures that emerged from the personal site: a side rail,
   compact archival lists, and square artifact previews. These are deliberately
   low-level enough to travel to other one-off projects.
   =========================================================================== */

.rail{
  --rail-width: 15rem;
  --rail-content: var(--measure-compact);
  --rail-gap: var(--gutter);
  display: grid;
  min-height: 100vh;
  gap: var(--rail-gap);
  grid-template-columns:
    minmax(0, fit-content(var(--rail-width)))
    minmax(0, var(--rail-content))
    1fr;
  grid-template-areas: "rail main .";
  align-items: start;
}
.rail__side{
  grid-area: rail;
  justify-self: end;
  position: sticky;
  top: 0;
  height: 100vh;
  padding: 2.5rem 0;
  display: flex;
  flex-direction: column;
  gap: 2rem;
  align-items: flex-end;
  text-align: right;
}
.rail__top{
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 1.5rem;
}
.rail__main{ grid-area: main; min-width: 0; padding: 2.5rem 0 5rem; }
.rail__content{ max-width: var(--rail-content); width: 100%; position: relative; }
.rail__nav,
.rail__links{
  display: flex;
  flex-direction: column;
  align-items: flex-end;
}
.rail__nav{ gap: 0.4rem; }
.rail__links{ gap: 0.35rem; margin-top: auto; }
.rail__nav a,
.rail__links a{
  color: var(--ink-2);
  text-decoration: none;
  white-space: nowrap;
}
.rail__nav a{ display: block; padding: 0.15rem 0; font-size: var(--step--1); }
.rail__links a{ font-family: var(--font-mono); font-size: 0.78rem; }
.rail__nav a:nth-child(2n){
  transform: translateX(calc(var(--disorder) * 0.45px * var(--mis-spread, 1)));
}
.rail__nav a:nth-child(2n + 1){
  transform: translateX(calc(var(--disorder) * -0.35px * var(--mis-spread, 1)));
}
.rail__nav a:hover,
.rail__links a:hover{ color: var(--ink); text-decoration: underline; text-underline-offset: 0.15em; }

.portrait{
  width: 120px;
  height: 120px;
  object-fit: cover;
  filter: grayscale(100%) contrast(1.1);
  mix-blend-mode: multiply;
}

.crumb{
  font-family: var(--font-mono);
  font-size: var(--step--1);
  color: var(--ink-2);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  margin-bottom: 3rem;
}
.crumb a{ text-decoration: none; }
.crumb a:hover{ text-decoration: underline; }

.entry-list{ list-style: none; padding: 0; margin: 0; }
.entry{ margin: 0 0 0.65em; line-height: 1.45; }
.entry:last-child{ margin-bottom: 0; }
.entry__title{ font-weight: 500; margin: 0 0 0.15em; }
.entry__title::before{
  content: "\2014\00a0";
  color: color-mix(in oklab, var(--p3) calc(var(--disorder) * 16%), var(--ink-3));
  text-shadow:
    calc(var(--disorder) * 0.04em * var(--mis-spread, 1))
    0 0 color-mix(in oklab, var(--p2) calc(var(--disorder) * 16%), transparent);
}
.entry__title a{ text-decoration: none; }
.entry__title a:hover{ text-decoration: underline; text-underline-offset: 0.12em; }
.entry__meta,
.entry__desc{ font-size: var(--step--1); color: var(--ink-2); }
.entry__meta{ line-height: 1.45; }
.entry__desc{ margin: 0.3em 0 0; line-height: 1.5; }

.preview-grid{
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 1rem;
  margin-top: 0.9rem;
}
.preview-card{ min-width: 0; line-height: 1.35; }
.preview-thumb-link{
  display: block;
  color: inherit;
  text-decoration: none;
}
.preview-thumb-link:hover{ text-decoration: none; }
.preview-thumb{
  position: relative;
  display: grid;
  place-items: center;
  aspect-ratio: 1;
  margin-bottom: 0.55rem;
  overflow: hidden;
  background: var(--paper-2);
  color: var(--ink);
  isolation: isolate;
}
.preview-thumb::before,
.preview-thumb::after{
  content: "";
  position: absolute;
  inset: 13%;
  border: var(--hair) solid color-mix(in oklab, var(--ink) 20%, transparent);
  transform: translate(calc(var(--disorder) * -0.15rem), calc(var(--disorder) * 0.12rem));
  z-index: -1;
}
.preview-thumb::after{
  inset: 26%;
  border-style: dashed;
  transform: translate(calc(var(--disorder) * 0.22rem), calc(var(--disorder) * -0.16rem));
}
.preview-thumb img{
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: grayscale(100%) contrast(1.1);
  mix-blend-mode: multiply;
}
.preview-mark{
  --preview-mark-spread: 0.62;
  font-family: var(--font-mono);
  font-size: clamp(1.45rem, 7vw, 3rem);
  font-weight: 500;
  line-height: 0.9;
  letter-spacing: 0;
  text-shadow:
    calc(var(--disorder) * 0.028em * var(--mis-spread, 1) * var(--preview-mark-spread))
    calc(var(--disorder) * 0.018em * var(--mis-spread, 1) * var(--preview-mark-spread))
    0 color-mix(in oklab, var(--p2) clamp(0%, calc(var(--disorder) * 14% * var(--mis-opacity, 1)), 42%), transparent),
    calc(var(--disorder) * -0.024em * var(--mis-spread, 1) * var(--preview-mark-spread))
    calc(var(--disorder) * -0.016em * var(--mis-spread, 1) * var(--preview-mark-spread))
    0 color-mix(in oklab, var(--p3) clamp(0%, calc(var(--disorder) * 12% * var(--mis-opacity, 1)), 36%), transparent);
}
.preview-title{ font-weight: 500; margin: 0 0 0.15rem; }
.preview-title a{ text-decoration: none; }
.preview-title a:hover{ text-decoration: underline; }
.preview-meta{
  font-family: var(--font-mono);
  font-size: var(--step--1);
  color: var(--ink-2);
  line-height: 1.35;
}
.preview-desc{
  font-size: var(--step--1);
  color: var(--ink-2);
  line-height: 1.45;
  margin-top: 0.35rem;
}
.preview-actions{
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem 0.65rem;
  margin-top: 0.45rem;
  font-family: var(--font-mono);
  font-size: var(--step--1);
}
.preview-actions a,
.preview-actions span{ text-decoration: none; }
.preview-actions a:hover{ text-decoration: underline; }

.thumb--tree{ background: #d8ddd8; }
.thumb--dot{ background: #ead779; }
.thumb--grpo{ background: #b7c7d2; }
.thumb--quiz,
.thumb--note{ background: #d8b7aa; }
.thumb--book,
.thumb--living{ background: #c8c0a4; }
.thumb--aero,
.thumb--essay{ background: #d4d9c5; }

.more-link{
  margin-top: 1.15rem;
  font-family: var(--font-mono);
  font-size: var(--step--1);
  text-transform: uppercase;
  letter-spacing: 0.06em;
}
.more-link a{
  display: inline-block;
  background: var(--accent);
  color: var(--paper);
  padding: 0.16rem 0.45rem 0.18rem;
  text-decoration: none;
  box-shadow:
    calc(0.18rem + var(--disorder) * 0.06rem)
    calc(0.18rem + var(--disorder) * 0.04rem)
    0 color-mix(in oklab, var(--p2) calc(10% + var(--disorder) * 9%), transparent);
}
.more-link a:hover{
  color: var(--paper);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  transform: translate(1px, 1px);
}

@media (max-width: 960px){
  .rail{
    grid-template-columns: 1fr;
    grid-template-areas:
      "rail"
      "main";
    gap: 0;
    padding: 0 1.5rem;
  }
  .rail__side{
    position: relative;
    justify-self: start;
    width: 100%;
    height: fit-content;
    padding: 2rem 0 1rem;
    align-items: flex-start;
    text-align: left;
    gap: 1rem;
  }
  .rail__top{ align-items: flex-start; gap: 1rem; }
  .rail__nav,
  .rail__links{
    flex-direction: row;
    flex-wrap: wrap;
    align-items: baseline;
  }
  .rail__nav{ gap: 0.15rem 0.9rem; }
  .rail__links{
    gap: 0.15rem 0.8rem;
    margin-top: 0;
    padding-top: 0.5rem;
    border-top: var(--hair) solid var(--hairline);
  }
  .rail__nav a{ white-space: normal; }
  .rail__main{ padding: 1rem 0 4rem; }
  .portrait{ width: 80px; height: 80px; }
  .preview-grid{ grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 500px){
  .rail{ padding: 0 1rem; }
  .preview-grid{ grid-template-columns: 1fr; }
}

/* ===========================================================================
   4b. FORM CONTROLS  — settings & inputs, on-system: square corners, hairline
   borders, mono labels, accent only on focus/checked. No rounded "friendly"
   inputs, no soft shadows. The accent appears solely to signal focus/state.
   =========================================================================== */
.field{ display:flex; flex-direction:column; gap:.4rem; margin-bottom:1.1rem; }
.field > label,
.field-label{
  font-family: var(--font-mono); font-size: var(--step--1);
  text-transform: uppercase; letter-spacing: 0.06em; color: var(--ink-2);
}
.field .hint{ font-family: var(--font-mono); font-size: 0.78em; color: var(--ink-3); }

input[type="text"], input[type="email"], input[type="password"], input[type="search"],
input[type="number"], input[type="url"], textarea, select{
  font-family: var(--font-sans); font-size: var(--step-0); color: var(--ink);
  background: var(--paper); border: var(--hair) solid var(--hairline);
  border-radius: 0; padding: .55rem .7rem; width: 100%;
  transition: border-color var(--dur) var(--ease);
}
html[data-mode="lab"] input, html[data-mode="lab"] textarea, html[data-mode="lab"] select,
html[data-mode="demo"] input, html[data-mode="demo"] textarea, html[data-mode="demo"] select{
  font-family: var(--font-mono); font-size: 0.92rem;
}
textarea{ resize: vertical; min-height: 5em; line-height: var(--lh-body); }
input:hover, textarea:hover, select:hover{ border-color: var(--ink-3); }
input:focus, textarea:focus, select:focus{ outline: none; border-color: var(--accent); box-shadow: inset 0 0 0 1px var(--accent); }
input::placeholder, textarea::placeholder{ color: var(--ink-3); }

select{
  appearance: none; -webkit-appearance: none; padding-right: 2rem;
  /* mono caret, drawn with a tiny inline triangle — no icon font */
  background-image: linear-gradient(45deg, transparent 50%, var(--ink-2) 50%),
                    linear-gradient(135deg, var(--ink-2) 50%, transparent 50%);
  background-position: calc(100% - 1.05rem) 1.05em, calc(100% - 0.7rem) 1.05em;
  background-size: 6px 6px, 6px 6px; background-repeat: no-repeat;
}

/* — Checkbox & radio: square accent box, square corners; radio is the lone circle. */
input[type="checkbox"], input[type="radio"]{
  appearance: none; -webkit-appearance: none; width: 1.05em; height: 1.05em;
  border: var(--hair) solid var(--ink-3); background: var(--paper);
  display: inline-grid; place-content: center; cursor: pointer; flex: 0 0 auto;
  transition: border-color var(--dur) var(--ease), background var(--dur) var(--ease);
  vertical-align: -0.18em;
}
input[type="radio"]{ border-radius: 50%; }
input[type="checkbox"]::before{ content:""; width: .58em; height: .58em; transform: scale(0);
  transition: transform var(--dur) var(--ease); background: var(--paper); clip-path: polygon(14% 44%,0 60%,40% 100%,100% 22%,86% 8%,40% 72%); }
input[type="radio"]::before{ content:""; width: .5em; height: .5em; border-radius: 50%; transform: scale(0);
  transition: transform var(--dur) var(--ease); background: var(--paper); }
input[type="checkbox"]:checked, input[type="radio"]:checked{ background: var(--accent); border-color: var(--accent); }
input[type="checkbox"]:checked::before, input[type="radio"]:checked::before{ transform: scale(1); }
input[type="checkbox"]:focus-visible, input[type="radio"]:focus-visible{ outline: 2px solid var(--accent); outline-offset: 2px; }
.choice{ display:flex; align-items:center; gap:.55rem; font-size: var(--step-0); cursor:pointer; }

/* — Toggle: a hard rectangular switch (no pill, no rounded track). — */
.switch{ position:relative; display:inline-flex; align-items:center; cursor:pointer; }
.switch input{ position:absolute; opacity:0; width:0; height:0; }
.switch .track{ width: 2.6em; height: 1.3em; border: var(--hair) solid var(--ink-3); background: var(--paper);
  position:relative; transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease); }
.switch .track::after{ content:""; position:absolute; top:1px; left:1px; width: calc(1.3em - 4px); height: calc(1.3em - 4px);
  background: var(--ink-3); transition: transform var(--dur) var(--ease), background var(--dur) var(--ease); }
.switch input:checked + .track{ border-color: var(--accent); }
.switch input:checked + .track::after{ transform: translateX(1.3em); background: var(--accent); }
.switch input:focus-visible + .track{ outline: 2px solid var(--accent); outline-offset: 2px; }

/* — Range: hairline rail, square accent thumb. — */
input[type="range"]{ -webkit-appearance:none; appearance:none; width:100%; height: 1.3em; background:transparent; cursor:pointer; }
input[type="range"]::-webkit-slider-runnable-track{ height: var(--hair); background: var(--ink-3); }
input[type="range"]::-moz-range-track{ height: var(--hair); background: var(--ink-3); }
input[type="range"]::-webkit-slider-thumb{ -webkit-appearance:none; appearance:none; width: .85em; height: .85em; background: var(--accent); margin-top: calc(-.42em + .5px); border:0; }
input[type="range"]::-moz-range-thumb{ width: .85em; height: .85em; background: var(--accent); border:0; border-radius:0; }
input[type="range"]:focus-visible::-webkit-slider-thumb{ outline: 2px solid var(--accent); outline-offset: 2px; }



/* — CROP MARKS — registration corner, "blueprint-y", structural not decorative.
   Wrap a region in .crop to get a top-left crop mark; .crop--tr/.bl/.br move it. */
.crop{ position: relative; }
.crop::before{
  content:""; position:absolute; top:-9px; left:-9px;
  width:10px; height:10px;
  border-left: var(--hair) solid var(--hairline);
  border-top:  var(--hair) solid var(--hairline);
}
.crop--plus::before{           /* "+" registration mark instead of corner */
  content:"+"; border:0; top:-1.1em; left:-0.5em;
  width:auto; height:auto; color:var(--ink-3);
  font-family:var(--font-mono); font-size:var(--step-0); line-height:1;
}

/* — BLUEPRINT FRAME — dashed hairline group box (lab mode). Names the group. */
.blueprint{
  border: var(--hair) dashed var(--hairline);
  padding: 1.5rem 1.25rem 1.25rem;
  position: relative;
}
.blueprint > .blueprint__legend{
  position:absolute; top:-0.66em; left:1rem;
  background: var(--paper); padding: 0 0.5em;
  font-family: var(--font-mono); font-size: var(--step--1);
  text-transform: uppercase; letter-spacing: 0.08em; color: var(--ink-2);
}

/* — HARD BOX — solid ink-bordered box, sharp corners (Tech Tree nodes). — */
.hardbox{
  border: 2px solid var(--ink);
  background: var(--paper);
  padding: 1rem 1.1rem;
}

/* — COLOR BLOCK — large block of color with a hairline ink border (studio). */
.block{
  background: var(--accent);
  color: var(--paper);
  border: var(--hair) solid var(--ink);
  padding: 1.25rem 1.4rem;
}

/* — HAIRLINE RULE — replaces dividers; sits under headings (doc/studio). — */
.rule{ border:0; border-top: var(--hair) solid var(--hairline); margin: 0; }

/* — DIRTIED FADE — the ONLY sanctioned "gradient": a textured, stepped fade,
   never a smooth marketing gradient. Used at bleed edges / underlays. — */
.fade-bleed{
  background:
    repeating-linear-gradient(180deg,
      color-mix(in oklab, var(--ink) 5%, transparent) 0 1px,
      transparent 1px 3px);
  -webkit-mask-image: linear-gradient(180deg, #000, transparent);
          mask-image: linear-gradient(180deg, #000, transparent);
}

/* — GRAYED UNDERLAY — image that bleeds UNDER text, desaturated/multiplied so
   the text stays legible. Signals labor without a hero graphic. — */
.underlay{
  filter: grayscale(100%) contrast(1.08);
  mix-blend-mode: multiply;
  opacity: 0.9;
}
html[data-mode="demo"] .underlay{ mix-blend-mode: screen; filter: grayscale(100%) contrast(1.1); }

/* — DOT MOTIF — the warm scatter cluster from the personal site. A signature.
   Built from CSS box-shadows (no image, no SVG file). Place inline near a
   heading. Sizes: .dots / .dots--sm. — */
.dots{
  position: relative; display:inline-block;
  width: 14px; height: 14px; margin-left: 0.1em; vertical-align: -0.1em;
}
.dots::before{
  content:""; position:absolute; left:0; top:0;
  width:6px; height:6px; border-radius:50%;
  background: var(--warm-coral);
  box-shadow:
     10px  -8px 0 -1px var(--warm-vermilion),
     -2px   9px 0 -1px var(--warm-orange),
     14px   6px 0  0   var(--warm-coral),
      4px  -4px 0 -2px var(--warm-rust),
     -6px  -2px 0 -2px var(--warm-orange),
      9px   2px 0 -2px var(--warm-rust),
     18px  -3px 0 -3px var(--warm-coral),
     -3px   3px 0 -3px var(--warm-vermilion);
}
.dots--sm{ transform: scale(0.7); transform-origin: left center; }

/* — STATUS DOT — filled / hollow circle for lab dashboards (active/inactive) */
.status{ display:inline-block; width:0.6em; height:0.6em; border-radius:50%;
  background: var(--ink-3); vertical-align: 0.05em; }
.status--on{ background:var(--ok); }
.status--warn{ background:var(--warn); }
.status--off{ background: transparent; border:1.5px solid var(--ink-3); }

/* — EXTERNAL-LINK MARK — typographic, not an icon font. — */
.ext::after{ content:"\2197"; /* ↗ */ font-family: var(--font-mono);
  font-size:0.85em; margin-left:0.15em; color: var(--ink-2); }

/* — EM-DASH BULLET — the list marker from the personal site. — */
.dashlist{ list-style:none; padding:0; margin:0; }
.dashlist > li{ position:relative; padding-left:1.4em; margin:0 0 0.5em; }
.dashlist > li::before{ content:"\2014"; position:absolute; left:0; color: var(--ink-3); }

/* — PROMPT MARK — ">" for lab/demo terminal-ish affordances. — */
.prompt::before{ content:"> "; font-family:var(--font-mono); color: var(--accent); }

/* Reduced motion: there's almost none, but honor it. */
@media (prefers-reduced-motion: reduce){
  *{ transition: none !important; animation: none !important; }
}
