let currentSessionId = null; let currentQuestionNumber = 1; let totalQuestions = 10; let currentScore = 0; let currentStreak = 0; let attemptsUsed = 0; const maxAttempts = 3; let selectedDifficulty = "all"; let selectedCategory = "all"; let lastQuestionData = null; let timerStartTime = null; let timerInterval = null; let elapsedSeconds = 0; const CATEGORY_LABELS = { de: { all: "✨ Alle Kategorien", books: "📖 Buchwissen", movies: "🎬 Filmwissen", creatures: "🐉 Magische Geschöpfe", spells: "🪄 Zaubersprüche & Tränke", characters: "🧙♂️ Charaktere", hogwarts: "🏰 Hogwarts & Orte", quidditch: "🧹 Quidditch", lore: "🔮 Artefakte & Lore", general: "⚡ Allgemein", harry_potter: "⚡ Harry Potter" }, en: { all: "✨ All Categories", books: "📖 Book Lore", movies: "🎬 Movie Trivia", creatures: "🐉 Magical Creatures", spells: "🪄 Spells & Potions", characters: "🧙♂️ Characters", hogwarts: "🏰 Hogwarts & Places", quidditch: "🧹 Quidditch", lore: "🔮 Artifacts & Lore", general: "⚡ General", harry_potter: "⚡ Harry Potter" } }; const UI_TEXT = { de: { pageTitle: "Harry Potter Wissensquiz", pageSubtitle: "Teste dein Wissen über Bücher, Filme, Geschöpfe und Zaubersprüche – direkt & ohne Umwege.", diffLabel: "Schwierigkeitsgrad", diffAll: "Alle Stufen", diffEasy: "Leicht", diffMed: "Mittel", diffHard: "Schwer (Experte)", catLabel: "Themengebiet wählen", catAll: "Alle Kategorien", catBooks: "Buchwissen", catMovies: "Filmwissen", catCreatures: "Magische Geschöpfe", catSpells: "Zaubersprüche & Tränke", catChars: "Charaktere", catHogwarts: "Hogwarts & Orte", catQuidditch: "Quidditch", catLore: "Artefakte & Lore", dashboardRules: "🎯 10 Fragen pro Runde·🔥 Streak-Zähler·💡 Bis zu 3 Versuche je Frage", startQuizBtn: "⚡ Quiz jetzt starten ➔", startBtnCasting: "⚡ Runde wird geladen...", pointsLabel: "Pkt.", questionLabel: "Frage", ofLabel: "/", attemptLabel: "Versuch", checkBtn: "✓ Antwort prüfen", hintBtn: "💡 Tipp anzeigen", hintHeading: "💡 Hinweis:", placeholder: "Deine Antwort eingeben...", noHint: "Kein Tipp vorhanden.", hintFailed: "Tipp konnte nicht geladen werden.", wrongFeedback: "Nicht ganz! Noch {attempts} Versuch(e).", exhaustedWrong: "Leider nicht gelöst (3 Fehlversuche).", solutionLabel: "Richtige Antwort:", startError: "Fehler beim Starten des Quiz: ", networkError: "Netzwerkfehler: " }, en: { pageTitle: "Harry Potter Trivia Quiz", pageSubtitle: "Test your knowledge across books, movies, creatures, and spells — straight to the action.", diffLabel: "Difficulty Level", diffAll: "All Levels", diffEasy: "Easy", diffMed: "Medium", diffHard: "Hard (Expert)", catLabel: "Select Topic", catAll: "All Categories", catBooks: "Book Lore", catMovies: "Movie Trivia", catCreatures: "Magical Creatures", catSpells: "Spells & Potions", catChars: "Characters", catHogwarts: "Hogwarts & Places", catQuidditch: "Quidditch", catLore: "Artifacts & Lore", dashboardRules: "🎯 10 Questions per Round·🔥 Answer Streak·💡 Up to 3 Attempts", startQuizBtn: "⚡ Start Quiz Now ➔", startBtnCasting: "⚡ Loading Round...", pointsLabel: "Pts", questionLabel: "Question", ofLabel: "/", attemptLabel: "Attempt", checkBtn: "✓ Check Answer", hintBtn: "💡 Show Hint", hintHeading: "💡 Hint:", placeholder: "Type your answer...", noHint: "No hint available.", hintFailed: "Could not load hint.", wrongFeedback: "Not quite! {attempts} attempt(s) left.", exhaustedWrong: "Out of attempts (3 failed tries).", solutionLabel: "Correct answer:", startError: "Error starting quiz: ", networkError: "Network error: " } }; // Web Audio API Sound Generator for crisp trivia feedback const audioCtx = new (window.AudioContext || window.webkitAudioContext)(); function playSound(type) { if (audioCtx.state === 'suspended') { audioCtx.resume(); } if (type === 'magic') { const notes = [523.25, 659.25, 783.99, 1046.50]; // C5, E5, G5, C6 notes.forEach((freq, index) => { const osc = audioCtx.createOscillator(); const gain = audioCtx.createGain(); osc.type = 'sine'; osc.frequency.value = freq; gain.gain.setValueAtTime(0.12, audioCtx.currentTime + index * 0.07); gain.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + index * 0.07 + 0.3); osc.connect(gain); gain.connect(audioCtx.destination); osc.start(audioCtx.currentTime + index * 0.07); osc.stop(audioCtx.currentTime + index * 0.07 + 0.3); }); } else if (type === 'wrong') { const osc = audioCtx.createOscillator(); const gain = audioCtx.createGain(); osc.type = 'triangle'; osc.frequency.setValueAtTime(180, audioCtx.currentTime); osc.frequency.exponentialRampToValueAtTime(95, audioCtx.currentTime + 0.22); gain.gain.setValueAtTime(0.12, audioCtx.currentTime); gain.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 0.22); osc.connect(gain); gain.connect(audioCtx.destination); osc.start(); osc.stop(audioCtx.currentTime + 0.22); } } function selectDifficulty(diff) { selectedDifficulty = diff; document.querySelectorAll(".diff-chip").forEach(chip => { chip.classList.toggle("active", chip.getAttribute("data-diff") === diff); }); } function selectCategory(cat) { selectedCategory = cat; document.querySelectorAll(".cat-chip").forEach(chip => { chip.classList.toggle("active", chip.getAttribute("data-cat") === cat); }); } function updateStreakUI(animate = false) { const badge = document.getElementById("streakBadge"); if (!badge) return; badge.innerText = currentStreak >= 2 ? `🔥 ${currentStreak} Streak` : `🔥 ${currentStreak}`; badge.classList.toggle("streak-hot", currentStreak >= 2); if (animate) { badge.classList.remove("streak-pop"); void badge.offsetWidth; badge.classList.add("streak-pop"); } } function onLanguageChange(lang) { applyQuizTranslations(lang); } function applyQuizTranslations(lang) { const t = UI_TEXT[lang] || UI_TEXT.de; const setTxt = (id, val) => { const el = document.getElementById(id); if (el && val) el.innerText = val; }; setTxt("pageTitle", t.pageTitle); setTxt("pageSubtitle", t.pageSubtitle); setTxt("diffLabel", t.diffLabel); setTxt("diffAllText", t.diffAll); setTxt("diffEasyText", t.diffEasy); setTxt("diffMedText", t.diffMed); setTxt("diffHardText", t.diffHard); setTxt("catLabel", t.catLabel); setTxt("catAllText", t.catAll); setTxt("catBooksText", t.catBooks); setTxt("catMoviesText", t.catMovies); setTxt("catCreaturesText", t.catCreatures); setTxt("catSpellsText", t.catSpells); setTxt("catCharsText", t.catChars); setTxt("catHogwartsText", t.catHogwarts); setTxt("catQuidditchText", t.catQuidditch); setTxt("catLoreText", t.catLore); const rulesEl = document.getElementById("dashboardRules"); if (rulesEl) rulesEl.innerHTML = t.dashboardRules; const startQuizBtn = document.getElementById("startQuizBtn"); if (startQuizBtn && !startQuizBtn.disabled) startQuizBtn.innerText = t.startQuizBtn; setTxt("checkBtn", t.checkBtn); setTxt("hintBtn", t.hintBtn); setTxt("hintBtnMC", t.hintBtn); setTxt("hintHeading", t.hintHeading); const answerInput = document.getElementById("answerInput"); if (answerInput) answerInput.placeholder = t.placeholder; if (lastQuestionData) { renderQuestion(lastQuestionData); } } let sessionQuestionIds = []; function getRecentQuestionIds() { try { const stored = localStorage.getItem("quiz_recent_qids"); return stored ? JSON.parse(stored) : []; } catch(e) { return []; } } function getWrongQuestionIds() { try { const stored = localStorage.getItem("quiz_wrong_qids"); return stored ? JSON.parse(stored) : []; } catch(e) { return []; } } function recordQuestionWrong(qid) { if (!qid) return; try { const wrong = getWrongQuestionIds(); if (!wrong.includes(qid)) { wrong.push(qid); if (wrong.length > 30) wrong.shift(); localStorage.setItem("quiz_wrong_qids", JSON.stringify(wrong)); } } catch(e) {} } function recordQuestionMastered(qid) { if (!qid) return; try { let wrong = getWrongQuestionIds(); if (wrong.includes(qid)) { wrong = wrong.filter(id => id !== qid); localStorage.setItem("quiz_wrong_qids", JSON.stringify(wrong)); } } catch(e) {} } function saveRecentQuestions(ids) { if (!ids || ids.length === 0) return; try { let recent = getRecentQuestionIds(); recent = Array.from(new Set([...ids, ...recent])).slice(0, 20); localStorage.setItem("quiz_recent_qids", JSON.stringify(recent)); } catch(e) {} } async function startGame() { const startQuizBtn = document.getElementById("startQuizBtn"); const t = UI_TEXT[currentLang] || UI_TEXT.de; if (startQuizBtn) { startQuizBtn.disabled = true; startQuizBtn.innerText = t.startBtnCasting; } playSound('magic'); sessionQuestionIds = []; currentStreak = 0; updateStreakUI(false); try { const recentIds = getRecentQuestionIds(); const wrongIds = getWrongQuestionIds(); const res = await fetch("/api/game/start", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ lang: currentLang, difficulty: selectedDifficulty, category: selectedCategory, recent_ids: recentIds, wrong_ids: wrongIds }) }); if (!res.ok) { throw new Error(currentLang === "en" ? "Could not start game session." : "Konnte Spiel nicht starten."); } const data = await res.json(); currentSessionId = data.session_id; currentQuestionNumber = data.question_number; totalQuestions = data.total_questions; currentScore = data.score; attemptsUsed = data.attempts_used; const introCard = document.getElementById("introCard"); if (introCard) { introCard.classList.add("hidden"); } document.getElementById("gameCard").classList.remove("hidden"); startTimer(); renderQuestion(data); } catch (err) { alert(t.startError + err.message); if (startQuizBtn) { startQuizBtn.disabled = false; startQuizBtn.innerText = t.startQuizBtn; } } } function startTimer() { timerStartTime = Date.now(); elapsedSeconds = 0; if (timerInterval) clearInterval(timerInterval); timerInterval = setInterval(updateTimer, 1000); updateTimer(); } function updateTimer() { if (!timerStartTime) return; elapsedSeconds = Math.floor((Date.now() - timerStartTime) / 1000); const m = Math.floor(elapsedSeconds / 60); const s = elapsedSeconds % 60; const badge = document.getElementById("timerBadge"); if (badge) { badge.innerText = `⏱️ ${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`; } } function renderQuestion(qData) { lastQuestionData = qData; if (qData.id && !sessionQuestionIds.includes(qData.id)) { sessionQuestionIds.push(qData.id); } const t = UI_TEXT[currentLang] || UI_TEXT.de; const catMap = CATEGORY_LABELS[currentLang] || CATEGORY_LABELS.de; document.getElementById("questionCounter").innerText = `${t.questionLabel} ${qData.question_number} ${t.ofLabel} ${totalQuestions}`; document.getElementById("pointsCounter").innerText = `🏆 ${currentScore} ${t.pointsLabel}`; const rawCat = (qData.category || "general").toLowerCase(); document.getElementById("badgeCategory").innerText = catMap[rawCat] || `⚡ ${rawCat.toUpperCase()}`; const badgeDiff = document.getElementById("badgeDifficulty"); if (badgeDiff) { const diff = qData.difficulty || "medium"; badgeDiff.className = `badge badge-diff badge-diff-${diff}`; let diffText = t.diffMed; let diffIcon = "🟡"; if (diff === "easy") { diffText = t.diffEasy; diffIcon = "🟢"; } else if (diff === "hard") { diffText = currentLang === "en" ? "Hard" : "Schwer"; diffIcon = "🔴"; } badgeDiff.innerText = `${diffIcon} ${diffText}`; } document.getElementById("attemptBadge").innerText = `${t.attemptLabel} ${attemptsUsed + 1} ${t.ofLabel} ${maxAttempts}`; document.getElementById("frageText").innerText = qData.question; const textGroup = document.getElementById("textAnswerGroup"); const choiceGroup = document.getElementById("choiceAnswerGroup"); const choiceContainer = document.getElementById("choiceOptionsContainer"); const input = document.getElementById("answerInput"); document.getElementById("hintBox").classList.add("hidden"); document.getElementById("feedbackBox").innerHTML = ""; if (qData.question_type === "multiple_choice" && qData.options && qData.options.length > 0) { textGroup.classList.add("hidden"); choiceGroup.classList.remove("hidden"); choiceContainer.innerHTML = ""; const letters = ["A", "B", "C", "D", "E", "F"]; qData.options.forEach((opt, idx) => { const btn = document.createElement("button"); btn.className = "choice-btn"; btn.type = "button"; btn.innerHTML = `${letters[idx] || "•"} ${opt}`; btn.onclick = () => submitAnswer(opt, btn); choiceContainer.appendChild(btn); }); } else { choiceGroup.classList.add("hidden"); textGroup.classList.remove("hidden"); input.value = ""; input.placeholder = t.placeholder; input.disabled = false; document.getElementById("checkBtn").disabled = false; input.focus(); } const hintBtn = document.getElementById("hintBtn"); if (hintBtn) hintBtn.disabled = false; const hintBtnMC = document.getElementById("hintBtnMC"); if (hintBtnMC) hintBtnMC.disabled = false; updateProgress(); } function updateProgress() { const percent = ((currentQuestionNumber - 1) / totalQuestions) * 100; document.getElementById("progressBar").style.width = percent + "%"; } function checkAnswer() { const input = document.getElementById("answerInput"); const answer = input.value.trim(); if (!answer) { input.focus(); return; } submitAnswer(answer); } async function submitAnswer(answer, clickedBtn = null) { const checkBtn = document.getElementById("checkBtn"); const input = document.getElementById("answerInput"); const t = UI_TEXT[currentLang] || UI_TEXT.de; if (checkBtn) checkBtn.disabled = true; if (input) input.disabled = true; const choiceBtns = document.querySelectorAll(".choice-btn"); choiceBtns.forEach(b => b.disabled = true); if (clickedBtn) { clickedBtn.classList.add("choice-selected"); } try { const res = await fetch("/api/game/check", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ session_id: currentSessionId, answer: answer }) }); if (!res.ok) { throw new Error(currentLang === "en" ? "Answer check failed." : "Fehler bei der Antwortprüfung."); } const result = await res.json(); currentScore = result.score; document.getElementById("pointsCounter").innerText = `🏆 ${currentScore} ${t.pointsLabel}`; const frageBox = document.getElementById("frageBox"); const feedbackBox = document.getElementById("feedbackBox"); if (result.correct) { playSound('magic'); currentStreak += 1; updateStreakUI(true); if (clickedBtn) { clickedBtn.classList.remove("choice-selected"); clickedBtn.classList.add("choice-correct"); } frageBox.classList.add("sparkle-effect"); if (result.question_id) { recordQuestionMastered(result.question_id); } const pts = result.points_earned || 100; const streakBonusText = currentStreak >= 3 ? ` · 🔥 ${currentStreak}er Serie!` : ""; const ptsText = (currentLang === "en") ? `✓ Correct! +${pts} Points${currentStreak >= 3 ? ` · 🔥 ${currentStreak} Streak!` : ""}` : `✓ Richtig! +${pts} Punkte${streakBonusText}`; feedbackBox.innerHTML = `${ptsText}`; setTimeout(() => { frageBox.classList.remove("sparkle-effect"); if (result.is_finished) { finishGame(); } else if (result.next_question) { currentQuestionNumber = result.next_question.question_number; attemptsUsed = 0; renderQuestion(result.next_question); } }, 1100); } else { playSound('wrong'); currentStreak = 0; updateStreakUI(false); if (clickedBtn) { clickedBtn.classList.remove("choice-selected"); clickedBtn.classList.add("choice-wrong"); } frageBox.classList.add("shake-effect"); setTimeout(() => frageBox.classList.remove("shake-effect"), 500); attemptsUsed = result.attempts_used; document.getElementById("attemptBadge").innerText = `${t.attemptLabel} ${Math.min(attemptsUsed + 1, maxAttempts)} ${t.ofLabel} ${maxAttempts}`; if (result.attempts_left === 0) { if (result.question_id) { recordQuestionWrong(result.question_id); } feedbackBox.innerHTML = `