Security: CSP gehärtet — unsafe-inline + unsafe-eval raus, SW by-v1100

Inline-Scripts extrahiert:
- boot-early.js: Theme + theme-color (synchron im <head>, VOR CSS)
- boot.js: Offline-Banner + Service-Worker-Registration + Update-Flow
- landing-init.js: Dark-mode + Scroll-Animationen + Live-Stats +
  Stay-In-App-Handler + Details-Toggle

Inline onclick-Handler in landing.html:
- 5× sessionStorage.setItem('by_stay_in_app','1') → data-stay-in-app
- 1× Details-Toggle → data-toggle-target + data-toggle-text-open
- JS-Handler in landing-init.js binden die data-Attribute

CSP-Header (main.py):
- script-src: 'unsafe-inline' und 'unsafe-eval' entfernt
- style-src 'unsafe-inline' bleibt (Inline-Styles bleiben für jetzt,
  zu viele Fundstellen)
- Umami bleibt whitelisted

SW STATIC_ASSETS erweitert um boot-early.js + boot.js.
make bump aktualisiert jetzt auch landing.html ?v= Anker.
Tests grün (19/19).
This commit is contained in:
rene 2026-05-27 06:23:47 +02:00
parent 15d319fbd5
commit 65cfa25e59
10 changed files with 267 additions and 226 deletions

View file

@ -4,13 +4,7 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="color-scheme" content="light dark">
<script>
(function(){
var mq = window.matchMedia('(prefers-color-scheme: dark)');
if (mq.matches) document.documentElement.classList.add('dark');
mq.addEventListener('change', function(e){ document.documentElement.classList.toggle('dark', e.matches); });
})();
</script>
<script src="/js/landing-init.js?v=1100"></script>
<title>Ban Yaro — Die Hunde-App für Deutschland, Österreich & Schweiz</title>
<meta name="description" content="Ban Yaro: Die kostenlose All-in-One Hunde-App für DACH. Tagebuch, Giftköder-Alarm, Training mit KI, Forum, Wurfbörse, Stammbaum, Inzucht-Check — DSGVO-konform, offline-fähig, ohne App Store.">
<meta name="keywords" content="Hunde App, Hunde Community, Wurfbörse, Züchter, Welpen kaufen, Stammbaum Hund, Inzuchtkoeffizient, Hundezucht, Impfpass Hund, Giftköder Alarm, Gassi Community, Hundetraining App, Hunde Forum, Hunde KI, Hundefilm Datenbank, Welpen Marktplatz">
@ -731,7 +725,7 @@
<h1>Weil jeder Moment<br>mit ihm zählt.</h1>
<p>Ban Yaro begleitet euch durch jeden gemeinsamen Tag — Tagebuch, Training und Gesundheit für Hundebesitzer, Stammbaum und Wurfverwaltung für Züchter. Eine App. Mit ganzem Herzen.</p>
<div style="display:flex;gap:1rem;justify-content:center;flex-wrap:wrap;margin-top:2rem">
<a href="/" class="cta-btn" onclick="sessionStorage.setItem('by_stay_in_app','1')">Kostenlos starten</a>
<a href="/" class="cta-btn" data-stay-in-app>Kostenlos starten</a>
<a href="/zuechter" class="cta-btn" style="background:transparent;color:white;border:2px solid rgba(255,255,255,.6)">Ich bin Züchter</a>
</div>
<div class="header-badges" style="margin-top:1.5rem">
@ -849,7 +843,7 @@
</p>
<a href="/" class="cta-btn"
style="align-self:flex-start;background:white;color:#a86e2e;margin:0;box-shadow:0 4px 20px rgba(0,0,0,.2)"
onclick="sessionStorage.setItem('by_stay_in_app','1')">Kostenlos starten</a>
data-stay-in-app>Kostenlos starten</a>
</div>
</div>
@ -951,7 +945,9 @@
</div>
</div>
<span class="section-cta-link" onclick="var c=document.getElementById('hundebesitzer-details');c.classList.toggle('open');this.textContent=c.classList.contains('open')?'▴ Weniger anzeigen':'+ Alle Features für Hundebesitzer ansehen ▾'">+ Alle Features für Hundebesitzer ansehen ▾</span>
<span class="section-cta-link" data-toggle-target="hundebesitzer-details"
data-toggle-text-open="▴ Weniger anzeigen"
style="cursor:pointer">+ Alle Features für Hundebesitzer ansehen ▾</span>
<div id="hundebesitzer-details" class="collapsible-content">
<div class="feature-group">
@ -1402,7 +1398,7 @@
<li>Persönlichkeitstest, Adoption, Ausgaben</li>
</ul>
<a href="/" class="section-cta-btn" style="display:block;text-align:center;margin-top:1.5rem"
onclick="sessionStorage.setItem('by_stay_in_app','1')">Kostenlos starten</a>
data-stay-in-app>Kostenlos starten</a>
</div>
<!-- Pro -->
@ -1419,7 +1415,7 @@
<li>Alles aus Kostenlos inklusive</li>
</ul>
<a href="/" class="section-cta-btn" style="display:block;text-align:center;margin-top:1.5rem"
onclick="sessionStorage.setItem('by_stay_in_app','1')">Pro starten</a>
data-stay-in-app>Pro starten</a>
</div>
<!-- Züchter -->
@ -1606,70 +1602,6 @@
</div>
</footer>
<script>
// App-Links: kein Redirect-Loop
document.querySelectorAll('a[href="/"], a[href^="/#"]').forEach(function(a) {
a.addEventListener('click', function() {
sessionStorage.setItem('by_stay_in_app', '1');
});
});
// Scroll-Animationen
var _observer = new IntersectionObserver(function(entries) {
entries.forEach(function(e) {
if (e.isIntersecting) {
e.target.classList.add('visible');
_observer.unobserve(e.target);
}
});
}, { threshold: 0.12 });
document.querySelectorAll('.outcome-card, .feature-card, .usp-item, .pricing-card').forEach(function(el) {
el.classList.add('fade-up');
_observer.observe(el);
});
document.querySelectorAll('.fade-up').forEach(function(el) {
_observer.observe(el);
});
// Live-Zahlen von /api/stats/public
var fmt = new Intl.NumberFormat('de-DE');
fetch('/api/stats/public')
.then(function(r) { return r.json(); })
.then(function(d) {
function set(id, val) {
var el = document.getElementById(id);
if (el) el.textContent = fmt.format(val);
}
// Stats-Band (weiter unten)
set('big-users', d.users);
set('big-dogs', d.dogs);
set('big-km', d.km);
set('big-posts', d.forum_posts);
set('big-diary', d.diary_entries);
set('big-kotbeutel', d.kotbeutel);
// Hero-Streifen: aufsteigend nach Wert sortiert, dynamisch aufgebaut
var heroStats = document.getElementById('hero-stats');
if (!heroStats || !d.users) return;
var items = [
{ val: d.users, label: 'Hundemenschen' },
{ val: d.dogs, label: 'Hunde' },
{ val: d.km, label: 'km Gassi-Wege' },
{ val: d.diary_entries, label: 'Tagebuch-Einträge' },
{ val: d.kotbeutel, label: 'Mülleimer für Kotbeutel'},
];
items.sort(function(a, b) { return a.val - b.val; });
heroStats.innerHTML = items.map(function(item, i) {
return (i > 0 ? '<span class="sep">·</span>' : '') +
'<strong>' + fmt.format(item.val) + '</strong> ' + item.label;
}).join('');
heroStats.style.display = 'flex';
})
.catch(function() {});
</script>
</body>
</html>