const { useState, useEffect, useRef, useCallback } = React; // ============================================================ // SONG DEFINITIONS // // Procedural synth songs are the built-in fallback. If // audio/songs.json exists (generated by tools/probe-songs.py), // those MP3-backed songs replace the builtins at startup. // ============================================================ const BUILTIN_SONGS = [ { title: "Bureaucratic Sunrise", artist: "The Paywalls", duration: 90, bpm: 120, gen: (s,t) => { ["C4","E4","G4","B4","C5","B4","G4","E4"].forEach((n,i) => s.triggerAttackRelease(n,"8n",t+i*0.25)); }, wave: "triangle", color: "#e74c3c" }, { title: "Terms of Sorrow", artist: "Subscription Model", duration: 105, bpm: 72, gen: (s,t) => { ["A3","C4","E4","A4","G4","E4","D4","C4"].forEach((n,i) => s.triggerAttackRelease(n,"4n",t+i*0.5)); }, wave: "sine", color: "#9b59b6" }, { title: "Micro Transaction Blues", artist: "Nickel & Dime", duration: 75, bpm: 140, gen: (s,t) => { ["E3","G3","A3","B3","D4","B3","A3","G3","E3","D3","E3","G3"].forEach((n,i) => s.triggerAttackRelease(n,"16n",t+i*0.18)); }, wave: "sawtooth", color: "#f39c12" }, { title: "404 Feeling Not Found", artist: "Errorcode", duration: 80, bpm: 100, gen: (s,t) => { ["D4","F#4","A4","D5","C#5","A4","F#4","D4","C#4","D4"].forEach((n,i) => s.triggerAttackRelease(n,"8n.",t+i*0.35)); }, wave: "square", color: "#3498db" }, { title: "EULA Lullaby", artist: "Fine Print", duration: 120, bpm: 56, gen: (s,t) => { ["G3","B3","D4","G4","F#4","D4","B3","A3","G3","B3"].forEach((n,i) => s.triggerAttackRelease(n,"2n",t+i*0.7)); }, wave: "sine", color: "#1abc9c" }, { title: "Wallet on Fire", artist: "DJ Overdraft", duration: 68, bpm: 155, gen: (s,t) => { ["C3","Eb3","G3","Bb3","C4","Eb4","G4","Eb4","C4","Bb3","G3","Eb3","C3"].forEach((n,i) => s.triggerAttackRelease(n,"16n",t+i*0.15)); }, wave: "sawtooth", color: "#e67e22" }, ]; // Mutable song list — replaced by audio/songs.json if available let SONGS = BUILTIN_SONGS; const SUBS = { pause: { name:"Pause Plus™", price:"$2.99/mo", micro:0.01, desc:"The freedom to stop. Whenever you want.", color:"#e74c3c", dismiss:"I like surprises" }, resume: { name:"Resume Rights™", price:"$2.49/mo", micro:0.02, desc:"You paused. Now un-pause. Two different things.", color:"#e67e22", dismiss:"Silence suits me" }, repeat: { name:"Loop Loyalty™", price:"$5.99/mo", micro:0.03, desc:"Hear it again. And again. And again.", color:"#9b59b6", dismiss:"Once was enough" }, unrepeat: { name:"Premium Loop Loyalty™", price:"$8.99/mo", micro:0.05, desc:"Escape the loop. If you dare.", color:"#8e44ad", dismiss:"I'll loop forever" }, skip: { name:"Skip Sprint™", price:"$4.99/mo", micro:0.05, desc:"Life's too short. Songs shouldn't be.", color:"#3498db", dismiss:"I'll wait it out" }, prev: { name:"Nostalgia Pass™", price:"$6.99/mo", micro:0.10, desc:"Go back. Premium memories cost more.", color:"#1abc9c", dismiss:"The past is gone" }, volume: { name:"Volume Freedom™", price:"$3.99/mo", micro:0.08, desc:"Break free from the 23-47% corridor.", color:"#f39c12", dismiss:"35% is fine actually" }, seek: { name:"Precision Seek™", price:"$2.99/mo", micro:0.04, desc:"Go where you mean to go. Radical concept.", color:"#e74c3c", dismiss:"I'll guess" }, shuffle: { name:"Smart Shuffle™", price:"$6.99/mo", micro:0.06, desc:"Actually random. Not weighted against you.", color:"#2ecc71", dismiss:"Order is fine" }, queue: { name:"Queue Capacity Pack™", price:"$1.49/3 slots",micro:0.50, desc:"Your queue is full (1 song max). Expand it.", color:"#e74c3c", dismiss:"One song is plenty" }, dark: { name:"Dark Mode™", price:"$0.99/mo", micro:0.99, desc:"Easy on the eyes. Hard on the wallet.", color:"#34495e", dismiss:"I love bright screens" }, lyrics: { name:"Full Verse™", price:"$3.49/mo", micro:0.07, desc:"See ALL the words. Not just some of them.", color:"#e74c3c", dismiss:"I'll guess the lyrics" }, continue: { name:"Continuity Plan™", price:"$4.99/mo", micro:0.02, desc:"Hear beyond 3 seconds. Revolutionary.", color:"#16a085", dismiss:"3 seconds is enough" }, exit: { name:"Silent Exit™", price:"$1.99/mo", micro:0.25, desc:"Close the app without an unskippable ad.", color:"#c0392b", dismiss:"I love ads actually" }, eq: { name:"EQ Preset Pack™", price:"$0.50/preset",micro:0.50, desc:"Shape your sound. Shape your spending.", color:"#d35400", dismiss:"Flat is a vibe" }, bluetooth: { name:"Wireless Freedom Pass™", price:"$2.99/mo", micro:0.15, desc:"Cut the cord. Not the cost.", color:"#2980b9", dismiss:"Wires are retro" }, like: { name:"Appreciation License™", price:"$1.99/mo", micro:0.25, desc:"Express feelings. For a fee.", color:"#e74c3c", dismiss:"I feel nothing" }, hd: { name:"HD Audio™", price:"$3.99/mo", micro:0.20, desc:"Upgrade from 64kbps Suffering Quality™.", color:"#9b59b6", dismiss:"Lo-fi is a genre" }, history: { name:"Recently Played™", price:"$2.49/mo", micro:0.15, desc:"We know what you listened to. Pay to find out too.", color:"#1abc9c", dismiss:"I'll forget" }, unsub: { name:"Cancellation Processing™",price:"$4.99/unsub", micro:4.99, desc:"Freedom from commitment. Commitment from your wallet.", color:"#c0392b", dismiss:"I'll keep paying" }, }; const LYRICS_DATA = [ [["We","charge","for","every","click"],["Your","wallet","feels","the","pain"],["Subscribe","or","live","in","silence"],["The","music","never","stops"],["Unless","you","pay","to","pause"]], [["Somewhere","deep","inside","the","EULA"],["A","clause","awaits","your","soul"],["Terms","conditions","fine","print","sorrow"],["Accept","decline","accept","forever"],["Your","data","sings","for","us"]], [["Micro","transactions","falling","like","rain"],["A","penny","here","a","dollar","there"],["Your","balance","slowly","fading"],["But","music","keeps","on","playing"],["Three","seconds","at","a","time"]], [["Error","four","oh","four","feeling"],["Not","found","inside","this","app"],["Pay","twenty","five","cents","learn"],["What","went","wrong","this","time"],["Spoiler","it","was","you"]], [["Hush","little","user","don't","say","word"],["Papa's","gonna","buy","premium","tier"],["And","if","that","premium","runs","out"],["Papa's","gonna","charge","card","again"],["Sleep","tight","wallet","light"]], [["Fire","fire","wallet","burning","bright"],["Every","button","costs","a","fee"],["The","flames","of","monetization"],["Light","the","path","to","bankruptcy"],["Dance","dance","overdraft","dance"]], ]; const AD_COPY = [ "TIRED OF AFFORDABLE MUSIC? Try Pay2Play! Premium Ultra™ — now with 40% more paywalls!", "Did you know? The average Pay2Play! user spends $847/year. Be above average.", "EXCLUSIVE: Unlock the ability to unlock abilities with our Meta-Unlock Bundle™!", "Pay $19.99/mo to remove this ad. Or $29.99/mo to also remove the next one.", "Your listening data has been sold to 47 advertisers. Upgrade to Privacy Plus™ ($12.99/mo) to make it 46.", "Pay2Play! Kids™ — Teach your children the value of money. One paywall at a time.", "RATE US 5 STARS and receive absolutely nothing in return!", "Pay2Play! was voted #1 Music Player by people who had no other choice.", ]; const fmt = s => `${Math.floor(s/60)}:${String(Math.floor(s%60)).padStart(2,"0")}`; // Cha-ching sound effect via Tone.js const playChaChing = async () => { try { await Tone.start(); const s = new Tone.Synth({ oscillator: { type: "triangle" }, envelope: { attack: 0.001, decay: 0.12, sustain: 0, release: 0.08 }, volume: -10 }).toDestination(); const now = Tone.now(); s.triggerAttackRelease("C7", "32n", now); s.triggerAttackRelease("E7", "32n", now + 0.08); setTimeout(() => s.dispose(), 500); } catch(e) {} }; // Annoying jingle for the exit ad const playAdJingle = async () => { try { await Tone.start(); const s = new Tone.Synth({ oscillator: { type: "square" }, envelope: { attack: 0.01, decay: 0.1, sustain: 0.3, release: 0.1 }, volume: -15 }).toDestination(); const now = Tone.now(); ["C5","E5","G5","C6","G5","E5","C5"].forEach((n, i) => s.triggerAttackRelease(n, "16n", now + i * 0.12) ); setTimeout(() => s.dispose(), 2000); } catch(e) {} }; // ============================================================ // COMPONENTS // ============================================================ function WelcomeBanner({ balance, onStart }) { const [fading, setFading] = useState(false); const dismiss = () => { setFading(true); setTimeout(onStart, 400); }; return (
🎵💸

Pay2Play!

Every feature costs money. Pause, skip, volume — all paywalled. Your complimentary balance: ${balance.toFixed(2)}

Part of donothireus.com
); } function Modal({ sub, phase, successText, onSub, onMicro, onClose, onTopUp }) { if (!sub) return null; const s = SUBS[sub]; return (
{phase === "success" ? ( <>
Congratulations!
{successText}
) : phase === "broke" ? ( <>
💸
Insufficient Funds
Even your wallet is on a free tier.
) : ( <>
⚡ FEATURE LOCKED ⚡
{s.name}
{s.desc}
)}
); } function ExitAd({ onDone }) { const [c, setC] = useState(15); const [adIdx, setAdIdx] = useState(() => Math.floor(Math.random() * AD_COPY.length)); const onDoneR = useRef(onDone); onDoneR.current = onDone; useEffect(() => { playAdJingle(); }, []); useEffect(() => { if(c<=0){onDoneR.current();return;} const t=setTimeout(()=>setC(x=>x-1),1000); return()=>clearTimeout(t); }, [c]); useEffect(() => { const t=setInterval(()=>setAdIdx(i=>(i+1)%AD_COPY.length),3500); return()=>clearInterval(t); }, []); return (
UNSKIPPABLE AD
{c}s
{AD_COPY[adIdx]}
Subscribe to Silent Exit™ ($1.99/mo) to skip this
); } function CBtn({ onClick, color, size=28, children, label }) { return ( ); } // ============================================================ // MAIN APP // ============================================================ function PayPlay() { const [ci, setCi] = useState(0); const [on, setOn] = useState(false); const [prog, setProg] = useState(0); const [vol, setVol] = useState(35); const [rep, setRep] = useState(false); const [shuf, setShuf] = useState(false); const [modal, setModal] = useState(null); const [modalPhase, setModalPhase] = useState("choose"); const [successText, setSuccessText] = useState(""); const [bal, setBal] = useState(() => +(0.50 + Math.random() * 9.50).toFixed(2)); const [subs, setSubs_] = useState({}); const [toastMsg, setToastMsg] = useState(""); const [toastColor, setToastColor] = useState("#00ee44"); const [toastVis, setToastVis] = useState(false); const [lyr, setLyr] = useState(false); const [showExit, setShowExit] = useState(false); const [showAd, setShowAd] = useState(false); const [spent, setSpent] = useState(0); const [acts, setActs] = useState(0); const [toneOk, setToneOk] = useState(false); const [bars, setBars] = useState(Array(16).fill(2)); const [welcome, setWelcome] = useState(true); const [bright, setBright] = useState(false); const [balKey, setBalKey] = useState(0); const [likes, setLikes] = useState(0); const [hdAudio, setHdAudio] = useState(false); const synthR = useRef(null); const loopR = useRef(null); const progT = useRef(null); const visT = useRef(null); const hitLimit = useRef(false); const audioR = useRef(null); const filterR = useRef(null); const milestoneR = useRef(new Set()); const song = SONGS[ci]; const has = k => subs[k]; // Theme-aware colors (toggle when Dark Mode joke triggers bright mode) const bg = bright ? "#f5f5f0" : "#0d0d1a"; const card = bright ? "#e8e8f0" : "#1c1c32"; const text = bright ? "#111" : "#fff"; const muted = bright ? "#555" : "#7788aa"; const accent= bright ? "#00aa33" : "#00ee44"; // Sync body background with theme useEffect(() => { document.body.style.background = bg; }, [bright]); // Spending milestones useEffect(() => { const checks = [[1,"Over $1 spent. Getting started."],[5,"$5 down. Commitment issues?"],[10,"$10 gone. You could buy actual music."],[20,"$20. Seek professional help."]]; checks.forEach(([threshold, msg]) => { if (spent >= threshold && !milestoneR.current.has(threshold)) { milestoneR.current.add(threshold); setTimeout(() => flash(msg, "#f39c12"), 600); } }); }, [spent]); // Color-coded toasts: red for charges, green for unlocks, yellow for info const flash = useCallback((msg, color = "#00ee44") => { setToastMsg(msg); setToastColor(color); setToastVis(true); setTimeout(() => setToastVis(false), 2500); }, []); // Charge with cha-ching sound + balance flash animation const charge = useCallback((a, l) => { setBal(b => Math.max(0, +(b - a).toFixed(2))); setSpent(t => +(t + a).toFixed(2)); setActs(x => x + 1); setBalKey(k => k + 1); flash(`-$${a.toFixed(2)} • ${l}`, "#e74c3c"); playChaChing(); }, [flash]); const tryAct = (k, fn) => { if (has(k)) { fn(); return; } setModal({ key: k, action: fn }); setModalPhase("choose"); }; // Modal: subscribe (full price) const onSubClick = () => { if (!modal) return; const s = SUBS[modal.key], p = parseFloat(s.price.replace(/[^0-9.]/g, "")); if (bal < p) { setModalPhase("broke"); return; } charge(p, s.name); setSubs_(prev => ({ ...prev, [modal.key]: true })); setSuccessText(`${s.name} is yours. Was it worth it? (It wasn't.)`); setModalPhase("success"); setTimeout(() => { modal.action(); setModal(null); setModalPhase("choose"); }, 1800); // Humor: pause and resume are separate products if (modal.key === "pause") setTimeout(() => flash("Note: unpausing requires Resume Rights™", "#f39c12"), 2500); }; // Modal: one-time micro-transaction const onMicroClick = () => { if (!modal) return; const s = SUBS[modal.key]; if (bal < s.micro) { setModalPhase("broke"); return; } charge(s.micro, `${s.name} (1x)`); setSuccessText(`One-time access. That's $${s.micro.toFixed(2)} you'll never see again.`); setModalPhase("success"); setTimeout(() => { modal.action(); setModal(null); setModalPhase("choose"); }, 1500); }; // Modal: top-up from insufficient-funds screen (with fee) const handleModalTopUp = () => { setBal(b => +(b + 9.50).toFixed(2)); setSpent(s => +(s + 0.50).toFixed(2)); setActs(x => x + 1); setBalKey(k => k + 1); flash("+$9.50 ($0.50 processing fee)", "#f39c12"); playChaChing(); setModalPhase("choose"); }; // ---- Audio engine ---- const initTone = useCallback(async () => { if (!toneOk) { await Tone.start(); setToneOk(true); } }, [toneOk]); const stopAudio = useCallback(() => { if (audioR.current) { audioR.current.pause(); audioR.current.currentTime = 0; audioR.current = null; } if (loopR.current) { loopR.current.stop(); loopR.current.dispose(); loopR.current = null; } if (synthR.current) { synthR.current.dispose(); synthR.current = null; } if (filterR.current) { filterR.current.dispose(); filterR.current = null; } try { Tone.getTransport().stop(); } catch(e) {} clearInterval(progT.current); clearInterval(visT.current); setBars(Array(16).fill(2)); }, []); const startAudio = useCallback(async (idx, from = 0) => { stopAudio(); hitLimit.current = false; const s = SONGS[idx]; // HTML5 Audio for self-hosted MP3s if (s.url) { const audio = new Audio(s.url); audio.currentTime = from; audio.volume = vol / 100; audioR.current = audio; audio.play().catch(() => {}); let p = from; progT.current = setInterval(() => { p = Math.floor(audio.currentTime); setProg(p); if (p >= s.duration || audio.ended) { clearInterval(progT.current); stopAudio(); setOn(false); setProg(0); } }, 1000); visT.current = setInterval(() => setBars(b => b.map(() => 2 + Math.random() * 28)), 150); return; } // Tone.js procedural synth (low-pass filtered unless HD Audio purchased) await initTone(); const filter = new Tone.Filter(hdAudio ? 20000 : 1200, "lowpass").toDestination(); filterR.current = filter; const synth = new Tone.Synth({ oscillator: { type: s.wave }, envelope: { attack: 0.05, decay: 0.2, sustain: 0.4, release: 0.8 }, volume: -30 + vol * 0.3 }).connect(filter); synthR.current = synth; const loop = new Tone.Loop(time => s.gen(synth, time), 2.5); loopR.current = loop; Tone.getTransport().bpm.value = s.bpm; Tone.getTransport().start(); loop.start(0); let p = from; progT.current = setInterval(() => { p += 1; setProg(p); if (p >= s.duration) { clearInterval(progT.current); stopAudio(); setOn(false); setProg(0); } }, 1000); visT.current = setInterval(() => setBars(b => b.map(() => 2 + Math.random() * 28)), 150); }, [initTone, stopAudio, vol, hdAudio]); useEffect(() => () => stopAudio(), [stopAudio]); // 3-second free limit with audio degradation at 2s useEffect(() => { if (!on || has("continue")) return; // Muffle audio 1 second before cutoff if (prog === 2) { if (synthR.current) synthR.current.volume.rampTo(-40, 0.8); if (audioR.current) audioR.current.volume = 0.1; } // Hard cutoff at 3 seconds if (prog >= 3 && !hitLimit.current) { hitLimit.current = true; stopAudio(); setOn(false); setModal({ key: "continue", action: () => { hitLimit.current = false; setOn(true); startAudio(ci, prog); } }); setModalPhase("choose"); } }, [prog, on, subs]); // ---- User actions ---- const handlePlay = async () => { if (on) { tryAct("pause", () => { stopAudio(); setOn(false); }); } else { if (prog > 0 && !has("continue")) { tryAct("resume", () => { setOn(true); startAudio(ci, prog); }); } else { if (!SONGS[ci].url) await initTone(); hitLimit.current = false; setOn(true); startAudio(ci, prog); } } }; const changeSong = idx => { stopAudio(); setCi(idx); setProg(0); setOn(false); hitLimit.current = false; setBars(Array(16).fill(2)); }; const handleSeek = e => { const r = e.currentTarget.getBoundingClientRect(), pct = (e.clientX - r.left) / r.width, tgt = Math.floor(pct * song.duration); if (has("seek")) { setProg(tgt); if (on) { stopAudio(); startAudio(ci, tgt); } } else { setProg(Math.max(0, Math.min(song.duration, tgt + Math.floor(Math.random() * 40) - 15))); flash("Seek landed somewhere in the general vicinity.", "#f39c12"); } }; const handleSeekKey = e => { let tgt = prog; if (e.key === "ArrowRight") tgt = Math.min(song.duration, prog + 5); else if (e.key === "ArrowLeft") tgt = Math.max(0, prog - 5); else return; e.preventDefault(); if (has("seek")) { setProg(tgt); if (on) { stopAudio(); startAudio(ci, tgt); } } else { setProg(Math.max(0, Math.min(song.duration, tgt + Math.floor(Math.random() * 40) - 15))); flash("Seek precision is approximate. Very approximate.", "#f39c12"); } }; const handleVol = e => { const v = parseInt(e.target.value); if (!has("volume") && (v < 23 || v > 47)) { tryAct("volume", () => { setVol(v); if (synthR.current) synthR.current.volume.value = -30 + v * 0.3; if (audioR.current) audioR.current.volume = v / 100; }); return; } setVol(v); if (synthR.current) synthR.current.volume.value = -30 + v * 0.3; if (audioR.current) audioR.current.volume = v / 100; }; const shareReceipt = () => { const lines = [ "Pay2Play! Receipt", "", `Total spent: $${spent.toFixed(2)}`, `Actions: ${acts} (${acts > 0 ? `$${(spent/acts).toFixed(2)}/action` : "free"})`, `Subscriptions: ${Object.keys(subs).length}`, Object.keys(subs).length > 0 ? Object.keys(subs).map(k => SUBS[k].name).join(", ") : "None (cheapskate)", "", `Try it: ${window.location.href}` ]; if (navigator.clipboard) { navigator.clipboard.writeText(lines.join("\n")).then(() => flash("Receipt copied! Share your shame.", accent)); } }; // Volume slider gradient: shows free zone (23-47%) when not subscribed const volBg = has("volume") ? `linear-gradient(to right,${song.color} ${vol}%,${card} ${vol}%)` : `linear-gradient(to right,${card} 0%,${card} 23%,${song.color} 23%,${song.color} ${vol}%,${song.color}33 ${vol}%,${song.color}33 47%,${card} 47%)`; const curLyrics = LYRICS_DATA[ci] || LYRICS_DATA[0]; const toastTextColor = ["#e74c3c","#f39c12"].includes(toastColor) ? "#fff" : "#000"; // ============================================================ // RENDER // ============================================================ return ( <> {welcome && setWelcome(false)} />} {showAd && { setShowAd(false); setShowExit(true); }} />} {/* Exit / receipt screen */} {showExit && (
👋
Thanks for listening!
Total spent: ${spent.toFixed(2)}
{acts} actions • ${acts>0?(spent/acts).toFixed(2):"0.00"}/action
{Object.keys(subs).length} subscription{Object.keys(subs).length !== 1 ? "s" : ""}
)} { setModal(null); setModalPhase("choose"); }} onTopUp={handleModalTopUp} /> {/* Toast notification */}
{toastMsg}
{/* ---- Main player ---- */}
{/* Sticky header with balance */}
PAY2PLAY!
The Worst Music Player
0?"balFlash 0.6s ease":"none",transition:"background 0.3s ease"}}>💰 ${bal.toFixed(2)}
{/* Active subscriptions marquee (speed scales with count) */} {Object.keys(subs).length > 0 && (
{Object.keys(subs).map(k => ✓ {SUBS[k].name})}
)} {/* Visualizer (responsive width) */}
{bars.map((h, i) =>
)} {/* Vinyl disc — freezes in place on pause */}
FREE TIER
{/* Free-listening countdown badge */} {on && !has("continue") && prog < 3 && (
FREE {3 - prog}s
)} {!toneOk && !SONGS[0].url &&
Click play to start audio
}
{/* Song info */}

{song.title}

{song.artist}
{/* Seek bar — larger touch target, keyboard accessible */}
{fmt(prog)} {!has("seek") && 🔒 approx.} {fmt(song.duration)}
{/* Transport controls */}
tryAct("shuffle", () => setShuf(s => !s))} color={shuf ? accent : muted} label="Shuffle">🔀 tryAct("prev", () => changeSong((ci - 1 + SONGS.length) % SONGS.length))} color={muted} size={36} label="Previous">⏮ tryAct("skip", () => changeSong((ci + 1) % SONGS.length))} color={muted} size={36} label="Next">⏭ rep ? tryAct("unrepeat", () => setRep(false)) : tryAct("repeat", () => setRep(true))} color={rep ? accent : muted} label="Repeat">🔁
{/* Volume slider with free-zone indicator */}
🔈 {vol}% {!has("volume") && 🔒 23-47%}
{!hdAudio && !song.url &&
🔊 64kbps Suffering Quality™ — Upgrade to HD Audio
} {/* Feature buttons (responsive grid) */}
{[ { l:"Lyrics", i:"📝", a:() => tryAct("lyrics", () => setLyr(x => !x)) }, { l:"HD Audio", i:"🔊", a:() => tryAct("hd", () => { setHdAudio(true); if(filterR.current) filterR.current.frequency.rampTo(20000, 0.5); flash("HD Audio enabled. Hear the difference? (There isn't one.)", "#9b59b6"); }) }, { l:"Like", i:"❤️", a:() => tryAct("like", () => { setLikes(l => l + 1); flash(`Liked! (${likes + 1} total). This does absolutely nothing.`, "#e74c3c"); }) }, { l:"History", i:"📜", a:() => tryAct("history", () => flash(`Recently played: "${song.title}". That's it. You're still on it.`, "#1abc9c")) }, { l:"EQ", i:"🎛️", a:() => tryAct("eq", () => flash("EQ: Flat ($0.50). Bass Boost: another $0.50. Each.", "#f39c12")) }, { l:"Bluetooth", i:"📡", a:() => tryAct("bluetooth", () => flash("Bluetooth connected... for now.", "#3498db")) }, { l:"Dark Mode", i:"🌙", a:() => tryAct("dark", () => { setBright(b => !b); flash(!bright ? "Welcome to Light Mode™. Suffering." : "Dark Mode restored. You paid for the default.", !bright ? "#f39c12" : "#00ee44"); }) }, { l:"Queue +", i:"📋", a:() => tryAct("queue", () => flash("Queue expanded to 4 slots! (3 are decorative)", "#00ee44")) }, ].map(f => )}
{/* Lyrics panel — animated slide */}
LYRICS {!has("lyrics") && "— REDACTED VERSION"}
{curLyrics.map((line, i) => (
{has("lyrics") ? line.join(" ") : line.map((w, j) => {j % 3 === 0 ? w : █████}{" "})}
))}
{/* Up Next — card styled, with price on lock icons */}

UP NEXT

{SONGS.map((s, i) => i !== ci && (
tryAct("skip", () => changeSong(i))} style={{display:"flex",alignItems:"center",gap:12,padding:"10px 20px",cursor:"pointer",borderBottom:`1px solid ${bright?"#ddd":muted+"11"}`}}>
{s.title}
{s.artist}
{fmt(s.duration)}
🔒 ${SUBS.skip.micro.toFixed(2)}
))}
{/* Session Economics */}

SESSION ECONOMICS

{[["Spent",`$${spent.toFixed(2)}`,"#e74c3c"],["Subscriptions",Object.keys(subs).length,"#f39c12"],["Actions",acts,text],["Cost/action",acts>0?`$${(spent/acts).toFixed(2)}`:"—","#e74c3c"]].map(([l,v,c]) => (
{l}{v}
))}
Pay2Play! v6.6.6 • Music procedurally generated — even the songs are cheaply made • Refunds available for $4.99 processing fee • © 2026 Suffering Inc.
); } // Load real songs from audio/songs.json if available, then render fetch("audio/songs.json") .then(r => { if (r.ok) return r.json(); throw new Error(); }) .then(songs => { if (Array.isArray(songs) && songs.length > 0) SONGS = songs; }) .catch(() => {}) // No songs.json — use procedural builtins .finally(() => ReactDOM.createRoot(document.getElementById("root")).render());