/* DAJAI.IO — DJ DECK
   The desk where she plays for fans.
   Two decks · live mixer · FX rack · sample pads · the dajai.io/music catalog
   plus a live fan-request queue + chat. State-of-the-art.
   ============================================================ */

const DAJAI_CATALOG = [
  { n: '01', t: 'two',           bpm: 92,  key: '8A',  dur: 228, plays: 184302, hue: '#ff2a3c', delta: +4.2,  sub: 'opening · the only song that needs two voices', cues: [0.04, 0.18, 0.42, 0.66, 0.84] },
  { n: '02', t: 'frozen anny',   bpm: 78,  key: '11A', dur: 252, plays: 96541,  hue: '#ff4d8a', delta: +1.8,  sub: 'cold-room version · b-side',                 cues: [0.06, 0.22, 0.45, 0.70] },
  { n: '03', t: 'drought vi',    bpm: 104, key: '4A',  dur: 302, plays: 152844, hue: '#ff6b2c', delta: +9.4,  sub: 'sixth pass · the one that hurts',            cues: [0.04, 0.18, 0.40, 0.68, 0.88] },
  { n: '04', t: 'lv. 89102',     bpm: 88,  key: '6A',  dur: 378, plays: 211093, hue: '#ffd166', delta: +12.1, sub: 'late vegas · zip code as ballad',            cues: [0.05, 0.20, 0.50, 0.80] },
  { n: '05', t: 'blue hour',     bpm: 70,  key: '1B',  dur: 284, plays: 84207,  hue: '#6fc8ff', delta: -0.6,  sub: 'cobalt window · pre-dawn mix',               cues: [0.08, 0.30, 0.62] },
  { n: '06', t: 'engineer',      bpm: 96,  key: '10A', dur: 211, plays: 64118,  hue: '#2da4ff', delta: +2.3,  sub: 'the song from the chair',                    cues: [0.06, 0.24, 0.48, 0.78] },
  { n: '07', t: 'morning drive', bpm: 110, key: '2B',  dur: 188, plays: 132455, hue: '#ffb020', delta: +5.7,  sub: 'one window, one road',                       cues: [0.04, 0.20, 0.46, 0.70, 0.92] },
  { n: '08', t: 'double yellow', bpm: 124, key: '7A',  dur: 174, plays: 41882,  hue: '#ff2a3c', delta: -2.4,  sub: 'two girls, one lane',                        cues: [0.05, 0.22, 0.44, 0.78] },
  { n: '09', t: 'she said no',   bpm: 86,  key: '12A', dur: 270, plays: 78903,  hue: '#ff4d8a', delta: +0.4,  sub: 'short version · take 7',                     cues: [0.06, 0.32, 0.58, 0.84] },
  { n: '10', t: 'the cobalt',    bpm: 64,  key: '3A',  dur: 442, plays: 58220,  hue: '#2da4ff', delta: +3.1,  sub: 'instrumental · for sleeping',                cues: [0.08, 0.34, 0.66] },
  { n: '11', t: '07 minutes early', bpm: 100, key: '5B', dur: 247, plays: 39411, hue: '#ff6b2c', delta: -1.1, sub: 'the airport song',                            cues: [0.04, 0.22, 0.50, 0.74] },
  { n: '12', t: 'off-air',       bpm: 60,  key: '9A',  dur: 528, plays: 28104,  hue: '#ff2a3c', delta: +0.0,  sub: 'closing · runs forever',                     cues: [0.10, 0.40, 0.70] },
];

/* deterministic procedural waveform */
function genWave(track, len = 256) {
  const seed = parseInt(track.n, 10) * 137;
  const arr = [];
  for (let i = 0; i < len; i++) {
    const p = i / len;
    // a few "sections": intro low, verse mid, chorus high, breakdown low, drop high, outro
    let env;
    if (p < 0.06) env = 0.25 + p * 6;          // intro fade-in
    else if (p < 0.22) env = 0.55;              // verse
    else if (p < 0.42) env = 0.85;              // chorus
    else if (p < 0.50) env = 0.35;              // breakdown
    else if (p < 0.66) env = 0.95;              // drop
    else if (p < 0.84) env = 0.70;              // bridge
    else env = 0.45 - (p - 0.84) * 1.5;         // outro
    // beat: every 4 samples is a "beat", every 16 is bass
    const beat = (i % 4 === 0) ? 0.25 : 0;
    const bass = (i % 16 === 0) ? 0.20 : 0;
    const noise = (((seed + i * 31 + i * i * 7) % 100) / 100) * 0.35;
    const v = Math.min(1, env + beat + bass + noise * 0.6);
    arr.push(v);
  }
  return arr;
}

const FAN_HANDLES = [
  '@nyx_lv', '@boomtown', '@miss_89102', '@cobaltghost', '@latetapes',
  '@lvsally', '@anonymous', '@she_made_me', '@morningroad', '@drought6',
  '@back_to_one', '@04am', '@printedpink', '@karaokevip', '@frozenanny',
  '@vinyl_phx', '@desertbird', '@neonchorus', '@onelane', '@studiofour',
];

const FAN_MESSAGES = [
  { t: 'PLAY DROUGHT VI', heart: false },
  { t: 'this drop ❤❤❤', heart: true },
  { t: 'the breakdown is unreal', heart: false },
  { t: 'who else is in late vegas rn', heart: false },
  { t: 'request: lv 89102 → frozen anny', heart: false },
  { t: 'shoutout from tokyo 04:12am ☕', heart: false },
  { t: 'PLAY BACK TO ONE OMG', heart: false },
  { t: 'this is the one. this is the song.', heart: true },
  { t: 'my whole apartment is hearing this', heart: false },
  { t: 'sync looks PERFECT', heart: false },
  { t: 'the cobalt is unfinished business', heart: false },
  { t: 'first time on dajai tv. life-changing.', heart: true },
  { t: 'request line: morning drive next pls', heart: false },
  { t: 'love from nyc 🚖', heart: false },
  { t: 'this is the engineer\'s desk?? insane', heart: false },
  { t: 'crossfade was BUTTER', heart: true },
  { t: 'i need this on vinyl', heart: false },
  { t: 'who said this was a b-side', heart: false },
  { t: 'play TWO with both girls!!', heart: false },
  { t: 'tomorrow karaoke night?', heart: false },
];

const SAMPLE_PADS = [
  { name: 'AYY',    c: '#ff2a3c' },
  { name: 'WOO',    c: '#ff6b2c' },
  { name: 'AIRHORN',c: '#ffd166' },
  { name: 'KICK',   c: '#74e3a3' },
  { name: 'CRASH',  c: '#6fc8ff' },
  { name: 'VOX A',  c: '#ff4d8a' },
  { name: 'VOX B',  c: '#2da4ff' },
  { name: 'DROP',   c: '#ff2a3c' },
];

const CRATES = ['ALL', 'NEW', 'A-SIDES', 'PEAK', 'LATE', 'KARAOKE'];
const FX_SLOTS = [
  { name: 'ECHO',    c: '#ff2a3c', sub: 'tempo-sync · 1/4' },
  { name: 'REVERB',  c: '#6fc8ff', sub: 'plate · large hall' },
  { name: 'FILTER',  c: '#ffd166', sub: 'lp ↔ hp sweep' },
  { name: 'FLANGER', c: '#ff4d8a', sub: 'rate · 0.5hz' },
];

/* ============================================================
   GENERIC KNOB — drag to set value (0..1, default 0.5)
   ============================================================ */
function Knob({ v, onChange, hue, size = 42, label, valFmt, center }) {
  const ref = React.useRef(null);
  const onMouseDown = (e) => {
    e.preventDefault();
    const startY = e.clientY;
    const startV = v;
    const move = (ev) => {
      const dy = startY - ev.clientY;
      const next = Math.max(0, Math.min(1, startV + dy / 200));
      onChange(next);
    };
    const up = () => {
      window.removeEventListener('mousemove', move);
      window.removeEventListener('mouseup', up);
    };
    window.addEventListener('mousemove', move);
    window.addEventListener('mouseup', up);
  };
  const onDouble = () => onChange(center !== undefined ? center : 0.5);
  return (
    <div className="knob-cluster">
      <div ref={ref} className="knob" style={{'--v': v, '--hue': hue, '--sz': `${size}px`}} onMouseDown={onMouseDown} onDoubleClick={onDouble}/>
      {label && <div className="knob-label">{label}</div>}
      {valFmt && <div className="knob-val">{valFmt(v)}</div>}
    </div>
  );
}

/* ============================================================
   FADER — vertical drag
   ============================================================ */
function Fader({ v, onChange, hue, h = 160 }) {
  const ref = React.useRef(null);
  const onMouseDown = (e) => {
    e.preventDefault();
    const startY = e.clientY;
    const startV = v;
    const move = (ev) => {
      const dy = startY - ev.clientY;
      const next = Math.max(0, Math.min(1, startV + dy / h));
      onChange(next);
    };
    const up = () => {
      window.removeEventListener('mousemove', move);
      window.removeEventListener('mouseup', up);
    };
    window.addEventListener('mousemove', move);
    window.addEventListener('mouseup', up);
  };
  return (
    <div className="fader-slot" style={{'--v': v, '--h': `${h}px`}}>
      {[0.1, 0.25, 0.5, 0.75, 0.9].map(p => (
        <div key={p} className="fader-tick" style={{top: `${(1-p)*100}%`}}/>
      ))}
      <div ref={ref} className="fader-cap" style={{'--hue': hue, top: `${(1 - v) * (h - 22)}px`}} onMouseDown={onMouseDown}/>
    </div>
  );
}

function XFader({ v, onChange }) {
  const ref = React.useRef(null);
  const onMouseDown = (e) => {
    if (!ref.current) return;
    e.preventDefault();
    const rect = ref.current.getBoundingClientRect();
    const startX = e.clientX;
    const startV = v;
    const move = (ev) => {
      const dx = ev.clientX - startX;
      const next = Math.max(0, Math.min(1, startV + dx / rect.width));
      onChange(next);
    };
    const up = () => { window.removeEventListener('mousemove', move); window.removeEventListener('mouseup', up); };
    window.addEventListener('mousemove', move);
    window.addEventListener('mouseup', up);
  };
  return (
    <div className="xfader-slot" ref={ref} style={{'--v': v}}>
      <div className="xfader-cap" style={{left: `calc(${v * 100}% - 14px)`}} onMouseDown={onMouseDown}/>
    </div>
  );
}

/* ============================================================
   VU METER (12 segments, animated)
   ============================================================ */
function VU({ level, c = '#74e3a3' }) {
  const segs = 16;
  return (
    <div className="vu" style={{'--c': c}}>
      {Array.from({length: segs}, (_, i) => {
        const lit = level >= (i / segs);
        const isPeak = i >= 12;
        const segC = isPeak ? '#ff2a3c' : (i >= 9 ? '#ffd166' : c);
        return <div key={i} className={`vu-seg ${lit ? 'lit' : ''}`} style={{'--c': segC}}/>;
      })}
    </div>
  );
}

/* ============================================================
   DECK — waveform, jog, hot cues, transport
   ============================================================ */
function Deck({ side, deck, others, setDeck, onLoad }) {
  const { track, playing, position, pitch, eq, gain, filter, fxOn, hotCues } = deck;
  const c = side === 'A' ? 'var(--deck-a)' : 'var(--deck-b)';
  const hue = track ? track.hue : c;
  const wave = React.useMemo(() => track ? genWave(track, 200) : [], [track]);
  const [beatPhase, setBeatPhase] = React.useState(0);

  // play loop: advance position, trigger beat phase
  React.useEffect(() => {
    if (!playing || !track) return;
    let raf;
    let last = performance.now();
    const tick = (now) => {
      const dt = (now - last) / 1000;
      last = now;
      const eff = track.bpm * (1 + (pitch - 0.5) * 0.16); // ±8%
      const beatsPerSec = eff / 60;
      setDeck(d => ({ ...d, position: Math.min(1, d.position + (dt / track.dur)) }));
      const beats = (now / 1000) * beatsPerSec;
      setBeatPhase(Math.floor(beats) % 4);
      raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(raf);
  }, [playing, track, pitch]);

  if (!track) {
    return (
      <div className="panel" style={{padding: 24, minHeight: 460, display:'flex', flexDirection:'column', alignItems:'center', justifyContent:'center', gap: 12}}>
        <div className="panel-screws"><span className="screw tl"/><span className="screw tr"/><span className="screw bl"/><span className="screw br"/></div>
        <div className="mono" style={{fontSize: 9, color: 'var(--faint)', letterSpacing: '0.3em'}}>DECK {side} · EMPTY</div>
        <div className="serif italic" style={{fontSize: 36, color: 'var(--dim)', letterSpacing: '-0.02em'}}>load a track →</div>
        <div className="mono" style={{fontSize: 9, color: c, letterSpacing: '0.22em'}}>FROM DAJAI.IO/MUSIC</div>
      </div>
    );
  }

  const eff = track.bpm * (1 + (pitch - 0.5) * 0.16);
  const elapsed = position * track.dur;
  const remaining = track.dur - elapsed;
  const fmt = (s) => `${Math.floor(s/60)}:${String(Math.floor(s%60)).padStart(2,'0')}`;

  const togglePlay = () => setDeck(d => ({...d, playing: !d.playing}));
  const cuePoint = () => setDeck(d => ({...d, position: 0}));
  const sync = () => {
    // sync to other deck's effective BPM
    const o = others[0];
    if (!o.track) return;
    const oEff = o.track.bpm * (1 + (o.pitch - 0.5) * 0.16);
    const ratio = oEff / track.bpm;
    const newPitch = 0.5 + ((ratio - 1) / 0.16);
    setDeck(d => ({...d, pitch: Math.max(0, Math.min(1, newPitch)), synced: true}));
    setTimeout(() => setDeck(d => ({...d, synced: false})), 1200);
  };
  const triggerCue = (i) => {
    const cue = track.cues[i];
    if (cue !== undefined) setDeck(d => ({...d, position: cue}));
    setDeck(d => ({...d, hotCues: {...d.hotCues, [i]: Date.now()}}));
  };

  return (
    <div className="panel" style={{padding: 18, position: 'relative'}}>
      <div className="panel-screws"><span className="screw tl"/><span className="screw tr"/><span className="screw bl"/><span className="screw br"/></div>

      {/* TOP — track meta + BPM/KEY/TIME */}
      <div style={{display: 'grid', gridTemplateColumns: '64px 1fr auto', gap: 14, alignItems: 'flex-start'}}>
        <div className="art-tile" style={{'--art': `linear-gradient(135deg, ${track.hue}, color-mix(in oklch, ${track.hue} 30%, #000))`, width: 64, height: 64}}>
          <div className="num">{track.n}</div>
        </div>
        <div>
          <div className="mono" style={{fontSize: 9, color: c, letterSpacing: '0.22em'}}>● DECK {side} · LOADED</div>
          <div className="serif italic" style={{fontSize: 26, color: 'var(--ink)', letterSpacing: '-0.015em', lineHeight: 1, marginTop: 4}}>{track.t}</div>
          <div className="mono" style={{fontSize: 9, color: 'var(--dim)', letterSpacing: '0.14em', marginTop: 4}}>DAJAI.IO/MUSIC · {track.plays.toLocaleString()} PLAYS</div>
        </div>
        <div style={{textAlign: 'right'}}>
          <div className="bpm-display" style={{'--hue': hue}}>
            {Math.floor(eff)}<span className="dec">.{String(Math.floor((eff % 1) * 100)).padStart(2,'0')}</span>
          </div>
          <div className="mono" style={{fontSize: 9, color: 'var(--faint)', letterSpacing: '0.22em', marginTop: 2}}>BPM · KEY {track.key}</div>
        </div>
      </div>

      {/* WAVEFORM — full track overview */}
      <div style={{marginTop: 14}}>
        <div className="wave" style={{'--hue': hue}}>
          {wave.map((v, i) => {
            const isBass = i % 16 === 0;
            const isBeat = i % 4 === 0;
            const played = (i / wave.length) <= position;
            return (
              <div key={i}
                className={`wave-bar ${played ? 'played' : ''} ${isBass ? 'bass' : isBeat ? 'mid' : ''}`}
                style={{height: `${v * 100}%`, '--bar': hue}}/>
            );
          })}
          {track.cues.map((cp, i) => (
            <div key={i} className="wave-cue" data-num={i+1} style={{left: `${cp * 100}%`, '--c': SAMPLE_PADS[i % SAMPLE_PADS.length].c}}/>
          ))}
          <div className="wave-playhead" style={{left: `${position * 100}%`, '--hue': hue}}/>
        </div>
      </div>

      {/* WAVEFORM — zoomed scrolling */}
      <div style={{marginTop: 6}}>
        <div className="wave-zoom" style={{'--hue': hue}}>
          <div className="wave-zoom-grid" style={{'--beat': '32px'}}/>
          <div className="wave-zoom-track" style={{
            left: `calc(50% - ${position * wave.length * 4 * 3}px)`,
          }}>
            {wave.map((v, i) => {
              const isBass = i % 16 === 0;
              const isBeat = i % 4 === 0;
              return (
                <React.Fragment key={i}>
                  {/* draw 4 sub-bars per wave sample for zoom density */}
                  {[0,1,2,3].map(s => {
                    const sv = v * (0.7 + Math.sin(i * 13 + s * 7) * 0.15);
                    return <div key={s} className="wb" style={{height: `${Math.max(8, sv * 100)}%`, background: isBass && s === 0 ? hue : (isBeat && s === 0 ? `color-mix(in oklch, ${hue} 70%, #fff)` : `color-mix(in oklch, ${hue} 50%, #1a0d0e)`)}}/>;
                  })}
                </React.Fragment>
              );
            })}
          </div>
          <div className="wave-zoom-playhead" style={{'--hue': hue}}/>
        </div>
      </div>

      {/* JOG + TRANSPORT + PADS + TEMPO */}
      <div style={{display: 'grid', gridTemplateColumns: '220px 1fr 32px', gap: 18, marginTop: 16, alignItems: 'flex-start'}}>

        {/* JOG WHEEL */}
        <div style={{position: 'relative', aspectRatio: 1}}>
          <div className="jog" style={{'--hue': hue, height: '100%', position: 'relative'}}>
            <div className="jog-platter"/>
            <div className="jog-vinyl" style={{'--hue': hue, '--rpm': `${60 / (eff / 33.33)}s`, '--play': playing ? 'running' : 'paused', transform: `rotate(${position * 360 * 30}deg)`}}/>
            <div className="jog-spindle" style={{'--hue': hue}}>
              <div className="num">{side}</div>
              <div className="lbl">{track.n}</div>
            </div>
            <div className="jog-ring">
              {track.cues.map((cp, i) => (
                <div key={i} className="cue-marker"
                  style={{
                    '--c': SAMPLE_PADS[i % SAMPLE_PADS.length].c,
                    transform: `translate(-50%, -50%) rotate(${cp * 360}deg) translateY(calc(-50% - 90px))`,
                  }}/>
              ))}
            </div>
          </div>
          {/* time + position label */}
          <div style={{position: 'absolute', bottom: -6, left: 0, right: 0, textAlign: 'center'}}>
            <span className="lcd red" style={{fontSize: 22, padding: '4px 10px', display: 'inline-block'}}>
              -{fmt(remaining)}
            </span>
          </div>
        </div>

        {/* MIDDLE — pads + transport */}
        <div style={{display: 'flex', flexDirection: 'column', gap: 10}}>
          {/* HOT CUES */}
          <div>
            <div style={{display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', marginBottom: 4}}>
              <span className="mono" style={{fontSize: 8, color: 'var(--faint)', letterSpacing: '0.22em'}}>HOT CUES</span>
              <span className="mono" style={{fontSize: 8, color: 'var(--gold)', letterSpacing: '0.22em'}}>{track.cues.length} POINTS</span>
            </div>
            <div className="pads">
              {[0,1,2,3,4,5,6,7].map(i => {
                const cue = track.cues[i];
                const padC = SAMPLE_PADS[i % SAMPLE_PADS.length].c;
                const lit = hotCues && hotCues[i] && (Date.now() - hotCues[i] < 300);
                return (
                  <div key={i}
                    className={`pad ${lit ? 'lit' : ''} ${cue !== undefined ? 'armed' : ''}`}
                    style={{'--c': cue !== undefined ? padC : '#3a2a2d'}}
                    onClick={() => cue !== undefined && triggerCue(i)}>
                    <div className="pad-num">{i + 1}</div>
                    <div className="pad-name">{cue !== undefined ? `${(cue * track.dur).toFixed(0)}s` : '—'}</div>
                  </div>
                );
              })}
            </div>
          </div>

          {/* TRANSPORT */}
          <div style={{display: 'grid', gridTemplateColumns: 'repeat(5, 1fr)', gap: 6, marginTop: 6}}>
            <button className={`btn cue ${position === 0 ? 'active' : ''}`} onClick={cuePoint}>◯ CUE</button>
            <button className={`btn play ${playing ? 'active' : ''}`} onClick={togglePlay}>{playing ? '❚❚' : '▶'} PLAY</button>
            <button className={`btn sync ${deck.synced ? 'active' : ''}`} onClick={sync}>SYNC</button>
            <button className="btn" onClick={() => setDeck(d => ({...d, position: Math.max(0, d.position - 0.02)}))}>«</button>
            <button className="btn" onClick={() => setDeck(d => ({...d, position: Math.min(1, d.position + 0.02)}))}>»</button>
          </div>

          {/* LOOP CONTROL + BEAT PHASE */}
          <div style={{display: 'grid', gridTemplateColumns: '1fr auto 1fr', gap: 10, alignItems: 'center', marginTop: 4}}>
            <div style={{display: 'flex', gap: 4}}>
              {[1, 2, 4, 8].map(beats => (
                <button key={beats} className={`btn ${deck.loopLen === beats ? 'active' : ''}`} style={{flex: 1, fontSize: 8, padding: '6px 4px', '--c': '#74e3a3'}}
                  onClick={() => setDeck(d => ({...d, loopLen: d.loopLen === beats ? null : beats}))}>
                  {beats}/4
                </button>
              ))}
            </div>
            <div className="beat-phase" style={{'--hue': hue}}>
              {[0,1,2,3].map(i => (
                <div key={i} className={`bp-dot ${i === beatPhase ? 'lit' : ''} ${i === 0 ? 'one' : ''}`}/>
              ))}
            </div>
            <div className="lcd cobalt" style={{fontSize: 14, padding: '4px 8px', textAlign: 'center'}}>
              {fmt(elapsed)}
            </div>
          </div>
        </div>

        {/* TEMPO FADER */}
        <div style={{display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 6}}>
          <span className="mono" style={{fontSize: 8, color: 'var(--faint)', letterSpacing: '0.22em'}}>±8%</span>
          <div className="tempo" style={{'--v': pitch, '--hue': hue}} onMouseDown={(e) => {
            const rect = e.currentTarget.getBoundingClientRect();
            const startY = e.clientY;
            const startV = pitch;
            const move = (ev) => {
              const dy = startY - ev.clientY;
              setDeck(d => ({...d, pitch: Math.max(0, Math.min(1, startV + dy / 160))}));
            };
            const up = () => { window.removeEventListener('mousemove', move); window.removeEventListener('mouseup', up); };
            window.addEventListener('mousemove', move);
            window.addEventListener('mouseup', up);
          }}>
            <div className="center-dent"/>
            <div className="tempo-cap" style={{'--v': pitch, top: `${(1 - pitch) * (160 - 14)}px`, '--hue': hue}}/>
          </div>
          <span className="mono" style={{fontSize: 9, color: hue, letterSpacing: '0.04em', textShadow: `0 0 4px ${hue}`}}>
            {((pitch - 0.5) * 16).toFixed(1)}%
          </span>
        </div>
      </div>
    </div>
  );
}

/* ============================================================
   MIXER — center column with EQ knobs, faders, crossfader
   ============================================================ */
function Mixer({deckA, deckB, setA, setB, xfader, setXfader, masterGain, setMasterGain}) {
  const [vu, setVu] = React.useState({a: 0, b: 0, master: 0});
  React.useEffect(() => {
    let raf;
    const tick = () => {
      // pseudo-VU based on playing state + position
      const aLevel = deckA.playing ? Math.min(1, deckA.gain * (1 - xfader * 0.6) * (0.6 + Math.sin(performance.now() / 80) * 0.3 + (genWave(deckA.track || {n:'01'}, 64)[Math.floor(deckA.position * 64) % 64] || 0) * 0.4)) : 0;
      const bLevel = deckB.playing ? Math.min(1, deckB.gain * (xfader * 0.6 + 0.4) * (0.6 + Math.cos(performance.now() / 75) * 0.3 + (genWave(deckB.track || {n:'01'}, 64)[Math.floor(deckB.position * 64) % 64] || 0) * 0.4)) : 0;
      const m = Math.min(1, (aLevel + bLevel) * masterGain);
      setVu({a: aLevel, b: bLevel, master: m});
      raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(raf);
  }, [deckA.playing, deckB.playing, xfader, masterGain, deckA.gain, deckB.gain, deckA.position, deckB.position, deckA.track, deckB.track]);

  const ChannelStrip = ({deck, set, side, hue}) => (
    <div style={{display: 'flex', flexDirection: 'column', gap: 8, alignItems: 'center', flex: 1}}>
      <div className="mono" style={{fontSize: 9, color: hue, letterSpacing: '0.22em'}}>CH {side}</div>

      {/* TRIM */}
      <Knob v={deck.gain} onChange={(v) => set(d => ({...d, gain: v}))} hue={hue} size={36} label="TRIM" valFmt={v => `${(v * 12 - 6).toFixed(1)}dB`}/>

      {/* EQ HI / MID / LOW */}
      <Knob v={deck.eq.hi}  onChange={(v) => set(d => ({...d, eq: {...d.eq, hi: v}}))}  hue={hue} size={42} label="HI"  valFmt={v => `${((v - 0.5) * 24).toFixed(0)}`} center={0.5}/>
      <Knob v={deck.eq.mid} onChange={(v) => set(d => ({...d, eq: {...d.eq, mid: v}}))} hue={hue} size={42} label="MID" valFmt={v => `${((v - 0.5) * 24).toFixed(0)}`} center={0.5}/>
      <Knob v={deck.eq.low} onChange={(v) => set(d => ({...d, eq: {...d.eq, low: v}}))} hue={hue} size={42} label="LOW" valFmt={v => `${((v - 0.5) * 24).toFixed(0)}`} center={0.5}/>

      {/* FILTER */}
      <Knob v={deck.filter} onChange={(v) => set(d => ({...d, filter: v}))} hue={hue} size={42} label="FILTER" valFmt={v => v < 0.5 ? `LP${(v*100).toFixed(0)}` : `HP${((v-0.5)*200).toFixed(0)}`} center={0.5}/>

      {/* FX assigns */}
      <div style={{display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 4, width: '100%'}}>
        {FX_SLOTS.map((fx, i) => (
          <button key={i} className={`btn ${deck.fxOn[i] ? 'active' : ''}`}
            style={{padding: '4px 4px', fontSize: 7, '--c': fx.c}}
            onClick={() => set(d => { const f = [...d.fxOn]; f[i] = !f[i]; return {...d, fxOn: f}; })}>
            FX{i+1}
          </button>
        ))}
      </div>

      {/* CUE/PFL */}
      <button className={`btn ${deck.cueing ? 'active' : ''}`} style={{width: '100%', fontSize: 8, padding: '6px 4px', '--c': '#ffd166'}}
        onClick={() => set(d => ({...d, cueing: !d.cueing}))}>HEADPHONE</button>

      {/* CHANNEL FADER + VU */}
      <div style={{display: 'flex', gap: 6, marginTop: 8, alignItems: 'flex-end', height: 180}}>
        <VU level={side === 'A' ? vu.a : vu.b} c={hue}/>
        <Fader v={deck.fader} onChange={(v) => set(d => ({...d, fader: v}))} hue={hue} h={160}/>
      </div>
      <div className="mono" style={{fontSize: 9, color: hue, letterSpacing: '0.04em', textShadow: `0 0 4px ${hue}`, fontVariantNumeric: 'tabular-nums'}}>
        {(deck.fader * 100).toFixed(0)}
      </div>
    </div>
  );

  return (
    <div className="panel" style={{padding: 16, position: 'relative', display: 'flex', flexDirection: 'column'}}>
      <div className="panel-screws"><span className="screw tl"/><span className="screw tr"/><span className="screw bl"/><span className="screw br"/></div>

      {/* MASTER STRIP TOP */}
      <div style={{display: 'flex', justifyContent: 'space-between', alignItems: 'center', borderBottom: '1px solid var(--line)', paddingBottom: 10, marginBottom: 12}}>
        <div className="mono" style={{fontSize: 9, color: 'var(--gold)', letterSpacing: '0.22em'}}>// MIXER · 02 CH</div>
        <div style={{display: 'flex', gap: 6}}>
          <span className="led" style={{'--c': '#74e3a3'}}/>
          <span className="led" style={{'--c': '#ffd166'}}/>
          <span className="led" style={{'--c': '#ff2a3c'}}/>
        </div>
      </div>

      {/* CHANNEL STRIPS A | B */}
      <div style={{display: 'flex', gap: 18, flex: 1}}>
        <ChannelStrip deck={deckA} set={setA} side="A" hue="var(--deck-a)"/>

        {/* MASTER (center column) */}
        <div style={{display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 8, padding: '0 4px', borderLeft: '1px solid var(--line)', borderRight: '1px solid var(--line)'}}>
          <div className="mono" style={{fontSize: 9, color: 'var(--gold)', letterSpacing: '0.22em'}}>MASTER</div>
          <Knob v={masterGain} onChange={setMasterGain} hue="var(--gold)" size={48} label="GAIN" valFmt={v => `${(v * 100).toFixed(0)}`} center={0.7}/>

          <div className="lcd amber" style={{fontSize: 26, padding: '6px 12px', textAlign: 'center', minWidth: 80}}>
            {Math.floor(deckA.track ? deckA.track.bpm * (1 + (deckA.pitch - 0.5) * 0.16) : 120)}
          </div>
          <span className="mono" style={{fontSize: 8, color: 'var(--gold)', letterSpacing: '0.22em'}}>MASTER BPM</span>

          <div style={{display: 'flex', gap: 6, marginTop: 8, height: 180, alignItems: 'flex-end'}}>
            <VU level={vu.master} c="#ffd166"/>
            <VU level={vu.master * 0.94} c="#ffd166"/>
          </div>

          <div className="mono" style={{fontSize: 8, color: 'var(--faint)', letterSpacing: '0.22em', marginTop: 6}}>L · R</div>
        </div>

        <ChannelStrip deck={deckB} set={setB} side="B" hue="var(--deck-b)"/>
      </div>

      {/* CROSSFADER */}
      <div style={{marginTop: 14, padding: '12px 4px 0', borderTop: '1px solid var(--line)'}}>
        <div style={{display: 'flex', justifyContent: 'space-between', marginBottom: 8}}>
          <span className="mono" style={{fontSize: 9, color: 'var(--deck-a)', letterSpacing: '0.22em'}}>◀ A</span>
          <span className="mono" style={{fontSize: 9, color: 'var(--gold)', letterSpacing: '0.22em'}}>CROSSFADER</span>
          <span className="mono" style={{fontSize: 9, color: 'var(--deck-b)', letterSpacing: '0.22em'}}>B ▶</span>
        </div>
        <XFader v={xfader} onChange={setXfader}/>
        <div style={{display: 'flex', gap: 4, marginTop: 8, justifyContent: 'space-between'}}>
          <button className="btn" style={{fontSize: 7, padding: '4px 6px', flex: 1}} onClick={() => setXfader(0)}>HARD ◀</button>
          <button className="btn" style={{fontSize: 7, padding: '4px 6px', flex: 1}} onClick={() => setXfader(0.5)}>CENTER</button>
          <button className="btn" style={{fontSize: 7, padding: '4px 6px', flex: 1}} onClick={() => setXfader(1)}>HARD ▶</button>
        </div>
      </div>
    </div>
  );
}

/* ============================================================
   FX RACK — 4 effect units
   ============================================================ */
function FXRack({deckA, deckB, fxParams, setFxParams}) {
  return (
    <div className="panel" style={{padding: 14, position: 'relative'}}>
      <div className="panel-screws"><span className="screw tl"/><span className="screw tr"/><span className="screw bl"/><span className="screw br"/></div>
      <div style={{display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', marginBottom: 12}}>
        <div className="mono" style={{fontSize: 9, color: 'var(--gold)', letterSpacing: '0.22em'}}>// FX RACK · 04 UNITS · TEMPO-SYNCED</div>
        <div className="mono" style={{fontSize: 9, color: 'var(--dim)', letterSpacing: '0.22em'}}>SEND-RETURN · BEATS LOCKED</div>
      </div>
      <div style={{display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 10}}>
        {FX_SLOTS.map((fx, i) => {
          const p = fxParams[i];
          const aOn = deckA.fxOn[i];
          const bOn = deckB.fxOn[i];
          const isOn = aOn || bOn;
          return (
            <div key={i} className={`fx-slot ${isOn ? 'on' : ''}`} style={{'--c': fx.c}}>
              <div style={{display: 'flex', flexDirection: 'column', gap: 4}}>
                <div className="serif italic" style={{fontSize: 22, color: fx.c, lineHeight: 1, letterSpacing: '-0.01em', textShadow: `0 0 4px ${fx.c}`}}>{fx.name}</div>
                <div className="mono" style={{fontSize: 8, color: 'var(--faint)', letterSpacing: '0.16em'}}>{fx.sub}</div>
                <div style={{display: 'flex', gap: 4, marginTop: 4}}>
                  <span className={`led ${aOn ? '' : 'off'}`} style={{'--c': 'var(--deck-a)'}}/>
                  <span className={`led ${bOn ? '' : 'off'}`} style={{'--c': 'var(--deck-b)'}}/>
                </div>
              </div>
              <div style={{display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 8, alignItems: 'center'}}>
                <Knob v={p.dryWet} onChange={(v) => setFxParams(prev => prev.map((x, j) => j === i ? {...x, dryWet: v} : x))} hue={fx.c} size={40} label="DRY/WET" valFmt={v => `${(v*100).toFixed(0)}%`}/>
                <Knob v={p.amount} onChange={(v) => setFxParams(prev => prev.map((x, j) => j === i ? {...x, amount: v} : x))} hue={fx.c} size={40} label="AMOUNT" valFmt={v => `${(v*100).toFixed(0)}`}/>
              </div>
            </div>
          );
        })}
      </div>
    </div>
  );
}

/* ============================================================
   PERFORMANCE PADS — 8 sample pads
   ============================================================ */
function PerfPads() {
  const [lit, setLit] = React.useState({});
  const trigger = (i) => {
    setLit(p => ({...p, [i]: Date.now()}));
    setTimeout(() => setLit(p => { const x = {...p}; delete x[i]; return x; }), 350);
  };
  return (
    <div className="panel" style={{padding: 14, position: 'relative'}}>
      <div className="panel-screws"><span className="screw tl"/><span className="screw tr"/><span className="screw bl"/><span className="screw br"/></div>
      <div style={{display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', marginBottom: 10}}>
        <div className="mono" style={{fontSize: 9, color: 'var(--gold)', letterSpacing: '0.22em'}}>// SAMPLER · 08 PADS · YELLOW = ARMED</div>
        <div style={{display: 'flex', gap: 6}}>
          <button className="btn active" style={{fontSize: 8, padding: '4px 8px', '--c': '#ffd166'}}>SAMPLES</button>
          <button className="btn" style={{fontSize: 8, padding: '4px 8px'}}>HOT CUE</button>
          <button className="btn" style={{fontSize: 8, padding: '4px 8px'}}>LOOP ROLL</button>
          <button className="btn" style={{fontSize: 8, padding: '4px 8px'}}>SLICER</button>
        </div>
      </div>
      <div className="pads eight">
        {SAMPLE_PADS.map((p, i) => (
          <div key={i} className={`pad armed ${lit[i] ? 'lit' : ''}`} style={{'--c': p.c}} onClick={() => trigger(i)}>
            <div className="pad-num">{i+1}</div>
            <div className="pad-name">{p.name}</div>
          </div>
        ))}
      </div>
    </div>
  );
}

/* ============================================================
   TRACK BROWSER
   ============================================================ */
function Browser({deckA, deckB, loadIntoDeck}) {
  const [search, setSearch] = React.useState('');
  const [crate, setCrate] = React.useState('ALL');
  const filtered = DAJAI_CATALOG.filter(t => {
    if (search && !t.t.includes(search.toLowerCase()) && !t.sub.includes(search.toLowerCase())) return false;
    if (crate === 'NEW' && parseInt(t.n) > 4) return false;
    if (crate === 'A-SIDES' && parseInt(t.n) % 2 === 0) return false;
    if (crate === 'PEAK' && t.bpm < 100) return false;
    if (crate === 'LATE' && t.bpm > 90) return false;
    if (crate === 'KARAOKE' && parseInt(t.n) % 3 !== 0) return false;
    return true;
  });
  return (
    <div className="panel" style={{padding: 14, position: 'relative'}}>
      <div className="panel-screws"><span className="screw tl"/><span className="screw tr"/><span className="screw bl"/><span className="screw br"/></div>
      {/* HEAD */}
      <div style={{display: 'grid', gridTemplateColumns: '1fr auto', gap: 12, alignItems: 'center', marginBottom: 12, paddingBottom: 12, borderBottom: '1px solid var(--line)'}}>
        <div>
          <div className="mono" style={{fontSize: 9, color: 'var(--gold)', letterSpacing: '0.22em'}}>// TRACK BROWSER · DAJAI.IO/MUSIC · 12 TRACKS</div>
          <div className="serif italic" style={{fontSize: 22, color: 'var(--ink)', lineHeight: 1, letterSpacing: '-0.02em', marginTop: 4}}>the catalog. only thing that gets played.</div>
        </div>
        <a href="https://dajai.io/music" className="mono" style={{fontSize: 10, color: 'var(--gold)', letterSpacing: '0.22em', borderBottom: '1px solid var(--gold)'}}>↗ DAJAI.IO/MUSIC</a>
      </div>

      {/* SEARCH + CRATES */}
      <div style={{display: 'grid', gridTemplateColumns: '300px 1fr', gap: 12, marginBottom: 10}}>
        <input className="search-input" placeholder="search by title, mood, key..." value={search} onChange={e => setSearch(e.target.value)}/>
        <div style={{display: 'flex', gap: 6, flexWrap: 'wrap'}}>
          {CRATES.map(c => (
            <button key={c} className={`crate-chip ${crate === c ? 'active' : ''}`} onClick={() => setCrate(c)}>{c}</button>
          ))}
        </div>
      </div>

      {/* HEAD ROW */}
      <div style={{background: 'rgba(0,0,0,0.4)', border: '1px solid var(--line)', borderBottom: 'none'}}>
        <div className="track-row head">
          <div>#</div>
          <div></div>
          <div>TITLE</div>
          <div>BPM</div>
          <div>KEY</div>
          <div>DUR</div>
          <div>PLAYS</div>
          <div>24H Δ</div>
          <div>HEAT</div>
          <div>LOAD</div>
        </div>
      </div>

      <div style={{maxHeight: 380, overflowY: 'auto', border: '1px solid var(--line)'}}>
        {filtered.map((c, i) => {
          const inA = deckA.track && deckA.track.n === c.n;
          const inB = deckB.track && deckB.track.n === c.n;
          const heatBars = Array.from({length: 14}, (_, j) => {
            const seed = (parseInt(c.n, 10) * 31 + j * 7) % 100;
            return 4 + (seed / 100) * 18;
          });
          return (
            <div key={c.n} className={`track-row ${inA ? 'in-deck-a' : ''} ${inB ? 'in-deck-b' : ''}`}>
              <div className="tr-num">{c.n}</div>
              <div className="art-tile" style={{'--art': `linear-gradient(135deg, ${c.hue}, color-mix(in oklch, ${c.hue} 30%, #000))`, width: 28, height: 28}}>
                <div className="num" style={{fontSize: 10, top: 1, left: 3}}>{c.n}</div>
              </div>
              <div>
                <div className="tr-name">{c.t}</div>
                <div className="tr-sub">{c.sub}</div>
              </div>
              <div className="tr-num-val">{c.bpm}</div>
              <div className="tr-num-val" style={{color: c.hue}}>{c.key}</div>
              <div className="tr-num-val">{Math.floor(c.dur/60)}:{String(c.dur%60).padStart(2,'0')}</div>
              <div className="tr-num-val">{c.plays.toLocaleString()}</div>
              <div className={c.delta > 0 ? 'tr-up' : c.delta < 0 ? 'tr-dn' : 'tr-num-val'}>
                {c.delta > 0 ? '▲' : c.delta < 0 ? '▼' : '◆'} {Math.abs(c.delta).toFixed(1)}
              </div>
              <div style={{display: 'flex', alignItems: 'end', gap: 1, height: 22}}>
                {heatBars.map((h, j) => <span key={j} style={{width: 3, height: h, background: c.hue, opacity: 0.4 + (h / 30)}}/>)}
              </div>
              <div style={{display: 'flex', gap: 4}}>
                <button className="tr-load a" onClick={() => loadIntoDeck('A', c)}>A</button>
                <button className="tr-load b" onClick={() => loadIntoDeck('B', c)}>B</button>
              </div>
            </div>
          );
        })}
      </div>
    </div>
  );
}

/* ============================================================
   FAN REQUEST QUEUE + LIVE CHAT
   ============================================================ */
function FanPanel({onAcceptToDeck}) {
  const [requests, setRequests] = React.useState(() => {
    const seed = [
      { fan: '@nyx_lv',     city: 'LV',    track: DAJAI_CATALOG[2], at: Date.now() - 18000 },
      { fan: '@boomtown',   city: 'NYC',   track: DAJAI_CATALOG[3], at: Date.now() - 42000 },
      { fan: '@miss_89102', city: 'PHX',   track: DAJAI_CATALOG[6], at: Date.now() - 67000 },
      { fan: '@cobaltghost',city: 'TYO',   track: DAJAI_CATALOG[9], at: Date.now() - 91000 },
      { fan: '@latetapes',  city: 'LDN',   track: DAJAI_CATALOG[1], at: Date.now() - 124000 },
    ];
    return seed;
  });
  const [chat, setChat] = React.useState([]);

  // generate periodic chat
  React.useEffect(() => {
    let i = 0;
    const id = setInterval(() => {
      const m = FAN_MESSAGES[Math.floor(Math.random() * FAN_MESSAGES.length)];
      const handle = FAN_HANDLES[Math.floor(Math.random() * FAN_HANDLES.length)];
      const c = ['#ffd166', '#6fc8ff', '#ff4d8a', '#74e3a3', '#ff6b2c'][Math.floor(Math.random() * 5)];
      setChat(prev => [{...m, handle, c, at: Date.now(), key: ++i + Date.now()}, ...prev].slice(0, 14));
    }, 1900);
    return () => clearInterval(id);
  }, []);

  // periodic new requests
  React.useEffect(() => {
    const id = setInterval(() => {
      const handle = FAN_HANDLES[Math.floor(Math.random() * FAN_HANDLES.length)];
      const cities = ['LV', 'NYC', 'PHX', 'TYO', 'LDN', 'BLR', 'CDMX', 'SF', 'BLN'];
      const track = DAJAI_CATALOG[Math.floor(Math.random() * DAJAI_CATALOG.length)];
      setRequests(prev => [{fan: handle, city: cities[Math.floor(Math.random() * cities.length)], track, at: Date.now()}, ...prev].slice(0, 8));
    }, 14000);
    return () => clearInterval(id);
  }, []);

  const accept = (req, side) => {
    onAcceptToDeck(side, req.track);
    setRequests(prev => prev.filter(r => r !== req));
  };
  const skip = (req) => setRequests(prev => prev.filter(r => r !== req));

  const ago = (ts) => {
    const s = Math.floor((Date.now() - ts) / 1000);
    if (s < 60) return `${s}s`;
    return `${Math.floor(s / 60)}m`;
  };

  return (
    <div style={{display: 'flex', flexDirection: 'column', gap: 16, height: '100%'}}>
      {/* REQUESTS */}
      <div className="panel" style={{padding: 14, position: 'relative', flex: 1}}>
        <div className="panel-screws"><span className="screw tl"/><span className="screw tr"/></div>
        <div style={{display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', marginBottom: 10, paddingBottom: 8, borderBottom: '1px solid var(--line)'}}>
          <div className="mono" style={{fontSize: 9, color: 'var(--pink)', letterSpacing: '0.22em'}}>// FAN REQUESTS · LIVE QUEUE</div>
          <div className="mono" style={{fontSize: 9, color: 'var(--gold)', letterSpacing: '0.22em'}}>{requests.length} WAITING</div>
        </div>
        <div style={{maxHeight: 280, overflowY: 'auto'}}>
          {requests.map((r, i) => (
            <div key={i} className="req-item">
              <div className="req-art" style={{'--art': `linear-gradient(135deg, ${r.track.hue}, color-mix(in oklch, ${r.track.hue} 30%, #000))`}}/>
              <div>
                <div className="req-name">{r.track.t}</div>
                <div className="req-fan">{r.fan} · {r.city} · {ago(r.at)} ago</div>
              </div>
              <button className="req-btn" style={{'--c': 'var(--deck-a)'}} onClick={() => accept(r, 'A')}>→ A</button>
              <button className="req-btn" style={{'--c': 'var(--deck-b)'}} onClick={() => accept(r, 'B')}>→ B</button>
            </div>
          ))}
        </div>
      </div>

      {/* CHAT */}
      <div className="panel" style={{padding: 14, position: 'relative', flex: 1, display: 'flex', flexDirection: 'column'}}>
        <div className="panel-screws"><span className="screw bl"/><span className="screw br"/></div>
        <div style={{display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', marginBottom: 8, paddingBottom: 8, borderBottom: '1px solid var(--line)'}}>
          <div className="mono" style={{fontSize: 9, color: 'var(--plasma-2)', letterSpacing: '0.22em'}}>// LIVE CHAT · 14,820 IN ROOM</div>
          <span className="led" style={{'--c': '#74e3a3'}}/>
        </div>
        <div style={{flex: 1, overflowY: 'auto', maxHeight: 280}}>
          {chat.map((m) => (
            <div key={m.key} className="chat-msg">
              <span className="chat-handle" style={{'--c': m.c, color: m.c}}>{m.handle}</span>
              <span className={`chat-text ${m.heart ? 'heart' : ''}`}>{m.t}</span>
            </div>
          ))}
        </div>
      </div>
    </div>
  );
}

/* ============================================================
   APP
   ============================================================ */
function makeDeck() {
  return {
    track: null,
    playing: false,
    position: 0,
    pitch: 0.5, // -8% to +8% — 0.5 = 0%
    fader: 0.85,
    gain: 0.7,
    eq: { hi: 0.5, mid: 0.5, low: 0.5 },
    filter: 0.5,
    fxOn: [false, false, false, false],
    cueing: false,
    loopLen: null,
    hotCues: {},
    synced: false,
  };
}

function App() {
  const [deckA, setDeckA] = React.useState(() => ({...makeDeck(), track: DAJAI_CATALOG[0], position: 0.32, playing: true}));
  const [deckB, setDeckB] = React.useState(() => ({...makeDeck(), track: DAJAI_CATALOG[3], position: 0.18}));
  const [xfader, setXfader] = React.useState(0.32);
  const [masterGain, setMasterGain] = React.useState(0.78);
  const [fxParams, setFxParams] = React.useState(FX_SLOTS.map(() => ({dryWet: 0.4, amount: 0.5})));
  const [recording, setRecording] = React.useState(true);
  const [broadcasting, setBroadcasting] = React.useState(true);
  const [hearts, setHearts] = React.useState([]);

  const setA = (fn) => setDeckA(prev => typeof fn === 'function' ? fn(prev) : fn);
  const setB = (fn) => setDeckB(prev => typeof fn === 'function' ? fn(prev) : fn);
  const loadIntoDeck = (side, track) => {
    if (side === 'A') setA(d => ({...d, track, position: 0, playing: false}));
    else setB(d => ({...d, track, position: 0, playing: false}));
  };

  // hearts streaming
  React.useEffect(() => {
    let i = 0;
    const id = setInterval(() => {
      i++;
      setHearts(h => [...h, {key: i, x: Math.random() * 100, delay: Math.random() * 0.4}].slice(-30));
      setTimeout(() => setHearts(h => h.filter(x => x.key !== i)), 2400);
    }, 380);
    return () => clearInterval(id);
  }, []);

  const [now, setNow] = React.useState(() => new Date());
  React.useEffect(() => {
    const id = setInterval(() => setNow(new Date()), 1000);
    return () => clearInterval(id);
  }, []);

  const masterBpm = deckA.track ? deckA.track.bpm * (1 + (deckA.pitch - 0.5) * 0.16) : 0;

  return (
    <div className="dj-body" style={{minHeight: '100vh'}}>
      {/* TOPBAR */}
      <div style={{display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '14px 24px', borderBottom: '1px solid var(--line)', background: 'rgba(5,3,4,0.92)', backdropFilter: 'blur(12px)', position: 'sticky', top: 0, zIndex: 50}}>
        <div style={{display: 'flex', alignItems: 'center', gap: 18}}>
          <a href="DAJAI.IO Music Page.html" className="serif italic" style={{fontSize: 22, color: 'var(--ink)', letterSpacing: '-0.02em'}}>← dajai.io</a>
          <span style={{height: 18, width: 1, background: 'var(--line)'}}/>
          <span className="mono" style={{fontSize: 10, color: 'var(--gold)', letterSpacing: '0.22em'}}>// DJ DECK · STUDIO 04 · LV-89102</span>
        </div>
        <div style={{display: 'flex', alignItems: 'center', gap: 14}}>
          <span className="lcd cobalt" style={{fontSize: 18, padding: '4px 10px'}}>{now.toTimeString().slice(0, 8)}</span>
          {recording && <span className="on-air-badge">REC</span>}
          {broadcasting && <span className="on-air-badge" style={{background: '#ffd166', color: '#000', boxShadow: '0 0 14px #ffd166'}}>● BROADCAST</span>}
          <a href="DAJAI TV.html" className="mono" style={{fontSize: 10, color: 'var(--cobalt)', letterSpacing: '0.22em'}}>→ DAJAI TV</a>
          <a href="STUDIO LIVE.html" className="mono" style={{fontSize: 10, color: 'var(--neon-orange)', letterSpacing: '0.22em'}}>→ STUDIO LIVE</a>
        </div>
      </div>

      {/* MASTER STRIP */}
      <div style={{display: 'grid', gridTemplateColumns: '1fr auto auto auto auto auto auto auto auto 1fr', gap: 18, alignItems: 'center', padding: '14px 24px', background: 'linear-gradient(180deg, #1a0d0e 0%, #0a0506 100%)', borderBottom: '1px solid #2a1518'}}>
        <div>
          <div className="mono" style={{fontSize: 9, color: 'var(--gold)', letterSpacing: '0.3em'}}>// SESSION 0148 · 06H 14M LIVE</div>
          <div className="serif italic" style={{fontSize: 36, color: 'var(--ink)', lineHeight: 0.9, letterSpacing: '-0.025em', marginTop: 2}}>
            she's playing for you <span style={{color: 'var(--gold)'}}>tonight.</span>
          </div>
        </div>
        <div style={{textAlign: 'center'}}>
          <div className="lcd amber" style={{fontSize: 32, padding: '6px 14px'}}>{Math.floor(masterBpm)}</div>
          <div className="mono" style={{fontSize: 8, color: 'var(--gold)', letterSpacing: '0.22em', marginTop: 4}}>MASTER BPM</div>
        </div>
        <div style={{textAlign: 'center'}}>
          <div className="lcd cobalt" style={{fontSize: 32, padding: '6px 14px', minWidth: 60}}>{deckA.track ? deckA.track.key : '—'}</div>
          <div className="mono" style={{fontSize: 8, color: 'var(--cobalt)', letterSpacing: '0.22em', marginTop: 4}}>KEY</div>
        </div>
        <div style={{textAlign: 'center'}}>
          <div className="lcd red" style={{fontSize: 32, padding: '6px 14px'}}>14,820</div>
          <div className="mono" style={{fontSize: 8, color: '#ff2a3c', letterSpacing: '0.22em', marginTop: 4}}>FANS</div>
        </div>
        <div style={{textAlign: 'center'}}>
          <div className="lcd pink" style={{fontSize: 32, padding: '6px 14px'}}>2,041</div>
          <div className="mono" style={{fontSize: 8, color: 'var(--pink)', letterSpacing: '0.22em', marginTop: 4}}>HEARTS / MIN</div>
        </div>
        <button className={`btn ${recording ? 'active' : ''}`} style={{'--c': '#ff2a3c', padding: '10px 14px'}} onClick={() => setRecording(r => !r)}>● REC</button>
        <button className={`btn ${broadcasting ? 'active' : ''}`} style={{'--c': '#ffd166', padding: '10px 14px'}} onClick={() => setBroadcasting(b => !b)}>📡 LIVE</button>
        <button className="btn" style={{'--c': '#74e3a3', padding: '10px 14px'}}>SAVE SET</button>
        <div style={{position: 'relative', height: 60, width: 80}}>
          <div className="heart-stream">
            {hearts.map(h => (
              <span key={h.key} className="heart" style={{left: `${h.x}%`, animationDelay: `${h.delay}s`}}>♥</span>
            ))}
          </div>
        </div>
      </div>

      {/* DECKS + MIXER */}
      <div style={{padding: 18, display: 'grid', gridTemplateColumns: '1fr 360px 1fr', gap: 18}}>
        <Deck side="A" deck={deckA} others={[deckB]} setDeck={setA} onLoad={(t) => loadIntoDeck('A', t)}/>
        <Mixer deckA={deckA} deckB={deckB} setA={setA} setB={setB} xfader={xfader} setXfader={setXfader} masterGain={masterGain} setMasterGain={setMasterGain}/>
        <Deck side="B" deck={deckB} others={[deckA]} setDeck={setB} onLoad={(t) => loadIntoDeck('B', t)}/>
      </div>

      {/* FX RACK + PADS */}
      <div style={{padding: '0 18px 18px', display: 'grid', gridTemplateColumns: '1.6fr 1fr', gap: 18}}>
        <FXRack deckA={deckA} deckB={deckB} fxParams={fxParams} setFxParams={setFxParams}/>
        <PerfPads/>
      </div>

      {/* BROWSER + FAN PANEL */}
      <div style={{padding: '0 18px 18px', display: 'grid', gridTemplateColumns: '1.6fr 1fr', gap: 18}}>
        <Browser deckA={deckA} deckB={deckB} loadIntoDeck={loadIntoDeck}/>
        <FanPanel onAcceptToDeck={loadIntoDeck}/>
      </div>

      {/* FOOTER LINE */}
      <div style={{padding: '32px 24px 60px', textAlign: 'center', borderTop: '1px solid var(--line)', marginTop: 18}}>
        <div className="mono" style={{fontSize: 9, color: 'var(--faint)', letterSpacing: '0.3em'}}>
          DJ DECK · DAJAI.IO · STUDIO 04 · LV-89102 · SESSION 0148 · NEVER OFF
        </div>
        <div className="serif italic" style={{fontSize: 18, color: 'var(--dim)', marginTop: 6, letterSpacing: '-0.01em'}}>
          the fans came. she pressed play. the room is open until <span style={{color: 'var(--gold)'}}>06:00 AM.</span>
        </div>
      </div>
    </div>
  );
}

ReactDOM.createRoot(document.getElementById('root')).render(<App/>);
