PRIORITY_PAGES erweitert auf 10 Seiten (war 8): zusätzlich health.js, notes.js, expenses.js. admin.js raus — 233 KB, offline irrelevant. Damit funktionieren offline ohne vorherigen Besuch: Tagebuch · Gesundheit · Karte · Gassi · Erste Hilfe · Notizblock Ausgaben · Routen · Giftköder · Vermisst. Offline-Indikator Step 2 prüft jetzt alle 7 vom User genannten Seiten (diary, map, walks, erste-hilfe, notes, expenses, routes) — Pfote wird grün wenn alle im Static-Cache sind. CSS-Färbung umgestellt: nur stroke (Linie) wird grün, kein fill mehr. Pfote behält ihre offene Optik, nur die Outlines wechseln von weiß zu Grün.
767 lines
33 KiB
HTML
767 lines
33 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="#0f1623" id="meta-theme-color">
|
|
<meta name="description" content="Ban Yaro — Die kostenlose Hunde-App für Deutschland. Tagebuch, Impfpass, Giftköder-Alarm, Gassi-Community, Hundesitting. DSGVO-konform, ohne App Store.">
|
|
<meta name="keywords" content="Hunde App, Hunde Tagebuch, Impfpass Hund, Giftköder Alarm, Gassi Community, Hundesitting, Hunde Wiki, Hunderassen, PWA Hunde, DSGVO Hunde App">
|
|
<link rel="canonical" href="https://banyaro.app/">
|
|
|
|
<!-- Preconnect: externe Hosts frühzeitig verbinden -->
|
|
<link rel="preconnect" href="https://tile.openstreetmap.org" crossorigin>
|
|
<link rel="dns-prefetch" href="https://tile.openstreetmap.org">
|
|
|
|
<!-- Open Graph -->
|
|
<meta property="og:type" content="website">
|
|
<meta property="og:title" content="Ban Yaro — Die deutschsprachige Hunde-Plattform">
|
|
<meta property="og:description" content="Alles rund um deinen Hund — Tagebuch, Impfpass, Giftköder-Alarm, Gassi-Community, Hundesitting. Kostenlos, DSGVO-konform, ohne App Store.">
|
|
<meta property="og:url" content="https://banyaro.app/">
|
|
<meta property="og:image" content="https://banyaro.app/icons/icon-512.png">
|
|
<meta property="og:locale" content="de_DE">
|
|
<meta property="og:site_name" content="Ban Yaro">
|
|
|
|
<!-- Twitter Card -->
|
|
<meta name="twitter:card" content="summary_large_image">
|
|
<meta name="twitter:title" content="Ban Yaro — Die deutschsprachige Hunde-Plattform">
|
|
<meta name="twitter:description" content="Alles rund um deinen Hund — Tagebuch, Impfpass, Giftköder-Alarm, Gassi-Community. Kostenlos, DSGVO-konform.">
|
|
<meta name="twitter:image" content="https://banyaro.app/icons/icon-512.png">
|
|
|
|
<!-- Structured Data -->
|
|
<script type="application/ld+json">
|
|
{
|
|
"@context": "https://schema.org",
|
|
"@type": "MobileApplication",
|
|
"name": "Ban Yaro",
|
|
"alternateName": "Ban Yaro — Die Hunde-Plattform",
|
|
"description": "Ban Yaro ist die kostenlose, deutschsprachige All-in-One Hunde-App. Digitales Tagebuch, Impfpass, Giftköder-Alarm, Gassi-Community, Hundesitting und mehr — DSGVO-konform, ohne App Store.",
|
|
"url": "https://banyaro.app",
|
|
"applicationCategory": "LifestyleApplication",
|
|
"applicationSubCategory": "PetApplication",
|
|
"operatingSystem": "iOS, Android, Web",
|
|
"inLanguage": "de",
|
|
"offers": {
|
|
"@type": "Offer",
|
|
"price": "0",
|
|
"priceCurrency": "EUR",
|
|
"availability": "https://schema.org/InStock"
|
|
},
|
|
"publisher": {
|
|
"@type": "Organization",
|
|
"name": "Ban Yaro",
|
|
"url": "https://banyaro.app"
|
|
},
|
|
"featureList": [
|
|
"Digitales Hunde-Tagebuch mit Fotos und GPS",
|
|
"Digitaler Impfpass und Gesundheitsakte",
|
|
"Giftköder-Alarm mit Push-Benachrichtigungen",
|
|
"Gassi-Community und GPS-Routen",
|
|
"Hundesitting-Vermittlung mit 8% Provision",
|
|
"NFC-Halsband-Tags",
|
|
"Hunde-Wiki mit Rassendatenbank",
|
|
"Verlorener Hund Alarm",
|
|
"Forum für Hundebesitzer",
|
|
"Offline-Modus via Service Worker"
|
|
],
|
|
"areaServed": ["DE", "AT", "CH"]
|
|
}
|
|
</script>
|
|
|
|
<!-- Eigenes Farbschema — verhindert Browser-eigenen Dark-Mode-Filter -->
|
|
<meta name="color-scheme" content="dark light">
|
|
|
|
<!-- 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="mobile-web-app-capable" content="yes">
|
|
<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 + theme-color Statusleiste vor CSS setzen -->
|
|
<script>
|
|
(function() {
|
|
var t = localStorage.getItem('by_theme');
|
|
var isDark = t === 'dark' || (t !== 'light' && window.matchMedia('(prefers-color-scheme: dark)').matches);
|
|
var isAndroid = /android/i.test(navigator.userAgent);
|
|
if (t === 'dark') document.documentElement.setAttribute('data-theme', 'dark');
|
|
if (t === 'light') document.documentElement.setAttribute('data-theme', 'light');
|
|
// Android: immer dunkel (Amber-Streifen nicht möglich transparent zu machen)
|
|
// iOS: black-translucent übernimmt das
|
|
var m = document.getElementById('meta-theme-color');
|
|
if (m) m.setAttribute('content', (isDark || isAndroid) ? '#0f1623' : '#C4843A');
|
|
})();
|
|
</script>
|
|
|
|
<!-- CSS: Reihenfolge ist wichtig — ?v= zwingt Browser zur Neuladung -->
|
|
<link rel="stylesheet" href="/css/design-system.css?v=1085">
|
|
<link rel="stylesheet" href="/css/layout.css?v=1085">
|
|
<link rel="stylesheet" href="/css/components.css?v=1085">
|
|
</head>
|
|
<body>
|
|
|
|
<!-- Offline-Banner -->
|
|
<div id="offline-banner" aria-live="polite"
|
|
style="display:none;position:fixed;top:0;left:0;right:0;z-index:9999;
|
|
background:#1f2937;color:#f3f4f6;font-size:0.78rem;font-weight:500;
|
|
padding:7px 16px;align-items:center;justify-content:center;gap:8px;
|
|
box-shadow:0 2px 8px rgba(0,0,0,.3)">
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="currentColor" viewBox="0 0 256 256">
|
|
<path d="M213.92,210.62l-160-176A8,8,0,1,0,42.08,45.38L81.06,88.86A152.34,152.34,0,0,0,26.49,130a8,8,0,0,0,11,11.61,136.36,136.36,0,0,1,52-37.29l19.2,21.12A96.09,96.09,0,0,0,67.6,160.59,8,8,0,1,0,79,172.2a80.12,80.12,0,0,1,33.5-23.89L128,165.37V224a8,8,0,0,0,16,0V183.94l69.92,76.92a8,8,0,1,0,11.84-10.76ZM128,141.46,108.42,120A80.38,80.38,0,0,1,128,116a79.91,79.91,0,0,1,19.59,2.43l-19.59,23Zm0-85.46a167.9,167.9,0,0,1,101.51,34.17,8,8,0,1,0,9.72-12.72A183.82,183.82,0,0,0,128,40a183.5,183.5,0,0,0-48.55,6.55L95,64.18A168.23,168.23,0,0,1,128,56Zm57.09,72.41a8,8,0,0,0,11.22-1.36,8,8,0,0,0-1.36-11.22,136.72,136.72,0,0,0-31.62-18.23L178,114.26A120.52,120.52,0,0,1,185.09,128.41Z"/>
|
|
</svg>
|
|
<span id="offline-banner-text">Offline — Änderungen werden gespeichert und synchronisiert sobald Verbindung besteht</span>
|
|
<span id="offline-queue-badge" style="display:none;background:rgba(239,68,68,.8);
|
|
border-radius:999px;padding:1px 7px;font-size:11px;font-weight:700"></span>
|
|
</div>
|
|
|
|
<!-- E-Mail-Verifikations-Banner -->
|
|
<div id="verify-banner" aria-live="polite"
|
|
style="display:none;position:fixed;top:0;left:0;right:0;z-index:9998;
|
|
background:#d97706;color:#fff;font-size:0.8rem;font-weight:500;
|
|
padding:8px 16px;align-items:center;justify-content:center;gap:10px;
|
|
box-shadow:0 2px 8px rgba(0,0,0,.2)">
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="currentColor" viewBox="0 0 256 256">
|
|
<path d="M224,48H32a8,8,0,0,0-8,8V192a16,16,0,0,0,16,16H216a16,16,0,0,0,16-16V56A8,8,0,0,0,224,48ZM98.71,128,40,181.81V74.19Zm11.84,10.85,12,11.05a8,8,0,0,0,10.82,0l12-11.05,58,53.15H52.57ZM157.29,128,216,74.19V181.81ZM40,61.62l88,80.15,88-80.15Z"/>
|
|
</svg>
|
|
<span>Bitte bestätige deine E-Mail-Adresse — wir haben dir eine Mail geschickt.</span>
|
|
<button id="verify-resend-btn"
|
|
style="background:rgba(255,255,255,.2);border:none;color:#fff;padding:3px 10px;
|
|
border-radius:999px;font-size:0.75rem;cursor:pointer;font-weight:600">
|
|
Erneut senden
|
|
</button>
|
|
<button id="verify-banner-close"
|
|
style="background:none;border:none;color:#fff;opacity:.7;cursor:pointer;
|
|
font-size:1rem;line-height:1;padding:0 4px" aria-label="Schließen">✕</button>
|
|
</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>
|
|
<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>
|
|
<div class="sidebar-item" data-page="notes">
|
|
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#note-pencil"></use></svg> Notizblock
|
|
</div>
|
|
<div class="sidebar-item" data-page="expenses">
|
|
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#receipt"></use></svg> Ausgaben
|
|
</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>
|
|
<div class="sidebar-item" data-page="jobs">
|
|
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#sparkle"></use></svg> Jobs
|
|
</div>
|
|
<div class="sidebar-item" data-page="adoption">
|
|
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#heart"></use></svg> Adoption
|
|
</div>
|
|
<div class="sidebar-item" data-page="wetter">
|
|
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#sun"></use></svg> Wetter
|
|
</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="playdate">
|
|
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#paw-print"></use></svg> Playdate
|
|
</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>
|
|
|
|
|
|
<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="recalls">
|
|
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#warning"></use></svg> Rückrufe
|
|
</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">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="wurfboerse">
|
|
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#paw-print"></use></svg> Wurfbörse
|
|
</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 id="sidebar-breeder-section" style="display:none">
|
|
<div style="padding:var(--space-3) var(--space-3) var(--space-1);font-size:10px;font-weight:700;
|
|
text-transform:uppercase;letter-spacing:.1em;color:var(--c-primary,#C4843A);
|
|
opacity:.8;display:flex;align-items:center;gap:6px">
|
|
<svg class="ph-icon" style="width:12px;height:12px" aria-hidden="true"><use href="/icons/phosphor.svg#lock-key"></use></svg>
|
|
Züchter
|
|
</div>
|
|
<div class="sidebar-item" data-page="zuchthunde" id="sidebar-zuchthunde"
|
|
style="color:var(--c-primary,#7c3aed)">
|
|
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#tree-structure"></use></svg> Zuchtkartei
|
|
</div>
|
|
<div class="sidebar-item" data-page="litters" id="sidebar-litters"
|
|
style="color:var(--c-primary,#7c3aed)">
|
|
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#certificate"></use></svg> Wurfverwaltung
|
|
</div>
|
|
<div class="sidebar-item" data-page="laeufi" id="sidebar-laeufi"
|
|
style="color:var(--c-primary,#7c3aed)">
|
|
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#thermometer"></use></svg> Läufigkeit
|
|
</div>
|
|
</div>
|
|
|
|
<div class="sidebar-item" data-page="social" id="sidebar-social"
|
|
style="display:none;color:var(--c-warning,#f59e0b)">
|
|
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#camera"></use></svg> Social Media
|
|
</div>
|
|
|
|
<div class="sidebar-item" data-page="moderation" id="sidebar-moderation"
|
|
style="display:none;color:var(--c-warning,#f59e0b)">
|
|
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#eye"></use></svg> Moderation
|
|
</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;flex-direction:column;gap:var(--space-2);padding-bottom:var(--space-2)">
|
|
<div style="display:flex;gap:var(--space-3);justify-content:center">
|
|
<span data-page="impressum" style="cursor:pointer;text-decoration:underline">Impressum</span>
|
|
<span data-page="datenschutz" style="cursor:pointer;text-decoration:underline">Datenschutz</span>
|
|
<span data-page="agb" style="cursor:pointer;text-decoration:underline">AGB</span>
|
|
</div>
|
|
<div style="display:flex;justify-content:center">
|
|
<span data-page="gruender" style="cursor:pointer;font-weight:600;font-size:var(--text-xs);
|
|
color:var(--c-text-muted);display:inline-flex;align-items:center;gap:5px">
|
|
<svg style="width:18px;height:18px;flex-shrink:0;fill:#f59e0b;color:#f59e0b" aria-hidden="true"><use href="/icons/phosphor.svg#trophy" style="fill:#f59e0b;color:#f59e0b"></use></svg>
|
|
die 100
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<!-- bot-trap: kein echter Nutzer klickt hier -->
|
|
<a href="/api/wiki/trap" aria-hidden="true" tabindex="-1"
|
|
style="position:absolute;width:1px;height:1px;opacity:0;pointer-events:none"
|
|
rel="nofollow noindex">.</a>
|
|
</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>
|
|
<span id="chat-nav-badge" class="hidden"
|
|
style="position:absolute;top:-3px;right:-3px;width:10px;height:10px;
|
|
border-radius:50%;background:var(--c-danger);
|
|
pointer-events:none;z-index:1"></span>
|
|
</button>
|
|
<button class="header-menu-btn" id="header-menu-btn" aria-label="Menü" style="position:relative">
|
|
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#list"></use></svg>
|
|
<span id="notif-nav-badge" class="hidden"
|
|
style="position:absolute;top:4px;right:4px;width:10px;height:10px;
|
|
border-radius:50%;background:var(--c-danger);
|
|
pointer-events:none;z-index:1"></span>
|
|
</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-hilfe">
|
|
<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-zuchthunde">
|
|
<div class="page-body page-container-wide"></div>
|
|
</section>
|
|
<section class="page" id="page-zucht-profil">
|
|
<div class="page-body page-container-wide"></div>
|
|
</section>
|
|
<section class="page" id="page-litters">
|
|
<div class="page-body page-container-wide"></div>
|
|
</section>
|
|
|
|
<section class="page" id="page-laeufi">
|
|
<div class="page-body page-container-wide"></div>
|
|
</section>
|
|
|
|
<section class="page" id="page-wurfboerse">
|
|
<div class="page-body page-container-wide"></div>
|
|
</section>
|
|
|
|
<section class="page" id="page-breeder">
|
|
<div class="page-body page-container"></div>
|
|
</section>
|
|
|
|
<section class="page" id="page-breeder-editor">
|
|
<div class="page-body page-container"></div>
|
|
</section>
|
|
|
|
<section class="page" id="page-social">
|
|
<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-moderation">
|
|
<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-agb">
|
|
<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>
|
|
|
|
<section class="page" id="page-notes">
|
|
<div class="page-body page-container"></div>
|
|
</section>
|
|
|
|
<section class="page" id="page-gruender">
|
|
<div class="page-body page-container"></div>
|
|
</section>
|
|
|
|
<section class="page" id="page-partner">
|
|
<div class="page-body page-container"></div>
|
|
</section>
|
|
|
|
<section class="page" id="page-partner-profil">
|
|
<div class="page-body page-container"></div>
|
|
</section>
|
|
|
|
<section class="page" id="page-jobs">
|
|
<div class="page-body page-container"></div>
|
|
</section>
|
|
|
|
<section class="page" id="page-expenses">
|
|
<div class="page-body page-container"></div>
|
|
</section>
|
|
|
|
<section class="page" id="page-recalls">
|
|
<div class="page-body page-container"></div>
|
|
</section>
|
|
|
|
<section class="page" id="page-adoption">
|
|
<div class="page-body page-container"></div>
|
|
</section>
|
|
|
|
<section class="page" id="page-wetter">
|
|
<div class="page-body page-container"></div>
|
|
</section>
|
|
|
|
<section class="page" id="page-playdate">
|
|
<div class="page-body page-container"></div>
|
|
</section>
|
|
|
|
<section class="page" id="page-ernaehrung">
|
|
<div class="page-body page-container"></div>
|
|
</section>
|
|
|
|
<section class="page" id="page-personality">
|
|
<div class="page-body page-container"></div>
|
|
</section>
|
|
|
|
<section class="page" id="page-reise">
|
|
<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" data-page="map">
|
|
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#map-trifold"></use></svg>
|
|
<span class="nav-item-label">Karte</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 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="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>
|
|
</nav>
|
|
|
|
</div><!-- #app -->
|
|
|
|
<!-- DREI WELTEN — JETZT | HUND | WELT -->
|
|
<div id="worlds-overlay">
|
|
<div id="world-dots">
|
|
<span class="wdot" data-w="0"></span>
|
|
<span class="wdot active" data-w="1"></span>
|
|
<span class="wdot" data-w="2"></span>
|
|
</div>
|
|
<div id="world-labels">
|
|
<span class="wlabel" data-w="0">JETZT</span>
|
|
<span class="wlabel" data-w="1">HUND</span>
|
|
<span class="wlabel" data-w="2">WELT</span>
|
|
</div>
|
|
<div id="worlds-track">
|
|
<div class="world-panel" id="wp-jetzt"><div id="wj-content"></div></div>
|
|
<div class="world-panel" id="wp-hund"><div id="wh-content"></div></div>
|
|
<div class="world-panel" id="wp-welt"><div id="ww-content"></div></div>
|
|
</div>
|
|
<button id="worlds-fab" aria-label="Hinzufügen">
|
|
<svg class="offline-paw" viewBox="0 0 256 256" aria-hidden="true" style="width:24px;height:24px">
|
|
<!-- 5 Sub-Pfade einzeln einfärbbar via .paw-elem; Default: weiß auf orange -->
|
|
<path class="paw-elem" data-step="1"
|
|
d="M128,104A36,36,0,0,0,93.43,130a43.49,43.49,0,0,1-20.67,25.9,32,32,0,0,0,27.73,57.62,72.49,72.49,0,0,1,55,0,32,32,0,0,0,27.73-57.62A43.46,43.46,0,0,1,162.57,130,36,36,0,0,0,128,104Z"
|
|
fill="none" stroke="currentColor" stroke-width="16" stroke-linecap="round" stroke-linejoin="round"/>
|
|
<circle class="paw-elem" data-step="2" cx="44" cy="108" r="20" fill="none" stroke="currentColor" stroke-width="16"/>
|
|
<circle class="paw-elem" data-step="3" cx="92" cy="60" r="20" fill="none" stroke="currentColor" stroke-width="16"/>
|
|
<circle class="paw-elem" data-step="4" cx="164" cy="60" r="20" fill="none" stroke="currentColor" stroke-width="16"/>
|
|
<circle class="paw-elem" data-step="5" cx="212" cy="108" r="20" fill="none" stroke="currentColor" stroke-width="16"/>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
<div id="worlds-back" aria-label="Zurück zur Welten-Navigation">
|
|
<svg class="ph-icon" style="width:22px;height:22px"><use href="/icons/phosphor.svg#arrow-left"></use></svg>
|
|
</div>
|
|
|
|
<!-- 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=1085"></script>
|
|
<script src="/js/ui.js?v=1085"></script>
|
|
<script src="/js/app.js?v=1085"></script>
|
|
<script src="/js/worlds.js?v=1085"></script>
|
|
<script src="/js/offline-indicator.js?v=1085"></script>
|
|
|
|
<!-- Feature-Seiten werden lazy geladen -->
|
|
|
|
<!-- Umami Analytics (self-hosted, cookiefrei, DSGVO-konform) -->
|
|
<script defer src="/stats/script.js" data-website-id="d1b5fe13-0e6f-4461-a176-c5439cbbc27f" data-api-host="/stats"></script>
|
|
|
|
|
|
<!-- Offline-Banner Logik -->
|
|
<script>
|
|
(function() {
|
|
function _updateBanner() {
|
|
var banner = document.getElementById('offline-banner');
|
|
if (!banner) return;
|
|
banner.style.display = navigator.onLine ? 'none' : 'flex';
|
|
}
|
|
window.addEventListener('offline', function() {
|
|
_updateBanner();
|
|
// Einmaliger Hinweis pro Session: App im Vordergrund lassen
|
|
if (!sessionStorage.getItem('by_offline_hint_shown')) {
|
|
sessionStorage.setItem('by_offline_hint_shown', '1');
|
|
setTimeout(function() {
|
|
window.UI?.toast?.info(
|
|
'App im Vordergrund lassen — so bleiben Offline-Funktionen wie GPS und Datenspeicherung aktiv.',
|
|
8000
|
|
);
|
|
}, 800);
|
|
}
|
|
// Queue-Count abfragen
|
|
if (navigator.serviceWorker) {
|
|
navigator.serviceWorker.ready.then(function(reg) {
|
|
if (reg.active) reg.active.postMessage({ type: 'QUEUE_COUNT' });
|
|
});
|
|
}
|
|
});
|
|
window.addEventListener('online', function() {
|
|
_updateBanner();
|
|
var badge = document.getElementById('offline-queue-badge');
|
|
if (badge) badge.style.display = 'none';
|
|
// Queue abarbeiten
|
|
if (navigator.serviceWorker) {
|
|
navigator.serviceWorker.ready.then(function(reg) {
|
|
if (reg.active) reg.active.postMessage({ type: 'PROCESS_QUEUE' });
|
|
});
|
|
}
|
|
});
|
|
// Initial prüfen
|
|
_updateBanner();
|
|
})();
|
|
</script>
|
|
|
|
<!-- Service Worker -->
|
|
<script>
|
|
if ('serviceWorker' in navigator) {
|
|
window.addEventListener('load', () => {
|
|
navigator.serviceWorker.register('/sw.js', { updateViaCache: 'none' })
|
|
.then(reg => {
|
|
function _watchSW(sw) {
|
|
if (!sw) return;
|
|
sw.addEventListener('statechange', () => {
|
|
if (sw.state === 'activated') {
|
|
// Flag nur prüfen, nicht konsumieren — controllerchange konsumiert ihn
|
|
if (sessionStorage.getItem('by_skip_sw_reload')) return;
|
|
window.location.replace('/?_t=' + Date.now());
|
|
}
|
|
});
|
|
}
|
|
// Listener VOR update() registrieren — verhindert Race Condition
|
|
reg.addEventListener('updatefound', () => _watchSW(reg.installing));
|
|
// Falls SW bereits installiert (Seite wurde nach SW-Install neu geladen)
|
|
if (reg.installing) _watchSW(reg.installing);
|
|
reg.update();
|
|
})
|
|
.catch(err => console.warn('SW Registration failed:', err));
|
|
});
|
|
|
|
// Backup: erneut prüfen wenn App aus dem Hintergrund kommt
|
|
document.addEventListener('visibilitychange', () => {
|
|
if (document.visibilityState === 'visible') {
|
|
navigator.serviceWorker.getRegistration().then(reg => reg?.update());
|
|
}
|
|
});
|
|
|
|
// Backup: controllerchange (falls updatefound nicht feuert)
|
|
// NICHT registrieren wenn diese Seite selbst durch einen SW-Reload entstand (_t= im URL)
|
|
// — verhindert Dauerschleife wenn clients.claim() erst nach Seitenstart feuert
|
|
if (!window._BY_SW_RELOAD) {
|
|
navigator.serviceWorker.addEventListener('controllerchange', () => {
|
|
if (sessionStorage.getItem('by_skip_sw_reload')) {
|
|
sessionStorage.removeItem('by_skip_sw_reload');
|
|
return;
|
|
}
|
|
window.location.replace('/?_t=' + Date.now());
|
|
});
|
|
}
|
|
|
|
navigator.serviceWorker.addEventListener('message', e => {
|
|
if (e.data?.type === 'QUEUE_PROCESSED') {
|
|
const { synced, failed, total } = e.data;
|
|
if (total === 0) return;
|
|
if (synced > 0 && window.UI?.toast) {
|
|
window.UI.toast.success(
|
|
synced === 1
|
|
? '1 offline gespeicherter Eintrag synchronisiert'
|
|
: `${synced} offline gespeicherte Einträge synchronisiert`
|
|
);
|
|
// Aktuelle Seite neu laden
|
|
window.App?.state && window.pages?.[window.App.state.page]?.module?.refresh?.();
|
|
}
|
|
if (failed > 0 && window.UI?.toast) {
|
|
window.UI.toast.warning(`${failed} Eintrag${failed > 1 ? 'e' : ''} noch nicht synchronisiert — kein Netz`);
|
|
}
|
|
return;
|
|
}
|
|
if (e.data?.type === 'QUEUE_COUNT') {
|
|
const badge = document.getElementById('offline-queue-badge');
|
|
if (badge) {
|
|
if (e.data.count > 0) {
|
|
badge.textContent = e.data.count;
|
|
badge.style.display = '';
|
|
} else {
|
|
badge.style.display = 'none';
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
if (e.data?.type === 'CHECK_NEARBY_ALERTS') {
|
|
window.App?._checkNearbyAlerts?.();
|
|
}
|
|
});
|
|
}
|
|
</script>
|
|
|
|
|
|
</body>
|
|
</html>
|