import { $answerResult, $guessArtist, $guessTitle, $npArtist, $npTitle, $npYear, } from "./dom.js"; import { state } from "./state.js"; import { cacheLastRoomId, cacheSessionId, clearSessionId, sendMsg } from "./ws.js"; import { renderRoom } from "./render.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 { if (r?.players?.length === 1) { const only = r.players[0]; if (only && only.id && only.id !== state.playerId) { state.playerId = only.id; try { localStorage.setItem("playerId", only.id); } catch { } } } } catch { } } function shortName(id) { if (!id) return "-"; const p = state.room?.players.find((x) => x.id === id); return p ? p.name : id.slice(0, 4); } export function handleConnected(msg) { state.playerId = msg.playerId; try { if (msg.playerId) localStorage.setItem("playerId", msg.playerId); } catch { } if (msg.sessionId) { const existing = localStorage.getItem("sessionId"); if (!existing) cacheSessionId(msg.sessionId); } // lazy import to avoid cycle import("./session.js").then(({ reusePlayerName, reconnectLastRoom }) => { reusePlayerName(); reconnectLastRoom(); }); if (state.room) { try { updatePlayerIdFromRoom(state.room); renderRoom(state.room); } catch { } } } export function handleRoomUpdate(msg) { if (msg?.room?.id) cacheLastRoomId(msg.room.id); const r = msg.room; updatePlayerIdFromRoom(r); renderRoom(r); } export function handlePlayTrack(msg) { const t = msg.track; state.lastTrack = t; state.revealed = false; $npTitle.textContent = "???"; $npArtist.textContent = ""; $npYear.textContent = ""; if ($guessTitle) $guessTitle.value = ""; if ($guessArtist) $guessArtist.value = ""; if ($answerResult) { $answerResult.textContent = ""; $answerResult.className = "mt-1 text-sm"; } // Load track with Howler, passing the filename for format detection const sound = loadTrack(t.url, t.file); const pf = document.getElementById("progressFill"); if (pf) pf.style.width = "0%"; const rd = document.getElementById("recordDisc"); if (rd) { rd.classList.remove("spin-record"); rd.src = "/hitstar.png"; } const { startAt, serverNow } = msg; const now = Date.now(); const offsetMs = startAt - serverNow; const localStart = now + offsetMs; const delay = Math.max(0, localStart - now); setTimeout(() => { if (sound && sound === getSound() && !sound.playing()) { sound.play(); const disc = document.getElementById("recordDisc"); if (disc) disc.classList.add("spin-record"); } }, 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); } export function handleSync(msg) { applySync(msg.startAt, msg.serverNow); } export function handleControl(msg) { const { action, startAt, serverNow } = msg; const sound = getSound(); if (!sound) return; if (action === "pause") { sound.pause(); const disc = document.getElementById("recordDisc"); if (disc) disc.classList.remove("spin-record"); sound.rate(1.0); } else if (action === "play") { if (startAt && serverNow) { const now = Date.now(); const elapsed = (now - startAt) / 1000; sound.seek(Math.max(0, elapsed)); } sound.play(); const disc = document.getElementById("recordDisc"); if (disc) disc.classList.add("spin-record"); } } export function handleReveal(msg) { const { result, track } = msg; $npTitle.textContent = track.title || track.id || "Track"; $npArtist.textContent = track.artist ? ` – ${track.artist}` : ""; $npYear.textContent = track.year ? ` (${track.year})` : ""; state.revealed = true; const $rb = document.getElementById("revealBanner"); if ($rb) { if (result.correct) { $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 const rd = document.getElementById("recordDisc"); if (rd && track?.file) { // Use track.file instead of track.id to include playlist folder prefix const coverUrl = `/cover/${encodeURIComponent(track.file)}`; const coverUrlWithTimestamp = `${coverUrl}?t=${Date.now()}`; const img = new Image(); img.onload = () => { rd.src = coverUrlWithTimestamp; }; img.onerror = () => { /* keep default logo */ }; img.src = coverUrlWithTimestamp; } } export function handleGameEnded(msg) { // Play winner fanfare playWinner(); // 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