import { useRef, useEffect, useState, useCallback } from 'react' const STYLE = { empty: { border: 'border-border', bg: 'bg-surface', icon: '●', iconColor: 'text-gray-700' }, recording: { border: 'border-red-500', bg: 'bg-red-950/20', icon: '⏺', iconColor: 'text-red-400' }, trimming: { border: 'border-amber-400', bg: 'bg-amber-950/20', icon: '✂', iconColor: 'text-amber-400'}, playing: { border: 'border-accent', bg: 'bg-accent/10', icon: '▶', iconColor: 'text-accent' }, muted: { border: 'border-border', bg: 'bg-surface', icon: '⏸', iconColor: 'text-gray-500' }, } const LABEL = { empty: 'tap to rec', recording: 'tap to stop', trimming: 'trimming…', playing: 'tap to mute', muted: 'tap to play', } export default function LoopSlot({ slot, slotIdx, audioCtxRef, masterStartRef, masterLenRef, onClick, onRetrim, onDelete, onVolumeChange }) { const progressRef = useRef(null) const rafRef = useRef(null) const [showVol, setShowVol] = useState(false) const [holdTimer, setHoldTimer] = useState(null) const [deleting, setDeleting] = useState(false) const { status, recordingDuration, volume, originalBuffer } = slot const style = STYLE[status] ?? STYLE.empty const isActive = status === 'playing' || status === 'muted' // ── Progress bar via rAF ───────────────────────────────────────────────── useEffect(() => { if (!isActive) { if (progressRef.current) progressRef.current.style.width = '0%' return } function tick() { const ctx = audioCtxRef.current const mStart = masterStartRef.current const mLen = masterLenRef.current if (ctx && mStart !== null && mLen && progressRef.current) { const pos = ((ctx.currentTime - mStart) % mLen) / mLen * 100 progressRef.current.style.width = `${pos}%` } rafRef.current = requestAnimationFrame(tick) } rafRef.current = requestAnimationFrame(tick) return () => { if (rafRef.current) cancelAnimationFrame(rafRef.current) } }, [isActive, audioCtxRef, masterStartRef, masterLenRef]) // ── Long-press to delete ────────────────────────────────────────────────── const onPointerDown = useCallback((e) => { e.preventDefault() const t = setTimeout(() => setDeleting(true), 500) setHoldTimer(t) }, []) const onPointerUp = useCallback(() => { if (holdTimer) { clearTimeout(holdTimer); setHoldTimer(null) } if (!deleting) onClick(slotIdx) }, [holdTimer, deleting, onClick, slotIdx]) const onPointerLeave = useCallback(() => { if (holdTimer) { clearTimeout(holdTimer); setHoldTimer(null) } }, [holdTimer]) const confirmDelete = useCallback(() => { setDeleting(false) onDelete(slotIdx) }, [onDelete, slotIdx]) return (