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:
@@ -301,7 +301,7 @@
|
|||||||
scroll-behavior: smooth;
|
scroll-behavior: smooth;
|
||||||
padding: 4px 0;
|
padding: 4px 0;
|
||||||
scrollbar-width: thin;
|
scrollbar-width: thin;
|
||||||
scrollbar-color: var(--lb-border) transparent;
|
scrollbar-color: rgba(200, 168, 81, 0.4) rgba(200, 168, 81, 0.08);
|
||||||
}
|
}
|
||||||
.timeline-strip::-webkit-scrollbar {
|
.timeline-strip::-webkit-scrollbar {
|
||||||
height: 4px;
|
height: 4px;
|
||||||
@@ -389,60 +389,137 @@
|
|||||||
margin-top: 2px;
|
margin-top: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Timeline Dropdown */
|
/* Timeline Dropdown (Horizontal Timeline) */
|
||||||
.timeline-dropdown {
|
.timeline-dropdown {
|
||||||
display: none;
|
display: none;
|
||||||
background: var(--lb-bg-secondary);
|
background: var(--lb-bg-secondary);
|
||||||
border: 1px solid var(--lb-border);
|
border: 1px solid var(--lb-border);
|
||||||
border-top: none;
|
border-top: none;
|
||||||
border-radius: 0 0 var(--radius-sm, 4px) var(--radius-sm, 4px);
|
border-radius: 0 0 var(--radius-sm, 4px) var(--radius-sm, 4px);
|
||||||
padding: 10px 14px;
|
padding: 8px 0;
|
||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
|
overflow-x: auto;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: rgba(200, 168, 81, 0.4) rgba(200, 168, 81, 0.08);
|
||||||
}
|
}
|
||||||
.timeline-dropdown.open {
|
.timeline-dropdown.open {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
.timeline-dropdown-header {
|
.timeline-dropdown::-webkit-scrollbar {
|
||||||
font-size: 0.78rem;
|
height: 6px;
|
||||||
color: var(--lb-text-sec);
|
|
||||||
margin-bottom: 8px;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
}
|
||||||
.timeline-snap-list {
|
.timeline-dropdown::-webkit-scrollbar-track {
|
||||||
display: grid;
|
background: rgba(200, 168, 81, 0.08);
|
||||||
grid-template-columns: repeat(auto-fill, minmax(210px, 1fr));
|
border-radius: 3px;
|
||||||
gap: 6px;
|
|
||||||
}
|
}
|
||||||
.timeline-snap-item {
|
.timeline-dropdown::-webkit-scrollbar-thumb {
|
||||||
display: inline-flex;
|
background: rgba(200, 168, 81, 0.35);
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
.timeline-dropdown::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: rgba(200, 168, 81, 0.55);
|
||||||
|
}
|
||||||
|
.h-timeline-track {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
min-width: min-content;
|
||||||
|
padding: 4px 24px;
|
||||||
|
gap: 32px;
|
||||||
|
}
|
||||||
|
.h-timeline-point {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
flex: 0 0 auto;
|
||||||
padding: 6px 12px;
|
min-width: 70px;
|
||||||
border-radius: var(--radius-sm, 4px);
|
position: relative;
|
||||||
border: 1px solid var(--lb-border);
|
|
||||||
background: transparent;
|
|
||||||
color: var(--lb-text-sec);
|
|
||||||
font-size: 0.78rem;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
padding: 4px 0;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
transition: all 0.2s;
|
transition: all 0.2s;
|
||||||
}
|
}
|
||||||
.timeline-snap-item:hover {
|
/* Connecting line through dot center */
|
||||||
background: var(--lb-bg-card);
|
.h-timeline-point::before {
|
||||||
border-color: rgba(200, 168, 81, 0.4);
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 29px;
|
||||||
|
left: -16px;
|
||||||
|
right: -16px;
|
||||||
|
height: 2px;
|
||||||
|
background: rgba(200, 168, 81, 0.2);
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
.h-timeline-point:first-child::before {
|
||||||
|
left: 50%;
|
||||||
|
right: -16px;
|
||||||
|
}
|
||||||
|
.h-timeline-point:last-child::before {
|
||||||
|
left: -16px;
|
||||||
|
right: 50%;
|
||||||
|
}
|
||||||
|
.h-timeline-point:only-child::before {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.h-timeline-time {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--lb-text-sec);
|
||||||
|
margin-bottom: 8px;
|
||||||
|
line-height: 1;
|
||||||
|
transition: color 0.2s;
|
||||||
|
}
|
||||||
|
.h-timeline-dot {
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: rgba(200, 168, 81, 0.35);
|
||||||
|
z-index: 1;
|
||||||
|
position: relative;
|
||||||
|
transition: all 0.2s;
|
||||||
|
box-shadow: 0 0 4px rgba(200, 168, 81, 0.1);
|
||||||
|
}
|
||||||
|
.h-timeline-meta {
|
||||||
|
font-size: 0.65rem;
|
||||||
|
color: var(--lb-text-sec);
|
||||||
|
line-height: 1.4;
|
||||||
|
margin-top: 2px;
|
||||||
|
transition: color 0.2s;
|
||||||
|
white-space: normal;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.h-timeline-meta:first-of-type {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
/* Hover */
|
||||||
|
.h-timeline-point:hover .h-timeline-time {
|
||||||
color: var(--lb-text);
|
color: var(--lb-text);
|
||||||
}
|
}
|
||||||
.timeline-snap-item.active {
|
.h-timeline-point:hover .h-timeline-dot {
|
||||||
background: rgba(200, 168, 81, 0.1);
|
background: var(--lb-accent);
|
||||||
border-color: var(--lb-accent);
|
box-shadow: 0 0 8px var(--lb-glow-soft);
|
||||||
|
transform: scale(1.2);
|
||||||
|
}
|
||||||
|
/* Active */
|
||||||
|
.h-timeline-point.active .h-timeline-time {
|
||||||
color: var(--lb-accent);
|
color: var(--lb-accent);
|
||||||
}
|
}
|
||||||
.timeline-snap-time {
|
.h-timeline-point.active .h-timeline-dot {
|
||||||
font-weight: 600;
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
margin: -1px 0;
|
||||||
|
background: var(--lb-accent);
|
||||||
|
box-shadow: 0 0 12px var(--lb-glow);
|
||||||
|
animation: h-timeline-pulse 2s infinite;
|
||||||
}
|
}
|
||||||
.timeline-snap-meta {
|
@keyframes h-timeline-pulse {
|
||||||
font-size: 0.7rem;
|
0%, 100% { box-shadow: 0 0 12px var(--lb-glow); transform: scale(1); }
|
||||||
opacity: 0.7;
|
50% { box-shadow: 0 0 20px var(--lb-glow), 0 0 6px var(--lb-accent); transform: scale(1.15); }
|
||||||
|
}
|
||||||
|
.h-timeline-point.active .h-timeline-meta {
|
||||||
|
color: var(--lb-accent);
|
||||||
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Tab Navigation */
|
/* Tab Navigation */
|
||||||
@@ -1377,15 +1454,19 @@ a.source-detail-article-title:hover {
|
|||||||
font-size: 0.95rem;
|
font-size: 0.95rem;
|
||||||
}
|
}
|
||||||
.timeline-dropdown {
|
.timeline-dropdown {
|
||||||
padding: 8px 10px;
|
padding: 6px 0;
|
||||||
}
|
}
|
||||||
.timeline-snap-list {
|
.h-timeline-track {
|
||||||
grid-template-columns: repeat(auto-fill, minmax(170px, 1fr));
|
padding: 4px 12px;
|
||||||
gap: 4px;
|
|
||||||
}
|
}
|
||||||
.timeline-snap-item {
|
.h-timeline-point {
|
||||||
padding: 5px 10px;
|
min-width: 64px;
|
||||||
font-size: 0.72rem;
|
}
|
||||||
|
.h-timeline-time {
|
||||||
|
font-size: 0.68rem;
|
||||||
|
}
|
||||||
|
.h-timeline-meta {
|
||||||
|
font-size: 0.6rem;
|
||||||
}
|
}
|
||||||
.tab-nav {
|
.tab-nav {
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
|
|||||||
@@ -164,7 +164,7 @@ var Lagebild = {
|
|||||||
var statsHtml = '';
|
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.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.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;
|
document.getElementById('hero-stats').innerHTML = statsHtml;
|
||||||
|
|
||||||
// Start count-up animations
|
// Start count-up animations
|
||||||
@@ -237,13 +237,13 @@ var Lagebild = {
|
|||||||
for (var j = 0; j < dates.length; j++) {
|
for (var j = 0; j < dates.length; j++) {
|
||||||
var date = dates[j];
|
var date = dates[j];
|
||||||
var daySnaps = groups[date];
|
var daySnaps = groups[date];
|
||||||
var latest = daySnaps[0];
|
|
||||||
var isActive = (j === dates.length - 1);
|
var isActive = (j === dates.length - 1);
|
||||||
|
var defaultSnap = isActive ? daySnaps[0] : daySnaps[daySnaps.length - 1];
|
||||||
var d = new Date(date + 'T12:00:00Z');
|
var d = new Date(date + 'T12:00:00Z');
|
||||||
|
|
||||||
h += '<button class="timeline-day' + (isActive ? ' active' : '') + '"';
|
h += '<button class="timeline-day' + (isActive ? ' active' : '') + '"';
|
||||||
h += ' data-date="' + date + '"';
|
h += ' data-date="' + date + '"';
|
||||||
h += ' data-snapshot-id="' + latest.id + '"';
|
h += ' data-snapshot-id="' + defaultSnap.id + '"';
|
||||||
if (daySnaps.length > 1) {
|
if (daySnaps.length > 1) {
|
||||||
h += ' title="' + daySnaps.length + ' Updates an diesem Tag"';
|
h += ' title="' + daySnaps.length + ' Updates an diesem Tag"';
|
||||||
}
|
}
|
||||||
@@ -251,7 +251,7 @@ var Lagebild = {
|
|||||||
if (isActive) h += '<span class="timeline-dot"></span>';
|
if (isActive) h += '<span class="timeline-dot"></span>';
|
||||||
h += '<span class="timeline-day-num">' + d.getUTCDate() + '</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-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) {
|
if (daySnaps.length > 1) {
|
||||||
h += '<span class="timeline-day-updates">' + daySnaps.length + 'x</span>';
|
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)
|
// Click handler for dropdown snapshot items (delegated, set up once)
|
||||||
var dropdown = document.getElementById('timeline-dropdown');
|
var dropdown = document.getElementById('timeline-dropdown');
|
||||||
dropdown.addEventListener('click', function(e) {
|
dropdown.addEventListener('click', function(e) {
|
||||||
var item = e.target.closest('.timeline-snap-item');
|
var item = e.target.closest('.h-timeline-point');
|
||||||
if (!item) return;
|
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');
|
for (var k = 0; k < items.length; k++) items[k].classList.remove('active');
|
||||||
item.classList.add('active');
|
item.classList.add('active');
|
||||||
|
|
||||||
@@ -327,7 +327,7 @@ var Lagebild = {
|
|||||||
|
|
||||||
// Show dropdown for newest day by default
|
// Show dropdown for newest day by default
|
||||||
var newestDate = dates[dates.length - 1];
|
var newestDate = dates[dates.length - 1];
|
||||||
if (newestDate && groups[newestDate].length > 1) {
|
if (newestDate) {
|
||||||
this.showTimelineDropdown(newestDate, groups[newestDate][0].id);
|
this.showTimelineDropdown(newestDate, groups[newestDate][0].id);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -336,30 +336,39 @@ var Lagebild = {
|
|||||||
var dropdown = document.getElementById('timeline-dropdown');
|
var dropdown = document.getElementById('timeline-dropdown');
|
||||||
var snaps = this.timelineGroups[dateKey];
|
var snaps = this.timelineGroups[dateKey];
|
||||||
|
|
||||||
if (!snaps || snaps.length <= 1) {
|
if (!snaps || snaps.length === 0) {
|
||||||
dropdown.classList.remove('open');
|
dropdown.classList.remove('open');
|
||||||
dropdown.innerHTML = '';
|
dropdown.innerHTML = '';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var d = new Date(dateKey + 'T12:00:00Z');
|
// Oldest left, newest right
|
||||||
var dateLabel = d.toLocaleDateString('de-DE', { day: 'numeric', month: 'long', year: 'numeric', timeZone: 'UTC' });
|
var ordered = snaps.slice().reverse();
|
||||||
|
|
||||||
var h = '<div class="timeline-dropdown-header">' + dateLabel + ' \u2013 ' + snaps.length + ' Updates</div>';
|
var h = '<div class="h-timeline-track">';
|
||||||
h += '<div class="timeline-snap-list">';
|
for (var i = 0; i < ordered.length; i++) {
|
||||||
for (var i = 0; i < snaps.length; i++) {
|
var snap = ordered[i];
|
||||||
var snap = snaps[i];
|
|
||||||
var isActive = (String(snap.id) === String(activeSnapId));
|
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 += ' data-snapshot-id="' + snap.id + '">';
|
||||||
h += '<span class="timeline-snap-time">' + this.fmtTimeOnly(snap.created_at) + ' Uhr</span>';
|
h += '<span class="h-timeline-time">' + this.fmtTimeOnly(snap.created_at) + '</span>';
|
||||||
h += '<span class="timeline-snap-meta">' + snap.article_count + ' Artikel, ' + snap.fact_check_count + ' Faktenchecks</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 += '</button>';
|
||||||
}
|
}
|
||||||
h += '</div>';
|
h += '</div>';
|
||||||
|
|
||||||
dropdown.innerHTML = h;
|
dropdown.innerHTML = h;
|
||||||
dropdown.classList.add('open');
|
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) {
|
toDateKey: function(iso) {
|
||||||
@@ -394,7 +403,7 @@ var Lagebild = {
|
|||||||
sources_json: sj || [],
|
sources_json: sj || [],
|
||||||
updated_at: sd.created_at,
|
updated_at: sd.created_at,
|
||||||
articles: this.data.articles,
|
articles: this.data.articles,
|
||||||
fact_checks: this.data.fact_checks
|
fact_checks: this.getFactChecksAtTime(sd.created_at)
|
||||||
};
|
};
|
||||||
this.allSnapshots[id] = this.currentView;
|
this.allSnapshots[id] = this.currentView;
|
||||||
this.renderCurrentView();
|
this.renderCurrentView();
|
||||||
@@ -410,6 +419,12 @@ var Lagebild = {
|
|||||||
if (document.getElementById('panel-karte').classList.contains('active')) {
|
if (document.getElementById('panel-karte').classList.contains('active')) {
|
||||||
this.renderMap();
|
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 ===== */
|
/* ===== 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 ===== */
|
/* ===== HILFSFUNKTIONEN ===== */
|
||||||
extractDomain: function(url) {
|
extractDomain: function(url) {
|
||||||
if (!url) return null;
|
if (!url) return null;
|
||||||
|
|||||||
In neuem Issue referenzieren
Einen Benutzer sperren