From fa8a7f1fc2b4e35d84be585df213a6c70ef61aaf Mon Sep 17 00:00:00 2001 From: viiivo <«vivien.vonburg@outlook.com»> Date: Tue, 21 Apr 2026 19:39:15 +0200 Subject: [PATCH] Add FAQ section, back button, and info modal to event pages; update styles and scripts --- css/index.css | 130 ++++++++++++++++++++++++++++++++++++++ css/stylesheet_global.css | 78 +++++++++++++++++++++++ event_detail.html | 2 +- event_overview.html | 34 +++++++--- index.html | 98 +++++++++++++++++++++++++++- js/event_detail.js | 9 +++ js/event_overview.js | 113 ++++++++++++++++++++++++++++++--- 7 files changed, 444 insertions(+), 20 deletions(-) diff --git a/css/index.css b/css/index.css index aec4272..bbdf820 100644 --- a/css/index.css +++ b/css/index.css @@ -254,6 +254,7 @@ background: var(--butter-light); border-radius: var(--radius-lg); font-family: var(--font-main); + display: none; font-weight: 400; font-size: 1.25rem; place-items: center; @@ -391,4 +392,133 @@ text-decoration: underline; font-size: 0.8rem; font-weight: 400; +} + + +/* --- FAQ Section: Akkordion --- */ + +.faq-section { + padding: var(--space-8) var(--space-4); + margin: var(--space-8) 0 var(--space-5); +} + +.faq-section h2 { + text-align: center; + margin-bottom: var(--space-5); + color: var(--brown); +} + +.faq-accordion { + width: 100%; + max-width: 56rem; + margin: 0 auto; + display: flex; + flex-direction: column; + gap: var(--space-2); +} + +.faq-item { + border: 1.5px solid var(--olive-light); + border-radius: var(--radius-lg); + overflow: hidden; + background: var(--white); + transition: background-color 0.2s ease, box-shadow var(--shadow-interaction); +} + +.faq-item:hover { + box-shadow: var(--shadow-interaction); +} + +.faq-trigger { + width: 100%; + display: flex; + align-items: center; + justify-content: space-between; + padding: var(--space-3) var(--space-4); + background: transparent; + border: none; + cursor: pointer; + font-size: 1.25rem; + font-weight: 400; + color: var(--olive); + text-align: left; + transition: background-color 0.2s ease; + font-family: var(--font-main); +} + +.faq-trigger:hover { + background-color: var(--butter-light); +} + +.faq-trigger:focus-visible { + outline: 2px solid var(--olive); + outline-offset: -2px; +} + +.faq-title { + flex: 1; + font-weight: 600; +} + +.faq-icon { + display: flex; + align-items: center; + justify-content: center; + width: 28px; + height: 28px; + font-size: 1.5rem; + font-weight: 300; + color: var(--olive); + transition: transform 0.3s ease; + flex-shrink: 0; +} + +.faq-trigger[aria-expanded="true"] .faq-icon { + transform: rotate(45deg); +} + +.faq-content { + padding: 0 var(--space-4); + max-height: 0; + overflow: hidden; + transition: max-height 0.3s ease, padding 0.3s ease; + font-size: 1.125rem; + line-height: 1.7; + color: var(--black); + font-family: var(--font-main); +} + +.faq-content p { + margin: 0; + padding: var(--space-3) 0; +} + +.faq-trigger[aria-expanded="true"] + .faq-content { + display: block; + max-height: 500px; + padding: var(--space-3) var(--space-4); +} + + +/* --- Responsive: FAQ Section --- */ + +@media (max-width: 768px) { + .faq-section { + padding: var(--space-40) var(--space-4); + margin: var(--space-40) 0 var(--space-5); + } + + .faq-trigger { + padding: var(--space-2) var(--space-3); + font-size: 1.125rem; + } + + .faq-content { + padding: 0 var(--space-3); + font-size: 1rem; + } + + .faq-content p { + padding: var(--space-2) 0; + } } \ No newline at end of file diff --git a/css/stylesheet_global.css b/css/stylesheet_global.css index 7c0ca4f..6e69c01 100644 --- a/css/stylesheet_global.css +++ b/css/stylesheet_global.css @@ -199,6 +199,19 @@ p { margin: 0 auto; } +/* + Content pages with sticky nav require top padding to avoid overlap. + Used on event_overview, event_detail, and similar pages. + */ +.container.page-content-safe { + padding-top: 6.5rem; +} + +/* Detail pages with back button need less top padding. */ +.container.page-content-safe.detail-page { + padding-top: 3.5rem; +} + .icon { width: 20px; height: 20px; @@ -409,6 +422,29 @@ p { border-color: var(--olive-dark); } +/* Butter-colored back button for detail pages. */ +.btn-back-to-overview { + display: inline-block; + padding: 0.5rem 1.5rem; + background-color: var(--butter); + border: 1.5px solid var(--olive-light); + border-radius: var(--radius-lg); + font-family: var(--font-main); + font-weight: 400; + font-size: 1.125rem; + color: var(--olive); + cursor: pointer; + text-decoration: none; + transition: background-color 0.2s ease, border-color 0.2s ease; + margin-bottom: var(--space-4); +} + +.btn-back-to-overview:hover, +.btn-back-to-overview:focus-visible { + background-color: var(--butter-light); + border-color: var(--olive); +} + .button--outline { background-color: transparent; @@ -594,6 +630,32 @@ p { gap: 8px; } +/* Info button for event overview page */ +.btn-info { + display: inline-flex; + align-items: center; + justify-content: center; + width: 2.5rem; + height: 2.5rem; + border: 1.5px solid var(--olive-light); + border-radius: 999px; + background-color: var(--butter); + color: var(--olive); + font-family: var(--font-main); + font-size: 1.5rem; + font-weight: 600; + line-height: 1; + cursor: pointer; + transition: background-color 0.2s ease, border-color 0.2s ease; + flex-shrink: 0; +} + +.btn-info:hover, +.btn-info:focus-visible { + background-color: var(--butter-light); + border-color: var(--olive); +} + /* Modal / Popup */ .modal { display: none; @@ -658,6 +720,22 @@ p { justify-content: center; } +.modal-close { + position: absolute; + right: 0; + top: 0; + font-size: 28px; + color: var(--black); + background: none; + border: none; + cursor: pointer; + width: 30px; + height: 30px; + display: flex; + align-items: center; + justify-content: center; +} + .modal-body { padding: var(--space-20) var(--space-20) var(--space-4) var(--space-20); } diff --git a/event_detail.html b/event_detail.html index 17751dc..168a28d 100644 --- a/event_detail.html +++ b/event_detail.html @@ -28,7 +28,7 @@ -
+

Lädt Event-Details...

diff --git a/event_overview.html b/event_overview.html index 7c7efde..7d74946 100644 --- a/event_overview.html +++ b/event_overview.html @@ -28,9 +28,12 @@ -
- -

Events

+
+ +
+

Events

+ +
@@ -63,8 +66,8 @@
-
- +
+
@@ -74,11 +77,11 @@
- +
- - - + + +
@@ -92,6 +95,19 @@ + + +
diff --git a/index.html b/index.html index e456ed7..4ec60be 100644 --- a/index.html +++ b/index.html @@ -36,7 +36,7 @@ einfach. lecker. gemeinsam.

Teile deine Leidenschaft, geniesse gemeinsam.

Ob du als leidenschaftlicher Hobbykoch Gastgeber sein möchtest oder als Feinschmecker einen Platz an einem lokalen Tisch suchst Invité verbindet Menschen durch die Kraft einer gemeinsamen Mahlzeit.

- Anmelden + Registrieren
@@ -149,6 +149,77 @@ + +
+
+

Häufig gestellte Fragen

+
+
+ +
+

Schritt 1: Kostenloses Konto erstellen
Gehe auf Invité, klicke auf "Jetzt beitreten" und fülle das Anmeldeformular aus. Du benötigst nur deine E-Mail und ein Passwort.

+

Schritt 2: Dein Profil ausfüllen
Lade ein Profilfoto hoch, schreib ein bisschen über dich und gib deine Allergien/Ernährungspräferenzen an. Das hilft anderen, dich besser kennenzulernen.

+

Schritt 3: Erkunde Events
Browsing durch unsere Events, filtere nach Diät oder Allergie-Einstellungen, und melde dich zu den Events an, die dich interessieren!

+

Schritt 4: Erstelle dein eigenes Event
Du kannst auch selbst ein Kochevent hosten! Klick auf "Event erstellen", beschreib dein Menü, und lade Gäste ein.

+
+
+ +
+ +
+

Nein, Invité ist komplett kostenlos. Alle Events basieren auf Freiwilligkeit und der Freude am Teilen. Es gibt keine versteckten Kosten – nur die pure Absicht, die Community zu stärken.

+
+
+ +
+ +
+

Ja, absolut! Du kannst dein eigenes Kochevent erstellen und Gäste einladen. Beschreibe dein Menü, die Teilnehmerzahl und weitere Details. Es ist deine Küche, dein Event, deine Regeln.

+
+
+ +
+ +
+

Bei jedem Event sehen dich die verfügbaren Plätze. Du kannst dich mit einem Klick anmelden. Eine Abmeldung ist bis 24 Stunden vor dem Event möglich – so respektieren wir den Aufwand des Gastgebers.

+
+
+ +
+ +
+

Ich kann Informationen zu Allergien und Ernährungseinstellungen in der Event-Beschreibung hinzufügen oder beim Anmelden angeben. So können Gastgeber und Gäste besser zusammenkommen und Überraschungen vermeiden.

+
+
+ +
+ +
+

Ja, dein Profil hilft anderen, dich besser kennenzulernen. Wir ermutigen zu Offenheit und gegenseitigem Vertrauen. Allerdings bleibt es deine Entscheidung, wem du deine Adresse mitteilst – die erfolgt nur 12 Stunden vor dem Event.

+
+
+
+
+
+ + + + + \ No newline at end of file diff --git a/js/event_detail.js b/js/event_detail.js index 6d2b94f..838675a 100644 --- a/js/event_detail.js +++ b/js/event_detail.js @@ -341,6 +341,7 @@ // Render complete detail page layout including: // hero metadata, host card, menu, participants, gallery and sticky action bar. detailContainer.innerHTML = ` +
@@ -465,6 +466,7 @@ // Lightbox behavior for gallery images: // open on image click, close via backdrop, close button or ESC. // --------------------------------------------------------- + const backButton = detailContainer.querySelector('[data-navigate-back]'); const lightbox = detailContainer.querySelector('.detail-lightbox'); const lightboxImage = detailContainer.querySelector('.detail-lightbox-image'); const lightboxClose = detailContainer.querySelector('.detail-lightbox-close'); @@ -586,5 +588,12 @@ } }); } + + // Back button navigation: returns to event overview page. + if (backButton) { + backButton.addEventListener('click', () => { + window.location.href = 'event_overview.html'; + }); + } } }); diff --git a/js/event_overview.js b/js/event_overview.js index 8dbd04d..c5b1e66 100644 --- a/js/event_overview.js +++ b/js/event_overview.js @@ -2,6 +2,7 @@ const EVENTS_STORAGE_KEY = 'socialCookingEvents'; const CURRENT_USER_KEY = 'socialCookingCurrentUser'; const REGISTRATION_STORAGE_KEY = 'socialCookingRegistrations'; + const INFO_MODAL_SHOWN_KEY = 'infoModalShownOnFirstLogin'; // ------------------------------------------------------------- // DOM references used throughout the page lifecycle. // ------------------------------------------------------------- @@ -15,10 +16,13 @@ // ------------------------------------------------------------- - // In-memory state for fetched events and currently active category. + // In-memory state for fetched events and currently active filters. + // Separate state for category, diet, and allergie selections. // ------------------------------------------------------------- let allEvents = []; let activeCategory = 'ALLE'; + let activeDiets = new Set(); + let activeAllergies = new Set(); const currentUser = getCurrentUser(); function getCurrentUser() { @@ -91,8 +95,13 @@ const savedCategory = sessionStorage.getItem('activeFilter') || 'ALLE'; const savedLocation = sessionStorage.getItem('activeLocation') || 'ALLE_ORTE'; const savedDate = sessionStorage.getItem('activeDate') || ''; + const savedDiets = sessionStorage.getItem('activeDiets') || ''; + const savedAllergies = sessionStorage.getItem('activeAllergies') || ''; activeCategory = savedCategory; + activeDiets = new Set(savedDiets ? savedDiets.split(',') : []); + activeAllergies = new Set(savedAllergies ? savedAllergies.split(',') : []); + if (locationFilter) { locationFilter.value = hasOption(locationFilter, savedLocation) ? savedLocation : 'ALLE_ORTE'; } @@ -283,26 +292,54 @@ return Array.from(selectElement.options).some(option => option.value === value); } - // Apply all filters together (category, location, date), update button state, render and persist. + // Apply all filters together (category, diet, allergie, location, date), update button state, render and persist. function applyFilters() { const selectedLocation = locationFilter ? locationFilter.value : 'ALLE_ORTE'; const selectedDate = dateFilter ? dateFilter.value : ''; + // Update active states for all filter types filterButtons.forEach(btn => { - if (btn.getAttribute('data-cat') === activeCategory) { - btn.classList.add('active'); - } else { - btn.classList.remove('active'); + const isCategoryButton = btn.getAttribute('data-cat') !== null; + const isDietButton = btn.getAttribute('data-diet') !== null; + const isAllergieButton = btn.getAttribute('data-allergie') !== null; + + if (isCategoryButton) { + if (btn.getAttribute('data-cat') === activeCategory) { + btn.classList.add('active'); + } else { + btn.classList.remove('active'); + } + } else if (isDietButton) { + if (activeDiets.has(btn.getAttribute('data-diet'))) { + btn.classList.add('active'); + } else { + btn.classList.remove('active'); + } + } else if (isAllergieButton) { + if (activeAllergies.has(btn.getAttribute('data-allergie'))) { + btn.classList.add('active'); + } else { + btn.classList.remove('active'); + } } }); const filtered = allEvents.filter(event => { const categoryMatch = activeCategory === 'ALLE' || event.category === activeCategory; + + // Diet filter: if no diets selected, show all. Otherwise, event MUST have at least one selected diet. + const dietMatch = activeDiets.size === 0 || + (event.diet && event.diet.split(', ').some(d => activeDiets.has(d.trim()))); + + // Allergie filter: if no allergies selected, show all. Otherwise, event MUST have at least one selected allergie. + const allergieMatch = activeAllergies.size === 0 || + (event.specifications && event.specifications.some(spec => activeAllergies.has(spec))); + const locationMatch = selectedLocation === 'ALLE_ORTE' || event.location === selectedLocation; const eventDateIso = parseEventDateToIso(event.date); const dateMatch = !selectedDate || eventDateIso === selectedDate; - return categoryMatch && locationMatch && dateMatch; + return categoryMatch && dietMatch && allergieMatch && locationMatch && dateMatch; }); renderEvents(filtered); @@ -310,6 +347,8 @@ sessionStorage.setItem('activeFilter', activeCategory); sessionStorage.setItem('activeLocation', selectedLocation); sessionStorage.setItem('activeDate', selectedDate); + sessionStorage.setItem('activeDiets', Array.from(activeDiets).join(',')); + sessionStorage.setItem('activeAllergies', Array.from(activeAllergies).join(',')); } // Render either: @@ -475,10 +514,32 @@ }); } - // Category filter interactions. + // Category filter interactions: mutually exclusive (radio button behavior). filterButtons.forEach(button => { button.addEventListener('click', () => { - activeCategory = button.getAttribute('data-cat'); + const categoryValue = button.getAttribute('data-cat'); + const dietValue = button.getAttribute('data-diet'); + const allergieValue = button.getAttribute('data-allergie'); + + if (categoryValue !== null) { + // Category filter: exclusive selection + activeCategory = categoryValue; + } else if (dietValue !== null) { + // Diet filter: toggle selection + if (activeDiets.has(dietValue)) { + activeDiets.delete(dietValue); + } else { + activeDiets.add(dietValue); + } + } else if (allergieValue !== null) { + // Allergie filter: toggle selection + if (activeAllergies.has(allergieValue)) { + activeAllergies.delete(allergieValue); + } else { + activeAllergies.add(allergieValue); + } + } + applyFilters(); }); }); @@ -501,6 +562,40 @@ } } + // Info button modal behavior + const infoButton = document.getElementById('info-button'); + const infoModal = document.getElementById('info-modal'); + const modalClose = infoModal?.querySelector('.modal-close'); + + if (infoButton && infoModal) { + infoButton.addEventListener('click', () => { + infoModal.classList.add('show'); + }); + } + + if (modalClose && infoModal) { + modalClose.addEventListener('click', () => { + infoModal.classList.remove('show'); + }); + } + + if (infoModal) { + infoModal.addEventListener('click', (event) => { + if (event.target === infoModal) { + infoModal.classList.remove('show'); + } + }); + } + + // Auto-open info modal on first login + if (currentUser && infoModal) { + const hasShownInfoModal = localStorage.getItem(INFO_MODAL_SHOWN_KEY); + if (!hasShownInfoModal) { + infoModal.classList.add('show'); + localStorage.setItem(INFO_MODAL_SHOWN_KEY, 'true'); + } + } + // Kick off initial load/render cycle. fetchEvents(); });