diff --git a/src/server-deno/presentation/WebSocketServer.ts b/src/server-deno/presentation/WebSocketServer.ts index 7a212d5..5b1a881 100644 --- a/src/server-deno/presentation/WebSocketServer.ts +++ b/src/server-deno/presentation/WebSocketServer.ts @@ -508,7 +508,13 @@ export class WebSocketServer { const timeline = room.state.timeline[player.id] || []; if (result.correct && timeline.length >= room.state.goal) { room.state.status = GameStatus.ENDED; - this.broadcast(room, WS_EVENTS.GAME_ENDED, { winner: player.id }); + const winnerPlayer = room.players.get(player.id); + this.broadcast(room, WS_EVENTS.GAME_ENDED, { + winner: player.id, + winnerName: winnerPlayer?.name || player.id.slice(0, 4), + score: timeline.length, + timeline: timeline, + }); } } catch (error) { logger.error(`Place guess error: ${error}`); @@ -632,7 +638,15 @@ export class WebSocketServer { const track = await this.gameService.drawNextTrack(room); if (!track) { - this.broadcast(room, WS_EVENTS.GAME_ENDED, { winner: this.gameService.getWinner(room) }); + const winnerId = this.gameService.getWinner(room); + const winnerPlayer = winnerId ? room.players.get(winnerId) : null; + const winnerTimeline = winnerId ? (room.state.timeline[winnerId] || []) : []; + this.broadcast(room, WS_EVENTS.GAME_ENDED, { + winner: winnerId, + winnerName: winnerPlayer?.name || (winnerId ? winnerId.slice(0, 4) : 'Unknown'), + score: winnerTimeline.length, + timeline: winnerTimeline, + }); this.stopSyncTimer(room); return; } diff --git a/src/server-deno/public/index.html b/src/server-deno/public/index.html index 01ad500..b350b48 100644 --- a/src/server-deno/public/index.html +++ b/src/server-deno/public/index.html @@ -32,6 +32,55 @@ #dashboard[open] .dashboard-chevron { transform: rotate(90deg); } + + /* Winner popup animations */ + @keyframes confetti-fall { + 0% { + transform: translateY(0) rotate(0deg); + opacity: 1; + } + + 100% { + transform: translateY(100vh) rotate(720deg); + opacity: 0; + } + } + + @keyframes popup-entrance { + 0% { + transform: scale(0.5) translateY(20px); + opacity: 0; + } + + 50% { + transform: scale(1.05); + } + + 100% { + transform: scale(1) translateY(0); + opacity: 1; + } + } + + @keyframes bounce-slow { + + 0%, + 100% { + transform: translateX(-50%) translateY(0); + } + + 50% { + transform: translateX(-50%) translateY(-10px); + } + } + + .animate-popup { + animation: popup-entrance 0.5s ease-out forwards; + } + + .animate-bounce-slow { + animation: bounce-slow 1.5s ease-in-out infinite; + } diff --git a/src/server-deno/public/js/handlers.js b/src/server-deno/public/js/handlers.js index e00fdda..a695d22 100644 --- a/src/server-deno/public/js/handlers.js +++ b/src/server-deno/public/js/handlers.js @@ -167,7 +167,107 @@ export function handleReveal(msg) { } export function handleGameEnded(msg) { - alert(`Gewinner: ${shortName(msg.winner)}`); + // Create and show the winner popup with confetti + showWinnerPopup(msg); +} + +function showWinnerPopup(msg) { + const winnerName = msg.winnerName || shortName(msg.winner); + const score = msg.score ?? 0; + const timeline = msg.timeline || []; + + // Create the popup overlay + const overlay = document.createElement('div'); + overlay.id = 'winnerOverlay'; + overlay.className = 'fixed inset-0 z-50 flex items-center justify-center p-4'; + overlay.style.cssText = 'background: rgba(0,0,0,0.75); backdrop-filter: blur(4px);'; + + // Create timeline cards HTML + const timelineHtml = timeline.length > 0 + ? timeline.map(t => ` +
Keine Karten
'; + + overlay.innerHTML = ` + +${escapeHtmlSimple(winnerName)}
+Score: ${score} Karten
+