/* ───────────── Tokens ───────────── */
:root {
  /* Fibonacci spacing (≈ golden ratio) */
  --s1: 5px; --s2: 8px; --s3: 13px; --s4: 21px; --s5: 34px; --s6: 55px; --s7: 89px;

  --bg: #0a0a0b;
  --card: #101011;
  --fg: #fafafa;
  --fg-2: #a1a1a6;
  --fg-3: #6b6b70;
  --line: rgba(255, 255, 255, .07);
  --line-2: rgba(255, 255, 255, .12);
  --hover: rgba(255, 255, 255, .06);
  --green: #1ed760;

  /* Polished-chrome ramp — the name, plus a cool accent on edges and hover.
     Neutral on purpose: the ambient glow behind the card is tinted from each
     song's cover art, so a coloured name would clash as the artwork changes. */
  --metal-1: #ffffff;
  /* The name's ramp, as tokens so the whole colourway moves from one place. */
  /* White. The highest-contrast pairing with black, and the only one that can't
     argue with the ambient glow behind the card, which is re-tinted from every
     song's cover art. The ramp is a faint top-lit sheen, not a colour. */
  --name-1: #ffffff;
  --name-2: #f4f7fb;
  --name-3: #dce3ec;
  --name-glow: rgba(196, 214, 240, .22);
  --rim: rgba(196, 214, 240, .22);

  --ease: cubic-bezier(0.16, 1, 0.3, 1);
  --ease-io: cubic-bezier(0.65, 0, 0.35, 1);

  color-scheme: dark;
}

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

/* Needed because some of these elements set their own `display`, which otherwise
   beats the `hidden` attribute (e.g. the bio row when there are no lines). */
[hidden] { display: none !important; }

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

body {
  margin: 0;
  position: relative;
  min-height: 100vh;
  min-height: 100svh;
  /* Normal flow now, not a centred grid — the page scrolls: hero, then the card. */
  padding: 0 max(16px, env(safe-area-inset-right)) 0 max(16px, env(safe-area-inset-left));
  background: var(--bg);
  color: var(--fg);
  font: 400 15px/1.55 "Inter Tight", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
  font-feature-settings: "ss01", "cv11";
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
}
body.is-scrubbing { user-select: none; -webkit-user-select: none; cursor: grabbing; }

button, a, [role="slider"] {
  -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;
}
button { font: inherit; color: inherit; background: none; border: 0; padding: 0; margin: 0; cursor: pointer; }

:focus { outline: none; }
:focus-visible { outline: 1.5px solid rgba(255, 255, 255, .55); outline-offset: 2px; }

/* ───────────── Ambient top light ───────────── */
/* ───────────── Background ─────────────
   Four layers instead of a flat fill: a tonal base, two very slow drifting
   lights, film grain to kill banding, and a vignette to hold the centre. */
.bg {
  position: fixed; inset: 0; z-index: 0; pointer-events: none; overflow: clip;
  background:
    radial-gradient(125% 85% at 50% -15%, #191920 0%, rgba(10, 10, 11, 0) 62%),
    radial-gradient(95% 65% at 8% 108%, #14151b 0%, rgba(10, 10, 11, 0) 58%),
    var(--bg);
}
.bg-glow {
  position: absolute; border-radius: 50%;
  filter: blur(80px);
  will-change: transform;
}
.bg-glow-a {
  width: 62vmax; height: 62vmax; left: -16vmax; top: -22vmax;
  background: radial-gradient(circle, rgba(126, 156, 214, .17) 0%, rgba(126, 156, 214, 0) 70%);
  animation: drift-a 38s var(--ease-io) infinite alternate;
}
.bg-glow-b {
  width: 52vmax; height: 52vmax; right: -14vmax; top: 34vmax;
  background: radial-gradient(circle, rgba(206, 186, 162, .11) 0%, rgba(206, 186, 162, 0) 70%);
  animation: drift-b 52s var(--ease-io) infinite alternate;
}
@keyframes drift-a { to { transform: translate3d(15vmax, 11vmax, 0) scale(1.16); } }
@keyframes drift-b { to { transform: translate3d(-13vmax, -9vmax, 0) scale(1.1); } }

/* Light streaks — long soft ellipses drifting left to right. Each one carries
   its own position/length/speed via custom properties set in the markup, and a
   NEGATIVE delay so the field is already mid-flight on load instead of all
   eight setting off together. */
.streak {
  position: absolute;
  left: 0; top: var(--t);
  width: var(--w); height: var(--h);
  border-radius: 50%;
  background: radial-gradient(closest-side,
    rgba(232, 241, 255, .85) 0%,
    rgba(206, 223, 253, .38) 42%,
    rgba(180, 202, 245, 0) 100%);
  opacity: var(--o);
  /* Light relative to the streak's own thickness — a fixed 7px blur on a 3px
     tall ellipse simply erased it. */
  filter: blur(4px);
  will-change: transform;
  animation: streak-drift var(--d) linear var(--delay) infinite;
}
@keyframes streak-drift {
  from { transform: translate3d(-70vw, 0, 0) rotate(var(--tilt)); }
  to   { transform: translate3d(170vw, 0, 0) rotate(var(--tilt)); }
}

/* Grain does the heavy lifting: it stops the big soft gradients from banding
   on cheap panels and is most of why this reads as "shot" rather than "filled". */
.bg-grain {
  position: absolute; inset: 0;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size: 180px 180px;
  opacity: .038;
}
.bg-vignette {
  position: absolute; inset: 0;
  background: radial-gradient(125% 95% at 50% 42%, rgba(0, 0, 0, 0) 38%, rgba(0, 0, 0, .6) 100%);
}

/* The canvas paints its glow onto an opaque base matching --bg. That was invisible
   against a flat black page, but over the layered background it showed as a hard
   dark disc. `screen` drops the near-black base and keeps only the light it adds.
   It has to sit on .ambient, not the canvas: .ambient is a fixed, z-indexed
   stacking context, so a blend on the child has nothing behind it to blend with. */
.ambient {
  position: fixed; inset: 0 0 auto 0; height: 40vh;
  pointer-events: none; z-index: 0; mix-blend-mode: screen;
}
.ambient canvas {
  position: absolute; inset: 0; width: 100%; height: 100%;
  opacity: 0; transition: opacity 1.3s var(--ease-io);
}
.ambient canvas.is-front { opacity: 1; }

/* ───────────── Enter gate ───────────── */
/* ───────────── Gate ───────────── */
.gate {
  position: fixed; inset: 0; z-index: 20;
  display: grid; place-items: center;
  width: 100%; height: 100%;
  background: var(--bg);
  color: var(--fg-2);
  font-size: 15px; font-weight: 500; letter-spacing: -0.01em;
  /* Short on purpose: the name slams in right behind this, and a long fade
     would hide the impact behind a half-opaque sheet. */
  transition: opacity 320ms var(--ease-io), visibility 0s linear 320ms;
}
.gate:focus-visible { outline: none; }
.gate:focus-visible .gate-label { color: var(--fg); }
.gate-label {
  display: inline-block;
  animation:
    gate-rise 1s var(--ease) both,
    gate-breathe 2.6s var(--ease-io) 1s infinite alternate;
}
@keyframes gate-rise { from { opacity: 0; transform: translateY(5px); filter: blur(4px); } }
@keyframes gate-breathe { to { opacity: .45; } }
body.is-in .gate { opacity: 0; visibility: hidden; pointer-events: none; }
/* Nothing to scroll to until they're in. */
body:not(.is-in) { overflow: hidden; }

/* ───────────── Sections ─────────────
   Plain scrolling, the way vgt.rip does it. Pinning the viewport and snapping
   to panels both fought the scroll: a mouse flick is ~300px, but escaping a
   snap point a screen tall needs ~420px, so small scrolls bounced back and the
   page felt stuck. Sections just move past; the motion is a reveal, not a lock. */
.scene {
  position: relative; z-index: 1;
  /* A panel scaled to 1.12 is ~12% wider than the screen, which pushed the page
     sideways once the pin (and its clip) was removed. `overflow-x: clip` is the
     one value that contains it while leaving vertical scrolling alone. */
  overflow-x: clip;
}
.panel {
  min-height: 100svh;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  padding: max(var(--s5), env(safe-area-inset-top)) 0 var(--s5);
  text-align: center;
  will-change: opacity, filter;
}
.panel-soc { gap: var(--s4); }

/* ── Impact entrance ──────────────────────────────────────────────────────
   The name arrives oversized and slams down to size, overshooting twice
   before it settles. The hero jolts and light bursts out of it on the frame
   it lands, so the hit reads as force rather than just a scale change. */
@keyframes smash {
  0%   { opacity: 0; transform: scale(2.3); }
  45%  { opacity: 1; }
  58%  { transform: scale(.9); }    /* overshoot past the target: the hit */
  72%  { transform: scale(1.055); } /* rebound */
  86%  { transform: scale(.982); }
  100% { opacity: 1; transform: scale(1); }
}
@keyframes jolt {
  0%   { transform: translate(0, 0); }
  12%  { transform: translate(-7px, 5px); }
  26%  { transform: translate(6px, -4px); }
  40%  { transform: translate(-5px, 3px); }
  56%  { transform: translate(4px, -2px); }
  72%  { transform: translate(-2px, 1px); }
  100% { transform: translate(0, 0); }
}
@keyframes flash {
  0%   { opacity: 0;   transform: scale(.55); }
  22%  { opacity: .85; }
  100% { opacity: 0;   transform: scale(1.5); }
}

.name { opacity: 0; }
/* .58s in is the landing frame — the jolt and the flash are timed to it. */
body.is-in .name { animation: smash .78s cubic-bezier(.16, .86, .18, 1) .2s both; }
body.is-in .hero { animation: jolt .4s var(--ease-io) .62s both; }

/* The impact is a burst of LIGHT, not an outline — an expanding bordered circle
   read as a grey ring drawn over the page. Clipped to the panel so it can't
   widen the layout at its largest. */
.flash {
  position: absolute; left: 50%; top: 50%; z-index: 0;
  width: 150vmax; height: 150vmax; margin: -75vmax 0 0 -75vmax;
  border-radius: 50%; pointer-events: none; opacity: 0;
  background: radial-gradient(circle,
    rgba(214, 228, 255, .16) 0%,
    rgba(186, 205, 245, .07) 26%,
    rgba(150, 175, 230, 0) 58%);
}
body.is-in .flash { animation: flash 1.15s cubic-bezier(.16, .86, .18, 1) .55s both; }
.panel-hero { position: relative; overflow: clip; }
.panel-hero > :not(.flash) { position: relative; z-index: 1; }




/* ───────────── What I trade ─────────────
   Candles grow up from the axis one after another, then the trend line draws
   itself across them. Decorative only — not real market data. */
.market {
  /* Leaves room either side for the panel's scale-up, so growing never
     pushes content past the clip edge. */
  width: min(1040px, 92%); margin: 0 auto;
  display: grid; grid-template-columns: 1fr 1fr;
  align-items: center; gap: var(--s6);
  text-align: left;
}
.m-who-line {
  margin: var(--s3) 0 0;
  font-size: clamp(21px, 3vw, 33px); font-weight: 500;
  line-height: 1.28; letter-spacing: -0.02em; color: var(--fg);
  max-width: 15ch;
}
/* One column once there isn't room for two. */
@media (max-width: 880px) {
  .market { grid-template-columns: 1fr; gap: var(--s5); text-align: center; }
  .m-who-line { max-width: none; }
  .m-title { margin-inline: 0; }
  .m-foot { justify-content: center; }
}
.m-kicker {
  margin: 0; font-size: 11px; font-weight: 600;
  letter-spacing: .2em; text-transform: uppercase; color: var(--fg-3);
}
.m-title {
  margin: var(--s2) 0 0;
  font-size: clamp(64px, 13vw, 118px); font-weight: 600;
  letter-spacing: -0.045em; line-height: 1;
  /* Same padding/negative-margin trick as .name: background-clip paints only
     inside the box, and the negative tracking pulls the box in past the ink. */
  margin-inline: -0.09em; padding: 0.06em 0.09em 0.12em;
  background: linear-gradient(170deg, var(--name-1) 0%, var(--name-2) 50%, var(--name-3) 100%);
  -webkit-background-clip: text; background-clip: text;
  color: transparent;
}
.m-plot { position: relative; margin-top: var(--s4); }
.m-chart { display: block; width: 100%; height: clamp(120px, 22vw, 190px); }
/* stroke-width is in user units; `vector-effect: non-scaling-stroke` would make
   the dash pattern screen-relative, which is what broke the old draw-on. */
.m-line { stroke: rgba(255, 255, 255, .85); stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.m-dot {
  position: absolute; width: 9px; height: 9px; margin: -4.5px 0 0 -4.5px;
  border-radius: 50%; background: #fff;
  box-shadow: 0 0 0 4px rgba(255, 255, 255, .12), 0 0 18px 2px rgba(255, 255, 255, .5);
  opacity: 0; scale: .4;
  transition: opacity .5s var(--ease) 1.75s, scale .7s var(--ease) 1.75s;
}
.market.is-seen .m-dot { opacity: 1; scale: 1; }

/* Live NQ figures */
.m-live { margin-top: var(--s3); }
.m-change { }
.m-price {
  font-size: clamp(34px, 6vw, 54px); font-weight: 600;
  letter-spacing: -0.03em; line-height: 1.05;
  font-variant-numeric: tabular-nums;
}
.m-change {
  margin-top: 2px;
  font-size: clamp(13px, 1.6vw, 15px); font-weight: 500;
  color: #7ee0a8; font-variant-numeric: tabular-nums;
}
.m-change.is-down { color: #ff8a7a; }
.m-foot {
  display: flex; flex-wrap: wrap; justify-content: flex-start; gap: var(--s2) var(--s4);
  margin-top: var(--s3);
  font-size: 12px; color: var(--fg-3); font-variant-numeric: tabular-nums;
}

/* Reveal — rise out of a blur rather than just fading, and land staggered.
   `filter: none` (not blur(0)) at the end so text finishes perfectly crisp. */
.m-kicker, .m-title, .m-who-line, .m-live, .m-foot {
  opacity: 0; translate: 0 26px; filter: blur(9px);
  transition: opacity .85s var(--ease), translate 1.2s var(--ease), filter .85s var(--ease);
}
.m-title { transition-delay: .1s; }
.m-who-line { transition-delay: .2s; }
.m-live { transition-delay: .26s; }
.m-foot { transition-delay: .34s; }
.market.is-seen .m-kicker,
.market.is-seen .m-title,
.market.is-seen .m-who-line,
.market.is-seen .m-live,
.market.is-seen .m-foot { opacity: 1; translate: 0 0; filter: none; }

/* A clip wipe, not stroke-dashoffset. The dash approach needs the path's exact
   length and silently breaks the line into repeating dashes when that number is
   off; a wipe cannot get out of sync with the geometry. */
.m-plot {
  clip-path: inset(0 100% 0 0); opacity: 0; translate: 0 18px;
  transition: clip-path 1.9s var(--ease-io) .3s,
              opacity .7s var(--ease) .3s,
              translate 1.1s var(--ease) .3s;
}
.market.is-seen .m-plot { clip-path: inset(0 0 0 0); opacity: 1; translate: 0 0; }

.s-head { width: min(560px, 100%); text-align: center; }
.s-title {
  margin: var(--s2) 0 0;
  font-size: clamp(40px, 7.5vw, 68px); font-weight: 600;
  letter-spacing: -0.04em; line-height: 1;
  margin-inline: -0.08em; padding: 0.05em 0.08em 0.1em;
  background: linear-gradient(170deg, var(--name-1) 0%, var(--name-2) 50%, var(--name-3) 100%);
  -webkit-background-clip: text; background-clip: text;
  color: transparent;
}
.s-head .m-kicker, .s-title {
  opacity: 0; translate: 0 26px; filter: blur(9px);
  transition: opacity .85s var(--ease), translate 1.2s var(--ease), filter .85s var(--ease);
}
.s-title { transition-delay: .1s; }
.s-head.is-seen .m-kicker,
.s-head.is-seen .s-title { opacity: 1; translate: 0 0; filter: none; }

/* ───────────── Card ───────────── */
.card {
  position: relative; z-index: 1;
  width: min(560px, 100%);
  padding: var(--s5) var(--s4) var(--s4);
  border-radius: var(--s4);
  /* Lit from above: a soft top sheen over a vertical ramp, never a flat fill. */
  background:
    radial-gradient(125% 85% at 50% -12%, rgba(255, 255, 255, .06) 0%, rgba(255, 255, 255, 0) 62%),
    linear-gradient(180deg, #17171b 0%, #101013 52%, #0c0c0f 100%);
  border: 1px solid var(--line);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .09),
    inset 0 -1px 0 rgba(0, 0, 0, .5),
    0 2px 5px rgba(0, 0, 0, .3),
    0 21px 55px -13px rgba(0, 0, 0, .6),
    0 55px 89px -34px rgba(0, 0, 0, .65);
  display: flex; flex-direction: column; align-items: center; text-align: center;
  will-change: transform;
  /* Sits below the fold, so it rises in when scrolled to rather than on entry.
     `scale`/`translate` are separate properties from `transform`, which the tilt owns. */
  margin: 0 auto;
  opacity: 0; scale: .97; translate: 0 28px;
  transition: opacity 1s var(--ease) .12s, scale 1.4s var(--ease) .12s, translate 1.2s var(--ease) .12s;
}
.card.is-seen { opacity: 1; scale: 1; translate: 0 0; }
/* First thing in the card now — the card's own padding handles the spacing. */
.card .links { margin-top: 0; }
.card > :not(.card-fx) { position: relative; z-index: 1; }

/* Spotlight layer: a pre-rendered dithered canvas moved under the cursor. */
.card-fx {
  position: absolute; inset: 0; z-index: 0;
  border-radius: calc(var(--s4) - 1px);
  overflow: hidden; pointer-events: none;
}
.spot {
  position: absolute; left: 0; top: 0; width: 520px; height: 520px;
  /* --mx/--my are border-box coords; the fx layer sits 1px inside the border. */
  transform: translate3d(calc(var(--mx, -999px) - 261px), calc(var(--my, -999px) - 261px), 0);
  opacity: 0; transition: opacity .55s var(--ease-io);
}
/* 1px border highlight following the cursor, masked to just the border ring.
   Many stops so the faint falloff doesn't band. */
.card::after {
  content: ""; position: absolute; inset: -1px; z-index: 2;
  border-radius: inherit; padding: 1px; pointer-events: none;
  background: radial-gradient(160px circle at var(--mx, -999px) var(--my, -999px),
    rgba(255,255,255,.30) 0%, rgba(255,255,255,.27) 10%, rgba(255,255,255,.23) 20%,
    rgba(255,255,255,.18) 30%, rgba(255,255,255,.135) 40%, rgba(255,255,255,.095) 50%,
    rgba(255,255,255,.062) 60%, rgba(255,255,255,.036) 70%, rgba(255,255,255,.017) 80%,
    rgba(255,255,255,.005) 90%, rgba(255,255,255,0) 100%);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
          mask: linear-gradient(#000 0 0) content-box exclude, linear-gradient(#000 0 0);
  opacity: 0; transition: opacity .55s var(--ease-io);
}
@media (hover: hover) {
  .card:hover .spot, .card:hover::after { opacity: 1; }
}

/* ───────────── Staggered entrance ───────────── */
[data-in] {
  opacity: 0; translate: 0 8px; filter: blur(5px);
  transition:
    opacity 1s var(--ease) calc(144ms + var(--i, 0) * 55ms),
    translate 1s var(--ease) calc(144ms + var(--i, 0) * 55ms),
    filter 1s var(--ease) calc(144ms + var(--i, 0) * 55ms);
}
/* `none` (not blur(0)) so text ends perfectly crisp. */
body.is-in [data-in] { opacity: 1; translate: 0 0; filter: none; }

/* ───────────── Identity ───────────── */
.avatar {
  display: block; width: var(--s7); height: var(--s7);
  border-radius: 50%; object-fit: cover;
  background: #000;
  /* Thin warm ring, a wider faint halo, then a deep drop so it sits above the card. */
  box-shadow:
    0 0 0 1px var(--rim),
    0 0 0 6px rgba(255, 255, 255, .022),
    0 14px 34px rgba(0, 0, 0, .6);
  margin-bottom: var(--s4);
}
.name {
  /* `background-clip: text` only paints inside the element's own box, and the
     glyphs overflow it in BOTH directions: the tight line-height sliced the
     bottom off the y, and the negative letter-spacing is also applied after the
     LAST character, making the box ~0.045em narrower than the ink and cutting
     the right edge off the o. Padding grows the painted box on every side; the
     matching negative margins cancel it, so nothing moves on the page. */
  margin: -0.16em -0.09em -0.2em;
  padding: 0.16em 0.09em 0.2em;
  /* Fluid and deliberately large — it's the whole first screen. */
  font-size: clamp(84px, 30vw, 184px); font-weight: 600; letter-spacing: -0.045em; line-height: 1.0;
  overflow-wrap: anywhere;
  /* One hue, light to deep — a single colour sits on black; a multi-hue sweep fights it. */
  background: linear-gradient(170deg,
    var(--name-1) 0%, var(--name-2) 50%, var(--name-3) 100%);
  -webkit-background-clip: text; background-clip: text;
  color: transparent;
}
/* `body.is-in [data-in] { filter: none }` has the same specificity as a bare
   `.name` rule and comes earlier, so the glow has to be declared here to win. */
/* A light source, not lit text: three stacked glows, each wider and fainter,
   so the letters appear to throw light onto the background behind them. */
body.is-in .name {
  filter:
    drop-shadow(0 0 12px rgba(255, 255, 255, .34))
    drop-shadow(0 0 44px rgba(206, 222, 255, .26))
    drop-shadow(0 0 104px rgba(168, 196, 245, .2));
}
.bio {
  margin: var(--s3) 0 0; min-height: var(--s5); max-width: 100%;
  display: flex; align-items: center; justify-content: center;
  font-size: clamp(16px, 2.1vw, 21px); font-weight: 500; color: var(--fg-2); letter-spacing: -0.01em;
}
.bio-line {
  display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%;
  transition: opacity 340ms var(--ease-io), transform 340ms var(--ease), filter 340ms var(--ease-io);
}
.bio-line.is-out { opacity: 0; transform: translateY(-5px); filter: blur(3px); }
.bio-line.is-pre { opacity: 0; transform: translateY(5px); filter: blur(3px); transition: none; }

/* ───────────── Links ───────────── */
.links {
  margin-top: 0;
  width: 100%;
  display: flex; flex-direction: column;
}
/* A row per link: icon, big name, handle, arrow — separated by hairlines. */
.link {
  position: relative;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto auto;
  align-items: center;
  gap: var(--s3);
  padding: var(--s3) var(--s2);
  text-align: left;
  color: var(--fg); text-decoration: none;
  border-bottom: 1px solid var(--line);
  /* opacity/translate are the scroll reveal below; `translate` is its own
     property, so it never fights the hover `transform`. */
  transition: color .2s var(--ease-io), transform .34s var(--ease),
              opacity .7s var(--ease), translate .95s var(--ease), filter .7s var(--ease);
}
.link:last-child { border-bottom: 0; }
.l-name {
  font-size: clamp(27px, 5.4vw, 40px); font-weight: 600;
  letter-spacing: -0.03em; line-height: 1.14;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.l-handle {
  font-size: 12px; font-weight: 500; color: var(--fg-3);
  white-space: nowrap; transition: color .2s var(--ease-io);
}
.l-arrow {
  font-size: 15px; line-height: 1; color: var(--fg-3);
  transition: color .2s var(--ease-io), transform .3s var(--ease);
}

/* ── Scroll reveal ────────────────────────────────────────────────────────
   The card rises as one, then the chips pop in one after another and the
   player slides up last, so it arrives in order instead of all at once. */
.card .link { opacity: 0; translate: 0 24px; filter: blur(7px); }
.card.is-seen .link { opacity: 1; translate: 0 0; filter: none; }
.card.is-seen .link:nth-child(1) { transition-delay: .26s; }
.card.is-seen .link:nth-child(2) { transition-delay: .36s; }
.card.is-seen .link:nth-child(3) { transition-delay: .46s; }
.card.is-seen .link:nth-child(4) { transition-delay: .56s; }
.card.is-seen .link:nth-child(5) { transition-delay: .66s; }

.card .player {
  opacity: 0; translate: 0 34px;
  transition: opacity .55s var(--ease) .34s, translate .8s var(--ease) .34s;
}
.card.is-seen .player { opacity: 1; translate: 0 0; }
/* Icon = CSS mask over currentColor, so it takes the text colour. */
.link i {
  width: 21px; height: 21px; display: block;
  background: currentColor;
  -webkit-mask: var(--icon) center / contain no-repeat;
          mask: var(--icon) center / contain no-repeat;
}
.link:focus-visible { color: var(--metal-1); outline: none; }
.link:focus-visible .l-arrow { transform: translate(2px, -2px); }
@media (hover: hover) {
  .link:hover { color: var(--metal-1); transform: translateX(4px); }
  .link:hover .l-handle, .link:hover .l-arrow { color: var(--fg-2); }
  .link:hover .l-arrow { transform: translate(2px, -2px); }
}

/* ───────────── Player ───────────── */
.player {
  width: 100%; margin-top: var(--s5);
  padding: var(--s3); border-radius: var(--s3);
  background: linear-gradient(180deg, rgba(255, 255, 255, .055) 0%, rgba(255, 255, 255, .02) 100%);
  border: 1px solid var(--line);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .07), 0 10px 26px -8px rgba(0, 0, 0, .55);
  text-align: left;
}
.player[hidden] { display: none; }

.p-top { display: grid; grid-template-columns: var(--s6) minmax(0, 1fr); align-items: center; gap: var(--s3); }
.p-art { width: var(--s6); height: var(--s6); border-radius: var(--s1); background: #1a1a1c; box-shadow: 0 5px 13px rgba(0, 0, 0, .45); overflow: hidden; }
.p-cover { display: block; width: 100%; height: 100%; object-fit: cover; }
.p-cover.is-empty { visibility: hidden; }
.p-title, .p-artist { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.p-title { font-size: 16px; font-weight: 600; letter-spacing: -0.015em; line-height: 1.35; }
.p-artist { font-size: 14px; color: var(--fg-2); line-height: 1.4; }

/* Song-change crossfade */
.p-art img, .p-meta { transition: opacity .22s var(--ease-io), filter .22s var(--ease-io); }
.player.is-swapping .p-art img, .player.is-swapping .p-meta { opacity: 0; filter: blur(3px); }

.ib {
  position: relative;
  display: grid; place-items: center;
  width: 32px; height: 32px; border-radius: 50%;
  color: var(--fg-2);
  transition: color .18s var(--ease-io), transform .28s var(--ease);
}
.ib svg { width: 16px; height: 16px; display: block; }
.ib:active { transform: scale(.94); }
@media (hover: hover) {
  .ib:hover { color: var(--fg); transform: scale(1.04); }
  .ib:hover:active { transform: scale(.94); }
}
/* Shuffle / repeat "on": green with a dot underneath, like Spotify. */
.tg[aria-pressed="true"] { color: var(--green); }
@media (hover: hover) { .tg[aria-pressed="true"]:hover { color: var(--green); } }
.tg::after {
  content: ""; position: absolute; left: 50%; bottom: 1px;
  width: 4px; height: 4px; margin-left: -2px; border-radius: 50%;
  background: var(--green);
  opacity: 0; transform: scale(.4);
  transition: opacity .2s var(--ease-io), transform .3s var(--ease);
}
.tg[aria-pressed="true"]::after { opacity: 1; transform: none; }

.p-ctrls { display: flex; align-items: center; justify-content: center; gap: var(--s3); margin-top: var(--s3); }
.p-play {
  display: grid; place-items: center;
  width: var(--s5); height: var(--s5); border-radius: 50%;
  background: linear-gradient(180deg, #ffffff 0%, #dfdfe4 100%); color: #000;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .9), 0 6px 18px rgba(0, 0, 0, .5);
  transition: transform .28s var(--ease), box-shadow .28s var(--ease);
}
.p-play svg { width: 14px; height: 14px; display: block; }
.p-play:active { transform: scale(.94); }
@media (hover: hover) {
  .p-play:hover { transform: scale(1.04); }
  .p-play:hover:active { transform: scale(.94); }
}
/* Icon swap: scale in rather than cut. */
.pop svg { animation: pop 260ms var(--ease) both; }
@keyframes pop { from { transform: scale(.8); opacity: .4; } }

.p-prog { display: grid; grid-template-columns: var(--s5) minmax(0, 1fr) var(--s5); align-items: center; gap: var(--s2); margin-top: var(--s2); }
.p-time {
  font-size: 12px; font-weight: 500; color: var(--fg-2);
  font-variant-numeric: tabular-nums; line-height: 1;
}
.p-time:first-child { text-align: right; }
.p-bar { position: relative; height: 13px; display: flex; align-items: center; cursor: pointer; border-radius: 4px; touch-action: none; }
.p-track { position: relative; width: 100%; height: 4px; border-radius: 2px; background: rgba(255, 255, 255, .18); overflow: hidden; }
.p-fill {
  position: absolute; inset: 0; border-radius: inherit;
  background: var(--fg);
  transform-origin: left center; transform: scaleX(0);
  transition: background-color .15s var(--ease-io);
}
.p-knob {
  position: absolute; top: 50%; left: 0;
  width: 12px; height: 12px; margin: -6px 0 0 -6px; border-radius: 50%;
  background: #fff; box-shadow: 0 2px 5px rgba(0, 0, 0, .5);
  opacity: 0; transform: scale(.6);
  transition: opacity .15s var(--ease-io), transform .25s var(--ease);
  pointer-events: none;
}
.p-bar.is-dragging .p-fill, .p-bar:focus-visible .p-fill { background: var(--green); }
.p-bar.is-dragging .p-knob, .p-bar:focus-visible .p-knob { opacity: 1; transform: none; }
@media (hover: hover) {
  .p-bar:hover .p-fill { background: var(--green); }
  .p-bar:hover .p-knob { opacity: 1; transform: none; }
}

/* Volume: mute button as the cap, short bar beside it. Shorter and dimmer than
   the seek bar on purpose, so the two don't read as the same control. */
.p-vol { display: flex; align-items: center; gap: var(--s2); margin-top: var(--s1); }
.p-vol .ib { width: 28px; height: 28px; margin-left: -6px; }
.p-vol .ib svg { width: 15px; height: 15px; }
.v-bar { width: 84px; flex: none; }
.v-bar .p-track { height: 3px; background: rgba(255, 255, 255, .14); }
.v-bar .p-fill { background: var(--fg-2); }
.v-bar .p-knob { width: 10px; height: 10px; margin: -5px 0 0 -5px; }
.v-bar.is-dragging .p-fill, .v-bar:focus-visible .p-fill { background: var(--fg); }
.p-vol.is-muted .v-bar { opacity: .4; }
@media (hover: hover) {
  .v-bar:hover .p-fill { background: var(--fg); }
}

/* ───────────── Footer ───────────── */
.foot {
  /* In flow at the end of the page now that the layout scrolls. */
  text-align: center; font-size: 12px; color: var(--fg-3); letter-spacing: -0.005em;
  padding-bottom: calc(var(--s4) + env(safe-area-inset-bottom));
}

/* YouTube players: kept out of the way. YouTube's embed terms don't allow hiding the
   player, so prefer audio files you own (see the songs comment in config.js). */
.yt { position: fixed; right: 0; bottom: 0; width: 200px; height: 200px; opacity: .001; pointer-events: none; z-index: -1; overflow: hidden; }
.yt iframe { width: 200px; height: 200px; border: 0; }

/* ───────────── Touch ───────────── */
@media (pointer: coarse) {
  .ib { width: 44px; height: 44px; }
  .p-play { width: 44px; height: 44px; }
  .p-ctrls { gap: var(--s1); }
  .p-top { gap: var(--s2); }
  .tg::after { bottom: 5px; }
  /* Tall invisible grab area, same thin visible track, knob always shown. */
  .p-bar { height: var(--s5); }
  .p-knob { opacity: 1; transform: scale(.75); }
  .p-bar.is-dragging .p-knob { transform: scale(1.15); }
  /* Thumb-sized mute button and a taller grab strip for the volume bar. */
  .p-vol { gap: var(--s1); margin-top: 0; }
  .p-vol .ib { width: 40px; height: 40px; margin-left: -9px; }
  .v-bar { width: 104px; }
}

@media (max-width: 359px) {
  .card { padding: var(--s4) var(--s3) var(--s3); }
  .player { padding: var(--s2); }
  .p-ctrls { gap: 0; }
}

/* ───────────── Reduced motion ───────────── */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation: none !important; transition: none !important; }
  [data-in] { translate: none; filter: none; }
  /* Everything that animates in is simply present instead — with animations and
     transitions off, anything left at opacity 0 would never become visible. */
  .panel { opacity: 1; filter: none; translate: none; scale: 1; }
  .card { scale: 1; translate: none; opacity: 1; }
  .name { opacity: 1; }
  .card .link, .card .player { opacity: 1; translate: none; scale: 1; filter: none; }
  .m-kicker, .m-title, .m-who-line, .s-title, .m-plot,
  .m-live, .m-foot { opacity: 1; translate: none; filter: none; }
  .m-plot { clip-path: none; }
  .bg-glow, .streak { animation: none; }
  .m-dot { opacity: 1; }
  .flash { display: none; }
}
