Erster Upload von MX Linux
This commit is contained in:
276
public/apps/magic.html
Normal file
276
public/apps/magic.html
Normal file
@@ -0,0 +1,276 @@
|
||||
<!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>
|
||||
Reference in New Issue
Block a user