fix: 3D-Karussell + exakte Leaflet-Karte wie /lagebild
- 3D-Perspektiv-Karussell: Zentrale Card gross, seitliche klein/gekippt - Klick auf seitliche Cards wechselt Ansicht, Dot-Navigation - Karte mit exakten Pulse-Markern (Ring + Dot Animation) - Dark Popups und Dark Legende wie bei /lagen/iran-konflikt/ - Kategorie-Farben und Labels aus der API Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Dieser Commit ist enthalten in:
@@ -130,11 +130,17 @@ a { color:inherit; text-decoration:none; }
|
||||
.feature-card h3 { font-size:1rem; font-weight:700; color:var(--navy); margin-bottom:8px; }
|
||||
.feature-card p { font-size:0.88rem; color:var(--text-light); line-height:1.6; }
|
||||
|
||||
/* ==================== CAROUSEL ==================== */
|
||||
.carousel-wrapper { overflow-x:auto; -webkit-overflow-scrolling:touch; scrollbar-width:none; margin:0 -24px; padding:0 24px; }
|
||||
.carousel-wrapper::-webkit-scrollbar { display:none; }
|
||||
.carousel { display:flex; gap:24px; padding:8px 0 16px; }
|
||||
.carousel-card { min-width:340px; max-width:380px; flex-shrink:0; background:var(--white); border-radius:var(--radius-lg); padding:28px 24px; box-shadow:var(--shadow); position:relative; display:flex; flex-direction:column; }
|
||||
/* ==================== 3D CAROUSEL ==================== */
|
||||
.carousel-viewport { perspective:1200px; overflow:visible; padding:40px 0 20px; }
|
||||
.carousel-track { display:flex; justify-content:center; align-items:center; position:relative; min-height:440px; }
|
||||
.carousel-card { width:380px; flex-shrink:0; background:var(--white); border-radius:var(--radius-lg); padding:28px 24px; box-shadow:var(--shadow); position:absolute; display:flex; flex-direction:column; transition:all 0.6s cubic-bezier(0.4,0,0.2,1); cursor:pointer; transform-style:preserve-3d; }
|
||||
.carousel-card.active { transform:scale(1) translateX(0) rotateY(0); z-index:3; opacity:1; pointer-events:all; }
|
||||
.carousel-card.left { transform:scale(0.78) translateX(-110%) rotateY(12deg); z-index:1; opacity:0.5; pointer-events:all; }
|
||||
.carousel-card.right { transform:scale(0.78) translateX(110%) rotateY(-12deg); z-index:1; opacity:0.5; pointer-events:all; }
|
||||
.carousel-card.hidden { transform:scale(0.6) translateX(0); z-index:0; opacity:0; pointer-events:none; }
|
||||
.carousel-nav { display:flex; justify-content:center; gap:10px; margin-top:24px; }
|
||||
.carousel-dot { width:10px; height:10px; border-radius:50%; border:2px solid var(--gold); background:transparent; cursor:pointer; transition:all 0.3s; padding:0; }
|
||||
.carousel-dot.active { background:var(--gold); }
|
||||
.card-live { border:2px solid var(--gold); box-shadow:0 4px 24px rgba(200,168,81,0.15); }
|
||||
.card-placeholder { border:2px dashed var(--gray-200); opacity:0.55; }
|
||||
.demo-badge { display:inline-block; padding:4px 14px; border-radius:20px; font-size:0.72rem; font-weight:700; letter-spacing:0.08em; text-transform:uppercase; margin-bottom:14px; width:fit-content; background:var(--gold); color:var(--navy); }
|
||||
@@ -162,18 +168,17 @@ a { color:inherit; text-decoration:none; }
|
||||
.map-title { font-size:1.1rem; font-weight:600; color:var(--navy); margin-bottom:16px; text-align:center; }
|
||||
#map-container { height:420px; border-radius:var(--radius-lg); overflow:hidden; box-shadow:var(--shadow); border:1px solid var(--gray-100); }
|
||||
|
||||
/* Map pulse markers */
|
||||
.pulse-marker { width:12px; height:12px; border-radius:50%; position:relative; }
|
||||
.pulse-marker::after { content:''; position:absolute; inset:-4px; border-radius:50%; border:2px solid; opacity:0.5; animation:mapPulse 2s infinite; }
|
||||
.pulse-marker.cat-primary { background:#E74C3C; }
|
||||
.pulse-marker.cat-primary::after { border-color:#E74C3C; }
|
||||
.pulse-marker.cat-secondary { background:#F39C12; }
|
||||
.pulse-marker.cat-secondary::after { border-color:#F39C12; }
|
||||
.pulse-marker.cat-tertiary { background:#3498DB; }
|
||||
.pulse-marker.cat-tertiary::after { border-color:#3498DB; }
|
||||
.pulse-marker.cat-mentioned { background:#95A5A6; }
|
||||
.pulse-marker.cat-mentioned::after { border-color:#95A5A6; }
|
||||
@keyframes mapPulse { 0%{transform:scale(1);opacity:0.5} 100%{transform:scale(2.5);opacity:0} }
|
||||
/* Map pulse markers (exact lagebild style) */
|
||||
.pulse-marker-wrapper { position:relative; width:20px; height:20px; }
|
||||
.pulse-marker-ring { position:absolute; inset:0; border-radius:50%; border:2px solid; animation:mapPulseRing 2s infinite; opacity:0; }
|
||||
.pulse-marker-ring:nth-child(2) { animation-delay:1s; }
|
||||
@keyframes mapPulseRing { 0%{transform:scale(0.5);opacity:0} 30%{opacity:0.6} 100%{transform:scale(2.5);opacity:0} }
|
||||
.pulse-marker-dot { position:absolute; top:50%; left:50%; width:8px; height:8px; margin:-4px 0 0 -4px; border-radius:50%; animation:pulseDot 2s infinite; }
|
||||
@keyframes pulseDot { 0%,100%{opacity:1;transform:scale(1)} 50%{opacity:0.5;transform:scale(0.7)} }
|
||||
/* Dark popup style */
|
||||
.leaflet-popup-content-wrapper { background:#151D2E!important; color:#E8ECF4!important; border:1px solid #1E2D45!important; border-radius:4px!important; box-shadow:0 4px 16px rgba(0,0,0,0.4)!important; }
|
||||
.leaflet-popup-tip { background:#151D2E!important; }
|
||||
.leaflet-popup-content { margin:10px 14px!important; font-size:0.85rem!important; }
|
||||
|
||||
/* ==================== TRUST ==================== */
|
||||
.trust-grid { margin-top:48px; }
|
||||
@@ -205,7 +210,7 @@ a { color:inherit; text-decoration:none; }
|
||||
.hero-title { font-size:2.8rem; }
|
||||
.section { padding:64px 0; }
|
||||
.workflow-connector { width:40px; }
|
||||
.carousel-card { min-width:300px; }
|
||||
|
||||
}
|
||||
|
||||
@media(max-width:768px) {
|
||||
@@ -224,12 +229,12 @@ a { color:inherit; text-decoration:none; }
|
||||
.hero-cta { flex-direction:column; }
|
||||
.hero-cta .btn { width:100%; }
|
||||
.footer-content { flex-direction:column; text-align:center; gap:16px; }
|
||||
.carousel-card { min-width:280px; }
|
||||
|
||||
#map-container { height:300px; }
|
||||
}
|
||||
|
||||
@media(max-width:480px) {
|
||||
.hero-title { font-size:1.8rem; }
|
||||
.container { padding:0 16px; }
|
||||
.carousel-card { min-width:260px; }
|
||||
|
||||
}
|
||||
|
||||
@@ -112,11 +112,11 @@
|
||||
<h2 class="section-title">Sehen Sie den Monitor in Aktion</h2>
|
||||
<p class="section-subtitle">Echte Lagebilder, erstellt vom AegisSight Monitor. Live und ohne Bearbeitung.</p>
|
||||
|
||||
<!-- Carousel -->
|
||||
<div class="carousel-wrapper">
|
||||
<div class="carousel" id="carousel">
|
||||
<!-- 3D Carousel -->
|
||||
<div class="carousel-viewport">
|
||||
<div class="carousel-track" id="carousel">
|
||||
<!-- Iran Card -->
|
||||
<div class="carousel-card card-live">
|
||||
<div class="carousel-card card-live active" data-index="0">
|
||||
<div class="demo-badge">LIVE</div>
|
||||
<h3 class="demo-title">Iran-Konflikt</h3>
|
||||
<div class="demo-stats" id="demo-stats-iran">
|
||||
@@ -141,18 +141,23 @@
|
||||
<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">
|
||||
<div class="carousel-card card-placeholder" data-index="1">
|
||||
<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">
|
||||
<div class="carousel-card card-placeholder" data-index="2"data-index="1">
|
||||
<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>
|
||||
</div>
|
||||
<div class="carousel-nav">
|
||||
<button class="carousel-dot active" data-index="0"></button>
|
||||
<button class="carousel-dot" data-index="1"></button>
|
||||
<button class="carousel-dot" data-index="2"></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Map -->
|
||||
|
||||
@@ -44,7 +44,6 @@
|
||||
/* ==================== HERO VIDEOS ==================== */
|
||||
var videos = document.querySelectorAll('.hero-video');
|
||||
var currentVideo = 0;
|
||||
var ROTATION_INTERVAL = 12000;
|
||||
var rotationTimer;
|
||||
|
||||
function switchVideo() {
|
||||
@@ -57,10 +56,9 @@
|
||||
}
|
||||
|
||||
function startRotation() {
|
||||
rotationTimer = setInterval(switchVideo, ROTATION_INTERVAL);
|
||||
rotationTimer = setInterval(switchVideo, 12000);
|
||||
}
|
||||
|
||||
// Pause when tab hidden
|
||||
document.addEventListener('visibilitychange', function () {
|
||||
if (document.hidden) {
|
||||
clearInterval(rotationTimer);
|
||||
@@ -73,10 +71,40 @@
|
||||
|
||||
if (videos.length > 1) startRotation();
|
||||
|
||||
/* ==================== 3D CAROUSEL ==================== */
|
||||
var cards = document.querySelectorAll('.carousel-card');
|
||||
var dots = document.querySelectorAll('.carousel-dot');
|
||||
var activeIndex = 0;
|
||||
|
||||
function positionCards(idx) {
|
||||
activeIndex = idx;
|
||||
cards.forEach(function (card, i) {
|
||||
card.classList.remove('active', 'left', 'right', 'hidden');
|
||||
if (i === idx) card.classList.add('active');
|
||||
else if (i === (idx - 1 + cards.length) % cards.length) card.classList.add('left');
|
||||
else if (i === (idx + 1) % cards.length) card.classList.add('right');
|
||||
else card.classList.add('hidden');
|
||||
});
|
||||
dots.forEach(function (dot, i) {
|
||||
dot.classList.toggle('active', i === idx);
|
||||
});
|
||||
}
|
||||
|
||||
cards.forEach(function (card, i) {
|
||||
card.addEventListener('click', function () {
|
||||
if (!card.classList.contains('active')) positionCards(i);
|
||||
});
|
||||
});
|
||||
|
||||
dots.forEach(function (dot, i) {
|
||||
dot.addEventListener('click', function () { positionCards(i); });
|
||||
});
|
||||
|
||||
positionCards(0);
|
||||
|
||||
/* ==================== SIMPLE MARKDOWN ==================== */
|
||||
function mdToText(md) {
|
||||
function mdToHtml(md) {
|
||||
if (!md) return '';
|
||||
// Remove ## headers and bold markers, keep text
|
||||
return md
|
||||
.replace(/^## .+$/gm, '')
|
||||
.replace(/^### .+$/gm, '')
|
||||
@@ -101,17 +129,13 @@
|
||||
return 'Aktualisiert vor ' + diffD + (diffD === 1 ? ' Tag' : ' Tagen');
|
||||
}
|
||||
|
||||
var liveData = null;
|
||||
|
||||
function loadLiveData() {
|
||||
fetch('/lagen/iran-konflikt/data/current.json?t=' + Date.now())
|
||||
.then(function (r) { if (!r.ok) throw new Error(r.status); return r.json(); })
|
||||
.then(function (data) {
|
||||
liveData = data;
|
||||
var inc = data.incident || {};
|
||||
var lag = data.current_lagebild || {};
|
||||
|
||||
// Stats
|
||||
var ea = document.getElementById('stat-articles');
|
||||
var es = document.getElementById('stat-sources');
|
||||
var ef = document.getElementById('stat-factchecks');
|
||||
@@ -125,10 +149,8 @@
|
||||
var excerptEl = document.getElementById('excerpt-text');
|
||||
var toggleBtn = document.getElementById('excerpt-toggle');
|
||||
if (excerptEl && lag.summary_markdown) {
|
||||
var html = mdToText(lag.summary_markdown);
|
||||
excerptEl.innerHTML = html;
|
||||
excerptEl.innerHTML = mdToHtml(lag.summary_markdown);
|
||||
toggleBtn.style.display = 'inline-block';
|
||||
|
||||
toggleBtn.addEventListener('click', function () {
|
||||
var expanded = excerptEl.classList.toggle('expanded');
|
||||
this.textContent = expanded ? 'Weniger anzeigen' : 'Weiterlesen';
|
||||
@@ -146,8 +168,8 @@
|
||||
});
|
||||
}
|
||||
|
||||
/* ==================== LEAFLET MAP ==================== */
|
||||
function initMap(locations, categoryLabels) {
|
||||
/* ==================== LEAFLET MAP (exact lagebild style) ==================== */
|
||||
function initMap(locations, apiLabels) {
|
||||
var mapEl = document.getElementById('map-container');
|
||||
if (!mapEl || typeof L === 'undefined') return;
|
||||
|
||||
@@ -155,46 +177,91 @@
|
||||
center: [33.0, 48.0],
|
||||
zoom: 5,
|
||||
zoomControl: true,
|
||||
scrollWheelZoom: false
|
||||
scrollWheelZoom: false,
|
||||
minZoom: 2,
|
||||
maxBounds: [[-85, -180], [85, 180]],
|
||||
maxBoundsViscosity: 1.0
|
||||
});
|
||||
|
||||
L.tileLayer('https://tile.openstreetmap.de/{z}/{x}/{y}.png', {
|
||||
attribution: '© OpenStreetMap',
|
||||
maxZoom: 18
|
||||
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>',
|
||||
maxZoom: 19,
|
||||
noWrap: true
|
||||
}).addTo(map);
|
||||
|
||||
var catColors = {
|
||||
primary: '#E74C3C',
|
||||
secondary: '#F39C12',
|
||||
tertiary: '#3498DB',
|
||||
mentioned: '#95A5A6'
|
||||
// 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',
|
||||
secondary: '#f59e0b',
|
||||
tertiary: '#3b82f6',
|
||||
mentioned: '#7b7b7b'
|
||||
};
|
||||
|
||||
var defaultLabels = {
|
||||
primary: 'Hauptgeschehen',
|
||||
secondary: 'Reaktionen',
|
||||
tertiary: 'Beteiligte',
|
||||
mentioned: 'Erwähnt'
|
||||
};
|
||||
|
||||
var categoryLabels = {};
|
||||
['primary', 'secondary', 'tertiary', 'mentioned'].forEach(function (k) {
|
||||
categoryLabels[k] = (apiLabels && apiLabels[k]) || defaultLabels[k];
|
||||
});
|
||||
|
||||
var usedCategories = {};
|
||||
var bounds = [];
|
||||
|
||||
locations.forEach(function (loc) {
|
||||
if (!loc.lat || !loc.lon) return;
|
||||
var cat = loc.category || 'mentioned';
|
||||
var color = catColors[cat] || catColors.mentioned;
|
||||
var catClass = 'cat-' + cat;
|
||||
var color = categoryColors[cat] || '#7b7b7b';
|
||||
usedCategories[cat] = true;
|
||||
|
||||
var icon = L.divIcon({
|
||||
className: 'pulse-marker ' + catClass,
|
||||
iconSize: [12, 12],
|
||||
iconAnchor: [6, 6]
|
||||
});
|
||||
var popup = '<strong style="color:#E8ECF4;">' + (loc.name || '') + '</strong>';
|
||||
if (loc.country_code) popup += ' <span style="color:#8896AB;font-size:0.8rem;">(' + loc.country_code + ')</span>';
|
||||
popup += '<br><span style="font-size:0.85rem;color:#8896AB;">' + (loc.article_count || 0) + ' Artikel</span>';
|
||||
|
||||
var marker = L.marker([loc.lat, loc.lon], { icon: icon }).addTo(map);
|
||||
var label = categoryLabels[cat] || cat;
|
||||
marker.bindPopup('<strong>' + loc.name + '</strong><br>' + label + ' (' + (loc.article_count || 0) + ' Artikel)');
|
||||
L.marker([loc.lat, loc.lon], { icon: pulseIcon(color) })
|
||||
.addTo(map)
|
||||
.bindPopup(popup);
|
||||
bounds.push([loc.lat, loc.lon]);
|
||||
});
|
||||
|
||||
// Dark legend (exact lagebild style)
|
||||
var legend = L.control({ position: 'bottomright' });
|
||||
legend.onAdd = function () {
|
||||
var div = L.DomUtil.create('div');
|
||||
div.style.cssText = 'background:#151D2E;padding:10px 14px;border-radius:4px;border:1px solid #1E2D45;box-shadow:0 2px 8px rgba(0,0,0,0.3);font-size:0.8rem;line-height:1.8;color:#E8ECF4;';
|
||||
var html = '<strong style="color:#C8A851;">Legende</strong><br>';
|
||||
['primary', 'secondary', 'tertiary', 'mentioned'].forEach(function (cat) {
|
||||
if (usedCategories[cat]) {
|
||||
html += '<span style="color:' + categoryColors[cat] + ';">●</span> ' + categoryLabels[cat] + '<br>';
|
||||
}
|
||||
});
|
||||
div.innerHTML = html;
|
||||
return div;
|
||||
};
|
||||
legend.addTo(map);
|
||||
|
||||
if (bounds.length > 0) {
|
||||
map.fitBounds(bounds, { padding: [30, 30], maxZoom: 7 });
|
||||
}
|
||||
|
||||
// Fix tile rendering on hidden tab / late load
|
||||
setTimeout(function () { map.invalidateSize(); }, 500);
|
||||
}
|
||||
|
||||
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren