This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
const view = document.getElementById('bhView');
|
||||
let catalog = [], session, index = 0, started = 0, selectedFile = '', selectedLine = 0, selected = '', options = [], answers = [];
|
||||
let catalog = [], selectedCase = 0, session, index = 0, started = 0, selectedFile = '', selectedLine = 0, selected = '', options = [], answers = [], handoff = null;
|
||||
const esc = value => String(value ?? '').replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
|
||||
const api = (url, opt) => fetch('/api/bughunter' + url, {headers:{'Content-Type':'application/json'}, ...opt}).then(async response => { const body = await response.json(); if (!response.ok) throw new Error(body.message || 'Serverfehler'); return body; });
|
||||
const post = (url, body) => api(url, {method:'POST', body:JSON.stringify(body)});
|
||||
@@ -10,10 +10,15 @@
|
||||
const state = () => answers[index];
|
||||
function loadCatalog() { api('/hunts').then(data => { catalog = data.hunts || []; renderCatalog(); }).catch(error); }
|
||||
function renderCatalog() {
|
||||
view.innerHTML = `<section class="bh-hero"><span class="bh-stamp">VERTRAULIC</span><span class="bh-kicker">DETektivbüro · DEBUGGING-FALLAKTEN</span><h1>Welche Spur verfolgst du?</h1><p>Arbeite dich durch echte Fehlerbilder: Ort, Kategorie und Fix müssen zusammenpassen. Präzision baut deine Ermittlungsserie auf.</p></section><section class="bh-grid">${catalog.map(h => `<article class="bh-case"><span class="bh-case-no">${esc(h.examScope)} · ${esc(h.difficulty).toUpperCase()}</span><h2>${esc(h.title)}</h2><p>${esc(h.description)}</p><div class="bh-tags"><span>${esc(h.language)}</span><span>${h.fileCount} Dateien</span><span>${h.bugCount} Bug${h.bugCount === 1 ? '' : 's'}</span></div><button class="bh-btn primary" data-start="${esc(h.slug)}">FALL ÖFFNEN</button></article>`).join('')}</section>`;
|
||||
view.querySelectorAll('[data-start]').forEach(button => button.addEventListener('click', () => begin(button.dataset.start)));
|
||||
if (!catalog.length) { error(new Error('Zurzeit sind keine Fallakten verfügbar.')); return; }
|
||||
selectedCase = Math.max(0, Math.min(selectedCase, catalog.length - 1));
|
||||
const selectedHunt = catalog[selectedCase];
|
||||
const deck = [selectedHunt, ...catalog.filter((_, i) => i !== selectedCase)];
|
||||
view.innerHTML = `<section class="bh-hero"><span class="bh-stamp">VERTRAULICH</span><span class="bh-kicker">DEBUG-FALLAKTEN</span><h1>Welche Spur verfolgst du?</h1><p>Öffne eine Akte, lies die Beweise und sichere deinen Befund Schritt für Schritt. Jede Akte bleibt so lange offen, wie du sie brauchst.</p></section><section class="bh-archive"><div class="bh-archive-head"><div><span class="bh-kicker">FALLARCHIV · ${catalog.length} AKTEN</span><h2>${esc(selectedHunt.title)}</h2></div><p>Klicke eine Akte an, um sie nach vorn zu holen.</p></div><div class="bh-case-deck">${deck.map((h, stackIndex) => { const originalIndex = catalog.indexOf(h), active = originalIndex === selectedCase; return `<article class="bh-case ${active ? 'active' : ''}" style="--stack:${stackIndex};--tilt:${(stackIndex % 2 ? 1 : -1) * Math.min(stackIndex, 4) * .45}deg" data-select="${originalIndex}" tabindex="0" role="button" aria-label="${esc(h.title)} öffnen"><span class="bh-case-no">${esc(h.examScope)} · ${esc(h.difficulty).toUpperCase()}</span><h2>${esc(h.title)}</h2>${active ? `<p>${esc(h.description)}</p><div class="bh-tags"><span>${esc(h.language)}</span><span>${h.fileCount} ${h.fileCount === 1 ? 'Datei' : 'Dateien'}</span><span>${h.bugCount} Bug${h.bugCount === 1 ? '' : 's'}</span></div><button class="bh-btn primary" data-start="${esc(h.slug)}">AKTE ÖFFNEN</button>` : '<span class="bh-case-peek">Zum Öffnen nach vorn holen</span>'}</article>`; }).join('')}</div></section>`;
|
||||
view.querySelectorAll('[data-select]').forEach(card => { const select = () => { selectedCase = Number(card.dataset.select); renderCatalog(); }; card.addEventListener('click', event => { if (!event.target.closest('[data-start]')) select(); }); card.addEventListener('keydown', event => { if (event.key === 'Enter' || event.key === ' ') { event.preventDefault(); select(); } }); });
|
||||
view.querySelector('[data-start]')?.addEventListener('click', event => { event.stopPropagation(); begin(event.currentTarget.dataset.start); });
|
||||
}
|
||||
function begin(slug) { api('/session/' + encodeURIComponent(slug)).then(data => { if (!data.found) throw new Error('Fallakte nicht verfügbar.'); session = data; index = 0; answers = session.bugs.map(() => ({locate:null, classify:null, fix:null})); started = Date.now(); renderBriefing(); }).catch(error); }
|
||||
function begin(slug) { api('/session/' + encodeURIComponent(slug)).then(data => { if (!data.found) throw new Error('Fallakte nicht verfügbar.'); session = data; index = 0; handoff = null; answers = session.bugs.map(() => ({locate:null, classify:null, fix:null})); started = Date.now(); renderBriefing(); }).catch(error); }
|
||||
function renderBriefing() {
|
||||
view.innerHTML = `<section class="bh-briefing"><span class="bh-stamp">NEUE AKTE</span><span class="bh-kicker">${esc(session.examScope)} · ${esc(session.difficulty).toUpperCase()} · ${esc(session.language)}</span><h1>${esc(session.title)}</h1><p>${esc(session.briefing)}</p><pre>${esc(session.consoleOutput)}</pre><div class="bh-rules"><div><b>1</b> Ort markieren</div><div><b>2</b> Fehler einordnen</div><div><b>3</b> sauberen Fix wählen</div></div><button class="bh-btn primary" id="bhStart">ERMITTLUNG STARTEN</button></section>`;
|
||||
document.getElementById('bhStart').addEventListener('click', () => { selectedFile = session.files[0]?.filename || ''; selectedLine = 0; selected = ''; options = []; renderHunt(); });
|
||||
@@ -32,6 +37,7 @@
|
||||
return `<div class="bh-code" aria-label="Code-Datei ${esc(file.filename)}">${file.lines.map((line, i) => { const lineNo = i + 1, marked = phase === 'locate' && selectedLine === lineNo; return `<button class="bh-line ${marked ? 'selected' : ''}" data-line="${lineNo}" ${phase === 'locate' ? '' : 'disabled'}><span>${lineNo}</span><code>${esc(line) || ' '}</code></button>`; }).join('')}</div>`;
|
||||
}
|
||||
function renderAction(phase, bug, s) {
|
||||
if (handoff) return `<section class="bh-action bh-handoff"><span class="bh-kicker">PHASE ABGESCHLOSSEN</span><h2>${esc(handoff.title)}</h2><p>${esc(handoff.message)}</p><button class="bh-btn primary" id="bhContinue">${esc(handoff.nextLabel)}</button></section>`;
|
||||
const labels = {locate:'1 · ORTEN', classify:'2 · KLASSIFIZIEREN', fix:'3 · FIXEN'};
|
||||
let content = '';
|
||||
if (phase === 'locate') content = `<p>Klicke die fehlerhafte Zeile im Code an.</p><p class="bh-selected">${selectedLine ? `${esc(selectedFile)}:${selectedLine}` : 'Noch keine Zeile markiert'}</p>`;
|
||||
@@ -41,6 +47,7 @@
|
||||
return `<section class="bh-action"><span class="bh-kicker">${labels[phase]}</span><h2>${phase === 'locate' ? 'Wo liegt der Bug?' : phase === 'classify' ? 'Wie heißt das Problem?' : 'Wie wird er behoben?'}</h2>${content}<p class="bh-attempts">Versuche: ${attempts}/3</p><div id="bhFeedback" class="bh-feedback" aria-live="polite"></div><div class="bh-actions"><button class="bh-btn primary" id="bhCheck">PRÜFEN</button><button class="bh-btn" id="bhReveal">AUFLÖSEN</button></div></section>`;
|
||||
}
|
||||
function bindAction(phase, bug) {
|
||||
if (handoff) { document.getElementById('bhContinue').addEventListener('click', () => { handoff = null; selected = ''; selectedLine = 0; renderHunt(); }); return; }
|
||||
if (phase === 'locate') view.querySelectorAll('[data-line]').forEach(line => line.addEventListener('click', () => { selectedLine = Number(line.dataset.line); renderHunt(); }));
|
||||
view.querySelectorAll('[data-choice]').forEach(button => button.addEventListener('click', () => { selected = button.dataset.choice; view.querySelectorAll('[data-choice]').forEach(item => item.classList.remove('selected')); button.classList.add('selected'); }));
|
||||
document.getElementById('bhCheck').addEventListener('click', () => submit(phase, bug, false));
|
||||
@@ -53,9 +60,16 @@
|
||||
post('/check', {bugId:bug.bugId, phase, reveal, submission:sub}).then(result => {
|
||||
if (!result.resolved) { state()[phase] = phaseState; feedback('Noch nicht stimmig. Lies die Beweise erneut.', 'bad'); return; }
|
||||
phaseState.revealed = reveal; phaseState.done = true; state()[phase] = phaseState;
|
||||
if (phase === 'classify') options = result.fixOptions || []; if (phase === 'fix') { showDebrief(result); return; }
|
||||
selected = ''; if (phase === 'locate') selectedLine = result.location?.line || selectedLine;
|
||||
feedback(reveal ? 'Auflösung notiert – weiter zur nächsten Phase.' : 'Spur bestätigt – weiter zur nächsten Phase.', 'ok'); setTimeout(renderHunt, 650);
|
||||
if (phase === 'classify') options = result.fixOptions || [];
|
||||
if (phase === 'fix') { showDebrief(result); return; }
|
||||
if (phase === 'locate') {
|
||||
const location = result.location || sub;
|
||||
handoff = { title: reveal ? 'Auflösung: Fundort markiert' : 'Fundort bestätigt', message: `Die relevante Stelle ist ${location.file}:${location.line}. Nimm dir einen Moment für den Code, bevor du die Fehlerklasse bestimmst.`, nextLabel: 'ZUR KATEGORIE' };
|
||||
} else {
|
||||
const category = result.category || sub.category;
|
||||
handoff = { title: reveal ? 'Auflösung: Fehlerklasse' : 'Fehlerklasse bestätigt', message: `Der Fehler gehört zur Kategorie „${category}“. Die möglichen Korrekturen liegen jetzt bereit.`, nextLabel: 'ZUR KORREKTUR' };
|
||||
}
|
||||
selected = ''; renderHunt();
|
||||
}).catch(err => feedback(err.message, 'bad'));
|
||||
}
|
||||
function showDebrief(result) {
|
||||
|
||||
Reference in New Issue
Block a user