diff --git a/public/app.js b/public/app.js
index 10ef8d1..906de5a 100644
--- a/public/app.js
+++ b/public/app.js
@@ -48,6 +48,10 @@ const SUBS = {
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 = [
@@ -88,13 +92,32 @@ const playChaChing = async () => {
} 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!
@@ -102,7 +125,7 @@ function WelcomeBanner({ balance, onStart }) {
Every feature costs money. Pause, skip, volume — all paywalled.
Your complimentary balance:
${balance.toFixed(2)}
-
+ {!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"); }) },
diff --git a/public/favicon.ico b/public/favicon.ico
new file mode 100644
index 0000000..402ce89
Binary files /dev/null and b/public/favicon.ico differ
diff --git a/public/index.html b/public/index.html
index 912d4e3..3175880 100644
--- a/public/index.html
+++ b/public/index.html
@@ -13,6 +13,7 @@
+
diff --git a/public/style.css b/public/style.css
index 362f3be..943dd3d 100644
--- a/public/style.css
+++ b/public/style.css
@@ -10,6 +10,7 @@ body { background: #0d0d1a; font-family: 'IBM Plex Mono', monospace; }
@keyframes balFlash { 0% { color:#00ee44 } 20% { color:#e74c3c } 50% { color:#ff6b6b } 100% { color:#00ee44 } }
@keyframes countPulse { 0%,100% { transform:scale(1) } 50% { transform:scale(1.1) } }
@keyframes successPop { 0% { transform:scale(0.5);opacity:0 } 50% { transform:scale(1.2) } 100% { transform:scale(1);opacity:1 } }
+@keyframes fadeOut { from { opacity:1 } to { opacity:0 } }
/* Range input — larger thumb for touch targets */
input[type=range] { -webkit-appearance:none; appearance:none; height:6px; border-radius:3px; outline:none; }
diff --git a/tools/favicon.svg b/tools/favicon.svg
new file mode 100644
index 0000000..614d152
--- /dev/null
+++ b/tools/favicon.svg
@@ -0,0 +1,5 @@
+
diff --git a/public/og-card.svg b/tools/og-card.svg
similarity index 100%
rename from public/og-card.svg
rename to tools/og-card.svg