diff --git a/public/css/style.css b/public/css/style.css index c9aadaa..160e39d 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -269,6 +269,24 @@ html, body { #next-scene:hover { background: var(--pink-soft); } @keyframes pop { from { transform: scale(0.85); opacity: 0; } to { transform: scale(1); opacity: 1; } } +/* ---- Fehleranzeige ---- */ +#error-box { + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + max-width: 82%; + background: #2a1020; + color: #ffd0e2; + border: 1px solid var(--pink); + padding: 16px 20px; + border-radius: 12px; + font-size: 14px; + line-height: 1.4; + text-align: center; + z-index: 20; +} + .hidden { display: none !important; } /* Mobile */ diff --git a/public/js/app.js b/public/js/app.js index 3456263..471cb92 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -31,12 +31,30 @@ function showToast(message) { showToast._t = setTimeout(() => toast.classList.remove('show'), 900); } +function showError(err) { + let box = document.getElementById('error-box'); + if (!box) { + box = document.createElement('div'); + box.id = 'error-box'; + stage.appendChild(box); + } + const msg = err && err.message ? err.message : String(err); + box.textContent = 'Fehler beim Laden der Szene: ' + msg; + box.classList.remove('hidden'); +} + +function clearError() { + const box = document.getElementById('error-box'); + if (box) box.classList.add('hidden'); +} + async function loadScene() { showLoading(true); + clearError(); winOverlay.classList.add('hidden'); try { const res = await fetch('/api/scene'); - if (!res.ok) throw new Error('Szene konnte nicht geladen werden.'); + if (!res.ok) throw new Error('Server-Antwort ' + res.status); current = await res.json(); await scene.render(current); scene.resetView(); @@ -44,7 +62,7 @@ async function loadScene() { document.getElementById('category-name').textContent = current.category || ''; } catch (err) { console.error(err); - showToast('Ups — Szene konnte nicht geladen werden.'); + showError(err); } finally { showLoading(false); } diff --git a/public/js/scene.js b/public/js/scene.js index 7da12fd..11c9530 100644 --- a/public/js/scene.js +++ b/public/js/scene.js @@ -17,7 +17,8 @@ let emojiAssetsAvailable = null; // pro Session: liegt ein Emoji-Bildset vor? function loadImage(url) { return new Promise((resolve) => { const img = new Image(); - img.crossOrigin = 'anonymous'; + // Kein crossOrigin: Assets sind same-origin. crossOrigin kann je nach Browser/Proxy + // dazu fuehren, dass das Canvas als "tainted" gilt und der Export fehlschlaegt. img.onload = () => resolve({ url, img }); img.onerror = () => resolve({ url, img: null }); img.src = url; @@ -155,9 +156,20 @@ export class Scene { ctx.restore(); } - return await new Promise((resolve) => { - canvas.toBlob((blob) => resolve(URL.createObjectURL(blob)), 'image/png'); - }); + // Export als Blob-URL; mit Fallback auf dataURL, falls toBlob nicht greift. + try { + const blob = await new Promise((resolve, reject) => { + try { + canvas.toBlob((b) => (b ? resolve(b) : reject(new Error('toBlob lieferte null'))), 'image/png'); + } catch (err) { + reject(err); + } + }); + return URL.createObjectURL(blob); + } catch (err) { + console.warn('[scene] toBlob fehlgeschlagen, versuche toDataURL:', err); + return canvas.toDataURL('image/png'); // wirft nur, wenn Canvas wirklich "tainted" ist + } } _drawItem(ctx, item) { diff --git a/server/index.js b/server/index.js index 8018524..e832642 100644 --- a/server/index.js +++ b/server/index.js @@ -57,8 +57,14 @@ if (scenePool.length === 0) { const app = express(); app.use(express.json()); -// Statisches Frontend + Assets -app.use(express.static(PUBLIC_DIR)); +// Statisches Frontend + Assets. +// no-cache fuers Frontend (HTML/JS/CSS): Browser/Proxy muss revalidieren -> nie altes +// Skript nach einem Update (haeufige Ursache fuer "geht gar nix" nach git pull). +app.use( + express.static(PUBLIC_DIR, { + setHeaders: (res) => res.setHeader('Cache-Control', 'no-cache'), + }) +); app.use('/scenes', express.static(SCENES_DIR)); app.use('/library', express.static(LIBRARY_DIR)); if (existsSync(PANZOOM_DIST)) {