/** * 🔐 AUTHENTIFIZIERUNG & ZUGANGSSCHUTZ (AUTH GUARD) 🔐 * ======================================================= * IT-Praxisportal – Benutzer-Authentifizierung, Wartungsmodus und Zugangsschutz. * - Strikter Login-Zwang für den Alpha-Test (sessionStorage als Standard). * - Vollständiger Wartungsmodus mit Fullscreen-Blockade für Nicht-Admins. * - Live-Statusabfrage (/api/platform/status) & Ankündigungsbanner. * - Vollständige Beseitigung unsicherer Offline-/Dummy-Fallbacks. */ (function () { 'use strict'; function escapeHtml(text) { if (!text) return ''; return String(text) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } // Session-Speicher auslesen (sessionStorage bevorzugt für Sitzungsbindung im Test) function getStoredToken() { return sessionStorage.getItem('auth_token') || localStorage.getItem('auth_token') || null; } function getStoredUser() { try { const raw = sessionStorage.getItem('auth_user') || localStorage.getItem('auth_user') || 'null'; return JSON.parse(raw); } catch (e) { return null; } } // Bereinigung veralteter oder gefälschter Dummy-Tokens const initialToken = getStoredToken(); const initialUser = getStoredUser(); if ((initialToken && initialToken.startsWith('local_token_')) || (initialUser && initialUser.isGuest)) { sessionStorage.removeItem('auth_token'); sessionStorage.removeItem('auth_user'); localStorage.removeItem('auth_token'); localStorage.removeItem('auth_user'); } window.AUTH = { token: getStoredToken(), user: getStoredUser(), platformStatus: { maintenance: false, announcement: '', allow_registration: true }, getToken() { if (!this.token) { this.token = getStoredToken(); } return this.token; }, getUser() { if (!this.user) { this.user = getStoredUser(); } return this.user; }, isLoggedIn() { const token = this.getToken(); const user = this.getUser(); return !!(token && user && !user.isGuest && user.email); }, isAdmin() { const user = this.getUser(); return !!(this.isLoggedIn() && user && user.role === 'admin'); }, isTeacher() { const user = this.getUser(); return !!(this.isLoggedIn() && user && (user.role === 'teacher' || user.role === 'admin')); }, setSession(token, user, rememberMe = true) { this.token = token; this.user = user; try { localStorage.setItem('auth_token', token); localStorage.setItem('auth_user', JSON.stringify(user)); sessionStorage.setItem('auth_token', token); sessionStorage.setItem('auth_user', JSON.stringify(user)); } catch (e) {} this.updateUI(); this.handleMaintenanceDisplay(); }, logout(redirect = true) { this.token = null; this.user = null; sessionStorage.removeItem('auth_token'); sessionStorage.removeItem('auth_user'); localStorage.removeItem('auth_token'); localStorage.removeItem('auth_user'); this.updateUI(); if (redirect) { const p = window.location.pathname; if (p.includes('/courses/') || p.includes('/lehrpfad_') || p.endsWith('workspace.html') || p.endsWith('dashboard.html') || p.endsWith('teacher.html') || p.endsWith('admin.html')) { window.location.href = window.location.origin + '/index.html?auth=login'; } else { this.handleMaintenanceDisplay(); } } }, async register(name, email, password, role = 'solo', rememberMe = false, profession = '') { try { const res = await fetch('/api/auth/register', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name, email, password, role, profession }) }); const data = await res.json().catch(() => ({})); if (!res.ok) { return { success: false, error: data.error || 'Registrierung fehlgeschlagen' }; } this.setSession(data.token, data.user, rememberMe); return { success: true, user: data.user }; } catch (err) { return { success: false, error: 'Netzwerkfehler: Backend-Server nicht erreichbar.' }; } }, async login(email, password, rememberMe = false) { try { const res = await fetch('/api/auth/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email, password }) }); const data = await res.json().catch(() => ({})); if (!res.ok) { return { success: false, error: data.error || 'Anmeldung fehlgeschlagen' }; } this.setSession(data.token, data.user, rememberMe); return { success: true, user: data.user }; } catch (err) { return { success: false, error: 'Netzwerkfehler: Backend-Server nicht erreichbar.' }; } }, async fetchMe() { if (!this.token) return; try { const res = await fetch('/api/auth/me', { headers: { 'Authorization': `Bearer ${this.token}` } }); if (res.ok) { const data = await res.json(); this.user = data.user; if (localStorage.getItem('auth_token')) { localStorage.setItem('auth_user', JSON.stringify(data.user)); } else { sessionStorage.setItem('auth_user', JSON.stringify(data.user)); } this.updateUI(); this.handleMaintenanceDisplay(); } else { const errData = await res.json().catch(() => ({})); if (res.status === 401 || res.status === 404) { this.logout(false); } else if (res.status === 503 && errData.maintenance) { this.handleMaintenanceDisplay(); } } } catch (e) { // Offline } }, async checkStatus() { try { const res = await fetch('/api/platform/status'); if (res.ok) { this.platformStatus = await res.json(); this.handleMaintenanceDisplay(); this.handleAnnouncementDisplay(); } } catch (e) { // Ignorieren } }, handleMaintenanceDisplay() { const isMaintenance = this.platformStatus && this.platformStatus.maintenance; const isAdmin = this.isAdmin(); let screen = document.getElementById('itp-maintenance-screen'); let adminBanner = document.getElementById('itp-maintenance-admin-banner'); if (isMaintenance) { if (!isAdmin) { if (!screen) { screen = document.createElement('div'); screen.id = 'itp-maintenance-screen'; screen.style.cssText = ` position: fixed; inset: 0; z-index: 999999; background: rgba(10, 15, 29, 0.96); backdrop-filter: blur(12px); display: flex; align-items: center; justify-content: center; padding: 24px; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; color: #f8fafc; text-align: center; `; screen.innerHTML = `
🛠️

Wartungsmodus aktiv

Das IT-Praxisportal wird derzeit planmäßig gewartet, um Sicherheits-Updates und neue Lernmodule einzuspielen. Reguläre Kurszugänge sind vorübergehend pausiert.

IT-Praxisportal Alpha • Alle Daten sind sicher
`; document.body.appendChild(screen); const btn = document.getElementById('maintenance-admin-login-btn'); if (btn) { btn.onclick = () => { window.openAuthModal('login', { allowClose: true, title: '🔐 Administrator-Login', message: 'Wartungsmodus aktiv: Der Zugang ist derzeit exklusiv für System-Administratoren freigeschaltet.', onSuccess: () => { window.location.reload(); } }); }; } } } else { if (screen) screen.remove(); if (!adminBanner) { adminBanner = document.createElement('div'); adminBanner.id = 'itp-maintenance-admin-banner'; adminBanner.style.cssText = ` background: linear-gradient(90deg, #b91c1c, #ea580c); color: white; padding: 8px 16px; font-size: 0.84rem; font-weight: 700; display: flex; align-items: center; justify-content: space-between; position: sticky; top: 0; z-index: 999998; box-shadow: 0 2px 8px rgba(0,0,0,0.3); `; adminBanner.innerHTML = `
⚠️ WARTUNGSMODUS AKTIV | Nur Administratoren haben derzeit Zugriff auf die Plattform.
⚙️ Admin-Panel öffnen `; document.body.insertBefore(adminBanner, document.body.firstChild); } } } else { if (screen) screen.remove(); if (adminBanner) adminBanner.remove(); } }, handleAnnouncementDisplay() { const bannerText = this.platformStatus && this.platformStatus.announcement; let el = document.getElementById('itp-announcement-banner'); if (!bannerText) { if (el) el.remove(); return; } if (sessionStorage.getItem('dismissed_announcement') === bannerText) { return; } if (!el) { el = document.createElement('div'); el.id = 'itp-announcement-banner'; el.style.cssText = ` background: #0284c7; color: white; padding: 8px 16px; font-size: 0.85rem; font-weight: 600; display: flex; align-items: center; justify-content: space-between; position: sticky; top: 0; z-index: 999990; `; document.body.insertBefore(el, document.body.firstChild); } el.innerHTML = `
📢 ${escapeHtml(bannerText)}
`; el.querySelector('button').onclick = () => { sessionStorage.setItem('dismissed_announcement', bannerText); el.remove(); }; }, requireAuth(options = {}) { if (this.isLoggedIn()) { return true; } this.showAuthBarrier(options); return false; }, showAuthBarrier(options = {}) { window.openAuthModal(options.mode || 'login', { allowClose: options.allowClose !== undefined ? options.allowClose : false, title: options.title || '🔒 Anmeldung erforderlich', message: options.message || 'Dieser Lernbereich erfordert ein Benutzerkonto. Bitte melde dich an oder erstelle einen kostenlosen Account.', onSuccess: () => { if (options.onSuccess) options.onSuccess(); else window.location.reload(); } }); }, updateUI() { const loggedIn = this.isLoggedIn(); const user = this.getUser(); // Topbar Elemente in index.html & dashboard.html const userSpan = document.getElementById('nav-auth-user'); const loginBtn = document.getElementById('nav-btn-login'); const registerBtn = document.getElementById('nav-btn-register'); const guestBtn = document.getElementById('nav-btn-guest'); const teacherLink = document.getElementById('nav-teacher-link'); const heroPrimaryCta = document.getElementById('hero-primary-cta'); // Modern User Avatar Dropdown Elemente const userDropdown = document.getElementById('portal-user-dropdown'); const userAvatar = document.getElementById('portal-user-avatar'); const userNameEl = document.getElementById('portal-user-name'); const ddFullName = document.getElementById('portal-dropdown-user-fullname'); const ddEmail = document.getElementById('portal-dropdown-user-email'); const ddBadge = document.getElementById('portal-dropdown-user-badge'); const ddTeacher = document.getElementById('dropdown-link-teacher'); const ddAdmin = document.getElementById('dropdown-link-admin'); const ddMgmtTitle = document.getElementById('dropdown-mgmt-title'); const ddMgmtDivider = document.getElementById('dropdown-teacher-divider'); // Mobile Drawer User Header const mobUserBox = document.getElementById('mobile-drawer-user-info'); const mobAvatar = document.getElementById('mobile-drawer-avatar'); const mobUsername = document.getElementById('mobile-drawer-username'); const mobRole = document.getElementById('mobile-drawer-role'); // Dashboard Links auf index.html / impressum / datenschutz const navDashLink = document.getElementById('nav-dashboard-link'); const mobDashLink = document.getElementById('mobile-drawer-dash-link'); const landingBanner = document.getElementById('landing-logged-in-banner'); const landingUserName = document.getElementById('landing-user-name'); if (guestBtn) guestBtn.style.display = 'none'; // Gast-Button permanent ausblenden function computeInitials(str) { if (!str) return 'IT'; const parts = str.trim().split(/\s+/); if (parts.length >= 2) { return (parts[0][0] + parts[1][0]).toUpperCase(); } return str.substring(0, 2).toUpperCase(); } const roleLabels = { 'admin': 'Administrator', 'teacher': 'Dozent / Ausbilder', 'student': 'Auszubildender', 'solo': 'Professional' }; if (loggedIn && user) { document.body.classList.add('logged-in'); const displayName = user.name || user.email.split('@')[0]; const initials = computeInitials(displayName); const roleLabel = roleLabels[user.role] || 'Teilnehmer'; const profStr = user.profession ? ` • ${user.profession}` : ''; // 1. User Dropdown befüllen if (userDropdown) { userDropdown.style.display = 'inline-block'; } if (userAvatar) userAvatar.innerText = initials; if (userNameEl) userNameEl.innerText = displayName; if (ddFullName) ddFullName.innerText = displayName; if (ddEmail) ddEmail.innerText = user.email || ''; if (ddBadge) { ddBadge.innerText = `${roleLabel}${profStr}`; } // Mobile Drawer User Header if (mobUserBox) mobUserBox.style.display = 'flex'; if (mobAvatar) mobAvatar.innerText = initials; if (mobUsername) mobUsername.innerText = displayName; if (mobRole) mobRole.innerText = `${roleLabel}${profStr}`; // Dozent / Admin im Dropdown const hasTeacher = (user.role === 'teacher' || user.role === 'admin'); const hasAdmin = (user.role === 'admin'); if (ddTeacher) ddTeacher.style.display = hasTeacher ? 'flex' : 'none'; if (ddAdmin) ddAdmin.style.display = hasAdmin ? 'flex' : 'none'; if (ddMgmtTitle) ddMgmtTitle.style.display = hasTeacher ? 'block' : 'none'; if (ddMgmtDivider) ddMgmtDivider.style.display = hasTeacher ? 'block' : 'none'; const mobMgmtTitle = document.getElementById('mobile-drawer-mgmt-title'); const mobMgmtDivider = document.getElementById('mobile-drawer-mgmt-divider'); if (mobMgmtTitle) mobMgmtTitle.style.display = hasTeacher ? 'block' : 'none'; if (mobMgmtDivider) mobMgmtDivider.style.display = hasTeacher ? 'block' : 'none'; if (userSpan) { userSpan.style.display = 'inline-flex'; userSpan.innerHTML = `${escapeHtml(displayName)}`; userSpan.style.cursor = 'pointer'; userSpan.title = 'Zum Kurs-Dashboard wechseln'; userSpan.onclick = () => { window.location.href = 'dashboard.html'; }; } if (loginBtn) { loginBtn.style.display = 'none'; // Bei aktivem User-Dropdown wird der separate Login-Btn ausgeblendet } if (registerBtn) { registerBtn.style.display = 'none'; // Ebenso } if (heroPrimaryCta) { heroPrimaryCta.innerHTML = 'Zum Dashboard & Kurse wählen →'; heroPrimaryCta.removeAttribute('onclick'); heroPrimaryCta.onclick = (e) => { e.preventDefault(); window.location.href = 'dashboard.html'; }; } // Alle allgemeinen Landing-Page Action Buttons anpassen document.querySelectorAll('.landing-cta-btn').forEach(btn => { btn.innerHTML = 'Zum Dashboard & Kurse wählen →'; btn.style.background = '#10b981'; btn.removeAttribute('onclick'); btn.onclick = (e) => { e.preventDefault(); window.location.href = 'dashboard.html'; }; }); const footerLoginBtn = document.getElementById('footer-cta-login'); if (footerLoginBtn) footerLoginBtn.style.display = 'none'; // Banner und Dashboard-Links einblenden if (landingBanner) { landingBanner.style.display = 'flex'; if (landingUserName) { landingUserName.innerText = displayName; } } if (navDashLink) navDashLink.style.display = 'none'; if (mobDashLink) mobDashLink.style.display = 'flex'; const mobLogoutBtn = document.getElementById('mobile-drawer-logout-btn'); if (mobLogoutBtn) mobLogoutBtn.style.display = 'flex'; const mobDrawerAuthActions = document.getElementById('mobile-drawer-auth-actions'); if (mobDrawerAuthActions) mobDrawerAuthActions.style.display = 'none'; } else { document.body.classList.remove('logged-in'); if (userDropdown) userDropdown.style.display = 'none'; if (mobUserBox) mobUserBox.style.display = 'none'; if (userSpan) { userSpan.style.display = 'none'; userSpan.onclick = null; } if (loginBtn) { loginBtn.style.display = 'inline-flex'; loginBtn.innerText = 'Anmelden'; loginBtn.style.background = 'rgba(255,255,255,0.12)'; loginBtn.style.color = 'white'; loginBtn.style.borderColor = 'rgba(255,255,255,0.2)'; loginBtn.onclick = () => window.openAuthModal('login', { allowClose: true }); } if (registerBtn) { registerBtn.style.display = 'inline-flex'; registerBtn.innerHTML = 'Kostenlos registrierenStarten'; registerBtn.style.background = '#0284c7'; registerBtn.onclick = () => window.openAuthModal('register', { allowClose: true }); } const mobDrawerAuthActions = document.getElementById('mobile-drawer-auth-actions'); if (mobDrawerAuthActions) mobDrawerAuthActions.style.display = 'flex'; if (mobDashLink) mobDashLink.style.display = 'none'; const mobLogoutBtn = document.getElementById('mobile-drawer-logout-btn'); if (mobLogoutBtn) mobLogoutBtn.style.display = 'none'; if (heroPrimaryCta) { heroPrimaryCta.innerHTML = 'Jetzt kostenlos registrieren →'; heroPrimaryCta.onclick = (e) => { e.preventDefault(); window.openAuthModal('register', { allowClose: true }); }; } // Alle allgemeinen Landing-Page Action Buttons für Besucher document.querySelectorAll('.landing-cta-btn').forEach(btn => { btn.innerHTML = 'Jetzt kostenlos registrieren & Kurse wählen →'; btn.style.background = '#0284c7'; btn.onclick = (e) => { e.preventDefault(); window.openAuthModal('register', { allowClose: true }); }; }); const footerLoginBtn = document.getElementById('footer-cta-login'); if (footerLoginBtn) footerLoginBtn.style.display = 'inline-flex'; if (landingBanner) landingBanner.style.display = 'none'; if (navDashLink) navDashLink.style.display = 'none'; } // 🔒 Strikter Rollen-Filter für Dozenten- und Admin-Funktionen const isTeacherOrAdmin = loggedIn && user && (user.role === 'teacher' || user.role === 'admin'); const isAdmin = loggedIn && user && user.role === 'admin'; document.querySelectorAll('.teacher-only, #nav-teacher-link, #mobile-drawer-teacher-link, #footer-teacher-link').forEach(el => { if (isTeacherOrAdmin) { el.style.display = (el.tagName === 'LI' || el.tagName === 'DIV') ? 'block' : 'inline-flex'; } else { el.style.display = 'none'; } }); document.querySelectorAll('.admin-only, #nav-admin-link, #mobile-drawer-admin-link, #footer-admin-link').forEach(el => { if (isAdmin) { el.style.display = (el.tagName === 'LI' || el.tagName === 'DIV') ? 'block' : 'inline-flex'; } else { el.style.display = 'none'; } }); // Aktualisiere ggf. vorhandene Curriculum-Aktions-Buttons document.querySelectorAll('.portal-card-action-btn').forEach(btn => { if (loggedIn) { btn.innerHTML = 'Im Lernbereich öffnen →'; btn.classList.remove('locked'); btn.style.background = '#10b981'; btn.onclick = () => { window.location.href = 'dashboard.html'; }; } else { btn.innerHTML = 'Lehrplan ansehen'; btn.classList.add('locked'); btn.style.background = '#0284c7'; btn.onclick = (e) => { e.preventDefault(); window.openAuthModal('register', { allowClose: true, title: 'Lehrplan freischalten', message: 'Erstelle einen kostenlosen Account in unter 30 Sekunden, um sofort vollen Zugriff auf alle Kurse und Module im Lernbereich zu erhalten.' }); }; } }); } }; // Auth Modal UI window.openAuthModal = function (initialMode = 'login', options = {}) { if (window.AUTH && window.AUTH.isLoggedIn()) { window.location.href = 'dashboard.html'; return; } const allowClose = options.allowClose !== false; let modal = document.getElementById('auth-modal-overlay'); if (modal) modal.remove(); const allowReg = window.AUTH.platformStatus.allow_registration !== false && !window.AUTH.platformStatus.maintenance; modal = document.createElement('div'); modal.id = 'auth-modal-overlay'; modal.className = 'certificate-overlay'; modal.style.zIndex = '999999'; modal.innerHTML = `
${allowClose ? ` ` : ''}
IT
${escapeHtml(options.title || (initialMode === 'register' && allowReg ? 'Kostenlos registrieren' : 'Anmelden'))}
IT-PRAXISPORTAL AUTHENTICATION

${escapeHtml(options.message || 'Erhalte sofortigen Zugriff auf 13 Praxis-Kurse, 216 interaktive Module und die Web-IDE.')}

${allowReg ? ` ` : ` `}
${!allowClose ? ` ← Zurück zur Startseite ` : ` `}
100% DSGVO-konform • Serverstandort Deutschland • Keine Tracking-Cookies
`; document.body.appendChild(modal); let isRegister = (initialMode === 'register' && allowReg); const tabLog = document.getElementById('auth-tab-login'); const tabReg = document.getElementById('auth-tab-register'); const nameField = document.getElementById('auth-field-name'); const roleField = document.getElementById('auth-field-role'); const profField = document.getElementById('auth-field-profession'); const submitBtn = document.getElementById('auth-submit-btn'); const errorBox = document.getElementById('auth-error-box'); const form = document.getElementById('auth-form'); function switchMode(reg) { isRegister = reg && allowReg; if (isRegister) { if (tabReg) { tabReg.style.background = '#ffffff'; tabReg.style.color = '#0284c7'; tabReg.style.boxShadow = '0 1px 3px rgba(0,0,0,0.1)'; } tabLog.style.background = 'transparent'; tabLog.style.color = '#64748b'; tabLog.style.boxShadow = 'none'; if (nameField) nameField.style.display = 'block'; if (roleField) roleField.style.display = 'block'; if (profField) profField.style.display = 'block'; submitBtn.innerText = 'Jetzt kostenlos registrieren →'; submitBtn.style.background = '#0284c7'; } else { tabLog.style.background = '#ffffff'; tabLog.style.color = '#0284c7'; tabLog.style.boxShadow = '0 1px 3px rgba(0,0,0,0.1)'; if (tabReg) { tabReg.style.background = 'transparent'; tabReg.style.color = '#64748b'; tabReg.style.boxShadow = 'none'; } if (nameField) nameField.style.display = 'none'; if (roleField) roleField.style.display = 'none'; if (profField) profField.style.display = 'none'; submitBtn.innerText = 'Anmelden →'; submitBtn.style.background = '#059669'; } errorBox.style.display = 'none'; } tabLog.onclick = () => switchMode(false); if (tabReg) tabReg.onclick = () => switchMode(true); switchMode(isRegister); if (allowClose) { const closeX = document.getElementById('auth-modal-close-x'); if (closeX) closeX.onclick = () => modal.remove(); const cancelBtn = document.getElementById('auth-cancel-btn'); if (cancelBtn) cancelBtn.onclick = () => modal.remove(); modal.onclick = (e) => { if (e.target === modal) modal.remove(); }; } form.onsubmit = async (e) => { e.preventDefault(); errorBox.style.display = 'none'; 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 || 'solo'; const profession = document.getElementById('auth-select-profession')?.value || ''; const rememberMe = document.getElementById('auth-remember-me')?.checked || false; submitBtn.disabled = true; submitBtn.innerText = 'Bitte warten...'; let result; if (isRegister) { result = await window.AUTH.register(name, email, password, role, rememberMe, profession); } else { result = await window.AUTH.login(email, password, rememberMe); } submitBtn.disabled = false; submitBtn.innerText = isRegister ? 'Jetzt kostenlos registrieren →' : 'Anmelden →'; if (result.success) { modal.remove(); if (options.onSuccess) { options.onSuccess(result.user); } else { try { window.AUTH.updateUI(); } catch (e) {} window.location.href = 'dashboard.html'; } } else { errorBox.innerText = result.error || 'Fehler bei der Authentifizierung'; errorBox.style.display = 'block'; } }; }; // Initialisierung beim Laden des DOMs function initAuth() { window.AUTH.updateUI(); window.AUTH.checkStatus(); window.AUTH.fetchMe(); const params = new URLSearchParams(window.location.search); const authParam = params.get('auth'); if (authParam === 'login' || authParam === 'register') { if (!window.AUTH.isLoggedIn()) { window.openAuthModal(authParam, { allowClose: true }); } else { window.location.replace('dashboard.html'); } } } // Globales User Avatar Dropdown Handling window.toggleUserDropdown = function (event) { if (event) { event.preventDefault(); event.stopPropagation(); } const menu = document.getElementById('portal-dropdown-menu'); const btn = document.getElementById('portal-user-btn'); if (!menu) return; const isShown = menu.classList.contains('show'); if (isShown) { menu.classList.remove('show'); if (btn) btn.setAttribute('aria-expanded', 'false'); } else { menu.classList.add('show'); if (btn) btn.setAttribute('aria-expanded', 'true'); } }; // Klick außerhalb schließt das Dropdown document.addEventListener('click', (e) => { const dd = document.getElementById('portal-user-dropdown'); const menu = document.getElementById('portal-dropdown-menu'); const btn = document.getElementById('portal-user-btn'); if (menu && menu.classList.contains('show')) { if (!dd || !dd.contains(e.target)) { menu.classList.remove('show'); if (btn) btn.setAttribute('aria-expanded', 'false'); } } }); // Escape-Taste schließt das Dropdown document.addEventListener('keydown', (e) => { if (e.key === 'Escape') { const menu = document.getElementById('portal-dropdown-menu'); const btn = document.getElementById('portal-user-btn'); if (menu && menu.classList.contains('show')) { menu.classList.remove('show'); if (btn) btn.setAttribute('aria-expanded', 'false'); } } }); if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initAuth); } else { initAuth(); } })();