// TapeDeck.jsx — v2: the deck is now a calm "Now playing" strip. // Same API as the old hi-fi deck (section, onEject, swapState, counter) so // app.jsx is untouched. The reading progress bar tracks real page scroll — // the tape position is how far through the side you've read. function SpinningReel({ color, playing }) { // small line-drawn tape glyph: two reels + a connecting window const reel = { width: 16, height: 16, borderRadius: '50%', border: '1.5px dashed var(--c-smoke)', animation: playing ? 'reel-spin 5s linear infinite' : 'none', flexShrink: 0, }; return (
); } function TapeDeck({ section, onEject, swapState, counter }) { const isEmpty = !section; const isPlaying = swapState === 'idle' && !!section; const [prog, setProg] = React.useState(0); const [hoverEject, setHoverEject] = React.useState(false); React.useEffect(() => { const onScroll = () => { const h = document.documentElement; const max = h.scrollHeight - window.innerHeight; setProg(max > 60 ? Math.min(1, Math.max(0, window.scrollY / max)) : 0); }; onScroll(); window.addEventListener('scroll', onScroll, { passive: true }); window.addEventListener('resize', onScroll); // content height settles after swap const t = setTimeout(onScroll, 700); return () => { window.removeEventListener('scroll', onScroll); window.removeEventListener('resize', onScroll); clearTimeout(t); }; }, [section && section.id, swapState]); const contentAnim = swapState === 'ejecting' ? 'tape-eject 420ms var(--ease-snap) forwards' : swapState === 'loading' ? 'tape-load 480ms var(--ease-out) both' : 'none'; return (
{isEmpty ? (
Nothing playing. Pick a side below.
) : ( <>
{swapState === 'loading' ? 'Loading' : swapState === 'ejecting' ? 'Ejecting' : 'Now playing'}
Side {section.side} · {section.title}
{/* reading progress = tape position */}
{Math.round(prog * 100)}%
)}
{!isEmpty && ( )}
); } window.TapeDeck = TapeDeck;