- crossOrigin von Asset-Images entfernt (haeufige Ursache fuer "tainted canvas", wodurch der Szenen-Export fehlschlug -> kaputtes Bild) - toBlob mit Fallback auf toDataURL - Ladefehler werden jetzt sichtbar im UI angezeigt (statt nur Konsole) - Frontend mit Cache-Control: no-cache -> kein veraltetes JS/CSS nach git pull Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
107 lines
3.1 KiB
JavaScript
107 lines
3.1 KiB
JavaScript
// App-Flow: beim Laden sofort eine zufaellige Szene holen (kein Start-Screen, keine
|
|
// Auswahl). Alle 3 Fuesse finden -> Gewinn-Overlay -> "Weiter" laedt die naechste.
|
|
|
|
import { Scene } from './scene.js';
|
|
import { Legend } from './legend.js';
|
|
|
|
const stage = document.getElementById('stage');
|
|
const world = document.getElementById('world');
|
|
const sceneImg = document.getElementById('scene-img');
|
|
const loading = document.getElementById('loading');
|
|
const winOverlay = document.getElementById('win-overlay');
|
|
const toast = document.getElementById('toast');
|
|
|
|
const scene = new Scene(stage, world, sceneImg);
|
|
const legend = new Legend(
|
|
document.getElementById('legend-items'),
|
|
document.getElementById('legend-counter')
|
|
);
|
|
|
|
let current = null; // aktuelles Szenen-Payload
|
|
let busy = false;
|
|
|
|
function showLoading(on) {
|
|
loading.classList.toggle('hidden', !on);
|
|
}
|
|
|
|
function showToast(message) {
|
|
toast.textContent = message;
|
|
toast.classList.add('show');
|
|
clearTimeout(showToast._t);
|
|
showToast._t = setTimeout(() => toast.classList.remove('show'), 900);
|
|
}
|
|
|
|
function showError(err) {
|
|
let box = document.getElementById('error-box');
|
|
if (!box) {
|
|
box = document.createElement('div');
|
|
box.id = 'error-box';
|
|
stage.appendChild(box);
|
|
}
|
|
const msg = err && err.message ? err.message : String(err);
|
|
box.textContent = 'Fehler beim Laden der Szene: ' + msg;
|
|
box.classList.remove('hidden');
|
|
}
|
|
|
|
function clearError() {
|
|
const box = document.getElementById('error-box');
|
|
if (box) box.classList.add('hidden');
|
|
}
|
|
|
|
async function loadScene() {
|
|
showLoading(true);
|
|
clearError();
|
|
winOverlay.classList.add('hidden');
|
|
try {
|
|
const res = await fetch('/api/scene');
|
|
if (!res.ok) throw new Error('Server-Antwort ' + res.status);
|
|
current = await res.json();
|
|
await scene.render(current);
|
|
scene.resetView();
|
|
legend.render(current.targets);
|
|
document.getElementById('category-name').textContent = current.category || '';
|
|
} catch (err) {
|
|
console.error(err);
|
|
showError(err);
|
|
} finally {
|
|
showLoading(false);
|
|
}
|
|
}
|
|
|
|
async function handleTap({ x, y }) {
|
|
if (busy || !current) return;
|
|
busy = true;
|
|
try {
|
|
const res = await fetch(`/api/scene/${current.sceneId}/guess`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ x, y }),
|
|
});
|
|
const data = await res.json();
|
|
if (data.hit) {
|
|
const isNew = legend.markFound(data.hit);
|
|
scene.pingAt(x, y, true);
|
|
if (isNew && legend.allFound()) {
|
|
setTimeout(() => winOverlay.classList.remove('hidden'), 400);
|
|
}
|
|
} else {
|
|
scene.pingAt(x, y, false);
|
|
}
|
|
} catch (err) {
|
|
console.error(err);
|
|
} finally {
|
|
busy = false;
|
|
}
|
|
}
|
|
|
|
scene.onTap(handleTap);
|
|
|
|
// Steuerung
|
|
document.getElementById('zoom-in').addEventListener('click', () => scene.zoomIn());
|
|
document.getElementById('zoom-out').addEventListener('click', () => scene.zoomOut());
|
|
document.getElementById('reset-view').addEventListener('click', () => scene.resetView());
|
|
document.getElementById('next-scene').addEventListener('click', loadScene);
|
|
|
|
// Los geht's — sofort.
|
|
loadScene();
|