Files
geograsim/App/login.php
T

372 lines
18 KiB
PHP

<?php
/* Login-Page mit Hero-Background-Overlay (Atlas-Empfehlung Option B, L.38).
Standard-Hauptnav klar oben, dahinter atmosphärischer Landingpage-Hero
geblurred, Login-Card als zentriertes Modal. Klick auf den Backdrop →
Landingpage; ESC ebenfalls. */
require_once __DIR__ . '/php/config/app.php';
$bp = BASE_PATH;
$activeMain = null;
?>
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>GeoGraSim — Anmelden</title>
<link rel="icon" type="image/png" href="favicon-96x96.png" sizes="96x96">
<link rel="icon" type="image/svg+xml" href="favicon.svg">
<link rel="stylesheet" href="assets/fonts/inter.css">
<style>
*{margin:0;padding:0;box-sizing:border-box}
body{font-family:'Inter',sans-serif;background:#f7f6f3;min-height:100vh}
/* Login-Shell — füllt den Bereich unter der Hauptnav */
.login-shell { position: relative; min-height: calc(100vh - 56px); overflow: hidden; }
.login-shell .hero-bg { z-index: 0; }
.login-overlay {
position: absolute; inset: 0; z-index: 1;
backdrop-filter: blur(2px);
-webkit-backdrop-filter: blur(2px);
background: rgba(247, 246, 243, 0.10);
display: flex; align-items: center; justify-content: center;
padding: 2rem 1rem;
cursor: pointer;
}
.login-card-inner { cursor: default; }
/* Login-Card — 2-spaltig (Anmelden links · Registrieren rechts) */
.card{background:#fff;border-radius:16px;padding:1.6rem 1.8rem;max-width:760px;width:96%;box-shadow:0 20px 60px rgba(0,0,0,.18)}
.login-grid{display:grid;grid-template-columns:1fr 1fr;gap:1.8rem;align-items:start;margin-top:.4rem}
.lg-col{display:flex;flex-direction:column}
.lg-col-register{border-left:1px solid rgba(0,0,0,.08);padding-left:1.8rem}
.lg-col h2{font-size:.95rem;font-weight:800;color:#1f4e5a;margin-bottom:.6rem;text-align:center}
@media (max-width:700px){
.card{padding:1.4rem 1.3rem;max-width:420px}
.login-grid{grid-template-columns:1fr;gap:1rem}
.lg-col-register{border-left:none;padding-left:0;border-top:1px solid rgba(0,0,0,.08);padding-top:1rem}
}
.logo{text-align:center;margin-bottom:1rem}
.logo p{font-size:.78rem;color:#8a8a8a;margin-top:.3rem}
label{display:block;font-size:.72rem;font-weight:600;color:#4a4a4a;margin-bottom:.2rem;margin-top:.8rem}
input{width:100%;padding:.55rem .7rem;border:1.5px solid rgba(0,0,0,.1);border-radius:7px;font-size:.82rem;font-family:inherit;outline:none;transition:border .15s}
input:focus{border-color:#4a7c8a}
.btn{width:100%;padding:.6rem;border:none;border-radius:8px;background:#4a7c8a;color:#fff;font-size:.85rem;font-weight:700;cursor:pointer;margin-top:1rem;transition:background .15s}
.btn:hover{background:#3a6470}
.btn-secondary{background:transparent;color:#4a7c8a;border:1.5px solid #4a7c8a}
.btn-secondary:hover{background:#dae8ec}
.msg{margin-top:.8rem;font-size:.75rem;padding:.4rem .6rem;border-radius:6px;display:none}
.msg.err{display:block;background:#f2ddd8;color:#c07a6b}
.msg.ok{display:block;background:#dceadd;color:#5a8a5e}
.link{text-align:center;margin-top:.8rem;font-size:.72rem;color:#8a8a8a}
.link a{color:#4a7c8a;text-decoration:none;font-weight:600}
.hidden{display:none !important}
.pw-wrap{position:relative}
.pw-wrap input{padding-right:2.2rem}
.pw-eye{position:absolute;right:.5rem;top:50%;transform:translateY(-50%);cursor:pointer;font-size:1rem;opacity:.4;user-select:none;line-height:1}
.pw-eye:hover{opacity:.7}
.tabs{display:flex;gap:0;margin-bottom:1rem;border-radius:8px;overflow:hidden;border:1px solid rgba(0,0,0,.08)}
.tab{flex:1;padding:.5rem;text-align:center;font-size:.75rem;font-weight:600;cursor:pointer;background:#f7f6f3;color:#8a8a8a;transition:all .15s}
.tab.on{background:#4a7c8a;color:#fff}
.divider{text-align:center;margin:1rem 0;color:#ccc;font-size:.72rem;position:relative}
.divider::before,.divider::after{content:'';position:absolute;top:50%;width:35%;height:1px;background:#e0e0e0}
.divider::before{left:0}
.divider::after{right:0}
.quick-section{border-top:1px solid rgba(0,0,0,.06);margin-top:1.2rem;padding-top:1rem}
.quick-section p{font-size:.68rem;color:#8a8a8a;text-align:center;margin-bottom:.6rem}
.quick-btns{display:flex;gap:.35rem}
.quick-btns button{flex:1;font-size:.72rem;padding:.45rem}
.dsgvo-row{
display:flex;align-items:flex-start;gap:.45rem;margin-top:1rem;
font-size:.72rem;font-weight:500;color:#4a4a4a;cursor:pointer
}
.dsgvo-row input{width:auto;margin-top:.18rem;cursor:pointer}
.dsgvo-row a{color:#4a7c8a;font-weight:600;text-decoration:underline}
</style>
</head>
<body>
<?php include __DIR__ . '/pages/_partials/public_topnav.php'; ?>
<div class="login-shell">
<?php include __DIR__ . '/pages/_partials/public_hero_bg.php'; ?>
<div class="login-overlay" id="login-overlay">
<div class="login-card-inner">
<div class="card">
<div class="logo">
<a href="index.html"><img src="assets/img/geograsim_t.svg" alt="GeoGraSim" style="height:52px"></a>
<p>Interaktive Simulationen für den Geografieunterricht</p>
</div>
<div class="login-grid">
<!-- ============== SPALTE LINKS: ANMELDEN ↔ REGISTRIEREN (Toggle) + Forgot/Reset ============== -->
<div class="lg-col lg-col-login">
<div id="view-login">
<h2>Einloggen</h2>
<label>E-Mail oder Benutzername</label>
<input type="text" id="l-user" placeholder="name@schule.at oder schueler01">
<p style="font-size:.66rem;color:#8a8a8a;margin:.15rem 0 .3rem">Lehrpersonen melden sich mit ihrer <strong>E-Mail-Adresse</strong> an, Schüler*innen mit ihrem <strong>Benutzernamen</strong>.</p>
<label>Passwort</label>
<div class="pw-wrap"><input type="password" id="l-pass"><span class="pw-eye" onclick="togglePw(this)">👁</span></div>
<button class="btn" onclick="doLogin()">Einloggen</button>
<div class="link"><a href="#" onclick="showView('forgot');return false">Passwort vergessen?</a></div>
<div class="divider">oder</div>
<button class="btn btn-secondary" onclick="showView('register')" style="margin-top:0">Registrieren — Account erstellen</button>
</div>
<div id="view-register" class="hidden">
<h2>Registrieren</h2>
<div class="tabs">
<div class="tab on" onclick="setRegType('teacher')">Lehrperson</div>
<div class="tab" onclick="setRegType('student')">Schüler*in</div>
</div>
<div id="reg-teacher">
<label>E-Mail-Adresse</label>
<input type="email" id="r-email" placeholder="name@schule.at">
<p style="font-size:.66rem;color:#8a8a8a;margin:.15rem 0 .4rem">Ihre E-Mail-Adresse ist zugleich Ihr <strong>Benutzername</strong> für die Anmeldung.</p>
<label>Passwort (mind. 8 Zeichen)</label>
<div class="pw-wrap"><input type="password" id="r-pass" minlength="8"><span class="pw-eye" onclick="togglePw(this)">👁</span></div>
<label>Passwort bestätigen</label>
<input type="password" id="r-pass2">
<label class="dsgvo-row">
<input type="checkbox" id="r-dsgvo">
<span>Ich akzeptiere die <a href="datenschutz.php" target="_blank" rel="noopener">Datenschutzerklärung</a>.</span>
</label>
<button class="btn" onclick="doRegisterTeacher()">Registrieren</button>
</div>
<div id="reg-student" class="hidden">
<label>Klassencode</label>
<input type="text" id="rs-code" placeholder="z.B. GEO1A" maxlength="6" autocapitalize="characters">
<label>Benutzername</label>
<input type="text" id="rs-user" placeholder="z.B. max01">
<label>Passwort (mind. 4 Zeichen)</label>
<div class="pw-wrap"><input type="password" id="rs-pass"><span class="pw-eye" onclick="togglePw(this)">👁</span></div>
<p style="font-size:.68rem;color:#8a8a8a;margin-top:.5rem">Schüler*innen-Zugänge werden von der Lehrperson erstellt. Frag nach Klassencode + Zugangsdaten.</p>
<button class="btn" onclick="doRegisterStudent()">Registrieren</button>
</div>
<div class="link"><a href="#" onclick="showView('login');return false">← Bereits registriert? Anmelden</a></div>
</div>
<!-- PASSWORT VERGESSEN -->
<div id="view-forgot" class="hidden">
<h2>Passwort zurücksetzen</h2>
<label>E-Mail-Adresse</label>
<input type="email" id="f-email" placeholder="name@schule.at">
<button class="btn" onclick="doResetRequest()">Link senden</button>
<div class="link"><a href="#" onclick="showView('login');return false">← Zurück zur Anmeldung</a></div>
</div>
<!-- PASSWORT RESET (via URL-Token) -->
<div id="view-reset" class="hidden">
<h2>Neues Passwort setzen</h2>
<label>Neues Passwort (mind. 8 Zeichen)</label>
<div class="pw-wrap"><input type="password" id="n-pass" minlength="8"><span class="pw-eye" onclick="togglePw(this)">👁</span></div>
<label>Passwort bestätigen</label>
<input type="password" id="n-pass2">
<button class="btn" onclick="doResetConfirm()">Passwort setzen</button>
</div>
</div>
<!-- ============== SPALTE RECHTS: SCHNELL-LOGIN (temporäre Test-Sektion, später raus) ============== -->
<div class="lg-col lg-col-register">
<h2>Zum Ausprobieren</h2>
<p style="font-size:.7rem;color:#8a8a8a;margin-bottom:.7rem;text-align:center">Schnell-Login mit Demo-Accounts:</p>
<div style="display:flex;flex-direction:column;gap:.4rem">
<button class="btn-secondary btn" onclick="quickLogin('teacher')" style="width:100%;font-size:.78rem">👩‍🏫 Test-Lehrperson</button>
<div class="quick-btns" style="display:flex;gap:.35rem">
<button class="btn-secondary btn" onclick="quickLogin('anna')" style="flex:1;font-size:.72rem;padding:.45rem">👧 Anna</button>
<button class="btn-secondary btn" onclick="quickLogin('ben')" style="flex:1;font-size:.72rem;padding:.45rem">👦 Ben</button>
<button class="btn-secondary btn" onclick="quickLogin('clara')" style="flex:1;font-size:.72rem;padding:.45rem">👧 Clara</button>
</div>
</div>
</div>
</div>
<div class="msg" id="msg"></div>
</div>
</div>
</div>
</div>
<script>
// Klick außerhalb der Card auf den Overlay → zurück zur Landingpage
document.getElementById('login-overlay').addEventListener('click', function(e) {
if (e.target.closest('.login-card-inner')) return;
location.href = 'index.html';
});
// ESC schließt ebenfalls
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') location.href = 'index.html';
});
function togglePw(el) {
var inp = el.previousElementSibling;
if (inp.type === 'password') { inp.type = 'text'; el.textContent = '🙈'; }
else { inp.type = 'password'; el.textContent = '👁'; }
}
var BASE = (window.__GGS__?.baseUrl) || '';
if (!BASE) { var p = location.pathname, i = p.indexOf('/geograsim'); if (i !== -1) { var rest = p.substring(i + '/geograsim'.length); BASE = rest.indexOf('/App/') === 0 ? p.substring(0, i) + '/geograsim/App' : p.substring(0, i) + '/geograsim'; } }
function showMsg(text, type) {
var m = document.getElementById('msg');
m.textContent = text; m.className = 'msg ' + type;
}
async function api(action, data) {
var res = await fetch(BASE + '/api/auth', {
method: 'POST', headers: {'Content-Type':'application/json'}, credentials:'same-origin',
body: JSON.stringify(Object.assign({action: action}, data))
});
return await res.json();
}
// === VIEWS ===
// Linke Spalte = Toggle login ↔ register ↔ forgot ↔ reset.
// Rechte Spalte = permanenter Schnell-Login-Block (außerhalb des Toggles).
function showView(view) {
['login','register','forgot','reset'].forEach(function(v) {
document.getElementById('view-' + v).classList.toggle('hidden', v !== view);
});
document.getElementById('msg').className = 'msg';
}
function setRegType(type) {
document.querySelectorAll('.tabs .tab').forEach(function(t, i) {
t.classList.toggle('on', (i === 0 && type === 'teacher') || (i === 1 && type === 'student'));
});
document.getElementById('reg-teacher').classList.toggle('hidden', type !== 'teacher');
document.getElementById('reg-student').classList.toggle('hidden', type !== 'student');
}
// === LOGIN (einheitlich) ===
async function doLogin() {
var user = document.getElementById('l-user').value.trim();
var pass = document.getElementById('l-pass').value;
if (!user || !pass) { showMsg('Bitte ausfüllen.', 'err'); return; }
var r = await api('login', {email: user, password: pass});
if (!r.error) {
showMsg('Willkommen, ' + r.displayName + '!', 'ok');
setTimeout(function() { location.href = 'teacher.html'; }, 600);
return;
}
var r2 = await api('student_login_by_username', {username: user, password: pass});
if (!r2.error) {
showMsg('Willkommen, ' + r2.displayName + '!', 'ok');
setTimeout(function() { location.href = 'schueler.html'; }, 600);
return;
}
showMsg('Benutzername oder Passwort falsch.', 'err');
}
// === REGISTRIERUNG LEHRPERSON ===
// Minimal-Felder: nur E-Mail + Passwort + DSGVO-Akzeptanz. displayName wird
// aus dem E-Mail-Local-Part abgeleitet (Backend braucht ihn als nicht-leer);
// echter Anzeigename lässt sich später im Backend/Profil ergänzen.
async function doRegisterTeacher() {
var email = document.getElementById('r-email').value.trim();
var pass = document.getElementById('r-pass').value;
var pass2 = document.getElementById('r-pass2').value;
var dsgvo = document.getElementById('r-dsgvo').checked;
if (!email || !pass) { showMsg('Bitte E-Mail und Passwort ausfüllen.', 'err'); return; }
if (pass !== pass2) { showMsg('Passwörter stimmen nicht überein.', 'err'); return; }
if (pass.length < 8) { showMsg('Passwort muss mindestens 8 Zeichen haben.', 'err'); return; }
if (!dsgvo) { showMsg('Bitte Datenschutzerklärung akzeptieren.', 'err'); return; }
var displayName = (email.split('@')[0] || 'Lehrperson').substring(0, 64);
var r = await api('register', {displayName: displayName, schoolName: '', email: email, password: pass});
if (r.error) { showMsg(r.error, 'err'); return; }
showMsg('Registrierung erfolgreich! Klasse "Meine Klasse" wurde angelegt.', 'ok');
setTimeout(function() { location.href = 'teacher.html'; }, 1200);
}
// === REGISTRIERUNG SCHÜLER*IN ===
async function doRegisterStudent() {
var code = document.getElementById('rs-code').value.trim().toUpperCase();
var user = document.getElementById('rs-user').value.trim();
var pass = document.getElementById('rs-pass').value;
if (!code || !user || !pass) { showMsg('Alle Felder ausfüllen.', 'err'); return; }
if (pass.length < 4) { showMsg('Passwort muss mindestens 4 Zeichen haben.', 'err'); return; }
var r = await api('student_register', {classCode: code, username: user, password: pass});
if (r.error) { showMsg(r.error, 'err'); return; }
showMsg('Account erstellt! Willkommen, ' + r.displayName + '!', 'ok');
setTimeout(function() { location.href = 'schueler.html'; }, 800);
}
// === PASSWORT VERGESSEN ===
async function doResetRequest() {
var email = document.getElementById('f-email').value.trim();
if (!email) { showMsg('Bitte E-Mail-Adresse eingeben.', 'err'); return; }
var r = await api('reset_request', {email: email});
showMsg(r.message || 'Falls ein Konto existiert, wurde eine E-Mail gesendet.', 'ok');
}
// === PASSWORT RESET (Token) ===
var resetToken = new URLSearchParams(location.search).get('reset');
if (resetToken) { showView('reset'); }
// === QR-Code-Deeplink: ?user=... &class=... vorbelegen ===
// Schüler*innen scannen ihren Klassen-Ticket-QR → User ist automatisch im Feld.
(function(){
var p = new URLSearchParams(location.search);
var u = p.get('user'); var c = p.get('class');
if (u) {
var el = document.getElementById('l-user'); if (el) el.value = u;
// Klassencode könnten wir in den Username schreiben — Login akzeptiert nur User+PIN,
// der Klassencode ist NICHT teil des Schüler-Login-Flows (er ist nur fürs Register).
// PIN-Feld kriegt den Fokus, damit Schüler*in nur noch PIN tippen muss.
var pw = document.getElementById('l-pass'); if (pw) pw.focus();
}
})();
async function doResetConfirm() {
var pass = document.getElementById('n-pass').value;
var pass2 = document.getElementById('n-pass2').value;
if (!pass || pass.length < 8) { showMsg('Passwort muss mindestens 8 Zeichen haben.', 'err'); return; }
if (pass !== pass2) { showMsg('Passwörter stimmen nicht überein.', 'err'); return; }
var r = await api('reset_confirm', {token: resetToken, newPassword: pass});
if (r.error) { showMsg(r.error, 'err'); return; }
showMsg('Passwort geändert! Sie können sich jetzt anmelden.', 'ok');
setTimeout(function() { location.href = 'login.html'; }, 1500);
}
// === QUICK LOGIN ===
async function quickLogin(who) {
showMsg('Anmelden...', 'ok');
if (who === 'teacher') {
var r = await api('login', {email: 'test@geograsim.at', password: 'test1234'});
if (r.error) { showMsg(r.error, 'err'); return; }
showMsg('Willkommen, ' + r.displayName + '!', 'ok');
setTimeout(function() { location.href = 'teacher.html'; }, 600);
} else {
var r = await api('student_login', {classCode: 'TEST01', username: who, password: 'test1234'});
if (r.error) { showMsg(r.error, 'err'); return; }
showMsg('Willkommen, ' + r.displayName + '!', 'ok');
setTimeout(function() { location.href = 'schueler.html'; }, 600);
}
}
// Enter-Key
document.getElementById('l-pass').addEventListener('keydown', function(e) { if(e.key==='Enter') doLogin(); });
document.getElementById('l-user').addEventListener('keydown', function(e) { if(e.key==='Enter') doLogin(); });
// Check ob schon eingeloggt
if (!resetToken) {
api('status', {}).then(function(r) {
if (r.role === 'teacher') location.href = 'teacher.html';
if (r.role === 'student') location.href = 'schueler.html';
});
}
</script>
<script src="assets/js/version-tag.js" defer></script>
</body>
</html>