:root {
  --bg: oklch(0.16 0.012 55);
  --fg: oklch(0.95 0.006 60);
  --fg-dim: oklch(0.68 0.01 60);
  --fg-faint: oklch(0.42 0.01 60);
  --line: oklch(0.30 0.012 55);
  --accent: #8ecae6;
}

* { box-sizing: border-box; }

html { scroll-behavior: smooth; }

body {
  position: relative;
  z-index: 0; /* establishes a stacking context so the fixed spectrum
                 canvas's z-index:-1 stays confined behind body's own
                 content instead of escaping to page-root level */
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font-family: 'Work Sans', system-ui, sans-serif;
  transition: background-color 0.25s ease; /* smooths the reverb-driven background shift */
}

a { color: var(--accent); text-decoration: none; }
a:hover { color: var(--fg); }

.mono { font-family: 'IBM Plex Mono', ui-monospace, SFMono-Regular, monospace; }

.label {
  font-size: 13px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--fg-dim);
}
.label.accent { color: var(--accent); font-size: 12px; letter-spacing: 0.18em; margin-bottom: 20px; }

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

/* reveal-on-scroll */
.reveal {
  opacity: 0;
  transform: translateY(18px);
  transition: opacity 0.8s ease, transform 0.8s ease;
}
.reveal.visible { opacity: 1; transform: translateY(0); }
@media (prefers-reduced-motion: reduce) {
  .reveal { opacity: 1; transform: none; transition: none; }
  html { scroll-behavior: auto; }
  .track.playing .track-play::before { animation: none; opacity: 0; }
}

/* header */
.site-header {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 28px 56px;
  border-bottom: 1px solid var(--line);
  overflow: hidden;
}
.site-header::before {
  content: '';
  position: absolute; left: 0; bottom: -1px; height: 1px;
  width: 100%;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: left;
  animation: header-line-draw 1.1s cubic-bezier(.25,.7,.3,1) 0.15s both;
}
.site-header .label,
.site-header .site-nav a {
  animation: header-drop 0.7s cubic-bezier(.25,.7,.3,1) both;
}
.site-header .site-nav a { animation-delay: 0.1s; }
.site-header .site-nav a + a { animation-delay: 0.18s; }
.site-nav { display: flex; align-items: center; gap: 28px; }
@keyframes header-line-draw {
  to { transform: scaleX(1); }
}
@keyframes header-drop {
  from { opacity: 0; transform: translateY(-10px); }
  to   { opacity: 1; transform: translateY(0); }
}
.site-header a { font-size: 13px; letter-spacing: 0.02em; position: relative; }
.site-header a::after {
  content: '';
  position: absolute; left: 0; right: 100%; bottom: -3px;
  height: 1px; background: var(--accent);
  transition: right 0.3s ease;
}
.site-header a:hover::after { right: 0; }
@media (prefers-reduced-motion: reduce) {
  .site-header::before { animation: none; transform: scaleX(1); }
  .site-header .label, .site-header .site-nav a { animation: none; }
}

/* hero */
.hero {
  position: relative;
  padding: 120px 56px 96px;
  overflow: hidden;
}
/* (hero-wave bars removed 2026-08-24 — they sat in front of the skyline) */
.hero-content { position: relative; max-width: 760px; }
.hero h1 {
  font-family: 'Newsreader', Georgia, serif;
  font-style: italic;
  font-weight: 400;
  font-size: 64px;
  line-height: 1.06;
  margin: 0 0 28px;
}
.hero .services {
  font-size: 13px;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--fg-dim);
  margin: 0;
}

/* work with me */
.hire { padding: 0 56px 110px; display: flex; justify-content: center; }
.hire-content { max-width: 640px; width: 100%; }
.hire-content p {
  font-family: 'Newsreader', Georgia, serif;
  font-size: 19px; line-height: 1.75;
  color: var(--fg-dim);
  margin: 18px 0 26px;
}
.hire-email {
  font-family: 'Newsreader', Georgia, serif;
  font-style: italic;
  font-size: 26px;
  color: var(--accent);
}

/* bio (bio.html) */
.bio { padding: 0 56px 120px; display: flex; justify-content: center; }
.bio-content {
  max-width: 640px; width: 100%;
  font-family: 'Newsreader', Georgia, serif;
  font-size: 19px; line-height: 1.75;
  color: var(--fg-dim);
}
.bio-content p { margin: 0 0 28px; }
.bio-content .quote {
  font-style: italic;
  color: var(--fg);
  border-left: 2px solid var(--accent);
  padding-left: 22px;
  margin: 44px 0;
}

/* work */
.work { padding: 0 56px 120px; transition: opacity 0.4s ease; }
.work-content { max-width: 900px; margin: 0 auto; }
/* Carried only across the playback fade-out, then removed in JS — a
   permanent transition here would lag the reverb fader, which drives this
   same section's opacity and is meant to track the drag instantly. */
.work.work-fading { transition: opacity 1.2s ease; }
@media (prefers-reduced-motion: reduce) { .work.work-fading { transition: none; } }
.work-header { border-bottom: 1px solid var(--line); padding-bottom: 20px; margin-bottom: 6px; }
.work-header h2 {
  font-family: 'Newsreader', Georgia, serif;
  font-weight: 400; font-size: 28px; margin: 0;
  /* the compressor pulse drives transform: scale (paint-only), not
     font-size (which forced per-frame layout) — anchor it to the left so
     it breathes in place */
  transform-origin: left center;
}
.work .note { font-size: 11px; color: var(--fg-faint); margin: 14px 0 28px; letter-spacing: 0.03em; }

.track-list { display: flex; flex-direction: column; }
.track-block { border-bottom: 1px solid var(--line); }
.track {
  position: relative;
  display: flex; align-items: center; gap: 22px;
  padding: 18px 0;
}

/* city skyline background — full-viewport, fixed, sits behind the
   spectrum curve (lower z-index). Semi-transparent so it reads as an
   atmospheric backdrop rather than competing with the page content;
   window-light brightness/blur is all drawn in canvas (main.js), this
   just places and dims the whole layer. */
.skyline-canvas {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: -2;
  display: block;
  pointer-events: none;
  opacity: 0.52; /* 0.42 → 0.52 (2026-08-24, Mason: more building/sky and light/building contrast — pairs with the darker fill + brighter window sprites in main.js) */
}

/* live spectrum analyzer — one shared canvas, fixed to the viewport,
   top edge at 25vh (where it visually sat when centered at 50vh tall)
   and now extending all the way to the bottom of the window. The curve's
   own amplitude scale (see ampHeight in main.js) stays anchored to that
   top edge, so peaks land in the same place — only the floor below them
   extends further. Faded at the top and sides, but NOT the bottom, since
   the whole point is for it to reach the window edge cleanly. */
.spectrum-analyzer {
  position: fixed;
  left: 0; top: 25vh; bottom: 0;
  width: 100%;
  z-index: -1;
  display: block;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.6s ease;
  /* farthest-corner sizing (not a fixed percentage) so the fade adapts to
     the box's actual proportions. IMPORTANT property of this specific
     shape: with the center anchored at the box's bottom-edge (50% 100%),
     the ellipse's y-radius is, BY CONSTRUCTION, always exactly equal to
     the box's own height (the top corners are always the farthest point,
     and their y-displacement from a bottom-anchored center IS the box
     height) — so the top-center row sits at EXACTLY the gradient's 100%
     mark, always, regardless of how tall or short the box is. At
     "transparent 100%" that meant the curve's peaks (which reach the
     box's own top edge at full amplitude) were being faded to fully
     invisible right where they were tallest. Pushing the "transparent"
     stop out to 200% moves that same row to roughly the gradient's
     halfway point instead — clearly visible, with the true corners
     (which sit further out still) fading more, preserving the
     atmospheric edge-fade the mask was added for. */
  -webkit-mask-image: radial-gradient(ellipse farthest-corner at 50% 100%, black 55%, transparent 200%);
  mask-image: radial-gradient(ellipse farthest-corner at 50% 100%, black 55%, transparent 200%);
}
.spectrum-analyzer.active { opacity: 0.41; }  /* halved 2026-08-22 per Mason (was 0.82) */

/* reverb control — a small fixed panel with a console-style vertical
   fader (drag, click-to-jump, scroll, or arrow keys — up/scroll-up
   increases, down/scroll-down decreases). Fixed to the left edge,
   vertically centered, well clear of the bottom of the viewport. Every
   input handler on the fader calls preventDefault(), including a
   non-passive wheel listener — without that, a trackpad scroll gesture
   over the control falls through to the page's native scroll instead of
   moving the fader. */
/* the shared control surface — two identical fader channels side by
   side, like adjacent channel strips on a console */
.fx-panel {
  position: fixed;
  left: 24px; bottom: 44px;   /* docked low (2026-08-23, Mason) so the buildings on the left stay visible; raised above the 34px street strip (2026-08-24) so traffic is never covered */
  z-index: 10;
  display: flex; flex-direction: column; align-items: center; gap: 16px;
  padding: 18px 16px;
  background: oklch(0.16 0.012 55 / 0.92);
  border: 1px solid var(--line);
  border-radius: 12px;
  transition: opacity 0.5s ease;
}
/* Before anything has played (no .active), the panel collapses to just
   the transport row — a small, always-reachable dock, so the Play button
   can start the first track cold (2026-08-23, Mason; this also retires
   the playtest finding that the button was unreachable pre-play). The
   fader channels only appear once something is actually playing. */
.fx-panel:not(.active) .fx-channels-row { display: none; }
.fx-panel:not(.active) .transport-minimize { display: none; }
.fx-panel:not(.active) .transport-row { border-top: none; padding-top: 0; }
.fx-channels-row {
  display: flex; flex-direction: row; align-items: flex-start; gap: 22px;
}
.fx-channel {
  display: flex; flex-direction: column; align-items: center; gap: 10px;
}

/* transport controls, beneath the fader channels — styled like physical
   buttons on a mixing console: a raised rubber-key bevel (light edge top,
   dark shadow beneath), pressing in slightly on click */
.transport-row {
  display: flex; flex-direction: row; gap: 12px;
  padding-top: 14px;
  border-top: 1px solid var(--line);
  width: 100%;
  justify-content: center;
}
.transport-btn {
  width: 38px; height: 30px;
  border-radius: 4px;
  border: 1px solid oklch(0.05 0.005 55);
  background: linear-gradient(180deg, oklch(0.30 0.012 55), oklch(0.17 0.01 55));
  color: var(--fg-faint);
  display: flex; align-items: center; justify-content: center;
  cursor: pointer;
  padding: 0;
  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.08) inset, 0 2px 3px rgba(0, 0, 0, 0.5);
  transition: color 0.15s ease, box-shadow 0.1s ease, transform 0.05s ease;
}
.transport-btn svg { width: 15px; height: 15px; }
.transport-btn:active {
  transform: translateY(1px);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.6) inset;
}
.transport-btn:focus-visible { outline: 1px solid var(--accent); outline-offset: 2px; }
/* each control keyed to its own color, like function-specific buttons on
   real mixer hardware — glow brightens on hover rather than shifting to
   one shared accent, since each button now has its own identity color */
.transport-stop { color: oklch(0.62 0.19 25); }     /* red */
.transport-play { color: oklch(0.72 0.17 145); }    /* green */
.transport-shuffle { color: var(--accent); }        /* ice blue */
/* neutral — a panel utility, not a transport action — but still needs to
   read clearly next to the vivid stop/play/shuffle icons: fg-faint (the
   same muted gray as the fader labels) was too close to the button's own
   dark gradient background to reliably stand out, so this uses fg-dim
   (a fair bit lighter) instead */
.transport-minimize { color: var(--fg-dim); }
.transport-stop:hover { box-shadow: 0 1px 0 rgba(255, 255, 255, 0.08) inset, 0 2px 3px rgba(0, 0, 0, 0.5), 0 0 6px oklch(0.62 0.19 25 / 0.6); }
.transport-play:hover { box-shadow: 0 1px 0 rgba(255, 255, 255, 0.08) inset, 0 2px 3px rgba(0, 0, 0, 0.5), 0 0 6px oklch(0.72 0.17 145 / 0.6); }
.transport-shuffle:hover { box-shadow: 0 1px 0 rgba(255, 255, 255, 0.08) inset, 0 2px 3px rgba(0, 0, 0, 0.5), 0 0 6px var(--accent); }
.transport-minimize:hover { color: var(--fg); box-shadow: 0 1px 0 rgba(255, 255, 255, 0.08) inset, 0 2px 3px rgba(0, 0, 0, 0.5), 0 0 6px var(--fg-dim); }
.transport-minimize svg { transition: transform 0.2s ease; }
/* channels row hidden, chevron flipped to point down (the direction
   clicking it will now move the row — back open) */
.fx-panel.minimized .fx-channels-row { display: none; }
.fx-panel.minimized .transport-minimize svg { transform: rotate(180deg); }
/* a strip of label tape with a marker scrawl — the way an engineer would
   actually label a channel on an old console */
.reverb-label {
  font-family: 'Permanent Marker', cursive;
  font-size: 15px;
  letter-spacing: 0.02em;
  color: oklch(0.22 0.02 55);
  background: oklch(0.88 0.02 85);
  padding: 3px 10px 5px;
  border-radius: 2px;
  transform: rotate(-2.5deg);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.35);
}
/* Modeled on a real console channel fader: a dark recessed slot cut into
   a panel, a tick-marked gain scale beside it (denser near the top, like
   a real dB scale — most of a fader's throw is attenuation, only a
   little is boost above unity), a highlighted unity/reference line
   positioned toward the top of the throw rather than dead center, and a
   wide plastic cap with grip ridges and a bright position line, the way
   your thumb actually finds it in a dark room. */
.reverb-fader {
  position: relative;
  width: 60px;
  height: 170px;
  cursor: ns-resize;
  touch-action: none;
  outline: none;
}

/* the console panel section the slot is recessed into */
.reverb-fader::before {
  /* semi-transparent rather than opaque, so the real console photo behind
     the whole panel shows through here too — the fader reads as sitting
     directly on that surface rather than a separate CSS-drawn box floating
     in front of it */
  content: '';
  position: absolute;
  left: 50%; top: -8px; bottom: -8px;
  width: 34px;
  margin-left: -17px;
  background: linear-gradient(to right, oklch(0.19 0.008 55 / 0.55), oklch(0.24 0.008 55 / 0.4) 45%, oklch(0.19 0.008 55 / 0.55));
  border-radius: 4px;
  border: 1px solid oklch(0.10 0.006 55 / 0.7);
}

/* tick-mark gain scale, denser near the top (unity) than the bottom,
   echoing a real fader's non-linear dB spacing */
.reverb-fader-scale {
  position: absolute;
  left: 50%; margin-left: 15px;
  top: 8px; bottom: 8px;
  width: 7px;
  background-image: repeating-linear-gradient(
    to bottom,
    var(--fg-faint) 0, var(--fg-faint) 1px, transparent 1px, transparent 12px
  );
  opacity: 0.4;
  -webkit-mask-image: linear-gradient(to bottom, black 0%, black 55%, transparent 100%);
  mask-image: linear-gradient(to bottom, black 0%, black 55%, transparent 100%);
}
.reverb-fader-scale::after {
  /* wider spacing below unity — attenuation needs less resolution than
     the fine boost range above it */
  content: '';
  position: absolute;
  left: 0; right: 0; top: 45%; bottom: 0;
  background-image: repeating-linear-gradient(
    to bottom,
    var(--fg-faint) 0, var(--fg-faint) 1px, transparent 1px, transparent 22px
  );
  opacity: 0.85;
}

/* the slot itself — dark, recessed, with an inner shadow suggesting real depth */
.reverb-fader-track {
  position: absolute;
  left: 50%; top: 6px; bottom: 6px;
  width: 7px;
  margin-left: -3.5px;
  background: oklch(0.07 0.006 55);
  border-radius: 3px;
  border: 1px solid oklch(0.04 0.004 55);
  box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.9), inset 0 -1px 2px rgba(0, 0, 0, 0.6);
}

/* unity/reference mark — toward the top of the throw, not the center,
   matching how a real gain scale is weighted */
.reverb-fader-unity {
  position: absolute;
  left: -9px; right: -9px;
  top: 22%;
  height: 2px;
  background: var(--accent);
  opacity: 0.5;
  border-radius: 1px;
}

/* the moving cap is Mason's own photo of a real fader cap from his own
   gear (img/fader-cap.jpg) — shot straight-on, so it's naturally
   portrait/tall with horizontal ribs, not the wide landscape shape used
   before. Sized to match that real aspect ratio rather than forcing a
   mismatched crop. */
.reverb-fader-cap {
  position: absolute;
  left: 50%;
  width: 28px;
  height: 48px;
  margin-left: -14px;
  margin-top: -24px;
  border-radius: 3px;
  background-image: url('../img/fader-cap.jpg');
  background-size: cover;
  background-position: center;
  border: 1px solid oklch(0.06 0.005 55);
  box-shadow: 0 2px 5px rgba(0, 0, 0, 0.6);
}
.reverb-fader-cap-line {
  position: absolute;
  left: 3px; right: 3px; top: 50%;
  height: 2px;
  margin-top: -1px;
  background: var(--accent);
  border-radius: 1px;
  box-shadow: 0 0 3px rgba(0, 0, 0, 0.8);
}
.reverb-fader:focus-visible .reverb-fader-cap { border-color: var(--accent); }
.reverb-value {
  font-size: 11px;
  color: var(--fg-faint);
  min-width: 32px;
  text-align: center;
}

.eq-curve {
  width: 64px;
  height: 34px;
  /* the fader cap overhangs the bottom of its own .reverb-fader box by
     ~24px (cap height 48px, centered via margin-top: -24px, positioned
     at the track's full 170px height when the fader sits at 0) — nudge
     this window down far enough that the cap can never overlap it, even
     resting all the way at the bottom */
  margin-top: 16px;
  background: oklch(0.10 0.01 55);
  border: 1px solid var(--line);
  border-radius: 3px;
  box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.6);
}

/* the "all faders maxed" easter egg now lives entirely on the skyline
   canvas (see main.js: heli.protesting, drawProtestMan, drawSpeechBubble)
   — no DOM panel needed here anymore. */

.gr-meter {
  width: 60px;
}
.gr-meter-svg {
  width: 100%;
  height: auto;
  overflow: visible;
}
.gr-meter-arc {
  fill: none;
  stroke: var(--line);
  stroke-width: 1.5;
  stroke-linecap: round;
}
.gr-meter-tick line {
  stroke: var(--fg-faint);
  stroke-width: 1;
}
.gr-meter-needle-group {
  transition: transform 0.15s ease-out;
}
.gr-meter-needle {
  stroke: var(--accent);
  stroke-width: 2;
  stroke-linecap: round;
  filter: drop-shadow(0 0 3px var(--accent));
}
.gr-meter-pivot {
  fill: var(--accent);
}

.gr-value {
  font-size: 12px;
  color: var(--accent);
  letter-spacing: 0.02em;
  min-width: 48px;
  text-align: center;
}

.reverb-type-btn {
  margin-top: -2px;
  padding: 3px 12px;
  font-size: 10px;
  letter-spacing: 0.04em;
  color: var(--fg-faint);
  background: transparent;
  border: 1px solid var(--line);
  border-radius: 10px;
  cursor: pointer;
  transition: color 0.2s ease, border-color 0.2s ease;
}
.reverb-type-btn:hover,
.reverb-type-btn:focus-visible {
  color: var(--accent);
  border-color: var(--accent);
  outline: none;
}

.track-play {
  position: relative;
  width: 40px; height: 40px; min-width: 40px;
  border-radius: 50%;
  border: 1px solid var(--line);
  background: transparent;
  display: flex; align-items: center; justify-content: center;
  cursor: pointer; padding: 0;
  transition: border-color 0.25s ease;
}
.track.playing .track-play { border-color: var(--accent); }
.track-play svg { width: 14px; height: 14px; }
.track-play .icon-pause { display: none; }
.track.playing .track-play .icon-play { display: none; }
.track.playing .track-play .icon-pause { display: block; }

/* animation 1 during playback: expanding pulse ring off the active play button */
.track-play::before {
  content: '';
  position: absolute; inset: 0;
  border-radius: 50%;
  border: 1px solid var(--accent);
  opacity: 0;
  pointer-events: none;
}
.track.playing .track-play::before {
  animation: play-pulse 1.8s ease-out infinite;
}
@keyframes play-pulse {
  0%   { transform: scale(1); opacity: 0.55; }
  100% { transform: scale(1.9); opacity: 0; }
}

.track-meta { min-width: 230px; }
.track-elapsed { display: none; }
.track.playing .track-duration { display: none; }
.track.playing .track-elapsed { display: inline; }
.track-title { font-size: 15px; color: var(--fg); transform-origin: left center; } /* pulse scales in place, same as the heading */

.track-wave {
  flex-grow: 1;
  width: 100%;
  height: 28px;
  display: block;
  cursor: pointer;
  touch-action: none;
  transition: filter 0.2s ease;
}
.track-wave:hover { filter: brightness(1.35); }

.track-duration { font-size: 12px; color: var(--fg-faint); min-width: 40px; text-align: right; }

.year-group { margin-bottom: 48px; }
.year-heading {
  font-size: 12px;
  letter-spacing: 0.14em;
  color: var(--accent);
  padding-bottom: 10px;
  margin-bottom: 4px;
  border-bottom: 1px solid var(--line);
}

/* contact */
.contact {
  padding: 100px 56px 140px;
  border-top: 1px solid var(--line);
  text-align: center;
}
.contact .label { margin-bottom: 20px; }
.contact-email {
  font-family: 'Newsreader', Georgia, serif;
  font-style: italic;
  font-size: 36px;
  color: var(--accent);
}
.contact .note { margin-top: 26px; }

/* ---- stem isolation ----
   A per-track disclosure panel: "Stems" under the track title opens a row
   of five colored instrument toggles beneath that track. Every stem plays
   by default (all buttons lit); toggling one off greys it out and mutes
   that stem's gain, leaving it running silently in sync. Only tracks
   present in stems.json get the control at all — the feature is per-track
   opt-in, since five stems cost roughly 3x the bytes of the stereo mix. */
.stem-toggle {
  display: inline-flex; align-items: center; gap: 4px;
  margin-top: 6px; padding: 2px 7px 2px 8px;
  font-size: 10px; letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--fg-faint);
  background: transparent;
  border: 1px solid var(--line);
  border-radius: 3px;
  cursor: pointer;
  transition: color 0.2s ease, border-color 0.2s ease;
}
.stem-toggle:hover { color: var(--accent); border-color: var(--accent); }
.stem-toggle:focus-visible { outline: 1px solid var(--accent); outline-offset: 2px; }
.stem-chevron { width: 11px; height: 11px; transition: transform 0.25s ease; }
.stem-toggle[aria-expanded="true"] { color: var(--accent); border-color: var(--accent); }
.stem-toggle[aria-expanded="true"] .stem-chevron { transform: rotate(180deg); }

.stem-panel { padding: 2px 0 18px 62px; }   /* 62px lines the panel up with the track title, clearing the play button */
.stem-panel.open { animation: stem-panel-in 0.22s ease-out both; }
@keyframes stem-panel-in {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: translateY(0); }
}

.stem-row { display: flex; flex-wrap: wrap; gap: 8px; }

/* Lit state paints from --stem-color, set per button in JS, so one rule
   serves all five instruments. Off state drops to --fg-faint — the same
   grey the site already uses for de-emphasized metadata — so a disabled
   stem reads as switched off rather than as a differently-colored one. */
.stem-btn {
  --stem-color: var(--accent);
  padding: 5px 13px;
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  font-size: 11px; letter-spacing: 0.1em; text-transform: uppercase;
  background: transparent;
  color: var(--fg-faint);
  border: 1px solid var(--line);
  border-radius: 3px;
  cursor: pointer;
  transition: color 0.2s ease, border-color 0.2s ease, background-color 0.2s ease, box-shadow 0.2s ease, opacity 0.2s ease;
}
.stem-btn.on {
  color: var(--stem-color);
  border-color: var(--stem-color);
  background: color-mix(in oklab, var(--stem-color) 12%, transparent);
  box-shadow: 0 0 7px color-mix(in oklab, var(--stem-color) 30%, transparent);
}
.stem-btn:not(.on) { opacity: 0.55; }
.stem-btn:hover { color: var(--stem-color); border-color: var(--stem-color); opacity: 1; }
.stem-btn:focus-visible { outline: 1px solid var(--accent); outline-offset: 2px; }

.stem-status { margin-top: 9px; font-size: 10px; letter-spacing: 0.08em; color: var(--fg-faint); min-height: 12px; }

@media (prefers-reduced-motion: reduce) {
  .stem-panel.open { animation: none; }
  .stem-chevron { transition: none; }
}

@media (max-width: 720px) {
  .site-header, .hero, .bio, .hire, .work, .contact { padding-left: 24px; padding-right: 24px; }
  .hero h1 { font-size: 42px; }
  .site-nav { gap: 16px; }
  .track { flex-wrap: wrap; }
  .track-meta { flex: 1 1 0%; min-width: 0; }
  .track-duration { flex-shrink: 0; }
  .track-wave { min-width: 100%; order: 4; }

  /* the fx-panel is sized for a desktop-width viewport by default — on a
     narrow phone screen it was wide/tall enough to sit directly over the
     track list's titles and cover a large share of the background.
     Shrink every dimension (not just a transform: scale, which would
     leave oversized touch targets and a stale hit box). The 3 channels
     stay side by side, same as desktop — with the minimize toggle below
     now available to collapse them out of the way entirely when they'd
     overlap something, a narrow single-column restack (tried earlier)
     isn't needed just to avoid overlap. Fader track height is driven
     live off the DOM (see faderCapTop in main.js), so nothing here needs
     to also change in JS. */
  /* mobile now bottom-docks too (2026-08-23, Mason's follow-up: move the
     fader section lower so it obscures less of the left-side buildings) —
     matches the desktop dock, just tighter to the corner */
  .fx-panel { left: 8px; top: auto; bottom: 44px; transform: none; padding: 8px 6px; gap: 10px; } /* clears the street strip (2026-08-24) */
  .fx-channels-row { gap: 6px; }
  .fx-channel { gap: 5px; width: 40px; }
  /* the channel label ("COMPRESSOR") is a whole word set in a wide
     script font — at any font-size that still reads, it's wider than the
     40px-wide fader/meter beneath it and was the actual thing forcing
     each channel wide. Letting it wrap to 2 lines inside the fixed
     channel width keeps the fader as the width-driving element instead. */
  .reverb-label { font-size: 9px; padding: 2px 3px 3px; white-space: normal; word-break: break-word; line-height: 1.15; text-align: center; }
  .reverb-fader { width: 34px; height: 110px; }
  .reverb-fader-cap { width: 18px; height: 31px; margin-left: -9px; margin-top: -15px; }
  .eq-curve { width: 40px; height: 22px; margin-top: 10px; }
  .gr-meter { width: 34px; }
  .reverb-value { font-size: 9px; min-width: 24px; }
  .gr-value { font-size: 10px; min-width: 34px; }
  /* "Medium" is the widest of the 3 reverb-type labels — tight enough
     padding/font here to stay inside the channel's 40px column, so it
     doesn't silently overflow past the panel's own measured width */
  .reverb-type-btn { padding: 2px 5px; font-size: 8px; }
  .stem-panel { padding-left: 0; }
  .stem-btn { padding: 6px 11px; font-size: 10px; }  /* slightly taller: a comfortable touch target */
  .transport-row { gap: 6px; padding-top: 8px; }
  .transport-btn { width: 28px; height: 22px; }
  .transport-btn svg { width: 12px; height: 12px; }
}
