Sprint D: Karten-Familie auf UI.map.create+svgMarker konsolidiert, SW by-v1107

Neue zentrale Helper (in Sprint B vorbereitet) jetzt von 5 Seiten genutzt:

walks.js (1 Karten-Init):
- L.map+L.tileLayer → await UI.map.create('walks-map', {...})
- _initMap zu async, Aufrufer in _switchView und _loadData angepasst
- Mini-Karte im Walk-Formular (Modal) bleibt unverändert
  (braucht eigene dragging/scrollWheelZoom-Options)
- view-toggle nicht migriert (responsive CSS-Konflikt mit Desktop)

poison.js (1 Karten-Init):
- L.map+L.tileLayer → await UI.map.create('poison-map', {...})
- _initMap zu async, manueller UI.loadLeaflet entfernt
- DangerCircle + User-Marker unverändert

events.js (1 Karten-Init + Diamant-Marker):
- await UI.map.create('ev-map', {...})
- Rotierter Diamant: L.divIcon+L.marker → UI.map.svgMarker
  (HTML 1:1 erhalten)

lost.js (1 Karten-Init + Puls-Marker):
- Eigene async _loadLeaflet() Funktion komplett entfernt — UI.map.create
  übernimmt das jetzt zentral
- await UI.map.create('lost-map', {...})
- Puls-Animation 🐕: L.divIcon+L.marker → UI.map.svgMarker
- _initMap zu async

routes.js (6 von 7 Karten-Inits):
- _suggestMap, _recMap, _searchMap, _navMap, trimMap, _buildDetailMap
  alle auf UI.map.create umgestellt + zu async
- _buildMiniMap (Route-Card-Preview) bleibt unverändert
  (braucht 6 spezifische Interaction-Disable Options)
- View-Toggle auf neue .map-list-toggle Klasse umgestellt
  (Border-Inline-Styles raus)

NEUE CSS-KLASSE in components.css:
- .map-list-toggle (vereinheitlichter Karten/Listen-Umschalter)
- Verwendet von routes.js; walks/events können später folgen

Tests 19/19 grün. GPS-Tracking-Logik (Polylines, Recording, Trim)
komplett unangetastet. Marker-Cluster-Logik unverändert.
This commit is contained in:
rene 2026-05-27 08:17:06 +02:00
parent c8ef4939f1
commit 73872e2c21
11 changed files with 130 additions and 152 deletions

View file

@ -1 +1 @@
1106
1107

View file

@ -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.
<div class="map-list-toggle">
<button class="active" data-view="list">Liste</button>
<button data-view="map">Karte</button>
</div>
============================================================ */
.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);
}

View file

@ -86,14 +86,14 @@
<title>Ban Yaro</title>
<!-- Theme + theme-color Statusleiste vor CSS setzen -->
<script src="/js/boot-early.js?v=1106"></script>
<script src="/js/boot-early.js?v=1107"></script>
<!-- CSS: Reihenfolge ist wichtig — ?v= zwingt Browser zur Neuladung -->
<link rel="stylesheet" href="/css/design-system.css?v=1106">
<link rel="stylesheet" href="/css/layout.css?v=1106">
<link rel="stylesheet" href="/css/components.css?v=1106">
<link rel="stylesheet" href="/css/utilities.css?v=1106">
<link rel="stylesheet" href="/css/lists.css?v=1106">
<link rel="stylesheet" href="/css/design-system.css?v=1107">
<link rel="stylesheet" href="/css/layout.css?v=1107">
<link rel="stylesheet" href="/css/components.css?v=1107">
<link rel="stylesheet" href="/css/utilities.css?v=1107">
<link rel="stylesheet" href="/css/lists.css?v=1107">
</head>
<body>
@ -617,11 +617,11 @@
<div id="modal-container"></div>
<!-- JS: Reihenfolge ist wichtig — erst Basis, dann Features -->
<script src="/js/api.js?v=1106"></script>
<script src="/js/ui.js?v=1106"></script>
<script src="/js/app.js?v=1106"></script>
<script src="/js/worlds.js?v=1106"></script>
<script src="/js/offline-indicator.js?v=1106"></script>
<script src="/js/api.js?v=1107"></script>
<script src="/js/ui.js?v=1107"></script>
<script src="/js/app.js?v=1107"></script>
<script src="/js/worlds.js?v=1107"></script>
<script src="/js/offline-indicator.js?v=1107"></script>
<!-- Feature-Seiten werden lazy geladen -->
@ -631,7 +631,7 @@
<!-- Boot: Offline-Banner + SW-Registration (extrahiert für CSP) -->
<script src="/js/boot.js?v=1106"></script>
<script src="/js/boot.js?v=1107"></script>
</body>

View file

@ -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;

View file

@ -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: `<div style="width:32px;height:32px;border-radius:50% 50% 50% 0;background:${color};border:2px solid #fff;display:flex;align-items:center;justify-content:center;font-size:14px;box-shadow:0 2px 6px rgba(0,0,0,0.3);transform:rotate(-45deg);color:#fff"><svg style="width:14px;height:14px;transform:rotate(45deg);fill:currentColor" viewBox="0 0 256 256"><use href="/icons/phosphor.svg#${typ.icon}"></use></svg></div>`,
iconSize: [32, 32], iconAnchor: [16, 32],
});
// Events nutzen rotierten Diamant-Marker (nicht Kreis) — UI.map.svgMarker mit custom HTML
const html = `<div style="width:32px;height:32px;border-radius:50% 50% 50% 0;background:${color};border:2px solid #fff;display:flex;align-items:center;justify-content:center;font-size:14px;box-shadow:0 2px 6px rgba(0,0,0,0.3);transform:rotate(-45deg);color:#fff"><svg style="width:14px;height:14px;transform:rotate(45deg);fill:currentColor" viewBox="0 0 256 256"><use href="/icons/phosphor.svg#${typ.icon}"></use></svg></div>`;
const popup = `
<div style="min-width:180px">
<strong>${UI.escape(ev.titel)}</strong><br>
@ -282,7 +280,7 @@ window.Page_events = (() => {
style="font-size:12px;color:var(--c-primary,#2563eb)">Details</a>
</div>
`;
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]);

View file

@ -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 : `<div style="background:${dotColor};color:#fff;border-radius:50%;
const html = `<div style="background:${dotColor};color:#fff;border-radius:50%;
width:34px;height:34px;
display:flex;align-items:center;justify-content:center;
font-size:17px;border:2px solid #fff;
animation:${anim} 1.8s ease-in-out infinite">🐕</div>`,
iconSize : [34, 34],
iconAnchor : [17, 17],
});
animation:${anim} 1.8s ease-in-out infinite">🐕</div>`;
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(`
<b>🔍 ${_escape(r.name)}</b><br>

View file

@ -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,
});
}
// ----------------------------------------------------------

View file

@ -193,20 +193,15 @@ window.Page_routes = (() => {
background:var(--c-surface);color:var(--c-text);outline:none;
box-sizing:border-box;">
</div>
<div style="display:flex;border:1.5px solid var(--c-border);border-radius:10px;
overflow:hidden;flex-shrink:0;height:46px;box-sizing:border-box">
<div class="map-list-toggle" style="flex-shrink:0;width:auto">
<button id="rk-view-list" title="Liste"
style="width:44px;height:100%;border:none;cursor:pointer;
display:flex;align-items:center;justify-content:center;
background:${_viewMode==='list' ? 'var(--c-primary)' : 'var(--c-surface)'};
color:${_viewMode==='list' ? '#fff' : 'var(--c-text-secondary)'}">
class="${_viewMode==='list' ? 'active' : ''}"
style="flex:0 0 auto;width:44px">
${UI.icon('list')}
</button>
<button id="rk-view-map" title="Karte"
style="width:44px;height:100%;border:none;border-left:1.5px solid var(--c-border);
cursor:pointer;display:flex;align-items:center;justify-content:center;
background:${_viewMode==='map' ? 'var(--c-primary)' : 'var(--c-surface)'};
color:${_viewMode==='map' ? '#fff' : 'var(--c-text-secondary)'}">
class="${_viewMode==='map' ? 'active' : ''}"
style="flex:0 0 auto;width:44px">
${UI.icon('map-trifold')}
</button>
</div>
@ -602,16 +597,18 @@ window.Page_routes = (() => {
</button>
</div>`;
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);

View file

@ -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();
}

View file

@ -4,7 +4,7 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="color-scheme" content="light dark">
<script src="/js/landing-init.js?v=1106"></script>
<script src="/js/landing-init.js?v=1107"></script>
<title>Ban Yaro — Die Hunde-App für Deutschland, Österreich & Schweiz</title>
<meta name="description" content="Ban Yaro: Die kostenlose All-in-One Hunde-App für DACH. Tagebuch, Giftköder-Alarm, Training mit KI, Forum, Wurfbörse, Stammbaum, Inzucht-Check — DSGVO-konform, offline-fähig, ohne App Store.">
<meta name="keywords" content="Hunde App, Hunde Community, Wurfbörse, Züchter, Welpen kaufen, Stammbaum Hund, Inzuchtkoeffizient, Hundezucht, Impfpass Hund, Giftköder Alarm, Gassi Community, Hundetraining App, Hunde Forum, Hunde KI, Hundefilm Datenbank, Welpen Marktplatz">

View file

@ -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