Lagebild: Timeline-Dropdown durch horizontale Timeline ersetzt

- Grid-Dropdown durch horizontale Zeitleiste ersetzt (links alt, rechts neu)
- Goldene Farbgebung: Punkte, Linie, aktiver Punkt pulsierend
- Sichtbare goldene Scrollbar bei vielen Einträgen
- Fester Abstand zwischen Punkten (gap statt flex:1)
- Auto-Scroll zum aktiven (aktuellsten) Punkt
- Andere Tage laden den ältesten Snapshot, aktueller Tag den neuesten
- Labels ausgeschrieben (Artikel, Faktenchecks)
- Timeline immer sichtbar (auch bei nur 1 Snapshot)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Dieser Commit ist enthalten in:
Claude Code
2026-03-08 13:19:25 +01:00
Ursprung 039c9a6832
Commit f9a0b1b3b9
2 geänderte Dateien mit 189 neuen und 57 gelöschten Zeilen

Datei anzeigen

@@ -164,7 +164,7 @@ var Lagebild = {
var statsHtml = '';
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');
statsHtml += this.statCard(this.icons.shieldCheck, '<span class="count-up" id="hero-fc-count" data-target="' + d.incident.factcheck_count + '">0</span>', 'Faktenchecks');
document.getElementById('hero-stats').innerHTML = statsHtml;
// Start count-up animations
@@ -237,13 +237,13 @@ var Lagebild = {
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 defaultSnap = isActive ? daySnaps[0] : daySnaps[daySnaps.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 + '"';
h += ' data-snapshot-id="' + defaultSnap.id + '"';
if (daySnaps.length > 1) {
h += ' title="' + daySnaps.length + ' Updates an diesem Tag"';
}
@@ -251,7 +251,7 @@ var Lagebild = {
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>';
h += '<span class="timeline-day-count">' + defaultSnap.article_count + '</span>';
if (daySnaps.length > 1) {
h += '<span class="timeline-day-updates">' + daySnaps.length + 'x</span>';
}
@@ -303,10 +303,10 @@ var Lagebild = {
// Click handler for dropdown snapshot items (delegated, set up once)
var dropdown = document.getElementById('timeline-dropdown');
dropdown.addEventListener('click', function(e) {
var item = e.target.closest('.timeline-snap-item');
var item = e.target.closest('.h-timeline-point');
if (!item) return;
var items = dropdown.querySelectorAll('.timeline-snap-item');
var items = dropdown.querySelectorAll('.h-timeline-point');
for (var k = 0; k < items.length; k++) items[k].classList.remove('active');
item.classList.add('active');
@@ -327,7 +327,7 @@ var Lagebild = {
// Show dropdown for newest day by default
var newestDate = dates[dates.length - 1];
if (newestDate && groups[newestDate].length > 1) {
if (newestDate) {
this.showTimelineDropdown(newestDate, groups[newestDate][0].id);
}
},
@@ -336,30 +336,39 @@ var Lagebild = {
var dropdown = document.getElementById('timeline-dropdown');
var snaps = this.timelineGroups[dateKey];
if (!snaps || snaps.length <= 1) {
if (!snaps || snaps.length === 0) {
dropdown.classList.remove('open');
dropdown.innerHTML = '';
return;
}
var d = new Date(dateKey + 'T12:00:00Z');
var dateLabel = d.toLocaleDateString('de-DE', { day: 'numeric', month: 'long', year: 'numeric', timeZone: 'UTC' });
// Oldest left, newest right
var ordered = snaps.slice().reverse();
var h = '<div class="timeline-dropdown-header">' + dateLabel + ' \u2013 ' + snaps.length + ' Updates</div>';
h += '<div class="timeline-snap-list">';
for (var i = 0; i < snaps.length; i++) {
var snap = snaps[i];
var h = '<div class="h-timeline-track">';
for (var i = 0; i < ordered.length; i++) {
var snap = ordered[i];
var isActive = (String(snap.id) === String(activeSnapId));
h += '<button class="timeline-snap-item' + (isActive ? ' active' : '') + '"';
h += '<button class="h-timeline-point' + (isActive ? ' active' : '') + '"';
h += ' data-snapshot-id="' + snap.id + '">';
h += '<span class="timeline-snap-time">' + this.fmtTimeOnly(snap.created_at) + ' Uhr</span>';
h += '<span class="timeline-snap-meta">' + snap.article_count + ' Artikel, ' + snap.fact_check_count + ' Faktenchecks</span>';
h += '<span class="h-timeline-time">' + this.fmtTimeOnly(snap.created_at) + '</span>';
h += '<span class="h-timeline-dot"></span>';
h += '<span class="h-timeline-meta">' + snap.article_count + ' Artikel</span>';
h += '<span class="h-timeline-meta">' + (snap.fact_check_count || 0) + ' Faktenchecks</span>';
h += '</button>';
}
h += '</div>';
dropdown.innerHTML = h;
dropdown.classList.add('open');
// Scroll to active point
var activePoint = dropdown.querySelector('.h-timeline-point.active');
if (activePoint) {
setTimeout(function() {
activePoint.scrollIntoView({ behavior: 'smooth', inline: 'center', block: 'nearest' });
}, 50);
}
},
toDateKey: function(iso) {
@@ -394,7 +403,7 @@ var Lagebild = {
sources_json: sj || [],
updated_at: sd.created_at,
articles: this.data.articles,
fact_checks: this.data.fact_checks
fact_checks: this.getFactChecksAtTime(sd.created_at)
};
this.allSnapshots[id] = this.currentView;
this.renderCurrentView();
@@ -410,6 +419,12 @@ var Lagebild = {
if (document.getElementById('panel-karte').classList.contains('active')) {
this.renderMap();
}
// Faktencheck-Zaehler aktualisieren (Badge + Hero)
var fcCount = (this.currentView.fact_checks || []).length;
var fcBadge = document.getElementById('tab-badge-faktenchecks');
if (fcBadge) fcBadge.textContent = fcCount;
var heroFc = document.getElementById('hero-fc-count');
if (heroFc) heroFc.textContent = fcCount.toLocaleString('de-DE');
},
/* ===== TAB: LAGEBILD ===== */
@@ -943,6 +958,42 @@ var Lagebild = {
}
},
/* ===== FAKTENCHECK-FILTER NACH ZEITRAUM ===== */
getFactChecksAtTime: function(cutoff) {
var allFCs = this.data.fact_checks || [];
if (!cutoff) return allFCs;
var cutoffTime = new Date(this.toUTC(cutoff)).getTime();
var filtered = [];
for (var i = 0; i < allFCs.length; i++) {
var fc = allFCs[i];
var hist = fc.status_history || [];
if (!hist.length) continue;
// Erster Eintrag = Erstellungszeitpunkt des Faktenchecks
var firstAt = new Date(this.toUTC(hist[0].at)).getTime();
if (firstAt > cutoffTime) continue;
// Status zum gewaehlten Zeitpunkt ermitteln
var statusAtTime = hist[0].status;
for (var j = 0; j < hist.length; j++) {
var stepTime = new Date(this.toUTC(hist[j].at)).getTime();
if (stepTime <= cutoffTime) {
statusAtTime = hist[j].status;
}
}
// Kopie mit angepasstem Status und getrimmter History
var copy = {};
for (var key in fc) { if (fc.hasOwnProperty(key)) copy[key] = fc[key]; }
copy.status = statusAtTime;
copy.status_history = [];
for (var j = 0; j < hist.length; j++) {
if (new Date(this.toUTC(hist[j].at)).getTime() <= cutoffTime) {
copy.status_history.push(hist[j]);
}
}
filtered.push(copy);
}
return filtered;
},
/* ===== HILFSFUNKTIONEN ===== */
extractDomain: function(url) {
if (!url) return null;