/** * AegisSight Tutorial - Interaktiver Rundgang durch den Monitor. * Erzeugt eine Demo-Lage mit Platzhalter-Inhalten, sodass der Rundgang * auch ohne bestehende Lagen funktioniert. */ const Tutorial = { _steps: [], _currentStep: -1, _isActive: false, _resizeTimer: null, _keyHandler: null, _resizeHandler: null, _demoRunning: false, _savedState: null, // Dashboard-Zustand vor dem Tutorial // DOM-Referenzen _els: { overlay: null, spotlight: null, bubble: null, cursor: null, }, init() { this._defineSteps(); this._els.overlay = document.getElementById('tutorial-overlay'); this._els.spotlight = document.getElementById('tutorial-spotlight'); this._els.bubble = document.getElementById('tutorial-bubble'); this._els.cursor = document.getElementById('tutorial-cursor'); }, // ----------------------------------------------------------------------- // Demo-Daten // ----------------------------------------------------------------------- _DEMO_SIDEBAR_ITEM: '
' + '' + '
' + '
Explosion in Hamburger Hafen
' + '
14 Artikel · demo-user
' + '
' + '' + '
', _DEMO_SIDEBAR_RESEARCH: '
' + '' + '
' + '
Analyse: Cyberangriffe auf Kritische Infrastruktur 2026
' + '
8 Artikel · demo-user
' + '
' + '
', _DEMO_SUMMARY: '

Am Morgen des 16. März 2026 kam es im Hamburger Hafen zu einer schweren Explosion ' + 'in einem Containerterminal am Burchardkai. Nach übereinstimmenden Berichten von ' + 'dpa [1], ' + 'Reuters [2] und ' + 'NDR [3] ' + 'ereignete sich die Detonation gegen 06:45 Uhr Ortszeit in einem Lagerbereich für Gefahrgut.

' + '

Die Hamburger Feuerwehr ist mit einem Großaufgebot vor Ort. Laut ' + 'Hamburger Abendblatt [4] ' + 'wurden mindestens 12 Personen verletzt, davon 3 schwer. Die Ursache der Explosion ist noch unklar. ' + 'Die Polizei Hamburg hat den Bereich weiträumig abgesperrt.

' + '

Der Hamburger Hafen, Europas drittgrößter Seehafen, hat den Betrieb im betroffenen Terminal vorübergehend ' + 'eingestellt. Auswirkungen auf den Schiffsverkehr werden derzeit geprüft ' + 'HPA [5].

', _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: 'Mindestens 12 Personen wurden bei dem Vorfall verletzt.', sources: 3 }, { status: 'established', icon: '✓', claim: 'Die Explosion fand im Bereich des Burchardkai-Terminals statt.', sources: 4 }, { status: 'unconfirmed', icon: '?', claim: 'Es soll sich um eine Fehlfunktion in einem Gefahrgutcontainer handeln.', sources: 1 }, { status: 'disputed', icon: '⚠', claim: 'Die Rauchwolke soll gesundheitsgefährdende Stoffe enthalten.', sources: 2 }, { status: 'contradicted', icon: '✗', claim: 'Der gesamte Hamburger Hafen wurde geschlossen.', sources: 2 }, ], _DEMO_SOURCES_STATS: '5 Kategorien · 14 Quellen · 14 Artikel', _DEMO_TIMELINE: [ { time: '06:45', title: 'Explosion im Containerterminal Burchardkai', source: 'dpa', type: 'article' }, { time: '07:02', title: 'Hamburger Feuerwehr rückt mit Großaufgebot aus', source: 'NDR', type: 'article' }, { time: '07:15', title: 'Polizei sperrt Hafengebiet weiträumig ab', source: 'Hamburger Abendblatt', type: 'article' }, { time: '07:30', title: 'Erste Meldungen über Verletzte', source: 'Reuters', type: 'article' }, { time: '08:00', title: 'Hamburg Port Authority stoppt Betrieb im Terminal', source: 'HPA', type: 'article' }, { time: '08:22', title: 'Lagebericht: Erster Überblick zum Vorfall', source: 'AegisSight', type: 'snapshot' }, { time: '09:10', title: 'Gefahrgut-Spezialisten am Einsatzort eingetroffen', source: 'tagesschau.de', type: 'article' }, ], // ----------------------------------------------------------------------- // Demo-View injizieren / entfernen // ----------------------------------------------------------------------- _injectDemoView() { // Zustand sichern var incidentView = document.getElementById('incident-view'); var emptyState = document.getElementById('empty-state'); this._savedState = { incidentViewDisplay: incidentView ? incidentView.style.display : 'none', emptyStateDisplay: emptyState ? emptyState.style.display : '', incidentTitle: document.getElementById('incident-title') ? document.getElementById('incident-title').textContent : '', summaryText: document.getElementById('summary-text') ? document.getElementById('summary-text').innerHTML : '', factcheckList: document.getElementById('factcheck-list') ? document.getElementById('factcheck-list').innerHTML : '', sourceStats: document.getElementById('source-overview-header-stats') ? document.getElementById('source-overview-header-stats').innerHTML : '', timeline: document.getElementById('timeline') ? document.getElementById('timeline').innerHTML : '', mapEmpty: document.getElementById('map-empty') ? document.getElementById('map-empty').style.display : '', layoutToolbar: document.getElementById('layout-toolbar') ? document.getElementById('layout-toolbar').style.display : 'none', typeBadge: document.getElementById('incident-type-badge') ? document.getElementById('incident-type-badge').innerHTML : '', refreshMode: document.getElementById('meta-refresh-mode') ? document.getElementById('meta-refresh-mode').innerHTML : '', headerStrip: document.getElementById('incident-header-strip') ? document.getElementById('incident-header-strip').style.display : '', fcFilters: document.getElementById('fc-filters') ? document.getElementById('fc-filters').innerHTML : '', mapStats: document.getElementById('map-stats') ? document.getElementById('map-stats').innerHTML : '', sidebarItems: null, }; // Sidebar: Demo-Einträge hinzufügen var activeList = document.getElementById('active-incidents'); if (activeList) { this._savedState.sidebarItems = activeList.innerHTML; activeList.insertAdjacentHTML('afterbegin', this._DEMO_SIDEBAR_ITEM); } // Empty-State verstecken, Incident-View anzeigen if (emptyState) emptyState.style.display = 'none'; if (incidentView) incidentView.style.display = ''; // Header var title = document.getElementById('incident-title'); if (title) title.textContent = 'Explosion in Hamburger Hafen'; var badge = document.getElementById('incident-type-badge'); if (badge) { badge.textContent = 'Live-Monitoring'; badge.className = 'incident-type-badge type-adhoc'; } var refreshMode = document.getElementById('meta-refresh-mode'); if (refreshMode) refreshMode.innerHTML = 'Auto-Refresh: 15 Min'; var creator = document.getElementById('incident-creator'); if (creator) creator.textContent = 'demo-user'; var desc = document.getElementById('incident-description'); if (desc) desc.textContent = 'Schwere Explosion im Hamburger Hafengebiet, Burchardkai-Terminal'; // Layout-Toolbar var toolbar = document.getElementById('layout-toolbar'); if (toolbar) toolbar.style.display = ''; // Lagebild var summaryText = document.getElementById('summary-text'); if (summaryText) summaryText.innerHTML = this._DEMO_SUMMARY; // Timestamp var ts = document.getElementById('lagebild-timestamp'); if (ts) ts.textContent = '16.03.2026, 08:22'; // Faktencheck var fcList = document.getElementById('factcheck-list'); if (fcList) { var fcHtml = ''; this._DEMO_FACTCHECKS.forEach(function(fc) { fcHtml += '
' + '' + '
' + '
' + fc.claim + '
' + '
' + '' + fc.sources + ' Quelle' + (fc.sources !== 1 ? 'n' : '') + '' + '
'; }); fcList.innerHTML = fcHtml; } // Faktencheck-Filter var fcFilters = document.getElementById('fc-filters'); if (fcFilters) { fcFilters.innerHTML = ''; } // Quellenübersicht var sourceStats = document.getElementById('source-overview-header-stats'); if (sourceStats) sourceStats.innerHTML = this._DEMO_SOURCES_STATS; // Timeline 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; } // Karte: "Keine Orte" ausblenden, Platzhalter einsetzen var mapEmpty = document.getElementById('map-empty'); if (mapEmpty) mapEmpty.style.display = 'none'; var mapContainer = document.getElementById('map-container'); if (mapContainer) { var mapPlaceholder = document.createElement('div'); mapPlaceholder.className = 'tutorial-demo tutorial-map-placeholder'; mapPlaceholder.style.cssText = 'width:100%;height:100%;display:flex;align-items:center;justify-content:center;' + 'background:var(--bg-secondary);color:var(--text-secondary);font-size:13px;'; mapPlaceholder.innerHTML = '
' + '
🌎
' + '
3 Orte erkannt: Hamburg, Burchardkai, Elbe
'; mapContainer.appendChild(mapPlaceholder); } var mapStats = document.getElementById('map-stats'); if (mapStats) mapStats.textContent = '3 Orte'; // Meta var metaUpdated = document.getElementById('meta-updated'); if (metaUpdated) metaUpdated.textContent = 'Aktualisiert: 16.03.2026, 09:10'; }, _removeDemoView() { if (!this._savedState) return; var s = this._savedState; // Sidebar Demo-Einträge entfernen document.querySelectorAll('.tutorial-demo').forEach(function(el) { el.remove(); }); // Sidebar wiederherstellen var activeList = document.getElementById('active-incidents'); if (activeList && s.sidebarItems !== null) activeList.innerHTML = s.sidebarItems; // Views wiederherstellen var incidentView = document.getElementById('incident-view'); if (incidentView) incidentView.style.display = s.incidentViewDisplay; var emptyState = document.getElementById('empty-state'); if (emptyState) emptyState.style.display = s.emptyStateDisplay; // Inhalte wiederherstellen var title = document.getElementById('incident-title'); if (title) title.textContent = s.incidentTitle; var summaryText = document.getElementById('summary-text'); if (summaryText) summaryText.innerHTML = s.summaryText; var fcList = document.getElementById('factcheck-list'); if (fcList) fcList.innerHTML = s.factcheckList; var sourceStats = document.getElementById('source-overview-header-stats'); if (sourceStats) sourceStats.innerHTML = s.sourceStats; var timeline = document.getElementById('timeline'); if (timeline) timeline.innerHTML = s.timeline; var mapEmpty = document.getElementById('map-empty'); if (mapEmpty) mapEmpty.style.display = s.mapEmpty; var toolbar = document.getElementById('layout-toolbar'); if (toolbar) toolbar.style.display = s.layoutToolbar; var badge = document.getElementById('incident-type-badge'); if (badge) badge.innerHTML = s.typeBadge; var refreshMode = document.getElementById('meta-refresh-mode'); if (refreshMode) refreshMode.innerHTML = s.refreshMode; var fcFilters = document.getElementById('fc-filters'); if (fcFilters) fcFilters.innerHTML = s.fcFilters; var mapStats = document.getElementById('map-stats'); if (mapStats) mapStats.innerHTML = s.mapStats; // Map-Platzhalter entfernen var mapPlaceholder = document.querySelector('.tutorial-map-placeholder'); if (mapPlaceholder) mapPlaceholder.remove(); // Meta var metaUpdated = document.getElementById('meta-updated'); if (metaUpdated) metaUpdated.textContent = ''; var creator = document.getElementById('incident-creator'); if (creator) creator.textContent = ''; var desc = document.getElementById('incident-description'); if (desc) desc.textContent = ''; var ts = document.getElementById('lagebild-timestamp'); if (ts) ts.textContent = ''; this._savedState = null; }, // ----------------------------------------------------------------------- // Highlight-Helfer: Einzelnes Sub-Element innerhalb einer Kachel markieren // ----------------------------------------------------------------------- _highlightSub(selector) { var el = document.querySelector(selector); if (!el) return; el.classList.add('tutorial-sub-highlight'); this._cleanupFns.push(function() { el.classList.remove('tutorial-sub-highlight'); }); }, _clearSubHighlights() { document.querySelectorAll('.tutorial-sub-highlight').forEach(function(el) { el.classList.remove('tutorial-sub-highlight'); }); }, _cleanupFns: [], // ----------------------------------------------------------------------- // Scroll-Helfer: Element in den sichtbaren Bereich scrollen // ----------------------------------------------------------------------- _scrollToTarget(selector) { return new Promise(function(resolve) { var el = document.querySelector(selector); if (!el) { resolve(); return; } var rect = el.getBoundingClientRect(); var vh = window.innerHeight; if (rect.top >= 0 && rect.bottom <= vh) { resolve(); return; } el.scrollIntoView({ behavior: 'smooth', block: 'center' }); setTimeout(resolve, 500); }); }, // ----------------------------------------------------------------------- // Step-Definitionen // ----------------------------------------------------------------------- _defineSteps() { this._steps = [ // 0 - Welcome { id: 'welcome', target: '#main-content', title: 'Willkommen im AegisSight Monitor', text: 'Dieser interaktive Rundgang führt Sie durch alle Funktionen des OSINT-Monitors. ' + 'Wir werden gemeinsam eine Demo-Lage anlegen und alle Bereiche des Dashboards erkunden.

' + 'Navigieren Sie mit den Pfeiltasten oder den Buttons. Mit Escape können Sie jederzeit abbrechen.', position: 'center', }, // 1 - Sidebar { id: 'sidebar', target: '.sidebar', title: 'Seitenleiste: Ihre Lagen', text: 'Die Seitenleiste zeigt all Ihre Lagen in drei Bereichen:

' + 'Live-Monitoring - Laufende Ereignisbeobachtung mit automatischer Aktualisierung
' + 'Recherchen - Themenanalysen ohne Echtzeit-Updates
' + 'Archiv - Abgeschlossene Lagen zur späteren Einsicht

' + 'Klicken Sie auf eine Lage, um sie im Hauptbereich zu öffnen.', position: 'right', }, // 2 - Neue Lage Button { id: 'new-incident-btn', target: '#new-incident-btn', title: 'Neue Lage anlegen', text: 'Hier starten Sie die Erstellung einer neuen Lage. Im nächsten Schritt zeigen wir Ihnen das Formular dazu.', position: 'right', onEnter: function() { setTimeout(function() { var overlay = document.getElementById('modal-new'); if (overlay && !overlay.classList.contains('active')) { overlay.classList.add('active'); } }, 1500); }, onExit: function() { var overlay = document.getElementById('modal-new'); if (overlay) overlay.classList.remove('active'); }, }, // 3 - Neue Lage Modal: Überblick { id: 'new-incident-modal', target: '#modal-new .modal', title: 'Lage konfigurieren', text: 'Das Formular zur Lage-Erstellung. Hier legen Sie fest:

' + 'Titel - Kurze Bezeichnung des Vorfalls
' + 'Beschreibung - Kontext und Hintergrundinformationen
' + 'Art der Lage - Live-Monitoring oder Analyse/Recherche
' + 'Quellen - Internationale und Telegram-Quellen ein/aus
' + 'Sichtbarkeit - Öffentlich oder privat für Ihr Team
' + 'Aktualisierung - Manuell oder automatisch mit Intervall', position: 'left', onEnter: function() { var overlay = document.getElementById('modal-new'); if (overlay && !overlay.classList.contains('active')) { overlay.classList.add('active'); } if (overlay) overlay.style.zIndex = '9002'; // Demo: Titel vorausfüllen var titleInput = document.getElementById('inc-title'); if (titleInput) titleInput.value = 'Explosion in Hamburger Hafen'; var descInput = document.getElementById('inc-description'); if (descInput) descInput.value = 'Schwere Explosion im Hamburger Hafengebiet, Burchardkai-Terminal'; }, onExit: function() { var overlay = document.getElementById('modal-new'); if (overlay) { overlay.classList.remove('active'); overlay.style.zIndex = ''; } var titleInput = document.getElementById('inc-title'); if (titleInput) titleInput.value = ''; var descInput = document.getElementById('inc-description'); if (descInput) descInput.value = ''; }, }, // 4 - Lage-Typ: Live vs Recherche { id: 'incident-type', target: '#modal-new .modal', title: 'Live-Monitoring vs. Recherche', text: 'Live-Monitoring beobachtet ein Ereignis in Echtzeit. ' + 'Hunderte Nachrichtenquellen werden automatisch durchsucht. ' + 'Ideal für aktuelle Vorfälle, Krisen oder sich entwickelnde Lagen.

' + 'Analyse/Recherche untersucht ein Thema tiefergehend. ' + 'Keine automatischen Updates, stattdessen gezielte Recherche mit eigenen Suchbegriffen. ' + 'Ideal für Hintergrundanalysen und Lageberichte.', position: 'left', onEnter: function() { var overlay = document.getElementById('modal-new'); if (overlay && !overlay.classList.contains('active')) { overlay.classList.add('active'); } if (overlay) overlay.style.zIndex = '9002'; Tutorial._highlightSub('#inc-type'); }, onExit: function() { var overlay = document.getElementById('modal-new'); if (overlay) { overlay.classList.remove('active'); overlay.style.zIndex = ''; } Tutorial._clearSubHighlights(); }, }, // 5 - Sidebar Filter { id: 'sidebar-filters', target: '.sidebar-filter', title: 'Lagen filtern', text: 'Mit diesen Filtern steuern Sie, welche Lagen angezeigt werden:

' + 'Alle - Zeigt sämtliche Lagen Ihrer Organisation
' + 'Eigene - Nur Lagen, die Sie selbst erstellt haben

' + 'Bei vielen Lagen hilft dies, den Überblick zu behalten.', position: 'right', }, // 6 - Demo-Lage einführen { id: 'demo-intro', target: '.incident-item.active.tutorial-demo', title: 'Unsere Demo-Lage', text: 'Für diesen Rundgang haben wir eine Demo-Lage erstellt: ' + '"Explosion in Hamburger Hafen". Sie sehen sie hier in der Seitenleiste ' + 'als aktive Lage mit Auto-Refresh.

' + 'In den folgenden Schritten erkunden wir alle Bereiche, ' + 'die nach dem Öffnen einer Lage im Hauptbereich erscheinen.', position: 'right', onEnter: function() { Tutorial._injectDemoView(); }, }, // 7 - Header-Bereich { id: 'incident-header', target: '#incident-header-strip', title: 'Lage-Kopfbereich', text: 'Der Kopfbereich zeigt alle wichtigen Informationen auf einen Blick:

' + 'Typ-Badge - Live-Monitoring oder Recherche
' + 'Auto-Refresh - Zeigt das Aktualisierungsintervall
' + 'Aktionsleiste - Aktualisieren, Bearbeiten, Exportieren, Archivieren und Löschen

' + 'Der Zeitstempel zeigt, wann die letzte Aktualisierung stattfand.', position: 'bottom', }, // 8 - Refresh-Button { id: 'refresh', target: '#refresh-btn', title: 'Manuelle Aktualisierung', text: 'Mit diesem Button starten Sie eine sofortige Aktualisierung der Lage. ' + 'Der Monitor durchsucht dann alle konfigurierten Quellen nach neuen Meldungen, ' + 'erstellt ein aktualisiertes Lagebild und führt einen neuen Faktencheck durch.

' + 'Bei Live-Monitoring-Lagen mit Auto-Refresh geschieht dies zusätzlich automatisch im eingestellten Intervall.', position: 'bottom', onEnter: function() { Tutorial._highlightSub('#refresh-btn'); }, onExit: function() { Tutorial._clearSubHighlights(); }, }, // 9 - Export { id: 'export', target: '#export-dropdown', title: 'Lagebericht exportieren', text: 'Exportieren Sie Ihre Lage in verschiedenen Formaten:

' + 'Lagebericht (Markdown/JSON) - Kompakte Zusammenfassung mit Faktencheck
' + 'Vollexport - Alle Daten inklusive Artikel und Quellen
' + 'Drucken / PDF - Druckoptimierte Ansicht für Berichte

' + 'Ideal, um Ergebnisse mit Kollegen zu teilen, die keinen Monitor-Zugang haben.', position: 'bottom', onEnter: function() { Tutorial._highlightSub('#export-dropdown'); }, onExit: function() { Tutorial._clearSubHighlights(); }, }, // 10 - Layout Toolbar { id: 'layout-toolbar', target: '#layout-toolbar', title: 'Layout-Steuerung', text: 'Mit der Layout-Steuerung passen Sie Ihr Dashboard individuell an:

' + 'Jeder Button steht für eine Kachel: Lagebild, Faktencheck, ' + 'Quellen, Timeline und Karte.

' + 'Klicken Sie auf einen Button, um die entsprechende Kachel ein- oder auszublenden. ' + 'Mit "Layout zurücksetzen" stellen Sie die Standardansicht wieder her.', position: 'bottom', }, // 11 - Lagebild (detailliert) { id: 'lagebild', target: '[gs-id="lagebild"]', title: 'Lagebild', text: 'Das Lagebild ist das Herzstück jeder Lage. Es wird automatisch aus allen gesammelten ' + 'Quellen erstellt und bei jeder Aktualisierung neu generiert.

' + 'Quellenverweise - Die nummerierten Verweise [1], [2] etc. verlinken ' + 'direkt zu den Originalartikeln. So können Sie jede Aussage nachprüfen.
' + 'Vollansicht - Klicken Sie auf "Lagebild" in der Kopfzeile für eine ' + 'große Darstellung mit mehr Platz zum Lesen.
' + 'Zeitstempel - Zeigt, wann dieses Lagebild zuletzt generiert wurde.', position: 'right', onEnter: function() { Tutorial._highlightSub('[gs-id="lagebild"] .card-title'); }, onExit: function() { Tutorial._clearSubHighlights(); }, }, // 12 - Quellenverweise im Lagebild { id: 'lagebild-sources', target: '#summary-text', title: 'Quellenverweise im Detail', text: 'Die farbigen Nummern im Text sind Quellenverweise. Wenn Sie mit der Maus darüberfahren, ' + 'sehen Sie den Namen der Quelle. Ein Klick öffnet den Originalartikel.

' + 'So können Sie jede Behauptung im Lagebild direkt an der Originalquelle überprüfen. ' + 'Je mehr unabhängige Quellen eine Information stützen, desto verlässlicher ist sie.', position: 'right', onEnter: function() { Tutorial._highlightSub('.source-ref[data-index="1"]'); }, onExit: function() { Tutorial._clearSubHighlights(); }, }, // 13 - Faktencheck (detailliert) { id: 'faktencheck', target: '[gs-id="faktencheck"]', title: 'Faktencheck', text: 'Der Faktencheck prüft automatisch die wichtigsten Behauptungen anhand aller verfügbaren Quellen. ' + 'Jeder Eintrag erhält einen Status:

' + '✓ Bestätigt/Gesichert - Durch mehrere unabhängige Quellen belegt
' + '? Unbestätigt - Nur aus einer Quelle bekannt
' + '⚠ Umstritten - Quellen widersprechen sich
' + '✗ Widerlegt - Zuverlässige Quellen widersprechen', position: 'left', onEnter: function() { Tutorial._highlightSub('#factcheck-card'); }, onExit: function() { Tutorial._clearSubHighlights(); }, }, // 14 - Faktencheck: Einzelner Eintrag { id: 'faktencheck-detail', target: '.factcheck-item[data-fc-status="confirmed"]', title: 'Faktencheck-Eintrag', text: 'Jeder Faktencheck-Eintrag besteht aus:

' + 'Status-Symbol - Farbcodiert für schnelle Einordnung (links)
' + 'Behauptung - Die geprüfte Aussage
' + 'Quellenanzahl - Wie viele Quellen diese Behauptung stützen

' + 'Die Filterfunktion oben rechts ermöglicht es, nach Status zu filtern, ' + 'z.B. nur unbestätigte Meldungen anzeigen.', position: 'left', onEnter: function() { 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() { Tutorial._clearSubHighlights(); }, }, // 15 - Quellen { id: 'quellen', target: '[gs-id="quellen"]', title: 'Quellenübersicht', text: 'Die Quellenübersicht zeigt alle für diese Lage verwendeten Quellen, gruppiert nach Kategorie:

' + 'Nachrichtenagenturen - dpa, Reuters, AFP
' + 'Öffentlich-Rechtlich - tagesschau, ZDF, NDR
' + 'Qualitätszeitungen - FAZ, SZ, ZEIT
' + 'Behörden - Offizielle Stellen und Pressemitteilungen

' + 'Klicken Sie auf die Kopfzeile, um die Gruppen aufzuklappen. ' + 'Die "Detailansicht" zeigt alle Quellen mit einzelnen Artikeln.', position: 'top', onEnter: function() { Tutorial._highlightSub('.source-overview-header-toggle'); }, onExit: function() { Tutorial._clearSubHighlights(); }, }, // 16 - Timeline { id: 'timeline', target: '[gs-id="timeline"]', title: 'Ereignis-Timeline', text: 'Die Timeline zeigt den chronologischen Verlauf aller Ereignisse:

' + 'Meldungen - Einzelne Artikel und Nachrichten aus den Quellen
' + 'Lageberichte - Automatisch erstellte Zusammenfassungen (hervorgehoben)

' + 'Nutzen Sie die Filter oben:
' + 'Alle/Meldungen/Lageberichte - Typ-Filter
' + '24h/7T/Alles - Zeitraum eingrenzen
' + 'Suchfeld - Freitextsuche in allen Einträgen', position: 'top', onEnter: function() { Tutorial._highlightSub('.ht-controls'); }, onExit: function() { Tutorial._clearSubHighlights(); }, }, // 17 - Karte { id: 'karte', target: '[gs-id="karte"]', title: 'Geografische Verteilung', text: 'Die Karte zeigt automatisch erkannte Orte aus den gesammelten Artikeln (Geoparsing).

' + 'Marker - Klicken Sie auf einen Marker für Details zum Ort und verknüpfte Artikel
' + 'Cluster - Bei vielen Markern werden nahe Orte gruppiert
' + 'Orte einlesen - Startet das Geoparsing manuell neu
' + 'Vollbild - Vergrößert die Karte auf den gesamten Bildschirm

' + 'Besonders bei internationalen Lagen bietet die Karte einen schnellen Überblick über die räumliche Verteilung.', position: 'top', onEnter: function() { Tutorial._highlightSub('#geoparse-btn'); setTimeout(function() { Tutorial._clearSubHighlights(); Tutorial._highlightSub('#map-expand-btn'); }, 3000); }, onExit: function() { Tutorial._clearSubHighlights(); }, }, // 18 - Drag Demo { id: 'drag-demo', target: '[gs-id="lagebild"] .card-header', title: 'Kacheln verschieben', text: 'Alle Kacheln im Dashboard lassen sich frei per Drag-and-Drop verschieben. ' + 'Greifen Sie dazu die Kopfzeile einer Kachel und ziehen Sie sie an die gewünschte Position.

' + 'Beobachten Sie die virtuelle Maus-Demo:', position: 'right', disableNav: true, onEnter: function() { Tutorial._simulateDrag(); }, }, // 19 - Resize Demo { id: 'resize-demo', target: '[gs-id="faktencheck"]', 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.

' + 'Beobachten Sie die virtuelle Maus-Demo:', position: 'left', disableNav: true, onEnter: function() { Tutorial._simulateResize(); }, }, // 20 - Theme { id: 'theme', target: '#theme-toggle', title: 'Design umschalten', text: 'Wechseln Sie zwischen dem dunklen und hellen Design. ' + 'Ihre Auswahl wird automatisch gespeichert und beim nächsten Besuch beibehalten.', position: 'bottom', }, // 21 - Quellen verwalten { id: 'sources-btn', target: '.sidebar-sources-link button:first-child', title: 'Quellenverwaltung öffnen', text: 'In der Seitenleiste ganz unten finden Sie den Zugang zur Quellenverwaltung. ' + 'Hier können Sie:

' + 'Neue Quellen hinzufügen - URL eingeben oder automatisch erkennen lassen
' + 'Bestehende Quellen bearbeiten - Kategorie, Sprache, Notizen anpassen
' + 'Quellen deaktivieren - Temporär oder dauerhaft ausschließen', position: 'right', onEnter: function() { setTimeout(function() { var overlay = document.getElementById('modal-sources'); if (overlay && !overlay.classList.contains('active')) { if (typeof App !== 'undefined' && App.openSourceManagement) { App.openSourceManagement(); } } }, 1500); }, }, // 22 - Quellen Modal { id: 'sources-modal', target: '#modal-sources .modal', title: 'Quellen verwalten', text: 'Die Quellenverwaltung zeigt alle konfigurierten Nachrichtenquellen. ' + 'Hier sehen Sie für jede Quelle:

' + 'Name und URL - Die Quellbezeichnung und Adresse
' + 'Kategorie - Nachrichtenagentur, Qualitätszeitung, etc.
' + 'Sprache - Deutsch, Englisch oder Mehrsprachig
' + 'Status - Aktiv oder deaktiviert

' + 'Sie können Quellen auch für einzelne Lagen ausschließen, ohne sie global zu deaktivieren.', position: 'left', onEnter: function() { var overlay = document.getElementById('modal-sources'); if (overlay && !overlay.classList.contains('active')) { if (typeof App !== 'undefined' && App.openSourceManagement) { App.openSourceManagement(); } } if (overlay) overlay.style.zIndex = '9002'; }, onExit: function() { var overlay = document.getElementById('modal-sources'); if (overlay) { overlay.classList.remove('active'); overlay.style.zIndex = ''; } }, }, // 23 - Chat { id: 'chat', target: '#chat-toggle-btn', title: 'Chat-Assistent', text: 'Der Chat-Assistent steht Ihnen jederzeit zur Verfügung. ' + 'Stellen Sie Fragen zur Bedienung des Monitors und erhalten Sie sofort eine Antwort.

' + 'Beispiele:
' + '"Wie erstelle ich eine neue Lage?"
' + '"Was bedeuten die Faktencheck-Status?"
' + '"Wie exportiere ich einen Lagebericht?"', position: 'left', }, // 24 - Ende { id: 'end', target: null, title: 'Rundgang abgeschlossen', text: 'Sie kennen jetzt alle wichtigen Funktionen des AegisSight Monitors.

' + 'Die Demo-Daten werden nach dem Schließen entfernt. ' + 'Erstellen Sie Ihre erste eigene Lage über den Button "+ Neue Lage" in der Seitenleiste.

' + 'Bei weiteren Fragen steht Ihnen der Chat-Assistent ' + 'oder unser Support unter support@aegis-sight.de zur Verfügung.', position: 'center', }, ]; }, // ----------------------------------------------------------------------- // Lifecycle // ----------------------------------------------------------------------- start() { if (this._isActive) return; this._isActive = true; this._currentStep = -1; // Chat schließen falls offen if (typeof Chat !== 'undefined' && Chat._isOpen) Chat.close(); // Overlay einblenden this._els.overlay.classList.add('active'); // Keyboard this._keyHandler = this._onKey.bind(this); document.addEventListener('keydown', this._keyHandler); // Resize this._resizeHandler = this._onResize.bind(this); window.addEventListener('resize', this._resizeHandler); this.next(); }, stop() { if (!this._isActive) return; // Aktuellen Step aufräumen if (this._currentStep >= 0) this._exitStep(this._currentStep); this._isActive = false; this._currentStep = -1; this._demoRunning = false; // Overlay ausblenden this._els.overlay.classList.remove('active'); this._els.spotlight.style.opacity = '0'; this._els.bubble.classList.remove('visible'); this._hideCursor(); this._clearSubHighlights(); // Cleanup-Callbacks this._cleanupFns.forEach(function(fn) { try { fn(); } catch(e) {} }); this._cleanupFns = []; // Demo-View entfernen this._removeDemoView(); // Events entfernen if (this._keyHandler) { document.removeEventListener('keydown', this._keyHandler); this._keyHandler = null; } if (this._resizeHandler) { window.removeEventListener('resize', this._resizeHandler); this._resizeHandler = null; } this._markSeen(); }, // ----------------------------------------------------------------------- // Navigation // ----------------------------------------------------------------------- next() { if (!this._isActive || this._demoRunning) return; var nextIdx = this._currentStep + 1; if (nextIdx >= this._steps.length) { this.stop(); return; } this.goToStep(nextIdx); }, prev() { if (!this._isActive || this._demoRunning) return; var prevIdx = this._currentStep - 1; if (prevIdx < 0) return; this.goToStep(prevIdx); }, goToStep(index) { if (index < 0 || index >= this._steps.length) return; if (this._currentStep >= 0) this._exitStep(this._currentStep); this._currentStep = index; this._enterStep(index); }, // ----------------------------------------------------------------------- // Step Enter/Exit // ----------------------------------------------------------------------- async _enterStep(i) { var step = this._steps[i]; // Erst scrollen, dann spotlighten if (step.target && step.position !== 'center') { await this._scrollToTarget(step.target); this._spotlightElement(step.target); } else { this._els.spotlight.style.opacity = '0'; } // Bubble konfigurieren und anzeigen this._showBubble(step, i); // onEnter-Callback if (step.onEnter) { step.onEnter(); } }, _exitStep(i) { var step = this._steps[i]; if (step.onExit) { step.onExit(); } this._hideCursor(); this._clearSubHighlights(); }, // ----------------------------------------------------------------------- // Spotlight // ----------------------------------------------------------------------- _spotlightElement(selector) { var el = document.querySelector(selector); if (!el) { this._els.spotlight.style.opacity = '0'; return; } var rect = el.getBoundingClientRect(); var pad = 8; var s = this._els.spotlight.style; s.top = (rect.top - pad) + 'px'; s.left = (rect.left - pad) + 'px'; s.width = (rect.width + pad * 2) + 'px'; s.height = (rect.height + pad * 2) + 'px'; s.borderRadius = '8px'; s.opacity = '1'; }, // ----------------------------------------------------------------------- // Bubble // ----------------------------------------------------------------------- _showBubble(step, index) { var bubble = this._els.bubble; var total = this._steps.length; // Inhalt var counterHtml = '
Schritt ' + (index + 1) + ' von ' + total + '
'; var titleHtml = '
' + step.title + '
'; var textHtml = '
' + step.text + '
'; // Fortschrittspunkte var dotsHtml = '
'; for (var d = 0; d < total; d++) { dotsHtml += ''; } dotsHtml += '
'; // Navigation var navHtml = '
'; if (index > 0 && !step.disableNav) { navHtml += ''; } else { navHtml += ''; } if (index < total - 1 && !step.disableNav) { navHtml += ''; } else if (index === total - 1) { navHtml += ''; } else { navHtml += ''; } navHtml += '
'; // Close-Button var closeHtml = ''; bubble.innerHTML = closeHtml + counterHtml + titleHtml + textHtml + dotsHtml + navHtml; // Positionierung this._positionBubble(step); // Sichtbar machen bubble.classList.add('visible'); }, _positionBubble(step) { var bubble = this._els.bubble; var bw = 360; // Zentriert (Welcome / End) if (step.position === 'center' || !step.target) { bubble.className = 'tutorial-bubble visible tutorial-pos-center'; bubble.style.top = '50%'; bubble.style.left = '50%'; bubble.style.transform = 'translate(-50%, -50%)'; bubble.style.width = bw + 'px'; return; } var el = document.querySelector(step.target); if (!el) { bubble.className = 'tutorial-bubble visible tutorial-pos-center'; bubble.style.top = '50%'; bubble.style.left = '50%'; bubble.style.transform = 'translate(-50%, -50%)'; bubble.style.width = bw + 'px'; return; } var rect = el.getBoundingClientRect(); var vw = window.innerWidth; var vh = window.innerHeight; var gap = 16; // Reset transform bubble.style.transform = ''; bubble.style.width = bw + 'px'; // Automatische Positionswahl falls nicht genug Platz var pos = step.position || 'bottom'; var bubbleHeight = 300; if (pos === 'bottom' && (rect.bottom + gap + bubbleHeight > vh)) pos = 'top'; if (pos === 'top' && (rect.top - gap - bubbleHeight < 0)) pos = 'bottom'; if (pos === 'right' && (rect.right + gap + bw > vw)) pos = 'left'; if (pos === 'left' && (rect.left - gap - bw < 0)) pos = 'right'; bubble.className = 'tutorial-bubble visible tutorial-pos-' + pos; switch (pos) { case 'bottom': bubble.style.top = (rect.bottom + gap) + 'px'; bubble.style.left = Math.max(8, Math.min(rect.left + rect.width / 2 - bw / 2, vw - bw - 8)) + 'px'; break; case 'top': bubble.style.top = (rect.top - gap) + 'px'; bubble.style.left = Math.max(8, Math.min(rect.left + rect.width / 2 - bw / 2, vw - bw - 8)) + 'px'; bubble.style.transform = 'translateY(-100%)'; break; case 'right': bubble.style.top = Math.max(8, rect.top + rect.height / 2 - bubbleHeight / 2) + 'px'; bubble.style.left = (rect.right + gap) + 'px'; break; case 'left': bubble.style.top = Math.max(8, rect.top + rect.height / 2 - bubbleHeight / 2) + 'px'; bubble.style.left = (rect.left - gap - bw) + 'px'; break; } }, // ----------------------------------------------------------------------- // Cursor (virtuelle Maus) // ----------------------------------------------------------------------- _showCursor(x, y, type) { var c = this._els.cursor; c.className = 'tutorial-cursor'; if (type) c.classList.add('tutorial-cursor-' + type); c.style.left = x + 'px'; c.style.top = y + 'px'; c.classList.add('visible'); }, _hideCursor() { this._els.cursor.classList.remove('visible'); }, _animateCursor(fromX, fromY, toX, toY, ms) { var self = this; return new Promise(function(resolve) { var c = self._els.cursor; var start = null; function easeInOutCubic(t) { return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2; } function frame(timestamp) { if (!self._isActive) { resolve(); return; } if (!start) start = timestamp; var elapsed = timestamp - start; var progress = Math.min(elapsed / ms, 1); var eased = easeInOutCubic(progress); var cx = fromX + (toX - fromX) * eased; var cy = fromY + (toY - fromY) * eased; c.style.left = cx + 'px'; c.style.top = cy + 'px'; if (progress < 1) { requestAnimationFrame(frame); } else { resolve(); } } requestAnimationFrame(frame); }); }, _wait(ms) { return new Promise(function(resolve) { setTimeout(resolve, ms); }); }, // ----------------------------------------------------------------------- // Drag-Demo // ----------------------------------------------------------------------- async _simulateDrag() { this._demoRunning = true; var el = document.querySelector('[gs-id="lagebild"] .card-header'); if (!el) { this._demoRunning = false; this._enableNavAfterDemo(); return; } var rect = el.getBoundingClientRect(); var startX = rect.left + rect.width / 2; var startY = rect.top + rect.height / 2; var endX = startX + 200; var endY = startY; this._showCursor(startX, startY, 'default'); await this._wait(500); this._els.cursor.classList.remove('tutorial-cursor-default'); this._els.cursor.classList.add('tutorial-cursor-grabbing'); await this._wait(300); await this._animateCursor(startX, startY, endX, endY, 1500); await this._wait(200); this._els.cursor.classList.remove('tutorial-cursor-grabbing'); this._els.cursor.classList.add('tutorial-cursor-default'); await this._animateCursor(endX, endY, startX, startY, 800); await this._wait(300); this._hideCursor(); this._demoRunning = false; this._enableNavAfterDemo(); }, // ----------------------------------------------------------------------- // Resize-Demo // ----------------------------------------------------------------------- async _simulateResize() { this._demoRunning = true; var el = document.querySelector('[gs-id="faktencheck"]'); if (!el) { this._demoRunning = false; this._enableNavAfterDemo(); return; } var rect = el.getBoundingClientRect(); var startX = rect.right - 4; var startY = rect.bottom - 4; var endX = startX + 100; var endY = startY + 60; this._showCursor(startX, startY, 'resize'); await this._wait(500); await this._animateCursor(startX, startY, endX, endY, 1200); await this._wait(200); await this._animateCursor(endX, endY, startX, startY, 800); await this._wait(300); this._hideCursor(); this._demoRunning = false; this._enableNavAfterDemo(); }, _enableNavAfterDemo() { var bubble = this._els.bubble; var nav = bubble.querySelector('.tutorial-bubble-nav'); if (!nav) return; var index = this._currentStep; var total = this._steps.length; var navHtml = ''; if (index > 0) { navHtml += ''; } else { navHtml += ''; } if (index < total - 1) { navHtml += ''; } else { navHtml += ''; } nav.innerHTML = navHtml; }, // ----------------------------------------------------------------------- // Keyboard // ----------------------------------------------------------------------- _onKey(e) { if (!this._isActive) return; switch (e.key) { case 'Escape': e.preventDefault(); this.stop(); break; case 'ArrowRight': case 'Enter': e.preventDefault(); this.next(); break; case 'ArrowLeft': e.preventDefault(); this.prev(); break; } }, // ----------------------------------------------------------------------- // Resize // ----------------------------------------------------------------------- _onResize() { if (!this._isActive) return; clearTimeout(this._resizeTimer); var self = this; this._resizeTimer = setTimeout(function() { if (!self._isActive || self._currentStep < 0) return; var step = self._steps[self._currentStep]; if (step.target && step.position !== 'center') { self._spotlightElement(step.target); } self._positionBubble(step); }, 150); }, // ----------------------------------------------------------------------- // Persistenz // ----------------------------------------------------------------------- _markSeen() { try { localStorage.setItem('osint_tutorial_seen', '1'); } catch(e) {} }, _hasSeen() { try { return localStorage.getItem('osint_tutorial_seen') === '1'; } catch(e) { return false; } }, };