/* ============================================================ BAN YARO — Offline-Bereitschafts-Indikator 5-stufige Pfote im Header, zeigt wie viel der App offline verfügbar ist. Klick → Status-Modal mit Nachlade-Button. ============================================================ */ window.OfflineIndicator = (() => { 'use strict'; // Cache-Namen — müssen mit sw.js übereinstimmen const CACHE_STATIC = `by-v${(window.APP_VER || '0')}-static`; const CACHE_TILES = 'ban-yaro-tiles-v1'; const CACHE_API = 'ban-yaro-api-v1'; const TILE_MIN = 50; // Mindest-Tiles für Stufe 4 // 5 Offline-Bereitschafts-Checks, in Reihenfolge der Pfoten-Stufen const CHECKS = [ { step: 1, title: 'App-Grundgerüst', detail: 'CSS, Layout und Hauptmodule — die Basis', probe: async () => (await caches.match('/css/design-system.css?v=' + window.APP_VER)) != null || (await caches.match('/css/design-system.css')) != null }, { step: 2, title: 'Wichtige Seiten', detail: 'Tagebuch, Karte, Gassi, Erste Hilfe', probe: async () => { const c = await caches.open(CACHE_STATIC).catch(() => null); if (!c) return false; const must = ['diary.js','map.js','walks.js','erste-hilfe.js']; const keys = await c.keys(); const have = new Set(keys.map(r => r.url)); return must.every(name => [...have].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 keys = await c.keys(); const urls = 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; const keys = await c.keys(); return keys.length >= TILE_MIN; } }, { step: 5, title: 'Training & Wissen', detail: 'Übungen, Wiki-Rassen, Wetter — Welt-Inhalte', 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/training/exercises')) && urls.some(u => u.includes('/api/wiki/rassen')); } }, ]; let _btn = null; let _svg = null; let _lastScore = -1; // ---------------------------------------------------------- // Score berechnen + Pfote einfärben // ---------------------------------------------------------- async function refresh() { if (!_btn) return; if (!('caches' in window)) { _btn.style.display = 'none'; return; } const results = await Promise.all(CHECKS.map(async c => { try { return { ...c, ok: await c.probe() }; } catch { return { ...c, ok: false }; } })); const score = results.filter(r => r.ok).length; _applyScore(score, results); _lastScore = score; return { score, results }; } function _applyScore(score, results) { if (!_svg) return; _svg.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); }); _btn.title = `Offline-Bereitschaft: ${score} von 5`; _btn.setAttribute('aria-label', `Offline-Bereitschaft: ${score} von 5`); } // ---------------------------------------------------------- // Status-Modal beim Klick // ---------------------------------------------------------- async function _openModal() { const data = await refresh(); if (!data) return; const { score, results } = data; const rows = results.map(r => `
${allOk ? 'Deine App ist voll offline-fähig. Du kannst Tagebuch, Karte und Daten auch ohne Internet nutzen.' : 'Je grüner deine Pfote, desto besser klappt die App ohne Internet. Fehlende Inhalte werden beim nächsten Online-Aufruf automatisch geladen.'}
${rows} `, footer: `