Feature: 100-point scoring, question memory & spaced repetition, random wizard name generator, cache-busting v2.2, gold footer pills

This commit is contained in:
kobold-ai
2026-09-21 13:53:33 +02:00
parent 26ff0660d3
commit a938123271
10 changed files with 257 additions and 33 deletions
+1 -1
View File
@@ -309,5 +309,5 @@
{% endblock %}
{% block scripts %}
<script src="/static/js/admin.js"></script>
<script src="/static/js/admin.js?v=2.2"></script>
{% endblock %}
+1 -1
View File
@@ -5,7 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0">
<title>{% block title %}Magisches Harry-Potter-Escape-Quiz{% endblock %}</title>
<link rel="icon" href="/static/favicon.ico">
<link rel="stylesheet" href="/static/css/style.css">
<link rel="stylesheet" href="/static/css/style.css?v=2.2">
{% block head %}{% endblock %}
</head>
<body>
+1 -1
View File
@@ -181,7 +181,7 @@
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><span class="gold-text font-weight-bold">${e.score} Pkt.</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>
+55 -9
View File
@@ -17,9 +17,9 @@
<div class="score-banner">
<p class="score-highlight" id="finaleScoreText">
{% if lang == 'en' %}
You achieved <span class="gold-text">{{ score }}</span> out of <span class="gold-text">{{ total }}</span> House Points!
You achieved <span class="gold-text">{{ score }}</span> out of <span class="gold-text">{{ max_score }}</span> House Points!
{% else %}
Du hast <span class="gold-text">{{ score }}</span> von <span class="gold-text">{{ total }}</span> Hauspunkten erzielt!
Du hast <span class="gold-text">{{ score }}</span> von <span class="gold-text">{{ max_score }}</span> Hauspunkten erzielt!
{% endif %}
</p>
<p class="time-highlight" id="finaleTimeText">
@@ -45,6 +45,9 @@
<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="button" id="diceBtn" class="btn btn-secondary btn-dice" onclick="rollRandomName()" title="Zufälliger Zauberer-Name">
🎲 <span class="dice-label" id="diceLabel">Zufall</span>
</button>
<button type="submit" id="lbSubmitBtn" class="btn btn-primary">
📜 Verewigen
</button>
@@ -91,17 +94,55 @@
<script>
const score = {{ score }};
const total = {{ total }};
const maxScore = {{ max_score }};
const timeSec = {{ time_seconds }};
let scoreAlreadySubmitted = false;
const WIZARD_NAMES = {
de: [
"Fliegender_Besen", "Goldener_Schnatz", "Butterbier_Genießer",
"Severus_Snape_Fan", "Dumbledores_Phaenix", "Hogwarts_Spion",
"Peeves_Geist", "Niffler_Raeuber", "Gryffindor_Loewe",
"Ravenclaw_Adler", "Hufflepuff_Dachs", "Slytherin_Schlange",
"Mondkalb_Freund", "Zauberschach_Meister", "Kessel_Explodierer",
"Alraunen_Zaehmer", "Krummbein_Fan", "Tarnumhang_Traeger",
"Zeitumkehrer_Reisender", "Kuerbissaft_Trinker", "Patronus_Beschwoerer",
"Erlesener_Zauberstab", "Kopfloser_Reiter", "Schokofrosch_Sammler",
"Verbotene_Abteilung", "Hippogreif_Reiter", "Hauselfen_Freund"
],
en: [
"Flying_Broom", "Golden_Snitch", "Butterbeer_Lover",
"Severus_Fan", "Dumbledores_Phoenix", "Hogwarts_Spy",
"Peeves_Poltergeist", "Niffler_Bandit", "Gryffindor_Lion",
"Ravenclaw_Eagle", "Hufflepuff_Badger", "Slytherin_Snake",
"Mooncalf_Buddy", "Wizard_Chess_Master", "Cauldron_Exploder",
"Mandrake_Whisperer", "Crookshanks_Fan", "Invisibility_Bearer",
"Time_Turner_Voyager", "Pumpkin_Juice_Drinker", "Patronus_Caster",
"Elder_Wand_Seeker", "Headless_Hunt_Guest", "Chocolate_Frog_Collector",
"Restricted_Section", "Hippogriff_Rider", "House_Elf_Defender"
]
};
function rollRandomName() {
const isDe = (currentLang !== "en");
const list = isDe ? WIZARD_NAMES.de : WIZARD_NAMES.en;
const name = list[Math.floor(Math.random() * list.length)];
const input = document.getElementById("nicknameInput");
if (input) {
input.value = name;
input.focus();
}
}
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!`,
scoreText: `Du hast <span class="gold-text">${score}</span> von <span class="gold-text">${maxScore}</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",
diceLabel: "Zufall",
lbBtn: "📜 Verewigen",
lbBtnSending: "✨ Wird verewigt...",
miniLbTitle: "Top Zauberer:",
@@ -115,11 +156,12 @@
},
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!`,
scoreText: `You achieved <span class="gold-text">${score}</span> out of <span class="gold-text">${maxScore}</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",
diceLabel: "Random",
lbBtn: "📜 Enter Score",
lbBtnSending: "✨ Entering score...",
miniLbTitle: "Top Wizards:",
@@ -225,10 +267,11 @@
};
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];
const pct = (s <= 10 && total <= 10) ? (s / total) : (s / maxScore);
if (pct >= 0.90) return TIERS_DATA[10];
if (pct >= 0.70) return TIERS_DATA[8];
if (pct >= 0.50) return TIERS_DATA[5];
if (pct >= 0.20) return TIERS_DATA[2];
return TIERS_DATA[0];
}
@@ -242,6 +285,7 @@
const lbTitle = document.getElementById("lbCardTitle");
const lbSub = document.getElementById("lbCardSub");
const nicknameInput = document.getElementById("nicknameInput");
const diceLabel = document.getElementById("diceLabel");
const lbSubmitBtn = document.getElementById("lbSubmitBtn");
const miniLbTitle = document.getElementById("miniLbTitle");
const thNick = document.getElementById("thNick");
@@ -256,6 +300,7 @@
if (timeText) timeText.innerHTML = t.timeText;
if (lbTitle) lbTitle.innerText = t.lbTitle;
if (lbSub) lbSub.innerText = t.lbSub;
if (diceLabel) diceLabel.innerText = t.diceLabel;
if (nicknameInput) nicknameInput.placeholder = t.lbPlaceholder;
if (lbSubmitBtn && !lbSubmitBtn.disabled) lbSubmitBtn.innerText = t.lbBtn;
if (miniLbTitle) miniLbTitle.innerText = t.miniLbTitle;
@@ -354,7 +399,7 @@
tr.innerHTML = `
<td><strong>${rankDisplay}</strong></td>
<td>${e.nickname}</td>
<td><span class="gold-text">${e.score}/${e.total_questions}</span></td>
<td><span class="gold-text">${e.score} Pkt.</span></td>
<td>${e.time_formatted}</td>
`;
body.appendChild(tr);
@@ -368,6 +413,7 @@
document.addEventListener("DOMContentLoaded", () => {
onLanguageChange(currentLang);
loadMiniLeaderboard();
rollRandomName();
});
</script>
{% endblock %}
+1 -1
View File
@@ -113,5 +113,5 @@
{% endblock %}
{% block scripts %}
<script src="/static/js/quiz.js"></script>
<script src="/static/js/quiz.js?v=2.2"></script>
{% endblock %}
+1 -1
View File
@@ -70,5 +70,5 @@
{% endblock %}
{% block scripts %}
<script src="/static/js/vorschlag.js"></script>
<script src="/static/js/vorschlag.js?v=2.2"></script>
{% endblock %}