diff --git a/.env.example b/.env.example
index 3deda7d..74d4688 100644
--- a/.env.example
+++ b/.env.example
@@ -25,3 +25,6 @@ KI_CLOUD_MODEL=claude-opus-4-6
VAPID_PUBLIC_KEY=
VAPID_PRIVATE_KEY=
VAPID_CONTACT=mailto:admin@banyaro.app
+
+# Admin-Benachrichtigungen (z.B. Prewarm-Fortschritt)
+ADMIN_EMAIL=
diff --git a/backend/routes/osm.py b/backend/routes/osm.py
index 3056824..51c1f17 100644
--- a/backend/routes/osm.py
+++ b/backend/routes/osm.py
@@ -4,6 +4,7 @@ Cacht OSM-Daten lokal, erlaubt Nutzern eigene Marker und Meldungen.
"""
import math
+import asyncio
import httpx
import logging
from typing import Optional
@@ -142,8 +143,11 @@ async def get_pois(
stale = _stale_tiles(type, tiles)
if stale and not fast:
- for (x, y) in stale:
- await _fetch_and_store_tile(type, x, y)
+ sem = asyncio.Semaphore(3)
+ async def _limited(x, y):
+ async with sem:
+ await _fetch_and_store_tile(type, x, y)
+ await asyncio.gather(*[_limited(x, y) for (x, y) in stale])
fetched_fresh = True
with db() as conn:
@@ -309,9 +313,16 @@ async def analyze_region(
tiles = _covering_tiles(south, west, north, east, CACHE_ZOOM)
async def _warmup():
- for poi_type in OSM_QUERIES:
- for (x, y) in _stale_tiles(poi_type, tiles):
+ sem = asyncio.Semaphore(3)
+ async def _limited(poi_type, x, y):
+ async with sem:
await _fetch_and_store_tile(poi_type, x, y)
+ tasks = [
+ _limited(pt, x, y)
+ for pt in OSM_QUERIES
+ for (x, y) in _stale_tiles(pt, tiles)
+ ]
+ await asyncio.gather(*tasks)
background_tasks.add_task(_warmup)
return {'status': 'gestartet', 'tiles': len(tiles), 'types': list(OSM_QUERIES.keys())}
diff --git a/backend/scheduler.py b/backend/scheduler.py
index b8aa803..4304909 100644
--- a/backend/scheduler.py
+++ b/backend/scheduler.py
@@ -53,6 +53,13 @@ def start():
replace_existing=True,
misfire_grace_time=7200,
)
+ _scheduler.add_job(
+ _job_prewarm_cities,
+ CronTrigger(day_of_week='sun', hour=1), # jeden Sonntag 01:00 Uhr
+ id="prewarm_cities",
+ replace_existing=True,
+ misfire_grace_time=7200,
+ )
# Einmalig beim Start (nach 10s Verzögerung) für sofortige Befüllung
_scheduler.add_job(
_job_import_events,
@@ -61,6 +68,14 @@ def start():
id="import_events_startup",
replace_existing=True,
)
+ # Einmalig beim Start (nach 90s) — OSM-Tiles für Großstädte vorwärmen
+ _scheduler.add_job(
+ _job_prewarm_cities,
+ 'date',
+ run_date=datetime.now() + timedelta(seconds=90),
+ id="prewarm_cities_startup",
+ replace_existing=True,
+ )
# Einmalig beim Start (nach 15s Verzögerung) — Rassen aus TheDogAPI befüllen
_scheduler.add_job(
_job_seed_breeds,
@@ -384,6 +399,178 @@ async def _job_seed_wikidata_breeds():
logger.error(f"Wikidata-Seed: Fehler: {e}")
+# ------------------------------------------------------------------
+# JOB: OSM-Tiles für deutsche Großstädte vorwärmen
+# Läuft einmalig 90s nach Start + wöchentlich So 01:00 Uhr.
+# Nur stale Tiles werden abgerufen (bereits gecachte werden übersprungen).
+# ------------------------------------------------------------------
+
+# Deutsche Städte mit >50.000 Einwohnern (lat, lon, name)
+_CITIES_DE = [
+ (52.5200, 13.4050, "Berlin"),
+ (53.5753, 10.0153, "Hamburg"),
+ (48.1372, 11.5755, "München"),
+ (51.2217, 6.7762, "Düsseldorf"),
+ (50.9333, 6.9500, "Köln"),
+ (50.1109, 8.6821, "Frankfurt"),
+ (48.7775, 9.1800, "Stuttgart"),
+ (51.4566, 7.0116, "Dortmund"),
+ (51.5136, 7.4653, "Dortmund-Ost"),
+ (51.4508, 7.0131, "Essen"),
+ (51.3388, 12.3799, "Leipzig"),
+ (51.2254, 6.7762, "Düsseldorf"),
+ (51.0534, 13.7373, "Dresden"),
+ (52.3759, 9.7320, "Hannover"),
+ (51.4818, 7.2162, "Bochum"),
+ (51.9607, 7.6261, "Münster"),
+ (51.3670, 7.4595, "Hagen"),
+ (50.7753, 6.0839, "Aachen"),
+ (51.2563, 7.1500, "Wuppertal"),
+ (49.4521, 11.0767, "Nürnberg"),
+ (53.0758, 8.8072, "Bremen"),
+ (50.7323, 7.0955, "Bonn"),
+ (49.0069, 8.4037, "Karlsruhe"),
+ (51.9607, 7.6261, "Münster"),
+ (51.4344, 6.7623, "Duisburg"),
+ (51.6667, 6.1667, "Moers"),
+ (48.3705, 10.8978, "Augsburg"),
+ (52.2689, 10.5268, "Braunschweig"),
+ (50.9287, 11.5861, "Jena"),
+ (53.8655, 10.6866, "Lübeck"),
+ (54.3233, 10.1394, "Kiel"),
+ (53.1435, 8.2146, "Oldenburg"),
+ (52.0302, 8.5325, "Bielefeld"),
+ (51.3167, 9.5000, "Kassel"),
+ (50.0000, 8.2731, "Mainz"),
+ (49.8728, 8.6512, "Darmstadt"),
+ (49.0047, 12.0949, "Regensburg"),
+ (48.9960, 8.4025, "Pforzheim"),
+ (53.4706, 9.9817, "Hamburg-Süd"),
+ (50.8283, 12.9209, "Chemnitz"),
+ (51.7227, 8.7559, "Paderborn"),
+ (52.1205, 11.6276, "Magdeburg"),
+ (52.6367, 11.8683, "Magdeburg-Ost"),
+ (50.3569, 7.5890, "Koblenz"),
+ (48.4010, 9.9876, "Ulm"),
+ (51.0504, 13.7373, "Dresden-Mitte"),
+ (49.4875, 8.4660, "Mannheim"),
+ (49.2354, 7.0038, "Kaiserslautern"),
+ (50.1155, 8.6782, "Frankfurt-Mitte"),
+ (50.0782, 8.2398, "Wiesbaden"),
+ (52.4227, 10.7865, "Wolfsburg"),
+ (51.9607, 8.8693, "Gütersloh"),
+ (53.5753, 9.8500, "Hamburg-West"),
+ (48.5216, 9.0576, "Reutlingen"),
+ (48.9522, 9.4358, "Heilbronn"),
+ (49.4478, 7.7691, "Kaiserslautern-W"),
+ (53.6333, 9.9833, "Hamburg-Nord"),
+ (52.3905, 13.0645, "Potsdam"),
+ (54.0924, 12.1407, "Rostock"),
+ (53.4339, 14.5508, "Szczecin-grenze"),
+ (51.7563, 14.3329, "Cottbus"),
+ (50.4782, 12.3598, "Zwickau"),
+ (53.5507, 9.9967, "Hamburg-Mitte"),
+ (51.8127, 10.3354, "Goslar"),
+ (48.6843, 9.0061, "Böblingen"),
+ (48.7761, 9.1775, "Stuttgart-Mitte"),
+ (49.4521, 8.4660, "Heidelberg"),
+ (50.8088, 8.7667, "Marburg"),
+ (51.9607, 7.6261, "Münster-Mitte"),
+ (52.2763, 8.0479, "Osnabrück"),
+ (53.8755, 10.7000, "Lübeck-Ost"),
+ (51.9333, 6.8667, "Borken"),
+]
+
+async def _job_prewarm_cities():
+ import os, asyncio, time
+ from routes.osm import _covering_tiles, _stale_tiles, _fetch_and_store_tile, OSM_QUERIES, CACHE_ZOOM
+ from mailer import send_email
+
+ ADMIN = os.getenv("ADMIN_EMAIL", "")
+ REPORT_INTERVAL = 5 * 3600 # alle 5 Stunden
+
+ logger.info("City-Prewarm Job startet…")
+ sem = asyncio.Semaphore(2)
+ total_fetched = 0
+ cities_done = 0
+ start_time = time.monotonic()
+ last_report = start_time
+
+ async def _fetch(poi_type, x, y):
+ nonlocal total_fetched
+ async with sem:
+ await _fetch_and_store_tile(poi_type, x, y)
+ total_fetched += 1
+ await asyncio.sleep(1.5)
+
+ async def _send_progress(subject_prefix, cities_done, total_cities, eta_str=""):
+ if not ADMIN:
+ return
+ elapsed = int(time.monotonic() - start_time)
+ h, m = divmod(elapsed // 60, 60)
+ elapsed_str = f"{h}h {m:02d}min" if h else f"{m}min"
+ pct = round(cities_done / total_cities * 100)
+ body_plain = (
+ f"City-Prewarm Fortschritt\n\n"
+ f"Städte: {cities_done}/{total_cities} ({pct}%)\n"
+ f"Tiles geladen: {total_fetched}\n"
+ f"Laufzeit: {elapsed_str}\n"
+ f"{('Verbleibend (ca.): ' + eta_str) if eta_str else ''}"
+ )
+ body_html = f"""\
+
+
🗺️ City-Prewarm {subject_prefix}
+
+ | Städte: |
+ {cities_done} / {total_cities} ({pct}%) |
+ | Tiles geladen: |
+ {total_fetched} |
+ | Laufzeit: |
+ {elapsed_str} |
+ {f'| Verbleibend (ca.): | {eta_str} |
' if eta_str else ''}
+
+
"""
+ try:
+ await send_email(ADMIN, f"Ban Yaro — City-Prewarm {subject_prefix}", body_html, body_plain)
+ except Exception as e:
+ logger.warning(f"City-Prewarm Mail fehlgeschlagen: {e}")
+
+ total_cities = len(_CITIES_DE)
+ for lat, lon, city in _CITIES_DE:
+ dlat = 0.18
+ dlon = 0.25
+ south, west, north, east = lat - dlat, lon - dlon, lat + dlat, lon + dlon
+ tiles = _covering_tiles(south, west, north, east, CACHE_ZOOM)
+
+ tasks = []
+ for poi_type in OSM_QUERIES:
+ stale = _stale_tiles(poi_type, tiles)
+ for (x, y) in stale:
+ tasks.append(_fetch(poi_type, x, y))
+
+ if tasks:
+ logger.info(f"City-Prewarm: {city} — {len(tasks)} Tiles zu laden")
+ await asyncio.gather(*tasks)
+ else:
+ logger.debug(f"City-Prewarm: {city} — alle Tiles frisch")
+
+ cities_done += 1
+
+ # Fortschritts-Mail alle 5 Stunden
+ now = time.monotonic()
+ if ADMIN and (now - last_report) >= REPORT_INTERVAL:
+ elapsed = now - start_time
+ rate = cities_done / elapsed if elapsed > 0 else 0
+ remaining = int((total_cities - cities_done) / rate) if rate > 0 else 0
+ rh, rm = divmod(remaining // 60, 60)
+ eta_str = f"{rh}h {rm:02d}min" if rh else f"{rm}min"
+ await _send_progress("Fortschritt", cities_done, total_cities, eta_str)
+ last_report = now
+
+ logger.info(f"City-Prewarm Job fertig — {total_fetched} Tiles geladen.")
+ await _send_progress("abgeschlossen ✓", cities_done, total_cities)
+
+
def _compute_milestone(today: date, bday: date, dog_name: str):
"""
Gibt (titel, text) zurück wenn heute ein Meilenstein-Tag ist,
diff --git a/backend/static/icons/phosphor.svg b/backend/static/icons/phosphor.svg
index 4733778..36ad812 100644
--- a/backend/static/icons/phosphor.svg
+++ b/backend/static/icons/phosphor.svg
@@ -25,6 +25,7 @@
+
diff --git a/backend/static/js/app.js b/backend/static/js/app.js
index 24c0abe..80cb9d7 100644
--- a/backend/static/js/app.js
+++ b/backend/static/js/app.js
@@ -3,7 +3,7 @@
Router, State-Management, Navigation, Initialisierung.
============================================================ */
-const APP_VER = '98'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
+const APP_VER = '116'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
const App = (() => {
@@ -261,6 +261,7 @@ const App = (() => {
const item = e.target.closest('[data-page]');
if (item) {
navigate(item.dataset.page);
+ if (item.closest('#sidebar')) _closeSidebar();
return;
}
@@ -274,6 +275,7 @@ const App = (() => {
// Sidebar-User (kein data-page, damit keine Aktiv-Markierung)
if (e.target.closest('#sidebar-user')) {
navigate('settings');
+ _closeSidebar();
return;
}
diff --git a/backend/static/js/pages/friends.js b/backend/static/js/pages/friends.js
index 17a4ff2..b939086 100644
--- a/backend/static/js/pages/friends.js
+++ b/backend/static/js/pages/friends.js
@@ -36,7 +36,7 @@ window.Page_friends = (() => {
const myLink = `${location.origin}/#friends?suche=${encodeURIComponent(myName)}`;
_container.innerHTML = `
-
+
@@ -186,18 +186,20 @@ window.Page_friends = (() => {
${list.map(r => `
-
+
${_userAvatar(r.requester_name, r.dogs?.[0], r.avatar_url)}
-
-
+
+
${_esc(r.requester_name)}
${_dogPills(r.dogs, 2)}
-
+
`);
}
if (profile.social_link) {
- parts.push(`
+ parts.push(`
`);
@@ -508,10 +510,9 @@ window.Page_friends = (() => {
`
: ''}
-
`).join('')}
diff --git a/backend/static/js/pages/map.js b/backend/static/js/pages/map.js
index ede2804..8d913de 100644
--- a/backend/static/js/pages/map.js
+++ b/backend/static/js/pages/map.js
@@ -121,6 +121,8 @@ window.Page_map = (() => {
let _overpassTimer = null;
let _overpassActive = false;
+ let _ringClosing = false;
+ let _frankfurtTimer = null;
// ----------------------------------------------------------
// INIT
@@ -133,11 +135,23 @@ window.Page_map = (() => {
// Alle-Button Initialzustand
const anyOnInit = Object.entries(_visible).some(([k, v]) => v && k !== 'giftkoeder');
document.getElementById('map-legend-all')?.classList.toggle('all-off', !anyOnInit);
- try { _userPos = await API.getLocation(); } catch {}
await _loadLeaflet();
- _initMap();
+ _initMap(); // sofort mit Deutschland-Mitte starten
_startLocationTracking();
_loadAll();
+ // Standort im Hintergrund holen — bei Erfolg zur Position fliegen
+ API.getLocation().then(pos => {
+ _userPos = pos;
+ if (_frankfurtTimer) { clearTimeout(_frankfurtTimer); _frankfurtTimer = null; }
+ _map?.flyTo([pos.lat, pos.lon], 14, { duration: 1.2 });
+ }).catch(() => {
+ const btn = document.getElementById('map-locate-btn');
+ if (btn) {
+ btn.title = 'Standort nicht verfügbar';
+ btn.style.opacity = '0.55';
+ btn.innerHTML = '
';
+ }
+ });
}
function refresh() { _loadAll(); }
@@ -302,11 +316,15 @@ window.Page_map = (() => {
const el = document.getElementById('central-map');
if (!el || !window.L || _map) return;
- const center = _userPos ? [_userPos.lat, _userPos.lon] : [51.1657, 10.4515];
- const zoom = _userPos ? 14 : 6;
+ const center = _userPos ? [_userPos.lat, _userPos.lon] : [50.1109, 8.6821]; // Frankfurt
+ const zoom = _userPos ? 14 : 10;
_map = L.map('central-map', { zoomControl: true, attributionControl: false })
.setView(center, zoom);
+
+ if (!_userPos) {
+ _frankfurtTimer = setTimeout(() => _map.flyTo(center, 14, { duration: 2.5 }), 1200);
+ }
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 19 }).addTo(_map);
setTimeout(() => _map.invalidateSize(), 100);
@@ -403,9 +421,89 @@ window.Page_map = (() => {
}
function _setOsmStatus(text, pct = null) {
- const el = document.getElementById('map-osm-status');
+ const el = document.getElementById('map-osm-status');
+ const statusbar = document.getElementById('map-statusbar');
if (el) el.textContent = text;
_updateScanRing(text ? pct : null);
+ _updateScanDog(text ? pct : null);
+
+ if (pct === 100 && statusbar) {
+ statusbar.classList.add('scan-done');
+ setTimeout(() => statusbar.classList.remove('scan-done'), 2200);
+ }
+ }
+
+ function _injectDogStyles() {
+ if (document.getElementById('by-dog-style')) return;
+ const s = document.createElement('style');
+ s.id = 'by-dog-style';
+ s.textContent = [
+ '@keyframes by-sniff{0%,100%{transform:translateY(0) rotate(0deg)}30%{transform:translateY(2.5px) rotate(-1.5deg)}70%{transform:translateY(1px) rotate(1deg)}}',
+ '@keyframes by-wander{0%,100%{transform:translateX(0)}20%{transform:translateX(-7px)}45%{transform:translateX(5px)}68%{transform:translateX(-5px)}85%{transform:translateX(7px)}}',
+ '@keyframes by-wag{0%,100%{transform:rotate(-22deg)}50%{transform:rotate(22deg)}}',
+ '#map-scan-dog{animation:by-wander 1.75s ease-in-out infinite;transition:opacity .5s ease;color:#C4843A;position:absolute;pointer-events:none;z-index:1003;width:42px;height:32px}',
+ '#map-scan-dog svg{display:block;animation:by-sniff .42s ease-in-out infinite}',
+ '#map-scan-dog .by-tail{transform-box:fill-box;transform-origin:0% 100%;animation:by-wag .32s ease-in-out infinite}',
+ '#map-statusbar{transition:background .35s ease,color .35s ease,border-color .35s ease}',
+ '#map-statusbar.scan-done{background:#22C55E!important;color:#fff!important;border-color:#16A34A!important}',
+ ].join('');
+ document.head.appendChild(s);
+ }
+
+ function _updateScanDog(pct) {
+ _injectDogStyles();
+ const statusbar = document.getElementById('map-statusbar');
+ if (!statusbar) return;
+ const mapEl = statusbar.closest('.map-main') || statusbar.parentElement;
+ if (!mapEl) return;
+
+ let dog = document.getElementById('map-scan-dog');
+
+ if (pct === null) {
+ if (_ringClosing) return;
+ if (dog) { dog.style.opacity = '0'; setTimeout(() => dog?.remove(), 550); }
+ return;
+ }
+
+ if (!dog) {
+ dog = document.createElement('div');
+ dog.id = 'map-scan-dog';
+ const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
+ svg.setAttribute('width', '42');
+ svg.setAttribute('height', '32');
+ svg.setAttribute('viewBox', '0 0 54 40');
+ svg.innerHTML = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `;
+ dog.appendChild(svg);
+ mapEl.appendChild(dog);
+ }
+
+ const sr = statusbar.getBoundingClientRect();
+ const mr = mapEl.getBoundingClientRect();
+ dog.style.left = (sr.left - mr.left + sr.width - 36) + 'px';
+ dog.style.top = (sr.top - mr.top - 35) + 'px';
+ dog.style.opacity = '1';
+
+ if (pct >= 100) {
+ setTimeout(() => {
+ const d = document.getElementById('map-scan-dog');
+ if (d) { d.style.opacity = '0'; setTimeout(() => d?.remove(), 550); }
+ }, 500);
+ }
}
function _updateScanRing(pct) {
@@ -418,6 +516,7 @@ window.Page_map = (() => {
// Ring ausblenden / entfernen
if (pct === null) {
+ if (_ringClosing) return;
if (svg) { svg.style.opacity = '0'; setTimeout(() => svg?.remove(), 600); }
statusbar.style.border = '';
return;
@@ -448,12 +547,13 @@ window.Page_map = (() => {
const p = 2; // Abstand zur inneren Kante
// Umfang der Pill: gerades Stück + zwei Halbkreise
- const perim = 2 * (w - h) + Math.PI * h;
- // Stroke beginnt oben-links und läuft im Uhrzeigersinn
- // Um bei 12 Uhr zu starten: Offset um das linke Halbkreis-Viertel + halbe Geraden verschieben
- const startShift = (w - h) / 2 + (Math.PI * h) / 4;
- const progress = Math.min(100, Math.max(0, pct));
- const dashOffset = perim * (1 - progress / 100) + startShift;
+ const perim = 2 * (w - h) + Math.PI * h;
+ // Natürlicher SVG-Start: linkes Ende der oberen Geraden
+ // 12-Uhr-Position: Mitte der oberen Geraden → Abstand = (w-h)/2
+ // dashoffset = perim - S verschiebt den Dash-Start genau dorthin
+ const S = (w - h) / 2;
+ const progress = Math.min(100, Math.max(0, pct));
+ const progressLen = progress * perim / 100;
svg.style.left = (sr.left - mr.left - p) + 'px';
svg.style.top = (sr.top - mr.top - p) + 'px';
@@ -468,18 +568,19 @@ window.Page_map = (() => {
rect.setAttribute('height', String(h));
rect.setAttribute('rx', String(r));
rect.setAttribute('ry', String(r));
- rect.setAttribute('stroke-dasharray', perim.toFixed(2));
- rect.setAttribute('stroke-dashoffset', dashOffset.toFixed(2));
+ rect.setAttribute('stroke-dasharray', `${progressLen.toFixed(2)} ${(perim - progressLen).toFixed(2)}`);
+ rect.setAttribute('stroke-dashoffset', (perim - S).toFixed(2));
// Original-Rahmen verstecken während Ring aktiv ist
statusbar.style.border = 'none';
if (progress >= 100) {
+ _ringClosing = true;
setTimeout(() => {
const s = document.getElementById('map-scan-ring');
if (s) s.style.opacity = '0';
statusbar.style.border = '';
- setTimeout(() => s?.remove(), 600);
+ setTimeout(() => { s?.remove(); _ringClosing = false; }, 600);
}, 500);
}
}
@@ -517,7 +618,8 @@ window.Page_map = (() => {
}
_overpassActive = true;
- const b = _map.getBounds();
+ _map.invalidateSize();
+ const b = _map.getBounds().pad(0.15);
const bbox = { south: b.getSouth(), west: b.getWest(), north: b.getNorth(), east: b.getEast() };
// Welche Layer bei diesem Zoom geladen werden
diff --git a/backend/static/js/pages/settings.js b/backend/static/js/pages/settings.js
index 7f9a172..385caee 100644
--- a/backend/static/js/pages/settings.js
+++ b/backend/static/js/pages/settings.js
@@ -246,7 +246,7 @@ window.Page_settings = (() => {
try {
const fd = new FormData();
fd.append('file', file);
- const res = await API.post('/api/profile/avatar', fd);
+ const res = await API.post('/profile/avatar', fd);
_appState.user.avatar_url = res.avatar_url;
UI.toast.success('Avatar aktualisiert.');
_render();
@@ -326,7 +326,7 @@ window.Page_settings = (() => {
const btn = document.querySelector('[form="profile-form"]');
const fd = UI.formData(e.target);
await UI.asyncButton(btn, async () => {
- const updated = await API.patch('/api/profile', {
+ const updated = await API.patch('/profile', {
bio: fd.bio || '',
wohnort: fd.wohnort || '',
erfahrung: fd.erfahrung || '',
diff --git a/backend/static/js/pages/trainingsplaene.js b/backend/static/js/pages/trainingsplaene.js
index b074ad4..037a559 100644
--- a/backend/static/js/pages/trainingsplaene.js
+++ b/backend/static/js/pages/trainingsplaene.js
@@ -124,7 +124,8 @@ window.Page_trainingsplaene = (() => {
];
const btns = plans.map(p => `
`).join('');
diff --git a/backend/static/sw.js b/backend/static/sw.js
index bbe91f9..5f08bc8 100644
--- a/backend/static/sw.js
+++ b/backend/static/sw.js
@@ -3,7 +3,7 @@
Offline-Cache + Push Notifications + Tile-Cache
============================================================ */
-const CACHE_VERSION = 'by-v123';
+const CACHE_VERSION = 'by-v141';
const CACHE_STATIC = `${CACHE_VERSION}-static`;
const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten