/* AegisSight Monitor - Product Page v2 */ (function () { 'use strict'; /* ==================== NAVBAR ==================== */ var navbar = document.getElementById('navbar'); window.addEventListener('scroll', function () { navbar.classList.toggle('scrolled', window.scrollY > 10); }); /* ==================== MOBILE MENU ==================== */ var toggle = document.querySelector('.mobile-menu-toggle'); var menu = document.getElementById('mobile-menu'); var overlay = document.getElementById('mobile-overlay'); function closeMenu() { toggle.classList.remove('active'); menu.classList.remove('open'); overlay.classList.remove('open'); toggle.setAttribute('aria-expanded', 'false'); } toggle.addEventListener('click', function () { var isOpen = menu.classList.contains('open'); if (isOpen) { closeMenu(); } else { toggle.classList.add('active'); menu.classList.add('open'); overlay.classList.add('open'); toggle.setAttribute('aria-expanded', 'true'); } }); overlay.addEventListener('click', closeMenu); menu.querySelectorAll('a').forEach(function (l) { l.addEventListener('click', closeMenu); }); /* ==================== SMOOTH SCROLL ==================== */ document.querySelectorAll('a[href^="#"]').forEach(function (link) { link.addEventListener('click', function (e) { var t = document.querySelector(this.getAttribute('href')); if (t) { e.preventDefault(); t.scrollIntoView({ behavior: 'smooth' }); } }); }); /* ==================== HERO SLIDER ==================== */ var heroSlides = document.querySelectorAll('.hero-slide'); var heroDots = document.querySelectorAll('.hero-dot'); var heroCurrentSlide = 0; var heroTimer = null; var HERO_INTERVAL = 8000; var heroIsTransitioning = false; function heroGoTo(index) { if (heroIsTransitioning || index === heroCurrentSlide || !heroSlides.length) return; heroIsTransitioning = true; var oldIndex = heroCurrentSlide; heroSlides[oldIndex].classList.add('exiting'); heroSlides[oldIndex].classList.remove('active'); if (heroDots[oldIndex]) heroDots[oldIndex].classList.remove('active'); setTimeout(function () { heroSlides[oldIndex].classList.remove('exiting'); heroCurrentSlide = index; heroSlides[heroCurrentSlide].classList.add('active'); if (heroDots[heroCurrentSlide]) heroDots[heroCurrentSlide].classList.add('active'); heroIsTransitioning = false; }, 400); } function heroNext() { heroGoTo((heroCurrentSlide + 1) % heroSlides.length); } function heroPrev() { heroGoTo((heroCurrentSlide - 1 + heroSlides.length) % heroSlides.length); } function heroStartAutoplay() { heroStopAutoplay(); heroTimer = setInterval(heroNext, HERO_INTERVAL); } function heroStopAutoplay() { if (heroTimer) { clearInterval(heroTimer); heroTimer = null; } } heroDots.forEach(function (dot, i) { dot.addEventListener('click', function () { heroGoTo(i); heroStartAutoplay(); }); }); var heroPrevBtn = document.querySelector('.hero-arrow-prev'); var heroNextBtn = document.querySelector('.hero-arrow-next'); if (heroPrevBtn) heroPrevBtn.addEventListener('click', function () { heroPrev(); heroStartAutoplay(); }); if (heroNextBtn) heroNextBtn.addEventListener('click', function () { heroNext(); heroStartAutoplay(); }); var heroSlider = document.querySelector('.hero-slider'); if (heroSlider) { heroSlider.addEventListener('mouseenter', heroStopAutoplay); heroSlider.addEventListener('mouseleave', heroStartAutoplay); var heroTouchStartX = 0; heroSlider.addEventListener('touchstart', function (e) { heroTouchStartX = e.changedTouches[0].screenX; heroStopAutoplay(); }, { passive: true }); heroSlider.addEventListener('touchend', function (e) { var diff = e.changedTouches[0].screenX - heroTouchStartX; if (Math.abs(diff) > 50) { if (diff < 0) heroNext(); else heroPrev(); } heroStartAutoplay(); }, { passive: true }); } document.addEventListener('visibilitychange', function () { if (document.hidden) { heroStopAutoplay(); } else { heroStartAutoplay(); } }); if (heroSlides.length > 1) heroStartAutoplay(); /* ==================== MAP STATE ==================== */ var mapInstance = null; var markerLayer = null; var legendControl = null; var lageData = {}; var dataLoaded = false; var lageTitles = { 'iran-konflikt': 'Gro\u00dflage - Irankonflikt', 'cyberangriffe': 'Cyberangriffe auf deutsche Infrastruktur', 'deepfakes': 'Rechtliche Lage von Deepfakes in Deutschland' }; /* ==================== 3D CAROUSEL ==================== */ var cards = document.querySelectorAll('.carousel-card'); var dots = document.querySelectorAll('.carousel-dot'); var activeIndex = 0; window.positionCards = 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); }); // Update map based on active Lage (only after data loaded) if (!dataLoaded) return; 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); // Stats-Bar aktualisieren var titleEl = document.querySelector('.live-stats-title'); if (titleEl) titleEl.textContent = lageTitles[lage] || lage; countUp(document.getElementById('stat-articles'), lageData[lage].article_count); countUp(document.getElementById('stat-sources'), lageData[lage].source_count); countUp(document.getElementById('stat-factchecks'), lageData[lage].factcheck_count); } else { if (mapSection) mapSection.classList.add('map-hidden'); clearMarkers(); } } 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); // Arrow navigation var prevBtn = document.querySelector('.carousel-prev'); var nextBtn = document.querySelector('.carousel-next'); if (prevBtn) prevBtn.addEventListener('click', function () { positionCards((activeIndex - 1 + cards.length) % cards.length); }); if (nextBtn) nextBtn.addEventListener('click', function () { positionCards((activeIndex + 1) % cards.length); }); /* ==================== SIMPLE MARKDOWN ==================== */ function mdToHtml(md) { if (!md) return ''; var lines = md.split('\n'); var html = ''; var inList = false; for (var i = 0; i < lines.length; i++) { var line = lines[i].trim(); if (!line) { if (inList) { html += ''; inList = false; } continue; } line = line.replace(/\[(\d+[a-z]?)\]/g, ''); line = line.replace(/\*\*(.+?)\*\*/g, '$1'); if (/^## /.test(line)) { if (inList) { html += ''; inList = false; } html += '

' + line.replace(/^## /, '') + '

'; } else if (/^### /.test(line)) { if (inList) { html += ''; inList = false; } html += '

' + line.replace(/^### /, '') + '

'; } else if (/^- /.test(line)) { if (!inList) { html += ''; inList = false; } html += '

' + line + '

'; } } if (inList) html += ''; return html; } /* ==================== COUNT-UP ANIMATION ==================== */ function countUp(el, target) { if (!el) return; if (!target) { el.textContent = '0'; return; } var duration = 1200; var startTime = null; function step(ts) { if (!startTime) startTime = ts; var progress = Math.min((ts - startTime) / duration, 1); var ease = 1 - Math.pow(1 - progress, 3); el.textContent = Math.floor(ease * target).toLocaleString('de-DE'); if (progress < 1) requestAnimationFrame(step); } requestAnimationFrame(step); } /* ==================== LIVE DATA ==================== */ function timeAgo(dateStr) { var diffMin = Math.floor((Date.now() - new Date(dateStr).getTime()) / 60000); if (diffMin < 1) return 'Gerade eben aktualisiert'; if (diffMin < 60) return 'Aktualisiert vor ' + diffMin + ' Min.'; var diffH = Math.floor(diffMin / 60); if (diffH < 24) return 'Aktualisiert vor ' + diffH + ' Std.'; var diffD = Math.floor(diffH / 24); return 'Aktualisiert vor ' + diffD + (diffD === 1 ? ' Tag' : ' Tagen'); } function loadLiveData() { fetch('/lagen/iran-konflikt/data/summary.json?t=' + Date.now()) .then(function (r) { if (!r.ok) throw new Error(r.status); return r.json(); }) .then(function (data) { var inc = data.incident || {}; // summary.json has flat structure var ea = document.getElementById('stat-articles'); var es = document.getElementById('stat-sources'); var ef = document.getElementById('stat-factchecks'); countUp(ea, inc.article_count); countUp(es, inc.source_count); countUp(ef, inc.factcheck_count); // Excerpt: pre-extracted in summary.json var excerptEl = document.getElementById('excerpt-text'); if (excerptEl && data.zusammenfassung) { excerptEl.innerHTML = mdToHtml(data.zusammenfassung); } // Store data and init map lageData['iran-konflikt'] = { locations: data.locations || [], category_labels: data.category_labels || {}, article_count: inc.article_count || 0, source_count: inc.source_count || 0, factcheck_count: inc.factcheck_count || 0 }; dataLoaded = true; createMap(); var mapSection = document.getElementById('map-section'); if (mapSection) mapSection.classList.remove('map-hidden'); showMarkers(data.locations || [], data.category_labels || {}); }) .catch(function () { }); } /* ==================== LEAFLET MAP ==================== */ function clearMarkers() { if (markerLayer) { mapInstance.removeLayer(markerLayer); markerLayer = null; } 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; 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: '© OpenStreetMap', maxZoom: 19, noWrap: true }).addTo(mapInstance); setTimeout(function () { mapInstance.invalidateSize(); }, 500); } function pulseIcon(color) { return L.divIcon({ className: '', html: '
' + '
' + '
' + '
' + '
', iconSize: [20, 20], iconAnchor: [10, 10], popupAnchor: [0, -12] }); } function buildPopup(loc) { var html = '' + (loc.name || '') + ''; if (loc.country_code) html += ' (' + loc.country_code + ')'; html += '
' + (loc.article_count || 0) + ' Artikel'; if (loc.top_articles && loc.top_articles.length > 0) { html += '
'; loc.top_articles.forEach(function (a) { var hl = (a.headline || '').replace(/\*\*/g, ''); if (hl.length > 60) hl = hl.substring(0, 60) + '\u2026'; if (a.url) { html += '' + hl + ''; } else { html += '' + hl + ''; } html += '' + (a.source || '') + ''; }); html += '
'; } return html; } function showMarkers(locations, apiLabels) { if (!mapInstance) createMap(); clearMarkers(); var categoryColors = { primary: '#ef4444', secondary: '#f59e0b', tertiary: '#3b82f6', mentioned: '#7b7b7b' }; var defaultLabels = { primary: 'Hauptgeschehen', secondary: 'Reaktionen', tertiary: 'Beteiligte', mentioned: 'Erw\u00e4hnt' }; var categoryLabels = {}; ['primary', 'secondary', 'tertiary', 'mentioned'].forEach(function (k) { categoryLabels[k] = (apiLabels && apiLabels[k]) || defaultLabels[k]; }); var clusterGroup = L.markerClusterGroup({ maxClusterRadius: 50, spiderfyOnMaxZoom: true, showCoverageOnHover: false, zoomToBoundsOnClick: true, disableClusteringAtZoom: 10 }); var usedCategories = {}; var bounds = []; locations.forEach(function (loc) { if (!loc.lat || !loc.lon) return; var cat = loc.category || 'mentioned'; var color = categoryColors[cat] || '#7b7b7b'; usedCategories[cat] = true; var marker; if (cat === 'primary' || cat === 'secondary') { marker = L.marker([loc.lat, loc.lon], { icon: pulseIcon(color) }); } else { marker = L.circleMarker([loc.lat, loc.lon], { radius: 5, fillColor: color, fillOpacity: 0.7, color: color, weight: 1, opacity: 0.9 }); } marker.bindPopup(buildPopup(loc), { maxWidth: 300 }); clusterGroup.addLayer(marker); bounds.push([loc.lat, loc.lon]); }); markerLayer = clusterGroup; mapInstance.addLayer(markerLayer); 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 = 'Legende
'; ['primary', 'secondary', 'tertiary', 'mentioned'].forEach(function (cat) { if (usedCategories[cat]) { html += ' ' + categoryLabels[cat] + '
'; } }); div.innerHTML = html; return div; }; legendControl = legend; legendControl.addTo(mapInstance); if (bounds.length > 0) { mapInstance.fitBounds(bounds, { padding: [30, 30], maxZoom: 7 }); } setTimeout(function () { mapInstance.invalidateSize(); }, 300); } /* ==================== CONTACT MODAL ==================== */ window.openContactModal = function () { document.getElementById('contact-modal').style.display = 'flex'; document.body.style.overflow = 'hidden'; }; window.closeContactModal = function () { document.getElementById('contact-modal').style.display = 'none'; document.body.style.overflow = ''; }; // Close on overlay click var modalOverlay = document.getElementById('contact-modal'); if (modalOverlay) { modalOverlay.addEventListener('click', function (e) { if (e.target === modalOverlay) closeContactModal(); }); } // Close on Escape document.addEventListener('keydown', function (e) { if (e.key === 'Escape' && modalOverlay && modalOverlay.style.display === 'flex') { closeContactModal(); } }); // Form submit -> server-side SMTP window.submitContact = function (e) { e.preventDefault(); var btn = e.target.querySelector('button[type="submit"]'); if (btn) { btn.disabled = true; btn.textContent = 'Wird gesendet...'; } fetch('/api/contact', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: document.getElementById('cf-name').value, organisation: document.getElementById('cf-org').value, email: document.getElementById('cf-email').value, message: document.getElementById('cf-message').value }) }) .then(function (r) { return r.json().then(function (d) { return { ok: r.ok, data: d }; }); }) .then(function (res) { if (res.ok) { document.getElementById('contact-form').style.display = 'none'; document.getElementById('form-success').style.display = 'block'; } else { alert(res.data.error || 'Fehler beim Senden'); if (btn) { btn.disabled = false; btn.textContent = 'Nachricht senden'; } } }) .catch(function () { alert('Verbindungsfehler. Bitte versuchen Sie es erneut.'); if (btn) { btn.disabled = false; btn.textContent = 'Nachricht senden'; } }); return false; }; /* ==================== LOAD DEEPFAKES DATA ==================== */ function loadDeepfakesData() { fetch('/lagen/deepfakes/data/summary.json?t=' + Date.now()) .then(function (r) { if (!r.ok) throw new Error(r.status); return r.json(); }) .then(function (data) { var excerptEl = document.getElementById('excerpt-text-deepfakes'); if (excerptEl && data.zusammenfassung) { var lines = data.zusammenfassung.split("\n"); var filtered = lines.filter(function(l) { var t = l.trim(); return !t || t.indexOf("## ") === 0 || t.indexOf("- ") === 0; }); excerptEl.innerHTML = mdToHtml(filtered.join("\n")); } // Store data for map lageData['deepfakes'] = { locations: data.locations || [], category_labels: data.category_labels || {}, article_count: (data.incident || {}).article_count || 0, source_count: (data.incident || {}).source_count || 0, factcheck_count: (data.incident || {}).factcheck_count || 0 }; }) .catch(function () { var el = document.getElementById('excerpt-text-deepfakes'); if (el) el.textContent = 'Daten konnten nicht geladen werden.'; }); } /* ==================== LOAD CYBERANGRIFFE DATA ==================== */ function loadCyberangriffeData() { fetch('/lagen/cyberangriffe/data/summary.json?t=' + Date.now()) .then(function (r) { if (!r.ok) throw new Error(r.status); return r.json(); }) .then(function (data) { var excerptEl = document.getElementById('excerpt-text-cyberangriffe'); if (excerptEl && data.zusammenfassung) { excerptEl.innerHTML = mdToHtml(data.zusammenfassung); } lageData['cyberangriffe'] = { locations: data.locations || [], category_labels: data.category_labels || {}, article_count: (data.incident || {}).article_count || 0, source_count: (data.incident || {}).source_count || 0, factcheck_count: (data.incident || {}).factcheck_count || 0 }; }) .catch(function () { var el = document.getElementById('excerpt-text-cyberangriffe'); if (el) el.textContent = 'Daten konnten nicht geladen werden.'; }); } /* ==================== INIT ==================== */ loadLiveData(); loadDeepfakesData(); loadCyberangriffeData(); })();