<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>DJ Requests</title>
<style>
:root{--bg:#0b1020;--bg2:#070b12;--card:rgba(255,255,255,.07);--line:rgba(255,255,255,.12);--txt:rgba(255,255,255,.92);--mut:rgba(255,255,255,.65)}
*{box-sizing:border-box;font-family:system-ui,-apple-system,Segoe UI,Roboto,Arial}
body{margin:0;min-height:100vh;color:var(--txt);
background: radial-gradient(900px 500px at 15% 10%, rgba(124,58,237,.35), transparent 60%),
radial-gradient(900px 500px at 85% 20%, rgba(34,197,94,.22), transparent 55%),
linear-gradient(180deg,var(--bg),var(--bg2))}
.wrap{width:min(1100px,92vw);margin:26px auto 50px}
header{display:flex;justify-content:space-between;gap:14px;align-items:flex-end}
h1{margin:0;font-size:34px}
p{margin:6px 0 0;color:var(--mut)}
.card{margin-top:14px;background:linear-gradient(180deg,var(--card),rgba(255,255,255,.04));
border:1px solid var(--line);border-radius:18px;padding:16px;backdrop-filter:blur(10px)}
.pill{display:inline-flex;gap:10px;align-items:center;padding:10px 14px;border-radius:999px;
border:1px solid var(--line);background:rgba(255,255,255,.06);color:var(--txt);text-decoration:none;cursor:pointer}
.grid{display:grid;grid-template-columns:1fr 1fr;gap:12px}
label span{display:block;color:var(--mut);font-size:12px;margin:2px 0 7px}
input,select,textarea{width:100%;border-radius:14px;border:1px solid var(--line);
background:rgba(0,0,0,.22);color:var(--txt);padding:12px;outline:none}
textarea{min-height:90px;resize:vertical}
.span2{grid-column:1/-1}
.btn{margin-top:12px;width:100%;border:none;border-radius:16px;padding:14px 16px;
background:linear-gradient(90deg, rgba(124,58,237,.85), rgba(34,197,94,.70));
color:white;font-weight:800;cursor:pointer;display:flex;justify-content:space-between}
.tiny{margin:10px 2px 0;color:var(--mut);font-size:12px}
.row{display:flex;gap:10px;flex-wrap:wrap}
.chips button{border-radius:999px;border:1px solid var(--line);background:rgba(255,255,255,.05);
color:var(--txt);padding:9px 12px;cursor:pointer}
.chips button.active{border-color:rgba(124,58,237,.65);background:rgba(124,58,237,.18)}
.list{display:grid;gap:12px;margin-top:12px}
.item{border-radius:18px;border:1px solid var(--line);background:rgba(255,255,255,.06);padding:14px;display:flex;justify-content:space-between;gap:14px}
.badge{font-size:12px;border:1px solid var(--line);border-radius:999px;padding:6px 10px;color:var(--mut);background:rgba(0,0,0,.2);display:inline-block;margin-right:8px}
.title{margin:8px 0 6px;font-size:18px;line-height:1.2}
.note{margin:10px 0 0;color:rgba(255,255,255,.78)}
.actions button{border-radius:14px;border:1px solid var(--line);background:rgba(255,255,255,.06);color:var(--txt);padding:10px 12px;cursor:pointer}
</style>
</head>
<body>
<div class="wrap">
<header>
<div>
<h1>DJ Requests</h1>
<p>Works on any network. Guests submit. DJ view updates live.</p>
</div>
<div class="row">
<a class="pill" href="#guest">Guest</a>
<a class="pill" href="#dj">DJ View</a>
</div>
</header>
<section id="guest" class="card">
<h2 style="margin:0 0 6px;font-size:16px">Guest page</h2>
<form id="form">
<div class="grid">
<label><span>Your name</span><input name="name" maxlength="40" placeholder="Mike / Sarah / Table 7"/></label>
<label><span>Type</span>
<select name="type">
<option value="song">Song request</option>
<option value="karaoke">Karaoke request</option>
<option value="message">Message</option>
</select>
</label>
<label class="span2" data-show="song karaoke"><span>Artist</span><input name="artist" maxlength="60" placeholder="Queen"/></label>
<label class="span2" data-show="song karaoke"><span>Song</span><input name="title" maxlength="60" placeholder="Don’t Stop Me Now"/></label>
<label class="span2" data-show="message song karaoke"><span>Note (optional)</span><textarea name="note" maxlength="180" placeholder="Dedication, vibe, karaoke chaos..."></textarea></label>
</div>
<button class="btn" type="submit"><span>Send request</span><span>↯</span></button>
<div class="tiny" id="status"></div>
</form>
</section>
<section id="dj" class="card">
<div class="row" style="justify-content:space-between;align-items:flex-end">
<div>
<h2 style="margin:0 0 6px;font-size:16px">DJ view (screen-share this)</h2>
<div class="tiny" id="count"></div>
</div>
<div class="chips" id="chips">
<button class="active" data-f="all">All</button>
<button data-f="song">Songs</button>
<button data-f="karaoke">Karaoke</button>
<button data-f="message">Messages</button>
<button data-f="new">Unplayed</button>
<button data-f="played">Played</button>
</div>
</div>
<div class="list" id="list"></div>
</section>
</div>
<script type="module">
import { createClient } from "https://cdn.jsdelivr.net/npm/@supabase/supabase-js@2/+esm";
const SUPABASE_URL = "PASTE_SUPABASE_URL";
const SUPABASE_ANON_KEY = "PASTE_SUPABASE_ANON_KEY";
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY);
const $ = (s)=>document.querySelector(s);
const $$ = (s)=>Array.from(document.querySelectorAll(s));
const esc = (s)=>String(s??"").replaceAll("&","&").replaceAll("<","<").replaceAll(">",">");
let all = [];
let filter = "all";
function applyTypeVisibility(){
const form = $("#form");
const type = form.type.value;
$$("[data-show]").forEach(el=>{
const allowed = el.getAttribute("data-show").split(" ");
el.style.display = allowed.includes(type) ? "" : "none";
});
}
async function load(){
const { data, error } = await supabase
.from("requests")
.select("*")
.order("created_at", { ascending: false })
.limit(300);
if (!error) all = data || [];
render();
}
function render(){
let items = [...all];
if (["song","karaoke","message"].includes(filter)) items = items.filter(r=>r.type===filter);
if (["new","played"].includes(filter)) items = items.filter(r=>r.status===filter);
$("#count").textContent = `${items.length} showing · ${all.length} total`;
$("#list").innerHTML = items.map(r=>{
const label = r.type === "song" ? "Song" : r.type === "karaoke" ? "Karaoke" : "Message";
const who = esc(r.name || "Anonymous");
const time = new Date(r.created_at).toLocaleTimeString([], {hour:"2-digit", minute:"2-digit"});
const mainTitle = r.type === "message"
? esc(r.note || "(no message)")
: `${esc(r.artist || "Unknown artist")} · ${esc(r.title || "Unknown song")}`;
return `
<div class="item" data-id="${r.id}">
<div>
<div>
<span class="badge">${label}</span>
<span class="badge">${who}</span>
<span class="badge">${time}</span>
${r.status === "played" ? `<span class="badge">Played</span>` : ``}
</div>
<div class="title">${mainTitle}</div>
${r.type !== "message" && r.note ? `<div class="note">Note: ${esc(r.note)}</div>` : ``}
</div>
<div class="actions">
<button data-a="toggle">${r.status==="played" ? "Unplay" : "Played"}</button>
</div>
</div>
`;
}).join("");
$$("#list .item").forEach(el=>{
el.addEventListener("click", async (e)=>{
const btn = e.target.closest("button");
if (!btn) return;
const id = el.getAttribute("data-id");
const rec = all.find(x=>x.id===id);
const next = rec.status === "played" ? "new" : "played";
await supabase.from("requests").update({ status: next }).eq("id", id);
});
});
}
$("#chips").addEventListener("click",(e)=>{
const b = e.target.closest("button");
if(!b) return;
filter = b.getAttribute("data-f");
$$("#chips button").forEach(x=>x.classList.toggle("active", x===b));
render();
});
const form = $("#form");
applyTypeVisibility();
form.type.addEventListener("change", applyTypeVisibility);
form.addEventListener("submit", async (e)=>{
e.preventDefault();
const payload = {
name: form.name.value?.trim() || "Anonymous",
type: form.type.value,
artist: form.artist?.value?.trim() || null,
title: form.title?.value?.trim() || null,
note: form.note?.value?.trim() || null
};
if ((payload.type==="song"||payload.type==="karaoke") && (!payload.artist || !payload.title)){
$("#status").textContent = "Add artist + song title so the DJ isn’t forced to guess.";
return;
}
if (payload.type==="message" && !payload.note){
$("#status").textContent = "Type a message.";
return;
}
const { error } = await supabase.from("requests").insert(payload);
$("#status").textContent = error ? "Didn’t send. Try again." : "Sent ↯";
if (!error){ form.artist.value=""; form.title.value=""; form.note.value=""; }
});
supabase.channel("requests-live")
.on("postgres_changes", { event: "*", schema: "public", table: "requests" }, () => load())
.subscribe();
load();
</script>
</body>
</html>