/* ==========================================================================
   ericfrye.info — site stylesheet
   One typeface (Archivo variable: width + weight axes), black and white,
   one signal colour. Structure is carried by hairlines and tick marks.
   ========================================================================== */

/* ── Tokens ──────────────────────────────────────────────────────────── */
:root {
  --field:      #E9E9E4;
  --paper:      #FFFFFF;
  --paper-2:    #F3F3EF;
  --ink:        #000000;
  --ink-2:      #303030;
  --mute:       #6A6A64;
  --rule:       #CFCFC8;
  --rule-firm:  #000000;
  --signal:     #2F2BE8;
  --signal-ink: #FFFFFF;
  --edge:       #171717;
  --shade:      rgba(0,0,0,.055);

  --sans: 'Archivo', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
  /* Data labels only, matching the Pulsar Hell figures. No font to download. */
  --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, 'Liberation Mono', monospace;

  --pad: clamp(18px, 4vw, 56px);
  --max: 1240px;
  --bar: 56px;

  --ease: cubic-bezier(.22,1,.36,1);
  color-scheme: light;
}

:root[data-theme="dark"] {
  --field:      #0C0E11;
  --paper:      #15181C;
  --paper-2:    #1B1F24;
  --ink:        #ECEDEF;
  --ink-2:      #C4C7CC;
  --mute:       #8D939B;
  --rule:       #2B3038;
  --rule-firm:  #ECEDEF;
  --signal:     #8C89FF;
  --signal-ink: #0C0E11;
  --edge:       #E4E7EB;
  --shade:      rgba(255,255,255,.06);
  color-scheme: dark;
}

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

html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { animation-duration: .001ms !important; animation-iteration-count: 1 !important; transition-duration: .001ms !important; }
}

body {
  margin: 0;
  background: var(--field);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 17px;
  line-height: 1.55;
  font-variation-settings: 'wdth' 100, 'wght' 400;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
}

img, svg, canvas { display: block; max-width: 100%; }
img { height: auto; }
a { color: inherit; text-decoration: none; }
h1, h2, h3, h4 { margin: 0; font-weight: 600; letter-spacing: -.02em; line-height: 1.05; }
p { margin: 0; }
ul, ol, dl, dd { margin: 0; padding: 0; }
li { list-style: none; }

:focus-visible {
  outline: 2px solid var(--signal);
  outline-offset: 3px;
  border-radius: 2px;
}

::selection { background: var(--signal); color: var(--signal-ink); }

:target, [id] { scroll-margin-top: calc(var(--chrome-h, 56px) + 14px); }

.wrap { width: 100%; max-width: var(--max); margin: 0 auto; padding-inline: var(--pad); }

.skip {
  position: absolute; left: 12px; top: -60px; z-index: 200;
  background: var(--ink); color: var(--paper);
  padding: 10px 14px; font-size: 14px; font-weight: 600;
  transition: top .2s var(--ease);
}
.skip:focus { top: 12px; }

/* Screen-reader-only text. Used by the hero decode, which hides the
   animating glyphs from assistive tech and exposes the real sentence. */
.sr-only {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0;
}

/* Hero decode. Each character animates inside a box locked to its own
   advance width, so a swapped glyph cannot move a line break: a uniform
   draw from the 62-glyph set is ~19% wider than the headline and never
   narrower, which was adding two lines and 165px mid-animation. The word
   wrapper is what keeps a run of inline-blocks from breaking mid-word.
   Both are torn down when the animation finishes. */
[data-scramble] .sc-w { white-space: nowrap; }
[data-scramble] .sc-c { display: inline-block; text-align: center; }

/* Display type: the width axis does the work, not a second typeface. */
.display {
  font-variation-settings: 'wdth' 112, 'wght' 600;
  letter-spacing: -.035em;
  line-height: .98;
}
.num { font-variant-numeric: tabular-nums; }

/* ── Top bar ─────────────────────────────────────────────────────────── */
.topbar {
  position: sticky; top: 0; z-index: 100;
  background: var(--field);
  background: color-mix(in srgb, var(--field) 86%, transparent);
  backdrop-filter: saturate(1.4) blur(10px);
  -webkit-backdrop-filter: saturate(1.4) blur(10px);
  border-bottom: 1px solid var(--edge);
}
.topbar-in {
  min-height: var(--bar);
  display: flex; align-items: center; gap: 16px;
  width: 100%; max-width: var(--max); margin: 0 auto; padding-inline: var(--pad);
}
.wordmark {
  font-variation-settings: 'wdth' 112, 'wght' 600;
  font-size: 16px; letter-spacing: -.02em; white-space: nowrap;
  display: inline-flex; align-items: center; gap: 9px;
}
.mark { width: 19px; height: 19px; flex: none; border-radius: 4px; }
:root[data-theme="dark"] .mark { filter: invert(1); }
.foot-mark { display: inline-block; vertical-align: -4px; margin-right: 8px; width: 17px; height: 17px; }
.nav { display: flex; align-items: center; gap: 4px; margin-left: auto; }
.nav a {
  font-size: 14.5px; padding: 8px 13px;
  color: var(--ink-2); white-space: nowrap;
  transition: color .18s var(--ease), background-color .18s var(--ease);
}
.nav a:hover { color: var(--ink); background: var(--shade); }
.nav a[aria-current="page"] { color: var(--ink); font-variation-settings: 'wdth' 100, 'wght' 600; }

.console { display: flex; align-items: center; gap: 6px; }
.console-sep { width: 1px; height: 22px; background: var(--rule); margin-inline: 6px; flex: none; }

.pill {
  display: inline-flex; align-items: center; gap: 7px;
  height: 32px; padding: 0 11px;
  border: 1px solid var(--rule); border-radius: 999px;
  background: transparent; color: var(--ink-2);
  font-family: inherit; font-size: 13px; line-height: 1;
  cursor: pointer; white-space: nowrap;
  transition: border-color .18s var(--ease), color .18s var(--ease);
}
.pill:hover { border-color: var(--rule-firm); color: var(--ink); }
.pill[aria-pressed="true"] { border-color: var(--signal); color: var(--ink); }
.pill svg { width: 15px; height: 15px; flex: none; }

/* live readout — Minneapolis, weather, local time */
.readout {
  display: inline-flex; align-items: center; gap: 8px;
  font-size: 13px; color: var(--mute); white-space: nowrap;
}
.readout b { font-variation-settings: 'wdth' 100, 'wght' 500; color: var(--ink-2); font-weight: 400; }
.readout .sky { font-size: 14px; line-height: 1; }
.readout [data-temp] { color: var(--ink-2); }
.pill[aria-pressed="true"] [data-sound-label] { color: var(--signal); }

/* sound control: power on the left, patch picker on the right */
.sound { position: relative; }
.sound-group { display: flex; }
.sound-group .pill { border-radius: 0; }
.sound-group .pill:first-child { border-radius: 999px 0 0 999px; padding-right: 10px; }
.sound-group .pill.caret { border-radius: 0 999px 999px 0; margin-left: -1px; padding-inline: 9px; }
.sound-group .pill.caret svg { width: 13px; height: 13px; }
.sound-group .pill[aria-pressed="true"] + .pill.caret { border-left-color: var(--signal); }
.sound-menu {
  position: absolute; right: 0; top: calc(100% + 8px);
  min-width: 208px; padding: 6px;
  background: var(--paper); border: 1px solid var(--edge);
  box-shadow: 0 12px 28px -18px rgba(0,0,0,.5);
  display: none;
}
.sound-menu[data-open="true"] { display: block; }
.sound-menu li + li { margin-top: 2px; }
.sound-menu button {
  width: 100%; text-align: left; cursor: pointer;
  background: transparent; border: 0; color: var(--ink-2);
  font-family: inherit; font-size: 14px; padding: 10px 12px;
  display: flex; align-items: baseline; gap: 8px;
  transition: background-color .16s var(--ease), color .16s var(--ease);
}
.sound-menu button:hover { background: var(--shade); color: var(--ink); }
.sound-menu button[aria-checked="true"] { color: var(--ink); font-variation-settings: 'wdth' 100, 'wght' 600; }
.sound-menu button span { color: var(--mute); font-size: 12.5px; font-variation-settings: 'wdth' 100, 'wght' 400; margin-left: auto; }
.sound-note { padding: 8px 10px 6px; font-size: 12px; color: var(--mute); line-height: 1.4; border-top: 1px solid var(--rule); margin-top: 6px; }

.menu-btn { display: none; }

@media (max-width: 860px) {
  .topbar-in { flex-wrap: wrap; row-gap: 0; }
  .console { margin-left: auto; }
  .nav {
    order: 3; width: 100%; margin-left: 0;
    border-top: 1px solid var(--rule);
    padding-block: 6px; gap: 2px; overflow-x: auto; scrollbar-width: none;
  }
  .nav::-webkit-scrollbar { display: none; }
  .readout .city { display: none; }
}
@media (max-width: 460px) {
  .readout { display: none; }
  .console-sep { display: none; }
}

/* The role bar and the results row share one system, taken from the card
   grid already solved on the Motif page: cells draw their own left and top
   hairline, the container clips the outer ones, and an unfilled cell shows
   the card's own background rather than a grey block. */
.meta-bar {
  display: flex; flex-wrap: wrap;
  border: 1px solid var(--edge); background: var(--paper); overflow: hidden;
}
.meta-bar > div {
  flex: 1 1 212px; min-width: 0; padding: 17px 20px 20px;
  box-shadow: -1px 0 0 var(--rule), 0 -1px 0 var(--rule);
}
.meta-bar dt {
  font-size: 10.5px; letter-spacing: .15em; text-transform: uppercase;
  color: var(--signal); font-variation-settings: 'wdth' 92, 'wght' 700;
}
.meta-bar dd { margin: 0; }
.meta-bar .lead {
  margin-top: 11px; font-size: 15.5px; line-height: 1.3;
  font-variation-settings: 'wdth' 100, 'wght' 600; color: var(--ink);
}
.meta-bar .detail {
  margin-top: 7px; font-size: 13.5px; line-height: 1.5;
  color: var(--ink-2); overflow-wrap: anywhere;
}

/* ── Hero ───────────────────────────────────────────────────────────
   The card pins and the writing scrolls past it, so the two columns can
   never fall out of balance — which is what kept opening a hole. */
.hero { padding-block: clamp(24px, 3.2vw, 44px) clamp(26px, 3.4vw, 46px); }
.hero-grid {
  display: grid; grid-template-columns: minmax(0, 300px) minmax(0, 1fr);
  gap: clamp(24px, 4vw, 64px); align-items: start;
}
/* The measure, not a <br>, sets the shape of the hero. 16ch is the point at
   which the headline fills the whole hero column from 1200px up — at 13ch it
   stopped around 620px inside a 756px column and left the right-hand third
   empty. Past 16ch the number is inert: the column, not the measure, is the
   binding constraint. balance then evens the five lines out; without it a
   measure this wide strands a single word around 1200-1280px. */
.hero-main h1 { font-size: clamp(34px, 6vw, 84px); max-width: 16ch;
  text-wrap: balance; }
.hero-main p {
  margin-top: 22px; font-size: clamp(15.5px, 1.3vw, 18.5px);
  color: var(--ink-2); max-width: 52ch;
}
.hero-main p + p { margin-top: 14px; }
.hero-main b { font-variation-settings: 'wdth' 100, 'wght' 600; font-weight: 400; color: var(--ink); }

.hud {
  position: sticky; top: calc(var(--chrome-h, 56px) + 22px);
  border: 1px solid var(--edge); background: var(--paper);
}
.hud-hd {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 10px 14px; background: var(--ink); color: var(--paper);
  font-family: var(--mono); font-size: 10px; letter-spacing: .12em; text-transform: uppercase;
}
.hud dl {
  display: grid; grid-template-columns: auto minmax(0, 1fr);
  gap: 8px 14px; padding: 14px; margin: 0; font-size: 13.5px;
}
.hud dt { color: var(--mute); }
.hud dd { margin: 0; color: var(--ink); min-width: 0; }
.hud dd a { border-bottom: 1px solid var(--signal); }
.hud dd a:hover { color: var(--signal); }
.hud-scope { position: relative; height: 92px; border-top: 1px solid var(--rule); }
.hud-scope canvas { position: absolute; inset: 0; width: 100%; height: 100%; }
.hud .trace-cap {
  display: flex; justify-content: space-between; gap: 10px; padding: 10px 14px 0;
  font-family: var(--mono); font-size: 10px; letter-spacing: .1em;
  text-transform: uppercase; color: var(--mute);
}
.hud-cta {
  display: block; margin: 12px 14px 14px; padding: 10px; text-align: center;
  background: var(--ink); color: var(--paper);
  font-family: var(--mono); font-size: 10.5px; letter-spacing: .12em; text-transform: uppercase;
  transition: background-color .2s var(--ease), color .2s var(--ease);
}
.hud-cta:hover { background: var(--signal); color: var(--signal-ink); }

@media (max-width: 900px) {
  .hero-grid { grid-template-columns: 1fr; }
  /* One column here, so the headline takes the width it has. balance keeps
     the line lengths even as that width changes — without it the wrap leaves
     "trust." alone on the last line around 380-400px. */
  .hero-main h1 { max-width: none; text-wrap: balance; }
  .hud { position: static; }
}

/* ── Section frame + tick rail ───────────────────────────────────────── */
.sec { padding-block: clamp(44px, 6vw, 88px); }
.sec-head {
  display: flex; align-items: baseline; justify-content: space-between; gap: 20px;
  padding-bottom: 14px; border-bottom: 1px solid var(--rule-firm);
}
.sec-head h2 { font-size: clamp(24px, 2.6vw, 34px); font-variation-settings: 'wdth' 108, 'wght' 600; }
.sec-head .aside { font-size: 14px; color: var(--mute); text-align: right; }
.sec-head .aside .lnk { border-bottom-color: var(--rule); }
.rail { display: flex; gap: 3px; align-items: flex-end; height: 12px; margin-top: 2px; }
.rail i {
  flex: 1; height: 2px; background: var(--rule);
  transition: background-color .5s var(--ease), height .5s var(--ease);
}
.rail i.on { background: var(--signal); height: 12px; }

/* ── Work grid ───────────────────────────────────────────────────────── */
.work-grid {
  margin-top: clamp(26px, 3vw, 40px);
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(30px, 3.4vw, 48px) clamp(20px, 2.4vw, 34px);
}
.proj {
  display: block; min-width: 0; background: var(--paper);
  border: 1px solid var(--edge);
  transition: border-color .3s var(--ease), transform .4s var(--ease);
}
.proj:hover, .proj:focus-visible { border-color: var(--rule-firm); transform: translateY(-3px); }
.proj-shot {
  position: relative; overflow: hidden;
  aspect-ratio: 7 / 4; background: var(--paper-2);
  border-bottom: 1px solid var(--rule);
}
.proj-shot img { transition: transform .7s var(--ease); }
.proj:hover .proj-shot img { transform: scale(1.02); }
.proj-shot img { width: 100%; height: 100%; object-fit: cover; object-position: left center; }
.proj-shot::after {
  content: ""; position: absolute; left: 0; bottom: 0; height: 3px; width: 0; z-index: 2;
  background: var(--signal); transition: width .5s var(--ease);
}
.proj:hover .proj-shot::after, .proj:focus-visible .proj-shot::after { width: 100%; }

.proj-body { padding: 18px 20px 22px; }
.proj-body h3 {
  font-size: clamp(19px, 1.7vw, 24px); line-height: 1.15;
  font-variation-settings: 'wdth' 106, 'wght' 600;
  transition: font-variation-settings .5s var(--ease);
}
.proj:hover .proj-body h3, .proj:focus-visible .proj-body h3 { font-variation-settings: 'wdth' 124, 'wght' 600; }
.proj-body h3 .yr {
  color: var(--mute); font-variation-settings: 'wdth' 100, 'wght' 400;
  font-size: 14px; margin-left: 9px; letter-spacing: 0; white-space: nowrap;
}
.proj-body p { margin-top: 10px; color: var(--ink-2); font-size: 15.5px; max-width: 54ch; }

.chips { display: flex; flex-wrap: wrap; gap: 7px; }
/* On a project card the pills are a row of their own: a stable height so the
   titles line up across the grid, and real space before the title. */
.proj-body .chips { min-height: 28px; margin-bottom: 16px; }
.chip {
  display: inline-flex; align-items: baseline; gap: 6px;
  border: 1px solid var(--rule); border-radius: 999px;
  padding: 5px 11px; font-size: 12.5px; line-height: 1.25;
  max-width: 100%; min-width: 0;
}
.chip .k { color: var(--mute); }
.chip .v { font-variation-settings: 'wdth' 100, 'wght' 600; font-variant-numeric: tabular-nums; }
.chip.plain { font-size: 13px; }

.facts {
  margin-top: 18px; padding-top: 15px; border-top: 1px solid var(--rule);
  display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 14px 30px;
}
.facts dt { font-size: 11.5px; color: var(--mute); margin-bottom: 4px; }
.facts dd { font-size: 13px; line-height: 1.45; color: var(--ink-2); overflow-wrap: anywhere; }

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


/* ── Prose sections (about / resume) ─────────────────────────────────── */
.split {
  display: grid; grid-template-columns: minmax(0, 260px) minmax(0, 1fr);
  gap: clamp(18px, 3vw, 56px); align-items: start;
  padding-block: clamp(34px, 4.4vw, 60px);
  border-top: 1px solid var(--rule);
}
.split:first-of-type { border-top: 0; }
.split > h2 {
  font-size: clamp(20px, 2vw, 26px); font-variation-settings: 'wdth' 108, 'wght' 600;
  position: sticky; top: calc(var(--bar) + 18px);
}
.prose p { font-size: 16.5px; color: var(--ink-2); max-width: 68ch; }
.prose p + p { margin-top: 14px; }
.prose p b, .prose li b { font-variation-settings: 'wdth' 100, 'wght' 600; font-weight: 400; color: var(--ink); }

.steps { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 20px 28px; margin-top: 22px; }
.step { border-top: 1px solid var(--rule-firm); padding: 14px 0 0; }
.step h3 { font-size: 16.5px; font-variation-settings: 'wdth' 100, 'wght' 600; }
.step p { margin-top: 5px; font-size: 15px; color: var(--ink-2); }

.rows { margin-top: 4px; }
.rows > div {
  display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 10px 18px;
  padding: 12px 0; border-top: 1px solid var(--rule); font-size: 15.5px; align-items: baseline;
}
.rows > div:first-child { border-top: 1px solid var(--rule-firm); }
.rows .what { color: var(--mute); text-align: right; font-size: 14.5px; }

.linkrow { display: flex; flex-wrap: wrap; gap: 12px 26px; margin-top: 22px; }
.lnk {
  display: inline-flex; align-items: center; gap: 9px;
  font-variation-settings: 'wdth' 100, 'wght' 600;
  border-bottom: 1.5px solid var(--rule-firm); padding-bottom: 2px;
}
.lnk:hover { color: var(--signal); border-bottom-color: var(--signal); }
.lnk svg { width: 13px; height: 13px; }

/* résumé */
.job { padding-block: 22px; border-top: 1px solid var(--rule); }
.job:first-child { border-top: 0; padding-top: 0; }
.job-head { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: 4px 18px; }
.job-head h3 { font-size: 18px; font-variation-settings: 'wdth' 104, 'wght' 600; max-width: 46ch; }
.job-head .when { font-size: 14px; color: var(--mute); font-variant-numeric: tabular-nums; white-space: nowrap; }
.job .org { margin-top: 3px; font-size: 14.5px; color: var(--mute); }
.job ul { margin-top: 14px; display: grid; gap: 10px; }
.job li { position: relative; padding-left: 18px; font-size: 15.5px; color: var(--ink-2); max-width: 74ch; }
.job li::before {
  content: ""; position: absolute; left: 0; top: .62em;
  width: 7px; height: 1px; background: var(--signal);
}
.job.compact p { margin-top: 6px; font-size: 15px; color: var(--ink-2); max-width: 74ch; }

.skills { display: grid; gap: 16px; }
.skills dt { font-variation-settings: 'wdth' 100, 'wght' 600; font-size: 15px; }
.skills dd { margin-top: 4px; font-size: 15px; color: var(--ink-2); max-width: 74ch; }

@media (max-width: 780px) {
  .split { grid-template-columns: 1fr; gap: 14px; }
  .split > h2 { position: static; }
  .steps { grid-template-columns: 1fr; }
}

/* ── Footer ─────────────────────────────────────────────────────────── */

.foot { border-top: 1px solid var(--rule); padding-block: 26px 40px; font-size: 13.5px; color: var(--mute); }
.foot-in { display: flex; flex-wrap: wrap; gap: 14px 28px; align-items: baseline; justify-content: space-between; }
.foot a { border-bottom: 1px solid var(--rule); }
.foot a:hover { color: var(--ink); border-bottom-color: var(--ink); }
.foot-links { display: flex; flex-wrap: wrap; gap: 10px 20px; }
.foot .colophon { max-width: 46ch; line-height: 1.5; }

/* ── 404 ─────────────────────────────────────────────────────────────── */
.oops { padding-block: clamp(60px, 12vw, 140px); }
.oops h1 { font-size: clamp(40px, 8vw, 84px); font-variation-settings: 'wdth' 112, 'wght' 600; }
.oops p { margin-top: 18px; font-size: 17px; color: var(--ink-2); max-width: 50ch; }

/* home: practice block sits under the section head, full measure */
.practice { padding-top: clamp(24px, 3vw, 38px); }
.practice .prose > p { font-size: clamp(17px, 1.6vw, 20px); color: var(--ink); max-width: 66ch; }


/* ── Sound and stage: sets, performances, releases ───────────────────
   Rows open in place. Nothing is fetched from YouTube, Vimeo or
   Bandcamp until the row is opened.                                  */
.media { margin-top: clamp(22px, 3vw, 34px); border-top: 1px solid var(--edge); }
.media-item { border-bottom: 1px solid var(--rule); }
.media-row {
  display: grid; grid-template-columns: 104px minmax(0, 1fr) auto auto;
  gap: 4px 22px; align-items: baseline; width: 100%; text-align: left;
  padding: 18px; margin: 0; border: 0; background: transparent;
  color: inherit; font: inherit; cursor: pointer;
  transition: background-color .22s var(--ease);
}
.media-row:hover { background: var(--paper); }
.media .kind { font-size: 12.5px; color: var(--mute); }
.media .mid { min-width: 0; }
.media .ttl {
  font-size: 17px; font-variation-settings: 'wdth' 102, 'wght' 600; letter-spacing: -.015em;
  transition: font-variation-settings .5s var(--ease), color .2s var(--ease);
}
.media-row:hover .ttl { color: var(--signal); font-variation-settings: 'wdth' 120, 'wght' 600; }
.media .sub { display: block; margin-top: 3px; font-size: 14.5px; color: var(--ink-2); }
.media .when { font-size: 13.5px; color: var(--mute); white-space: nowrap; text-align: right; }
.media .cue {
  font-size: 12px; color: var(--mute); white-space: nowrap;
  border: 1px solid var(--rule); padding: 4px 9px; align-self: center;
  transition: border-color .2s var(--ease), color .2s var(--ease);
}
.media-row:hover .cue { border-color: var(--signal); color: var(--signal); }
.media-row[aria-expanded="true"] .cue { border-color: var(--signal); color: var(--signal); }

.media-player { padding: 0 18px 22px; }
.media-facade {
  position: relative; display: grid; place-items: center;
  height: clamp(84px, 11vw, 120px); cursor: pointer;
  border: 1px solid var(--edge); background: var(--paper);
  color: color-mix(in srgb, var(--signal) 55%, transparent);
  overflow: hidden;
}
.media-facade .wave { position: absolute; inset: 0; width: 100%; height: 100%; opacity: .6; }
.media-facade::after {
  content: "Play"; position: relative;
  background: var(--ink); color: var(--paper);
  font-size: 12.5px; font-variation-settings: 'wdth' 100, 'wght' 600;
  padding: 8px 16px;
}
.media-facade:hover::after { background: var(--signal); color: var(--signal-ink); }
.media-frame {
  position: relative; aspect-ratio: 16 / 9;
  border: 1px solid var(--edge); background: #000;
}
.media-frame.audio {
  aspect-ratio: auto; height: 120px; max-width: 640px;
  background: var(--paper);
}
.media-frame iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }
.media-frame.audio iframe { position: static; height: 120px; }
.media-credit { margin-top: 10px; font-size: 12.5px; color: var(--mute); }
.media-credit a { border-bottom: 1px solid var(--rule); }
.media-credit a:hover { color: var(--signal); border-bottom-color: var(--signal); }

@media (max-width: 620px) {
  .media-row { grid-template-columns: minmax(0, 1fr) auto; }
  .media .kind { grid-column: 1 / -1; }
  .media .cue { grid-column: 2; }
}

/* ── Background timeline ─────────────────────────────────────────────── */
.tl { margin-top: 8px; border-top: 1px solid var(--rule-firm); }
.tl-item {
  display: grid; grid-template-columns: minmax(0, 220px) minmax(0, 1fr);
  gap: 10px 32px; padding: 22px 18px; margin-inline: -18px;
  border-bottom: 1px solid var(--rule);
  transition: background-color .22s var(--ease);
}
.tl-item:hover { background: var(--paper); }
.tl-where { display: flex; align-items: flex-start; gap: 11px; min-width: 0; }
.tl-mark {
  flex: none; width: 30px; height: 28px; border: 1px solid var(--rule-firm);
  display: grid; place-items: center; overflow: hidden;
  color: var(--signal); background: var(--paper);
}
.tl-mark svg { width: 28px; height: 26px; }
.tl-mark img { width: 22px; height: 22px; object-fit: contain; }
:root[data-theme="dark"] .tl-mark img { filter: invert(1) hue-rotate(180deg); }
.tl-org { font-size: 15px; font-variation-settings: 'wdth' 100, 'wght' 600; }
.tl-loc { display: block; margin-top: 2px; font-size: 13px; color: var(--mute); font-variation-settings: 'wdth' 100, 'wght' 400; }
.tl-when { font-size: 13.5px; color: var(--mute); font-variant-numeric: tabular-nums; }
.tl-role { font-size: 17px; font-variation-settings: 'wdth' 102, 'wght' 600; letter-spacing: -.015em; margin-top: 2px; }
.tl-body { font-size: 15.5px; color: var(--ink-2); margin-top: 6px; max-width: 68ch; }
.tl-body a { border-bottom: 1px solid var(--rule); }
.tl-body a:hover { color: var(--signal); border-bottom-color: var(--signal); }

@media (max-width: 780px) {
  .tl-item { grid-template-columns: 1fr; gap: 6px; }
}


/* ── The eighth cell: the instrument ─────────────────────────────────
   The header control in miniature, opened out: a larger reading of the
   grain model and the parameters it is drawn from. Same anatomy as the
   project cards beside it, and the same drawing language as the figures
   in the Deviation DSP case study. */
.inst {
  display: flex; flex-direction: column; min-width: 0; margin: 0;
  background: var(--paper); border: 1px solid var(--edge);
}
.inst-hd {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 9px 12px 9px 16px; background: var(--ink); color: var(--paper);
  font-family: var(--mono); font-size: 10.5px; letter-spacing: .1em; text-transform: uppercase;
}
.inst-power {
  border: 1px solid color-mix(in srgb, var(--paper) 45%, transparent);
  background: transparent; color: var(--paper); cursor: pointer;
  font: inherit; letter-spacing: .1em; padding: 4px 12px;
  transition: background-color .18s var(--ease), border-color .18s var(--ease), color .18s var(--ease);
}
.inst-power:hover { border-color: var(--paper); }
.inst-power[aria-pressed="true"] { background: var(--signal); border-color: var(--signal); color: var(--signal-ink); }

.scope-screen {
  position: relative; aspect-ratio: 7 / 4; overflow: hidden;
  background: var(--paper-2); color: var(--ink);
  border-bottom: 1px solid var(--edge);
}
.scope-screen canvas { position: absolute; inset: 0; width: 100%; height: 100%; }

.inst-body { padding: 14px 16px 18px; }
.inst-patches { display: flex; margin-bottom: 18px; }
.inst-patches button {
  flex: 1 1 0; cursor: pointer; background: transparent; color: var(--ink-2);
  border: 1px solid var(--rule); border-left-width: 0;
  font-family: var(--mono); font-size: 10.5px; letter-spacing: .1em; text-transform: uppercase;
  padding: 8px 4px;
  transition: color .18s var(--ease), background-color .18s var(--ease), border-color .18s var(--ease);
}
.inst-patches button:first-child { border-left-width: 1px; }
.inst-patches button:hover { color: var(--ink); background: var(--shade); }
.inst-patches button[aria-pressed="true"] {
  color: var(--signal-ink); background: var(--signal); border-color: var(--signal);
}

.scope-controls { display: grid; gap: 15px; }
.pair { display: grid; grid-template-columns: 1fr 1fr; gap: 12px 16px; }
.inst-patches.engines { margin-bottom: 10px; }
.inst-patches.engines button { font-variation-settings: 'wdth' 100, 'wght' 700; }
.sound-group {
  padding: 8px 10px 4px; font-family: var(--mono); font-size: 9.5px;
  letter-spacing: .14em; text-transform: uppercase; color: var(--mute);
}
.slider { display: grid; grid-template-columns: 1fr auto; gap: 7px 10px; align-items: baseline; }
.slider label {
  font-family: var(--mono); font-size: 9.5px; letter-spacing: .12em;
  text-transform: uppercase; color: var(--mute);
}
.slider output {
  font-family: var(--mono); font-size: 12px; color: var(--ink);
  font-variant-numeric: tabular-nums; text-align: right;
}
.slider input[type="range"] {
  grid-column: 1 / -1; width: 100%; margin: 0; height: 18px;
  accent-color: var(--signal); cursor: pointer;
}
.scope-readout {
  margin-top: 18px; padding-top: 12px; border-top: 1px solid var(--rule);
  font-family: var(--mono); font-size: 10.5px; letter-spacing: .04em;
  color: var(--mute); font-variant-numeric: tabular-nums;
}
.inst figcaption { margin-top: 10px; font-size: 12.5px; line-height: 1.45; color: var(--mute); max-width: 42ch; }


/* ── Sound and stage: sets, performances, releases ───────────────────
   Rows open in place. Nothing is fetched from YouTube, Vimeo or
   Bandcamp until the row is opened.                                  */
.media { margin-top: clamp(22px, 3vw, 34px); border-top: 1px solid var(--edge); }
.media-item { border-bottom: 1px solid var(--rule); }
.media-row {
  display: grid; grid-template-columns: 104px minmax(0, 1fr) auto auto;
  gap: 4px 22px; align-items: baseline; width: 100%; text-align: left;
  padding: 18px; margin: 0; border: 0; background: transparent;
  color: inherit; font: inherit; cursor: pointer;
  transition: background-color .22s var(--ease);
}
.media-row:hover { background: var(--paper); }
.media .kind { font-size: 12.5px; color: var(--mute); }
.media .mid { min-width: 0; }
.media .ttl {
  font-size: 17px; font-variation-settings: 'wdth' 102, 'wght' 600; letter-spacing: -.015em;
  transition: font-variation-settings .5s var(--ease), color .2s var(--ease);
}
.media-row:hover .ttl { color: var(--signal); font-variation-settings: 'wdth' 120, 'wght' 600; }
.media .sub { display: block; margin-top: 3px; font-size: 14.5px; color: var(--ink-2); }
.media .when { font-size: 13.5px; color: var(--mute); white-space: nowrap; text-align: right; }
.media .cue {
  font-size: 12px; color: var(--mute); white-space: nowrap;
  border: 1px solid var(--rule); padding: 4px 9px; align-self: center;
  transition: border-color .2s var(--ease), color .2s var(--ease);
}
.media-row:hover .cue { border-color: var(--signal); color: var(--signal); }
.media-row[aria-expanded="true"] .cue { border-color: var(--signal); color: var(--signal); }

.media-player { padding: 0 18px 22px; }
.media-facade {
  position: relative; display: grid; place-items: center;
  height: clamp(84px, 11vw, 120px); cursor: pointer;
  border: 1px solid var(--edge); background: var(--paper);
  color: color-mix(in srgb, var(--signal) 55%, transparent);
  overflow: hidden;
}
.media-facade .wave { position: absolute; inset: 0; width: 100%; height: 100%; opacity: .6; }
.media-facade::after {
  content: "Play"; position: relative;
  background: var(--ink); color: var(--paper);
  font-size: 12.5px; font-variation-settings: 'wdth' 100, 'wght' 600;
  padding: 8px 16px;
}
.media-facade:hover::after { background: var(--signal); color: var(--signal-ink); }
.media-frame {
  position: relative; aspect-ratio: 16 / 9;
  border: 1px solid var(--edge); background: #000;
}
.media-frame.audio {
  aspect-ratio: auto; height: 120px; max-width: 640px;
  background: var(--paper);
}
.media-frame iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }
.media-frame.audio iframe { position: static; height: 120px; }
.media-credit { margin-top: 10px; font-size: 12.5px; color: var(--mute); }
.media-credit a { border-bottom: 1px solid var(--rule); }
.media-credit a:hover { color: var(--signal); border-bottom-color: var(--signal); }

@media (max-width: 620px) {
  .media-row { grid-template-columns: minmax(0, 1fr) auto; }
  .media .kind { grid-column: 1 / -1; }
  .media .cue { grid-column: 2; }
}

/* ── Background timeline ─────────────────────────────────────────────── */
.tl { margin-top: 8px; border-top: 1px solid var(--rule-firm); }
.tl-item {
  display: grid; grid-template-columns: minmax(0, 220px) minmax(0, 1fr);
  gap: 10px 32px; padding: 22px 18px; margin-inline: -18px;
  border-bottom: 1px solid var(--rule);
  transition: background-color .22s var(--ease);
}
.tl-item:hover { background: var(--paper); }
.tl-where { display: flex; align-items: flex-start; gap: 11px; min-width: 0; }
.tl-mark {
  flex: none; width: 30px; height: 28px; border: 1px solid var(--rule-firm);
  display: grid; place-items: center; overflow: hidden;
  color: var(--signal); background: var(--paper);
}
.tl-mark svg { width: 28px; height: 26px; }
.tl-mark img { width: 22px; height: 22px; object-fit: contain; }
:root[data-theme="dark"] .tl-mark img { filter: invert(1) hue-rotate(180deg); }
.tl-org { font-size: 15px; font-variation-settings: 'wdth' 100, 'wght' 600; }
.tl-loc { display: block; margin-top: 2px; font-size: 13px; color: var(--mute); font-variation-settings: 'wdth' 100, 'wght' 400; }
.tl-when { font-size: 13.5px; color: var(--mute); font-variant-numeric: tabular-nums; }
.tl-role { font-size: 17px; font-variation-settings: 'wdth' 102, 'wght' 600; letter-spacing: -.015em; margin-top: 2px; }
.tl-body { font-size: 15.5px; color: var(--ink-2); margin-top: 6px; max-width: 68ch; }
.tl-body a { border-bottom: 1px solid var(--rule); }
.tl-body a:hover { color: var(--signal); border-bottom-color: var(--signal); }

@media (max-width: 780px) {
  .tl-item { grid-template-columns: 1fr; gap: 6px; }
}


/* ── .proj-note: unused ──────────────────────────────────────────────
   No HTML or JS in the repo's history has ever used this class; the
   eighth cell is .inst, above. The prose below describes a panel that
   never shipped.
   Same anatomy as the project cards beside it: a field on top, a body
   underneath. Only the field inverts, so the grain has something to read
   against in either theme. */
.proj-note {
  display: flex; flex-direction: column; min-width: 0;
  background: var(--paper); border: 1px solid var(--edge);
}
.proj-note .field {
  position: relative; aspect-ratio: 7 / 4; overflow: hidden;
  background: var(--ink); color: var(--paper);
  border-bottom: 1px solid var(--edge);
}
.proj-note canvas { position: absolute; inset: 0; width: 100%; height: 100%; }
.proj-note .stamp {
  position: absolute; left: 18px; bottom: 15px;
  font-size: 11.5px; letter-spacing: .02em;
  color: color-mix(in srgb, var(--paper) 60%, transparent);
}
.proj-note-body { padding: 18px 20px 22px; }
.proj-note-body p { font-size: 16px; line-height: 1.5; color: var(--ink-2); max-width: 44ch; }
.proj-note-body p + p { margin-top: 12px; }
.proj-note-body .lnk { margin-top: 18px; }

/* ── Sound and stage: sets, performances, releases ───────────────────
   Rows open in place. Nothing is fetched from YouTube, Vimeo or
   Bandcamp until the row is opened.                                  */
.media { margin-top: clamp(22px, 3vw, 34px); border-top: 1px solid var(--edge); }
.media-item { border-bottom: 1px solid var(--rule); }
.media-row {
  display: grid; grid-template-columns: 104px minmax(0, 1fr) auto auto;
  gap: 4px 22px; align-items: baseline; width: 100%; text-align: left;
  padding: 18px; margin: 0; border: 0; background: transparent;
  color: inherit; font: inherit; cursor: pointer;
  transition: background-color .22s var(--ease);
}
.media-row:hover { background: var(--paper); }
.media .kind { font-size: 12.5px; color: var(--mute); }
.media .mid { min-width: 0; }
.media .ttl {
  font-size: 17px; font-variation-settings: 'wdth' 102, 'wght' 600; letter-spacing: -.015em;
  transition: font-variation-settings .5s var(--ease), color .2s var(--ease);
}
.media-row:hover .ttl { color: var(--signal); font-variation-settings: 'wdth' 120, 'wght' 600; }
.media .sub { display: block; margin-top: 3px; font-size: 14.5px; color: var(--ink-2); }
.media .when { font-size: 13.5px; color: var(--mute); white-space: nowrap; text-align: right; }
.media .cue {
  font-size: 12px; color: var(--mute); white-space: nowrap;
  border: 1px solid var(--rule); padding: 4px 9px; align-self: center;
  transition: border-color .2s var(--ease), color .2s var(--ease);
}
.media-row:hover .cue { border-color: var(--signal); color: var(--signal); }
.media-row[aria-expanded="true"] .cue { border-color: var(--signal); color: var(--signal); }

.media-player { padding: 0 18px 22px; }
.media-facade {
  position: relative; display: grid; place-items: center;
  height: clamp(84px, 11vw, 120px); cursor: pointer;
  border: 1px solid var(--edge); background: var(--paper);
  color: color-mix(in srgb, var(--signal) 55%, transparent);
  overflow: hidden;
}
.media-facade .wave { position: absolute; inset: 0; width: 100%; height: 100%; opacity: .6; }
.media-facade::after {
  content: "Play"; position: relative;
  background: var(--ink); color: var(--paper);
  font-size: 12.5px; font-variation-settings: 'wdth' 100, 'wght' 600;
  padding: 8px 16px;
}
.media-facade:hover::after { background: var(--signal); color: var(--signal-ink); }
.media-frame {
  position: relative; aspect-ratio: 16 / 9;
  border: 1px solid var(--edge); background: #000;
}
.media-frame.audio {
  aspect-ratio: auto; height: 120px; max-width: 640px;
  background: var(--paper);
}
.media-frame iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }
.media-frame.audio iframe { position: static; height: 120px; }
.media-credit { margin-top: 10px; font-size: 12.5px; color: var(--mute); }
.media-credit a { border-bottom: 1px solid var(--rule); }
.media-credit a:hover { color: var(--signal); border-bottom-color: var(--signal); }

@media (max-width: 620px) {
  .media-row { grid-template-columns: minmax(0, 1fr) auto; }
  .media .kind { grid-column: 1 / -1; }
  .media .cue { grid-column: 2; }
}

/* ── Background timeline ─────────────────────────────────────────────── */
.tl { margin-top: 8px; border-top: 1px solid var(--rule-firm); }
.tl-item {
  display: grid; grid-template-columns: minmax(0, 220px) minmax(0, 1fr);
  gap: 10px 32px; padding: 22px 18px; margin-inline: -18px;
  border-bottom: 1px solid var(--rule);
  transition: background-color .22s var(--ease);
}
.tl-item:hover { background: var(--paper); }
.tl-where { display: flex; align-items: flex-start; gap: 11px; min-width: 0; }
.tl-mark {
  flex: none; width: 30px; height: 28px; border: 1px solid var(--rule-firm);
  display: grid; place-items: center; overflow: hidden;
  color: var(--signal); background: var(--paper);
}
.tl-mark svg { width: 28px; height: 26px; }
.tl-mark img { width: 22px; height: 22px; object-fit: contain; }
:root[data-theme="dark"] .tl-mark img { filter: invert(1) hue-rotate(180deg); }
.tl-org { font-size: 15px; font-variation-settings: 'wdth' 100, 'wght' 600; }
.tl-loc { display: block; margin-top: 2px; font-size: 13px; color: var(--mute); font-variation-settings: 'wdth' 100, 'wght' 400; }
.tl-when { font-size: 13.5px; color: var(--mute); font-variant-numeric: tabular-nums; }
.tl-role { font-size: 17px; font-variation-settings: 'wdth' 102, 'wght' 600; letter-spacing: -.015em; margin-top: 2px; }
.tl-body { font-size: 15.5px; color: var(--ink-2); margin-top: 6px; max-width: 68ch; }
.tl-body a { border-bottom: 1px solid var(--rule); }
.tl-body a:hover { color: var(--signal); border-bottom-color: var(--signal); }

@media (max-width: 780px) {
  .tl-item { grid-template-columns: 1fr; gap: 6px; }
}


/* ── .proj-note: unused ──────────────────────────────────────────────
   No HTML or JS in the repo's history has ever used this class; the
   eighth cell is .inst, above. The prose below describes a panel that
   never shipped.
   Seven projects in a two-up grid leave one empty. This panel fills it
   deliberately: a grain field drifting over an inverted card.        */
.proj-note {
  display: flex; flex-direction: column; min-width: 0;
  background: var(--ink); color: var(--paper);
  border: 1px solid var(--edge);
}
.proj-note .field { position: relative; aspect-ratio: 7 / 4; overflow: hidden; }
.proj-note canvas { position: absolute; inset: 0; width: 100%; height: 100%; }
.proj-note .stamp {
  position: absolute; left: 16px; bottom: 14px;
  font-size: 11.5px; letter-spacing: .02em;
  color: color-mix(in srgb, var(--paper) 55%, transparent);
}
.proj-note-body { padding: 18px 20px 22px; border-top: 1px solid color-mix(in srgb, var(--paper) 22%, transparent); }
.proj-note-body p { font-size: 16.5px; line-height: 1.45; color: color-mix(in srgb, var(--paper) 88%, transparent); max-width: 42ch; }
.proj-note-body p + p { margin-top: 12px; }
.proj-note-body .lnk {
  margin-top: 18px; color: var(--paper);
  border-bottom-color: color-mix(in srgb, var(--paper) 45%, transparent);
}
.proj-note-body .lnk:hover { color: var(--signal); border-bottom-color: var(--signal); }


/* ── Feed ────────────────────────────────────────────────────────────
   The same row component the Sound list uses, with a line of commentary
   and a filter across the top. */
.feed-filter { display: flex; flex-wrap: wrap; gap: 7px; margin-bottom: 22px; }
.feed-filter button {
  cursor: pointer; background: transparent; color: var(--ink-2);
  border: 1px solid var(--rule); border-radius: 999px;
  font-family: var(--mono); font-size: 10.5px; letter-spacing: .1em; text-transform: uppercase;
  padding: 7px 14px;
  transition: color .18s var(--ease), background-color .18s var(--ease), border-color .18s var(--ease);
}
.feed-filter button:hover { color: var(--ink); border-color: var(--rule-firm); }
.feed-filter button[aria-pressed="true"] {
  background: var(--ink); border-color: var(--ink); color: var(--paper);
}
.feed .say {
  display: block; margin-top: 9px; font-size: 15px; line-height: 1.5;
  color: var(--ink-2); max-width: 62ch;
  font-variation-settings: 'wdth' 100, 'wght' 400; letter-spacing: 0;
}
.feed .say em { font-style: italic; }
.feed .media-row { align-items: start; padding-block: 22px; }
.feed .kind { padding-top: 3px; }
.feed .when { padding-top: 3px; }
.media-item[hidden] { display: none; }


/* ── Portfolio index (private) ───────────────────────────────────────── */
.plist { border-top: 1px solid var(--edge); }
.prow {
  display: grid; grid-template-columns: 44px minmax(0, 1.1fr) minmax(0, 1.4fr) auto;
  gap: 18px; align-items: baseline; padding: 22px 2px;
  border-bottom: 1px solid var(--rule); transition: background-color .2s var(--ease);
}
.prow:hover { background: var(--shade); }
.prow .pn { font-family: var(--mono); font-size: 11px; color: var(--mute); }
.prow .pt {
  font-size: clamp(18px, 2vw, 24px); font-variation-settings: 'wdth' 106, 'wght' 600;
  letter-spacing: -.02em; transition: font-variation-settings .3s var(--ease);
}
.prow:hover .pt { font-variation-settings: 'wdth' 122, 'wght' 600; }
.prow .pd { font-size: 15px; color: var(--ink-2); }
.prow .pm { font-family: var(--mono); font-size: 10.5px; color: var(--mute); white-space: nowrap; }
@media (max-width: 720px) {
  .prow { grid-template-columns: 34px 1fr; gap: 6px 14px; }
  .prow .pd, .prow .pm { grid-column: 2; }
}


/* GitHub and LinkedIn sit in the header, at nav weight rather than as footnotes */
/* Selector has to out-specify `.nav a` (0,1,1) — that rule sets
   padding: 8px 13px, and under border-box it would leave these boxes an 8px
   content width, crushing the glyph horizontally instead of scaling it. A
   bare `.nav-soc` (0,1,0) loses to it and the padding silently survives. */
.nav a.nav-soc {
  display: inline-flex; align-items: center; justify-content: center;
  width: 34px; height: 34px; color: var(--ink-2); flex: none;
  padding: 0;
  transition: color .18s var(--ease), background-color .18s var(--ease);
}
.nav a.nav-soc:hover { color: var(--ink); background: var(--shade); }
/* Sized to sit with the nav text rather than read as a footnote: 18px is
   about the cap height of the 14.5px links, so the marks and the words
   carry the same visual weight. Colour comes from the same --ink-2 /
   --ink pair the links use, resting and hover. */
.nav a.nav-soc svg { display: block; width: 18px; height: 18px; }

/* Touch-target floor once the nav wraps to a thumb-reachable row of its own.
   This has to sit after the base rule above, not up in the earlier 860px
   block: same specificity, and the later declaration wins. */
@media (max-width: 860px) {
  .nav a.nav-soc { width: 44px; height: 44px; }
}
