new build
All checks were successful
Deploy / deploy (push) Successful in 51s

This commit is contained in:
2026-07-22 21:40:34 +02:00
parent eb16827fdf
commit 2e800d4839
11 changed files with 674 additions and 98 deletions

View File

@@ -49,6 +49,15 @@ body {
.nav-links a:hover { color: var(--color-primary); }
.nav-actions { display: flex; align-items: center; gap: 10px; }
.profile-menu { position: relative; }
.profile-menu__trigger { display: inline-flex; align-items: center; gap: 10px; padding: 6px 10px 6px 6px; border: 1px solid var(--color-border); border-radius: 999px; background: #fff; color: var(--color-text); cursor: pointer; font: inherit; }
.profile-menu__trigger:hover { border-color: var(--color-primary); }
.profile-menu__avatar { width: 34px; height: 34px; border-radius: 999px; display: inline-flex; align-items: center; justify-content: center; background: var(--color-primary); color: var(--color-primary-contrast); font-weight: 700; }
.profile-menu__name { font-weight: 600; }
.profile-menu__dropdown { position: absolute; top: calc(100% + 10px); right: 0; min-width: 220px; padding: 10px; display: none; background: #fff; border: 1px solid var(--color-border); border-radius: var(--radius-md); box-shadow: var(--shadow-card); }
.profile-menu.is-open .profile-menu__dropdown { display: grid; gap: 4px; }
.profile-menu__dropdown a { display: block; padding: 10px 12px; border-radius: 10px; font-weight: 600; color: var(--color-text); }
.profile-menu__dropdown a:hover { background: #f8f4ec; color: var(--color-primary); }
.menu-toggle { display: none; background: transparent; border: 1px solid var(--color-border); padding: 8px 10px; border-radius: var(--radius-sm); }
.mobile-menu { display: none; padding: 12px 16px; border-top: 1px solid var(--color-border); background: #fff; }
.mobile-menu a, .mobile-menu button { display: block; width: 100%; text-align: left; margin-bottom: 10px; }
@@ -56,7 +65,8 @@ body {
@media (max-width: 900px){
.nav-links { display: none; }
.menu-toggle { display: inline-flex; }
.nav-actions .btn { display: none; }
.nav-actions .btn,
.profile-menu { display: none; }
.mobile-menu.open { display: block; }
}

View File

@@ -5,6 +5,9 @@ document.addEventListener('DOMContentLoaded', () => {
const locationPreference = body.dataset.locationPreference || 'prompt';
const header = document.querySelector('.site-header');
const headerSentinel = document.getElementById('headerSentinel');
const matomoConfigNode = document.getElementById('matomoConfig');
const consentStateKey = 'pkt_cookie_consent';
let matomoLoaded = false;
// Logo-Logik
const pickLogo = (gender) => {
@@ -41,6 +44,19 @@ document.addEventListener('DOMContentLoaded', () => {
mobileMenu?.classList.toggle('open');
});
});
const profileMenu = document.querySelector('[data-profile-menu]');
const profileMenuTrigger = document.querySelector('[data-profile-menu-trigger]');
profileMenuTrigger?.addEventListener('click', () => {
const next = !profileMenu?.classList.contains('is-open');
profileMenu?.classList.toggle('is-open', next);
profileMenuTrigger.setAttribute('aria-expanded', next ? 'true' : 'false');
});
document.addEventListener('click', (event) => {
if (!profileMenu || !profileMenuTrigger) return;
if (profileMenu.contains(event.target)) return;
profileMenu.classList.remove('is-open');
profileMenuTrigger.setAttribute('aria-expanded', 'false');
});
// Scroll zu Events
const scrollBtn = document.getElementById('scrollToEvents');
@@ -78,8 +94,13 @@ document.addEventListener('DOMContentLoaded', () => {
quickGeo: document.getElementById('quickGeo'),
eventsSection: document.getElementById('events'),
locationPreferenceModal: document.getElementById('locationPreferenceModal'),
consentBanner: document.getElementById('cookieConsentBanner'),
consentModal: document.getElementById('cookieConsentModal'),
consentAnalytics: document.getElementById('consentAnalytics'),
consentExternalServices: document.getElementById('consentExternalServices'),
};
let effectiveLocationPreference = locationPreference;
let consentState = null;
const fmtDate = (iso) => {
const d = new Date(iso);
@@ -88,6 +109,127 @@ document.addEventListener('DOMContentLoaded', () => {
const now = () => new Date();
const deleteCookie = (name) => {
document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; SameSite=Lax`;
};
const readConsent = () => {
try {
const raw = window.localStorage.getItem(consentStateKey);
if (!raw) return null;
const parsed = JSON.parse(raw);
if (!parsed || typeof parsed !== 'object') return null;
return {
necessary: true,
analytics: Boolean(parsed.analytics),
external_services: Boolean(parsed.external_services),
updatedAt: parsed.updatedAt || null,
};
} catch (err) {
return null;
}
};
const writeConsent = (nextState) => {
consentState = {
necessary: true,
analytics: Boolean(nextState.analytics),
external_services: Boolean(nextState.external_services),
updatedAt: new Date().toISOString(),
};
try {
window.localStorage.setItem(consentStateKey, JSON.stringify(consentState));
} catch (err) {
// ignore
}
window.PKTConsent = window.PKTConsent || {};
window.PKTConsent.get = () => consentState;
window.PKTConsent.has = (category) => {
if (category === 'necessary') return true;
return Boolean(consentState?.[category]);
};
};
const closeConsentModal = () => {
el.consentModal?.classList.remove('open');
};
const openConsentModal = () => {
if (el.consentAnalytics) el.consentAnalytics.checked = Boolean(consentState?.analytics);
if (el.consentExternalServices) el.consentExternalServices.checked = Boolean(consentState?.external_services);
el.consentModal?.classList.add('open');
};
const deleteMatomoCookies = () => {
['_pk_id', '_pk_ses', '_pk_ref', '_pk_cvar', 'mtm_consent', 'mtm_consent_removed'].forEach((prefix) => {
deleteCookie(prefix);
const hostParts = window.location.hostname.split('.');
while (hostParts.length > 1) {
document.cookie = `${prefix}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; domain=.${hostParts.join('.')}; SameSite=Lax`;
hostParts.shift();
}
});
};
const loadMatomo = () => {
if (matomoLoaded || !matomoConfigNode || !consentState?.analytics) return;
try {
const cfg = JSON.parse(matomoConfigNode.textContent || '{}');
if (!cfg.url || !cfg.siteId) return;
const _paq = window._paq = window._paq || [];
_paq.push(['setDomains', Array.isArray(cfg.domains) ? cfg.domains : []]);
_paq.push(['trackPageView']);
_paq.push(['enableLinkTracking']);
_paq.push(['setTrackerUrl', cfg.url + 'matomo.php']);
_paq.push(['setSiteId', cfg.siteId]);
const script = document.createElement('script');
script.async = true;
script.src = cfg.url + 'matomo.js';
document.head.appendChild(script);
matomoLoaded = true;
} catch (err) {
// ignore
}
};
const showConsentBannerIfNeeded = () => {
if (!consentState && el.consentBanner) {
el.consentBanner.removeAttribute('hidden');
}
};
const applyConsent = (nextState, options = {}) => {
const previous = consentState;
writeConsent(nextState);
if (!consentState.external_services) {
clearStoredLocation();
}
if (!consentState.analytics) {
deleteMatomoCookies();
} else {
loadMatomo();
}
el.consentBanner?.setAttribute('hidden', 'hidden');
closeConsentModal();
if (previous && options.reloadOnChange !== false) {
const analyticsChanged = previous.analytics !== consentState.analytics;
const externalChanged = previous.external_services !== consentState.external_services;
if (analyticsChanged || externalChanged) {
window.location.reload();
}
}
};
const requireExternalServicesConsent = (message = 'Für diese Funktion werden externe Dienste benötigt.') => {
if (consentState?.external_services) {
return true;
}
alert(message);
openConsentModal();
return false;
};
const setCookie = (name, value, days = 30) => {
const expires = new Date(Date.now() + (days * 86400000)).toUTCString();
document.cookie = `${name}=${encodeURIComponent(value)}; expires=${expires}; path=/; SameSite=Lax`;
@@ -142,7 +284,7 @@ document.addEventListener('DOMContentLoaded', () => {
} catch (err) {
// ignore
}
document.cookie = 'pkt_user_location=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; SameSite=Lax';
deleteCookie('pkt_user_location');
if (el.quickLat) el.quickLat.value = '';
if (el.quickLng) el.quickLng.value = '';
if (el.quickLoc) el.quickLoc.value = '';
@@ -465,6 +607,10 @@ document.addEventListener('DOMContentLoaded', () => {
renderSlider(null);
return null;
}
if (!requireExternalServicesConsent('Für standortbezogene Treffen werden Standort- und Kartendienste erst nach deiner Einwilligung aktiviert.')) {
renderSlider(null);
return null;
}
const stored = applyStoredLocationToForm();
if (stored && effectiveLocationPreference !== 'enabled' && sessionPreference !== 'session') {
@@ -511,6 +657,9 @@ document.addEventListener('DOMContentLoaded', () => {
};
el.quickGeo?.addEventListener('click', () => {
if (!requireExternalServicesConsent('Für die Standortermittlung benötigen wir deine Einwilligung zu externen Diensten und Standortfunktionen.')) {
return;
}
if (!navigator.geolocation) {
alert('Geolocation wird nicht unterstützt.');
return;
@@ -554,6 +703,28 @@ document.addEventListener('DOMContentLoaded', () => {
btn.addEventListener('click', () => el.modal?.classList.remove('open'));
});
el.modal?.addEventListener('click', (e) => { if (e.target === el.modal) el.modal.classList.remove('open'); });
document.querySelectorAll('[data-consent-open]').forEach(btn => {
btn.addEventListener('click', openConsentModal);
});
document.querySelectorAll('[data-consent-close]').forEach(btn => {
btn.addEventListener('click', closeConsentModal);
});
document.querySelectorAll('[data-consent-necessary]').forEach(btn => {
btn.addEventListener('click', () => applyConsent({ analytics: false, external_services: false }));
});
document.querySelector('[data-consent-accept-all]')?.addEventListener('click', () => {
applyConsent({ analytics: true, external_services: true });
});
document.querySelector('[data-consent-save]')?.addEventListener('click', () => {
applyConsent({
analytics: Boolean(el.consentAnalytics?.checked),
external_services: Boolean(el.consentExternalServices?.checked),
});
});
el.consentModal?.addEventListener('click', (e) => {
if (e.target === el.consentModal) closeConsentModal();
});
document.addEventListener('pkt:open-consent', openConsentModal);
document.querySelectorAll('[data-nav-events]').forEach(link => {
link.addEventListener('click', async () => {
@@ -563,6 +734,12 @@ document.addEventListener('DOMContentLoaded', () => {
});
});
consentState = readConsent();
if (consentState?.analytics) {
loadMatomo();
}
showConsentBannerIfNeeded();
try {
if (!isLoggedIn) {
const guestPreference = window.localStorage.getItem('pkt_location_preference_guest');
@@ -581,7 +758,7 @@ document.addEventListener('DOMContentLoaded', () => {
renderSlider(initialLocation);
renderThreadSlider();
if (effectiveLocationPreference === 'enabled' && window.location.pathname === '/') {
if (effectiveLocationPreference === 'enabled' && window.location.pathname === '/' && consentState?.external_services) {
ensureEventsLocation();
} else if (window.location.hash === '#events' || window.location.hash === '#quicksearch') {
ensureEventsLocation();

195
public/page/datenschutz.php Normal file
View File

@@ -0,0 +1,195 @@
<?php
declare(strict_types=1);
$app = app();
$config = $app->config();
$sessionCookie = $config->cookiePrefix() . 'session';
$clientCookie = $config->cookiePrefix() . 'client';
?>
<main class="container section legal-page">
<div class="content-card content-card--narrow page-copy">
<h1>Datenschutz &amp; Cookies</h1>
<p class="muted small">Stand: 22. Juli 2026</p>
<p>
Diese Hinweise erklären, welche personenbezogenen Daten beim Besuch und bei der Nutzung von Papa-Kind-Treff
verarbeitet werden, wofür dies geschieht und welche Auswahlmöglichkeiten du bei Cookies, Analyse und externen Diensten hast.
</p>
<section>
<h2>1. Verantwortlicher</h2>
<p>
Lars Gebhardt-Kusche<br>
Hammerstraße 47B<br>
14167 Berlin
</p>
<p>
E-Mail: <a href="mailto:rechtliches@papa-kind-treff.info">rechtliches@papa-kind-treff.info</a><br>
Telefon: +49 (171) 33 10 538
</p>
</section>
<section>
<h2>2. Aufruf der Website</h2>
<p>
Beim rein informatorischen Besuch der Website verarbeitet der Server technisch notwendige Verbindungsdaten,
zum Beispiel aufgerufene Adresse, Datum und Uhrzeit, übertragene Datenmenge, Browser-Informationen,
Referrer und IP-Adresse. Diese Verarbeitung ist erforderlich, um die Website auszuliefern,
Stabilität und Sicherheit sicherzustellen und Missbrauch abzuwehren.
</p>
<p>
Rechtsgrundlage ist Art. 6 Abs. 1 lit. f DSGVO. Soweit auf Informationen in deinem Endgerät zugegriffen wird,
richtet sich dies zusätzlich nach § 25 TDDDG.
</p>
</section>
<section>
<h2>3. Registrierung, Login und Mitgliederbereich</h2>
<p>
Wenn du ein Konto anlegst oder den Mitgliederbereich nutzt, verarbeiten wir die dafür notwendigen Daten,
insbesondere E-Mail-Adresse, Passwort-Hash, Verifikationsstatus sowie die von dir gepflegten Profilangaben.
Dazu können Anzeigename, Name, Ort, Sprachen, Kurzbeschreibung, optionale Kinderangaben,
eigene Events, Event-Teilnahmen und Community-Inhalte gehören.
</p>
<p>
Rechtsgrundlage ist Art. 6 Abs. 1 lit. b DSGVO, soweit die Verarbeitung für die Durchführung des
Nutzungsverhältnisses erforderlich ist.
</p>
</section>
<section>
<h2>4. Community, Beiträge und Events</h2>
<p>
Wenn du Themen, Antworten oder Termine veröffentlichst, werden diese Inhalte mit deinem Konto verknüpft und
entsprechend der jeweiligen Sichtbarkeit innerhalb der Plattform angezeigt. Moderationsvorgänge wie Meldungen,
Rollen, Bewerbungen oder Community-Sperren werden verarbeitet, soweit dies für den Betrieb einer sicheren und
funktionsfähigen Community erforderlich ist.
</p>
<p>
Rechtsgrundlage ist Art. 6 Abs. 1 lit. b DSGVO sowie ergänzend Art. 6 Abs. 1 lit. f DSGVO
für den sicheren und geordneten Betrieb der Plattform.
</p>
</section>
<section>
<h2>5. Verschlüsselte und sensible Angaben</h2>
<p>
Bestimmte Angaben, insbesondere Telefoninformationen und einzelne Kinderdaten, werden nicht nur organisatorisch,
sondern zusätzlich verschlüsselt verarbeitet. Das dient dem Schutz besonders sensibler Angaben innerhalb der Plattform.
</p>
</section>
<section>
<h2>6. Cookies, lokale Speicherung und Sitzungsdaten</h2>
<p>
Die Website verwendet technisch notwendige Cookies und, je nach deiner Auswahl, weitere lokale Speichermechanismen
wie `localStorage` und `sessionStorage`.
</p>
<p>
Aktuell werden insbesondere folgende technisch relevanten Einträge verwendet:
</p>
<ul class="list">
<li>`<?= htmlspecialchars($sessionCookie, ENT_QUOTES) ?>`: Sitzungs-Cookie für Login und geschützte Bereiche</li>
<li>`<?= htmlspecialchars($clientCookie, ENT_QUOTES) ?>`: technische Client-Kennung zur Wiedererkennung des Browsers</li>
<li>`pkt_cookie_consent`: Speichert deine Auswahl zu Analyse und externen Diensten</li>
<li>`pkt_user_location`, `pkt_user_location_session`, `pkt_user_location_session_preference`, `pkt_location_preference_guest`: nur bei Nutzung standortbezogener Funktionen und abhängig von Einwilligung bzw. Auswahl</li>
</ul>
<p>
Technisch notwendige Cookies und Speicherungen werden auf Grundlage von Art. 6 Abs. 1 lit. f DSGVO
beziehungsweise Art. 6 Abs. 1 lit. b DSGVO genutzt sowie im Rahmen von § 25 Abs. 2 TDDDG,
soweit sie für die Bereitstellung des ausdrücklich gewünschten Dienstes erforderlich sind.
</p>
</section>
<section>
<h2>7. Einwilligungsverwaltung</h2>
<p>
Für nicht notwendige Funktionen verwenden wir eine eigene Einwilligungsverwaltung. Dort kannst du auswählen,
ob Analyse und externe Dienste aktiviert werden dürfen. Deine Auswahl kann jederzeit über den Link
„Cookie-Einstellungen“ im Footer geändert werden.
</p>
<p>
Rechtsgrundlage für einwilligungspflichtige Funktionen ist Art. 6 Abs. 1 lit. a DSGVO
in Verbindung mit § 25 Abs. 1 TDDDG.
</p>
</section>
<section>
<h2>8. Matomo-Analyse</h2>
<p>
Sofern du der Kategorie „Analyse“ zustimmst, wird Matomo zur Reichweitenmessung und Nutzungsanalyse eingebunden.
Dabei können Nutzungsdaten und technisch erforderliche Analyse-Cookies verarbeitet werden.
</p>
<p>
Matomo wird erst nach deiner Einwilligung geladen. Wenn du die Einwilligung widerrufst,
wird die Analyse für künftige Besuche beendet.
</p>
<p>
Empfänger in diesem Zusammenhang ist die Matomo-Instanz unter `matomo.my-statistics.info`.
</p>
</section>
<section>
<h2>9. Standort, Karten und externe Dienste</h2>
<p>
Wenn du standortbezogene Funktionen nutzt oder freigibst, kann der Browser deinen aktuellen Standort abfragen.
Je nach Auswahl wird dieser nur für den aktuellen Besuch oder darüber hinaus lokal gespeichert,
damit dir passende Treffen in deiner Nähe angezeigt werden können.
</p>
<p>
Für Karten- und Adressfunktionen werden derzeit externe Dienste genutzt:
</p>
<ul class="list">
<li>`unpkg.com` für die Bereitstellung von Leaflet-Dateien</li>
<li>OpenStreetMap Nominatim für Geocoding und Reverse-Geocoding</li>
</ul>
<p>
Diese Funktionen werden erst aktiv, wenn du der Kategorie „Externe Dienste, Karten und Standort“ zugestimmt hast.
</p>
</section>
<section>
<h2>10. Empfänger und Kategorien von Empfängern</h2>
<p>
Eine Weitergabe erfolgt nur, soweit sie für den Betrieb der Website oder einzelner Funktionen erforderlich ist.
Dazu können insbesondere technische Hosting-Dienstleister, E-Mail-Dienstleister,
Analyse-Dienstleister sowie die oben genannten externen Karten- und Standortdienste gehören.
</p>
</section>
<section>
<h2>11. Speicherdauer</h2>
<p>
Wir speichern personenbezogene Daten nur so lange, wie dies für die jeweiligen Zwecke erforderlich ist
oder gesetzliche Aufbewahrungspflichten bestehen. Kontodaten, Profilinformationen, Community-Inhalte
und Eventdaten bleiben grundsätzlich gespeichert, solange das Konto genutzt wird oder bis eine Löschung
beantragt beziehungsweise technisch vorgesehen ist, soweit keine gesetzlichen oder berechtigten Gründe
für eine längere Aufbewahrung bestehen.
</p>
<p>
Lokale Einwilligungs- und Standortinformationen werden abhängig von ihrer Funktion entweder sitzungsbezogen
oder bis zu einer Änderung oder Löschung im Browser gespeichert.
</p>
</section>
<section>
<h2>12. Deine Rechte</h2>
<p>
Du hast nach Maßgabe der gesetzlichen Voraussetzungen das Recht auf Auskunft, Berichtigung, Löschung,
Einschränkung der Verarbeitung, Datenübertragbarkeit und Widerspruch.
Eine erteilte Einwilligung kannst du jederzeit mit Wirkung für die Zukunft widerrufen.
</p>
<p>
Außerdem hast du das Recht, dich bei einer Datenschutz-Aufsichtsbehörde zu beschweren.
</p>
</section>
<section>
<h2>13. Hinweis zur Aktualisierung</h2>
<p>
Diese Hinweise werden angepasst, wenn sich Funktionen, eingesetzte Dienste oder rechtliche Anforderungen ändern.
Insbesondere bei neuen Cookies, Tracking-Technologien oder Drittanbietern wird diese Seite ergänzt.
</p>
</section>
</div>
</main>