/* =============================================
   HOP STEW — Motion Layer

   Every duration and stagger in this file is a
   subdivision of a 90 BPM boom-bap grid:

     1 bar     = 2667ms
     1 beat    =  667ms
     1/8 note  =  333ms
     1/16 note =  167ms

   Motion that lands on the grid reads as musical
   instead of merely animated. Nothing here moves
   anything but transform / opacity / filter, and
   the whole layer is switched off under
   prefers-reduced-motion at the bottom.
   ============================================= */

:root {
  --bar:        2667ms;
  --beat:        667ms;
  --eighth:      333ms;
  --sixteenth:   167ms;
  --two-beat:   1333ms;

  /* Decelerate hard, like a break landing */
  --ease-punch:  cubic-bezier(0.16, 1, 0.30, 1);
  /* Slight overshoot for hover states */
  --ease-snap:   cubic-bezier(0.34, 1.40, 0.64, 1);
  --ease-groove: cubic-bezier(0.65, 0, 0.35, 1);

  --spin-33:    1800ms;  /* one revolution at 33 1/3 RPM */
}

/* =============================================
   1. FILM GRAIN
   A single composited layer over the whole page.
   Painted once, then only transformed.
   ============================================= */
body::after {
  content: "";
  position: fixed;
  /* Just enough overhang to cover the jitter below —
     any larger and this becomes a needlessly big
     composited layer. */
  inset: -12%;
  z-index: 300;
  pointer-events: none;
  opacity: 0.055;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)'/%3E%3C/svg%3E");
  will-change: transform;
  animation: grain var(--eighth) steps(1) infinite;
}

@keyframes grain {
  0%   { transform: translate3d(0, 0, 0); }
  25%  { transform: translate3d(-1.5%, 1%, 0); }
  50%  { transform: translate3d(1%, -1.5%, 0); }
  75%  { transform: translate3d(-1%, -1%, 0); }
  100% { transform: translate3d(1.5%, 0.5%, 0); }
}

/* =============================================
   2. SCROLL REVEAL
   .reveal is the hook already used across every
   page. data-motion picks the direction, and JS
   sets --stagger per group on an 1/16 grid.

   Guarded behind .js so a failed script can never
   leave the page blank.
   ============================================= */
.js .reveal {
  opacity: 0;
  transform: translate3d(0, 34px, 0);
  transition:
    opacity   var(--beat) var(--ease-punch),
    transform var(--beat) var(--ease-punch),
    filter    var(--beat) var(--ease-punch);
  transition-delay: var(--stagger, 0ms);
}

.js .reveal[data-motion="left"]  { transform: translate3d(-46px, 0, 0); }
.js .reveal[data-motion="right"] { transform: translate3d(46px, 0, 0); }
.js .reveal[data-motion="scale"] { transform: scale(0.94); }
.js .reveal[data-motion="drop"]  { transform: translate3d(0, -28px, 0); }
.js .reveal[data-motion="dust"]  {
  transform: translate3d(0, 22px, 0) scale(0.985);
  filter: blur(6px);
}
/* Fade only — for full-bleed bands that shouldn't slide */
.js .reveal[data-motion="none"]  { transform: none; }

.js .reveal.visible {
  opacity: 1;
  transform: none;
  filter: none;
}

/* A not-yet-revealed [data-motion="right"] element sits
   46px right of its resting place, which can poke past
   the viewport and raise a horizontal scrollbar until it
   reveals. clip (not hidden) contains it without creating
   a scroll container, so position: sticky still works. */
main {
  overflow-x: clip;
}

/* Word-by-word type reveal — the headline equivalent
   of dropping in one bar at a time.

   Split targets are held at opacity 0 until the script
   has rewritten them, so nobody sees the un-split text
   flash and then leave. */
.js [data-split] { opacity: 0; }
.js [data-split].is-split { opacity: 1; }

/* A split heading animates through its words, so it
   opts out of the whole-block reveal transform. */
.js .reveal.is-split { transform: none; }

.split-line {
  display: inline-block;
  overflow: hidden;
  vertical-align: bottom;
  padding-bottom: 0.08em;
}

.split-word {
  display: inline-block;
  transform: translate3d(0, 110%, 0);
  transition: transform var(--two-beat) var(--ease-punch);
  transition-delay: var(--stagger, 0ms);
}

/* Words unmask when the heading itself is revealed, or
   when a revealing ancestor brings it into view. */
.is-split.visible .split-word,
.visible .is-split .split-word { transform: none; }

/* =============================================
   3. SCROLL PROGRESS — a fader across the top
   ============================================= */
.scroll-progress {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 150;
  width: 100%;
  height: 3px;
  transform: scaleX(var(--progress, 0));
  transform-origin: 0 50%;
  background: linear-gradient(90deg, var(--gold), var(--red));
  will-change: transform;
}

/* =============================================
   4. THE RECORD
   Grooves are a repeating radial gradient; the
   sheen sits outside the spinning layer so the
   highlight stays put while the disc turns.
   ============================================= */
.vinyl {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background:
    repeating-radial-gradient(circle at 50% 50%,
      rgba(255, 255, 255, 0.055) 0 1px,
      rgba(0, 0, 0, 0) 1px 5px),
    radial-gradient(circle at 50% 50%, #191919 0 30%, #0c0c0c 30% 100%);
  box-shadow:
    inset 0 0 0 1px rgba(255, 255, 255, 0.07),
    inset 0 0 60px rgba(0, 0, 0, 0.8);
  animation: spin var(--spin-dur, 22s) linear infinite;
  will-change: transform;
}

/* Centre label */
.vinyl::before {
  content: "";
  position: absolute;
  inset: 33%;
  border-radius: 50%;
  background:
    radial-gradient(circle at 50% 50%, var(--dark) 0 10%, rgba(0,0,0,0) 10.5%),
    radial-gradient(circle at 35% 30%, #c33032 0 40%, var(--red) 40% 72%, var(--red-dark) 72% 100%);
  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.5);
}

/* Spindle hole */
.vinyl::after {
  content: "";
  position: absolute;
  inset: 48.4%;
  border-radius: 50%;
  background: var(--dark);
}

.vinyl-scratch {
  position: absolute;
  inset: 0;
}

.vinyl-scratch.is-scratching {
  animation: scratch var(--two-beat) var(--ease-groove);
}

/* Hand on the platter — drag back, let it go */
@keyframes scratch {
  0%   { transform: rotate(0deg); }
  18%  { transform: rotate(-16deg); }
  38%  { transform: rotate(10deg); }
  58%  { transform: rotate(-7deg); }
  78%  { transform: rotate(3deg); }
  100% { transform: rotate(0deg); }
}

.vinyl-sheen {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  pointer-events: none;
  background: linear-gradient(118deg,
    rgba(255, 255, 255, 0) 28%,
    rgba(255, 255, 255, 0.13) 44%,
    rgba(255, 255, 255, 0.02) 52%,
    rgba(255, 255, 255, 0) 62%);
}

@keyframes spin { to { transform: rotate(360deg); } }

/* Hero record — cropped off the right edge like a
   sleeve half out of the crate. */
.hero-vinyl {
  position: absolute;
  top: 50%;
  right: -9vw;
  width: min(32vw, 420px);
  aspect-ratio: 1;
  transform: translateY(-50%);
  z-index: 2;
  opacity: 0.92;
  filter: drop-shadow(0 30px 60px rgba(0, 0, 0, 0.65));
  cursor: grab;
}

.hero-vinyl:active { cursor: grabbing; }

@media (max-width: 1024px) {
  .hero-vinyl { right: -28vw; width: 62vw; opacity: 0.5; }
}

@media (max-width: 767px) {
  .hero-vinyl { display: none; }
}

/* =============================================
   5. HERO — parallax bed, slow push-in, EQ
   ============================================= */
/* headerbg.* is only 293px tall and near-black, so it
   dissolves at hero scale. bg.* is the same visual
   family at 1280x863 and holds up. */
.hero-media {
  position: absolute;
  inset: -8% 0;
  z-index: 0;
  background: url('../images/bg.jpg') center/cover no-repeat;
  background-image: image-set(
    url('../images/bg.avif') type('image/avif'),
    url('../images/bg.webp') type('image/webp'),
    url('../images/bg.jpg') type('image/jpeg')
  );
  filter: brightness(1.9) contrast(1.1) sepia(0.18);
  transform: translate3d(0, var(--parallax, 0px), 0) scale(1.06);
  will-change: transform;
  animation: push-in 18s var(--ease-groove) both;
}

@keyframes push-in {
  from { scale: 1.14; }
  to   { scale: 1; }
}

/* Spectrum analyser sitting on the hero's baseline */
.eq {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 2;
  display: flex;
  align-items: flex-end;
  gap: 3px;
  height: 76px;
  padding: 0 2px;
  pointer-events: none;
  opacity: 0.45;
  /* Fade out at both edges and toward the top so the bars
     sit in the image rather than on top of it */
  mask-image:
    linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent),
    linear-gradient(180deg, transparent, #000 65%);
  -webkit-mask-composite: source-in;
  mask-composite: intersect;
}

.eq span {
  flex: 1;
  min-width: 2px;
  height: 100%;   /* scaleY needs a box to scale */
  border-radius: 2px 2px 0 0;
  transform: scaleY(0.1);
  transform-origin: bottom;
  background: linear-gradient(180deg, var(--gold), var(--red) 70%, var(--red-dark));
  animation: eq-bounce var(--dur) var(--ease-groove) var(--delay) infinite alternate;
  will-change: transform;
}

@keyframes eq-bounce {
  from { transform: scaleY(0.06); }
  to   { transform: scaleY(1); }
}

/* Hero copy drops in on the downbeat. Split headings
   run their own word animation instead. */
.js .hero-content > *:not([data-split]) {
  opacity: 0;
  transform: translate3d(0, 26px, 0);
  animation: hero-in var(--two-beat) var(--ease-punch) forwards;
  animation-delay: var(--stagger, 0ms);
}

@keyframes hero-in {
  to { opacity: 1; transform: none; }
}

/* =============================================
   6. CALL TO ACTION — pulses on 2 and 4
   ============================================= */
.btn-pulse::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  border: 2px solid var(--red);
  pointer-events: none;
  animation: pulse-ring var(--two-beat) var(--ease-groove) infinite;
}

@keyframes pulse-ring {
  0%        { transform: scale(1);    opacity: 0.9; }
  70%, 100% { transform: scale(1.32); opacity: 0; }
}

/* =============================================
   7. COLLAB BANNER
   The names land one after another on a 1/32
   grid — a roll call rather than a crawl.
   Delays come from data-stagger-children in JS.
   ============================================= */
.js [data-stagger-children] > * {
  opacity: 0;
  transform: translate3d(0, 14px, 0);
  transition:
    opacity   var(--beat) var(--ease-punch),
    transform var(--beat) var(--ease-punch);
  transition-delay: var(--stagger, 0ms);
}

.js .visible [data-stagger-children] > *,
.js [data-stagger-children].visible > * {
  opacity: 1;
  transform: none;
}

/* The star between names catches the light on hover */
.collab-list li::before {
  transition: transform var(--beat) var(--ease-snap);
}

.collab-list li:hover::before { transform: rotate(180deg) scale(1.25); }

/* =============================================
   8. PLATFORM CARDS — needle drop
   ============================================= */
.platform-card {
  position: relative;
  overflow: hidden;
  isolation: isolate;
}

/* Groove ring that opens out from under the label */
.platform-card .platform-icon {
  position: relative;
  display: block;
  border-radius: 50%;
}

.platform-card .platform-icon::before {
  content: "";
  position: absolute;
  inset: -14px;
  border-radius: 50%;
  border: 1px solid rgba(181, 124, 21, 0.5);
  opacity: 0;
  transform: scale(0.82);
  transition:
    opacity   var(--beat) var(--ease-punch),
    transform var(--beat) var(--ease-snap);
}

.platform-card:hover .platform-icon::before,
.platform-card:focus-visible .platform-icon::before {
  opacity: 1;
  transform: scale(1);
}

/* One full turn in, and a backspin on the way out */
.platform-card .platform-icon img {
  transition: transform var(--bar) linear;
}

.platform-card:hover .platform-icon img,
.platform-card:focus-visible .platform-icon img {
  transform: rotate(360deg);
}

/* Red sweep across the base of the card */
.platform-card::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 4px;
  background: linear-gradient(90deg, var(--red), var(--gold));
  transform: scaleX(0);
  transform-origin: left;
  transition: transform var(--beat) var(--ease-punch);
}

.platform-card:hover::after,
.platform-card:focus-visible::after { transform: scaleX(1); }

/* The Podomatic label spins up when you reach for it */
.podomatic-logo img {
  transition: transform var(--bar) linear, box-shadow var(--beat) ease;
}

.podomatic-logo a:hover img,
.podomatic-logo a:focus-visible img {
  transform: rotate(360deg);
  box-shadow: 0 10px 30px rgba(39, 36, 29, 0.28);
}

/* =============================================
   9. CARD LIFTS
   ============================================= */
.video-item .video-wrap,
.podo-episode,
.episode-card,
.gear-card,
.quote-card,
.video-card {
  transition:
    transform  var(--beat) var(--ease-punch),
    box-shadow var(--beat) var(--ease-punch),
    border-color var(--beat) var(--ease-punch);
}

.video-item:hover .video-wrap {
  transform: translateY(-5px);
  box-shadow: 0 18px 40px rgba(39, 36, 29, 0.28);
}

.podo-episode {
  position: relative;
  overflow: hidden;
}

.podo-episode::before {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 3px;
  background: var(--gold);
  transform: scaleY(0);
  transform-origin: top;
  transition: transform var(--beat) var(--ease-punch);
}

.podo-episode:hover::before,
.podo-episode:focus-within::before { transform: scaleY(1); }

.podo-episode:hover {
  transform: translateY(-4px);
  box-shadow: 0 16px 34px rgba(39, 36, 29, 0.16);
}

.quote-card:hover { transform: translateY(-4px); }

.quote-card:hover .quote-mark { animation: nod var(--two-beat) var(--ease-groove); }

/* The head nod */
@keyframes nod {
  0%, 100% { transform: translateY(0) rotate(0deg); }
  30%      { transform: translateY(-5px) rotate(-4deg); }
  60%      { transform: translateY(2px) rotate(2deg); }
}

/* Photos in the about stack push in slightly on hover */
.about-shot img {
  transition: transform var(--bar) var(--ease-punch);
}

.about-shot:hover img { transform: scale(1.04); }

.gear-card .gear-icon {
  transition: transform var(--beat) var(--ease-snap);
}

.gear-card:hover .gear-icon { transform: translateY(-4px) scale(1.12) rotate(-6deg); }

/* Artist chips shuffle up one after another */
.bio-artists li {
  transition:
    transform    var(--eighth) var(--ease-snap),
    border-color var(--eighth) ease,
    color        var(--eighth) ease,
    background   var(--eighth) ease;
}

.bio-artists li:hover { transform: translateY(-3px) rotate(-1.5deg); }

/* =============================================
   10. TIMELINE — markers punch in on the beat
   ============================================= */
.js .timeline-line {
  transform: translateX(-50%) scaleY(0);
  transform-origin: top;
  transition: transform var(--bar) var(--ease-groove);
}

.js .timeline-line.visible { transform: translateX(-50%) scaleY(1); }

.js .timeline-marker {
  transform: scale(0);
  transition: transform var(--beat) var(--ease-snap);
  transition-delay: var(--stagger, 0ms);
}

.js .reveal.visible .timeline-marker { transform: scale(1); }

@media (max-width: 1024px) {
  .js .timeline-line { transform: none; }
  .js .timeline-line.visible { transform: scaleY(1); }
}

/* =============================================
   11. BACK TO TOP — a 45 on a spindle
   ============================================= */
.to-top {
  position: fixed;
  right: 1.25rem;
  bottom: 1.25rem;
  z-index: 140;
  width: 54px;
  height: 54px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: none;
  cursor: pointer;
  opacity: 0;
  transform: translate3d(0, 20px, 0) scale(0.8);
  pointer-events: none;
  transition:
    opacity   var(--beat) var(--ease-punch),
    transform var(--beat) var(--ease-snap);
}

.to-top.is-visible {
  opacity: 1;
  transform: none;
  pointer-events: auto;
}

.to-top .vinyl {
  animation-duration: var(--spin-33);
  animation-play-state: paused;
  box-shadow:
    0 6px 18px rgba(0, 0, 0, 0.4),
    inset 0 0 0 1px rgba(255, 255, 255, 0.12);
}

.to-top:hover .vinyl,
.to-top:focus-visible .vinyl { animation-play-state: running; }

.to-top .to-top-arrow {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  color: var(--white);
  font-size: 15px;
  line-height: 1;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.7);
}

.to-top:hover { transform: translateY(-3px); }

/* =============================================
   12. STAT COUNTER
   ============================================= */
/* Sits high against the numeral, like a superscript */
.stat-plus {
  display: inline-block;
  align-self: flex-start;
  margin-left: -0.5rem;
  color: var(--gold-lit);
  font-family: var(--font-display);
  font-weight: 800;
  font-size: clamp(1.6rem, 3vw, 2.4rem);
  line-height: 1;
  transform: scale(0);
  transform-origin: bottom left;
  transition: transform var(--beat) var(--ease-snap);
  transition-delay: var(--beat);
}

.stat.visible .stat-plus,
.reveal.visible .stat-plus { transform: scale(1); }

/* =============================================
   13. REDUCED MOTION
   Everything above is decoration. Strip it all,
   keep the page fully legible and usable.
   ============================================= */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }

  body::after { animation: none; }

  .js .reveal,
  .js [data-split],
  .js [data-stagger-children] > *,
  .js .hero-content > * {
    opacity: 1 !important;
    transform: none !important;
    filter: none !important;
    transition: none !important;
    animation: none !important;
  }

  .split-word { transform: none !important; transition: none !important; }

  .vinyl,
  .eq span,
  .hero-media,
  .btn-pulse::before {
    animation: none !important;
  }

  .eq span { transform: scaleY(0.4); }
  .btn-pulse::before { display: none; }

  /* JS marks everything visible up front under reduced
     motion, so the .visible rules already hold. Only the
     transitions need removing. */
  .js .timeline-line,
  .js .timeline-marker { transition: none !important; }

  .to-top { transition: opacity var(--eighth) linear; }

  .stat-plus { transform: none; transition: none; }

  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}
