class AuthManager { constructor() { this.isAuthenticated = false; this.username = null; this._resolveLogin = null; this._overlayEl = null; } async initialize() { const status = await this._checkStatus(); if (status.authenticated) { this.isAuthenticated = true; this.username = status.username; return; } return this._showLoginOverlay(); } async _checkStatus() { try { const res = await fetch('/api/auth/status'); return await res.json(); } catch { return { authenticated: false }; } } async login(username, password) { try { const res = await fetch('/api/auth/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username, password }) }); const data = await res.json(); if (res.ok && data.success) { this.isAuthenticated = true; this.username = data.username; this._hideLoginOverlay(); if (this._resolveLogin) this._resolveLogin(); return { success: true }; } return { success: false, error: data.error || 'Invalid credentials' }; } catch { return { success: false, error: 'Connection error' }; } } async logout() { try { await fetch('/api/auth/logout', { method: 'POST' }); } catch { /* ignore */ } window.location.reload(); } _showLoginOverlay() { return new Promise(resolve => { this._resolveLogin = resolve; const overlay = document.createElement('div'); overlay.className = 'login-overlay aurora-bg aurora-static'; overlay.innerHTML = `

Step softly back into your own private universe

`; document.body.appendChild(overlay); this._overlayEl = overlay; // Focus username after animation setTimeout(() => { document.getElementById('login-username')?.focus(); }, 100); document.getElementById('login-form').addEventListener('submit', async (e) => { e.preventDefault(); const username = document.getElementById('login-username').value.trim(); const password = document.getElementById('login-password').value; const errorEl = document.getElementById('login-error'); const btn = document.getElementById('login-submit'); errorEl.querySelector('.login-error-text').textContent = ''; errorEl.classList.remove('visible'); btn.classList.add('loading'); btn.disabled = true; const result = await this.login(username, password); if (!result.success) { errorEl.querySelector('.login-error-text').textContent = result.error; errorEl.classList.add('visible'); btn.classList.remove('loading'); btn.disabled = false; document.getElementById('login-password').value = ''; document.getElementById('login-password').focus(); } }); }); } _hideLoginOverlay() { if (!this._overlayEl) return; this._overlayEl.classList.add('hiding'); setTimeout(() => { this._overlayEl?.remove(); this._overlayEl = null; }, 250); } interceptFetch() { const original = window.fetch.bind(window); const self = this; window.fetch = async function(...args) { // Skip auth endpoints to avoid infinite loops const url = typeof args[0] === 'string' ? args[0] : args[0]?.url || ''; if (url.includes('/api/auth/')) return original(...args); const response = await original(...args); if (response.status === 401 && self.isAuthenticated) { self.isAuthenticated = false; await self._showLoginOverlay(); // Retry the original request after re-login return original(...args); } return response; }; } } window.authManager = new AuthManager();