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) <noreply@anthropic.com>
Dieser Commit ist enthalten in:
@@ -764,7 +764,7 @@
|
|||||||
<script src="/static/js/api_network.js?v=20260316a"></script>
|
<script src="/static/js/api_network.js?v=20260316a"></script>
|
||||||
<script src="/static/js/network-graph.js?v=20260316a"></script>
|
<script src="/static/js/network-graph.js?v=20260316a"></script>
|
||||||
<script src="/static/js/app_network.js?v=20260316a"></script>
|
<script src="/static/js/app_network.js?v=20260316a"></script>
|
||||||
<script src="/static/js/tutorial.js?v=20260316n"></script>
|
<script src="/static/js/tutorial.js?v=20260316o"></script>
|
||||||
<script src="/static/js/chat.js?v=20260316f"></script>
|
<script src="/static/js/chat.js?v=20260316f"></script>
|
||||||
<script>document.addEventListener("DOMContentLoaded",function(){Chat.init();Tutorial.init()});</script>
|
<script>document.addEventListener("DOMContentLoaded",function(){Chat.init();Tutorial.init()});</script>
|
||||||
|
|
||||||
|
|||||||
@@ -52,16 +52,16 @@ const Tutorial = {
|
|||||||
|
|
||||||
_DEMO_SUMMARY: '<p>Am 16. März 2026 kam es im Hamburger Hafen zu einer Explosion '
|
_DEMO_SUMMARY: '<p>Am 16. März 2026 kam es im Hamburger Hafen zu einer Explosion '
|
||||||
+ 'im Containerterminal Burchardkai '
|
+ 'im Containerterminal Burchardkai '
|
||||||
+ '<span class="source-ref" data-index="1" title="dpa">[1]</span> '
|
+ '<span class="source-ref" data-index="1" title="dpa" style="color:var(--accent);cursor:pointer;">[1]</span> '
|
||||||
+ '<span class="source-ref" data-index="2" title="Reuters">[2]</span>. '
|
+ '<span class="source-ref" data-index="2" title="Reuters" style="color:var(--accent);cursor:pointer;">[2]</span>. '
|
||||||
+ 'Die Detonation ereignete sich gegen 06:45 Uhr in einem Gefahrgutbereich. '
|
+ 'Die Detonation ereignete sich gegen 06:45 Uhr in einem Gefahrgutbereich. '
|
||||||
+ 'Mindestens 12 Personen wurden verletzt '
|
+ 'Mindestens 12 Personen wurden verletzt '
|
||||||
+ '<span class="source-ref" data-index="3" title="NDR">[3]</span>.</p>'
|
+ '<span class="source-ref" data-index="3" title="NDR" style="color:var(--accent);cursor:pointer;">[3]</span>.</p>'
|
||||||
+ '<p>Die Feuerwehr ist mit einem Großaufgebot vor Ort. Der Hafenbetrieb im betroffenen Terminal wurde '
|
+ '<p>Die Feuerwehr ist mit einem Großaufgebot vor Ort. Der Hafenbetrieb im betroffenen Terminal wurde '
|
||||||
+ 'vorübergehend eingestellt '
|
+ 'vorübergehend eingestellt '
|
||||||
+ '<span class="source-ref" data-index="4" title="HPA">[4]</span>. '
|
+ '<span class="source-ref" data-index="4" title="HPA" style="color:var(--accent);cursor:pointer;">[4]</span>. '
|
||||||
+ 'Die Ursache ist noch unklar '
|
+ 'Die Ursache ist noch unklar '
|
||||||
+ '<span class="source-ref" data-index="5" title="Hamburger Abendblatt">[5]</span>.</p>',
|
+ '<span class="source-ref" data-index="5" title="Hamburger Abendblatt" style="color:var(--accent);cursor:pointer;">[5]</span>.</p>',
|
||||||
|
|
||||||
_DEMO_FACTCHECKS: [
|
_DEMO_FACTCHECKS: [
|
||||||
{ status: 'confirmed', icon: '✓', claim: 'Eine Explosion ereignete sich am 16.03.2026 gegen 06:45 Uhr im Hamburger Hafen.', sources: 5 },
|
{ 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' },
|
{ 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 = '<div class="ht-axis">';
|
||||||
|
// Datums-Marker
|
||||||
|
html += '<div class="ht-day-markers">';
|
||||||
|
html += '<div class="ht-day-marker" style="left:0%;"><div class="ht-day-marker-label">16. Mär.</div><div class="ht-day-marker-line"></div></div>';
|
||||||
|
html += '</div>';
|
||||||
|
// Punkte
|
||||||
|
html += '<div class="ht-points">';
|
||||||
|
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 += '<div class="' + cls + '" style="left:' + pos + '%;width:' + size + 'px;height:' + size + 'px;" data-idx="' + i + '">';
|
||||||
|
html += '<div class="ht-tooltip">' + e.time + ': ' + e.title + '</div>';
|
||||||
|
html += '</div>';
|
||||||
|
});
|
||||||
|
html += '</div>';
|
||||||
|
// Achsenlinie
|
||||||
|
html += '<div class="ht-axis-line"></div>';
|
||||||
|
// Labels
|
||||||
|
html += '<div class="ht-axis-labels">';
|
||||||
|
['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 += '<div class="ht-axis-label" style="left:' + pos + '%;">' + t + '</div>';
|
||||||
|
});
|
||||||
|
html += '</div>';
|
||||||
|
html += '</div>';
|
||||||
|
// Detail-Panel fuer aktiven Punkt
|
||||||
|
html += '<div class="ht-detail-panel">';
|
||||||
|
var first = entries[0];
|
||||||
|
html += '<div class="ht-detail-header"><span class="ht-detail-time">16.03.2026, ' + first.time + '</span><span class="ht-detail-count">1 Eintrag</span></div>';
|
||||||
|
html += '<div class="ht-detail-entries">';
|
||||||
|
html += '<div class="ht-detail-entry"><div class="ht-detail-entry-source">' + first.source + '</div><div class="ht-detail-entry-title">' + first.title + '</div></div>';
|
||||||
|
html += '</div></div>';
|
||||||
|
return html;
|
||||||
|
},
|
||||||
|
|
||||||
// -----------------------------------------------------------------------
|
// -----------------------------------------------------------------------
|
||||||
// Demo-View injizieren / entfernen
|
// Demo-View injizieren / entfernen
|
||||||
// -----------------------------------------------------------------------
|
// -----------------------------------------------------------------------
|
||||||
@@ -141,6 +190,11 @@ const Tutorial = {
|
|||||||
if (typeof LayoutManager !== 'undefined' && !LayoutManager._initialized) {
|
if (typeof LayoutManager !== 'undefined' && !LayoutManager._initialized) {
|
||||||
LayoutManager.init();
|
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
|
// GridStack Resize triggern damit Kacheln korrekt positioniert werden
|
||||||
if (typeof LayoutManager !== 'undefined' && LayoutManager._grid) {
|
if (typeof LayoutManager !== 'undefined' && LayoutManager._grid) {
|
||||||
LayoutManager._grid.engine.nodes.forEach(function(n) {
|
LayoutManager._grid.engine.nodes.forEach(function(n) {
|
||||||
@@ -191,23 +245,13 @@ const Tutorial = {
|
|||||||
var sourceStats = document.getElementById('source-overview-header-stats');
|
var sourceStats = document.getElementById('source-overview-header-stats');
|
||||||
if (sourceStats) sourceStats.innerHTML = this._DEMO_SOURCES_STATS;
|
if (sourceStats) sourceStats.innerHTML = this._DEMO_SOURCES_STATS;
|
||||||
|
|
||||||
// Timeline
|
// Timeline (Achsen-basiert wie im Original)
|
||||||
var timeline = document.getElementById('timeline');
|
var timeline = document.getElementById('timeline');
|
||||||
if (timeline) {
|
if (timeline) {
|
||||||
var tlHtml = '<div class="ht-timeline">';
|
timeline.innerHTML = this._buildDemoTimelineHTML();
|
||||||
this._DEMO_TIMELINE.forEach(function(ev) {
|
|
||||||
var cls = ev.type === 'snapshot' ? 'ht-entry ht-snapshot' : 'ht-entry';
|
|
||||||
tlHtml += '<div class="' + cls + '">'
|
|
||||||
+ '<div class="ht-time">' + ev.time + '</div>'
|
|
||||||
+ '<div class="ht-dot"></div>'
|
|
||||||
+ '<div class="ht-content">'
|
|
||||||
+ '<div class="ht-title">' + ev.title + '</div>'
|
|
||||||
+ '<div class="ht-source">' + ev.source + '</div>'
|
|
||||||
+ '</div></div>';
|
|
||||||
});
|
|
||||||
tlHtml += '</div>';
|
|
||||||
timeline.innerHTML = tlHtml;
|
|
||||||
}
|
}
|
||||||
|
var articleCount = document.getElementById('article-count');
|
||||||
|
if (articleCount) articleCount.textContent = '7 Einträge';
|
||||||
|
|
||||||
// Karte: Stats setzen + Platzhalter anzeigen, echte Map erst im Vollbild-Step
|
// Karte: Stats setzen + Platzhalter anzeigen, echte Map erst im Vollbild-Step
|
||||||
var mapEmpty = document.getElementById('map-empty');
|
var mapEmpty = document.getElementById('map-empty');
|
||||||
@@ -288,6 +332,11 @@ const Tutorial = {
|
|||||||
var ts = document.getElementById('lagebild-timestamp');
|
var ts = document.getElementById('lagebild-timestamp');
|
||||||
if (ts) ts.textContent = '';
|
if (ts) ts.textContent = '';
|
||||||
|
|
||||||
|
// Gespeichertes Layout wiederherstellen
|
||||||
|
if (typeof LayoutManager !== 'undefined' && LayoutManager._grid && s.savedLayout) {
|
||||||
|
LayoutManager._applyLayout(s.savedLayout);
|
||||||
|
}
|
||||||
|
|
||||||
this._savedState = null;
|
this._savedState = null;
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -825,6 +874,12 @@ const Tutorial = {
|
|||||||
+ 'z.B. nur unbestätigte Meldungen anzeigen.',
|
+ 'z.B. nur unbestätigte Meldungen anzeigen.',
|
||||||
position: 'left',
|
position: 'left',
|
||||||
onEnter: function() {
|
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"]');
|
var item = document.querySelector('.factcheck-item[data-fc-status="confirmed"]');
|
||||||
if (item) item.classList.add('tutorial-sub-highlight');
|
if (item) item.classList.add('tutorial-sub-highlight');
|
||||||
Tutorial._cleanupFns.push(function() {
|
Tutorial._cleanupFns.push(function() {
|
||||||
@@ -1276,6 +1331,18 @@ const Tutorial = {
|
|||||||
|
|
||||||
// Sichtbar machen
|
// Sichtbar machen
|
||||||
bubble.classList.add('visible');
|
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) {
|
_positionBubble(step) {
|
||||||
@@ -1418,16 +1485,15 @@ const Tutorial = {
|
|||||||
var fsStats = document.getElementById('map-fullscreen-stats');
|
var fsStats = document.getElementById('map-fullscreen-stats');
|
||||||
if (!overlay || !fsContainer) return;
|
if (!overlay || !fsContainer) return;
|
||||||
|
|
||||||
// Overlay anzeigen (\u00fcber Tutorial-Z-Index)
|
// Overlay anzeigen
|
||||||
overlay.classList.add('active');
|
overlay.classList.add('active');
|
||||||
overlay.style.zIndex = '9002';
|
overlay.style.zIndex = '9002';
|
||||||
|
|
||||||
if (fsStats) fsStats.textContent = '3 Orte / 9 Artikel';
|
if (fsStats) fsStats.textContent = '3 Orte / 9 Artikel';
|
||||||
|
|
||||||
// Alte Demo-Map entfernen falls vorhanden
|
// Alte Demo-Map entfernen
|
||||||
this._destroyDemoMap();
|
this._destroyDemoMap();
|
||||||
|
|
||||||
// Neue Map im Fullscreen-Container erstellen
|
// Map im Fullscreen-Container
|
||||||
fsContainer.innerHTML = '';
|
fsContainer.innerHTML = '';
|
||||||
var mapDiv = document.createElement('div');
|
var mapDiv = document.createElement('div');
|
||||||
mapDiv.id = 'tutorial-fs-map';
|
mapDiv.id = 'tutorial-fs-map';
|
||||||
@@ -1435,10 +1501,11 @@ const Tutorial = {
|
|||||||
fsContainer.appendChild(mapDiv);
|
fsContainer.appendChild(mapDiv);
|
||||||
|
|
||||||
var isDark = !document.documentElement.getAttribute('data-theme') || document.documentElement.getAttribute('data-theme') !== 'light';
|
var isDark = !document.documentElement.getAttribute('data-theme') || document.documentElement.getAttribute('data-theme') !== 'light';
|
||||||
|
// Start weit herausgezoomt (Europa)
|
||||||
this._demoMap = L.map(mapDiv, {
|
this._demoMap = L.map(mapDiv, {
|
||||||
zoomControl: true,
|
zoomControl: true,
|
||||||
attributionControl: true,
|
attributionControl: true,
|
||||||
}).setView([53.545, 9.98], 13);
|
}).setView([51.0, 10.0], 5);
|
||||||
|
|
||||||
var tileUrl = isDark
|
var tileUrl = isDark
|
||||||
? 'https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png'
|
? 'https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png'
|
||||||
@@ -1448,7 +1515,7 @@ const Tutorial = {
|
|||||||
maxZoom: 19,
|
maxZoom: 19,
|
||||||
}).addTo(this._demoMap);
|
}).addTo(this._demoMap);
|
||||||
|
|
||||||
// Marker
|
// Marker (aber noch nicht sichtbar bei Zoom 5)
|
||||||
var locations = [
|
var locations = [
|
||||||
{ lat: 53.5325, lon: 9.9275, name: 'Burchardkai Terminal', articles: 6, cat: 'primary' },
|
{ 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' },
|
{ lat: 53.5460, lon: 9.9690, name: 'Hamburg Innenstadt', articles: 2, cat: 'secondary' },
|
||||||
@@ -1496,14 +1563,18 @@ const Tutorial = {
|
|||||||
legend.addTo(this._demoMap);
|
legend.addTo(this._demoMap);
|
||||||
this._demoMapLegend = legend;
|
this._demoMapLegend = legend;
|
||||||
|
|
||||||
// Resize + Start Demo
|
// Resize + animierter Zoom auf Hamburg
|
||||||
var map = this._demoMap;
|
var map = this._demoMap;
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
if (map) map.invalidateSize();
|
if (map) map.invalidateSize();
|
||||||
|
// Sanft auf Hamburg zoomen
|
||||||
|
setTimeout(function() {
|
||||||
|
if (map) map.flyTo([53.54, 9.97], 13, { duration: 2.5 });
|
||||||
|
// Nach Zoom: Demo starten
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
if (map) map.setView([53.545, 9.98], 13);
|
|
||||||
self._simulateMapDemo();
|
self._simulateMapDemo();
|
||||||
}, 300);
|
}, 3000);
|
||||||
|
}, 500);
|
||||||
}, 200);
|
}, 200);
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -1960,11 +2031,16 @@ const Tutorial = {
|
|||||||
requestAnimationFrame(frame);
|
requestAnimationFrame(frame);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Aufräumen
|
// Aufräumen - exakte Originalgröße wiederherstellen
|
||||||
tile.style.width = '';
|
tile.style.width = origW + 'px';
|
||||||
tile.style.height = '';
|
tile.style.height = origH + 'px';
|
||||||
tile.style.transition = '';
|
tile.style.transition = '';
|
||||||
tile.style.zIndex = '';
|
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();
|
this._hideCursor();
|
||||||
await this._wait(200);
|
await this._wait(200);
|
||||||
|
|
||||||
|
|||||||
In neuem Issue referenzieren
Einen Benutzer sperren