Anpassung Layout
This commit is contained in:
commit
17c26b1cb5
130
css/index.css
130
css/index.css
@ -243,6 +243,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;
|
||||||
@ -381,3 +382,132 @@
|
|||||||
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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 {
|
.icon {
|
||||||
width: 20px;
|
width: 20px;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
@ -460,6 +473,29 @@ label {
|
|||||||
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;
|
||||||
@ -648,6 +684,32 @@ label {
|
|||||||
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;
|
||||||
@ -712,6 +774,22 @@ label {
|
|||||||
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);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,11 +4,11 @@
|
|||||||
"title": "Italienische Tavolata",
|
"title": "Italienische Tavolata",
|
||||||
"location": "Luzern",
|
"location": "Luzern",
|
||||||
"address": "Pilatusstrasse 18, 6003 Luzern",
|
"address": "Pilatusstrasse 18, 6003 Luzern",
|
||||||
"date": "11. APR. 2026",
|
"date": "22. APR. 2026",
|
||||||
"time": "15:30 UHR",
|
"time": "15:30 UHR",
|
||||||
"category": "Dinner",
|
"category": "Dinner",
|
||||||
"diet": "Vegetarisch",
|
"diet": "Vegetarisch",
|
||||||
"spots": 6,
|
"spots": 8,
|
||||||
"host": {
|
"host": {
|
||||||
"name": "Ferdinando",
|
"name": "Ferdinando",
|
||||||
"initial": "F"
|
"initial": "F"
|
||||||
@ -45,7 +45,7 @@
|
|||||||
"title": "Noche Peruana",
|
"title": "Noche Peruana",
|
||||||
"location": "Chur",
|
"location": "Chur",
|
||||||
"address": "Obere Gasse 41, 7000 Chur",
|
"address": "Obere Gasse 41, 7000 Chur",
|
||||||
"date": "16. Juni 2026",
|
"date": "12. April 2026",
|
||||||
"time": "19:00 UHR",
|
"time": "19:00 UHR",
|
||||||
"category": "Dinner",
|
"category": "Dinner",
|
||||||
"diet": "Omnivore",
|
"diet": "Omnivore",
|
||||||
|
|||||||
@ -28,7 +28,11 @@
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
<!-- Main content: detail page gets fully injected by JavaScript -->
|
<!-- Main content: detail page gets fully injected by JavaScript -->
|
||||||
|
<<<<<<< HEAD
|
||||||
<main class="container layout-wide">
|
<main class="container layout-wide">
|
||||||
|
=======
|
||||||
|
<main class="container page-content-safe detail-page">
|
||||||
|
>>>>>>> e3ac1a11f091bcc9b224ce4ae81743564e160e37
|
||||||
<!-- 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>
|
||||||
|
|||||||
@ -28,10 +28,19 @@
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
<!-- Main content: page headline, filter controls and dynamic event list -->
|
<!-- Main content: page headline, filter controls and dynamic event list -->
|
||||||
|
<<<<<<< HEAD
|
||||||
<main class="container layout-wide">
|
<main class="container layout-wide">
|
||||||
<!-- Page headline -->
|
<!-- Page headline -->
|
||||||
<p class="badge margin-bottom-40">Event finden</p>
|
<p class="badge margin-bottom-40">Event finden</p>
|
||||||
<h1>Was darf es sein?</h1>
|
<h1>Was darf es sein?</h1>
|
||||||
|
=======
|
||||||
|
<main class="container page-content-safe">
|
||||||
|
<!-- Page headline with info button -->
|
||||||
|
<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>
|
||||||
|
>>>>>>> e3ac1a11f091bcc9b224ce4ae81743564e160e37
|
||||||
|
|
||||||
<!-- Filter section: category chips + location/date filters -->
|
<!-- Filter section: category chips + location/date filters -->
|
||||||
<section class="filter-section">
|
<section class="filter-section">
|
||||||
@ -64,9 +73,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
<p class="filter-label">Ernährungsform</p>
|
<p class="filter-label">Ernährungsform</p>
|
||||||
<div class="filter-row">
|
<div class="filter-row">
|
||||||
<!-- Primary category filter buttons -->
|
<!-- Primary category filter buttons -->
|
||||||
|
=======
|
||||||
|
<div class="filter-row">
|
||||||
|
<!-- Diet filter buttons -->
|
||||||
|
>>>>>>> e3ac1a11f091bcc9b224ce4ae81743564e160e37
|
||||||
<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>
|
||||||
@ -77,11 +91,11 @@
|
|||||||
|
|
||||||
<p class="filter-label">Allergene</p>
|
<p class="filter-label">Allergene</p>
|
||||||
<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>
|
||||||
|
|
||||||
@ -95,6 +109,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">×</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>
|
||||||
|
|
||||||
|
|||||||
98
index.html
98
index.html
@ -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>
|
||||||
@ -1,6 +1,7 @@
|
|||||||
document.addEventListener('DOMContentLoaded', async () => {
|
document.addEventListener('DOMContentLoaded', async () => {
|
||||||
const EVENTS_STORAGE_KEY = 'socialCookingEvents';
|
const EVENTS_STORAGE_KEY = 'socialCookingEvents';
|
||||||
const CURRENT_USER_KEY = 'socialCookingCurrentUser';
|
const CURRENT_USER_KEY = 'socialCookingCurrentUser';
|
||||||
|
const USERS_STORAGE_KEY = 'socialCookingUsers';
|
||||||
const REGISTRATION_STORAGE_KEY = 'socialCookingRegistrations';
|
const REGISTRATION_STORAGE_KEY = 'socialCookingRegistrations';
|
||||||
// -------------------------------------------------------------
|
// -------------------------------------------------------------
|
||||||
// DOM entry point and shared asset path.
|
// 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) {
|
function setRegistrationMap(registrationMap) {
|
||||||
localStorage.setItem(REGISTRATION_STORAGE_KEY, JSON.stringify(registrationMap));
|
localStorage.setItem(REGISTRATION_STORAGE_KEY, JSON.stringify(registrationMap));
|
||||||
}
|
}
|
||||||
@ -114,7 +166,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const msUntilStart = eventDateTime.getTime() - Date.now();
|
const msUntilStart = eventDateTime.getTime() - Date.now();
|
||||||
const twentyfourHoursInMs = 12 * 60 * 60 * 1000;
|
const twentyfourHoursInMs = 24 * 60 * 60 * 1000;
|
||||||
|
|
||||||
return msUntilStart <= twentyfourHoursInMs;
|
return msUntilStart <= twentyfourHoursInMs;
|
||||||
}
|
}
|
||||||
@ -138,7 +190,7 @@
|
|||||||
return { daysLeft, isClosed: false };
|
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) {
|
function isAddressVisibleWindow(event) {
|
||||||
const eventDateTime = parseEventDateTime(event);
|
const eventDateTime = parseEventDateTime(event);
|
||||||
if (!eventDateTime || Number.isNaN(eventDateTime.getTime())) {
|
if (!eventDateTime || Number.isNaN(eventDateTime.getTime())) {
|
||||||
@ -146,7 +198,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const msUntilStart = eventDateTime.getTime() - Date.now();
|
const msUntilStart = eventDateTime.getTime() - Date.now();
|
||||||
const twentyfourHoursInMs = 12 * 60 * 60 * 1000;
|
const twentyfourHoursInMs = 24 * 60 * 60 * 1000;
|
||||||
|
|
||||||
return msUntilStart >= 0 && msUntilStart <= twentyfourHoursInMs;
|
return msUntilStart >= 0 && msUntilStart <= twentyfourHoursInMs;
|
||||||
}
|
}
|
||||||
@ -283,16 +335,26 @@
|
|||||||
const specifications = Array.isArray(event.specifications) && event.specifications.length > 0
|
const specifications = Array.isArray(event.specifications) && event.specifications.length > 0
|
||||||
? event.specifications
|
? 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 registrationMap = getRegistrationMap();
|
||||||
const extraRegistrations = countRegistrationsForEvent(registrationMap, event.id);
|
const participants = getResolvedParticipants(event, registrationMap);
|
||||||
const remainingParticipants = Math.max(0, participants.length + extraRegistrations - visibleParticipants.length);
|
const galleryImages = Array.isArray(event.gallery)
|
||||||
|
? event.gallery.filter(Boolean)
|
||||||
|
: [];
|
||||||
|
const galleryMarkup = galleryImages.length > 0
|
||||||
|
? `
|
||||||
|
<div class="detail-gallery detail-gallery-large">
|
||||||
|
${galleryImages.slice(0, 9).map((img, index) => `
|
||||||
|
<button class="detail-gallery-item" type="button" aria-label="Bild ${index + 1} gross anzeigen" data-fullsrc="${img}">
|
||||||
|
<img src="${img}" alt="${event.title} Bild ${index + 1}" class="detail-gallery-image">
|
||||||
|
</button>
|
||||||
|
`).join('')}
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: '';
|
||||||
|
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 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 freePlaces = Math.max(0, totalGuests - confirmedGuests);
|
||||||
const isFull = freePlaces === 0;
|
const isFull = freePlaces === 0;
|
||||||
const isRegistrationClosed = isRegistrationClosedForEvent(event);
|
const isRegistrationClosed = isRegistrationClosedForEvent(event);
|
||||||
@ -331,7 +393,12 @@
|
|||||||
<p>${event.address}</p>
|
<p>${event.address}</p>
|
||||||
</article>
|
</article>
|
||||||
`
|
`
|
||||||
: '';
|
: `
|
||||||
|
<article class="detail-panel">
|
||||||
|
<h2 class="detail-section-title">Adresse</h2>
|
||||||
|
<p>Vielen Dank für die Anmeldung! Die Adresse für diesen Event wird 24 Stunden vorher genau hier sichtbar sein.</p>
|
||||||
|
</article>
|
||||||
|
`;
|
||||||
const detailChips = [
|
const detailChips = [
|
||||||
`<span class="event-tag">${eventCategory}</span>`,
|
`<span class="event-tag">${eventCategory}</span>`,
|
||||||
...event.diet.split(', ').filter(d => d.trim() && d !== 'Keine Angabe').map(d => `<span class="event-tag">${getDietLabel(d.trim())}</span>`),
|
...event.diet.split(', ').filter(d => d.trim() && d !== 'Keine Angabe').map(d => `<span class="event-tag">${getDietLabel(d.trim())}</span>`),
|
||||||
@ -340,7 +407,12 @@
|
|||||||
|
|
||||||
// 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.
|
||||||
|
<<<<<<< HEAD
|
||||||
detailcontainer.innerHTML = `
|
detailcontainer.innerHTML = `
|
||||||
|
=======
|
||||||
|
detailContainer.innerHTML = `
|
||||||
|
<button type="button" class="btn-back-to-overview" data-navigate-back>Zurück</button>
|
||||||
|
>>>>>>> e3ac1a11f091bcc9b224ce4ae81743564e160e37
|
||||||
|
|
||||||
<section class="detail-hero">
|
<section class="detail-hero">
|
||||||
<div class="detail-top-row">
|
<div class="detail-top-row">
|
||||||
@ -400,13 +472,7 @@
|
|||||||
${addressPanelMarkup}
|
${addressPanelMarkup}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="detail-gallery detail-gallery-large">
|
${galleryMarkup}
|
||||||
${galleryImages.slice(0, 9).map((img, index) => `
|
|
||||||
<button class="detail-gallery-item" type="button" aria-label="Bild ${index + 1} gross anzeigen" data-fullsrc="${img}">
|
|
||||||
<img src="${img}" alt="${event.title} Bild ${index + 1}" class="detail-gallery-image">
|
|
||||||
</button>
|
|
||||||
`).join('')}
|
|
||||||
</div>
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="detail-action-bar">
|
<section class="detail-action-bar">
|
||||||
@ -474,11 +540,20 @@
|
|||||||
// 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.
|
||||||
// ---------------------------------------------------------
|
// ---------------------------------------------------------
|
||||||
|
<<<<<<< HEAD
|
||||||
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');
|
||||||
const galleryButtons = detailcontainer.querySelectorAll('.detail-gallery-item');
|
const galleryButtons = detailcontainer.querySelectorAll('.detail-gallery-item');
|
||||||
const registerButton = detailcontainer.querySelector('[data-register-button]');
|
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.
|
// Harte Absicherung: Eigene Events sind auf der Detailseite immer deaktiviert.
|
||||||
if (registerButton && isOwnEvent) {
|
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';
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
const EVENTS_STORAGE_KEY = 'socialCookingEvents';
|
const EVENTS_STORAGE_KEY = 'socialCookingEvents';
|
||||||
const CURRENT_USER_KEY = 'socialCookingCurrentUser';
|
const CURRENT_USER_KEY = 'socialCookingCurrentUser';
|
||||||
|
const USERS_STORAGE_KEY = 'socialCookingUsers';
|
||||||
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 +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 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() {
|
||||||
@ -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) {
|
function setRegistrationMap(registrationMap) {
|
||||||
localStorage.setItem(REGISTRATION_STORAGE_KEY, JSON.stringify(registrationMap));
|
localStorage.setItem(REGISTRATION_STORAGE_KEY, JSON.stringify(registrationMap));
|
||||||
}
|
}
|
||||||
@ -91,8 +147,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 +344,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 +399,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:
|
||||||
@ -353,10 +444,9 @@
|
|||||||
const displayTime = formatEventTime(event.time);
|
const displayTime = formatEventTime(event.time);
|
||||||
|
|
||||||
// Capacity logic:
|
// Capacity logic:
|
||||||
// spots = total capacity, participants.length = booked seats.
|
// spots = total capacity, resolved participants = booked seats.
|
||||||
const baseParticipants = Array.isArray(event.participants) ? event.participants.length : 0;
|
const resolvedParticipants = getResolvedParticipants(event, registrationMap);
|
||||||
const extraRegistrations = countRegistrationsForEvent(registrationMap, event.id);
|
const bookedSeats = resolvedParticipants.length;
|
||||||
const bookedSeats = baseParticipants + extraRegistrations;
|
|
||||||
const totalCapacity = event.spots;
|
const totalCapacity = event.spots;
|
||||||
const freePlaces = Math.max(0, totalCapacity - bookedSeats);
|
const freePlaces = Math.max(0, totalCapacity - bookedSeats);
|
||||||
const isFull = freePlaces === 0;
|
const isFull = freePlaces === 0;
|
||||||
@ -475,10 +565,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 +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.
|
// Kick off initial load/render cycle.
|
||||||
fetchEvents();
|
fetchEvents();
|
||||||
});
|
});
|
||||||
|
|||||||
@ -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) {
|
function isAddressVisibleWindow(event) {
|
||||||
const eventDateTime = parseEventDateTime(event);
|
const eventDateTime = parseEventDateTime(event);
|
||||||
if (!eventDateTime || Number.isNaN(eventDateTime.getTime())) {
|
if (!eventDateTime || Number.isNaN(eventDateTime.getTime())) {
|
||||||
@ -557,7 +557,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const msUntilStart = eventDateTime.getTime() - Date.now();
|
const msUntilStart = eventDateTime.getTime() - Date.now();
|
||||||
const twentyfourHoursInMs = 12 * 60 * 60 * 1000;
|
const twentyfourHoursInMs = 24 * 60 * 60 * 1000;
|
||||||
|
|
||||||
return msUntilStart >= 0 && msUntilStart <= twentyfourHoursInMs;
|
return msUntilStart >= 0 && msUntilStart <= twentyfourHoursInMs;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user