${esc(g.title)}
${esc(g.description || '')}
/* Dev Lab Hub – lädt den Spiele-Katalog aus /api/devlab/catalog (DB-gestützt) und gruppiert
die Spiele nach Trainingspfad. Spielkarten verlinken auf /devlab/games/{slug}. */
(function () {
const PATHS = [
{ id: 'syntax', marker: '{}', title: 'Syntax & Grundlagen', type: 'Grundlagenpfad',
copy: 'Sprachsyntax, Strukturen und Muster verstehen, bevor der Code komplex wird.',
mechanics: ['Lücken füllen', 'Zusammensetzen', 'Multiple Choice', 'Regex live'], badge: 'Syntax Core' },
{ id: 'debugging', marker: '>', title: 'Debugging & Problemlösung', type: 'Ermittlungspfad',
copy: 'Fehler finden, Ursache erkennen und den passenden Reparaturschritt wählen.',
mechanics: ['Bug finden', 'Lücken füllen', 'Befehlsfolgen', 'Storyfälle'], badge: 'Bug Hunter' },
{ id: 'visual', marker: '[]', title: 'Praxis & Visualisierung', type: 'Werkstattpfad',
copy: 'Direkt sehen, was Regeln, Layouts, APIs und Kaskaden im System bewirken.',
mechanics: ['Zuordnen', 'Multiple Choice', 'Statuscodes', 'Kaskade'], badge: 'Visual Builder' }
];
const pathRoot = document.getElementById('dl-stations');
const gameRoot = document.getElementById('dl-games');
const mechanicsRoot = document.getElementById('dl-mechanics');
const ui = {
title: document.getElementById('dl-world-title'), type: document.getElementById('dl-world-type'),
icon: document.getElementById('dl-world-icon'), copy: document.getElementById('dl-world-copy'),
levels: document.getElementById('dl-levels'), xp: document.getElementById('dl-xp'),
badge: document.getElementById('dl-badge'), gameCount: document.getElementById('dl-game-count')
};
const esc = (v) => String(v).replace(/&/g, '&').replace(//g, '>');
let byPath = {};
function renderStations(activeId) {
pathRoot.innerHTML = PATHS.map((p) => {
const games = byPath[p.id] || [];
return ``;
}).join('');
}
function renderPath(p) {
const games = byPath[p.id] || [];
const totalTasks = games.reduce((sum, g) => sum + (g.taskCount || 0), 0);
ui.title.textContent = p.title; ui.type.textContent = p.type; ui.icon.textContent = p.marker;
ui.copy.textContent = p.copy; ui.levels.textContent = games.length; ui.xp.textContent = totalTasks; ui.badge.textContent = p.badge;
ui.gameCount.textContent = `${games.length} Spiele`;
mechanicsRoot.innerHTML = p.mechanics.map((m) => `${esc(m)}`).join('');
gameRoot.innerHTML = games.length ? games.map((g) => `
${esc(g.description || '')}${esc(g.title)}
Für diesen Pfad sind noch keine Spiele freigeschaltet.
'; renderStations(p.id); } pathRoot.addEventListener('click', (e) => { const btn = e.target.closest('.dl-station'); if (!btn) return; const p = PATHS.find((x) => x.id === btn.dataset.path); if (p) renderPath(p); }); fetch('/api/devlab/catalog') .then((res) => res.json()) .then((data) => { byPath = {}; (data.games || []).forEach((g) => { const key = PATHS.some((p) => p.id === g.path) ? g.path : 'syntax'; (byPath[key] = byPath[key] || []).push(g); }); renderPath(PATHS[0]); }) .catch(() => renderPath(PATHS[0])); })();