Ereignis-Timeline: Überlappungen oben auflösen

Im Top-Bereich der Achse kollidierten Tagesmarker, Themen-Labels und
Lagebericht-Stempel auf der gleichen Höhe. Jetzt klare Schichten:

- Tagesmarker (Heute/Gestern/Datum): top 0
- Themen-Labels: eigene Schiene direkt darunter (top 22 / 42 hourly),
  nicht mehr Kind der Bar — verhindert Wandern bei verschieden hohen
  Bars
- Bars: nach unten verschoben (top 44 / 64 hourly)
- Lagebericht-Linien: gehen jetzt nur durch den Bar-Bereich,
  Pin-Symbol (Cap) hängt UNTEN an der Achsenlinie statt oben in den
  Tagesmarkern
- Heute-Linie: bei stündlicher Granularität ausgeblendet (Tagesmarker
  zeigt eh "Heute, ..."), bei Tag/Woche/Monat weiterhin aktiv

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Dieser Commit ist enthalten in:
2026-05-01 15:10:16 +02:00
Ursprung 370bb94b26
Commit 58eb1298ca
2 geänderte Dateien mit 61 neuen und 35 gelöschten Zeilen

Datei anzeigen

@@ -1182,15 +1182,35 @@ const App = {
});
html += '</div>';
// "Heute"-Linie wenn heute im sichtbaren Bereich liegt
// "Heute"-Linie nur bei taeglicher/woechentlicher/monatlicher Granularitaet sinnvoll —
// bei "hour" zeigt der Tagesmarker eh schon "Heute, ..."
const now = Date.now();
if (rangeEnd > rangeStart && now >= rangeStart && now <= rangeEnd) {
if (!isHourly && rangeEnd > rangeStart && now >= rangeStart && now <= rangeEnd) {
const todayPos = ((now - rangeStart) / (rangeEnd - rangeStart)) * 100;
html += `<div class="ht-today-line" style="left:${todayPos}%;" aria-hidden="true">
<div class="ht-today-label">Heute</div>
</div>`;
}
// Themen-Labels als eigene Schiene (nicht mehr Kind der Bar) — verhindert Ueberlappung mit Datums-Markern
const labelEntries = [];
buckets.forEach((bucket, idx) => {
if (!topActiveIdx.has(idx)) return;
const articleCount = bucket.entries.filter(e => e.kind === 'article').length;
if (articleCount < 2) return;
const kw = this._extractBucketKeyword(bucket);
if (!kw) return;
const pos = this._bucketPositionPercent(bucket, rangeStart, rangeEnd, buckets.length);
labelEntries.push({ pos, text: kw });
});
if (labelEntries.length > 0) {
html += '<div class="ht-bucket-labels">';
labelEntries.forEach(le => {
html += `<div class="ht-bucket-label" style="left:${le.pos}%;">${UI.escape(le.text)}</div>`;
});
html += '</div>';
}
// Saeulen pro Bucket
html += '<div class="ht-bars">';
buckets.forEach((bucket, idx) => {
@@ -1200,8 +1220,6 @@ const App = {
// Bar-Hoehe: Anteil articleCount/articleMax, mindestens 6%, maximal 100%
const heightPct = articleCount === 0 ? 0 : Math.max(6, Math.round((articleCount / articleMax) * 100));
const isActive = this._activePointIndex === idx;
const showLabel = topActiveIdx.has(idx) && articleCount >= 2;
const keyword = showLabel ? this._extractBucketKeyword(bucket) : null;
const top3 = this._topRelevantHeadlines(bucket, 3);
const remaining = bucket.entries.filter(e => e.kind === 'article').length - top3.length;
@@ -1220,10 +1238,6 @@ const App = {
if (snapshotCount > 0) {
html += `<div class="ht-bar-snap-cap" aria-hidden="true"></div>`;
}
// Themen-Label fuer aktivste Buckets
if (keyword) {
html += `<div class="ht-bucket-label">${UI.escape(keyword)}</div>`;
}
// Hover-Vorschaukarte
html += `<div class="ht-hover-card">
<div class="ht-hover-card-head">${UI.escape(bucket.label)} &middot; ${articleCount + snapshotCount} Eintr${(articleCount + snapshotCount) === 1 ? 'ag' : 'äge'}</div>`;