diff --git a/app.js b/app.js index 9c3f985..8d47a85 100644 --- a/app.js +++ b/app.js @@ -14,6 +14,54 @@ document.addEventListener('DOMContentLoaded', () => { }); } + // --- Tablet Mockup Interactive Demo --- + const captureBtn = document.getElementById('demo-capture-btn'); + const screenWelcome = document.getElementById('demo-screen-welcome'); + const screenCountdown = document.getElementById('demo-screen-countdown'); + const screenPreview = document.getElementById('demo-screen-preview'); + const timerDisplay = document.getElementById('demo-timer'); + const flashOverlay = document.getElementById('demo-flash'); + + const btnPrint = document.getElementById('demo-btn-print'); + const btnReset = document.getElementById('demo-btn-reset'); + + if (captureBtn && screenWelcome && screenCountdown && screenPreview && timerDisplay && flashOverlay) { + captureBtn.addEventListener('click', () => { + // Step 1: Switch screen to countdown + screenWelcome.style.display = 'none'; + screenCountdown.style.display = 'flex'; + + // Step 2: 3-second countdown countdown + let timeLeft = 3; + timerDisplay.textContent = timeLeft; + + const timerInterval = setInterval(() => { + timeLeft--; + if (timeLeft > 0) { + timerDisplay.textContent = timeLeft; + } else { + clearInterval(timerInterval); + // Step 3: Trigger Camera Flash overlay + flashOverlay.classList.add('flash-active'); + + setTimeout(() => { + flashOverlay.classList.remove('flash-active'); + // Step 4: Transition to captured photo preview screen + screenCountdown.style.display = 'none'; + screenPreview.style.display = 'flex'; + }, 400); + } + }, 800); + }); + } + + if (btnReset && screenWelcome && screenPreview) { + btnReset.addEventListener('click', () => { + screenPreview.style.display = 'none'; + screenWelcome.style.display = 'flex'; + }); + } + // --- Accordion FAQ --- const accordionHeaders = document.querySelectorAll('.accordion-header'); @@ -91,6 +139,9 @@ document.addEventListener('DOMContentLoaded', () => { localStorage.setItem('schnappix_upvoted_ids', JSON.stringify(upvotedIds)); }; + // Admin State + let isAdmin = sessionStorage.getItem('schnappix_is_admin') === 'true'; + const renderFeedback = (filter = 'all') => { if (!feedbackList) return; feedbackList.innerHTML = ''; @@ -115,11 +166,17 @@ document.addEventListener('DOMContentLoaded', () => { categoryLabel = 'Lob'; } + // Show delete button if user is admin + const deleteBtnHtml = isAdmin + ? `` + : ''; + card.innerHTML = `
${escapeHtml(item.text)}
@@ -136,6 +193,22 @@ document.addEventListener('DOMContentLoaded', () => { }); setupUpvoteButtons(); + setupAdminDeleteButtons(); + }; + + const setupAdminDeleteButtons = () => { + if (!isAdmin) return; + const deleteButtons = document.querySelectorAll('.fb-admin-delete-btn'); + deleteButtons.forEach(btn => { + btn.addEventListener('click', () => { + const id = btn.getAttribute('data-id'); + if (confirm('Möchten Sie diesen Eintrag wirklich permanent löschen?')) { + feedbackData = feedbackData.filter(item => item.id !== id); + saveToLocalStorage(); + renderFeedback(getActiveFilter()); + } + }); + }); }; const escapeHtml = (str) => { @@ -224,25 +297,10 @@ document.addEventListener('DOMContentLoaded', () => { const modals = document.querySelectorAll('.modal-overlay'); const closeButtons = document.querySelectorAll('.modal-close'); - modalTriggers.forEach(trigger => { - trigger.addEventListener('click', (e) => { - e.preventDefault(); - const targetId = trigger.getAttribute('href').substring(1); - const targetModal = document.getElementById(targetId); - if (targetModal) { - targetModal.classList.add('open'); - document.body.style.overflow = 'hidden'; // Stop scrolling background - } - }); - }); - + // --- Modal close helpers for admin modals --- const closeModal = (modal) => { modal.classList.remove('open'); - // Only restore scroll if no other modals are open - const anyOpen = Array.from(modals).some(m => m.classList.contains('open')); - if (!anyOpen) { - document.body.style.overflow = ''; - } + document.body.style.overflow = ''; }; closeButtons.forEach(btn => { @@ -253,7 +311,6 @@ document.addEventListener('DOMContentLoaded', () => { }); modals.forEach(modal => { - // Close modal when clicking outside of the content block modal.addEventListener('click', (e) => { if (e.target === modal) { closeModal(modal); @@ -261,7 +318,6 @@ document.addEventListener('DOMContentLoaded', () => { }); }); - // Close modal on Escape key press window.addEventListener('keydown', (e) => { if (e.key === 'Escape') { modals.forEach(modal => { @@ -270,6 +326,100 @@ document.addEventListener('DOMContentLoaded', () => { } }); + // --- Admin Panel Modals & Operations --- + const adminTrigger = document.getElementById('admin-login-trigger'); + const adminLoginModal = document.getElementById('admin-login-modal'); + const adminPanelModal = document.getElementById('admin-panel-modal'); + const adminLoginForm = document.getElementById('admin-login-form'); + const adminPasswordInput = document.getElementById('admin-password'); + const adminLoginError = document.getElementById('admin-login-error'); + + const adminLogoutBtn = document.getElementById('admin-logout-btn'); + const adminExportBtn = document.getElementById('admin-export-btn'); + const exportContainer = document.getElementById('export-container'); + const exportTextarea = document.getElementById('export-textarea'); + + if (adminTrigger) { + adminTrigger.addEventListener('click', (e) => { + e.preventDefault(); + if (isAdmin) { + adminPanelModal.classList.add('open'); + } else { + adminLoginModal.classList.add('open'); + adminPasswordInput.focus(); + } + document.body.style.overflow = 'hidden'; + }); + } + + if (adminLoginForm) { + adminLoginForm.addEventListener('submit', (e) => { + e.preventDefault(); + const password = adminPasswordInput.value; + // Admin password matching the default app settings: "1234" + if (password === '1234') { + isAdmin = true; + sessionStorage.setItem('schnappix_is_admin', 'true'); + adminPasswordInput.value = ''; + adminLoginError.style.display = 'none'; + + // Switch Modals + adminLoginModal.classList.remove('open'); + adminPanelModal.classList.add('open'); + + // Re-render to show trash cans + renderFeedback(getActiveFilter()); + } else { + adminLoginError.style.display = 'block'; + adminPasswordInput.value = ''; + adminPasswordInput.focus(); + } + }); + } + + if (adminLogoutBtn) { + adminLogoutBtn.addEventListener('click', () => { + isAdmin = false; + sessionStorage.removeItem('schnappix_is_admin'); + adminPanelModal.classList.remove('open'); + document.body.style.overflow = ''; + + // Hide export code if visible + if (exportContainer) exportContainer.style.display = 'none'; + + // Re-render to hide trash cans + renderFeedback(getActiveFilter()); + }); + } + + if (adminExportBtn && exportContainer && exportTextarea) { + adminExportBtn.addEventListener('click', () => { + // Strip out own custom elements or internal helper flags if necessary + const cleanFeedback = feedbackData.map(item => ({ + id: item.id, + author: item.author, + type: item.type, + title: item.title, + text: item.text, + upvotes: item.upvotes, + voted: false + })); + + exportTextarea.value = JSON.stringify(cleanFeedback, null, 4); + exportContainer.style.display = 'block'; + exportTextarea.select(); + }); + } + + // Modal Close overrides to reset error states + const allCloseButtons = document.querySelectorAll('.modal-close'); + allCloseButtons.forEach(btn => { + btn.addEventListener('click', () => { + if (adminLoginError) adminLoginError.style.display = 'none'; + if (exportContainer) exportContainer.style.display = 'none'; + }); + }); + // Initial render renderFeedback(); }); diff --git a/datenschutz.html b/datenschutz.html new file mode 100644 index 0000000..1e00ef4 --- /dev/null +++ b/datenschutz.html @@ -0,0 +1,105 @@ + + + + + +Die folgenden Hinweise geben einen einfachen Überblick darüber, was mit Ihren personenbezogenen Daten passiert, wenn Sie diese Website besuchen. Personenbezogene Daten sind alle Daten, mit denen Sie persönlich identifiziert werden können.
+ +Diese Webseite nutzt den lokalen Speicher (Local Storage) Ihres Browsers, um die von Ihnen geschriebenen Feedbacks sowie Ihre Stimmen (Upvotes) zu speichern. Dies geschieht ausschließlich auf Ihrem Endgerät und wird nicht an externe Server übertragen, es sei denn, Sie senden explizit ein Feedback ab.
+ +Der Provider der Seiten erhebt und speichert automatisch Informationen in sogenannten Server-Logfiles, die Ihr Browser automatisch an uns übermittelt. Dies sind Browsertyp, Betriebssystem, Referrer URL, Hostname des zugreifenden Rechners und Uhrzeit der Serveranfrage. Diese Daten sind nicht bestimmten Personen zuzuordnen.
+ +Wenn Sie den Feedback Hub nutzen, werden Ihre Eingaben (Name, Titel, Text, Kategorie) gespeichert, um die Funktionalität der Feedback-Plattform bereitzustellen. Bitte geben Sie keine sensiblen personenbezogenen Daten im Freitextfeld an.
+Alles Wissenswerte zur Einrichtung, Hardware-Kompatibilität und Anpassung der Photobooth App.
+Damit der Kiosk-Modus (LockTask) ohne Bestätigungsdialoge funktioniert, muss die App als Device Owner festgelegt werden. Führe dazu folgenden ADB-Befehl aus:
+adb shell dpm set-device-owner de.orfel.schnappix/de.orfel.schnappix.KioskDeviceAdminReceiver
+ Danach kannst du den Kiosk-Modus einfach im passwortgeschützten Admin-Bereich ein- und ausschalten.
+Erstelle ein transparentes PNG im Format 1500 x 1000 px (3:2 Querformat, max. 500 KB) und lege es im Ordner assets/frames/ als frame_name.png ab. Trage das Template anschließend in src/data/frames.ts ein und führe einen neuen Build aus.
Es werden alle Drucker unterstützt, die das IPP (Internet Printing Protocol) im lokalen Netzwerk unterstützen. Beliebte Fotodrucker wie der Canon CP1300 lassen sich direkt über ihre lokale IP-Adresse ansteuern.
+Schnappix nutzt die moderne Android MediaStore API und speichert Collagen sowie Originale direkt in der Galerie deines Tablets unter den Ordnern:
+Schnappix - <EventName> - CollagenSchnappix - <EventName> - OriginaleEntdecke die geballte Funktionsvielfalt, die Schnappix zur optimalen Fotobox-App macht.
+Dank nativer Kotlin-Module sperrt die App das Tablet bombensicher für Gäste. Kein Verlassen der App ohne Admin-Code möglich.
+Integrierter IPP-Drucker-Support (z.B. Canon Selphy). Fotos werden direkt aus dem Vorschaubildschirm gedruckt.
+Interaktiver Editor wie bei Social Media. Emojis, Datumsanzeige und Eventname frei verschieben, drehen und skalieren.
+Nimm 1-5 Fotos hintereinander auf. Die App generiert automatisch eine wunderschöne Collage zum Drucken und Speichern.
+Blende maßgeschneiderte, transparente PNG-Rahmen über die Aufnahmen. Perfekt für Geburtstage, Hochzeiten oder Firmenfeiern.
+Nutze entweder die Frontkamera deines Tablets oder schließe eine externe USB-Kamera an für überragende Bildqualität.
+Deine Meinung hilft uns, Schnappix noch besser zu machen. Hinterlasse Feedback oder stimme für Ideen ab!
+Jannik [Nachname]
+ [Straße und Hausnummer]
+ [PLZ und Ort]
E-Mail: info@schnappix.de
+ Telefon: [Telefonnummer - optional]
Jannik [Nachname]
+ [Adresse wie oben]
Die Europäische Kommission stellt eine Plattform zur Online-Streitbeilegung (OS) bereit: https://ec.europa.eu/consumers/odr.
+ Unsere E-Mail-Adresse finden Sie oben im Impressum. Wir sind nicht bereit oder verpflichtet, an Streitbeilegungsverfahren vor einer Verbraucherschlichtungsstelle teilzunehmen.
Zeit für ein Erinnerungsfoto!
Dank nativer Kotlin-Module sperrt die App das Tablet bombensicher für Gäste. Kein Verlassen der App ohne Admin-Code möglich.
-Integrierter IPP-Drucker-Support (z.B. Canon Selphy). Fotos werden direkt aus dem Vorschaubildschirm gedruckt.
-Interaktiver Editor wie bei Social Media. Emojis, Datumsanzeige und Eventname frei verschieben, drehen und skalieren.
-Nimm 1-5 Fotos hintereinander auf. Die App generiert automatisch eine wunderschöne Collage zum Drucken und Speichern.
-Blende maßgeschneiderte, transparente PNG-Rahmen über die Aufnahmen. Perfekt für Geburtstage, Hochzeiten oder Firmenfeiern.
-Nutze entweder die Frontkamera deines Tablets oder schließe eine externe USB-Kamera an für überragende Bildqualität.
-Damit der Kiosk-Modus (LockTask) ohne Bestätigungsdialoge funktioniert, muss die App als Device Owner festgelegt werden. Führe dazu folgenden ADB-Befehl aus:
-adb shell dpm set-device-owner de.orfel.schnappix/de.orfel.schnappix.KioskDeviceAdminReceiver
- Danach kannst du den Kiosk-Modus einfach im passwortgeschützten Admin-Bereich ein- und ausschalten.
-Erstelle ein transparentes PNG im Format 1500 x 1000 px (3:2 Querformat, max. 500 KB) und lege es im Ordner assets/frames/ als frame_name.png ab. Trage das Template anschließend in src/data/frames.ts ein und führe einen neuen Build aus.
Es werden alle Drucker unterstützt, die das IPP (Internet Printing Protocol) im lokalen Netzwerk unterstützen. Beliebte Fotodrucker wie der Canon CP1300 lassen sich direkt über ihre lokale IP-Adresse ansteuern.
-Schnappix nutzt die moderne Android MediaStore API und speichert Collagen sowie Originale direkt in der Galerie deines Tablets unter den Ordnern:
-Schnappix - <EventName> - CollagenSchnappix - <EventName> - OriginaleDeine Meinung hilft uns, Schnappix noch besser zu machen. Hinterlasse Feedback oder stimme für Ideen ab!
- -