Files
Media/public/apps/magic.html
2026-02-02 09:45:14 +01:00

277 lines
22 KiB
HTML

<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>Magic Selfie</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
<script src="../js/tailwind.js"></script>
<script src="../js/camera_utils.js"></script>
<script src="../js/selfie_segmentation.js"></script>
<style>
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
html {
width: 100%; height: 100%;
background-color: #0f172a;
}
body {
/* Fixiertes Layout */
position: fixed;
top: 0; left: 0; right: 0; bottom: 0;
margin: 0;
background-color: #0f172a;
color: white;
overflow: hidden !important;
user-select: none;
touch-action: none;
transition: background-color 0.5s;
animation: fadeIn 0.4s ease-out;
box-sizing: border-box;
/* Body Padding */
padding-top: calc(10px + env(safe-area-inset-top));
padding-bottom: max(20px, env(safe-area-inset-bottom));
}
.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="flex flex-col md:flex-row bg-slate-900" onclick="window.resetIdleTimer()" ontouchstart="window.resetIdleTimer()">
<button onclick="goHome()" class="absolute left-6 z-[100] bg-slate-800 text-white border-4 border-slate-600 hover:border-white px-6 py-3 rounded-full text-xl font-black shadow-2xl flex items-center gap-3 transition-transform active:scale-95 no-underline" style="top: calc(20px + env(safe-area-inset-top));">
🏠 MENÜ
</button>
<div class="absolute w-full text-center pointer-events-none z-50 hidden md:block" style="top: calc(20px + env(safe-area-inset-top));">
<h1 class="text-3xl font-black text-white drop-shadow-[0_4px_4px_rgba(0,0,0,0.8)] tracking-widest uppercase">
✨ MAGIC SELFIE
</h1>
</div>
<button onclick="window.toggleInfo(); playSound('click')" class="absolute 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" style="top: calc(20px + env(safe-area-inset-top));">
?
</button>
<div class="relative flex-1 bg-black flex flex-col items-center justify-center overflow-hidden h-[60%] md:h-full w-full rounded-b-3xl md:rounded-r-3xl md:rounded-bl-none shadow-2xl z-10">
<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>
// --- AUDIO SYSTEM ---
const _soundCache = {
'click': new Audio('../assets/sounds/click.mp3'),
'shutter': new Audio('../assets/sounds/shutter.mp3'),
'success': new Audio('../assets/sounds/success.mp3')
};
Object.values(_soundCache).forEach(s => s.load());
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 ---
window.goHome = function() {
playSound('click');
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');
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');
const dataUrl = document.getElementById('preview-img-display').src; const link = document.createElement('a'); link.download = 'magic-selfie.png'; link.href = dataUrl; link.click();
}
window.printFromPreview = async function(el) {
playSound('success');
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-Selfie' });
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>