Erster Upload von MX Linux

This commit is contained in:
2026-02-02 09:45:14 +01:00
commit a25d0becaf
109 changed files with 6801 additions and 0 deletions

174
public/apps/comic.html Normal file
View File

@@ -0,0 +1,174 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>Comic Studio</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="https://cdn.tailwindcss.com"></script> <style>
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
html {
width: 100%; height: 100%;
background-color: #facc15;
}
body {
/* Fixiertes Layout für maximale Stabilität */
position: fixed;
top: 0; left: 0; right: 0; bottom: 0;
margin: 0;
background-color: #facc15;
color: black;
overflow: hidden !important;
user-select: none;
touch-action: none;
font-family: sans-serif;
animation: fadeIn 0.4s ease-out;
transition: background-color 0.3s ease;
box-sizing: border-box;
/* OPTIMIERUNG: Näher an den Rand rutschen */
/* Oben: Notch + 10px (statt vorher 60px pauschal) */
padding-top: calc(10px + env(safe-area-inset-top));
/* Unten: Home-Balken + 20px */
padding-bottom: max(20px, env(safe-area-inset-bottom));
}
.font-comic { font-family: 'Comic Sans MS', 'Chalkboard SE', sans-serif; }
.no-scrollbar::-webkit-scrollbar { display: none; }
/* Sprechblasen */
.bubble-container { position: absolute; z-index: 20; display: flex; flex-direction: column; align-items: center; left: 50%; bottom: 25%; transform: translateX(-50%); }
.bubble-handle { background-color: #3b82f6; color: white; border-radius: 50%; width: 40px; height: 40px; display: flex; align-items: center; justify-content: center; cursor: move; box-shadow: 0 4px 6px rgba(0,0,0,0.3); margin-bottom: -15px; z-index: 30; font-size: 24px; touch-action: none; border: 2px solid white; }
.bubble-text { background: white; border: 4px solid black; border-radius: 2rem; padding: 1rem 1.5rem; min-width: 160px; max-width: 280px; text-align: center; font-family: 'Comic Sans MS', sans-serif; font-size: 1.5rem; line-height: 1.2; box-shadow: 8px 8px 0px rgba(0,0,0,0.2); outline: none; cursor: text; }
button:active { transform: scale(0.95); transition: transform 0.1s; }
@media print {
@page { size: landscape; margin: 0mm; }
body * { visibility: hidden; }
#print-container, #print-container * { visibility: visible; }
#print-container {
position: fixed; left: 0; top: 0; width: 100vw; height: 100vh;
display: flex !important; align-items: center; justify-content: center;
background: white; z-index: 99999; margin: 0; padding: 0;
}
#print-img { width: 100%; height: 100%; object-fit: contain; }
}
</style>
</head>
<body class="flex flex-col px-2 md:px-4"
onclick="if(window.resetIdleTimer) window.resetIdleTimer()"
ontouchstart="if(window.resetIdleTimer) window.resetIdleTimer()">
<div class="flex-none flex items-center justify-between gap-2 mb-1 bg-white/20 backdrop-blur-md p-1 rounded-3xl shadow-lg border-2 border-white/40 overflow-hidden relative z-50">
<div class="flex items-center gap-2 shrink-0 pl-1">
<button onclick="goHome()" class="bg-slate-900 text-white border-4 border-slate-700 hover:border-white px-3 py-2 rounded-full text-lg font-black shadow-xl flex items-center gap-2 transition-transform active:scale-95 no-underline focus:outline-none">
🏠 <span class="hidden lg:inline">MENÜ</span>
</button>
<h1 class="text-lg md:text-2xl font-black font-comic text-black tracking-widest drop-shadow-sm uppercase leading-none hidden md:block">
COMIC
</h1>
</div>
<div class="flex gap-1 overflow-x-auto no-scrollbar px-2 shrink-0 justify-center">
<button onclick="changeBg('#facc15')" class="w-10 h-10 rounded-xl bg-yellow-400 border-4 border-white hover:scale-110 transition shadow-md ring-2 ring-transparent active:ring-black"></button>
<button onclick="changeBg('#ef4444')" class="w-10 h-10 rounded-xl bg-red-500 border-4 border-white hover:scale-110 transition shadow-md ring-2 ring-transparent active:ring-black"></button>
<button onclick="changeBg('#3b82f6')" class="w-10 h-10 rounded-xl bg-blue-500 border-4 border-white hover:scale-110 transition shadow-md ring-2 ring-transparent active:ring-black"></button>
<button onclick="changeBg('#22c55e')" class="w-10 h-10 rounded-xl bg-green-500 border-4 border-white hover:scale-110 transition shadow-md ring-2 ring-transparent active:ring-black"></button>
<button onclick="changeBg('#a855f7')" class="w-10 h-10 rounded-xl bg-purple-500 border-4 border-white hover:scale-110 transition shadow-md ring-2 ring-transparent active:ring-black"></button>
<button onclick="changeBg('#ffffff')" class="w-10 h-10 rounded-xl bg-white border-4 border-gray-300 hover:scale-110 transition shadow-md ring-2 ring-transparent active:ring-black"></button>
</div>
<div class="flex items-center gap-2 shrink-0 pr-1">
<button onclick="switchCamera(); playSound('click')" class="w-12 h-12 bg-white hover:bg-gray-100 text-black rounded-full flex items-center justify-center text-xl shadow-lg border-4 border-slate-900 active:scale-95 transition" title="Kamera wechseln">
🔄
</button>
<div class="relative w-24 h-16 md:w-32 md:h-20 bg-black rounded-xl border-4 border-black shadow-xl overflow-hidden hidden sm:block">
<video id="webcam" autoplay playsinline muted class="w-full h-full object-cover opacity-90"></video>
<div class="absolute bottom-0 right-0 bg-red-600 text-white text-[8px] px-1 rounded-tl font-black animate-pulse pointer-events-none">LIVE</div>
</div>
<button onclick="toggleInfo(); playSound('click')" class="w-12 h-12 bg-slate-900 text-white rounded-full font-bold border-4 border-slate-700 hover:border-white shadow-xl flex items-center justify-center transition-transform active:scale-95 text-xl">
?
</button>
</div>
</div>
<div class="flex-1 flex flex-col md:flex-row gap-3 w-full max-w-[95rem] mx-auto mb-2 px-1 overflow-y-auto no-scrollbar z-10 relative min-h-0">
<div class="flex-1 bg-white border-[6px] border-black relative shadow-[4px_4px_0px_0px_rgba(0,0,0,0.8)] rounded-xl overflow-hidden group min-h-[150px]" id="panel-0">
<canvas class="w-full h-full object-cover bg-gray-100 pointer-events-none"></canvas>
<div class="absolute top-0 left-0 bg-black text-white px-3 py-0 text-2xl font-black font-comic rounded-br-lg pointer-events-none z-10">1</div>
<div class="bubble-container"><div class="bubble-handle"></div><div contenteditable="true" class="bubble-text" onfocus="clearText(this, 'Start...')" onblur="resetText(this, 'Start...')">Start...</div></div>
<button onclick="snap(0)" class="absolute bottom-2 right-2 bg-blue-600 hover:bg-blue-500 text-white w-16 h-16 rounded-full text-3xl shadow-xl border-4 border-white z-30 active:scale-90 transition flex items-center justify-center">📸</button>
</div>
<div class="flex-1 bg-white border-[6px] border-black relative shadow-[4px_4px_0px_0px_rgba(0,0,0,0.8)] rounded-xl overflow-hidden group min-h-[150px]" id="panel-1">
<canvas class="w-full h-full object-cover bg-gray-100 pointer-events-none"></canvas>
<div class="absolute top-0 left-0 bg-black text-white px-3 py-0 text-2xl font-black font-comic rounded-br-lg pointer-events-none z-10">2</div>
<div class="bubble-container"><div class="bubble-handle"></div><div contenteditable="true" class="bubble-text" onfocus="clearText(this, 'Dann...')" onblur="resetText(this, 'Dann...')">Dann...</div></div>
<button onclick="snap(1)" class="absolute bottom-2 right-2 bg-blue-600 hover:bg-blue-500 text-white w-16 h-16 rounded-full text-3xl shadow-xl border-4 border-white z-30 active:scale-90 transition flex items-center justify-center">📸</button>
</div>
<div class="flex-1 bg-white border-[6px] border-black relative shadow-[4px_4px_0px_0px_rgba(0,0,0,0.8)] rounded-xl overflow-hidden group min-h-[150px]" id="panel-2">
<canvas class="w-full h-full object-cover bg-gray-100 pointer-events-none"></canvas>
<div class="absolute top-0 left-0 bg-black text-white px-3 py-0 text-2xl font-black font-comic rounded-br-lg pointer-events-none z-10">3</div>
<div class="bubble-container"><div class="bubble-handle"></div><div contenteditable="true" class="bubble-text" onfocus="clearText(this, 'Ende!')" onblur="resetText(this, 'Ende!')">Ende!</div></div>
<button onclick="snap(2)" class="absolute bottom-2 right-2 bg-blue-600 hover:bg-blue-500 text-white w-16 h-16 rounded-full text-3xl shadow-xl border-4 border-white z-30 active:scale-90 transition flex items-center justify-center">📸</button>
</div>
</div>
<div class="text-center flex justify-center gap-4 px-4 z-20 relative shrink-0">
<button onclick="playSound('click'); downloadComic()" class="flex-1 max-w-xs bg-slate-900 text-white font-black font-comic text-xl md:text-2xl py-3 rounded-2xl border-b-[6px] border-slate-700 hover:border-slate-500 hover:translate-y-[-2px] active:border-b-0 active:translate-y-2 transition-all shadow-xl flex items-center justify-center gap-2">💾 SPEICHERN</button>
<button id="btn-print" onclick="playSound('click'); window.printComic()" class="flex-1 max-w-xs bg-white text-black font-black font-comic text-xl md:text-2xl py-3 rounded-2xl border-b-[6px] border-gray-300 hover:border-gray-400 hover:translate-y-[-2px] active:border-b-0 active:translate-y-2 transition-all shadow-xl flex items-center justify-center gap-2">🖨️ DRUCKEN</button>
</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="toggleInfo()">
<div class="bg-slate-800 border-4 border-yellow-400 rounded-[2rem] max-w-3xl w-full p-8 shadow-2xl relative" onclick="event.stopPropagation()">
<button onclick="toggleInfo(); playSound('click')" class="absolute top-6 right-6 text-white hover:text-yellow-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="grid grid-cols-1 md:grid-cols-3 gap-6 text-base">
<div class="p-6 bg-slate-700/50 rounded-2xl border border-slate-600 flex flex-col items-center text-center"><div class="text-4xl mb-4 text-blue-400">📸</div><h3 class="text-white font-bold text-xl mb-2">Fotografieren</h3><p class="text-slate-300 leading-snug">Drücke auf den blauen Kamera-Button in jedem Bildrahmen.</p></div>
<div class="p-6 bg-slate-700/50 rounded-2xl border border-slate-600 flex flex-col items-center text-center"><div class="text-4xl mb-4 text-white">💬</div><h3 class="text-white font-bold text-xl mb-2">Texten</h3><p class="text-slate-300 leading-snug">Tippe auf den Text. Du kannst die Blase am blauen Griff verschieben.</p></div>
<div class="p-6 bg-slate-700/50 rounded-2xl border border-slate-600 flex flex-col items-center text-center"><div class="text-4xl mb-4 text-yellow-400">🎨</div><h3 class="text-white font-bold text-xl mb-2">Hintergrund</h3><p class="text-slate-300 leading-snug">Wähle oben eine Farbe für deinen Comic aus!</p></div>
</div>
<button onclick="toggleInfo(); playSound('click')" class="mt-8 w-full bg-yellow-500 hover:bg-yellow-400 text-black 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>
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) { if (_soundCache[id]) { const s = _soundCache[id].cloneNode(); s.volume = 1.0; s.currentTime = 0; s.play().catch(e => console.warn(e)); } };
window.goHome = function() { playSound('click'); setTimeout(() => { window.location.href = '../index.html'; }, 300); };
const video = document.getElementById('webcam'); let currentFacingMode = 'user'; let currentStream; let idleTimer; let currentBgColor = '#facc15';
function resetIdleTimer() { clearTimeout(idleTimer); idleTimer = setTimeout(() => { window.location.href = '../index.html'; }, 120000); }
['mousedown', 'touchstart', 'scroll', 'keydown', 'input'].forEach(evt => document.addEventListener(evt, resetIdleTimer, {passive: true})); resetIdleTimer();
function toggleInfo() { 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'); } }
async function startCamera() { try { if (currentStream) { currentStream.getTracks().forEach(track => track.stop()); } const stream = await navigator.mediaDevices.getUserMedia({ video: { width: { ideal: 1280 }, height: { ideal: 720 }, facingMode: currentFacingMode } }); currentStream = stream; video.srcObject = stream; } catch (err) { alert("Kamera Fehler: " + err.message); } }
function switchCamera() { currentFacingMode = (currentFacingMode === 'user') ? 'environment' : 'user'; startCamera(); }
function snap(panelIndex) { playSound('shutter'); const panel = document.getElementById('panel-' + panelIndex); const canvas = panel.querySelector('canvas'); const ctx = canvas.getContext('2d'); canvas.width = 1280; canvas.height = 720; const vRatio = video.videoWidth / video.videoHeight; const cRatio = canvas.width / canvas.height; let sx, sy, sWidth, sHeight; if (vRatio > cRatio) { sHeight = video.videoHeight; sWidth = sHeight * cRatio; sx = (video.videoWidth - sWidth) / 2; sy = 0; } else { sWidth = video.videoWidth; sHeight = sWidth / cRatio; sx = 0; sy = (video.videoHeight - sHeight) / 2; } ctx.save(); if(currentFacingMode === 'user') { ctx.scale(-1, 1); ctx.drawImage(video, sx, sy, sWidth, sHeight, -canvas.width, 0, canvas.width, canvas.height); } else { ctx.drawImage(video, sx, sy, sWidth, sHeight, 0, 0, canvas.width, canvas.height); } ctx.restore(); panel.style.borderColor = '#3b82f6'; setTimeout(() => panel.style.borderColor = 'black', 200); }
function changeBg(color) { playSound('click'); currentBgColor = color; document.body.style.backgroundColor = color; }
function clearText(el, defaultText) { if (el.innerText.trim() === defaultText) el.innerText = ""; }
function resetText(el, defaultText) { if (el.innerText.trim() === "") el.innerText = defaultText; }
const handles = document.querySelectorAll('.bubble-handle'); let activeDrag = null; let startX, startY, initialLeft, initialTop;
handles.forEach(handle => { handle.addEventListener('mousedown', startDrag); handle.addEventListener('touchstart', startDrag, {passive: false}); });
function startDrag(e) { e.preventDefault(); activeDrag = e.target.parentElement; const clientX = e.clientX || e.touches[0].clientX; const clientY = e.clientY || e.touches[0].clientY; startX = clientX; startY = clientY; initialLeft = activeDrag.offsetLeft; initialTop = activeDrag.offsetTop; document.addEventListener('mousemove', doDrag); document.addEventListener('mouseup', stopDrag); document.addEventListener('touchmove', doDrag, {passive: false}); document.addEventListener('touchend', stopDrag); }
function doDrag(e) { if (!activeDrag) return; e.preventDefault(); const clientX = e.clientX || e.touches[0].clientX; const clientY = e.clientY || e.touches[0].clientY; const dx = clientX - startX; const dy = clientY - startY; activeDrag.style.left = (initialLeft + dx) + 'px'; activeDrag.style.top = (initialTop + dy) + 'px'; activeDrag.style.transform = 'none'; }
function stopDrag() { activeDrag = null; document.removeEventListener('mousemove', doDrag); document.removeEventListener('mouseup', stopDrag); document.removeEventListener('touchmove', doDrag); document.removeEventListener('touchend', stopDrag); }
function generateComicImage() { const refPanel = document.getElementById('panel-0'); const refRect = refPanel.getBoundingClientRect(); const screenRatio = refRect.width / refRect.height; const panelW = 1280; const panelH = panelW / screenRatio; const border = 60; const gap = 40; const titleSpace = 250; const master = document.createElement('canvas'); master.width = (panelW * 3) + (gap * 2) + (border * 2); master.height = panelH + (border * 2) + titleSpace; const ctx = master.getContext('2d'); ctx.fillStyle = currentBgColor; ctx.fillRect(0, 0, master.width, master.height); ctx.fillStyle = 'black'; ctx.font = '900 120px "Comic Sans MS", sans-serif'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText("MEINE FOTO-STORY", master.width / 2, titleSpace / 2); [0, 1, 2].forEach(i => { const panelDiv = document.getElementById('panel-' + i); const srcCanvas = panelDiv.querySelector('canvas'); const textDiv = panelDiv.querySelector('.bubble-text'); const panelOffsetX = border + (i * (panelW + gap)); const panelOffsetY = titleSpace; ctx.fillStyle = '#eee'; ctx.fillRect(panelOffsetX, panelOffsetY, panelW, panelH); if (srcCanvas.width > 0) { const srcW = srcCanvas.width; const srcH = srcCanvas.height; const srcRatio = srcW / srcH; let sW, sH, sX, sY; if (srcRatio > screenRatio) { sH = srcH; sW = srcH * screenRatio; sX = (srcW - sW) / 2; sY = 0; } else { sW = srcW; sH = srcW / screenRatio; sX = 0; sY = (srcH - sH) / 2; } ctx.drawImage(srcCanvas, sX, sY, sW, sH, panelOffsetX, panelOffsetY, panelW, panelH); } ctx.lineWidth = 20; ctx.strokeStyle = 'black'; ctx.strokeRect(panelOffsetX, panelOffsetY, panelW, panelH); ctx.fillStyle = 'black'; ctx.fillRect(panelOffsetX, panelOffsetY, 120, 120); ctx.fillStyle = 'white'; ctx.font = 'bold 80px sans-serif'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText(i + 1, panelOffsetX + 60, panelOffsetY + 65); const text = textDiv.innerText.trim(); if (text) { const domPanelRect = panelDiv.getBoundingClientRect(); const domBubbleRect = textDiv.getBoundingClientRect(); const scaleX = panelW / domPanelRect.width; const scaleY = panelH / domPanelRect.height; const relX = domBubbleRect.left - domPanelRect.left; const relY = domBubbleRect.top - domPanelRect.top; const bubbleX = panelOffsetX + (relX * scaleX); const bubbleY = panelOffsetY + (relY * scaleY); drawRoundedRectBubble(ctx, bubbleX, bubbleY, text); } }); return master.toDataURL('image/png'); }
function drawRoundedRectBubble(ctx, x, y, text) { ctx.font = 'bold 50px "Comic Sans MS", sans-serif'; const padding = 50; const lineHeight = 60; const maxWidth = 500; const words = text.split(' '); let line = ''; let lines = []; for(let n = 0; n < words.length; n++) { let testLine = line + words[n] + ' '; let metrics = ctx.measureText(testLine); if (metrics.width > maxWidth && n > 0) { lines.push(line); line = words[n] + ' '; } else { line = testLine; } } lines.push(line); let maxLineWidth = 0; lines.forEach(l => { const m = ctx.measureText(l); if(m.width > maxLineWidth) maxLineWidth = m.width; }); const boxW = maxLineWidth + (padding * 2); const boxH = (lines.length * lineHeight) + (padding * 2); ctx.fillStyle = 'rgba(0,0,0,0.2)'; roundRect(ctx, x + 15, y + 15, boxW, boxH, 40, true, false); ctx.fillStyle = 'white'; ctx.strokeStyle = 'black'; ctx.lineWidth = 10; roundRect(ctx, x, y, boxW, boxH, 40, true, true); ctx.fillStyle = 'black'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; let ty = y + padding + (lineHeight/2); const centerX = x + (boxW/2); lines.forEach(l => { ctx.fillText(l.trim(), centerX, ty); ty += lineHeight; }); }
function roundRect(ctx, x, y, w, h, r, fill, stroke) { ctx.beginPath(); ctx.moveTo(x+r, y); ctx.arcTo(x+w, y, x+w, y+h, r); ctx.arcTo(x+w, y+h, x, y+h, r); ctx.arcTo(x, y+h, x, y, r); ctx.arcTo(x, y, x+w, y, r); ctx.closePath(); if (fill) ctx.fill(); if (stroke) ctx.stroke(); }
function downloadComic() { playSound('success'); const dataUrl = generateComicImage(); const link = document.createElement('a'); link.download = 'mein-comic.png'; link.href = dataUrl; link.click(); }
window.printComic = async function() { const btn = document.getElementById('btn-print'); const oldText = btn ? btn.innerText : "🖨️ DRUCKEN"; if(btn) { btn.innerText = "⏳ Sende..."; btn.disabled = true; } const dataUrl = generateComicImage(); try { if (window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.DirectPrinter) { await window.Capacitor.Plugins.DirectPrinter.printBase64({ data: dataUrl, name: 'Comic-Studio' }); if(btn) btn.innerText = "✅ Gedruckt!"; playSound('success'); } else { console.warn("DirectPrinter Plugin nicht gefunden, nutze Browser Print."); const printImg = document.getElementById('print-img'); printImg.src = dataUrl; printImg.onload = () => { setTimeout(() => { window.print(); }, 500); }; if(btn) btn.innerText = "✅ OK"; playSound('success'); } } 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.onload = startCamera;
</script>
</body>
</html>

137
public/apps/gif.html Normal file
View File

@@ -0,0 +1,137 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>Loop Cam</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>
<style>
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
html { width: 100%; height: 100%; background-color: #0f172a; }
body {
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;
overscroll-behavior: none;
font-family: sans-serif;
animation: fadeIn 0.4s ease-out;
box-sizing: border-box;
padding-top: calc(10px + env(safe-area-inset-top));
padding-bottom: max(20px, env(safe-area-inset-bottom));
}
video, canvas { display: block; }
.no-scrollbar::-webkit-scrollbar { display: none; }
button:active { transform: scale(0.95); transition: transform 0.1s; }
</style>
</head>
<body class="flex flex-col p-2 md:p-4"
onclick="if(window.resetIdleTimer) window.resetIdleTimer()"
ontouchstart="if(window.resetIdleTimer) window.resetIdleTimer()">
<div class="flex-none flex justify-between items-center z-50 mb-2 relative h-16">
<button onclick="goHome()" class="bg-slate-800 text-white border-4 border-slate-600 hover:border-white px-4 py-2 rounded-full text-lg font-black shadow-xl flex items-center gap-2 transition-transform active:scale-95 no-underline focus:outline-none shrink-0 z-50">
🏠 MENÜ
</button>
<div class="absolute inset-0 flex items-center justify-center pointer-events-none z-0">
<h1 class="text-2xl md:text-4xl font-black text-white drop-shadow-[0_4px_4px_rgba(0,0,0,0.8)] tracking-widest uppercase">
✨ LOOP CAM
</h1>
</div>
<button onclick="toggleInfo(); playSound('click')" class="w-12 h-12 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-xl shrink-0 z-50">
?
</button>
</div>
<div class="flex-1 flex flex-row gap-2 md:gap-4 min-h-0 items-center justify-center w-full max-w-[95rem] mx-auto overflow-hidden pb-1">
<div class="w-20 md:w-24 flex flex-col gap-2 bg-gray-900/50 p-2 rounded-2xl border border-white/10 shadow-2xl shrink-0 h-full max-h-full overflow-hidden">
<div class="text-[8px] font-bold text-gray-500 uppercase tracking-widest text-center w-full py-1 shrink-0">Filter</div>
<div class="flex-1 overflow-y-auto no-scrollbar flex flex-col items-center justify-evenly w-full pb-1">
<button onclick="setFilter('')" class="w-16 h-12 rounded-xl bg-white text-black font-black text-[10px] hover:scale-105 active:scale-95 transition border-2 border-transparent flex flex-col items-center justify-center"><span></span> NORM</button>
<button onclick="setFilter('grayscale(100%) contrast(1.2)')" class="w-16 h-12 rounded-xl bg-gray-600 text-white font-black text-[10px] hover:scale-105 active:scale-95 transition border-2 border-white/20 flex flex-col items-center justify-center" style="filter: grayscale(100%)"><span></span> S/W</button>
<button onclick="setFilter('sepia(80%)')" class="w-16 h-12 rounded-xl bg-amber-700 text-white font-black text-[10px] hover:scale-105 active:scale-95 transition border-2 border-white/20 flex flex-col items-center justify-center" style="filter: sepia(80%)"><span>📜</span> ALT</button>
<button onclick="setFilter('invert(100%)')" class="w-16 h-12 rounded-xl bg-blue-600 text-white font-black text-[10px] hover:scale-105 active:scale-95 transition border-2 border-white/20 flex flex-col items-center justify-center" style="filter: invert(100%)"><span>💀</span> XRAY</button>
<button onclick="setFilter('saturate(250%)')" class="w-16 h-12 rounded-xl bg-pink-500 text-white font-black text-[10px] hover:scale-105 active:scale-95 transition border-2 border-white/20 flex flex-col items-center justify-center" style="filter: saturate(250%)"><span>🎨</span> POP</button>
<button onclick="setFilter('hue-rotate(90deg)')" class="w-16 h-12 rounded-xl bg-emerald-500 text-white font-black text-[10px] hover:scale-105 active:scale-95 transition border-2 border-white/20 flex flex-col items-center justify-center" style="filter: hue-rotate(90deg)"><span>👽</span> ALIEN</button>
</div>
</div>
<div class="flex-1 flex flex-col items-center h-full min-w-0 gap-2 md:gap-4 overflow-hidden">
<div id="video-container" class="relative flex-1 w-full bg-gray-900 rounded-[1.5rem] overflow-hidden border-[6px] border-gray-800 shadow-2xl group transition-colors duration-200 flex items-center justify-center min-h-0">
<video id="video" autoplay playsinline muted class="w-full h-full object-contain"></video>
<canvas id="canvas" class="w-full h-full object-contain hidden"></canvas>
<div id="countdown" class="absolute inset-0 flex items-center justify-center text-[8rem] font-black text-white hidden bg-black/50 backdrop-blur-sm z-30">3</div>
<div id="rec-indicator" class="absolute top-4 right-4 hidden items-center gap-2 z-30 bg-red-600 text-white px-3 py-1 rounded-full font-black text-sm animate-pulse shadow-lg"><div class="w-2 h-2 bg-white rounded-full"></div> REC</div>
<div id="saving-overlay" class="absolute inset-0 bg-black/80 z-50 hidden flex-col items-center justify-center text-white">
<div class="text-6xl animate-spin mb-4">💾</div>
<div class="text-2xl font-bold">Speichere...</div>
</div>
</div>
<div class="h-16 md:h-20 w-full flex-shrink-0 flex items-center justify-center relative">
<div id="start-overlay" class="absolute w-full flex justify-center items-center gap-4 z-20 pointer-events-none">
<button onclick="switchCamera(); playSound('click')" id="switch-btn" class="pointer-events-auto w-14 h-14 bg-slate-700 hover:bg-slate-600 text-white rounded-xl shadow-xl border-b-4 border-slate-900 active:border-b-0 active:translate-y-2 transition-all flex items-center justify-center shrink-0" title="Kamera wechseln"><span class="text-2xl">🔄</span></button>
<button onclick="playSound('click'); startCountdown()" class="pointer-events-auto bg-sky-500 hover:bg-sky-400 text-white text-2xl font-black py-3 px-8 rounded-xl shadow-2xl border-b-4 border-sky-700 active:border-b-0 active:translate-y-2 transition-all w-full max-w-xs flex items-center justify-center gap-2"><span>🎥</span> ACTION!</button>
</div>
<div id="action-buttons" class="hidden gap-2 md:gap-4 w-full justify-center z-20 pointer-events-none">
<button onclick="reset(); playSound('click')" class="pointer-events-auto bg-yellow-500 hover:bg-yellow-400 text-black text-xl font-black py-3 px-6 rounded-xl border-b-4 border-yellow-700 active:border-b-0 active:translate-y-2 transition-all shadow-xl flex items-center justify-center gap-2 flex-1 max-w-[150px]">🔄 NOCHMAL</button>
<button onclick="saveLoopVideo(); playSound('success')" class="pointer-events-auto bg-green-600 hover:bg-green-500 text-white text-xl font-black py-3 px-6 rounded-xl border-b-4 border-green-800 active:border-b-0 active:translate-y-2 transition-all shadow-xl flex items-center justify-center gap-2 flex-1 max-w-[150px]">💾 SPEICHERN</button>
</div>
</div>
</div>
<div class="w-20 md:w-24 flex-shrink-0 flex flex-col justify-center gap-2 z-40 bg-gray-900/50 p-2 rounded-2xl border border-white/10 shadow-2xl h-full max-h-full overflow-hidden transition-opacity opacity-30 pointer-events-none" id="speed-controls">
<div class="text-center text-[8px] font-bold text-gray-400 mb-1 uppercase tracking-widest shrink-0">Tempo</div>
<div class="flex-1 flex flex-col justify-center gap-2">
<button id="btn-slow" onclick="setSpeed(150)" class="w-full h-16 bg-green-700 hover:bg-green-600 text-white rounded-lg shadow-lg border-b-2 border-green-900 transition active:scale-95 flex flex-col items-center justify-center gap-1 opacity-50"><span class="text-xl">🐢</span><span class="text-[8px] font-black uppercase">Langsam</span></button>
<button id="btn-norm" onclick="setSpeed(40)" class="w-full h-16 bg-blue-700 hover:bg-blue-600 text-white rounded-lg shadow-lg border-b-2 border-blue-900 transition active:scale-95 flex flex-col items-center justify-center gap-1 opacity-50"><span class="text-xl">🚶</span><span class="text-[8px] font-black uppercase">Mittel</span></button>
<button id="btn-turbo" onclick="setSpeed(10)" class="w-full h-16 bg-red-700 hover:bg-red-600 text-white rounded-lg shadow-lg border-b-2 border-red-900 transition active:scale-95 flex flex-col items-center justify-center gap-1 opacity-50"><span class="text-xl">🚀</span><span class="text-[8px] font-black uppercase">Turbo</span></button>
</div>
</div>
</div>
<div id="info-modal" class="hidden fixed inset-0 z-[200] bg-black/80 backdrop-blur-sm items-center justify-center p-4" onclick="toggleInfo()">
<div class="bg-slate-800 border-4 border-sky-500 rounded-[2rem] max-w-2xl w-full p-8 shadow-2xl relative" onclick="event.stopPropagation()">
<button onclick="toggleInfo(); playSound('click')" class="absolute top-6 right-6 text-white hover:text-sky-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 links einen <b>Filter</b>.</p>
<p>2. Drücke unten auf <b>ACTION!</b>.</p>
<p>3. Nach der Aufnahme kannst du rechts das <b>Tempo</b> ändern.</p>
<p>4. Mit <b>🔄</b> wechselst du die Kamera.</p>
</div>
<button onclick="toggleInfo(); playSound('click')" class="mt-8 w-full bg-sky-600 hover:bg-sky-500 text-white font-black py-4 rounded-xl text-xl transition">ALLES KLAR!</button>
</div>
</div>
<script>
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) { if(_soundCache[id]) { const s = _soundCache[id].cloneNode(); s.volume = 1.0; s.play().catch(e=>{}); } };
window.goHome = function() { playSound('click'); setTimeout(() => { window.location.href = '../index.html'; }, 300); };
let idleTimer;
window.resetIdleTimer = function() { clearTimeout(idleTimer); idleTimer = setTimeout(() => { if (document.getElementById('countdown').classList.contains('hidden') && document.getElementById('saving-overlay').classList.contains('hidden')) { window.location.href = '../index.html'; } else { window.resetIdleTimer(); } }, 120000); };
['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'); modal.classList.toggle('hidden'); modal.classList.toggle('flex'); }
let video, canvas, ctx, videoContainer, recIndicator, startBtn, resultBtns, speedWrapper, switchBtn; let frames = []; let speed = 40; let loopInterval; let currentStream; let currentFacingMode = 'user';
window.initCam = async function() { video = document.getElementById('video'); canvas = document.getElementById('canvas'); ctx = canvas.getContext('2d'); videoContainer = document.getElementById('video-container'); recIndicator = document.getElementById('rec-indicator'); startBtn = document.getElementById('start-overlay'); resultBtns = document.getElementById('action-buttons'); speedWrapper = document.getElementById('speed-controls'); switchBtn = document.getElementById('switch-btn'); if(!video) return; try { if (currentStream && typeof currentStream.getTracks === 'function') currentStream.getTracks().forEach(track => track.stop()); const stream = await navigator.mediaDevices.getUserMedia({ video: { width: {ideal: 1280}, height: {ideal: 720}, facingMode: currentFacingMode } }); currentStream = stream; video.srcObject = stream; video.onloadedmetadata = () => { video.play(); }; video.style.transform = (currentFacingMode === 'user') ? 'scaleX(-1)' : 'scaleX(1)'; } catch (err) { alert("Kamera-Fehler: " + err.message); } }
window.switchCamera = function() { currentFacingMode = (currentFacingMode === 'user') ? 'environment' : 'user'; window.initCam(); }
window.setFilter = function(filter) { playSound('click'); if(video) video.style.filter = filter; if(canvas) canvas.style.filter = filter; }
window.startCountdown = function() { startBtn.classList.add('hidden'); switchBtn.classList.add('hidden'); const cd = document.getElementById('countdown'); cd.classList.remove('hidden'); cd.style.display = 'flex'; let count = 3; cd.innerText = count; const timer = setInterval(() => { count--; if (count > 0) { cd.innerText = count; } else { clearInterval(timer); cd.style.display = 'none'; playSound('shutter'); record(); } }, 800); }
function record() { frames = []; canvas.width = video.videoWidth; canvas.height = video.videoHeight; videoContainer.classList.replace('border-gray-800', 'border-red-600'); recIndicator.classList.remove('hidden'); recIndicator.style.display = 'flex'; let startTime = Date.now(); function captureLoop() { if (Date.now() - startTime < 2000) { ctx.save(); if (currentFacingMode === 'user') { ctx.translate(canvas.width, 0); ctx.scale(-1, 1); } ctx.drawImage(video, 0, 0, canvas.width, canvas.height); ctx.restore(); frames.push(ctx.getImageData(0, 0, canvas.width, canvas.height)); requestAnimationFrame(captureLoop); } else { play(); } } captureLoop(); }
function play() { videoContainer.classList.replace('border-red-600', 'border-gray-800'); recIndicator.classList.add('hidden'); recIndicator.style.display = ''; video.classList.add('hidden'); canvas.classList.remove('hidden'); resultBtns.classList.remove('hidden'); resultBtns.style.display = 'flex'; speedWrapper.classList.remove('opacity-30', 'pointer-events-none'); window.setSpeed(40); const loopFrames = [...frames, ...[...frames].reverse()]; startLoop(loopFrames, 0); }
function startLoop(loopFrames, i) { clearInterval(loopInterval); loopInterval = setInterval(() => { ctx.putImageData(loopFrames[i], 0, 0); i = (i + 1) % loopFrames.length; }, speed); }
window.setSpeed = function(ms) { playSound('click'); speed = ms; updateSpeedUI(); if (canvas && !canvas.classList.contains('hidden')) { const loopFrames = [...frames, ...[...frames].reverse()]; startLoop(loopFrames, 0); } }
function updateSpeedUI() { const btns = { 150: document.getElementById('btn-slow'), 40: document.getElementById('btn-norm'), 10: document.getElementById('btn-turbo') }; Object.values(btns).forEach(btn => { if(btn) { btn.classList.remove('opacity-100', 'scale-105', 'border-white'); btn.classList.add('opacity-50', 'border-transparent'); } }); const active = btns[speed]; if(active) { active.classList.remove('opacity-50', 'border-transparent'); active.classList.add('opacity-100', 'scale-105', 'border-white'); } }
window.saveLoopVideo = function() { document.getElementById('saving-overlay').classList.remove('hidden'); document.getElementById('saving-overlay').style.display = 'flex'; const stream = canvas.captureStream(30); const recorder = new MediaRecorder(stream, { mimeType: 'video/webm' }); const chunks = []; recorder.ondataavailable = e => chunks.push(e.data); recorder.onstop = () => { const blob = new Blob(chunks, { type: 'video/webm' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'mein-loop.webm'; a.click(); document.getElementById('saving-overlay').classList.add('hidden'); document.getElementById('saving-overlay').style.display = ''; }; recorder.start(); setTimeout(() => recorder.stop(), 3000); }
window.reset = function() { clearInterval(loopInterval); video.classList.remove('hidden'); canvas.classList.add('hidden'); startBtn.classList.remove('hidden'); resultBtns.classList.add('hidden'); resultBtns.style.display = 'none'; speedWrapper.classList.add('opacity-30', 'pointer-events-none'); switchBtn.classList.remove('hidden'); window.setSpeed(40); videoContainer.classList.replace('border-red-600', 'border-gray-800'); recIndicator.classList.add('hidden'); recIndicator.style.display = ''; }
window.addEventListener('beforeunload', () => { try { if(currentStream) currentStream.getTracks().forEach(track => track.stop()); } catch(e) {} });
window.onload = window.initCam;
</script>
</body>
</html>

276
public/apps/magic.html Normal file
View 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>

258
public/apps/magic_old.html Normal file
View File

@@ -0,0 +1,258 @@
<!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>

154
public/apps/news.html Normal file
View File

@@ -0,0 +1,154 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>News Studio</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>
<style>
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
html { width: 100%; height: 100%; background-color: #0f172a; }
body {
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: manipulation;
font-family: sans-serif;
animation: fadeIn 0.4s ease-out;
box-sizing: border-box;
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; }
.animate-blink { animation: blink 1.5s infinite; }
@keyframes blink { 0% { opacity: 1; } 50% { opacity: 0; } 100% { opacity: 1; } }
.ticker-wrap { width: 100%; overflow: hidden; background-color: #fbbf24; color: black; font-weight: 900; white-space: nowrap; box-sizing: border-box; }
.ticker { display: inline-block; padding-left: 100%; animation: ticker 25s linear infinite; }
@keyframes ticker { 0% { transform: translate3d(0, 0, 0); } 100% { transform: translate3d(-100%, 0, 0); } }
.editable-input { background: transparent; border: none; color: inherit; width: 100%; text-align: center; outline: none; font-family: inherit; text-transform: uppercase; text-overflow: ellipsis; }
.editable-input:focus { background: rgba(255,255,255,0.1); border-radius: 4px; }
</style>
</head>
<body class="flex flex-col"
onclick="if(window.resetIdleTimer) window.resetIdleTimer()"
ontouchstart="if(window.resetIdleTimer) window.resetIdleTimer()">
<div class="flex-none flex justify-between items-center z-50 px-4 relative bg-slate-900 shadow-md h-16">
<button onclick="goHome()" class="bg-slate-800 text-white border-4 border-slate-600 hover:border-white px-4 py-2 rounded-full text-lg font-black shadow-xl flex items-center gap-2 transition-transform active:scale-95 no-underline focus:outline-none shrink-0 z-50">
🏠 MENÜ
</button>
<div class="absolute inset-0 flex items-center justify-center pointer-events-none z-0">
<h1 class="text-2xl md:text-4xl font-black text-blue-400 font-sans tracking-tighter drop-shadow-[0_4px_4px_rgba(0,0,0,0.8)] uppercase italic transform -skew-x-6">
📰 NEWS
</h1>
</div>
<button onclick="toggleInfo(); playSound('click')" class="w-12 h-12 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-xl shrink-0 z-50">
?
</button>
</div>
<div class="flex-1 relative w-full overflow-hidden bg-black shadow-inner">
<div class="absolute inset-0 z-0">
<video id="video" autoplay playsinline muted class="w-full h-full object-cover opacity-90"></video>
<canvas id="canvas" class="hidden"></canvas>
</div>
<div class="absolute inset-0 z-20 flex flex-col justify-between pointer-events-none pb-4">
<div class="w-full flex justify-end p-4 md:p-6">
<div class="flex items-center gap-2 bg-red-600/90 px-4 py-2 rounded-lg shadow-lg border-2 border-red-400 pointer-events-auto transform rotate-1 hover:rotate-0 transition-transform">
<div class="w-3 h-3 md:w-4 md:h-4 bg-white rounded-full animate-blink"></div>
<span class="font-black text-lg md:text-xl tracking-widest text-white">LIVE</span>
</div>
</div>
<div class="flex flex-col w-full pointer-events-auto items-center">
<div class="w-[90%] md:max-w-5xl bg-blue-800/95 border-l-8 border-blue-500 p-2 md:p-4 mb-2 shadow-2xl transform -skew-x-6 relative group hover:bg-blue-700/95 transition-colors">
<div class="text-blue-300 text-xs md:text-sm font-bold uppercase tracking-[0.2em] mb-1 ml-2 not-italic transform skew-x-6">Breaking News</div>
<input type="text" id="headline-input" value="SENSATION IM JUGENDZENTRUM!" class="editable-input text-2xl md:text-4xl font-black text-white drop-shadow-md transform skew-x-6 placeholder-blue-300">
</div>
<div class="self-start ml-4 md:ml-16 w-auto max-w-[80%] bg-red-600/95 text-white font-bold px-6 py-2 transform -skew-x-6 shadow-xl text-lg md:text-xl border-l-4 border-red-300 mb-4 hover:bg-red-500/95 transition-colors">
<input type="text" value="🔴 VOR ORT: MEDIENSTATION" class="editable-input w-full text-left transform skew-x-6">
</div>
<div class="ticker-wrap bg-yellow-400 py-2 md:py-3 border-y-4 border-black shadow-xl mb-4 md:mb-6">
<div class="ticker text-xl md:text-3xl text-black">
+++ WETTER: ES REGNET KONFETTI +++ SCHULE FÄLLT HEUTE AUS +++ EISCREME FÜR ALLE +++ KATZE WIRD BÜRGERMEISTER +++ EXTRA BLATT +++
</div>
</div>
<div class="flex justify-center gap-4 md:gap-6 pb-2 md:pb-4 w-full">
<button onclick="switchCamera(); playSound('click')" class="bg-slate-800/90 hover:bg-slate-700 text-white w-14 h-14 md:w-16 md:h-16 rounded-full border-2 border-slate-500 shadow-xl backdrop-blur flex items-center justify-center active:scale-95 transition">
<span class="text-2xl md:text-3xl">🔄</span>
</button>
<button onclick="capturePreview()" class="bg-white hover:bg-gray-200 text-black font-black h-14 md:h-16 px-8 md:px-10 rounded-full border-b-8 border-gray-400 active:border-b-0 active:translate-y-2 shadow-2xl text-lg md:text-xl flex items-center gap-3 transition active:scale-95">
<span>📸</span> FOTO
</button>
</div>
</div>
</div>
</div>
<div id="preview-modal" class="hidden fixed inset-0 z-[100] bg-black/90 flex flex-col items-center justify-center p-4 backdrop-blur-md">
<button onclick="goHome()" class="absolute top-6 left-6 z-50 bg-blue-900/90 backdrop-blur border-4 border-blue-500 hover:border-white px-4 py-2 rounded-full text-lg font-black shadow-xl flex items-center gap-2 transition-transform active:scale-95 text-white">
🏠 MENÜ
</button>
<h2 class="text-white text-3xl font-black mb-4">DEIN FOTO</h2>
<div class="relative w-full max-w-4xl h-auto border-8 border-white shadow-2xl rounded-lg overflow-hidden bg-black mb-8">
<img id="preview-img" src="" class="w-full h-auto object-contain">
</div>
<div class="flex gap-6">
<button onclick="closePreview(); playSound('click')" class="bg-red-600 hover:bg-red-500 text-white font-black py-4 px-8 rounded-2xl text-xl shadow-lg border-b-8 border-red-800 active:border-b-0 active:translate-y-2 transition flex items-center gap-2">
🗑️ LÖSCHEN
</button>
<button id="btn-print-confirm" onclick="confirmPrint()" class="bg-green-600 hover:bg-green-500 text-white font-black py-4 px-12 rounded-2xl text-xl shadow-lg border-b-8 border-green-800 active:border-b-0 active:translate-y-2 transition flex items-center gap-2">
🖨️ DRUCKEN
</button>
</div>
</div>
<div id="info-modal" class="hidden fixed inset-0 z-[200] bg-black/80 backdrop-blur-sm items-center justify-center p-4" onclick="toggleInfo()">
<div class="bg-slate-800 border-4 border-blue-500 rounded-[2rem] max-w-lg w-full p-8 shadow-2xl relative" onclick="event.stopPropagation()">
<button onclick="toggleInfo(); playSound('click')" class="absolute top-6 right-6 text-white hover:text-blue-400 text-4xl font-bold transition"></button>
<h2 class="text-3xl font-black text-white mb-6 border-b border-slate-600 pb-4">📰 NEWS STUDIO</h2>
<ul class="text-slate-300 space-y-3 mb-8 list-disc pl-6 text-lg">
<li>Tippe auf die <b>blaue Schlagzeile</b> und schreibe deinen Text.</li>
<li>Tippe auf den <b>roten Ort</b>.</li>
<li>Drücke auf <b>FOTO</b> für eine Vorschau.</li>
<li>Wenn es dir gefällt, drücke auf <b>DRUCKEN</b>.</li>
</ul>
<button onclick="toggleInfo(); playSound('click')" class="w-full bg-blue-600 hover:bg-blue-500 text-white font-black py-4 rounded-xl text-xl transition">ALLES KLAR!</button>
</div>
</div>
<script>
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) { if(_soundCache[id]) { const s = _soundCache[id].cloneNode(); s.volume = 1.0; s.play().catch(e=>{}); } };
window.goHome = function() { playSound('click'); setTimeout(() => { window.location.href = '../index.html'; }, 300); };
let idleTimer;
window.resetIdleTimer = function() { clearTimeout(idleTimer); idleTimer = setTimeout(() => { window.location.href = '../index.html'; }, 120000); };
['mousedown', 'touchstart', 'scroll', 'keydown', 'input'].forEach(evt => document.addEventListener(evt, window.resetIdleTimer, {passive: true})); window.resetIdleTimer();
function toggleInfo() { const m = document.getElementById('info-modal'); m.classList.toggle('hidden'); m.classList.toggle('flex'); }
let video = document.getElementById('video'); let currentStream; let currentFacingMode = 'user';
async function initCam() { try { if (currentStream && currentStream.getTracks) currentStream.getTracks().forEach(track => track.stop()); const stream = await navigator.mediaDevices.getUserMedia({ video: { width: {ideal: 1280}, height: {ideal: 720}, facingMode: currentFacingMode } }); currentStream = stream; video.srcObject = stream; video.style.transform = (currentFacingMode === 'user') ? 'scaleX(-1)' : 'scaleX(1)'; } catch (err) { console.error("Kamera Fehler:", err); } }
function switchCamera() { currentFacingMode = (currentFacingMode === 'user') ? 'environment' : 'user'; initCam(); }
function capturePreview() { playSound('shutter'); const canvas = document.getElementById('canvas'); const ctx = canvas.getContext('2d'); canvas.width = video.videoWidth || 1280; canvas.height = video.videoHeight || 720; ctx.save(); if (currentFacingMode === 'user') { ctx.translate(canvas.width, 0); ctx.scale(-1, 1); } ctx.drawImage(video, 0, 0, canvas.width, canvas.height); ctx.restore(); const headline = document.getElementById('headline-input').value.toUpperCase(); ctx.fillStyle = "rgba(30, 64, 175, 0.95)"; const boxH = 140; const boxY = canvas.height - 220; const boxMargin = 60; const boxW = canvas.width - (boxMargin * 2); ctx.save(); ctx.translate(boxMargin, boxY); ctx.transform(1, 0, -0.1, 1, 0, 0); ctx.fillRect(0, 0, boxW, boxH); ctx.fillStyle = "#3b82f6"; ctx.fillRect(0, 0, 25, boxH); ctx.restore(); ctx.fillStyle = "white"; ctx.font = "900 55px sans-serif"; ctx.textAlign = "center"; ctx.fillText(headline, canvas.width / 2, boxY + 90, boxW - 100); ctx.fillStyle = "#93c5fd"; ctx.font = "bold 24px sans-serif"; ctx.textAlign = "left"; ctx.fillText("BREAKING NEWS", boxMargin + 40, boxY + 35); const redW = 550; const redY = boxY - 65; const redX = boxMargin + 40; ctx.save(); ctx.translate(redX, redY); ctx.transform(1, 0, -0.1, 1, 0, 0); ctx.fillStyle = "rgba(220, 38, 38, 0.95)"; ctx.fillRect(0, 0, redW, 60); ctx.fillStyle = "#fca5a5"; ctx.fillRect(0, 0, 10, 60); ctx.restore(); ctx.fillStyle = "white"; ctx.font = "bold 30px sans-serif"; ctx.textAlign = "left"; ctx.fillText("🔴 VOR ORT: MEDIENSTATION", redX + 30, redY + 40); ctx.fillStyle = "#fbbf24"; ctx.fillRect(0, canvas.height - 70, canvas.width, 70); ctx.fillStyle = "black"; ctx.font = "900 35px sans-serif"; ctx.textAlign = "left"; ctx.fillText("+++ WETTER: ES REGNET KONFETTI +++ SCHULE FÄLLT HEUTE AUS +++ EXTRA BLATT +++", 20, canvas.height - 22); ctx.fillStyle = "#dc2626"; ctx.fillRect(canvas.width - 160, 40, 120, 50); ctx.fillStyle = "white"; ctx.font = "900 30px sans-serif"; ctx.textAlign = "center"; ctx.fillText("LIVE", canvas.width - 100, 75); const dataUrl = canvas.toDataURL('image/png'); document.getElementById('preview-img').src = dataUrl; document.getElementById('preview-modal').classList.remove('hidden'); document.getElementById('preview-modal').classList.add('flex'); }
function closePreview() { document.getElementById('preview-modal').classList.add('hidden'); document.getElementById('preview-modal').classList.remove('flex'); }
async function confirmPrint() { const btn = document.getElementById('btn-print-confirm'); btn.innerText = "⏳ DRUCKE..."; const img = document.getElementById('preview-img'); const dataUrl = img.src; try { if (window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.DirectPrinter) { await window.Capacitor.Plugins.DirectPrinter.printBase64({ data: dataUrl, name: 'News-Flash' }); } else { const link = document.createElement('a'); link.download = 'breaking-news.png'; link.href = dataUrl; link.click(); } playSound('success'); setTimeout(() => { closePreview(); btn.innerText = "🖨️ DRUCKEN"; }, 1000); } catch(e) { console.error(e); alert("Druck-Fehler: " + e.message); btn.innerText = "❌ FEHLER"; } }
window.onload = initCam;
</script>
</body>
</html>

118
public/apps/pixel.html Normal file
View File

@@ -0,0 +1,118 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>Pixel Labor</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>
<style>
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
html { width: 100%; height: 100%; background-color: #0f172a; }
body {
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;
font-family: sans-serif;
animation: fadeIn 0.4s ease-out;
box-sizing: border-box;
padding-top: calc(10px + env(safe-area-inset-top));
padding-bottom: max(20px, env(safe-area-inset-bottom));
}
.pixel-grid { display: grid; grid-template-columns: repeat(16, 1fr); grid-template-rows: repeat(16, 1fr); gap: 1px; background: #334155; border: 8px solid #10b981; box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.7); border-radius: 1rem; overflow: hidden; touch-action: none; width: 100%; height: 100%; aspect-ratio: 1 / 1; }
.cell { background-color: white; cursor: pointer; transition: background-color 0.05s; }
.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: 90%; max-height: 90%; object-fit: contain; border: 4px solid black; image-rendering: pixelated; } }
</style>
</head>
<body class="flex flex-col p-4"
onclick="if(window.resetIdleTimer) window.resetIdleTimer()"
ontouchstart="if(window.resetIdleTimer) window.resetIdleTimer()">
<div class="flex-none flex justify-between items-center shrink-0 mb-2 px-2 relative z-50 h-16">
<button onclick="goHome()" class="bg-slate-800 text-white border-4 border-slate-600 hover:border-white px-6 py-2 rounded-full text-lg font-black shadow-xl flex items-center gap-2 transition-transform active:scale-95 no-underline focus:outline-none shrink-0 z-50">
🏠 MENÜ
</button>
<div class="absolute inset-0 flex items-center justify-center pointer-events-none z-0">
<h1 class="text-2xl md:text-4xl font-black text-emerald-400 font-mono tracking-wider drop-shadow-[0_4px_4px_rgba(0,0,0,0.8)] uppercase">
&lt;PIXEL&gt;
</h1>
</div>
<button onclick="toggleInfo(); playSound('click')" class="w-12 h-12 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-xl shrink-0 z-50">
?
</button>
</div>
<div class="flex-1 flex flex-col lg:flex-row items-center justify-center gap-4 min-h-0 w-full max-w-[95rem] mx-auto pb-1">
<div class="relative w-full max-w-[650px] flex-shrink-0 flex items-center justify-center p-1 min-h-0 h-full">
<div id="grid" class="pixel-grid max-h-full"></div>
</div>
<div class="bg-slate-800 p-3 lg:p-4 rounded-[1.5rem] border-4 border-slate-700 flex flex-row lg:flex-col gap-3 shadow-2xl w-full lg:w-auto h-auto lg:h-full justify-between overflow-x-auto lg:overflow-visible lg:overflow-y-auto no-scrollbar">
<div class="grid grid-cols-4 lg:grid-cols-2 gap-2 flex-shrink-0 justify-items-center" id="palette">
<div class="color-btn w-12 h-12 lg:w-14 lg:h-14 rounded-xl bg-black border-4 border-slate-600 cursor-pointer active:scale-90 transition ring-4 ring-white shadow-lg" data-color="#000000" onclick="setColor(this)"></div>
<div class="color-btn w-12 h-12 lg:w-14 lg:h-14 rounded-xl bg-white border-4 border-slate-600 cursor-pointer active:scale-90 transition shadow-lg" data-color="#ffffff" onclick="setColor(this)"></div>
<div class="color-btn w-12 h-12 lg:w-14 lg:h-14 rounded-xl bg-red-500 border-4 border-slate-600 cursor-pointer active:scale-90 transition shadow-lg" data-color="#ef4444" onclick="setColor(this)"></div>
<div class="color-btn w-12 h-12 lg:w-14 lg:h-14 rounded-xl bg-blue-500 border-4 border-slate-600 cursor-pointer active:scale-90 transition shadow-lg" data-color="#3b82f6" onclick="setColor(this)"></div>
<div class="color-btn w-12 h-12 lg:w-14 lg:h-14 rounded-xl bg-green-500 border-4 border-slate-600 cursor-pointer active:scale-90 transition shadow-lg" data-color="#22c55e" onclick="setColor(this)"></div>
<div class="color-btn w-12 h-12 lg:w-14 lg:h-14 rounded-xl bg-yellow-400 border-4 border-slate-600 cursor-pointer active:scale-90 transition shadow-lg" data-color="#facc15" onclick="setColor(this)"></div>
<div class="color-btn w-12 h-12 lg:w-14 lg:h-14 rounded-xl bg-purple-500 border-4 border-slate-600 cursor-pointer active:scale-90 transition shadow-lg" data-color="#a855f7" onclick="setColor(this)"></div>
<div class="color-btn w-12 h-12 lg:w-14 lg:h-14 rounded-xl bg-slate-600 border-4 border-slate-500 cursor-pointer active:scale-90 transition flex items-center justify-center text-2xl shadow-lg" data-color="white" onclick="setColor(this)" title="Radierer">🧽</div>
</div>
<div class="h-1 w-full bg-slate-700 my-1 lg:block hidden rounded-full shrink-0"></div>
<div class="w-1 h-full bg-slate-700 mx-2 lg:hidden block rounded-full shrink-0"></div>
<div class="flex flex-col gap-2 w-full min-w-[160px] shrink-0">
<div class="flex gap-2">
<button onclick="downloadArt()" class="flex-1 bg-emerald-600 hover:bg-emerald-500 text-white py-3 px-2 rounded-xl font-black text-base shadow-lg active:translate-y-1 transition border-b-4 border-emerald-800 active:border-b-0 flex items-center justify-center gap-1">💾 SAVE</button>
<button id="btn-print" onclick="window.printArt(this)" class="flex-1 bg-white text-emerald-900 hover:bg-gray-200 py-3 px-2 rounded-xl font-black text-base shadow-lg active:translate-y-1 transition border-b-4 border-gray-300 active:border-b-0 flex items-center justify-center gap-1">🖨️ DRUCK</button>
</div>
<button onclick="clearGrid()" class="bg-slate-700 hover:bg-red-500/80 text-white py-3 px-4 rounded-xl font-bold transition text-base border-b-4 border-slate-900 active:border-b-0 active:translate-y-1 w-full flex items-center justify-center gap-2">🗑️ LÖSCHEN</button>
</div>
</div>
</div>
<div id="info-modal" class="hidden fixed inset-0 z-[200] bg-black/80 backdrop-blur-sm items-center justify-center p-4" onclick="toggleInfo()">
<div class="bg-slate-800 border-4 border-emerald-500 rounded-[2rem] max-w-3xl w-full p-8 shadow-2xl relative" onclick="event.stopPropagation()">
<button onclick="toggleInfo(); playSound('click')" class="absolute top-6 right-6 text-white hover:text-emerald-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="grid grid-cols-1 md:grid-cols-3 gap-6 text-base">
<div class="p-6 bg-slate-700/50 rounded-2xl border border-slate-600 flex flex-col items-center text-center"><div class="text-4xl mb-4">🎨</div><h3 class="text-emerald-400 font-bold text-xl mb-2">Malen</h3><p class="text-slate-300 leading-snug">Wähle rechts eine <b>Farbe</b> und tippe oder ziehe über die Quadrate.</p></div>
<div class="p-6 bg-slate-700/50 rounded-2xl border border-slate-600 flex flex-col items-center text-center"><div class="text-4xl mb-4">🧽</div><h3 class="text-white font-bold text-xl mb-2">Radieren</h3><p class="text-slate-300 leading-snug">Wähle den <b>Schwamm</b> oder drücke auf "LÖSCHEN", um von vorne zu beginnen.</p></div>
<div class="p-6 bg-slate-700/50 rounded-2xl border border-slate-600 flex flex-col items-center text-center"><div class="text-4xl mb-4">💾</div><h3 class="text-white font-bold text-xl mb-2">Fertig?</h3><p class="text-slate-300 leading-snug">Speichere dein Pixel-Kunstwerk oder drucke es direkt aus.</p></div>
</div>
<button onclick="toggleInfo(); playSound('click')" class="mt-8 w-full bg-emerald-600 hover:bg-emerald-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="" alt="Druckvorschau"></div>
<script>
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) { if (_soundCache[id]) { const s = _soundCache[id].cloneNode(); s.volume = 1.0; s.play().catch(e => console.warn(e)); } };
function playPaintSound() {}
window.goHome = function() { playSound('click'); setTimeout(() => { window.location.href = '../index.html'; }, 300); };
const grid = document.getElementById('grid'); let currentColor = '#000000'; let isDrawing = false; let idleTimer;
function resetIdleTimer() { clearTimeout(idleTimer); idleTimer = setTimeout(() => { window.location.href = '../index.html'; }, 90000); }
['mousedown', 'touchstart', 'scroll', 'keydown', 'input'].forEach(evt => document.addEventListener(evt, resetIdleTimer, {passive: true})); resetIdleTimer();
function toggleInfo() { const m = document.getElementById('info-modal'); m.classList.toggle('hidden'); m.classList.toggle('flex'); }
function initGrid() { grid.innerHTML = ''; for(let i=0; i<256; i++) { const cell = document.createElement('div'); cell.className = 'cell'; cell.addEventListener('mousedown', () => { isDrawing = true; cell.style.backgroundColor = currentColor; playPaintSound(); }); cell.addEventListener('mouseenter', () => { if(isDrawing) { cell.style.backgroundColor = currentColor; } }); cell.addEventListener('touchstart', (e) => { isDrawing = true; cell.style.backgroundColor = currentColor; playPaintSound(); }, {passive: false}); grid.appendChild(cell); } }
grid.addEventListener('touchmove', (e) => { e.preventDefault(); if(!isDrawing) return; const t = e.touches[0]; const el = document.elementFromPoint(t.clientX, t.clientY); if(el && el.classList.contains('cell')) { el.style.backgroundColor = currentColor; } }, {passive: false});
document.body.addEventListener('mouseup', () => isDrawing = false); document.body.addEventListener('touchend', () => isDrawing = false);
function setColor(btn) { playSound('click'); currentColor = btn.getAttribute('data-color'); document.querySelectorAll('.color-btn').forEach(b => b.classList.remove('ring-4', 'ring-white')); btn.classList.add('ring-4', 'ring-white'); }
function clearGrid() { playSound('click'); if(confirm("Wirklich alles löschen?")) { document.querySelectorAll('.cell').forEach(c => c.style.backgroundColor = 'white'); playSound('shutter'); } }
function generateCanvas() { const c = document.createElement('canvas'); const ctx = c.getContext('2d'); const s = 32; c.width = 16 * s; c.height = 16 * s; document.querySelectorAll('.cell').forEach((cell, i) => { const x = (i % 16) * s; const y = Math.floor(i / 16) * s; ctx.fillStyle = cell.style.backgroundColor || 'white'; ctx.fillRect(x, y, s, s); }); return c; }
function downloadArt() { playSound('success'); const c = generateCanvas(); const l = document.createElement('a'); l.download = 'mein-pixel-art.png'; l.href = c.toDataURL(); l.click(); }
window.printArt = async function(el) { playSound('click'); const btn = el || document.getElementById('btn-print'); let oldText = ""; if(btn) { oldText = btn.innerHTML; btn.innerText = "⏳ Sende..."; btn.disabled = true; } const c = generateCanvas(); const d = c.toDataURL(); try { if (window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.DirectPrinter) { await window.Capacitor.Plugins.DirectPrinter.printBase64({ data: d, name: 'Pixel-Art' }); if(btn) btn.innerText = "✅ Gedruckt!"; playSound('success'); } else { const img = document.getElementById('print-img'); img.src = d; setTimeout(() => { window.print(); }, 100); if(btn) btn.innerText = "✅ OK"; playSound('success'); } } catch (e) { alert("Fehler: " + e.message); if(btn) btn.innerText = "❌ Fehler"; } setTimeout(() => { if(btn) { btn.innerHTML = oldText || '🖨️ <span class="hidden xl:inline">DRUCK</span>'; btn.disabled = false; } }, 3000); }
window.onload = initGrid;
</script>
</body>
</html>

126
public/apps/rec.html Normal file
View File

@@ -0,0 +1,126 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>Mikro Check</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>
<style>
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
html { width: 100%; height: 100%; background-color: #0f172a; }
body {
position: fixed; top: 0; left: 0; right: 0; bottom: 0; margin: 0;
background-color: #0f172a;
color: white;
overflow: hidden;
user-select: none;
touch-action: manipulation;
animation: fadeIn 0.4s ease-out;
box-sizing: border-box;
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; }
.rec-active { animation: hardPulse 0.8s cubic-bezier(0.4, 0, 0.6, 1) infinite; background-color: #ef4444 !important; border-color: white !important; box-shadow: 0 0 30px rgba(239, 68, 68, 0.6) !important; }
@keyframes hardPulse { 0%, 100% { transform: scale(1); opacity: 1; } 50% { transform: scale(1.1); opacity: 0.9; } }
.playing { box-shadow: 0 0 20px #22c55e; border-color: #86efac !important; }
.effect-active { ring: 4px solid white; transform: scale(1.1); box-shadow: 0 0 15px rgba(255,255,255,0.4); z-index: 10; }
</style>
</head>
<body class="flex flex-col px-4"
onclick="if(window.resetIdleTimer) window.resetIdleTimer()"
ontouchstart="if(window.resetIdleTimer) window.resetIdleTimer()">
<div class="flex justify-between items-center mb-4 z-50 flex-none h-16">
<button onclick="goHome()" class="bg-slate-800 border-4 border-slate-600 hover:border-white px-4 py-2 rounded-full text-lg font-black shadow-xl flex items-center gap-2 transition-transform active:scale-95 text-white">
🏠 MENÜ
</button>
<button onclick="toggleInfo(); playSound('click')" class="w-12 h-12 bg-slate-800 text-white rounded-full font-bold border-4 border-slate-600 hover:border-white shadow-xl flex items-center justify-center text-xl">
?
</button>
</div>
<div class="flex-1 flex flex-col gap-4 relative w-full max-w-5xl mx-auto min-h-0">
<div class="relative w-full h-32 flex-none bg-slate-800 rounded-3xl border-4 border-slate-700 shadow-inner flex items-center justify-center overflow-hidden">
<canvas id="visualizer" class="w-full h-full"></canvas>
<div id="instruction-text" class="absolute text-slate-500 font-bold text-xl text-center px-4 pointer-events-none">
Mikrofon bereit... 🎤
</div>
</div>
<div class="flex-1 grid grid-cols-2 gap-4 min-h-0">
<div class="bg-slate-800 rounded-3xl border-4 border-slate-700 p-4 flex flex-col items-center justify-between shadow-lg relative overflow-hidden">
<div class="absolute top-0 left-0 bg-slate-700 px-4 py-1 rounded-br-xl font-black text-slate-400">SPUR 1</div>
<div id="timer-1" class="text-3xl md:text-4xl font-black font-mono text-slate-600 mt-2 transition-colors">00:00</div>
<div class="flex flex-col items-center gap-3 w-full">
<button id="btn-rec-1" onclick="toggleRecording(1)" class="w-20 h-20 md:w-24 md:h-24 rounded-full bg-slate-700 border-8 border-slate-600 shadow-xl flex items-center justify-center transition-all hover:bg-slate-600 group">
<div id="icon-rec-1" class="w-6 h-6 md:w-8 md:h-8 bg-red-500 rounded-full shadow-lg group-hover:scale-110 transition-transform"></div>
</button>
<div id="fx-box-1" class="hidden flex gap-2 w-full justify-center mt-2">
<button onclick="setEffect(1, 0.6)" data-rate="0.6" class="fx-btn-1 w-12 h-12 rounded-xl bg-purple-600 text-2xl shadow-md border-b-4 border-purple-800 active:border-b-0 active:translate-y-1 transition flex items-center justify-center" title="Monster">🧟</button>
<button onclick="setEffect(1, 1)" data-rate="1" class="fx-btn-1 w-12 h-12 rounded-xl bg-blue-600 text-2xl shadow-md border-b-4 border-blue-800 active:border-b-0 active:translate-y-1 transition flex items-center justify-center" title="Normal">🙂</button>
<button onclick="setEffect(1, 1.7)" data-rate="1.7" class="fx-btn-1 w-12 h-12 rounded-xl bg-yellow-500 text-2xl shadow-md border-b-4 border-yellow-700 active:border-b-0 active:translate-y-1 transition flex items-center justify-center" title="Maus">🐭</button>
</div>
<button id="btn-play-1" onclick="playTrack(1)" class="hidden w-full bg-green-600 hover:bg-green-500 text-white font-black py-2 rounded-xl text-base shadow-lg border-b-4 border-green-800 active:border-b-0 active:translate-y-1 transition-all flex items-center justify-center gap-2"><span>▶️</span> ANHÖREN</button>
<button id="btn-del-1" onclick="deleteTrack(1)" class="hidden text-slate-500 hover:text-red-400 text-xs font-bold uppercase tracking-wider py-1">Löschen</button>
</div>
</div>
<div class="bg-slate-800 rounded-3xl border-4 border-slate-700 p-4 flex flex-col items-center justify-between shadow-lg relative overflow-hidden">
<div class="absolute top-0 left-0 bg-slate-700 px-4 py-1 rounded-br-xl font-black text-slate-400">SPUR 2</div>
<div id="timer-2" class="text-3xl md:text-4xl font-black font-mono text-slate-600 mt-2 transition-colors">00:00</div>
<div class="flex flex-col items-center gap-3 w-full">
<button id="btn-rec-2" onclick="toggleRecording(2)" class="w-20 h-20 md:w-24 md:h-24 rounded-full bg-slate-700 border-8 border-slate-600 shadow-xl flex items-center justify-center transition-all hover:bg-slate-600 group">
<div id="icon-rec-2" class="w-6 h-6 md:w-8 md:h-8 bg-red-500 rounded-full shadow-lg group-hover:scale-110 transition-transform"></div>
</button>
<div id="fx-box-2" class="hidden flex gap-2 w-full justify-center mt-2">
<button onclick="setEffect(2, 0.6)" data-rate="0.6" class="fx-btn-2 w-12 h-12 rounded-xl bg-purple-600 text-2xl shadow-md border-b-4 border-purple-800 active:border-b-0 active:translate-y-1 transition flex items-center justify-center" title="Monster">🧟</button>
<button onclick="setEffect(2, 1)" data-rate="1" class="fx-btn-2 w-12 h-12 rounded-xl bg-blue-600 text-2xl shadow-md border-b-4 border-blue-800 active:border-b-0 active:translate-y-1 transition flex items-center justify-center" title="Normal">🙂</button>
<button onclick="setEffect(2, 1.7)" data-rate="1.7" class="fx-btn-2 w-12 h-12 rounded-xl bg-yellow-500 text-2xl shadow-md border-b-4 border-yellow-700 active:border-b-0 active:translate-y-1 transition flex items-center justify-center" title="Maus">🐭</button>
</div>
<button id="btn-play-2" onclick="playTrack(2)" class="hidden w-full bg-blue-600 hover:bg-blue-500 text-white font-black py-2 rounded-xl text-base shadow-lg border-b-4 border-blue-800 active:border-b-0 active:translate-y-1 transition-all flex items-center justify-center gap-2"><span>▶️</span> ANHÖREN</button>
<button id="btn-del-2" onclick="deleteTrack(2)" class="hidden text-slate-500 hover:text-red-400 text-xs font-bold uppercase tracking-wider py-1">Löschen</button>
</div>
</div>
</div>
</div>
<div id="info-modal" class="hidden fixed inset-0 z-[200] bg-black/80 backdrop-blur-sm items-center justify-center p-4" onclick="toggleInfo()">
<div class="bg-slate-800 border-4 border-red-500 rounded-[2rem] max-w-lg w-full p-8 shadow-2xl relative" onclick="event.stopPropagation()">
<button onclick="toggleInfo(); playSound('click')" class="absolute top-6 right-6 text-white hover:text-red-400 text-4xl font-bold transition"></button>
<h2 class="text-3xl font-black text-white mb-6 border-b border-slate-600 pb-4">🎙️ VOICE CHANGER</h2>
<ul class="text-slate-300 space-y-3 mb-8 list-disc pl-6 text-lg">
<li>Nimm links oder rechts etwas auf.</li>
<li>Drücke auf <b>Stopp</b>.</li>
<li>Jetzt erscheint Magie! ✨</li>
<li>Wähle: 🧟 <b>Monster</b> oder 🐭 <b>Maus</b>.</li>
<li>Hör es dir an!</li>
</ul>
<button onclick="toggleInfo(); playSound('click')" class="w-full bg-red-600 hover:bg-red-500 text-white font-black py-4 rounded-xl text-xl transition">ALLES KLAR!</button>
</div>
</div>
<script>
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) { if(_soundCache[id]) { const s = _soundCache[id].cloneNode(); s.volume = 1.0; s.play().catch(e=>{}); } };
window.goHome = function() { playSound('click'); setTimeout(() => { window.location.href = '../index.html'; }, 300); };
function toggleInfo() { const m = document.getElementById('info-modal'); m.classList.toggle('hidden'); m.classList.toggle('flex'); }
let micStream=null; let audioContext=null; let analyser=null; let dataArray=null; let gainNode=null;
const tracks = { 1: { isRecording: false, mediaRecorder: null, chunks: [], audio: new Audio(), hasData: false, timerInt: null, startTime: 0, playbackRate: 1, elementSource: null }, 2: { isRecording: false, mediaRecorder: null, chunks: [], audio: new Audio(), hasData: false, timerInt: null, startTime: 0, playbackRate: 1, elementSource: null } };
let idleTimer;
window.resetIdleTimer = function() { clearTimeout(idleTimer); const rec = tracks[1].isRecording || tracks[2].isRecording; if(!rec) { idleTimer = setTimeout(() => { window.location.href = '../index.html'; }, 120000); } };
['mousedown', 'touchstart', 'scroll', 'keydown', 'input'].forEach(evt => document.addEventListener(evt, window.resetIdleTimer, {passive: true})); window.resetIdleTimer();
async function initAudio() { try { if(!micStream) { micStream = await navigator.mediaDevices.getUserMedia({ audio: true }); audioContext = new (window.AudioContext || window.webkitAudioContext)(); gainNode = audioContext.createGain(); gainNode.gain.value = 1.5; gainNode.connect(audioContext.destination); analyser = audioContext.createAnalyser(); const s = audioContext.createMediaStreamSource(micStream); s.connect(analyser); analyser.fftSize = 256; dataArray = new Uint8Array(analyser.frequencyBinCount); drawVisualizer(); } if (audioContext && audioContext.state === 'suspended') await audioContext.resume(); } catch (e) { alert("Mikrofon Fehler: " + e.message); } }
function drawVisualizer() { const c = document.getElementById('visualizer'); if(!c) return; const ctx = c.getContext('2d'); c.width = c.offsetWidth; c.height = c.offsetHeight; function render() { requestAnimationFrame(render); if(!analyser) return; analyser.getByteFrequencyData(dataArray); ctx.fillStyle = '#1e293b'; ctx.fillRect(0, 0, c.width, c.height); const active = tracks[1].isRecording || tracks[2].isRecording; const alpha = active ? 1 : 0.3; let x = 0; const barW = (c.width / analyser.frequencyBinCount) * 2.5; for (let i = 0; i < analyser.frequencyBinCount; i++) { const h = dataArray[i] / 255 * c.height * 0.9; const r = active ? (h+50) : 50; const g = active ? 50 : (h+100); const b = active ? 50 : 200; ctx.fillStyle = `rgba(${r},${g},${b},${alpha})`; ctx.fillRect(x, c.height - h, barW, h); x += barW + 1; } } render(); }
async function toggleRecording(id) { await initAudio(); const t = tracks[id]; const btn = document.getElementById(`btn-rec-${id}`); const icon = document.getElementById(`icon-rec-${id}`); const playBtn = document.getElementById(`btn-play-${id}`); const delBtn = document.getElementById(`btn-del-${id}`); const fxBox = document.getElementById(`fx-box-${id}`); const timer = document.getElementById(`timer-${id}`); if (!t.isRecording) { playSound('shutter'); if(t.hasData) deleteTrack(id); t.mediaRecorder = new MediaRecorder(micStream); t.chunks = []; t.mediaRecorder.ondataavailable = e => t.chunks.push(e.data); t.mediaRecorder.onstop = () => { const b = new Blob(t.chunks, { type: 'audio/ogg; codecs=opus' }); t.audio.src = URL.createObjectURL(b); t.audio.preservesPitch = false; t.audio.mozPreservesPitch = false; t.audio.webkitPreservesPitch = false; t.hasData = true; playBtn.classList.remove('hidden'); delBtn.classList.remove('hidden'); fxBox.classList.remove('hidden'); fxBox.classList.add('flex'); setEffect(id, 1); playSound('success'); }; t.mediaRecorder.start(); t.isRecording = true; document.getElementById('instruction-text').classList.add('hidden'); btn.classList.add('rec-active'); icon.classList.replace('bg-red-500', 'bg-white'); timer.classList.replace('text-slate-600', 'text-white'); t.startTime = Date.now(); t.timerInt = setInterval(() => { const d = Math.floor((Date.now() - t.startTime) / 1000); timer.innerText = `${Math.floor(d/60).toString().padStart(2,'0')}:${(d%60).toString().padStart(2,'0')}`; }, 1000); } else { t.mediaRecorder.stop(); t.isRecording = false; clearInterval(t.timerInt); btn.classList.remove('rec-active'); icon.classList.replace('bg-white', 'bg-red-500'); timer.classList.replace('text-white', 'text-slate-600'); } }
function setEffect(id, rate) { playSound('click'); tracks[id].playbackRate = rate; document.querySelectorAll(`.fx-btn-${id}`).forEach(b => { b.classList.remove('effect-active', 'ring-4', 'ring-white'); if(b.getAttribute('data-rate') === rate.toString()) b.classList.add('effect-active', 'ring-4', 'ring-white'); }); }
function playTrack(id) { const t = tracks[id]; if(!t.hasData) return; playSound('click'); const btn = document.getElementById(`btn-play-${id}`); if (audioContext && !t.elementSource) { t.elementSource = audioContext.createMediaElementSource(t.audio); t.elementSource.connect(gainNode); } if (!t.audio.paused) { t.audio.pause(); t.audio.currentTime = 0; btn.classList.remove('playing'); btn.innerHTML = "<span>▶️</span> ANHÖREN"; } else { t.audio.playbackRate = t.playbackRate; t.audio.preservesPitch = false; if (audioContext.state === 'suspended') audioContext.resume(); t.audio.play(); btn.classList.add('playing'); btn.innerHTML = "<span>⏹</span> STOP"; t.audio.onended = () => { btn.classList.remove('playing'); btn.innerHTML = "<span>▶️</span> ANHÖREN"; }; } }
function deleteTrack(id) { playSound('click'); const t = tracks[id]; t.audio.pause(); t.chunks = []; t.hasData = false; document.getElementById(`btn-play-${id}`).classList.add('hidden'); document.getElementById(`btn-del-${id}`).classList.add('hidden'); document.getElementById(`fx-box-${id}`).classList.add('hidden'); document.getElementById(`fx-box-${id}`).classList.remove('flex'); document.getElementById(`timer-${id}`).innerText = "00:00"; }
</script>
</body>
</html>

161
public/apps/sound.html Normal file
View File

@@ -0,0 +1,161 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>Sound Labor</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>
<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; /* Dunkelblau */
color: white;
overflow: hidden !important;
user-select: none;
touch-action: manipulation;
font-family: sans-serif;
animation: fadeIn 0.4s ease-out;
box-sizing: border-box;
/* SAFE AREA PADDING */
padding-top: calc(10px + env(safe-area-inset-top));
padding-bottom: max(20px, env(safe-area-inset-bottom));
}
#pad-container {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-template-rows: repeat(3, 1fr);
gap: 0.5rem; /* Etwas enger für mehr Platz */
width: 100%;
height: 100%;
}
.pad-item {
background-color: #1e293b;
border-radius: 1rem;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
cursor: pointer;
border-bottom: 6px solid #0f172a;
position: relative;
user-select: none;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
transition: all 0.1s;
}
.pad-item:active { border-bottom-width: 0; transform: translateY(4px); }
button:active, a:active { transform: scale(0.95); transition: transform 0.1s; }
.pulse-rec { animation: pulseRed 1s infinite; border-color: #ef4444 !important; box-shadow: 0 0 25px #ef4444; }
@keyframes pulseRed { 0% { opacity: 1; } 50% { opacity: 0.6; } 100% { opacity: 1; } }
.fx-active { ring: 4px solid white; transform: scale(1.1); box-shadow: 0 0 15px rgba(255,255,255,0.5); }
.no-scrollbar::-webkit-scrollbar { display: none; }
</style>
</head>
<body class="flex flex-col p-2 md:p-4"
onclick="if(window.resetIdleTimer) window.resetIdleTimer()"
ontouchstart="if(window.resetIdleTimer) window.resetIdleTimer()">
<div class="flex-none flex justify-between items-center z-50 mb-2 relative h-16">
<button onclick="goHome()" class="bg-slate-800 text-white border-4 border-slate-600 hover:border-white px-4 py-2 rounded-full text-lg font-black shadow-xl flex items-center gap-2 transition-transform active:scale-95 no-underline focus:outline-none shrink-0 z-50">
🏠 MENÜ
</button>
<div class="absolute inset-0 flex items-center justify-center pointer-events-none z-0">
<h1 class="text-2xl md:text-4xl font-black text-white drop-shadow-[0_4px_4px_rgba(0,0,0,0.8)] tracking-widest uppercase">
✨ SOUND
</h1>
</div>
<button onclick="toggleInfo(); playSound('click')" class="w-12 h-12 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-xl shrink-0 z-50">
?
</button>
</div>
<div class="flex flex-col xl:flex-row justify-center items-center gap-2 z-40 mb-2 shrink-0">
<div id="visualizer-box" class="hidden relative w-48 h-12 bg-slate-800 rounded-xl border-2 border-red-500 overflow-hidden shadow-[0_0_20px_rgba(239,68,68,0.5)]">
<canvas id="visualizer" class="w-full h-full"></canvas>
<div class="absolute inset-0 flex items-center justify-center text-red-500 font-black tracking-widest text-[10px] pointer-events-none animate-pulse">● REC</div>
</div>
<div class="flex gap-2 bg-slate-800/50 p-1 rounded-xl border border-slate-700 items-center">
<button onclick="setGlobalFx(0.6, this)" class="fx-btn w-10 h-10 rounded-lg bg-purple-600 text-xl shadow-md border-b-2 border-purple-800 active:border-b-0 active:translate-y-1 transition" title="Monster">🧟</button>
<button onclick="setGlobalFx(1.0, this)" class="fx-btn w-10 h-10 rounded-lg bg-blue-600 text-xl shadow-md border-b-2 border-blue-800 active:border-b-0 active:translate-y-1 transition fx-active ring-4 ring-white" title="Normal">🙂</button>
<button onclick="setGlobalFx(1.7, this)" class="fx-btn w-10 h-10 rounded-lg bg-yellow-500 text-xl shadow-md border-b-2 border-yellow-700 active:border-b-0 active:translate-y-1 transition" title="Maus">🐭</button>
</div>
<div class="flex gap-2 bg-slate-800 p-1 rounded-2xl border-2 border-slate-700 shadow-xl w-full xl:w-auto justify-center">
<button id="btn-rec" onclick="setMode('rec')" class="flex items-center justify-center gap-2 px-4 py-2 rounded-xl font-black text-base transition-all border-2 border-transparent bg-slate-700 hover:bg-slate-600 text-gray-300 active:scale-95">
<div class="w-3 h-3 rounded-full bg-red-500 shadow-sm"></div> REC
</button>
<button id="btn-loop" onclick="setMode('loop')" class="flex items-center justify-center gap-2 px-4 py-2 rounded-xl font-black text-base transition-all border-2 border-transparent bg-slate-700 hover:bg-slate-600 text-gray-300 active:scale-95">
🔁 LOOP
</button>
<button onclick="stopAll(); playSound('click')" class="px-4 py-2 rounded-xl font-black text-base bg-slate-700 hover:bg-red-500 hover:text-white transition-all text-gray-300 border-2 border-transparent active:scale-95">
⏹ STOP
</button>
</div>
</div>
<div id="status-bar" class="text-center text-slate-400 mb-1 font-mono text-sm uppercase tracking-widest font-bold transition-all duration-300 shrink-0">
Modus: SPIELEN
</div>
<div id="pad-container" class="flex-1 min-h-0 w-full max-w-5xl mx-auto z-10 pb-1"></div>
<div id="info-modal" class="hidden fixed inset-0 z-[200] bg-black/80 backdrop-blur-sm items-center justify-center p-4" onclick="toggleInfo()">
<div class="bg-slate-800 border-4 border-rose-500 rounded-[2rem] max-w-3xl w-full p-8 shadow-2xl relative" onclick="event.stopPropagation()">
<button onclick="toggleInfo(); playSound('click')" class="absolute top-6 right-6 text-white hover:text-rose-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="grid grid-cols-1 md:grid-cols-3 gap-6 text-base">
<div class="p-6 bg-slate-700/50 rounded-2xl border border-slate-600 flex flex-col items-center text-center"><div class="text-4xl mb-4">🧟</div><h3 class="text-purple-400 font-bold text-xl mb-2">Effekte</h3><p class="text-slate-300 leading-snug">Wähle <b>VOR</b> der Aufnahme oben Monster oder Maus.</p></div>
<div class="p-6 bg-slate-700/50 rounded-2xl border border-slate-600 flex flex-col items-center text-center"><div class="text-4xl mb-4">🎤</div><h3 class="text-rose-400 font-bold text-xl mb-2">Aufnehmen</h3><p class="text-slate-300 leading-snug">1. Tippe <b>REC</b>.<br>2. Wähle Pad.<br>3. Sprich.<br>4. Tippe Pad zum Stop.</p></div>
<div class="p-6 bg-slate-700/50 rounded-2xl border border-slate-600 flex flex-col items-center text-center"><div class="text-4xl mb-4">🔁</div><h3 class="text-blue-400 font-bold text-xl mb-2">Loopen</h3><p class="text-slate-300 leading-snug">Tippe <b>LOOP</b> und wähle Pads für Dauerschleife.</p></div>
</div>
<button onclick="toggleInfo(); playSound('click')" class="mt-8 w-full bg-rose-600 hover:bg-rose-500 text-white font-black py-4 rounded-xl text-xl transition">ALLES KLAR!</button>
</div>
</div>
<script>
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) { if(_soundCache[id]) { const s = _soundCache[id].cloneNode(); s.volume = 1.0; s.play().catch(e=>{}); } };
function goHome() { playSound('click'); setTimeout(() => { window.location.href = '../index.html'; }, 300); }
function toggleInfo() { const m = document.getElementById('info-modal'); m.classList.toggle('hidden'); m.classList.toggle('flex'); }
let idleTimer;
function resetIdleTimer() { clearTimeout(idleTimer); idleTimer = setTimeout(() => { if (typeof pads !== 'undefined' && Array.isArray(pads) && !pads.some(p => p.isRecording)) window.location.href = '../index.html'; }, 120000); }
['mousedown', 'touchstart', 'scroll', 'keydown', 'input'].forEach(evt => document.addEventListener(evt, resetIdleTimer, {passive: true})); resetIdleTimer();
let currentRecRate = 1.0;
function setGlobalFx(rate, btn) { playSound('click'); currentRecRate = rate; document.querySelectorAll('.fx-btn').forEach(b => b.classList.remove('fx-active', 'ring-4', 'ring-white')); btn.classList.add('fx-active', 'ring-4', 'ring-white'); }
let audioCtx; let pads = []; let currentMode = 'play'; let analyser, dataArray, visualizerFrame;
function init() { try { audioCtx = new (window.AudioContext || window.webkitAudioContext)(); } catch(e) {} const container = document.getElementById('pad-container'); if(!container) return; container.innerHTML = ''; for(let i = 0; i < 12; i++) { pads.push({ buffer: null, loop: false, sourceNode: null, isRecording: false, mediaRecorder: null, chunks: [], playbackRate: 1.0 }); const el = document.createElement('div'); el.id = `pad-${i}`; el.className = `pad-item transition-all duration-100`; el.innerHTML = `<div class="text-4xl md:text-6xl font-black text-slate-700 transition-colors duration-300" id="label-${i}">${i+1}</div><div id="icon-loop-${i}" class="hidden absolute top-2 right-2 text-xl bg-blue-600 text-white rounded px-1 shadow-md">🔁</div><div id="icon-fx-${i}" class="absolute bottom-2 right-2 text-xl drop-shadow-md"></div><div id="icon-rec-${i}" class="hidden absolute top-3 left-3 w-4 h-4 bg-red-500 rounded-full animate-pulse shadow-[0_0_10px_red]"></div><div id="status-${i}" class="hidden absolute bottom-2 w-full text-center text-xs font-black uppercase tracking-widest text-emerald-400">BELEGT</div>`; el.onpointerdown = (e) => { e.preventDefault(); handlePadTouch(i); }; container.appendChild(el); } }
window.setMode = function(mode) { playSound('click'); if (currentMode === mode) currentMode = 'play'; else currentMode = mode; updateUI(); }
function updateUI() { const btnRec = document.getElementById('btn-rec'); const btnLoop = document.getElementById('btn-loop'); const status = document.getElementById('status-bar'); const inactive = "border-transparent bg-slate-700 text-gray-300 hover:bg-slate-600"; const activeRec = "border-red-500 text-red-400 bg-slate-900 shadow-[0_0_10px_rgba(239,68,68,0.2)]"; const activeLoop = "border-blue-500 text-blue-400 bg-slate-900 shadow-[0_0_10px_rgba(59,130,246,0.2)]"; const base = "flex items-center justify-center gap-2 px-4 py-2 rounded-xl font-black text-base transition-all border-2 active:scale-95 "; btnRec.className = base + (currentMode === 'rec' ? activeRec : inactive); btnLoop.className = base + (currentMode === 'loop' ? activeLoop : inactive); if (currentMode === 'rec') { status.innerText = "🔴 AUFNAHME"; status.className = "text-center mb-1 font-mono text-sm uppercase tracking-widest text-red-400 animate-pulse font-bold"; } else if (currentMode === 'loop') { status.innerText = "🔁 LOOP SETZEN"; status.className = "text-center mb-1 font-mono text-sm uppercase tracking-widest text-blue-400 font-bold"; } else { status.innerText = "Modus: SPIELEN"; status.className = "text-center text-slate-400 mb-1 font-mono text-sm uppercase tracking-widest font-bold"; } }
async function handlePadTouch(i) { resetIdleTimer(); if (audioCtx && audioCtx.state === 'suspended') await audioCtx.resume(); const pad = pads[i]; const el = document.getElementById(`pad-${i}`); if (currentMode === 'play') { if (!pad.buffer) return; if (pad.loop) { if (pad.sourceNode) { pad.sourceNode.stop(); pad.sourceNode = null; el.classList.remove('bg-blue-600', 'border-blue-800'); el.style.backgroundColor = "#1e293b"; } else { playMusicPad(i, true); el.style.backgroundColor = "#2563eb"; } } else { playMusicPad(i, false); el.style.backgroundColor = "#10b981"; setTimeout(() => el.style.backgroundColor = "#1e293b", 150); } } else if (currentMode === 'loop') { playSound('click'); if (!pad.buffer) return; pad.loop = !pad.loop; document.getElementById(`icon-loop-${i}`).classList.toggle('hidden', !pad.loop); el.style.transform = "scale(0.95)"; setTimeout(() => el.style.transform = "scale(1)", 100); } else if (currentMode === 'rec') { if (pad.isRecording) stopRecording(i); else startRecording(i); } }
function playMusicPad(i, loop) { const pad = pads[i]; if (pad.sourceNode) { try { pad.sourceNode.stop(); } catch(e){} pad.sourceNode = null; } const source = audioCtx.createBufferSource(); source.buffer = pad.buffer; source.loop = loop; source.playbackRate.value = pad.playbackRate; source.connect(audioCtx.destination); source.start(0); if (loop) pad.sourceNode = source; }
function startVisualizer(stream) { document.getElementById('visualizer-box').classList.remove('hidden'); const canvas = document.getElementById('visualizer'); const ctx = canvas.getContext('2d'); const source = audioCtx.createMediaStreamSource(stream); analyser = audioCtx.createAnalyser(); source.connect(analyser); analyser.fftSize = 256; dataArray = new Uint8Array(analyser.frequencyBinCount); function draw() { visualizerFrame = requestAnimationFrame(draw); analyser.getByteFrequencyData(dataArray); canvas.width = canvas.offsetWidth; canvas.height = canvas.offsetHeight; ctx.fillStyle = '#1e293b'; ctx.fillRect(0, 0, canvas.width, canvas.height); const barWidth = (canvas.width / analyser.frequencyBinCount) * 2.5; let x = 0; for(let i = 0; i < analyser.frequencyBinCount; i++) { const barHeight = dataArray[i] / 255 * canvas.height; ctx.fillStyle = `rgb(${barHeight+50},50,50)`; ctx.fillRect(x, canvas.height - barHeight, barWidth, barHeight); x += barWidth + 1; } } draw(); }
function stopVisualizer() { document.getElementById('visualizer-box').classList.add('hidden'); cancelAnimationFrame(visualizerFrame); }
async function startRecording(i) { playSound('shutter'); const pad = pads[i]; const el = document.getElementById(`pad-${i}`); try { const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); startVisualizer(stream); pad.mediaRecorder = new MediaRecorder(stream); pad.chunks = []; pad.mediaRecorder.ondataavailable = (e) => pad.chunks.push(e.data); pad.mediaRecorder.onstop = async () => { const blob = new Blob(pad.chunks, { 'type' : 'audio/webm; codecs=opus' }); const arrayBuffer = await blob.arrayBuffer(); audioCtx.decodeAudioData(arrayBuffer, (decodedBuffer) => { pad.buffer = decodedBuffer; pad.playbackRate = currentRecRate; updatePadVisuals(i, true); }, (e) => console.error(e)); stream.getTracks().forEach(track => track.stop()); stopVisualizer(); }; pad.mediaRecorder.start(); pad.isRecording = true; document.getElementById(`icon-rec-${i}`).classList.remove('hidden'); el.classList.add('pulse-rec'); } catch (err) { alert("Mikrofon Fehler: " + err.message); } }
function stopRecording(i) { playSound('success'); const pad = pads[i]; const el = document.getElementById(`pad-${i}`); if (pad.mediaRecorder && pad.mediaRecorder.state !== 'inactive') pad.mediaRecorder.stop(); pad.isRecording = false; document.getElementById(`icon-rec-${i}`).classList.add('hidden'); el.classList.remove('pulse-rec'); window.setMode('play'); }
function updatePadVisuals(i, hasSound) { const el = document.getElementById(`pad-${i}`); const label = document.getElementById(`label-${i}`); const status = document.getElementById(`status-${i}`); const iconFx = document.getElementById(`icon-fx-${i}`); const pad = pads[i]; if (hasSound) { el.style.borderColor = "#10b981"; label.classList.remove('text-slate-700'); label.classList.add('text-white'); status.classList.remove('hidden'); if(pad.playbackRate < 0.9) iconFx.innerText = "🧟"; else if(pad.playbackRate > 1.2) iconFx.innerText = "🐭"; else iconFx.innerText = ""; } }
window.stopAll = function() { if(pads && pads.length > 0) { pads.forEach((pad, i) => { if (pad.sourceNode) { try { pad.sourceNode.stop(); } catch(e){} pad.sourceNode = null; } const el = document.getElementById(`pad-${i}`); if(el) { el.style.backgroundColor = "#1e293b"; el.classList.remove('bg-blue-600'); } }); } }
init();
</script>
</body>
</html>

BIN
public/assets/dino.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 677 KiB

BIN
public/assets/dschungel.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

BIN
public/assets/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
public/assets/news.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

BIN
public/assets/ozean.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 681 KiB

BIN
public/assets/paris.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

BIN
public/assets/qr.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
public/assets/schloss.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 903 KiB

Binary file not shown.

Binary file not shown.

BIN
public/assets/sounds/brick.mp3 Executable file

Binary file not shown.

Binary file not shown.

BIN
public/assets/sounds/fail.mp3 Executable file

Binary file not shown.

BIN
public/assets/sounds/paddle.mp3 Executable file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
public/assets/sounds/wall.mp3 Executable file

Binary file not shown.

BIN
public/assets/stadion.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 875 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

BIN
public/assets/weltraum.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

BIN
public/assets/wolken.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

1559
public/cordova.js vendored Normal file

File diff suppressed because it is too large Load Diff

36
public/cordova_plugins.js vendored Normal file
View File

@@ -0,0 +1,36 @@
cordova.define('cordova/plugin_list', function(require, exports, module) {
module.exports = [
{
"id": "cordova-plugin-printer.Printer",
"file": "plugins/cordova-plugin-printer/www/printer.js",
"pluginId": "cordova-plugin-printer",
"clobbers": [
"cordova.plugins.printer"
]
},
{
"id": "cordova-plugin-x-socialsharing.SocialSharing",
"file": "plugins/cordova-plugin-x-socialsharing/www/SocialSharing.js",
"pluginId": "cordova-plugin-x-socialsharing",
"clobbers": [
"window.plugins.socialsharing"
]
},
{
"id": "es6-promise-plugin.Promise",
"file": "plugins/es6-promise-plugin/www/promise.js",
"pluginId": "es6-promise-plugin",
"runs": true
}
];
module.exports.metadata =
// TOP OF METADATA
{
"cordova-plugin-printer": "0.8.0",
"cordova-plugin-x-socialsharing": "6.0.4",
"es6-promise-plugin": "4.2.2"
};
// BOTTOM OF METADATA
});

270
public/index.html Normal file
View File

@@ -0,0 +1,270 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>MedienStation Hub</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>
<style>
/* Globaler Reset */
html, body { margin: 0; padding: 0; width: 100%; height: 100%; overflow: hidden; }
body {
background-color: #0f172a;
color: white;
font-family: sans-serif;
user-select: none;
-webkit-user-select: none;
touch-action: manipulation;
}
.font-comic { font-family: 'Comic Sans MS', 'Chalkboard SE', sans-serif; }
.app-card { cursor: pointer; transition: transform 0.1s; }
.app-card:active { transform: scale(0.95); filter: brightness(1.2); }
.no-scrollbar::-webkit-scrollbar { display: none; }
/* Animationen */
.fade-in { animation: fadeIn 0.5s ease-out; }
@keyframes fadeIn { from { opacity: 0; transform: scale(0.98); } to { opacity: 1; transform: scale(1); } }
/* Grid & Ghost Mode für QR Modal */
#qr-modal {
display: grid !important;
place-items: center;
position: fixed;
top: 0; left: 0; width: 100vw; height: 100vh;
z-index: 99999;
background-color: rgba(0,0,0,0.85);
backdrop-filter: blur(5px);
opacity: 0;
pointer-events: none;
transition: opacity 0.3s ease-in-out;
}
#qr-modal.modal-visible {
opacity: 1;
pointer-events: auto;
}
</style>
</head>
<body class="p-4 pt-8 box-border fade-in relative" onpointerdown="resetHubTimer()" ontouchstart="resetHubTimer()">
<div class="grid grid-cols-12 grid-rows-12 gap-3 h-full w-full pb-20">
<div class="col-span-12 row-span-2 bg-slate-800 rounded-3xl px-6 flex justify-between items-center shadow-lg border border-slate-700 relative overflow-hidden group gap-4">
<div class="absolute -right-10 -top-10 w-64 h-64 bg-yellow-400 rounded-full blur-3xl opacity-10 group-hover:opacity-20 transition-opacity duration-500 pointer-events-none"></div>
<div class="z-10 flex-1 select-none cursor-pointer hover:opacity-80 transition-opacity flex flex-col justify-center" onclick="triggerAdmin()">
<h3 class="text-slate-400 text-xs font-bold tracking-[0.2em] uppercase mb-1">DEINE MISSION</h3>
<div id="mission-text" class="text-xl md:text-3xl lg:text-4xl font-black text-white leading-tight truncate">Wähle eine App! 👉</div>
</div>
<button onclick="playSound('click'); newMission()" class="shrink-0 z-10 bg-yellow-400 hover:bg-yellow-300 text-black font-black text-lg py-3 px-8 rounded-full shadow-lg active:translate-y-1 transition-all transform hover:scale-105 flex items-center gap-2">
<span class="text-2xl">🎲</span> <span class="hidden md:inline">NEU</span>
</button>
</div>
<div onclick="openApp('apps/sound.html', 'Sound')" class="app-card col-span-3 row-span-5 bg-gradient-to-br from-rose-500 to-rose-600 rounded-3xl flex flex-col items-center justify-center shadow-lg border-b-8 border-rose-800 active:border-b-0 group">
<div class="text-6xl mb-2 drop-shadow-md group-hover:-translate-y-2 transition-transform">🎹</div>
<h3 class="text-2xl font-bold">Sound</h3>
</div>
<div onclick="openApp('apps/rec.html', 'Mikro')" class="app-card col-span-3 row-span-5 bg-gradient-to-br from-red-600 to-red-700 rounded-3xl flex flex-col items-center justify-center shadow-lg border-b-8 border-red-900 active:border-b-0">
<div class="text-6xl mb-2 drop-shadow-md">🎙️</div>
<h3 class="text-2xl font-bold">Mikro</h3>
</div>
<div onclick="openApp('apps/gif.html', 'Loop')" class="app-card col-span-3 row-span-5 bg-gradient-to-br from-sky-500 to-sky-600 rounded-3xl flex flex-col items-center justify-center shadow-lg border-b-8 border-sky-800 active:border-b-0 group">
<div class="text-6xl mb-2 drop-shadow-md group-hover:rotate-12 transition-transform">📹</div>
<h3 class="text-2xl font-bold">Loop</h3>
</div>
<div onclick="openApp('apps/magic.html', 'Magic')" class="app-card col-span-3 row-span-5 bg-gradient-to-br from-violet-500 to-violet-600 rounded-3xl flex flex-col items-center justify-center shadow-lg border-b-8 border-violet-800 active:border-b-0">
<div class="text-6xl mb-2 drop-shadow-md"></div>
<h3 class="text-2xl font-bold">Magic</h3>
</div>
<div onclick="openApp('apps/pixel.html', 'Pixel')" class="app-card col-span-3 row-span-5 bg-gradient-to-br from-emerald-500 to-emerald-700 rounded-3xl flex flex-col items-center justify-center shadow-lg border-b-8 border-emerald-900 active:border-b-0">
<div class="text-6xl mb-2 drop-shadow-md font-mono">👾</div>
<h3 class="text-2xl font-bold">Pixel</h3>
</div>
<div onclick="openApp('apps/news.html', 'News')" class="app-card col-span-3 row-span-5 bg-gradient-to-br from-blue-700 to-blue-900 rounded-3xl flex flex-col items-center justify-center shadow-lg border-b-8 border-blue-950 active:border-b-0 relative overflow-hidden">
<div class="text-6xl mb-2 drop-shadow-md relative z-10">📰</div>
<h3 class="text-2xl font-bold relative z-10">News</h3>
</div>
<div onclick="openApp('apps/comic.html', 'Comic')" class="app-card col-span-3 row-span-5 bg-gradient-to-r from-yellow-400 to-orange-400 rounded-3xl flex items-center justify-center shadow-lg border-b-8 border-orange-600 active:border-b-0 group gap-2">
<div class="text-5xl drop-shadow-md transform -rotate-6 transition-transform group-hover:rotate-0">📸</div>
<h3 class="text-2xl font-black font-comic tracking-wide text-black">Comic</h3>
</div>
<div onclick="toggleInfo(); playSound('click')" class="app-card col-span-3 row-span-5 bg-slate-700 hover:bg-slate-600 rounded-3xl flex flex-col items-center justify-center shadow-lg border-b-8 border-slate-900 active:border-b-0 border-2 border-slate-600">
<div class="text-6xl mb-2 drop-shadow-md">💡</div>
<h3 class="text-2xl font-bold text-slate-200">Infos</h3>
</div>
</div>
<div class="fixed bottom-6 w-full flex flex-col items-center justify-center pointer-events-none z-40">
<button onclick="toggleQR()" class="pointer-events-auto bg-slate-800/90 backdrop-blur border border-slate-600 px-8 py-3 rounded-full shadow-2xl flex items-center gap-4 hover:bg-slate-700 hover:scale-105 hover:border-white transition-all">
<img src="assets/logo.png" class="h-12 w-auto object-contain" alt="Logo" onerror="this.style.display='none'">
<div class="flex flex-col text-left">
<span class="text-[10px] text-slate-400 font-bold uppercase tracking-widest leading-none mb-1">Ein Projekt der</span>
<span class="text-lg font-black text-white leading-none tracking-wide">AV-MEDIENZENTRALE</span>
</div>
</button>
</div>
<div id="info-modal" class="hidden fixed inset-0 z-[200] bg-black/90 backdrop-blur-md flex items-center justify-center p-6">
<div class="bg-slate-800 border-2 border-slate-600 rounded-[2.5rem] w-full max-w-5xl p-8 shadow-2xl relative max-h-[90vh] overflow-y-auto no-scrollbar">
<button onclick="toggleInfo(); playSound('click')" class="absolute top-6 right-6 text-white text-5xl font-bold hover:text-slate-300 transition"></button>
<h2 class="text-4xl font-black text-white mb-6 border-b-2 border-slate-600 pb-2 tracking-wide uppercase">Apps & Funktionen</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-12">
<div class="p-4 bg-slate-700/50 rounded-2xl flex items-center gap-4 border border-slate-600/50"><div class="text-5xl shrink-0">🎹</div><div><h3 class="text-rose-400 font-black text-xl">Sound</h3><p class="text-slate-300">Baue eigene Beats & Songs.</p></div></div>
<div class="p-4 bg-slate-700/50 rounded-2xl flex items-center gap-4 border border-slate-600/50"><div class="text-5xl shrink-0">🎙️</div><div><h3 class="text-red-400 font-black text-xl">Mikro</h3><p class="text-slate-300">Verstelle deine Stimme (Monster/Maus).</p></div></div>
<div class="p-4 bg-slate-700/50 rounded-2xl flex items-center gap-4 border border-slate-600/50"><div class="text-5xl shrink-0">📹</div><div><h3 class="text-sky-400 font-black text-xl">Loop</h3><p class="text-slate-300">Drehe lustige Wackel-Videos.</p></div></div>
<div class="p-4 bg-slate-700/50 rounded-2xl flex items-center gap-4 border border-slate-600/50"><div class="text-5xl shrink-0"></div><div><h3 class="text-violet-400 font-black text-xl">Magic</h3><p class="text-slate-300">Green Screen: Beame dich weg!</p></div></div>
<div class="p-4 bg-slate-700/50 rounded-2xl flex items-center gap-4 border border-slate-600/50"><div class="text-5xl shrink-0">👾</div><div><h3 class="text-emerald-400 font-black text-xl">Pixel</h3><p class="text-slate-300">Male Retro-Kunstwerke.</p></div></div>
<div class="p-4 bg-slate-700/50 rounded-2xl flex items-center gap-4 border border-slate-600/50"><div class="text-5xl shrink-0">📰</div><div><h3 class="text-blue-400 font-black text-xl">News</h3><p class="text-slate-300">Werde Nachrichtensprecher.</p></div></div>
<div class="p-4 bg-slate-700/50 rounded-2xl flex items-center gap-4 border border-slate-600/50 md:col-span-2"><div class="text-5xl shrink-0">📸</div><div><h3 class="text-orange-400 font-black text-xl">Comic</h3><p class="text-slate-300">Erstelle deine eigene Foto-Story mit Sprechblasen.</p></div></div>
</div>
<h2 class="text-4xl font-black text-white mb-6 border-b-2 border-slate-600 pb-2 tracking-wide uppercase">Pädagogik & Konzept</h2>
<div class="space-y-6 text-slate-300">
<div>
<h4 class="text-2xl font-bold text-yellow-400 mb-2">🎯 Das Ziel</h4>
<p class="text-lg">Die MedienStation fördert spielerisch <b>Medienkompetenz</b>. Kinder produzieren selbst Medien (Bild, Ton, Video), statt sie nur zu konsumieren. Dabei lernen sie technische Grundlagen und hinterfragen Medieninhalte ("Fake News", Bildbearbeitung).</p>
</div>
<div>
<h4 class="text-2xl font-bold text-rose-400 mb-2">💡 Reflexions-Fragen für alle Apps</h4>
<ul class="list-disc pl-6 space-y-2 text-lg">
<li><b>🎹 Sound:</b> Wie verändert Musik die Stimmung eines Films? Würde ein Horrorfilm mit lustiger Musik noch gruselig wirken?</li>
<li><b>🎙️ Mikro:</b> Wie verändert eine tiefe oder hohe Stimme deine Wirkung auf andere? Fühlst du dich als "Monster" mutiger?</li>
<li><b>📹 Loop:</b> Warum wirken Loops oft lustig? Was passiert, wenn eine Bewegung nie aufhört?</li>
<li><b>✨ Magic:</b> Wirkte das Foto echt? Traust du Bildern im Internet jetzt noch genauso schnell?</li>
<li><b>👾 Pixel:</b> Wie viele Quadrate (Pixel) braucht man mindestens, um ein Gesicht zu erkennen?</li>
<li><b>📰 News:</b> Fühlt man sich wichtiger, wenn "LIVE" und "BREAKING NEWS" im Bild steht?</li>
<li><b>📸 Comic:</b> Erzählt ein Bild mehr als 1000 Worte? Wie verändert die Sprechblase die Bedeutung des Fotos?</li>
</ul>
</div>
</div>
</div>
</div>
<div id="qr-modal" onclick="toggleQR()">
<div onclick="event.stopPropagation()"
style="width: 90%; max-width: 400px; background: white; padding: 40px; border-radius: 30px; text-align: center; box-shadow: 0 20px 50px rgba(0,0,0,0.5);">
<h3 style="color: #0f172a; font-size: 24px; font-weight: 900; text-transform: uppercase; margin: 0 0 20px 0;">Besuche uns!</h3>
<div style="width: 250px; height: 250px; background: #f8fafc; border: 2px solid #e2e8f0; border-radius: 15px; margin: 0 auto 20px auto; display: flex; align-items: center; justify-content: center;">
<img src="assets/qr.png" alt="QR Code" style="width: 230px; height: 230px; object-fit: contain;"
onerror="this.src='data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDAgMTAwIj48cmVjdCB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgZmlsbD0iI2ZmZiIvPjx0ZXh0IHg9IjUwIiB5PSI1MCIgZm9udC1zaXplPSIxMCIgdGV4dC1hbmNob3I9Im1pZGRsZSI+UVIgQ29kZTwvdGV4dD48L3N2Zz4='">
</div>
<p style="color: #64748b; font-weight: bold; margin-bottom: 25px;">Scan den Code mit deinem Handy.</p>
<button onclick="toggleQR()"
style="width: 100%; background-color: #0f172a; color: white; border: none; padding: 15px; font-size: 18px; font-weight: 900; border-radius: 12px; cursor: pointer;">
SCHLIESSEN
</button>
</div>
</div>
<div id="admin-modal" class="hidden fixed inset-0 bg-black/90 z-[300] flex items-center justify-center p-4">
<div class="bg-slate-800 p-8 rounded-3xl border-2 border-slate-600 w-full max-w-lg">
<h2 class="text-3xl font-black text-white mb-6">⚙️ ADMIN MENU</h2>
<div class="grid grid-cols-2 gap-4">
<button onclick="location.reload()" class="bg-blue-600 text-white font-bold py-4 rounded-xl">🔄 Reload</button>
<button onclick="closeAdmin()" class="bg-slate-600 text-white font-bold py-4 rounded-xl">Zurück</button>
</div>
</div>
</div>
<script>
// --- MISSIONEN ---
const missions = [
// Standard
"Erstelle ein Geräusch: Kälte ❄️",
"Reise mit Magic Selfie auf den Mond 🚀",
"Werbung für 'Unsichtbare Socken' 🧦",
"Loop: 'Hallo' ohne Worte sagen 👋",
"Witz in 3 Comic-Bildern 🤡",
"Magic Selfie: Du fliegst durch die Luft ✨",
"Vertone einen Elefanten mit Schluckauf 🐘",
"Pixel-Monster Haustier malen 👾",
"News: Ufo gelandet! 👽",
"Interviewe dich selbst aus der Zukunft! 🎤",
"Erfinde einen geheimen Handschlag! 🤝",
"Mache ein Geräusch wie ein wütender Toaster! 🍞",
"Zeige deinen besten Roboter-Tanz 🤖",
"News: Hausaufgaben offiziell verboten! 🚫",
// Neue kreative Ideen
"Pixel-Art: Dein Traumhaus aus Süßigkeiten 🍭",
"Sound: Ein Beat nur aus Körpergeräuschen 👏",
"Comic: Der Tag, an dem die Schwerkraft ausfiel 🎈",
"News: Katzen übernehmen die Weltherrschaft! 😼",
"Loop: Ein Zaubertrick, der schiefgeht 🪄"
];
const playSound = (id) => {
const a = new Audio(`assets/sounds/${id}.mp3`);
a.volume = 1.0;
a.play().catch(() => {});
};
function openApp(url, appName) {
playSound('click');
setTimeout(() => { window.location.href = url; }, 300);
}
function newMission() {
const txt = document.getElementById('mission-text');
txt.innerText = missions[Math.floor(Math.random() * missions.length)];
}
let hubIdleTimer;
function resetHubTimer() {
clearTimeout(hubIdleTimer);
hubIdleTimer = setTimeout(() => {
document.getElementById('admin-modal').classList.add('hidden');
document.getElementById('info-modal').classList.add('hidden');
document.getElementById('qr-modal').classList.remove('modal-visible');
document.getElementById('mission-text').innerText = "Wähle eine App! 👉";
tapCount = 0;
}, 60000);
}
resetHubTimer();
function toggleInfo() {
resetHubTimer();
const m = document.getElementById('info-modal');
m.classList.toggle('hidden');
m.classList.toggle('flex');
}
function toggleQR() {
resetHubTimer();
const modal = document.getElementById('qr-modal');
modal.classList.toggle('modal-visible');
playSound('click');
}
let tapCount = 0; let tapTimer;
function triggerAdmin() {
resetHubTimer(); tapCount++; clearTimeout(tapTimer); tapTimer = setTimeout(() => { tapCount = 0; }, 1000);
if (tapCount >= 5) {
tapCount = 0;
if (prompt("PIN:") === "1234") {
document.getElementById('admin-modal').classList.remove('hidden');
document.getElementById('admin-modal').classList.add('flex');
}
}
}
function closeAdmin() {
document.getElementById('admin-modal').classList.add('hidden');
document.getElementById('admin-modal').classList.remove('flex');
}
</script>
</body>
</html>

87
public/js/audio.js Normal file
View File

@@ -0,0 +1,87 @@
(function() {
console.log("🔊 AUDIO SYSTEM GESTARTET");
// 1. Pfad bestimmen
const isSubApp = window.location.pathname.includes('/apps/');
// Wir probieren beide Pfade, falls einer falsch ist
const basePath = isSubApp ? '../assets/sounds/' : 'assets/sounds/';
// 2. Sounds laden mit Fehlerprüfung
const soundNames = ['click', 'shutter', 'success'];
const audioStore = {};
soundNames.forEach(name => {
const fullPath = basePath + name + '.mp3';
const audio = new Audio();
// Event Listener VOR dem src setzen
audio.addEventListener('canplaythrough', () => {
console.log(`✅ Sound geladen: ${name} (${fullPath})`);
});
audio.addEventListener('error', (e) => {
console.error(`❌ FEHLER bei Sound ${name}:`, e);
console.error(`Versuchter Pfad: ${fullPath}`);
// Versuch: Absoluter Pfad als Fallback für Android
if (!audio.src.startsWith('http') && !audio.src.startsWith('file')) {
console.log("Versuche Fallback-Pfad...");
}
});
audio.src = fullPath;
audio.load();
audioStore[name] = audio;
});
audioStore['click'].volume = 0.5;
audioStore['shutter'].volume = 1.0;
audioStore['success'].volume = 0.8;
// 3. Globale Play Funktion
window.playSound = function(name) {
const sound = audioStore[name];
if (sound) {
console.log(`▶️ Spiele: ${name}`);
const clone = sound.cloneNode();
clone.volume = sound.volume;
const promise = clone.play();
if (promise !== undefined) {
promise.then(() => {
console.log(`🔊 ${name} abgespielt!`);
}).catch(error => {
console.error(`🚫 Autoplay blockiert oder Fehler bei ${name}:`, error);
alert("Audio Fehler: " + error.message); // Damit du es am Tablet siehst
});
}
} else {
console.error(`❓ Unbekannter Sound: ${name}`);
}
};
// 4. Klick-Listener an alle Buttons
function initButtonSounds() {
const buttons = document.querySelectorAll('button, a, .pad-item, .pad-btn');
console.log(`Found ${buttons.length} clickable elements.`);
buttons.forEach(btn => {
if(btn.dataset.soundAttached) return;
btn.dataset.soundAttached = "true";
btn.addEventListener('touchstart', () => {
const txt = (btn.innerText || "").toUpperCase();
if (!txt.includes('FOTO') && !txt.includes('REC') && !txt.includes('DRUCKEN') && !txt.includes('WÜRFELN')) {
window.playSound('click');
}
}, {passive: true});
});
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initButtonSounds);
} else {
initButtonSounds();
}
window.refreshAudio = initButtonSounds;
})();

View File

@@ -0,0 +1,93 @@
(function(){/*
Copyright The Closure Library Authors.
SPDX-License-Identifier: Apache-2.0
*/
'use strict';var x;function aa(a){var b=0;return function(){return b<a.length?{done:!1,value:a[b++]}:{done:!0}}}var ba="function"==typeof Object.defineProperties?Object.defineProperty:function(a,b,c){if(a==Array.prototype||a==Object.prototype)return a;a[b]=c.value;return a};
function ca(a){a=["object"==typeof globalThis&&globalThis,a,"object"==typeof window&&window,"object"==typeof self&&self,"object"==typeof global&&global];for(var b=0;b<a.length;++b){var c=a[b];if(c&&c.Math==Math)return c}throw Error("Cannot find global object");}var y=ca(this);function z(a,b){if(b)a:{var c=y;a=a.split(".");for(var d=0;d<a.length-1;d++){var e=a[d];if(!(e in c))break a;c=c[e]}a=a[a.length-1];d=c[a];b=b(d);b!=d&&null!=b&&ba(c,a,{configurable:!0,writable:!0,value:b})}}
z("Symbol",function(a){function b(g){if(this instanceof b)throw new TypeError("Symbol is not a constructor");return new c(d+(g||"")+"_"+e++,g)}function c(g,f){this.h=g;ba(this,"description",{configurable:!0,writable:!0,value:f})}if(a)return a;c.prototype.toString=function(){return this.h};var d="jscomp_symbol_"+(1E9*Math.random()>>>0)+"_",e=0;return b});
z("Symbol.iterator",function(a){if(a)return a;a=Symbol("Symbol.iterator");for(var b="Array Int8Array Uint8Array Uint8ClampedArray Int16Array Uint16Array Int32Array Uint32Array Float32Array Float64Array".split(" "),c=0;c<b.length;c++){var d=y[b[c]];"function"===typeof d&&"function"!=typeof d.prototype[a]&&ba(d.prototype,a,{configurable:!0,writable:!0,value:function(){return da(aa(this))}})}return a});function da(a){a={next:a};a[Symbol.iterator]=function(){return this};return a}
function A(a){var b="undefined"!=typeof Symbol&&Symbol.iterator&&a[Symbol.iterator];return b?b.call(a):{next:aa(a)}}function ea(a){if(!(a instanceof Array)){a=A(a);for(var b,c=[];!(b=a.next()).done;)c.push(b.value);a=c}return a}var fa="function"==typeof Object.assign?Object.assign:function(a,b){for(var c=1;c<arguments.length;c++){var d=arguments[c];if(d)for(var e in d)Object.prototype.hasOwnProperty.call(d,e)&&(a[e]=d[e])}return a};z("Object.assign",function(a){return a||fa});
var ha="function"==typeof Object.create?Object.create:function(a){function b(){}b.prototype=a;return new b},ia;if("function"==typeof Object.setPrototypeOf)ia=Object.setPrototypeOf;else{var ja;a:{var ka={a:!0},la={};try{la.__proto__=ka;ja=la.a;break a}catch(a){}ja=!1}ia=ja?function(a,b){a.__proto__=b;if(a.__proto__!==b)throw new TypeError(a+" is not extensible");return a}:null}var ma=ia;
function na(a,b){a.prototype=ha(b.prototype);a.prototype.constructor=a;if(ma)ma(a,b);else for(var c in b)if("prototype"!=c)if(Object.defineProperties){var d=Object.getOwnPropertyDescriptor(b,c);d&&Object.defineProperty(a,c,d)}else a[c]=b[c];a.za=b.prototype}function oa(){this.m=!1;this.j=null;this.i=void 0;this.h=1;this.v=this.s=0;this.l=null}function pa(a){if(a.m)throw new TypeError("Generator is already running");a.m=!0}oa.prototype.u=function(a){this.i=a};
function qa(a,b){a.l={ma:b,na:!0};a.h=a.s||a.v}oa.prototype.return=function(a){this.l={return:a};this.h=this.v};function D(a,b,c){a.h=c;return{value:b}}function ra(a){this.h=new oa;this.i=a}function sa(a,b){pa(a.h);var c=a.h.j;if(c)return ta(a,"return"in c?c["return"]:function(d){return{value:d,done:!0}},b,a.h.return);a.h.return(b);return ua(a)}
function ta(a,b,c,d){try{var e=b.call(a.h.j,c);if(!(e instanceof Object))throw new TypeError("Iterator result "+e+" is not an object");if(!e.done)return a.h.m=!1,e;var g=e.value}catch(f){return a.h.j=null,qa(a.h,f),ua(a)}a.h.j=null;d.call(a.h,g);return ua(a)}function ua(a){for(;a.h.h;)try{var b=a.i(a.h);if(b)return a.h.m=!1,{value:b.value,done:!1}}catch(c){a.h.i=void 0,qa(a.h,c)}a.h.m=!1;if(a.h.l){b=a.h.l;a.h.l=null;if(b.na)throw b.ma;return{value:b.return,done:!0}}return{value:void 0,done:!0}}
function va(a){this.next=function(b){pa(a.h);a.h.j?b=ta(a,a.h.j.next,b,a.h.u):(a.h.u(b),b=ua(a));return b};this.throw=function(b){pa(a.h);a.h.j?b=ta(a,a.h.j["throw"],b,a.h.u):(qa(a.h,b),b=ua(a));return b};this.return=function(b){return sa(a,b)};this[Symbol.iterator]=function(){return this}}function wa(a){function b(d){return a.next(d)}function c(d){return a.throw(d)}return new Promise(function(d,e){function g(f){f.done?d(f.value):Promise.resolve(f.value).then(b,c).then(g,e)}g(a.next())})}
function E(a){return wa(new va(new ra(a)))}
z("Promise",function(a){function b(f){this.i=0;this.j=void 0;this.h=[];this.u=!1;var h=this.l();try{f(h.resolve,h.reject)}catch(k){h.reject(k)}}function c(){this.h=null}function d(f){return f instanceof b?f:new b(function(h){h(f)})}if(a)return a;c.prototype.i=function(f){if(null==this.h){this.h=[];var h=this;this.j(function(){h.m()})}this.h.push(f)};var e=y.setTimeout;c.prototype.j=function(f){e(f,0)};c.prototype.m=function(){for(;this.h&&this.h.length;){var f=this.h;this.h=[];for(var h=0;h<f.length;++h){var k=
f[h];f[h]=null;try{k()}catch(l){this.l(l)}}}this.h=null};c.prototype.l=function(f){this.j(function(){throw f;})};b.prototype.l=function(){function f(l){return function(m){k||(k=!0,l.call(h,m))}}var h=this,k=!1;return{resolve:f(this.I),reject:f(this.m)}};b.prototype.I=function(f){if(f===this)this.m(new TypeError("A Promise cannot resolve to itself"));else if(f instanceof b)this.L(f);else{a:switch(typeof f){case "object":var h=null!=f;break a;case "function":h=!0;break a;default:h=!1}h?this.F(f):this.s(f)}};
b.prototype.F=function(f){var h=void 0;try{h=f.then}catch(k){this.m(k);return}"function"==typeof h?this.M(h,f):this.s(f)};b.prototype.m=function(f){this.v(2,f)};b.prototype.s=function(f){this.v(1,f)};b.prototype.v=function(f,h){if(0!=this.i)throw Error("Cannot settle("+f+", "+h+"): Promise already settled in state"+this.i);this.i=f;this.j=h;2===this.i&&this.K();this.H()};b.prototype.K=function(){var f=this;e(function(){if(f.D()){var h=y.console;"undefined"!==typeof h&&h.error(f.j)}},1)};b.prototype.D=
function(){if(this.u)return!1;var f=y.CustomEvent,h=y.Event,k=y.dispatchEvent;if("undefined"===typeof k)return!0;"function"===typeof f?f=new f("unhandledrejection",{cancelable:!0}):"function"===typeof h?f=new h("unhandledrejection",{cancelable:!0}):(f=y.document.createEvent("CustomEvent"),f.initCustomEvent("unhandledrejection",!1,!0,f));f.promise=this;f.reason=this.j;return k(f)};b.prototype.H=function(){if(null!=this.h){for(var f=0;f<this.h.length;++f)g.i(this.h[f]);this.h=null}};var g=new c;b.prototype.L=
function(f){var h=this.l();f.T(h.resolve,h.reject)};b.prototype.M=function(f,h){var k=this.l();try{f.call(h,k.resolve,k.reject)}catch(l){k.reject(l)}};b.prototype.then=function(f,h){function k(p,n){return"function"==typeof p?function(q){try{l(p(q))}catch(t){m(t)}}:n}var l,m,r=new b(function(p,n){l=p;m=n});this.T(k(f,l),k(h,m));return r};b.prototype.catch=function(f){return this.then(void 0,f)};b.prototype.T=function(f,h){function k(){switch(l.i){case 1:f(l.j);break;case 2:h(l.j);break;default:throw Error("Unexpected state: "+
l.i);}}var l=this;null==this.h?g.i(k):this.h.push(k);this.u=!0};b.resolve=d;b.reject=function(f){return new b(function(h,k){k(f)})};b.race=function(f){return new b(function(h,k){for(var l=A(f),m=l.next();!m.done;m=l.next())d(m.value).T(h,k)})};b.all=function(f){var h=A(f),k=h.next();return k.done?d([]):new b(function(l,m){function r(q){return function(t){p[q]=t;n--;0==n&&l(p)}}var p=[],n=0;do p.push(void 0),n++,d(k.value).T(r(p.length-1),m),k=h.next();while(!k.done)})};return b});
function xa(a,b){a instanceof String&&(a+="");var c=0,d=!1,e={next:function(){if(!d&&c<a.length){var g=c++;return{value:b(g,a[g]),done:!1}}d=!0;return{done:!0,value:void 0}}};e[Symbol.iterator]=function(){return e};return e}z("Array.prototype.keys",function(a){return a?a:function(){return xa(this,function(b){return b})}});
z("Array.prototype.fill",function(a){return a?a:function(b,c,d){var e=this.length||0;0>c&&(c=Math.max(0,e+c));if(null==d||d>e)d=e;d=Number(d);0>d&&(d=Math.max(0,e+d));for(c=Number(c||0);c<d;c++)this[c]=b;return this}});function F(a){return a?a:Array.prototype.fill}z("Int8Array.prototype.fill",F);z("Uint8Array.prototype.fill",F);z("Uint8ClampedArray.prototype.fill",F);z("Int16Array.prototype.fill",F);z("Uint16Array.prototype.fill",F);z("Int32Array.prototype.fill",F);
z("Uint32Array.prototype.fill",F);z("Float32Array.prototype.fill",F);z("Float64Array.prototype.fill",F);z("Object.is",function(a){return a?a:function(b,c){return b===c?0!==b||1/b===1/c:b!==b&&c!==c}});z("Array.prototype.includes",function(a){return a?a:function(b,c){var d=this;d instanceof String&&(d=String(d));var e=d.length;c=c||0;for(0>c&&(c=Math.max(c+e,0));c<e;c++){var g=d[c];if(g===b||Object.is(g,b))return!0}return!1}});
z("String.prototype.includes",function(a){return a?a:function(b,c){if(null==this)throw new TypeError("The 'this' value for String.prototype.includes must not be null or undefined");if(b instanceof RegExp)throw new TypeError("First argument to String.prototype.includes must not be a regular expression");return-1!==this.indexOf(b,c||0)}});var ya=this||self;
function Aa(a,b){a=a.split(".");var c=ya;a[0]in c||"undefined"==typeof c.execScript||c.execScript("var "+a[0]);for(var d;a.length&&(d=a.shift());)a.length||void 0===b?c[d]&&c[d]!==Object.prototype[d]?c=c[d]:c=c[d]={}:c[d]=b};function Ba(a){var b;a:{if(b=ya.navigator)if(b=b.userAgent)break a;b=""}return-1!=b.indexOf(a)};var Ca=Array.prototype.map?function(a,b){return Array.prototype.map.call(a,b,void 0)}:function(a,b){for(var c=a.length,d=Array(c),e="string"===typeof a?a.split(""):a,g=0;g<c;g++)g in e&&(d[g]=b.call(void 0,e[g],g,a));return d};var Da={},Ea=null;function Fa(a){var b=a.length,c=3*b/4;c%3?c=Math.floor(c):-1!="=.".indexOf(a[b-1])&&(c=-1!="=.".indexOf(a[b-2])?c-2:c-1);var d=new Uint8Array(c),e=0;Ga(a,function(g){d[e++]=g});return e!==c?d.subarray(0,e):d}
function Ga(a,b){function c(k){for(;d<a.length;){var l=a.charAt(d++),m=Ea[l];if(null!=m)return m;if(!/^[\s\xa0]*$/.test(l))throw Error("Unknown base64 encoding at char: "+l);}return k}Ha();for(var d=0;;){var e=c(-1),g=c(0),f=c(64),h=c(64);if(64===h&&-1===e)break;b(e<<2|g>>4);64!=f&&(b(g<<4&240|f>>2),64!=h&&b(f<<6&192|h))}}
function Ha(){if(!Ea){Ea={};for(var a="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789".split(""),b=["+/=","+/","-_=","-_.","-_"],c=0;5>c;c++){var d=a.concat(b[c].split(""));Da[c]=d;for(var e=0;e<d.length;e++){var g=d[e];void 0===Ea[g]&&(Ea[g]=e)}}}};var Ia="undefined"!==typeof Uint8Array,Ja=!(Ba("Trident")||Ba("MSIE"))&&"function"===typeof ya.btoa;
function Ka(a){if(!Ja){var b;void 0===b&&(b=0);Ha();b=Da[b];for(var c=Array(Math.floor(a.length/3)),d=b[64]||"",e=0,g=0;e<a.length-2;e+=3){var f=a[e],h=a[e+1],k=a[e+2],l=b[f>>2];f=b[(f&3)<<4|h>>4];h=b[(h&15)<<2|k>>6];k=b[k&63];c[g++]=l+f+h+k}l=0;k=d;switch(a.length-e){case 2:l=a[e+1],k=b[(l&15)<<2]||d;case 1:a=a[e],c[g]=b[a>>2]+b[(a&3)<<4|l>>4]+k+d}return c.join("")}for(b="";10240<a.length;)b+=String.fromCharCode.apply(null,a.subarray(0,10240)),a=a.subarray(10240);b+=String.fromCharCode.apply(null,
a);return btoa(b)}var La=RegExp("[-_.]","g");function Ma(a){switch(a){case "-":return"+";case "_":return"/";case ".":return"=";default:return""}}function Na(a){if(!Ja)return Fa(a);La.test(a)&&(a=a.replace(La,Ma));a=atob(a);for(var b=new Uint8Array(a.length),c=0;c<a.length;c++)b[c]=a.charCodeAt(c);return b}var Oa;function Pa(){return Oa||(Oa=new Uint8Array(0))}var Qa={};var Ra="function"===typeof Uint8Array.prototype.slice,G=0,H=0;function Sa(a){var b=0>a;a=Math.abs(a);var c=a>>>0;a=Math.floor((a-c)/4294967296);b&&(c=A(Ta(c,a)),b=c.next().value,a=c.next().value,c=b);G=c>>>0;H=a>>>0}var Ua="function"===typeof BigInt;function Ta(a,b){b=~b;a?a=~a+1:b+=1;return[a,b]};function Va(a,b){this.i=a>>>0;this.h=b>>>0}
function Wa(a){if(!a)return Xa||(Xa=new Va(0,0));if(!/^-?\d+$/.test(a))return null;if(16>a.length)Sa(Number(a));else if(Ua)a=BigInt(a),G=Number(a&BigInt(4294967295))>>>0,H=Number(a>>BigInt(32)&BigInt(4294967295));else{var b=+("-"===a[0]);H=G=0;for(var c=a.length,d=b,e=(c-b)%6+b;e<=c;d=e,e+=6)d=Number(a.slice(d,e)),H*=1E6,G=1E6*G+d,4294967296<=G&&(H+=G/4294967296|0,G%=4294967296);b&&(b=A(Ta(G,H)),a=b.next().value,b=b.next().value,G=a,H=b)}return new Va(G,H)}var Xa;function Ya(a,b){return Error("Invalid wire type: "+a+" (at position "+b+")")}function Za(){return Error("Failed to read varint, encoding is invalid.")}function $a(a,b){return Error("Tried to read past the end of the data "+b+" > "+a)};function K(){throw Error("Invalid UTF8");}function ab(a,b){b=String.fromCharCode.apply(null,b);return null==a?b:a+b}var bb=void 0,cb,db="undefined"!==typeof TextDecoder,eb,fb="undefined"!==typeof TextEncoder;var gb;function hb(a){if(a!==Qa)throw Error("illegal external caller");}function ib(a,b){hb(b);this.V=a;if(null!=a&&0===a.length)throw Error("ByteString should be constructed with non-empty values");}function jb(){return gb||(gb=new ib(null,Qa))}function kb(a){hb(Qa);var b=a.V;b=null==b||Ia&&null!=b&&b instanceof Uint8Array?b:"string"===typeof b?Na(b):null;return null==b?b:a.V=b};function lb(a){if("string"===typeof a)return{buffer:Na(a),C:!1};if(Array.isArray(a))return{buffer:new Uint8Array(a),C:!1};if(a.constructor===Uint8Array)return{buffer:a,C:!1};if(a.constructor===ArrayBuffer)return{buffer:new Uint8Array(a),C:!1};if(a.constructor===ib)return{buffer:kb(a)||Pa(),C:!0};if(a instanceof Uint8Array)return{buffer:new Uint8Array(a.buffer,a.byteOffset,a.byteLength),C:!1};throw Error("Type not convertible to a Uint8Array, expected a Uint8Array, an ArrayBuffer, a base64 encoded string, a ByteString or an Array of numbers");
};function mb(a,b){this.i=null;this.m=!1;this.h=this.j=this.l=0;nb(this,a,b)}function nb(a,b,c){c=void 0===c?{}:c;a.S=void 0===c.S?!1:c.S;b&&(b=lb(b),a.i=b.buffer,a.m=b.C,a.l=0,a.j=a.i.length,a.h=a.l)}mb.prototype.reset=function(){this.h=this.l};function L(a,b){a.h=b;if(b>a.j)throw $a(a.j,b);}
function ob(a){var b=a.i,c=a.h,d=b[c++],e=d&127;if(d&128&&(d=b[c++],e|=(d&127)<<7,d&128&&(d=b[c++],e|=(d&127)<<14,d&128&&(d=b[c++],e|=(d&127)<<21,d&128&&(d=b[c++],e|=d<<28,d&128&&b[c++]&128&&b[c++]&128&&b[c++]&128&&b[c++]&128&&b[c++]&128)))))throw Za();L(a,c);return e}function pb(a,b){if(0>b)throw Error("Tried to read a negative byte length: "+b);var c=a.h,d=c+b;if(d>a.j)throw $a(b,a.j-c);a.h=d;return c}var qb=[];function rb(){this.h=[]}rb.prototype.length=function(){return this.h.length};rb.prototype.end=function(){var a=this.h;this.h=[];return a};function sb(a,b,c){for(;0<c||127<b;)a.h.push(b&127|128),b=(b>>>7|c<<25)>>>0,c>>>=7;a.h.push(b)}function M(a,b){for(;127<b;)a.h.push(b&127|128),b>>>=7;a.h.push(b)};function tb(a,b){if(qb.length){var c=qb.pop();nb(c,a,b);a=c}else a=new mb(a,b);this.h=a;this.j=this.h.h;this.i=this.l=-1;this.setOptions(b)}tb.prototype.setOptions=function(a){a=void 0===a?{}:a;this.ca=void 0===a.ca?!1:a.ca};tb.prototype.reset=function(){this.h.reset();this.j=this.h.h;this.i=this.l=-1};
function ub(a){var b=a.h;if(b.h==b.j)return!1;a.j=a.h.h;var c=ob(a.h)>>>0;b=c>>>3;c&=7;if(!(0<=c&&5>=c))throw Ya(c,a.j);if(1>b)throw Error("Invalid field number: "+b+" (at position "+a.j+")");a.l=b;a.i=c;return!0}
function vb(a){switch(a.i){case 0:if(0!=a.i)vb(a);else a:{a=a.h;for(var b=a.h,c=b+10,d=a.i;b<c;)if(0===(d[b++]&128)){L(a,b);break a}throw Za();}break;case 1:a=a.h;L(a,a.h+8);break;case 2:2!=a.i?vb(a):(b=ob(a.h)>>>0,a=a.h,L(a,a.h+b));break;case 5:a=a.h;L(a,a.h+4);break;case 3:b=a.l;do{if(!ub(a))throw Error("Unmatched start-group tag: stream EOF");if(4==a.i){if(a.l!=b)throw Error("Unmatched end-group tag");break}vb(a)}while(1);break;default:throw Ya(a.i,a.j);}}var wb=[];function xb(){this.j=[];this.i=0;this.h=new rb}function N(a,b){0!==b.length&&(a.j.push(b),a.i+=b.length)}function yb(a,b){if(b=b.R){N(a,a.h.end());for(var c=0;c<b.length;c++)N(a,kb(b[c])||Pa())}};var O="function"===typeof Symbol&&"symbol"===typeof Symbol()?Symbol():void 0;function P(a,b){if(O)return a[O]|=b;if(void 0!==a.A)return a.A|=b;Object.defineProperties(a,{A:{value:b,configurable:!0,writable:!0,enumerable:!1}});return b}function zb(a,b){O?a[O]&&(a[O]&=~b):void 0!==a.A&&(a.A&=~b)}function Q(a){var b;O?b=a[O]:b=a.A;return null==b?0:b}function R(a,b){O?a[O]=b:void 0!==a.A?a.A=b:Object.defineProperties(a,{A:{value:b,configurable:!0,writable:!0,enumerable:!1}})}
function Ab(a){P(a,1);return a}function Bb(a,b){R(b,(a|0)&-51)}function Cb(a,b){R(b,(a|18)&-41)};var Db={};function Eb(a){return null!==a&&"object"===typeof a&&!Array.isArray(a)&&a.constructor===Object}var Fb,Gb=[];R(Gb,23);Fb=Object.freeze(Gb);function Hb(a){if(Q(a.o)&2)throw Error("Cannot mutate an immutable Message");}function Ib(a){var b=a.length;(b=b?a[b-1]:void 0)&&Eb(b)?b.g=1:(b={},a.push((b.g=1,b)))};function Jb(a){var b=a.i+a.G;return a.B||(a.B=a.o[b]={})}function S(a,b){return-1===b?null:b>=a.i?a.B?a.B[b]:void 0:a.o[b+a.G]}function U(a,b,c,d){Hb(a);Kb(a,b,c,d)}function Kb(a,b,c,d){a.j&&(a.j=void 0);b>=a.i||d?Jb(a)[b]=c:(a.o[b+a.G]=c,(a=a.B)&&b in a&&delete a[b])}function Lb(a,b,c,d){var e=S(a,b);Array.isArray(e)||(e=Fb);var g=Q(e);g&1||Ab(e);if(d)g&2||P(e,2),c&1||Object.freeze(e);else{d=!(c&2);var f=g&2;c&1||!f?d&&g&16&&!f&&zb(e,16):(e=Ab(Array.prototype.slice.call(e)),Kb(a,b,e))}return e}
function Mb(a,b){var c=S(a,b);var d=null==c?c:"number"===typeof c||"NaN"===c||"Infinity"===c||"-Infinity"===c?Number(c):void 0;null!=d&&d!==c&&Kb(a,b,d);return d}
function Nb(a,b,c,d,e){a.h||(a.h={});var g=a.h[c],f=Lb(a,c,3,e);if(!g){var h=f;g=[];var k=!!(Q(a.o)&16);f=!!(Q(h)&2);var l=h;!e&&f&&(h=Array.prototype.slice.call(h));for(var m=f,r=0;r<h.length;r++){var p=h[r];var n=b,q=!1;q=void 0===q?!1:q;p=Array.isArray(p)?new n(p):q?new n:void 0;if(void 0!==p){n=p.o;var t=q=Q(n);f&&(t|=2);k&&(t|=16);t!=q&&R(n,t);n=t;m=m||!!(2&n);g.push(p)}}a.h[c]=g;k=Q(h);b=k|33;b=m?b&-9:b|8;k!=b&&(m=h,Object.isFrozen(m)&&(m=Array.prototype.slice.call(m)),R(m,b),h=m);l!==h&&Kb(a,
c,h);(e||d&&f)&&P(g,2);d&&Object.freeze(g);return g}e||(e=Object.isFrozen(g),d&&!e?Object.freeze(g):!d&&e&&(g=Array.prototype.slice.call(g),a.h[c]=g));return g}function Ob(a,b,c){var d=!!(Q(a.o)&2);b=Nb(a,b,c,d,d);a=Lb(a,c,3,d);if(!(d||Q(a)&8)){for(d=0;d<b.length;d++){c=b[d];if(Q(c.o)&2){var e=Pb(c,!1);e.j=c}else e=c;c!==e&&(b[d]=e,a[d]=e.o)}P(a,8)}return b}
function V(a,b,c){if(null!=c&&"number"!==typeof c)throw Error("Value of float/double field must be a number|null|undefined, found "+typeof c+": "+c);U(a,b,c)}function Qb(a,b,c,d,e){Hb(a);var g=Nb(a,c,b,!1,!1);c=null!=d?d:new c;a=Lb(a,b,2,!1);void 0!=e?(g.splice(e,0,c),a.splice(e,0,c.o)):(g.push(c),a.push(c.o));c.C()&&zb(a,8);return c}function Rb(a,b){return null==a?b:a}function W(a,b,c){c=void 0===c?0:c;return Rb(Mb(a,b),c)};var Sb;function Tb(a){switch(typeof a){case "number":return isFinite(a)?a:String(a);case "object":if(a)if(Array.isArray(a)){if(0!==(Q(a)&128))return a=Array.prototype.slice.call(a),Ib(a),a}else{if(Ia&&null!=a&&a instanceof Uint8Array)return Ka(a);if(a instanceof ib){var b=a.V;return null==b?"":"string"===typeof b?b:a.V=Ka(b)}}}return a};function Ub(a,b,c,d){if(null!=a){if(Array.isArray(a))a=Vb(a,b,c,void 0!==d);else if(Eb(a)){var e={},g;for(g in a)e[g]=Ub(a[g],b,c,d);a=e}else a=b(a,d);return a}}function Vb(a,b,c,d){var e=Q(a);d=d?!!(e&16):void 0;a=Array.prototype.slice.call(a);for(var g=0;g<a.length;g++)a[g]=Ub(a[g],b,c,d);c(e,a);return a}function Wb(a){return a.ja===Db?a.toJSON():Tb(a)}function Xb(a,b){a&128&&Ib(b)};function Yb(a,b,c){c=void 0===c?Cb:c;if(null!=a){if(Ia&&a instanceof Uint8Array)return a.length?new ib(new Uint8Array(a),Qa):jb();if(Array.isArray(a)){var d=Q(a);if(d&2)return a;if(b&&!(d&32)&&(d&16||0===d))return R(a,d|2),a;a=Vb(a,Yb,d&4?Cb:c,!0);b=Q(a);b&4&&b&2&&Object.freeze(a);return a}return a.ja===Db?Zb(a):a}}
function $b(a,b,c,d,e,g,f){if(a=a.h&&a.h[c]){d=Q(a);d&2?d=a:(g=Ca(a,Zb),Cb(d,g),Object.freeze(g),d=g);Hb(b);f=null==d?Fb:Ab([]);if(null!=d){g=!!d.length;for(a=0;a<d.length;a++){var h=d[a];g=g&&!(Q(h.o)&2);f[a]=h.o}g=(g?8:0)|1;a=Q(f);(a&g)!==g&&(Object.isFrozen(f)&&(f=Array.prototype.slice.call(f)),R(f,a|g));b.h||(b.h={});b.h[c]=d}else b.h&&(b.h[c]=void 0);Kb(b,c,f,e)}else U(b,c,Yb(d,g,f),e)}function Zb(a){if(Q(a.o)&2)return a;a=Pb(a,!0);P(a.o,2);return a}
function Pb(a,b){var c=a.o,d=[];P(d,16);var e=a.constructor.h;e&&d.push(e);e=a.B;if(e){d.length=c.length;d.fill(void 0,d.length,c.length);var g={};d[d.length-1]=g}0!==(Q(c)&128)&&Ib(d);b=b||a.C()?Cb:Bb;g=a.constructor;Sb=d;d=new g(d);Sb=void 0;a.R&&(d.R=a.R.slice());g=!!(Q(c)&16);for(var f=e?c.length-1:c.length,h=0;h<f;h++)$b(a,d,h-a.G,c[h],!1,g,b);if(e)for(var k in e)$b(a,d,+k,e[k],!0,g,b);return d};function X(a,b,c){null==a&&(a=Sb);Sb=void 0;var d=this.constructor.i||0,e=0<d,g=this.constructor.h,f=!1;if(null==a){a=g?[g]:[];var h=48;var k=!0;e&&(d=0,h|=128);R(a,h)}else{if(!Array.isArray(a))throw Error();if(g&&g!==a[0])throw Error();var l=h=P(a,0);if(k=0!==(16&l))(f=0!==(32&l))||(l|=32);if(e)if(128&l)d=0;else{if(0<a.length){var m=a[a.length-1];if(Eb(m)&&"g"in m){d=0;l|=128;delete m.g;var r=!0,p;for(p in m){r=!1;break}r&&a.pop()}}}else if(128&l)throw Error();h!==l&&R(a,l)}this.G=(g?0:-1)-d;this.h=
void 0;this.o=a;a:{g=this.o.length;d=g-1;if(g&&(g=this.o[d],Eb(g))){this.B=g;this.i=d-this.G;break a}void 0!==b&&-1<b?(this.i=Math.max(b,d+1-this.G),this.B=void 0):this.i=Number.MAX_VALUE}if(!e&&this.B&&"g"in this.B)throw Error('Unexpected "g" flag in sparse object of message that is not a group type.');if(c){b=k&&!f&&!0;e=this.i;var n;for(k=0;k<c.length;k++)f=c[k],f<e?(f+=this.G,(d=a[f])?ac(d,b):a[f]=Fb):(n||(n=Jb(this)),(d=n[f])?ac(d,b):n[f]=Fb)}}
X.prototype.toJSON=function(){return Vb(this.o,Wb,Xb)};X.prototype.C=function(){return!!(Q(this.o)&2)};function ac(a,b){if(Array.isArray(a)){var c=Q(a),d=1;!b||c&2||(d|=16);(c&d)!==d&&R(a,c|d)}}X.prototype.ja=Db;X.prototype.toString=function(){return this.o.toString()};function bc(a,b,c){if(c){var d={},e;for(e in c){var g=c[e],f=g.ra;f||(d.J=g.xa||g.oa.W,g.ia?(d.aa=cc(g.ia),f=function(h){return function(k,l,m){return h.J(k,l,m,h.aa)}}(d)):g.ka?(d.Z=dc(g.da.P,g.ka),f=function(h){return function(k,l,m){return h.J(k,l,m,h.Z)}}(d)):f=d.J,g.ra=f);f(b,a,g.da);d={J:d.J,aa:d.aa,Z:d.Z}}}yb(b,a)}var ec=Symbol();function fc(a,b,c){return a[ec]||(a[ec]=function(d,e){return b(d,e,c)})}
function gc(a){var b=a[ec];if(!b){var c=hc(a);b=function(d,e){return ic(d,e,c)};a[ec]=b}return b}function jc(a){var b=a.ia;if(b)return gc(b);if(b=a.wa)return fc(a.da.P,b,a.ka)}function kc(a){var b=jc(a),c=a.da,d=a.oa.U;return b?function(e,g){return d(e,g,c,b)}:function(e,g){return d(e,g,c)}}function lc(a,b){var c=a[b];"function"==typeof c&&0===c.length&&(c=c(),a[b]=c);return Array.isArray(c)&&(mc in c||nc in c||0<c.length&&"function"==typeof c[0])?c:void 0}
function oc(a,b,c,d,e,g){b.P=a[0];var f=1;if(a.length>f&&"number"!==typeof a[f]){var h=a[f++];c(b,h)}for(;f<a.length;){c=a[f++];for(var k=f+1;k<a.length&&"number"!==typeof a[k];)k++;h=a[f++];k-=f;switch(k){case 0:d(b,c,h);break;case 1:(k=lc(a,f))?(f++,e(b,c,h,k)):d(b,c,h,a[f++]);break;case 2:k=f++;k=lc(a,k);e(b,c,h,k,a[f++]);break;case 3:g(b,c,h,a[f++],a[f++],a[f++]);break;case 4:g(b,c,h,a[f++],a[f++],a[f++],a[f++]);break;default:throw Error("unexpected number of binary field arguments: "+k);}}return b}
var pc=Symbol();function cc(a){var b=a[pc];if(!b){var c=qc(a);b=function(d,e){return rc(d,e,c)};a[pc]=b}return b}function dc(a,b){var c=a[pc];c||(c=function(d,e){return bc(d,e,b)},a[pc]=c);return c}var nc=Symbol();function sc(a,b){a.push(b)}function tc(a,b,c){a.push(b,c.W)}function uc(a,b,c,d){var e=cc(d),g=qc(d).P,f=c.W;a.push(b,function(h,k,l){return f(h,k,l,g,e)})}function vc(a,b,c,d,e,g){var f=dc(d,g),h=c.W;a.push(b,function(k,l,m){return h(k,l,m,d,f)})}
function qc(a){var b=a[nc];if(b)return b;b=oc(a,a[nc]=[],sc,tc,uc,vc);mc in a&&nc in a&&(a.length=0);return b}var mc=Symbol();function wc(a,b){a[0]=b}function xc(a,b,c,d){var e=c.U;a[b]=d?function(g,f,h){return e(g,f,h,d)}:e}function yc(a,b,c,d,e){var g=c.U,f=gc(d),h=hc(d).P;a[b]=function(k,l,m){return g(k,l,m,h,f,e)}}function zc(a,b,c,d,e,g,f){var h=c.U,k=fc(d,e,g);a[b]=function(l,m,r){return h(l,m,r,d,k,f)}}
function hc(a){var b=a[mc];if(b)return b;b=oc(a,a[mc]={},wc,xc,yc,zc);mc in a&&nc in a&&(a.length=0);return b}
function ic(a,b,c){for(;ub(b)&&4!=b.i;){var d=b.l,e=c[d];if(!e){var g=c[0];g&&(g=g[d])&&(e=c[d]=kc(g))}if(!e||!e(b,a,d)){e=b;d=a;g=e.j;vb(e);var f=e;if(!f.ca){e=f.h.h-g;f.h.h=g;f=f.h;if(0==e)e=jb();else{g=pb(f,e);if(f.S&&f.m)e=f.i.subarray(g,g+e);else{f=f.i;var h=g;e=g+e;e=h===e?Pa():Ra?f.slice(h,e):new Uint8Array(f.subarray(h,e))}e=0==e.length?jb():new ib(e,Qa)}(g=d.R)?g.push(e):d.R=[e]}}}return a}
function rc(a,b,c){for(var d=c.length,e=1==d%2,g=e?1:0;g<d;g+=2)(0,c[g+1])(b,a,c[g]);bc(a,b,e?c[0]:void 0)}function Ac(a,b){return{U:a,W:b}}
var Y=Ac(function(a,b,c){if(5!==a.i)return!1;a=a.h;var d=a.i,e=a.h,g=d[e];var f=d[e+1];var h=d[e+2];d=d[e+3];L(a,a.h+4);f=(g<<0|f<<8|h<<16|d<<24)>>>0;a=2*(f>>31)+1;g=f>>>23&255;f&=8388607;U(b,c,255==g?f?NaN:Infinity*a:0==g?a*Math.pow(2,-149)*f:a*Math.pow(2,g-150)*(f+Math.pow(2,23)));return!0},function(a,b,c){b=Mb(b,c);if(null!=b){M(a.h,8*c+5);a=a.h;var d=+b;0===d?0<1/d?G=H=0:(H=0,G=2147483648):isNaN(d)?(H=0,G=2147483647):(d=(c=0>d?-2147483648:0)?-d:d,3.4028234663852886E38<d?(H=0,G=(c|2139095040)>>>
0):1.1754943508222875E-38>d?(d=Math.round(d/Math.pow(2,-149)),H=0,G=(c|d)>>>0):(b=Math.floor(Math.log(d)/Math.LN2),d*=Math.pow(2,-b),d=Math.round(8388608*d),16777216<=d&&++b,H=0,G=(c|b+127<<23|d&8388607)>>>0));c=G;a.h.push(c>>>0&255);a.h.push(c>>>8&255);a.h.push(c>>>16&255);a.h.push(c>>>24&255)}}),Bc=Ac(function(a,b,c){if(0!==a.i)return!1;var d=a.h,e=0,g=a=0,f=d.i,h=d.h;do{var k=f[h++];e|=(k&127)<<g;g+=7}while(32>g&&k&128);32<g&&(a|=(k&127)>>4);for(g=3;32>g&&k&128;g+=7)k=f[h++],a|=(k&127)<<g;L(d,
h);if(128>k){d=e>>>0;k=a>>>0;if(a=k&2147483648)d=~d+1>>>0,k=~k>>>0,0==d&&(k=k+1>>>0);d=4294967296*k+(d>>>0)}else throw Za();U(b,c,a?-d:d);return!0},function(a,b,c){b=S(b,c);null!=b&&("string"===typeof b&&Wa(b),null!=b&&(M(a.h,8*c),"number"===typeof b?(a=a.h,Sa(b),sb(a,G,H)):(c=Wa(b),sb(a.h,c.i,c.h))))}),Cc=Ac(function(a,b,c){if(0!==a.i)return!1;U(b,c,ob(a.h));return!0},function(a,b,c){b=S(b,c);if(null!=b&&null!=b)if(M(a.h,8*c),a=a.h,c=b,0<=c)M(a,c);else{for(b=0;9>b;b++)a.h.push(c&127|128),c>>=7;a.h.push(1)}}),
Dc=Ac(function(a,b,c){if(2!==a.i)return!1;var d=ob(a.h)>>>0;a=a.h;var e=pb(a,d);a=a.i;if(db){var g=a,f;(f=cb)||(f=cb=new TextDecoder("utf-8",{fatal:!0}));a=e+d;g=0===e&&a===g.length?g:g.subarray(e,a);try{var h=f.decode(g)}catch(r){if(void 0===bb){try{f.decode(new Uint8Array([128]))}catch(p){}try{f.decode(new Uint8Array([97])),bb=!0}catch(p){bb=!1}}!bb&&(cb=void 0);throw r;}}else{h=e;d=h+d;e=[];for(var k=null,l,m;h<d;)l=a[h++],128>l?e.push(l):224>l?h>=d?K():(m=a[h++],194>l||128!==(m&192)?(h--,K()):
e.push((l&31)<<6|m&63)):240>l?h>=d-1?K():(m=a[h++],128!==(m&192)||224===l&&160>m||237===l&&160<=m||128!==((g=a[h++])&192)?(h--,K()):e.push((l&15)<<12|(m&63)<<6|g&63)):244>=l?h>=d-2?K():(m=a[h++],128!==(m&192)||0!==(l<<28)+(m-144)>>30||128!==((g=a[h++])&192)||128!==((f=a[h++])&192)?(h--,K()):(l=(l&7)<<18|(m&63)<<12|(g&63)<<6|f&63,l-=65536,e.push((l>>10&1023)+55296,(l&1023)+56320))):K(),8192<=e.length&&(k=ab(k,e),e.length=0);h=ab(k,e)}U(b,c,h);return!0},function(a,b,c){b=S(b,c);if(null!=b){var d=!1;
d=void 0===d?!1:d;if(fb){if(d&&/(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])/.test(b))throw Error("Found an unpaired surrogate");b=(eb||(eb=new TextEncoder)).encode(b)}else{for(var e=0,g=new Uint8Array(3*b.length),f=0;f<b.length;f++){var h=b.charCodeAt(f);if(128>h)g[e++]=h;else{if(2048>h)g[e++]=h>>6|192;else{if(55296<=h&&57343>=h){if(56319>=h&&f<b.length){var k=b.charCodeAt(++f);if(56320<=k&&57343>=k){h=1024*(h-55296)+k-56320+65536;g[e++]=h>>18|240;g[e++]=h>>12&63|128;
g[e++]=h>>6&63|128;g[e++]=h&63|128;continue}else f--}if(d)throw Error("Found an unpaired surrogate");h=65533}g[e++]=h>>12|224;g[e++]=h>>6&63|128}g[e++]=h&63|128}}b=e===g.length?g:g.subarray(0,e)}M(a.h,8*c+2);M(a.h,b.length);N(a,a.h.end());N(a,b)}}),Ec=Ac(function(a,b,c,d,e){if(2!==a.i)return!1;b=Qb(b,c,d);c=a.h.j;d=ob(a.h)>>>0;var g=a.h.h+d,f=g-c;0>=f&&(a.h.j=g,e(b,a,void 0,void 0,void 0),f=g-a.h.h);if(f)throw Error("Message parsing ended unexpectedly. Expected to read "+(d+" bytes, instead read "+
(d-f)+" bytes, either the data ended unexpectedly or the message misreported its own length"));a.h.h=g;a.h.j=c;return!0},function(a,b,c,d,e){b=Ob(b,d,c);if(null!=b)for(d=0;d<b.length;d++){var g=a;M(g.h,8*c+2);var f=g.h.end();N(g,f);f.push(g.i);g=f;e(b[d],a);f=a;var h=g.pop();for(h=f.i+f.h.length()-h;127<h;)g.push(h&127|128),h>>>=7,f.i++;g.push(h);f.i++}});function Fc(a){return function(b,c){a:{if(wb.length){var d=wb.pop();d.setOptions(c);nb(d.h,b,c);b=d}else b=new tb(b,c);try{var e=hc(a);var g=ic(new e.P,b,e);break a}finally{e=b.h,e.i=null,e.m=!1,e.l=0,e.j=0,e.h=0,e.S=!1,b.l=-1,b.i=-1,100>wb.length&&wb.push(b)}g=void 0}return g}}function Gc(a){return function(){var b=new xb;rc(this,b,qc(a));N(b,b.h.end());for(var c=new Uint8Array(b.i),d=b.j,e=d.length,g=0,f=0;f<e;f++){var h=d[f];c.set(h,g);g+=h.length}b.j=[c];return c}};function Z(a){X.call(this,a)}na(Z,X);var Hc=[Z,1,Cc,2,Y,3,Dc,4,Dc];Z.prototype.l=Gc(Hc);function Ic(a){X.call(this,a,-1,Jc)}na(Ic,X);Ic.prototype.addClassification=function(a,b){Qb(this,1,Z,a,b);return this};var Jc=[1],Kc=Fc([Ic,1,Ec,Hc]);function Lc(a){X.call(this,a)}na(Lc,X);var Mc=[Lc,1,Y,2,Y,3,Y,4,Y,5,Y];Lc.prototype.l=Gc(Mc);function Nc(a){X.call(this,a,-1,Oc)}na(Nc,X);var Oc=[1],Pc=Fc([Nc,1,Ec,Mc]);function Qc(a){X.call(this,a)}na(Qc,X);var Rc=[Qc,1,Y,2,Y,3,Y,4,Y,5,Y,6,Bc],Sc=Fc(Rc);Qc.prototype.l=Gc(Rc);function Tc(a,b,c){c=a.createShader(0===c?a.VERTEX_SHADER:a.FRAGMENT_SHADER);a.shaderSource(c,b);a.compileShader(c);if(!a.getShaderParameter(c,a.COMPILE_STATUS))throw Error("Could not compile WebGL shader.\n\n"+a.getShaderInfoLog(c));return c};function Uc(a){return Ob(a,Z,1).map(function(b){var c=S(b,1);return{index:null==c?0:c,qa:W(b,2),label:null!=S(b,3)?Rb(S(b,3),""):void 0,displayName:null!=S(b,4)?Rb(S(b,4),""):void 0}})};function Vc(a){return{x:W(a,1),y:W(a,2),z:W(a,3),visibility:null!=Mb(a,4)?W(a,4):void 0}};function Wc(a,b){this.i=a;this.h=b;this.m=0}
function Xc(a,b,c){Yc(a,b);if("function"===typeof a.h.canvas.transferToImageBitmap)return Promise.resolve(a.h.canvas.transferToImageBitmap());if(c)return Promise.resolve(a.h.canvas);if("function"===typeof createImageBitmap)return createImageBitmap(a.h.canvas);void 0===a.j&&(a.j=document.createElement("canvas"));return new Promise(function(d){a.j.height=a.h.canvas.height;a.j.width=a.h.canvas.width;a.j.getContext("2d",{}).drawImage(a.h.canvas,0,0,a.h.canvas.width,a.h.canvas.height);d(a.j)})}
function Yc(a,b){var c=a.h;if(void 0===a.s){var d=Tc(c,"\n attribute vec2 aVertex;\n attribute vec2 aTex;\n varying vec2 vTex;\n void main(void) {\n gl_Position = vec4(aVertex, 0.0, 1.0);\n vTex = aTex;\n }",0),e=Tc(c,"\n precision mediump float;\n varying vec2 vTex;\n uniform sampler2D sampler0;\n void main(){\n gl_FragColor = texture2D(sampler0, vTex);\n }",1),g=c.createProgram();c.attachShader(g,d);c.attachShader(g,e);c.linkProgram(g);if(!c.getProgramParameter(g,c.LINK_STATUS))throw Error("Could not compile WebGL program.\n\n"+
c.getProgramInfoLog(g));d=a.s=g;c.useProgram(d);e=c.getUniformLocation(d,"sampler0");a.l={O:c.getAttribLocation(d,"aVertex"),N:c.getAttribLocation(d,"aTex"),ya:e};a.v=c.createBuffer();c.bindBuffer(c.ARRAY_BUFFER,a.v);c.enableVertexAttribArray(a.l.O);c.vertexAttribPointer(a.l.O,2,c.FLOAT,!1,0,0);c.bufferData(c.ARRAY_BUFFER,new Float32Array([-1,-1,-1,1,1,1,1,-1]),c.STATIC_DRAW);c.bindBuffer(c.ARRAY_BUFFER,null);a.u=c.createBuffer();c.bindBuffer(c.ARRAY_BUFFER,a.u);c.enableVertexAttribArray(a.l.N);c.vertexAttribPointer(a.l.N,
2,c.FLOAT,!1,0,0);c.bufferData(c.ARRAY_BUFFER,new Float32Array([0,1,0,0,1,0,1,1]),c.STATIC_DRAW);c.bindBuffer(c.ARRAY_BUFFER,null);c.uniform1i(e,0)}d=a.l;c.useProgram(a.s);c.canvas.width=b.width;c.canvas.height=b.height;c.viewport(0,0,b.width,b.height);c.activeTexture(c.TEXTURE0);a.i.bindTexture2d(b.glName);c.enableVertexAttribArray(d.O);c.bindBuffer(c.ARRAY_BUFFER,a.v);c.vertexAttribPointer(d.O,2,c.FLOAT,!1,0,0);c.enableVertexAttribArray(d.N);c.bindBuffer(c.ARRAY_BUFFER,a.u);c.vertexAttribPointer(d.N,
2,c.FLOAT,!1,0,0);c.bindFramebuffer(c.DRAW_FRAMEBUFFER?c.DRAW_FRAMEBUFFER:c.FRAMEBUFFER,null);c.clearColor(0,0,0,0);c.clear(c.COLOR_BUFFER_BIT);c.colorMask(!0,!0,!0,!0);c.drawArrays(c.TRIANGLE_FAN,0,4);c.disableVertexAttribArray(d.O);c.disableVertexAttribArray(d.N);c.bindBuffer(c.ARRAY_BUFFER,null);a.i.bindTexture2d(0)}function Zc(a){this.h=a};var $c=new Uint8Array([0,97,115,109,1,0,0,0,1,4,1,96,0,0,3,2,1,0,10,9,1,7,0,65,0,253,15,26,11]);function ad(a,b){return b+a}function bd(a,b){window[a]=b}function cd(a){var b=document.createElement("script");b.setAttribute("src",a);b.setAttribute("crossorigin","anonymous");return new Promise(function(c){b.addEventListener("load",function(){c()},!1);b.addEventListener("error",function(){c()},!1);document.body.appendChild(b)})}
function dd(){return E(function(a){switch(a.h){case 1:return a.s=2,D(a,WebAssembly.instantiate($c),4);case 4:a.h=3;a.s=0;break;case 2:return a.s=0,a.l=null,a.return(!1);case 3:return a.return(!0)}})}
function ed(a){this.h=a;this.listeners={};this.l={};this.L={};this.s={};this.v={};this.M=this.u=this.ga=!0;this.I=Promise.resolve();this.fa="";this.D={};this.locateFile=a&&a.locateFile||ad;if("object"===typeof window)var b=window.location.pathname.toString().substring(0,window.location.pathname.toString().lastIndexOf("/"))+"/";else if("undefined"!==typeof location)b=location.pathname.toString().substring(0,location.pathname.toString().lastIndexOf("/"))+"/";else throw Error("solutions can only be loaded on a web page or in a web worker");
this.ha=b;if(a.options){b=A(Object.keys(a.options));for(var c=b.next();!c.done;c=b.next()){c=c.value;var d=a.options[c].default;void 0!==d&&(this.l[c]="function"===typeof d?d():d)}}}x=ed.prototype;x.close=function(){this.j&&this.j.delete();return Promise.resolve()};
function fd(a){var b,c,d,e,g,f,h,k,l,m,r;return E(function(p){switch(p.h){case 1:if(!a.ga)return p.return();b=void 0===a.h.files?[]:"function"===typeof a.h.files?a.h.files(a.l):a.h.files;return D(p,dd(),2);case 2:c=p.i;if("object"===typeof window)return bd("createMediapipeSolutionsWasm",{locateFile:a.locateFile}),bd("createMediapipeSolutionsPackedAssets",{locateFile:a.locateFile}),f=b.filter(function(n){return void 0!==n.data}),h=b.filter(function(n){return void 0===n.data}),k=Promise.all(f.map(function(n){var q=
gd(a,n.url);if(void 0!==n.path){var t=n.path;q=q.then(function(w){a.overrideFile(t,w);return Promise.resolve(w)})}return q})),l=Promise.all(h.map(function(n){return void 0===n.simd||n.simd&&c||!n.simd&&!c?cd(a.locateFile(n.url,a.ha)):Promise.resolve()})).then(function(){var n,q,t;return E(function(w){if(1==w.h)return n=window.createMediapipeSolutionsWasm,q=window.createMediapipeSolutionsPackedAssets,t=a,D(w,n(q),2);t.i=w.i;w.h=0})}),m=function(){return E(function(n){a.h.graph&&a.h.graph.url?n=D(n,
gd(a,a.h.graph.url),0):(n.h=0,n=void 0);return n})}(),D(p,Promise.all([l,k,m]),7);if("function"!==typeof importScripts)throw Error("solutions can only be loaded on a web page or in a web worker");d=b.filter(function(n){return void 0===n.simd||n.simd&&c||!n.simd&&!c}).map(function(n){return a.locateFile(n.url,a.ha)});importScripts.apply(null,ea(d));e=a;return D(p,createMediapipeSolutionsWasm(Module),6);case 6:e.i=p.i;a.m=new OffscreenCanvas(1,1);a.i.canvas=a.m;g=a.i.GL.createContext(a.m,{antialias:!1,
alpha:!1,va:"undefined"!==typeof WebGL2RenderingContext?2:1});a.i.GL.makeContextCurrent(g);p.h=4;break;case 7:a.m=document.createElement("canvas");r=a.m.getContext("webgl2",{});if(!r&&(r=a.m.getContext("webgl",{}),!r))return alert("Failed to create WebGL canvas context when passing video frame."),p.return();a.K=r;a.i.canvas=a.m;a.i.createContext(a.m,!0,!0,{});case 4:a.j=new a.i.SolutionWasm,a.ga=!1,p.h=0}})}
function hd(a){var b,c,d,e,g,f,h,k;return E(function(l){if(1==l.h){if(a.h.graph&&a.h.graph.url&&a.fa===a.h.graph.url)return l.return();a.u=!0;if(!a.h.graph||!a.h.graph.url){l.h=2;return}a.fa=a.h.graph.url;return D(l,gd(a,a.h.graph.url),3)}2!=l.h&&(b=l.i,a.j.loadGraph(b));c=A(Object.keys(a.D));for(d=c.next();!d.done;d=c.next())e=d.value,a.j.overrideFile(e,a.D[e]);a.D={};if(a.h.listeners)for(g=A(a.h.listeners),f=g.next();!f.done;f=g.next())h=f.value,id(a,h);k=a.l;a.l={};a.setOptions(k);l.h=0})}
x.reset=function(){var a=this;return E(function(b){a.j&&(a.j.reset(),a.s={},a.v={});b.h=0})};
x.setOptions=function(a,b){var c=this;if(b=b||this.h.options){for(var d=[],e=[],g={},f=A(Object.keys(a)),h=f.next();!h.done;g={X:g.X,Y:g.Y},h=f.next())if(h=h.value,!(h in this.l&&this.l[h]===a[h])){this.l[h]=a[h];var k=b[h];void 0!==k&&(k.onChange&&(g.X=k.onChange,g.Y=a[h],d.push(function(l){return function(){var m;return E(function(r){if(1==r.h)return D(r,l.X(l.Y),2);m=r.i;!0===m&&(c.u=!0);r.h=0})}}(g))),k.graphOptionXref&&(h=Object.assign({},{calculatorName:"",calculatorIndex:0},k.graphOptionXref,
{valueNumber:1===k.type?a[h]:0,valueBoolean:0===k.type?a[h]:!1,valueString:2===k.type?a[h]:""}),e.push(h)))}if(0!==d.length||0!==e.length)this.u=!0,this.H=(void 0===this.H?[]:this.H).concat(e),this.F=(void 0===this.F?[]:this.F).concat(d)}};
function jd(a){var b,c,d,e,g,f,h;return E(function(k){switch(k.h){case 1:if(!a.u)return k.return();if(!a.F){k.h=2;break}b=A(a.F);c=b.next();case 3:if(c.done){k.h=5;break}d=c.value;return D(k,d(),4);case 4:c=b.next();k.h=3;break;case 5:a.F=void 0;case 2:if(a.H){e=new a.i.GraphOptionChangeRequestList;g=A(a.H);for(f=g.next();!f.done;f=g.next())h=f.value,e.push_back(h);a.j.changeOptions(e);e.delete();a.H=void 0}a.u=!1;k.h=0}})}
x.initialize=function(){var a=this;return E(function(b){return 1==b.h?D(b,fd(a),2):3!=b.h?D(b,hd(a),3):D(b,jd(a),0)})};function gd(a,b){var c,d;return E(function(e){if(b in a.L)return e.return(a.L[b]);c=a.locateFile(b,"");d=fetch(c).then(function(g){return g.arrayBuffer()});a.L[b]=d;return e.return(d)})}x.overrideFile=function(a,b){this.j?this.j.overrideFile(a,b):this.D[a]=b};x.clearOverriddenFiles=function(){this.D={};this.j&&this.j.clearOverriddenFiles()};
x.send=function(a,b){var c=this,d,e,g,f,h,k,l,m,r;return E(function(p){switch(p.h){case 1:if(!c.h.inputs)return p.return();d=1E3*(void 0===b||null===b?performance.now():b);return D(p,c.I,2);case 2:return D(p,c.initialize(),3);case 3:e=new c.i.PacketDataList;g=A(Object.keys(a));for(f=g.next();!f.done;f=g.next())if(h=f.value,k=c.h.inputs[h]){a:{var n=a[h];switch(k.type){case "video":var q=c.s[k.stream];q||(q=new Wc(c.i,c.K),c.s[k.stream]=q);0===q.m&&(q.m=q.i.createTexture());if("undefined"!==typeof HTMLVideoElement&&
n instanceof HTMLVideoElement){var t=n.videoWidth;var w=n.videoHeight}else"undefined"!==typeof HTMLImageElement&&n instanceof HTMLImageElement?(t=n.naturalWidth,w=n.naturalHeight):(t=n.width,w=n.height);w={glName:q.m,width:t,height:w};t=q.h;t.canvas.width=w.width;t.canvas.height=w.height;t.activeTexture(t.TEXTURE0);q.i.bindTexture2d(q.m);t.texImage2D(t.TEXTURE_2D,0,t.RGBA,t.RGBA,t.UNSIGNED_BYTE,n);q.i.bindTexture2d(0);q=w;break a;case "detections":q=c.s[k.stream];q||(q=new Zc(c.i),c.s[k.stream]=q);
q.data||(q.data=new q.h.DetectionListData);q.data.reset(n.length);for(w=0;w<n.length;++w){t=n[w];var v=q.data,B=v.setBoundingBox,J=w;var I=t.la;var u=new Qc;V(u,1,I.sa);V(u,2,I.ta);V(u,3,I.height);V(u,4,I.width);V(u,5,I.rotation);U(u,6,I.pa);I=u.l();B.call(v,J,I);if(t.ea)for(v=0;v<t.ea.length;++v){u=t.ea[v];B=q.data;J=B.addNormalizedLandmark;I=w;u=Object.assign({},u,{visibility:u.visibility?u.visibility:0});var C=new Lc;V(C,1,u.x);V(C,2,u.y);V(C,3,u.z);u.visibility&&V(C,4,u.visibility);u=C.l();J.call(B,
I,u)}if(t.ba)for(v=0;v<t.ba.length;++v)B=q.data,J=B.addClassification,I=w,u=t.ba[v],C=new Z,V(C,2,u.qa),u.index&&U(C,1,u.index),u.label&&U(C,3,u.label),u.displayName&&U(C,4,u.displayName),u=C.l(),J.call(B,I,u)}q=q.data;break a;default:q={}}}l=q;m=k.stream;switch(k.type){case "video":e.pushTexture2d(Object.assign({},l,{stream:m,timestamp:d}));break;case "detections":r=l;r.stream=m;r.timestamp=d;e.pushDetectionList(r);break;default:throw Error("Unknown input config type: '"+k.type+"'");}}c.j.send(e);
return D(p,c.I,4);case 4:e.delete(),p.h=0}})};
function kd(a,b,c){var d,e,g,f,h,k,l,m,r,p,n,q,t,w;return E(function(v){switch(v.h){case 1:if(!c)return v.return(b);d={};e=0;g=A(Object.keys(c));for(f=g.next();!f.done;f=g.next())h=f.value,k=c[h],"string"!==typeof k&&"texture"===k.type&&void 0!==b[k.stream]&&++e;1<e&&(a.M=!1);l=A(Object.keys(c));f=l.next();case 2:if(f.done){v.h=4;break}m=f.value;r=c[m];if("string"===typeof r)return t=d,w=m,D(v,ld(a,m,b[r]),14);p=b[r.stream];if("detection_list"===r.type){if(p){var B=p.getRectList();for(var J=p.getLandmarksList(),
I=p.getClassificationsList(),u=[],C=0;C<B.size();++C){var T=Sc(B.get(C)),od=W(T,1),pd=W(T,2),qd=W(T,3),rd=W(T,4),sd=W(T,5,0),za=void 0;za=void 0===za?0:za;T={la:{sa:od,ta:pd,height:qd,width:rd,rotation:sd,pa:Rb(S(T,6),za)},ea:Ob(Pc(J.get(C)),Lc,1).map(Vc),ba:Uc(Kc(I.get(C)))};u.push(T)}B=u}else B=[];d[m]=B;v.h=7;break}if("proto_list"===r.type){if(p){B=Array(p.size());for(J=0;J<p.size();J++)B[J]=p.get(J);p.delete()}else B=[];d[m]=B;v.h=7;break}if(void 0===p){v.h=3;break}if("float_list"===r.type){d[m]=
p;v.h=7;break}if("proto"===r.type){d[m]=p;v.h=7;break}if("texture"!==r.type)throw Error("Unknown output config type: '"+r.type+"'");n=a.v[m];n||(n=new Wc(a.i,a.K),a.v[m]=n);return D(v,Xc(n,p,a.M),13);case 13:q=v.i,d[m]=q;case 7:r.transform&&d[m]&&(d[m]=r.transform(d[m]));v.h=3;break;case 14:t[w]=v.i;case 3:f=l.next();v.h=2;break;case 4:return v.return(d)}})}
function ld(a,b,c){var d;return E(function(e){return"number"===typeof c||c instanceof Uint8Array||c instanceof a.i.Uint8BlobList?e.return(c):c instanceof a.i.Texture2dDataOut?(d=a.v[b],d||(d=new Wc(a.i,a.K),a.v[b]=d),e.return(Xc(d,c,a.M))):e.return(void 0)})}
function id(a,b){for(var c=b.name||"$",d=[].concat(ea(b.wants)),e=new a.i.StringList,g=A(b.wants),f=g.next();!f.done;f=g.next())e.push_back(f.value);g=a.i.PacketListener.implement({onResults:function(h){for(var k={},l=0;l<b.wants.length;++l)k[d[l]]=h.get(l);var m=a.listeners[c];m&&(a.I=kd(a,k,b.outs).then(function(r){r=m(r);for(var p=0;p<b.wants.length;++p){var n=k[d[p]];"object"===typeof n&&n.hasOwnProperty&&n.hasOwnProperty("delete")&&n.delete()}r&&(a.I=r)}))}});a.j.attachMultiListener(e,g);e.delete()}
x.onResults=function(a,b){this.listeners[b||"$"]=a};Aa("Solution",ed);Aa("OptionType",{BOOL:0,NUMBER:1,ua:2,0:"BOOL",1:"NUMBER",2:"STRING"});function md(a){void 0===a&&(a=0);switch(a){case 1:return"selfie_segmentation_landscape.tflite";default:return"selfie_segmentation.tflite"}}
function nd(a){var b=this;a=a||{};this.h=new ed({locateFile:a.locateFile,files:function(c){return[{simd:!0,url:"selfie_segmentation_solution_simd_wasm_bin.js"},{simd:!1,url:"selfie_segmentation_solution_wasm_bin.js"},{data:!0,url:md(c.modelSelection)}]},graph:{url:"selfie_segmentation.binarypb"},listeners:[{wants:["segmentation_mask","image_transformed"],outs:{image:{type:"texture",stream:"image_transformed"},segmentationMask:{type:"texture",stream:"segmentation_mask"}}}],inputs:{image:{type:"video",
stream:"input_frames_gpu"}},options:{useCpuInference:{type:0,graphOptionXref:{calculatorType:"InferenceCalculator",fieldName:"use_cpu_inference"},default:"object"!==typeof window||void 0===window.navigator?!1:"iPad Simulator;iPhone Simulator;iPod Simulator;iPad;iPhone;iPod".split(";").includes(navigator.platform)||navigator.userAgent.includes("Mac")&&"ontouchend"in document},selfieMode:{type:0,graphOptionXref:{calculatorType:"GlScalerCalculator",calculatorIndex:1,fieldName:"flip_horizontal"}},modelSelection:{type:1,
graphOptionXref:{calculatorType:"ConstantSidePacketCalculator",calculatorName:"ConstantSidePacketCalculatorModelSelection",fieldName:"int_value"},onChange:function(c){var d,e,g;return E(function(f){if(1==f.h)return d=md(c),e="third_party/mediapipe/modules/selfie_segmentation/"+d,D(f,gd(b.h,d),2);g=f.i;b.h.overrideFile(e,g);return f.return(!0)})}}}})}x=nd.prototype;x.close=function(){this.h.close();return Promise.resolve()};x.onResults=function(a){this.h.onResults(a)};
x.initialize=function(){var a=this;return E(function(b){return D(b,a.h.initialize(),0)})};x.reset=function(){this.h.reset()};x.send=function(a){var b=this;return E(function(c){return D(c,b.h.send(a),0)})};x.setOptions=function(a){this.h.setOptions(a)};Aa("SelfieSegmentation",nd);Aa("VERSION","0.1.1675465747");}).call(this);

83
public/js/tailwind.js Normal file

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,398 @@
cordova.define("cordova-plugin-printer.Printer", function(require, exports, module) {
/*
Copyright 2013 Sebastián Katzer
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
var exec = require('cordova/exec'),
ua = navigator.userAgent.toLowerCase(),
isIOS = ua.indexOf('ipad') > -1 || ua.indexOf('iphone') > -1;
// Defaults
exports._defaults = {
// name: 'unknown',
// duplex: 'none',
// orientation: 'landscape',
// monochrome: false,
// photo: false,
// copies: 1,
// pageCount: 1,
// maxHeight: '10cm',
// maxWidth: '10cm',
// font: {
// name: 'Helvetica',
// align: 'left',
// italic: false,
// bold: false,
// color: '#FF0000'
// },
// margin: {
// top: 0,
// left: 0,
// bottom: 0,
// right: 0
// },
// ui: {
// hideNumberOfCopies: false,
// hidePaperFormat: false,
// top: 30,
// left: 40,
// height: 0,
// width: 0
// },
// paper: {
// height: 0,
// width: 0,
// length: 0,
// name: 'A4'
// },
// header: {
// height: '1cm',
// labels: [{
// text: 'Awesome Printer Plug-in',
// font: {
// align: 'center',
// italic: true,
// color: '#FF0000'
// }
// },{
// showPageIndex: true,
// font: {
// align: 'right',
// bold: true
// }
// }]
// },
// footer: {
// height: '3mm',
// label: {
// text: 'Copyright (c) 2013-2019 Sebastián Katzer',
// font: { size: 9 },
// top: '1.5mm',
// right: '5mm'
// }
// }
};
/**
* Test if the printer service is able to print
* the ressource specified by URL.
*
* @param [ String ] uri A file URI.
* @param [ Function ] callback The callback function.
* @param [ Object ] scope The scope for the function.
*
* @return [ Void ]
*/
exports.canPrintItem = function (uri, callback, scope)
{
if (typeof uri == 'function')
{
scope = callback;
callback = uri;
uri = null;
}
var fn = this._createCallbackFn(callback, scope);
exec(fn, null, 'Printer', 'check', [uri]);
};
/**
* Returns a list of all printable document types.
*
* @param [ Function ] callback The callback function.
* @param [ Object ] scope The scope for the function.
*
* @return [ Void ]
*/
exports.getPrintableTypes = function (callback, scope)
{
var fn = this._createCallbackFn(callback, scope);
exec(fn, null, 'Printer', 'types', []);
};
/**
* Displays system interface for selecting a printer.
*
* @param [ Object ] options Optional ui options.
* @param [ Function ] callback The callback function.
* @param [ Object ] scope The scope for the function.
*
* @return [ Void ]
*/
exports.pick = function (options, callback, scope)
{
if (typeof options == 'function')
{
scope = callback;
callback = options;
options = {};
}
var fn = this._createCallbackFn(callback, scope),
params = this._mergeWithDefaults({ ui: options });
if (isIOS)
{
exec(fn, null, 'Printer', 'pick', [params]);
}
else if (fn)
{
fn(null);
}
};
/**
* Sends the content to the printer.
*
* @param [ String ] content The plain/html text or a file URI.
* @param [ Object ] options Options for the print job.
* @param [ Function ] callback The callback function.
* @param [ Object ] scope The scope for the function.
*/
exports.print = function (content, options, callback, scope)
{
if (typeof content == 'function')
{
scope = options;
callback = content;
options = {};
content = null;
}
if (typeof options == 'function')
{
scope = callback;
callback = options;
options = typeof content != 'string' ? content : {};
content = typeof content == 'string' ? content : null;
}
var fn = this._createCallbackFn(callback, scope),
params = this._mergeWithDefaults(options || {});
exec(fn, null, 'Printer', 'print', [content || '', params]);
};
/**
* The (platform specific) default settings.
*
* @return [ Object ]
*/
exports.getDefaults = function ()
{
var map = Object.assign({}, this._defaults);
for (var key in map)
{
if (Array.isArray(map[key]))
{
map[key] = Array.from(map[key]);
}
else if (Object.prototype.isPrototypeOf(map[key]))
{
map[key] = Object.assign({}, map[key]);
}
}
return map;
};
/**
* Overwrite default settings.
*
* @param [ Object ] newDefaults New default values.
*
* @return [ Void ]
*/
exports.setDefaults = function (newDefaults)
{
Object.assign(this._defaults, newDefaults);
};
/**
* Merge custom properties with the default values.
*
* @param [ Object ] options Set of custom values.
*
* @retrun [ Object ]
*/
exports._mergeWithDefaults = function (options)
{
var defaults = this.getDefaults();
if (options.duplex && typeof options.duplex == 'boolean')
{
options.duplex = options.duplex ? 'long' : 'none';
}
Object.assign(defaults, options);
for (var key in defaults)
{
if (defaults[key] !== null)
{
options[key] = defaults[key];
}
else
{
delete options[key];
}
}
return options;
};
/**
* @private
*
* Creates a callback, which will be executed
* within a specific scope.
*
* @param [ Function ] callback The callback function.
* @param [ Object ] scope The scope for the function.
*
* @return [ Function ] The new callback function
*/
exports._createCallbackFn = function (callback, scope)
{
if (typeof callback !== 'function')
return;
return function () {
callback.apply(scope || this, arguments);
};
};
// Polyfill for Object.assign
if (typeof Object.assign != 'function') {
Object.assign = function(target) {
'use strict';
if (target == null) {
throw new TypeError('Cannot convert undefined or null to object');
}
target = Object(target);
for (var index = 1; index < arguments.length; index++) {
var source = arguments[index];
if (source != null) {
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}
}
return target;
};
}
// Polyfill for Array.from
// Production steps of ECMA-262, Edition 6, 22.1.2.1
// Reference: https://people.mozilla.org/~jorendorff/es6-draft.html#sec-array.from
if (!Array.from) {
Array.from = (function () {
var toStr = Object.prototype.toString;
var isCallable = function (fn) {
return typeof fn === 'function' || toStr.call(fn) === '[object Function]';
};
var toInteger = function (value) {
var number = Number(value);
if (isNaN(number)) { return 0; }
if (number === 0 || !isFinite(number)) { return number; }
return (number > 0 ? 1 : -1) * Math.floor(Math.abs(number));
};
var maxSafeInteger = Math.pow(2, 53) - 1;
var toLength = function (value) {
var len = toInteger(value);
return Math.min(Math.max(len, 0), maxSafeInteger);
};
// The length property of the from method is 1.
return function from(arrayLike/*, mapFn, thisArg */) {
// 1. Let C be the this value.
var C = this;
// 2. Let items be ToObject(arrayLike).
var items = Object(arrayLike);
// 3. ReturnIfAbrupt(items).
if (arrayLike == null) {
throw new TypeError("Array.from requires an array-like object - not null or undefined");
}
// 4. If mapfn is undefined, then let mapping be false.
var mapFn = arguments.length > 1 ? arguments[1] : void undefined;
var T;
if (typeof mapFn !== 'undefined') {
// 5. else
// 5. a If IsCallable(mapfn) is false, throw a TypeError exception.
if (!isCallable(mapFn)) {
throw new TypeError('Array.from: when provided, the second argument must be a function');
}
// 5. b. If thisArg was supplied, let T be thisArg; else let T be undefined.
if (arguments.length > 2) {
T = arguments[2];
}
}
// 10. Let lenValue be Get(items, "length").
// 11. Let len be ToLength(lenValue).
var len = toLength(items.length);
// 13. If IsConstructor(C) is true, then
// 13. a. Let A be the result of calling the [[Construct]] internal method of C with an argument list containing the single item len.
// 14. a. Else, Let A be ArrayCreate(len).
var A = isCallable(C) ? Object(new C(len)) : new Array(len);
// 16. Let k be 0.
var k = 0;
// 17. Repeat, while k < len… (also steps a - h)
var kValue;
while (k < len) {
kValue = items[k];
if (mapFn) {
A[k] = typeof T === 'undefined' ? mapFn(kValue, k) : mapFn.call(T, kValue, k);
} else {
A[k] = kValue;
}
k += 1;
}
// 18. Let putStatus be Put(A, "length", len, true).
A.length = len;
// 20. Return A.
return A;
};
}());
}
});

View File

@@ -0,0 +1,159 @@
cordova.define("cordova-plugin-x-socialsharing.SocialSharing", function(require, exports, module) {
function SocialSharing() {
}
// Override this method (after deviceready) to set the location where you want the iPad popup arrow to appear.
// If not overridden with different values, the popup is not used. Example:
//
// window.plugins.socialsharing.iPadPopupCoordinates = function() {
// return "100,100,200,300";
// };
SocialSharing.prototype.iPadPopupCoordinates = function () {
// left,top,width,height
return "-1,-1,-1,-1";
};
SocialSharing.prototype.setIPadPopupCoordinates = function (coords) {
console.log("Deprecated - setIPadPopupCoordinates no longer works since plugin version 5.5.0. See https://github.com/EddyVerbruggen/SocialSharing-PhoneGap-Plugin/issues/1052");
// left,top,width,height
cordova.exec(function() {}, this._getErrorCallback(function() {}, "setIPadPopupCoordinates"), "SocialSharing", "setIPadPopupCoordinates", [coords]);
};
SocialSharing.prototype.available = function (callback) {
cordova.exec(function (avail) {
callback(avail ? true : false);
}, null, "SocialSharing", "available", []);
};
// this is the recommended way to share as it is the most feature-rich with respect to what you pass in and get back
SocialSharing.prototype.shareWithOptions = function (options, successCallback, errorCallback) {
cordova.exec(successCallback, this._getErrorCallback(errorCallback, "shareWithOptions"), "SocialSharing", "shareWithOptions", [options]);
};
SocialSharing.prototype.shareW3C = function (sharedata) {
return new Promise(function(resolve, reject) {
var options = {
subject: sharedata.title,
message: sharedata.text,
url: sharedata.url,
iPadCoordinates: sharedata.iPadCoordinates || undefined
};
if(sharedata.hasOwnProperty('title') ||
sharedata.hasOwnProperty('text') ||
sharedata.hasOwnProperty('url')) {
cordova.exec(resolve, reject, "SocialSharing", "shareWithOptions", [options]);
} else {
reject();
}
});
};
SocialSharing.prototype.share = function (message, subject, fileOrFileArray, url, iPadCoordinates, successCallback, errorCallback) {
if (typeof iPadCoordinates === 'function') {
errorCallback = successCallback;
successCallback = iPadCoordinates;
iPadCoordinates = "";
}
cordova.exec(successCallback, this._getErrorCallback(errorCallback, "share"), "SocialSharing", "share", [message, subject, this._asArray(fileOrFileArray), url, iPadCoordinates]);
};
SocialSharing.prototype.shareViaTwitter = function (message, file /* multiple not allowed by twitter */, url, successCallback, errorCallback) {
var fileArray = this._asArray(file);
var ecb = this._getErrorCallback(errorCallback, "shareViaTwitter");
if (fileArray.length > 1) {
ecb("shareViaTwitter supports max one file");
} else {
cordova.exec(successCallback, ecb, "SocialSharing", "shareViaTwitter", [message, null, fileArray, url]);
}
};
SocialSharing.prototype.shareViaFacebook = function (message, fileOrFileArray, url, successCallback, errorCallback) {
cordova.exec(successCallback, this._getErrorCallback(errorCallback, "shareViaFacebook"), "SocialSharing", "shareViaFacebook", [message, null, this._asArray(fileOrFileArray), url]);
};
SocialSharing.prototype.shareViaFacebookWithPasteMessageHint = function (message, fileOrFileArray, url, pasteMessageHint, successCallback, errorCallback) {
pasteMessageHint = pasteMessageHint || "If you like you can paste a message from your clipboard";
cordova.exec(successCallback, this._getErrorCallback(errorCallback, "shareViaFacebookWithPasteMessageHint"), "SocialSharing", "shareViaFacebookWithPasteMessageHint", [message, null, this._asArray(fileOrFileArray), url, pasteMessageHint]);
};
SocialSharing.prototype.shareViaWhatsApp = function (message, fileOrFileArray, url, successCallback, errorCallback) {
cordova.exec(successCallback, this._getErrorCallback(errorCallback, "shareViaWhatsApp"), "SocialSharing", "shareViaWhatsApp", [message, null, this._asArray(fileOrFileArray), url, null, null]);
};
SocialSharing.prototype.shareViaWhatsAppToReceiver = function (receiver, message, fileOrFileArray, url, successCallback, errorCallback) {
cordova.exec(successCallback, this._getErrorCallback(errorCallback, "shareViaWhatsAppToReceiver"), "SocialSharing", "shareViaWhatsApp", [message, null, this._asArray(fileOrFileArray), url, receiver, null]);
};
SocialSharing.prototype.shareViaWhatsAppToPhone = function (phone, message, fileOrFileArray, url, successCallback, errorCallback) {
cordova.exec(successCallback, this._getErrorCallback(errorCallback, "shareViaWhatsAppToPhone"), "SocialSharing", "shareViaWhatsApp", [message, null, this._asArray(fileOrFileArray), url, null, phone]);
};
SocialSharing.prototype.shareViaSMS = function (options, phonenumbers, successCallback, errorCallback) {
var opts = options;
if (typeof options === "string") {
opts = {"message":options}; // for backward compatibility as the options param used to be the message
}
cordova.exec(successCallback, this._getErrorCallback(errorCallback, "shareViaSMS"), "SocialSharing", "shareViaSMS", [opts, phonenumbers]);
};
SocialSharing.prototype.shareViaEmail = function (message, subject, toArray, ccArray, bccArray, fileOrFileArray, successCallback, errorCallback) {
cordova.exec(successCallback, this._getErrorCallback(errorCallback, "shareViaEmail"), "SocialSharing", "shareViaEmail", [message, subject, this._asArray(toArray), this._asArray(ccArray), this._asArray(bccArray), this._asArray(fileOrFileArray)]);
};
SocialSharing.prototype.canShareVia = function (via, message, subject, fileOrFileArray, url, successCallback, errorCallback) {
cordova.exec(successCallback, this._getErrorCallback(errorCallback, "canShareVia"), "SocialSharing", "canShareVia", [message, subject, this._asArray(fileOrFileArray), url, via]);
};
SocialSharing.prototype.canShareViaEmail = function (successCallback, errorCallback) {
cordova.exec(successCallback, this._getErrorCallback(errorCallback, "canShareViaEmail"), "SocialSharing", "canShareViaEmail", []);
};
SocialSharing.prototype.shareViaInstagram = function (message, fileOrFileArray, successCallback, errorCallback) {
cordova.exec(successCallback, this._getErrorCallback(errorCallback, "shareViaInstagram"), "SocialSharing", "shareViaInstagram", [message, null, this._asArray(fileOrFileArray), null]);
};
SocialSharing.prototype.shareVia = function (via, message, subject, fileOrFileArray, url, successCallback, errorCallback) {
cordova.exec(successCallback, this._getErrorCallback(errorCallback, "shareVia"), "SocialSharing", "shareVia", [message, subject, this._asArray(fileOrFileArray), url, via]);
};
SocialSharing.prototype.saveToPhotoAlbum = function (fileOrFileArray, successCallback, errorCallback) {
cordova.exec(successCallback, this._getErrorCallback(errorCallback, "saveToPhotoAlbum"), "SocialSharing", "saveToPhotoAlbum", [this._asArray(fileOrFileArray)]);
};
SocialSharing.prototype._asArray = function (param) {
if (param == null) {
param = [];
} else if (typeof param === 'string') {
param = new Array(param);
}
return param;
};
SocialSharing.prototype._getErrorCallback = function (ecb, functionName) {
if (typeof ecb === 'function') {
return ecb;
} else {
return function (result) {
console.log("The injected error callback of '" + functionName + "' received: " + JSON.stringify(result));
}
}
};
SocialSharing.install = function () {
if (!window.plugins) {
window.plugins = {};
}
window.plugins.socialsharing = new SocialSharing();
// Note only polyfill navigator.share if it is not defined, since shareW3C implements L1 of the spec,
// and an existing navigator.share method could implement L2.
if (!navigator.share) {
navigator.share = window.plugins.socialsharing.shareW3C;
}
return window.plugins.socialsharing;
};
cordova.addConstructor(SocialSharing.install);
});

File diff suppressed because one or more lines are too long