Lagebild Design-Refresh: Dark Theme mit Navy/Gold Design System
- CSS komplett neu: Dark Theme mit eigenen Variablen (--lb-*), Glasmorphism Stat-Cards, Timeline-Strip - Hero: Count-Up Animationen, Faktencheck-Quick-Stats, Powered-by Badge - Kalender-Timeline ersetzt Snapshot-Dropdown (Tage gruppiert, Gold-Akzent für aktiven Tag) - Tab-Badges mit Quellen-/Faktencheck-Zähler - Dark Map Tiles (CartoDB Dark Matter) mit dunkler Legende und Popups - Scroll-Reveal Animationen (IntersectionObserver) - Smooth-Scroll für Zitat-Links mit Gold-Highlight - Favicons via Google Favicon API bei Artikeln - Responsive: Mobile 2x2 Stat-Grid, scrollbare Timeline - Nav + Footer Dark Override ohne Änderung an main.css Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Dieser Commit ist enthalten in:
@@ -60,22 +60,32 @@
|
||||
</div>
|
||||
<h1 id="hero-title">LAGEBILD</h1>
|
||||
<p class="hero-incident-title" id="incident-title"></p>
|
||||
<div class="hero-meta" id="hero-meta"></div>
|
||||
|
||||
<!-- Stat Cards -->
|
||||
<div class="hero-stats" id="hero-stats"></div>
|
||||
|
||||
<!-- FC Quick Stats -->
|
||||
<div class="hero-fc-stats" id="hero-fc-stats"></div>
|
||||
|
||||
<!-- Powered By -->
|
||||
<div class="hero-powered-by">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/></svg>
|
||||
<span>Daten durch AegisSight Monitor — KI-gestützte OSINT-Analyse</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Snapshot Selector + Tabs -->
|
||||
<!-- Control Bar: Timeline + Tabs -->
|
||||
<div class="control-bar">
|
||||
<div class="container">
|
||||
<div class="snapshot-selector">
|
||||
<label>Lagebild vom:</label>
|
||||
<select id="snapshot-select"></select>
|
||||
<div class="timeline-wrapper">
|
||||
<div class="timeline-strip" id="timeline-strip"></div>
|
||||
</div>
|
||||
<div class="tab-nav" id="tab-nav">
|
||||
<button class="tab-btn active" data-tab="lagebild">Lagebild</button>
|
||||
<button class="tab-btn" data-tab="quellen">Quellen</button>
|
||||
<button class="tab-btn" data-tab="quellen">Quellen <span class="tab-badge" id="tab-badge-quellen"></span></button>
|
||||
<button class="tab-btn" data-tab="karte">Karte</button>
|
||||
<button class="tab-btn" data-tab="faktenchecks">Faktenchecks</button>
|
||||
<button class="tab-btn" data-tab="faktenchecks">Faktenchecks <span class="tab-badge" id="tab-badge-faktenchecks"></span></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -124,7 +134,7 @@
|
||||
<p class="card-description">Geografische Einordnung der Meldungen</p>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div id="map-container" style="height:500px;border-radius:12px;overflow:hidden;"></div>
|
||||
<div id="map-container" style="height:500px;border-radius:4px;overflow:hidden;"></div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
@@ -142,6 +152,7 @@
|
||||
|
||||
<!-- CTA -->
|
||||
<section class="lagebild-cta">
|
||||
<p class="cta-powered-text">Dieses Lagebild wird automatisch durch unseren KI-Monitor erstellt.</p>
|
||||
<div class="cta-content">
|
||||
<h3>Interesse an AegisSight Monitor?</h3>
|
||||
<p>Erhalten Sie Echtzeit-Lagebilder für Ihre Organisation mit KI-gestützter Analyse und Faktencheck.</p>
|
||||
@@ -184,7 +195,7 @@
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<!-- Scripts: nur die nötigen, OHNE animations -->
|
||||
<!-- Scripts -->
|
||||
<script src="../js/config.js"></script>
|
||||
<script src="../js/translations.js"></script>
|
||||
<script src="../js/mobile-nav.js"></script>
|
||||
|
||||
Datei-Diff unterdrückt, da er zu groß ist
Diff laden
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* AegisSight Lagebild Page v3
|
||||
* Vollständige Darstellung aller Daten mit Tabs
|
||||
* AegisSight Lagebild Page - Dark Theme Design Refresh
|
||||
* Count-Up, Timeline, Scroll-Reveal, Smooth-Scroll, Favicons
|
||||
*/
|
||||
|
||||
/** Feste Zeitzone fuer alle Anzeigen — NIEMALS aendern. */
|
||||
@@ -12,6 +12,18 @@ var Lagebild = {
|
||||
currentView: null,
|
||||
map: null,
|
||||
|
||||
/* ===== Inline SVG Icons ===== */
|
||||
icons: {
|
||||
clock: '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>',
|
||||
fileText: '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/></svg>',
|
||||
globe: '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg>',
|
||||
shieldCheck: '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/><polyline points="9 12 11 14 15 10"/></svg>',
|
||||
checkCircle: '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg>',
|
||||
helpCircle: '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>',
|
||||
xCircle: '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="15" y1="9" x2="9" y2="15"/><line x1="9" y1="9" x2="15" y2="15"/></svg>',
|
||||
externalLink: '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/></svg>'
|
||||
},
|
||||
|
||||
async init() {
|
||||
if (typeof initTranslations === 'function') {
|
||||
try { initTranslations(); } catch(e) {}
|
||||
@@ -30,6 +42,8 @@ var Lagebild = {
|
||||
this.render();
|
||||
this.initTabs();
|
||||
this.initLangToggle();
|
||||
this.initSmoothScroll();
|
||||
this.initScrollReveal();
|
||||
} catch (e) {
|
||||
console.error('Lagebild laden fehlgeschlagen:', e);
|
||||
this.showError();
|
||||
@@ -38,50 +52,156 @@ var Lagebild = {
|
||||
|
||||
render: function() {
|
||||
this.renderHero();
|
||||
this.renderSnapshotSelector();
|
||||
this.renderTimeline();
|
||||
this.renderTabBadges();
|
||||
this.renderCurrentView();
|
||||
},
|
||||
|
||||
/* ===== HERO ===== */
|
||||
renderHero: function() {
|
||||
var d = this.data;
|
||||
document.getElementById('incident-title').textContent = this.fixUmlauts(d.incident.title);
|
||||
var metaHtml = '';
|
||||
metaHtml += this.metaBadge('clock', 'Stand: ' + this.fmtDT(this.data.generated_at));
|
||||
metaHtml += this.metaBadge('doc', d.incident.article_count + ' Artikel');
|
||||
metaHtml += this.metaBadge('globe', d.incident.source_count + ' Quellen');
|
||||
metaHtml += this.metaBadge('check', d.incident.factcheck_count + ' Faktenchecks');
|
||||
document.getElementById('hero-meta').innerHTML = metaHtml;
|
||||
},
|
||||
|
||||
metaBadge: function(icon, text) {
|
||||
var icons = {
|
||||
clock: '<circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/>',
|
||||
doc: '<path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><polyline points="14 2 14 8 20 8"/>',
|
||||
globe: '<circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 014 10 15.3 15.3 0 01-4 10 15.3 15.3 0 01-4-10 15.3 15.3 0 014-10z"/>',
|
||||
check: '<path d="M22 11.08V12a10 10 0 11-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/>'
|
||||
};
|
||||
return '<div class="meta-badge"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">' + icons[icon] + '</svg><span>' + text + '</span></div>';
|
||||
},
|
||||
// Stat Cards
|
||||
var statsHtml = '';
|
||||
statsHtml += this.statCard(this.icons.clock, this.fmtDateOnly(d.generated_at), 'Stand');
|
||||
statsHtml += this.statCard(this.icons.fileText, '<span class="count-up" data-target="' + d.incident.article_count + '">0</span>', 'Artikel');
|
||||
statsHtml += this.statCard(this.icons.globe, '<span class="count-up" data-target="' + d.incident.source_count + '">0</span>', 'Quellen');
|
||||
statsHtml += this.statCard(this.icons.shieldCheck, '<span class="count-up" data-target="' + d.incident.factcheck_count + '">0</span>', 'Faktenchecks');
|
||||
document.getElementById('hero-stats').innerHTML = statsHtml;
|
||||
|
||||
renderSnapshotSelector: function() {
|
||||
var select = document.getElementById('snapshot-select');
|
||||
select.innerHTML = '';
|
||||
var optC = document.createElement('option');
|
||||
optC.value = 'current';
|
||||
optC.textContent = this.fmtDT(this.data.current_lagebild.updated_at || this.data.generated_at) + ' — Aktuell';
|
||||
optC.selected = true;
|
||||
select.appendChild(optC);
|
||||
var snaps = this.data.available_snapshots || [];
|
||||
for (var i = 0; i < snaps.length; i++) {
|
||||
var s = snaps[i];
|
||||
var opt = document.createElement('option');
|
||||
opt.value = s.id;
|
||||
opt.textContent = this.fmtDT(s.created_at) + ' — ' + s.article_count + ' Artikel, ' + s.fact_check_count + ' Faktenchecks';
|
||||
select.appendChild(opt);
|
||||
// FC Quick Stats
|
||||
var checks = d.fact_checks || [];
|
||||
var stats = { confirmed: 0, unconfirmed: 0, contradicted: 0, developing: 0, established: 0, disputed: 0 };
|
||||
for (var k = 0; k < checks.length; k++) {
|
||||
var st = checks[k].status || 'developing';
|
||||
if (stats[st] !== undefined) stats[st]++;
|
||||
}
|
||||
var confirmed = stats.confirmed + stats.established;
|
||||
var open = stats.unconfirmed + stats.developing;
|
||||
var contradicted = stats.contradicted + stats.disputed;
|
||||
|
||||
var fcHtml = '';
|
||||
fcHtml += '<span class="fc-quick-badge success">' + this.icons.checkCircle + ' <span class="count-up" data-target="' + confirmed + '">0</span> Bestätigt</span>';
|
||||
fcHtml += '<span class="fc-quick-badge warning">' + this.icons.helpCircle + ' <span class="count-up" data-target="' + open + '">0</span> Offen</span>';
|
||||
fcHtml += '<span class="fc-quick-badge error">' + this.icons.xCircle + ' <span class="count-up" data-target="' + contradicted + '">0</span> Widerlegt</span>';
|
||||
document.getElementById('hero-fc-stats').innerHTML = fcHtml;
|
||||
|
||||
// Start count-up animations
|
||||
var self = this;
|
||||
select.addEventListener('change', function() {
|
||||
if (this.value === 'current') {
|
||||
requestAnimationFrame(function() {
|
||||
var els = document.querySelectorAll('.count-up');
|
||||
for (var i = 0; i < els.length; i++) {
|
||||
self.animateCount(els[i], parseInt(els[i].getAttribute('data-target')), 800);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
statCard: function(icon, value, label) {
|
||||
return '<div class="stat-card">' +
|
||||
'<div class="stat-card-icon">' + icon + '</div>' +
|
||||
'<div class="stat-card-content">' +
|
||||
'<span class="stat-card-value">' + value + '</span>' +
|
||||
'<span class="stat-card-label">' + label + '</span>' +
|
||||
'</div></div>';
|
||||
},
|
||||
|
||||
/* ===== COUNT-UP ANIMATION ===== */
|
||||
animateCount: function(element, target, duration) {
|
||||
var start = performance.now();
|
||||
function update(now) {
|
||||
var elapsed = now - start;
|
||||
var progress = Math.min(elapsed / duration, 1);
|
||||
var eased = 1 - Math.pow(1 - progress, 3); // easeOutCubic
|
||||
var current = Math.round(target * eased);
|
||||
element.textContent = current.toLocaleString('de-DE');
|
||||
if (progress < 1) {
|
||||
requestAnimationFrame(update);
|
||||
}
|
||||
}
|
||||
requestAnimationFrame(update);
|
||||
},
|
||||
|
||||
/* ===== TIMELINE STRIP ===== */
|
||||
renderTimeline: function() {
|
||||
var snaps = this.data.available_snapshots || [];
|
||||
var current = {
|
||||
id: 'current',
|
||||
article_count: this.data.incident.article_count,
|
||||
fact_check_count: this.data.incident.factcheck_count,
|
||||
created_at: this.data.generated_at
|
||||
};
|
||||
var all = [current].concat(snaps);
|
||||
|
||||
// Group by date
|
||||
var groups = {};
|
||||
for (var i = 0; i < all.length; i++) {
|
||||
var s = all[i];
|
||||
var dateKey = this.toDateKey(s.created_at);
|
||||
if (!groups[dateKey]) groups[dateKey] = [];
|
||||
groups[dateKey].push(s);
|
||||
}
|
||||
|
||||
// Sort each group descending (newest first)
|
||||
for (var dk in groups) {
|
||||
groups[dk].sort(function(a, b) {
|
||||
return new Date(Lagebild.toUTC(b.created_at)) - new Date(Lagebild.toUTC(a.created_at));
|
||||
});
|
||||
}
|
||||
|
||||
var dates = Object.keys(groups).sort();
|
||||
var strip = document.getElementById('timeline-strip');
|
||||
var h = '';
|
||||
|
||||
for (var j = 0; j < dates.length; j++) {
|
||||
var date = dates[j];
|
||||
var daySnaps = groups[date];
|
||||
var latest = daySnaps[0];
|
||||
var isActive = (j === dates.length - 1);
|
||||
var d = new Date(date + 'T12:00:00Z');
|
||||
|
||||
h += '<button class="timeline-day' + (isActive ? ' active' : '') + '"';
|
||||
h += ' data-date="' + date + '"';
|
||||
h += ' data-snapshot-id="' + latest.id + '"';
|
||||
if (daySnaps.length > 1) {
|
||||
h += ' title="' + daySnaps.length + ' Updates an diesem Tag"';
|
||||
}
|
||||
h += '>';
|
||||
if (isActive) h += '<span class="timeline-dot"></span>';
|
||||
h += '<span class="timeline-day-num">' + d.getUTCDate() + '</span>';
|
||||
h += '<span class="timeline-day-month">' + d.toLocaleDateString('de-DE', { month: 'short', timeZone: 'UTC' }) + '</span>';
|
||||
h += '<span class="timeline-day-count">' + latest.article_count + '</span>';
|
||||
if (daySnaps.length > 1) {
|
||||
h += '<span class="timeline-day-updates">' + daySnaps.length + 'x</span>';
|
||||
}
|
||||
if (isActive) {
|
||||
h += '<span class="timeline-day-label">Aktuell</span>';
|
||||
}
|
||||
h += '</button>';
|
||||
}
|
||||
|
||||
strip.innerHTML = h;
|
||||
|
||||
// Scroll to active day
|
||||
var active = strip.querySelector('.timeline-day.active');
|
||||
if (active) {
|
||||
setTimeout(function() {
|
||||
active.scrollIntoView({ behavior: 'smooth', inline: 'center', block: 'nearest' });
|
||||
}, 150);
|
||||
}
|
||||
|
||||
// Click handler
|
||||
var self = this;
|
||||
strip.addEventListener('click', function(e) {
|
||||
var btn = e.target.closest('.timeline-day');
|
||||
if (!btn) return;
|
||||
|
||||
var allDays = strip.querySelectorAll('.timeline-day');
|
||||
for (var k = 0; k < allDays.length; k++) allDays[k].classList.remove('active');
|
||||
btn.classList.add('active');
|
||||
|
||||
var snapId = btn.getAttribute('data-snapshot-id');
|
||||
if (snapId === 'current') {
|
||||
self.currentView = {
|
||||
summary: self.data.current_lagebild.summary_markdown,
|
||||
sources_json: self.data.current_lagebild.sources_json,
|
||||
@@ -91,11 +211,26 @@ var Lagebild = {
|
||||
};
|
||||
self.renderCurrentView();
|
||||
} else {
|
||||
self.loadSnapshot(parseInt(this.value));
|
||||
self.loadSnapshot(parseInt(snapId));
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
toDateKey: function(iso) {
|
||||
if (!iso) return '';
|
||||
var d = new Date(this.toUTC(iso));
|
||||
return d.toLocaleDateString('en-CA', { timeZone: TIMEZONE });
|
||||
},
|
||||
|
||||
/* ===== TAB BADGES ===== */
|
||||
renderTabBadges: function() {
|
||||
var quellenBadge = document.getElementById('tab-badge-quellen');
|
||||
var fcBadge = document.getElementById('tab-badge-faktenchecks');
|
||||
if (quellenBadge) quellenBadge.textContent = this.data.incident.source_count;
|
||||
if (fcBadge) fcBadge.textContent = this.data.incident.factcheck_count;
|
||||
},
|
||||
|
||||
/* ===== SNAPSHOT LOADING ===== */
|
||||
loadSnapshot: async function(id) {
|
||||
if (this.allSnapshots[id]) {
|
||||
this.currentView = this.allSnapshots[id];
|
||||
@@ -131,7 +266,7 @@ var Lagebild = {
|
||||
}
|
||||
},
|
||||
|
||||
// ===== TAB: LAGEBILD =====
|
||||
/* ===== TAB: LAGEBILD ===== */
|
||||
renderSummary: function() {
|
||||
var v = this.currentView;
|
||||
document.getElementById('lagebild-timestamp').textContent = this.fmtDT(v.updated_at);
|
||||
@@ -144,7 +279,7 @@ var Lagebild = {
|
||||
renderInlineSources: function() {
|
||||
var sources = this.currentView.sources_json || [];
|
||||
if (!sources.length) { document.getElementById('inline-sources').innerHTML = ''; return; }
|
||||
var h = '<h3 style="font-size:1rem;font-weight:700;color:#1a1a2e;margin:0 0 12px;">Quellenverzeichnis</h3>';
|
||||
var h = '<h3 class="inline-sources-title">Quellenverzeichnis</h3>';
|
||||
h += '<ul class="inline-sources-list">';
|
||||
for (var i = 0; i < sources.length; i++) {
|
||||
var s = sources[i];
|
||||
@@ -157,13 +292,12 @@ var Lagebild = {
|
||||
document.getElementById('inline-sources').innerHTML = h;
|
||||
},
|
||||
|
||||
// ===== TAB: QUELLEN =====
|
||||
/* ===== TAB: QUELLEN ===== */
|
||||
renderSourcesTab: function() {
|
||||
var sources = this.currentView.sources_json || [];
|
||||
var articles = this.currentView.articles || [];
|
||||
var h = '';
|
||||
|
||||
// Zitierte Quellen
|
||||
h += '<div class="sources-section"><h3>Im Lagebild zitierte Quellen (' + sources.length + ')</h3>';
|
||||
if (sources.length) {
|
||||
h += '<ol class="sources-list">';
|
||||
@@ -178,7 +312,6 @@ var Lagebild = {
|
||||
h += '</div>';
|
||||
document.getElementById('sources-cited').innerHTML = h;
|
||||
|
||||
// Alle Artikel
|
||||
document.getElementById('articles-heading').textContent = 'Alle gesammelten Artikel (' + articles.length + ')';
|
||||
var ah = '';
|
||||
for (var i = 0; i < articles.length; i++) {
|
||||
@@ -186,11 +319,13 @@ var Lagebild = {
|
||||
var dt = a.published_at || a.collected_at || '';
|
||||
var dObj = dt ? new Date(this.toUTC(dt)) : null;
|
||||
var hl = this.fixUmlauts(a.headline_de || a.headline || '');
|
||||
var domain = this.extractDomain(a.source_url);
|
||||
|
||||
ah += '<div class="article-card">';
|
||||
ah += '<div class="article-date">';
|
||||
if (dObj && !isNaN(dObj.getTime())) {
|
||||
ah += '<span class="day">' + dObj.toLocaleDateString('de-DE', {day:'numeric', timeZone:TIMEZONE}) + '</span>';
|
||||
ah += '<span class="month">' + dObj.toLocaleDateString('de-DE', {month:'short', timeZone:TIMEZONE}) + '</span>';
|
||||
ah += '<span class="day">' + dObj.toLocaleDateString('de-DE', { day: 'numeric', timeZone: TIMEZONE }) + '</span>';
|
||||
ah += '<span class="month">' + dObj.toLocaleDateString('de-DE', { month: 'short', timeZone: TIMEZONE }) + '</span>';
|
||||
}
|
||||
ah += '</div><div class="article-info">';
|
||||
ah += '<p class="article-headline">';
|
||||
@@ -198,29 +333,31 @@ var Lagebild = {
|
||||
ah += this.esc(hl);
|
||||
if (a.source_url) ah += '</a>';
|
||||
ah += '</p><div class="article-meta">';
|
||||
ah += '<span class="article-source">' + this.esc(a.source || '') + '</span>';
|
||||
ah += '<span class="article-source">';
|
||||
if (domain) ah += '<img class="article-favicon" src="https://www.google.com/s2/favicons?domain=' + encodeURIComponent(domain) + '&sz=16" width="16" height="16" alt="" loading="lazy"> ';
|
||||
ah += this.esc(a.source || '') + '</span>';
|
||||
if (a.language) ah += '<span class="article-lang">' + a.language.toUpperCase() + '</span>';
|
||||
if (dObj && !isNaN(dObj.getTime())) {
|
||||
ah += '<span>' + dObj.toLocaleDateString('de-DE', {day:'2-digit',month:'2-digit',year:'numeric',timeZone:TIMEZONE}) + ', ' + dObj.toLocaleTimeString('de-DE', {hour:'2-digit',minute:'2-digit',timeZone:TIMEZONE}) + ' Uhr</span>';
|
||||
ah += '<span>' + dObj.toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric', timeZone: TIMEZONE }) + ', ' + dObj.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit', timeZone: TIMEZONE }) + ' Uhr</span>';
|
||||
}
|
||||
if (a.source_url) ah += '<span><a href="' + this.esc(a.source_url) + '" target="_blank" rel="noopener" style="color:#0f72b5;text-decoration:none;font-size:0.78rem;">Artikel lesen →</a></span>';
|
||||
if (a.source_url) ah += '<a class="article-link" href="' + this.esc(a.source_url) + '" target="_blank" rel="noopener">Artikel lesen ' + this.icons.externalLink + '</a>';
|
||||
ah += '</div></div></div>';
|
||||
}
|
||||
document.getElementById('articles-content').innerHTML = ah;
|
||||
},
|
||||
|
||||
// Placeholder for separate articles render (not used in tab structure)
|
||||
renderArticlesTab: function() {},
|
||||
|
||||
// ===== TAB: KARTE =====
|
||||
/* ===== TAB: KARTE ===== */
|
||||
renderMap: function() {
|
||||
if (this.map) { this.map.remove(); this.map = null; }
|
||||
this.map = L.map('map-container').setView([33.0, 48.0], 5);
|
||||
|
||||
// Deutsche Kartenbeschriftungen via OpenStreetMap DE
|
||||
L.tileLayer('https://tile.openstreetmap.de/{z}/{x}/{y}.png', {
|
||||
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>',
|
||||
maxZoom: 18
|
||||
// Dark map tiles (CartoDB Dark Matter)
|
||||
L.tileLayer('https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png', {
|
||||
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> © <a href="https://carto.com/">CARTO</a>',
|
||||
maxZoom: 19,
|
||||
subdomains: 'abcd'
|
||||
}).addTo(this.map);
|
||||
|
||||
var redIcon = L.icon({
|
||||
@@ -254,17 +391,17 @@ var Lagebild = {
|
||||
|
||||
for (var i = 0; i < locs.length; i++) {
|
||||
var l = locs[i];
|
||||
L.marker([l.lat, l.lng], {icon: l.ic})
|
||||
L.marker([l.lat, l.lng], { icon: l.ic })
|
||||
.addTo(this.map)
|
||||
.bindPopup('<strong>' + l.n + '</strong><br><span style="font-size:0.85rem;color:#444;">' + l.d + '</span>');
|
||||
.bindPopup('<strong style="color:#E8ECF4;">' + l.n + '</strong><br><span style="font-size:0.85rem;color:#8896AB;">' + l.d + '</span>');
|
||||
}
|
||||
|
||||
// Legende
|
||||
var legend = L.control({position: 'bottomright'});
|
||||
// Dark legend
|
||||
var legend = L.control({ position: 'bottomright' });
|
||||
legend.onAdd = function() {
|
||||
var div = L.DomUtil.create('div', 'map-legend');
|
||||
div.style.cssText = 'background:#fff;padding:10px 14px;border-radius:8px;box-shadow:0 2px 8px rgba(0,0,0,0.15);font-size:0.8rem;line-height:1.8;';
|
||||
div.innerHTML = '<strong>Legende</strong><br>'
|
||||
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;';
|
||||
div.innerHTML = '<strong style="color:#C8A851;">Legende</strong><br>'
|
||||
+ '<span style="color:#cb2b3e;">●</span> Angegriffene Ziele<br>'
|
||||
+ '<span style="color:#f39c12;">●</span> Vergeltung / Eskalation<br>'
|
||||
+ '<span style="color:#2a81cb;">●</span> Strategische Akteure';
|
||||
@@ -272,19 +409,23 @@ var Lagebild = {
|
||||
};
|
||||
legend.addTo(this.map);
|
||||
|
||||
// Dark popup styling
|
||||
var style = document.createElement('style');
|
||||
style.textContent = '.lagebild-page .leaflet-popup-content-wrapper{background:#151D2E;color:#E8ECF4;border:1px solid #1E2D45;border-radius:4px;box-shadow:0 4px 16px rgba(0,0,0,0.4);}.lagebild-page .leaflet-popup-tip{background:#151D2E;}';
|
||||
document.head.appendChild(style);
|
||||
|
||||
setTimeout(function() { if (Lagebild.map) Lagebild.map.invalidateSize(); }, 300);
|
||||
},
|
||||
|
||||
// ===== TAB: FAKTENCHECKS =====
|
||||
/* ===== TAB: FAKTENCHECKS ===== */
|
||||
renderFactChecksTab: function() {
|
||||
var checks = this.currentView.fact_checks || [];
|
||||
if (!checks.length) {
|
||||
document.getElementById('factchecks-content').innerHTML = '<p style="color:#888">Keine Faktenchecks verfügbar.</p>';
|
||||
document.getElementById('factchecks-content').innerHTML = '<p style="color:#8896AB">Keine Faktenchecks verfügbar.</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
// Statistik
|
||||
var stats = {confirmed:0, unconfirmed:0, contradicted:0, developing:0, established:0, disputed:0};
|
||||
var stats = { confirmed: 0, unconfirmed: 0, contradicted: 0, developing: 0, established: 0, disputed: 0 };
|
||||
for (var k = 0; k < checks.length; k++) {
|
||||
var st = checks[k].status || 'developing';
|
||||
if (stats[st] !== undefined) stats[st]++;
|
||||
@@ -298,7 +439,6 @@ var Lagebild = {
|
||||
h += '<div class="fc-stat contradicted"><span class="fc-stat-num">' + (stats.contradicted + stats.disputed) + '</span><span class="fc-stat-label">Widerlegt</span></div>';
|
||||
h += '</div>';
|
||||
|
||||
// Sortierung: mit History zuerst, dann sources_count
|
||||
checks = checks.slice().sort(function(a, b) {
|
||||
var aH = (a.status_history || []).length;
|
||||
var bH = (b.status_history || []).length;
|
||||
@@ -318,15 +458,12 @@ var Lagebild = {
|
||||
h += '</div>';
|
||||
h += '<p class="factcheck-claim">' + this.esc(this.fixUmlauts(fc.claim || '')) + '</p>';
|
||||
|
||||
// Vollständige Evidenz anzeigen
|
||||
if (fc.evidence) {
|
||||
var ev = this.fixUmlauts(fc.evidence);
|
||||
// URLs in der Evidenz klickbar machen
|
||||
ev = this.esc(ev).replace(/(https?:\/\/[^\s,)]+)/g, '<a href="$1" target="_blank" rel="noopener" style="color:#0f72b5;">$1</a>');
|
||||
ev = this.esc(ev).replace(/(https?:\/\/[^\s,)]+)/g, '<a href="$1" target="_blank" rel="noopener">$1</a>');
|
||||
h += '<div class="factcheck-evidence"><strong>Evidenz:</strong> ' + ev + '</div>';
|
||||
}
|
||||
|
||||
// Status-Verlauf
|
||||
if (hasProg) {
|
||||
h += '<div class="status-progression">';
|
||||
h += '<span class="progression-label">Verlauf:</span>';
|
||||
@@ -345,7 +482,7 @@ var Lagebild = {
|
||||
document.getElementById('factchecks-content').innerHTML = h;
|
||||
},
|
||||
|
||||
// ===== TABS =====
|
||||
/* ===== TABS ===== */
|
||||
initTabs: function() {
|
||||
var btns = document.querySelectorAll('.tab-btn');
|
||||
var self = this;
|
||||
@@ -374,7 +511,55 @@ var Lagebild = {
|
||||
});
|
||||
},
|
||||
|
||||
// ===== HILFSFUNKTIONEN =====
|
||||
/* ===== SMOOTH SCROLL FOR CITATIONS ===== */
|
||||
initSmoothScroll: function() {
|
||||
document.addEventListener('click', function(e) {
|
||||
var link = e.target.closest('.citation-ref');
|
||||
if (!link) return;
|
||||
e.preventDefault();
|
||||
var href = link.getAttribute('href');
|
||||
if (!href) return;
|
||||
var targetId = href.substring(1);
|
||||
var target = document.getElementById(targetId);
|
||||
if (!target) return;
|
||||
|
||||
target.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
target.classList.add('source-highlight');
|
||||
setTimeout(function() {
|
||||
target.classList.remove('source-highlight');
|
||||
}, 1500);
|
||||
});
|
||||
},
|
||||
|
||||
/* ===== SCROLL REVEAL ===== */
|
||||
initScrollReveal: function() {
|
||||
var cards = document.querySelectorAll('.content-card, .lagebild-cta');
|
||||
if (!('IntersectionObserver' in window)) {
|
||||
// Fallback: show all immediately
|
||||
for (var i = 0; i < cards.length; i++) cards[i].classList.add('revealed');
|
||||
return;
|
||||
}
|
||||
var observer = new IntersectionObserver(function(entries) {
|
||||
entries.forEach(function(entry) {
|
||||
if (entry.isIntersecting) {
|
||||
entry.target.classList.add('revealed');
|
||||
observer.unobserve(entry.target);
|
||||
}
|
||||
});
|
||||
}, { threshold: 0.1 });
|
||||
|
||||
for (var i = 0; i < cards.length; i++) {
|
||||
cards[i].classList.add('reveal');
|
||||
observer.observe(cards[i]);
|
||||
}
|
||||
},
|
||||
|
||||
/* ===== HILFSFUNKTIONEN ===== */
|
||||
extractDomain: function(url) {
|
||||
if (!url) return null;
|
||||
try { return new URL(url).hostname; } catch(e) { return null; }
|
||||
},
|
||||
|
||||
fixUmlauts: function(text) {
|
||||
if (!text) return text;
|
||||
var skip = ['Israel','Israelis','Jazeera','Euronews','Reuters','Februar',
|
||||
@@ -394,9 +579,9 @@ var Lagebild = {
|
||||
},
|
||||
|
||||
stLabel: function(s) {
|
||||
return {confirmed:'Bestätigt',unconfirmed:'Unbestätigt',established:'Gesichert',
|
||||
unverified:'Nicht verifiziert',contradicted:'Widerlegt',disputed:'Umstritten',
|
||||
developing:'In Entwicklung','false':'Falsch'}[s] || s;
|
||||
return { confirmed: 'Bestätigt', unconfirmed: 'Unbestätigt', established: 'Gesichert',
|
||||
unverified: 'Nicht verifiziert', contradicted: 'Widerlegt', disputed: 'Umstritten',
|
||||
developing: 'In Entwicklung', 'false': 'Falsch' }[s] || s;
|
||||
},
|
||||
|
||||
mdToHtml: function(md) {
|
||||
@@ -404,11 +589,11 @@ var Lagebild = {
|
||||
var lines = md.split('\n'), html = '', inList = false;
|
||||
for (var i = 0; i < lines.length; i++) {
|
||||
var l = lines[i];
|
||||
if (/^### (.+)$/.test(l)) { if(inList){html+='</ul>';inList=false;} html+='<h3>'+l.replace(/^### /,'')+'</h3>'; continue; }
|
||||
if (/^## (.+)$/.test(l)) { if(inList){html+='</ul>';inList=false;} html+='<h2>'+l.replace(/^## /,'')+'</h2>'; continue; }
|
||||
if (/^[-*] (.+)$/.test(l)) { if(!inList){html+='<ul>';inList=true;} html+='<li>'+l.replace(/^[-*] /,'')+'</li>'; continue; }
|
||||
if (inList) { html+='</ul>'; inList=false; }
|
||||
if (l.trim()==='') continue;
|
||||
if (/^### (.+)$/.test(l)) { if (inList) { html += '</ul>'; inList = false; } html += '<h3>' + l.replace(/^### /, '') + '</h3>'; continue; }
|
||||
if (/^## (.+)$/.test(l)) { if (inList) { html += '</ul>'; inList = false; } html += '<h2>' + l.replace(/^## /, '') + '</h2>'; continue; }
|
||||
if (/^[-*] (.+)$/.test(l)) { if (!inList) { html += '<ul>'; inList = true; } html += '<li>' + l.replace(/^[-*] /, '') + '</li>'; continue; }
|
||||
if (inList) { html += '</ul>'; inList = false; }
|
||||
if (l.trim() === '') continue;
|
||||
html += '<p>' + l + '</p>';
|
||||
}
|
||||
if (inList) html += '</ul>';
|
||||
@@ -417,17 +602,12 @@ var Lagebild = {
|
||||
return html;
|
||||
},
|
||||
|
||||
esc: function(s) { if(!s)return''; var d=document.createElement('div'); d.textContent=s; return d.innerHTML; },
|
||||
truncUrl: function(u) { try{return new URL(u).hostname;}catch(e){return u;} },
|
||||
esc: function(s) { if (!s) return ''; var d = document.createElement('div'); d.textContent = s; return d.innerHTML; },
|
||||
|
||||
// Timestamps aus der DB sind UTC, aber ohne Zeitzone-Suffix.
|
||||
// Diese Funktion haengt 'Z' an falls noetig, damit der Browser korrekt nach CET konvertiert.
|
||||
toUTC: function(s) {
|
||||
if (!s) return s;
|
||||
s = String(s).trim();
|
||||
// Hat schon Zeitzone (+00:00, Z, +01:00 etc.)? Dann nichts tun.
|
||||
if (/[Zz]$/.test(s) || /[+-]\d{2}:?\d{2}$/.test(s)) return s;
|
||||
// "2026-03-07 00:42:01" -> "2026-03-07T00:42:01Z"
|
||||
return s.replace(' ', 'T') + 'Z';
|
||||
},
|
||||
|
||||
@@ -436,24 +616,33 @@ var Lagebild = {
|
||||
try {
|
||||
var d = new Date(this.toUTC(iso));
|
||||
if (isNaN(d.getTime())) return iso;
|
||||
var opts = {timeZone:TIMEZONE, weekday:'long', day:'numeric', month:'long', year:'numeric', hour:'2-digit', minute:'2-digit', hour12:false};
|
||||
var opts = { timeZone: TIMEZONE, weekday: 'long', day: 'numeric', month: 'long', year: 'numeric', hour: '2-digit', minute: '2-digit', hour12: false };
|
||||
var parts = new Intl.DateTimeFormat('de-DE', opts).formatToParts(d);
|
||||
var p = {};
|
||||
parts.forEach(function(x){ p[x.type] = x.value; });
|
||||
parts.forEach(function(x) { p[x.type] = x.value; });
|
||||
return p.weekday + ', ' + p.day + '. ' + p.month + ' ' + p.year
|
||||
+ ' um ' + p.hour + ':' + p.minute + ' Uhr';
|
||||
} catch(e) { return iso; }
|
||||
},
|
||||
|
||||
fmtDateOnly: function(iso) {
|
||||
if (!iso) return '';
|
||||
try {
|
||||
var d = new Date(this.toUTC(iso));
|
||||
if (isNaN(d.getTime())) return iso;
|
||||
return d.toLocaleDateString('de-DE', { day: 'numeric', month: 'short', year: 'numeric', timeZone: TIMEZONE });
|
||||
} catch(e) { return iso; }
|
||||
},
|
||||
|
||||
fmtShort: function(iso) {
|
||||
if (!iso) return '';
|
||||
try { return new Date(this.toUTC(iso)).toLocaleDateString('de-DE', {day:'numeric',month:'short',hour:'2-digit',minute:'2-digit',timeZone:TIMEZONE}); }
|
||||
try { return new Date(this.toUTC(iso)).toLocaleDateString('de-DE', { day: 'numeric', month: 'short', hour: '2-digit', minute: '2-digit', timeZone: TIMEZONE }); }
|
||||
catch(e) { return iso; }
|
||||
},
|
||||
|
||||
showError: function() {
|
||||
document.getElementById('summary-content').innerHTML =
|
||||
'<div class="lagebild-error"><p>Das Lagebild konnte nicht geladen werden. Bitte versuchen Sie es sp\u00e4ter erneut.</p></div>';
|
||||
'<div class="lagebild-error"><p>Das Lagebild konnte nicht geladen werden. Bitte versuchen Sie es später erneut.</p></div>';
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren