Initial commit: fuesse.sexy — Where's the Feet
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>
This commit is contained in:
88
public/js/app.js
Normal file
88
public/js/app.js
Normal file
@@ -0,0 +1,88 @@
|
||||
// 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();
|
||||
Reference in New Issue
Block a user