Add FAQ section, back button, and info modal to event pages; update styles and scripts

This commit is contained in:
viiivo 2026-04-21 19:39:15 +02:00
parent 24dc61a887
commit fa8a7f1fc2
7 changed files with 444 additions and 20 deletions

View File

@ -254,6 +254,7 @@
background: var(--butter-light); background: var(--butter-light);
border-radius: var(--radius-lg); border-radius: var(--radius-lg);
font-family: var(--font-main); font-family: var(--font-main);
display: none;
font-weight: 400; font-weight: 400;
font-size: 1.25rem; font-size: 1.25rem;
place-items: center; place-items: center;
@ -391,4 +392,133 @@
text-decoration: underline; text-decoration: underline;
font-size: 0.8rem; font-size: 0.8rem;
font-weight: 400; 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;
}
} }

View File

@ -199,6 +199,19 @@ p {
margin: 0 auto; 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 { .icon {
width: 20px; width: 20px;
height: 20px; height: 20px;
@ -409,6 +422,29 @@ p {
border-color: var(--olive-dark); 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 { .button--outline {
background-color: transparent; background-color: transparent;
@ -594,6 +630,32 @@ p {
gap: 8px; 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 / Popup */
.modal { .modal {
display: none; display: none;
@ -658,6 +720,22 @@ p {
justify-content: center; 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 { .modal-body {
padding: var(--space-20) var(--space-20) var(--space-4) var(--space-20); padding: var(--space-20) var(--space-20) var(--space-4) var(--space-20);
} }

View File

@ -28,7 +28,7 @@
</header> </header>
<!-- Main content: detail page gets fully injected by JavaScript --> <!-- Main content: detail page gets fully injected by JavaScript -->
<main class="container"> <main class="container page-content-safe detail-page">
<!-- Render target: loading, error state or full detail layout --> <!-- Render target: loading, error state or full detail layout -->
<div id="detail-view"> <div id="detail-view">
<p>Lädt Event-Details...</p> <p>Lädt Event-Details...</p>

View File

@ -28,9 +28,12 @@
</header> </header>
<!-- Main content: page headline, filter controls and dynamic event list --> <!-- Main content: page headline, filter controls and dynamic event list -->
<main class="container"> <main class="container page-content-safe">
<!-- Page headline --> <!-- Page headline with info button -->
<h1>Events</h1> <div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 2rem;">
<h1 style="margin-bottom: 0;">Events</h1>
<button type="button" class="btn-info" id="info-button" aria-label="Information über kostenlose Events">?</button>
</div>
<!-- Filter section: category chips + location/date filters --> <!-- Filter section: category chips + location/date filters -->
<section class="filter-section"> <section class="filter-section">
@ -63,8 +66,8 @@
</div> </div>
</div> </div>
<div class="filter-row"> <div class="filter-row">
<!-- Primary category filter buttons --> <!-- Diet filter buttons -->
<div class="category-group"> <div class="category-group">
<button class="category-item" type="button" data-diet="Fleisch">Fleisch</button> <button class="category-item" type="button" data-diet="Fleisch">Fleisch</button>
<button class="category-item" type="button" data-diet="Fisch">Fisch</button> <button class="category-item" type="button" data-diet="Fisch">Fisch</button>
@ -74,11 +77,11 @@
</div> </div>
<div class="filter-row"> <div class="filter-row">
<!-- Primary category filter buttons --> <!-- Allergen filter buttons -->
<div class="category-group"> <div class="category-group">
<button class="category-item" type="button" data-cat="Fleisch">glutenfrei</button> <button class="category-item" type="button" data-allergie="glutenfrei">glutenfrei</button>
<button class="category-item" type="button" data-cat="Fisch">laktosefrei</button> <button class="category-item" type="button" data-allergie="laktosefrei">laktosefrei</button>
<button class="category-item" type="button" data-cat="Vegetarisch">ohne Nüsse</button> <button class="category-item" type="button" data-allergie="ohne Nüsse">ohne Nüsse</button>
</div> </div>
</div> </div>
@ -92,6 +95,19 @@
<!-- Seitenlogik: Daten laden, filtern und Event-Karten rendern --> <!-- Seitenlogik: Daten laden, filtern und Event-Karten rendern -->
<script src="js/event_overview.js"></script> <script src="js/event_overview.js"></script>
<!-- Info Modal: Kostenlose Events Info -->
<div id="info-modal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h2>Warum Invité kostenlos ist</h2>
<button type="button" class="modal-close" aria-label="Popup schließen">&times;</button>
</div>
<div class="modal-body">
<p>Alle Events bei uns sind komplett kostenlos. Invité basiert rein auf Freiwilligkeit und der Freude am Teilen. Kein Geldfluss, keine versteckten Kosten nur die pure Absicht, die Community zu stärken und den sozialen Zusammenhalt in unserer Nachbarschaft zu fördern. Egal ob du den Kochlöffel schwingst oder dich als Gast dazu gesellst: Bei uns zählt nur die menschliche Begegnung.</p>
</div>
</div>
</div>
<!-- Snackbar: Feedback bei An-/Abmeldung --> <!-- Snackbar: Feedback bei An-/Abmeldung -->
<div class="snackbar" id="snackbar"></div> <div class="snackbar" id="snackbar"></div>

View File

@ -36,7 +36,7 @@
<span class="badge margin-bottom-40">einfach. lecker. gemeinsam.</span> <span class="badge margin-bottom-40">einfach. lecker. gemeinsam.</span>
<h1>Teile deine Leidenschaft, geniesse gemeinsam.</h1> <h1>Teile deine Leidenschaft, geniesse gemeinsam.</h1>
<p>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.</p> <p>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.</p>
<a class="button-primary" href="signup.html">Anmelden</a> <a class="button-primary" href="signup.html">Registrieren</a>
</div> </div>
<div class="hero__right"> <div class="hero__right">
@ -149,6 +149,77 @@
<script src="js/index-carousel.js"></script> <script src="js/index-carousel.js"></script>
<!-- FAQ Section: Akkordion mit häufig gestellten Fragen -->
<section class="faq-section">
<div class="container">
<h2>Häufig gestellte Fragen</h2>
<div class="faq-accordion">
<div class="faq-item">
<button type="button" class="faq-trigger" aria-expanded="false">
<span class="faq-title">Wie kann ich bei Invité anfangen?</span>
<span class="faq-icon">+</span>
</button>
<div class="faq-content">
<p><strong>Schritt 1: Kostenloses Konto erstellen</strong><br>Gehe auf Invité, klicke auf "Jetzt beitreten" und fülle das Anmeldeformular aus. Du benötigst nur deine E-Mail und ein Passwort.</p>
<p><strong>Schritt 2: Dein Profil ausfüllen</strong><br>Lade ein Profilfoto hoch, schreib ein bisschen über dich und gib deine Allergien/Ernährungspräferenzen an. Das hilft anderen, dich besser kennenzulernen.</p>
<p><strong>Schritt 3: Erkunde Events</strong><br>Browsing durch unsere Events, filtere nach Diät oder Allergie-Einstellungen, und melde dich zu den Events an, die dich interessieren!</p>
<p><strong>Schritt 4: Erstelle dein eigenes Event</strong><br>Du kannst auch selbst ein Kochevent hosten! Klick auf "Event erstellen", beschreib dein Menü, und lade Gäste ein.</p>
</div>
</div>
<div class="faq-item">
<button type="button" class="faq-trigger" aria-expanded="false">
<span class="faq-title">Fallen bei Invité Kosten an?</span>
<span class="faq-icon">+</span>
</button>
<div class="faq-content">
<p>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.</p>
</div>
</div>
<div class="faq-item">
<button type="button" class="faq-trigger" aria-expanded="false">
<span class="faq-title">Kann ich ein eigenes Event erstellen?</span>
<span class="faq-icon">+</span>
</button>
<div class="faq-content">
<p>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.</p>
</div>
</div>
<div class="faq-item">
<button type="button" class="faq-trigger" aria-expanded="false">
<span class="faq-title">Wie funktioniert die An-/Abmeldung?</span>
<span class="faq-icon">+</span>
</button>
<div class="faq-content">
<p>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.</p>
</div>
</div>
<div class="faq-item">
<button type="button" class="faq-trigger" aria-expanded="false">
<span class="faq-title">Was ist mit Allergien und Diäten?</span>
<span class="faq-icon">+</span>
</button>
<div class="faq-content">
<p>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.</p>
</div>
</div>
<div class="faq-item">
<button type="button" class="faq-trigger" aria-expanded="false">
<span class="faq-title">Ist Invité sicher und vertrauenswürdig?</span>
<span class="faq-icon">+</span>
</button>
<div class="faq-content">
<p>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.</p>
</div>
</div>
</div>
</div>
</section>
<div class="footer"> <div class="footer">
<div class="footer-left"> <div class="footer-left">
<p class="p-small inline">© <img src="assets/logo_invite.svg" alt="Invité Logo" class="footer-invite_logo" /></p> <p class="p-small inline">© <img src="assets/logo_invite.svg" alt="Invité Logo" class="footer-invite_logo" /></p>
@ -165,5 +236,30 @@
<a href="datenschutz.html" class="link-text-footer">Datenschutz</a> <a href="datenschutz.html" class="link-text-footer">Datenschutz</a>
</div> </div>
</div> </div>
<!-- FAQ Akkordion Toggle Script -->
<script>
document.addEventListener('DOMContentLoaded', function() {
const faqTriggers = document.querySelectorAll('.faq-trigger');
faqTriggers.forEach((trigger) => {
trigger.addEventListener('click', function(e) {
e.preventDefault();
const isExpanded = this.getAttribute('aria-expanded') === 'true';
// Close all other items (optional: comment out to allow multiple open)
faqTriggers.forEach((otherTrigger) => {
if (otherTrigger !== trigger) {
otherTrigger.setAttribute('aria-expanded', 'false');
}
});
// Toggle current item
this.setAttribute('aria-expanded', !isExpanded);
});
});
});
</script>
</body> </body>
</html> </html>

View File

@ -341,6 +341,7 @@
// Render complete detail page layout including: // Render complete detail page layout including:
// hero metadata, host card, menu, participants, gallery and sticky action bar. // hero metadata, host card, menu, participants, gallery and sticky action bar.
detailContainer.innerHTML = ` detailContainer.innerHTML = `
<button type="button" class="btn-back-to-overview" data-navigate-back>Zurück</button>
<section class="detail-hero"> <section class="detail-hero">
<div class="detail-top-row"> <div class="detail-top-row">
@ -465,6 +466,7 @@
// Lightbox behavior for gallery images: // Lightbox behavior for gallery images:
// open on image click, close via backdrop, close button or ESC. // 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 lightbox = detailContainer.querySelector('.detail-lightbox');
const lightboxImage = detailContainer.querySelector('.detail-lightbox-image'); const lightboxImage = detailContainer.querySelector('.detail-lightbox-image');
const lightboxClose = detailContainer.querySelector('.detail-lightbox-close'); 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';
});
}
} }
}); });

View File

@ -2,6 +2,7 @@
const EVENTS_STORAGE_KEY = 'socialCookingEvents'; const EVENTS_STORAGE_KEY = 'socialCookingEvents';
const CURRENT_USER_KEY = 'socialCookingCurrentUser'; const CURRENT_USER_KEY = 'socialCookingCurrentUser';
const REGISTRATION_STORAGE_KEY = 'socialCookingRegistrations'; const REGISTRATION_STORAGE_KEY = 'socialCookingRegistrations';
const INFO_MODAL_SHOWN_KEY = 'infoModalShownOnFirstLogin';
// ------------------------------------------------------------- // -------------------------------------------------------------
// DOM references used throughout the page lifecycle. // 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 allEvents = [];
let activeCategory = 'ALLE'; let activeCategory = 'ALLE';
let activeDiets = new Set();
let activeAllergies = new Set();
const currentUser = getCurrentUser(); const currentUser = getCurrentUser();
function getCurrentUser() { function getCurrentUser() {
@ -91,8 +95,13 @@
const savedCategory = sessionStorage.getItem('activeFilter') || 'ALLE'; const savedCategory = sessionStorage.getItem('activeFilter') || 'ALLE';
const savedLocation = sessionStorage.getItem('activeLocation') || 'ALLE_ORTE'; const savedLocation = sessionStorage.getItem('activeLocation') || 'ALLE_ORTE';
const savedDate = sessionStorage.getItem('activeDate') || ''; const savedDate = sessionStorage.getItem('activeDate') || '';
const savedDiets = sessionStorage.getItem('activeDiets') || '';
const savedAllergies = sessionStorage.getItem('activeAllergies') || '';
activeCategory = savedCategory; activeCategory = savedCategory;
activeDiets = new Set(savedDiets ? savedDiets.split(',') : []);
activeAllergies = new Set(savedAllergies ? savedAllergies.split(',') : []);
if (locationFilter) { if (locationFilter) {
locationFilter.value = hasOption(locationFilter, savedLocation) ? savedLocation : 'ALLE_ORTE'; locationFilter.value = hasOption(locationFilter, savedLocation) ? savedLocation : 'ALLE_ORTE';
} }
@ -283,26 +292,54 @@
return Array.from(selectElement.options).some(option => option.value === value); 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() { function applyFilters() {
const selectedLocation = locationFilter ? locationFilter.value : 'ALLE_ORTE'; const selectedLocation = locationFilter ? locationFilter.value : 'ALLE_ORTE';
const selectedDate = dateFilter ? dateFilter.value : ''; const selectedDate = dateFilter ? dateFilter.value : '';
// Update active states for all filter types
filterButtons.forEach(btn => { filterButtons.forEach(btn => {
if (btn.getAttribute('data-cat') === activeCategory) { const isCategoryButton = btn.getAttribute('data-cat') !== null;
btn.classList.add('active'); const isDietButton = btn.getAttribute('data-diet') !== null;
} else { const isAllergieButton = btn.getAttribute('data-allergie') !== null;
btn.classList.remove('active');
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 filtered = allEvents.filter(event => {
const categoryMatch = activeCategory === 'ALLE' || event.category === activeCategory; 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 locationMatch = selectedLocation === 'ALLE_ORTE' || event.location === selectedLocation;
const eventDateIso = parseEventDateToIso(event.date); const eventDateIso = parseEventDateToIso(event.date);
const dateMatch = !selectedDate || eventDateIso === selectedDate; const dateMatch = !selectedDate || eventDateIso === selectedDate;
return categoryMatch && locationMatch && dateMatch; return categoryMatch && dietMatch && allergieMatch && locationMatch && dateMatch;
}); });
renderEvents(filtered); renderEvents(filtered);
@ -310,6 +347,8 @@
sessionStorage.setItem('activeFilter', activeCategory); sessionStorage.setItem('activeFilter', activeCategory);
sessionStorage.setItem('activeLocation', selectedLocation); sessionStorage.setItem('activeLocation', selectedLocation);
sessionStorage.setItem('activeDate', selectedDate); sessionStorage.setItem('activeDate', selectedDate);
sessionStorage.setItem('activeDiets', Array.from(activeDiets).join(','));
sessionStorage.setItem('activeAllergies', Array.from(activeAllergies).join(','));
} }
// Render either: // Render either:
@ -475,10 +514,32 @@
}); });
} }
// Category filter interactions. // Category filter interactions: mutually exclusive (radio button behavior).
filterButtons.forEach(button => { filterButtons.forEach(button => {
button.addEventListener('click', () => { 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(); 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. // Kick off initial load/render cycle.
fetchEvents(); fetchEvents();
}); });