Tutorial: Umfassende Verbesserungen an Schritten 5,8+,11,20-26,28,31,32

- Schritt 5: Simuliertes Dropdown statt einfachem Wechsel
- Ab Schritt 8: Verbessertes Modal-Scrolling fuer alle Formularfelder
- Schritt 11: Cursor-Demo zwischen Alle/Eigene Filtern
- Schritt 20: Status-Durchlauf statt Scroll-Sprung
- Schritt 21: Quellenübersicht nach Timeline/Karte verschoben
- Schritt 22: Timeline mit Cursor-Navigation durch Zeitpunkte
- Schritt 23: Cursor zeigt Orte einlesen + Vollbild Buttons
- Schritt 24: Z-Index Fix fuer Bubble ueber Vollbild-Karte
- Schritt 25/26: Kombinierte Drag+Resize Demo
- Schritt 28/31: Position hoeher (position:top statt right/left)
- Schritt 32: Bubble tiefer zentriert (55% statt 50%)
- 6 neue Simulationsfunktionen hinzugefuegt
Dieser Commit ist enthalten in:
Claude Dev
2026-03-23 21:23:07 +01:00
Ursprung 6b4af4cf2a
Commit 584183951f

Datei anzeigen

@@ -731,16 +731,24 @@ const Tutorial = {
Tutorial._clearSubHighlights(); Tutorial._clearSubHighlights();
}, },
}, },
// 5 - Sidebar Filter // Sidebar Filter
{ {
id: 'sidebar-filters', id: 'sidebar-filters',
target: '.sidebar-filter', target: '.sidebar-filter',
title: 'Lagen filtern', title: 'Lagen filtern',
text: 'Mit diesen Filtern steuern Sie, welche Lagen angezeigt werden:<br><br>' text: 'Mit diesen Filtern steuern Sie, welche Lagen angezeigt werden:<br><br>'
+ '<strong>Alle</strong> - Zeigt sämtliche Lagen Ihrer Organisation<br>' + '<strong>Alle</strong> - Zeigt s\u00e4mtliche Lagen Ihrer Organisation<br>'
+ '<strong>Eigene</strong> - Nur Lagen, die Sie selbst erstellt haben<br><br>' + '<strong>Eigene</strong> - Nur Lagen, die Sie selbst erstellt haben<br><br>'
+ 'Bei vielen Lagen hilft dies, den Überblick zu behalten.', + 'Bei vielen Lagen hilft dies, den \u00dcberblick zu behalten.',
position: 'right', position: 'right',
disableNav: true,
onEnter: function() {
Tutorial._runDemo(Tutorial._simulateFilterSwitch);
},
onExit: function() {
Tutorial._clearSubHighlights();
Tutorial._hideCursor();
},
}, },
// 6 - Demo-Lage einführen // 6 - Demo-Lage einführen
{ {
@@ -872,33 +880,97 @@ const Tutorial = {
Tutorial._clearSubHighlights(); Tutorial._clearSubHighlights();
}, },
}, },
// 14 - Faktencheck: Einzelner Eintrag // Faktencheck: Status-Demo
{ {
id: 'faktencheck-detail', id: 'faktencheck-detail',
target: '.factcheck-item[data-fc-status="confirmed"]', target: '.factcheck-item[data-fc-status="confirmed"]',
title: 'Faktencheck-Eintrag', title: 'Faktencheck-Eintrag',
text: 'Jeder Faktencheck-Eintrag besteht aus:<br><br>' text: 'Jeder Faktencheck-Eintrag besteht aus:<br><br>'
+ '<strong>Status-Symbol</strong> - Farbcodiert für schnelle Einordnung (links)<br>' + '<strong>Status-Symbol</strong> - Farbcodiert f\u00fcr schnelle Einordnung<br>'
+ '<strong>Behauptung</strong> - Die geprüfte Aussage<br>' + '<strong>Behauptung</strong> - Die gepr\u00fcfte Aussage<br>'
+ '<strong>Quellenanzahl</strong> - Wie viele Quellen diese Behauptung stützen<br><br>' + '<strong>Quellenanzahl</strong> - Wie viele Quellen diese Behauptung st\u00fctzen<br><br>'
+ 'Die Filterfunktion oben rechts ermöglicht es, nach Status zu filtern, ' + 'Beobachten Sie, wie der Cursor die verschiedenen Status durchgeht:',
+ 'z.B. nur unbestätigte Meldungen anzeigen.',
position: 'left', position: 'left',
disableNav: true,
onEnter: function() { onEnter: function() {
// Zum nicht-verifizierten Eintrag scrollen Tutorial._runDemo(Tutorial._simulateFactcheckStati);
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() {
if (item) item.classList.remove('tutorial-sub-highlight');
});
}, },
onExit: function() { onExit: function() {
Tutorial._clearSubHighlights(); Tutorial._clearSubHighlights();
Tutorial._hideCursor();
},
},
// Ereignis-Timeline
{
id: 'timeline',
target: '[gs-id="timeline"]',
title: 'Ereignis-Timeline',
text: 'Die Timeline zeigt den chronologischen Verlauf aller Ereignisse. '
+ 'Klicken Sie auf einen Zeitpunkt, um die zugeh\u00f6rige Meldung anzuzeigen.<br><br>'
+ '<strong>Punkte</strong> - Meldungen und Lageberichte auf der Zeitachse<br>'
+ '<strong>Detail-Panel</strong> - Zeigt Quelle, Titel und Zeitstempel<br><br>'
+ 'So k\u00f6nnen Sie auch \u00e4ltere Lagebilder abrufen und den Verlauf nachvollziehen.',
position: 'top',
disableNav: true,
onEnter: function() {
Tutorial._runDemo(Tutorial._simulateTimeline);
},
onExit: function() {
Tutorial._clearSubHighlights();
Tutorial._hideCursor();
},
},
// Karte: Kachel-Ansicht
{
id: 'karte',
target: '[gs-id="karte"]',
title: 'Geografische Verteilung',
text: 'Die Karte zeigt per <strong>Geoparsing</strong> automatisch erkannte Orte aus den Quellen.<br><br>'
+ '<strong>Orte einlesen</strong> - Startet das Geoparsing manuell neu<br>'
+ '<strong>Vollbild</strong> - Vergr\u00f6\u00dfert die Karte auf den gesamten Bildschirm<br><br>'
+ 'Im n\u00e4chsten Schritt \u00f6ffnen wir die Vollbildansicht.',
position: 'top',
disableNav: true,
onEnter: function() {
if (Tutorial._demoMapTileMap) {
Tutorial._demoMapTileMap.invalidateSize();
setTimeout(function() {
if (Tutorial._demoMapTileMap) Tutorial._demoMapTileMap.setView([53.545, 9.98], 12);
}, 200);
}
Tutorial._runDemo(Tutorial._simulateKarteButtons);
},
onExit: function() {
Tutorial._clearSubHighlights();
Tutorial._hideCursor();
},
},
// Karte: Vollbild
{
id: 'karte-fullscreen',
target: '.map-fullscreen-header',
title: 'Karte im Vollbild',
text: 'Die Orte werden automatisch aus den Quellenartikeln erkannt und auf der Karte dargestellt:<br><br>'
+ '<strong style="color:#EF4444;">&#9679; Hauptereignisort</strong> - Wo das Ereignis stattfindet<br>'
+ '<strong style="color:#F59E0B;">&#9679; Erw\u00e4hnt</strong> - In Meldungen genannte Orte<br>'
+ '<strong style="color:#3B82F6;">&#9679; Kontext</strong> - Orte im weiteren Zusammenhang<br><br>'
+ 'Klicken Sie auf einen Marker f\u00fcr Details und verkn\u00fcpfte Artikel.',
position: 'left',
disableNav: true,
onEnter: function() {
var chatBtn = document.getElementById('chat-toggle-btn');
if (chatBtn) chatBtn.style.display = 'none';
var bubble = document.getElementById('tutorial-bubble');
if (bubble) bubble.style.zIndex = '99999';
Tutorial._openDemoMapFullscreen();
},
onExit: function() {
Tutorial._closeDemoMapFullscreen();
var chatBtn = document.getElementById('chat-toggle-btn');
if (chatBtn) chatBtn.style.display = '';
var bubble = document.getElementById('tutorial-bubble');
if (bubble) bubble.style.zIndex = '';
Tutorial._clearSubHighlights();
}, },
}, },
// 15 - Quellen // 15 - Quellen
@@ -921,105 +993,24 @@ const Tutorial = {
Tutorial._clearSubHighlights(); Tutorial._clearSubHighlights();
}, },
}, },
// 16 - Timeline // Dashboard anpassen (Drag + Resize)
{ {
id: 'timeline', id: 'layout-demo',
target: '[gs-id="timeline"]',
title: 'Ereignis-Timeline',
text: 'Die Timeline zeigt den chronologischen Verlauf aller Ereignisse:<br><br>'
+ '<strong>Meldungen</strong> - Einzelne Artikel und Nachrichten aus den Quellen<br>'
+ '<strong>Lageberichte</strong> - Automatisch erstellte Zusammenfassungen (hervorgehoben)<br><br>'
+ 'Nutzen Sie die Filter oben:<br>'
+ '<strong>Alle/Meldungen/Lageberichte</strong> - Typ-Filter<br>'
+ '<strong>24h/7T/Alles</strong> - Zeitraum eingrenzen<br>'
+ '<strong>Suchfeld</strong> - Freitextsuche in allen Einträgen',
position: 'top',
onEnter: function() {
Tutorial._highlightSub('.ht-controls');
},
onExit: function() {
Tutorial._clearSubHighlights();
},
},
// 17 - Karte: Kachel-Ansicht
{
id: 'karte',
target: '[gs-id="karte"]',
title: 'Geografische Verteilung',
text: 'Die Karte zeigt per <strong>Geoparsing</strong> automatisch erkannte Orte aus den Quellen.<br><br>'
+ '<strong>Orte einlesen</strong> - Startet das Geoparsing manuell neu<br>'
+ '<strong>Vollbild</strong> - Vergr\u00f6\u00dfert die Karte auf den gesamten Bildschirm<br><br>'
+ 'Im n\u00e4chsten Schritt \u00f6ffnen wir die Vollbildansicht und schauen uns die Marker im Detail an.',
position: 'top',
onEnter: function() {
// Tile-Map Resize triggern
if (Tutorial._demoMapTileMap) {
Tutorial._demoMapTileMap.invalidateSize();
setTimeout(function() {
if (Tutorial._demoMapTileMap) Tutorial._demoMapTileMap.setView([53.545, 9.98], 12);
}, 200);
}
Tutorial._highlightSub('#geoparse-btn');
Tutorial._stepTimeout(function() {
Tutorial._clearSubHighlights();
Tutorial._highlightSub('#map-expand-btn');
}, 2500);
},
onExit: function() {
Tutorial._clearSubHighlights();
},
},
// 18 - Karte: Vollbild + Zoom + Marker-Demo
{
id: 'karte-fullscreen',
target: '.map-fullscreen-header',
title: 'Karte im Vollbild',
text: 'Die Karte zoomt jetzt auf den Ereignisort. Die Marker zeigen:<br><br>'
+ '<strong style="color:#EF4444;">&#9679; Hauptereignisort</strong> - Burchardkai Terminal (6 Artikel)<br>'
+ '<strong style="color:#F59E0B;">&#9679; Erw\u00e4hnt</strong> - Hamburg Innenstadt (2 Artikel)<br>'
+ '<strong style="color:#3B82F6;">&#9679; Kontext</strong> - Elbe / Hafengebiet (1 Artikel)<br><br>'
+ 'Die <strong>Legende</strong> unten rechts erkl\u00e4rt die Farbkategorien. '
+ 'Klicken Sie auf einen Marker f\u00fcr Details und verkn\u00fcpfte Artikel.',
position: 'left',
disableNav: true,
onEnter: function() {
var chatBtn = document.getElementById('chat-toggle-btn');
if (chatBtn) chatBtn.style.display = 'none';
Tutorial._openDemoMapFullscreen();
},
onExit: function() {
Tutorial._closeDemoMapFullscreen();
var chatBtn = document.getElementById('chat-toggle-btn');
if (chatBtn) chatBtn.style.display = '';
Tutorial._clearSubHighlights();
},
},
// 18 - Drag Demo
{
id: 'drag-demo',
target: '[gs-id="lagebild"] .card-header', target: '[gs-id="lagebild"] .card-header',
title: 'Kacheln verschieben', title: 'Dashboard anpassen',
text: 'Alle Kacheln im Dashboard lassen sich frei per Drag-and-Drop verschieben. ' text: 'Alle Kacheln lassen sich frei verschieben und in der Gr\u00f6\u00dfe anpassen. '
+ 'Greifen Sie dazu die Kopfzeile einer Kachel und ziehen Sie sie an die gewünschte Position.<br><br>' + 'Greifen Sie die Kopfzeile zum Verschieben oder den unteren Rand zum Vergr\u00f6\u00dfern.<br><br>'
+ 'Beobachten Sie die virtuelle Maus-Demo:', + 'Beobachten Sie die Demo: Erst wird das Lagebild verschoben, dann vergr\u00f6\u00dfert.',
position: 'right', position: 'right',
disableNav: true, disableNav: true,
onEnter: function() { onEnter: function() {
Tutorial._runDemo(Tutorial._simulateDrag); Tutorial._runDemo(Tutorial._simulateLayoutDemo);
}, },
}, onExit: function() {
// 19 - Resize Demo if (typeof LayoutManager !== 'undefined' && LayoutManager._grid) {
{ LayoutManager._applyLayout(LayoutManager.DEFAULT_LAYOUT);
id: 'resize-demo', }
target: '[gs-id="faktencheck"]', Tutorial._hideCursor();
title: 'Kacheln in der Größe anpassen',
text: 'Ziehen Sie am rechten unteren Rand einer Kachel, um ihre Größe zu verändern. '
+ 'So können Sie wichtigen Inhalten wie dem Faktencheck mehr Platz einräumen.<br><br>'
+ 'Beobachten Sie die virtuelle Maus-Demo:',
position: 'left',
disableNav: true,
onEnter: function() {
Tutorial._runDemo(Tutorial._simulateResize);
}, },
}, },
// 20 - Theme // 20 - Theme
@@ -1031,17 +1022,17 @@ const Tutorial = {
+ 'Ihre Auswahl wird automatisch gespeichert und beim nächsten Besuch beibehalten.', + 'Ihre Auswahl wird automatisch gespeichert und beim nächsten Besuch beibehalten.',
position: 'bottom', position: 'bottom',
}, },
// 21 - Quellen verwalten // Quellen verwalten
{ {
id: 'sources-btn', id: 'sources-btn',
target: '.sidebar-sources-link button:first-child', target: '.sidebar-sources-link button:first-child',
title: 'Quellenverwaltung öffnen', title: 'Quellenverwaltung \u00f6ffnen',
text: 'In der Seitenleiste ganz unten finden Sie den Zugang zur Quellenverwaltung. ' text: 'In der Seitenleiste ganz unten finden Sie den Zugang zur Quellenverwaltung. '
+ 'Hier können Sie:<br><br>' + 'Hier k\u00f6nnen Sie:<br><br>'
+ '<strong>Neue Quellen hinzufügen</strong> - URL eingeben oder automatisch erkennen lassen<br>' + '<strong>Neue Quellen hinzuf\u00fcgen</strong> - URL eingeben oder automatisch erkennen lassen<br>'
+ '<strong>Bestehende Quellen bearbeiten</strong> - Kategorie, Sprache, Notizen anpassen<br>' + '<strong>Bestehende Quellen bearbeiten</strong> - Kategorie, Sprache, Notizen anpassen<br>'
+ '<strong>Quellen deaktivieren</strong> - Temporär oder dauerhaft ausschließen', + '<strong>Quellen deaktivieren</strong> - Tempor\u00e4r oder dauerhaft ausschlie\u00dfen',
position: 'right', position: 'top',
onEnter: function() { onEnter: function() {
Tutorial._stepTimeout(function() { Tutorial._stepTimeout(function() {
var overlay = document.getElementById('modal-sources'); var overlay = document.getElementById('modal-sources');
@@ -1122,30 +1113,36 @@ const Tutorial = {
Tutorial._hideCursor(); Tutorial._hideCursor();
}, },
}, },
// 23 - Chat // Chat-Assistent
{ {
id: 'chat', id: 'chat',
target: '#chat-toggle-btn', target: '#chat-toggle-btn',
title: 'Chat-Assistent', title: 'Chat-Assistent',
text: 'Der Chat-Assistent steht Ihnen jederzeit zur Verfügung. ' text: 'Der Chat-Assistent steht Ihnen jederzeit zur Verf\u00fcgung. '
+ 'Stellen Sie Fragen zur Bedienung des Monitors und erhalten Sie sofort eine Antwort.<br><br>' + 'Stellen Sie Fragen zur Bedienung des Monitors und erhalten Sie sofort eine Antwort.<br><br>'
+ 'Beispiele:<br>' + 'Beispiele:<br>'
+ '"Wie erstelle ich eine neue Lage?"<br>' + '"Wie erstelle ich eine neue Lage?"<br>'
+ '"Was bedeuten die Faktencheck-Status?"<br>' + '"Was bedeuten die Faktencheck-Status?"<br>'
+ '"Wie exportiere ich einen Lagebericht?"', + '"Wie exportiere ich einen Lagebericht?"',
position: 'left', position: 'top',
}, },
// 24 - Ende // Ende
{ {
id: 'end', id: 'end',
target: null, target: null,
title: 'Rundgang abgeschlossen', title: 'Rundgang abgeschlossen',
text: 'Sie kennen jetzt alle wichtigen Funktionen des AegisSight Monitors.<br><br>' text: 'Sie kennen jetzt alle wichtigen Funktionen des AegisSight Monitors.<br><br>'
+ 'Die Demo-Daten werden nach dem Schließen entfernt. ' + 'Die Demo-Daten werden nach dem Schlie\u00dfen entfernt. '
+ 'Erstellen Sie Ihre erste eigene Lage über den Button "+ Neue Lage" in der Seitenleiste.<br><br>' + 'Erstellen Sie Ihre erste eigene Lage \u00fcber den Button "+ Neue Lage" in der Seitenleiste.<br><br>'
+ 'Bei weiteren Fragen steht Ihnen der Chat-Assistent ' + 'Bei weiteren Fragen steht Ihnen der Chat-Assistent '
+ 'oder unser Support unter support@aegis-sight.de zur Verfügung.', + 'oder unser Support unter support@aegis-sight.de zur Verf\u00fcgung.',
position: 'center', position: 'center',
onEnter: function() {
Tutorial._stepTimeout(function() {
var bubble = document.getElementById('tutorial-bubble');
if (bubble) bubble.style.top = '55%';
}, 50);
},
}, },
]; ];
}, },
@@ -1746,8 +1743,11 @@ const Tutorial = {
var el = document.querySelector(selector); var el = document.querySelector(selector);
var modalBody = document.querySelector('#modal-new .modal-body'); var modalBody = document.querySelector('#modal-new .modal-body');
if (!el || !modalBody) return; if (!el || !modalBody) return;
var elTop = el.offsetTop - modalBody.offsetTop; var elRect = el.getBoundingClientRect();
modalBody.scrollTo({ top: Math.max(0, elTop - 20), behavior: 'smooth' }); var bodyRect = modalBody.getBoundingClientRect();
var offset = elRect.top - bodyRect.top + modalBody.scrollTop;
var targetScroll = offset - Math.min(40, bodyRect.height * 0.2);
modalBody.scrollTo({ top: Math.max(0, targetScroll), behavior: 'smooth' });
}, },
// ----------------------------------------------------------------------- // -----------------------------------------------------------------------
@@ -1787,6 +1787,8 @@ const Tutorial = {
// ----------------------------------------------------------------------- // -----------------------------------------------------------------------
// Step 4: Art der Lage (Typ-Wechsel) // Step 4: Art der Lage (Typ-Wechsel)
// ----------------------------------------------------------------------- // -----------------------------------------------------------------------
// Step 4: Art der Lage (simuliertes Dropdown)
// -----------------------------------------------------------------------
async _simulateTypeSwitch() { async _simulateTypeSwitch() {
this._demoRunning = true; this._demoRunning = true;
var sel = document.getElementById('inc-type'); var sel = document.getElementById('inc-type');
@@ -1795,15 +1797,67 @@ const Tutorial = {
var pos = await this._cursorToElement('#inc-type'); var pos = await this._cursorToElement('#inc-type');
await this._wait(300); await this._wait(300);
// Wechsel zu Recherche // Simuliertes Dropdown
sel.value = 'research'; var rect = sel.getBoundingClientRect();
sel.dispatchEvent(new Event('change')); var dropdown = document.createElement('div');
await this._wait(2000); dropdown.id = 'tutorial-fake-dropdown';
dropdown.style.cssText = 'position:fixed;z-index:99999;background:var(--bg-card,#1a1f2e);'
+ 'border:1px solid var(--border,#2a3040);border-radius:8px;box-shadow:0 8px 32px rgba(0,0,0,0.4);'
+ 'overflow:hidden;left:' + rect.left + 'px;top:' + (rect.bottom + 4) + 'px;width:' + rect.width + 'px;'
+ 'opacity:0;transform:translateY(-4px);transition:opacity 0.2s,transform 0.2s;';
// Zur\u00fcck zu Live-Monitoring var opt1 = document.createElement('div');
opt1.textContent = 'Live-Monitoring';
opt1.style.cssText = 'padding:10px 14px;font-size:14px;color:var(--text-primary,#e0e0e0);'
+ 'background:var(--accent-alpha,rgba(212,175,55,0.15));transition:background 0.15s;';
var opt2 = document.createElement('div');
opt2.textContent = 'Analyse / Recherche';
opt2.style.cssText = 'padding:10px 14px;font-size:14px;color:var(--text-primary,#e0e0e0);'
+ 'transition:background 0.15s;';
dropdown.appendChild(opt1);
dropdown.appendChild(opt2);
document.body.appendChild(dropdown);
await this._wait(50);
dropdown.style.opacity = '1';
dropdown.style.transform = 'translateY(0)';
await this._wait(400);
// Cursor zu Live-Monitoring
var opt1Rect = opt1.getBoundingClientRect();
await this._animateCursor(pos.x, pos.y,
opt1Rect.left + opt1Rect.width / 2, opt1Rect.top + opt1Rect.height / 2, 400);
await this._wait(1200);
// Cursor zu Analyse/Recherche
var opt2Rect = opt2.getBoundingClientRect();
await this._animateCursor(
opt1Rect.left + opt1Rect.width / 2, opt1Rect.top + opt1Rect.height / 2,
opt2Rect.left + opt2Rect.width / 2, opt2Rect.top + opt2Rect.height / 2, 400);
opt1.style.background = '';
opt2.style.background = 'var(--accent-alpha,rgba(212,175,55,0.15))';
sel.value = 'research';
try { sel.dispatchEvent(new Event('change')); } catch(e) {}
await this._wait(1500);
// Zurueck zu Live-Monitoring
opt1Rect = opt1.getBoundingClientRect();
opt2Rect = opt2.getBoundingClientRect();
await this._animateCursor(
opt2Rect.left + opt2Rect.width / 2, opt2Rect.top + opt2Rect.height / 2,
opt1Rect.left + opt1Rect.width / 2, opt1Rect.top + opt1Rect.height / 2, 400);
opt2.style.background = '';
opt1.style.background = 'var(--accent-alpha,rgba(212,175,55,0.15))';
sel.value = 'adhoc'; sel.value = 'adhoc';
sel.dispatchEvent(new Event('change')); try { sel.dispatchEvent(new Event('change')); } catch(e) {}
await this._wait(800); await this._wait(1000);
dropdown.style.opacity = '0';
dropdown.style.transform = 'translateY(-4px)';
await this._wait(250);
if (dropdown.parentNode) dropdown.remove();
this._hideCursor(); this._hideCursor();
this._demoRunning = false; this._demoRunning = false;
@@ -2209,6 +2263,281 @@ const Tutorial = {
this._enableNavAfterDemo(); this._enableNavAfterDemo();
}, },
// -----------------------------------------------------------------------
// Sidebar-Filter Demo
// -----------------------------------------------------------------------
async _simulateFilterSwitch() {
this._demoRunning = true;
await this._wait(400);
var btns = document.querySelectorAll('.sidebar-filter button, .sidebar-filter .filter-btn');
var allBtn = null, ownBtn = null;
btns.forEach(function(b) {
var t = b.textContent.trim().toLowerCase();
if (t === 'alle') allBtn = b;
if (t === 'eigene') ownBtn = b;
});
if (!allBtn) allBtn = document.querySelector('.sidebar-filter [data-filter="all"]');
if (!ownBtn) ownBtn = document.querySelector('.sidebar-filter [data-filter="own"]');
if (!allBtn || !ownBtn) { this._demoRunning = false; this._enableNavAfterDemo(); return; }
var ar = allBtn.getBoundingClientRect();
this._showCursor(ar.left - 30, ar.top - 20, 'default');
await this._wait(300);
await this._animateCursor(ar.left - 30, ar.top - 20, ar.left + ar.width/2, ar.top + ar.height/2, 400);
allBtn.classList.add('tutorial-sub-highlight');
await this._wait(1500);
allBtn.classList.remove('tutorial-sub-highlight');
var or2 = ownBtn.getBoundingClientRect();
await this._animateCursor(ar.left+ar.width/2, ar.top+ar.height/2, or2.left+or2.width/2, or2.top+or2.height/2, 500);
ownBtn.classList.add('tutorial-sub-highlight');
await this._wait(1500);
ownBtn.classList.remove('tutorial-sub-highlight');
ar = allBtn.getBoundingClientRect();
or2 = ownBtn.getBoundingClientRect();
await this._animateCursor(or2.left+or2.width/2, or2.top+or2.height/2, ar.left+ar.width/2, ar.top+ar.height/2, 500);
allBtn.classList.add('tutorial-sub-highlight');
await this._wait(1000);
allBtn.classList.remove('tutorial-sub-highlight');
this._hideCursor();
this._demoRunning = false;
this._enableNavAfterDemo();
},
// -----------------------------------------------------------------------
// Faktencheck-Stati Demo
// -----------------------------------------------------------------------
async _simulateFactcheckStati() {
this._demoRunning = true;
await this._wait(400);
var fcList = document.getElementById('factcheck-list');
if (!fcList) { this._demoRunning = false; this._enableNavAfterDemo(); return; }
var stati = ['confirmed','established','unconfirmed','disputed','contradicted'];
var prevX, prevY;
for (var i = 0; i < stati.length; i++) {
if (!this._isActive) break;
var item = fcList.querySelector('[data-fc-status="' + stati[i] + '"]');
if (!item) continue;
item.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
await this._wait(300);
var r = item.getBoundingClientRect();
var tx = r.left + 20, ty = r.top + r.height/2;
if (prevX !== undefined) {
await this._animateCursor(prevX, prevY, tx, ty, 400);
} else {
this._showCursor(tx - 40, ty - 30, 'default');
await this._wait(200);
await this._animateCursor(tx - 40, ty - 30, tx, ty, 400);
}
item.classList.add('tutorial-sub-highlight');
await this._wait(1500);
if (i < stati.length - 1) item.classList.remove('tutorial-sub-highlight');
prevX = tx; prevY = ty;
}
this._hideCursor();
this._demoRunning = false;
this._enableNavAfterDemo();
},
// -----------------------------------------------------------------------
// Timeline Demo
// -----------------------------------------------------------------------
async _simulateTimeline() {
this._demoRunning = true;
await this._wait(400);
var points = document.querySelectorAll('.ht-point');
if (!points.length) { this._demoRunning = false; this._enableNavAfterDemo(); return; }
var detailPanel = document.querySelector('.ht-detail-panel');
var entries = this._DEMO_TIMELINE;
var prevX, prevY;
for (var i = 0; i < Math.min(points.length, entries.length); i++) {
if (!this._isActive) break;
var point = points[i];
var entry = entries[i];
var r = point.getBoundingClientRect();
var tx = r.left + r.width/2, ty = r.top + r.height/2;
if (prevX !== undefined) {
await this._animateCursor(prevX, prevY, tx, ty, 400);
} else {
this._showCursor(tx - 40, ty - 30, 'default');
await this._wait(200);
await this._animateCursor(tx - 40, ty - 30, tx, ty, 400);
}
points.forEach(function(p) { p.classList.remove('active'); });
point.classList.add('active');
if (detailPanel) {
var isSn = entry.type === 'snapshot';
detailPanel.innerHTML = '<div class="ht-detail-header"><span class="ht-detail-time">16.03.2026, '
+ entry.time + '</span><span class="ht-detail-count">' + (isSn ? 'Lagebericht' : '1 Meldung')
+ '</span></div><div class="ht-detail-entries"><div class="ht-detail-entry"><div class="ht-detail-entry-source">'
+ entry.source + '</div><div class="ht-detail-entry-title">' + entry.title + '</div></div></div>';
}
await this._wait(1500);
prevX = tx; prevY = ty;
}
this._hideCursor();
this._demoRunning = false;
this._enableNavAfterDemo();
},
// -----------------------------------------------------------------------
// Karte-Buttons Demo
// -----------------------------------------------------------------------
async _simulateKarteButtons() {
this._demoRunning = true;
await this._wait(400);
if (this._demoMapTileMap) {
this._demoMapTileMap.invalidateSize();
this._demoMapTileMap.setView([53.545, 9.98], 12);
}
var geoBtn = document.getElementById('geoparse-btn');
var expandBtn = document.getElementById('map-expand-btn');
var prevX, prevY;
if (geoBtn) {
this._highlightSub('#geoparse-btn');
var gr = geoBtn.getBoundingClientRect();
this._showCursor(gr.left - 40, gr.top - 30, 'default');
await this._wait(300);
await this._animateCursor(gr.left-40, gr.top-30, gr.left+gr.width/2, gr.top+gr.height/2, 500);
prevX = gr.left+gr.width/2; prevY = gr.top+gr.height/2;
await this._wait(2000);
this._clearSubHighlights();
}
if (expandBtn) {
this._highlightSub('#map-expand-btn');
var er = expandBtn.getBoundingClientRect();
if (prevX !== undefined) {
await this._animateCursor(prevX, prevY, er.left+er.width/2, er.top+er.height/2, 500);
} else {
this._showCursor(er.left+er.width/2, er.top+er.height/2, 'default');
}
await this._wait(2000);
this._clearSubHighlights();
}
this._hideCursor();
this._demoRunning = false;
this._enableNavAfterDemo();
},
// -----------------------------------------------------------------------
// Layout Demo (Drag + Resize kombiniert)
// -----------------------------------------------------------------------
async _simulateLayoutDemo() {
this._demoRunning = true;
var lbTile = document.querySelector('[gs-id="lagebild"]');
var fcTile = document.querySelector('[gs-id="faktencheck"]');
if (!lbTile || !fcTile) { this._demoRunning = false; this._enableNavAfterDemo(); return; }
var grid = typeof LayoutManager !== 'undefined' ? LayoutManager._grid : null;
var self = this;
// Phase 1: Drag
var lbH = lbTile.querySelector('.card-header');
if (lbH) {
var lbR = lbH.getBoundingClientRect();
var fcR = fcTile.getBoundingClientRect();
var sx = lbR.left+lbR.width/2, sy = lbR.top+lbR.height/2;
var ex = fcR.left+fcR.width/2, ey = fcR.top+lbR.height/2;
this._showCursor(sx-40, sy-30, 'default');
await this._wait(300);
await this._animateCursor(sx-40, sy-30, sx, sy, 400);
await this._wait(200);
this._els.cursor.classList.remove('tutorial-cursor-default');
this._els.cursor.classList.add('tutorial-cursor-grabbing');
await this._wait(200);
lbTile.style.transition = 'none';
lbTile.style.zIndex = '9002';
var dx = ex-sx, dy = ey-sy;
var ms = null;
await new Promise(function(resolve) {
function f(ts) {
if (!self._isActive) { resolve(); return; }
if (!ms) ms = ts;
var p = Math.min((ts-ms)/1200,1);
var e2 = p<0.5?4*p*p*p:1-Math.pow(-2*p+2,3)/2;
lbTile.style.transform = 'translate('+(dx*e2)+'px,'+(dy*e2)+'px)';
self._els.cursor.style.left = (sx+dx*e2)+'px';
self._els.cursor.style.top = (sy+dy*e2)+'px';
if (p<1) requestAnimationFrame(f); else resolve();
}
requestAnimationFrame(f);
});
await this._wait(400);
lbTile.style.transform = '';
lbTile.style.transition = '';
lbTile.style.zIndex = '';
if (grid) {
var ln = grid.engine.nodes.find(function(n){return n.el===lbTile;});
var fn = grid.engine.nodes.find(function(n){return n.el===fcTile;});
if (ln && fn) {
var tmpX=ln.x, tmpY=ln.y;
grid.update(lbTile,{x:fn.x,y:fn.y});
grid.update(fcTile,{x:tmpX,y:tmpY});
grid.compact();
}
}
this._els.cursor.classList.remove('tutorial-cursor-grabbing');
this._els.cursor.classList.add('tutorial-cursor-default');
await this._wait(800);
}
// Phase 2: Resize
lbTile = document.querySelector('[gs-id="lagebild"]');
if (lbTile) {
var tr = lbTile.getBoundingClientRect();
var hx = tr.right-6, hy = tr.bottom-6;
await this._animateCursor(
parseFloat(this._els.cursor.style.left)||hx-40,
parseFloat(this._els.cursor.style.top)||hy-30, hx, hy, 500);
await this._wait(200);
this._els.cursor.classList.remove('tutorial-cursor-default');
this._els.cursor.classList.add('tutorial-cursor-resize');
await this._wait(200);
lbTile.style.transition = 'none';
lbTile.style.zIndex = '9002';
lbTile.style.transformOrigin = 'top left';
var ow = tr.width, oh = tr.height;
var epx = ow*0.6, epy = oh*0.5;
var rs = null;
await new Promise(function(resolve) {
function f(ts) {
if (!self._isActive) { resolve(); return; }
if (!rs) rs = ts;
var p = Math.min((ts-rs)/1200,1);
var e3 = p<0.5?4*p*p*p:1-Math.pow(-2*p+2,3)/2;
lbTile.style.transform = 'scale('+(1+epx/ow*e3)+','+(1+epy/oh*e3)+')';
self._els.cursor.style.left = (hx+epx*e3)+'px';
self._els.cursor.style.top = (hy+epy*e3)+'px';
if (p<1) requestAnimationFrame(f); else resolve();
}
requestAnimationFrame(f);
});
await this._wait(600);
lbTile.style.transform = '';
lbTile.style.transformOrigin = '';
lbTile.style.transition = '';
lbTile.style.zIndex = '';
if (grid) {
var ln2 = grid.engine.nodes.find(function(n){return n.el===lbTile;});
if (ln2) { grid.update(lbTile,{w:12,h:Math.max(ln2.h+3,6)}); grid.compact(); }
}
await this._wait(600);
}
this._hideCursor();
this._demoRunning = false;
this._enableNavAfterDemo();
},
// ----------------------------------------------------------------------- // -----------------------------------------------------------------------
// Navigation nach Demo-Ende freigeben // Navigation nach Demo-Ende freigeben
// ----------------------------------------------------------------------- // -----------------------------------------------------------------------