Wimmel-Suchspiel: finde 3 versteckte Fuesse in einer randvollen Szene. - Node/Express-Server generiert Szenen prozedural aus einer gemeinsamen Library (library/props = Deko, library/feet = Fuesse); Spezial-Level (emoji) unter scenes/ - Serverseitige Trefferpruefung (nicht per DOM austrickbar) - Frontend rendert die Szene als ein Bild -> fluessiges Zoom/Pan (panzoom) - Dark Mode, kein Start-Screen, Installationsskript + Deploy-Doku (Proxmox LXC) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
89 lines
2.6 KiB
JavaScript
89 lines
2.6 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 svg = document.getElementById('scene-svg');
|
|
const layer = document.getElementById('scene-layer');
|
|
const stage = document.getElementById('stage');
|
|
const loading = document.getElementById('loading');
|
|
const winOverlay = document.getElementById('win-overlay');
|
|
const toast = document.getElementById('toast');
|
|
|
|
const scene = new Scene(svg, layer, stage);
|
|
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);
|
|
}
|
|
|
|
async function loadScene() {
|
|
showLoading(true);
|
|
winOverlay.classList.add('hidden');
|
|
try {
|
|
const res = await fetch('/api/scene');
|
|
if (!res.ok) throw new Error('Szene konnte nicht geladen werden.');
|
|
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);
|
|
showToast('Ups — Szene konnte nicht geladen werden.');
|
|
} 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();
|