/* ============================================================
   BAN YARO — Drei Welten Navigation
   JETZT | HUND | WELT  —  horizontales Swipe-System
   ============================================================ */

window.Worlds = (() => {

  let _state        = null;
  let _cur          = 1;       // 0=JETZT  1=HUND  2=WELT
  let _visible      = false;
  let _map          = null;
  let _weltInited   = false;
  let _lastUserId   = undefined;
  let _dogs         = [];      // gecachte Hundesliste
  let _dogIdx       = 0;       // aktuell angezeigter Hund

  // Touch-Tracking
  const _t = { x:0, y:0, active:false, vert:null, moved:0 };

  // ── OFFLINE-CACHE (localStorage) ────────────────────────────
  // Letzten bekannten Zustand speichern → sofort zeigen, dann updaten

  function _wSave(key, data) {
    try { localStorage.setItem('w3_' + key, JSON.stringify({ ts: Date.now(), data })); } catch {}
  }
  function _wLoad(key) {
    try {
      const p = JSON.parse(localStorage.getItem('w3_' + key) || 'null');
      return p ? { data: p.data, ageMin: Math.round((Date.now() - p.ts) / 60000) } : null;
    } catch { return null; }
  }
  // API-Aufruf mit Cache-Fallback: sofort Cache, dann Netz
  async function _cachedGet(cacheKey, path) {
    const cached = _wLoad(cacheKey);
    let fresh = null;
    try {
      fresh = await API.get(path);
      _wSave(cacheKey, fresh);
    } catch {}
    return { data: fresh ?? cached?.data ?? null, fromCache: !fresh, ageMin: fresh ? 0 : (cached?.ageMin ?? null) };
  }

  // ── PUBLIC ──────────────────────────────────────────────────

  async function init(appState) {
    _state = appState;
    _cur   = 1; // immer HUND als Start
    _setupSwipe();
    _setupButtons();
    _goTo(_cur, false);
    show();
  }

  function show(worldIdx) {
    const ov = document.getElementById('worlds-overlay');
    if (!ov) return;
    ov.style.display = 'block';
    requestAnimationFrame(() => ov.classList.add('worlds-visible'));
    _visible = true;
    document.getElementById('app-header')?.classList.add('worlds-hidden');
    document.getElementById('bottom-nav')?.classList.add('worlds-hidden');
    document.getElementById('worlds-back')?.classList.remove('worlds-back-visible');
    if (worldIdx != null) _goTo(worldIdx, false);
    if (_cur === 2 && !_weltInited) { _weltInited = true; _renderWelt(); }

    // Nach Login/Logout: Config aus DB laden, dann rendern
    const currentUserId = _state?.user?.id ?? null;
    if (currentUserId !== _lastUserId) {
      _lastUserId = currentUserId;
      if (currentUserId) {
        _loadConfigFromServer().then(() => { _renderJetzt(); _renderHund(); });
      } else {
        _cfgCache = null;
        _renderJetzt();
        _renderHund();
      }
    }
  }

  function hide() {
    const ov = document.getElementById('worlds-overlay');
    if (!ov) return;
    ov.classList.remove('worlds-visible');
    ov.style.display = 'none';
    _visible = false;
    document.getElementById('worlds-back')?.classList.add('worlds-back-visible');
  }

  function navigateTo(pageId) {
    hide();
    if (window.App?.navigate) window.App.navigate(pageId);
  }

  // ── SWIPE ────────────────────────────────────────────────────

  function _setupSwipe() {
    const track = document.getElementById('worlds-track');
    if (!track) return;

    track.addEventListener('touchstart', e => {
      _t.x = e.touches[0].clientX;
      _t.y = e.touches[0].clientY;
      _t.active = true; _t.vert = null; _t.moved = 0;
      track.style.transition = 'none';
    }, { passive: true });

    track.addEventListener('touchmove', e => {
      if (!_t.active) return;
      const dx = e.touches[0].clientX - _t.x;
      const dy = e.touches[0].clientY - _t.y;
      if (_t.vert === null) _t.vert = Math.abs(dy) > Math.abs(dx) + 4;
      if (_t.vert) return;
      e.preventDefault();
      // Nicht über erste/letzte Seite hinausziehen
      const cdx = _cur === 0 ? Math.min(0, dx) : _cur === 2 ? Math.max(0, dx) : dx;
      _t.moved = cdx;
      const base = -_cur * (100 / 3);
      track.style.transform = `translateX(calc(${base}% + ${cdx}px))`;
    }, { passive: false });

    track.addEventListener('touchend', () => {
      if (!_t.active || _t.vert) { _t.active = false; return; }
      _t.active = false;
      let next = _cur;
      if (_t.moved < -55 && _cur < 2) next = _cur + 1;
      else if (_t.moved > 55 && _cur > 0) next = _cur - 1;
      _goTo(next, true);
      if (next === 2 && !_weltInited) { _weltInited = true; _renderWelt(); }
    });

    // Mausrad-Navigation (Desktop)
    let _wheelCooldown = false;
    track.addEventListener('wheel', e => {
      e.preventDefault();
      if (_wheelCooldown) return;
      const next = e.deltaX > 30 || e.deltaY > 30  ? Math.min(2, _cur + 1)
                 : e.deltaX < -30 || e.deltaY < -30 ? Math.max(0, _cur - 1)
                 : _cur;
      if (next === _cur) return;
      _wheelCooldown = true;
      setTimeout(() => { _wheelCooldown = false; }, 500);
      _goTo(next, true);
      if (next === 2 && !_weltInited) { _weltInited = true; _renderWelt(); }
    }, { passive: false });
  }

  function _goTo(idx, animated) {
    _cur = Math.max(0, Math.min(2, idx));
    const track = document.getElementById('worlds-track');
    if (!track) return;
    track.style.transition = animated
      ? 'transform 0.32s cubic-bezier(0.25, 0.46, 0.45, 0.94)'
      : 'none';
    track.style.transform = `translateX(${-_cur * (100 / 3)}%)`;
    _updateDots();
    _updateFab();
    // Karte neu rendern nachdem Transition abgeschlossen
    if (_cur === 2 && _map) {
      setTimeout(() => _map.invalidateSize(), animated ? 380 : 50);
    }
  }

  function _updateDots() {
    document.querySelectorAll('.wdot').forEach((d, i) => d.classList.toggle('active', i === _cur));
    document.querySelectorAll('.wlabel').forEach((l, i) => l.classList.toggle('active', i === _cur));
  }

  function _updateFab() {
    const fab = document.getElementById('worlds-fab');
    if (!fab) return;
    const icons  = ['note-pencil', 'paw-print', 'warning'];
    const titles = ['Schnelleintrag', 'Hund-Eintrag', 'Alarm melden'];
    fab.querySelector('use')?.setAttribute('href', `/icons/phosphor.svg#${icons[_cur]}`);
    fab.title = titles[_cur];
  }

  function _setupButtons() {
    document.getElementById('worlds-fab')?.addEventListener('click', _openFab);
    document.getElementById('worlds-back')?.addEventListener('click', () => show());
    document.querySelectorAll('.wdot').forEach((dot, i) => {
      dot.style.pointerEvents = 'auto';
      dot.addEventListener('click', () => {
        _goTo(i, true);
        if (i === 2 && !_weltInited) { _weltInited = true; _renderWelt(); }
      });
    });
    document.querySelectorAll('.wlabel').forEach((lbl, i) => {
      lbl.style.pointerEvents = 'auto';
      lbl.style.cursor = 'pointer';
      lbl.addEventListener('click', () => {
        _goTo(i, true);
        if (i === 2 && !_weltInited) { _weltInited = true; _renderWelt(); }
      });
    });
  }

  function _openFab() {
    const isWelt = _cur === 2;
    const dogName = _state?.user ? null : null; // falls mehrere Hunde: erweiterbar

    const options = isWelt ? [
      { icon:'skull',         color:'#EF4444', label:'Giftköder melden',    sub:'Warnung für andere Hundebesitzer',  page:'poison'  },
      { icon:'dog',           color:'#3B82F6', label:'Verlorenen Hund melden', sub:'Hilf beim Wiederfinden',         page:'lost'    },
      { icon:'map-pin',       color:'#10B981', label:'Ort vorschlagen',     sub:'Neuen POI auf der Karte',           page:'map'     },
    ] : [
      { icon:'book-open',     color:'#8B5CF6', label:'Tagebucheintrag',     sub:'Erlebnis, Foto oder Notiz',        page:'diary',   action:'openNew' },
      { icon:'target',        color:'#F59E0B', label:'Training aufzeichnen',sub:'Übung absolviert',                 page:'uebungen' },
      { icon:'heartbeat',     color:'#EF4444', label:'Tierarztbesuch',      sub:'Befund oder Impfung eintragen',    page:'health'   },
      { icon:'wave-sine',     color:'#06B6D4', label:'Gewicht messen',      sub:'Aktuelles Gewicht eintragen',      page:'health'   },
    ];

    // Overlay erstellen
    const ov = document.createElement('div');
    ov.id = 'fab-overlay';
    ov.style.cssText = 'position:fixed;inset:0;z-index:300;display:flex;flex-direction:column;justify-content:flex-end';
    ov.innerHTML = `
      <div id="fab-backdrop" style="position:absolute;inset:0;background:rgba(0,0,0,0.55);backdrop-filter:blur(2px)"></div>
      <div style="position:relative;z-index:1;background:var(--c-bg);border-radius:24px 24px 0 0;
                  padding:20px 16px calc(env(safe-area-inset-bottom,16px) + 16px);
                  box-shadow:0 -8px 32px rgba(0,0,0,0.2)">
        <div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:16px">
          <div style="font-size:var(--text-base);font-weight:700">
            ${isWelt ? 'Was möchtest du melden?' : 'Was möchtest du eintragen?'}
          </div>
          <button id="fab-close" style="background:var(--c-border);border:none;border-radius:50%;
                  width:28px;height:28px;cursor:pointer;display:flex;align-items:center;justify-content:center">
            <svg class="ph-icon" style="width:14px;height:14px"><use href="/icons/phosphor.svg#x"></use></svg>
          </button>
        </div>
        <div style="display:flex;flex-direction:column;gap:10px">
          ${options.map(o => `
            <button class="fab-option" data-page="${o.page}" data-action="${o.action || ''}"
                    style="display:flex;align-items:center;gap:14px;width:100%;
                           background:var(--c-bg-card);border:1px solid var(--c-border);
                           border-radius:14px;padding:14px 16px;cursor:pointer;text-align:left;
                           transition:background .12s">
              <div style="width:40px;height:40px;border-radius:12px;background:${o.color}18;
                           display:flex;align-items:center;justify-content:center;flex-shrink:0">
                <svg class="ph-icon" style="width:1.2rem;height:1.2rem;color:${o.color}">
                  <use href="/icons/phosphor.svg#${o.icon}"></use>
                </svg>
              </div>
              <div>
                <div style="font-size:var(--text-sm);font-weight:700">${o.label}</div>
                <div style="font-size:var(--text-xs);color:var(--c-text-secondary);margin-top:2px">${o.sub}</div>
              </div>
            </button>
          `).join('')}
        </div>
      </div>
    `;

    document.body.appendChild(ov);

    const _close = () => ov.remove();
    ov.querySelector('#fab-backdrop').addEventListener('click', _close);
    ov.querySelector('#fab-close').addEventListener('click', _close);
    ov.querySelectorAll('.fab-option').forEach(btn => {
      btn.addEventListener('click', () => {
        _close();
        const page = btn.dataset.page;
        const action = btn.dataset.action;
        navigateTo(page);
        if (action === 'openNew') {
          setTimeout(() => window.App?.callModule?.(page, 'openNew'), 400);
        }
      });
    });
  }

  // ── CHIP-KONFIGURATION ──────────────────────────────────────
  // Alle verfügbaren Chips mit Metadaten

  const _ALL_CHIPS = [
    { icon:'note-pencil',       label:'Notizblock',       page:'notes'          },
    { icon:'currency-eur',      label:'Ausgaben',         page:'expenses'       },
    { icon:'first-aid',         label:'Erste Hilfe',      page:'erste-hilfe'    },
    { icon:'handshake',         label:'Playdate',         page:'playdate'       },
    { icon:'chat-circle-dots',  label:'Nachrichten',      page:'chat'           },
    { icon:'sun',               label:'Wetter',           page:'wetter'         },

    { icon:'book-open',         label:'Tagebuch',         page:'diary'          },
    { icon:'heartbeat',         label:'Gesundheit',       page:'health'         },
    { icon:'target',            label:'Übungen',          page:'uebungen'       },
    { icon:'list-checks',       label:'Trainings-\npläne',page:'trainingsplaene'},
    { icon:'heart',             label:'Adoption',         page:'adoption'       },
    { icon:'house-line',        label:'Sitting',          page:'sitting'        },
    { icon:'books',             label:'Wiki',             page:'wiki'           },
    { icon:'scales',            label:'Wurfbörse',        page:'wurfboerse'     },
    { icon:'map-trifold',       label:'Karte',            page:'map'            },
    { icon:'push-pin',          label:'Forum',            page:'forum'          },
    { icon:'users',             label:'Freunde',          page:'friends'        },
    { icon:'paw-print',         label:'Gassi',            page:'walks'          },
    { icon:'skull',             label:'Giftköder',        page:'poison'         },
    { icon:'warning-circle',    label:'Rückrufe',         page:'recalls'        },
    { icon:'dog',               label:'Verlorene',        page:'lost'           },
    { icon:'path',              label:'Routen',           page:'routes'         },
    { icon:'calendar-dots',     label:'Events',           page:'events'         },
    { icon:'sparkle',           label:'Jobs',             page:'jobs'           },
    { icon:'book-open',         label:'Knigge',           page:'knigge'         },
    { icon:'film-slate',        label:'Filme',            page:'movies'         },
    { icon:'tree-structure',    label:'Zucht-\nkartei',   page:'zuchthunde',    role:'breeder' },
    { icon:'notebook',          label:'Wurfverw.',        page:'litters',       role:'breeder' },
    { icon:'sparkle',           label:'Social',           page:'social',        role:'social'  },
    { icon:'shield-check',      label:'Moderation',       page:'moderation',    role:'mod'     },
    { icon:'gear',              label:'Admin',            page:'admin',         role:'admin'   },
  ];

  const _DEFAULT_CONFIG = {
    jetzt: ['notes','expenses','erste-hilfe','playdate','chat','wetter','settings'],
    hund:  ['diary','health','uebungen','trainingsplaene','adoption','sitting','wiki','wurfboerse'],
    welt:  ['map','forum','friends','walks','poison','recalls','lost','routes','events','jobs','knigge','movies'],
  };

  // _cfgCache: wird beim Init aus DB geladen, Fallback localStorage → Default
  let _cfgCache = null;

  async function _loadConfigFromServer() {
    try {
      const res = await API.get('/profile/world-config');
      if (res?.config) {
        _cfgCache = res.config;
        try { localStorage.setItem('world_chips', JSON.stringify(_cfgCache)); } catch {}
        return;
      }
      // Noch nichts in DB: lokale Config hochladen (einmalige Migration)
      const local = (() => { try { return JSON.parse(localStorage.getItem('world_chips') || 'null'); } catch { return null; } })();
      if (local) {
        _cfgCache = local;
        API.put('/profile/world-config', { config: local }).catch(() => {});
        return;
      }
    } catch {}
    // Fallback: localStorage → Default
    try { _cfgCache = JSON.parse(localStorage.getItem('world_chips') || 'null') || _DEFAULT_CONFIG; }
    catch { _cfgCache = _DEFAULT_CONFIG; }
  }

  function _getConfig() {
    return _cfgCache || _DEFAULT_CONFIG;
  }

  function _saveConfig(cfg) {
    _cfgCache = cfg;
    try { localStorage.setItem('world_chips', JSON.stringify(cfg)); } catch {}
    if (_state?.user) {
      API.put('/profile/world-config', { config: cfg }).catch(() => {});
    }
  }
  function _chipMeta(page) {
    return _ALL_CHIPS.find(c => c.page === page) || null;
  }
  function _chipAllowed(chip) {
    const u = _state?.user;
    if (!chip?.role) return true;
    if (chip.role === 'breeder') return u?.rolle === 'breeder' || u?.rolle === 'admin';
    if (chip.role === 'social')  return u?.is_social_media   || u?.rolle === 'admin';
    if (chip.role === 'mod')     return u?.rolle === 'admin' || u?.rolle === 'moderator' || u?.is_moderator;
    if (chip.role === 'admin')   return u?.rolle === 'admin';
    return true;
  }
  function _chipsForWorld(world) {
    const pages = _getConfig()[world] || _DEFAULT_CONFIG[world];
    return pages.map(_chipMeta).filter(c => c && _chipAllowed(c));
  }

  // ── KONFIGURATIONS-MODAL ─────────────────────────────────────

  function _openConfigModal() {
    let cfg = JSON.parse(JSON.stringify(_getConfig())); // deep copy
    let _drag = null; // { page, fromWorld, ghost }

    const worldColors  = { jetzt:'rgba(196,132,58,0.6)', hund:'rgba(196,132,58,0.8)', welt:'rgba(99,130,220,0.6)' };
    const worldLabels  = { jetzt:'JETZT', hund:'HUND', welt:'WELT', pool:'Nicht verwendet' };
    const allAssigned  = () => new Set([...cfg.jetzt, ...cfg.hund, ...cfg.welt]);
    const poolChips    = () => _ALL_CHIPS.filter(c => _chipAllowed(c) && !allAssigned().has(c.page) && !c.pinned);

    const bottomNav = document.getElementById('bottom-nav');
    if (bottomNav) bottomNav.style.display = 'none';

    const ov = document.createElement('div');
    ov.id = 'wc-overlay';
    ov.style.cssText = 'position:fixed;inset:0;z-index:500;display:flex;flex-direction:column;justify-content:flex-end';
    document.body.appendChild(ov);

    const _removeDragListeners = () => {
      document.removeEventListener('touchmove',   _onDragMove);
      document.removeEventListener('touchend',    _onDragEnd);
      document.removeEventListener('touchcancel', _onDragEnd);
    };
    const _cancelDrag = () => {
      if (!_drag) return;
      _removeDragListeners();
      _drag.ghost?.remove();
      if (_drag.chipEl) _drag.chipEl.style.opacity = '';
      ov.querySelectorAll('.wc-zone').forEach(z => z.style.borderColor = 'transparent');
      _drag = null;
    };

    const _closeModal = () => {
      _cancelDrag(); // laufenden Drag abbrechen
      ov.remove();
      if (bottomNav) bottomNav.style.removeProperty('display');
    };

    function _render() {
      ov.innerHTML = `
        <div id="wc-bg" style="position:absolute;inset:0;background:rgba(0,0,0,0.7);backdrop-filter:blur(4px)"></div>
        <div id="wc-sheet" style="position:relative;z-index:1;background:rgba(18,22,32,0.97);border-radius:24px 24px 0 0;
                max-height:92vh;overflow-y:auto;-webkit-overflow-scrolling:touch;
                padding:0 0 calc(env(safe-area-inset-bottom,16px)+16px)">
          <!-- Header -->
          <div style="display:flex;align-items:center;justify-content:space-between;
                      padding:20px 20px 16px;position:sticky;top:0;background:rgba(18,22,32,0.97);
                      border-bottom:1px solid rgba(255,255,255,0.1);z-index:2">
            <button id="wc-cancel" style="background:none;border:none;color:rgba(255,255,255,0.5);
                    cursor:pointer;font-size:var(--text-sm);padding:8px">Abbrechen</button>
            <div style="font-size:var(--text-base);font-weight:700;color:white">Welten einrichten</div>
            <button id="wc-save" style="background:var(--c-primary);color:white;border:none;
                    border-radius:999px;padding:7px 16px;cursor:pointer;font-weight:700;
                    font-size:var(--text-sm)">Fertig</button>
          </div>
          <!-- Hinweis + Reset -->
          <div style="padding:10px 20px 6px;display:flex;align-items:center;justify-content:space-between;gap:12px">
            <div style="font-size:var(--text-xs);color:rgba(255,255,255,0.4);flex:1">
              Lang drücken &amp; ziehen zum Verschieben. ✕ zum Entfernen.
            </div>
            <button id="wc-reset" style="background:none;border:1px solid rgba(255,255,255,0.2);
                    color:rgba(255,255,255,0.5);border-radius:999px;padding:5px 12px;
                    cursor:pointer;font-size:11px;white-space:nowrap;flex-shrink:0">
              Zurücksetzen
            </button>
          </div>
          ${['jetzt','hund','welt','pool'].map(w => {
            const chips = w === 'pool' ? poolChips() : (cfg[w] || []).map(_chipMeta).filter(c => c && _chipAllowed(c));
            const col   = worldColors[w] || 'var(--c-border)';
            return `
              <div style="padding:12px 14px 6px">
                <div style="font-size:10px;font-weight:800;letter-spacing:.1em;color:rgba(255,255,255,0.5);
                            margin-bottom:8px;display:flex;align-items:center;gap:8px">
                  <span style="display:inline-block;width:12px;height:12px;border-radius:50%;
                               background:${col}"></span>
                  ${worldLabels[w]}
                  ${w !== 'pool' ? `<span style="opacity:.5">(${chips.length})</span>` : ''}
                </div>
                <div class="wc-zone" data-zone="${w}"
                     style="display:grid;grid-template-columns:repeat(4,1fr);grid-auto-rows:80px;gap:8px;
                            min-height:${w==='pool'&&chips.length===0?'40px':'auto'};
                            border:2px dashed transparent;border-radius:16px;padding:4px;
                            transition:border-color .2s">
                  ${chips.map(c => `
                    <div class="wc-chip" data-page="${c.page}" data-zone="${w}"
                         style="background:rgba(38,46,62,0.95);border:1.5px solid ${col};
                                border-radius:16px;padding:10px 4px 8px;height:80px;box-sizing:border-box;
                                display:flex;flex-direction:column;align-items:center;justify-content:center;gap:5px;
                                cursor:grab;position:relative;min-width:0;overflow:hidden;
                                user-select:none;-webkit-tap-highlight-color:transparent;touch-action:none">
                      ${!c.pinned ? `
                      <button class="wc-remove" data-page="${c.page}" data-zone="${w}"
                              style="position:absolute;top:-6px;right:-6px;width:18px;height:18px;
                                     border-radius:50%;background:#EF4444;border:none;cursor:pointer;
                                     display:flex;align-items:center;justify-content:center;z-index:2">
                        <svg class="ph-icon" style="width:9px;height:9px;color:white">
                          <use href="/icons/phosphor.svg#x"></use>
                        </svg>
                      </button>` : `
                      <div style="position:absolute;top:-4px;right:-4px;width:14px;height:14px;
                                  border-radius:50%;background:rgba(196,132,58,0.9);
                                  display:flex;align-items:center;justify-content:center"
                           title="Immer sichtbar">
                        <svg class="ph-icon" style="width:8px;height:8px;color:white">
                          <use href="/icons/phosphor.svg#lock-simple"></use>
                        </svg>
                      </div>`}
                      <svg class="ph-icon" style="width:1.4rem;height:1.4rem;color:white;flex-shrink:0">
                        <use href="/icons/phosphor.svg#${c.icon}"></use>
                      </svg>
                      <span style="font-size:9px;font-weight:600;color:rgba(255,255,255,0.9);
                                   line-height:1.2;text-align:center;width:100%;
                                   overflow:hidden;display:-webkit-box;
                                   -webkit-line-clamp:2;-webkit-box-orient:vertical;
                                   padding:0 2px">${c.label.replace('\n','<br>')}</span>
                    </div>
                  `).join('')}
                  ${chips.length === 0 ? `<div style="grid-column:1/-1;text-align:center;
                    font-size:var(--text-xs);color:rgba(255,255,255,0.35);padding:12px">
                    ${w==='pool'?'Alle Chips sind zugeordnet':'Leer — ziehe Chips hierher'}
                  </div>` : ''}
                </div>
              </div>
            `;
          }).join('')}
        </div>
      `;
      _bindEvents();
    }

    function _bindEvents() {
      ov.querySelector('#wc-bg')?.addEventListener('click', _closeModal);
      ov.querySelector('#wc-cancel')?.addEventListener('click', _closeModal);
      ov.querySelector('#wc-reset')?.addEventListener('click', () => {
        cfg = JSON.parse(JSON.stringify(_DEFAULT_CONFIG));
        _render();
      });
      ov.querySelector('#wc-save')?.addEventListener('click', () => {
        _saveConfig(cfg);
        _closeModal();
        _renderJetzt(); _renderHund(); _renderWelt();
      });

      // Remove-Buttons
      ov.querySelectorAll('.wc-remove').forEach(btn => {
        btn.addEventListener('click', e => {
          e.stopPropagation();
          const page = btn.dataset.page, zone = btn.dataset.zone;
          const meta = _chipMeta(page);
          if (meta?.pinned) return; // gepinnte Chips können nicht entfernt werden
          if (zone !== 'pool') cfg[zone] = cfg[zone].filter(p => p !== page);
          _render();
        });
      });

      // Touch-Drag
      ov.querySelectorAll('.wc-chip').forEach(chip => {
        chip.addEventListener('touchstart', e => _onDragStart(e, chip), { passive: true });
      });
      document.addEventListener('touchmove', _onDragMove, { passive: false });
      document.addEventListener('touchend', _onDragEnd);
    }

    function _onDragStart(e, chipEl) {
      if (_drag) _cancelDrag();
      const touch = e.touches[0];
      // Drag erst nach Bewegungs-Threshold aktivieren (verhindert Scroll-Konflikte)
      _drag = {
        page: chipEl.dataset.page, zone: chipEl.dataset.zone,
        chipEl, ghost: null, dropZone: null, active: false,
        startX: touch.clientX, startY: touch.clientY, ox: 0, oy: 0,
      };
      document.addEventListener('touchmove',   _onDragMove,  { passive: false });
      document.addEventListener('touchend',    _onDragEnd);
      document.addEventListener('touchcancel', _onDragEnd);
    }

    function _activateDrag(touch) {
      const rect = _drag.chipEl.getBoundingClientRect();
      _drag.ox = _drag.startX - rect.left;
      _drag.oy = _drag.startY - rect.top;
      _drag.active = true;
      const ghost = _drag.chipEl.cloneNode(true);
      ghost.querySelectorAll('button').forEach(b => b.style.display = 'none');
      ghost.style.position     = 'fixed';
      ghost.style.zIndex       = '9999';
      ghost.style.opacity      = '0.9';
      ghost.style.pointerEvents= 'none';
      ghost.style.transform    = 'scale(1.08) rotate(-2deg)';
      ghost.style.width        = rect.width  + 'px';
      ghost.style.height       = rect.height + 'px';
      ghost.style.left         = (touch.clientX - _drag.ox) + 'px';
      ghost.style.top          = (touch.clientY - _drag.oy) + 'px';
      ghost.style.transition   = 'none';
      ghost.style.boxShadow    = '0 8px 24px rgba(0,0,0,0.5)';
      document.body.appendChild(ghost);
      _drag.ghost = ghost;
      _drag.chipEl.style.opacity = '0.2';
    }

    function _onDragMove(e) {
      if (!_drag) return;
      const touch = e.touches[0];

      if (!_drag.active) {
        const dx = Math.abs(touch.clientX - _drag.startX);
        const dy = Math.abs(touch.clientY - _drag.startY);
        if (dx < 8 && dy < 8) return; // Threshold noch nicht erreicht
        _activateDrag(touch);
      }

      e.preventDefault(); // Scroll erst NACH Threshold blockieren
      _drag.ghost.style.left = (touch.clientX - _drag.ox) + 'px';
      _drag.ghost.style.top  = (touch.clientY - _drag.oy) + 'px';

      let foundZone = null;
      ov.querySelectorAll('.wc-zone').forEach(z => {
        const r = z.getBoundingClientRect();
        const over = touch.clientX >= r.left && touch.clientX <= r.right
                  && touch.clientY >= r.top  && touch.clientY <= r.bottom;
        z.style.borderColor = over ? (worldColors[z.dataset.zone] || 'rgba(196,132,58,0.8)') : 'transparent';
        if (over) foundZone = z.dataset.zone;
      });
      _drag.dropZone = foundZone;
    }

    function _onDragEnd() {
      if (!_drag) return;
      _removeDragListeners();

      const { ghost, chipEl, dropZone, page, zone: fromZone, active } = _drag;
      _drag = null;
      ov.querySelectorAll('.wc-zone').forEach(z => z.style.borderColor = 'transparent');
      ghost?.remove();
      if (chipEl) chipEl.style.opacity = '';

      if (!active) return; // nur Tap, kein Drag — nichts verschieben

      const meta = _chipMeta(page);
      if (dropZone && dropZone !== fromZone && !(meta?.pinned && dropZone === 'pool')) {
        if (fromZone !== 'pool') cfg[fromZone] = cfg[fromZone].filter(p => p !== page);
        if (dropZone !== 'pool' && !cfg[dropZone].includes(page)) cfg[dropZone].push(page);
        _render();
      }
    }

    _render();
  }

  // ── PANORAMA HINTERGRUNDBILD ─────────────────────────────────

  async function _loadDailyImage(dog) {
    if (!dog) return null;
    const todayKey = 'bg_' + new Date().toISOString().slice(0, 10);
    const cached = _wLoad(todayKey);
    if (cached?.data) return cached.data;
    try {
      const r = await _cachedGet(`diary_${dog.id}`, `/dogs/${dog.id}/diary?limit=30`);
      const entries = r.data?.entries || r.data || [];
      const withPhotos = entries.filter(e => (e.foto_urls?.length || e.foto_url));
      if (!withPhotos.length) { const u = dog.foto_url || null; if(u) _wSave(todayKey, u); return u; }
      const day = Math.floor(Date.now() / 86400000);
      const entry = withPhotos[day % withPhotos.length];
      const url = (entry.foto_urls?.[0] || entry.foto_url);
      _wSave(todayKey, url);
      return url;
    } catch { return dog.foto_url || null; }
  }

  function _applyBgImage(url) {
    const track = document.getElementById('worlds-track');
    if (!track) return;
    if (url) {
      const img = new Image();
      img.onload  = () => { track.style.backgroundImage = `url('${url}')`; track.style.backgroundSize = '100% auto'; track.style.backgroundPosition = '0 40%'; track.style.backgroundRepeat = 'no-repeat'; };
      img.onerror = () => _applyBgImage(null);
      img.src = url;
    } else {
      track.style.backgroundImage = 'linear-gradient(160deg,#1a1f35 0%,#16213e 33%,#1a2535 67%,#0f1921 100%)';
      track.style.backgroundSize  = '100% 100%';
    }
  }

  // ── CHIP-HELPER ──────────────────────────────────────────────

  function _chip(icon, label, page) {
    return `
      <div class="world-chip" data-wnav="${page}">
        <svg class="ph-icon" style="width:1.4rem;height:1.4rem">
          <use href="/icons/phosphor.svg#${icon}"></use>
        </svg>
        <span class="world-chip-label">${label}</span>
      </div>`;
  }

  // ── JETZT WORLD ──────────────────────────────────────────────

  async function _renderJetzt() {
    const el = document.getElementById('wj-content');
    if (!el) return;

    const user = _state?.user;
    el.innerHTML = _skeleton(3);

    const [weatherRes, dogsRes, alertsRes] = await Promise.allSettled([
      _getCachedWeather(),
      user ? _cachedGet('dogs', '/dogs') : Promise.resolve({ data: [], fromCache: false, ageMin: 0 }),
      user ? _getNearbyAlerts() : Promise.resolve([]),
    ]);

    const weatherObj = weatherRes.value || { data: null, fromCache: false, ageMin: 0 };
    const dogsObj    = dogsRes.value   || { data: [], fromCache: false, ageMin: 0 };
    const w          = weatherObj.data;
    const dogList    = dogsObj.data || [];
    const dog        = dogList[0] || null;
    const alertList  = alertsRes.value || [];
    const isOffline  = weatherObj.fromCache && dogsObj.fromCache;
    const staleMin   = Math.max(weatherObj.ageMin || 0, dogsObj.ageMin || 0);

    // Panorama-Bild setzen (nur wenn noch kein Bild vorhanden)
    const track = document.getElementById('worlds-track');
    if (dog && !track?.style.backgroundImage?.startsWith('url')) {
      _loadDailyImage(dog).then(_applyBgImage);
    } else if (!dog) { _applyBgImage(null); }

    const hour      = new Date().getHours();
    const greet     = hour < 5 ? 'Gute Nacht' : hour < 12 ? 'Guten Morgen' : hour < 18 ? 'Hallo' : 'Guten Abend';
    const firstName = user?.name?.split(' ')[0] || '';
    const dayStr    = new Date().toLocaleDateString('de-DE', { weekday:'long', day:'numeric', month:'long' });
    const stale     = isOffline && staleMin > 5
      ? `<span style="font-size:9px;opacity:0.5;margin-left:6px">· Offline</span>` : '';
    const weatherLine = w
      ? `${Math.round(w.temp_c)}° ${_esc(w.desc?.split(' ')[0] || '')} · ${Math.round(w.wind_kmh || 0)} km/h · ${w.precip_prob || 0}% Regen`
      : '';

    // Streak für 3er-Chip-Zeile
    let streakVal = '—', streakCol = 'rgba(255,255,255,0.4)';
    if (user && dog) {
      try {
        const sr = await _cachedGet(`streak_${dog.id}`, `/streak/${dog.id}`);
        const s = sr.data;
        const streak = s?.current_streak || 0;
        const trainedToday = s?.last_training_date === new Date().toISOString().slice(0,10);
        streakCol = trainedToday ? '#10B981' : (streak > 0 ? '#F59E0B' : 'rgba(255,255,255,0.4)');
        streakVal = streak > 0
          ? (trainedToday ? `✓ ${streak} Tage` : `🔥 ${streak} Tage`)
          : (trainedToday ? '✓ Heute' : 'Heute starten');
      } catch {}
    }

    // Alert-Reminder
    const alertHtml = alertList.slice(0,1).map(a => `
      <div class="world-reminder" data-wnav="${a.page}" style="border-color:rgba(239,68,68,0.5)">
        <svg class="ph-icon" style="width:1.1rem;height:1.1rem;color:#EF4444">
          <use href="/icons/phosphor.svg#${a.icon}"></use>
        </svg>
        <span style="font-size:var(--text-xs);font-weight:700;color:#FCA5A5">${_esc(a.title)}</span>
      </div>`).join('');

    // Feature Chips aus Config
    const features = user
      ? _chipsForWorld('jetzt')
      : [
          { icon:'sun',      label:'Wetter',      page:'wetter'      },
          { icon:'first-aid',label:'Erste Hilfe', page:'erste-hilfe' },
          { icon:'gear',     label:'Anmelden',    page:'settings'    },
        ];

    // User-Avatar für JETZT Info-Card
    const uFoto = _state?.user?.avatar_url;
    const uInitial = firstName ? firstName.charAt(0).toUpperCase() : '?';
    const userAvatarHtml = `
      <div data-wnav="settings" style="flex-shrink:0;cursor:pointer"
           title="Mein Profil">
        ${uFoto
          ? `<img src="${_esc(uFoto)}" style="width:40px;height:40px;border-radius:50%;
                    object-fit:cover;border:2px solid rgba(255,255,255,0.35);display:block">`
          : `<div style="width:40px;height:40px;border-radius:50%;background:var(--c-primary);
                    display:flex;align-items:center;justify-content:center;
                    font-size:var(--text-base);font-weight:800;color:white;
                    border:2px solid rgba(255,255,255,0.35)">${uInitial}</div>`}
      </div>`;

    el.innerHTML = `
      <div class="world-top">
        <div class="world-info-card">
          <div style="display:flex;align-items:center;gap:12px">
            <div style="flex:1;min-width:0">
              <div class="world-info-title">
                ${_esc(greet)}${firstName ? `, <span style="color:var(--c-primary)">${_esc(firstName)}</span>` : ''}${stale}
              </div>
              <div class="world-info-sub">${_esc(dayStr)}${weatherLine ? ' · ' + weatherLine : ''}</div>
            </div>
            ${user ? userAvatarHtml : ''}
          </div>
        </div>
        ${alertHtml}
        ${user && dog ? `
        <div class="wj-chip-row">
          <div class="wj-chip" data-wnav="uebungen">
            <svg class="ph-icon" style="width:1.2rem;height:1.2rem;color:${streakCol}">
              <use href="/icons/phosphor.svg#target"></use></svg>
            <span class="wj-chip-label">Streak</span>
            <span class="wj-chip-val">${streakVal}</span>
          </div>
          <div class="wj-chip" data-wnav="routes">
            <svg class="ph-icon" style="width:1.2rem;height:1.2rem;color:var(--c-primary)">
              <use href="/icons/phosphor.svg#path"></use></svg>
            <span class="wj-chip-label">Gassirunde</span>
            <span class="wj-chip-val" id="wj-route-val">…</span>
          </div>
          <div class="wj-chip" data-wnav="uebungen">
            <svg class="ph-icon" style="width:1.2rem;height:1.2rem;color:var(--c-primary)">
              <use href="/icons/phosphor.svg#barbell"></use></svg>
            <span class="wj-chip-label">Übung</span>
            <span class="wj-chip-val" id="wj-exercise-val">…</span>
          </div>
        </div>` : ''}
      </div>
      <div class="world-bottom">
        <div class="world-section-label">Deine Bereiche</div>
        <div class="world-chips-grid">
          ${features.map(f => _chip(f.icon, f.label, f.page)).join('')}
        </div>
        <div class="world-footer-links">
          <span data-wnav="impressum">Impressum</span>
        </div>
      </div>
    `;
    el.querySelectorAll('[data-wnav]').forEach(e => e.addEventListener('click', () => navigateTo(e.dataset.wnav)));

    if (user && dog) {
      _loadJetztExercise(dog);
      _loadJetztRoute();
    }
  }

  async function _loadJetztExercise(dog) {
    const valEl = document.getElementById('wj-exercise-val');
    if (!valEl) return;
    try {
      const res = await _cachedGet(`dash_${dog.id}`, `/dogs/${dog.id}/welcome-dashboard`);
      const ex = res.data?.daily_exercise;
      valEl.textContent = ex?.name || '—';
    } catch { valEl.textContent = '—'; }
  }

  async function _loadJetztRoute() {
    const valEl = document.getElementById('wj-route-val');
    if (!valEl) return;

    // Tages-Cache (gleicher Key wie welcome.js)
    const today    = new Date().toISOString().slice(0, 10);
    const cacheKey = 'by_daily_route_' + today;
    const cached   = localStorage.getItem(cacheKey);
    if (cached) {
      try { _applyJetztRoute(valEl, JSON.parse(cached)); return; } catch {}
    }

    let loc;
    try { loc = await API.getLocation({ timeout: 5000, maximumAge: 300000 }); }
    catch { valEl.textContent = 'Standort nötig'; return; }

    const dayIdx = Math.floor(Date.now() / 86400000);
    const km     = [2, 4, 6][dayIdx % 3];
    const seed   = dayIdx % 5;
    try {
      const result = await API.post('/routes/suggest', { lat: loc.lat, lon: loc.lon, distance_km: km, seed });
      if (!result?.gps_track?.length) { valEl.textContent = 'Keine Route gefunden'; return; }
      localStorage.setItem(cacheKey, JSON.stringify(result));
      // alte Einträge aufräumen
      Object.keys(localStorage)
        .filter(k => k.startsWith('by_daily_route_') && k !== cacheKey)
        .forEach(k => localStorage.removeItem(k));
      _applyJetztRoute(valEl, result);
    } catch { valEl.textContent = 'Route nicht verfügbar'; }
  }

  function _applyJetztRoute(valEl, result) {
    const durStr = result.dauer_min < 60
      ? `${result.dauer_min} min`
      : `${Math.floor(result.dauer_min / 60)}h ${result.dauer_min % 60 > 0 ? ' ' + (result.dauer_min % 60) + 'min' : ''}`;
    valEl.textContent = `${result.distanz_km} km · ${durStr}`;
  }

  // ── HUND WORLD ───────────────────────────────────────────────

  async function _renderHund() {
    const el = document.getElementById('wh-content');
    if (!el) return;
    const user = _state?.user;

    if (!user) {
      el.innerHTML = `
        <div class="world-info-card" style="text-align:center">
          <div style="font-size:4rem;margin-bottom:12px">🐾</div>
          <div class="world-info-title">Dein Hund wartet</div>
          <div class="world-info-sub" style="margin-bottom:20px">Melde dich an um loszulegen</div>
          <button class="btn btn-primary" onclick="Worlds.navigateTo('settings')">Anmelden</button>
        </div>`;
      return;
    }

    el.innerHTML = _skeleton(4);
    const dogsRes = await _cachedGet('dogs', '/dogs');
    const dogs = dogsRes.data || [];

    if (!dogs.length) {
      el.innerHTML = `
        <div class="world-info-card" style="text-align:center">
          <div style="font-size:4rem;margin-bottom:12px">🐶</div>
          <div class="world-info-title">Noch kein Hund angelegt</div>
          <div class="world-info-sub" style="margin-bottom:20px">Erstelle das Profil deines Hundes</div>
          <button class="btn btn-primary" onclick="Worlds.navigateTo('dog-profile')">Hund anlegen</button>
        </div>`;
      return;
    }

    // Hunde global cachen für schnelles Cycling
    _dogs = dogs;
    if (_dogIdx >= _dogs.length) _dogIdx = 0;
    const dog = _dogs[_dogIdx];

    const [streakRes, diaryRes] = await Promise.allSettled([
      _cachedGet(`streak_${dog.id}`, `/streak/${dog.id}`),
      _cachedGet(`diary_${dog.id}`, `/dogs/${dog.id}/diary?limit=1`),
    ]);
    const streak    = streakRes.value?.data ?? streakRes.value;
    const diaryData = diaryRes.value?.data ?? diaryRes.value;
    const lastEntry = diaryData?.entries?.[0] || diaryData?.[0] || null;

    const ageStr    = dog.alter_jahre ? _fmtAlter(dog.alter_jahre) : '';
    const stats     = [dog.rasse, ageStr, dog.gewicht_kg ? dog.gewicht_kg + ' kg' : null].filter(Boolean).join(' · ');
    const otherDogs = _dogs.filter((_, i) => i !== _dogIdx);
    const chips     = _chipsForWorld('hund');

    // Avatar des aktuellen Hundes (links, → Hundeprofil)
    const dogAvatarHtml = dog.foto_url
      ? `<img src="${_esc(dog.foto_url)}" style="width:44px;height:44px;border-radius:50%;
                  object-fit:cover;border:2px solid rgba(255,255,255,0.35);
                  cursor:pointer;flex-shrink:0" id="wh-avatar">`
      : `<div id="wh-avatar" style="width:44px;height:44px;border-radius:50%;background:var(--c-primary);
                  display:flex;align-items:center;justify-content:center;
                  font-size:1.3rem;flex-shrink:0;cursor:pointer;
                  border:2px solid rgba(255,255,255,0.25)">🐶</div>`;

    // Andere Hunde-Avatare (rechts, überlagernd, → Hund wechseln)
    const otherAvatarsHtml = otherDogs.length > 0 ? `
      <div style="display:flex;align-items:center;flex-shrink:0">
        ${otherDogs.slice(0, 4).map((d, i) => {
          const targetIdx = _dogs.indexOf(d);
          return `
            <div class="wh-other-dog" data-idx="${targetIdx}"
                 style="width:30px;height:30px;border-radius:50%;overflow:hidden;
                        border:2px solid rgba(255,255,255,0.4);
                        margin-left:${i > 0 ? '-10px' : '0'};
                        cursor:pointer;position:relative;z-index:${10 - i}">
              ${d.foto_url
                ? `<img src="${_esc(d.foto_url)}" style="width:100%;height:100%;object-fit:cover">`
                : `<div style="width:100%;height:100%;background:rgba(100,70,40,0.85);
                               display:flex;align-items:center;justify-content:center;
                               font-size:10px;font-weight:700;color:white">
                     ${_esc(d.name?.charAt(0)?.toUpperCase() || '?')}
                   </div>`}
            </div>`;
        }).join('')}
      </div>` : '';

    el.innerHTML = `
      <div class="world-top">
        <div class="world-info-card">
          <div style="display:grid;grid-template-columns:1fr auto 1fr;align-items:center">
            <div style="justify-self:start">${dogAvatarHtml}</div>
            <div id="wh-cycle-btn" style="text-align:center;
                 cursor:${_dogs.length > 1 ? 'pointer' : 'default'};padding:0 8px">
              <div class="world-info-title">${_esc(dog.name)}</div>
              ${stats ? `<div class="world-info-sub">${_esc(stats)}</div>` : ''}
            </div>
            <div style="justify-self:end;display:flex;align-items:center">${otherAvatarsHtml}</div>
          </div>
        </div>
      </div>
      <div class="world-bottom">
        <div class="world-section-label">Alles über ${_esc(dog.name)}</div>
        <div class="world-chips-grid">
          ${chips.map(c => _chip(c.icon, c.label, c.page)).join('')}
        </div>
        <div class="world-footer-links">
          <span data-wnav="gruender">Die 100 Gründer</span>
        </div>
      </div>
    `;

    // Avatar → Hundeprofil
    el.querySelector('#wh-avatar')?.addEventListener('click', () => navigateTo('dog-profile'));
    // Chips
    el.querySelectorAll('[data-wnav]').forEach(e => e.addEventListener('click', () => navigateTo(e.dataset.wnav)));
    // Name → nächster Hund
    if (_dogs.length > 1) {
      el.querySelector('#wh-cycle-btn')?.addEventListener('click', () => {
        _dogIdx = (_dogIdx + 1) % _dogs.length;
        _renderHund();
      });
    }
    // Andere Hund-Avatare → zu diesem Hund wechseln
    el.querySelectorAll('.wh-other-dog').forEach(btn => {
      btn.addEventListener('click', () => {
        const idx = parseInt(btn.dataset.idx);
        if (!isNaN(idx) && idx !== _dogIdx) { _dogIdx = idx; _renderHund(); }
      });
    });
  }

  // ── WELT WORLD ───────────────────────────────────────────────

  const _QUOTES = [
    { t:'Ein Hund ist die einzige Kreatur, die dich mehr liebt als sich selbst.', a:'Josh Billings' },
    { t:'Hunde haben Besitzer. Katzen haben Personal.', a:'Unbekannt' },
    { t:'Bis man einen Hund geliebt hat, ist ein Teil der Seele unerwacht.', a:'Anatole France' },
    { t:'Hunde sind nicht unser ganzes Leben — aber sie machen unser Leben ganz.', a:'Roger Caras' },
    { t:'Der Hund hat nur einen Fehler: Er vertraut dem Menschen zu sehr.', a:'Unbekannt' },
    { t:'Ein treuer Hund ist besser als ein unzuverlässiger Mensch.', a:'Deutsches Sprichwort' },
    { t:'Wer einen Hund hat, gibt nie allein zu Tisch.', a:'Unbekannt' },
    { t:'Die reinste Form der Freude ist die Freude eines Hundes.', a:'Milan Kundera' },
    { t:'Hunde lachen nicht mit dem Mund, sondern mit dem Schwanz.', a:'Max Eastman' },
    { t:'Ein Haus ohne Hund ist ein leeres Haus.', a:'Unbekannt' },
    { t:'Wenn du verstehen willst, was Liebe ist, beobachte einen Hund.', a:'Unbekannt' },
    { t:'Der Hund fragt nicht warum. Er fragt nur: wann gehen wir?', a:'Unbekannt' },
    { t:'Hunde riechen nicht schlecht. Sie riechen nur anders als wir denken.', a:'Unbekannt' },
    { t:'In einer Welt voller Unsicherheiten ist der Hund das Verlässlichste.', a:'Unbekannt' },
    { t:'Ein Spaziergang mit einem Hund ist nie wirklich ein Umweg.', a:'Unbekannt' },
    { t:'Hunde bringen das Beste im Menschen hervor.', a:'Unbekannt' },
    { t:'Wer mit Hunden liegt, steht mit Flöhen auf — und mit einem Lächeln.', a:'Unbekannt' },
    { t:'Ein Hund weiß, wann du traurig bist. Er weiß nicht warum. Aber er ist da.', a:'Unbekannt' },
    { t:'Das Geheimnis eines Hundes: Er beurteilt dich nie.', a:'Unbekannt' },
    { t:'Der Hund ist Gattung: Mensch.', a:'Charles M. Schulz' },
    { t:'Hunde haben viel zu sagen. Wir haben verlernt zuzuhören.', a:'Unbekannt' },
    { t:'Ein guter Hund macht aus einem schlechten Tag einen erträglichen.', a:'Unbekannt' },
    { t:'Der Hund ist der philosophischste aller Haustiere.', a:'George Graham Vest' },
    { t:'Wer einen Hund hat, braucht keinen Therapeuten.', a:'Unbekannt' },
    { t:'Hunde lieben bedingungslos. Das ist ihr größtes Geschenk.', a:'Unbekannt' },
    { t:'Der schönste Empfang ist der eines Hundes an der Tür.', a:'Unbekannt' },
    { t:'Ein Hund ändert dein Leben. Meistens zum Besseren.', a:'Unbekannt' },
    { t:'Hunde altern in Würde. Menschen könnten von ihnen lernen.', a:'Unbekannt' },
    { t:'Kein Psychiater der Welt kann so gut zuhören wie ein Hund.', a:'Unbekannt' },
    { t:'Wo Hunde sind, da ist das Zuhause.', a:'Unbekannt' },
    { t:'Der Hund hat keinen Begriff von Vergangenheit oder Zukunft. Er lebt.', a:'Milan Kundera' },
  ];

  function _renderWelt() {
    const el = document.getElementById('ww-content');
    if (!el) return;
    const user     = _state?.user;
    const isMod    = user?.rolle === 'admin' || user?.rolle === 'moderator' || user?.is_moderator;
    const isAdmin  = user?.rolle === 'admin';
    const isSocial = user?.is_social_media || isAdmin;

    // Tagesbasierter Spruch
    const day   = Math.floor(Date.now() / 86400000);
    const quote = _QUOTES[day % _QUOTES.length];

    const chips = _chipsForWorld('welt');

    el.innerHTML = `
      <div class="world-top">
        <div class="world-info-card">
          <div style="font-size:11px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;
                      color:rgba(255,255,255,0.45);margin-bottom:10px">Gedanke des Tages</div>
          <div style="font-size:var(--text-sm);color:white;line-height:1.55;font-style:italic">
            »${_esc(quote.t)}«
          </div>
          <div style="font-size:10px;color:rgba(255,255,255,0.45);margin-top:8px;text-align:right">
            — ${_esc(quote.a)}
          </div>
        </div>
      </div>
      <div class="world-bottom">
        <div class="world-section-label">Die Welt da draußen</div>
        <div class="world-chips-grid">
          ${chips.map(c => _chip(c.icon, c.label, c.page)).join('')}
        </div>
        <div class="world-footer-links">
          <span data-wnav="datenschutz">Datenschutz</span>
        </div>
      </div>
    `;
    el.querySelectorAll('[data-wnav]').forEach(e => e.addEventListener('click', () => navigateTo(e.dataset.wnav)));
  }

  // ── HELPERS ──────────────────────────────────────────────────

  async function _getCachedWeather() {
    const cached = _wLoad('weather');
    let fresh = null, pos = null;
    try {
      pos   = await API.getLocation({ timeout: 5000, maximumAge: 600_000 });
      fresh = await API.weather.get(pos.lat, pos.lon);
      _wSave('weather', fresh);
    } catch {}
    const data = fresh ?? cached?.data ?? null;
    return { data, fromCache: !fresh, ageMin: fresh ? 0 : (cached?.ageMin ?? null) };
  }

  async function _getWeather() {
    const r = await _getCachedWeather();
    return r.data;
  }

  async function _getNearbyAlerts() {
    const out = [];
    try {
      const pos = await API.getLocation({ timeout: 4000, maximumAge: 600_000 });
      const [p, l] = await Promise.allSettled([
        API.get(`/poison/nearby?lat=${pos.lat}&lon=${pos.lon}&radius=5`).catch(() => []),
        API.get(`/lost/nearby?lat=${pos.lat}&lon=${pos.lon}&radius=5`).catch(() => []),
      ]);
      if (p.value?.length) out.push({ icon:'skull',         color:'#EF4444', title:'Giftköder in der Nähe', sub:`${p.value.length} Meldung${p.value.length>1?'en':''}`, page:'poison' });
      if (l.value?.length) out.push({ icon:'dog',           color:'#3B82F6', title:'Verlorener Hund',       sub:`${l.value.length} Meldung${l.value.length>1?'en':''}`, page:'lost' });
    } catch {}
    return out;
  }

  function _skeleton(n) {
    return Array.from({length: n}, (_, i) => `
      <div style="height:${i===0?80:60}px;background:var(--c-border);border-radius:12px;margin:0 16px 10px;opacity:${0.7 - i*0.1};animation:pulse 1.5s ease-in-out infinite"></div>
    `).join('');
  }

  function _fmtDate(d) {
    if (!d) return '';
    try { return new Date(d).toLocaleDateString('de-DE', { day:'numeric', month:'short' }); }
    catch { return d; }
  }

  function _fmtAlter(j) {
    if (!j) return '';
    if (j < 1) return `${Math.round(j * 12)} Monate`;
    return j < 2 ? '1 Jahr' : `${Math.round(j)} Jahre`;
  }

  function _esc(s) {
    if (s == null) return '';
    return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
  }

  return { init, show, hide, navigateTo, openConfig: _openConfigModal, get _visible() { return _visible; } };

})();
