From fe66a5abe2266b9c6b806db5268127bd43995110 Mon Sep 17 00:00:00 2001 From: Honey Date: Mon, 9 Feb 2026 19:26:02 +0100 Subject: [PATCH] PWA Banner entfernt und Laden verbessert --- public/{img => icons}/icon-192.png | Bin public/{img => icons}/icon-512.png | Bin public/index.html | 81 ++++++++----------- public/manifest.json | 8 +- public/script.js | 126 ++++++++++++++++++++--------- public/style.css | 22 ++++- server.js | 55 ++++++++++--- 7 files changed, 186 insertions(+), 106 deletions(-) rename public/{img => icons}/icon-192.png (100%) rename public/{img => icons}/icon-512.png (100%) diff --git a/public/img/icon-192.png b/public/icons/icon-192.png similarity index 100% rename from public/img/icon-192.png rename to public/icons/icon-192.png diff --git a/public/img/icon-512.png b/public/icons/icon-512.png similarity index 100% rename from public/img/icon-512.png rename to public/icons/icon-512.png diff --git a/public/index.html b/public/index.html index 49b6280..2c57e97 100644 --- a/public/index.html +++ b/public/index.html @@ -21,24 +21,41 @@ Wer zahlt? + + - - - -
+

Who pays?

@@ -52,12 +69,13 @@ src="img/a.png" alt="Person" class="w-48 h-48 rounded-full shadow-md border-4 border-white object-cover transition-transform duration-500" + style="display: none;" />
- Load ... + LΓ€dt ...
@@ -94,39 +112,6 @@ .then(reg => console.log("βœ… Service Worker registriert", reg.scope)) .catch(err => console.error("❌ SW Fehler:", err)); } - - // PWA Install Prompt - let deferredPrompt; - const installBanner = document.getElementById("installBanner"); - const installBtn = document.getElementById("installBtn"); - const dismissBtn = document.getElementById("dismissBtn"); - - window.addEventListener("beforeinstallprompt", (e) => { - console.log("βœ… PWA installierbar!"); - e.preventDefault(); - deferredPrompt = e; - installBanner.classList.remove("hidden"); - }); - - installBtn.addEventListener("click", async () => { - if (!deferredPrompt) return; - - deferredPrompt.prompt(); - const { outcome } = await deferredPrompt.userChoice; - console.log(`PWA install: ${outcome}`); - - deferredPrompt = null; - installBanner.classList.add("hidden"); - }); - - dismissBtn.addEventListener("click", () => { - installBanner.classList.add("hidden"); - }); - - window.addEventListener("appinstalled", () => { - console.log("βœ… PWA erfolgreich installiert!"); - deferredPrompt = null; - }); diff --git a/public/manifest.json b/public/manifest.json index f812ea0..490c007 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -11,25 +11,25 @@ "categories": ["utilities", "lifestyle"], "icons": [ { - "src": "/img/icon-192.png", + "src": "/icons/icon-192.png", "sizes": "192x192", "type": "image/png", "purpose": "any" }, { - "src": "/img/icon-192.png", + "src": "/icons/icon-192.png", "sizes": "192x192", "type": "image/png", "purpose": "maskable" }, { - "src": "/img/icon-512.png", + "src": "/icons/icon-512.png", "sizes": "512x512", "type": "image/png", "purpose": "any" }, { - "src": "/img/icon-512.png", + "src": "/icons/icon-512.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" diff --git a/public/script.js b/public/script.js index 731560e..505e052 100644 --- a/public/script.js +++ b/public/script.js @@ -4,11 +4,56 @@ const button = document.getElementById("toggleButton"); const reasonInput = document.getElementById("reasonInput"); const lastReasonBox = document.getElementById("lastReasonBox"); const lastReasonText = document.getElementById("lastReasonText"); +const mainContent = document.getElementById("mainContent"); + +// Preload images to prevent flickering +const imageCache = {}; +function preloadImage(src) { + return new Promise((resolve, reject) => { + if (imageCache[src]) { + resolve(imageCache[src]); + return; + } + + const image = new Image(); + image.onload = () => { + imageCache[src] = image; + resolve(image); + }; + image.onerror = reject; + image.src = src; + }); +} + +// Preload all images on page load +async function preloadAllImages() { + try { + await Promise.all([ + preloadImage('img/a.png'), + preloadImage('img/b.png'), + preloadImage('img/bgA.jpg'), + preloadImage('img/bgB.jpg') + ]); + console.log("βœ… Alle Bilder vorgeladen"); + } catch (err) { + console.warn("⚠️ Fehler beim Vorladen:", err); + } +} async function loadStatus() { + // Preload images first + await preloadAllImages(); + const res = await fetch("/status"); const data = await res.json(); - showStatus(data.current, false); + + // Set background and image without animation on first load + setBackgroundAndImage(data.current, false); + + // Show content smoothly + img.style.display = 'block'; + mainContent.classList.remove('loading'); + mainContent.classList.add('loaded'); // Show last reason if available if (data.lastReason) { @@ -17,59 +62,63 @@ async function loadStatus() { } } -function showStatus(person, animate = true) { +function setBackgroundAndImage(person, animate = true) { const file = person === "A" ? "img/a.png" : "img/b.png"; const label = person === "A" ? "Honey" : "Kirby"; + const bgImage = person === "A" ? "url('img/bgA.jpg')" : "url('img/bgB.jpg')"; - // Hintergrundbilder fΓΌr A und B - const bgA = "url('img/bgA.jpg')"; - const bgB = "url('img/bgB.jpg')"; - - // Hintergrundanimation (optional) - document.body.style.transition = "background 0.4s ease"; - - // Hintergrund setzen - document.body.style.backgroundImage = person === "A" ? bgA : bgB; + // Set background immediately (smooth transition via CSS) + document.body.style.backgroundImage = bgImage; document.body.style.backgroundSize = "cover"; document.body.style.backgroundPosition = "center"; if (animate) { img.classList.add("rotate"); - } - - setTimeout(() => { + + // Change image and name in the middle of rotation + setTimeout(() => { + img.src = file; + nameEl.textContent = label; + }, 300); // Half of rotation duration + + // Remove rotation class after animation + setTimeout(() => { + img.classList.remove("rotate"); + }, 600); + } else { + // First load - no animation img.src = file; nameEl.textContent = label; - }, 150); - - setTimeout(() => { - img.classList.remove("rotate"); - }, 600); + } } - button.addEventListener("click", async () => { const reason = reasonInput.value.trim(); - const res = await fetch("/toggle", { - method: "POST", - headers: { - "Content-Type": "application/json" - }, - body: JSON.stringify({ reason: reason || undefined }) - }); - - const data = await res.json(); - showStatus(data.current); - - // Update last reason display - if (reason) { - lastReasonText.textContent = reason; - lastReasonBox.classList.remove("hidden"); + try { + const res = await fetch("/toggle", { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ reason: reason || undefined }) + }); + + const data = await res.json(); + setBackgroundAndImage(data.current, true); + + // Update last reason display + if (reason) { + lastReasonText.textContent = reason; + lastReasonBox.classList.remove("hidden"); + } + + // Clear input + reasonInput.value = ""; + } catch (err) { + console.error("Fehler beim Wechseln:", err); + nameEl.textContent = "Fehler!"; } - - // Clear input - reasonInput.value = ""; }); // Allow Enter key to trigger switch @@ -79,4 +128,5 @@ reasonInput.addEventListener("keypress", (e) => { } }); +// Start loading loadStatus(); \ No newline at end of file diff --git a/public/style.css b/public/style.css index 68511bd..dddabde 100644 --- a/public/style.css +++ b/public/style.css @@ -19,11 +19,11 @@ opacity: 1; } 50% { - transform: rotateY(180deg); /* Half flip */ - opacity: 0; /* Make the front disappear */ + transform: rotateY(90deg); + opacity: 0.3; } 100% { - transform: rotateY(360deg); /* Complete flip */ + transform: rotateY(0deg); opacity: 1; } } @@ -31,5 +31,19 @@ .rotate { animation: rotate 0.6s ease-in-out; transform-style: preserve-3d; - backface-visibility: hidden; /* Hide the back during flip */ +} + +/* Smooth transitions */ +#personImg { + will-change: transform, opacity; +} + +#personName { + will-change: opacity; + transition: opacity 0.3s ease; +} + +/* Background transition */ +body { + will-change: background-image; } \ No newline at end of file diff --git a/server.js b/server.js index 37f0292..75af079 100644 --- a/server.js +++ b/server.js @@ -57,20 +57,43 @@ const PUBLIC_BASE_URL = process.env.PUBLIC_BASE_URL || "http://localhost:3000"; const DISCORD_WEBHOOK_URL = process.env.DISCORD_WEBHOOK_URL || "https://discord.com/api/webhooks/1450872860607381515/zRbiXOykKPUfLJ_H87wD2OxQJPLdro6yQzU2T48Pg4fxSNaXEqBFgxdHGTiIDmmKGHIF"; // -------------------- -// State Handling +// State Handling - MIT PERSISTENT lastReason // -------------------- function getState() { if (!fs.existsSync(stateFile)) { + const initialState = { + current: "A", + lastReason: null + }; fs.writeFileSync( stateFile, - JSON.stringify({ current: "A", lastReason: null }, null, 2) + JSON.stringify(initialState, null, 2) ); + console.log("βœ… Neue state.json erstellt"); + return initialState; + } + + try { + const state = JSON.parse(fs.readFileSync(stateFile, "utf-8")); + // Ensure lastReason exists in state + if (!state.hasOwnProperty('lastReason')) { + state.lastReason = null; + } + console.log(`πŸ“– State geladen: ${state.current}, Letzter Grund: ${state.lastReason || 'keiner'}`); + return state; + } catch (err) { + console.error("❌ Fehler beim Lesen von state.json:", err); + return { current: "A", lastReason: null }; } - return JSON.parse(fs.readFileSync(stateFile, "utf-8")); } function setState(newState) { - fs.writeFileSync(stateFile, JSON.stringify(newState, null, 2)); + try { + fs.writeFileSync(stateFile, JSON.stringify(newState, null, 2)); + console.log(`πŸ’Ύ State gespeichert: ${newState.current}, Grund: ${newState.lastReason || 'keiner'}`); + } catch (err) { + console.error("❌ Fehler beim Speichern von state.json:", err); + } } // -------------------- @@ -133,7 +156,8 @@ async function sendDiscordEmbed(previousKey, currentKey, reason) { // -------------------- app.get("/status", (req, res) => { - res.json(getState()); + const state = getState(); + res.json(state); }); app.post("/toggle", async (req, res) => { @@ -141,8 +165,13 @@ app.post("/toggle", async (req, res) => { const previousKey = state.current; const reason = req.body?.reason; + // Wechseln state.current = state.current === "A" ? "B" : "A"; + + // Grund speichern (oder null wenn keiner angegeben) state.lastReason = reason || null; + + // State persistent speichern setState(state); const logMessage = reason @@ -151,6 +180,7 @@ app.post("/toggle", async (req, res) => { console.log(logMessage); + // Discord Benachrichtigung await sendDiscordEmbed(previousKey, state.current, reason); res.json({ @@ -167,27 +197,22 @@ app.get("/health", (req, res) => { // -------------------- // SPA Fallback - NO CRASH // -------------------- -// Only serve index.html for navigation requests, not for missing static files app.use((req, res, next) => { - // If request already handled by static middleware, skip if (res.headersSent) return; - // If it's an API route that wasn't matched, 404 if (req.url.startsWith('/api/')) { return res.status(404).json({ error: 'Not found' }); } - // If Accept header indicates browser navigation (HTML), serve index.html const acceptsHtml = req.accepts('html'); if (acceptsHtml) { return res.sendFile(path.join(__dirname, "public/index.html")); } - // Otherwise 404 res.status(404).send('Not found'); }); -// Error handler to prevent crashes +// Error handler app.use((err, req, res, next) => { console.error('Server error:', err); res.status(500).json({ error: 'Internal server error' }); @@ -202,6 +227,12 @@ app.listen(PORT, "0.0.0.0", () => { console.log(`πŸ“ Static files directory: ${path.join(__dirname, "public")}`); console.log(`πŸ”’ Trust proxy: enabled (for reverse proxy support)`); + // Initial state laden und anzeigen + const initialState = getState(); + console.log(`\n🎲 Aktueller Status:`); + console.log(` πŸ‘€ Wer zahlt: ${CONFIG[initialState.current].name}`); + console.log(` πŸ“ Letzter Grund: ${initialState.lastReason || 'Keiner gespeichert'}`); + // Verify critical files exist const criticalFiles = [ 'public/index.html', @@ -216,7 +247,7 @@ app.listen(PORT, "0.0.0.0", () => { console.log(` ${exists ? 'βœ…' : '❌'} ${file}`); }); - console.log('\nπŸ“ Test URLs (adjust domain for your setup):'); + console.log('\nπŸ“ Test URLs:'); console.log(` http://localhost:${PORT}/`); console.log(` http://localhost:${PORT}/service-worker.js`); console.log(` http://localhost:${PORT}/manifest.json\n`);