Files
geograsim/App/profil.html
T
Adminator a68c58ac9e Weltkueche Teil A: Land pro Lehrer persistent (teachers.country) + Sim-Verdrahtung
- profil.html: Länderauswahl (AT/DE/CH/LI) im Lehrer-Profil, lädt/zeigt aktuelles Land.
- profile.php: speichert country in teachers.country; gibt country zurück.
- weltkueche.php: injiziert window.WK_COUNTRY = Country::current() (Kaskade Schüler→Klasse→Lehrer→AT) — Fundament für die konsumentenland-basierte Herkunft (Teil B).
Spalte teachers.country existierte bereits, keine Migration nötig.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-21 12:13:27 +02:00

527 lines
32 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>GeoGraSim — Profil</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}
/* Topbar 1:1 wie schueler.html — gleiche Kopfzeile in der Schüler-Ansicht */
.topbar{background:#fff;padding:.55rem 1.2rem;display:flex;align-items:center;gap:.7rem;border-bottom:1px solid rgba(0,0,0,.06);position:sticky;top:0;z-index:10}
.topbar .logo{height:28px}
.topbar .spacer{flex:1}
.topbar .user-pill{display:flex;align-items:center;gap:.35rem;padding:.15rem .6rem .15rem .15rem;background:#dae8ec;border-radius:99px;font-size:.72rem;font-weight:600;color:#4a7c8a;text-decoration:none;cursor:pointer}
.topbar .user-pill:hover{background:#c8dce2}
.topbar .user-pill .emoji{font-size:1.2rem;width:28px;height:28px;border-radius:50%;display:inline-flex;align-items:center;justify-content:center;background:#fff;overflow:hidden}
.topbar .user-pill .emoji img{width:100%;height:100%;object-fit:cover;display:block}
.topbar .btn-sm{padding:.25rem .55rem;border:1px solid rgba(0,0,0,.08);border-radius:6px;background:#fff;font-size:.65rem;font-weight:600;cursor:pointer;color:#4a4a4a;text-decoration:none}
.topbar .btn-sm:hover{background:#dae8ec;color:#4a7c8a}
.wrap{max-width:600px;margin:2rem auto;padding:0 1rem}
/* Tab-Navigation 1:1 wie schueler.html — Profil ist hier aktiv. */
.tabs{display:flex;gap:0;margin-bottom:1.2rem;border-radius:10px;overflow:hidden;border:1.5px solid rgba(0,0,0,.06);background:#fff}
.tab{flex:1;padding:.6rem;text-align:center;font-size:.78rem;font-weight:700;cursor:pointer;color:#8a8a8a;transition:all .15s;border:none;background:transparent;text-decoration:none}
.tab.on{background:#4a7c8a;color:#fff}
.tab:hover:not(.on){background:#f0eee6}
.tabs.hidden{display:none}
.card{background:#fff;border-radius:12px;padding:1.2rem;box-shadow:0 2px 8px rgba(0,0,0,.04);border:1px solid rgba(0,0,0,.04);margin-bottom:1rem}
.card-h{font-size:.62rem;font-weight:700;color:#4a7c8a;text-transform:uppercase;letter-spacing:.05em;margin-bottom:.6rem}
.avatar-section{text-align:center;padding:1rem 0}
.avatar-current{width:100px;height:100px;border-radius:50%;cursor:pointer;transition:transform .2s;display:inline-block;background:#dae8ec;font-size:3.5rem;line-height:100px;text-align:center;overflow:hidden}
.avatar-current img{width:100%;height:100%;object-fit:cover;display:block}
.avatar-current:hover{transform:scale(1.08)}
.avatar-name{font-size:1.1rem;font-weight:700;margin-top:.5rem}
.avatar-role{font-size:.72rem;color:#8a8a8a;margin-top:.1rem}
.avatar-picker{display:none;grid-template-columns:repeat(6,1fr);gap:.6rem .4rem;margin-top:1rem;padding:.6rem;background:#f7f6f3;border-radius:10px;max-height:520px;overflow-y:auto}
.avatar-picker.open{display:grid}
.avatar-picker-section{grid-column:1/-1;font-size:.66rem;font-weight:700;text-transform:uppercase;letter-spacing:.06em;margin:.6rem .2rem .15rem;padding-bottom:.2rem;border-bottom:1px solid rgba(0,0,0,.08)}
.avatar-picker-section:first-child{margin-top:0}
.avatar-opt{cursor:pointer;display:flex;flex-direction:column;align-items:center;text-align:center;gap:.25rem;transition:transform .15s}
.avatar-opt > img{width:100%;aspect-ratio:1;border-radius:50%;object-fit:cover;border:2.5px solid transparent;background:#dae8ec;display:block}
.avatar-opt:hover{transform:scale(1.06)}
.avatar-opt:hover > img{border-color:#9bbac0}
.avatar-opt.active > img{border-color:#4a7c8a;box-shadow:0 0 0 2px #4a7c8a}
.avatar-opt-name{font-size:.58rem;line-height:1.2;color:#4a4a4a;font-weight:600;hyphens:auto;word-break:break-word}
.avatar-opt.active .avatar-opt-name{color:#1f4b37}
.field{margin-bottom:.8rem}
.field label{display:block;font-size:.68rem;font-weight:600;color:#4a4a4a;margin-bottom:.15rem}
.field input{width:100%;padding:.45rem .65rem;border:1.5px solid rgba(0,0,0,.1);border-radius:7px;font-size:.8rem;font-family:inherit;outline:none;transition:border .15s}
.field input:focus{border-color:#4a7c8a}
.field input:disabled{background:#f7f6f3;color:#8a8a8a}
.btn{padding:.45rem .9rem;border:none;border-radius:7px;background:#4a7c8a;color:#fff;font-size:.75rem;font-weight:700;cursor:pointer;transition:background .15s}
.btn:hover{background:#3a6470}
.btn-danger{background:#c07a6b}
.btn-danger:hover{background:#a05a4b}
.msg{font-size:.72rem;padding:.35rem .6rem;border-radius:6px;margin-top:.5rem;display:none}
.msg.ok{display:block;background:#dceadd;color:#5a8a5e}
.msg.err{display:block;background:#f2ddd8;color:#c07a6b}
.info-row{display:flex;justify-content:space-between;padding:.3rem 0;border-bottom:1px solid rgba(0,0,0,.03);font-size:.78rem}
.info-row:last-child{border:none}
.info-label{color:#8a8a8a;font-weight:500}
.info-val{font-weight:600}
</style>
</head>
<body>
<div class="topbar">
<a href="schueler.html" id="logo-link" aria-label="Cockpit"><img src="assets/img/geograsim_t.svg" alt="GeoGraSim" class="logo"></a>
<span class="spacer"></span>
<a href="profil.html" class="user-pill" id="user-pill"><span class="emoji" id="user-emoji">🧑‍🎓</span><span id="user-name"></span></a>
<button class="btn-sm" onclick="logout()">Abmelden</button>
</div>
<div class="wrap" id="content">
<!-- Tab-Navigation für Schüler*innen — Module / Erfolge / Profil. -->
<nav class="tabs hidden" id="student-tabs" aria-label="Schüler-Navigation">
<a class="tab" href="schueler.html" id="tab-link-modules">Meine Module</a>
<a class="tab" href="schueler.html#achievements" id="tab-link-achievements">Meine Erfolge</a>
<a class="tab on" href="profil.html" id="tab-link-profile">Profil</a>
</nav>
<!-- Tab-Navigation für Lehrkräfte — spiegelt das Cockpit-Menü. Klick auf
Cockpit-Tab landet zurück in teacher.html mit Hash → richtiger Tab aktiv. -->
<nav class="tabs hidden" id="teacher-tabs" aria-label="Lehrer-Navigation">
<a class="tab" href="teacher.html">Übersicht</a>
<a class="tab" href="teacher.html#modules">Modulfreigabe</a>
<a class="tab" href="teacher.html#students">Klasse</a>
<a class="tab" href="teacher.html#live">Live</a>
<a class="tab" href="teacher.html#results">Ergebnisse</a>
<a class="tab" href="teacher.html#licenses">Lizenzen</a>
<a class="tab on" href="profil.html">Profil</a>
</nav>
<div id="profile-content">
<div style="text-align:center;padding:3rem;color:#8a8a8a">Laden...</div>
</div>
</div>
<script>
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'; } }
// Bild-Avatare im Plattform-Bildstil (1024×1024 PNG, kreisrund maskiert).
// Slugs entsprechen Datei-Namen: avatar-<slug>.png unter assets/img/avatars/.
// Pro Avatar: name = "Vorname/Bezeichnung — Rolle". Picker zeigt das als
// Tooltip + Label. Bilder, die noch nicht generiert sind, fallen per onerror
// auf einen farbigen Initial-Kreis im Bildstil zurück.
var AVATAR_GROUPS = [
{ name: 'In der Geo-Welt', color: '#1f4b37', items: [
{slug:'pilotin', name:'Yara, Pilotin', emoji:'👩‍✈️'},
{slug:'pilot', name:'Liu, Pilot', emoji:'👨‍✈️'},
{slug:'kartografin', name:'Annika, Kartografin', emoji:'🗺️'},
{slug:'geologe', name:'Marco, Geologe', emoji:'⛏️'},
{slug:'meteorologin', name:'Aïcha, Meteorologin', emoji:'⛅'},
{slug:'stadtplaner', name:'Henrik, Stadtplaner', emoji:'🏙️'},
{slug:'forscherin', name:'Mira, Feldforscherin', emoji:'🔬'},
{slug:'forscher', name:'David, Forscher', emoji:'🧑‍🔬'},
{slug:'taucherin', name:'Naomi, Taucherin', emoji:'🤿'},
{slug:'astronaut', name:'Carlos, Astronaut', emoji:'👨‍🚀'},
{slug:'ozeanographin', name:'Linnea, Ozeanographin', emoji:'🌊'},
{slug:'tropenbiologe', name:'Kwame, Tropenbiologe', emoji:'🦋'},
]},
{ name: 'Cool & lustig', color: '#e8a247', items: [
{slug:'coole-girl', name:'Zoe, cool', emoji:'😎'},
{slug:'coole-boy', name:'Mateo, cool', emoji:'🕶️'},
{slug:'lustige-girl', name:'Lina, lustig', emoji:'🤪'},
{slug:'lustige-boy', name:'Felix, lustig', emoji:'😜'},
{slug:'nerd-girl', name:'Sophie, Nerd', emoji:'🤓'},
{slug:'sportlich-boy', name:'Diego, sportlich', emoji:'⚽'},
{slug:'kuenstlerin', name:'Mia, Künstlerin', emoji:'🎨'},
{slug:'musiker', name:'Jamal, Musiker', emoji:'🎸'},
{slug:'gaertnerin', name:'Hanna, Gärtnerin', emoji:'🌱'},
{slug:'abenteurer', name:'Oskar, Abenteurer', emoji:'🎒'},
{slug:'skaterin', name:'Reni, Skaterin', emoji:'🛹'},
{slug:'dj', name:'Kofi, DJ', emoji:'🎧'},
]},
{ name: 'Tier-Begleiter', color: '#4a7c4e', items: [
{slug:'fuchs-explorer', name:'Rufus, Fuchs-Explorer', emoji:'🦊'},
{slug:'eule-prof', name:'Hedwig, Eulen-Professorin', emoji:'🦉'},
{slug:'baer-foerster', name:'Bruno, Bären-Förster', emoji:'🐻'},
{slug:'pinguin-polar', name:'Pingu, Polar-Pinguin', emoji:'🐧'},
{slug:'affe-tropen', name:'Coco, Tropen-Affe', emoji:'🐵'},
{slug:'delfin-meer', name:'Delphi, Meeres-Delfin', emoji:'🐬'},
{slug:'adler-pilot', name:'Aron, Adler-Pilot', emoji:'🦅'},
{slug:'schildkroete-w', name:'Schildi, Weltreise-Schildkröte', emoji:'🐢'},
{slug:'katze-stadt', name:'Minka, Stadt-Katze', emoji:'🐱'},
{slug:'biene-natur', name:'Bea, Natur-Biene', emoji:'🐝'},
{slug:'wolf-tundra', name:'Yuki, Tundra-Wolf', emoji:'🐺'},
{slug:'krake-tiefsee', name:'Otto, Tiefsee-Krake', emoji:'🐙'},
]},
{ name: 'Aliens erforschen die Welt', color: '#7c3aed', items: [
{slug:'alien-geologin', name:'Zorblax, Stein-Forscherin von Geo-7', emoji:'👽'},
{slug:'alien-ozean', name:'Aquana, Ozean-Spähterin von Aqualon', emoji:'🛸'},
{slug:'alien-wetter', name:'Tempest, Wetter-Beobachter von Nimbus', emoji:'⛈️'},
{slug:'alien-stadt', name:'Urbanix, Stadt-Analyst von Metropolis-9', emoji:'🌃'},
{slug:'alien-tiere', name:'Faunor, Tier-Studienleiter von Zoonia', emoji:'🐾'},
{slug:'alien-pflanzen', name:'Floranth, Pflanzen-Diplomat von Verdantis', emoji:'🌿'},
{slug:'alien-vulkan', name:'Magmara, Vulkan-Sondiererin von Pyrox', emoji:'🌋'},
{slug:'alien-arktis', name:'Glacium, Eis-Späher von Kryonia', emoji:'🧊'},
{slug:'alien-wueste', name:'Solara, Wüsten-Forscherin von Helios', emoji:'🏜️'},
{slug:'alien-regenwald', name:'Veridia, Regenwald-Späherin von Chlora', emoji:'🌳'},
{slug:'alien-berg', name:'Altimos, Berg-Vermesser von Alpinis', emoji:'🏔️'},
{slug:'alien-meer', name:'Marinus, Meeres-Erkunder von Pelagos', emoji:'🌊'},
]},
{ name: 'Berühmte Forschungsmaschinen', color: '#0d47a1', items: [
{slug:'bathyscaph-trieste', name:'Trieste — Tauchboot, 1960 Marianengraben (Piccard/Walsh)', emoji:'🛥️'},
{slug:'zeppelin-graf', name:'Graf Zeppelin — Welt-Umrundung 1929', emoji:'🎈'},
{slug:'ballon-charliere', name:'Charlière — erster Wasserstoff-Ballon 1783', emoji:'🎈'},
{slug:'polar-fram', name:'Fram — Nansens Polar-Schiff 1893', emoji:'⛵'},
{slug:'dampfschiff-beagle', name:'HMS Beagle — Darwins Forschungsschiff 1831', emoji:'🚢'},
{slug:'tauchglocke-halley', name:'Halleysche Tauchglocke — 1690 erster Mensch unter Wasser', emoji:'🛎️'},
{slug:'sonde-voyager', name:'Voyager 1 — fliegt seit 1977 ins All', emoji:'🛰️'},
{slug:'eisbohrer-vostok', name:'Vostok-Eisbohrkern — 420.000 Jahre Klima', emoji:'🧊'},
{slug:'rover-curiosity', name:'Curiosity — NASA Mars-Rover seit 2012', emoji:'🤖'},
{slug:'satellit-landsat', name:'Landsat — Erdbeobachtung seit 1972', emoji:'🛰️'},
{slug:'forschungsschiff-meteor', name:'FS Meteor — deutsches Forschungsschiff', emoji:'⚓'},
{slug:'wettersatellit-meteosat', name:'Meteosat — Wettersatellit über Europa', emoji:'📡'},
]},
{ name: 'Berühmte Forscherinnen und Forscher', color: '#8d6e63', items: [
{slug:'humboldt-alexander', name:'Alexander von Humboldt (17691859) — Universalgelehrter, Südamerika', emoji:'🌿'},
{slug:'mercator-gerhard', name:'Gerhard Mercator (15121594) — Kartograph, Welt-Karte', emoji:'🗺️'},
{slug:'cook-james', name:'James Cook (17281779) — Pazifik-Entdecker', emoji:'⚓'},
{slug:'polo-marco', name:'Marco Polo (12541324) — Reise nach China', emoji:'🐫'},
{slug:'ibn-battuta', name:'Ibn Battuta (13041369) — 120.000 km durch die Welt', emoji:'🕌'},
{slug:'nansen-fridtjof', name:'Fridtjof Nansen (18611930) — Polarforscher, Nobelpreis', emoji:'🧭'},
{slug:'piccard-auguste', name:'Auguste Piccard (18841962) — Stratosphäre + Tiefsee', emoji:'🎈'},
{slug:'amundsen-roald', name:'Roald Amundsen (18721928) — erster am Südpol 1911', emoji:'🐧'},
{slug:'earhart-amelia', name:'Amelia Earhart (18971937) — Pilotin, Atlantik-Überquerung', emoji:'✈️'},
{slug:'earle-sylvia', name:'Sylvia Earle (*1935) — Meeresbiologin, „Her Deepness"', emoji:'🐠'},
{slug:'anning-mary', name:'Mary Anning (17991847) — Paläontologin, erste Saurierfunde', emoji:'🦕'},
{slug:'carson-rachel', name:'Rachel Carson (19071964) — Umweltschutz-Pionierin', emoji:'🌳'},
]},
];
// Slug → Emoji-Lookup (fuer Fallback-Rendering)
var AVATAR_EMOJIS = {};
AVATAR_GROUPS.forEach(function(g){
(g.items || []).forEach(function(it){ if (it.emoji) AVATAR_EMOJIS[it.slug] = it.emoji; });
});
// Flach-Liste aller Slugs (für Validierung/Ausgangsdaten)
var ALL_AVATAR_SLUGS = AVATAR_GROUPS.reduce(function(a,g){
return a.concat((g.items || g.slugs || []).map(function(it){ return typeof it === 'string' ? it : it.slug; }));
}, []);
// Slug → Name (für Tooltip-Lookup beim aktuellen Avatar)
var AVATAR_NAMES = {};
AVATAR_GROUPS.forEach(function(g){
(g.items || []).forEach(function(it){ AVATAR_NAMES[it.slug] = it.name; });
});
// Stil-konformer Fallback wenn das PNG (noch) nicht existiert — Emoji im
// Farbkreis statt nur Initial-Buchstabe (deutlich erkennbarer).
// AVATAR_EMOJIS wird unten beim Erstellen der Gruppen befuellt.
function makeAvatarFallback(slug, color) {
var emoji = (typeof AVATAR_EMOJIS !== 'undefined' && AVATAR_EMOJIS[slug]) || '';
if (!emoji) {
// Fallback im Fallback: Initial-Buchstabe (für unbekannte Slugs).
emoji = (slug || '?').replace(/^(alien|avatar)-?/,'').replace(/[-_].*/,'').charAt(0).toUpperCase() || '?';
}
var svg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">'
+ '<rect width="100" height="100" fill="' + (color||'#4a7c8a') + '"/>'
+ '<text x="50" y="68" font-size="56" text-anchor="middle" '
+ 'dominant-baseline="middle">' + emoji + '</text></svg>';
return 'data:image/svg+xml;utf8,' + encodeURIComponent(svg);
}
// Klassische Emojis als Fallback (Lehrkräfte sehen sie am Anfang noch).
var EMOJI_OPTIONS = ['🧑‍🎓','👩‍🎓','👨‍🎓','🧑‍🏫','👩‍🏫','👨‍🏫','🦊','🐱','🐶','🐻','🐼','🦁','🐧','🦉','🌍','🚀'];
function escHtml(s) { return String(s==null?'':s).replace(/[&<>"]/g, function(c){ return {'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[c]; }); }
function escAttr(s) { return escHtml(s).replace(/'/g, '&#39;'); }
// Thumb (256x256 webp, ~10 KB) ist die Standard-URL fuer Listen/Picker/Pille.
// Fuer die grosse Profil-Vorschau gibt's avatarFullUrl (1024 PNG).
function avatarThumbUrl(slug) { return BASE + '/assets/img/avatars/thumbs/avatar-' + slug + '.webp'; }
function avatarFullUrl(slug) { return BASE + '/assets/img/avatars/avatar-' + slug + '.png'; }
function avatarUrl(slug) { return avatarThumbUrl(slug); } // alias for backward compat
function isAvatarSlug(v) { return typeof v === 'string' && v.indexOf('avatar:') === 0; }
function avatarValue(slug) { return 'avatar:' + slug; }
function avatarSlugFromValue(v) { return isAvatarSlug(v) ? v.substring(7) : null; }
// Welche Gruppe enthält den Slug? (für Fallback-Farbe)
function avatarColorForSlug(slug) {
for (var i = 0; i < AVATAR_GROUPS.length; i++) {
var items = AVATAR_GROUPS[i].items || [];
for (var j = 0; j < items.length; j++) if (items[j].slug === slug) return AVATAR_GROUPS[i].color || '#4a7c8a';
}
return '#4a7c8a';
}
function renderAvatar(value, fallback) {
var slug = avatarSlugFromValue(value);
if (slug) {
var fb = makeAvatarFallback(slug, avatarColorForSlug(slug));
return '<img src="' + avatarUrl(slug) + '" alt="" onerror="this.onerror=null;this.src=\'' + fb + '\'">';
}
return value || fallback || '🧑‍🎓';
}
async function api(endpoint, data) {
var opts = data !== undefined
? {method:'POST',headers:{'Content-Type':'application/json'},credentials:'same-origin',body:JSON.stringify(data)}
: {credentials:'same-origin'};
var r = await fetch(BASE + '/api/' + endpoint, opts);
return await r.json();
}
function logout() {
api('auth', {action:'logout'}).then(function() { location.href = 'index.html'; });
}
async function loadProfile() {
var r = await api('profile');
if (r.role === 'guest' || !r.profile) {
location.href = 'login.html';
return;
}
// Logo → Cockpit (rollen-abhängig)
document.getElementById('logo-link').href = (r.role === 'student') ? 'schueler.html' : 'teacher.html';
// Rollen-spezifische Tab-Bar einblenden — analog zur jeweiligen Cockpit-Navi.
if (r.role === 'student') {
document.getElementById('student-tabs').classList.remove('hidden');
} else if (r.role === 'teacher') {
document.getElementById('teacher-tabs').classList.remove('hidden');
}
// User-Pill: Name + Avatar (gleiche Darstellung wie auf dem Cockpit)
document.getElementById('user-name').textContent = r.profile.display_name || r.profile.username || (r.role === 'student' ? 'Schüler*in' : 'Lehrperson');
var avatarVal = r.profile.emoji_avatar || (r.role === 'student' ? '🧑‍🎓' : '👩‍🏫');
var emojiEl = document.getElementById('user-emoji');
var slug = avatarSlugFromValue(avatarVal);
if (slug) {
var fb = makeAvatarFallback(slug, avatarColorForSlug(slug));
emojiEl.innerHTML = '<img src="' + avatarUrl(slug) + '" alt="" onerror="this.onerror=null;this.src=\'' + fb + '\'">';
} else {
emojiEl.innerHTML = '';
emojiEl.textContent = avatarVal;
}
if (r.role === 'student') {
renderStudentProfile(r.profile);
} else if (r.role === 'teacher') {
renderTeacherProfile(r.profile);
}
}
function renderStudentProfile(p) {
var html = '';
var current = p.emoji_avatar || '🧑‍🎓';
// Avatar + Name
html += '<div class="card"><div class="avatar-section">';
html += '<div class="avatar-current" id="avatar-btn" onclick="togglePicker()">' + renderAvatar(current, '🧑‍🎓') + '</div>';
html += '<div class="avatar-name">' + (p.display_name || p.username) + '</div>';
html += '<div class="avatar-role">Klasse ' + (p.class_name || '') + '</div>';
html += '<div class="avatar-picker" id="avatar-picker">';
AVATAR_GROUPS.forEach(function(g) {
html += '<div class="avatar-picker-section" style="color:' + (g.color||'#8a8a8a') + '">' + g.name + '</div>';
(g.items || []).forEach(function(it) {
var slug = it.slug, name = it.name || slug;
var val = avatarValue(slug);
var cls = val === current ? ' active' : '';
var fb = makeAvatarFallback(slug, g.color || '#4a7c8a');
html += '<div class="avatar-opt' + cls + '" data-val="' + val + '" title="' + escAttr(name) + '" onclick="selectAvatar(\'' + val + '\')">'
+ '<img src="' + avatarUrl(slug) + '" alt="' + escAttr(name) + '" onerror="this.onerror=null;this.src=\'' + fb + '\'">'
+ '<div class="avatar-opt-name">' + escHtml(name) + '</div>'
+ '</div>';
});
});
html += '</div></div></div>';
// Info
html += '<div class="card"><div class="card-h">Kontoinformationen</div>';
html += '<div class="info-row"><span class="info-label">Anzeigename</span><span class="info-val">' + (p.display_name || p.username) + '</span></div>';
html += '<div class="info-row"><span class="info-label">Benutzername</span><span class="info-val">' + p.username + '</span></div>';
html += '<div class="info-row"><span class="info-label">Klasse</span><span class="info-val">' + (p.class_name || '—') + '</span></div>';
html += '<div class="info-row"><span class="info-label">Klassencode</span><span class="info-val">' + (p.join_code || '—') + '</span></div>';
html += '<div class="info-row"><span class="info-label">Dabei seit</span><span class="info-val">' + new Date(p.created_at).toLocaleDateString('de-AT') + '</span></div>';
html += '<p style="font-size:.65rem;color:#8a8a8a;margin-top:.6rem">Anzeigename und Klasse vergibt die Lehrperson. Du kannst dein Profilbild ändern.</p>';
html += '</div>';
// === Meine Reflexionen — wird gleich nachgeladen ===
html += '<div class="card" id="student-reflections-card">'
+ '<div class="card-h">💭 Meine Reflexionen</div>'
+ '<p style="font-size:.78rem;color:#8a8a8a;margin-bottom:.6rem">Hier siehst du, was du am Ende deiner Durchgänge in den Simulationen notiert hast — deine eigenen Gedanken zu jedem Abschluss.</p>'
+ '<div id="student-reflections-list">Lade …</div>'
+ '</div>';
document.getElementById('profile-content').innerHTML = html;
_loadStudentReflections();
}
async function _loadStudentReflections() {
try {
var data = await api('profile?view=reflections');
var host = document.getElementById('student-reflections-list');
if (!host) return;
if (data.error) { host.innerHTML = '<p style="color:#c07a6b;font-size:.82rem">' + escHtml(data.error) + '</p>'; return; }
var items = data.items || [];
if (!items.length) {
host.innerHTML = '<p style="font-size:.82rem;color:#8a8a8a;font-style:italic">Du hast noch keine Reflexionen geschrieben. Am Ende eines Durchgangs wirst du gefragt, was dir am besten gefallen hat oder am schwierigsten war — deine Antwort erscheint dann hier.</p>';
return;
}
var html = '<div style="display:flex;flex-direction:column;gap:.6rem">';
items.forEach(function(it) {
var when = it.recordedAt ? new Date(it.recordedAt).toLocaleDateString('de-AT', {day:'2-digit',month:'2-digit',year:'numeric'}) : '';
html += '<div style="background:#fbfaf6;border:1px solid #e2ddd1;border-left:4px solid #c9913a;border-radius:8px;padding:.7rem .9rem">'
+ '<div style="display:flex;justify-content:space-between;font-size:.7rem;color:#8a8a8a;margin-bottom:.3rem">'
+ '<span><span style="background:#f0ede5;padding:.06rem .4rem;border-radius:99px;font-weight:600;color:#3a3a3a">' + escHtml(it.simId) + '</span>'
+ (it.level ? ' · Stufe ' + escHtml(it.level) : '') + '</span>'
+ '<span>' + escHtml(when) + '</span>'
+ '</div>'
+ '<div style="font-size:.78rem;color:#6a6a6a;font-style:italic;margin-bottom:.3rem">' + escHtml(it.question || '—') + '</div>'
+ '<div style="font-size:.95rem;color:#2a2a2a;line-height:1.45">' + escHtml(it.answer || '—') + '</div>'
+ '</div>';
});
html += '</div>';
if (items.length >= 50) {
html += '<p style="font-size:.7rem;color:#8a8a8a;margin-top:.5rem">Letzte 50 Reflexionen — älterere sind im Lehrer-Archiv.</p>';
}
host.innerHTML = html;
} catch (e) {
var host = document.getElementById('student-reflections-list');
if (host) host.innerHTML = '<p style="color:#c07a6b;font-size:.82rem">Reflexionen konnten nicht geladen werden.</p>';
}
}
function renderTeacherProfile(p) {
var html = '';
var current = p.emoji_avatar || '👩‍🏫';
html += '<div class="card"><div class="avatar-section">';
html += '<div class="avatar-current" id="avatar-btn" onclick="togglePicker()">' + renderAvatar(current, '👩‍🏫') + '</div>';
html += '<div class="avatar-name">' + (p.display_name || '') + '</div>';
html += '<div class="avatar-role">Lehrperson' + (p.school_name ? ' · ' + p.school_name : '') + '</div>';
html += '<div class="avatar-picker" id="avatar-picker">';
AVATAR_GROUPS.forEach(function(g) {
html += '<div class="avatar-picker-section" style="color:' + (g.color||'#8a8a8a') + '">' + g.name + '</div>';
(g.items || []).forEach(function(it) {
var slug = it.slug, name = it.name || slug;
var val = avatarValue(slug);
var cls = val === current ? ' active' : '';
var fb = makeAvatarFallback(slug, g.color || '#4a7c8a');
html += '<div class="avatar-opt' + cls + '" data-val="' + val + '" title="' + escAttr(name) + '" onclick="selectAvatar(\'' + val + '\')">'
+ '<img src="' + avatarUrl(slug) + '" alt="' + escAttr(name) + '" onerror="this.onerror=null;this.src=\'' + fb + '\'">'
+ '<div class="avatar-opt-name">' + escHtml(name) + '</div>'
+ '</div>';
});
});
html += '<div class="avatar-picker-section">Klassische Symbole</div>';
EMOJI_OPTIONS.forEach(function(e) {
var cls = e === current ? ' active' : '';
html += '<div class="avatar-opt' + cls + '" data-val="' + e + '" onclick="selectAvatar(\'' + e + '\')">' + e + '</div>';
});
html += '</div></div></div>';
html += '<div class="card"><div class="card-h">Kontoinformationen</div>';
html += '<div class="info-row"><span class="info-label">E-Mail</span><span class="info-val">' + (p.email || '—') + '</span></div>';
html += '<div class="info-row"><span class="info-label">Schule</span><span class="info-val">' + (p.school_name || '—') + '</span></div>';
html += '<div class="info-row"><span class="info-label">Dabei seit</span><span class="info-val">' + new Date(p.created_at).toLocaleDateString('de-AT') + '</span></div>';
html += '</div>';
html += '<div class="card"><div class="card-h">Profil bearbeiten</div>';
html += '<div class="field"><label>Anzeigename</label><input type="text" id="pf-name" value="' + (p.display_name || '') + '"></div>';
html += '<div class="field"><label>E-Mail</label><input type="email" id="pf-email" value="' + (p.email || '') + '"></div>';
html += '<div class="field"><label>Schule</label><input type="text" id="pf-school" value="' + (p.school_name || '') + '"></div>';
html += '<div class="field"><label>Land (bestimmt die „lokale" Herkunft der Zutaten in Weltküche)</label><select id="pf-country">' +
[['AT','🇦🇹 Österreich'],['DE','🇩🇪 Deutschland'],['CH','🇨🇭 Schweiz'],['LI','🇱🇮 Liechtenstein']]
.map(function(o){ return '<option value="'+o[0]+'"'+(((p.country||'AT').toUpperCase())===o[0]?' selected':'')+'>'+o[1]+'</option>'; }).join('') +
'</select></div>';
html += '<button class="btn" onclick="saveTeacher()">Speichern</button>';
html += '<div class="msg" id="msg-teacher"></div>';
html += '</div>';
html += '<div class="card"><div class="card-h">Passwort ändern</div>';
html += '<div class="field"><label>Altes Passwort</label><input type="password" id="pf-oldpw"></div>';
html += '<div class="field"><label>Neues Passwort (mind. 8 Zeichen)</label><input type="password" id="pf-newpw"></div>';
html += '<button class="btn" onclick="changePw()">Passwort ändern</button>';
html += '<div class="msg" id="msg-pw"></div>';
html += '</div>';
html += '<div class="card">';
html += '<a href="teacher.html" class="btn" style="text-decoration:none;display:inline-block">Zum Dashboard</a>';
html += '</div>';
html += '<div class="card" style="border-color:#f2ddd8">';
html += '<div class="card-h" style="color:#c07a6b">Konto löschen</div>';
html += '<p style="font-size:.7rem;color:#8a8a8a;margin-bottom:.5rem">Ihr Konto wird deaktiviert. Ihre Klassen bleiben erhalten, Schüler*innen werden zu Autodidakten.</p>';
html += '<button class="btn" style="background:#c07a6b" onclick="deleteAccount()">Konto endgültig löschen</button>';
html += '</div>';
document.getElementById('profile-content').innerHTML = html;
}
function togglePicker() {
document.getElementById('avatar-picker').classList.toggle('open');
}
async function selectAvatar(value) {
// value kann 'avatar:<slug>' ODER ein Emoji-Zeichen sein.
// textContent würde 'avatar:eule-prof' wörtlich als Text rendern → riesige
// Buchstaben im Kreis. Stattdessen renderAvatar → IMG oder Emoji-Text.
document.getElementById('avatar-btn').innerHTML = renderAvatar(value);
document.querySelectorAll('.avatar-opt').forEach(function(o) {
o.classList.toggle('active', o.dataset.val === value);
});
// User-Pille oben rechts SOFORT aktualisieren — keine Reload-Pflicht.
// Beide Selektoren probieren, weil schueler.html '#user-pill .emoji' nutzt
// und profil.html ein '#user-emoji' direkt vergibt. Cache-Buster via ?v=
// erzwingt Re-Render, falls der Browser das Pillen-Bild gecached hat.
var pillEmojis = [
document.getElementById('user-emoji'),
document.querySelector('#user-pill .emoji'),
document.querySelector('.user-pill .emoji'),
].filter(function(el, i, arr) { return el && arr.indexOf(el) === i; });
var slug = avatarSlugFromValue(value);
var cacheBuster = '?v=' + Date.now();
pillEmojis.forEach(function(pillEmoji) {
if (slug) {
var fb = makeAvatarFallback(slug, avatarColorForSlug(slug));
pillEmoji.innerHTML = '<img src="' + avatarThumbUrl(slug) + cacheBuster + '" alt="" onerror="this.onerror=null;this.src=\'' + fb + '\'">';
} else {
pillEmoji.innerHTML = '';
pillEmoji.textContent = value;
}
});
await api('profile', {emojiAvatar: value});
}
async function saveName() {
var name = document.getElementById('pf-name').value.trim();
if (!name) return;
var r = await api('profile', {displayName: name});
var m = document.getElementById('msg-name');
m.className = r.error ? 'msg err' : 'msg ok';
m.textContent = r.error || 'Gespeichert!';
}
async function saveTeacher() {
var name = document.getElementById('pf-name').value.trim();
var email = document.getElementById('pf-email').value.trim();
var school = document.getElementById('pf-school').value.trim();
var country = (document.getElementById('pf-country') || {}).value || 'AT';
var r = await api('profile', {displayName: name, email: email, schoolName: school, country: country});
var m = document.getElementById('msg-teacher');
m.className = r.error ? 'msg err' : 'msg ok';
m.textContent = r.error || 'Gespeichert!';
}
async function changePw() {
var old = document.getElementById('pf-oldpw').value;
var pw = document.getElementById('pf-newpw').value;
if (!old || !pw) return;
var r = await api('profile', {oldPassword: old, newPassword: pw});
var m = document.getElementById('msg-pw');
m.className = r.error ? 'msg err' : 'msg ok';
m.textContent = r.error || 'Passwort geändert!';
if (!r.error) { document.getElementById('pf-oldpw').value = ''; document.getElementById('pf-newpw').value = ''; }
}
async function deleteAccount() {
if (!confirm('Sind Sie sicher? Ihr Konto wird deaktiviert. Diese Aktion kann nicht rückgängig gemacht werden.')) return;
if (!confirm('Wirklich löschen? Alle Ihre Klassen werden archiviert.')) return;
var r = await api('profile', {action: 'delete_account'});
if (r.error) { alert(r.error); return; }
alert('Konto wurde gelöscht.');
location.href = 'index.html';
}
loadProfile();
</script>
<script src="assets/js/version-tag.js" defer></script>
</body>
</html>