Files
quizify/templates/quiz_buzzer_multiplayer.html
Simon cc07d21138 modified: templates/quiz_buzzer_multiplayer.html
modified:   templates/team_setup.html
2025-12-13 19:15:52 +01:00

847 lines
32 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html>
<head>
<title>{{ translations['quiz_title'] }} Buzzer Multiplayer</title>
<script src="https://sdk.scdn.co/spotify-player.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #0a0e27 0%, #1a1f3a 50%, #0f1419 100%);
color: #e0e0e0;
min-height: 100vh;
padding: 20px;
display: flex;
flex-direction: column;
align-items: center;
}
.quiz-container {
max-width: 1200px;
width: 100%;
background: rgba(25, 30, 45, 0.95);
border-radius: 20px;
padding: 40px;
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.5);
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.05);
}
.scoreboard {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
margin-bottom: 30px;
}
.player-card {
background: rgba(15, 20, 25, 0.8);
border-radius: 12px;
padding: 20px;
border: 2px solid rgba(29, 185, 84, 0.3);
transition: all 0.3s ease;
}
.player-card.active {
border-color: #1DB954;
box-shadow: 0 0 20px rgba(29, 185, 84, 0.5);
}
.player-card.buzzed {
border-color: #f44336;
box-shadow: 0 0 20px rgba(244, 67, 54, 0.5);
}
.player-name {
font-size: 1.3em;
font-weight: bold;
color: #1DB954;
margin-bottom: 10px;
}
.player-score {
font-size: 2em;
font-weight: bold;
color: #e0e0e0;
}
.buzzer-timer {
text-align: center;
font-size: 4em;
font-weight: bold;
color: #1DB954;
margin: 30px 0;
text-shadow: 0 0 20px rgba(29, 185, 84, 0.5);
}
.buzzer-timer.warning {
color: #FFA500;
animation: pulse 1s infinite;
}
.buzzer-timer.critical {
color: #f44336;
animation: pulse 0.5s infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; transform: scale(1); }
50% { opacity: 0.7; transform: scale(1.05); }
}
.points-display {
text-align: center;
font-size: 2.5em;
font-weight: bold;
margin: 20px 0;
}
.points-display.high {
color: #4CAF50;
}
.points-display.medium {
color: #FFA500;
}
.points-display.low {
color: #f44336;
}
.buzzer-grid {
display: flex;
justify-content: center;
gap: 20px;
margin: 20px 0;
flex-wrap: wrap;
}
.player-buzzer {
width: 180px;
height: 180px;
font-size: 1.4em;
font-weight: bold;
text-transform: uppercase;
letter-spacing: 2px;
background: linear-gradient(135deg, #f44336 0%, #d32f2f 100%);
color: white;
border: 3px solid rgba(244, 67, 54, 0.5);
border-radius: 50%;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 10px 40px rgba(244, 67, 54, 0.5);
display: flex;
align-items: center;
justify-content: center;
text-align: center;
}
.player-buzzer:hover:not(.disabled) {
transform: scale(1.05);
box-shadow: 0 15px 50px rgba(244, 67, 54, 0.7);
}
.player-buzzer:active:not(.disabled) {
transform: scale(0.95);
}
.player-buzzer.disabled {
background: linear-gradient(135deg, #666 0%, #444 100%);
border-color: rgba(100, 100, 100, 0.3);
cursor: not-allowed;
opacity: 0.5;
}
.player-buzzer.start {
width: 200px;
height: 200px;
background: linear-gradient(135deg, #1DB954 0%, #1ed760 100%);
border-color: rgba(29, 185, 84, 0.3);
box-shadow: 0 10px 40px rgba(29, 185, 84, 0.5);
}
.player-buzzer.start:hover {
box-shadow: 0 15px 50px rgba(29, 185, 84, 0.7);
}
.answer-section {
display: none;
text-align: center;
margin-top: 30px;
}
.answer-section.active {
display: block;
animation: slideIn 0.3s ease;
}
@keyframes slideIn {
from { opacity: 0; transform: translateY(-10px); }
to { opacity: 1; transform: translateY(0); }
}
.btn {
padding: 12px 24px;
margin: 5px;
background: linear-gradient(135deg, #1DB954 0%, #1ed760 100%);
color: white;
border: none;
border-radius: 25px;
cursor: pointer;
font-size: 1em;
font-weight: 600;
transition: all 0.3s ease;
box-shadow: 0 4px 15px rgba(29, 185, 84, 0.3);
}
.btn:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(29, 185, 84, 0.5);
}
.btn-secondary {
background: linear-gradient(135deg, #535353 0%, #6b6b6b 100%);
box-shadow: 0 4px 15px rgba(83, 83, 83, 0.3);
}
.btn-danger {
background: linear-gradient(135deg, #f44336 0%, #e57373 100%);
}
input[type="text"] {
padding: 14px 20px;
width: 100%;
max-width: 400px;
border: 2px solid rgba(29, 185, 84, 0.3);
background: rgba(15, 20, 35, 0.6);
color: #e0e0e0;
border-radius: 25px;
font-size: 16px;
transition: all 0.3s ease;
outline: none;
}
input[type="text"]:focus {
border-color: #1DB954;
box-shadow: 0 0 15px rgba(29, 185, 84, 0.4);
}
.result-container {
margin: 25px 0;
padding: 20px;
border-radius: 15px;
}
.search-results {
position: relative;
max-width: 400px;
margin: 10px auto;
background: rgba(15, 20, 35, 0.95);
border-radius: 12px;
max-height: 300px;
overflow-y: auto;
display: none;
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.4);
}
.search-item {
padding: 12px 20px;
cursor: pointer;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
transition: background 0.2s;
}
.search-item:hover {
background: rgba(29, 185, 84, 0.2);
}
.search-item:last-child {
border-bottom: none;
}
.current-player {
text-align: center;
font-size: 1.5em;
color: #1DB954;
margin: 20px 0;
font-weight: bold;
}
</style>
<script>
let allTracks = {{ all_tracks|tojson }};
let currentGameMode = "{{ game_mode }}";
let correctAnswer = "";
const i18n = {{ translations|tojson }};
let buzzTimer = null;
let startTime = null;
let buzzTime = null;
let maxPoints = parseInt(localStorage.getItem('buzzer_max_points')) || 1000;
let gracePeriod = parseInt(localStorage.getItem('buzzer_grace_period')) || 5;
let decayRate = parseInt(localStorage.getItem('buzzer_decay_rate')) || 50;
let gameStarted = false;
let currentBuzzer = null;
let buzzedPlayers = []; // Liste der Spieler die schon gebuzzert haben
let canBuzz = false; // Kann man buzzern?
let pendingPoints = 0; // Punkte die noch nicht gezählt wurden
// Lade Team-Namen und Anzahl
const teamCount = parseInt(localStorage.getItem('team_count')) || 4;
const teamNames = JSON.parse(localStorage.getItem('team_names') || '["Spieler 1", "Spieler 2", "Spieler 3", "Spieler 4"]');
const buzzerMode = localStorage.getItem('buzzer_mode') || 'central';
const keyMappings = JSON.parse(localStorage.getItem('key_mappings') || '{"1":"Q","2":"W","3":"E","4":"R"}');
// Spieler-Daten
let players = [
{ id: 1, name: teamNames[0] || 'Spieler 1', score: {{ player_scores[0] if player_scores else 0 }} },
{ id: 2, name: teamNames[1] || 'Spieler 2', score: {{ player_scores[1] if player_scores else 0 }} },
{ id: 3, name: teamNames[2] || 'Spieler 3', score: {{ player_scores[2] if player_scores else 0 }} },
{ id: 4, name: teamNames[3] || 'Spieler 4', score: {{ player_scores[3] if player_scores else 0 }} }
];
// Tastatur-Events für beide Modi
document.addEventListener('keydown', function(e) {
const active = document.activeElement;
const isInput = active && (active.tagName === 'INPUT' || active.tagName === 'TEXTAREA');
if (isInput) return; // Keine Aktionen in Eingabefeldern
const key = e.key.toUpperCase();
if (!gameStarted) {
// Im persönlichen Modus: Nur die erste Spieler-Taste startet
if (buzzerMode === 'personal' && key === keyMappings['1']) {
e.preventDefault();
startGame();
}
// Im zentralen Modus: Leertaste startet
else if (buzzerMode === 'central' && e.code === 'Space') {
e.preventDefault();
startGame();
}
} else if (canBuzz && !currentBuzzer) {
// Persönlicher Modus: Jede Spieler-Taste buzzert direkt
if (buzzerMode === 'personal') {
for (let i = 1; i <= teamCount; i++) {
if (key === keyMappings[i.toString()] && !buzzedPlayers.includes(i)) {
e.preventDefault();
triggerBuzzPersonal(i);
return;
}
}
}
// Zentraler Modus: Leertaste -> Spielerauswahl
else if (buzzerMode === 'central' && e.code === 'Space') {
e.preventDefault();
triggerBuzz();
}
}
});
window.onSpotifyWebPlaybackSDKReady = () => {
const token = '{{ access_token }}';
const player = new Spotify.Player({
name: 'Musik Quiz Buzzer Multiplayer',
getOAuthToken: cb => { cb(token); },
volume: 0.5
});
player.addListener('initialization_error', ({ message }) => { console.error(message); });
player.addListener('authentication_error', ({ message }) => { console.error(message); });
player.addListener('account_error', ({ message }) => { console.error(message); });
player.addListener('playback_error', ({ message }) => { console.error(message); });
player.addListener('ready', ({ device_id }) => {
console.log('Ready with Device ID', device_id);
document.getElementById('device_id').value = device_id;
window.deviceId = device_id;
setCorrectAnswer();
initializePlayers();
updateScoreboard();
});
player.addListener('player_state_changed', state => {
if (!state) return;
if (!state.paused && gameStarted && !startTime) {
console.log('Music started playing, starting timer in 1 second');
setTimeout(() => {
startBuzzerTimer();
}, 1000);
}
});
player.addListener('not_ready', ({ device_id }) => {
console.log('Device ID has gone offline', device_id);
});
player.connect();
window.spotifyPlayer = player;
};
function initializePlayers() {
// Update Spieler-Namen und verstecke inaktive Spieler
for (let i = 1; i <= 4; i++) {
const card = document.getElementById(`player${i}`);
const selectBtn = document.getElementById(`selectPlayer${i}`);
if (i <= teamCount) {
card.querySelector('.player-name').textContent = players[i - 1].name;
card.style.display = 'block';
if (selectBtn) {
selectBtn.textContent = players[i - 1].name;
selectBtn.style.display = 'block';
}
} else {
card.style.display = 'none';
if (selectBtn) {
selectBtn.style.display = 'none';
}
}
}
// Update Buzzer-Hint basierend auf Modus
const hintElement = document.getElementById('buzzerHint');
if (buzzerMode === 'personal') {
let hintText = '⌨️ Buzzer-Tasten: ';
for (let i = 1; i <= teamCount; i++) {
hintText += `${players[i-1].name}=[${keyMappings[i.toString()]}] `;
}
hintElement.innerHTML = hintText;
} else {
hintElement.innerHTML = '⌨️ Drücke LEERTASTE zum Buzzern!';
}
}
function updateScoreboard() {
players.forEach((player, index) => {
const card = document.getElementById(`player${player.id}`);
if (card) {
card.querySelector('.player-score').textContent = player.score;
}
});
}
function startGame() {
if (gameStarted) return;
gameStarted = true;
var startBtn = document.getElementById('startButton');
if (startBtn) {
startBtn.classList.add('disabled');
startBtn.style.cursor = 'wait';
startBtn.style.display = 'none';
}
document.getElementById('buzzerHint').style.display = 'block';
const device_id = window.deviceId;
const startPosition = getOption('startPosition', 'start');
let position_ms = 0;
if (startPosition === 'random') {
const duration = {{ track.duration_ms if track.duration_ms else 180000 }};
position_ms = Math.floor(Math.random() * (duration - 30000));
}
fetch(`/play_track?device_id=${device_id}&track_uri={{ track.uri }}&position_ms=${position_ms}`, { method: 'POST' })
.then(response => response.json())
.then(() => {
console.log('Play command sent');
canBuzz = true;
})
.catch(error => console.error('Error starting playback:', error));
}
function startBuzzerTimer() {
startTime = Date.now();
canBuzz = true;
updateTimer();
}
function updateTimer() {
// Stoppe Timer wenn nicht mehr gebuzzert werden kann (während Spielerauswahl oder Antwort)
if (!canBuzz) return;
const elapsed = (Date.now() - startTime) / 1000;
const timerDisplay = document.getElementById('buzzerTimer');
const pointsDisplay = document.getElementById('pointsDisplay');
timerDisplay.textContent = elapsed.toFixed(2) + 's';
const currentPoints = calculatePoints(elapsed);
pointsDisplay.textContent = currentPoints + ' Punkte';
timerDisplay.className = 'buzzer-timer';
pointsDisplay.className = 'points-display';
if (elapsed > gracePeriod + 10) {
timerDisplay.classList.add('critical');
pointsDisplay.classList.add('low');
} else if (elapsed > gracePeriod) {
timerDisplay.classList.add('warning');
pointsDisplay.classList.add('medium');
} else {
pointsDisplay.classList.add('high');
}
buzzTimer = requestAnimationFrame(updateTimer);
}
function calculatePoints(elapsed) {
if (elapsed <= gracePeriod) {
return maxPoints;
}
const overtime = elapsed - gracePeriod;
const points = Math.max(0, maxPoints - Math.floor(overtime * decayRate));
return points;
}
function triggerBuzz() {
if (currentBuzzer !== null) return;
if (!canBuzz) return;
canBuzz = false; // Verhindere weitere Buzzes
buzzTime = Date.now();
const elapsed = (buzzTime - startTime) / 1000;
// Stoppe Timer-Updates
cancelAnimationFrame(buzzTimer);
// Friere Timer-Anzeige ein
const timerDisplay = document.getElementById('buzzerTimer');
const pointsDisplay = document.getElementById('pointsDisplay');
timerDisplay.textContent = elapsed.toFixed(2) + 's';
const points = calculatePoints(elapsed);
pointsDisplay.textContent = points + ' Punkte';
// Speichere die Zeit beim Buzzern für spätere Berechnung
window.pausedAt = Date.now();
if (window.spotifyPlayer) {
window.spotifyPlayer.pause();
}
// Zeige Spielerauswahl
document.getElementById('playerSelection').style.display = 'block';
document.getElementById('currentPlayer').textContent = 'Wer hat gebuzzert?';
pendingPoints = points;
}
function triggerBuzzPersonal(playerId) {
if (currentBuzzer !== null) return;
if (!canBuzz) return;
if (buzzedPlayers.includes(playerId)) {
alert(`${players[playerId - 1].name} hat bereits gebuzzert!`);
return;
}
canBuzz = false; // Verhindere weitere Buzzes
buzzTime = Date.now();
const elapsed = (buzzTime - startTime) / 1000;
// Stoppe Timer-Updates
cancelAnimationFrame(buzzTimer);
// Friere Timer-Anzeige ein
const timerDisplay = document.getElementById('buzzerTimer');
const pointsDisplay = document.getElementById('pointsDisplay');
timerDisplay.textContent = elapsed.toFixed(2) + 's';
const points = calculatePoints(elapsed);
pointsDisplay.textContent = points + ' Punkte';
// Speichere die Zeit beim Buzzern für spätere Berechnung
window.pausedAt = Date.now();
if (window.spotifyPlayer) {
window.spotifyPlayer.pause();
}
// Direkt Spieler setzen (keine Auswahl nötig)
currentBuzzer = playerId;
buzzedPlayers.push(playerId);
pendingPoints = points;
// Markiere Spieler
document.getElementById(`player${playerId}`).classList.add('buzzed');
document.getElementById('currentPlayer').textContent = `${players[playerId - 1].name} antwortet...`;
// Zeige Antwortfeld direkt (keine Spielerauswahl)
document.getElementById('answerSection').classList.add('active');
window.earnedPoints = pendingPoints;
}
function selectPlayer(playerId) {
if (buzzedPlayers.includes(playerId)) {
alert('Dieser Spieler hat bereits gebuzzert!');
return;
}
currentBuzzer = playerId;
buzzedPlayers.push(playerId);
// Markiere Spieler
document.getElementById(`player${playerId}`).classList.add('buzzed');
document.getElementById('currentPlayer').textContent = `${players[playerId - 1].name} antwortet...`;
// Verstecke Spielerauswahl, zeige Antwortfeld
document.getElementById('playerSelection').style.display = 'none';
document.getElementById('answerSection').classList.add('active');
window.earnedPoints = pendingPoints;
}
function buzz(playerId) {
selectPlayer(playerId);
}
function setCorrectAnswer() {
if (currentGameMode === 'artist') {
correctAnswer = "{{ track.artists[0].name | clean }}";
document.getElementById('question-text').innerText = i18n.question_artist || 'Guess the artist!';
document.getElementById('answerInput').placeholder = i18n.input_artist || 'Artist name';
} else if (currentGameMode === 'title') {
correctAnswer = "{{ track.name | clean }}";
document.getElementById('question-text').innerText = i18n.question_title || 'Guess the title!';
document.getElementById('answerInput').placeholder = i18n.input_title || 'Song title';
} else if (currentGameMode === 'year') {
correctAnswer = "{{ track.album.release_date[:4] }}";
document.getElementById('question-text').innerText = i18n.question_year || 'Guess the year!';
document.getElementById('answerInput').placeholder = i18n.input_year || 'Year';
document.getElementById('answerInput').type = "number";
}
}
function searchTracks() {
const query = document.getElementById('answerInput').value;
if (query.length < 2) {
document.getElementById('searchResults').style.display = 'none';
return;
}
fetch('/search_track', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
query: query,
all_tracks: allTracks
})
})
.then(response => response.json())
.then(data => {
const resultsContainer = document.getElementById('searchResults');
resultsContainer.innerHTML = '';
if (data.results.length === 0) {
resultsContainer.style.display = 'none';
return;
}
data.results.forEach(result => {
const item = document.createElement('div');
item.className = 'search-item';
item.innerHTML = `<strong>${result.name}</strong> - ${result.artist}`;
item.onclick = function() {
document.getElementById('answerInput').value =
currentGameMode === 'artist' ? result.artist : result.name;
resultsContainer.style.display = 'none';
};
resultsContainer.appendChild(item);
});
resultsContainer.style.display = 'block';
})
.catch(error => {
console.error('Error searching tracks:', error);
});
}
function checkAnswer() {
const guess = document.getElementById('answerInput').value;
if (!guess) return;
// Disable input während Prüfung
document.getElementById('answerInput').disabled = true;
document.querySelector('button[onclick="checkAnswer()"]').disabled = true;
fetch('/check_answer_buzzer', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
guess: guess,
correct_answer: correctAnswer,
game_mode: currentGameMode,
playlist_id: '{{ playlist_id }}',
earned_points: window.earnedPoints,
player_id: currentBuzzer,
apply_points: false // Punkte NICHT sofort anwenden
})
})
.then(response => response.json())
.then(data => {
const resultContainer = document.getElementById('resultContainer');
if (data.correct) {
// Speichere Punkte, aber wende sie noch NICHT an
pendingPoints = window.earnedPoints;
resultContainer.innerHTML = `
<div style="background:linear-gradient(135deg, rgba(76, 175, 80, 0.2) 0%, rgba(56, 142, 60, 0.2) 100%); padding:20px; border-radius:12px; border:2px solid #4CAF50;">
<h3 style="color:#4CAF50; margin-bottom:10px;">✅ ${i18n.correct || 'Richtig'}</h3>
<p style="font-size:1.5em; margin:10px 0;"><strong style="color:#1DB954;">+${window.earnedPoints}</strong> Punkte für ${players[currentBuzzer - 1].name}!</p>
<div style="margin-top:20px; padding:20px; background:rgba(15,20,25,0.8); border-radius:12px;">
<h4 style="color:#1DB954; margin-bottom:15px;">Song Info:</h4>
<p style="margin:8px 0;"><strong style="color:#1DB954;">Titel:</strong> <span style="color:#e0e0e0;">{{ track.name | clean }}</span></p>
<p style="margin:8px 0;"><strong style="color:#1DB954;">Künstler:</strong> <span style="color:#e0e0e0;">{{ track.artists[0].name | clean }}</span></p>
<p style="margin:8px 0;"><strong style="color:#1DB954;">Album:</strong> <span style="color:#e0e0e0;">{{ track.album.name | clean }}</span></p>
<a href="{{ track.external_urls.spotify }}" target="_blank" style="display:inline-block; margin-top:10px; padding:8px 16px; background:linear-gradient(135deg, #1DB954 0%, #1ed760 100%); color:#fff; text-decoration:none; border-radius:20px; font-weight:600;">Auf Spotify öffnen</a>
</div>
</div>
`;
document.getElementById('nextQuestionBtn').style.display = 'inline-block';
document.getElementById('nextQuestionBtn').onclick = function() {
// Wende Punkte beim Klick auf "Nächster Song" an
players[currentBuzzer - 1].score += pendingPoints;
updateScoreboard();
window.location.href = '/quiz/{{ playlist_id }}?mode={{ game_mode }}&buzzer=1&local_multiplayer=1';
};
} else {
// Minuspunkte: 25% der möglichen Punkte
const minusPoints = Math.floor(window.earnedPoints * 0.25);
pendingPoints = -minusPoints;
resultContainer.innerHTML = `
<div style="background:linear-gradient(135deg, rgba(244, 67, 54, 0.2) 0%, rgba(211, 47, 47, 0.2) 100%); padding:20px; border-radius:12px; border:2px solid #f44336;">
<h3 style="color:#f44336; margin-bottom:10px;">❌ Falsch</h3>
<p style="font-size:1.1em; margin:10px 0;">Deine Antwort: <strong style="color:#f44336;">${guess}</strong></p>
<p style="font-size:1.1em; margin:10px 0;"><strong style="color:#f44336;">-${minusPoints}</strong> Punkte für ${players[currentBuzzer - 1].name}</p>
<div style="margin-top:20px;">
<button class="btn" onclick="continueGame()" style="background:linear-gradient(135deg, #FFA500 0%, #FF8C00 100%);">🔄 Weiterbuzzern</button>
<button class="btn" onclick="showSolution()" style="background:linear-gradient(135deg, #1DB954 0%, #1ed760 100%);">📝 Lösung anzeigen</button>
</div>
</div>
`;
}
})
.catch(error => {
console.error('Error checking answer:', error);
});
}
function continueGame() {
// Wende Minuspunkte an
players[currentBuzzer - 1].score += pendingPoints;
updateScoreboard();
// Reset für nächsten Buzzer
document.getElementById('answerSection').classList.remove('active');
document.getElementById('answerInput').value = '';
document.getElementById('answerInput').disabled = false;
document.querySelector('button[onclick="checkAnswer()"]').disabled = false;
document.getElementById('resultContainer').innerHTML = '';
document.getElementById(`player${currentBuzzer}`).classList.remove('buzzed');
currentBuzzer = null;
canBuzz = true;
// Berechne Pausenzeit und passe startTime an
const pauseDuration = Date.now() - window.pausedAt;
startTime += pauseDuration;
// Musik weiterspielen
if (window.spotifyPlayer) {
window.spotifyPlayer.resume();
}
// Timer weiterlaufen lassen (von der pausierten Zeit)
buzzTimer = requestAnimationFrame(updateTimer);
}
function showSolution() {
// Wende Minuspunkte an
players[currentBuzzer - 1].score += pendingPoints;
updateScoreboard();
const resultContainer = document.getElementById('resultContainer');
resultContainer.innerHTML = `
<div style="margin-top:20px; padding:20px; background:rgba(15,20,25,0.8); border-radius:12px; border:2px solid #1DB954;">
<h4 style="color:#1DB954; margin-bottom:15px;">Lösung:</h4>
<p style="margin:8px 0;"><strong style="color:#1DB954;">Titel:</strong> <span style="color:#e0e0e0;">{{ track.name | clean }}</span></p>
<p style="margin:8px 0;"><strong style="color:#1DB954;">Künstler:</strong> <span style="color:#e0e0e0;">{{ track.artists[0].name | clean }}</span></p>
<p style="margin:8px 0;"><strong style="color:#1DB954;">Album:</strong> <span style="color:#e0e0e0;">{{ track.album.name | clean }}</span></p>
<a href="{{ track.external_urls.spotify }}" target="_blank" style="display:inline-block; margin-top:10px; padding:8px 16px; background:linear-gradient(135deg, #1DB954 0%, #1ed760 100%); color:#fff; text-decoration:none; border-radius:20px; font-weight:600;">Auf Spotify öffnen</a>
</div>
`;
document.getElementById('nextQuestionBtn').style.display = 'inline-block';
}
function switchGameMode(mode) {
window.location.href = `/reset_quiz/{{ playlist_id }}?next_mode=${mode}&buzzer=1&local_multiplayer=1`;
}
function getOption(key, defaultValue) {
return localStorage.getItem(key) || defaultValue;
}
window.onload = function() {
document.getElementById('startPosition').value = getOption('startPosition', 'start');
maxPoints = parseInt(localStorage.getItem('buzzer_max_points')) || 1000;
gracePeriod = parseInt(localStorage.getItem('buzzer_grace_period')) || 5;
decayRate = parseInt(localStorage.getItem('buzzer_decay_rate')) || 50;
document.getElementById('pointsDisplay').textContent = maxPoints + ' Punkte';
// Update Starttext basierend auf Modus
const currentPlayerText = document.getElementById('currentPlayer');
if (buzzerMode === 'personal') {
currentPlayerText.textContent = `Drücke [${keyMappings['1']}] zum Starten`;
} else {
currentPlayerText.textContent = 'Drücke LEERTASTE zum Starten';
}
};
</script>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<div class="quiz-container">
<!-- Scoreboard -->
<div class="scoreboard">
<div class="player-card" id="player1">
<div class="player-name">Spieler 1</div>
<div class="player-score">0</div>
</div>
<div class="player-card" id="player2">
<div class="player-name">Spieler 2</div>
<div class="player-score">0</div>
</div>
<div class="player-card" id="player3">
<div class="player-name">Spieler 3</div>
<div class="player-score">0</div>
</div>
<div class="player-card" id="player4">
<div class="player-name">Spieler 4</div>
<div class="player-score">0</div>
</div>
</div>
<input type="hidden" id="device_id" value="">
<h2 id="question-text" style="text-align:center; margin:20px 0;">{{ translations['question_title'] }}</h2>
<!-- Timer & Points Display -->
<div class="buzzer-timer" id="buzzerTimer">0.00s</div>
<div class="points-display high" id="pointsDisplay">1000 Punkte</div>
<div class="current-player" id="currentPlayer">Drücke START um zu beginnen</div>
<!-- Buzzer Hint -->
<div id="buzzerHint" style="text-align:center; font-size:1.5em; color:#1DB954; margin:20px 0; display:none;">
⌨️ Drücke LEERTASTE zum Buzzern!
</div>
<!-- Player Selection (versteckt bis gebuzzert) -->
<div id="playerSelection" style="display:none; text-align:center; margin:30px 0;">
<h3 style="margin-bottom:20px; color:#1DB954;">Wer hat gebuzzert?</h3>
<div class="buzzer-grid">
<button class="player-buzzer" id="selectPlayer1" onclick="selectPlayer(1)">Spieler 1</button>
<button class="player-buzzer" id="selectPlayer2" onclick="selectPlayer(2)">Spieler 2</button>
<button class="player-buzzer" id="selectPlayer3" onclick="selectPlayer(3)">Spieler 3</button>
<button class="player-buzzer" id="selectPlayer4" onclick="selectPlayer(4)">Spieler 4</button>
</div>
</div>
<!-- START Button -->
<div style="text-align:center; margin:30px 0;">
<button class="player-buzzer start" id="startButton" onclick="startGame()">
▶️<br>START
</button>
</div>
<!-- Answer Section -->
<div class="answer-section" id="answerSection">
<h3 style="margin-bottom:15px;">{{ translations['enter_answer'] if translations.get('enter_answer') else 'Enter your answer' }}:</h3>
<input type="text" id="answerInput" placeholder="{{ translations['input_title'] }}" oninput="searchTracks()">
<button class="btn" onclick="checkAnswer()">{{ translations['answer_button'] if translations.get('answer_button') else 'Submit' }}</button>
<div id="searchResults" class="search-results"></div>
<div id="resultContainer" class="result-container"></div>
<a id="nextQuestionBtn" href="/quiz/{{ playlist_id }}?mode={{ game_mode }}&buzzer=1&local_multiplayer=1" class="btn" style="display: none; margin-top:15px;">{{ translations['next_question'] if translations.get('next_question') else 'Next Question' }}</a>
</div>
<!-- Game Mode Switcher -->
<div style="text-align:center; margin-top:30px;">
<button class="btn {{ 'btn-success' if game_mode == 'artist' else 'btn-secondary' }}" onclick="switchGameMode('artist')">{{ translations['guess_artist'] }}</button>
<button class="btn {{ 'btn-success' if game_mode == 'title' else 'btn-secondary' }}" onclick="switchGameMode('title')">{{ translations['guess_title'] }}</button>
<button class="btn {{ 'btn-success' if game_mode == 'year' else 'btn-secondary' }}" onclick="switchGameMode('year')">{{ translations['guess_year'] }}</button>
</div>
<!-- Navigation -->
<div style="text-align:center; margin-top:30px;">
<a href="/reset_scores/{{ playlist_id }}?mode={{ game_mode }}&buzzer=1&local_multiplayer=1" class="btn" style="background:linear-gradient(135deg, #FFA500 0%, #FF8C00 100%);" onclick="return confirm('Alle Punkte zurücksetzen?')">🔄 Punkte zurücksetzen</a>
<a href="/reset_quiz/{{ playlist_id }}?buzzer=1&local_multiplayer=1" class="btn btn-danger">{{ translations['end_quiz'] if translations.get('end_quiz') else '🏁 End Quiz' }}</a>
<a href="/playlists" class="btn btn-secondary">{{ translations['back_to_playlists'] if translations.get('back_to_playlists') else '⬅️ Back' }}</a>
</div>
</div>
</body>
</html>