Files
quiz/app/static/js/quiz.js
T

684 lines
29 KiB
JavaScript
Executable File
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.
let currentSessionId = null;
let currentQuestionNumber = 1;
let totalQuestions = 10;
let currentScore = 0;
let attemptsUsed = 0;
const maxAttempts = 3;
let selectedDifficulty = "all";
let lastQuestionData = null;
let timerStartTime = null;
let timerInterval = null;
let elapsedSeconds = 0;
const UI_TEXT = {
de: {
pageTitle: "✨ Magisches Hogwarts-Escape-Quiz ✨",
pageSubtitle: "10 knifflige Rätsel aus der Zauberwelt warten auf dich",
envelopeLetter: "Hogwarts-Schule für Hexerei und Zauberei",
envelopeRecipient: "An den ehrenwerten Zauberschüler",
envelopeLocation: "Auf dem Prüfungspfad • Schloss Hogwarts",
letterSchoolTitle: "Hogwarts-Schule für Hexerei und Zauberei",
letterHeadmaster: "Schulleiter: Albus Dumbledore<br><span>(Großhexenmeister, Träger des Merlin-Ordens Erster Klasse)</span>",
letterSalutation: "Liebe Hexe, lieber Zauberer,",
letterParagraph1: "die schwebenden Kerzen der Großen Halle flackern in unruhigem Schein, und der Sprechende Hut hat nach Jahrhunderten des Schweigens ein wohlbehütetes Geheimnis preisgegeben: Tief in den uralten Gewölben des Schlosses – verborgen hinter den wachsamen Wasserspeiern und den staubigen Bänden der Verbotenen Abteilung – wurde eine magische Prüfungsurkunde versiegelt.",
letterParagraph2: "Zehn arkanische Prüfungen des Scharfsinns, der Zauberkunde und der Entschlossenheit liegen vor dir. Nur wer wahren Mut beweist und die uralten Rätsel meistert, sichert sich seinen ehrenvollen Platz in den Chroniken von Hogwarts und trägt bis zu <strong>1.000 Hauspunkte</strong> für sein Haus davon!",
letterParagraph3: "Halte deinen Zauberstab bereit, besinne dich auf deine Flüche und Gegenzauber und nimm dich vor verhexten Trugschlüssen in Acht. Wir erwarten deine Eule nicht später als mit dem nächsten Glockenschlag des Uhrenturms.",
letterClosing: "Mit vorzüglicher Hochachtung,",
letterSignature: "Minerva McGonagall",
letterSignTitle: "Stellvertretende Schulleiterin",
introDescClosed: "Ein verzauberter Pergamentbrief aus Hogwarts liegt vor dir.<br>Wähle deinen Schwierigkeitsgrad und brich das rote Zaubersiegel:",
introDescOpened: "Lies die Botschaft der Schulleitung aufmerksam.<br>Sobald du bereit bist, besteige den Hogwarts-Express zur Prüfung:",
introDesc: "Ein verzauberter Pergamentbrief aus Hogwarts liegt vor dir.<br>Wähle deinen Schwierigkeitsgrad und brich das rote Zaubersiegel:",
diffLabel: "Schwierigkeitsgrad wählen:",
diffAll: "Alle gemischt",
diffEasy: "Leicht",
diffMed: "Mittel",
diffHard: "Schwer",
breakSealBtn: "📜 Siegel brechen & Brief öffnen",
startQuizBtn: "🚂 Hogwarts-Express besteigen & Prüfung beginnen ➔",
closeLetterBtn: "↩ Brief wieder schließen",
startBtn: "📜 Siegel brechen & Brief öffnen",
startBtnCasting: "✨ Zauberstab wird vorbereitet...",
pointsLabel: "🏆 Hauspunkte",
questionLabel: "Frage",
ofLabel: "von",
attemptLabel: "Versuch",
checkBtn: "✨ Antwort prüfen",
hintBtn: "💡 Hinweis anzeigen",
hintHeading: "📜 Notiz aus der verbotenen Abteilung:",
placeholder: "Deine Antwort hier eingeben...",
noHint: "Kein Tipp vorhanden.",
hintFailed: "Tipp konnte nicht geladen werden.",
correctFeedback: "✨ Richtig! 1 Hauspunkt für dein Haus! ✨",
wrongFeedback: "Leider falsch! Noch {attempts} Versuch(e).",
exhaustedWrong: "Leider nicht richtig (3 Fehlversuche).",
solutionLabel: "Richtige Lösung:",
startError: "Fehler beim Starten des Quiz: ",
networkError: "Netzwerkfehler: "
},
en: {
pageTitle: "✨ Magical Hogwarts Escape Quiz ✨",
pageSubtitle: "10 tricky wizarding world puzzles await you",
envelopeLetter: "Hogwarts School of Witchcraft and Wizardry",
envelopeRecipient: "To the Esteemed Wizarding Student",
envelopeLocation: "On the Path of Trials • Hogwarts Castle",
letterSchoolTitle: "Hogwarts School of Witchcraft and Wizardry",
letterHeadmaster: "Headmaster: Albus Dumbledore<br><span>(Order of Merlin, First Class, Grand Sorc.)</span>",
letterSalutation: "Dear Witch or Wizard,",
letterParagraph1: "The floating candles of the Great Hall flicker with restless anticipation, and the Sorting Hat has stirred after centuries of silence to unveil a closely guarded secret: Deep within the ancient castle vaults—past the watchful gargoyles and the dust of the Restricted Section—a legendary trial has been unsealed.",
letterParagraph2: "Ten arcane trials of wit, magical lore, and courage lie ahead of you. Only those who prove true valor and decipher the hidden mysteries shall claim their place in the eternal Hogwarts archives and earn up to <strong>1,000 House Points</strong> for their house!",
letterParagraph3: "Ready your wand, recall your spells and counter-curses, and beware of enchanted trickery. We await your owl by no later than the next chime of the clock tower.",
letterClosing: "Yours sincerely,",
letterSignature: "Minerva McGonagall",
letterSignTitle: "Deputy Headmistress",
introDescClosed: "An enchanted Hogwarts parchment letter lies before you.<br>Choose your difficulty and break the crimson magical seal:",
introDescOpened: "Read the message from Hogwarts leadership carefully.<br>When you are ready, board the Hogwarts Express for your trial:",
introDesc: "An enchanted Hogwarts parchment letter lies before you.<br>Choose your difficulty and break the crimson magical seal:",
diffLabel: "Choose difficulty level:",
diffAll: "All mixed",
diffEasy: "Easy",
diffMed: "Medium",
diffHard: "Hard",
breakSealBtn: "📜 Break Seal & Open Letter",
startQuizBtn: "🚂 Board Hogwarts Express & Begin Trials ➔",
closeLetterBtn: "↩ Close letter again",
startBtn: "📜 Break Seal & Open Letter",
startBtnCasting: "✨ Preparing wand...",
pointsLabel: "🏆 House Points",
questionLabel: "Question",
ofLabel: "of",
attemptLabel: "Attempt",
checkBtn: "✨ Check Answer",
hintBtn: "💡 Show Hint",
hintHeading: "📜 Note from the Restricted Section:",
placeholder: "Type your answer here...",
noHint: "No hint available.",
hintFailed: "Could not load hint.",
correctFeedback: "✨ Correct! 1 House Point for your House! ✨",
wrongFeedback: "Incorrect! {attempts} attempt(s) remaining.",
exhaustedWrong: "Unfortunately incorrect (3 failed attempts).",
solutionLabel: "Correct solution:",
startError: "Error starting quiz: ",
networkError: "Network error: "
}
};
// Web Audio API Sound Generator for magic chimes and buzzers
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
function playSound(type) {
if (audioCtx.state === 'suspended') {
audioCtx.resume();
}
if (type === 'magic') {
// Magical bright arpeggio chime
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.15, audioCtx.currentTime + index * 0.08);
gain.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + index * 0.08 + 0.35);
osc.connect(gain);
gain.connect(audioCtx.destination);
osc.start(audioCtx.currentTime + index * 0.08);
osc.stop(audioCtx.currentTime + index * 0.08 + 0.35);
});
} else if (type === 'wrong') {
// Soft error buzz
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.type = 'sawtooth';
osc.frequency.setValueAtTime(150, audioCtx.currentTime);
osc.frequency.exponentialRampToValueAtTime(80, audioCtx.currentTime + 0.25);
gain.gain.setValueAtTime(0.1, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 0.25);
osc.connect(gain);
gain.connect(audioCtx.destination);
osc.start();
osc.stop(audioCtx.currentTime + 0.25);
}
}
function selectDifficulty(diff) {
selectedDifficulty = diff;
document.querySelectorAll(".diff-chip").forEach(chip => {
chip.classList.toggle("active", chip.getAttribute("data-diff") === diff);
});
}
function onLanguageChange(lang) {
applyQuizTranslations(lang);
}
function applyQuizTranslations(lang) {
const t = UI_TEXT[lang] || UI_TEXT.de;
const pageTitle = document.getElementById("pageTitle");
const pageSubtitle = document.getElementById("pageSubtitle");
const envelopeLetter = document.getElementById("envelopeLetter");
const introDesc = document.getElementById("introDesc");
const diffLabel = document.getElementById("diffLabel");
const diffAllText = document.getElementById("diffAllText");
const diffEasyText = document.getElementById("diffEasyText");
const diffMedText = document.getElementById("diffMedText");
const diffHardText = document.getElementById("diffHardText");
const startBtn = document.getElementById("startBtn");
const checkBtn = document.getElementById("checkBtn");
const hintBtn = document.getElementById("hintBtn");
const hintBtnMC = document.getElementById("hintBtnMC");
const hintHeading = document.getElementById("hintHeading");
const answerInput = document.getElementById("answerInput");
const letterSchoolTitle = document.getElementById("letterSchoolTitle");
const letterHeadmaster = document.getElementById("letterHeadmaster");
const letterSalutation = document.getElementById("letterSalutation");
const letterParagraph1 = document.getElementById("letterParagraph1");
const letterParagraph2 = document.getElementById("letterParagraph2");
const letterParagraph3 = document.getElementById("letterParagraph3");
const letterClosing = document.getElementById("letterClosing");
const letterSignature = document.getElementById("letterSignature");
const letterSignTitle = document.getElementById("letterSignTitle");
const envelopeRecipient = document.getElementById("envelopeRecipient");
const envelopeLocation = document.getElementById("envelopeLocation");
const breakSealBtn = document.getElementById("breakSealBtn");
const startQuizBtn = document.getElementById("startQuizBtn");
const closeLetterBtn = document.getElementById("closeLetterBtn");
if (pageTitle) pageTitle.innerText = t.pageTitle;
if (pageSubtitle) pageSubtitle.innerText = t.pageSubtitle;
if (envelopeLetter) envelopeLetter.innerText = t.envelopeLetter;
if (envelopeRecipient && t.envelopeRecipient) envelopeRecipient.innerText = t.envelopeRecipient;
if (envelopeLocation && t.envelopeLocation) envelopeLocation.innerText = t.envelopeLocation;
if (letterSchoolTitle && t.letterSchoolTitle) letterSchoolTitle.innerText = t.letterSchoolTitle;
if (letterHeadmaster && t.letterHeadmaster) letterHeadmaster.innerHTML = t.letterHeadmaster;
if (letterSalutation && t.letterSalutation) letterSalutation.innerText = t.letterSalutation;
if (letterParagraph1 && t.letterParagraph1) letterParagraph1.innerHTML = t.letterParagraph1;
if (letterParagraph2 && t.letterParagraph2) letterParagraph2.innerHTML = t.letterParagraph2;
if (letterParagraph3 && t.letterParagraph3) letterParagraph3.innerHTML = t.letterParagraph3;
if (letterClosing && t.letterClosing) letterClosing.innerText = t.letterClosing;
if (letterSignature && t.letterSignature) letterSignature.innerText = t.letterSignature;
if (letterSignTitle && t.letterSignTitle) letterSignTitle.innerText = t.letterSignTitle;
const envelope = document.getElementById("envelope");
if (introDesc) {
if (envelope && envelope.classList.contains("envelope-opened") && t.introDescOpened) {
introDesc.innerHTML = t.introDescOpened;
} else if (t.introDescClosed) {
introDesc.innerHTML = t.introDescClosed;
} else if (t.introDesc) {
introDesc.innerHTML = t.introDesc;
}
}
if (diffLabel) diffLabel.innerText = t.diffLabel;
if (diffAllText) diffAllText.innerText = t.diffAll;
if (diffEasyText) diffEasyText.innerText = t.diffEasy;
if (diffMedText) diffMedText.innerText = t.diffMed;
if (diffHardText) diffHardText.innerText = t.diffHard;
if (breakSealBtn && !breakSealBtn.disabled) breakSealBtn.innerText = t.breakSealBtn || t.startBtn;
if (startQuizBtn && !startQuizBtn.disabled) startQuizBtn.innerText = t.startQuizBtn;
if (closeLetterBtn) closeLetterBtn.innerText = t.closeLetterBtn;
if (startBtn && !startBtn.disabled) startBtn.innerText = t.startBtn;
if (checkBtn) checkBtn.innerText = t.checkBtn;
if (hintBtn) hintBtn.innerText = t.hintBtn;
if (hintBtnMC) hintBtnMC.innerText = t.hintBtn;
if (hintHeading) hintHeading.innerText = t.hintHeading;
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) {}
}
function breakSealAndOpenLetter() {
const envelope = document.getElementById("envelope");
if (!envelope || envelope.classList.contains("envelope-opened")) return;
playSound('magic');
envelope.classList.add("envelope-opened");
// Start background music if enabled or first click
const bgm = document.getElementById("bgm");
if (bgm && bgm.paused) {
bgm.play().then(() => {
const icon = document.getElementById("audioIcon");
const text = document.getElementById("audioText");
if (icon && text) {
icon.innerText = "🔊";
text.innerText = (currentLang === "de") ? "Musik: An" : "Music: On";
}
localStorage.setItem("bgm_enabled", "true");
}).catch(() => {});
}
const breakSealBtn = document.getElementById("breakSealBtn");
const startBtn = document.getElementById("startBtn");
const letterActions = document.getElementById("letterActions");
const introDesc = document.getElementById("introDesc");
const t = UI_TEXT[currentLang] || UI_TEXT.de;
if (breakSealBtn) breakSealBtn.classList.add("hidden");
if (startBtn) startBtn.classList.add("hidden");
if (letterActions) {
letterActions.classList.remove("hidden");
letterActions.classList.add("fade-in-action");
}
if (introDesc && t.introDescOpened) {
introDesc.innerHTML = t.introDescOpened;
}
}
function closeLetter() {
const envelope = document.getElementById("envelope");
if (!envelope) return;
envelope.classList.remove("envelope-opened");
const breakSealBtn = document.getElementById("breakSealBtn");
const startBtn = document.getElementById("startBtn");
const letterActions = document.getElementById("letterActions");
const introDesc = document.getElementById("introDesc");
const t = UI_TEXT[currentLang] || UI_TEXT.de;
if (letterActions) letterActions.classList.add("hidden");
if (breakSealBtn) breakSealBtn.classList.remove("hidden");
if (startBtn) startBtn.classList.remove("hidden");
if (introDesc && t.introDescClosed) {
introDesc.innerHTML = t.introDescClosed;
}
}
async function startQuizFromLetter() {
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 = [];
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,
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.style.transition = "opacity 0.4s ease, transform 0.4s ease";
introCard.style.opacity = "0";
introCard.style.transform = "scale(0.97)";
}
setTimeout(() => {
if (introCard) {
introCard.classList.add("hidden");
introCard.style.opacity = "";
introCard.style.transform = "";
}
document.getElementById("gameCard").classList.remove("hidden");
startTimer();
renderQuestion(data);
}, 400);
} catch (err) {
alert(t.startError + err.message);
if (startQuizBtn) {
startQuizBtn.disabled = false;
startQuizBtn.innerText = t.startQuizBtn;
}
}
}
// Fallback alias for startGame
function startGame() {
const envelope = document.getElementById("envelope");
if (envelope && !envelope.classList.contains("envelope-opened")) {
breakSealAndOpenLetter();
} else {
startQuizFromLetter();
}
}
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;
document.getElementById("questionCounter").innerText = `${t.questionLabel} ${qData.question_number} ${t.ofLabel} ${totalQuestions}`;
document.getElementById("pointsCounter").innerText = `${t.pointsLabel}: ${currentScore}`;
document.getElementById("badgeCategory").innerText = `⚡ ${qData.category.replace("_", " ").toUpperCase()}`;
// Difficulty badge
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 = t.diffHard;
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 = `<span class="choice-letter">${letters[idx] || "•"}</span> <span class="choice-text">${opt}</span>`;
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;
// Disable all choice buttons
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 = `${t.pointsLabel}: ${currentScore}`;
const frageBox = document.getElementById("frageBox");
const feedbackBox = document.getElementById("feedbackBox");
if (result.correct) {
// Success!
playSound('magic');
if (clickedBtn) {
clickedBtn.classList.remove("choice-selected");
clickedBtn.classList.add("choice-correct");
}
frageBox.classList.add("sparkle-effect");
// Mark question as mastered in history
if (result.question_id) {
recordQuestionMastered(result.question_id);
}
const pts = result.points_earned || 100;
const ptsText = (currentLang === "en")
? `✨ Correct! +${pts} House Points! ✨`
: `✨ Richtig! +${pts} Hauspunkte für dein Haus! ✨`;
feedbackBox.innerHTML = `<span class="feedback-correct">${ptsText}</span>`;
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);
}
}, 1200);
} else {
// Incorrect
playSound('wrong');
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) {
// Exhausted all attempts -> record for spaced repetition learning
if (result.question_id) {
recordQuestionWrong(result.question_id);
}
feedbackBox.innerHTML = `
<div class="feedback-wrong">${t.exhaustedWrong}</div>
<div class="feedback-revealed">${t.solutionLabel} <strong>${result.revealed_answer}</strong></div>
`;
setTimeout(() => {
if (result.is_finished) {
finishGame();
} else if (result.next_question) {
currentQuestionNumber = result.next_question.question_number;
attemptsUsed = 0;
renderQuestion(result.next_question);
}
}, 2200);
} else {
// Still attempts remaining
const msg = t.wrongFeedback.replace("{attempts}", result.attempts_left);
feedbackBox.innerHTML = `<span class="feedback-wrong">${msg}</span>`;
if (checkBtn) checkBtn.disabled = false;
if (input) {
input.disabled = false;
input.select();
}
// Re-enable choice buttons
choiceBtns.forEach(b => b.disabled = false);
}
}
} catch (err) {
alert(t.networkError + err.message);
if (checkBtn) checkBtn.disabled = false;
if (input) input.disabled = false;
choiceBtns.forEach(b => b.disabled = false);
}
}
async function requestHint() {
if (!currentSessionId) return;
const hintBox = document.getElementById("hintBox");
const hintText = document.getElementById("hintText");
const t = UI_TEXT[currentLang] || UI_TEXT.de;
try {
const res = await fetch(`/api/game/hint?session_id=${encodeURIComponent(currentSessionId)}`);
const data = await res.json();
hintText.innerText = data.hint || t.noHint;
hintBox.classList.remove("hidden");
} catch (err) {
hintText.innerText = t.hintFailed;
hintBox.classList.remove("hidden");
}
}
function finishGame() {
if (timerInterval) clearInterval(timerInterval);
saveRecentQuestions(sessionQuestionIds);
document.getElementById("progressBar").style.width = "100%";
window.location.href = `/finale?score=${currentScore}&total=${totalQuestions}&time=${elapsedSeconds}&lang=${currentLang}&diff=${encodeURIComponent(selectedDifficulty)}`;
}
// Enter key to submit
document.addEventListener("DOMContentLoaded", () => {
const input = document.getElementById("answerInput");
if (input) {
input.addEventListener("keydown", (e) => {
if (e.key === "Enter") {
e.preventDefault();
checkAnswer();
}
});
}
applyQuizTranslations(currentLang);
});