From ffb8dddc4fa6717c64c8a8bf3d266160ea24c8de Mon Sep 17 00:00:00 2001 From: UserIsMH Date: Fri, 1 May 2026 15:38:09 +0200 Subject: [PATCH] Ereignis-Timeline: Snapshot-zentriertes Konzept MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Komplette Neufassung der horizontalen Timeline. Lageberichte sind die natürlichen Anker einer OSINT-Lage; Artikel werden um sie herum gruppiert. Aufbau: - Quanti-Strip oben: schmale Heatmap-Reihe (ein Quadrat pro Stunde/Tag/ Woche/Monat je nach Spannweite), Farbintensität = Aktivität. Quadrate mit Lagebericht haben goldene Unterkante. Klick auf Quadrat öffnet Detail-Panel mit allen Meldungen des Zeitfensters. - Lagebericht-Kette darunter: jede Karte zeigt Datum, Vorschau-Text aus dem Snapshot, Anzahl Meldungen + Fakten. Karten sind durch Stränge verbunden, die "X Meldungen"-Pille tragen — Klick auf Strang öffnet Liste der Meldungen zwischen den beiden Lageberichten. - "Aktuell"-Marker am rechten Ende mit pulsierendem Pin. Filter: - Alle: Strip + Kette - Meldungen: Strip + vertikaler Stream - Lageberichte: nur Karten ohne Strip/Stränge Edge-Case: Lagen ohne Lagebericht zeigen Strip + Stream als Fallback. Mobile (<900px): Kette stapelt vertikal, Strip bleibt horizontal. Alte Bar-Achse, Punkte, Bucket-Merge, Day-Markers etc. komplett entfernt — die alte Achse war für sporadische OSINT-Aktivität das falsche Pattern. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/static/css/style.css | 427 ++++++++++++++-------------- src/static/dashboard.html | 4 +- src/static/js/app.js | 567 ++++++++++++++++++++++---------------- 3 files changed, 563 insertions(+), 435 deletions(-) diff --git a/src/static/css/style.css b/src/static/css/style.css index d944697..ee8167b 100644 --- a/src/static/css/style.css +++ b/src/static/css/style.css @@ -2450,213 +2450,215 @@ a.dev-source-pill:hover { padding: 12px 20px 8px; } -/* Achsen-Container */ -.ht-axis { - position: relative; - height: 110px; +/* === Snapshot-zentrierte Timeline === */ +.ht-tl { + display: flex; + flex-direction: column; + gap: var(--sp-xl); } -/* Stündliches Layout: höher wegen Datums-Markern oben */ -.ht-axis--hourly { - height: 130px; +.ht-tl-hint { + font-size: 12px; + color: var(--text-tertiary); + font-style: italic; + padding: 4px 0; } -/* Punkte-Bereich (über der Linie) */ -.ht-points { - position: absolute; - left: 4%; - right: 4%; - top: 0; - height: 56px; +/* Quanti-Strip (Heatmap-Zeile) */ +.ht-strip { + display: flex; + flex-direction: column; + gap: 4px; } - -.ht-axis--hourly .ht-points { - top: 20px; +.ht-strip-cells { + display: grid; + grid-auto-flow: column; + grid-auto-columns: minmax(8px, 1fr); + gap: 2px; + height: 18px; } - -/* Achsenlinie */ -.ht-axis-line { - position: absolute; - left: 2%; - right: 2%; - top: 60px; - height: 2px; +.ht-strip-cell { + background: color-mix(in srgb, var(--accent) calc(var(--intensity) * 70%), var(--border)); + border-radius: 2px; + cursor: pointer; + transition: transform 0.15s ease, box-shadow 0.15s ease; + min-height: 16px; +} +.ht-strip-cell.empty { background: var(--border); + opacity: 0.4; + cursor: default; +} +.ht-strip-cell:hover:not(.empty) { + transform: scaleY(1.4); + box-shadow: var(--glow-accent); +} +.ht-strip-cell.has-snapshot { + box-shadow: inset 0 -2px 0 var(--accent); +} +.ht-strip-labels { + display: grid; + gap: 2px; + font-size: 9px; + font-family: var(--font-mono); + color: var(--text-tertiary); +} +.ht-strip-label { + text-align: left; + white-space: nowrap; } -.ht-axis--hourly .ht-axis-line { - top: 80px; +/* Lagebericht-Kette */ +.ht-chain-scroll { + overflow-x: auto; + overflow-y: visible; + padding: 4px 0 8px; +} +.ht-chain { + display: flex; + align-items: stretch; + gap: 0; + min-width: max-content; } -/* Datums-Marker (vertikale Linie + Datum oben, nur bei Stunden-Granularität) */ -.ht-day-markers { - position: absolute; - left: 4%; - right: 4%; - top: 0; - bottom: 0; - pointer-events: none; +/* Lagebericht-Karte */ +.ht-card { + flex: 0 0 220px; + padding: 12px; + border: 1px solid var(--border); + border-radius: var(--radius-lg); + background: var(--bg-elevated); + cursor: pointer; + display: flex; + flex-direction: column; + gap: 4px; + transition: border-color 0.15s ease, transform 0.15s ease, box-shadow 0.15s ease; + outline: none; + position: relative; } - -.ht-day-marker { - position: absolute; - top: 0; -} - -.ht-day-marker-label { +.ht-card::before { + content: ''; position: absolute; top: 0; left: 0; - transform: translateX(-50%); - font-size: 10px; + right: 0; + height: 3px; + background: var(--accent); + border-radius: var(--radius-lg) var(--radius-lg) 0 0; +} +.ht-card:hover { + border-color: var(--accent); + transform: translateY(-1px); + box-shadow: var(--shadow-sm); +} +.ht-card:focus-visible { box-shadow: 0 0 0 3px var(--tint-accent-strong); } +.ht-card.active { + border-color: var(--accent); + box-shadow: var(--glow-accent-strong); +} +.ht-card-icon { + color: var(--accent); + margin-bottom: 2px; +} +.ht-card-date { + font-size: 11px; font-family: var(--font-mono); font-weight: 600; color: var(--accent); - white-space: nowrap; } - -.ht-day-marker-line { - position: absolute; - top: 14px; - height: 66px; - width: 1px; - left: 0; - background: var(--accent); - opacity: 0.2; -} - -/* Punkt (Basis) */ -.ht-point { - position: absolute; - bottom: 0; - transform: translateX(-50%); - border-radius: 50%; - background: var(--text-disabled); - border: 2px solid var(--bg-card); - cursor: pointer; - transition: all 0.2s ease; - z-index: 2; -} - -.ht-point:hover { - box-shadow: var(--glow-accent); - z-index: 4; -} - -.ht-point.active { - box-shadow: var(--glow-accent-strong); - z-index: 4; -} - -/* Dimmen: nicht-aktive Punkte verblassen wenn ein Punkt aktiv ist */ -.ht-points:has(.ht-point.active) .ht-point:not(.active) { - opacity: 0.3; - transition: opacity 0.3s ease; -} - -/* Pfeil über dem aktiven Punkt */ -.ht-point.active::after { - content: '▼'; - position: absolute; - bottom: calc(100% + 2px); - left: 50%; - transform: translateX(-50%); - font-size: 10px; - color: var(--accent); - pointer-events: none; - line-height: 1; -} - -/* Snapshot-Punkt (Raute) */ -.ht-point.ht-snapshot-point { - border-radius: 2px; - transform: translateX(-50%) rotate(45deg); - background: var(--accent); - box-shadow: var(--glow-accent); -} - -.ht-point.ht-snapshot-point .ht-tooltip, -.ht-point.ht-snapshot-point .ht-point-count { - transform: rotate(-45deg); -} - -.ht-point.ht-snapshot-point .ht-tooltip { - transform: rotate(-45deg) translateX(-50%); - transform-origin: bottom left; -} - -/* Gemischter Punkt (Gold-Kreis) */ -.ht-point.ht-mixed-point { - background: var(--accent); - border: 2px solid var(--bg-card); -} - -/* Tooltip (über dem Punkt) */ -.ht-tooltip { - position: absolute; - bottom: calc(100% + 6px); - left: 50%; - transform: translateX(-50%); - background: var(--bg-secondary); +.ht-card-preview { + font-size: 13px; color: var(--text-primary); - font-size: 11px; - padding: 3px 8px; - border-radius: var(--radius); - white-space: nowrap; - pointer-events: none; - opacity: 0; - visibility: hidden; - transition: opacity 0.15s ease, visibility 0.15s ease; - border: 1px solid var(--border); - z-index: 10; + line-height: 1.4; + overflow: hidden; + display: -webkit-box; + -webkit-line-clamp: 3; + -webkit-box-orient: vertical; + flex: 1; } - -.ht-point:hover .ht-tooltip { - opacity: 1; - visibility: visible; -} - -/* Zahl unter dem Punkt */ -.ht-point-count { - position: absolute; - top: calc(100% + 6px); - left: 50%; - transform: translateX(-50%); +.ht-card-meta { font-size: 10px; - font-family: var(--font-mono); - color: var(--text-disabled); - white-space: nowrap; - pointer-events: none; -} - -.ht-point.active .ht-point-count, -.ht-point:hover .ht-point-count { - color: var(--accent); -} - -/* Achsen-Labels (unter der Linie) */ -.ht-axis-labels { - position: absolute; - left: 4%; - right: 4%; - top: 72px; - height: 20px; -} - -.ht-axis--hourly .ht-axis-labels { - top: 90px; -} - -.ht-axis-label { - position: absolute; - transform: translateX(-50%); - font-size: 10px; - font-family: var(--font-mono); color: var(--text-tertiary); - white-space: nowrap; + font-family: var(--font-mono); + margin-top: 4px; } -/* Leerer Zustand */ +/* Verbindungs-Strang zwischen Karten */ +.ht-link { + flex: 0 1 auto; + min-width: 80px; + display: flex; + align-items: center; + justify-content: center; + gap: 6px; + padding: 0 8px; + cursor: pointer; + outline: none; + color: var(--text-secondary); + transition: color 0.15s ease; +} +.ht-link:hover { color: var(--accent); } +.ht-link:focus-visible { color: var(--accent); } +.ht-link-line { + flex: 1; + height: 2px; + background: var(--border); + border-radius: 1px; + min-width: 12px; + transition: background 0.15s ease; +} +.ht-link:hover .ht-link-line { background: var(--accent); } +.ht-link-count { + font-size: 11px; + font-family: var(--font-mono); + font-weight: 600; + white-space: nowrap; + background: var(--bg-card); + padding: 2px 8px; + border-radius: 10px; + border: 1px solid var(--border); + transition: border-color 0.15s ease; +} +.ht-link:hover .ht-link-count { border-color: var(--accent); color: var(--accent); } +.ht-link--quiet .ht-link-line { background: var(--border); opacity: 0.5; } + +/* "Aktuell"-Marker am Ende */ +.ht-now { + flex: 0 0 auto; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 6px; + padding: 0 12px; + min-width: 80px; +} +.ht-now-pulse { + width: 12px; + height: 12px; + border-radius: 50%; + background: var(--accent); + box-shadow: 0 0 0 0 var(--accent); + animation: ht-pulse 2s ease-out infinite; +} +@keyframes ht-pulse { + 0% { box-shadow: 0 0 0 0 rgba(150, 121, 26, 0.4); } + 70% { box-shadow: 0 0 0 12px rgba(150, 121, 26, 0); } + 100% { box-shadow: 0 0 0 0 rgba(150, 121, 26, 0); } +} +.ht-now-label { + font-size: 10px; + font-family: var(--font-mono); + font-weight: 700; + color: var(--accent); + text-transform: uppercase; + letter-spacing: 0.5px; +} + +/* Stream / Empty */ +.ht-stream { + margin-top: 8px; +} .ht-empty { padding: 20px; text-align: center; @@ -2664,20 +2666,18 @@ a.dev-source-pill:hover { color: var(--text-tertiary); } -/* Detail-Panel */ -.ht-detail-panel { - margin-top: 8px; +/* Detail-Panel (Zeitfenster oder Snapshot) */ +.ht-detail { + margin-top: 4px; border: 1px solid var(--border); border-radius: var(--radius); background: var(--bg-secondary); animation: ht-slide-down 0.2s ease; } - @keyframes ht-slide-down { from { opacity: 0; transform: translateY(-8px); } - to { opacity: 1; transform: translateY(0); } + to { opacity: 1; transform: translateY(0); } } - .ht-detail-header { display: flex; justify-content: space-between; @@ -2685,14 +2685,12 @@ a.dev-source-pill:hover { padding: 8px 12px; border-bottom: 1px solid var(--border); } - .ht-detail-title { font-size: 12px; font-weight: 600; color: var(--accent); font-family: var(--font-mono); } - .ht-detail-close { background: none; border: none; @@ -2702,21 +2700,46 @@ a.dev-source-pill:hover { padding: 0 4px; line-height: 1; } - -.ht-detail-close:hover { - color: var(--text-primary); -} - -.ht-detail-content { - max-height: 350px; +.ht-detail-close:hover { color: var(--text-primary); } +.ht-detail-body { + max-height: 480px; overflow-y: auto; - padding: 4px 12px; + padding: 8px 14px; +} +.ht-detail-body::-webkit-scrollbar { width: 6px; } +.ht-detail-body::-webkit-scrollbar-track { background: var(--bg-primary); border-radius: 3px; } +.ht-detail-body::-webkit-scrollbar-thumb { background: var(--text-disabled); border-radius: 3px; } +.ht-detail-body::-webkit-scrollbar-thumb:hover { background: var(--text-secondary); } + +/* Mobile: Kette vertikal stapeln, Strip bleibt horizontal */ +@media (max-width: 900px) { + .ht-chain { + flex-direction: column; + align-items: stretch; + min-width: auto; + } + .ht-card { flex: 0 0 auto; width: 100%; } + .ht-link { + min-width: auto; + width: 100%; + flex-direction: column; + gap: 4px; + padding: 8px 0; + } + .ht-link-line { + width: 2px; + height: 14px; + flex: 0 0 auto; + } + .ht-now { width: 100%; } } -.ht-detail-content::-webkit-scrollbar { width: 6px; } -.ht-detail-content::-webkit-scrollbar-track { background: var(--bg-primary); border-radius: 3px; } -.ht-detail-content::-webkit-scrollbar-thumb { background: var(--text-disabled); border-radius: 3px; } -.ht-detail-content::-webkit-scrollbar-thumb:hover { background: var(--text-secondary); } +/* Reduced Motion */ +@media (prefers-reduced-motion: reduce) { + .ht-now-pulse { animation: none; } + .ht-detail { animation: none; } + .ht-card { transition: none; } +} /* === Briefing Listen === */ .briefing-content ul { diff --git a/src/static/dashboard.html b/src/static/dashboard.html index 0c9e2e5..05004db 100644 --- a/src/static/dashboard.html +++ b/src/static/dashboard.html @@ -13,7 +13,7 @@ - +