/* ============================================================ BAN YARO — Offline-Bereitschafts-Anzeige IM Welten-FAB Färbt die 5 Pfoten-Pfade je nach Cache-Stand grün: 1 = App-Shell · 2 = Wichtige Seiten · 3 = Hund-/Tagebuchdaten 4 = Karten-Tiles · 5 = Training & Wissen ============================================================ */ window.OfflineIndicator = (() => { 'use strict'; // Cache-Namen dynamisch finden — robust gegen Versions-Updates const CACHE_TILES = 'ban-yaro-tiles-v1'; const CACHE_API = 'ban-yaro-api-v1'; const TILE_MIN = 50; async function _staticCache() { const names = await caches.keys(); const found = names.find(n => /^by-v\d+-static$/.test(n)); return found ? await caches.open(found) : null; } const CHECKS = [ { step: 1, title: 'App-Grundgerüst', detail: 'CSS, Layout und Hauptmodule — die Basis', probe: async () => { const c = await _staticCache(); if (!c) return false; const urls = (await c.keys()).map(r => r.url); return urls.some(u => u.includes('/css/design-system.css')) && urls.some(u => u.includes('/js/app.js')); } }, { step: 2, title: 'Wichtige Seiten', detail: 'Tagebuch, Karte, Gassi, Erste Hilfe, Notizblock, Ausgaben, Routen', probe: async () => { const c = await _staticCache(); if (!c) return false; const must = ['diary.js','map.js','walks.js','erste-hilfe.js','notes.js','expenses.js','routes.js']; const urls = (await c.keys()).map(r => r.url); return must.every(name => urls.some(u => u.includes('/js/pages/' + name))); } }, { step: 3, title: 'Hund- und Tagebuchdaten', detail: 'Letzte Einträge und Hund-Profil', probe: async () => { const c = await caches.open(CACHE_API).catch(() => null); if (!c) return false; const urls = (await c.keys()).map(r => r.url); return urls.some(u => /\/api\/dogs\/\d+/.test(u)) && urls.some(u => /\/api\/dogs\/\d+\/diary/.test(u)); } }, { step: 4, title: 'Karten-Kacheln', detail: `Mindestens ${TILE_MIN} Tiles im Umkreis`, probe: async () => { const c = await caches.open(CACHE_TILES).catch(() => null); if (!c) return false; return (await c.keys()).length >= TILE_MIN; } }, { step: 5, title: 'Welt-Daten', detail: 'Streak, Wetter, Hundepass — kommt beim Welten-Aufruf', probe: async () => { const c = await caches.open(CACHE_API).catch(() => null); if (!c) return false; const urls = (await c.keys()).map(r => r.url); return urls.some(u => u.includes('/api/streak/')) && urls.some(u => u.includes('/api/weather')); } }, ]; // Tile-Prefetch-Konfiguration const TILE_PREFETCH = [ { zoom: 14, radius: 3 }, // 7x7 = 49 Tiles im Nahbereich { zoom: 13, radius: 1 }, // 3x3 = 9 Tiles für Übersicht ]; let _fab = null; async function refresh() { _fab = document.getElementById('worlds-fab'); if (!_fab || !('caches' in window)) return null; const results = await Promise.all(CHECKS.map(async c => { try { return { ...c, ok: await c.probe() }; } catch { return { ...c, ok: false }; } })); _fab.querySelectorAll('.paw-elem').forEach(el => { const step = Number(el.dataset.step); const isOk = results.find(r => r.step === step)?.ok; el.classList.toggle('filled', !!isOk); }); const score = results.filter(r => r.ok).length; _fab.setAttribute('data-offline-score', `${score}/5`); return { score, results }; } // Optional aufrufbar: zeigt das Status-Modal mit Nachlade-Button async function openStatus() { const data = await refresh(); if (!data) return; const { score, results } = data; const missing = results.filter(r => !r.ok); const rows = results.map(r => `
${missing.length === 0 ? 'Voll offline-fähig — Tagebuch, Karte und Daten funktionieren auch ohne Internet.' : 'Je grüner deine Pfote im FAB, desto mehr klappt offline. Fehlende Inhalte werden beim nächsten Online-Aufruf automatisch geladen.'}
${rows} `, footer: `