: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 > a {
  animation: header-drop 0.7s cubic-bezier(.25,.7,.3,1) both;
}
.site-header > a { animation-delay: 0.1s; }
@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 > a { animation: none; }
}

/* hero */
.hero {
  position: relative;
  padding: 120px 56px 96px;
  overflow: hidden;
}
.hero-wave {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  width: 100%; height: 120px;
  opacity: 0.14;
  pointer-events: none;
}
.hero-wave rect {
  fill: var(--fg-dim);
  transform-origin: bottom;
  animation: wave-breathe 5s ease-in-out infinite alternate;
}
@keyframes wave-breathe {
  from { transform: scaleY(0.92); }
  to   { transform: scaleY(1.06); }
}
.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;
}

/* bio */
.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; }
.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;
}
.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 {
  position: relative;
  display: flex; align-items: center; gap: 22px;
  padding: 18px 0;
  border-bottom: 1px solid var(--line);
}

/* 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.42;
}

/* 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 — a fixed percentage here previously
     broke when the box got much taller: its "fully transparent" radius
     landed well inside the box, right around where the curve's peaks
     live, silently masking them away even though they were drawn fine */
  -webkit-mask-image: radial-gradient(ellipse farthest-corner at 50% 100%, black 55%, transparent 100%);
  mask-image: radial-gradient(ellipse farthest-corner at 50% 100%, black 55%, transparent 100%);
}
.spectrum-analyzer.active { opacity: 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; top: 50%;
  transform: translateY(-50%);
  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;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.5s ease;
}
.fx-panel.active { opacity: 1; pointer-events: auto; }
.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); }

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

@media (max-width: 720px) {
  .site-header, .hero, .bio, .work, .contact { padding-left: 24px; padding-right: 24px; }
  .hero h1 { font-size: 42px; }
  .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. */
  .fx-panel { left: 8px; padding: 8px 6px; gap: 10px; }
  .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; }
  .transport-row { gap: 6px; padding-top: 8px; }
  .transport-btn { width: 28px; height: 22px; }
  .transport-btn svg { width: 12px; height: 12px; }
}
