Files
geograsim/App/login.html
T
Adminator 9a61f55cb1 Atlas: Infrastruktur + Team-Konventionen + Sprachregel Lernarbeit
- Design-System (assets/css/design-system.css) mit 21 Komponenten,
  iPad-Responsive-Breakpoints, Touch-Ziele 36px, Music-Player,
  Glossar-Tooltips
- Templates (sims/template.html + student/teacher-Dashboard)
- Docs: module-interface.md (inkl. 4a Sprachregel, 4b Leichte Sprache,
  4c iPad), content-architecture.md, crash-recovery.md, music-registry.md
- Admin-Infrastruktur: admin-modules.html + api/admin-modules.php
  (Titel, Emoji, Bild, Status, Dauer, Alter pro Modul)
- Inbox-System: _inbox/README.md + _status.md fuer Atlas + Briefings
  an Klima, Glossar, Lehrplan, Fluss
- Zentrale SFX-Pipeline (scripts/generate-sounds.py)
- DALL-E-Bilder: 8 Badges + 5 Glossar-Repraesentationsbilder (Querformat)
- Logo + Inter-Font lokal
- PHP-APIs: admin, glossar, levels, licenses, progress, waypoints,
  assignments, profile, tickets
- Spielsprache entfernt (admin-modules, admin-levels, schueler)
- Landing-Page-Bearbeitungen (Boote sichtbarer, Button-Hintergrund)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 11:51:14 +02:00

285 lines
14 KiB
HTML

<!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;display:flex;align-items:center;justify-content:center}
.card{background:#fff;border-radius:16px;padding:2rem;max-width:400px;width:90%;box-shadow:0 8px 30px rgba(0,0,0,.08)}
.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}
</style>
</head>
<body>
<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 style="text-align:center;margin-bottom:1rem"><a href="index.html" style="font-size:.72rem;color:#4a7c8a;text-decoration:none;font-weight:600">← Zurück zur Startseite</a></div>
<!-- LOGIN (einheitlich für alle) -->
<div id="view-login">
<h2 style="font-size:1rem;font-weight:800;color:#1f4e5a;margin-bottom:.8rem;text-align:center">Einloggen</h2>
<label>E-Mail oder Benutzername</label>
<input type="text" id="l-user" placeholder="name@schule.at oder schueler01">
<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>
<!-- REGISTRIERUNG -->
<div id="view-register" class="hidden">
<h2 style="font-size:1rem;font-weight:800;color:#1f4e5a;margin-bottom:.8rem;text-align:center">Registrieren — Account erstellen</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>Vorname</label>
<input type="text" id="r-first" placeholder="Vorname">
<label>Nachname</label>
<input type="text" id="r-last" placeholder="Nachname">
<label>E-Mail</label>
<input type="email" id="r-email" placeholder="name@schule.at">
<label>Schule (optional)</label>
<input type="text" id="r-school" placeholder="Name der Schule">
<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">
<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. Frage deine Lehrperson nach dem Klassencode und deinen 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">
<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">
<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 class="msg" id="msg"></div>
<!-- Schnell-Login -->
<div class="quick-section">
<p>Zum Ausprobieren — Schnell-Login:</p>
<div style="display:flex;flex-direction:column;gap:.35rem">
<button class="btn-secondary btn" onclick="quickLogin('teacher')" style="width:100%;font-size:.72rem;padding:.45rem">👩‍🏫 Test-Lehrperson</button>
<div class="quick-btns">
<button class="btn-secondary btn" onclick="quickLogin('anna')" style="font-size:.72rem;padding:.45rem">👧 Anna</button>
<button class="btn-secondary btn" onclick="quickLogin('ben')" style="font-size:.72rem;padding:.45rem">👦 Ben</button>
<button class="btn-secondary btn" onclick="quickLogin('clara')" style="font-size:.72rem;padding:.45rem">👧 Clara</button>
</div>
</div>
</div>
</div>
<script>
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 ===
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; }
// Versuche als Lehrperson
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;
}
// Versuche als Schüler*in (sucht über alle Klassen)
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 ===
async function doRegisterTeacher() {
var first = document.getElementById('r-first').value.trim();
var last = document.getElementById('r-last').value.trim();
var email = document.getElementById('r-email').value.trim();
var school = document.getElementById('r-school').value.trim();
var pass = document.getElementById('r-pass').value;
var pass2 = document.getElementById('r-pass2').value;
if (!first || !last || !email || !pass) { showMsg('Bitte alle Pflichtfelder 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; }
var displayName = first + ' ' + last;
var r = await api('register', {displayName: displayName, schoolName: school, 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'); }
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>
</body>
</html>