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 += '
16. Mär.
'; + 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 += '
16.03.2026, ' + first.time + '1 Eintrag
'; + 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);