259 lines
21 KiB
HTML
259 lines
21 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="de">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<title>Magic Booth</title>
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
|
|
|
<script src="../js/tailwind.js"></script>
|
|
<script src="../js/camera_utils.js"></script>
|
|
<script src="../js/selfie_segmentation.js"></script>
|
|
|
|
<style>
|
|
/* HIER gehört das CSS hin (unsichtbar für den Nutzer) */
|
|
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
|
|
body {
|
|
background-color: #facc15;
|
|
overflow: hidden;
|
|
user-select: none;
|
|
touch-action: none;
|
|
transition: background-color 0.5s;
|
|
animation: fadeIn 0.4s ease-out; /* Die Animation */
|
|
}
|
|
|
|
body { background-color: #0f172a; color: white; overflow: hidden; user-select: none; touch-action: none; }
|
|
.no-scrollbar::-webkit-scrollbar { display: none; }
|
|
button:active { transform: scale(0.95); transition: transform 0.1s; }
|
|
|
|
@media print {
|
|
body * { visibility: hidden; }
|
|
#print-container, #print-container * { visibility: visible; }
|
|
#print-container { position: fixed; left: 0; top: 0; width: 100%; height: 100%; display: flex !important; align-items: center; justify-content: center; background: white; z-index: 99999; }
|
|
#print-img { max-width: 100%; max-height: 100%; object-fit: contain; }
|
|
}
|
|
</style>
|
|
</head>
|
|
<body class="h-screen w-screen flex flex-col md:flex-row p-0 m-0 bg-slate-900" onclick="window.resetIdleTimer()" ontouchstart="window.resetIdleTimer()">
|
|
|
|
|
|
<button onclick="goHome()" class="absolute top-6 left-6 z-[100] bg-slate-800 text-white border-4 border-slate-600 hover:border-white px-8 py-4 rounded-full text-2xl font-black shadow-2xl flex items-center gap-3 transition-transform active:scale-95 no-underline">
|
|
🏠 MENÜ
|
|
</button>
|
|
|
|
<button onclick="window.toggleInfo(); playSound('click')" class="absolute top-6 right-6 z-[100] w-14 h-14 bg-slate-800 text-white rounded-full font-bold border-4 border-slate-600 hover:border-white shadow-xl flex items-center justify-center transition-transform active:scale-95 text-2xl">
|
|
?
|
|
</button>
|
|
|
|
<div class="relative flex-1 bg-black flex flex-col items-center justify-center overflow-hidden h-[60%] md:h-full w-full">
|
|
<div class="relative w-full h-full flex items-center justify-center p-2 md:p-8 pb-24">
|
|
<video id="ai-video" autoplay playsinline muted class="hidden"></video>
|
|
<canvas id="ai-canvas" class="max-w-full max-h-full object-contain shadow-2xl rounded-lg"></canvas>
|
|
|
|
<div id="preview-overlay" class="absolute inset-0 bg-slate-900 z-50 hidden flex-col items-center justify-center">
|
|
<h2 class="text-3xl font-bold text-white mb-6 animate-bounce">Dein magisches Foto!</h2>
|
|
<div class="relative max-w-[90%] max-h-[60%] border-8 border-white shadow-2xl rounded-xl overflow-hidden bg-black">
|
|
<img id="preview-img-display" class="w-full h-full object-contain" src="">
|
|
</div>
|
|
<div class="flex gap-6 mt-8">
|
|
<button onclick="window.closePreview(); playSound('click')" class="bg-yellow-500 hover:bg-yellow-400 text-black font-black py-4 px-8 rounded-2xl text-xl shadow-xl active:scale-95 transition border-b-8 border-yellow-700 active:border-b-0 active:translate-y-2 flex items-center gap-2">🔄 NOCHMAL</button>
|
|
<button id="btn-print" onclick="window.printFromPreview(this)" class="bg-white text-slate-900 font-black py-4 px-8 rounded-2xl text-xl shadow-xl hover:bg-gray-100 active:scale-95 transition border-b-8 border-gray-400 active:border-b-0 active:translate-y-2 flex items-center gap-2">🖨️ DRUCKEN</button>
|
|
<button onclick="window.saveFromPreview()" class="bg-violet-600 text-white font-black py-4 px-8 rounded-2xl text-xl shadow-xl hover:bg-violet-500 active:scale-95 transition border-b-8 border-violet-800 active:border-b-0 active:translate-y-2 flex items-center gap-2">💾 FOTO</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="ai-loading" class="absolute inset-0 bg-black/95 flex flex-col items-center justify-center z-40 p-4 text-center">
|
|
<div class="text-6xl animate-spin mb-4">🔮</div>
|
|
<div class="text-2xl font-bold mb-4">Lade KI...</div>
|
|
<div id="ai-debug-box" class="text-xs text-left font-mono bg-black/80 border border-gray-700 p-4 rounded-xl mt-4 max-w-lg w-full h-32 overflow-y-auto opacity-70"><div>[Log] App gestartet...</div></div>
|
|
<div class="mt-8 flex gap-6">
|
|
<button onclick="location.reload()" class="border-2 border-white/30 px-6 py-3 rounded-full hover:bg-white/10">Neu laden</button>
|
|
<button onclick="window.drawFallback(); document.getElementById('ai-loading').style.display='none'; playSound('click')" class="bg-red-600 px-6 py-3 rounded-full font-bold hover:bg-red-500">Ohne KI starten</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="main-controls" class="absolute bottom-6 flex gap-4 z-30 w-full justify-center px-4">
|
|
<button onclick="window.switchCamera(); playSound('click')" class="w-20 bg-slate-700 text-white font-black py-4 rounded-2xl text-2xl shadow-xl hover:scale-105 active:scale-95 transition border-b-8 border-slate-900 active:border-b-0 active:translate-y-2 flex items-center justify-center" title="Kamera wechseln">🔄</button>
|
|
<button onclick="window.capturePhoto()" class="flex-1 max-w-[200px] bg-violet-600 text-white font-black py-4 rounded-2xl text-lg md:text-xl shadow-xl hover:scale-105 active:scale-95 transition border-b-8 border-violet-800 active:border-b-0 active:translate-y-2 flex items-center justify-center gap-2">✨ FOTO</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="h-[40%] md:h-full md:w-1/3 min-w-[280px] max-w-md bg-slate-800 p-4 border-t-4 md:border-t-0 md:border-l-4 border-slate-700 overflow-y-auto no-scrollbar z-20 flex flex-col">
|
|
<h3 class="text-white font-black text-xl mb-4 text-center uppercase tracking-wider sticky top-0 bg-slate-800 pb-2 z-10 pt-2">🌍 Reiseziel</h3>
|
|
<div class="grid grid-cols-2 gap-3 pb-4">
|
|
<div onclick="window.setBg('../assets/weltraum.jpg'); playSound('click')" class="aspect-square bg-slate-700 rounded-2xl cursor-pointer border-4 border-transparent hover:border-violet-400 relative group overflow-hidden active:scale-95 transition shadow-lg"><img src="../assets/weltraum.jpg" class="w-full h-full object-cover" onerror="this.style.display='none'"><div class="absolute inset-0 flex items-center justify-center bg-indigo-900/50 z-0"><span class="text-5xl">🚀</span></div><span class="absolute bottom-2 left-3 text-xs font-black drop-shadow-md z-10 text-white uppercase tracking-wider">WELTALL</span></div>
|
|
<div onclick="window.setBg('../assets/paris.jpg'); playSound('click')" class="aspect-square bg-slate-700 rounded-2xl cursor-pointer border-4 border-transparent hover:border-violet-400 relative group overflow-hidden active:scale-95 transition shadow-lg"><img src="../assets/paris.jpg" class="w-full h-full object-cover" onerror="this.style.display='none'"><div class="absolute inset-0 flex items-center justify-center bg-blue-900/50 z-0"><span class="text-5xl">🇫🇷</span></div><span class="absolute bottom-2 left-3 text-xs font-black drop-shadow-md z-10 text-white uppercase tracking-wider">PARIS</span></div>
|
|
<div onclick="window.setBg('../assets/dschungel.jpg'); playSound('click')" class="aspect-square bg-slate-700 rounded-2xl cursor-pointer border-4 border-transparent hover:border-violet-400 relative group overflow-hidden active:scale-95 transition shadow-lg"><img src="../assets/dschungel.jpg" class="w-full h-full object-cover" onerror="this.style.display='none'"><div class="absolute inset-0 flex items-center justify-center bg-green-900/50 z-0"><span class="text-5xl">🌴</span></div><span class="absolute bottom-2 left-3 text-xs font-black drop-shadow-md z-10 text-white uppercase tracking-wider">DSCHUNGEL</span></div>
|
|
<div onclick="window.setBg('../assets/unterwasser.jpg'); playSound('click')" class="aspect-square bg-slate-700 rounded-2xl cursor-pointer border-4 border-transparent hover:border-violet-400 relative group overflow-hidden active:scale-95 transition shadow-lg"><img src="../assets/unterwasser.jpg" class="w-full h-full object-cover" onerror="this.style.display='none'"><div class="absolute inset-0 flex items-center justify-center bg-cyan-900/50 z-0"><span class="text-5xl">🐠</span></div><span class="absolute bottom-2 left-3 text-xs font-black drop-shadow-md z-10 text-white uppercase tracking-wider">OZEAN</span></div>
|
|
<div onclick="window.setBg('../assets/wolken.jpg'); playSound('click')" class="aspect-square bg-slate-700 rounded-2xl cursor-pointer border-4 border-transparent hover:border-violet-400 relative group overflow-hidden active:scale-95 transition shadow-lg"><img src="../assets/wolken.jpg" class="w-full h-full object-cover" onerror="this.style.display='none'"><div class="absolute inset-0 flex items-center justify-center bg-sky-500/50 z-0"><span class="text-5xl">☁️</span></div><span class="absolute bottom-2 left-3 text-xs font-black drop-shadow-md z-10 text-white uppercase tracking-wider">HIMMEL</span></div>
|
|
<div onclick="window.setBg('../assets/schloss.jpg'); playSound('click')" class="aspect-square bg-slate-700 rounded-2xl cursor-pointer border-4 border-transparent hover:border-violet-400 relative group overflow-hidden active:scale-95 transition shadow-lg"><img src="../assets/schloss.jpg" class="w-full h-full object-cover" onerror="this.style.display='none'"><div class="absolute inset-0 flex items-center justify-center bg-purple-900/50 z-0"><span class="text-5xl">🏰</span></div><span class="absolute bottom-2 left-3 text-xs font-black drop-shadow-md z-10 text-white uppercase tracking-wider">SCHLOSS</span></div>
|
|
<div onclick="window.setBg('../assets/dino.jpg'); playSound('click')" class="aspect-square bg-slate-700 rounded-2xl cursor-pointer border-4 border-transparent hover:border-violet-400 relative group overflow-hidden active:scale-95 transition shadow-lg"><img src="../assets/dino.jpg" class="w-full h-full object-cover" onerror="this.style.display='none'"><div class="absolute inset-0 flex items-center justify-center bg-amber-900/50 z-0"><span class="text-5xl">🦖</span></div><span class="absolute bottom-2 left-3 text-xs font-black drop-shadow-md z-10 text-white uppercase tracking-wider">DINO</span></div>
|
|
<div onclick="window.setBg('../assets/stadion.jpg'); playSound('click')" class="aspect-square bg-slate-700 rounded-2xl cursor-pointer border-4 border-transparent hover:border-violet-400 relative group overflow-hidden active:scale-95 transition shadow-lg"><img src="../assets/stadion.jpg" class="w-full h-full object-cover" onerror="this.style.display='none'"><div class="absolute inset-0 flex items-center justify-center bg-green-800/50 z-0"><span class="text-5xl">⚽</span></div><span class="absolute bottom-2 left-3 text-xs font-black drop-shadow-md z-10 text-white uppercase tracking-wider">STADION</span></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="info-modal" class="hidden fixed inset-0 z-[200] bg-black/80 backdrop-blur-sm flex items-center justify-center p-4" onclick="window.toggleInfo()">
|
|
<div class="bg-slate-800 border-4 border-violet-500 rounded-[2rem] max-w-2xl w-full p-8 shadow-2xl relative" onclick="event.stopPropagation()">
|
|
<button onclick="window.toggleInfo(); playSound('click')" class="absolute top-6 right-6 text-white hover:text-violet-400 text-4xl font-bold transition">✖</button>
|
|
<h2 class="text-4xl font-black text-white mb-8 border-b border-slate-600 pb-4 flex items-center gap-4"><span class="text-6xl">✨</span> ANLEITUNG</h2>
|
|
<div class="text-lg text-slate-300 space-y-4">
|
|
<p>1. Wähle rechts einen <b>Hintergrund</b>.</p>
|
|
<p>2. Die KI schneidet dich automatisch aus.</p>
|
|
<p>3. Drücke <b>FOTO</b> für eine Vorschau.</p>
|
|
<p>4. Dann kannst du <b>Drucken</b> oder nochmal probieren.</p>
|
|
</div>
|
|
<button onclick="window.toggleInfo(); playSound('click')" class="mt-8 w-full bg-violet-600 hover:bg-violet-500 text-white font-black py-4 rounded-xl text-xl transition">ALLES KLAR!</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="print-container" class="hidden"><img id="print-img" src=""></div>
|
|
|
|
<script>
|
|
// --- ROBUSTES AUDIO SYSTEM (ANTI-ABSTURZ) ---
|
|
const _soundCache = {
|
|
'click': new Audio('../assets/sounds/click.mp3'),
|
|
'shutter': new Audio('../assets/sounds/shutter.mp3'),
|
|
'success': new Audio('../assets/sounds/success.mp3')
|
|
};
|
|
|
|
// Sounds vorladen
|
|
Object.values(_soundCache).forEach(s => s.load());
|
|
|
|
// Abspiel-Funktion
|
|
window.playSound = function(id) {
|
|
const baseSound = _soundCache[id];
|
|
if (baseSound) {
|
|
const soundClone = baseSound.cloneNode();
|
|
soundClone.volume = 1.0;
|
|
soundClone.play().catch(e => console.warn("Sound-Problem:", e));
|
|
}
|
|
};
|
|
|
|
// --- HOME FUNKTION (Diese fehlte wahrscheinlich!) ---
|
|
window.goHome = function() {
|
|
playSound('click');
|
|
// Kleine Verzögerung, damit der Sound noch zu hören ist
|
|
setTimeout(() => {
|
|
window.location.href = '../index.html';
|
|
}, 300);
|
|
};
|
|
|
|
let idleTimer;
|
|
function resetIdleTimer() {
|
|
clearTimeout(idleTimer);
|
|
idleTimer = setTimeout(() => {
|
|
if (document.getElementById('preview-overlay').classList.contains('hidden')) { window.location.href = '../index.html'; }
|
|
}, 90000);
|
|
}
|
|
['mousedown', 'touchstart', 'scroll', 'keydown', 'input'].forEach(evt => document.addEventListener(evt, window.resetIdleTimer, {passive: true}));
|
|
window.resetIdleTimer();
|
|
|
|
window.toggleInfo = function() {
|
|
const modal = document.getElementById('info-modal');
|
|
if (modal.classList.contains('hidden')) { modal.classList.remove('hidden'); modal.classList.add('flex'); } else { modal.classList.add('hidden'); modal.classList.remove('flex'); }
|
|
}
|
|
window.logAi = function(msg, color="white") {
|
|
const box = document.getElementById('ai-debug-box');
|
|
if(box) { box.innerHTML += `<div><span style="color:${color}">${msg}</span></div>`; box.scrollTop = box.scrollHeight; }
|
|
console.log(msg);
|
|
}
|
|
|
|
let seg = null; const video = document.getElementById('ai-video'); const canvas = document.getElementById('ai-canvas'); const ctx = canvas.getContext('2d'); const loadingEl = document.getElementById('ai-loading');
|
|
let isRunning = false; let bgImage = new Image(); bgImage.src = "../assets/weltraum.jpg"; let bgColor = null; let loadTimer = null; let currentFacingMode = 'user'; let currentStream;
|
|
|
|
async function checkFiles() {
|
|
const files = ['selfie_segmentation.binarypb', 'selfie_segmentation_solution_simd_wasm_bin.wasm'];
|
|
for(let f of files) { try { const r = await fetch('../models/' + f, {method: 'GET', cache: 'no-store'}); if(!r.ok) window.logAi(`WARNUNG: ${f} fehlt evtl.`, "orange"); else window.logAi(`OK: ${f}`, "green"); } catch(e) { window.logAi(`Check ignorieren: ${e.message}`, "gray"); } }
|
|
}
|
|
|
|
async function initCam() {
|
|
checkFiles();
|
|
try {
|
|
if(currentStream) currentStream.getTracks().forEach(t => t.stop());
|
|
window.logAi("Starte Kamera...");
|
|
const stream = await navigator.mediaDevices.getUserMedia({ video: { width: {ideal: 1280}, height: {ideal: 720}, facingMode: currentFacingMode } });
|
|
currentStream = stream; video.srcObject = stream; await video.play();
|
|
if(currentFacingMode === 'user') { video.style.transform = 'scaleX(-1)'; } else { video.style.transform = 'scaleX(1)'; }
|
|
loadTimer = setTimeout(() => { window.logAi("Timeout! Klicke 'Ohne KI starten'.", "yellow"); }, 30000);
|
|
window.logAi("Warte auf Videodaten...");
|
|
waitForVideo();
|
|
} catch(e) { window.logAi("Kamera-Fehler: " + e.message, "red"); alert("Kamera konnte nicht gestartet werden."); }
|
|
}
|
|
function waitForVideo() {
|
|
if (video.readyState >= 2 && video.videoWidth > 0) { window.logAi("Video läuft. Starte KI..."); setupAI(); } else { requestAnimationFrame(waitForVideo); }
|
|
}
|
|
window.switchCamera = function() { currentFacingMode = (currentFacingMode === 'user') ? 'environment' : 'user'; initCam(); }
|
|
function setupAI() {
|
|
if(seg) { loadingEl.style.display = 'none'; isRunning = true; processLoop(); return; }
|
|
window.logAi("Lade KI-Modell...", "cyan");
|
|
try {
|
|
seg = new SelfieSegmentation({locateFile: (file) => `../models/${file}`});
|
|
seg.setOptions({ modelSelection: 1, selfieMode: true });
|
|
seg.onResults(onResults);
|
|
isRunning = true; processLoop();
|
|
} catch(e) { window.logAi("KI Init Fehler: " + e, "red"); }
|
|
}
|
|
async function processLoop() { if(!isRunning) return; if(video.videoWidth > 0 && !video.paused) { try { await seg.send({image: video}); } catch(e) {} } requestAnimationFrame(processLoop); }
|
|
function onResults(results) {
|
|
if(loadingEl.style.display !== 'none') { clearTimeout(loadTimer); loadingEl.style.display = 'none'; window.logAi("KI läuft!", "green"); }
|
|
canvas.width = video.videoWidth; canvas.height = video.videoHeight;
|
|
ctx.save(); ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
ctx.drawImage(results.segmentationMask, 0, 0, canvas.width, canvas.height);
|
|
ctx.globalCompositeOperation = 'source-in'; ctx.drawImage(results.image, 0, 0, canvas.width, canvas.height);
|
|
ctx.globalCompositeOperation = 'destination-over';
|
|
if (bgColor) { ctx.fillStyle = bgColor; ctx.fillRect(0, 0, canvas.width, canvas.height); }
|
|
else if (bgImage.complete && bgImage.naturalWidth > 0) { ctx.drawImage(bgImage, 0, 0, canvas.width, canvas.height); }
|
|
else { ctx.fillStyle = '#111'; ctx.fillRect(0, 0, canvas.width, canvas.height); }
|
|
ctx.restore();
|
|
}
|
|
window.setBg = function(path, color = null) { if (color) { bgColor = color; } else { bgColor = null; bgImage.src = path; } }
|
|
|
|
window.capturePhoto = function() {
|
|
playSound('shutter'); // SOUND
|
|
flashEffect(); isRunning = false; const dataUrl = canvas.toDataURL('image/png');
|
|
document.getElementById('preview-img-display').src = dataUrl;
|
|
document.getElementById('preview-overlay').classList.remove('hidden'); document.getElementById('preview-overlay').style.display = 'flex';
|
|
document.getElementById('main-controls').classList.add('hidden');
|
|
}
|
|
window.closePreview = function() {
|
|
document.getElementById('preview-overlay').classList.add('hidden'); document.getElementById('preview-overlay').style.display = 'none';
|
|
document.getElementById('main-controls').classList.remove('hidden'); isRunning = true; processLoop();
|
|
}
|
|
window.saveFromPreview = function() {
|
|
playSound('success'); // SOUND
|
|
const dataUrl = document.getElementById('preview-img-display').src; const link = document.createElement('a'); link.download = 'magic-booth.png'; link.href = dataUrl; link.click();
|
|
}
|
|
window.printFromPreview = async function(el) {
|
|
playSound('success'); // SOUND
|
|
const btn = el || document.getElementById('btn-print'); const oldText = btn ? btn.innerText : "🖨️ DRUCKEN";
|
|
if (btn) { btn.innerText = "⏳ Sende..."; btn.disabled = true; }
|
|
const dataUrl = document.getElementById('preview-img-display').src;
|
|
try {
|
|
if (window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.DirectPrinter) {
|
|
await window.Capacitor.Plugins.DirectPrinter.printBase64({ data: dataUrl, name: 'Magic-Booth' });
|
|
if (btn) btn.innerText = "✅ Gedruckt!";
|
|
} else {
|
|
console.warn("DirectPrinter Plugin nicht gefunden, nutze Browser Print.");
|
|
document.getElementById('print-img').src = dataUrl; setTimeout(() => window.print(), 250);
|
|
if (btn) btn.innerText = "✅ OK";
|
|
}
|
|
} catch (error) { console.error("Druckfehler:", error); alert("Drucken fehlgeschlagen: " + error.message); if (btn) btn.innerText = "❌ Fehler"; }
|
|
setTimeout(() => { if (btn) { btn.innerText = oldText; btn.disabled = false; } }, 3000);
|
|
}
|
|
window.takePhoto = function() { window.capturePhoto(); }
|
|
function flashEffect() { canvas.style.filter = "brightness(10)"; setTimeout(() => canvas.style.filter = "none", 100); }
|
|
window.drawFallback = function() {
|
|
isRunning = false; loadingEl.style.display = 'none'; window.logAi("KI deaktiviert.");
|
|
function loop() {
|
|
canvas.width = video.videoWidth; canvas.height = video.videoHeight;
|
|
ctx.save(); if(currentFacingMode === 'user') ctx.scale(-1, 1);
|
|
ctx.drawImage(video, currentFacingMode === 'user' ? -canvas.width : 0, 0); ctx.restore();
|
|
ctx.fillStyle = "white"; ctx.font = "20px sans-serif"; ctx.fillText("Ohne KI", 20, 40);
|
|
requestAnimationFrame(loop);
|
|
} loop();
|
|
}
|
|
initCam();
|
|
</script>
|
|
</body>
|
|
</html>
|