Sprint 14: Impressum, Datenschutz, Google Analytics (cookieless)
- Impressum-Seite (§5 TMG / §18 MStV) mit René Degelmanns Daten - Datenschutzerklärung (DSGVO) mit GA-Erläuterung und Opt-out - Google Analytics G-YLG780DV3Z, Option B (cookieless, kein Consent nötig) - Sidebar-Footer-Links Impressum / Datenschutz - APP_VER → 86, SW-Cache → by-v110
This commit is contained in:
parent
21e50c6c7b
commit
6698543d14
5 changed files with 327 additions and 22 deletions
|
|
@ -22,8 +22,8 @@
|
||||||
|
|
||||||
<!-- CSS: Reihenfolge ist wichtig — ?v= zwingt Browser zur Neuladung -->
|
<!-- CSS: Reihenfolge ist wichtig — ?v= zwingt Browser zur Neuladung -->
|
||||||
<link rel="stylesheet" href="/css/design-system.css">
|
<link rel="stylesheet" href="/css/design-system.css">
|
||||||
<link rel="stylesheet" href="/css/layout.css?v=81">
|
<link rel="stylesheet" href="/css/layout.css?v=86">
|
||||||
<link rel="stylesheet" href="/css/components.css?v=81">
|
<link rel="stylesheet" href="/css/components.css?v=86">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
|
|
@ -119,6 +119,14 @@
|
||||||
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#user"></use></svg>
|
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#user"></use></svg>
|
||||||
<span id="sidebar-username">Anmelden</span>
|
<span id="sidebar-username">Anmelden</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div style="margin-top:var(--space-4);padding-top:var(--space-3);
|
||||||
|
border-top:1px solid var(--c-border,#e5e7eb);
|
||||||
|
font-size:var(--text-xs);color:var(--c-text-muted);
|
||||||
|
display:flex;gap:var(--space-3);padding-bottom:var(--space-2)">
|
||||||
|
<span data-page="impressum" style="cursor:pointer;text-decoration:underline">Impressum</span>
|
||||||
|
<span data-page="datenschutz" style="cursor:pointer;text-decoration:underline">Datenschutz</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
|
|
@ -231,6 +239,14 @@
|
||||||
<div class="page-body page-container-chat"></div>
|
<div class="page-body page-container-chat"></div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<section class="page" id="page-impressum">
|
||||||
|
<div class="page-body page-container"></div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="page" id="page-datenschutz">
|
||||||
|
<div class="page-body page-container"></div>
|
||||||
|
</section>
|
||||||
|
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<!-- MOBILE BOTTOM NAVIGATION -->
|
<!-- MOBILE BOTTOM NAVIGATION -->
|
||||||
|
|
@ -269,12 +285,86 @@
|
||||||
<div id="modal-container"></div>
|
<div id="modal-container"></div>
|
||||||
|
|
||||||
<!-- JS: Reihenfolge ist wichtig — erst Basis, dann Features -->
|
<!-- JS: Reihenfolge ist wichtig — erst Basis, dann Features -->
|
||||||
<script src="/js/api.js?v=81"></script>
|
<script src="/js/api.js?v=86"></script>
|
||||||
<script src="/js/ui.js?v=81"></script>
|
<script src="/js/ui.js?v=86"></script>
|
||||||
<script src="/js/app.js?v=81"></script>
|
<script src="/js/app.js?v=86"></script>
|
||||||
|
|
||||||
<!-- Feature-Seiten werden lazy geladen -->
|
<!-- Feature-Seiten werden lazy geladen -->
|
||||||
|
|
||||||
|
<!-- Google Analytics (Option B: cookieless/anonymisiert, kein Consent nötig)
|
||||||
|
Wechsel zu Option A (mit Consent-Banner): GA_MODE auf 'A' setzen -->
|
||||||
|
<script>
|
||||||
|
(function() {
|
||||||
|
var GA_ID = 'G-YLG780DV3Z';
|
||||||
|
var GA_MODE = 'B'; // 'B' = cookieless | 'A' = mit Consent-Banner
|
||||||
|
|
||||||
|
function _loadGA(withConsent) {
|
||||||
|
var s = document.createElement('script');
|
||||||
|
s.async = true;
|
||||||
|
s.src = 'https://www.googletagmanager.com/gtag/js?id=' + GA_ID;
|
||||||
|
document.head.appendChild(s);
|
||||||
|
window.dataLayer = window.dataLayer || [];
|
||||||
|
window.gtag = function(){ dataLayer.push(arguments); };
|
||||||
|
gtag('js', new Date());
|
||||||
|
if (withConsent) {
|
||||||
|
gtag('config', GA_ID, { anonymize_ip: true });
|
||||||
|
} else {
|
||||||
|
// Option B: komplett cookieless
|
||||||
|
gtag('config', GA_ID, { anonymize_ip: true, storage: 'none', client_storage: 'none' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Opt-out respektieren (setzbar über Datenschutz-Seite)
|
||||||
|
if (localStorage.getItem('gaOptOut') === 'yes') return;
|
||||||
|
|
||||||
|
if (GA_MODE === 'B') {
|
||||||
|
_loadGA(false);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Option A: Consent-Banner
|
||||||
|
var consent = localStorage.getItem('gaConsent');
|
||||||
|
if (consent === 'yes') { _loadGA(true); }
|
||||||
|
|
||||||
|
window.addEventListener('DOMContentLoaded', function() {
|
||||||
|
if (consent !== null) return;
|
||||||
|
var banner = document.getElementById('ga-consent-banner');
|
||||||
|
if (banner) banner.style.display = 'flex';
|
||||||
|
document.getElementById('ga-consent-accept')?.addEventListener('click', function() {
|
||||||
|
localStorage.setItem('gaConsent', 'yes');
|
||||||
|
_loadGA(true);
|
||||||
|
banner.style.display = 'none';
|
||||||
|
});
|
||||||
|
document.getElementById('ga-consent-decline')?.addEventListener('click', function() {
|
||||||
|
localStorage.setItem('gaConsent', 'no');
|
||||||
|
banner.style.display = 'none';
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- GA Consent-Banner (nur für Option A aktiv, im Mode B versteckt) -->
|
||||||
|
<div id="ga-consent-banner"
|
||||||
|
style="display:none;position:fixed;bottom:0;left:0;right:0;z-index:9000;
|
||||||
|
background:var(--c-surface,#fff);border-top:1px solid var(--c-border,#e5e7eb);
|
||||||
|
padding:var(--space-3) var(--space-4);flex-wrap:wrap;
|
||||||
|
align-items:center;gap:var(--space-3)">
|
||||||
|
<span style="flex:1;min-width:200px;font-size:var(--text-xs);color:var(--c-text-secondary)">
|
||||||
|
Wir nutzen Google Analytics (anonymisiert) um die App zu verbessern.
|
||||||
|
<span data-page="datenschutz" style="color:var(--c-primary);cursor:pointer;text-decoration:underline">Mehr erfahren</span>
|
||||||
|
</span>
|
||||||
|
<div style="display:flex;gap:var(--space-2)">
|
||||||
|
<button id="ga-consent-decline"
|
||||||
|
style="padding:var(--space-2) var(--space-3);border:1px solid var(--c-border,#e5e7eb);
|
||||||
|
border-radius:var(--radius-md);background:transparent;cursor:pointer;
|
||||||
|
font-size:var(--text-xs);color:var(--c-text-secondary)">Ablehnen</button>
|
||||||
|
<button id="ga-consent-accept"
|
||||||
|
style="padding:var(--space-2) var(--space-3);border:none;
|
||||||
|
border-radius:var(--radius-md);background:var(--c-primary,#C4843A);
|
||||||
|
color:#fff;cursor:pointer;font-size:var(--text-xs)">Akzeptieren</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Service Worker -->
|
<!-- Service Worker -->
|
||||||
<script>
|
<script>
|
||||||
if ('serviceWorker' in navigator) {
|
if ('serviceWorker' in navigator) {
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
Router, State-Management, Navigation, Initialisierung.
|
Router, State-Management, Navigation, Initialisierung.
|
||||||
============================================================ */
|
============================================================ */
|
||||||
|
|
||||||
const APP_VER = '81'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
|
const APP_VER = '86'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
|
||||||
|
|
||||||
const App = (() => {
|
const App = (() => {
|
||||||
|
|
||||||
|
|
@ -54,6 +54,8 @@ const App = (() => {
|
||||||
friends: { title: 'Freunde', module: null, requiresAuth: true },
|
friends: { title: 'Freunde', module: null, requiresAuth: true },
|
||||||
chat: { title: 'Nachrichten', module: null, requiresAuth: true },
|
chat: { title: 'Nachrichten', module: null, requiresAuth: true },
|
||||||
admin: { title: 'Admin', module: null, requiresAuth: true },
|
admin: { title: 'Admin', module: null, requiresAuth: true },
|
||||||
|
impressum: { title: 'Impressum', module: null },
|
||||||
|
datenschutz: { title: 'Datenschutz', module: null },
|
||||||
};
|
};
|
||||||
|
|
||||||
// ----------------------------------------------------------
|
// ----------------------------------------------------------
|
||||||
|
|
@ -179,7 +181,7 @@ const App = (() => {
|
||||||
background:var(--c-primary-subtle);
|
background:var(--c-primary-subtle);
|
||||||
display:flex;align-items:center;justify-content:center">
|
display:flex;align-items:center;justify-content:center">
|
||||||
<svg style="width:36px;height:36px;color:var(--c-primary)" aria-hidden="true">
|
<svg style="width:36px;height:36px;color:var(--c-primary)" aria-hidden="true">
|
||||||
<use href="/icons/phosphor.svg#${_esc(gate.icon)}"></use>
|
<use href="/icons/phosphor.svg#${UI.escape(gate.icon)}"></use>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -187,11 +189,11 @@ const App = (() => {
|
||||||
<div style="max-width:300px">
|
<div style="max-width:300px">
|
||||||
<h2 style="font-size:var(--text-xl);font-weight:var(--weight-bold);
|
<h2 style="font-size:var(--text-xl);font-weight:var(--weight-bold);
|
||||||
color:var(--c-text);margin:0 0 var(--space-2)">
|
color:var(--c-text);margin:0 0 var(--space-2)">
|
||||||
${_esc(title)}
|
${UI.escape(title)}
|
||||||
</h2>
|
</h2>
|
||||||
<p style="font-size:var(--text-sm);color:var(--c-text-secondary);
|
<p style="font-size:var(--text-sm);color:var(--c-text-secondary);
|
||||||
line-height:1.6;margin:0">
|
line-height:1.6;margin:0">
|
||||||
${_esc(gate.text)}
|
${UI.escape(gate.text)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -472,7 +474,7 @@ const App = (() => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const avHtml = d => d.foto_url
|
const avHtml = d => d.foto_url
|
||||||
? `<img src="${_esc(d.foto_url)}" alt="${_esc(d.name)}">`
|
? `<img src="${UI.escape(d.foto_url)}" alt="${UI.escape(d.name)}">`
|
||||||
: `<span>🐕</span>`;
|
: `<span>🐕</span>`;
|
||||||
|
|
||||||
// Inaktive Hunde rechts
|
// Inaktive Hunde rechts
|
||||||
|
|
@ -480,7 +482,7 @@ const App = (() => {
|
||||||
if (others.length === 1) {
|
if (others.length === 1) {
|
||||||
othersHtml = `
|
othersHtml = `
|
||||||
<div class="dog-sw-others">
|
<div class="dog-sw-others">
|
||||||
<div class="dog-sw-other" data-dog-id="${others[0].id}" title="${_esc(others[0].name)}">
|
<div class="dog-sw-other" data-dog-id="${others[0].id}" title="${UI.escape(others[0].name)}">
|
||||||
${avHtml(others[0])}
|
${avHtml(others[0])}
|
||||||
</div>
|
</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
|
|
@ -491,7 +493,7 @@ const App = (() => {
|
||||||
<div class="dog-sw-others">
|
<div class="dog-sw-others">
|
||||||
<div class="dog-sw-stack" id="dog-sw-stack-${ctxId}">
|
<div class="dog-sw-stack" id="dog-sw-stack-${ctxId}">
|
||||||
${visible.map((d, i) => `
|
${visible.map((d, i) => `
|
||||||
<div class="dog-sw-other dog-sw-other--${i}" data-dog-id="${d.id}" title="${_esc(d.name)}">
|
<div class="dog-sw-other dog-sw-other--${i}" data-dog-id="${d.id}" title="${UI.escape(d.name)}">
|
||||||
${avHtml(d)}
|
${avHtml(d)}
|
||||||
</div>`).join('')}
|
</div>`).join('')}
|
||||||
${extraCount > 0 ? `<div class="dog-sw-more">+${extraCount}</div>` : ''}
|
${extraCount > 0 ? `<div class="dog-sw-more">+${extraCount}</div>` : ''}
|
||||||
|
|
@ -500,7 +502,7 @@ const App = (() => {
|
||||||
${others.map(d => `
|
${others.map(d => `
|
||||||
<div class="dog-qp-item" data-dog-id="${d.id}">
|
<div class="dog-qp-item" data-dog-id="${d.id}">
|
||||||
<div class="dog-qp-av">${avHtml(d)}</div>
|
<div class="dog-qp-av">${avHtml(d)}</div>
|
||||||
<span>${_esc(d.name)}</span>
|
<span>${UI.escape(d.name)}</span>
|
||||||
</div>`).join('')}
|
</div>`).join('')}
|
||||||
</div>
|
</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
|
|
@ -508,7 +510,7 @@ const App = (() => {
|
||||||
|
|
||||||
const titleClass = ctxId === 'sb' ? 'sidebar-logo-text' : 'header-title';
|
const titleClass = ctxId === 'sb' ? 'sidebar-logo-text' : 'header-title';
|
||||||
el.innerHTML = `
|
el.innerHTML = `
|
||||||
<div class="dog-sw-active" id="dog-sw-active-${ctxId}" title="${_esc(dog.name)} bearbeiten">
|
<div class="dog-sw-active" id="dog-sw-active-${ctxId}" title="${UI.escape(dog.name)} bearbeiten">
|
||||||
${avHtml(dog)}
|
${avHtml(dog)}
|
||||||
</div>
|
</div>
|
||||||
<span class="${titleClass} dog-sw-title">Ban Yaro</span>
|
<span class="${titleClass} dog-sw-title">Ban Yaro</span>
|
||||||
|
|
@ -562,12 +564,6 @@ const App = (() => {
|
||||||
_notifyDogChange();
|
_notifyDogChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
function _esc(str) {
|
|
||||||
if (!str) return '';
|
|
||||||
return String(str).replace(/&/g, '&').replace(/</g, '<')
|
|
||||||
.replace(/>/g, '>').replace(/"/g, '"');
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------
|
// ----------------------------------------------------------
|
||||||
// INITIALISIERUNG
|
// INITIALISIERUNG
|
||||||
// ----------------------------------------------------------
|
// ----------------------------------------------------------
|
||||||
|
|
@ -590,12 +586,24 @@ const App = (() => {
|
||||||
navigate(startPage, false, hashParams);
|
navigate(startPage, false, hashParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------
|
||||||
|
// AUTH-GATE HELPER — einheitlicher "Bitte anmelden"-Block
|
||||||
|
// ----------------------------------------------------------
|
||||||
|
function requireAuth(container, { icon = 'user', text = 'Melde dich an, um diese Funktion zu nutzen.' } = {}) {
|
||||||
|
container.innerHTML = UI.emptyState({
|
||||||
|
icon: UI.icon(icon),
|
||||||
|
title: 'Anmelden erforderlich',
|
||||||
|
text,
|
||||||
|
action: `<button class="btn btn-primary" onclick="App.navigate('settings')">Anmelden</button>`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------
|
// ----------------------------------------------------------
|
||||||
// ÖFFENTLICHE API
|
// ÖFFENTLICHE API
|
||||||
// (andere Module können App.state, App.navigate etc. nutzen)
|
// (andere Module können App.state, App.navigate etc. nutzen)
|
||||||
// ----------------------------------------------------------
|
// ----------------------------------------------------------
|
||||||
return { init, navigate, state, setActiveDog, renderDogSwitcher: _renderDogSwitcher,
|
return { init, navigate, state, setActiveDog, renderDogSwitcher: _renderDogSwitcher,
|
||||||
getInstallPrompt: () => _installPrompt };
|
getInstallPrompt: () => _installPrompt, requireAuth };
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
|
|
||||||
141
backend/static/js/pages/datenschutz.js
Normal file
141
backend/static/js/pages/datenschutz.js
Normal file
|
|
@ -0,0 +1,141 @@
|
||||||
|
/* ============================================================
|
||||||
|
BAN YARO — Datenschutzerklärung
|
||||||
|
============================================================ */
|
||||||
|
|
||||||
|
window.Page_datenschutz = (() => {
|
||||||
|
|
||||||
|
function init(container) {
|
||||||
|
const optOut = localStorage.getItem('gaOptOut') === 'yes';
|
||||||
|
const gaSection = `
|
||||||
|
<section style="margin-bottom:var(--space-6)">
|
||||||
|
<h2 style="font-size:var(--text-base);font-weight:var(--weight-semibold);
|
||||||
|
color:var(--c-text);margin:0 0 var(--space-2)">Google Analytics</h2>
|
||||||
|
<p style="font-size:var(--text-sm);color:var(--c-text-secondary);line-height:1.7;margin:0 0 var(--space-3)">
|
||||||
|
Wir nutzen Google Analytics 4 (Google LLC, USA) zur anonymisierten Analyse der App-Nutzung.
|
||||||
|
Deine IP-Adresse wird gekürzt, es werden keine Cookies gesetzt und keine
|
||||||
|
personenbezogenen Daten gespeichert. Die Verarbeitung erfolgt auf Basis von
|
||||||
|
Art. 6 Abs. 1 lit. f DSGVO (berechtigtes Interesse an anonymer Nutzungsanalyse).
|
||||||
|
Du kannst der Erhebung jederzeit widersprechen.
|
||||||
|
</p>
|
||||||
|
${optOut ? `
|
||||||
|
<p style="font-size:var(--text-sm);color:var(--c-success,#16a34a);margin:0 0 var(--space-3)">
|
||||||
|
Analytics ist für dich <strong>deaktiviert</strong>.
|
||||||
|
</p>
|
||||||
|
<button id="ga-optin-btn"
|
||||||
|
style="padding:var(--space-2) var(--space-4);border:1px solid var(--c-border,#e5e7eb);
|
||||||
|
border-radius:var(--radius-md);background:transparent;cursor:pointer;
|
||||||
|
font-size:var(--text-sm);color:var(--c-text-secondary)">
|
||||||
|
Analytics wieder aktivieren
|
||||||
|
</button>
|
||||||
|
` : `
|
||||||
|
<button id="ga-optout-btn"
|
||||||
|
style="padding:var(--space-2) var(--space-4);border:1px solid var(--c-border,#e5e7eb);
|
||||||
|
border-radius:var(--radius-md);background:transparent;cursor:pointer;
|
||||||
|
font-size:var(--text-sm);color:var(--c-text-secondary)">
|
||||||
|
Analytics deaktivieren (Opt-out)
|
||||||
|
</button>
|
||||||
|
`}
|
||||||
|
</section>
|
||||||
|
`;
|
||||||
|
|
||||||
|
container.innerHTML = `
|
||||||
|
<div style="max-width:640px;margin:0 auto;padding:var(--space-6) var(--space-4)">
|
||||||
|
|
||||||
|
<h1 style="font-size:var(--text-2xl);font-weight:var(--weight-bold);
|
||||||
|
color:var(--c-text);margin:0 0 var(--space-6)">Datenschutzerklärung</h1>
|
||||||
|
|
||||||
|
<section style="margin-bottom:var(--space-6)">
|
||||||
|
<h2 style="font-size:var(--text-base);font-weight:var(--weight-semibold);
|
||||||
|
color:var(--c-text);margin:0 0 var(--space-2)">Verantwortlicher</h2>
|
||||||
|
<p style="font-size:var(--text-sm);color:var(--c-text-secondary);line-height:1.7;margin:0">
|
||||||
|
René Degelmann, Ringstr. 26, 85560 Ebersberg<br>
|
||||||
|
E-Mail: <a href="mailto:mail@motocamp.de"
|
||||||
|
style="color:var(--c-primary)">mail@motocamp.de</a>
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section style="margin-bottom:var(--space-6)">
|
||||||
|
<h2 style="font-size:var(--text-base);font-weight:var(--weight-semibold);
|
||||||
|
color:var(--c-text);margin:0 0 var(--space-2)">Welche Daten wir verarbeiten</h2>
|
||||||
|
<p style="font-size:var(--text-sm);color:var(--c-text-secondary);line-height:1.7;margin:0">
|
||||||
|
Bei der Registrierung und Nutzung von Ban Yaro werden folgende Daten verarbeitet:
|
||||||
|
</p>
|
||||||
|
<ul style="font-size:var(--text-sm);color:var(--c-text-secondary);
|
||||||
|
line-height:1.7;margin:var(--space-2) 0 0;padding-left:var(--space-5)">
|
||||||
|
<li><strong>Accountdaten:</strong> Benutzername, E-Mail-Adresse, Passwort (gehashed)</li>
|
||||||
|
<li><strong>Hundeprofil:</strong> Name, Rasse, Alter, Foto (freiwillig)</li>
|
||||||
|
<li><strong>Standortdaten:</strong> Nur wenn du Gassi-Treffen, Giftköder-Meldungen oder die
|
||||||
|
Karte nutzt (jeweils nur nach expliziter Browser-Freigabe)</li>
|
||||||
|
<li><strong>Inhalte:</strong> Tagebucheinträge, Fotos, Forenbeiträge, Chatnachrichten</li>
|
||||||
|
<li><strong>Technische Daten:</strong> IP-Adresse (serverseitig für Sicherheit/Rate-Limiting),
|
||||||
|
Browser-Typ</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section style="margin-bottom:var(--space-6)">
|
||||||
|
<h2 style="font-size:var(--text-base);font-weight:var(--weight-semibold);
|
||||||
|
color:var(--c-text);margin:0 0 var(--space-2)">Rechtsgrundlage</h2>
|
||||||
|
<p style="font-size:var(--text-sm);color:var(--c-text-secondary);line-height:1.7;margin:0">
|
||||||
|
Die Verarbeitung erfolgt auf Basis von Art. 6 Abs. 1 lit. b DSGVO (Vertragserfüllung)
|
||||||
|
für alle zur Bereitstellung des Dienstes notwendigen Daten, sowie Art. 6 Abs. 1 lit. a
|
||||||
|
DSGVO (Einwilligung) für optionale Funktionen wie Standortfreigabe und Analytics.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section style="margin-bottom:var(--space-6)">
|
||||||
|
<h2 style="font-size:var(--text-base);font-weight:var(--weight-semibold);
|
||||||
|
color:var(--c-text);margin:0 0 var(--space-2)">Datenweitergabe</h2>
|
||||||
|
<p style="font-size:var(--text-sm);color:var(--c-text-secondary);line-height:1.7;margin:0">
|
||||||
|
Deine Daten werden nicht an Dritte verkauft oder zu Werbezwecken weitergegeben.
|
||||||
|
Öffentliche Inhalte (Forum, Wiki, Giftköder-Karte) sind für alle Nutzer sichtbar.
|
||||||
|
Profile sind standardmäßig nur für registrierte Nutzer sichtbar.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
${gaSection}
|
||||||
|
|
||||||
|
<section style="margin-bottom:var(--space-6)">
|
||||||
|
<h2 style="font-size:var(--text-base);font-weight:var(--weight-semibold);
|
||||||
|
color:var(--c-text);margin:0 0 var(--space-2)">Deine Rechte (DSGVO)</h2>
|
||||||
|
<p style="font-size:var(--text-sm);color:var(--c-text-secondary);line-height:1.7;margin:0">
|
||||||
|
Du hast das Recht auf <strong>Auskunft</strong> (Art. 15), <strong>Berichtigung</strong>
|
||||||
|
(Art. 16), <strong>Löschung</strong> (Art. 17), <strong>Einschränkung der Verarbeitung</strong>
|
||||||
|
(Art. 18) sowie <strong>Datenportabilität</strong> (Art. 20). Zur Ausübung deiner Rechte
|
||||||
|
wende dich per E-Mail an
|
||||||
|
<a href="mailto:mail@motocamp.de" style="color:var(--c-primary)">mail@motocamp.de</a>.
|
||||||
|
Du hast außerdem das Recht, bei der zuständigen Aufsichtsbehörde Beschwerde einzulegen.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section style="margin-bottom:var(--space-6)">
|
||||||
|
<h2 style="font-size:var(--text-base);font-weight:var(--weight-semibold);
|
||||||
|
color:var(--c-text);margin:0 0 var(--space-2)">Speicherdauer</h2>
|
||||||
|
<p style="font-size:var(--text-sm);color:var(--c-text-secondary);line-height:1.7;margin:0">
|
||||||
|
Deine Daten werden gelöscht, sobald du deinen Account löschst. Server-Logs
|
||||||
|
werden nach 30 Tagen automatisch gelöscht.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<p style="font-size:var(--text-xs);color:var(--c-text-muted);margin:0">
|
||||||
|
Stand: April 2026
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
container.querySelector('#ga-optout-btn')?.addEventListener('click', () => {
|
||||||
|
localStorage.setItem('gaOptOut', 'yes');
|
||||||
|
UI.toast.success('Analytics deaktiviert. Wirksam nach nächstem App-Neustart.');
|
||||||
|
init(container);
|
||||||
|
});
|
||||||
|
container.querySelector('#ga-optin-btn')?.addEventListener('click', () => {
|
||||||
|
localStorage.removeItem('gaOptOut');
|
||||||
|
UI.toast.success('Analytics wieder aktiviert. Wirksam nach nächstem App-Neustart.');
|
||||||
|
init(container);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function refresh() {}
|
||||||
|
|
||||||
|
return { init, refresh };
|
||||||
|
})();
|
||||||
66
backend/static/js/pages/impressum.js
Normal file
66
backend/static/js/pages/impressum.js
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
/* ============================================================
|
||||||
|
BAN YARO — Impressum
|
||||||
|
============================================================ */
|
||||||
|
|
||||||
|
window.Page_impressum = (() => {
|
||||||
|
|
||||||
|
function init(container) {
|
||||||
|
container.innerHTML = `
|
||||||
|
<div style="max-width:640px;margin:0 auto;padding:var(--space-6) var(--space-4)">
|
||||||
|
|
||||||
|
<h1 style="font-size:var(--text-2xl);font-weight:var(--weight-bold);
|
||||||
|
color:var(--c-text);margin:0 0 var(--space-6)">Impressum</h1>
|
||||||
|
|
||||||
|
<section style="margin-bottom:var(--space-6)">
|
||||||
|
<h2 style="font-size:var(--text-base);font-weight:var(--weight-semibold);
|
||||||
|
color:var(--c-text);margin:0 0 var(--space-2)">Angaben gemäß § 5 TMG</h2>
|
||||||
|
<p style="font-size:var(--text-sm);color:var(--c-text-secondary);line-height:1.7;margin:0">
|
||||||
|
René Degelmann<br>
|
||||||
|
Ringstr. 26<br>
|
||||||
|
85560 Ebersberg
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section style="margin-bottom:var(--space-6)">
|
||||||
|
<h2 style="font-size:var(--text-base);font-weight:var(--weight-semibold);
|
||||||
|
color:var(--c-text);margin:0 0 var(--space-2)">Kontakt</h2>
|
||||||
|
<p style="font-size:var(--text-sm);color:var(--c-text-secondary);line-height:1.7;margin:0">
|
||||||
|
E-Mail: <a href="mailto:mail@motocamp.de"
|
||||||
|
style="color:var(--c-primary)">mail@motocamp.de</a>
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section style="margin-bottom:var(--space-6)">
|
||||||
|
<h2 style="font-size:var(--text-base);font-weight:var(--weight-semibold);
|
||||||
|
color:var(--c-text);margin:0 0 var(--space-2)">Verantwortlich für den Inhalt
|
||||||
|
gemäß § 18 Abs. 2 MStV</h2>
|
||||||
|
<p style="font-size:var(--text-sm);color:var(--c-text-secondary);line-height:1.7;margin:0">
|
||||||
|
René Degelmann<br>
|
||||||
|
(Anschrift wie oben)
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section style="margin-bottom:var(--space-6)">
|
||||||
|
<h2 style="font-size:var(--text-base);font-weight:var(--weight-semibold);
|
||||||
|
color:var(--c-text);margin:0 0 var(--space-2)">Haftungshinweis</h2>
|
||||||
|
<p style="font-size:var(--text-sm);color:var(--c-text-secondary);line-height:1.7;margin:0">
|
||||||
|
Die Inhalte dieser App wurden mit größtmöglicher Sorgfalt erstellt. Für die Richtigkeit,
|
||||||
|
Vollständigkeit und Aktualität der Inhalte übernehmen wir keine Gewähr. Als
|
||||||
|
Diensteanbieter sind wir gemäß § 7 Abs. 1 TMG für eigene Inhalte verantwortlich.
|
||||||
|
Für nutzergenerierte Inhalte (z. B. Forenbeiträge, Giftköder-Meldungen) übernehmen wir
|
||||||
|
keine Haftung; diese liegen in der Verantwortung der jeweiligen Nutzer.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<p style="font-size:var(--text-xs);color:var(--c-text-muted);margin:0">
|
||||||
|
Stand: April 2026
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function refresh() {}
|
||||||
|
|
||||||
|
return { init, refresh };
|
||||||
|
})();
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
Offline-Cache + Push Notifications + Tile-Cache
|
Offline-Cache + Push Notifications + Tile-Cache
|
||||||
============================================================ */
|
============================================================ */
|
||||||
|
|
||||||
const CACHE_VERSION = 'by-v105';
|
const CACHE_VERSION = 'by-v110';
|
||||||
const CACHE_STATIC = `${CACHE_VERSION}-static`;
|
const CACHE_STATIC = `${CACHE_VERSION}-static`;
|
||||||
const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten
|
const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue