// EsploraBeat — Beat catalog tab // Owns: BeatFilterRail, BeatGrid, BeatCard, Waveform, StickyPlayer, BuyModal // Sort options + active filter pills are owned by parent (Esplora.jsx); beat catalog // just renders its rail + grid + active player. const BeatIcons = window.SonaraIcons; const { useState: useStateBeat, useMemo: useMemoBeat } = React; // Reusable Geo filter for the rail (free-text location + quick chips + "near me" radius) function BeatGeoFilter() { const [mode, setMode] = useStateBeat("any"); // "any" | "near" | "country" | "region" | "custom" const [query, setQuery] = useStateBeat(""); const [radius, setRadius] = useStateBeat(50); const chips = [ { id: "any", label: "Ovunque" }, { id: "near", label: "Vicino a me", icon: true }, { id: "country", label: "Italia" }, ]; return (
{ setQuery(e.target.value); setMode("custom"); }} /> {query && ( )}
{chips.map(c => ( ))}
{mode === "near" && (
Raggio {radius} km
setRadius(Number(e.target.value))} />
)}
); } // ---------- mock data ---------- const BEAT_GENRES = ["Trap", "Drill", "Hip-Hop", "R&B", "Afro", "Pop", "House", "Reggaeton", "UK Garage"]; const BEAT_MOODS = ["Dark", "Hype", "Chill", "Emo", "Aggressive", "Romantic", "Dreamy", "Bouncy"]; const BEAT_KEYS = ["A min", "A maj", "B min", "C# min", "D min", "E min", "F maj", "F# min", "G min", "G# min"]; const BEAT_PRODS = ["Yoshi", "Marco V.", "DK Beats", "Riva", "Nemo", "Kyo", "Astra", "Frey", "Lume", "Tide"]; function beatRand(seed) { let s = seed; return () => (s = (s * 9301 + 49297) % 233280) / 233280; } function makeBeats() { const r = beatRand(7); const titles = [ "Notte Lunga","Asfalto","Skyline","Vetro Rotto","Mare Nero","Polvere d'Oro","Riflesso","Ferro Caldo", "Anima Lenta","Frequenza 88","Capo Bianco","Vento Sud","Eco","Tempesta","Velluto","Magma","Onda Lunga", "Carbone","Vela","Solstizio","Treno Notturno","Luce Bassa","Pioggia 4am","Nebbia Soft" ]; return titles.map((title, i) => { const bpm = 80 + Math.floor(r() * 80); const exclusive = r() > 0.55; const lease = [29, 39, 49, 59, 79][Math.floor(r() * 5)]; const exclusivePrice = lease * (8 + Math.floor(r() * 16)); return { id: i + 1, title, producer: BEAT_PRODS[i % BEAT_PRODS.length], verified: r() > 0.45, genre: BEAT_GENRES[i % BEAT_GENRES.length], mood: BEAT_MOODS[i % BEAT_MOODS.length], bpm, key: BEAT_KEYS[i % BEAT_KEYS.length], duration: `${Math.floor(2 + r() * 2)}:${String(Math.floor(r() * 60)).padStart(2, "0")}`, lease, exclusive, exclusivePrice, plays: Math.floor(800 + r() * 12000), art: i % 6, stems: r() > 0.5, peaks: Array.from({ length: 64 }, (_, k) => { const base = 0.25 + 0.5 * Math.abs(Math.sin(i * 0.7 + k * 0.31)); return Math.min(1, base + (r() - 0.5) * 0.3); }), }; }); } const ALL_BEATS = makeBeats(); // ---------- waveform ---------- function Waveform({ peaks, progress = 0, playing = false, compact = false }) { const h = compact ? 28 : 44; return (
{peaks.map((p, i) => ( ))}
); } // ---------- filter rail (Beat-specific) ---------- function BeatFilterRail() { const [genre, setGenre] = useStateBeat("Tutti"); const [mood, setMood] = useStateBeat("Tutti"); const [bpmMin, setBpmMin] = useStateBeat(60); const [bpmMax, setBpmMax] = useStateBeat(180); const [licenseType, setLicenseType] = useStateBeat("any"); const [stemsOnly, setStemsOnly] = useStateBeat(false); return ( ); } // ---------- beat card ---------- function BeatCard({ beat, isPlaying, onPlay, onLike, onBuy, hero, liked }) { const arts = ["art-a","art-b","art-c","art-d","art-e","art-f"]; return (
{beat.duration}
{beat.exclusive && Esclusiva disp.} {beat.bpm} BPM
{beat.title}
{beat.producer.charAt(0)} {beat.producer} {beat.verified && }
{beat.genre} {beat.mood} {beat.key} {beat.stems && + Stems}
€{beat.lease}licenza {beat.exclusive && ( €{beat.exclusivePrice}esclusiva )}
); } // ---------- BuyModal ---------- function BuyModal({ beat, onClose }) { const [tier, setTier] = useStateBeat("lease"); const tiers = [ { id: "lease", t: "Licenza non-esclusiva", p: beat.lease, desc: "Stream illimitato, monetizzazione fino a 50k stream. MP3 + WAV.", includes: ["MP3 + WAV", "Streaming + monetizzazione", "Crediti producer obbligatori"] }, { id: "lease-plus", t: "Lease + Stems", p: beat.lease + 30, desc: "Tutto della licenza, più gli stems separati per mix custom.", includes: ["MP3 + WAV + Stems separati", "Streaming + monetizzazione", "Mixaggio personalizzabile"] }, { id: "excl", t: "Esclusiva", p: beat.exclusivePrice, desc: "Diritti esclusivi. Il beat viene rimosso dal catalogo entro 24h. Solo 18+.", includes: ["Tutti i file + stems", "Diritti esclusivi a vita", "Contratto firmato digitalmente", "Rimozione catalogo entro 24h"], flag: "Solo 18+" }, ]; const sel = tiers.find(t => t.id === tier); return (
e.stopPropagation()}>
Acquisto licenza
{beat.title}
di {beat.producer} {beat.verified && } {beat.bpm} BPM · {beat.key} · {beat.genre}
Scegli la licenza
{tiers.map(t => ( ))}
Cosa ricevi
    {sel.includes.map(it => (
  • {it}
  • ))}
{tier === "excl" && (
Contratto digitale richiesto
Allo step successivo firmerai il contratto di cessione esclusiva (SES). Verifica età 18+ richiesta.
)}
Metodo
Prezzo€{sel.p}
Saldo wallet usato−€25,00
Da pagare€{Math.max(0, sel.p - 25)}
{tier === "excl" ? "Vai al contratto →" : "Conferma acquisto →"}
Pagamento sicuro. Beat pronti: download immediato. Esclusive: contratto + verifica.
); } // ---------- StickyPlayer ---------- function StickyPlayer({ beat, playing, onToggle, onClose, style = "expanded" }) { if (!beat) return null; const arts = ["art-a","art-b","art-c","art-d","art-e","art-f"]; return (
{beat.title}
{beat.producer} · {beat.bpm} BPM · {beat.key}
{style === "expanded" && (
1:14 {beat.duration}
)}
Acquista €{beat.lease}
); } // ---------- BeatCatalog ---------- function BeatCatalog({ playerStyle = "expanded", cardStyle = "standard" }) { const [playing, setPlaying] = useStateBeat({ id: null, on: false }); const [liked, setLiked] = useStateBeat(new Set([3, 7, 12])); const [buying, setBuying] = useStateBeat(null); const playBeat = (b) => setPlaying(p => p.id === b.id ? { id: b.id, on: !p.on } : { id: b.id, on: true }); const toggleLike = (id) => setLiked(s => { const n = new Set(s); n.has(id) ? n.delete(id) : n.add(id); return n; }); const beats = ALL_BEATS; const playingBeat = beats.find(b => b.id === playing.id); return ( <>
{beats.map(b => ( playBeat(b)} onLike={() => toggleLike(b.id)} onBuy={() => setBuying(b)} /> ))}
{playingBeat && ( setPlaying(p => ({ ...p, on: !p.on }))} onClose={() => setPlaying({ id: null, on: false })} style={playerStyle} /> )} {buying && setBuying(null)}/>} ); } window.BeatCatalog = BeatCatalog; window.ALL_BEATS = ALL_BEATS; window.BEAT_COUNT = ALL_BEATS.length;