/* 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 (video-driven mit Endcard) ==================== */ var heroEl = document.querySelector('.hero'); var heroSlides = document.querySelectorAll('.hero-slide'); var heroDots = document.querySelectorAll('.hero-dot'); var heroCurrentSlide = 0; var heroEndcardTimer = null; var heroFallbackTimer = null; var heroIsTransitioning = false; var HERO_ENDCARD_MS = 3000; var HERO_FALLBACK_MS = 25000; function heroClearTimers() { if (heroEndcardTimer) { clearTimeout(heroEndcardTimer); heroEndcardTimer = null; } if (heroFallbackTimer) { clearTimeout(heroFallbackTimer); heroFallbackTimer = null; } } function heroPlaySlideVideo(slide) { var v = slide && slide.querySelector('video'); if (!v) return; try { v.currentTime = 0; } catch (err) { /* ignore */ } var p = v.play(); if (p && typeof p.catch === 'function') p.catch(function () { /* autoplay blocked */ }); } function heroPauseSlideVideo(slide) { var v = slide && slide.querySelector('video'); if (v) v.pause(); } function heroEnterEndcard() { if (!heroSlides.length) return; var slide = heroSlides[heroCurrentSlide]; if (!slide || slide.classList.contains('ended')) return; slide.classList.add('ended'); if (heroEl) heroEl.classList.add('endcard'); heroClearTimers(); heroEndcardTimer = setTimeout(function () { heroEndcardTimer = null; heroNext(); }, HERO_ENDCARD_MS); } function heroStartSlide() { var slide = heroSlides[heroCurrentSlide]; if (!slide) return; slide.classList.remove('ended'); if (heroEl) heroEl.classList.remove('endcard'); heroPlaySlideVideo(slide); heroClearTimers(); heroFallbackTimer = setTimeout(function () { heroFallbackTimer = null; heroEnterEndcard(); }, HERO_FALLBACK_MS); } function heroGoTo(index) { if (heroIsTransitioning || index === heroCurrentSlide || !heroSlides.length) return; heroIsTransitioning = true; heroClearTimers(); var oldIndex = heroCurrentSlide; heroSlides[oldIndex].classList.add('exiting'); heroSlides[oldIndex].classList.remove('active', 'ended'); if (heroEl) heroEl.classList.remove('endcard'); if (heroDots[oldIndex]) heroDots[oldIndex].classList.remove('active'); heroPauseSlideVideo(heroSlides[oldIndex]); setTimeout(function () { heroSlides[oldIndex].classList.remove('exiting'); heroCurrentSlide = index; heroSlides[heroCurrentSlide].classList.add('active'); if (heroDots[heroCurrentSlide]) heroDots[heroCurrentSlide].classList.add('active'); heroStartSlide(); heroIsTransitioning = false; }, 400); } function heroNext() { heroGoTo((heroCurrentSlide + 1) % heroSlides.length); } function heroPrev() { heroGoTo((heroCurrentSlide - 1 + heroSlides.length) % heroSlides.length); } // Pro Video: 'ended' → Endcard-Phase starten heroSlides.forEach(function (slide) { var v = slide.querySelector('video'); if (!v) return; v.addEventListener('ended', function () { if (slide.classList.contains('active')) heroEnterEndcard(); }); }); heroDots.forEach(function (dot, i) { dot.addEventListener('click', function () { heroGoTo(i); }); }); var heroPrevBtn = document.querySelector('.hero-arrow-prev'); var heroNextBtn = document.querySelector('.hero-arrow-next'); if (heroPrevBtn) heroPrevBtn.addEventListener('click', heroPrev); if (heroNextBtn) heroNextBtn.addEventListener('click', heroNext); var heroSlider = document.querySelector('.hero-slider'); if (heroSlider) { var heroTouchStartX = 0; heroSlider.addEventListener('touchstart', function (e) { heroTouchStartX = e.changedTouches[0].screenX; }, { 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(); } }, { passive: true }); } document.addEventListener('visibilitychange', function () { var slide = heroSlides[heroCurrentSlide]; if (!slide) return; if (document.hidden) { heroClearTimers(); heroPauseSlideVideo(slide); return; } if (slide.classList.contains('ended')) { heroEndcardTimer = setTimeout(function () { heroEndcardTimer = null; heroNext(); }, HERO_ENDCARD_MS); } else { var v = slide.querySelector('video'); if (v) { var p = v.play(); if (p && typeof p.catch === 'function') p.catch(function () {}); } heroFallbackTimer = setTimeout(function () { heroFallbackTimer = null; heroEnterEndcard(); }, HERO_FALLBACK_MS); } }); // Initialer Start (Slide 0 ist bereits .active im HTML) if (heroSlides.length) heroStartSlide(); /* ==================== 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); }); /* ==================== NEUESTE ENTWICKLUNGEN (Live-Monitoring) ==================== */ function htmlEscape(s) { return String(s == null ? '' : s) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"'); } function normalizeSourceName(s) { return String(s || '').toLowerCase().replace(/^(der|die|das)\s+/, '').replace(/\s+/g, ' ').trim(); } function renderLatestDevelopments(text, sources) { if (!text) return null; sources = Array.isArray(sources) ? sources : []; var lines = text.split('\n').map(function (l) { return l.trim(); }) .filter(function (l) { return l && (l.charAt(0) === '-' || l.charAt(0) === '['); }); if (!lines.length) return null; var bulletRe = /^(?:-\s*)?\[\s*(\d{1,2})\.(\d{1,2})\.?(?:\d{2,4})?\s+(\d{1,2}:\d{2})\s*\]\s*(.+?)\s*$/; var trailingRe = /\s*\{([^{}]+)\}\s*\.?\s*$/; var citationRe = /\[(\d+[a-z]?)\]/g; var junkRe = /^(unbekannt|unknown|n\/?a|keine|keine quelle|tba)$/i; function buildPill(src, name) { var disp = (src && src.name) || name; var url = (src && src.url) || ''; var tgMatch = url.match(/^https?:\/\/t\.me\/([^\/?#]+)/i); var label = tgMatch ? disp + ' (t.me/' + tgMatch[1] + ')' : disp; var e = htmlEscape(label); var titleEsc = htmlEscape(disp); if (src && src.url) { return '' + e + ''; } return '' + e + ''; } function lookupByName(name) { var n = normalizeSourceName(name); if (!n) return null; var exact = sources.find(function (s) { return normalizeSourceName(s.name) === n; }); if (exact) return exact; return sources.find(function (s) { var sn = normalizeSourceName(s.name); return sn && (sn.indexOf(n) !== -1 || n.indexOf(sn) !== -1); }) || null; } var cards = []; for (var i = 0; i < lines.length; i++) { var m = bulletRe.exec(lines[i]); if (!m) continue; var date = String(m[1]).padStart(2, '0') + '.' + String(m[2]).padStart(2, '0') + '.'; var time = m[3]; var body = m[4]; var pills = ''; var t = trailingRe.exec(body); if (t) { body = body.replace(trailingRe, '').trim(); var items = t[1].split(',').map(function (n) { return n.trim(); }).filter(Boolean); var seen = {}; pills = items.map(function (item) { var pipeIdx = item.indexOf('|'); var itemName = pipeIdx >= 0 ? item.slice(0, pipeIdx).trim() : item.trim(); var itemUrl = pipeIdx >= 0 ? item.slice(pipeIdx + 1).trim() : ''; if (!itemName || junkRe.test(itemName)) return ''; var key = normalizeSourceName(itemName); if (seen[key]) return ''; seen[key] = true; if (itemUrl) { return buildPill({ name: itemName, url: itemUrl }, itemName); } return buildPill(lookupByName(itemName), itemName); }).filter(Boolean).join(''); } if (!pills) { var nums = []; var cm; while ((cm = citationRe.exec(body)) !== null) { if (nums.indexOf(cm[1]) === -1) nums.push(cm[1]); } citationRe.lastIndex = 0; if (nums.length) { body = body.replace(citationRe, '').replace(/\s+/g, ' ').trim(); pills = nums.map(function (num) { var src = sources.find(function (s) { return String(s.nr) === num || Number(s.nr) === Number(num); }); return src ? buildPill(src, src.name) : ''; }).filter(Boolean).join(''); } } var head = '
' + 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: '