/* =================================================================== Creator Workspace - Anmeldeseite Kein Framework, keine externen Abhängigkeiten. =================================================================== */ (() => { 'use strict'; const $ = (id) => document.getElementById(id); const rollen = Array.from(document.querySelectorAll('.rolle')); const formular = $('formular'); const code = $('code'); const auge = $('auge'); const fehler = $('fehler'); const hinweis = $('hinweis'); const knopf = $('knopf'); const fuer = $('fuer'); const NAMEN = { admin: 'DogFather', creator: 'Creator', scout: 'Scout' }; const SPEICHER = 'dfw.rolle'; let gewaehlt = null; /* ---------- Rolle wählen ------------------------------------------ */ function waehle(rolle, fokus = false) { gewaehlt = rolle; rollen.forEach((b) => b.setAttribute('aria-checked', String(b.dataset.rolle === rolle))); fuer.textContent = `· ${NAMEN[rolle]}`; fuer.hidden = false; melde(''); try { localStorage.setItem(SPEICHER, rolle); } catch { /* Privater Modus */ } if (fokus) code.focus(); } rollen.forEach((b) => { b.addEventListener('click', () => waehle(b.dataset.rolle, true)); // Pfeiltasten wie bei echten Radio-Feldern b.addEventListener('keydown', (e) => { const i = rollen.indexOf(b); let ziel = null; if (e.key === 'ArrowDown' || e.key === 'ArrowRight') ziel = rollen[(i + 1) % rollen.length]; if (e.key === 'ArrowUp' || e.key === 'ArrowLeft') ziel = rollen[(i - 1 + rollen.length) % rollen.length]; if (!ziel) return; e.preventDefault(); ziel.focus(); waehle(ziel.dataset.rolle); }); }); // Zuletzt genutzte Rolle vorauswählen - spart bei täglicher Nutzung einen Klick. try { const zuletzt = localStorage.getItem(SPEICHER); if (zuletzt && NAMEN[zuletzt]) waehle(zuletzt); } catch { /* egal */ } /* ---------- Code anzeigen / verbergen ------------------------------ */ auge.addEventListener('click', () => { const sichtbar = code.type === 'text'; code.type = sichtbar ? 'password' : 'text'; auge.setAttribute('aria-pressed', String(!sichtbar)); auge.setAttribute('aria-label', sichtbar ? 'Code anzeigen' : 'Code verbergen'); code.focus(); }); /* ---------- Feststelltaste ----------------------------------------- */ const pruefeFeststell = (e) => { if (typeof e.getModifierState !== 'function') return; hinweis.hidden = !e.getModifierState('CapsLock'); }; code.addEventListener('keyup', pruefeFeststell); code.addEventListener('keydown', pruefeFeststell); code.addEventListener('blur', () => { hinweis.hidden = true; }); /* ---------- Rückmeldung -------------------------------------------- */ function melde(text) { fehler.textContent = text; } function laden(an) { knopf.disabled = an; knopf.querySelector('.knopf__text').textContent = an ? 'Prüfe …' : 'Anmelden'; } /* ---------- Absenden ------------------------------------------------ */ formular.addEventListener('submit', async (e) => { e.preventDefault(); const wert = code.value.trim(); if (!gewaehlt) { melde('Bitte zuerst auswählen, womit du dich anmeldest.'); rollen[0].focus(); return; } if (!wert) { melde('Bitte den Zugangscode eingeben.'); code.focus(); return; } melde(''); laden(true); try { const antwort = await fetch('/workspace/api/anmelden', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ rolle: gewaehlt, code: wert }), credentials: 'same-origin', }); if (antwort.ok) { const ziel = (await antwort.json().catch(() => ({}))).weiter || '/workspace'; document.body.classList.add('oeffnet'); setTimeout(() => { location.assign(ziel); }, 260); return; } // 404 heißt: Es gibt noch gar keine Gegenstelle. Das ist derzeit der // Normalfall - die Seite steht schon, der Login-Server wird noch gebaut. // Eine ehrliche Meldung ist hier besser als "Code stimmt nicht". if (antwort.status === 404) { melde('Der Zugang wird gerade eingerichtet – Anmelden ist noch nicht möglich.'); return; } // Bewusst keine Auskunft darüber, ob Rolle oder Code falsch war. melde(antwort.status === 429 ? 'Zu viele Versuche. Bitte kurz warten.' : 'Zugangscode stimmt nicht.'); code.select(); } catch { melde('Keine Verbindung zum Server. Bitte erneut versuchen.'); } finally { laden(false); } }); /* ---------- Sehr dezente Parallaxe ---------------------------------- */ const ruhig = matchMedia('(prefers-reduced-motion: reduce)'); const bild = document.querySelector('.buehne__bild'); const grob = matchMedia('(pointer: coarse)'); if (bild && !ruhig.matches && !grob.matches) { let ziel = { x: 0, y: 0 }, ist = { x: 0, y: 0 }, laeuft = false; addEventListener('pointermove', (e) => { ziel.x = (e.clientX / innerWidth - 0.5) * -10; // max. 10 px ziel.y = (e.clientY / innerHeight - 0.5) * -6; if (!laeuft) { laeuft = true; requestAnimationFrame(schritt); } }, { passive: true }); function schritt() { ist.x += (ziel.x - ist.x) * 0.06; ist.y += (ziel.y - ist.y) * 0.06; bild.style.transform = `scale(1.03) translate3d(${ist.x.toFixed(2)}px, ${ist.y.toFixed(2)}px, 0)`; if (Math.abs(ziel.x - ist.x) > 0.05 || Math.abs(ziel.y - ist.y) > 0.05) { requestAnimationFrame(schritt); } else { laeuft = false; } } } })();