Files
papa-kind-treff.info/public/assets/js/app.js
2025-12-28 23:57:29 +01:00

226 lines
7.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
document.addEventListener('DOMContentLoaded', () => {
const body = document.body;
const isLoggedIn = body.dataset.auth === '1';
const childGender = body.dataset.childGender || '';
const header = document.querySelector('.site-header');
const headerSentinel = document.getElementById('headerSentinel');
// Logo-Logik
const pickLogo = (gender) => {
if (gender === 'female') return 'logo_female.png';
if (gender === 'male') return 'logo_male.png';
return Math.random() < 0.5 ? 'logo_female.png' : 'logo_male.png';
};
const chosenLogo = pickLogo(childGender);
document.querySelectorAll('[data-logo-img]').forEach(img => {
img.src = `/assets/bilder/${chosenLogo}`;
});
// Header shrink via IntersectionObserver (no flicker around threshold)
if (header && headerSentinel && 'IntersectionObserver' in window) {
const observer = new IntersectionObserver(([entry]) => {
header.classList.toggle('is-scrolled', !entry.isIntersecting);
}, {
rootMargin: '-1px 0px 0px 0px',
threshold: 0,
});
observer.observe(headerSentinel);
} else if (header) {
// Fallback with simple threshold
const limit = 120;
const onScroll = () => header.classList.toggle('is-scrolled', window.scrollY > limit);
onScroll();
window.addEventListener('scroll', onScroll, { passive: true });
}
// Mobile Menü
const mobileMenu = document.getElementById('mobileMenu');
document.querySelectorAll('.menu-toggle').forEach(btn => {
btn.addEventListener('click', () => {
mobileMenu?.classList.toggle('open');
});
});
// Scroll zu Events
const scrollBtn = document.getElementById('scrollToEvents');
if (scrollBtn) {
scrollBtn.addEventListener('click', () => {
document.getElementById('events')?.scrollIntoView({ behavior: 'smooth' });
});
}
// Demo-Events
const events = [
{
id: 1,
title: 'Spielplatzrunde im Park',
teaser: 'Lockeres Treffen mit Kaffee, Sandspielzeug und Picknick.',
description: 'Wir treffen uns am großen Spielplatz im Kiez. Bringt Snacks mit, wir teilen. Für alle Altersstufen offen.',
city: 'Berlin',
zip: '10437',
region: 'Prenzlauer Berg',
topic: 'outdoor',
startsAt: '2025-08-10T10:00:00',
allowKids: true,
ageGroup: 'kids',
visibility: 'public',
contact: 'papa-berlin@example.com',
locationLabel: 'Mauerpark',
},
{
id: 2,
title: 'Väter-Kaffee & Austausch',
teaser: 'Elternzeit, Job, Schlaf wir reden über alles.',
description: 'Reservierter Tisch im Café. Fokus auf Austausch unter Vätern, Kinder optional.',
city: 'Hamburg',
zip: '22767',
region: 'Altona',
topic: 'kaffee',
startsAt: '2025-08-12T19:00:00',
allowKids: false,
ageGroup: '',
visibility: 'members',
contact: 'altona-dads@example.com',
locationLabel: 'Café Elbseite',
},
{
id: 3,
title: 'Sport & Spiel im Park',
teaser: 'Ballspiele und leichtes Workout, Kinder toben mit.',
description: 'Wir bringen Bälle und Slackline. Warm-up, danach freies Spiel. Bitte Wasser mitbringen.',
city: 'München',
zip: '80804',
region: 'Schwabing',
topic: 'sport',
startsAt: '2025-08-15T11:00:00',
allowKids: true,
ageGroup: 'school',
visibility: 'public',
contact: 'schwabing-sport@example.com',
locationLabel: 'Luitpoldpark',
},
{
id: 4,
title: 'Workshop: Erste Hilfe für Kids',
teaser: 'Erste Hilfe Basics speziell für Kinder-Unfälle.',
description: 'Zertifizierter Trainer, kleiner Materialbeitrag. Kinder können mitgebracht werden.',
city: 'Köln',
zip: '50667',
region: 'Innenstadt',
topic: 'workshop',
startsAt: '2025-08-20T18:30:00',
allowKids: true,
ageGroup: 'kids',
visibility: 'members',
contact: 'koeln-workshop@example.com',
locationLabel: 'Familienzentrum Mitte',
},
];
const state = { topic: 'all', age: '', region: '', query: '' };
const el = {
list: document.getElementById('eventList'),
chips: document.querySelectorAll('[data-filter]'),
locInput: document.getElementById('locInput'),
topicSelect: document.getElementById('topicSelect'),
ageSelect: document.getElementById('ageSelect'),
btnSearch: document.getElementById('btnSearch'),
btnGeo: document.getElementById('btnGeo'),
};
const fmtDate = (iso) => {
const d = new Date(iso);
return d.toLocaleDateString('de-DE', { weekday: 'short', day: '2-digit', month: 'short', hour: '2-digit', minute: '2-digit' });
};
const tag = (label) => `<span class="badge">${label}</span>`;
const renderCard = (item) => {
const guest = !isLoggedIn;
const access = item.visibility === 'members' && guest ? '<div class="event__access">Nur für Mitglieder</div>' : '';
const desc = guest ? `<p class="muted">Melde dich an, um die volle Beschreibung zu sehen.</p>` : `<p>${item.description}</p>`;
const contact = !guest ? `<div class="muted small">Kontakt: ${item.contact}</div>` : '';
const kids = item.allowKids ? 'Mit Kindern' : 'Ohne Kinder';
const tags = [
tag(item.topic === 'kaffee' ? 'Kaffee & Austausch' : item.topic.charAt(0).toUpperCase() + item.topic.slice(1)),
tag(kids),
item.ageGroup ? tag(`Alter: ${item.ageGroup}`) : '',
].filter(Boolean).join('');
return `
<article class="card">
<div class="event__body">
${access}
<div class="event__meta">
<span>${fmtDate(item.startsAt)}</span>
<span>📍 ${item.region || item.city}</span>
<span>${item.visibility === 'public' ? 'Öffentlich' : 'Mitglieder'}</span>
</div>
<h3>${item.title}</h3>
<p class="muted">${guest ? item.teaser : item.description.slice(0, 140) + '...'}</p>
${guest ? '' : desc}
<div class="event__tags">${tags}</div>
${contact}
<div class="flex gap-12">
<button class="btn ghost">Details</button>
${!guest ? '<button class="btn">Teilnehmen</button>' : '<button class="btn">Anmelden</button>'}
</div>
</div>
</article>`;
};
const matchesFilter = (ev) => {
const topicOk = state.topic === 'all' || state.topic === ev.topic;
const ageOk = !state.age || ev.ageGroup === state.age || (state.age === 'baby' && ev.ageGroup === 'kids'); // simple fallback
const regionOk = !state.region || ev.region.toLowerCase().includes(state.region) || ev.city.toLowerCase().includes(state.region) || ev.zip.startsWith(state.region);
const queryOk = !state.query || (ev.title + ev.teaser + ev.description + ev.city + ev.region).toLowerCase().includes(state.query);
return topicOk && ageOk && regionOk && queryOk;
};
const renderEvents = () => {
if (!el.list) return;
const filtered = events.filter(matchesFilter);
el.list.innerHTML = filtered.map(renderCard).join('') || '<p class="muted">Keine Events gefunden.</p>';
};
if (el.chips.length > 0) {
el.chips[0].classList.add('active');
}
el.chips.forEach(chip => {
chip.addEventListener('click', () => {
el.chips.forEach(c => c.classList.remove('active'));
chip.classList.add('active');
state.topic = chip.dataset.filter;
renderEvents();
});
});
if (el.btnSearch) {
el.btnSearch.addEventListener('click', () => {
state.region = (el.locInput?.value || '').trim().toLowerCase();
state.topic = el.topicSelect?.value || 'all';
state.age = el.ageSelect?.value || '';
renderEvents();
});
}
if (el.btnGeo) {
el.btnGeo.addEventListener('click', () => {
if (!navigator.geolocation) {
alert('Geolocation wird nicht unterstützt.');
return;
}
navigator.geolocation.getCurrentPosition(
() => {
state.region = 'nähe';
renderEvents();
},
() => alert('Standort konnte nicht ermittelt werden.')
);
});
}
renderEvents();
});