diff --git a/web/app.py b/web/app.py
index bcc983a..17d560c 100644
--- a/web/app.py
+++ b/web/app.py
@@ -133,7 +133,7 @@ async def security_headers(request: Request, call_next):
"default-src 'self'; "
"script-src 'self' https://cdn.tailwindcss.com https://unpkg.com; "
"style-src 'self' https://cdn.tailwindcss.com https://unpkg.com 'unsafe-inline'; "
- "img-src 'self' data: https://*.tile.openstreetmap.org https://tile.openstreetmap.org; "
+ "img-src 'self' data: https://*.tile.openstreetmap.org https://tile.openstreetmap.org https://unpkg.com; "
"connect-src 'self'; frame-ancestors 'none';"
)
return resp
diff --git a/web/static/map.js b/web/static/map.js
index d9dc155..9cc6c3e 100644
--- a/web/static/map.js
+++ b/web/static/map.js
@@ -1,15 +1,13 @@
// lazyflat — Leaflet flat map
-// A single Leaflet map instance; re-initialised after every HTMX swap of
-// the Wohnungen body. Also flushes size when the view toggle flips from
-// list to map (Leaflet needs invalidateSize on a hidden-then-shown map).
+// Initialised LAZILY: we only build the Leaflet instance when the container
+// actually has a rendered size (> 0 height). Building it on a hidden 0×0
+// container leaves Leaflet in a state where tiles never load.
let mapInstance = null;
const BERLIN_CENTER = [52.52, 13.405];
const BERLIN_ZOOM = 11;
-function initFlatsMap() {
- const el = document.getElementById("flats-map");
- if (!el || typeof L === "undefined") return;
+function buildMap(el) {
if (mapInstance) {
try { mapInstance.remove(); } catch (e) {}
mapInstance = null;
@@ -25,29 +23,23 @@ function initFlatsMap() {
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
attribution: "© OpenStreetMap",
maxZoom: 18,
+ subdomains: "abc",
}).addTo(mapInstance);
let data = [];
- try {
- data = JSON.parse(el.dataset.flats || "[]");
- } catch (e) {
- console.warn("flats-map: bad JSON in data-flats", e);
- }
-
+ try { data = JSON.parse(el.dataset.flats || "[]"); } catch (e) {}
const bounds = [];
data.forEach((f) => {
if (typeof f.lat !== "number" || typeof f.lng !== "number") return;
- const m = L.marker([f.lat, f.lng]).addTo(mapInstance);
const rent = f.rent ? Math.round(f.rent) + " €" : "";
const rooms = f.rooms ? f.rooms + " Zi" : "";
const size = f.size ? Math.round(f.size) + " m²" : "";
const meta = [rooms, size, rent].filter(Boolean).join(" · ");
const safeAddr = (f.address || "").replace(/${safeAddr}` +
- (meta ? `
${meta}` : "") +
- `
Zur Anzeige →`,
+ L.marker([f.lat, f.lng]).addTo(mapInstance).bindPopup(
+ `${safeAddr}` + (meta ? `
${meta}` : "") +
+ `
Zur Anzeige →`,
);
bounds.push([f.lat, f.lng]);
});
@@ -58,10 +50,20 @@ function initFlatsMap() {
}
}
-function flushMapSize() {
- if (mapInstance) {
- setTimeout(() => mapInstance.invalidateSize(), 50);
+function ensureMap() {
+ const el = document.getElementById("flats-map");
+ if (!el || typeof L === "undefined") return;
+
+ // Container not actually visible yet → bail, we'll retry when the view toggles.
+ if (el.clientHeight < 10) return;
+
+ // Existing instance bound to THIS element → just recheck size.
+ if (mapInstance && mapInstance._container === el) {
+ try { mapInstance.invalidateSize(); } catch (e) {}
+ return;
}
+
+ buildMap(el);
}
function wireViewToggle() {
@@ -69,33 +71,35 @@ function wireViewToggle() {
if (r.dataset.wired === "1") return;
r.dataset.wired = "1";
r.addEventListener("change", (e) => {
- try {
- localStorage.setItem("lazyflat_view_mode", e.target.value);
- } catch (err) {}
- flushMapSize();
+ try { localStorage.setItem("lazyflat_view_mode", e.target.value); } catch (err) {}
+ // Wait for CSS :has() to reflow, then build/size the map.
+ requestAnimationFrame(() => requestAnimationFrame(ensureMap));
+ // belt & suspenders — re-check a couple more times in case of layout shifts.
+ setTimeout(ensureMap, 120);
+ setTimeout(ensureMap, 400);
});
});
}
function restoreView() {
let stored = null;
- try {
- stored = localStorage.getItem("lazyflat_view_mode");
- } catch (err) {}
+ try { stored = localStorage.getItem("lazyflat_view_mode"); } catch (e) {}
if (!stored) return;
- const el = document.querySelector(
- `input[name="view_mode"][value="${stored}"]`,
- );
+ const el = document.querySelector(`input[name="view_mode"][value="${stored}"]`);
if (el && !el.checked) {
el.checked = true;
- flushMapSize();
+ // Manually dispatching change would bubble and double-fire; call directly.
+ requestAnimationFrame(() => requestAnimationFrame(ensureMap));
+ setTimeout(ensureMap, 120);
+ setTimeout(ensureMap, 400);
}
}
function onReady() {
- initFlatsMap();
wireViewToggle();
restoreView();
+ ensureMap(); // handles the case where map view is already visible
}
+
document.addEventListener("DOMContentLoaded", onReady);
document.body && document.body.addEventListener("htmx:afterSwap", onReady);