Add BugHunter clean solve replay variants
mindflux-ci / build (push) Successful in 22s

This commit is contained in:
AliceDarkest
2026-07-13 21:55:07 +02:00
parent 6d243c8987
commit 3eef7cdb40
11 changed files with 101 additions and 27 deletions
@@ -12,4 +12,5 @@ body[class*="mf-a11y-skin-"] .mf-a11y-head{background:color-mix(in srgb,var(--mf
body[class*="mf-a11y-skin-"] .mf-a11y-close{background:var(--mf-a11y-accent);color:var(--mf-a11y-ink)}
body[class*="mf-a11y-skin-"] .mf-a11y-option[aria-pressed="true"]{border-color:var(--mf-a11y-accent);background:color-mix(in srgb,var(--mf-a11y-accent) 18%,var(--surface));box-shadow:inset 0 0 0 2px var(--mf-a11y-accent)}
.bh-cabinet-level{grid-column:1/-1;margin:.5rem 0 -.15rem;padding-bottom:.35rem;border-bottom:1px solid #60473d;color:var(--bh-accent);font:700 .66rem "Courier New",monospace;letter-spacing:.12em}
.bh-drawer.solved{overflow:hidden}.bh-solved-stamp{display:inline-block;border:3px solid #79bf91;border-radius:4px;padding:.24rem .48rem;color:#79bf91;font:700 .78rem/1 "Courier New",monospace;letter-spacing:.12em;transform:rotate(-12deg);text-shadow:none;box-shadow:0 0 12px color-mix(in srgb,#79bf91 26%,transparent);pointer-events:none}.bh-drawer .bh-solved-stamp{position:absolute;right:3.1rem;top:.75rem}.bh-solved-stamp-detail{position:absolute;right:1.4rem;top:1.25rem;font-size:1rem}.bh-solved-stamp-result{margin:0 0 .8rem;font-size:1.15rem}
@media(max-width:700px){.mf-a11y-panel{inset:70px 6px auto 6px!important;width:auto;max-height:calc(100dvh - 82px)}}
+6 -4
View File
@@ -16,15 +16,17 @@
const drawers = ['junior','senior','principal'].map(level => {
const cases = catalog.map((hunt, i) => ({hunt, i})).filter(entry => entry.hunt.difficulty === level);
if (!cases.length) return '';
return `<div class="bh-cabinet-level">${difficultyNames[level]}</div>${cases.map(({hunt, i}) => `<button class="bh-drawer ${i === selectedCase ? 'active' : ''}" data-select="${i}" ${i === selectedCase ? 'aria-pressed="true"' : 'aria-pressed="false"'}><span class="bh-drawer-handle"></span><span class="bh-case-no">${esc(hunt.examScope)} · ${esc(hunt.difficulty).toUpperCase()}</span><strong>${esc(hunt.title)}</strong><small>${hunt.bugCount} Bug${hunt.bugCount === 1 ? '' : 's'} · ${hunt.fileCount} ${hunt.fileCount === 1 ? 'Datei' : 'Dateien'}</small></button>`).join('')}`;
return `<div class="bh-cabinet-level">${difficultyNames[level]}</div>${cases.map(({hunt, i}) => `<button class="bh-drawer ${hunt.solved ? 'solved' : ''} ${i === selectedCase ? 'active' : ''}" data-select="${i}" ${i === selectedCase ? 'aria-pressed="true"' : 'aria-pressed="false"'}><span class="bh-drawer-handle"></span>${hunt.solved ? '<span class="bh-solved-stamp">GELÖST</span>' : ''}<span class="bh-case-no">${esc(hunt.examScope)} · ${esc(hunt.difficulty).toUpperCase()}</span><strong>${esc(hunt.title)}</strong><small>${hunt.bugCount} Bug${hunt.bugCount === 1 ? '' : 's'} · ${hunt.fileCount} ${hunt.fileCount === 1 ? 'Datei' : 'Dateien'}</small></button>`).join('')}`;
}).join('');
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">AKTENSCHRANK · ${catalog.length} AKTEN</span><h2>Fallarchiv</h2></div><p>Nach Schwierigkeit sortiert: Grundlagen, Praxis, Komplexfälle.</p></div><div class="bh-cabinet" aria-label="Fallakten nach Schwierigkeit"><div class="bh-cabinet-label">BUGHUNTER · ARCHIV</div>${drawers}</div><article class="bh-case-detail"><span class="bh-stamp">AUSGEWÄHLTE AKTE</span><span class="bh-kicker">${esc(selectedHunt.examScope)} · ${esc(selectedHunt.difficulty).toUpperCase()} · ${esc(selectedHunt.language)}</span><h2>${esc(selectedHunt.title)}</h2><p>${esc(selectedHunt.description)}</p><div class="bh-tags"><span>${selectedHunt.fileCount} ${selectedHunt.fileCount === 1 ? 'Datei' : 'Dateien'}</span><span>${selectedHunt.bugCount} Bug${selectedHunt.bugCount === 1 ? '' : 's'}</span></div><button class="bh-btn primary" data-start="${esc(selectedHunt.slug)}">AKTE ÖFFNEN</button></article></section>`;
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">AKTENSCHRANK · ${catalog.length} AKTEN</span><h2>Fallarchiv</h2></div><p>Nach Schwierigkeit sortiert: Grundlagen, Praxis, Komplexfälle.</p></div><div class="bh-cabinet" aria-label="Fallakten nach Schwierigkeit"><div class="bh-cabinet-label">BUGHUNTER · ARCHIV</div>${drawers}</div><article class="bh-case-detail">${selectedHunt.solved ? '<span class="bh-solved-stamp bh-solved-stamp-detail">GELÖST</span>' : ''}<span class="bh-stamp">AUSGEWÄHLTE AKTE</span><span class="bh-kicker">${esc(selectedHunt.examScope)} · ${esc(selectedHunt.difficulty).toUpperCase()} · ${esc(selectedHunt.language)}</span><h2>${esc(selectedHunt.title)}</h2><p>${esc(selectedHunt.description)}</p><div class="bh-tags"><span>${selectedHunt.fileCount} ${selectedHunt.fileCount === 1 ? 'Datei' : 'Dateien'}</span><span>${selectedHunt.bugCount} Bug${selectedHunt.bugCount === 1 ? '' : 's'}</span></div><div class="bh-actions"><button class="bh-btn primary" data-start="${esc(selectedHunt.slug)}">${selectedHunt.solved ? 'ERNEUT SPIELEN' : 'AKTE ÖFFNEN'}</button>${selectedHunt.solved ? `<button class="bh-btn" data-reset="${esc(selectedHunt.slug)}">FALL VARIIEREN</button>` : ''}</div></article></section>`;
view.querySelectorAll('[data-select]').forEach(drawer => drawer.addEventListener('click', () => { selectedCase = Number(drawer.dataset.select); renderCatalog(); }));
view.querySelector('[data-start]')?.addEventListener('click', event => begin(event.currentTarget.dataset.start));
view.querySelector('[data-reset]')?.addEventListener('click', event => resetCase(event.currentTarget.dataset.reset));
}
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 resetCase(slug) { post('/reset/' + encodeURIComponent(slug), {}).then(() => begin(slug)).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>`;
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)}${session.variant ? ` · VARIANTE ${session.variant + 1}` : ''}</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(); });
}
function activePhase() { const s = state(); return !s.locate?.done ? 'locate' : !s.classify?.done ? 'classify' : 'fix'; }
@@ -83,7 +85,7 @@
function finish() { post('/finish', {slug:session.slug, durationSeconds:seconds(), items:answers.map((answer, i) => ({bugId:session.bugs[i].bugId, locate:strip(answer.locate), classify:strip(answer.classify), fix:strip(answer.fix)}))}).then(renderResult).catch(error); }
function strip(value) { return value ? {submission:value.submission, attempts:value.attempts, revealed:!!value.revealed} : null; }
function comboText() { let combo = 0; for (let i = 0; i < index; i++) { const a = answers[i]; const clean = ['locate','classify','fix'].every(key => a[key] && a[key].attempts === 1 && !a[key].revealed); combo = clean ? combo + 1 : 0; } return `×${combo}`; }
function renderResult(result) { view.innerHTML = `<section class="bh-result"><span class="bh-stamp">ERMITTLUNG ABGESCHLOSSEN</span><h1>Fallakten geschlossen.</h1><div class="bh-score">${result.score}</div><p class="bh-stars">${'★'.repeat(result.stars)}${'☆'.repeat(3-result.stars)}</p><div class="bh-stats"><div><b>${result.baseScore}</b>Basis</div><div><b>+${result.comboBonus}</b>Combo</div><div><b>${result.bugsSolved}/${result.totalBugs}</b>gelöst</div><div><b>×${result.maxCombo}</b>Serie</div><div><b>${result.highscore}</b>Highscore</div><div><b>${result.plays}</b>Akten</div></div><button class="bh-btn primary" id="bhAgain">WEITERE FÄLLE</button></section>`; document.getElementById('bhAgain').addEventListener('click', loadCatalog); }
function renderResult(result) { view.innerHTML = `<section class="bh-result">${result.cleanSolve ? '<span class="bh-solved-stamp bh-solved-stamp-result">GELÖST</span>' : ''}<span class="bh-stamp">ERMITTLUNG ABGESCHLOSSEN</span><h1>${result.cleanSolve ? 'Fall fehlerfrei geschlossen.' : 'Fallakten geschlossen.'}</h1><div class="bh-score">${result.score}</div><p class="bh-stars">${'★'.repeat(result.stars)}${'☆'.repeat(3-result.stars)}</p><div class="bh-stats"><div><b>${result.baseScore}</b>Basis</div><div><b>+${result.comboBonus}</b>Combo</div><div><b>${result.bugsSolved}/${result.totalBugs}</b>gelöst</div><div><b>×${result.maxCombo}</b>Serie</div><div><b>${result.highscore}</b>Highscore</div><div><b>${result.plays}</b>Akten</div></div><button class="bh-btn primary" id="bhAgain">WEITERE FÄLLE</button></section>`; document.getElementById('bhAgain').addEventListener('click', loadCatalog); }
function feedback(message, type) { const target = document.getElementById('bhFeedback'); if (target) { target.textContent = message; target.className = 'bh-feedback ' + type; } }
function error(err) { view.innerHTML = `<section class="bh-result"><h1>AKTE NICHT LESBAR</h1><p>${esc(err.message || err)}</p><button class="bh-btn" id="bhReload">ERNEUT VERSUCHEN</button></section>`; document.getElementById('bhReload').addEventListener('click', loadCatalog); }
loadCatalog();
+1 -1
View File
@@ -32,7 +32,7 @@
document.head.appendChild(accessibilityCss);
var accessibilityPlacementCss = document.createElement('link');
accessibilityPlacementCss.rel = 'stylesheet';
accessibilityPlacementCss.href = '/css/accessibility-placement.css?v=4';
accessibilityPlacementCss.href = '/css/accessibility-placement.css?v=5';
accessibilityPlacementCss.setAttribute('data-accessibility', '');
document.head.appendChild(accessibilityPlacementCss);
}