adb5646b5e
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
77 lines
3.9 KiB
JavaScript
77 lines
3.9 KiB
JavaScript
/* 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, '<').replace(/>/g, '>');
|
||
let byPath = {};
|
||
|
||
function renderStations(activeId) {
|
||
pathRoot.innerHTML = PATHS.map((p) => {
|
||
const games = byPath[p.id] || [];
|
||
return `<button type="button" class="dl-station${p.id === activeId ? ' active' : ''}" data-path="${p.id}">
|
||
<span class="dl-station-icon">${esc(p.marker)}</span>
|
||
<span><h3>${esc(p.title)}</h3><p>${esc(p.copy)}</p>
|
||
<span class="dl-station-meta"><span class="dl-chip">${games.length} Spiele</span><span class="dl-chip">${esc(p.type)}</span></span>
|
||
</span></button>`;
|
||
}).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) => `<span class="dl-chip">${esc(m)}</span>`).join('');
|
||
gameRoot.innerHTML = games.length ? games.map((g) => `
|
||
<article class="dl-game playable">
|
||
<div><h4>${esc(g.title)}</h4><p>${esc(g.description || '')}</p></div>
|
||
<div class="dl-game-actions">
|
||
<span class="dl-game-state">${g.taskCount} Aufgaben</span>
|
||
<a class="mf-btn" href="/devlab/games/${encodeURIComponent(g.slug)}">Starten</a>
|
||
</div>
|
||
</article>`).join('') : '<p class="dl-empty">Für diesen Pfad sind noch keine Spiele freigeschaltet.</p>';
|
||
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]));
|
||
})();
|