Feature: 40 bilingual questions, random 10 game run, difficulty levels, and full i18n support

This commit is contained in:
kobold-ai
2026-09-21 12:33:34 +02:00
parent eafa1f833e
commit cf89cd31c7
15 changed files with 1369 additions and 138 deletions
+19
View File
@@ -54,6 +54,7 @@
<div class="q-header">
<div style="display: flex; gap: 8px; align-items: center;">
<span class="badge badge-warning">⚠️ Prüfung ausstehend</span>
<span class="badge badge-diff badge-diff-{{ q.difficulty or 'medium' }}">{{ (q.difficulty or 'medium')|capitalize }}</span>
{% if q.question_type == 'multiple_choice' %}
<span class="badge" style="background: rgba(155, 89, 182, 0.2); border-color: #9b59b6; color: #d2b4de;">🔘 Multiple-Choice</span>
{% else %}
@@ -143,6 +144,7 @@
<div class="q-header">
<div style="display: flex; gap: 8px; align-items: center;">
<span class="badge badge-success">Reihenfolge #{{ q.sort_order }}</span>
<span class="badge badge-diff badge-diff-{{ q.difficulty or 'medium' }}">{{ (q.difficulty or 'medium')|capitalize }}</span>
{% if q.question_type == 'multiple_choice' %}
<span class="badge" style="background: rgba(155, 89, 182, 0.2); border-color: #9b59b6; color: #d2b4de;">🔘 Multiple-Choice</span>
{% else %}
@@ -182,6 +184,14 @@
<option value="multiple_choice" {% if q.question_type == 'multiple_choice' %}selected{% endif %}>🔘 Multiple Choice</option>
</select>
</div>
<div class="form-group">
<label>Schwierigkeitsgrad:</label>
<select name="difficulty" class="input-text">
<option value="easy" {% if q.difficulty == 'easy' %}selected{% endif %}>🟢 Leicht</option>
<option value="medium" {% if q.difficulty == 'medium' or not q.difficulty %}selected{% endif %}>🟡 Mittel</option>
<option value="hard" {% if q.difficulty == 'hard' %}selected{% endif %}>🔴 Schwer</option>
</select>
</div>
<div class="form-group">
<label>Richtige Antwort:</label>
<input type="text" name="canonical_answer" class="input-text" value="{{ q.canonical_answer }}" required>
@@ -221,6 +231,15 @@
</select>
</div>
<div class="form-group">
<label for="new_diff">Schwierigkeitsgrad:</label>
<select name="difficulty" id="new_diff" class="input-text">
<option value="easy">🟢 Leicht</option>
<option value="medium" selected>🟡 Mittel</option>
<option value="hard">🔴 Schwer</option>
</select>
</div>
<div class="form-group">
<label for="new_type">Fragentyp:</label>
<select name="question_type" id="new_type" class="input-text" onchange="toggleAdminNewType(this.value)">
+62 -7
View File
@@ -19,6 +19,11 @@
</a>
<div class="header-right">
<div class="lang-switch-group" role="group" aria-label="Sprachauswahl / Language">
<button type="button" id="langDe" class="lang-btn active" onclick="setLanguage('de')" title="Deutsch">🇩🇪 DE</button>
<button type="button" id="langEn" class="lang-btn" onclick="setLanguage('en')" title="English">🇬🇧 EN</button>
</div>
<button id="audioToggle" class="audio-btn" title="Musik an/aus" onclick="toggleAudio()">
<span id="audioIcon">🔇</span>
<span id="audioText" class="audio-label">Musik: Aus</span>
@@ -30,9 +35,9 @@
</div>
<nav id="navLinks" class="nav-links">
<a href="/" class="nav-link" onclick="closeMobileMenu()">✨ Zum Quiz</a>
<a href="/vorschlag" class="nav-link" onclick="closeMobileMenu()">✍️ Frage einreichen</a>
<a href="/admin" class="nav-link" onclick="closeMobileMenu()">🔒 Admin-Cockpit</a>
<a href="/" class="nav-link" id="navQuiz" onclick="closeMobileMenu()">✨ Zum Quiz</a>
<a href="/vorschlag" class="nav-link" id="navSuggest" onclick="closeMobileMenu()">✍️ Frage einreichen</a>
<a href="/admin" class="nav-link" id="navAdmin" onclick="closeMobileMenu()">🔒 Admin-Cockpit</a>
</nav>
</div>
</header>
@@ -42,7 +47,7 @@
</main>
<footer class="footer">
<p>⚡ Magisches Hogwarts-Escape-Quiz · Betrieben mit Liebe & Zauberkraft ⚡</p>
<p id="footerText">⚡ Magisches Hogwarts-Escape-Quiz · Betrieben mit Liebe & Zauberkraft ⚡</p>
</footer>
<script>
@@ -52,11 +57,56 @@
const navLinks = document.getElementById("navLinks");
const menuIcon = document.getElementById("menuIcon");
let currentLang = localStorage.getItem("quiz_lang") || "de";
function setLanguage(lang) {
currentLang = (lang === "en") ? "en" : "de";
localStorage.setItem("quiz_lang", currentLang);
document.documentElement.lang = currentLang;
updateLanguageUI();
if (typeof onLanguageChange === "function") {
onLanguageChange(currentLang);
}
}
function updateLanguageUI() {
const isDe = currentLang === "de";
const deBtn = document.getElementById("langDe");
const enBtn = document.getElementById("langEn");
if (deBtn && enBtn) {
deBtn.classList.toggle("active", isDe);
enBtn.classList.toggle("active", !isDe);
}
const navQuiz = document.getElementById("navQuiz");
const navSuggest = document.getElementById("navSuggest");
const navAdmin = document.getElementById("navAdmin");
const footerText = document.getElementById("footerText");
if (navQuiz) navQuiz.innerText = isDe ? "✨ Zum Quiz" : "✨ To Quiz";
if (navSuggest) navSuggest.innerText = isDe ? "✍️ Frage einreichen" : "✍️ Submit Question";
if (navAdmin) navAdmin.innerText = isDe ? "🔒 Admin-Cockpit" : "🔒 Admin Cockpit";
if (footerText) {
footerText.innerText = isDe
? "⚡ Magisches Hogwarts-Escape-Quiz · Betrieben mit Liebe & Zauberkraft ⚡"
: "⚡ Magical Hogwarts Escape Quiz · Powered by Love & Wizardry ⚡";
}
if (audioText) {
const isPlaying = bgm && !bgm.paused;
if (isDe) {
audioText.innerText = isPlaying ? "Musik: An" : "Musik: Aus";
} else {
audioText.innerText = isPlaying ? "Music: On" : "Music: Off";
}
}
}
function toggleAudio() {
const isDe = currentLang === "de";
if (bgm.paused) {
bgm.play().then(() => {
audioIcon.innerText = "🔊";
audioText.innerText = "Musik: An";
audioText.innerText = isDe ? "Musik: An" : "Music: On";
localStorage.setItem("bgm_enabled", "true");
}).catch(err => {
console.log("Audio play blocked:", err);
@@ -64,7 +114,7 @@
} else {
bgm.pause();
audioIcon.innerText = "🔇";
audioText.innerText = "Musik: Aus";
audioText.innerText = isDe ? "Musik: Aus" : "Music: Off";
localStorage.setItem("bgm_enabled", "false");
}
}
@@ -84,11 +134,16 @@
if (localStorage.getItem("bgm_enabled") === "true" && bgm.paused) {
bgm.play().then(() => {
audioIcon.innerText = "🔊";
audioText.innerText = "Musik: An";
audioText.innerText = (currentLang === "de") ? "Musik: An" : "Music: On";
}).catch(() => {});
}
document.removeEventListener("click", initAudioOnInteraction);
}, { once: true });
// Initialize UI on load
document.addEventListener("DOMContentLoaded", () => {
updateLanguageUI();
});
</script>
{% block scripts %}{% endblock %}
</body>
+79 -8
View File
@@ -1,35 +1,106 @@
{% extends "base.html" %}
{% block title %}🎉 Das große Finale – Magisches Quiz{% endblock %}
{% block title %}{% if lang == 'en' %}🎉 The Grand Finale – Magical Quiz{% else %}🎉 Das große Finale – Magisches Quiz{% endif %}{% endblock %}
{% block content %}
<div class="quiz-wrapper">
<div class="card finale-card">
<div class="finale-header">
<span class="sparkle-icon">✨</span>
<h1 class="glow-title">🎉 Happy Birthday Schatz! 🎉</h1>
<h1 class="glow-title" id="finaleTitle">🎉 Happy Birthday Schatz! 🎉</h1>
<span class="sparkle-icon">✨</span>
</div>
<div class="score-banner">
<p class="score-highlight">
<p class="score-highlight" id="finaleScoreText">
{% if lang == 'en' %}
You secured <span class="gold-text">{{ score }}</span> out of <span class="gold-text">{{ total }}</span> possible House Points for your House!
{% else %}
Du hast <span class="gold-text">{{ score }}</span> von <span class="gold-text">{{ total }}</span> möglichen Hauspunkten für dein Haus gesichert!
{% endif %}
</p>
</div>
<div class="reward-box">
<h2>💛 Dein magischer Gewinn: Ein unvergesslicher Abend Zuhause 💛</h2>
<p class="reward-description">
<h2 id="rewardTitle">
{% if lang == 'en' %}
💛 Your Magical Reward: An Unforgettable Evening at Home 💛
{% else %}
💛 Dein magischer Gewinn: Ein unvergesslicher Abend Zuhause 💛
{% endif %}
</h2>
<p class="reward-description" id="rewardDesc">
{% if lang == 'en' %}
We'll order delicious food and make ourselves totally comfortable: relax, laugh, and enjoy quality time together.<br><br>
Plus, we'll play a brand new <strong>Online Escape Game</strong> of your choice!
{% else %}
Wir holen uns lecker Essen und machen es uns so richtig gemütlich: entspannen, lachen und die gemeinsame Zeit genießen.<br><br>
Dazu spielen wir ein brandneues <strong>Online Escape Game</strong> deiner Wahl!
{% endif %}
</p>
<p class="love-note" id="loveNote">
{% if lang == 'en' %}
I'm so excited for our wonderful evening together! 💫
{% else %}
Ich freue mich riesig auf unseren gemeinsamen Abend! 💫
{% endif %}
</p>
<p class="love-note">Ich freue mich riesig auf unseren gemeinsamen Abend! 💫</p>
</div>
<div class="btn-group finale-actions">
<a href="/" class="btn btn-primary">🔄 Erneut spielen</a>
<a href="/vorschlag" class="btn btn-secondary">✍️ Eigene Frage vorschlagen</a>
<a href="/" class="btn btn-primary" id="playAgainBtn">
{% if lang == 'en' %}🔄 Play Again{% else %}🔄 Erneut spielen{% endif %}
</a>
<a href="/vorschlag" class="btn btn-secondary" id="suggestBtn">
{% if lang == 'en' %}✍️ Suggest Your Own Question{% else %}✍️ Eigene Frage vorschlagen{% endif %}
</a>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
const finaleScore = {{ score }};
const finaleTotal = {{ total }};
function onLanguageChange(lang) {
const isDe = lang === "de";
const title = document.getElementById("finaleTitle");
const scoreText = document.getElementById("finaleScoreText");
const rewardTitle = document.getElementById("rewardTitle");
const rewardDesc = document.getElementById("rewardDesc");
const loveNote = document.getElementById("loveNote");
const playAgain = document.getElementById("playAgainBtn");
const suggest = document.getElementById("suggestBtn");
if (title) title.innerText = isDe ? "🎉 Happy Birthday Schatz! 🎉" : "🎉 Happy Birthday Darling! 🎉";
if (scoreText) {
scoreText.innerHTML = isDe
? `Du hast <span class="gold-text">${finaleScore}</span> von <span class="gold-text">${finaleTotal}</span> möglichen Hauspunkten für dein Haus gesichert!`
: `You secured <span class="gold-text">${finaleScore}</span> out of <span class="gold-text">${finaleTotal}</span> possible House Points for your House!`;
}
if (rewardTitle) {
rewardTitle.innerText = isDe
? "💛 Dein magischer Gewinn: Ein unvergesslicher Abend Zuhause 💛"
: "💛 Your Magical Reward: An Unforgettable Evening at Home 💛";
}
if (rewardDesc) {
rewardDesc.innerHTML = isDe
? "Wir holen uns lecker Essen und machen es uns so richtig gemütlich: entspannen, lachen und die gemeinsame Zeit genießen.<br><br>Dazu spielen wir ein brandneues <strong>Online Escape Game</strong> deiner Wahl!"
: "We'll order delicious food and make ourselves totally comfortable: relax, laugh, and enjoy quality time together.<br><br>Plus, we'll play a brand new <strong>Online Escape Game</strong> of your choice!";
}
if (loveNote) {
loveNote.innerText = isDe
? "Ich freue mich riesig auf unseren gemeinsamen Abend! 💫"
: "I'm so excited for our wonderful evening together! 💫";
}
if (playAgain) playAgain.innerText = isDe ? "🔄 Erneut spielen" : "🔄 Play Again";
if (suggest) suggest.innerText = isDe ? "✍️ Eigene Frage vorschlagen" : "✍️ Suggest Your Own Question";
}
document.addEventListener("DOMContentLoaded", () => {
onLanguageChange(currentLang);
});
</script>
{% endblock %}
+30 -7
View File
@@ -5,8 +5,8 @@
{% block content %}
<div class="quiz-wrapper">
<header class="quiz-header">
<h1 class="glow-title">✨ Magisches Hogwarts-Escape-Quiz ✨</h1>
<p class="subtitle">15 knifflige Rätsel aus der Zauberwelt warten auf dich</p>
<h1 class="glow-title" id="pageTitle">✨ Magisches Hogwarts-Escape-Quiz ✨</h1>
<p class="subtitle" id="pageSubtitle">10 knifflige Rätsel aus der Zauberwelt warten auf dich</p>
</header>
<!-- Brief-Intro / Startmaske -->
@@ -16,15 +16,35 @@
<div class="envelope-flap"></div>
<div class="wax-seal">H</div>
<div class="letter-preview">
<p class="letter-text">Hogwarts-Schule für Hexerei und Zauberei</p>
<p class="letter-text" id="envelopeLetter">Hogwarts-Schule für Hexerei und Zauberei</p>
</div>
</div>
</div>
<div class="intro-content">
<p class="intro-text">
<p class="intro-text" id="introDesc">
Ein verzauberter Brief aus Hogwarts schwebt vor dir in der Luft.<br>
Bist du bereit, die Prüfungen abzulegen und deine Hauspunkte zu sichern?
</p>
<!-- Difficulty Selector Chips -->
<div class="difficulty-selection">
<span class="difficulty-label" id="diffLabel">Schwierigkeitsgrad wählen:</span>
<div class="difficulty-chips" role="radiogroup" aria-label="Schwierigkeitsgrad">
<button type="button" class="diff-chip active" data-diff="all" onclick="selectDifficulty('all')">
<span class="diff-icon">🎲</span> <span class="diff-text" id="diffAllText">Alle gemischt</span>
</button>
<button type="button" class="diff-chip" data-diff="easy" onclick="selectDifficulty('easy')">
<span class="diff-icon">🟢</span> <span class="diff-text" id="diffEasyText">Leicht</span>
</button>
<button type="button" class="diff-chip" data-diff="medium" onclick="selectDifficulty('medium')">
<span class="diff-icon">🟡</span> <span class="diff-text" id="diffMedText">Mittel</span>
</button>
<button type="button" class="diff-chip" data-diff="hard" onclick="selectDifficulty('hard')">
<span class="diff-icon">🔴</span> <span class="diff-text" id="diffHardText">Schwer</span>
</button>
</div>
</div>
<button id="startBtn" class="btn btn-primary btn-large" onclick="startGame()">
📜 Siegel brechen & Brief öffnen
</button>
@@ -36,7 +56,7 @@
<!-- Fortschritt & Punkte -->
<div class="status-panel">
<div class="progress-info">
<span id="questionCounter">Frage 1 von {{ question_count }}</span>
<span id="questionCounter">Frage 1 von 10</span>
<span id="pointsCounter" class="points-badge">🏆 Hauspunkte: 0</span>
</div>
<div class="progress-container">
@@ -47,7 +67,10 @@
<!-- Frage-Container -->
<div id="frageBox" class="card question-card">
<div class="question-meta">
<span id="badgeCategory" class="badge">⚡ Harry Potter</span>
<div class="meta-left">
<span id="badgeCategory" class="badge">⚡ Harry Potter</span>
<span id="badgeDifficulty" class="badge badge-diff">🟢 Leicht</span>
</div>
<span id="attemptBadge" class="attempt-badge">Versuch 1 von 3</span>
</div>
@@ -78,7 +101,7 @@
</div>
<div id="hintBox" class="hint-box hidden">
<strong>📜 Notiz aus der verbotenen Abteilung:</strong>
<strong id="hintHeading">📜 Notiz aus der verbotenen Abteilung:</strong>
<p id="hintText"></p>
</div>
+19 -10
View File
@@ -5,29 +5,38 @@
{% block content %}
<div class="quiz-wrapper">
<div class="card vorschlag-card">
<h1 class="glow-title">✨ Neue Frage für das Quiz vorschlagen ✨</h1>
<p class="subtitle">Bereichere das magische Quiz mit kniffligen Harry-Potter- oder Escape-Rätseln!</p>
<h1 class="glow-title" id="suggTitle">✨ Neue Frage für das Quiz vorschlagen ✨</h1>
<p class="subtitle" id="suggSubtitle">Bereichere das magische Quiz mit kniffligen Harry-Potter- oder Escape-Rätseln!</p>
<div class="info-alert">
<div class="info-alert" id="suggAlert">
ℹ️ <strong>Hinweis zur Moderation:</strong> Eingereichte Fragen werden nicht sofort öffentlich sichtbar, sondern erhalten automatisch den Status <em>„Prüfung ausstehend“</em>. Unser Zaubereiministerium prüft und testet die Antwort, bevor die Frage freigeschaltet wird.
</div>
<form id="suggestionForm" class="magic-form" onsubmit="submitSuggestion(event)">
<div class="form-group">
<label for="name">Dein Name / Zauberer-Alias (optional):</label>
<label for="name" id="suggNameLabel">Dein Name / Zauberer-Alias (optional):</label>
<input type="text" id="name" name="name" class="input-text" placeholder="z. B. Hermine Granger">
</div>
<div class="form-group">
<label for="frage">Deine Frage: <span class="required">*</span></label>
<label for="frage" id="suggFrageLabel">Deine Frage: <span class="required">*</span></label>
<textarea id="frage" name="frage" class="textarea" rows="3" placeholder="z. B. Wie heißt der Schulleiter von Hogwarts?" required></textarea>
</div>
<div class="form-group">
<label for="q_type">Art der Frage:</label>
<label for="diff_select" id="suggDiffLabel">Schwierigkeitsgrad:</label>
<select id="diff_select" name="difficulty" class="input-text">
<option value="easy">🟢 Leicht / Easy</option>
<option value="medium" selected>🟡 Mittel / Medium</option>
<option value="hard">🔴 Schwer / Hard</option>
</select>
</div>
<div class="form-group">
<label for="q_type" id="suggTypeLabel">Art der Frage:</label>
<select id="q_type" name="q_type" class="input-text" onchange="toggleVorschlagType(this.value)">
<option value="text" selected>🔤 Freitext-Antwort (Tippen mit Fehlertoleranz)</option>
<option value="multiple_choice">🔘 Multiple Choice (Auswahl aus 4 Antwortmöglichkeiten)</option>
<option value="text" selected id="optText">🔤 Freitext-Antwort (Tippen mit Fehlertoleranz)</option>
<option value="multiple_choice" id="optMC">🔘 Multiple Choice (Auswahl aus 4 Antwortmöglichkeiten)</option>
</select>
</div>
@@ -39,14 +48,14 @@
<!-- Zusätzliche Optionen für Multiple Choice -->
<div id="mcOptionsGroup" class="form-group hidden" style="background: rgba(255,255,255,0.03); padding: 15px; border-radius: 8px; border: 1px dashed var(--border-gold);">
<label style="color: var(--gold);">Falsche Antwortmöglichkeiten (zur Auswahl für die Spieler):</label>
<label id="mcWrongLabel" style="color: var(--gold);">Falsche Antwortmöglichkeiten (zur Auswahl für die Spieler):</label>
<input type="text" id="wrong1" class="input-text" placeholder="Falsche Option 1 (z. B. Severus Snape)" style="margin-bottom: 8px;">
<input type="text" id="wrong2" class="input-text" placeholder="Falsche Option 2 (z. B. Minerva McGonagall)" style="margin-bottom: 8px;">
<input type="text" id="wrong3" class="input-text" placeholder="Falsche Option 3 (z. B. Rubeus Hagrid)">
</div>
<div class="form-group">
<label for="tipp">Tipp / Hinweis (optional):</label>
<label for="tipp" id="suggTippLabel">Tipp / Hinweis (optional):</label>
<input type="text" id="tipp" name="tipp" class="input-text" placeholder="z. B. Er trägt eine Halbmondbrille.">
</div>