diff --git a/css/index.css b/css/index.css index ea7e7c5..cb991d7 100644 --- a/css/index.css +++ b/css/index.css @@ -243,6 +243,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; @@ -380,4 +381,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 a032bba..581614e 100644 --- a/css/stylesheet_global.css +++ b/css/stylesheet_global.css @@ -250,6 +250,19 @@ label { } } +/* + 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; @@ -460,6 +473,29 @@ label { 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; @@ -648,6 +684,32 @@ label { 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; @@ -712,6 +774,22 @@ label { 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/data/events.json b/data/events.json index c09641e..fb162e7 100644 --- a/data/events.json +++ b/data/events.json @@ -4,11 +4,11 @@ "title": "Italienische Tavolata", "location": "Luzern", "address": "Pilatusstrasse 18, 6003 Luzern", - "date": "11. APR. 2026", + "date": "22. APR. 2026", "time": "15:30 UHR", "category": "Dinner", "diet": "Vegetarisch", - "spots": 6, + "spots": 8, "host": { "name": "Ferdinando", "initial": "F" @@ -45,7 +45,7 @@ "title": "Noche Peruana", "location": "Chur", "address": "Obere Gasse 41, 7000 Chur", - "date": "16. Juni 2026", + "date": "12. April 2026", "time": "19:00 UHR", "category": "Dinner", "diet": "Omnivore", diff --git a/event_detail.html b/event_detail.html index a13ab96..dbd8ce9 100644 --- a/event_detail.html +++ b/event_detail.html @@ -28,7 +28,11 @@ +<<<<<<< HEAD
+======= +
+>>>>>>> e3ac1a11f091bcc9b224ce4ae81743564e160e37

Lädt Event-Details...

diff --git a/event_overview.html b/event_overview.html index 08566f2..916827d 100644 --- a/event_overview.html +++ b/event_overview.html @@ -28,10 +28,19 @@ +<<<<<<< HEAD

Event finden

Was darf es sein?

+======= +
+ +
+

Events

+ +
+>>>>>>> e3ac1a11f091bcc9b224ce4ae81743564e160e37
@@ -64,9 +73,14 @@
+<<<<<<< HEAD

Ernährungsform

+======= +
+ +>>>>>>> e3ac1a11f091bcc9b224ce4ae81743564e160e37
@@ -77,11 +91,11 @@

Allergene

- +
- - - + + +
@@ -95,6 +109,19 @@ + + +
diff --git a/index.html b/index.html index a4a84b6..21e0eba 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 43a038b..cee4a73 100644 --- a/js/event_detail.js +++ b/js/event_detail.js @@ -1,6 +1,7 @@ document.addEventListener('DOMContentLoaded', async () => { const EVENTS_STORAGE_KEY = 'socialCookingEvents'; const CURRENT_USER_KEY = 'socialCookingCurrentUser'; + const USERS_STORAGE_KEY = 'socialCookingUsers'; const REGISTRATION_STORAGE_KEY = 'socialCookingRegistrations'; // ------------------------------------------------------------- // DOM entry point and shared asset path. @@ -50,6 +51,57 @@ } } + function getStoredUsers() { + try { + const stored = localStorage.getItem(USERS_STORAGE_KEY); + return stored ? JSON.parse(stored) : []; + } catch (error) { + console.error('Benutzerdaten konnten nicht gelesen werden.', error); + return []; + } + } + + function getUserDisplayName(user) { + if (!user) { + return ''; + } + + const firstName = String(user.vorname || '').trim(); + const lastName = String(user.nachname || '').trim(); + + return (firstName || `${firstName} ${lastName}`.trim() || String(user.email || '').trim()).trim(); + } + + function getResolvedParticipants(event, registrationMap) { + const baseParticipants = Array.isArray(event.participants) + ? event.participants.map(name => String(name || '').trim()).filter(Boolean) + : []; + const usersByEmail = new Map( + getStoredUsers().map(user => [String(user.email || '').trim().toLowerCase(), user]) + ); + const participantLookup = new Set(baseParticipants.map(name => name.toLowerCase())); + + Object.entries(registrationMap || {}).forEach(([email, ids]) => { + const isRegisteredForEvent = Array.isArray(ids) + && ids.map(id => Number(id)).includes(Number(event.id)); + + if (!isRegisteredForEvent) { + return; + } + + const user = usersByEmail.get(String(email || '').trim().toLowerCase()); + const displayName = getUserDisplayName(user) || String(email || '').trim(); + const normalizedName = displayName.toLowerCase(); + + if (displayName && !participantLookup.has(normalizedName)) { + baseParticipants.push(displayName); + participantLookup.add(normalizedName); + } + }); + + return baseParticipants; + } + function setRegistrationMap(registrationMap) { localStorage.setItem(REGISTRATION_STORAGE_KEY, JSON.stringify(registrationMap)); } @@ -114,7 +166,7 @@ } const msUntilStart = eventDateTime.getTime() - Date.now(); - const twentyfourHoursInMs = 12 * 60 * 60 * 1000; + const twentyfourHoursInMs = 24 * 60 * 60 * 1000; return msUntilStart <= twentyfourHoursInMs; } @@ -138,7 +190,7 @@ return { daysLeft, isClosed: false }; } - // Adresse ist nur im 12h-Fenster VOR Eventstart sichtbar. + // Adresse ist nur im 24h-Fenster VOR Eventstart sichtbar. function isAddressVisibleWindow(event) { const eventDateTime = parseEventDateTime(event); if (!eventDateTime || Number.isNaN(eventDateTime.getTime())) { @@ -146,7 +198,7 @@ } const msUntilStart = eventDateTime.getTime() - Date.now(); - const twentyfourHoursInMs = 12 * 60 * 60 * 1000; + const twentyfourHoursInMs = 24 * 60 * 60 * 1000; return msUntilStart >= 0 && msUntilStart <= twentyfourHoursInMs; } @@ -283,16 +335,26 @@ const specifications = Array.isArray(event.specifications) && event.specifications.length > 0 ? event.specifications : []; - const participants = Array.isArray(event.participants) ? event.participants : []; - const galleryImages = Array.isArray(event.gallery) && event.gallery.length > 0 - ? event.gallery - : [event.image, event.image, event.image]; - const visibleParticipants = participants.slice(0, 6); const registrationMap = getRegistrationMap(); - const extraRegistrations = countRegistrationsForEvent(registrationMap, event.id); - const remainingParticipants = Math.max(0, participants.length + extraRegistrations - visibleParticipants.length); + const participants = getResolvedParticipants(event, registrationMap); + const galleryImages = Array.isArray(event.gallery) + ? event.gallery.filter(Boolean) + : []; + const galleryMarkup = galleryImages.length > 0 + ? ` + + ` + : ''; + const visibleParticipants = participants.slice(0, 6); + const remainingParticipants = Math.max(0, participants.length - visibleParticipants.length); const totalGuests = Number.isFinite(event.spots) ? event.spots : 0; - const confirmedGuests = participants.length + extraRegistrations; + const confirmedGuests = participants.length; const freePlaces = Math.max(0, totalGuests - confirmedGuests); const isFull = freePlaces === 0; const isRegistrationClosed = isRegistrationClosedForEvent(event); @@ -331,7 +393,12 @@

${event.address}

` - : ''; + : ` +
+

Adresse

+

Vielen Dank für die Anmeldung! Die Adresse für diesen Event wird 24 Stunden vorher genau hier sichtbar sein.

+
+ `; const detailChips = [ `${eventCategory}`, ...event.diet.split(', ').filter(d => d.trim() && d !== 'Keine Angabe').map(d => `${getDietLabel(d.trim())}`), @@ -340,7 +407,12 @@ // Render complete detail page layout including: // hero metadata, host card, menu, participants, gallery and sticky action bar. +<<<<<<< HEAD detailcontainer.innerHTML = ` +======= + detailContainer.innerHTML = ` + +>>>>>>> e3ac1a11f091bcc9b224ce4ae81743564e160e37
@@ -400,13 +472,7 @@ ${addressPanelMarkup}
- + ${galleryMarkup}
@@ -474,11 +540,20 @@ // Lightbox behavior for gallery images: // open on image click, close via backdrop, close button or ESC. // --------------------------------------------------------- +<<<<<<< HEAD const lightbox = detailcontainer.querySelector('.detail-lightbox'); const lightboxImage = detailcontainer.querySelector('.detail-lightbox-image'); const lightboxClose = detailcontainer.querySelector('.detail-lightbox-close'); const galleryButtons = detailcontainer.querySelectorAll('.detail-gallery-item'); const registerButton = detailcontainer.querySelector('[data-register-button]'); +======= + 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'); + const galleryButtons = detailContainer.querySelectorAll('.detail-gallery-item'); + const registerButton = detailContainer.querySelector('[data-register-button]'); +>>>>>>> e3ac1a11f091bcc9b224ce4ae81743564e160e37 // Harte Absicherung: Eigene Events sind auf der Detailseite immer deaktiviert. if (registerButton && isOwnEvent) { @@ -595,5 +670,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..5ecbb4c 100644 --- a/js/event_overview.js +++ b/js/event_overview.js @@ -1,7 +1,9 @@ document.addEventListener('DOMContentLoaded', () => { const EVENTS_STORAGE_KEY = 'socialCookingEvents'; const CURRENT_USER_KEY = 'socialCookingCurrentUser'; + const USERS_STORAGE_KEY = 'socialCookingUsers'; const REGISTRATION_STORAGE_KEY = 'socialCookingRegistrations'; + const INFO_MODAL_SHOWN_KEY = 'infoModalShownOnFirstLogin'; // ------------------------------------------------------------- // DOM references used throughout the page lifecycle. // ------------------------------------------------------------- @@ -15,10 +17,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() { @@ -69,6 +74,57 @@ } } + function getStoredUsers() { + try { + const stored = localStorage.getItem(USERS_STORAGE_KEY); + return stored ? JSON.parse(stored) : []; + } catch (error) { + console.error('Benutzerdaten konnten nicht gelesen werden.', error); + return []; + } + } + + function getUserDisplayName(user) { + if (!user) { + return ''; + } + + const firstName = String(user.vorname || '').trim(); + const lastName = String(user.nachname || '').trim(); + + return (firstName || `${firstName} ${lastName}`.trim() || String(user.email || '').trim()).trim(); + } + + function getResolvedParticipants(event, registrationMap) { + const baseParticipants = Array.isArray(event.participants) + ? event.participants.map(name => String(name || '').trim()).filter(Boolean) + : []; + const usersByEmail = new Map( + getStoredUsers().map(user => [String(user.email || '').trim().toLowerCase(), user]) + ); + const participantLookup = new Set(baseParticipants.map(name => name.toLowerCase())); + + Object.entries(registrationMap || {}).forEach(([email, ids]) => { + const isRegisteredForEvent = Array.isArray(ids) + && ids.map(id => Number(id)).includes(Number(event.id)); + + if (!isRegisteredForEvent) { + return; + } + + const user = usersByEmail.get(String(email || '').trim().toLowerCase()); + const displayName = getUserDisplayName(user) || String(email || '').trim(); + const normalizedName = displayName.toLowerCase(); + + if (displayName && !participantLookup.has(normalizedName)) { + baseParticipants.push(displayName); + participantLookup.add(normalizedName); + } + }); + + return baseParticipants; + } + function setRegistrationMap(registrationMap) { localStorage.setItem(REGISTRATION_STORAGE_KEY, JSON.stringify(registrationMap)); } @@ -91,8 +147,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 +344,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 +399,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: @@ -353,10 +444,9 @@ const displayTime = formatEventTime(event.time); // Capacity logic: - // spots = total capacity, participants.length = booked seats. - const baseParticipants = Array.isArray(event.participants) ? event.participants.length : 0; - const extraRegistrations = countRegistrationsForEvent(registrationMap, event.id); - const bookedSeats = baseParticipants + extraRegistrations; + // spots = total capacity, resolved participants = booked seats. + const resolvedParticipants = getResolvedParticipants(event, registrationMap); + const bookedSeats = resolvedParticipants.length; const totalCapacity = event.spots; const freePlaces = Math.max(0, totalCapacity - bookedSeats); const isFull = freePlaces === 0; @@ -475,10 +565,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 +613,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(); }); diff --git a/js/my_profil.js b/js/my_profil.js index 9b0c9e7..9fb3a8b 100644 --- a/js/my_profil.js +++ b/js/my_profil.js @@ -549,7 +549,7 @@ }); } - // Gibt true zurück, wenn ein Event innerhalb der nächsten 12 Stunden startet. + // Gibt true zurück, wenn ein Event innerhalb der nächsten 24 Stunden startet. function isAddressVisibleWindow(event) { const eventDateTime = parseEventDateTime(event); if (!eventDateTime || Number.isNaN(eventDateTime.getTime())) { @@ -557,7 +557,7 @@ } const msUntilStart = eventDateTime.getTime() - Date.now(); - const twentyfourHoursInMs = 12 * 60 * 60 * 1000; + const twentyfourHoursInMs = 24 * 60 * 60 * 1000; return msUntilStart >= 0 && msUntilStart <= twentyfourHoursInMs; }