diff --git a/.gamification.json b/.gamification.json
new file mode 100644
index 0000000..15de29d
--- /dev/null
+++ b/.gamification.json
@@ -0,0 +1,14 @@
+{
+ "spieler_name": "Python-Entwicklerin",
+ "xp": 0,
+ "level": 1,
+ "titel": "Code-Küken 🐣",
+ "rang": "Bronze I",
+ "geloeste_kapitel": [],
+ "bestandene_tests": 0,
+ "freigeschaltete_badges": [],
+ "streak_tage": 1,
+ "letzter_aktiver_tag": "2026-08-29",
+ "historie": [],
+ "zuletzt_aktualisiert": "2026-08-29T14:51:01.563840"
+}
\ No newline at end of file
diff --git a/assets/gamification.js b/assets/gamification.js
new file mode 100644
index 0000000..f45ed3c
--- /dev/null
+++ b/assets/gamification.js
@@ -0,0 +1,205 @@
+/**
+ * 🎮 GAMIFICATION ENGINE FÜR DAS PYTHON LERNPORTAL 🎮
+ * ====================================================
+ * Funktioniert 100% lokal ohne Server (file://) via gamification_data.js & localStorage!
+ */
+
+(function () {
+ // Standard-Kataloge als Fallback, falls gamification_data.js noch nicht existiert
+ const DEFAULT_LEVELS = [
+ { level: 1, min_xp: 0, max_xp: 150, titel: "Code-Küken 🐣", rang: "Bronze I" },
+ { level: 2, min_xp: 151, max_xp: 400, titel: "Code-Detektivin 🔍", rang: "Bronze II" },
+ { level: 3, min_xp: 401, max_xp: 800, titel: "Logik-Bastlerin ⚙️", rang: "Silber I" },
+ { level: 4, min_xp: 801, max_xp: 1300, titel: "Klassen-Baumeisterin 🏗️", rang: "Silber II" },
+ { level: 5, min_xp: 1301, max_xp: 2000, titel: "Git-Strategin 🌿", rang: "Gold I" },
+ { level: 6, min_xp: 2001, max_xp: 2900, titel: "TDD-Qualitätsprüferin 🧪", rang: "Gold II" },
+ { level: 7, min_xp: 2901, max_xp: 4000, titel: "Vererbungs-Meisterin 🧬", rang: "Platin I" },
+ { level: 8, min_xp: 4001, max_xp: 5300, titel: "Exception-Wächterin 🛡️", rang: "Platin II" },
+ { level: 9, min_xp: 5301, max_xp: 6800, titel: "GUI-Entwicklerin 🖥️", rang: "Diamant" },
+ { level: 10, min_xp: 6801, max_xp: 99999, titel: "Software-Architektin 🏆", rang: "Großmeisterin" }
+ ];
+
+ const DEFAULT_BADGES = {
+ rechenkonegin: { icon: "🧮", name: "Rechenkönigin", desc: "Grundlagen G01 & G02 abgeschlossen (Zahlen, Operatoren & Typen gemeistert)." },
+ dialog_profi: { icon: "💬", name: "Dialog-Profi", desc: "Grundlagen G03 abgeschlossen (Interaktive Ein-/Ausgabe mit f-Strings)." },
+ weichenstellerin: { icon: "🚦", name: "Weichenstellerin", desc: "Grundlagen G04 abgeschlossen (Bedingungslogik & Schaltjahre gemeistert)." },
+ schleifen_dompteurin: { icon: "🎡", name: "Schleifen-Dompteurin", desc: "Grundlagen G05 abgeschlossen (While- & For-Schleifen gebändigt)." },
+ funktions_zauberin: { icon: "🪄", name: "Funktions-Zauberin", desc: "Grundlagen G06 abgeschlossen (Eigene Funktionen & Module gebaut)." },
+ listen_archivarin: { icon: "📋", name: "Listen-Archivarin", desc: "Grundlagen G07 & G08 abgeschlossen (Sequenzen & Strings analysiert)." },
+ daten_strategin: { icon: "🗃️", name: "Daten-Strategin", desc: "Grundlagen G09 & G10 abgeschlossen (Dicts, Sets & Comprehensions gemeistert)." },
+ bug_jaegerin: { icon: "🔍", name: "Bug-Jägerin", desc: "Kapitel 00 gelöst (Alle Fehler-Bugs im Warm-up aufgespürt)." },
+ erste_architektin: { icon: "🏗️", name: "Erste Architektin", desc: "Kapitel 01 bis 03 gelöst (Erste OOP-Klassen, Konstruktoren & Methoden)." },
+ zeit_reisende: { icon: "🌿", name: "Git-Zeitreisende", desc: "Kapitel 04c Git-Versionskontrolle verstanden & Spielstände gesichert." },
+ tamagotchi_mama: { icon: "🥚", name: "Tamagotchi-Mama", desc: "Kapitel 06 Mini-Projekt abgeschlossen (Ein lebendiges Haustier gebaut)." },
+ qualitaets_garantin: { icon: "🧪", name: "TDD-Qualitätsgarantin", desc: "Kapitel 09 abgeschlossen (Eigene professionelle Unit Tests geschrieben)." },
+ oop_grossmeisterin: { icon: "🧬", name: "Vererbungs-Koryphäe", desc: "Kapitel 10 bis 12 abgeschlossen (Vererbung, Polymorphie & Exceptions)." },
+ software_architektin: { icon: "🏆", name: "Meister-Entwicklerin", desc: "Kapitel 16 Master-Projekt abgeschlossen & vollwertige Desktop-App gebaut!" }
+ };
+
+ // Daten abrufen
+ function getGamificationData() {
+ if (window.GAMIFICATION_DATA) {
+ return window.GAMIFICATION_DATA;
+ }
+ const saved = localStorage.getItem("python_gamification_state");
+ if (saved) {
+ try { return JSON.parse(saved); } catch (e) { }
+ }
+ return {
+ spieler_name: "Python-Entwicklerin",
+ xp: 0,
+ level: 1,
+ titel: "Code-Küken 🐣",
+ rang: "Bronze I",
+ geloeste_kapitel: [],
+ bestandene_tests: 0,
+ freigeschaltete_badges: [],
+ streak_tage: 1
+ };
+ }
+
+ function getLevelInfo(xp) {
+ const levels = window.GAMIFICATION_LEVEL_STUFEN || DEFAULT_LEVELS;
+ for (let info of levels) {
+ if (xp >= info.min_xp && xp <= info.max_xp) {
+ const nextXp = info.max_xp + 1;
+ const xpInLevel = xp - info.min_xp;
+ const xpForLevel = nextXp - info.min_xp;
+ const prozent = xpForLevel > 0 ? Math.min(100, Math.round((xpInLevel / xpForLevel) * 100)) : 100;
+ return { ...info, naechste_stufe_xp: nextXp, prozent: prozent };
+ }
+ }
+ return { level: 10, titel: "Software-Architektin 🏆", rang: "Großmeisterin", naechste_stufe_xp: 99999, prozent: 100 };
+ }
+
+ // HUD & Trophäenraum rendern
+ function renderGamificationHUD() {
+ const data = getGamificationData();
+ const lvlInfo = getLevelInfo(data.xp || 0);
+ const badgesKatalog = window.GAMIFICATION_BADGES_KATALOG || DEFAULT_BADGES;
+ const unlockedBadges = new Set(data.freigeschaltete_badges || []);
+ const badgeCount = unlockedBadges.size;
+ const totalBadges = Object.keys(badgesKatalog).length;
+
+ // Finde oder erstelle den HUD-Container
+ let hud = document.getElementById("gamer-hud");
+ if (!hud) {
+ const mainContainer = document.querySelector(".container") || document.body;
+ hud = document.createElement("div");
+ hud.id = "gamer-hud";
+ mainContainer.prepend(hud);
+ }
+
+ hud.innerHTML = `
+
+
+
+
+
+ Fortschritt zu Level ${lvlInfo.level + 1}
+ ${data.xp || 0} / ${lvlInfo.naechste_stufe_xp} XP (${lvlInfo.prozent}%)
+
+
+
+
+
+
+
+
🎖️ Dein Trophäenraum
+
+ ${Object.keys(badgesKatalog).map(bId => {
+ const b = badgesKatalog[bId];
+ const isUnlocked = unlockedBadges.has(bId);
+ return `
+
+
${isUnlocked ? b.icon : '🔒'}
+
+
${b.name}
+
${b.desc}
+
${isUnlocked ? '✅ Freigeschaltet' : '🔒 Gesperrt'}
+
+
+ `;
+ }).join("")}
+
+
+
+ `;
+
+ // Event Listener für Trophäenraum Toggle
+ const toggleBtn = document.getElementById("toggle-trophies-btn");
+ const trophyRoom = document.getElementById("trophy-room");
+ if (toggleBtn && trophyRoom) {
+ toggleBtn.addEventListener("click", function () {
+ const isHidden = trophyRoom.style.display === "none";
+ trophyRoom.style.display = isHidden ? "block" : "none";
+ toggleBtn.innerText = isHidden
+ ? `🔼 Trophäenraum einklappen (${badgeCount}/${totalBadges})`
+ : `🏆 Trophäenraum öffnen (${badgeCount}/${totalBadges} freigeschaltet)`;
+ });
+ }
+ }
+
+ // Checkbox-Persistenz für alle Aufgaben-Seiten (localStorage)
+ function initTaskCheckboxes() {
+ const checkboxes = document.querySelectorAll(".task-checkbox");
+ if (!checkboxes.length) return;
+
+ const pageKey = "task_progress_" + window.location.pathname;
+ const savedStates = JSON.parse(localStorage.getItem(pageKey) || "{}");
+
+ checkboxes.forEach((cb) => {
+ const id = cb.id;
+ if (id && savedStates[id]) {
+ cb.checked = true;
+ }
+ cb.addEventListener("change", function () {
+ const states = JSON.parse(localStorage.getItem(pageKey) || "{}");
+ states[id] = cb.checked;
+ localStorage.setItem(pageKey, JSON.stringify(states));
+ });
+ });
+ }
+
+ // Initialisierung beim Laden
+ if (document.readyState === "loading") {
+ document.addEventListener("DOMContentLoaded", function () {
+ renderGamificationHUD();
+ initTaskCheckboxes();
+ });
+ } else {
+ renderGamificationHUD();
+ initTaskCheckboxes();
+ }
+})();
diff --git a/assets/gamification_data.js b/assets/gamification_data.js
new file mode 100644
index 0000000..ef17816
--- /dev/null
+++ b/assets/gamification_data.js
@@ -0,0 +1,236 @@
+// Automatisch generierter lokaler Gamification-Spielstand
+window.GAMIFICATION_DATA = {
+ "spieler_name": "Python-Entwicklerin",
+ "xp": 0,
+ "level": 1,
+ "titel": "Code-Küken 🐣",
+ "rang": "Bronze I",
+ "geloeste_kapitel": [],
+ "bestandene_tests": 0,
+ "freigeschaltete_badges": [],
+ "streak_tage": 1,
+ "letzter_aktiver_tag": "2026-08-29",
+ "historie": [],
+ "zuletzt_aktualisiert": "2026-08-29T14:51:01.563840"
+};
+window.GAMIFICATION_BADGES_KATALOG = {
+ "rechenkonegin": {
+ "id": "rechenkonegin",
+ "icon": "🧮",
+ "name": "Rechenkönigin",
+ "desc": "Grundlagen G01 & G02 abgeschlossen (Zahlen, Operatoren & Typen gemeistert).",
+ "xp_bonus": 100,
+ "kriterium": [
+ "g01_erste_schritte_taschenrechner",
+ "g02_variablen_und_datentypen"
+ ]
+ },
+ "dialog_profi": {
+ "id": "dialog_profi",
+ "icon": "💬",
+ "name": "Dialog-Profi",
+ "desc": "Grundlagen G03 abgeschlossen (Interaktive Ein-/Ausgabe mit f-Strings).",
+ "xp_bonus": 75,
+ "kriterium": [
+ "g03_ein_und_ausgabe"
+ ]
+ },
+ "weichenstellerin": {
+ "id": "weichenstellerin",
+ "icon": "🚦",
+ "name": "Weichenstellerin",
+ "desc": "Grundlagen G04 abgeschlossen (Bedingungslogik & Schaltjahre gemeistert).",
+ "xp_bonus": 75,
+ "kriterium": [
+ "g04_verzweigungen_und_bedingungen"
+ ]
+ },
+ "schleifen_dompteurin": {
+ "id": "schleifen_dompteurin",
+ "icon": "🎡",
+ "name": "Schleifen-Dompteurin",
+ "desc": "Grundlagen G05 abgeschlossen (While- & For-Schleifen gebändigt).",
+ "xp_bonus": 100,
+ "kriterium": [
+ "g05_schleifen_und_wiederholungen"
+ ]
+ },
+ "funktions_zauberin": {
+ "id": "funktions_zauberin",
+ "icon": "🪄",
+ "name": "Funktions-Zauberin",
+ "desc": "Grundlagen G06 abgeschlossen (Eigene Funktionen & Module gebaut).",
+ "xp_bonus": 100,
+ "kriterium": [
+ "g06_funktionen_und_module"
+ ]
+ },
+ "listen_archivarin": {
+ "id": "listen_archivarin",
+ "icon": "📋",
+ "name": "Listen-Archivarin",
+ "desc": "Grundlagen G07 & G08 abgeschlossen (Sequenzen & Strings analysiert).",
+ "xp_bonus": 120,
+ "kriterium": [
+ "g07_listen_und_sequenzen",
+ "g08_textverarbeitung_und_strings"
+ ]
+ },
+ "daten_strategin": {
+ "id": "daten_strategin",
+ "icon": "🗃️",
+ "name": "Daten-Strategin",
+ "desc": "Grundlagen G09 & G10 abgeschlossen (Dicts, Sets & Comprehensions gemeistert).",
+ "xp_bonus": 150,
+ "kriterium": [
+ "g09_dictionaries_und_sets",
+ "g10_comprehensions_datum_algorithmen"
+ ]
+ },
+ "bug_jaegerin": {
+ "id": "bug_jaegerin",
+ "icon": "🔍",
+ "name": "Bug-Jägerin",
+ "desc": "Kapitel 00 gelöst (Alle Fehler-Bugs im Warm-up aufgespürt).",
+ "xp_bonus": 100,
+ "kriterium": [
+ "00_fehlersuche_und_grundlagen"
+ ]
+ },
+ "erste_architektin": {
+ "id": "erste_architektin",
+ "icon": "🏗️",
+ "name": "Erste Architektin",
+ "desc": "Kapitel 01 bis 03 gelöst (Erste OOP-Klassen, Konstruktoren & Methoden).",
+ "xp_bonus": 200,
+ "kriterium": [
+ "01_einstieg_klassen",
+ "02_init_und_self",
+ "03_methoden_und_verhalten"
+ ]
+ },
+ "tamagotchi_mama": {
+ "id": "tamagotchi_mama",
+ "icon": "🥚",
+ "name": "Tamagotchi-Mama",
+ "desc": "Kapitel 06 Mini-Projekt abgeschlossen (Ein lebendiges Haustier gebaut).",
+ "xp_bonus": 250,
+ "kriterium": [
+ "06_abschlussprojekt_tamagotchi"
+ ]
+ },
+ "zeit_reisende": {
+ "id": "zeit_reisende",
+ "icon": "🌿",
+ "name": "Git-Zeitreisende",
+ "desc": "Kapitel 04c Git-Versionskontrolle verstanden & Spielstände gesichert.",
+ "xp_bonus": 100,
+ "kriterium": [
+ "04_str_und_darstellung"
+ ]
+ },
+ "qualitaets_garantin": {
+ "id": "qualitaets_garantin",
+ "icon": "🧪",
+ "name": "TDD-Qualitätsgarantin",
+ "desc": "Kapitel 09 abgeschlossen (Eigene professionelle Unit Tests geschrieben).",
+ "xp_bonus": 200,
+ "kriterium": [
+ "09_eigene_unit_tests_schreiben"
+ ]
+ },
+ "oop_grossmeisterin": {
+ "id": "oop_grossmeisterin",
+ "icon": "🧬",
+ "name": "Vererbungs-Koryphäe",
+ "desc": "Kapitel 10 bis 12 abgeschlossen (Vererbung, Polymorphie & Exceptions).",
+ "xp_bonus": 300,
+ "kriterium": [
+ "10_vererbung_und_super",
+ "11_polymorphie_und_interfaces",
+ "12_exceptions_und_fehlerbehandlung"
+ ]
+ },
+ "software_architektin": {
+ "id": "software_architektin",
+ "icon": "🏆",
+ "name": "Meister-Entwicklerin",
+ "desc": "Kapitel 16 Master-Projekt abgeschlossen & vollwertige Desktop-App gebaut!",
+ "xp_bonus": 500,
+ "kriterium": [
+ "16_master_abschlussprojekt"
+ ]
+ }
+};
+window.GAMIFICATION_LEVEL_STUFEN = [
+ {
+ "level": 1,
+ "min_xp": 0,
+ "max_xp": 150,
+ "titel": "Code-Küken 🐣",
+ "rang": "Bronze I"
+ },
+ {
+ "level": 2,
+ "min_xp": 151,
+ "max_xp": 400,
+ "titel": "Code-Detektivin 🔍",
+ "rang": "Bronze II"
+ },
+ {
+ "level": 3,
+ "min_xp": 401,
+ "max_xp": 800,
+ "titel": "Logik-Bastlerin ⚙️",
+ "rang": "Silber I"
+ },
+ {
+ "level": 4,
+ "min_xp": 801,
+ "max_xp": 1300,
+ "titel": "Klassen-Baumeisterin 🏗️",
+ "rang": "Silber II"
+ },
+ {
+ "level": 5,
+ "min_xp": 1301,
+ "max_xp": 2000,
+ "titel": "Git-Strategin 🌿",
+ "rang": "Gold I"
+ },
+ {
+ "level": 6,
+ "min_xp": 2001,
+ "max_xp": 2900,
+ "titel": "TDD-Qualitätsprüferin 🧪",
+ "rang": "Gold II"
+ },
+ {
+ "level": 7,
+ "min_xp": 2901,
+ "max_xp": 4000,
+ "titel": "Vererbungs-Meisterin 🧬",
+ "rang": "Platin I"
+ },
+ {
+ "level": 8,
+ "min_xp": 4001,
+ "max_xp": 5300,
+ "titel": "Exception-Wächterin 🛡️",
+ "rang": "Platin II"
+ },
+ {
+ "level": 9,
+ "min_xp": 5301,
+ "max_xp": 6800,
+ "titel": "GUI-Entwicklerin 🖥️",
+ "rang": "Diamant"
+ },
+ {
+ "level": 10,
+ "min_xp": 6801,
+ "max_xp": 99999,
+ "titel": "Software-Architektin 🏆",
+ "rang": "Großmeisterin"
+ }
+];
diff --git a/assets/style.css b/assets/style.css
index b0a6a32..d10bdc1 100644
--- a/assets/style.css
+++ b/assets/style.css
@@ -506,6 +506,235 @@ summary:hover {
flex-wrap: wrap;
}
+/* ==========================================================================
+ GAMIFICATION HUD & TROPHY ROOM STYLES
+ ========================================================================== */
+
+.hud-card {
+ background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%);
+ color: #f8fafc;
+ border-radius: var(--radius-md);
+ padding: 24px;
+ margin-bottom: 30px;
+ box-shadow: var(--shadow-md);
+ border: 1px solid rgba(255, 255, 255, 0.1);
+}
+
+.hud-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ flex-wrap: wrap;
+ gap: 15px;
+ margin-bottom: 18px;
+}
+
+.hud-user {
+ display: flex;
+ align-items: center;
+ gap: 14px;
+}
+
+.hud-avatar {
+ font-size: 2.2rem;
+ background: rgba(99, 102, 241, 0.2);
+ border: 2px solid var(--primary);
+ width: 54px;
+ height: 54px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 50%;
+ box-shadow: 0 0 15px rgba(99, 102, 241, 0.4);
+}
+
+.hud-name {
+ font-size: 1.25rem;
+ font-weight: 800;
+ color: #ffffff;
+}
+
+.hud-title {
+ font-size: 0.92rem;
+ color: #94a3b8;
+}
+
+.hud-rank {
+ color: #fbbf24;
+ font-weight: 700;
+}
+
+.hud-stats {
+ display: flex;
+ gap: 12px;
+ flex-wrap: wrap;
+}
+
+.hud-stat-box {
+ background: rgba(255, 255, 255, 0.07);
+ border: 1px solid rgba(255, 255, 255, 0.1);
+ padding: 8px 14px;
+ border-radius: var(--radius-sm);
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ min-width: 90px;
+}
+
+.hud-stat-value {
+ font-size: 1rem;
+ font-weight: 800;
+ color: #38bdf8;
+}
+
+.hud-stat-label {
+ font-size: 0.75rem;
+ color: #94a3b8;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+}
+
+.hud-progress-container {
+ margin: 15px 0;
+}
+
+.hud-progress-labels {
+ display: flex;
+ justify-content: space-between;
+ font-size: 0.85rem;
+ color: #cbd5e1;
+ margin-bottom: 6px;
+}
+
+.hud-progress-bar-bg {
+ background: rgba(255, 255, 255, 0.12);
+ height: 14px;
+ border-radius: 7px;
+ overflow: hidden;
+ position: relative;
+}
+
+.hud-progress-bar-fill {
+ background: linear-gradient(90deg, #10b981 0%, #06b6d4 100%);
+ height: 100%;
+ border-radius: 7px;
+ transition: width 0.6s cubic-bezier(0.4, 0, 0.2, 1);
+ box-shadow: 0 0 10px rgba(16, 185, 129, 0.5);
+}
+
+.hud-footer {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ flex-wrap: wrap;
+ gap: 10px;
+ margin-top: 15px;
+ padding-top: 15px;
+ border-top: 1px solid rgba(255, 255, 255, 0.1);
+}
+
+.hud-btn {
+ background: linear-gradient(135deg, #4f46e5 0%, #6366f1 100%);
+ color: white;
+ border: none;
+ padding: 8px 16px;
+ border-radius: var(--radius-sm);
+ font-weight: 700;
+ font-size: 0.88rem;
+ cursor: pointer;
+ transition: all 0.2s;
+ display: inline-flex;
+ align-items: center;
+ gap: 8px;
+}
+
+.hud-btn:hover {
+ transform: translateY(-1px);
+ box-shadow: 0 4px 12px rgba(99, 102, 241, 0.4);
+}
+
+.hud-cli-tip {
+ font-size: 0.85rem;
+ color: #94a3b8;
+}
+
+.hud-cli-tip code {
+ background: rgba(0, 0, 0, 0.3);
+ color: #38bdf8;
+ border: 1px solid rgba(255, 255, 255, 0.1);
+}
+
+/* Trophy Room Grid */
+.trophy-room {
+ margin-top: 20px;
+ padding-top: 15px;
+ border-top: 1px dashed rgba(255, 255, 255, 0.15);
+}
+
+.trophy-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
+ gap: 14px;
+ margin-top: 12px;
+}
+
+.trophy-card {
+ padding: 14px;
+ border-radius: var(--radius-sm);
+ display: flex;
+ gap: 12px;
+ align-items: flex-start;
+ transition: all 0.2s;
+}
+
+.trophy-unlocked {
+ background: rgba(16, 185, 129, 0.1);
+ border: 1px solid #10b981;
+ box-shadow: 0 0 10px rgba(16, 185, 129, 0.15);
+}
+
+.trophy-locked {
+ background: rgba(255, 255, 255, 0.04);
+ border: 1px solid rgba(255, 255, 255, 0.08);
+ opacity: 0.65;
+}
+
+.trophy-icon {
+ font-size: 1.8rem;
+ flex-shrink: 0;
+}
+
+.trophy-info {
+ flex: 1;
+}
+
+.trophy-title {
+ font-size: 0.95rem;
+ font-weight: 700;
+ color: #ffffff;
+ margin-bottom: 3px;
+}
+
+.trophy-desc {
+ font-size: 0.82rem;
+ color: #94a3b8;
+ line-height: 1.35;
+ margin-bottom: 6px;
+}
+
+.trophy-badge-status {
+ font-size: 0.75rem;
+ font-weight: 700;
+}
+
+.trophy-unlocked .trophy-badge-status {
+ color: #10b981;
+}
+
+.trophy-locked .trophy-badge-status {
+ color: #64748b;
+}
+
/* Responsive */
@media (max-width: 600px) {
header h1 { font-size: 1.6rem; }
@@ -513,4 +742,6 @@ summary:hover {
.footer-nav { flex-direction: column; }
.footer-nav .btn { width: 100%; justify-content: center; }
.extension-grid { grid-template-columns: 1fr; }
+ .hud-header { flex-direction: column; align-items: flex-start; }
+ .hud-stats { width: 100%; justify-content: space-between; }
}
diff --git a/gamification.py b/gamification.py
new file mode 100644
index 0000000..f594756
--- /dev/null
+++ b/gamification.py
@@ -0,0 +1,312 @@
+"""
+🎮 PERSISTENTES GAMIFICATION-SYSTEM FÜR PYTHON LERNPORTAL 🎮
+============================================================
+Wissenschaftlich fundiert nach der Selbstbestimmungstheorie (Deci & Ryan)
+und dem Octalysis-Framework (Yu-kai Chou).
+
+Funktionsweise:
+- Speichert den Spielstand lokal in '.gamification.json'.
+- Schreibt zusätzlich 'assets/gamification_data.js', damit index.html auch
+ ohne Webserver (per Doppelklick / file://) ohne CORS-Probleme darauf zugreifen kann!
+- Verwaltet XP, Level-Aufstiege, Streaks und 14 didaktische Trophäen (Badges).
+"""
+
+import json
+import os
+from datetime import datetime, date
+from pathlib import Path
+from typing import Dict, List, Any, Optional
+
+ROOT_DIR = Path(__file__).parent.resolve()
+DATA_JSON_PATH = ROOT_DIR / ".gamification.json"
+DATA_JS_PATH = ROOT_DIR / "assets" / "gamification_data.js"
+
+# 10 Entwickler-Level mit XP-Schwellen und Titeln
+LEVEL_STUFEN = [
+ {"level": 1, "min_xp": 0, "max_xp": 150, "titel": "Code-Küken 🐣", "rang": "Bronze I"},
+ {"level": 2, "min_xp": 151, "max_xp": 400, "titel": "Code-Detektivin 🔍", "rang": "Bronze II"},
+ {"level": 3, "min_xp": 401, "max_xp": 800, "titel": "Logik-Bastlerin ⚙️", "rang": "Silber I"},
+ {"level": 4, "min_xp": 801, "max_xp": 1300, "titel": "Klassen-Baumeisterin 🏗️", "rang": "Silber II"},
+ {"level": 5, "min_xp": 1301, "max_xp": 2000, "titel": "Git-Strategin 🌿", "rang": "Gold I"},
+ {"level": 6, "min_xp": 2001, "max_xp": 2900, "titel": "TDD-Qualitätsprüferin 🧪", "rang": "Gold II"},
+ {"level": 7, "min_xp": 2901, "max_xp": 4000, "titel": "Vererbungs-Meisterin 🧬", "rang": "Platin I"},
+ {"level": 8, "min_xp": 4001, "max_xp": 5300, "titel": "Exception-Wächterin 🛡️", "rang": "Platin II"},
+ {"level": 9, "min_xp": 5301, "max_xp": 6800, "titel": "GUI-Entwicklerin 🖥️", "rang": "Diamant"},
+ {"level": 10, "min_xp": 6801, "max_xp": 99999, "titel": "Software-Architektin 🏆", "rang": "Großmeisterin"},
+]
+
+# 14 freischaltbare Trophäen mit didaktischen Kriterien
+BADGES_KATALOG = {
+ "rechenkonegin": {
+ "id": "rechenkonegin",
+ "icon": "🧮",
+ "name": "Rechenkönigin",
+ "desc": "Grundlagen G01 & G02 abgeschlossen (Zahlen, Operatoren & Typen gemeistert).",
+ "xp_bonus": 100,
+ "kriterium": ["g01_erste_schritte_taschenrechner", "g02_variablen_und_datentypen"]
+ },
+ "dialog_profi": {
+ "id": "dialog_profi",
+ "icon": "💬",
+ "name": "Dialog-Profi",
+ "desc": "Grundlagen G03 abgeschlossen (Interaktive Ein-/Ausgabe mit f-Strings).",
+ "xp_bonus": 75,
+ "kriterium": ["g03_ein_und_ausgabe"]
+ },
+ "weichenstellerin": {
+ "id": "weichenstellerin",
+ "icon": "🚦",
+ "name": "Weichenstellerin",
+ "desc": "Grundlagen G04 abgeschlossen (Bedingungslogik & Schaltjahre gemeistert).",
+ "xp_bonus": 75,
+ "kriterium": ["g04_verzweigungen_und_bedingungen"]
+ },
+ "schleifen_dompteurin": {
+ "id": "schleifen_dompteurin",
+ "icon": "🎡",
+ "name": "Schleifen-Dompteurin",
+ "desc": "Grundlagen G05 abgeschlossen (While- & For-Schleifen gebändigt).",
+ "xp_bonus": 100,
+ "kriterium": ["g05_schleifen_und_wiederholungen"]
+ },
+ "funktions_zauberin": {
+ "id": "funktions_zauberin",
+ "icon": "🪄",
+ "name": "Funktions-Zauberin",
+ "desc": "Grundlagen G06 abgeschlossen (Eigene Funktionen & Module gebaut).",
+ "xp_bonus": 100,
+ "kriterium": ["g06_funktionen_und_module"]
+ },
+ "listen_archivarin": {
+ "id": "listen_archivarin",
+ "icon": "📋",
+ "name": "Listen-Archivarin",
+ "desc": "Grundlagen G07 & G08 abgeschlossen (Sequenzen & Strings analysiert).",
+ "xp_bonus": 120,
+ "kriterium": ["g07_listen_und_sequenzen", "g08_textverarbeitung_und_strings"]
+ },
+ "daten_strategin": {
+ "id": "daten_strategin",
+ "icon": "🗃️",
+ "name": "Daten-Strategin",
+ "desc": "Grundlagen G09 & G10 abgeschlossen (Dicts, Sets & Comprehensions gemeistert).",
+ "xp_bonus": 150,
+ "kriterium": ["g09_dictionaries_und_sets", "g10_comprehensions_datum_algorithmen"]
+ },
+ "bug_jaegerin": {
+ "id": "bug_jaegerin",
+ "icon": "🔍",
+ "name": "Bug-Jägerin",
+ "desc": "Kapitel 00 gelöst (Alle Fehler-Bugs im Warm-up aufgespürt).",
+ "xp_bonus": 100,
+ "kriterium": ["00_fehlersuche_und_grundlagen"]
+ },
+ "erste_architektin": {
+ "id": "erste_architektin",
+ "icon": "🏗️",
+ "name": "Erste Architektin",
+ "desc": "Kapitel 01 bis 03 gelöst (Erste OOP-Klassen, Konstruktoren & Methoden).",
+ "xp_bonus": 200,
+ "kriterium": ["01_einstieg_klassen", "02_init_und_self", "03_methoden_und_verhalten"]
+ },
+ "tamagotchi_mama": {
+ "id": "tamagotchi_mama",
+ "icon": "🥚",
+ "name": "Tamagotchi-Mama",
+ "desc": "Kapitel 06 Mini-Projekt abgeschlossen (Ein lebendiges Haustier gebaut).",
+ "xp_bonus": 250,
+ "kriterium": ["06_abschlussprojekt_tamagotchi"]
+ },
+ "zeit_reisende": {
+ "id": "zeit_reisende",
+ "icon": "🌿",
+ "name": "Git-Zeitreisende",
+ "desc": "Kapitel 04c Git-Versionskontrolle verstanden & Spielstände gesichert.",
+ "xp_bonus": 100,
+ "kriterium": ["04_str_und_darstellung"]
+ },
+ "qualitaets_garantin": {
+ "id": "qualitaets_garantin",
+ "icon": "🧪",
+ "name": "TDD-Qualitätsgarantin",
+ "desc": "Kapitel 09 abgeschlossen (Eigene professionelle Unit Tests geschrieben).",
+ "xp_bonus": 200,
+ "kriterium": ["09_eigene_unit_tests_schreiben"]
+ },
+ "oop_grossmeisterin": {
+ "id": "oop_grossmeisterin",
+ "icon": "🧬",
+ "name": "Vererbungs-Koryphäe",
+ "desc": "Kapitel 10 bis 12 abgeschlossen (Vererbung, Polymorphie & Exceptions).",
+ "xp_bonus": 300,
+ "kriterium": ["10_vererbung_und_super", "11_polymorphie_und_interfaces", "12_exceptions_und_fehlerbehandlung"]
+ },
+ "software_architektin": {
+ "id": "software_architektin",
+ "icon": "🏆",
+ "name": "Meister-Entwicklerin",
+ "desc": "Kapitel 16 Master-Projekt abgeschlossen & vollwertige Desktop-App gebaut!",
+ "xp_bonus": 500,
+ "kriterium": ["16_master_abschlussprojekt"]
+ }
+}
+
+
+class GamificationManager:
+ """Verwaltet den persistenten Gamification-Status."""
+
+ def __init__(self):
+ self.state = self._load_state()
+ self.save_state()
+
+ def _default_state(self) -> Dict[str, Any]:
+ return {
+ "spieler_name": "Python-Entwicklerin",
+ "xp": 0,
+ "level": 1,
+ "titel": "Code-Küken 🐣",
+ "rang": "Bronze I",
+ "geloeste_kapitel": [],
+ "bestandene_tests": 0,
+ "freigeschaltete_badges": [],
+ "streak_tage": 1,
+ "letzter_aktiver_tag": str(date.today()),
+ "historie": [],
+ "zuletzt_aktualisiert": datetime.now().isoformat()
+ }
+
+ def _load_state(self) -> Dict[str, Any]:
+ if DATA_JSON_PATH.exists():
+ try:
+ with open(DATA_JSON_PATH, "r", encoding="utf-8") as f:
+ data = json.load(f)
+ return {**self._default_state(), **data}
+ except Exception:
+ pass
+ return self._default_state()
+
+ def save_state(self):
+ """Speichert den Status in JSON und exportiert gleichzeitig gamification_data.js für den Browser."""
+ self.state["zuletzt_aktualisiert"] = datetime.now().isoformat()
+
+ # 1. JSON speichern
+ try:
+ with open(DATA_JSON_PATH, "w", encoding="utf-8") as f:
+ json.dump(self.state, f, indent=2, ensure_ascii=False)
+ except Exception as e:
+ print(f"Warnung beim Speichern von .gamification.json: {e}")
+
+ # 2. JS-Export für lokale Browser-Nutzung (CORS-Frei via file://)
+ try:
+ DATA_JS_PATH.parent.mkdir(parents=True, exist_ok=True)
+ js_content = f"""// Automatisch generierter lokaler Gamification-Spielstand
+window.GAMIFICATION_DATA = {json.dumps(self.state, indent=2, ensure_ascii=False)};
+window.GAMIFICATION_BADGES_KATALOG = {json.dumps(BADGES_KATALOG, indent=2, ensure_ascii=False)};
+window.GAMIFICATION_LEVEL_STUFEN = {json.dumps(LEVEL_STUFEN, indent=2, ensure_ascii=False)};
+"""
+ with open(DATA_JS_PATH, "w", encoding="utf-8") as f:
+ f.write(js_content)
+ except Exception as e:
+ print(f"Warnung beim Exportieren von gamification_data.js: {e}")
+
+ def get_level_info(self, xp: int) -> Dict[str, Any]:
+ """Berechnet Level, Titel und Fortschritt zur nächsten Stufe."""
+ for info in LEVEL_STUFEN:
+ if info["min_xp"] <= xp <= info["max_xp"]:
+ naechste_xp = info["max_xp"] + 1
+ xp_in_level = xp - info["min_xp"]
+ xp_fuer_level = naechste_xp - info["min_xp"]
+ prozent = min(100, int((xp_in_level / xp_fuer_level) * 100)) if xp_fuer_level > 0 else 100
+ return {
+ "level": info["level"],
+ "titel": info["titel"],
+ "rang": info["rang"],
+ "aktuelle_xp": xp,
+ "min_xp": info["min_xp"],
+ "max_xp": info["max_xp"],
+ "naechste_stufe_xp": naechste_xp,
+ "prozent": prozent
+ }
+ # Fallback höchstes Level
+ return {
+ "level": 10,
+ "titel": "Software-Architektin 🏆",
+ "rang": "Großmeisterin",
+ "aktuelle_xp": xp,
+ "min_xp": 6801,
+ "max_xp": 99999,
+ "naechste_stufe_xp": 99999,
+ "prozent": 100
+ }
+
+ def sync_mit_test_ergebnissen(self, bestandene_kapitel_ordner: List[str], anzahl_bestandene_tests: int) -> Dict[str, Any]:
+ """
+ Gleicht die aktuellen Testergebnisse ab, vergibt XP und schaltet Badges frei.
+ Gibt ein Event-Dictionary für feierliche Terminal-Meldungen zurück.
+ """
+ alte_geloeste = set(self.state.get("geloeste_kapitel", []))
+ neue_geloeste = set(bestandene_kapitel_ordner)
+ alte_badges = set(self.state.get("freigeschaltete_badges", []))
+ altes_level = self.state.get("level", 1)
+
+ neu_geloest = neue_geloeste - alte_geloeste
+
+ # Basis-XP Berechnung:
+ # 100 XP pro gelöstem Kapitel + 10 XP pro bestandenem Test
+ berechnete_xp = (len(neue_geloeste) * 100) + (anzahl_bestandene_tests * 10)
+
+ # Badges überprüfen und Bonus-XP addieren
+ neu_freigeschaltet_badges = []
+ for badge_id, badge in BADGES_KATALOG.items():
+ kriterien = badge["kriterium"]
+ # Alle Kriterien-Ordner müssen gelöst sein
+ if all(k in neue_geloeste for k in kriterien):
+ if badge_id not in alte_badges:
+ neu_freigeschaltet_badges.append(badge)
+ berechnete_xp += badge["xp_bonus"]
+
+ # Streak-Berechnung
+ heute = str(date.today())
+ letzter_tag = self.state.get("letzter_aktiver_tag", heute)
+ if letzter_tag != heute:
+ try:
+ diff = (date.today() - date.fromisoformat(letzter_tag)).days
+ if diff == 1:
+ self.state["streak_tage"] = self.state.get("streak_tage", 1) + 1
+ elif diff > 1:
+ self.state["streak_tage"] = 1
+ except Exception:
+ self.state["streak_tage"] = 1
+ self.state["letzter_aktiver_tag"] = heute
+
+ # Level-Update
+ lvl_info = self.get_level_info(berechnete_xp)
+ neues_level = lvl_info["level"]
+
+ # Status aktualisieren
+ self.state["xp"] = berechnete_xp
+ self.state["level"] = neues_level
+ self.state["titel"] = lvl_info["titel"]
+ self.state["rang"] = lvl_info["rang"]
+ self.state["geloeste_kapitel"] = list(neue_geloeste)
+ self.state["bestandene_tests"] = anzahl_bestandene_tests
+ self.state["freigeschaltete_badges"] = [
+ b_id for b_id, b in BADGES_KATALOG.items()
+ if all(k in neue_geloeste for k in b["kriterium"])
+ ]
+
+ self.save_state()
+
+ return {
+ "neue_kapitel": list(neu_geloest),
+ "neue_badges": neu_freigeschaltet_badges,
+ "level_up": neues_level > altes_level,
+ "altes_level": altes_level,
+ "neues_level": neues_level,
+ "lvl_info": lvl_info,
+ "gesamt_xp": berechnete_xp
+ }
+
+
+# Singleton-Instanz
+manager = GamificationManager()
diff --git a/index.html b/index.html
index c9baa65..5ff0391 100644
--- a/index.html
+++ b/index.html
@@ -518,5 +518,8 @@ object has no attribute 'gang'
+
+
+