From 4a1ab67703deb8744bad2f4a186db42e28ed635c Mon Sep 17 00:00:00 2001
From: Claude Dev
Date: Mon, 16 Mar 2026 16:37:57 +0100
Subject: [PATCH] Tutorial: Umfangreiche visuelle Korrekturen
Step 17 (Lagebild): Quellenverweise [1]-[5] jetzt in Gold (var(--accent))
wie echte Links im Original.
Step 20 (Faktencheck-Detail): Scrollt beim Betreten ans Ende der
Faktencheck-Liste um auch unbestaetigte/widerlegte Eintraege zu zeigen,
scrollt dann zurueck nach oben.
Step 22 (Timeline): Komplett ueberarbeitete Demo-Timeline mit echtem
Achsen-basiertem Layout (ht-axis, ht-points, ht-day-markers, ht-detail-panel)
statt einfacher Listenansicht. Entspricht dem Original-Rendering.
Step 23 (Karte): Startet jetzt bei Europa-Zoom (Zoom 5), dann animierter
flyTo auf Hamburg (Zoom 13, 2.5s Dauer). Marker und Legende wie bisher.
Step 25 (Resize): Stellt exakte Originalgroesse nach Demo wieder her,
entfernt CSS-Werte erst nach 100ms damit GridStack uebernehmen kann.
Step 27+30 (Bubble-Position): Post-Render-Check verhindert dass Bubbles
unter den Viewport-Rand rutschen, verschiebt sie automatisch nach oben.
Layout: Tutorial erzwingt Standard-Layout beim Start (DEFAULT_LAYOUT),
stellt das vom Nutzer angepasste Layout nach Tutorial-Ende wieder her.
Co-Authored-By: Claude Opus 4.6 (1M context)
---
src/static/dashboard.html | 2 +-
src/static/js/tutorial.js | 140 +++++++++++++++++++++++++++++---------
2 files changed, 109 insertions(+), 33 deletions(-)
diff --git a/src/static/dashboard.html b/src/static/dashboard.html
index 97ebdc8..e0b1c85 100644
--- a/src/static/dashboard.html
+++ b/src/static/dashboard.html
@@ -764,7 +764,7 @@
-
+
diff --git a/src/static/js/tutorial.js b/src/static/js/tutorial.js
index 02d1e21..7ee612a 100644
--- a/src/static/js/tutorial.js
+++ b/src/static/js/tutorial.js
@@ -52,16 +52,16 @@ const Tutorial = {
_DEMO_SUMMARY: 'Am 16. März 2026 kam es im Hamburger Hafen zu einer Explosion '
+ 'im Containerterminal Burchardkai '
- + '[1] '
- + '[2]. '
+ + '[1] '
+ + '[2]. '
+ 'Die Detonation ereignete sich gegen 06:45 Uhr in einem Gefahrgutbereich. '
+ 'Mindestens 12 Personen wurden verletzt '
- + '[3].
'
+ + '[3].
'
+ 'Die Feuerwehr ist mit einem Großaufgebot vor Ort. Der Hafenbetrieb im betroffenen Terminal wurde '
+ 'vorübergehend eingestellt '
- + '[4]. '
+ + '[4]. '
+ 'Die Ursache ist noch unklar '
- + '[5].
',
+ + '[5].',
_DEMO_FACTCHECKS: [
{ status: 'confirmed', icon: '✓', claim: 'Eine Explosion ereignete sich am 16.03.2026 gegen 06:45 Uhr im Hamburger Hafen.', sources: 5 },
@@ -84,6 +84,55 @@ const Tutorial = {
{ time: '09:10', title: 'Gefahrgut-Spezialisten am Einsatzort eingetroffen', source: 'tagesschau.de', type: 'article' },
],
+ _buildDemoTimelineHTML() {
+ // Achsen-basierte Timeline wie im Original
+ var entries = this._DEMO_TIMELINE;
+ var times = ['06:45','07:02','07:15','07:30','08:00','08:22','09:10'];
+ var startMin = 6*60+45;
+ var endMin = 9*60+10;
+ var range = endMin - startMin;
+
+ var html = '';
+ // Datums-Marker
+ html += '
';
+ html += '
';
+ html += '
';
+ // Punkte
+ html += '
';
+ entries.forEach(function(e, i) {
+ var parts = e.time.split(':');
+ var min = parseInt(parts[0])*60 + parseInt(parts[1]);
+ var pos = ((min - startMin) / range) * 92 + 4;
+ var isSnapshot = e.type === 'snapshot';
+ var cls = 'ht-point' + (isSnapshot ? ' ht-snapshot-point' : '') + (i === 0 ? ' active' : '');
+ var size = isSnapshot ? 14 : 10;
+ html += '
';
+ html += '
' + e.time + ': ' + e.title + '
';
+ html += '
';
+ });
+ html += '
';
+ // Achsenlinie
+ html += '
';
+ // Labels
+ html += '
';
+ ['07:00','08:00','09:00'].forEach(function(t) {
+ var parts = t.split(':');
+ var min = parseInt(parts[0])*60;
+ var pos = ((min - startMin) / range) * 92 + 4;
+ html += '
' + t + '
';
+ });
+ html += '
';
+ html += '
';
+ // Detail-Panel fuer aktiven Punkt
+ html += '';
+ var first = entries[0];
+ html += '';
+ html += '
';
+ html += '
' + first.source + '
' + first.title + '
';
+ html += '
';
+ return html;
+ },
+
// -----------------------------------------------------------------------
// Demo-View injizieren / entfernen
// -----------------------------------------------------------------------
@@ -141,6 +190,11 @@ const Tutorial = {
if (typeof LayoutManager !== 'undefined' && !LayoutManager._initialized) {
LayoutManager.init();
}
+ // Aktuelles Layout sichern und Standard-Layout erzwingen
+ if (typeof LayoutManager !== 'undefined' && LayoutManager._grid) {
+ this._savedState.savedLayout = LayoutManager._grid.save(false);
+ LayoutManager._applyLayout(LayoutManager.DEFAULT_LAYOUT);
+ }
// GridStack Resize triggern damit Kacheln korrekt positioniert werden
if (typeof LayoutManager !== 'undefined' && LayoutManager._grid) {
LayoutManager._grid.engine.nodes.forEach(function(n) {
@@ -191,23 +245,13 @@ const Tutorial = {
var sourceStats = document.getElementById('source-overview-header-stats');
if (sourceStats) sourceStats.innerHTML = this._DEMO_SOURCES_STATS;
- // Timeline
+ // Timeline (Achsen-basiert wie im Original)
var timeline = document.getElementById('timeline');
if (timeline) {
- var tlHtml = '';
- this._DEMO_TIMELINE.forEach(function(ev) {
- var cls = ev.type === 'snapshot' ? 'ht-entry ht-snapshot' : 'ht-entry';
- tlHtml += '
'
- + '
' + ev.time + '
'
- + '
'
- + '
'
- + '
' + ev.title + '
'
- + '
' + ev.source + '
'
- + '
';
- });
- tlHtml += '
';
- timeline.innerHTML = tlHtml;
+ timeline.innerHTML = this._buildDemoTimelineHTML();
}
+ var articleCount = document.getElementById('article-count');
+ if (articleCount) articleCount.textContent = '7 Einträge';
// Karte: Stats setzen + Platzhalter anzeigen, echte Map erst im Vollbild-Step
var mapEmpty = document.getElementById('map-empty');
@@ -288,6 +332,11 @@ const Tutorial = {
var ts = document.getElementById('lagebild-timestamp');
if (ts) ts.textContent = '';
+ // Gespeichertes Layout wiederherstellen
+ if (typeof LayoutManager !== 'undefined' && LayoutManager._grid && s.savedLayout) {
+ LayoutManager._applyLayout(s.savedLayout);
+ }
+
this._savedState = null;
},
@@ -825,6 +874,12 @@ const Tutorial = {
+ 'z.B. nur unbestätigte Meldungen anzeigen.',
position: 'left',
onEnter: function() {
+ // Zum nicht-verifizierten Eintrag scrollen
+ var fcList = document.getElementById('factcheck-list');
+ if (fcList) fcList.scrollTo({ top: fcList.scrollHeight, behavior: 'smooth' });
+ Tutorial._stepTimeout(function() {
+ if (fcList) fcList.scrollTo({ top: 0, behavior: 'smooth' });
+ }, 2000);
var item = document.querySelector('.factcheck-item[data-fc-status="confirmed"]');
if (item) item.classList.add('tutorial-sub-highlight');
Tutorial._cleanupFns.push(function() {
@@ -1276,6 +1331,18 @@ const Tutorial = {
// Sichtbar machen
bubble.classList.add('visible');
+
+ // Sicherheitscheck: Bubble nicht unter Viewport-Rand
+ var self = this;
+ requestAnimationFrame(function() {
+ var bRect = bubble.getBoundingClientRect();
+ var vHeight = window.innerHeight;
+ if (bRect.bottom > vHeight - 8) {
+ var shift = bRect.bottom - vHeight + 16;
+ var currentTop = parseFloat(bubble.style.top) || 0;
+ bubble.style.top = (currentTop - shift) + 'px';
+ }
+ });
},
_positionBubble(step) {
@@ -1418,16 +1485,15 @@ const Tutorial = {
var fsStats = document.getElementById('map-fullscreen-stats');
if (!overlay || !fsContainer) return;
- // Overlay anzeigen (\u00fcber Tutorial-Z-Index)
+ // Overlay anzeigen
overlay.classList.add('active');
overlay.style.zIndex = '9002';
-
if (fsStats) fsStats.textContent = '3 Orte / 9 Artikel';
- // Alte Demo-Map entfernen falls vorhanden
+ // Alte Demo-Map entfernen
this._destroyDemoMap();
- // Neue Map im Fullscreen-Container erstellen
+ // Map im Fullscreen-Container
fsContainer.innerHTML = '';
var mapDiv = document.createElement('div');
mapDiv.id = 'tutorial-fs-map';
@@ -1435,10 +1501,11 @@ const Tutorial = {
fsContainer.appendChild(mapDiv);
var isDark = !document.documentElement.getAttribute('data-theme') || document.documentElement.getAttribute('data-theme') !== 'light';
+ // Start weit herausgezoomt (Europa)
this._demoMap = L.map(mapDiv, {
zoomControl: true,
attributionControl: true,
- }).setView([53.545, 9.98], 13);
+ }).setView([51.0, 10.0], 5);
var tileUrl = isDark
? 'https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png'
@@ -1448,7 +1515,7 @@ const Tutorial = {
maxZoom: 19,
}).addTo(this._demoMap);
- // Marker
+ // Marker (aber noch nicht sichtbar bei Zoom 5)
var locations = [
{ lat: 53.5325, lon: 9.9275, name: 'Burchardkai Terminal', articles: 6, cat: 'primary' },
{ lat: 53.5460, lon: 9.9690, name: 'Hamburg Innenstadt', articles: 2, cat: 'secondary' },
@@ -1496,14 +1563,18 @@ const Tutorial = {
legend.addTo(this._demoMap);
this._demoMapLegend = legend;
- // Resize + Start Demo
+ // Resize + animierter Zoom auf Hamburg
var map = this._demoMap;
setTimeout(function() {
if (map) map.invalidateSize();
+ // Sanft auf Hamburg zoomen
setTimeout(function() {
- if (map) map.setView([53.545, 9.98], 13);
- self._simulateMapDemo();
- }, 300);
+ if (map) map.flyTo([53.54, 9.97], 13, { duration: 2.5 });
+ // Nach Zoom: Demo starten
+ setTimeout(function() {
+ self._simulateMapDemo();
+ }, 3000);
+ }, 500);
}, 200);
},
@@ -1960,11 +2031,16 @@ const Tutorial = {
requestAnimationFrame(frame);
});
- // Aufräumen
- tile.style.width = '';
- tile.style.height = '';
+ // Aufräumen - exakte Originalgröße wiederherstellen
+ tile.style.width = origW + 'px';
+ tile.style.height = origH + 'px';
tile.style.transition = '';
tile.style.zIndex = '';
+ // Nach kurzer Verzögerung CSS-Werte entfernen damit GridStack wieder übernimmt
+ setTimeout(function() {
+ tile.style.width = '';
+ tile.style.height = '';
+ }, 100);
this._hideCursor();
await this._wait(200);