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/network-graph.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>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 '
|
||||
+ 'im Containerterminal Burchardkai '
|
||||
+ '<span class="source-ref" data-index="1" title="dpa">[1]</span> '
|
||||
+ '<span class="source-ref" data-index="2" title="Reuters">[2]</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" style="color:var(--accent);cursor:pointer;">[2]</span>. '
|
||||
+ 'Die Detonation ereignete sich gegen 06:45 Uhr in einem Gefahrgutbereich. '
|
||||
+ '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 '
|
||||
+ '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 '
|
||||
+ '<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: [
|
||||
{ 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 = '<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
|
||||
// -----------------------------------------------------------------------
|
||||
@@ -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 = '<div class="ht-timeline">';
|
||||
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;
|
||||
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.flyTo([53.54, 9.97], 13, { duration: 2.5 });
|
||||
// Nach Zoom: Demo starten
|
||||
setTimeout(function() {
|
||||
if (map) map.setView([53.545, 9.98], 13);
|
||||
self._simulateMapDemo();
|
||||
}, 300);
|
||||
}, 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);
|
||||
|
||||
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren