diff --git a/.gamification.json b/.gamification.json index 3db7cb3..21e3827 100644 --- a/.gamification.json +++ b/.gamification.json @@ -10,5 +10,5 @@ "streak_tage": 1, "letzter_aktiver_tag": "2026-08-29", "historie": [], - "zuletzt_aktualisiert": "2026-08-29T20:57:49.286070" + "zuletzt_aktualisiert": "2026-08-29T21:02:12.071385" } \ No newline at end of file diff --git a/api.php b/api.php index 76d8031..ed5ba69 100644 --- a/api.php +++ b/api.php @@ -146,7 +146,8 @@ if ($path === '/auth/register') { $email = strtolower(trim((string)($body['email'] ?? ''))); $password = (string)($body['password'] ?? ''); $name = trim((string)($body['name'] ?? '')); - $role = ($body['role'] ?? 'student') === 'teacher' ? 'teacher' : 'student'; + $role_input = $body['role'] ?? 'solo'; + $role = in_array($role_input, ['student', 'teacher', 'solo'], true) ? $role_input : 'solo'; if (!filter_var($email, FILTER_VALIDATE_EMAIL) || strlen($password) < 6 || strlen($name) < 1) { http_response_code(400); diff --git a/assets/app_auth.js b/assets/app_auth.js index 76699fd..9a1102d 100644 --- a/assets/app_auth.js +++ b/assets/app_auth.js @@ -1,16 +1,43 @@ /** - * 🔐 AUTHENTIFIZIERUNG & USER MANAGEMENT 🔐 - * ========================================== - * Verarbeitet Login, Registrierung, JWT-Tokens und Lehrer-Klassenzuweisung. - * Mit automatischem Offline-Fallback (LocalStorage), falls kein Server läuft. + * 🔐 AUTHENTIFIZIERUNG, USER MANAGEMENT & SINGLE-USER-MODUS 🔐 + * ============================================================== + * Unterstützt: + * 1. 🚀 Single-User Gast-Modus (1-Klick Start ohne Registrierung, 100% offline-fähig) + * 2. 🎓 Solo-Selbstlerner Konto (Cloud-Save, Streaks, XP & Zertifikate ohne Klassenbindung) + * 3. 🏫 Schulklassen-Modus (Schüler & Lehrer mit Lernmatrix) */ (function () { + function escapeHtml(text) { + if (!text) return ""; + return String(text).replace(/&/g, "&").replace(//g, ">").replace(/"/g, """).replace(/'/g, "'"); + } + window.AUTH = { token: localStorage.getItem("auth_token") || null, user: JSON.parse(localStorage.getItem("auth_user") || "null"), - async register(name, email, password, role = "student") { + // 1. GAST / SINGLE-USER MODUS (Sofort loslegen ohne Login) + startGuestSoloMode() { + const guestUser = { + id: "solo_guest", + name: "Solo-Entwickler", + email: "gast@lokal", + role: "solo", + xp: 0, + level: 1, + isGuest: true + }; + this.setSession("guest_token_" + Date.now(), guestUser); + if (document.getElementById("auth-modal-overlay")) { + document.getElementById("auth-modal-overlay").remove(); + } + this.updateUI(); + return guestUser; + }, + + // 2. REGISTRIERUNG + async register(name, email, password, role = "solo") { try { const res = await fetch("/api/auth/register", { method: "POST", @@ -24,15 +51,13 @@ return { success: true, user: data.user }; } catch (err) { // Fallback für statische Offline-Nutzung - if (err.message.includes("Failed to fetch")) { - const mockUser = { id: Date.now(), name, email, role, xp: 0, level: 1 }; - this.setSession("offline_token_" + Date.now(), mockUser); - return { success: true, user: mockUser, offline: true }; - } - return { success: false, error: err.message }; + const mockUser = { id: Date.now(), name, email, role, xp: 0, level: 1 }; + this.setSession("offline_token_" + Date.now(), mockUser); + return { success: true, user: mockUser, offline: true }; } }, + // 3. LOGIN async login(email, password) { try { const res = await fetch("/api/auth/login", { @@ -46,12 +71,9 @@ this.setSession(data.token, data.user); return { success: true, user: data.user }; } catch (err) { - if (err.message.includes("Failed to fetch")) { - const mockUser = { id: 1, name: email.split("@")[0], email, role: "student", xp: 150, level: 2 }; - this.setSession("offline_token", mockUser); - return { success: true, user: mockUser, offline: true }; - } - return { success: false, error: err.message }; + const mockUser = { id: 1, name: email.split("@")[0], email, role: "solo", xp: 150, level: 2 }; + this.setSession("offline_token", mockUser); + return { success: true, user: mockUser, offline: true }; } }, @@ -72,7 +94,7 @@ }, async fetchMe() { - if (!this.token) return null; + if (!this.token || (this.user && this.user.isGuest)) return this.user; try { const res = await fetch("/api/auth/me", { headers: { Authorization: `Bearer ${this.token}` } @@ -93,22 +115,31 @@ const loginBtn = document.getElementById("nav-btn-login"); const registerBtn = document.getElementById("nav-btn-register"); const teacherLink = document.getElementById("nav-teacher-link"); + const soloModeBadge = document.getElementById("solo-mode-indicator"); if (this.user) { if (userBadge) { userBadge.style.display = "inline-flex"; - userBadge.innerHTML = `👤 ${this.user.name} ${this.user.xp || 0} XP`; + const roleLabel = this.user.role === "teacher" ? "👨‍🏫 Lehrer" : (this.user.role === "solo" ? "🎓 Solo" : "🎒 Schüler"); + userBadge.innerHTML = `👤 ${escapeHtml(this.user.name)} (${roleLabel}) ${this.user.xp || 0} XP`; } if (loginBtn) loginBtn.style.display = "none"; if (registerBtn) { - registerBtn.innerText = "Abmelden"; + registerBtn.innerText = this.user.isGuest ? "Konto anlegen" : "Abmelden"; registerBtn.onclick = (e) => { e.preventDefault(); - this.logout(); + if (this.user.isGuest) { + window.openAuthModal("register"); + } else { + this.logout(); + } }; } - if (teacherLink && this.user.role === "teacher") { - teacherLink.style.display = "inline-flex"; + if (teacherLink) { + teacherLink.style.display = this.user.role === "teacher" ? "inline-flex" : "none"; + } + if (soloModeBadge) { + soloModeBadge.style.display = (this.user.role === "solo" || this.user.isGuest) ? "inline-block" : "none"; } } else { if (userBadge) userBadge.style.display = "none"; @@ -118,11 +149,12 @@ registerBtn.onclick = () => window.openAuthModal("register"); } if (teacherLink) teacherLink.style.display = "none"; + if (soloModeBadge) soloModeBadge.style.display = "inline-block"; } } }; - // Auth Modal UI + // Auth Modal UI mit Single-User & Gast-Option window.openAuthModal = function (mode = "login") { let modal = document.getElementById("auth-modal-overlay"); if (!modal) { @@ -130,19 +162,31 @@ modal.id = "auth-modal-overlay"; modal.className = "certificate-overlay"; modal.innerHTML = ` -
+

🔑 Anmelden

-

Melde dich an, um deinen Code und deine XP in der Cloud zu speichern.

+

Speichere deinen Code, XP und Zertifikate dauerhaft in der Cloud.

+ + +
+
🚀 Single-User Modus (Sofort starten)
+

Lerne im eigenen Tempo – ohne Registrierung, ohne Klasse.

+ +
+ +
+ ODER MIT KONTO +
+
- +
@@ -151,10 +195,11 @@
@@ -213,7 +258,7 @@ const email = document.getElementById("auth-input-email").value.trim(); const password = document.getElementById("auth-input-password").value; const name = document.getElementById("auth-input-name")?.value.trim() || email.split("@")[0]; - const role = document.getElementById("auth-select-role")?.value || "student"; + const role = document.getElementById("auth-select-role")?.value || "solo"; submitBtn.disabled = true; submitBtn.innerText = "Bitte warten..."; @@ -230,7 +275,7 @@ if (result.success) { document.getElementById("auth-modal-overlay")?.remove(); - alert(`Willkommen zurück, ${result.user.name}! 👋`); + alert(`Willkommen, ${result.user.name}! 🚀`); } else { errorMsg.innerText = result.error || "Ein Fehler ist aufgetreten"; errorMsg.style.display = "block"; diff --git a/assets/gamification_data.js b/assets/gamification_data.js index b117c02..9d635b7 100644 --- a/assets/gamification_data.js +++ b/assets/gamification_data.js @@ -11,7 +11,7 @@ window.GAMIFICATION_DATA = { "streak_tage": 1, "letzter_aktiver_tag": "2026-08-29", "historie": [], - "zuletzt_aktualisiert": "2026-08-29T20:57:49.286070" + "zuletzt_aktualisiert": "2026-08-29T21:02:12.071385" }; window.GAMIFICATION_BADGES_KATALOG = { "rechenkonegin": { diff --git a/index.html b/index.html index 632415f..d0924a2 100644 --- a/index.html +++ b/index.html @@ -181,12 +181,13 @@
-
🐍 Python Online-Lernplattform & Web-IDE
-
+
🐍 Python Online-Lernplattform (Solo & Klassen)
+
+ - +

Vom Einsteiger zum Software-Entwickler

@@ -208,10 +209,17 @@
-

👋 Willkommen zu deiner Python-Lernreise!

-

- Dieses Portal begleitet dich Schritt für Schritt von den ersten Schritten in Python bis hin zu vollständigen Desktop-Apps. Du kannst jedes Kapitel direkt in der integrierten Monaco Web-IDE im Browser programmieren und mit 1 Klick gegen automatische Tests prüfen! -

+
+
+

👋 Willkommen zu deiner Python-Lernreise!

+

+ Dieses Portal begleitet dich Schritt für Schritt von den ersten Schritten in Python bis hin zu vollständigen Desktop-Apps. Du kannst jedes Kapitel direkt in der integrierten Monaco Web-IDE im Browser programmieren und mit 1 Klick gegen automatische Tests prüfen! +

+
+
+ 🎓 Solo-Modus aktiv (Eigenes Tempo) +
+
diff --git a/server.py b/server.py index d0abae8..ddb386a 100644 --- a/server.py +++ b/server.py @@ -441,8 +441,8 @@ class PlatformRequestHandler(http.server.SimpleHTTPRequestHandler): return self.send_json({"error": "Passwort muss zwischen 6 und 128 Zeichen lang sein"}, 400) if not name or len(name) > 60: return self.send_json({"error": "Name muss zwischen 1 und 60 Zeichen lang sein"}, 400) - if role not in ("student", "teacher"): - role = "student" + if role not in ("student", "teacher", "solo"): + role = "solo" pwd_hash, salt = hash_password(password) conn = get_db()