Atlas: Phase A Integrations-Konzept + Cockpit + End-Screen

- Integrations-Konzept dokumentiert (App/docs/integration-konzept.md)
- Module-IDs vereinheitlicht: sim-XX -> semantische IDs (klima/heli/fluss/logistik/...)
- class_modules um current_level + quiz_enabled Spalten erweitert
- modules.php liefert dynamisch aus module_info, inkl. forcedLevel, quizEnabled,
  play_url, dueDate; neuer Filter ?module_id=X fuer einzeln
- schueler.html Cockpit: komplett API-first, teacher_started-Sektion als
  Auftrags-Block, neue IDs, Sprachregel 4a (Starten statt Spielen)
- teacher.html: Hardcoded ALL_MODS entfernt, laedt dynamisch
- Design-System: .ggs-endscreen-Komponente fuer einheitliche Modul-Abschluesse
- index.html Landing-Links gefixt: App/modul-X -> modul-X, V1-HTMLs auf
  Front-Controller-Routen (fluss, heli-game, stadt)
- 6 Inbox-Auftraege an Instanzen (Klima/Heli/Fluss/Logistik/Lehrplan/Glossar)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-23 11:42:19 +02:00
parent b94658f57c
commit b356c79134
14 changed files with 1266 additions and 101 deletions
+58 -64
View File
@@ -94,16 +94,19 @@ if (!BASE) { var p = location.pathname, i = p.indexOf('/geograsim'); if (i !== -
var profile = null;
var modules = [];
var MODULE_MAP = {
'sim-05': {name:'Klimasimulator 2D', href:'game.html', img:'assets/img/card-climate.png', icon:'🌡️'},
'sim-05-3d': {name:'Klimasimulator 3D', href:'game-3d.html', img:'assets/img/card-climate.png', icon:'🌊'},
'sim-07': {name:'Erdbeben & Plattentektonik', href:'erdbeben.html', img:'assets/img/card-tectonic.png', icon:'🌋'},
'sim-09': {name:'Energiewende-Planer', href:'energiemix.html', img:'assets/img/card-energy.png', icon:'⚡'},
'sim-10': {name:'Lieferketten-Planer', href:'lieferketten.html', img:'assets/img/card-globe.png', icon:'👕'},
'sim-11': {name:'Regenwald-Expedition', href:'regenwald.html', img:'assets/img/card-climate.png', icon:'🌴'},
'sim-12': {name:'Flussmanagement', href:'fluss.html', img:'assets/img/card-fluss.png', icon:'🏞️'},
'sim-13': {name:'Stadt & Raumplanung', href:'stadt.html', img:'assets/img/card-city.png', icon:'🏗️'},
'sim-14': {name:'Helikopter-Navigation', href:'heli-game', img:'assets/img/card-heli.png', icon:'🚁'},
// Module kommen aus /api/modules?student=1 (inkl. play_url, icon, name, mode).
// card_image + Fallback-Beschreibung ergaenzen wir clientseitig.
var CARD_IMAGES = {
'klima': 'assets/img/card-climate.png',
'klima-3d': 'assets/img/card-climate.png',
'fluss': 'assets/img/card-fluss.png',
'heli': 'assets/img/card-heli.png',
'stadt': 'assets/img/card-city.png',
'erdbeben': 'assets/img/card-tectonic.png',
'energiemix': 'assets/img/card-energy.png',
'lieferketten': 'assets/img/card-globe.png',
'regenwald': 'assets/img/card-climate.png',
'logistik': 'assets/img/card-globe.png'
};
var BADGES = [
@@ -153,85 +156,76 @@ async function init() {
renderProfile();
}
function buildModuleUrl(mod) {
// Bevorzugt play_url aus module_info; Fallback: module_id
var base = mod.play_url || mod.id;
// Relativ zum aktuellen Front-Controller-Pfad — Apache-Rewrite macht den Rest
var url = base.indexOf('/') === 0 ? base : base;
if (mod.mode === 'teacher_started' && mod.forcedLevel) {
url += (url.indexOf('?') === -1 ? '?' : '&') + 'level=' + mod.forcedLevel;
}
return url;
}
async function renderModules() {
var html = '';
// Aktive Aufgaben der Klasse laden
var modList = [];
try {
var assignments = await api('assignments?student=1');
if (!assignments.error && assignments.length > 0) {
html += '<div style="margin-bottom:1rem">';
assignments.forEach(function(a) {
var m = MODULE_MAP[a.module_id];
if (!m) return;
var end = a.ends_at ? '<br><span style="font-size:.6rem;color:#8a8a8a">Zu erledigen bis ' + new Date(a.ends_at).toLocaleDateString('de-AT',{day:'2-digit',month:'2-digit',year:'numeric',hour:'2-digit',minute:'2-digit'}) + '</span>' : '';
html += '<a href="'+m.href+'" class="mod-card" style="border-color:#e8833a;border-width:2px;margin-bottom:.5rem">';
html += '<div class="mod-img" style="height:80px;background:linear-gradient(135deg,#e8833a,#f4a261)"><span style="font-size:2.5rem">'+m.icon+'</span></div>';
html += '<div class="mod-body">';
html += '<h3 style="color:#e8833a">📋 '+m.icon+' '+m.name+' — Level '+a.level+'</h3>';
html += '<p>Aufgabe von deiner Lehrperson'+end+'</p>';
html += '<span class="mod-status" style="background:#fde8d0;color:#e8833a">▶ Jetzt starten</span>';
html += '</div></a>';
});
html += '</div>';
}
modList = await api('modules?student=1');
if (modList.error) modList = [];
} catch(e) {}
// Modul-Status laden
var modStatus = {};
try {
var mods = await api('modules?student=1');
if (!mods.error) mods.forEach(function(m) { modStatus[m.id] = m.mode; });
} catch(e) {}
// Lehrer-gesteuerte Auftraege zuerst
var teacherStarted = modList.filter(function(m) { return m.mode === 'teacher_started'; });
if (teacherStarted.length > 0) {
html += '<div style="margin-bottom:1rem">';
html += '<p style="font-size:.78rem;font-weight:700;color:#e8833a;margin-bottom:.4rem">📋 Auftraege von deiner Lehrperson</p>';
teacherStarted.forEach(function(m) {
var due = m.dueDate ? '<br><span style="font-size:.6rem;color:#8a8a8a">Zu erledigen bis ' + new Date(m.dueDate).toLocaleDateString('de-AT',{day:'2-digit',month:'2-digit',year:'numeric',hour:'2-digit',minute:'2-digit'}) + '</span>' : '';
var quiz = m.quizEnabled ? '<br><span style="font-size:.6rem;color:#4a7c8a">📝 Mit kurzem Quiz</span>' : '';
var url = buildModuleUrl(m);
html += '<a href="'+url+'" class="mod-card" style="border-color:#e8833a;border-width:2px;margin-bottom:.5rem">';
html += '<div class="mod-img" style="height:80px;background:linear-gradient(135deg,#e8833a,#f4a261)"><span style="font-size:2.5rem">'+(m.icon||'📚')+'</span></div>';
html += '<div class="mod-body">';
html += '<h3 style="color:#e8833a">'+m.icon+' '+m.name+(m.forcedLevel ? ' — Stufe '+m.forcedLevel : '')+'</h3>';
html += '<p>Auftrag von deiner Lehrperson'+due+quiz+'</p>';
html += '<span class="mod-status" style="background:#fde8d0;color:#e8833a">▶ Jetzt starten</span>';
html += '</div></a>';
});
html += '</div>';
}
// Alle Module
// Regulaere Module (free + locked)
html += '<div class="mod-grid">';
var entries = Object.entries(MODULE_MAP);
entries.forEach(function(e) {
var id = e[0], m = e[1];
var mode = modStatus[id] || 'free'; // Default: frei wenn keine Klasse
var isLocked = mode === 'locked';
var imgHtml = m.img
? '<img src="'+m.img+'" alt="">'
: '<span style="font-size:3rem">'+m.icon+'</span>';
modList.filter(function(m) { return m.mode !== 'teacher_started'; }).forEach(function(m) {
var isLocked = m.mode === 'locked';
var cardImg = CARD_IMAGES[m.id];
var imgHtml = cardImg ? '<img src="'+cardImg+'" alt="">' : '<span style="font-size:3rem">'+(m.icon||'📚')+'</span>';
var url = buildModuleUrl(m);
if (isLocked) {
html += '<div class="mod-card locked">';
html += '<div class="mod-img" style="position:relative">'+imgHtml+'<div class="lock-icon">🔒</div></div>';
} else {
html += '<a href="'+m.href+'" class="mod-card">';
html += '<a href="'+url+'" class="mod-card">';
html += '<div class="mod-img">'+imgHtml+'</div>';
}
html += '<div class="mod-body">';
html += '<h3>'+m.icon+' '+m.name+'</h3>';
html += '<p>'+getModuleDesc(id)+'</p>';
html += '<h3>'+(m.icon||'📚')+' '+m.name+'</h3>';
html += '<p>'+(m.desc||'')+'</p>';
if (isLocked) {
html += '<span class="mod-status locked-s">🔒 Gesperrt</span>';
html += '</div></div>';
} else {
html += '<span class="mod-status available">▶ Spielen</span>';
html += '<span class="mod-status available">▶ Starten</span>';
html += '</div></a>';
}
// Level-Badge (wird asynchron gefüllt)
// Wird nach dem Rendern per API geladen
});
html += '</div>';
document.getElementById('tab-modules').innerHTML = html;
}
function getModuleDesc(id) {
var descs = {
'sim-05': 'Inselstaat durch die Klimakrise führen (2D-Ansicht)',
'sim-05-3d': 'Inselstaat durch die Klimakrise führen (3D)',
'sim-07': '50 Jahre Stadtplanung in einer Erdbebenzone',
'sim-09': 'Deine Region bis 2050 auf Erneuerbare umstellen',
'sim-10': 'Was kostet dein T-Shirt wirklich?',
'sim-11': 'Mit dem Boot den Fluss hoch — Tiere und Pflanzen entdecken',
'sim-12': 'Deiche, Renaturierung und Bewässerung managen',
'sim-13': 'Plane eine Stadt mit Wohngebieten und Infrastruktur',
'sim-14': 'Steuere einen Rettungshubschrauber durch Österreich',
};
return descs[id] || '';
}
// Modul-Beschreibungen kommen jetzt aus module_info.short_desc via API.
async function renderAchievements() {
// Lade echte Fortschrittsdaten