Feature: In-game timer, anonymous leaderboard, witty tiered finale results, legal pages, button renames

This commit is contained in:
kobold-ai
2026-09-21 13:23:29 +02:00
parent 16bffa3be8
commit 26ff0660d3
12 changed files with 1508 additions and 74 deletions
+12
View File
@@ -36,6 +36,7 @@
<nav id="navLinks" class="nav-links">
<a href="/" class="nav-link" id="navQuiz" onclick="closeMobileMenu()">✨ Zum Quiz</a>
<a href="/bestenliste" class="nav-link" id="navLeaderboard" onclick="closeMobileMenu()">🏆 Bestenliste</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>
@@ -48,6 +49,11 @@
<footer class="footer">
<p id="footerText">⚡ Magisches Hogwarts-Escape-Quiz · Betrieben mit Liebe & Zauberkraft ⚡</p>
<div class="footer-links">
<a href="/impressum" class="footer-link" id="footerImpressum">⚖️ Impressum</a>
<span class="footer-sep">·</span>
<a href="/datenschutz" class="footer-link" id="footerDatenschutz">🛡️ Datenschutz</a>
</div>
</footer>
<script>
@@ -79,11 +85,15 @@
}
const navQuiz = document.getElementById("navQuiz");
const navLeaderboard = document.getElementById("navLeaderboard");
const navSuggest = document.getElementById("navSuggest");
const navAdmin = document.getElementById("navAdmin");
const footerText = document.getElementById("footerText");
const footerImpressum = document.getElementById("footerImpressum");
const footerDatenschutz = document.getElementById("footerDatenschutz");
if (navQuiz) navQuiz.innerText = isDe ? "✨ Zum Quiz" : "✨ To Quiz";
if (navLeaderboard) navLeaderboard.innerText = isDe ? "🏆 Bestenliste" : "🏆 Leaderboard";
if (navSuggest) navSuggest.innerText = isDe ? "✍️ Frage einreichen" : "✍️ Submit Question";
if (navAdmin) navAdmin.innerText = isDe ? "🔒 Admin-Cockpit" : "🔒 Admin Cockpit";
if (footerText) {
@@ -91,6 +101,8 @@
? "⚡ Magisches Hogwarts-Escape-Quiz · Betrieben mit Liebe & Zauberkraft ⚡"
: "⚡ Magical Hogwarts Escape Quiz · Powered by Love & Wizardry ⚡";
}
if (footerImpressum) footerImpressum.innerText = isDe ? "⚖️ Impressum" : "⚖️ Legal Notice";
if (footerDatenschutz) footerDatenschutz.innerText = isDe ? "🛡️ Datenschutz" : "🛡️ Privacy Policy";
if (audioText) {
const isPlaying = bgm && !bgm.paused;
if (isDe) {
+205
View File
@@ -0,0 +1,205 @@
{% extends "base.html" %}
{% block title %}{% if lang == 'en' %}🏆 Hogwarts Hall of Fame – Leaderboard{% else %}🏆 Die Große Hogwarts-Ruhmeshalle – Bestenliste{% endif %}{% endblock %}
{% block content %}
<div class="quiz-wrapper">
<div class="card leaderboard-card">
<div class="leaderboard-header" style="text-align: center; margin-bottom: 20px;">
<span class="sparkle-icon">🏆</span>
<h1 class="glow-title" id="lbPageTitle">
{% if lang == 'en' %}🏆 The Grand Hogwarts Hall of Fame 🏆{% else %}🏆 Die Große Hogwarts-Ruhmeshalle 🏆{% endif %}
</h1>
<p class="subtitle" id="lbPageSubtitle">
{% if lang == 'en' %}The most brilliant witches and wizards of all time{% else %}Die schlauesten Hexen und Zauberer aller Zeiten{% endif %}
</p>
</div>
<!-- Filter tabs for difficulty -->
<div class="diff-filter-tabs">
<button type="button" class="tab-btn active" data-diff="all" onclick="filterLeaderboard('all', this)" id="tabAll">
🎲 {% if lang == 'en' %}All Mixed{% else %}Alle gemischt{% endif %}
</button>
<button type="button" class="tab-btn" data-diff="easy" onclick="filterLeaderboard('easy', this)" id="tabEasy">
🟢 {% if lang == 'en' %}Easy{% else %}Leicht{% endif %}
</button>
<button type="button" class="tab-btn" data-diff="medium" onclick="filterLeaderboard('medium', this)" id="tabMed">
🟡 {% if lang == 'en' %}Medium{% else %}Mittel{% endif %}
</button>
<button type="button" class="tab-btn" data-diff="hard" onclick="filterLeaderboard('hard', this)" id="tabHard">
🔴 {% if lang == 'en' %}Hard{% else %}Schwer{% endif %}
</button>
</div>
<div class="table-responsive" style="margin-top: 20px;">
<table class="lb-table">
<thead>
<tr>
<th style="width: 50px;">#</th>
<th id="thWizard">Zauberer</th>
<th id="thScore">Punkte</th>
<th id="thDuration">Zeit</th>
<th id="thDiff">Schwierigkeit</th>
<th id="thDate">Datum</th>
</tr>
</thead>
<tbody id="leaderboardBody">
<tr>
<td colspan="6" style="text-align: center; padding: 25px; color: var(--text-muted);">
✨ Lade die Pergamente der Ruhmeshalle...
</td>
</tr>
</tbody>
</table>
</div>
<div class="btn-group" style="margin-top: 30px;">
<a href="/" class="btn btn-primary" id="btnPlayNow">
✨ {% if lang == 'en' %}Play Now & Secure Rank{% else %}Jetzt spielen & Platz sichern{% endif %}
</a>
<a href="/vorschlag" class="btn btn-secondary" id="btnSuggestQ">
✍️ {% if lang == 'en' %}Submit Question{% else %}Frage einreichen{% endif %}
</a>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
let currentFilter = "all";
const LB_I18N = {
de: {
title: "🏆 Die Große Hogwarts-Ruhmeshalle 🏆",
sub: "Die schlauesten Hexen und Zauberer aller Zeiten",
tabAll: "🎲 Alle gemischt",
tabEasy: "🟢 Leicht",
tabMed: "🟡 Mittel",
tabHard: "🔴 Schwer",
thWizard: "Zauberer",
thScore: "Punkte",
thDuration: "Zeit",
thDiff: "Schwierigkeit",
thDate: "Datum",
btnPlay: "✨ Jetzt spielen & Platz sichern",
btnSuggest: "✍️ Frage einreichen",
empty: "Noch keine Einträge vorhanden. Sei der Erste, der sich verewigt!",
loading: "✨ Lade die Pergamente der Ruhmeshalle..."
},
en: {
title: "🏆 The Grand Hogwarts Hall of Fame 🏆",
sub: "The most brilliant witches and wizards of all time",
tabAll: "🎲 All Mixed",
tabEasy: "🟢 Easy",
tabMed: "🟡 Medium",
tabHard: "🔴 Hard",
thWizard: "Wizard",
thScore: "Points",
thDuration: "Time",
thDiff: "Difficulty",
thDate: "Date",
btnPlay: "✨ Play Now & Secure Rank",
btnSuggest: "✍️ Submit Question",
empty: "No entries yet. Be the first to enter the Hall of Fame!",
loading: "✨ Unrolling the scrolls of the Hall of Fame..."
}
};
function onLanguageChange(lang) {
const isDe = (lang !== "en");
const t = isDe ? LB_I18N.de : LB_I18N.en;
const title = document.getElementById("lbPageTitle");
const sub = document.getElementById("lbPageSubtitle");
const tabAll = document.getElementById("tabAll");
const tabEasy = document.getElementById("tabEasy");
const tabMed = document.getElementById("tabMed");
const tabHard = document.getElementById("tabHard");
const thWizard = document.getElementById("thWizard");
const thScore = document.getElementById("thScore");
const thDuration = document.getElementById("thDuration");
const thDiff = document.getElementById("thDiff");
const thDate = document.getElementById("thDate");
const btnPlay = document.getElementById("btnPlayNow");
const btnSuggest = document.getElementById("btnSuggestQ");
if (title) title.innerText = t.title;
if (sub) sub.innerText = t.sub;
if (tabAll) tabAll.innerText = t.tabAll;
if (tabEasy) tabEasy.innerText = t.tabEasy;
if (tabMed) tabMed.innerText = t.tabMed;
if (tabHard) tabHard.innerText = t.tabHard;
if (thWizard) thWizard.innerText = t.thWizard;
if (thScore) thScore.innerText = t.thScore;
if (thDuration) thDuration.innerText = t.thDuration;
if (thDiff) thDiff.innerText = t.thDiff;
if (thDate) thDate.innerText = t.thDate;
if (btnPlay) btnPlay.innerText = t.btnPlay;
if (btnSuggest) btnSuggest.innerText = t.btnSuggest;
loadLeaderboard(currentFilter);
}
function filterLeaderboard(diff, btn) {
currentFilter = diff;
document.querySelectorAll(".diff-filter-tabs .tab-btn").forEach(b => b.classList.remove("active"));
if (btn) btn.classList.add("active");
loadLeaderboard(diff);
}
async function loadLeaderboard(diff) {
const tbody = document.getElementById("leaderboardBody");
const isDe = (currentLang !== "en");
const t = isDe ? LB_I18N.de : LB_I18N.en;
tbody.innerHTML = `<tr><td colspan="6" style="text-align:center; padding: 25px; color: var(--text-muted);">${t.loading}</td></tr>`;
try {
const url = (diff && diff !== "all") ? `/api/leaderboard?difficulty=${diff}` : "/api/leaderboard";
const res = await fetch(url);
const entries = await res.json();
if (!entries || entries.length === 0) {
tbody.innerHTML = `<tr><td colspan="6" style="text-align:center; padding: 30px; color: var(--text-muted);">${t.empty}</td></tr>`;
return;
}
tbody.innerHTML = "";
const medals = ["🥇", "🥈", "🥉"];
entries.forEach(e => {
const tr = document.createElement("tr");
const rankDisplay = medals[e.rank - 1] || `${e.rank}.`;
const diffBadgeClass = `badge-diff-${e.difficulty || 'medium'}`;
let diffLabel = e.difficulty;
if (e.difficulty === "easy") diffLabel = isDe ? "🟢 Leicht" : "🟢 Easy";
else if (e.difficulty === "medium") diffLabel = isDe ? "🟡 Mittel" : "🟡 Medium";
else if (e.difficulty === "hard") diffLabel = isDe ? "🔴 Schwer" : "🔴 Hard";
else diffLabel = isDe ? "🎲 Gemischt" : "🎲 Mixed";
tr.innerHTML = `
<td><strong>${rankDisplay}</strong></td>
<td><strong>${escapeHtml(e.nickname)}</strong></td>
<td><span class="gold-text font-weight-bold">${e.score}/${e.total_questions}</span></td>
<td>${e.time_formatted}</td>
<td><span class="badge badge-diff ${diffBadgeClass}">${diffLabel}</span></td>
<td class="text-muted" style="font-size: 0.85rem;">${e.date}</td>
`;
tbody.appendChild(tr);
});
} catch (err) {
tbody.innerHTML = `<tr><td colspan="6" style="text-align:center; color: #ff7675; padding: 20px;">Fehler beim Laden: ${err.message}</td></tr>`;
}
}
function escapeHtml(str) {
if (!str) return "";
return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
}
document.addEventListener("DOMContentLoaded", () => {
onLanguageChange(currentLang);
});
</script>
{% endblock %}
+254
View File
@@ -0,0 +1,254 @@
{% extends "base.html" %}
{% block title %}{% if lang == 'en' %}🛡️ Privacy Policy (Datenschutz){% else %}🛡️ Datenschutzerklärung{% endif %}{% endblock %}
{% block content %}
<div class="quiz-wrapper">
<div class="card legal-card">
<h1 class="glow-title" id="dsTitle">
{% if lang == 'en' %}🛡️ Privacy Policy{% else %}🛡️ Datenschutzerklärung{% endif %}
</h1>
<p class="subtitle" id="dsSub">
{% if lang == 'en' %}Information pursuant to the EU General Data Protection Regulation (GDPR / DSGVO){% else %}Informationen zur Datenverarbeitung gemäß Art. 13 DSGVO{% endif %}
</p>
<div class="legal-content" id="dsContent">
{% if lang == 'en' %}
<h3>1. Responsible Controller</h3>
<p>
Responsible for data processing on <code>dev.waiswuaschd.de</code>:<br>
Maximilian R. (Waiswuaschd Project)<br>
Email: <a href="mailto:kontakt@waiswuaschd.de" style="color: var(--gold);">kontakt@waiswuaschd.de</a>
</p>
<h3 style="margin-top: 20px;">2. General Principles: Data Minimization & Privacy by Design</h3>
<p>
We treat your data with the utmost care. This portal is built with strict data minimization in mind:
<strong>No user accounts are required, no tracking tools (e.g. Google Analytics, Meta Pixel) are used, and no data is sold to third parties.</strong>
</p>
<h3 style="margin-top: 20px;">3. Server Log Files</h3>
<p>
When accessing our website, the web server automatically records standard access data (server logs):
</p>
<ul style="margin-left: 20px; color: var(--text-muted);">
<li>IP address of the requesting device</li>
<li>Date and time of access</li>
<li>Requested URL and HTTP status code</li>
<li>Browser type and version</li>
</ul>
<p style="margin-top: 8px;">
This data is processed pursuant to Art. 6(1)(f) GDPR to ensure technical system stability and IT security (e.g. DDoS protection). Log files are automatically deleted after standard server retention periods.
</p>
<h3 style="margin-top: 20px;">4. Cookies & Local Browser Storage</h3>
<p>
We use strictly necessary technical storage only:
</p>
<ul style="margin-left: 20px; color: var(--text-muted);">
<li><strong>Session Cookie (<code>quiz_session</code>):</strong> A temporary identifier required to coordinate your 10 quiz questions. It expires when you close your browser session.</li>
<li><strong>Browser LocalStorage:</strong> Used solely to remember your chosen language (<code>quiz_lang</code>) and background music setting (<code>bgm_enabled</code>) between pages.</li>
</ul>
<h3 style="margin-top: 20px;">5. Anonymous Leaderboard & Question Suggestions</h3>
<p>
Entering your name into the <em>Hogwarts Hall of Fame (Leaderboard)</em> or submitting a quiz question is completely voluntary.
We explicitly ask you <strong>not</strong> to provide your real name, but rather a playful wizard alias or nickname (e.g. <code>Golden_Snitch_99</code>).
</p>
<h3 style="margin-top: 20px;">6. Your Rights under the GDPR</h3>
<p>
Under EU GDPR, you have the right to information (Art. 15), correction (Art. 16), deletion (Art. 17), and restriction of processing (Art. 18).
For any data privacy inquiries, please contact us at <a href="mailto:kontakt@waiswuaschd.de" style="color: var(--gold);">kontakt@waiswuaschd.de</a>.
</p>
{% else %}
<h3>1. Verantwortliche Stelle</h3>
<p>
Verantwortlich für die Datenverarbeitung auf <code>dev.waiswuaschd.de</code>:<br>
Maximilian R. (Waiswuaschd Projekt)<br>
E-Mail: <a href="mailto:kontakt@waiswuaschd.de" style="color: var(--gold);">kontakt@waiswuaschd.de</a>
</p>
<h3 style="margin-top: 20px;">2. Grundsatz: Datensparsamkeit & Privatsphäre</h3>
<p>
Der Schutz deiner persönlichen Daten ist uns ein wichtiges Anliegen. Unser Quiz-Portal ist nach dem Prinzip der <strong>Datensparsamkeit</strong> konzipiert:
Es gibt <strong>keine Pflicht zur Registrierung, keine Tracking-Tools (wie Google Analytics oder Facebook-Pixel) und keine Weitergabe von Daten an Werbedienstleister</strong>.
</p>
<h3 style="margin-top: 20px;">3. Server-Logfiles</h3>
<p>
Beim Aufrufen unserer Webseiten erfasst der Webserver automatisch technische Informationen (Server-Logfiles):
</p>
<ul style="margin-left: 20px; color: var(--text-muted);">
<li>IP-Adresse des anfragenden Geräts</li>
<li>Datum und Uhrzeit des Zugriffs</li>
<li>Abgerufene URL und HTTP-Statuscode</li>
<li>Browsertyp und Betriebssystem</li>
</ul>
<p style="margin-top: 8px;">
Die Verarbeitung erfolgt auf Grundlage von Art. 6 Abs. 1 lit. f DSGVO zur Gewährleistung der Systemsicherheit, Stabilität und Abwehr von Cyberangriffen. Logfiles werden nach Ablauf der regulären Server-Aufbewahrungsfrist automatisch gelöscht.
</p>
<h3 style="margin-top: 20px;">4. Cookies & Lokale Speicherung (LocalStorage)</h3>
<p>
Wir setzen ausschließlich technisch notwendige Hilfsmittel ein:
</p>
<ul style="margin-left: 20px; color: var(--text-muted);">
<li><strong>Session-Cookie (<code>quiz_session</code>):</strong> Wird benötigt, um deinen Spielstand und deine 10 Fragen während der laufenden Quizrunde zuzuordnen.</li>
<li><strong>Lokaler Browser-Speicher (LocalStorage):</strong> Speichert lediglich deine Spracheinstellung (<code>quiz_lang</code>) und Musikpräferenz (<code>bgm_enabled</code>), damit du diese beim nächsten Seitenaufruf nicht neu einstellen musst.</li>
</ul>
<h3 style="margin-top: 20px;">5. Anonyme Bestenliste & Frage-Vorschläge</h3>
<p>
Der Eintrag in die <em>Hogwarts-Ruhmeshalle (Bestenliste)</em> sowie das Einreichen eigener Quizfragen erfolgt auf rein freiwilliger Basis.
Wir bitten dich ausdrücklich darum, <strong>keine Klarnamen</strong> einzugeben, sondern ein magisches Pseudonym bzw. einen Alias (z. B. <code>Zauberlehrling_42</code>).
</p>
<h3 style="margin-top: 20px;">6. Deine Rechte gemäß DSGVO</h3>
<p>
Du hast nach der DSGVO jederzeit das Recht auf unentgeltliche Auskunft über deine gespeicherten Daten (Art. 15), Berichtigung (Art. 16) oder Löschung (Art. 17). Wende dich bei Fragen gerne direkt per E-Mail an uns.
</p>
{% endif %}
</div>
<div class="btn-group" style="margin-top: 30px;">
<a href="/" class="btn btn-primary" id="btnBackQuizDs">
✨ {% if lang == 'en' %}Back to Quiz{% else %}Zurück zum Quiz{% endif %}
</a>
<a href="/impressum" class="btn btn-secondary" id="btnToImp">
⚖️ {% if lang == 'en' %}Legal Notice{% else %}Zum Impressum{% endif %}
</a>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
const DS_TEXTS = {
de: {
title: "🛡️ Datenschutzerklärung",
sub: "Informationen zur Datenverarbeitung gemäß Art. 13 DSGVO",
btnBack: "✨ Zurück zum Quiz",
btnToImp: "⚖️ Zum Impressum",
html: `
<h3>1. Verantwortliche Stelle</h3>
<p>
Verantwortlich für die Datenverarbeitung auf <code>dev.waiswuaschd.de</code>:<br>
Maximilian R. (Waiswuaschd Projekt)<br>
E-Mail: <a href="mailto:kontakt@waiswuaschd.de" style="color: var(--gold);">kontakt@waiswuaschd.de</a>
</p>
<h3 style="margin-top: 20px;">2. Grundsatz: Datensparsamkeit & Privatsphäre</h3>
<p>
Der Schutz deiner persönlichen Daten ist uns ein wichtiges Anliegen. Unser Quiz-Portal ist nach dem Prinzip der <strong>Datensparsamkeit</strong> konzipiert:
Es gibt <strong>keine Pflicht zur Registrierung, keine Tracking-Tools (wie Google Analytics oder Facebook-Pixel) und keine Weitergabe von Daten an Werbedienstleister</strong>.
</p>
<h3 style="margin-top: 20px;">3. Server-Logfiles</h3>
<p>
Beim Aufrufen unserer Webseiten erfasst der Webserver automatisch technische Informationen (Server-Logfiles):
</p>
<ul style="margin-left: 20px; color: var(--text-muted);">
<li>IP-Adresse des anfragenden Geräts</li>
<li>Datum und Uhrzeit des Zugriffs</li>
<li>Abgerufene URL und HTTP-Statuscode</li>
<li>Browsertyp und Betriebssystem</li>
</ul>
<p style="margin-top: 8px;">
Die Verarbeitung erfolgt auf Grundlage von Art. 6 Abs. 1 lit. f DSGVO zur Gewährleistung der Systemsicherheit, Stabilität und Abwehr von Cyberangriffen. Logfiles werden nach Ablauf der regulären Server-Aufbewahrungsfrist automatisch gelöscht.
</p>
<h3 style="margin-top: 20px;">4. Cookies & Lokale Speicherung (LocalStorage)</h3>
<p>
Wir setzen ausschließlich technisch notwendige Hilfsmittel ein:
</p>
<ul style="margin-left: 20px; color: var(--text-muted);">
<li><strong>Session-Cookie (<code>quiz_session</code>):</strong> Wird benötigt, um deinen Spielstand und deine 10 Fragen während der laufenden Quizrunde zuzuordnen.</li>
<li><strong>Lokaler Browser-Speicher (LocalStorage):</strong> Speichert lediglich deine Spracheinstellung (<code>quiz_lang</code>) und Musikpräferenz (<code>bgm_enabled</code>), damit du diese beim nächsten Seitenaufruf nicht neu einstellen musst.</li>
</ul>
<h3 style="margin-top: 20px;">5. Anonyme Bestenliste & Frage-Vorschläge</h3>
<p>
Der Eintrag in die <em>Hogwarts-Ruhmeshalle (Bestenliste)</em> sowie das Einreichen eigener Quizfragen erfolgt auf rein freiwilliger Basis.
Wir bitten dich ausdrücklich darum, <strong>keine Klarnamen</strong> einzugeben, sondern ein magisches Pseudonym bzw. einen Alias (z. B. <code>Zauberlehrling_42</code>).
</p>
<h3 style="margin-top: 20px;">6. Deine Rechte gemäß DSGVO</h3>
<p>
Du hast nach der DSGVO jederzeit das Recht auf unentgeltliche Auskunft über deine gespeicherten Daten (Art. 15), Berichtigung (Art. 16) oder Löschung (Art. 17). Wende dich bei Fragen gerne direkt per E-Mail an uns.
</p>
`
},
en: {
title: "🛡️ Privacy Policy",
sub: "Information pursuant to the EU General Data Protection Regulation (GDPR / DSGVO)",
btnBack: "✨ Back to Quiz",
btnToImp: "⚖️ Legal Notice",
html: `
<h3>1. Responsible Controller</h3>
<p>
Responsible for data processing on <code>dev.waiswuaschd.de</code>:<br>
Maximilian R. (Waiswuaschd Project)<br>
Email: <a href="mailto:kontakt@waiswuaschd.de" style="color: var(--gold);">kontakt@waiswuaschd.de</a>
</p>
<h3 style="margin-top: 20px;">2. General Principles: Data Minimization & Privacy by Design</h3>
<p>
We treat your data with the utmost care. This portal is built with strict data minimization in mind:
<strong>No user accounts are required, no tracking tools (e.g. Google Analytics, Meta Pixel) are used, and no data is sold to third parties.</strong>
</p>
<h3 style="margin-top: 20px;">3. Server Log Files</h3>
<p>
When accessing our website, the web server automatically records standard access data (server logs):
</p>
<ul style="margin-left: 20px; color: var(--text-muted);">
<li>IP address of the requesting device</li>
<li>Date and time of access</li>
<li>Requested URL and HTTP status code</li>
<li>Browser type and version</li>
</ul>
<p style="margin-top: 8px;">
This data is processed pursuant to Art. 6(1)(f) GDPR to ensure technical system stability and IT security (e.g. DDoS protection). Log files are automatically deleted after standard server retention periods.
</p>
<h3 style="margin-top: 20px;">4. Cookies & Local Browser Storage</h3>
<p>
We use strictly necessary technical storage only:
</p>
<ul style="margin-left: 20px; color: var(--text-muted);">
<li><strong>Session Cookie (<code>quiz_session</code>):</strong> A temporary identifier required to coordinate your 10 quiz questions. It expires when you close your browser session.</li>
<li><strong>Browser LocalStorage:</strong> Used solely to remember your chosen language (<code>quiz_lang</code>) and background music setting (<code>bgm_enabled</code>) between pages.</li>
</ul>
<h3 style="margin-top: 20px;">5. Anonymous Leaderboard & Question Suggestions</h3>
<p>
Entering your name into the <em>Hogwarts Hall of Fame (Leaderboard)</em> or submitting a quiz question is completely voluntary.
We explicitly ask you <strong>not</strong> to provide your real name, but rather a playful wizard alias or nickname (e.g. <code>Golden_Snitch_99</code>).
</p>
<h3 style="margin-top: 20px;">6. Your Rights under the GDPR</h3>
<p>
Under EU GDPR, you have the right to information (Art. 15), correction (Art. 16), deletion (Art. 17), and restriction of processing (Art. 18).
For any data privacy inquiries, please contact us at <a href="mailto:kontakt@waiswuaschd.de" style="color: var(--gold);">kontakt@waiswuaschd.de</a>.
</p>
`
}
};
function onLanguageChange(lang) {
const isDe = (lang !== "en");
const t = isDe ? DS_TEXTS.de : DS_TEXTS.en;
document.getElementById("dsTitle").innerText = t.title;
document.getElementById("dsSub").innerText = t.sub;
document.getElementById("dsContent").innerHTML = t.html;
document.getElementById("btnBackQuizDs").innerText = t.btnBack;
document.getElementById("btnToImp").innerText = t.btnToImp;
}
document.addEventListener("DOMContentLoaded", () => {
onLanguageChange(currentLang);
});
</script>
{% endblock %}
+323 -56
View File
@@ -1,58 +1,86 @@
{% extends "base.html" %}
{% block title %}{% if lang == 'en' %}🎉 The Grand Finale – Magical Quiz{% else %}🎉 Das große Finale – Magisches Quiz{% endif %}{% endblock %}
{% block title %}{% if lang == 'en' %}⚡ Hogwarts Report Card – Grand Finale{% else %}⚡ Das große Hogwarts-Zeugnis{% 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" id="finaleTitle">🎉 Happy Birthday Schatz! 🎉</h1>
<h1 class="glow-title" id="finaleTitle">
{% if lang == 'en' %}⚡ The Great Hogwarts Report Card ⚡{% else %}⚡ Das große Hogwarts-Zeugnis ⚡{% endif %}
</h1>
<span class="sparkle-icon">✨</span>
</div>
<!-- Score & Time Stats -->
<div class="score-banner">
<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!
You achieved <span class="gold-text">{{ score }}</span> out of <span class="gold-text">{{ total }}</span> House Points!
{% else %}
Du hast <span class="gold-text">{{ score }}</span> von <span class="gold-text">{{ total }}</span> möglichen Hauspunkten für dein Haus gesichert!
Du hast <span class="gold-text">{{ score }}</span> von <span class="gold-text">{{ total }}</span> Hauspunkten erzielt!
{% endif %}
</p>
<p class="time-highlight" id="finaleTimeText">
⏱️ {% if lang == 'en' %}Time needed:{% else %}Benötigte Zeit:{% endif %} <strong class="gold-text">{{ time_formatted }}</strong>
</p>
</div>
<div class="reward-box">
<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>
<!-- Tiered Wizard Evaluation -->
<div class="reward-box tier-box" id="tierBox">
<div class="tier-badge-container">
<span id="tierGrade" class="tier-grade-badge {{ tier.badge_class }}">{{ tier.grade }}</span>
</div>
<h2 id="tierTitle">{{ tier.title }}</h2>
<p class="tier-subtitle" id="tierSubtitle">{{ tier.subtitle }}</p>
<p class="reward-description" id="tierDesc">{{ tier.description }}</p>
</div>
<!-- Leaderboard Submission Card -->
<div class="card leaderboard-entry-card" id="lbFormCard">
<h3 id="lbCardTitle">🏆 Trage dich in die Ruhmeshalle ein!</h3>
<p class="subtitle" id="lbCardSub">Völlig anonym – wähle deinen Zauberer-Spitznamen:</p>
<form id="leaderboardForm" class="magic-form" onsubmit="submitScoreToLeaderboard(event)" style="margin-top: 12px;">
<div class="input-with-btn">
<input type="text" id="nicknameInput" class="input-text" placeholder="z. B. Fliegender_Besen oder Lord_Voldi" maxlength="30" required autocomplete="off">
<button type="submit" id="lbSubmitBtn" class="btn btn-primary">
📜 Verewigen
</button>
</div>
</form>
<div id="lbFeedback" class="feedback-box hidden" style="margin-top: 10px;"></div>
<!-- Mini Leaderboard Table -->
<div id="miniLbContainer" class="mini-lb-table-wrapper hidden" style="margin-top: 15px;">
<h4 id="miniLbTitle" style="color: var(--gold); margin-bottom: 8px;">Top Zauberer heute:</h4>
<div class="table-responsive">
<table class="lb-table">
<thead>
<tr>
<th>#</th>
<th id="thNick">Zauberer</th>
<th id="thPoints">Punkte</th>
<th id="thTime">Zeit</th>
</tr>
</thead>
<tbody id="miniLbBody"></tbody>
</table>
</div>
</div>
</div>
<div class="btn-group finale-actions">
<a href="/" class="btn btn-primary" id="playAgainBtn">
{% if lang == 'en' %}🔄 Play Again{% else %}🔄 Erneut spielen{% endif %}
{% if lang == 'en' %}🔄 Play Again{% else %}🔄 Noch einmal spielen{% endif %}
</a>
<a href="/bestenliste" class="btn btn-secondary" id="hallOfFameBtn">
{% if lang == 'en' %}🏆 Hall of Fame{% else %}🏆 Zur Bestenliste{% endif %}
</a>
<a href="/vorschlag" class="btn btn-secondary" id="suggestBtn">
{% if lang == 'en' %}✍️ Suggest Your Own Question{% else %}✍️ Eigene Frage vorschlagen{% endif %}
{% if lang == 'en' %}✍️ Submit Question{% else %}✍️ Frage vorschlagen{% endif %}
</a>
</div>
</div>
@@ -61,46 +89,285 @@
{% block scripts %}
<script>
const finaleScore = {{ score }};
const finaleTotal = {{ total }};
const score = {{ score }};
const total = {{ total }};
const timeSec = {{ time_seconds }};
let scoreAlreadySubmitted = false;
const FINALE_I18N = {
de: {
title: "⚡ Das große Hogwarts-Zeugnis ⚡",
scoreText: `Du hast <span class="gold-text">${score}</span> von <span class="gold-text">${total}</span> Hauspunkten erzielt!`,
timeText: `⏱️ Benötigte Zeit: <strong class="gold-text">${formatSecondsDe(timeSec)}</strong>`,
lbTitle: "🏆 Trage dich in die Ruhmeshalle ein!",
lbSub: "Völlig anonym – wähle deinen Zauberer-Spitznamen:",
lbPlaceholder: "z. B. Fliegender_Besen oder Lord_Voldi",
lbBtn: "📜 Verewigen",
lbBtnSending: "✨ Wird verewigt...",
miniLbTitle: "Top Zauberer:",
thNick: "Zauberer",
thPoints: "Punkte",
thTime: "Zeit",
playAgain: "🔄 Noch einmal spielen",
hallOfFame: "🏆 Zur Bestenliste",
suggest: "✍️ Frage vorschlagen",
rankSuccess: (rank) => `✨ Glückwunsch! Du stehst auf **Platz #${rank}** der Ruhmeshalle!`
},
en: {
title: "⚡ The Great Hogwarts Report Card ⚡",
scoreText: `You achieved <span class="gold-text">${score}</span> out of <span class="gold-text">${total}</span> House Points!`,
timeText: `⏱️ Time needed: <strong class="gold-text">${formatSecondsEn(timeSec)}</strong>`,
lbTitle: "🏆 Enter the Hogwarts Hall of Fame!",
lbSub: "Completely anonymous – choose your wizard nickname:",
lbPlaceholder: "e.g. Flying_Broom or Lord_Voldi",
lbBtn: "📜 Enter Score",
lbBtnSending: "✨ Entering score...",
miniLbTitle: "Top Wizards:",
thNick: "Wizard",
thPoints: "Points",
thTime: "Time",
playAgain: "🔄 Play Again",
hallOfFame: "🏆 Hall of Fame",
suggest: "✍️ Submit Question",
rankSuccess: (rank) => `✨ Congratulations! You are **Rank #${rank}** in the Hall of Fame!`
}
};
function formatSecondsDe(sec) {
if (sec >= 60) {
const m = Math.floor(sec / 60);
const s = sec % 60;
return `${m} Min. ${s} Sek.`;
}
return `${sec} Sek.`;
}
function formatSecondsEn(sec) {
if (sec >= 60) {
const m = Math.floor(sec / 60);
const s = sec % 60;
return `${m} min. ${s} sec.`;
}
return `${sec} sec.`;
}
const TIERS_DATA = {
10: {
grade: "O",
badge_class: "tier-outstanding",
de: {
title: "🧙‍♂️ Großmeister der Zauberei",
sub: "Note: O (Ohnegleichen)",
desc: "Unglaublich! Albus Dumbledore überlegt bereits, dir das Büro des Schulleiters zu überlassen. Du kennst selbst die geheimsten Winkel von Hogwarts besser als der Fast Kopflose Nick. Das Zaubereiministerium verneigt sich in Ehrfurcht!"
},
en: {
title: "🧙‍♂️ Grandmaster of Wizardry",
sub: "Grade: O (Outstanding)",
desc: "Incredible! Albus Dumbledore is already considering handing you the headmaster's office. You know the secret passages of Hogwarts better than Nearly Headless Nick. The Ministry of Magic bows in deep awe!"
}
},
8: {
grade: "E",
badge_class: "tier-exceeds",
de: {
title: "⚡ Meister-Hexe / Zauberer",
sub: "Note: E (Erwartungen übertroffen)",
desc: "Hermine Granger nickt anerkennend und Professor McGonagall vergibt 50 Punkte für dein Haus! Dein Zauberstab zuckt blitzschnell und dein Wissen lässt selbst Ravenclaws erblassen."
},
en: {
title: "⚡ Master Witch / Wizard",
sub: "Grade: E (Exceeds Expectations)",
desc: "Hermione Granger nods in deep approval and Professor McGonagall awards 50 points to your House! Your wandwork is razor-sharp and your knowledge makes even Ravenclaws jealous."
}
},
5: {
grade: "A",
badge_class: "tier-acceptable",
de: {
title: "📜 Solider Hogwarts-Schüler",
sub: "Note: A (Annehmbar)",
desc: "Bestanden! Ein paar Kessel sind zwar im Zaubertränke-Unterricht explodiert und Peeves hat dich mit Kreide beworfen, aber du hast dich tapfer durchgeschlagen. Eine solide magische Leistung!"
},
en: {
title: "📜 Solid Hogwarts Student",
sub: "Grade: A (Acceptable)",
desc: "Passed! A few cauldrons blew up during Potions class and Peeves threw chalk at your head, but you bravely fought your way through. A solid magical performance!"
}
},
2: {
grade: "M",
badge_class: "tier-poor",
de: {
title: "🧹 Quaffel-Fänger in Not",
sub: "Note: M (Mies)",
desc: "Au weia... Hat Ron Weasley dir seinen mit Tesafilm geklebten Zauberstab geliehen? Deine Eule hat sich wohl im Nebel verflogen. Zeit für ein paar Extrastunden in der Bibliothek bei Madam Pince!"
},
en: {
title: "🧹 Quaffle Catcher in Distress",
sub: "Grade: P (Poor)",
desc: "Yikes... Did Ron Weasley lend you his Spellotaped wand? Your owl must have gotten lost in the fog. Time for extra study sessions in the library with Madam Pince!"
}
},
0: {
grade: "T",
badge_class: "tier-troll",
de: {
title: "🧌 Bergtroll im Kerker",
sub: "Note: T (Troll)",
desc: "Alarm im Schloss! Du hast mit der Holzkeule auf die Fragen eingeschlagen. Schnell weg, bevor Hausmeister Filch dich beim lebenslangen Nachsitzen in den Kerkern erwischt!"
},
en: {
title: "🧌 Mountain Troll in the Dungeon",
sub: "Grade: T (Troll)",
desc: "Troll in the dungeon! You smashed the questions with a wooden club. Run quickly before caretaker Filch catches you for lifelong detention in the dungeons!"
}
}
};
function getTierForScore(s) {
if (s >= 10) return TIERS_DATA[10];
if (s >= 8) return TIERS_DATA[8];
if (s >= 5) return TIERS_DATA[5];
if (s >= 2) return TIERS_DATA[2];
return TIERS_DATA[0];
}
function onLanguageChange(lang) {
const isDe = lang === "de";
const isDe = (lang !== "en");
const t = isDe ? FINALE_I18N.de : FINALE_I18N.en;
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 timeText = document.getElementById("finaleTimeText");
const lbTitle = document.getElementById("lbCardTitle");
const lbSub = document.getElementById("lbCardSub");
const nicknameInput = document.getElementById("nicknameInput");
const lbSubmitBtn = document.getElementById("lbSubmitBtn");
const miniLbTitle = document.getElementById("miniLbTitle");
const thNick = document.getElementById("thNick");
const thPoints = document.getElementById("thPoints");
const thTime = document.getElementById("thTime");
const playAgain = document.getElementById("playAgainBtn");
const hallOfFame = document.getElementById("hallOfFameBtn");
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 (title) title.innerText = t.title;
if (scoreText) scoreText.innerHTML = t.scoreText;
if (timeText) timeText.innerHTML = t.timeText;
if (lbTitle) lbTitle.innerText = t.lbTitle;
if (lbSub) lbSub.innerText = t.lbSub;
if (nicknameInput) nicknameInput.placeholder = t.lbPlaceholder;
if (lbSubmitBtn && !lbSubmitBtn.disabled) lbSubmitBtn.innerText = t.lbBtn;
if (miniLbTitle) miniLbTitle.innerText = t.miniLbTitle;
if (thNick) thNick.innerText = t.thNick;
if (thPoints) thPoints.innerText = t.thPoints;
if (thTime) thTime.innerText = t.thTime;
if (playAgain) playAgain.innerText = t.playAgain;
if (hallOfFame) hallOfFame.innerText = t.hallOfFame;
if (suggest) suggest.innerText = t.suggest;
// Tier evaluation
const tier = getTierForScore(score);
const tierContent = isDe ? tier.de : tier.en;
const tierGrade = document.getElementById("tierGrade");
const tierTitle = document.getElementById("tierTitle");
const tierSubtitle = document.getElementById("tierSubtitle");
const tierDesc = document.getElementById("tierDesc");
if (tierGrade) {
tierGrade.className = `tier-grade-badge ${tier.badge_class}`;
tierGrade.innerText = tier.grade;
}
if (rewardTitle) {
rewardTitle.innerText = isDe
? "💛 Dein magischer Gewinn: Ein unvergesslicher Abend Zuhause 💛"
: "💛 Your Magical Reward: An Unforgettable Evening at Home 💛";
if (tierTitle) tierTitle.innerText = tierContent.title;
if (tierSubtitle) tierSubtitle.innerText = tierContent.sub;
if (tierDesc) tierDesc.innerText = tierContent.desc;
}
async function submitScoreToLeaderboard(event) {
event.preventDefault();
if (scoreAlreadySubmitted) return;
const isDe = (currentLang !== "en");
const t = isDe ? FINALE_I18N.de : FINALE_I18N.en;
const nickname = document.getElementById("nicknameInput").value.trim();
const submitBtn = document.getElementById("lbSubmitBtn");
const feedback = document.getElementById("lbFeedback");
submitBtn.disabled = true;
submitBtn.innerText = t.lbBtnSending;
try {
const res = await fetch("/api/leaderboard", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
nickname: nickname,
score: score,
total_questions: total,
time_seconds: timeSec,
difficulty: "all",
lang: currentLang
})
});
const data = await res.json();
if (res.ok && data.success) {
scoreAlreadySubmitted = true;
feedback.className = "feedback-box feedback-success";
feedback.innerHTML = `
<div style="background: rgba(46, 204, 113, 0.2); border: 1px solid #2ecc71; color: #55efc4; padding: 12px; border-radius: 8px;">
✨ <strong>${data.nickname}</strong>: ${t.rankSuccess(data.rank)}
</div>
`;
feedback.classList.remove("hidden");
document.getElementById("nicknameInput").disabled = true;
submitBtn.innerText = isDe ? "✅ Eingetragen" : "✅ Entered";
loadMiniLeaderboard();
} else {
feedback.className = "feedback-box alert alert-danger";
feedback.innerText = data.detail || (isDe ? "Fehler beim Eintragen." : "Error entering score.");
feedback.classList.remove("hidden");
submitBtn.disabled = false;
submitBtn.innerText = t.lbBtn;
}
} catch (err) {
feedback.className = "feedback-box alert alert-danger";
feedback.innerText = "Netzwerkfehler: " + err.message;
feedback.classList.remove("hidden");
submitBtn.disabled = false;
submitBtn.innerText = t.lbBtn;
}
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!";
}
async function loadMiniLeaderboard() {
try {
const res = await fetch("/api/leaderboard?limit=5");
const entries = await res.json();
const body = document.getElementById("miniLbBody");
const container = document.getElementById("miniLbContainer");
body.innerHTML = "";
const medals = ["🥇", "🥈", "🥉"];
entries.forEach(e => {
const tr = document.createElement("tr");
const rankDisplay = medals[e.rank - 1] || `${e.rank}.`;
tr.innerHTML = `
<td><strong>${rankDisplay}</strong></td>
<td>${e.nickname}</td>
<td><span class="gold-text">${e.score}/${e.total_questions}</span></td>
<td>${e.time_formatted}</td>
`;
body.appendChild(tr);
});
container.classList.remove("hidden");
} catch (err) {
console.error("Could not load mini leaderboard", err);
}
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);
loadMiniLeaderboard();
});
</script>
{% endblock %}
+164
View File
@@ -0,0 +1,164 @@
{% extends "base.html" %}
{% block title %}{% if lang == 'en' %}⚖️ Legal Notice (Impressum){% else %}⚖️ Impressum{% endif %}{% endblock %}
{% block content %}
<div class="quiz-wrapper">
<div class="card legal-card">
<h1 class="glow-title" id="impTitle">
{% if lang == 'en' %}⚖️ Legal Notice (Impressum){% else %}⚖️ Impressum{% endif %}
</h1>
<p class="subtitle" id="impSub">
{% if lang == 'en' %}Information according to § 5 DDG{% else %}Angaben gemäß § 5 Digitale-Dienste-Gesetz (DDG){% endif %}
</p>
<div class="legal-content" id="impContent">
{% if lang == 'en' %}
<h3>Website Operator & Contact</h3>
<p>
<strong>Magisches Hogwarts Escape Quiz</strong><br>
A private, non-commercial web project on <code>dev.waiswuaschd.de</code>.<br>
Operator: Maximilian R. (Waiswuaschd Project)<br>
Contact: <a href="mailto:kontakt@waiswuaschd.de" style="color: var(--gold);">kontakt@waiswuaschd.de</a>
</p>
<h3 style="margin-top: 20px;">Non-Commercial Fan Project Disclaimer</h3>
<p>
This website is a purely non-commercial, private fan project created for educational, puzzle, and entertainment purposes.
No commercial profit or advertising revenue is generated through this service.
</p>
<h3 style="margin-top: 20px;">Copyright & Trademarks</h3>
<p>
<strong>Harry Potter</strong> characters, names, and related indicia are trademarks of and © Warner Bros. Entertainment Inc. and J.K. Rowling.
Harry Potter publishing rights © Bloomsbury Publishing Plc and Carlsen Verlag.
All rights belong to their respective copyright and trademark owners. No infringement is intended.
</p>
<h3 style="margin-top: 20px;">Liability for Content & External Links</h3>
<p>
As a service provider, we are responsible for our own content on these pages under general laws. We make every effort to keep information up to date and accurate, but cannot accept liability for external links to third-party websites over whose content we have no control.
</p>
{% else %}
<h3>Diensteanbieter & Kontakt</h3>
<p>
<strong>Magisches Hogwarts-Escape-Quiz</strong><br>
Ein rein privates, nicht-kommerzielles Projekt auf <code>dev.waiswuaschd.de</code>.<br>
Betreiber: Maximilian R. (Waiswuaschd Projekt)<br>
E-Mail-Kontakt: <a href="mailto:kontakt@waiswuaschd.de" style="color: var(--gold);">kontakt@waiswuaschd.de</a>
</p>
<h3 style="margin-top: 20px;">Nicht-kommerzielles Fan-Projekt</h3>
<p>
Diese Webseite ist ein reines Fan-Projekt, das unentgeltlich und aus Freude an Zauberrätseln und Harry Potter betrieben wird. Es werden keinerlei kommerzielle Interessen verfolgt, keine Werbung geschaltet und keine Einnahmen erzielt.
</p>
<h3 style="margin-top: 20px;">Urheberrecht & Markenzeichen</h3>
<p>
<strong>Harry Potter</strong>, Charaktere, Namen und zugehörige Kennzeichen sind Marken von und © Warner Bros. Entertainment Inc. und J.K. Rowling.<br>
Die Verlagsrechte liegen bei Bloomsbury Publishing Plc sowie Carlsen Verlag GmbH. Alle Rechte verbleiben bei den jeweiligen Rechteinhabern. Es besteht keinerlei Absicht zur Verletzung von Marken- oder Urheberrechten.
</p>
<h3 style="margin-top: 20px;">Haftung für Inhalte und Links</h3>
<p>
Die Inhalte unserer Seiten wurden mit größter Sorgfalt erstellt. Für die Richtigkeit, Vollständigkeit und Aktualität der Inhalte können wir jedoch keine Gewähr übernehmen. Verlinkungen zu externen Webseiten Dritter unterliegen der Haftung der jeweiligen Betreiber.
</p>
{% endif %}
</div>
<div class="btn-group" style="margin-top: 30px;">
<a href="/" class="btn btn-primary" id="btnBackQuiz">
✨ {% if lang == 'en' %}Back to Quiz{% else %}Zurück zum Quiz{% endif %}
</a>
<a href="/datenschutz" class="btn btn-secondary" id="btnToPrivacy">
🛡️ {% if lang == 'en' %}Privacy Policy{% else %}Zur Datenschutzerklärung{% endif %}
</a>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
const IMP_TEXTS = {
de: {
title: "⚖️ Impressum",
sub: "Angaben gemäß § 5 Digitale-Dienste-Gesetz (DDG)",
btnBack: "✨ Zurück zum Quiz",
btnToPrivacy: "🛡️ Zur Datenschutzerklärung",
html: `
<h3>Diensteanbieter & Kontakt</h3>
<p>
<strong>Magisches Hogwarts-Escape-Quiz</strong><br>
Ein rein privates, nicht-kommerzielles Projekt auf <code>dev.waiswuaschd.de</code>.<br>
Betreiber: Maximilian R. (Waiswuaschd Projekt)<br>
E-Mail-Kontakt: <a href="mailto:kontakt@waiswuaschd.de" style="color: var(--gold);">kontakt@waiswuaschd.de</a>
</p>
<h3 style="margin-top: 20px;">Nicht-kommerzielles Fan-Projekt</h3>
<p>
Diese Webseite ist ein reines Fan-Projekt, das unentgeltlich und aus Freude an Zauberrätseln und Harry Potter betrieben wird. Es werden keinerlei kommerzielle Interessen verfolgt, keine Werbung geschaltet und keine Einnahmen erzielt.
</p>
<h3 style="margin-top: 20px;">Urheberrecht & Markenzeichen</h3>
<p>
<strong>Harry Potter</strong>, Charaktere, Namen und zugehörige Kennzeichen sind Marken von und © Warner Bros. Entertainment Inc. und J.K. Rowling.<br>
Die Verlagsrechte liegen bei Bloomsbury Publishing Plc sowie Carlsen Verlag GmbH. Alle Rechte verbleiben bei den jeweiligen Rechteinhabern. Es besteht keinerlei Absicht zur Verletzung von Marken- oder Urheberrechten.
</p>
<h3 style="margin-top: 20px;">Haftung für Inhalte und Links</h3>
<p>
Die Inhalte unserer Seiten wurden mit größter Sorgfalt erstellt. Für die Richtigkeit, Vollständigkeit und Aktualität der Inhalte können wir jedoch keine Gewähr übernehmen. Verlinkungen zu externen Webseiten Dritter unterliegen der Haftung der jeweiligen Betreiber.
</p>
`
},
en: {
title: "⚖️ Legal Notice (Impressum)",
sub: "Information according to § 5 DDG",
btnBack: "✨ Back to Quiz",
btnToPrivacy: "🛡️ Privacy Policy",
html: `
<h3>Website Operator & Contact</h3>
<p>
<strong>Magical Hogwarts Escape Quiz</strong><br>
A private, non-commercial web project on <code>dev.waiswuaschd.de</code>.<br>
Operator: Maximilian R. (Waiswuaschd Project)<br>
Contact: <a href="mailto:kontakt@waiswuaschd.de" style="color: var(--gold);">kontakt@waiswuaschd.de</a>
</p>
<h3 style="margin-top: 20px;">Non-Commercial Fan Project Disclaimer</h3>
<p>
This website is a purely non-commercial, private fan project created for educational, puzzle, and entertainment purposes.
No commercial profit or advertising revenue is generated through this service.
</p>
<h3 style="margin-top: 20px;">Copyright & Trademarks</h3>
<p>
<strong>Harry Potter</strong> characters, names, and related indicia are trademarks of and © Warner Bros. Entertainment Inc. and J.K. Rowling.
Harry Potter publishing rights © Bloomsbury Publishing Plc and Carlsen Verlag.
All rights belong to their respective copyright and trademark owners. No infringement is intended.
</p>
<h3 style="margin-top: 20px;">Liability for Content & External Links</h3>
<p>
As a service provider, we are responsible for our own content on these pages under general laws. We make every effort to keep information up to date and accurate, but cannot accept liability for external links to third-party websites over whose content we have no control.
</p>
`
}
};
function onLanguageChange(lang) {
const isDe = (lang !== "en");
const t = isDe ? IMP_TEXTS.de : IMP_TEXTS.en;
document.getElementById("impTitle").innerText = t.title;
document.getElementById("impSub").innerText = t.sub;
document.getElementById("impContent").innerHTML = t.html;
document.getElementById("btnBackQuiz").innerText = t.btnBack;
document.getElementById("btnToPrivacy").innerText = t.btnToPrivacy;
}
document.addEventListener("DOMContentLoaded", () => {
onLanguageChange(currentLang);
});
</script>
{% endblock %}
+5 -4
View File
@@ -53,10 +53,11 @@
<!-- Spielbereich (initial versteckt) -->
<div id="gameCard" class="game-area hidden">
<!-- Fortschritt & Punkte -->
<!-- Fortschritt & Punkte & Zeit -->
<div class="status-panel">
<div class="progress-info">
<span id="questionCounter">Frage 1 von 10</span>
<span id="timerBadge" class="timer-badge">⏱️ 00:00</span>
<span id="pointsCounter" class="points-badge">🏆 Hauspunkte: 0</span>
</div>
<div class="progress-container">
@@ -82,10 +83,10 @@
<div class="btn-group">
<button id="checkBtn" class="btn btn-primary" onclick="checkAnswer()">
✨ Zauber prüfen
✨ Antwort prüfen
</button>
<button id="hintBtn" class="btn btn-secondary" onclick="requestHint()">
💡 Zaubertipp
💡 Hinweis anzeigen
</button>
</div>
</div>
@@ -95,7 +96,7 @@
<div id="choiceOptionsContainer" class="choice-grid"></div>
<div class="btn-group" style="margin-top: 15px;">
<button id="hintBtnMC" class="btn btn-secondary" onclick="requestHint()">
💡 Zaubertipp
💡 Hinweis anzeigen
</button>
</div>
</div>