feat: Implement initial client-side UI rendering for game rooms, player dashboards, and track timelines, alongside core WebSocket server functionality.
All checks were successful
Build and Push Docker Image / docker (push) Successful in 13s

This commit is contained in:
2026-01-04 18:24:06 +01:00
parent a1f1b41987
commit 9373726347
8 changed files with 466 additions and 21 deletions

View File

@@ -9,7 +9,9 @@ import {
import { state } from "./state.js";
import { cacheLastRoomId, cacheSessionId, clearSessionId, sendMsg } from "./ws.js";
import { renderRoom } from "./render.js";
import { applySync, loadTrack, getSound } from "./audio.js";
import { applySync, loadTrack, getSound, stopAudioPlayback } from "./audio.js";
import { playCorrect, playWrong, playWinner, playTurnStart, playCoinEarned } from "./sfx.js";
import { showReaction, celebrateCorrect } from "./reactions.js";
function updatePlayerIdFromRoom(r) {
try {
@@ -101,6 +103,11 @@ export function handlePlayTrack(msg) {
}
}, delay);
// Play turn start sound if it's the current player's turn
if (state.room?.state?.currentGuesser === state.playerId) {
playTurnStart();
}
if (state.room) renderRoom(state.room);
}
@@ -143,10 +150,13 @@ export function handleReveal(msg) {
$rb.textContent = "Richtig!";
$rb.className =
"inline-block rounded-md bg-emerald-600 text-white px-3 py-1 text-sm font-medium";
playCorrect();
celebrateCorrect(); // Auto 🎉 animation
} else {
$rb.textContent = "Falsch!";
$rb.className =
"inline-block rounded-md bg-rose-600 text-white px-3 py-1 text-sm font-medium";
playWrong();
}
}
// Note: placeArea visibility is now controlled by renderRoom() based on game phase
@@ -167,6 +177,8 @@ export function handleReveal(msg) {
}
export function handleGameEnded(msg) {
// Play winner fanfare
playWinner();
// Create and show the winner popup with confetti
showWinnerPopup(msg);
}
@@ -208,9 +220,14 @@ function showWinnerPopup(msg) {
${timelineHtml}
</div>
</div>
<button id="closeWinnerBtn" class="mt-6 px-6 py-3 bg-white text-indigo-600 font-bold rounded-xl hover:bg-indigo-100 transition-colors shadow-lg">
Schließen
</button>
<div class="mt-6 flex flex-col sm:flex-row gap-3 justify-center">
<button id="rematchBtn" class="px-6 py-3 bg-emerald-500 text-white font-bold rounded-xl hover:bg-emerald-600 transition-colors shadow-lg flex items-center justify-center gap-2">
🔄 Rematch
</button>
<button id="closeWinnerBtn" class="px-6 py-3 bg-white text-indigo-600 font-bold rounded-xl hover:bg-indigo-100 transition-colors shadow-lg">
Schließen
</button>
</div>
</div>
`;
@@ -219,6 +236,13 @@ function showWinnerPopup(msg) {
// Start confetti animation
createConfetti();
// Rematch button handler
document.getElementById('rematchBtn').addEventListener('click', () => {
stopAudioPlayback(); // Stop the music
sendMsg({ type: 'rematch' });
overlay.remove();
});
// Close button handler
document.getElementById('closeWinnerBtn').addEventListener('click', () => {
overlay.remove();
@@ -333,8 +357,12 @@ export function onMessage(ev) {
parts.push(msg.correctTitle ? "Titel ✓" : "Titel ✗");
parts.push(msg.correctArtist ? "Künstler ✓" : "Künstler ✗");
let coin = "";
if (msg.awarded) coin = " +1 Token";
else if (msg.alreadyAwarded) coin = " (bereits erhalten)";
if (msg.awarded) {
coin = " +1 Token";
playCoinEarned();
} else if (msg.alreadyAwarded) {
coin = " (bereits erhalten)";
}
$answerResult.textContent = `${parts.join(" · ")}${coin}`;
$answerResult.className = okBoth
? "mt-1 text-sm text-emerald-600"
@@ -345,5 +373,10 @@ export function onMessage(ev) {
}
default:
return;
case "reaction": {
// Show reaction from another player
showReaction(msg.emoji, msg.playerName);
return;
}
}
}