document.addEventListener('DOMContentLoaded', () => { // --- Mobile Menu Toggle --- const menuToggle = document.querySelector('.menu-toggle'); const navLinks = document.querySelector('.nav-links'); if (menuToggle && navLinks) { menuToggle.addEventListener('click', () => { navLinks.classList.toggle('mobile-open'); const icon = menuToggle.querySelector('i'); if (icon) { icon.classList.toggle('fa-bars'); icon.classList.toggle('fa-xmark'); } }); } // --- Accordion FAQ --- const accordionHeaders = document.querySelectorAll('.accordion-header'); accordionHeaders.forEach(header => { header.addEventListener('click', () => { const item = header.parentElement; const isActive = item.classList.contains('active'); // Close all items document.querySelectorAll('.accordion-item').forEach(i => { i.classList.remove('active'); const content = i.querySelector('.accordion-content'); if (content) content.style.maxHeight = null; }); // Toggle active item if (!isActive) { item.classList.add('active'); const content = item.querySelector('.accordion-content'); if (content) { content.style.maxHeight = content.scrollHeight + "px"; } } }); }); // --- Feedback Hub Logic --- const feedbackForm = document.getElementById('feedback-form'); const feedbackList = document.getElementById('feedback-list'); const filterButtons = document.querySelectorAll('.filter-btn'); // Default Seed Data const defaultFeedback = [ { id: 'fb-1', author: 'Markus T.', type: 'feature', title: 'QR-Code Download für Gäste', text: 'Es wäre super cool, wenn nach der Aufnahme ein QR-Code angezeigt wird, über den sich die Gäste das Foto direkt auf das Handy laden können.', upvotes: 42, voted: false }, { id: 'fb-2', author: 'Sabrina', type: 'love', title: 'Sticker-Editor ist genial!', text: 'Unsere Hochzeitsgäste haben stundenlang Emojis und Eventtexte verschoben. Die Bedienung ist wirklich super intuitiv und einfach.', upvotes: 28, voted: false }, { id: 'fb-3', author: 'EventTech GmbH', type: 'bug', title: 'Drucker-Timeout bei schlechtem WLAN', text: 'Manchmal meldet die App einen Fehler, wenn der Drucker im lokalen Netzwerk kurz die Verbindung verliert. Ein automatischer Retry wäre hilfreich.', upvotes: 15, voted: false } ]; // Load from LocalStorage or seed defaults let feedbackData = JSON.parse(localStorage.getItem('schnappix_feedback')); if (!feedbackData) { feedbackData = defaultFeedback; localStorage.setItem('schnappix_feedback', JSON.stringify(feedbackData)); } // Upvote storage track (local to session/device) let upvotedIds = JSON.parse(localStorage.getItem('schnappix_upvoted_ids')) || []; const saveToLocalStorage = () => { localStorage.setItem('schnappix_feedback', JSON.stringify(feedbackData)); localStorage.setItem('schnappix_upvoted_ids', JSON.stringify(upvotedIds)); }; const renderFeedback = (filter = 'all') => { if (!feedbackList) return; feedbackList.innerHTML = ''; // Sort by upvotes descending const sortedData = [...feedbackData].sort((a, b) => b.upvotes - a.upvotes); sortedData.forEach(item => { if (filter !== 'all' && item.type !== filter) return; const isVoted = upvotedIds.includes(item.id); const card = document.createElement('div'); card.className = 'feedback-item glass'; let badgeIcon = '✨'; let categoryLabel = 'Feature'; if (item.type === 'bug') { badgeIcon = '🐛'; categoryLabel = 'Bug'; } else if (item.type === 'love') { badgeIcon = '💖'; categoryLabel = 'Lob'; } card.innerHTML = `
${badgeIcon} ${categoryLabel} von ${escapeHtml(item.author)}

${escapeHtml(item.title)}

${escapeHtml(item.text)}

`; feedbackList.appendChild(card); }); setupUpvoteButtons(); }; const escapeHtml = (str) => { return str.replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """) .replace(/'/g, "'"); }; const setupUpvoteButtons = () => { const buttons = document.querySelectorAll('.upvote-btn'); buttons.forEach(btn => { btn.addEventListener('click', () => { const id = btn.getAttribute('data-id'); const index = feedbackData.findIndex(item => item.id === id); if (index !== -1) { if (upvotedIds.includes(id)) { // Undo upvote feedbackData[index].upvotes--; upvotedIds = upvotedIds.filter(vId => vId !== id); } else { // Upvote feedbackData[index].upvotes++; upvotedIds.push(id); } saveToLocalStorage(); renderFeedback(getActiveFilter()); } }); }); }; const getActiveFilter = () => { const activeBtn = document.querySelector('.filter-btn.active'); return activeBtn ? activeBtn.getAttribute('data-filter') : 'all'; }; // Filter switching filterButtons.forEach(btn => { btn.addEventListener('click', () => { filterButtons.forEach(b => b.classList.remove('active')); btn.classList.add('active'); renderFeedback(btn.getAttribute('data-filter')); }); }); // Form Submission if (feedbackForm) { feedbackForm.addEventListener('submit', (e) => { e.preventDefault(); const authorInput = document.getElementById('fb-author'); const typeSelect = document.getElementById('fb-type'); const titleInput = document.getElementById('fb-title'); const textInput = document.getElementById('fb-text'); const newFb = { id: 'fb-' + Date.now(), author: authorInput.value, type: typeSelect.value, title: titleInput.value, text: textInput.value, upvotes: 1, voted: true }; feedbackData.push(newFb); upvotedIds.push(newFb.id); // Auto-upvote own post saveToLocalStorage(); // Reset form feedbackForm.reset(); // Re-render renderFeedback(getActiveFilter()); // Smooth scroll to list feedbackList.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); }); } // --- Modal Management for Impressum & Privacy --- const modalTriggers = document.querySelectorAll('.legal-trigger'); 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 } }); }); 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 = ''; } }; closeButtons.forEach(btn => { btn.addEventListener('click', () => { const modal = btn.closest('.modal-overlay'); if (modal) closeModal(modal); }); }); modals.forEach(modal => { // Close modal when clicking outside of the content block modal.addEventListener('click', (e) => { if (e.target === modal) { closeModal(modal); } }); }); // Close modal on Escape key press window.addEventListener('keydown', (e) => { if (e.key === 'Escape') { modals.forEach(modal => { if (modal.classList.contains('open')) closeModal(modal); }); } }); // Initial render renderFeedback(); });