/* ============================================================
   BAN YARO — Zentrale Karte
   Layer: eigene Orte, Giftköder, OSM-POIs, Community-Pins
   Features: Clustering, Standort-Dot, Gefahren-Radius
   ============================================================ */

window.Page_map = (() => {

  let _container     = null;
  let _appState      = null;
  let _map           = null;
  let _leafletLoaded = false;
  let _userPos       = null;
  let _placingMarker = false;
  let _tempMarker    = null;

  // Standort-Tracking
  let _locationMarker   = null;
  let _locationAccuracy = null;
  let _watchId          = null;

  // GPS-Aufzeichnung
  let _recActive    = false;
  let _recPaused    = false;
  let _wakeLock     = null;
  let _recTrack     = [];
  let _recDistKm    = 0;
  let _recStartTime = null;
  let _recTimerInt  = null;
  let _recPolyline  = null;
  let _pocketOverlay = null;
  let _pocketHideTimer = null;
  let _recMarker    = null;
  let _recWatchId   = null;

  // Cluster-Gruppen pro Layer (für OSM-Marker)
  let _clusterGroups = {};

  // Layer-Marker (Arrays von Leaflet-Markern)
  let _layers = {
    restaurant:  [],
    freilauf:    [],
    shop:        [],
    kotbeutel:   [],
    tierarzt:    [],
    hundeschule: [],
    poison:      [],
    muell:       [],
    dog_park:    [],
    wasser:      [],
    bank:        [],
    giftkoeder:  [],
    gefahr:      [],
    parkplatz:   [],
    treffpunkt:  [],
    community:   [],
    zuechter:    [],
  };

  const VISIBLE_KEY = 'by_map_visible_v1';

  let _visible = {};
  // Gespeicherten Zustand laden, Fallback: alles sichtbar
  (() => {
    const saved = (() => { try { return JSON.parse(localStorage.getItem(VISIBLE_KEY) || 'null'); } catch { return null; } })();
    Object.keys(_layers).forEach(k => {
      _visible[k] = saved ? (saved[k] !== false) : true;
    });
  })();

  function _saveVisible() {
    try { localStorage.setItem(VISIBLE_KEY, JSON.stringify(_visible)); } catch {}
  }

  // z: zIndexOffset — höher = weiter oben bei Überlappung
  const TYPEN = {
    restaurant:  { icon: '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#fork-knife"></use></svg>',       label: 'Restaurant',     color: '#F97316', z:  10 },
    freilauf:    { icon: '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#dog"></use></svg>',              label: 'Freilauf',        color: '#22C55E', z:  20 },
    shop:        { icon: '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#shopping-cart"></use></svg>',    label: 'Shop',            color: '#3B82F6', z:  15 },
    kotbeutel:   { icon: '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#bag"></use></svg>',              label: 'Kotbeutel',      color: '#84A98C', z:   5 },
    tierarzt:    { icon: '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#first-aid"></use></svg>',        label: 'Tierarzt',        color: '#EF4444', z:  40 },
    hundeschule: { icon: '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#graduation-cap"></use></svg>',   label: 'Hundeschule',     color: '#8B5CF6', z:  30 },
    poison:      { icon: '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#warning"></use></svg>',          label: 'Giftköder',      color: '#DC2626', z: 100 },
    muell:       { icon: '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#trash"></use></svg>',            label: 'Mülleimer',       color: '#6B7280', z: -20 },
    dog_park:    { icon: '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#leaf"></use></svg>',             label: 'Hundewiese',      color: '#15803D', z:   5 },
    wasser:      { icon: '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#drop"></use></svg>',             label: 'Wasserstelle',    color: '#0EA5E9', z:  35 },
    bank:        { icon: '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#chair"></use></svg>',            label: 'Bank',             color: '#92400E', z: -30 },
    giftkoeder:  { icon: '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#skull"></use></svg>',            label: 'Giftköder',        color: '#DC2626', z:  80 },
    gefahr:      { icon: '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#warning"></use></svg>',          label: 'Gefahr',          color: '#F59E0B', z:  60 },
    parkplatz:   { icon: '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#car"></use></svg>',              label: 'Parkplatz',       color: '#2563EB', z:   5 },
    treffpunkt:  { icon: '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#handshake"></use></svg>',        label: 'Treffpunkt',      color: '#7C3AED', z:  25 },
    community:   { icon: '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#push-pin"></use></svg>',         label: 'Sonstiges',       color: '#F59E0B', z:  30 },
    zuechter:    { icon: '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#certificate"></use></svg>',      label: 'Züchter',         color: '#7C3AED', z:  50 },
  };

  // Frontend-Layer → Backend-Typ Mapping
  const OSM_LAYER_MAP = {
    muell:      'waste_basket',
    dog_park:   'dog_park',
    wasser:     'drinking_water',
    tierarzt:   'tierarzt',
    shop:       'shop',
    restaurant: 'restaurant',
    bank:       'bank',
    giftkoeder: 'giftkoeder',
    kotbeutel:  'kotbeutel',
    gefahr:     'gefahr',
    parkplatz:  'parkplatz',
    treffpunkt: 'treffpunkt',
    community:  'sonstiges',
  };

  // Gefahren-Radius-Kreis: prominente rote Fläche
  const DANGER_RADIUS = { poison: 100, giftkoeder: 100 };

  // Layer die schon ab Zoom 10 geladen werden (nicht erst ab 14)
  const EARLY_LAYERS = new Set(['giftkoeder']);
  const DANGER_CIRCLE_STYLE = {
    color: '#DC2626', fillColor: '#DC2626',
    fillOpacity: 0.12, weight: 2, dashArray: null,
    interactive: false,
  };

  let _overpassTimer  = null;
  let _overpassActive = false;
  let _ringClosing    = false;
  let _frankfurtTimer = null;
  let _autoRetryCount = 0;   // begrenzt Auto-Retry auf max 3x pro Kartenposition

  // ----------------------------------------------------------
  // INIT
  // ----------------------------------------------------------
  async function init(container, appState) {
    _container = container;
    _appState  = appState;
    Object.assign(_container.style, { padding: '0', overflow: 'hidden', position: 'relative', gap: '0' });
    _render();
    // Alle-Button Initialzustand
    const anyOnInit = Object.entries(_visible).some(([k, v]) => v && k !== 'giftkoeder');
    document.getElementById('map-legend-all')?.classList.toggle('all-off', !anyOnInit);
    await _loadLeaflet();
    _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 });
      _loadWeather(pos.lat, pos.lon);
    }).catch(() => {
      const btn = document.getElementById('map-locate-btn');
      if (btn) {
        btn.title = 'Standort nicht verfügbar';
        btn.style.opacity = '0.55';
        btn.innerHTML = '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#map-pin-slash"></use></svg>';
      }
    });
  }

  function refresh() {
    // Leaflet kennt die Container-Größe nach Seitenwechsel nicht — neu berechnen
    setTimeout(() => { _map?.invalidateSize(); _scheduleOsmLoad(); }, 150);
    setTimeout(() => _map?.invalidateSize(), 600);
    _loadAll();
  }
  function onDogChange() {}

  // ----------------------------------------------------------
  // RENDER
  // ----------------------------------------------------------
  function _render() {
    _container.innerHTML = `
      <div class="map-full-layout">

        <div class="map-legend" id="map-legend">
          <button class="map-legend-btn map-legend-all" id="map-legend-all" title="Alle ein-/ausblenden">
            <svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#list"></use></svg>
          </button>
          ${Object.entries(TYPEN).filter(([k]) => k !== 'giftkoeder').map(([k, t]) => `
            <button class="map-legend-btn${_visible[k] !== false ? ' active' : ''}" data-layer="${k}" style="--layer-color:${t.color}">
              ${t.icon} <span class="map-legend-label">${t.label}</span>
            </button>
          `).join('')}
        </div>

        <div id="central-map" class="map-full"></div>

        <!-- Fadenkreuz-Overlay (nur im Placement-Modus sichtbar) -->
        <div class="map-crosshair" id="map-crosshair">
          <div class="map-crosshair-pin"><svg class="ph-icon" aria-hidden="true" style="width:28px;height:28px"><use href="/icons/phosphor.svg#map-pin"></use></svg></div>
          <div class="map-crosshair-shadow"></div>
        </div>
        <div class="map-place-bar" id="map-place-bar">
          <span class="map-place-hint">Karte verschieben · Pin landet genau hier</span>
          <div class="map-place-btns">
            <button class="btn btn-secondary" id="map-place-cancel">Abbrechen</button>
            <button class="btn btn-primary" id="map-place-confirm"><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#map-pin"></use></svg> Hier platzieren</button>
          </div>
        </div>

        <div class="map-fabs">
          <button class="map-fab map-fab--pin" id="map-pin-btn" title="Marker setzen"><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#push-pin"></use></svg></button>
          <button class="map-fab" id="map-locate-btn" title="Meinen Standort"><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#map-pin"></use></svg></button>
        </div>

        <div class="map-statusbar" id="map-statusbar">
          <span id="map-zoom-info"></span>
          <span id="map-osm-status"></span>
          <span class="map-statusbar-sep map-weather-chip--hidden" id="map-weather-sep">·</span>
          <span class="map-weather-chip--hidden" id="map-weather-info"></span>
        </div>

        <!-- Aufzeichnungs-Panel (erscheint beim Start) -->
        <div class="map-rec-panel" id="map-rec-panel">
          <div class="map-rec-stats">
            <div class="map-rec-stat">
              <span class="map-rec-val" id="rec-stat-dist">0.00</span>
              <span class="map-rec-lbl">km</span>
            </div>
            <div class="map-rec-stat map-rec-stat--main">
              <span class="map-rec-val" id="rec-stat-time">00:00</span>
              <span class="map-rec-lbl">Zeit</span>
            </div>
            <div class="map-rec-stat">
              <span class="map-rec-val" id="rec-stat-pace">–:––</span>
              <span class="map-rec-lbl">min/km</span>
            </div>
          </div>
          <div class="map-rec-actions">
            <button class="btn btn-secondary map-rec-action-btn" id="rec-panel-pause">Pause</button>
            <button class="btn btn-danger map-rec-action-btn" id="rec-panel-stop"><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#floppy-disk"></use></svg> Speichern</button>
          </div>
          <div class="map-rec-hint" id="map-rec-hint">
            Bildschirm bleibt aktiv — GPS läuft
          </div>
        </div>

      </div>
    `;

    const legendBtns = Object.keys(TYPEN).filter(k => k !== 'giftkoeder').length + 1; // +1 Alle-Btn
    document.getElementById('map-legend')
      ?.style.setProperty('--map-legend-cols', Math.ceil(legendBtns / 2));

    document.getElementById('map-legend').addEventListener('click', e => {
      const btn = e.target.closest('.map-legend-btn');
      if (!btn) return;

      // "Alle"-Button
      if (btn.id === 'map-legend-all') {
        const anyOn = Object.entries(_visible)
          .some(([k, v]) => v && k !== 'giftkoeder');
        // Wenn irgendetwas sichtbar → alles aus; sonst alles an
        const newState = !anyOn;
        Object.entries(TYPEN).filter(([k]) => k !== 'giftkoeder').forEach(([k]) => {
          _visible[k] = newState;
          document.querySelector(`.map-legend-btn[data-layer="${k}"]`)
            ?.classList.toggle('active', newState);
          _applyVisibility(k);
        });
        btn.classList.toggle('all-off', !newState);
        _saveVisible();
        return;
      }

      const layer = btn.dataset.layer;
      _visible[layer] = !_visible[layer];
      btn.classList.toggle('active', _visible[layer]);
      _applyVisibility(layer);
      // Alle-Button-Zustand aktualisieren
      const anyOn = Object.entries(_visible).some(([k, v]) => v && k !== 'giftkoeder');
      document.getElementById('map-legend-all')?.classList.toggle('all-off', !anyOn);
      _saveVisible();
    });

    document.getElementById('map-locate-btn').addEventListener('click', () => {
      if (_userPos) {
        _map?.setView([_userPos.lat, _userPos.lon], 16);
      } else {
        UI.toast.error('Standort noch nicht verfügbar.');
      }
    });

    document.getElementById('map-pin-btn').addEventListener('click', _togglePlacementMode);
  }

  // ----------------------------------------------------------
  // Leaflet + MarkerCluster laden
  // ----------------------------------------------------------
  async function _loadLeaflet() {
    if (_leafletLoaded) return;

    // Leaflet-Basis: nur laden wenn noch nicht vorhanden (diary.js kann es vorgeladen haben)
    if (!window.L) {
      const lCss = document.createElement('link');
      lCss.rel = 'stylesheet'; lCss.href = '/css/leaflet.css';
      document.head.appendChild(lCss);

      await new Promise(resolve => {
        const s = document.createElement('script');
        s.src = '/js/leaflet.js'; s.onload = resolve;
        document.head.appendChild(s);
      });
    }

    // MarkerCluster: separat prüfen — diary.js lädt Leaflet ohne MarkerCluster
    if (!window.L.markerClusterGroup) {
      ['MarkerCluster.css', 'MarkerCluster.Default.css'].forEach(f => {
        const l = document.createElement('link');
        l.rel = 'stylesheet'; l.href = `/css/${f}`;
        document.head.appendChild(l);
      });

      await new Promise(resolve => {
        const s = document.createElement('script');
        s.src = '/js/leaflet.markercluster.js'; s.onload = resolve;
        document.head.appendChild(s);
      });
    }

    _leafletLoaded = true;
  }

  // ----------------------------------------------------------
  // Karte initialisieren
  // ----------------------------------------------------------
  function _initMap() {
    const el = document.getElementById('central-map');
    if (!el || !window.L || _map) return;

    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);
    setTimeout(() => _map.invalidateSize(), 600);
    window.addEventListener('resize', () => _map.invalidateSize());

    _map.on('moveend zoomend', () => { _autoRetryCount = 0; _updateZoomDisplay(); _scheduleOsmLoad(); });
    setTimeout(() => { _updateZoomDisplay(); _scheduleOsmLoad(); }, 800);

    // Fadenkreuz-Animation beim Kartenverschieben
    _map.on('movestart', () => {
      document.getElementById('map-crosshair')?.classList.add('dragging');
    });
    _map.on('moveend', () => {
      document.getElementById('map-crosshair')?.classList.remove('dragging');
    });
  }

  // ----------------------------------------------------------
  // Standort-Tracking — pulsierender blauer Punkt
  // ----------------------------------------------------------
  function _startLocationTracking() {
    if (!navigator.geolocation || !_map || !window.L) return;

    const icon = L.divIcon({
      className: 'loc-icon',
      html: '<div class="loc-dot"></div><div class="loc-ring"></div>',
      iconSize:   [24, 24],
      iconAnchor: [12, 12],
    });

    _watchId = navigator.geolocation.watchPosition(
      pos => {
        const { latitude: lat, longitude: lon, accuracy: acc } = pos.coords;
        _userPos = { lat, lon };
        if (_locationMarker) {
          _locationMarker.setLatLng([lat, lon]);
          _locationAccuracy?.setLatLng([lat, lon]).setRadius(acc);
        } else {
          _locationAccuracy = L.circle([lat, lon], {
            radius: acc, color: '#3B82F6', fillColor: '#3B82F6',
            fillOpacity: 0.1, weight: 1, interactive: false,
          }).addTo(_map);
          _locationMarker = L.marker([lat, lon], {
            icon, zIndexOffset: 500, interactive: false,
          }).addTo(_map);
        }
      },
      () => {},
      { enableHighAccuracy: true, maximumAge: 5000, timeout: 15000 }
    );
  }

  // ----------------------------------------------------------
  // Cluster-Gruppe holen / erstellen
  // ----------------------------------------------------------
  function _getCluster(layerKey) {
    if (!_clusterGroups[layerKey]) {
      _clusterGroups[layerKey] = L.markerClusterGroup({
        maxClusterRadius: 50,
        spiderfyOnMaxZoom: true,
        showCoverageOnHover: false,
        animate: true,
        chunkedLoading: true,
        iconCreateFunction: cluster => {
          const t = TYPEN[layerKey];
          const n = cluster.getChildCount();
          return L.divIcon({
            className: '',
            html: `<div style="background:${t.color};color:#fff;font-size:13px;font-weight:700;
              width:36px;height:36px;border-radius:50%;
              display:flex;align-items:center;justify-content:center;
              box-shadow:0 2px 6px rgba(0,0,0,0.4);
              border:2px solid rgba(255,255,255,0.8)">${n}</div>`,
            iconSize: [36, 36], iconAnchor: [18, 18],
          });
        },
      });
      if (_visible[layerKey] !== false) {
        _clusterGroups[layerKey].addTo(_map);
      }
    }
    return _clusterGroups[layerKey];
  }

  function _updateZoomDisplay() {
    if (!_map) return;
    const z  = Math.round(_map.getZoom());
    const el = document.getElementById('map-zoom-info');
    if (!el) return;
    if (z < 10)      { el.textContent = `Zoom ${z} · ab 10: Giftköder`;       el.style.opacity = '0.5'; }
    else if (z < 14) { el.textContent = `Zoom ${z} · ab 14: alle Layer`;      el.style.opacity = '0.7'; }
    else             { el.textContent = `Zoom ${z}`;                           el.style.opacity = '1';   }
  }

  function _setOsmStatus(text, pct = null) {
    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 = `
        <ellipse cx="33" cy="22" rx="14" ry="8" fill="currentColor"/>
        <ellipse cx="16" cy="20" rx="6"  ry="7" fill="currentColor"/>
        <ellipse cx="8"  cy="27" rx="7"  ry="6" fill="currentColor"/>
        <ellipse cx="14" cy="16" rx="3"  ry="5" fill="currentColor" transform="rotate(15,14,16)" opacity=".85"/>
        <ellipse cx="3"  cy="30" rx="3.5" ry="2.5" fill="currentColor"/>
        <ellipse cx="1.5" cy="29" rx="2" ry="1.5" fill="#7a4f2a"/>
        <circle  cx="9"  cy="24" r="1.3" fill="white" opacity=".9"/>
        <rect x="22" y="28" width="3" height="10" rx="1.5" fill="currentColor"/>
        <rect x="29" y="29" width="3" height="9"  rx="1.5" fill="currentColor"/>
        <rect x="37" y="28" width="3" height="9"  rx="1.5" fill="currentColor"/>
        <rect x="42" y="27" width="3" height="9"  rx="1.5" fill="currentColor"/>
        <g class="by-tail" transform="translate(47,17)">
          <path d="M0,0 Q6,-10 4,-18" stroke="currentColor" stroke-width="3" fill="none" stroke-linecap="round"/>
        </g>
      `;
      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) {
    const statusbar = document.getElementById('map-statusbar');
    if (!statusbar) return;
    const mapEl = statusbar.closest('.map-main') || statusbar.parentElement;
    if (!mapEl) return;

    let svg = document.getElementById('map-scan-ring');

    // Ring ausblenden / entfernen
    if (pct === null) {
      if (_ringClosing) return;
      if (svg) { svg.style.opacity = '0'; setTimeout(() => svg?.remove(), 600); }
      statusbar.style.border = '';
      return;
    }

    // SVG einmalig erzeugen
    if (!svg) {
      svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
      svg.id = 'map-scan-ring';
      svg.setAttribute('overflow', 'visible');
      svg.style.cssText = 'position:absolute;pointer-events:none;z-index:1002;transition:opacity 0.55s ease';
      const path = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
      path.id = 'map-scan-ring-rect';
      path.setAttribute('fill',             'none');
      path.setAttribute('stroke',           '#C4843A');
      path.setAttribute('stroke-width',     '3');
      path.setAttribute('stroke-linecap',   'round');
      svg.appendChild(path);
      mapEl.appendChild(svg);
    }

    // Position relativ zum Map-Container berechnen
    const sr = statusbar.getBoundingClientRect();
    const mr = mapEl.getBoundingClientRect();
    const w  = sr.width;
    const h  = sr.height;
    const r  = h / 2;          // border-radius-full = Hälfte der Höhe
    const p  = 2;              // Abstand zur inneren Kante

    // Umfang der Pill: gerades Stück + zwei Halbkreise
    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';
    svg.style.width   = (w + p * 2) + 'px';
    svg.style.height  = (h + p * 2) + 'px';
    svg.style.opacity = '1';

    const rect = document.getElementById('map-scan-ring-rect');
    rect.setAttribute('x',                  String(p));
    rect.setAttribute('y',                  String(p));
    rect.setAttribute('width',              String(w));
    rect.setAttribute('height',             String(h));
    rect.setAttribute('rx',                 String(r));
    rect.setAttribute('ry',                 String(r));
    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(); _ringClosing = false; }, 600);
      }, 500);
    }
  }

  // ----------------------------------------------------------
  // OSM-Layer laden
  // ----------------------------------------------------------
  function _scheduleOsmLoad() {
    clearTimeout(_overpassTimer);
    _overpassTimer = setTimeout(_loadOsmLayers, 600);
  }

  async function _loadOsmLayers() {
    if (!_map || !window.L || _overpassActive) return;
    const zoom = _map.getZoom();

    // Unter Zoom 10: alles ausblenden
    if (zoom < 10) {
      Object.keys(OSM_LAYER_MAP).forEach(k => {
        _layers[k].filter(m => !m._ownPlace).forEach(m => m._dangerCircle?.remove());
        _clusterGroups[k]?.clearLayers();
        _layers[k] = _layers[k].filter(m => m._ownPlace);
      });
      _setOsmStatus('');
      return;
    }

    // Zoom 10–13: normale OSM-Layer ausblenden, EARLY_LAYERS behalten/laden
    if (zoom < 14) {
      Object.keys(OSM_LAYER_MAP).filter(k => !EARLY_LAYERS.has(k)).forEach(k => {
        _layers[k].filter(m => !m._ownPlace).forEach(m => m._dangerCircle?.remove());
        _clusterGroups[k]?.clearLayers();
        _layers[k] = _layers[k].filter(m => m._ownPlace);
      });
    }

    _overpassActive = true;
    _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
    const activeLayers = zoom >= 14
      ? Object.entries(OSM_LAYER_MAP)
      : Object.entries(OSM_LAYER_MAP).filter(([k]) => EARLY_LAYERS.has(k));

    // OSM-Marker eines Layers ersetzen, eigene Orte behalten
    function _replaceOsmMarkers(layerKey, pois) {
      const cluster = _getCluster(layerKey);
      // Alte OSM-Marker entfernen
      const oldOsm = _layers[layerKey].filter(m => !m._ownPlace);
      oldOsm.forEach(m => m._dangerCircle?.remove());
      cluster.removeLayers(oldOsm);
      _layers[layerKey] = _layers[layerKey].filter(m => m._ownPlace);
      // Neue Marker erstellen und in Cluster packen
      const t = TYPEN[layerKey];
      const newMarkers = pois.map(poi => _createOsmMarker(poi, layerKey, t));
      cluster.addLayers(newMarkers);
      _layers[layerKey].push(...newMarkers);
      // Sicherstellen dass der Cluster auf der Karte ist (kann durch vorherigen Toggle fehlen)
      if (_visible[layerKey] !== false && _map && !_map.hasLayer(cluster)) {
        cluster.addTo(_map);
      }
    }

    // Phase 1: sofort DB-Daten zeigen (fast=true)
    _setOsmStatus('Lade…');
    const fastTasks = activeLayers.map(async ([layerKey, osmType]) => {
      const params = new URLSearchParams({ type: osmType, fast: 'true', ...bbox });
      try {
        const pois = await fetch(`/api/osm/pois?${params}`).then(r => r.json());
        _replaceOsmMarkers(layerKey, pois);
        return pois.length;
      } catch { return 0; }
    });
    const fastCounts = await Promise.all(fastTasks);
    const fastTotal  = fastCounts.reduce((a, b) => a + b, 0);
    if (fastTotal > 0) _setOsmStatus(`${fastTotal} aus Datenbank`, 20);

    // Phase 2: Overpass für fehlende Tiles — mit %-Fortschritt
    let _done = 0;
    const _total = activeLayers.length;
    _setOsmStatus('Scanne…', 20);

    const freshTasks = activeLayers.map(async ([layerKey, osmType]) => {
      const params = new URLSearchParams({ type: osmType, ...bbox });
      try {
        const pois = await fetch(`/api/osm/pois?${params}`).then(r => r.json());
        const osmCount = _layers[layerKey].filter(m => !m._ownPlace).length;
        if (pois.length !== osmCount) _replaceOsmMarkers(layerKey, pois);
        _done++;
        const pct   = Math.round(20 + _done / _total * 80);
        const total = Object.values(_layers).flat().filter(m => !m._ownPlace).length;
        _setOsmStatus(pct < 100 ? `Scanne…` : `${total} Marker`, pct);
        return pois.length;
      } catch {
        _done++;
        const pct   = Math.round(20 + _done / _total * 80);
        const total = Object.values(_layers).flat().filter(m => !m._ownPlace).length;
        _setOsmStatus(pct < 100 ? `Scanne…` : `${total} Marker`, pct);
        return _layers[layerKey].filter(m => !m._ownPlace).length;
      }
    });
    await Promise.all(freshTasks);
    _overpassActive = false;

    const totalLoaded = Object.values(_layers).flat().filter(m => !m._ownPlace).length;
    const allHidden   = Object.keys(OSM_LAYER_MAP).every(k => _visible[k] === false);
    if (totalLoaded > 0 && allHidden) {
      _setOsmStatus('Layer deaktiviert — Liste antippen', 100);
    }

    // Wenn 0 OSM-Marker: Hintergrund-Fetch läuft noch — max 3× automatisch nachfragen
    if (totalLoaded === 0 && zoom >= 14 && _autoRetryCount < 3) {
      _autoRetryCount++;
      const delay = _autoRetryCount * 30000; // 30s, 60s, 90s
      _setOsmStatus(`Neue Umgebung – Daten werden geladen…`);
      setTimeout(() => { if (!_overpassActive) _scheduleOsmLoad(); }, delay);
    }
  }

  // ----------------------------------------------------------
  // Spezielles Giftköder-Icon (pulsierend)
  // ----------------------------------------------------------
  function _poisonDivIcon() {
    return L.divIcon({
      className: '',
      html: `<div class="poison-marker">
               <div class="poison-ring"></div>
               <div class="poison-ring"></div>
               <div class="poison-dot"><svg class="ph-icon" aria-hidden="true" style="width:20px;height:20px"><use href="/icons/phosphor.svg#skull"></use></svg></div>
             </div>`,
      iconSize:   [48, 48],
      iconAnchor: [24, 24],
    });
  }

  function _addDangerCircle(lat, lon) {
    return L.circle([lat, lon], { radius: 100, ...DANGER_CIRCLE_STYLE }).addTo(_map);
  }

  // ----------------------------------------------------------
  // OSM-Marker erstellen (geht in Cluster, NICHT direkt auf Karte)
  // ----------------------------------------------------------
  function _createOsmMarker(poi, layerKey, t) {
    const isPoison = DANGER_RADIUS[layerKey] !== undefined;
    const icon = isPoison
      ? _poisonDivIcon()
      : L.divIcon({
          className: '',
          html: `<div style="background:${t.color};color:#fff;font-size:15px;
            width:32px;height:32px;border-radius:50%;
            display:flex;align-items:center;justify-content:center;
            box-shadow:0 2px 5px rgba(0,0,0,0.35);
            border:2px solid rgba(255,255,255,0.7)">${t.icon}</div>`,
          iconSize: [32, 32], iconAnchor: [16, 16],
        });

    const label  = poi.name || t.label;
    const marker = L.marker([poi.lat, poi.lon], { icon, zIndexOffset: t.z ?? 0 })
      .bindTooltip(label, { direction: 'top', offset: [0, -16] });

    marker.on('click', () => _showMarkerPopup(marker, poi, layerKey, t, label));

    if (isPoison) {
      marker._dangerCircle = _addDangerCircle(poi.lat, poi.lon);
    }

    return marker;
  }

  function _showMarkerPopup(marker, poi, layerKey, t, label) {
    const isOwn  = poi.source === 'user' && poi.own;
    const isUser = poi.source === 'user';

    const actionBtn = isOwn
      ? `<button class="btn btn-danger btn-sm" id="mp-action">Löschen</button>`
      : `<button class="btn btn-secondary btn-sm" id="mp-action">Als ungültig melden</button>`;

    const openHours = poi.opening_hours
      ? `<div style="font-size:11px;color:#555;margin-bottom:4px"><svg class="ph-icon" aria-hidden="true" style="width:12px;height:12px"><use href="/icons/phosphor.svg#clock"></use></svg> ${poi.opening_hours}</div>` : '';
    const phone = poi.phone
      ? `<div style="font-size:11px;margin-bottom:4px"><a href="tel:${poi.phone}" style="color:var(--c-primary);text-decoration:none">${poi.phone}</a></div>` : '';
    const website = poi.website
      ? `<div style="font-size:11px;margin-bottom:6px"><a href="${poi.website}" target="_blank" rel="noopener" style="color:var(--c-primary);text-decoration:none"><svg class="ph-icon" aria-hidden="true" style="width:12px;height:12px"><use href="/icons/phosphor.svg#arrow-square-out"></use></svg> Website</a></div>` : '';

    marker.bindPopup(`
      <div style="min-width:170px;max-width:240px">
        <div style="font-weight:600;margin-bottom:6px">${t.icon} ${label}</div>
        ${poi.notiz    ? `<div style="font-size:12px;color:#666;margin-bottom:6px">${poi.notiz}</div>` : ''}
        ${openHours}${phone}${website}
        <div style="font-size:11px;color:#999;margin-bottom:10px">
          ${isUser
            ? `<svg class="ph-icon" aria-hidden="true" style="width:11px;height:11px"><use href="/icons/phosphor.svg#push-pin"></use></svg> Community-Pin${poi.username ? ' · <b>' + poi.username + '</b>' : ''}`
            : '<svg class="ph-icon" aria-hidden="true" style="width:11px;height:11px"><use href="/icons/phosphor.svg#map-trifold"></use></svg> OpenStreetMap'}
        </div>
        ${actionBtn}
      </div>
    `, { maxWidth: 260 }).openPopup();

    setTimeout(() => {
      document.getElementById('mp-action')?.addEventListener('click', () => {
        marker.closePopup();
        if (isOwn) _deleteUserPoi(poi.user_poi_id, marker, layerKey);
        else       _showReportDialog(poi);
      });
    }, 50);
  }

  // ----------------------------------------------------------
  // Marker setzen (Placement-Mode)
  // ----------------------------------------------------------
  function _togglePlacementMode() {
    if (!_appState?.user) { App.navigate('welcome'); return; }
    _placingMarker = !_placingMarker;
    const btn = document.getElementById('map-pin-btn');
    if (_placingMarker) {
      btn?.classList.add('active');
      btn && (btn.textContent = '\u2715');
      // Fadenkreuz + Bestätigen-Leiste einblenden
      document.getElementById('map-crosshair')?.classList.add('active');
      document.getElementById('map-place-bar')?.classList.add('active');
      document.getElementById('map-place-confirm').onclick = () => {
        const center = _map.getCenter();
        _exitPlacementMode();
        _confirmPlacement(center);
      };
      document.getElementById('map-place-cancel').onclick = _exitPlacementMode;
    } else {
      _exitPlacementMode();
    }
  }

  function _exitPlacementMode() {
    _placingMarker = false;
    const btn = document.getElementById('map-pin-btn');
    btn?.classList.remove('active');
    btn && (btn.innerHTML = '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#push-pin"></use></svg>');
    document.getElementById('map-crosshair')?.classList.remove('active', 'dragging');
    document.getElementById('map-place-bar')?.classList.remove('active');
    _tempMarker?.remove();
    _tempMarker = null;
  }

  // Einzelne Basis-Typen — Mehrfachauswahl möglich (außer giftkoeder = exklusiv)
  const PIN_TYPES = [
    { type: 'giftkoeder',    icon: '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#skull"></use></svg>',        label: 'Giftköder',   color: '#DC2626', exclusive: true },
    { type: 'waste_basket',  icon: '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#trash"></use></svg>',         label: 'Mülleimer',   color: '#6B7280' },
    { type: 'kotbeutel',     icon: '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#bag"></use></svg>',           label: 'Kotbeutel',   color: '#84A98C' },
    { type: 'bank',          icon: '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#park"></use></svg>',          label: 'Sitzbank',    color: '#92400E' },
    { type: 'drinking_water',icon: '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#drop"></use></svg>',          label: 'Wasserstelle',color: '#0EA5E9' },
    { type: 'dog_park',      icon: '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#leaf"></use></svg>',          label: 'Hundewiese',  color: '#15803D' },
    { type: 'parkplatz',     icon: '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#car"></use></svg>',           label: 'Parkplatz',   color: '#2563EB' },
    { type: 'treffpunkt',    icon: '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#handshake"></use></svg>',     label: 'Treffpunkt',  color: '#7C3AED' },
    { type: 'sonstiges',     icon: '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#push-pin"></use></svg>',      label: 'Sonstiges',   color: '#F59E0B' },
  ];

  function _confirmPlacement(latlng) {
    _tempMarker?.remove();
    _tempMarker = L.circleMarker([latlng.lat, latlng.lng], {
      radius: 10, color: '#F59E0B', fillColor: '#F59E0B', fillOpacity: 0.6,
    }).addTo(_map);

    let _selectedTypes = new Set(['giftkoeder']);

    UI.modal.open({
      title: '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#push-pin"></use></svg> Marker setzen',
      body: `
        <form id="poi-form" class="flex flex-col gap-3">
          <div>
            <label class="form-label">Typ auswählen <span style="font-size:var(--text-xs);color:var(--c-text-muted);font-weight:normal">(Mehrfachauswahl möglich)</span></label>
            <div class="poi-type-grid">
              ${PIN_TYPES.map(p => `
                <button type="button" class="poi-type-btn${p.type === 'giftkoeder' ? ' selected' : ''}"
                  data-type="${p.type}" data-excl="${p.exclusive ? '1' : ''}" style="--pt-color:${p.color}">
                  <span class="poi-type-icon">${p.icon}</span>
                  <span class="poi-type-label">${p.label}</span>
                </button>
              `).join('')}
            </div>
          </div>
          <div>
            <label class="form-label">Name (optional)</label>
            <input class="form-input" id="poi-name" placeholder="z.B. Brunnen im Stadtpark">
          </div>
          <div>
            <label class="form-label">Notiz (optional)</label>
            <input class="form-input" id="poi-notiz" placeholder="Weitere Infos\u2026">
          </div>
        </form>
      `,
      footer: `
        <button class="btn btn-secondary" id="poi-cancel">Abbrechen</button>
        <button class="btn btn-primary" id="poi-save">Speichern</button>
      `,
    });

    document.querySelector('.poi-type-grid')?.addEventListener('click', e => {
      const btn = e.target.closest('.poi-type-btn');
      if (!btn) return;
      const t = btn.dataset.type;
      if (btn.dataset.excl) {
        _selectedTypes = new Set([t]);
        document.querySelectorAll('.poi-type-btn').forEach(b => b.classList.toggle('selected', b.dataset.type === t));
      } else {
        if (_selectedTypes.has('giftkoeder')) {
          _selectedTypes.delete('giftkoeder');
          document.querySelector('[data-excl="1"]')?.classList.remove('selected');
        }
        if (_selectedTypes.has(t)) {
          if (_selectedTypes.size > 1) { _selectedTypes.delete(t); btn.classList.remove('selected'); }
        } else {
          _selectedTypes.add(t); btn.classList.add('selected');
        }
      }
    });

    document.getElementById('poi-cancel')?.addEventListener('click', () => {
      UI.modal.close();
      _exitPlacementMode();
    });

    document.getElementById('poi-save')?.addEventListener('click', async () => {
      const name  = document.getElementById('poi-name').value.trim() || null;
      const notiz = document.getElementById('poi-notiz').value.trim() || null;
      const type  = [..._selectedTypes].join(',');
      UI.modal.close();
      await _saveUserPoi({ type, lat: latlng.lat, lon: latlng.lng, name, notiz });
      _exitPlacementMode();
    });
  }

  async function _saveUserPoi(data) {
    try {
      const res = await fetch('/api/osm/user-poi', {
        method:  'POST',
        headers: { 'Content-Type': 'application/json' },
        credentials: 'include',
        body:    JSON.stringify(data),
      });
      if (res.status === 401) { UI.toast.error('Bitte erst anmelden.'); return; }
      if (!res.ok) throw new Error();
      UI.toast.success('Marker gespeichert!');
      _scheduleOsmLoad();
    } catch {
      UI.toast.error('Fehler beim Speichern.');
    }
  }

  // ----------------------------------------------------------
  // Melden / Löschen
  // ----------------------------------------------------------
  function _showReportDialog(poi) {
    UI.modal.open({
      title: 'Marker melden',
      body: `
        <p class="text-secondary">Warum ist dieser Marker falsch?</p>
        <div class="flex flex-col gap-2" id="report-options">
          <button class="btn btn-secondary" data-grund="existiert_nicht">Existiert nicht mehr</button>
          <button class="btn btn-secondary" data-grund="falsche_position">Falsche Position</button>
          <button class="btn btn-secondary" data-grund="spam">Spam / Missbrauch</button>
          <button class="btn btn-secondary" data-grund="sonstiges">Sonstiges</button>
        </div>
      `,
    });

    document.getElementById('report-options')?.addEventListener('click', async e => {
      const btn = e.target.closest('[data-grund]');
      if (!btn) return;
      UI.modal.close();
      try {
        const body = {
          type:  poi.source === 'user' ? 'user_poi' : 'osm',
          grund: btn.dataset.grund,
          osm_id:      poi.source === 'osm'  ? poi.id           : undefined,
          user_poi_id: poi.source === 'user' ? poi.user_poi_id  : undefined,
        };
        const res = await fetch('/api/osm/report', {
          method: 'POST', headers: { 'Content-Type': 'application/json' },
          credentials: 'include', body: JSON.stringify(body),
        });
        if (res.status === 401) { UI.toast.error('Bitte erst anmelden.'); return; }
        const data = await res.json();
        if (data.status === 'bereits_gemeldet') {
          UI.toast.info('Du hast diesen Marker bereits gemeldet.');
        } else {
          UI.toast.success('Meldung eingereicht. Danke!');
        }
      } catch { UI.toast.error('Fehler beim Melden.'); }
    });
  }

  async function _deleteUserPoi(poiId, marker, layerKey) {
    try {
      const res = await fetch(`/api/osm/user-poi/${poiId}`, {
        method: 'DELETE', credentials: 'include',
      });
      if (!res.ok) throw new Error();
      _clusterGroups[layerKey]?.removeLayer(marker);
      marker._dangerCircle?.remove();
      _layers[layerKey] = _layers[layerKey].filter(m => m !== marker);
      UI.toast.success('Marker gelöscht.');
    } catch { UI.toast.error('Fehler beim Löschen.'); }
  }

  // ----------------------------------------------------------
  // Eigene Orte + Giftköder laden
  // ----------------------------------------------------------
  async function _loadAll() {
    // Falls Overpass-Job steckengeblieben: zurücksetzen
    _overpassActive = false;
    // Cluster-Gruppen leeren (OSM-Marker)
    Object.values(_clusterGroups).forEach(cg => cg.clearLayers());
    // Eigene-Orte-Marker direkt von Karte entfernen
    Object.values(_layers).flat().filter(m => m._ownPlace).forEach(m => {
      m._dangerCircle?.remove();
      m.remove();
    });
    // Giftköder-Kreise
    (_layers.poison || []).forEach(m => m._dangerCircle?.remove());
    Object.keys(_layers).forEach(k => { _layers[k] = []; });

    const [places, poisonList, breederList] = await Promise.allSettled([
      API.places.list(),
      _userPos ? API.poison.listNearby(_userPos.lat, _userPos.lon, 10000) : Promise.resolve([]),
      API.breeder.mapMarkers(),
    ]);

    if (places.status === 'fulfilled')     _addPlaces(places.value);
    if (poisonList.status === 'fulfilled') _addPoison(poisonList.value);
    if (breederList.status === 'fulfilled') _addBreeders(breederList.value);
    _scheduleOsmLoad();
  }

  function _addPlaces(places) {
    if (!_map || !window.L) return;
    places.forEach(place => {
      const t = TYPEN[place.typ];
      if (!t) return;
      const m = _createSimpleMarker(place.lat, place.lon, t, place.name,
        () => UI.toast.info(`${t.icon} ${place.name}${place.adresse ? ' \u00b7 ' + place.adresse : ''}`));
      m._ownPlace = true;
      _layers[place.typ]?.push(m);
      if (!_visible[place.typ]) m.setOpacity(0);
    });
  }

  function _addPoison(items) {
    if (!_map || !window.L) return;
    items.forEach(p => {
      const tooltip = `Giftk\u00f6der-Alarm${p.beschreibung ? ': ' + p.beschreibung : ''}`;
      const m = L.marker([p.lat, p.lon], { icon: _poisonDivIcon(), zIndexOffset: 100 })
        .addTo(_map)
        .bindTooltip(tooltip, { direction: 'top', offset: [0, -24] })
        .on('click', () => App.navigate('poison'));
      m._ownPlace    = true;
      m._dangerCircle = _addDangerCircle(p.lat, p.lon);
      _layers.poison.push(m);
      if (!_visible.poison) {
        m.setOpacity(0);
        m._dangerCircle.setStyle({ opacity: 0, fillOpacity: 0 });
      }
    });
  }

  function _addBreeders(breeders) {
    if (!_map || !window.L) return;
    const t       = TYPEN.zuechter;
    const cluster = _getCluster('zuechter');
    const markers = [];

    breeders.forEach(b => {
      // Ohne Koordinaten: stillen Skip
      if (b.location_lat == null || b.location_lng == null) return;

      const icon = L.divIcon({
        className: '',
        html: `<div style="background:${t.color};color:#fff;font-size:15px;
          width:32px;height:32px;border-radius:50%;
          display:flex;align-items:center;justify-content:center;
          box-shadow:0 2px 5px rgba(0,0,0,0.35);
          border:2px solid rgba(255,255,255,0.7)">${t.icon}</div>`,
        iconSize: [32, 32], iconAnchor: [16, 16],
      });

      const marker = L.marker([b.location_lat, b.location_lng], { icon, zIndexOffset: t.z ?? 0 })
        .bindTooltip(_esc(b.zwingername), { direction: 'top', offset: [0, -16] });

      marker.on('click', () => {
        const rasseText = b.rasse_text ? `<div style="font-size:12px;color:#666;margin-bottom:4px">${_esc(b.rasse_text)}</div>` : '';
        const stadtText = b.stadt      ? `<div style="font-size:12px;color:#888;margin-bottom:8px">${_esc(b.stadt)}</div>`      : '';

        marker.bindPopup(`
          <div style="min-width:170px;max-width:240px">
            <div style="font-weight:600;margin-bottom:6px">${t.icon} ${_esc(b.zwingername)}</div>
            ${rasseText}${stadtText}
            <button class="btn btn-primary btn-sm" id="breeder-profile-btn">Profil ansehen</button>
          </div>
        `, { maxWidth: 260 }).openPopup();

        setTimeout(() => {
          document.getElementById('breeder-profile-btn')?.addEventListener('click', () => {
            marker.closePopup();
            App.navigate('breeder', true, { zwingername: b.zwingername });
          });
        }, 50);
      });

      markers.push(marker);
      _layers.zuechter.push(marker);
    });

    cluster.addLayers(markers);
    if (_visible.zuechter !== false && _map && !_map.hasLayer(cluster)) {
      cluster.addTo(_map);
    }
  }

  function _createSimpleMarker(lat, lon, t, tooltip, onClick) {
    const icon = L.divIcon({
      className: '',
      html: `<div style="background:${t.color};color:#fff;font-size:15px;
        width:32px;height:32px;border-radius:50%;
        display:flex;align-items:center;justify-content:center;
        box-shadow:0 2px 5px rgba(0,0,0,0.35);
        border:2px solid rgba(255,255,255,0.7)">${t.icon}</div>`,
      iconSize: [32, 32], iconAnchor: [16, 16],
    });
    return L.marker([lat, lon], { icon, zIndexOffset: t.z ?? 0 })
      .addTo(_map)
      .bindTooltip(tooltip, { direction: 'top', offset: [0, -16] })
      .on('click', onClick);
  }

  // ----------------------------------------------------------
  // Layer ein/ausblenden
  // ----------------------------------------------------------
  function _applyVisibility(layer) {
    // poison-Toggle steuert auch giftkoeder-Community-Pins mit
    const keys = layer === 'poison' ? ['poison', 'giftkoeder'] : [layer];
    keys.forEach(k => {
      const on = _visible[layer];
      _visible[k] = on;
      if (_clusterGroups[k]) {
        on ? _clusterGroups[k].addTo(_map) : _clusterGroups[k].remove();
      }
      (_layers[k] || []).forEach(m => {
        if (m._ownPlace) m.setOpacity?.(on ? 1 : 0);
        if (m._dangerCircle) {
          m._dangerCircle.setStyle(on
            ? { opacity: 1, fillOpacity: 0.12 }
            : { opacity: 0, fillOpacity: 0 }
          );
        }
      });
    });
  }

  // ----------------------------------------------------------
  // Offline-Kacheln vorladen
  // ----------------------------------------------------------
  function _tileCoords(lat, lon, zoom) {
    const n      = Math.pow(2, zoom);
    const x      = Math.floor((lon + 180) / 360 * n);
    const latRad = lat * Math.PI / 180;
    const y      = Math.floor((1 - Math.log(Math.tan(latRad) + 1 / Math.cos(latRad)) / Math.PI) / 2 * n);
    return { x, y };
  }

  function _collectTileUrls(bounds, minZoom, maxZoom) {
    const urls = [];
    const subdomains = ['a', 'b', 'c'];
    for (let z = minZoom; z <= maxZoom; z++) {
      const sw = _tileCoords(bounds.getSouth(), bounds.getWest(), z);
      const ne = _tileCoords(bounds.getNorth(), bounds.getEast(), z);
      for (let x = sw.x; x <= ne.x; x++) {
        for (let y = ne.y; y <= sw.y; y++) {
          const s = subdomains[Math.abs(x + y) % 3];
          urls.push(`https://${s}.tile.openstreetmap.org/${z}/${x}/${y}.png`);
        }
      }
    }
    return urls;
  }

  async function _cacheTiles() {
    if (!_map) return;
    if (!('serviceWorker' in navigator) || !navigator.serviceWorker.controller) {
      UI.toast.warning('Service Worker nicht bereit \u2014 bitte Seite neu laden.');
      return;
    }

    const bounds = _map.getBounds();
    // Padding: 1 Kachel nach außen auf Zoom 14
    const padded = bounds.pad(0.15);

    // Zoom 12–15 innerhalb der aktuellen Kartenansicht
    const urls = _collectTileUrls(padded, 12, 15);

    if (urls.length === 0) {
      UI.toast.info('Keine Kacheln im Bereich.');
      return;
    }

    if (urls.length > 800) {
      UI.toast.warning(`Bereich zu groß (${urls.length} Kacheln). Bitte weiter reinzoomen.`);
      return;
    }

    const btn = document.getElementById('map-offline-btn');
    if (btn) btn.classList.add('loading');
    _setOsmStatus(`Offline: 0 / ${urls.length} Kacheln…`);

    // Progress via postMessage vom SW
    const onMessage = evt => {
      if (evt.data?.type !== 'CACHE_TILES_PROGRESS') return;
      const { done, total } = evt.data;
      if (done >= total) {
        navigator.serviceWorker.removeEventListener('message', onMessage);
        if (btn) btn.classList.remove('loading');
        _setOsmStatus('');
        UI.toast.success(`${total} Kacheln offline gespeichert!`);
      } else {
        _setOsmStatus(`Offline: ${done} / ${total} Kacheln…`);
      }
    };
    navigator.serviceWorker.addEventListener('message', onMessage);

    navigator.serviceWorker.controller.postMessage({ type: 'CACHE_TILES', urls });
  }

  // ----------------------------------------------------------
  // Pocket-Modus Overlay
  // ----------------------------------------------------------
  function _showPocketOverlay() {
    if (_pocketOverlay) return;
    const el = document.createElement('div');
    el.id = 'pocket-overlay';
    el.innerHTML = `
      <div class="po-status" id="po-status">GPS läuft</div>
      <div class="po-time"   id="po-time">0:00</div>
      <div class="po-dist"   id="po-dist">0.00 km</div>
      <div class="po-hint">Tippen für Steuerung</div>
      <div class="po-controls" id="po-controls">
        <button class="po-btn" id="po-pause">⏸ Pause</button>
        <button class="po-btn po-btn--stop" id="po-stop">⏹ Stopp</button>
      </div>
    `;
    el.style.cssText = `
      position:fixed;inset:0;z-index:9998;background:#000;
      display:flex;flex-direction:column;align-items:center;justify-content:center;
      color:#fff;font-family:inherit;user-select:none;
    `;
    el.querySelector('.po-status').style.cssText =
      'font-size:0.85rem;color:#888;margin-bottom:1rem;letter-spacing:0.05em;text-transform:uppercase';
    el.querySelector('.po-time').style.cssText =
      'font-size:4.5rem;font-weight:700;letter-spacing:-0.02em;line-height:1';
    el.querySelector('.po-dist').style.cssText =
      'font-size:1.5rem;color:#aaa;margin-top:0.5rem';
    el.querySelector('.po-hint').style.cssText =
      'font-size:0.75rem;color:#444;margin-top:2.5rem';
    const ctrl = el.querySelector('.po-controls');
    ctrl.style.cssText =
      'display:none;gap:1rem;margin-top:2rem;flex-direction:row';
    el.querySelectorAll('.po-btn').forEach(b => {
      b.style.cssText =
        'padding:0.75rem 1.5rem;border:1px solid #555;border-radius:0.75rem;' +
        'background:#111;color:#fff;font-size:1rem;cursor:pointer';
    });
    el.querySelector('.po-btn--stop').style.cssText +=
      'border-color:#c0392b;color:#e74c3c';

    // Tippen → Controls 4s einblenden, dann ausblenden
    el.addEventListener('click', e => {
      if (e.target.closest('#po-controls')) return;
      ctrl.style.display = 'flex';
      el.querySelector('.po-hint').style.color = '#666';
      clearTimeout(_pocketHideTimer);
      _pocketHideTimer = setTimeout(() => {
        ctrl.style.display = 'none';
        el.querySelector('.po-hint').style.color = '#444';
      }, 4000);
    });

    el.querySelector('#po-pause').addEventListener('click', () => {
      _togglePause();
      el.querySelector('#po-pause').textContent =
        _recPaused ? '▶ Weiter' : '⏸ Pause';
      el.querySelector('#po-status').textContent =
        _recPaused ? 'Pausiert' : 'GPS läuft';
    });
    el.querySelector('#po-stop').addEventListener('click', () => {
      _hidePocketOverlay();
      _stopRecording();
    });

    document.body.appendChild(el);
    _pocketOverlay = el;
  }

  function _hidePocketOverlay() {
    clearTimeout(_pocketHideTimer);
    _pocketOverlay?.remove();
    _pocketOverlay = null;
  }

  function _updatePocketOverlay() {
    if (!_pocketOverlay) return;
    const elapsed = Math.floor((Date.now() - _recStartTime) / 1000);
    const mm = String(Math.floor(elapsed / 60)).padStart(1, '0');
    const ss = String(elapsed % 60).padStart(2, '0');
    const timeEl = _pocketOverlay.querySelector('#po-time');
    const distEl = _pocketOverlay.querySelector('#po-dist');
    if (timeEl) timeEl.textContent = `${mm}:${ss}`;
    if (distEl) distEl.textContent = `${_recDistKm.toFixed(2)} km`;
  }

  // ----------------------------------------------------------
  // GPS-Aufzeichnung
  // ----------------------------------------------------------
  function _toggleRecording() {
    if (!_recActive) _startRecording();
    else             _stopRecording();
  }

  async function _startRecording() {
    if (!_appState.user) {
      UI.toast.warning('Bitte zuerst anmelden.');
      App.navigate('settings');
      return;
    }
    if (!navigator.geolocation) {
      UI.toast.error('GPS nicht verfügbar.');
      return;
    }
    _recActive    = true;
    _recPaused    = false;
    _recTrack     = [];
    _recDistKm    = 0;
    _recStartTime = Date.now();

    // FAB umschalten
    const btn = document.getElementById('map-rec-btn');
    if (btn) { btn.innerHTML = '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#floppy-disk"></use></svg>'; btn.classList.add('recording'); }

    // Aufzeichnungs-Panel einblenden
    const panel = document.getElementById('map-rec-panel');
    if (panel) panel.classList.add('active');
    document.getElementById('rec-panel-pause').onclick = _togglePause;
    document.getElementById('rec-panel-stop').onclick  = _stopRecording;

    // Wake Lock — Bildschirm wach halten
    await _acquireWakeLock();
    const hint = document.getElementById('map-rec-hint');
    if (hint) hint.textContent = _wakeLock
      ? 'Bildschirm bleibt aktiv — GPS läuft'
      : 'Bildschirm-Lock nicht unterstützt — Bildschirm aktiv lassen';

    // Sichtbarkeit: Wake Lock bei Tab-Wechsel neu anfordern
    document.addEventListener('visibilitychange', _onVisibilityChange);

    _recTimerInt = setInterval(_updateRecStatus, 1000);

    _recWatchId = navigator.geolocation.watchPosition(
      pos => {
        if (_recPaused) return;
        const { latitude: lat, longitude: lon } = pos.coords;
        if (_recTrack.length > 0) {
          const prev = _recTrack[_recTrack.length - 1];
          const d = _haversineRec(prev.lat, prev.lon, lat, lon);
          if (d < 3) return;
          _recDistKm += d / 1000;
        }
        _recTrack.push({ lat, lon });
        _updateRecMap(lat, lon);
        _updateRecStatus();
      },
      () => {},
      { enableHighAccuracy: true, maximumAge: 0, timeout: 10000 }
    );
    UI.toast.success('Aufzeichnung gestartet — los geht\'s!');

    // Pocket-Modus aktivieren wenn in Einstellungen eingeschaltet
    if (localStorage.getItem('by_pocket_mode') === 'true') {
      setTimeout(_showPocketOverlay, 800); // kurz warten damit Toast sichtbar war
    }
  }

  async function _onVisibilityChange() {
    if (_recActive && document.visibilityState === 'visible' && !_wakeLock) {
      await _acquireWakeLock();
    }
  }

  async function _acquireWakeLock() {
    if (!('wakeLock' in navigator)) return;
    try {
      _wakeLock = await navigator.wakeLock.request('screen');
      _wakeLock.addEventListener('release', () => { _wakeLock = null; });
    } catch {}
  }

  function _releaseWakeLock() {
    _wakeLock?.release();
    _wakeLock = null;
  }

  function _togglePause() {
    _recPaused = !_recPaused;
    const btn = document.getElementById('rec-panel-pause');
    if (btn) btn.textContent = _recPaused ? '▶ Weiter' : '⏸ Pause';
    const panel = document.getElementById('map-rec-panel');
    panel?.classList.toggle('paused', _recPaused);
  }

  function _haversineRec(lat1, lon1, lat2, lon2) {
    const R = 6371000;
    const p1 = lat1 * Math.PI / 180, p2 = lat2 * Math.PI / 180;
    const dp = (lat2 - lat1) * Math.PI / 180;
    const dl = (lon2 - lon1) * Math.PI / 180;
    const a = Math.sin(dp/2)**2 + Math.cos(p1)*Math.cos(p2)*Math.sin(dl/2)**2;
    return 2 * R * Math.asin(Math.sqrt(a));
  }

  function _updateRecMap(lat, lon) {
    if (!_map || !window.L) return;
    const ll = [lat, lon];
    if (!_recPolyline) {
      _recPolyline = L.polyline([ll], { color: '#EF4444', weight: 5, opacity: 0.9 }).addTo(_map);
    } else {
      _recPolyline.addLatLng(ll);
    }
    if (!_recMarker) {
      _recMarker = L.circleMarker(ll, {
        radius: 8, color: '#EF4444', fillColor: '#fff', fillOpacity: 1, weight: 3,
      }).addTo(_map);
    } else {
      _recMarker.setLatLng(ll);
    }
    _map.panTo(ll);
  }

  function _updateRecStatus() {
    const secs  = Math.floor((Date.now() - _recStartTime) / 1000);
    const mm    = String(Math.floor(secs / 60)).padStart(2, '0');
    const ss    = String(secs % 60).padStart(2, '0');
    const pace  = _recDistKm > 0.05
      ? (() => { const pSec = secs / _recDistKm / 60; const pm = Math.floor(pSec); const ps = String(Math.round((pSec-pm)*60)).padStart(2,'0'); return `${pm}:${ps}`; })()
      : '–:––';

    const distEl = document.getElementById('rec-stat-dist');
    const timeEl = document.getElementById('rec-stat-time');
    const paceEl = document.getElementById('rec-stat-pace');
    if (distEl) distEl.textContent = _recDistKm.toFixed(2);
    if (timeEl) timeEl.textContent = `${mm}:${ss}`;
    if (paceEl) paceEl.textContent = pace;
    _updatePocketOverlay();
  }

  function _stopRecording() {
    if (_recWatchId !== null) { navigator.geolocation.clearWatch(_recWatchId); _recWatchId = null; }
    if (_recTimerInt)         { clearInterval(_recTimerInt); _recTimerInt = null; }
    _recActive = false;
    _releaseWakeLock();
    _hidePocketOverlay();
    document.removeEventListener('visibilitychange', _onVisibilityChange);

    const btn = document.getElementById('map-rec-btn');
    if (btn) { btn.innerHTML = '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#path"></use></svg>'; btn.classList.remove('recording'); }

    const panel = document.getElementById('map-rec-panel');
    if (panel) panel.classList.remove('active', 'paused');

    if (_recTrack.length < 2) {
      UI.toast.warning('Zu wenige GPS-Punkte — bitte etwas länger laufen.');
      if (_recPolyline) { _recPolyline.remove(); _recPolyline = null; }
      if (_recMarker)   { _recMarker.remove();   _recMarker   = null; }
      return;
    }

    const dauMin = Math.max(1, Math.floor((Date.now() - _recStartTime) / 1000 / 60));
    _showRecSaveModal(_recTrack, _recDistKm, dauMin);
  }

  async function _prefillRouteName(track, distKm) {
    const nameInput = document.querySelector('#rec-save-form [name="name"]');
    if (!nameInput || nameInput.value) return;
    const pt   = track[0];
    const date = new Date().toLocaleDateString('de-DE', { day:'2-digit', month:'2-digit', year:'numeric' });
    const km   = distKm.toFixed(1);
    let ort = '';
    try {
      const r    = await fetch(`https://nominatim.openstreetmap.org/reverse?lat=${pt.lat}&lon=${pt.lon}&format=json&zoom=13&addressdetails=1&accept-language=de`, { cache: 'no-store' });
      const data = await r.json();
      const a    = data.address || {};
      ort = a.village || a.town || a.suburb || a.city_district || a.city || a.municipality || '';
    } catch {}
    if (!nameInput.value) nameInput.value = ort
      ? `Gassirunde ${ort} · ${date} · ${km} km`
      : `Gassirunde · ${date} · ${km} km`;
  }

  function _showRecSaveModal(track, distKm, dauMin) {
    const body = `
      <p style="color:var(--c-text-secondary);margin-bottom:var(--space-4)">
        ${track.length} GPS-Punkte · ${distKm.toFixed(2)} km · ca. ${dauMin} min
      </p>
      <form id="rec-save-form" autocomplete="off">
        <div class="form-group">
          <label class="form-label">Name der Route *</label>
          <input class="form-control" type="text" name="name"
                 placeholder="Wird automatisch ermittelt…" required>
        </div>
        <div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3)">
          <div class="form-group">
            <label class="form-label">Schwierigkeit</label>
            <select class="form-control" name="schwierigkeit">
              <option value="leicht">Leicht</option>
              <option value="mittel">Mittel</option>
              <option value="anspruchsvoll">Anspruchsvoll</option>
            </select>
          </div>
          <div class="form-group">
            <label class="form-label">Untergrund</label>
            <select class="form-control" name="untergrund">
              <option value="">– unbekannt –</option>
              <option value="wald">Wald</option>
              <option value="asphalt">Asphalt</option>
              <option value="wiese">Wiese</option>
              <option value="mix">Mix</option>
            </select>
          </div>
        </div>
        <div class="form-group">
          <label class="form-label">Hundetauglichkeit</label>
          <div class="rk-paw-select" id="rec-paw-select">
            <button type="button" class="rk-paw-btn" data-val="eingeschränkt"><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#paw-print"></use></svg> Eingeschränkt</button>
            <button type="button" class="rk-paw-btn" data-val="gut"><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#paw-print"></use></svg><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#paw-print"></use></svg> Gut</button>
            <button type="button" class="rk-paw-btn selected" data-val="sehr_gut"><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#paw-print"></use></svg><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#paw-print"></use></svg><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#paw-print"></use></svg> Sehr gut</button>
            <button type="button" class="rk-paw-btn" data-val="premium"><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#paw-print"></use></svg><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#paw-print"></use></svg><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#paw-print"></use></svg><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#paw-print"></use></svg> Premium</button>
          </div>
          <input type="hidden" name="hunde_tauglichkeit" id="rec-paw-val" value="sehr_gut">
        </div>
        <div class="form-group" style="display:flex;gap:var(--space-4)">
          <label style="display:flex;align-items:center;gap:var(--space-2);cursor:pointer">
            <input type="checkbox" name="schatten"> <svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#tree"></use></svg> Viel Schatten
          </label>
          <label style="display:flex;align-items:center;gap:var(--space-2);cursor:pointer">
            <input type="checkbox" name="leine_empfohlen"> <svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#tag"></use></svg> Leine empfohlen
          </label>
        </div>
        <div class="form-group">
          <label style="display:flex;align-items:center;gap:var(--space-2);cursor:pointer">
            <input type="checkbox" name="is_public"> <svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#globe"></use></svg> Öffentlich (von allen sichtbar)
          </label>
        </div>
        <div class="form-group">
          <label class="form-label">Beschreibung <span style="color:var(--c-text-secondary)">(optional)</span></label>
          <textarea class="form-control" name="beschreibung" rows="2"
                    placeholder="Besonderheiten, Highlights, Tipps…"></textarea>
        </div>
      </form>
    `;

    const footer = `
      <button type="button" class="btn btn-secondary flex-1" id="rms-discard">Verwerfen</button>
      <button type="submit" form="rec-save-form" class="btn btn-primary flex-1"><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#floppy-disk"></use></svg> Speichern</button>
    `;

    UI.modal.open({ title: '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#path"></use></svg> Route benennen', body, footer });

    _prefillRouteName(track, distKm); // async, füllt Name-Feld sobald Nominatim antwortet

    document.getElementById('rec-paw-select')?.addEventListener('click', e => {
      const btn = e.target.closest('.rk-paw-btn');
      if (!btn) return;
      document.querySelectorAll('.rk-paw-btn').forEach(b => b.classList.remove('selected'));
      btn.classList.add('selected');
      document.getElementById('rec-paw-val').value = btn.dataset.val;
    });

    document.getElementById('rms-discard')?.addEventListener('click', () => {
      UI.modal.close();
      if (_recPolyline) { _recPolyline.remove(); _recPolyline = null; }
      if (_recMarker)   { _recMarker.remove();   _recMarker   = null; }
    });

    document.getElementById('rec-save-form')?.addEventListener('submit', async e => {
      e.preventDefault();
      const btn = document.querySelector('[form="rec-save-form"][type="submit"]');
      const fd  = UI.formData(e.target);
      await UI.asyncButton(btn, async () => {
        const saved = await API.routes.create({
          name:            fd.name?.trim(),
          beschreibung:    fd.beschreibung || null,
          gps_track:       track,
          distanz_km:      Math.round(distKm * 100) / 100,
          dauer_min:       dauMin,
          schwierigkeit:   fd.schwierigkeit || 'leicht',
          untergrund:      fd.untergrund    || null,
          schatten:        'schatten'        in fd,
          leine_empfohlen: 'leine_empfohlen' in fd,
          is_public:          'is_public' in fd,
          hunde_tauglichkeit: fd.hunde_tauglichkeit || 'sehr_gut',
        });
        UI.modal.close();
        if (_recPolyline) { _recPolyline.remove(); _recPolyline = null; }
        if (_recMarker)   { _recMarker.remove();   _recMarker   = null; }
        if (saved.is_valid === false) {
          UI.toast.warning(`Route „${saved.name}" gespeichert — wird nicht für Statistiken gewertet (Geschwindigkeit zu hoch).`);
        } else {
          UI.toast.success(`Route „${saved.name}" gespeichert!`);
        }
      });
    });
  }

  // ----------------------------------------------------------
  // WETTER-CHIP
  // ----------------------------------------------------------
  async function _loadWeather(lat, lon) {
    const info = document.getElementById('map-weather-info');
    const sep  = document.getElementById('map-weather-sep');
    if (!info) return;
    try {
      const w = await API.weather.get(lat, lon);
      const temp = w.temp_c != null ? `${Math.round(w.temp_c)}°` : '–';
      const icon = `<svg class="ph-icon" aria-hidden="true" style="width:12px;height:12px;vertical-align:-2px"><use href="/icons/phosphor.svg#${w.icon}"></use></svg>`;
      const regen = w.precip_prob != null ? ` · 💧 ${w.precip_prob}%` : '';
      let zecken = '';
      if (w.zecken_warnung) {
        const col = w.zecken_warnung === 'hoch' ? '#991B1B' : '#92400E';
        zecken = ` · <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="${col}" style="display:inline;width:14px;height:14px;vertical-align:-2px" title="Zeckenrisiko ${w.zecken_warnung}"><ellipse cx="128" cy="68" rx="26" ry="22"/><ellipse cx="128" cy="158" rx="88" ry="80"/><path d="M52,120 Q20,106 8,94" fill="none" stroke="${col}" stroke-width="14" stroke-linecap="round"/><path d="M44,142 Q12,136 2,132" fill="none" stroke="${col}" stroke-width="14" stroke-linecap="round"/><path d="M44,164 Q12,162 2,162" fill="none" stroke="${col}" stroke-width="14" stroke-linecap="round"/><path d="M52,184 Q22,192 10,204" fill="none" stroke="${col}" stroke-width="14" stroke-linecap="round"/><path d="M204,120 Q236,106 248,94" fill="none" stroke="${col}" stroke-width="14" stroke-linecap="round"/><path d="M212,142 Q244,136 254,132" fill="none" stroke="${col}" stroke-width="14" stroke-linecap="round"/><path d="M212,164 Q244,162 254,162" fill="none" stroke="${col}" stroke-width="14" stroke-linecap="round"/><path d="M204,184 Q234,192 246,204" fill="none" stroke="${col}" stroke-width="14" stroke-linecap="round"/></svg>`;
      }
      info.innerHTML = `${icon} ${temp} ${w.desc}${regen}${zecken}`;
      info.classList.remove('map-weather-chip--hidden');
      sep.classList.remove('map-weather-chip--hidden');
    } catch { /* still */ }
  }

  return { init, refresh, onDogChange, startRecording: _startRecording, stopRecording: _stopRecording, isRecording: () => _recActive };

})();
