feat: Karte reagiert auf Karussell-Wechsel

- Map-Instanz wird einmalig erstellt, Marker dynamisch gewechselt
- data-lage Attribute auf Carousel-Cards fuer Lagen-Zuordnung
- Bei Lage mit Daten: Marker + Legende angezeigt
- Bei Platzhalter: Karte ausgeblendet, 'Kartendaten folgen'
- Zukunftssicher: Neue Lagen brauchen nur data-lage + summary.json

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Dieser Commit ist enthalten in:
Claude Code
2026-04-06 19:03:59 +02:00
Ursprung b1a5293d82
Commit 6e7f5b5c5f
3 geänderte Dateien mit 70 neuen und 40 gelöschten Zeilen

Datei anzeigen

@@ -179,6 +179,10 @@ a { color:inherit; text-decoration:none; }
/* ==================== MAP ==================== */
.map-section { margin-top:48px; }
.map-title { font-size:1.1rem; font-weight:600; color:var(--navy); margin-bottom:16px; text-align:center; }
.map-section { transition:opacity 0.3s; }
.map-section.map-hidden #map-container { display:none; }
.map-section.map-hidden .map-empty { display:flex!important; }
.map-empty { display:none; align-items:center; justify-content:center; height:300px; border:2px dashed var(--gray-200); border-radius:var(--radius-lg); color:var(--gray-400); font-size:1rem; background:var(--white); }
#map-container { height:420px; border-radius:var(--radius-lg); overflow:hidden; box-shadow:var(--shadow); border:1px solid var(--gray-100); }
/* Map pulse markers (exact lagebild style) */

Datei anzeigen

@@ -163,7 +163,7 @@
<button class="carousel-arrow carousel-next" aria-label="Nächste Lage">&#8250;</button>
<div class="carousel-track" id="carousel">
<!-- Iran Card -->
<div class="carousel-card card-live active" data-index="0">
<div class="carousel-card card-live active" data-index="0" data-lage="iran-konflikt">
<div class="demo-badge">LIVE</div>
<h3 class="demo-title">Iran-Konflikt</h3>
@@ -173,13 +173,13 @@
<a href="/lagen/iran-konflikt/" class="btn btn-primary btn-block">Vollständiges Lagebild öffnen</a>
</div>
<!-- Placeholder 2 -->
<div class="carousel-card card-placeholder" data-index="1">
<div class="carousel-card card-placeholder" data-index="1" data-lage="">
<div class="demo-badge badge-soon">Demnächst</div>
<h3 class="demo-title placeholder-title">Weitere Lage</h3>
<p class="placeholder-text">In Vorbereitung</p>
</div>
<!-- Placeholder 3 -->
<div class="carousel-card card-placeholder" data-index="2">
<div class="carousel-card card-placeholder" data-index="2" data-lage="">
<div class="demo-badge badge-soon">Demnächst</div>
<h3 class="demo-title placeholder-title">Weitere Lage</h3>
<p class="placeholder-text">In Vorbereitung</p>
@@ -193,9 +193,10 @@
</div>
<!-- Map -->
<div class="map-section">
<h3 class="map-title">Geografische Verortung der Meldungen</h3>
<div class="map-section" id="map-section">
<h3 class="map-title" id="map-title">Geografische Verortung der Meldungen</h3>
<div id="map-container"></div>
<div class="map-empty" id="map-empty" style="display:none">Kartendaten folgen</div>
</div>
</div>
</section>

Datei anzeigen

@@ -71,6 +71,12 @@
if (videos.length > 1) startRotation();
/* ==================== MAP STATE ==================== */
var mapInstance = null;
var markerLayer = null;
var legendControl = null;
var lageData = {};
/* ==================== 3D CAROUSEL ==================== */
var cards = document.querySelectorAll('.carousel-card');
var dots = document.querySelectorAll('.carousel-dot');
@@ -88,6 +94,16 @@
dots.forEach(function (dot, i) {
dot.classList.toggle('active', i === idx);
});
// Update map based on active Lage
var lage = cards[idx].getAttribute('data-lage');
var mapSection = document.getElementById('map-section');
if (lage && lageData[lage]) {
mapSection.classList.remove('map-hidden');
showMarkers(lageData[lage].locations, lageData[lage].category_labels);
} else {
if (mapSection) mapSection.classList.add('map-hidden');
clearMarkers();
}
}
cards.forEach(function (card, i) {
@@ -188,50 +204,59 @@ function mdToHtml(md) {
excerptEl.innerHTML = mdToHtml(data.zusammenfassung);
}
// Map
if (data.locations && data.locations.length > 0) {
initMap(data.locations, data.category_labels || {});
}
// Store data and init map
lageData['iran-konflikt'] = {
locations: data.locations || [],
category_labels: data.category_labels || {}
};
createMap();
showMarkers(data.locations || [], data.category_labels || {});
})
.catch(function () {
});
}
/* ==================== LEAFLET MAP (exact lagebild style) ==================== */
function initMap(locations, apiLabels) {
/* ==================== LEAFLET MAP ==================== */
function clearMarkers() {
if (markerLayer) markerLayer.clearLayers();
if (legendControl && mapInstance) { mapInstance.removeControl(legendControl); legendControl = null; }
}
function createMap() {
if (mapInstance) return;
var mapEl = document.getElementById('map-container');
if (!mapEl || typeof L === 'undefined') return;
var map = L.map(mapEl, {
center: [33.0, 48.0],
zoom: 5,
zoomControl: true,
scrollWheelZoom: true,
minZoom: 2,
maxBounds: [[-85, -180], [85, 180]],
maxBoundsViscosity: 1.0
mapInstance = L.map(mapEl, {
center: [33.0, 48.0], zoom: 5, zoomControl: true, scrollWheelZoom: true,
minZoom: 2, maxBounds: [[-85, -180], [85, 180]], maxBoundsViscosity: 1.0
});
L.tileLayer('https://tile.openstreetmap.de/{z}/{x}/{y}.png', {
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>',
maxZoom: 19,
noWrap: true
}).addTo(map);
maxZoom: 19, noWrap: true
}).addTo(mapInstance);
markerLayer = L.layerGroup().addTo(mapInstance);
setTimeout(function () { mapInstance.invalidateSize(); }, 500);
}
function pulseIcon(color) {
return L.divIcon({
className: '',
html: '<div class="pulse-marker-wrapper">'
+ '<div class="pulse-marker-ring" style="border-color:' + color + '"></div>'
+ '<div class="pulse-marker-ring" style="border-color:' + color + '"></div>'
+ '<div class="pulse-marker-dot" style="background:' + color + ';box-shadow:0 0 10px ' + color + '"></div>'
+ '</div>',
iconSize: [20, 20], iconAnchor: [10, 10], popupAnchor: [0, -12]
});
}
function showMarkers(locations, apiLabels) {
if (!mapInstance) createMap();
clearMarkers();
// Exact same pulse icon as lagebild
function pulseIcon(color) {
return L.divIcon({
className: '',
html: '<div class="pulse-marker-wrapper">'
+ '<div class="pulse-marker-ring" style="border-color:' + color + '"></div>'
+ '<div class="pulse-marker-ring" style="border-color:' + color + '"></div>'
+ '<div class="pulse-marker-dot" style="background:' + color + ';box-shadow:0 0 10px ' + color + '"></div>'
+ '</div>',
iconSize: [20, 20],
iconAnchor: [10, 10],
popupAnchor: [0, -12]
});
}
var categoryColors = {
primary: '#ef4444',
@@ -266,7 +291,7 @@ function mdToHtml(md) {
popup += '<br><span style="font-size:0.85rem;color:#8896AB;">' + (loc.article_count || 0) + ' Artikel</span>';
L.marker([loc.lat, loc.lon], { icon: pulseIcon(color) })
.addTo(map)
.addTo(markerLayer)
.bindPopup(popup);
bounds.push([loc.lat, loc.lon]);
});
@@ -285,13 +310,13 @@ function mdToHtml(md) {
div.innerHTML = html;
return div;
};
legend.addTo(map);
legend.addTo(mapInstance);
if (bounds.length > 0) {
map.fitBounds(bounds, { padding: [30, 30], maxZoom: 7 });
mapInstance.fitBounds(bounds, { padding: [30, 30], maxZoom: 7 });
}
setTimeout(function () { map.invalidateSize(); }, 500);
setTimeout(function () { mapInstance.invalidateSize(); }, 300);
}
/* ==================== INIT ==================== */