import { $audio, $copyRoomCode, $leaveRoom, $nameDisplay, $nameLobby, $npArtist, $npTitle, $npYear, $pauseBtn, $placeBtn, $readyChk, $roomId, $roomCode, $slotSelect, $startGame, $volumeSlider, $playBtn, $nextBtn, $createRoom, $joinRoom, $lobby, $room, $setNameLobby, $guessTitle, $guessArtist, $answerResult } from './dom.js'; import { state } from './state.js'; import { connectWS, sendMsg } from './ws.js'; import { renderRoom } from './render.js'; import { initAudioUI, applySync } from './audio.js'; function showToast(msg) { const el = document.getElementById('toast'); if (!el) { return; } el.textContent = msg; el.style.opacity = '1'; setTimeout(() => { el.style.opacity = '0'; }, 1200); } function handleConnected(msg) { state.playerId = msg.playerId; const stored = localStorage.getItem('playerName'); if (stored) { if ($nameLobby && $nameLobby.value !== stored) { $nameLobby.value = stored; } if ($nameDisplay) { $nameDisplay.textContent = stored; } sendMsg({ type: 'set_name', name: stored }); } if (state.room?.id) { sendMsg({ type: 'join_room', code: state.room.id }); } } function handleRoomUpdate(msg) { renderRoom(msg.room); } function handlePlayTrack(msg) { const t = msg.track; state.lastTrack = t; state.revealed = false; $npTitle.textContent = '???'; $npArtist.textContent = ''; $npYear.textContent = ''; // reset answer UI if ($guessTitle) $guessTitle.value = ''; if ($guessArtist) $guessArtist.value = ''; if ($answerResult) { $answerResult.textContent=''; $answerResult.className='mt-1 text-sm'; } try { $audio.preload = 'auto'; } catch {} $audio.src = t.url; const pf = document.getElementById('progressFill'); if (pf) { pf.style.width = '0%'; } const rd = document.getElementById('recordDisc'); if (rd) { rd.classList.remove('spin-record'); } const { startAt, serverNow } = msg; const now = Date.now(); const offsetMs = startAt - serverNow; const localStart = now + offsetMs; const delay = Math.max(0, localStart - now); setTimeout(() => { $audio.currentTime = 0; $audio.play().catch(() => {}); const disc = document.getElementById('recordDisc'); if (disc) { disc.classList.add('spin-record'); } }, delay); if (state.room) { renderRoom(state.room); } } function handleSync(msg) { applySync(msg.startAt, msg.serverNow); } function handleControl(msg) { const { action, startAt, serverNow } = msg; if (action === 'pause') { $audio.pause(); const disc = document.getElementById('recordDisc'); if (disc) { disc.classList.remove('spin-record'); } $audio.playbackRate = 1.0; } else if (action === 'play') { if (startAt && serverNow) { const now = Date.now(); const elapsed = (now - startAt) / 1000; $audio.currentTime = Math.max(0, elapsed); } $audio.play().catch(() => {}); const disc = document.getElementById('recordDisc'); if (disc) { disc.classList.add('spin-record'); } } } 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'; } else { $rb.textContent = 'Falsch!'; $rb.className = 'inline-block rounded-md bg-rose-600 text-white px-3 py-1 text-sm font-medium'; } } const $placeArea = document.getElementById('placeArea'); if ($placeArea) { $placeArea.classList.add('hidden'); } } function handleGameEnded(msg) { alert(`Gewinner: ${shortName(msg.winner)}`); } function onMessage(ev) { const msg = JSON.parse(ev.data); switch (msg.type) { case 'connected': handleConnected(msg); break; case 'room_update': handleRoomUpdate(msg); break; case 'play_track': handlePlayTrack(msg); break; case 'sync': handleSync(msg); break; case 'control': handleControl(msg); break; case 'reveal': handleReveal(msg); break; case 'game_ended': handleGameEnded(msg); break; case 'answer_result': { if ($answerResult) { if (!msg.ok) { $answerResult.textContent = '⛔ Eingabe ungültig oder gerade nicht möglich'; $answerResult.className = 'mt-1 text-sm text-rose-600'; } else { const okBoth = !!(msg.correctTitle && msg.correctArtist); const parts = []; 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)'; $answerResult.textContent = `${parts.join(' · ')}${coin}`; $answerResult.className = okBoth ? 'mt-1 text-sm text-emerald-600' : 'mt-1 text-sm text-amber-600'; } } break; } default: break; } } function shortName(id) { if (!id) return '-'; const p = state.room?.players.find((x) => x.id === id); return p ? p.name : id.slice(0, 4); } function wire(el, type, handler) { if (el) { el.addEventListener(type, handler); } } function wireUi() { initAudioUI(); wire($setNameLobby, 'click', () => { const name = $nameLobby.value.trim(); if (!name) return; localStorage.setItem('playerName', name); if ($nameDisplay) { $nameDisplay.textContent = name; } sendMsg({ type: 'set_name', name }); }); wire($createRoom, 'click', () => sendMsg({ type: 'create_room' })); wire($joinRoom, 'click', () => { const code = $roomCode.value.trim(); if (code) { sendMsg({ type: 'join_room', code }); } }); wire($leaveRoom, 'click', () => { sendMsg({ type: 'leave_room' }); state.room = null; $lobby.classList.remove('hidden'); $room.classList.add('hidden'); }); wire($startGame, 'click', () => sendMsg({ type: 'start_game' })); wire($readyChk, 'change', (e) => { const val = !!e.target.checked; state.pendingReady = val; sendMsg({ type: 'set_ready', ready: val }); }); wire($placeBtn, 'click', () => { const slot = parseInt($slotSelect.value, 10); sendMsg({ type: 'place_guess', slot }); }); wire($playBtn, 'click', () => sendMsg({ type: 'player_control', action: 'play' })); wire($pauseBtn, 'click', () => sendMsg({ type: 'player_control', action: 'pause' })); wire($nextBtn, 'click', () => sendMsg({ type: 'next_track' })); if ($volumeSlider && $audio) { try { $volumeSlider.value = String($audio.volume ?? 1); } catch {} $volumeSlider.addEventListener('input', () => { $audio.volume = parseFloat($volumeSlider.value); }); } if ($copyRoomCode) { $copyRoomCode.style.display = 'inline-block'; wire($copyRoomCode, 'click', () => { if (state.room?.id) { navigator.clipboard.writeText(state.room.id).then(() => { $copyRoomCode.textContent = '✔️'; showToast('Code kopiert!'); setTimeout(() => { $copyRoomCode.textContent = '📋'; }, 1200); }); } }); } if ($roomId) { wire($roomId, 'click', () => { if (state.room?.id) { navigator.clipboard.writeText(state.room.id).then(() => { $roomId.title = 'Kopiert!'; showToast('Code kopiert!'); setTimeout(() => { $roomId.title = 'Klicken zum Kopieren'; }, 1200); }); } }); $roomId.style.cursor = 'pointer'; } // Answer submit const form = document.getElementById('answerForm'); if (form) { form.addEventListener('submit', (e) => { e.preventDefault(); const title = ($guessTitle?.value || '').trim(); const artist = ($guessArtist?.value || '').trim(); if (!title || !artist) { if ($answerResult) { $answerResult.textContent = 'Bitte Titel und Künstler eingeben'; $answerResult.className = 'mt-1 text-sm text-amber-600'; } return; } sendMsg({ type: 'submit_answer', guess: { title, artist } }); }); } } // boot wireUi(); connectWS(onMessage); // restore name immediately if present (() => { const saved = localStorage.getItem('playerName'); if (saved) { if ($nameLobby && $nameLobby.value !== saved) { $nameLobby.value = saved; } if ($nameDisplay) { $nameDisplay.textContent = saved; } } })();