banyaro/backend/static/index.html
rene 5141ba9969 Session 2026-04-20: Medien-Konvertierung, Umami Analytics, Username/Privacy
- HEIC→JPEG, MOV/AVI→MP4 Konvertierung bei allen Upload-Endpoints (media_utils.py)
- ffmpeg im Docker-Image, Video-Thumbnails (extract_video_thumb, poster-Attribut)
- Google Analytics entfernt, Umami self-hosted eingebunden (index.html, datenschutz.js)
- Admin-Panel Analytics-Tab: Stat-Cards, Sparkline 7 Tage, Top-Seiten (Umami-API-Proxy)
- Admin-Panel Tab-Icons korrigiert (aus vorhandenem Phosphor-Sprite)
- users.real_name Spalte: Username öffentlich, echter Name privat und optional
- Registrierung: Label "Benutzername", Leerzeichen verboten, Profanity-Blockliste
- Datenschutzerklärung: GA-Abschnitt durch Umami-Text ersetzt
2026-04-20 18:36:58 +02:00

383 lines
15 KiB
HTML

<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
<meta name="theme-color" content="#C4843A">
<meta name="description" content="Ban Yaro — Die Hunde-Plattform. Alles rund um deinen Hund.">
<!-- Favicons -->
<link rel="icon" type="image/x-icon" href="/favicon.ico">
<link rel="icon" type="image/png" sizes="32x32" href="/icons/favicon-32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/icons/favicon-16.png">
<!-- PWA -->
<link rel="manifest" href="/manifest.json">
<link rel="apple-touch-icon" sizes="180x180" href="/icons/icon-180.png">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="apple-mobile-web-app-title" content="Ban Yaro">
<title>Ban Yaro</title>
<!-- Theme vor CSS setzen — verhindert Flash of unstyled content -->
<script>
(function() {
var t = localStorage.getItem('by_theme');
if (t === 'dark') document.documentElement.setAttribute('data-theme', 'dark');
if (t === 'light') document.documentElement.setAttribute('data-theme', 'light');
// 'system' (oder kein Wert) → kein data-theme → @media prefers-color-scheme greift
})();
</script>
<!-- CSS: Reihenfolge ist wichtig — ?v= zwingt Browser zur Neuladung -->
<link rel="stylesheet" href="/css/design-system.css">
<link rel="stylesheet" href="/css/layout.css?v=237">
<link rel="stylesheet" href="/css/components.css?v=232">
</head>
<body>
<!-- Offline-Banner -->
<div id="offline-banner">Kein Internet — du bist offline</div>
<!-- Backdrop + Sidebar direkt im body (kein Ancestor-Stacking-Context) -->
<div id="sidebar-backdrop" class="sidebar-backdrop"></div>
<nav id="sidebar" role="navigation" aria-label="Hauptnavigation">
<div class="sidebar-logo" id="sidebar-dog-switcher">
<img class="sidebar-logo-img" src="/icons/icon-180.png" alt="Ban Yaro">
<span class="sidebar-logo-text" style="cursor:pointer" title="Über Ban Yaro">Ban Yaro</span>
</div>
<div class="sidebar-add">
<button class="btn btn-primary btn-full" id="sidebar-add">+ Neu erstellen</button>
</div>
<div class="sidebar-nav">
<span class="sidebar-section-label">Mein Hund</span>
<div class="sidebar-item active" data-page="diary">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#book-open"></use></svg> Tagebuch
</div>
<div class="sidebar-item" data-page="health">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#first-aid"></use></svg> Gesundheit
</div>
<span class="sidebar-section-label">Entdecken</span>
<div class="sidebar-item" data-page="map">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#map-trifold"></use></svg> Karte
</div>
<div class="sidebar-item" data-page="routes">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#path"></use></svg> Routen
</div>
<div class="sidebar-item" data-page="events">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#calendar-dots"></use></svg> Events
</div>
<span class="sidebar-section-label">Soziales</span>
<div class="sidebar-item" data-page="friends">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#users"></use></svg> Freunde
<span class="sidebar-item-badge" id="friends-badge" style="display:none">0</span>
</div>
<div class="sidebar-item" data-page="chat">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#chat-circle-dots"></use></svg> Nachrichten
<span class="sidebar-item-badge" id="chat-badge" style="display:none">0</span>
</div>
<div class="sidebar-item" data-page="notifications">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#bell"></use></svg> Aktuelles
<span class="sidebar-item-badge" id="notif-badge" style="display:none">0</span>
</div>
<span class="sidebar-section-label">Community</span>
<div class="sidebar-item" data-page="poison">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#warning-octagon"></use></svg> Giftköder
<span class="sidebar-item-badge" id="poison-badge" style="display:none">0</span>
</div>
<div class="sidebar-item" data-page="walks">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#paw-print"></use></svg> Gassi-Treffen
</div>
<div class="sidebar-item" data-page="sitting">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#house-line"></use></svg> Sitting
</div>
<div class="sidebar-item" data-page="forum">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#push-pin"></use></svg> Forum
</div>
<div class="sidebar-item" data-page="lost">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#magnifying-glass"></use></svg> Verlorener Hund
<span class="sidebar-item-badge" id="lost-badge" style="display:none">0</span>
</div>
<span class="sidebar-section-label">Training</span>
<div class="sidebar-item" data-page="uebungen">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#target"></use></svg> Übungen
</div>
<div class="sidebar-item" data-page="trainingsplaene">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#clipboard-text"></use></svg> Trainingspläne
</div>
<span class="sidebar-section-label">Wissen</span>
<div class="sidebar-item" data-page="wiki">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#books"></use></svg> Wiki
</div>
<div class="sidebar-item" data-page="knigge">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#handshake"></use></svg> Knigge
</div>
<div class="sidebar-item" data-page="movies">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#film-slate"></use></svg> Filme
</div>
<div class="sidebar-item" data-page="erste-hilfe">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#first-aid"></use></svg> Erste Hilfe
</div>
<div class="sidebar-item" data-page="admin" id="sidebar-admin"
style="display:none;color:var(--c-danger,#ef4444)">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#shield"></use></svg> Admin
</div>
<div class="sidebar-item sidebar-item--user" id="sidebar-user">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#user"></use></svg>
<span id="sidebar-username">Anmelden</span>
</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>
</nav>
<!-- ============================================================
APP SHELL
============================================================ -->
<div id="app">
<!-- MOBILE HEADER -->
<header id="app-header">
<button class="header-back hidden" id="header-back" aria-label="Zurück"><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#arrow-left"></use></svg></button>
<div id="header-dog-switcher" class="dog-switcher">
<span class="header-title" id="header-title">Ban Yaro</span>
</div>
<div id="header-actions"></div>
<button id="header-user-btn" aria-label="Profil"
style="width:36px;height:36px;border-radius:50%;border:2px solid var(--c-border);
background:var(--c-surface-2);cursor:pointer;flex-shrink:0;
display:flex;align-items:center;justify-content:center;overflow:hidden;
padding:0;position:relative">
<svg id="header-user-icon" class="ph-icon" aria-hidden="true"
style="width:18px;height:18px;color:var(--c-text-muted)">
<use href="/icons/phosphor.svg#user"></use>
</svg>
</button>
<button class="header-menu-btn" id="header-menu-btn" aria-label="Menü"><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#list"></use></svg></button>
</header>
<!-- HAUPT-INHALTSBEREICH -->
<main id="page-content" role="main">
<!-- Jede Seite ist ein <section class="page"> -->
<section class="page" id="page-welcome">
<div class="page-body page-container"></div>
</section>
<section class="page" id="page-onboarding">
<div class="page-body page-container"></div>
</section>
<section class="page active" id="page-diary">
<div class="page-body page-container">
<!-- wird von diary.js befüllt -->
</div>
</section>
<section class="page" id="page-health">
<div class="page-body page-container"></div>
</section>
<section class="page" id="page-dog-profile">
<div class="page-body page-container"></div>
</section>
<section class="page" id="page-map">
<div class="page-body page-container-wide"></div>
</section>
<section class="page" id="page-routes">
<div class="page-body page-container"></div>
</section>
<section class="page" id="page-events">
<div class="page-body page-container"></div>
</section>
<section class="page" id="page-poison">
<div class="page-body page-container"></div>
</section>
<section class="page" id="page-walks">
<div class="page-body page-container"></div>
</section>
<section class="page" id="page-sitting">
<div class="page-body page-container"></div>
</section>
<section class="page" id="page-forum">
<div class="page-body page-container"></div>
</section>
<section class="page" id="page-wiki">
<div class="page-body page-container"></div>
</section>
<section class="page" id="page-knigge">
<div class="page-body page-container"></div>
</section>
<section class="page" id="page-movies">
<div class="page-body page-container"></div>
</section>
<section class="page" id="page-uebungen">
<div class="page-body page-container"></div>
</section>
<section class="page" id="page-trainingsplaene">
<div class="page-body page-container"></div>
</section>
<section class="page" id="page-erste-hilfe">
<div class="page-body page-container"></div>
</section>
<section class="page" id="page-lost">
<div class="page-body page-container"></div>
</section>
<section class="page" id="page-settings">
<div class="page-body page-container"></div>
</section>
<section class="page" id="page-admin">
<div class="page-body page-container"></div>
</section>
<section class="page" id="page-friends">
<div class="page-body page-container"></div>
</section>
<section class="page" id="page-chat">
<div class="page-body page-container-chat"></div>
</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>
<section class="page" id="page-widget">
<div class="page-body page-container"></div>
</section>
<section class="page" id="page-notifications">
<div class="page-body page-container"></div>
</section>
</main>
<!-- MOBILE BOTTOM NAVIGATION -->
<nav id="bottom-nav" role="navigation" aria-label="Hauptnavigation">
<div class="nav-item active" data-page="diary">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#book-open"></use></svg>
<span class="nav-item-label">Tagebuch</span>
</div>
<div class="nav-item" data-page="routes">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#path"></use></svg>
<span class="nav-item-label">Routen</span>
</div>
<!-- Mittlerer + Button -->
<div class="nav-item nav-item-center" id="nav-add">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#plus"></use></svg>
</div>
<div class="nav-item" data-page="forum">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#push-pin"></use></svg>
<span class="nav-item-label">Forum</span>
</div>
<div class="nav-item" data-page="notifications">
<span style="position:relative;display:inline-flex">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#bell"></use></svg>
<span class="nav-badge hidden" id="notif-nav-badge">0</span>
</span>
<span class="nav-item-label">Aktuelles</span>
</div>
</nav>
</div><!-- #app -->
<!-- TOAST CONTAINER (außerhalb der App, immer sichtbar) -->
<div class="toast-container" id="toast-container" role="alert" aria-live="polite"></div>
<!-- MODAL CONTAINER -->
<div id="modal-container"></div>
<!-- JS: Reihenfolge ist wichtig — erst Basis, dann Features -->
<script src="/js/api.js?v=93"></script>
<script src="/js/ui.js?v=93"></script>
<script src="/js/app.js?v=93"></script>
<!-- Feature-Seiten werden lazy geladen -->
<!-- Umami Analytics (self-hosted, cookiefrei, DSGVO-konform) -->
<script defer src="https://umami.motocamp.de/script.js" data-website-id="d1b5fe13-0e6f-4461-a176-c5439cbbc27f"></script>
<!-- Offline-Banner Logik -->
<script>
(function() {
var _wasOffline = false;
var banner = document.getElementById('offline-banner');
function setOffline() {
_wasOffline = true;
if (banner) banner.style.display = 'block';
}
function setOnline() {
if (banner) banner.style.display = 'none';
if (_wasOffline) {
_wasOffline = false;
// UI.toast ist verfügbar sobald ui.js geladen ist
if (window.UI && UI.toast) {
UI.toast.success('Wieder online');
}
}
}
window.addEventListener('offline', setOffline);
window.addEventListener('online', setOnline);
// Initialzustand prüfen
if (!navigator.onLine) setOffline();
})();
</script>
<!-- Service Worker -->
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js', { updateViaCache: 'none' })
.catch(err => console.log('SW Registration failed:', err));
});
// Wenn ein neuer SW die Kontrolle übernimmt (nach Update),
// Seite neu laden — sonst hat app.js neue Seiten-JS aber altes api.js im Speicher.
navigator.serviceWorker.addEventListener('controllerchange', () => {
window.location.reload();
});
}
</script>
</body>
</html>