feat(karte): lokale Deutschland-Basemap statt OpenStreetMap-Kacheln (keine externen CDN)

- static/geo/deutschland-bundeslaender.geojson (BKG, vereinfacht, ~40KB)
- map.js: Tile-Layer entfernt, Bundeslaender-GeoJSON per same-origin fetch
  als Vektor-Hintergrund; Detail-Minikarte als Deutschland-Locator
- hugo.toml: OSM tileURL entfernt, Attribution nur noch als Text
- CSS: neutraler Kartenhintergrund statt Tile-Grau
- Ergebnis: 0 externe Requests (per Playwright-Netzwerk-Audit verifiziert)
This commit is contained in:
maik
2026-08-19 13:16:16 +02:00
parent 7d2949bf42
commit bfcb2ce6c0
6 changed files with 73 additions and 20 deletions
+3 -1
View File
@@ -474,9 +474,11 @@ a:hover { color: var(--op-blau-dunkel); }
width: 100%;
height: 560px;
border: 2px solid var(--op-blau);
background: var(--op-papier);
background: #d9e1ea; /* neutrale Fläche „ausserhalb DE" (kein Tile-Grau) */
z-index: 0; /* unter der Sticky-Navbar halten */
}
/* Leaflet-eigener Container-Hintergrund (ohne Tiles) an den Hausstil angleichen */
.op-map .leaflet-container { background: #d9e1ea; }
.op-map--klein { height: 320px; }
/* Leaflet-Popup im Hausstil: Bild oben, gelber Body (wie im Original) */
+45 -9
View File
@@ -1,6 +1,7 @@
/* =========================================================================
OP·Karte Leaflet + OpenStreetMap
- farbige Kreis-Marker je Kategorie, Clustering (markercluster)
OP·Karte Leaflet mit LOKALER Deutschland-Karte (keine externen Tiles/CDN)
- Basemap = Bundesländer-GeoJSON aus dem Repo (static/geo/…), vektoriell
- farbige Pin-Marker je Kategorie, Clustering (markercluster)
- Kategorie-Filter (Checkboxen) + Textsuche, synchron mit der Card-Liste
- Klick auf Card öffnet den zugehörigen Marker
Daten kommen aus <script type="application/json" id="op-map-data">.
@@ -18,15 +19,45 @@
var places = payload.places || [];
var colors = payload.colors || {};
var defaultColor = payload.defaultColor || "#1e2758";
var tileUrl = payload.tileUrl || "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png";
var attribution = payload.attribution ||
'&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>-Mitwirkende';
var klein = !!payload.klein;
var geojsonUrl = payload.geojsonUrl || "";
var attribution = payload.attribution || "Geodaten: © GeoBasis-DE / BKG";
var germanyBounds = null;
var map = L.map(el, { scrollWheelZoom: false });
L.tileLayer(tileUrl, { maxZoom: 19, attribution: attribution }).addTo(map);
var map = L.map(el, {
scrollWheelZoom: false,
minZoom: 5, maxZoom: 12,
attributionControl: true
});
map.attributionControl.setPrefix(false).addAttribution(attribution);
map.on("focus", function () { map.scrollWheelZoom.enable(); });
map.on("blur", function () { map.scrollWheelZoom.disable(); });
// Lokale Deutschland-Karte (Bundesländer) als Vektor-Hintergrund laden.
// Same-Origin-Fetch aus dem Repo kein externer Request.
if (geojsonUrl) {
fetch(geojsonUrl)
.then(function (r) { return r.ok ? r.json() : null; })
.then(function (geo) {
if (!geo) return;
var land = L.geoJSON(geo, {
style: {
color: "#1e2758", weight: 1, opacity: .85,
fillColor: "#f4f1e6", fillOpacity: 1
},
interactive: false
}).addTo(map);
land.bringToBack();
germanyBounds = land.getBounds();
// Detail-Minikarte = Verortung: ganz Deutschland mit dem einen Pin zeigen.
// Grosse Karte: nur wenn (noch) keine Marker den Ausschnitt bestimmen.
if (klein || !records.length) {
try { map.fitBounds(germanyBounds.pad(0.02)); } catch (e) {}
}
})
.catch(function () {});
}
var useCluster = places.length > 1 && typeof L.markerClusterGroup === "function";
var group = useCluster
? L.markerClusterGroup({ maxClusterRadius: 45, showCoverageOnHover: false })
@@ -93,8 +124,13 @@
});
var countCards = cards.filter(function (c) { return !c.classList.contains("is--hidden"); }).length;
if (countEl) countEl.textContent = countCards;
if (bounds.isValid()) map.fitBounds(bounds.pad(0.15));
else if (allBounds) map.fitBounds(allBounds.pad(0.15));
if (bounds.isValid()) {
// Auf der Detail-Minikarte nicht ins Leere zoomen: Umkreis statt Punkt
if (klein) map.fitBounds(bounds.pad(0.15), { maxZoom: 8 });
else map.fitBounds(bounds.pad(0.15));
}
else if (allBounds) map.fitBounds(allBounds.pad(0.15), klein ? { maxZoom: 8 } : undefined);
else if (germanyBounds) map.fitBounds(germanyBounds.pad(0.02));
}
// --- DOM-Hooks ---