-
+
+
+
+
+
+
+
+
Now Playing
+
+
+
+
+
+
+
+
+
+
+
Teilnehmer
+
+
+
+
+ Scoreboard
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
-
diff --git a/src/main/resources/public/index.html b/src/main/resources/public/index.html
index d2dcc5d..af9e34a 100644
--- a/src/main/resources/public/index.html
+++ b/src/main/resources/public/index.html
@@ -1,17 +1,77 @@
-
-
Spotify – Zuletzt gehörte Songs
+
+
+
Spotify Roulette – Login
+
+
+
+
-
Spotify – Zuletzt gehörte Songs
-
-
+
+
+
+ Spotify Roulette
+ Status: Abgemeldet
+
+
+
+
+
+
Du wirst zu Spotify weitergeleitet, um die Berechtigungen zu bestätigen.
+
+
+
diff --git a/src/main/resources/public/js/game.js b/src/main/resources/public/js/game.js
index a9e7505..ee93bbb 100644
--- a/src/main/resources/public/js/game.js
+++ b/src/main/resources/public/js/game.js
@@ -6,17 +6,31 @@ import { setupStartRound } from "./start-round.js";
const gameId = getParam("gameId");
const username = getParam("username");
+// --- kleine Helper ---
+function showToast(msg, ms = 2200) {
+ const t = document.getElementById("toast");
+ if (!t) return;
+ t.textContent = msg;
+ t.classList.add("show");
+ setTimeout(() => t.classList.remove("show"), ms);
+}
+function esc(s) {
+ const d = document.createElement("div");
+ d.textContent = String(s ?? "");
+ return d.innerHTML;
+}
+
// 1) Parameter prüfen
if (!gameId || !username) {
alert("Ungültige oder fehlende URL-Parameter!");
throw new Error("Missing gameId or username");
}
-// 2) Copy to clipboard (unverändert)
+// 2) Copy to clipboard (unverändert, aber mit Toast)
(function copyCodeToClipboard(code) {
if (navigator.clipboard && window.isSecureContext) {
navigator.clipboard.writeText(code)
- .then(() => console.log(`GameCode ${code} copied to clipboard`))
+ .then(() => showToast(`Spiel-Code ${code} kopiert`))
.catch(err => console.error("Clipboard write failed:", err));
} else {
const ta = document.createElement("textarea");
@@ -28,7 +42,7 @@ if (!gameId || !username) {
ta.select();
try {
document.execCommand("copy");
- console.log(`GameCode ${code} copied via execCommand`);
+ showToast(`Spiel-Code ${code} kopiert`);
} catch (err) {
console.error("Fallback copy failed:", err);
}
@@ -36,16 +50,14 @@ if (!gameId || !username) {
}
})(gameId);
-// 3) Visuelles Feedback (unverändert)
-const notice = document.createElement("div");
-notice.textContent = `Spiel-Code ${gameId} in die Zwischenablage kopiert!`;
-Object.assign(notice.style, { /* … Styles … */ });
-document.body.append(notice);
-setTimeout(() => notice.remove(), 3000);
-
-// 4) Spiel-Code ins DOM schreiben
+// 3) Spiel-Code ins DOM schreiben
document.getElementById("gameId").textContent = gameId;
+// 4) Drawer toggles für Songliste
+const drawer = document.getElementById("songDrawer");
+document.getElementById("toggleSongList")?.addEventListener("click", () => drawer?.classList.toggle("open"));
+document.getElementById("closeDrawer")?.addEventListener("click", () => drawer?.classList.remove("open"));
+
// 5) WebSocket mit Reconnect-Logik
let socket;
function connectWebSocket() {
@@ -57,7 +69,7 @@ function connectWebSocket() {
socket.addEventListener("open", () => {
console.log("WebSocket connected. Requesting player list...");
socket.send(JSON.stringify({ type: "requestPlayers" }));
- setupStartRound(socket);
+ setupStartRound(socket); // deaktiviert den Start-Button beim Klicken
});
socket.addEventListener("message", async ({ data }) => {
@@ -66,7 +78,6 @@ function connectWebSocket() {
switch (msg.type) {
case "players":
- console.log("Empfangene Spieler:", msg.players);
renderList("#playersList", msg.players, username);
break;
case "reload":
@@ -106,13 +117,25 @@ const optionsDiv = document.getElementById("options");
const resultP = document.getElementById("result");
const scoreboard = document.getElementById("scoreboard");
-// 8) Funktion zum Anzeigen einer neuen Runde
-//let playLock = false;
+// --- SVG helpers für Pie-Slices ---
+const SVGNS = "http://www.w3.org/2000/svg";
+const VB = 1000; // viewBox 0..1000
+const CX = 500, CY = 500, R = 480;
-async function handleRoundStart({ownerOptions, songUri, allTracks, trackInfos}) {
+function polar(cx, cy, r, deg){
+ const rad = (deg - 90) * Math.PI/180; // 0° oben
+ return { x: cx + r * Math.cos(rad), y: cy + r * Math.sin(rad) };
+}
+function wedgePath(cx, cy, r, a0, a1){
+ const p0 = polar(cx, cy, r, a0);
+ const p1 = polar(cx, cy, r, a1);
+ const largeArc = ((a1 - a0 + 360) % 360) > 180 ? 1 : 0;
+ return `M ${cx} ${cy} L ${p0.x} ${p0.y} A ${r} ${r} 0 ${largeArc} 1 ${p1.x} ${p1.y} Z`;
+}
+
+// 6) Neue Runde anzeigen
+async function handleRoundStart({ ownerOptions, songUri, trackInfos }) {
// UI zurücksetzen
- //if (playLock) return; // Verhindert parallele Ausführung
- //playLock = true;
resultP.textContent = "";
optionsDiv.innerHTML = "";
songEmbed.innerHTML = "";
@@ -120,60 +143,103 @@ async function handleRoundStart({ownerOptions, songUri, allTracks, trackInfos})
// Song einbetten
const trackId = songUri.split(":")[2];
songEmbed.innerHTML = `
-
`;
+
`;
+ // Spotify-Playback (optional, wenn vorhanden)
if (window.playOnSpotify && typeof window.playOnSpotify === "function") {
- await window.playOnSpotify(songUri, username); // Warten bis fertig
+ await window.playOnSpotify(songUri, username);
}
- //if (window.playOnSpotify && typeof window.playOnSpotify === "function") {
- // window.playOnSpotify(songUri, username);
- //}
+ // Optionen als Tortenstücke (SVG) rendern
+ const svg = document.createElementNS(SVGNS, "svg");
+ svg.setAttribute("viewBox", `0 0 ${VB} ${VB}`);
+ svg.setAttribute("class", "options-svg");
+ optionsDiv.innerHTML = "";
+ optionsDiv.appendChild(svg);
- // Dynamische Kreisverteilung der Buttons
- // Warten, bis #options gerendert ist
- setTimeout(() => {
- const optsRect = optionsDiv.getBoundingClientRect();
- const radius = Math.min(optsRect.width, optsRect.height) / 2 - 50; // 50px Abstand zum Rand
+ const n = ownerOptions.length;
+ ownerOptions.forEach((user, i) => {
+ const a0 = (360 / n) * i;
+ const a1 = (360 / n) * (i + 1);
- ownerOptions.forEach((user, i) => {
- const btn = document.createElement("button");
- btn.textContent = user;
- btn.classList.add("player-option");
- const angle = 360 * i / ownerOptions.length;
- btn.style.transform = `rotate(${angle}deg) translateY(-${radius}px) rotate(${-angle}deg)`;
- btn.addEventListener("click", () => {
- socket.send(JSON.stringify({
- type: "guess",
- username: username,
- guess: user
- }));
- optionsDiv.querySelectorAll("button").forEach(b => b.disabled = true);
- });
- optionsDiv.appendChild(btn);
+ const path = document.createElementNS(SVGNS, "path");
+ path.setAttribute("d", wedgePath(CX, CY, R, a0, a1));
+ path.setAttribute("class", "wedge");
+ path.setAttribute("data-user", user);
+ path.addEventListener("click", () => {
+ socket.send(JSON.stringify({
+ type: "guess",
+ username: username,
+ guess: user
+ }));
+ svg.querySelectorAll(".wedge").forEach(w => w.classList.add("disabled"));
+ path.classList.add("selected");
});
- }, 0);
- startBtn.hidden = true;
- roundArea.hidden = false;
+ // Label mittig im Segment
+ const mid = (a0 + a1) / 2;
+ const P = polar(CX, CY, R * 0.58, mid);
+ const text = document.createElementNS(SVGNS, "text");
- const songList = document.getElementById("songList");
- songList.innerHTML = "";
- //trackinfos ist eine map bestehend aus aus Spielername und Liste von Track-Infos
- const userTracks = trackInfos?.[username] ?? [];
- userTracks.forEach(trackInfo => {
- const li = document.createElement("li");
- li.textContent = trackInfo;
- songList.appendChild(li);
+ const span = a1 - a0; // Winkelbreite des Segments
+ const font = Math.max(18, Math.min(42, 26 * (span / 60)));
+ text.setAttribute("font-size", font);
+ text.setAttribute("font-weight", "800");
+ // Bessere Lesbarkeit auf dunklem Slice
+ text.setAttribute("paint-order", "stroke");
+ text.setAttribute("stroke", "#000");
+ text.setAttribute("stroke-width", "2");
+ text.setAttribute("fill", "#fff");
+ // überschreibt CSS-Größe
+ text.setAttribute("x", P.x);
+ text.setAttribute("y", P.y);
+ text.setAttribute("class", "wedge-label");
+ text.setAttribute("text-anchor", "middle");
+ text.setAttribute("dominant-baseline", "middle");
+ text.textContent = user;
+
+ svg.appendChild(path);
+ svg.appendChild(text);
});
- //playLock = false;
+
+ // Start-Button ausblenden + Rundensektion einblenden
+ startBtn.hidden = true;
+ startBtn.disabled = true;
+ roundArea.hidden = false;
+
+ // Geladene Songs (beide Orte befüllen: Aside + Drawer)
+ const songList = document.getElementById("songList");
+ const songListArea = document.getElementById("songListArea");
+ const userTracks = trackInfos?.[username] ?? [];
+
+ if (songList) {
+ songList.innerHTML = "";
+ userTracks.forEach(t => {
+ const li = document.createElement("li");
+ li.textContent = t;
+ songList.appendChild(li);
+ });
+ }
+ if (songListArea) {
+ songListArea.innerHTML = "";
+ userTracks.forEach(t => {
+ // Erwartetes Format "Titel - Künstler"
+ const [title, artists] = String(t).split(" - ");
+ const row = document.createElement("div");
+ row.className = "track";
+ row.innerHTML = `
+
+
${esc(title ?? t)}${esc(artists ?? "")}
`;
+ songListArea.appendChild(row);
+ });
+ }
}
-// 9) Funktion zum Anzeigen des Ergebnisses
+// 7) Ergebnis + Weiter-Button
function renderScoreboard(scores) {
scoreboard.innerHTML = "";
Object.entries(scores).forEach(([user, pts]) => {
@@ -186,14 +252,35 @@ function renderScoreboard(scores) {
function handleRoundResult({ scores, guesses, owner }) {
renderScoreboard(scores);
+ try {
+ const wedges = document.querySelectorAll("#options .wedge");
+
+ // Owner-Slice immer grün
+ wedges.forEach(w => {
+ if (w.getAttribute("data-user") === owner) w.classList.add("correct");
+ });
+
+ // Nur die EIGENE Auswahl einfärben: rot wenn falsch, sonst grün
+ const myGuess = guesses?.[username];
+ if (myGuess) {
+ const myWedge = Array.from(wedges).find(w => w.getAttribute("data-user") === myGuess);
+ if (myWedge) {
+ if (myGuess === owner) {
+ myWedge.classList.add("correct");
+ } else {
+ myWedge.classList.add("wrong"); // nur dieser wird rot
+ }
+ }
+ }
+ } catch (e) {}
+
resultP.innerHTML = "";
- Object.entries(guesses).forEach(([user, guess]) => {
+ Object.entries(guesses || {}).forEach(([user, guess]) => {
const correct = guess === owner;
const icon = correct ? "✅" : "❌";
const delta = correct ? "+3" : "-1";
- const msg = `${icon} ${user} hat auf ${guess} getippt${correct ? " (richtig!)" : " (falsch)"} [${delta}]`;
- const p = document.createElement("p");
- p.textContent = msg;
+ const p = document.createElement("p");
+ p.textContent = `${icon} ${user} hat auf ${guess} getippt${correct ? " (richtig!)" : " (falsch)"} [${delta}]`;
resultP.appendChild(p);
});
@@ -205,24 +292,25 @@ function handleRoundResult({ scores, guesses, owner }) {
nextBtn.hidden = true;
nextBtn.disabled = true;
resultP.textContent = "";
- startBtn.hidden = true;
+ startBtn.hidden = true;
startBtn.disabled = true;
- roundArea.hidden = true;
+ roundArea.hidden = true;
};
}
-function handleGameEnd({winner}) {
+
+// 8) Spielende -> Start-Button zurück
+function handleGameEnd({ winner }) {
resultP.textContent = `🎉 ${winner} hat gewonnen!`;
setTimeout(() => {
startBtn.hidden = false;
- roundArea.hidden = true;
startBtn.disabled = false;
+ roundArea.hidden = true;
scoreboard.innerHTML = "";
}, 6000);
}
-// Spotify-Playback Funktion
-// game.js
+// Spotify-Playback Funktion (unverändert)
async function playOnSpotify(trackUri, username) {
const deviceId = document.getElementById("deviceSelect")?.value;
if (!deviceId) {
diff --git a/src/main/resources/public/js/lobby.js b/src/main/resources/public/js/lobby.js
index 99df629..a4c44ab 100644
--- a/src/main/resources/public/js/lobby.js
+++ b/src/main/resources/public/js/lobby.js
@@ -12,8 +12,13 @@ document.getElementById("createGame").addEventListener("click", () => {
window.location.href = `/create-game.html?username=${encodeURIComponent(username)}`;
});
-document.getElementById("joinGame").addEventListener("click", () => {
- window.location.href = username
- ? `/join-game.html?username=${encodeURIComponent(username)}`
- : "/join-game.html";
+//document.getElementById("joinGame").addEventListener("click", () => {
+ // window.location.href = username
+ // ? `/join-game.html?username=${encodeURIComponent(username)}`
+ // : "/join-game.html";
+//});
+// NEU: statt Redirect -> Fokus auf Eingabe
+document.getElementById("joinGame")?.addEventListener("click", () => {
+ const input = document.getElementById("gameId");
+ if (input) { input.focus(); input.select(); }
});
diff --git a/src/main/resources/public/lobby.html b/src/main/resources/public/lobby.html
index 63d7e23..32eabcf 100644
--- a/src/main/resources/public/lobby.html
+++ b/src/main/resources/public/lobby.html
@@ -1,15 +1,231 @@
-
-
Lobby – Spotify Roulette
+
+
+
Spotify Roulette – Lobby
+
+
+
+
-
Lobby
-
Was möchtest du tun?
-
-
+
+
+
+ Lobby
+ Angemeldet als —
+
+
+
+
+
Neues Spiel
+
+
+
+
Spiel erstellenStarte eine neue Runde und teile den Code.
+
+
+
+
+
+
+
Spiel beitreten
+
+
+
+
Bestehendem Spiel beitretenDu hast schon einen Code? Gib ihn unten ein.
+
+
+
+
+
+
Tipp: Code ist vierstellig (z. B. 6767).
+
+
+
+
+
+
+
+
+
+
+
+