diff --git a/VERSION b/VERSION
index 5642d7e..6a333d3 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-1106
\ No newline at end of file
+1107
\ No newline at end of file
diff --git a/backend/static/css/components.css b/backend/static/css/components.css
index 69113a5..309f5e4 100644
--- a/backend/static/css/components.css
+++ b/backend/static/css/components.css
@@ -8944,3 +8944,44 @@ svg.empty-state-icon {
.offline-status-row .osr-text { flex: 1; min-width: 0; }
.offline-status-row .osr-title { font-weight: 600; }
.offline-status-row .osr-detail { font-size: var(--text-xs); color: var(--c-text-muted); margin-top: 2px; }
+
+/* ============================================================
+ .map-list-toggle — vereinheitlichter Karten/Listen-Umschalter
+ Verwendet von walks.js, events.js, routes.js, etc.
+
+
+
+
+
+ ============================================================ */
+.map-list-toggle {
+ display: flex;
+ border: 1.5px solid var(--c-border);
+ border-radius: var(--radius-md);
+ overflow: hidden;
+ background: var(--c-surface);
+}
+.map-list-toggle button {
+ flex: 1;
+ height: 44px;
+ border: none;
+ background: transparent;
+ color: var(--c-text-secondary);
+ cursor: pointer;
+ font-size: var(--text-sm);
+ font-weight: var(--weight-medium);
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: var(--space-1);
+ transition: background 0.15s, color 0.15s;
+ -webkit-tap-highlight-color: transparent;
+}
+.map-list-toggle button.active {
+ background: var(--c-primary);
+ color: #fff;
+}
+.map-list-toggle button:not(.active):hover {
+ background: var(--c-surface-2);
+ color: var(--c-text);
+}
diff --git a/backend/static/index.html b/backend/static/index.html
index 113e542..8306b18 100644
--- a/backend/static/index.html
+++ b/backend/static/index.html
@@ -86,14 +86,14 @@
Ban Yaro
-
+
-
-
-
-
-
+
+
+
+
+
@@ -617,11 +617,11 @@
-
-
-
-
-
+
+
+
+
+
@@ -631,7 +631,7 @@
-
+
diff --git a/backend/static/js/app.js b/backend/static/js/app.js
index 08770c9..20fe464 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 = '1106'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
+const APP_VER = '1107'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
const APP_VERSION = '1.6.0'; // ← semantische Version, wird bei make release gesetzt
window.APP_VER = APP_VER; // global verfügbar für andere Module (z.B. offline-indicator)
window.APP_VERSION = APP_VERSION;
diff --git a/backend/static/js/pages/events.js b/backend/static/js/pages/events.js
index a170280..afb7e62 100644
--- a/backend/static/js/pages/events.js
+++ b/backend/static/js/pages/events.js
@@ -248,8 +248,10 @@ window.Page_events = (() => {
await UI.loadLeaflet(true); // true = mit MarkerCluster
if (!_map) {
- _map = L.map('ev-map', { zoomControl: true }).setView([51.1657, 10.4515], 6);
- L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 19 }).addTo(_map);
+ _map = await UI.map.create('ev-map', {
+ center: [51.1657, 10.4515], zoom: 6,
+ zoomControl: true, attributionControl: false,
+ });
}
// Cluster-Gruppe aufräumen und neu befüllen
@@ -266,12 +268,8 @@ window.Page_events = (() => {
const typ = TYPEN.find(t => t.id === ev.typ) || TYPEN[TYPEN.length - 1];
const d = new Date(ev.datum + 'T00:00:00');
const datum = d.toLocaleDateString('de-DE', { day: 'numeric', month: 'short', year: 'numeric' });
- // Events nutzen rotierten Diamant-Marker (nicht Kreis) — UI.leafletMarker() nicht anwendbar
- const icon = L.divIcon({
- className: '',
- html: ``,
- iconSize: [32, 32], iconAnchor: [16, 32],
- });
+ // Events nutzen rotierten Diamant-Marker (nicht Kreis) — UI.map.svgMarker mit custom HTML
+ const html = ``;
const popup = `
${UI.escape(ev.titel)}
@@ -282,7 +280,7 @@ window.Page_events = (() => {
style="font-size:12px;color:var(--c-primary,#2563eb)">Details
`;
- const m = L.marker([ev.lat, ev.lon], { icon }).bindPopup(popup);
+ const m = UI.map.svgMarker(ev.lat, ev.lon, html, { size: 32, anchorY: 32 }).bindPopup(popup);
_clusterGroup.addLayer(m);
_markers.push(m);
bounds.push([ev.lat, ev.lon]);
diff --git a/backend/static/js/pages/lost.js b/backend/static/js/pages/lost.js
index ad6de69..10b96d3 100644
--- a/backend/static/js/pages/lost.js
+++ b/backend/static/js/pages/lost.js
@@ -130,54 +130,24 @@ window.Page_lost = (() => {
document.getElementById('lost-btn-report')
?.addEventListener('click', _showReportForm);
- await _loadLeaflet();
- _initMap();
+ await _initMap();
setTimeout(() => _map?.invalidateSize(), 100);
await _locateAndLoad();
}
// ----------------------------------------------------------
- // LEAFLET DYNAMISCH LADEN
+ // KARTE INITIALISIEREN (lädt Leaflet via UI.map.create)
// ----------------------------------------------------------
- async function _loadLeaflet() {
- if (_leafletLoaded || window.L) { _leafletLoaded = true; return; }
-
- await new Promise(resolve => {
- if (document.querySelector('link[href*="leaflet"]')) { resolve(); return; }
- const link = document.createElement('link');
- link.rel = 'stylesheet';
- link.href = '/css/leaflet.css';
- link.onload = resolve;
- link.onerror = resolve;
- document.head.appendChild(link);
- });
-
- await new Promise((resolve, reject) => {
- if (document.querySelector('script[src*="leaflet"]')) { resolve(); return; }
- const s = document.createElement('script');
- s.src = '/js/leaflet.js';
- s.onload = resolve;
- s.onerror = reject;
- document.head.appendChild(s);
- });
-
- _leafletLoaded = true;
- }
-
- // ----------------------------------------------------------
- // KARTE INITIALISIEREN
- // ----------------------------------------------------------
- function _initMap() {
+ async function _initMap() {
_injectStyles();
const mapEl = document.getElementById('lost-map');
- if (!mapEl || !window.L || _map) return;
+ if (!mapEl || _map) return;
- _map = L.map('lost-map', { zoomControl: true, attributionControl: false })
- .setView([51.1657, 10.4515], 6);
-
- L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
- maxZoom: 19,
- }).addTo(_map);
+ _map = await UI.map.create('lost-map', {
+ center: [51.1657, 10.4515], zoom: 6,
+ zoomControl: true, attributionControl: false,
+ });
+ _leafletLoaded = true;
}
// ----------------------------------------------------------
@@ -303,22 +273,17 @@ window.Page_lost = (() => {
_reports.forEach(r => {
const dotColor = r._isPending ? '#d97706' : '#e74c3c';
const anim = r._isPending ? 'by-lost-pulse-p' : 'by-lost-pulse-r';
- const icon = L.divIcon({
- className : '',
- html : `🐕
`,
- iconSize : [34, 34],
- iconAnchor : [17, 17],
- });
+ animation:${anim} 1.8s ease-in-out infinite">🐕`;
const distStr = r.distanz_m !== undefined
? (r.distanz_m < 1000 ? `${Math.round(r.distanz_m)} m` : `${(r.distanz_m / 1000).toFixed(1)} km`)
: '';
- const marker = L.marker([r.lat, r.lon], { icon })
+ const marker = UI.map.svgMarker(r.lat, r.lon, html, { size: 34, anchorY: 17 })
.addTo(_map)
.bindPopup(`
🔍 ${_escape(r.name)}
diff --git a/backend/static/js/pages/poison.js b/backend/static/js/pages/poison.js
index 6547a25..f9d1151 100644
--- a/backend/static/js/pages/poison.js
+++ b/backend/static/js/pages/poison.js
@@ -94,8 +94,7 @@ window.Page_poison = (() => {
document.getElementById('poison-btn-erstehilfe')
?.addEventListener('click', () => App.navigate('erste-hilfe', true, { tab: 'lebensgefahr' }));
- await UI.loadLeaflet();
- _initMap();
+ await _initMap();
// Leaflet muss nach CSS-Load die Container-Größe neu berechnen
setTimeout(() => _map?.invalidateSize(), 100);
await _locateAndLoad();
@@ -104,17 +103,16 @@ window.Page_poison = (() => {
// ----------------------------------------------------------
// KARTE INITIALISIEREN
// ----------------------------------------------------------
- function _initMap() {
+ async function _initMap() {
const mapEl = document.getElementById('poison-map');
- if (!mapEl || !window.L || _map) return;
-
- _map = L.map('poison-map', { zoomControl: true, attributionControl: false })
- .setView([51.1657, 10.4515], 6); // Deutschland-Mitte
-
- L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
- maxZoom: 19,
- }).addTo(_map);
+ if (!mapEl || _map) return;
+ _map = await UI.map.create('poison-map', {
+ center: [51.1657, 10.4515], // Deutschland-Mitte
+ zoom: 6,
+ zoomControl: true,
+ attributionControl: false,
+ });
}
// ----------------------------------------------------------
diff --git a/backend/static/js/pages/routes.js b/backend/static/js/pages/routes.js
index 1f5bc33..acb48c6 100644
--- a/backend/static/js/pages/routes.js
+++ b/backend/static/js/pages/routes.js
@@ -193,20 +193,15 @@ window.Page_routes = (() => {
background:var(--c-surface);color:var(--c-text);outline:none;
box-sizing:border-box;">
-
+
@@ -602,16 +597,18 @@ window.Page_routes = (() => {
`;
- const _initMap = () => {
+ const _initMap = async () => {
const mapEl = document.getElementById('rks-map');
- if (!mapEl || !window.L) return;
+ if (!mapEl) return;
if (_suggestMap) { _suggestMap.remove(); _suggestMap = null; }
const track = result.gps_track || [];
if (track.length < 2) return;
const lls = track.map(p => [p.lat, p.lon]);
- _suggestMap = L.map(mapEl, { zoomControl: false, attributionControl: false,
- dragging: true, touchZoom: true, scrollWheelZoom: false });
- L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 19 }).addTo(_suggestMap);
+ _suggestMap = await UI.map.create(mapEl, {
+ center: lls[0], zoom: 14,
+ zoomControl: false, attributionControl: false,
+ });
+ _suggestMap.scrollWheelZoom.disable();
const poly = L.polyline(lls, { color: '#C4843A', weight: 4, opacity: 0.9 }).addTo(_suggestMap);
L.circleMarker(lls[0], { radius:7, color:'#22C55E', fillColor:'#22C55E', fillOpacity:1, weight:2 }).addTo(_suggestMap);
L.circleMarker(lls.at(-1), { radius:7, color:'#EF4444', fillColor:'#EF4444', fillOpacity:1, weight:2 }).addTo(_suggestMap);
@@ -619,12 +616,7 @@ window.Page_routes = (() => {
_suggestMap.fitBounds(poly.getBounds(), { padding: [16, 16] });
setTimeout(() => _suggestMap?.invalidateSize(), 120);
};
- if (window.L) { _initMap(); } else {
- let tries = 0;
- const poll = setInterval(() => {
- if (window.L || ++tries > 40) { clearInterval(poll); if (window.L) _initMap(); }
- }, 100);
- }
+ _initMap();
document.getElementById('rks-nav-btn')?.addEventListener('click', () => {
_openNavOverlay({ id: 'suggest-' + Date.now(), name: result.name,
@@ -668,8 +660,6 @@ window.Page_routes = (() => {
if (!_appState.user) { UI.toast.warning('Bitte anmelden.'); return; }
if (_recOvl) return;
- try { await (UI.loadLeaflet?.() ?? Promise.resolve()); }
- catch { UI.toast.warning('Karte offline nicht verfügbar — GPS-Aufzeichnung läuft trotzdem.'); }
const ovl = document.createElement('div');
ovl.id = 'rk-rec-ovl';
@@ -733,10 +723,10 @@ window.Page_routes = (() => {
// Map-Setup: Leaflet könnte offline fehlen → alles in try/catch
const pos = _userPos || { lat: 48.1, lon: 11.5 };
try {
- if (!window.L) throw new Error('Leaflet not loaded');
- _recMap = L.map(ovl.querySelector('#rk-rec-map-wrap'), { zoomControl: false, attributionControl: false })
- .setView([pos.lat, pos.lon], 15);
- L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 19 }).addTo(_recMap);
+ _recMap = await UI.map.create(ovl.querySelector('#rk-rec-map-wrap'), {
+ center: [pos.lat, pos.lon], zoom: 15,
+ zoomControl: false, attributionControl: false,
+ });
_recLocMarker = L.circleMarker([pos.lat, pos.lon], {
radius: 8, color: '#fff', weight: 2.5, fillColor: '#3b82f6', fillOpacity: 1
}).addTo(_recMap);
@@ -1127,8 +1117,7 @@ window.Page_routes = (() => {
document.body.appendChild(sec);
document.getElementById('rk-map-back')?.addEventListener('click', () => _switchView('list'));
- // Wie _initMiniMaps: pollen bis window.L bereit ist
- _pollAndInitSearchMap();
+ _initSearchMap();
} else {
document.getElementById('rk-map-section')?.remove();
@@ -1140,26 +1129,16 @@ window.Page_routes = (() => {
// ----------------------------------------------------------
// Suchkarte
// ----------------------------------------------------------
- function _pollAndInitSearchMap() {
- if (window.L) { _initSearchMap(); return; }
- let tries = 0;
- const poll = setInterval(() => {
- if (window.L || ++tries > 40) {
- clearInterval(poll);
- if (window.L) _initSearchMap();
- }
- }, 100);
- }
-
- function _initSearchMap() {
+ async function _initSearchMap() {
if (!document.getElementById('rk-search-map')) return;
const center = _userPos ? [_userPos.lat, _userPos.lon] : [51.1, 10.4];
const zoom = _userPos ? 13 : 6;
- _searchMap = L.map('rk-search-map', { zoomControl: true, attributionControl: false })
- .setView(center, zoom);
- L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 19 }).addTo(_searchMap);
+ _searchMap = await UI.map.create('rk-search-map', {
+ center, zoom,
+ zoomControl: true, attributionControl: false,
+ });
setTimeout(() => _searchMap?.invalidateSize(), 100);
setTimeout(() => _searchMap?.invalidateSize(), 600);
_renderRoutesOnMap();
@@ -1586,8 +1565,6 @@ window.Page_routes = (() => {
try { await DeviceOrientationEvent.requestPermission(); } catch {}
}
- await UI.loadLeaflet?.() ?? Promise.resolve();
-
const ovl = document.createElement('div');
ovl.id = 'rk-nav-ovl';
ovl.style.cssText = 'position:fixed;inset:0;z-index:850;display:flex;flex-direction:column;background:var(--c-bg)';
@@ -1700,9 +1677,10 @@ window.Page_routes = (() => {
// Karte initialisieren
const mapEl = document.getElementById('rk-nav-map');
const mid = track[Math.floor(track.length / 2)];
- _navMap = L.map(mapEl, { zoomControl: false, attributionControl: false })
- .setView([mid.lat, mid.lon], 15);
- L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 19 }).addTo(_navMap);
+ _navMap = await UI.map.create(mapEl, {
+ center: [mid.lat, mid.lon], zoom: 15,
+ zoomControl: false, attributionControl: false,
+ });
// Route-Polylines: erledigt (grün) + ausstehend (orange)
const doneLine = L.polyline([], { color: '#22c55e', weight: 5, opacity: 0.85 }).addTo(_navMap);
@@ -2106,12 +2084,12 @@ window.Page_routes = (() => {
document.body.appendChild(ovl);
// Map initialisieren
- await UI.loadLeaflet?.() ?? Promise.resolve();
const mapEl = document.getElementById('rk-trim-map');
const center = fullTrack[Math.floor(fullTrack.length/2)];
- const trimMap = L.map(mapEl, { zoomControl: false, attributionControl: false })
- .setView([center.lat, center.lon], 14);
- L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 19 }).addTo(trimMap);
+ const trimMap = await UI.map.create(mapEl, {
+ center: [center.lat, center.lon], zoom: 14,
+ zoomControl: false, attributionControl: false,
+ });
// Marker & Polylines
let greyBefore = L.polyline([], { color: '#9ca3af', weight: 3, opacity: 0.5 }).addTo(trimMap);
@@ -2368,9 +2346,9 @@ window.Page_routes = (() => {
route.gps_track = [...route.gps_track].reverse();
// Karte neu aufbauen mit umgekehrtem Track
const el = document.getElementById('rk-detail-map');
- if (el && window.L) {
+ if (el) {
if (_detailMap) { _detailMap.remove(); _detailMap = null; }
- _detailMap = _buildDetailMap(el, route.gps_track);
+ _detailMap = await _buildDetailMap(el, route.gps_track);
}
UI.toast.success('Route dauerhaft umgekehrt');
} catch (err) { UI.toast.error(err.message); }
@@ -2424,10 +2402,10 @@ window.Page_routes = (() => {
// Mini-Map
let _detailMap = null;
- setTimeout(() => {
+ setTimeout(async () => {
const el = document.getElementById('rk-detail-map');
if (!el || !track.length) return;
- if (window.L) _detailMap = _buildDetailMap(el, track);
+ _detailMap = await _buildDetailMap(el, track);
}, 80);
// Nearby POIs laden
@@ -2546,10 +2524,12 @@ window.Page_routes = (() => {
}
}
- function _buildDetailMap(el, track) {
- const m = L.map(el, { zoomControl: false, attributionControl: false });
- L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 19 }).addTo(m);
+ async function _buildDetailMap(el, track) {
const lls = track.map(p => [p.lat, p.lon]);
+ const m = await UI.map.create(el, {
+ center: lls[0], zoom: 14,
+ zoomControl: false, attributionControl: false,
+ });
const poly = L.polyline(lls, { color: '#C4843A', weight: 4, opacity: 0.85 }).addTo(m);
_addRouteArrows(m, track, '#3b82f6');
L.circleMarker(lls[0], { radius:7, color:'#22C55E', fillColor:'#22C55E', fillOpacity:1 }).addTo(m);
diff --git a/backend/static/js/pages/walks.js b/backend/static/js/pages/walks.js
index 8b6dc24..e56f569 100644
--- a/backend/static/js/pages/walks.js
+++ b/backend/static/js/pages/walks.js
@@ -222,8 +222,7 @@ window.Page_walks = (() => {
document.getElementById('walks-map-view').style.display = view === 'karte' ? '' : 'none';
if (view === 'karte') {
- UI.loadLeaflet().then(() => {
- _initMap();
+ _initMap().then(() => {
setTimeout(() => _map?.invalidateSize(), 150);
setTimeout(() => _map?.invalidateSize(), 400);
});
@@ -245,8 +244,7 @@ window.Page_walks = (() => {
_renderList();
_renderMarkers();
if (window.innerWidth >= 1024) {
- UI.loadLeaflet().then(() => {
- _initMap();
+ _initMap().then(() => {
setTimeout(() => _map?.invalidateSize(), 150);
setTimeout(() => _map?.invalidateSize(), 400);
});
@@ -363,13 +361,11 @@ window.Page_walks = (() => {
// ----------------------------------------------------------
// Leaflet + Karte
// ----------------------------------------------------------
- function _initMap() {
+ async function _initMap() {
const el = document.getElementById('walks-map');
- if (!el || !window.L || _map) return;
+ if (!el || _map) return;
const center = _userPos ? [_userPos.lat, _userPos.lon] : [51.1657, 10.4515];
- _map = L.map('walks-map', { zoomControl: true, attributionControl: false })
- .setView(center, _userPos ? 12 : 6);
- L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 19 }).addTo(_map);
+ _map = await UI.map.create('walks-map', { center, zoom: _userPos ? 12 : 6 });
_renderMarkers();
}
diff --git a/backend/static/landing.html b/backend/static/landing.html
index f5e3ad8..568d8bd 100644
--- a/backend/static/landing.html
+++ b/backend/static/landing.html
@@ -4,7 +4,7 @@
-
+
Ban Yaro — Die Hunde-App für Deutschland, Österreich & Schweiz
diff --git a/backend/static/sw.js b/backend/static/sw.js
index d0ef7bf..d02565b 100644
--- a/backend/static/sw.js
+++ b/backend/static/sw.js
@@ -4,7 +4,7 @@
============================================================ */
// ← EINZIGE Stelle für die Version — STATIC_ASSETS und CACHE_VERSION leiten sich ab
-const VER = '1106';
+const VER = '1107';
const CACHE_VERSION = `by-v${VER}`;
const CACHE_STATIC = `${CACHE_VERSION}-static`;
const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten