/**
 * 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,
    _lastExitedStep: -1,
    _highestStep: -1,
    _stepTimers: [],      // setTimeout-IDs fuer den aktuellen Step
    _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: '<div class="incident-item active tutorial-demo" data-id="demo">'
        + '<span class="incident-dot active" aria-hidden="true"></span>'
        + '<div style="flex:1;min-width:0;">'
        + '<div class="incident-name">Explosion in Hamburger Hafen</div>'
        + '<div class="incident-meta">14 Artikel &middot; demo-user</div>'
        + '</div>'
        + '<span class="badge badge-auto" role="img" aria-label="Auto-Refresh aktiv">&#x21bb;</span>'
        + '</div>',

    _DEMO_SIDEBAR_RESEARCH: '<div class="incident-item tutorial-demo" data-id="demo-research">'
        + '<span class="incident-dot active" aria-hidden="true"></span>'
        + '<div style="flex:1;min-width:0;">'
        + '<div class="incident-name">Analyse: Cyberangriffe auf Kritische Infrastruktur 2026</div>'
        + '<div class="incident-meta">8 Artikel &middot; demo-user</div>'
        + '</div>'
        + '</div>',

    _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" 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" 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" style="color:var(--accent);cursor:pointer;">[4]</span>. '
        + 'Die Ursache ist noch unklar '
        + '<span class="source-ref" data-index="5" title="Hamburger Abendblatt" style="color:var(--accent);cursor:pointer;">[5]</span>.</p>',

    _DEMO_FACTCHECKS: [
        { status: 'confirmed', icon: '&#10003;', claim: 'Eine Explosion ereignete sich am 16.03.2026 gegen 06:45 Uhr im Hamburger Hafen.', sources: 5 },
        { status: 'confirmed', icon: '&#10003;', claim: 'Mindestens 12 Personen wurden bei dem Vorfall verletzt.', sources: 3 },
        { status: 'established', icon: '&#10003;', 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: '&#9888;', claim: 'Die Rauchwolke soll gesundheitsgefährdende Stoffe enthalten.', sources: 2 },
        { status: 'contradicted', icon: '&#10007;', claim: 'Der gesamte Hamburger Hafen wurde geschlossen.', sources: 2 },
    ],

    _DEMO_SOURCES_STATS: '5 Kategorien &middot; 14 Quellen &middot; 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' },
    ],

    _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
    // -----------------------------------------------------------------------
    _injectDemoView() {
        // Zustand sichern
        var incidentView = document.getElementById('incident-view');
        var emptyState = document.getElementById('empty-state');
        this._savedState = {
            gridWasInitialized: typeof LayoutManager !== 'undefined' && LayoutManager._initialized,
            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 = '<span class="auto-badge">Auto-Refresh: 15 Min</span>';
        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 = '';

        // GridStack initialisieren falls noch nicht geschehen
        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.engine.nodes.map(function(n) { return { id: n.el ? n.el.getAttribute("gs-id") : n.id, x: n.x, y: n.y, w: n.w, h: n.h }; });
            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) {
                LayoutManager._grid.update(n.el, { x: n.x, y: n.y, w: n.w, h: n.h });
            });
            // Sicherheits-Relayout
            setTimeout(function() {
                if (LayoutManager._grid) {
                    LayoutManager._grid.compact();
                    LayoutManager._grid.engine.nodes.forEach(function(n) {
                        if (n.el) n.el.style.position = '';
                    });
                }
            }, 100);
        }

        // 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 += '<div class="factcheck-item" data-fc-status="' + fc.status + '">'
                    + '<div class="factcheck-icon ' + fc.status + '" aria-hidden="true">' + fc.icon + '</div>'
                    + '<div style="flex:1;">'
                    + '<div class="factcheck-claim">' + fc.claim + '</div>'
                    + '<div style="display:flex;align-items:center;gap:6px;margin-top:2px;">'
                    + '<span class="factcheck-sources">' + fc.sources + ' Quelle' + (fc.sources !== 1 ? 'n' : '') + '</span>'
                    + '</div></div></div>';
            });
            fcList.innerHTML = fcHtml;
        }

        // Faktencheck-Filter
        var fcFilters = document.getElementById('fc-filters');
        if (fcFilters) {
            fcFilters.innerHTML = '<button class="fc-dropdown-toggle" style="pointer-events:none;">Filter</button>';
        }

        // Quellenübersicht
        var sourceStats = document.getElementById('source-overview-header-stats');
        if (sourceStats) sourceStats.innerHTML = this._DEMO_SOURCES_STATS;

        // Timeline (Achsen-basiert wie im Original)
        var timeline = document.getElementById('timeline');
        if (timeline) {
            timeline.innerHTML = this._buildDemoTimelineHTML();
        }
        var articleCount = document.getElementById('article-count');
        if (articleCount) articleCount.textContent = '7 Einträge';

        // Karte: Echte Leaflet-Map in der Kachel initialisieren
        var mapEmpty = document.getElementById('map-empty');
        if (mapEmpty) mapEmpty.style.display = 'none';
        var mapStats = document.getElementById('map-stats');
        if (mapStats) mapStats.textContent = '3 Orte / 9 Artikel';
        this._initDemoMapInTile();

        // 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;

        // Demo-Map entfernen (Kachel + Fullscreen)
        this._destroyDemoMap();

        // 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 = '';

        // Gespeichertes Layout wiederherstellen
        if (typeof LayoutManager !== 'undefined' && LayoutManager._grid && s.savedLayout) {
            LayoutManager._applyLayout(s.savedLayout);
        }

        this._savedState = null;
    },

    // -----------------------------------------------------------------------
    // Demo-Karte mit Leaflet
    // -----------------------------------------------------------------------
    _demoMap: null,
    _demoMapMarkers: [],
    _demoMapLegend: null,
    _demoMapTileMap: null,  // Map-Instanz in der Kachel

    _DEMO_MAP_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' },
        { lat: 53.5380, lon: 9.9400, name: 'Elbe / Hafengebiet', articles: 1, cat: 'tertiary' },
    ],
    _DEMO_MAP_COLORS: { primary: '#EF4444', secondary: '#F59E0B', tertiary: '#3B82F6' },
    _DEMO_MAP_LABELS: { primary: 'Hauptereignisort', secondary: 'Erwähnt', tertiary: 'Kontext' },

    _createDemoMarkers(map) {
        var markers = [];
        var self = this;
        this._DEMO_MAP_LOCATIONS.forEach(function(loc) {
            var color = self._DEMO_MAP_COLORS[loc.cat];
            var icon = L.divIcon({
                className: 'tutorial-map-marker',
                html: '<div style="width:14px;height:14px;border-radius:50%;background:' + color
                    + ';border:2px solid #fff;box-shadow:0 2px 8px rgba(0,0,0,0.5);"></div>',
                iconSize: [14, 14],
                iconAnchor: [7, 7],
            });
            var marker = L.marker([loc.lat, loc.lon], { icon: icon });
            var label = self._DEMO_MAP_LABELS[loc.cat];
            var popupHtml = '<div class="map-popup">'
                + '<div class="map-popup-title">' + loc.name + '</div>'
                + '<div style="font-size:11px;margin:4px 0;"><span style="display:inline-block;width:10px;height:10px;border-radius:50%;background:' + color + ';margin-right:4px;vertical-align:middle;"></span>' + label + '</div>'
                + '<div class="map-popup-count">' + loc.articles + ' Artikel</div>'
                + '</div>';
            marker.bindPopup(popupHtml, { maxWidth: 250, className: 'map-popup-container' });
            marker.addTo(map);
            markers.push(marker);
        });
        return markers;
    },

    _addDemoLegend(map) {
        var self = this;
        var legend = L.control({ position: 'bottomright' });
        legend.onAdd = function() {
            var div = L.DomUtil.create('div', 'map-legend-ctrl');
            L.DomEvent.disableClickPropagation(div);
            var html = '<strong style="display:block;margin-bottom:6px;">Legende</strong>';
            ['primary', 'secondary', 'tertiary'].forEach(function(cat) {
                html += '<div class="map-legend-item" style="display:flex;align-items:center;gap:6px;margin:3px 0;">'
                    + '<span style="width:10px;height:10px;border-radius:50%;background:' + self._DEMO_MAP_COLORS[cat] + ';flex-shrink:0;"></span>'
                    + '<span>' + self._DEMO_MAP_LABELS[cat] + '</span></div>';
            });
            div.innerHTML = html;
            return div;
        };
        legend.addTo(map);
        return legend;
    },

    // Map in der Dashboard-Kachel initialisieren
    _initDemoMapInTile() {
        if (typeof L === 'undefined') return;
        var container = document.getElementById('map-container');
        if (!container) return;

        // Container-Höhe sicherstellen
        var gsItem = container.closest('.grid-stack-item');
        if (gsItem) {
            var hdr = container.closest('.map-card');
            var headerEl = hdr ? hdr.querySelector('.card-header') : null;
            var headerH = headerEl ? headerEl.offsetHeight : 40;
            var available = gsItem.offsetHeight - headerH - 4;
            container.style.height = Math.max(available, 200) + 'px';
        }

        this._demoMapTileMap = L.map(container, {
            zoomControl: true,
            attributionControl: false,
        }).setView([53.545, 9.98], 12);

        L.tileLayer('https://tile.openstreetmap.de/{z}/{x}/{y}.png', {
            maxZoom: 18, noWrap: true,
        }).addTo(this._demoMapTileMap);

        this._createDemoMarkers(this._demoMapTileMap);
        this._addDemoLegend(this._demoMapTileMap);

        var map = this._demoMapTileMap;
        [200, 500, 1000].forEach(function(d) {
            setTimeout(function() { if (map) map.invalidateSize(); }, d);
        });
    },

    _destroyDemoMap() {
        if (this._demoMap) {
            this._demoMap.remove();
            this._demoMap = null;
        }
        if (this._demoMapTileMap) {
            this._demoMapTileMap.remove();
            this._demoMapTileMap = null;
        }
        this._demoMapMarkers = [];
        this._demoMapLegend = null;
    },

    // -----------------------------------------------------------------------
    // Highlight-Helfer: Einzelnes Sub-Element innerhalb einer Kachel markieren
    // -----------------------------------------------------------------------
    _highlightSub(selector) {
        // Ignorieren wenn der Step bereits verlassen wurde
        if (!this._isActive) return;
        var el = document.querySelector(selector);
        if (!el) return;
        el.classList.add('tutorial-sub-highlight');
    },

    _clearSubHighlights() {
        document.querySelectorAll('.tutorial-sub-highlight').forEach(function(el) {
            el.classList.remove('tutorial-sub-highlight');
        });
    },

    _cleanupFns: [],

    // Timer-Helfer: Timeouts die beim Step-Wechsel automatisch gecancelt werden
    _stepTimeout(fn, ms) {
        var id = setTimeout(fn, ms);
        this._stepTimers.push(id);
        return id;
    },

    _clearStepTimers() {
        this._stepTimers.forEach(function(id) { clearTimeout(id); });
        this._stepTimers = [];
    },

    // Sichere Demo-Ausfuehrung: Faengt Fehler ab und stellt Navigation sicher
    _runDemo(fn) {
        var self = this;
        var finished = false;
        function done() {
            if (finished) return;
            finished = true;
            self._hideCursor();
            self._demoRunning = false;
            self._enableNavAfterDemo();
        }
        // Fallback-Timeout: Nach 30s wird Demo auf jeden Fall beendet
        var fallback = setTimeout(done, 30000);
        try {
            var result = fn.call(this);
            if (result && typeof result.then === 'function') {
                result.then(function() {
                    clearTimeout(fallback);
                    // Demo-Methode hat _enableNavAfterDemo selbst aufgerufen,
                    // aber falls nicht, machen wir es hier
                    if (self._demoRunning) done();
                }).catch(function(e) {
                    clearTimeout(fallback);
                    done();
                });
            } else {
                // Synchron beendet
                clearTimeout(fallback);
                if (self._demoRunning) done();
            }
        } catch(e) {
            clearTimeout(fallback);
            done();
        }
    },

    // -----------------------------------------------------------------------
    // 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.<br><br>'
                    + '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:<br><br>'
                    + '<strong>Live-Monitoring</strong> - Laufende Ereignisbeobachtung mit automatischer Aktualisierung<br>'
                    + '<strong>Recherchen</strong> - Themenanalysen ohne Echtzeit-Updates<br>'
                    + '<strong>Archiv</strong> - Abgeschlossene Lagen zur späteren Einsicht<br><br>'
                    + 'Klicken Sie auf eine Lage, um sie im Hauptbereich zu öffnen.',
                position: 'right',
            },
            // 2 - Neuer Fall Button
            {
                id: 'new-incident-btn',
                target: '#new-incident-btn',
                title: 'Neuen Fall anlegen',
                text: 'Mit diesem Button \u00f6ffnen Sie das Formular zur Erstellung eines neuen Falls. '
                    + 'Klicken Sie auf <strong>Weiter</strong>, um das Formular zu \u00f6ffnen und alle Felder durchzugehen.',
                position: 'right',
                onExit: function() {
                    // Modal oeffnen fuer den naechsten Step
                    var nextStep = Tutorial._steps[Tutorial._currentStep];
                    var nextIsModal = nextStep && nextStep.target && nextStep.target.indexOf('#modal-') !== -1;
                    if (nextIsModal) {
                        var overlay = document.getElementById('modal-new');
                        if (overlay && !overlay.classList.contains('active')) overlay.classList.add('active');
                    }
                },
            },
            // 3 - Titel und Beschreibung (Cursor-Demo)
            {
                id: 'form-title-desc',
                target: '#modal-new .modal',
                bubbleTarget: '#inc-title',
                title: 'Titel und Beschreibung',
                text: 'Geben Sie einen aussagekräftigen <strong>Titel</strong> ein, der das Ereignis klar beschreibt. '
                    + 'Die <strong>Beschreibung</strong> liefert zusätzlichen Kontext für die Recherche.<br><br>'
                    + 'Beobachten Sie die Eingabe:',
                position: 'left',
                disableNav: true,
                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._scrollModalTo('#inc-title');
                    var t = document.getElementById('inc-title'); if (t) t.value = '';
                    var d = document.getElementById('inc-description'); if (d) d.value = '';
                    Tutorial._runDemo(Tutorial._simulateFormTitleDesc);
                },
                onExit: function() {
                    Tutorial._clearSubHighlights();
                },
            },
            // 4 - Art der Lage (Cursor-Demo)
            {
                id: 'form-type',
                target: '#modal-new .modal',
                bubbleTarget: '#inc-type',
                title: 'Art der Lage',
                text: '<strong>Live-Monitoring</strong> beobachtet ein Ereignis in Echtzeit. Hunderte Quellen werden '
                    + 'laufend durchsucht. Ideal für aktuelle Krisen und sich entwickelnde Lagen.<br><br>'
                    + '<strong>Recherche</strong> analysiert ein Thema tiefergehend ohne automatische Updates. '
                    + 'Ideal für Hintergrundanalysen und Lageberichte.',
                position: 'left',
                disableNav: true,
                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._scrollModalTo('#inc-type');
                    Tutorial._stepTimeout(function() {
                        Tutorial._highlightSub('#inc-type');
                        Tutorial._runDemo(Tutorial._simulateTypeSwitch);
                    }, 600);
                },
                onExit: function() {
                    var sel = document.getElementById('inc-type');
                    if (sel) { sel.value = 'adhoc'; try { sel.dispatchEvent(new Event('change')); } catch(e) {} }
                    Tutorial._clearSubHighlights();
                },
            },
            // 5 - Quellen
            {
                id: 'form-sources',
                target: '#modal-new .modal',
                bubbleTarget: '#inc-international',
                title: 'Quellen konfigurieren',
                text: '<strong>Internationale Quellen</strong> bezieht englischsprachige und internationale Medien '
                    + 'ein (Reuters, BBC, Al Jazeera etc.). Erhöht die Abdeckung, aber auch den Analyseumfang.<br><br>'
                    + '<strong>Telegram-Kanäle</strong> liefern oft frühzeitige OSINT-Informationen, '
                    + 'können aber auch unbestätigte Meldungen enthalten. Für sensible Lagen empfohlen.',
                position: 'left',
                disableNav: true,
                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._runDemo(Tutorial._simulateFormSources);
                },
                onExit: function() {
                    Tutorial._clearSubHighlights();
                },
            },
            // 6 - Sichtbarkeit
            {
                id: 'form-visibility',
                target: '#modal-new .modal',
                bubbleTarget: '#inc-visibility',
                title: 'Sichtbarkeit',
                text: '<strong>Öffentlich</strong> bedeutet, dass alle Nutzer Ihrer Organisation diese Lage sehen '
                    + 'und darauf zugreifen können.<br><br>'
                    + '<strong>Privat</strong> macht die Lage nur für Sie persönlich sichtbar. '
                    + 'Nützlich für persönliche Recherchen oder sensible Vorgänge.',
                position: 'left',
                disableNav: true,
                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._runDemo(Tutorial._simulateFormVisibility);
                },
                onExit: function() {
                    Tutorial._clearSubHighlights();
                },
            },
            // 7 - Aktualisierung und Intervall
            {
                id: 'form-refresh',
                target: '#modal-new .modal',
                bubbleTarget: '#inc-refresh-mode',
                title: 'Aktualisierung',
                text: '<strong>Manuell</strong>: Sie starten Aktualisierungen selbst per Button.<br>'
                    + '<strong>Automatisch</strong>: Der Monitor aktualisiert im eingestellten Intervall.<br><br>'
                    + '<strong>Wichtig:</strong> Kürzere Intervalle liefern aktuellere Daten, '
                    + 'erhöhen aber den Creditverbrauch. Für die meisten Lagen sind 15 bis 30 Minuten ein guter Richtwert.',
                position: 'left',
                disableNav: true,
                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._runDemo(Tutorial._simulateFormRefresh);
                },
                onExit: function() {
                    Tutorial._clearSubHighlights();
                },
            },
            // 8 - Aufbewahrung
            {
                id: 'form-retention',
                target: '#modal-new .modal',
                bubbleTarget: '#inc-retention',
                title: 'Aufbewahrung',
                text: 'Legen Sie fest, wie lange die Lage aktiv bleibt. Nach Ablauf der Frist '
                    + 'wird sie automatisch ins <strong>Archiv</strong> verschoben.<br><br>'
                    + 'Setzen Sie den Wert auf <strong>0</strong> für unbegrenzte Aufbewahrung. '
                    + 'Standard sind 30 Tage.',
                position: 'left',
                disableNav: true,
                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._runDemo(Tutorial._simulateFormRetention);
                },
                onExit: function() {
                    Tutorial._clearSubHighlights();
                },
            },
            // 9 - E-Mail-Benachrichtigungen
            {
                id: 'form-notifications',
                target: '#modal-new .modal',
                bubbleTarget: '#inc-notify-summary',
                title: 'E-Mail-Benachrichtigungen',
                text: 'Lassen Sie sich per E-Mail informieren bei:<br><br>'
                    + '<strong>Neues Lagebild</strong> - Wenn eine aktualisierte Zusammenfassung vorliegt<br>'
                    + '<strong>Neue Artikel</strong> - Wenn neue Quellen gefunden werden<br>'
                    + '<strong>Statusänderung Faktencheck</strong> - Wenn sich die Bewertung einer Behauptung ändert<br><br>'
                    + 'So bleiben Sie auch ohne ständiges Einloggen auf dem Laufenden.',
                position: 'left',
                disableNav: true,
                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._runDemo(Tutorial._simulateFormNotifications);
                },
                onExit: function() {
                    var overlay = document.getElementById('modal-new');
                    if (overlay) {
                        overlay.classList.remove('active');
                        overlay.style.zIndex = '';
                    }
                    Tutorial._clearSubHighlights();
                },
            },
            // Sidebar Filter
            {
                id: 'sidebar-filters',
                target: '.sidebar-filter',
                title: 'Lagen filtern',
                text: 'Mit diesen Filtern steuern Sie, welche Lagen angezeigt werden:<br><br>'
                    + '<strong>Alle</strong> - Zeigt s\u00e4mtliche Lagen Ihrer Organisation<br>'
                    + '<strong>Eigene</strong> - Nur Lagen, die Sie selbst erstellt haben<br><br>'
                    + 'Bei vielen Lagen hilft dies, den \u00dcberblick zu behalten.',
                position: 'right',
                disableNav: true,
                onEnter: function() {
                    Tutorial._runDemo(Tutorial._simulateFilterSwitch);
                },
                onExit: function() {
                    Tutorial._clearSubHighlights();
                    Tutorial._hideCursor();
                },
            },
            // 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.<br><br>'
                    + '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:<br><br>'
                    + '<strong>Typ-Badge</strong> - Live-Monitoring oder Recherche<br>'
                    + '<strong>Auto-Refresh</strong> - Zeigt das Aktualisierungsintervall<br>'
                    + '<strong>Aktionsleiste</strong> - Aktualisieren, Bearbeiten, Exportieren, Archivieren und Löschen<br><br>'
                    + '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.<br><br>'
                    + '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:<br><br>'
                    + '<strong>Lagebericht (Markdown/JSON)</strong> - Kompakte Zusammenfassung mit Faktencheck<br>'
                    + '<strong>Vollexport</strong> - Alle Daten inklusive Artikel und Quellen<br>'
                    + '<strong>Drucken / PDF</strong> - Druckoptimierte Ansicht für Berichte<br><br>'
                    + '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:<br><br>'
                    + 'Jeder Button steht für eine Kachel: <strong>Lagebild</strong>, <strong>Faktencheck</strong>, '
                    + '<strong>Quellen</strong>, <strong>Timeline</strong> und <strong>Karte</strong>.<br><br>'
                    + '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.<br><br>'
                    + '<strong>Quellenverweise</strong> - Die nummerierten Verweise [1], [2] etc. verlinken '
                    + 'direkt zu den Originalartikeln. So können Sie jede Aussage nachprüfen.<br>'
                    + '<strong>Vollansicht</strong> - Klicken Sie auf "Lagebild" in der Kopfzeile für eine '
                    + 'große Darstellung mit mehr Platz zum Lesen.<br>'
                    + '<strong>Zeitstempel</strong> - 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.<br><br>'
                    + '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:<br><br>'
                    + '<strong style="color:var(--success);">&#10003; Bestätigt/Gesichert</strong> - Durch mehrere unabhängige Quellen belegt<br>'
                    + '<strong style="color:var(--warning);">? Unbestätigt</strong> - Nur aus einer Quelle bekannt<br>'
                    + '<strong style="color:var(--warning);">&#9888; Umstritten</strong> - Quellen widersprechen sich<br>'
                    + '<strong style="color:var(--error);">&#10007; Widerlegt</strong> - Zuverlässige Quellen widersprechen',
                position: 'left',
                onEnter: function() {
                    Tutorial._highlightSub('#factcheck-card');
                },
                onExit: function() {
                    Tutorial._clearSubHighlights();
                },
            },
            // Faktencheck: Status-Demo
            {
                id: 'faktencheck-detail',
                target: '[gs-id="faktencheck"]',
                title: 'Faktencheck-Eintrag',
                text: 'Jeder Faktencheck-Eintrag besteht aus:<br><br>'
                    + '<strong>Status-Symbol</strong> - Farbcodiert f\u00fcr schnelle Einordnung<br>'
                    + '<strong>Behauptung</strong> - Die gepr\u00fcfte Aussage<br>'
                    + '<strong>Quellenanzahl</strong> - Wie viele Quellen diese Behauptung st\u00fctzen<br><br>'
                    + 'Beobachten Sie, wie der Cursor die verschiedenen Status durchgeht:',
                position: 'left',
                disableNav: true,
                onEnter: function() {
                    Tutorial._runDemo(Tutorial._simulateFactcheckStati);
                },
                onExit: function() {
                    Tutorial._clearSubHighlights();
                    Tutorial._hideCursor();
                },
            },
            // Ereignis-Timeline
            {
                id: 'timeline',
                target: '[gs-id="timeline"]',
                deferSpotlight: true,
                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: 'bottom',
                disableNav: true,
                onEnter: function() {
                    // Timeline-Kachel vergroessern damit Detail-Panel + Achse sichtbar
                    var tile = document.querySelector('[gs-id="timeline"]');
                    if (tile && typeof LayoutManager !== 'undefined' && LayoutManager._grid) {
                        var node = LayoutManager._grid.engine.nodes.find(function(n) { return n.el === tile; });
                        if (node) {
                            Tutorial._savedTimelineH = node.h;
                            LayoutManager._grid.update(tile, { h: Math.max(node.h, 5) });
                            LayoutManager._grid.compact();
                        }
                    }
                    // Nach GridStack-Update: Spotlight + Bubble neu positionieren
                    Tutorial._stepTimeout(function() {
                        if (tile) tile.scrollIntoView({ behavior: 'smooth', block: 'start' });
                        var step = Tutorial._steps[Tutorial._currentStep];
                        if (step && step.id === 'timeline') {
                            Tutorial._spotlightElement(step.target);
                            Tutorial._positionBubble(step);
                            Tutorial._els.bubble.classList.add('visible');
                        }
                    }, 300);
                    Tutorial._runDemo(Tutorial._simulateTimeline);
                },
                onExit: function() {
                    // Kachel-Hoehe wiederherstellen
                    if (Tutorial._savedTimelineH) {
                        var tile = document.querySelector('[gs-id="timeline"]');
                        if (tile && typeof LayoutManager !== 'undefined' && LayoutManager._grid) {
                            LayoutManager._grid.update(tile, { h: Tutorial._savedTimelineH });
                        }
                        delete Tutorial._savedTimelineH;
                    }
                    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>'
                    + 'Die <strong>Legende</strong> unten rechts erkl\u00e4rt die Farbkategorien. '
                    + 'Klicken Sie auf einen Marker f\u00fcr Details und verkn\u00fcpfte Artikel.',
                position: 'left',
                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
            {
                id: 'quellen',
                target: '[gs-id="quellen"]',
                title: 'Quellenübersicht',
                text: 'Die Quellenübersicht zeigt alle für diese Lage verwendeten Quellen, gruppiert nach Kategorie:<br><br>'
                    + '<strong>Nachrichtenagenturen</strong> - dpa, Reuters, AFP<br>'
                    + '<strong>Öffentlich-Rechtlich</strong> - tagesschau, ZDF, NDR<br>'
                    + '<strong>Qualitätszeitungen</strong> - FAZ, SZ, ZEIT<br>'
                    + '<strong>Telegram</strong> - OSINT-Kanäle und Nachrichtenkanäle<br>'
                    + '<strong>Behörden</strong> - Offizielle Stellen und Pressemitteilungen<br><br>'
                    + '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();
                },
            },
            // Dashboard anpassen (Drag + Resize)
            {
                id: 'layout-demo',
                target: '[gs-id="lagebild"] .card-header',
                title: 'Dashboard anpassen',
                text: 'Alle Kacheln lassen sich frei verschieben und in der Gr\u00f6\u00dfe anpassen. '
                    + 'Greifen Sie die Kopfzeile zum Verschieben oder den unteren Rand zum Vergr\u00f6\u00dfern.<br><br>'
                    + 'Beobachten Sie die Demo: Erst wird das Lagebild verschoben, dann vergr\u00f6\u00dfert.',
                position: 'right',
                disableNav: true,
                onEnter: function() {
                    // Spotlight ausblenden waehrend Drag-Demo
                    Tutorial._els.spotlight.style.opacity = '0';
                    Tutorial._runDemo(Tutorial._simulateLayoutDemo);
                },
                onExit: function() {
                    if (typeof LayoutManager !== 'undefined' && LayoutManager._grid) {
                        LayoutManager._applyLayout(LayoutManager.DEFAULT_LAYOUT);
                    }
                    Tutorial._hideCursor();
                },
            },
            // Design umschalten
            {
                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\u00e4chsten Besuch beibehalten.',
                position: 'bottom',
                disableNav: true,
                onEnter: function() {
                    Tutorial._runDemo(Tutorial._simulateThemeToggle);
                },
                onExit: function() {
                    Tutorial._hideCursor();
                    // Sicherstellen dass Dark-Theme aktiv ist
                    if (document.documentElement.getAttribute('data-theme') === 'light') {
                        var btn = document.getElementById('theme-toggle');
                        if (btn) btn.click();
                    }
                },
            },
            // Quellen verwalten
            {
                id: 'sources-btn',
                target: '.sidebar-sources-link button:first-child',
                title: 'Quellenverwaltung \u00f6ffnen',
                text: 'In der Seitenleiste ganz unten finden Sie den Zugang zur Quellenverwaltung. '
                    + 'Hier k\u00f6nnen Sie:<br><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>Quellen deaktivieren</strong> - Tempor\u00e4r oder dauerhaft ausschlie\u00dfen',
                position: 'top',
                onEnter: function() {
                    // Button sichtbar halten per z-index
                    var srcLink = document.querySelector('.sidebar-sources-link');
                    if (srcLink) srcLink.style.zIndex = '9003';
                    Tutorial._stepTimeout(function() {
                        var overlay = document.getElementById('modal-sources');
                        if (overlay && !overlay.classList.contains('active')) {
                            if (typeof App !== 'undefined' && App.openSourceManagement) {
                                App.openSourceManagement();
                            }
                        }
                    }, 1500);
                },
                onExit: function() {
                    var srcLink = document.querySelector('.sidebar-sources-link');
                    if (srcLink) srcLink.style.zIndex = '';
                    var overlay = document.getElementById('modal-sources');
                    if (overlay) {
                        overlay.classList.remove('active');
                        overlay.style.zIndex = '';
                    }
                },
            },
            // 22 - Quellen Modal: Info-Icon + Tooltip
            {
                id: 'sources-modal',
                target: '#modal-sources .modal',
                title: 'Quellendetails anzeigen',
                text: 'Jede Quelle hat ein <strong>Info-Symbol (i)</strong>, das Details wie Typ, Sprache und '
                    + 'Ausrichtung anzeigt. Beobachten Sie den Tooltip:<br><br>'
                    + 'Klicken Sie auf <strong>Weiter</strong>, wenn Sie den Tooltip gelesen haben.',
                position: 'left',
                disableNav: true,
                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';
                    Tutorial._stepTimeout(function() {
                        Tutorial._runDemo(Tutorial._simulateSourcesInfoIcon);
                    }, 1500);
                },
                onExit: function() {
                    var tip = document.getElementById('tutorial-tooltip');
                    if (tip) tip.remove();
                    Tutorial._clearSubHighlights();
                    Tutorial._hideCursor();
                },
            },
            // 23 - Quellen Modal: Aktionen
            {
                id: 'sources-modal-actions',
                target: '#modal-sources .modal',
                title: 'Quellen verwalten',
                text: '<strong>+ Quelle</strong> - Neue Quellen per URL oder Domain hinzuf\u00fcgen. '
                    + 'Der Monitor erkennt RSS-Feeds automatisch.<br><br>'
                    + '<strong>Ausschlie\u00dfen</strong> - Sperrt eine Quelle f\u00fcr einzelne Lagen, '
                    + 'ohne sie global zu deaktivieren.<br><br>'
                    + '<strong>Kategorie-Badge</strong> - Farbige Kennzeichnung der Quellkategorie '
                    + '(Nachrichtenagentur, \u00d6ffentlich-Rechtlich, etc.)',
                position: 'left',
                disableNav: true,
                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';
                    Tutorial._runDemo(Tutorial._simulateSourcesActions);
                },
                onExit: function() {
                    var overlay = document.getElementById('modal-sources');
                    if (overlay) {
                        overlay.classList.remove('active');
                        overlay.style.zIndex = '';
                    }
                    Tutorial._clearSubHighlights();
                    Tutorial._hideCursor();
                },
            },
            // Chat-Assistent
            {
                id: 'chat',
                target: '#chat-toggle-btn',
                title: 'Chat-Assistent',
                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>'
                    + 'Beispiele:<br>'
                    + '"Wie erstelle ich einen neuen Fall?"<br>'
                    + '"Was bedeuten die Faktencheck-Status?"<br>'
                    + '"Wie exportiere ich einen Lagebericht?"',
                position: 'top',
            },
            // Ende
            {
                id: 'end',
                target: null,
                title: 'Rundgang abgeschlossen',
                text: 'Sie kennen jetzt alle wichtigen Funktionen des AegisSight Monitors.<br><br>'
                    + 'Die Demo-Daten werden nach dem Schlie\u00dfen entfernt. '
                    + 'Erstellen Sie Ihre erste eigene Lage \u00fcber den Button "+ Neuer Fall" in der Seitenleiste.<br><br>'
                    + 'Bei weiteren Fragen steht Ihnen der Chat-Assistent '
                    + 'oder unser Support unter support@aegis-sight.de zur Verf\u00fcgung.',
                position: 'center',
                onEnter: function() {
                    Tutorial._stepTimeout(function() {
                        var bubble = document.getElementById('tutorial-bubble');
                        if (bubble) bubble.style.top = '55%';
                    }, 50);
                },
            },
        ];
    },

    // -----------------------------------------------------------------------
    // Lifecycle
    // -----------------------------------------------------------------------
    async start(forceRestart) {
        // Bei Restart: laufendes Tutorial erst sauber beenden
        if (this._isActive && forceRestart) {
            this._isRestarting = true;
            this.stop();
        }
        if (this._isActive) return;

        // Chat schliessen falls offen
        if (typeof Chat !== 'undefined' && Chat._isOpen) Chat.close();

        // Server-State laden (Fallback: direkt starten)
        var state = null;
        try { state = await API.getTutorialState(); } catch(e) {}

        // Resume-Dialog wenn mittendrin abgebrochen
        if (!forceRestart && state && !state.completed && state.current_step !== null && state.current_step > 0) {
            this._showResumeDialog(state.current_step);
            return;
        }

        if (forceRestart) {
            try { await API.resetTutorialState(); } catch(e) {}
        }
        this._startInternal(forceRestart ? 0 : null);
    },

    _showResumeDialog(step) {
        var self = this;
        var overlay = document.createElement('div');
        overlay.className = 'tutorial-resume-overlay';
        overlay.innerHTML = '<div class=tutorial-resume-dialog>'
            + '<p>Sie haben den Rundgang bei <strong>Schritt ' + (step + 1) + '/' + this._steps.length + '</strong> unterbrochen.</p>'
            + '<div class=tutorial-resume-actions>'
            + '<button class=tutorial-btn tutorial-btn-next id=tutorial-resume-btn>Fortsetzen</button>'
            + '<button class=tutorial-btn tutorial-btn-secondary id=tutorial-restart-btn>Neu starten</button>'
            + '</div></div>';
        document.body.appendChild(overlay);
        document.getElementById('tutorial-resume-btn').addEventListener('click', function() {
            overlay.remove();
            self._startInternal(step);
        });
        document.getElementById('tutorial-restart-btn').addEventListener('click', async function() {
            overlay.remove();
            try { await API.resetTutorialState(); } catch(e) {}
            self._startInternal(0);
        });
    },

    async _startInternal(resumeStep) {
        this._isActive = true;
        this._highestStep = -1;
        this._currentStep = -1;

        // Server-State auf Anfang setzen wenn Neustart
        if (!resumeStep || resumeStep === 0) {
            try { await API.saveTutorialState({ current_step: 0, completed: false }); } catch(e) {}
        }
        this._isRestarting = false;

        // Overlay einblenden + Klicks blockieren
        this._els.overlay.classList.add('active');
        document.body.classList.add('tutorial-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);

        if (resumeStep && resumeStep > 0) {
            this.goToStep(resumeStep);
        } else {
            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 + Klicks freigeben
        this._els.overlay.classList.remove('active');
        document.body.classList.remove('tutorial-active');
        this._els.spotlight.style.opacity = '0';
        this._els.bubble.classList.remove('visible');
        this._hideCursor();
        this._clearSubHighlights();

        // Step-Timers canceln
        this._clearStepTimers();

        // Alle Modals schliessen die das Tutorial geoeffnet haben koennte
        ['modal-new', 'modal-sources'].forEach(function(id) {
            var overlay = document.getElementById(id);
            if (overlay) {
                overlay.classList.remove('active');
                overlay.style.zIndex = '';
            }
        });

        // Cleanup-Callbacks
        this._cleanupFns.forEach(function(fn) { try { fn(); } catch(e) {} });
        this._cleanupFns = [];

        // Formular-Inputs zuruecksetzen
        var titleInput = document.getElementById('inc-title');
        if (titleInput) titleInput.value = '';
        var descInput = document.getElementById('inc-description');
        if (descInput) descInput.value = '';
        var selType = document.getElementById('inc-type');
        if (selType) { selType.value = 'adhoc'; try { selType.dispatchEvent(new Event('change')); } catch(e) {} }

        // 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;
        }

        // Fortschritt serverseitig speichern (nicht bei Restart)
        if (this._isRestarting) {
            // Restart - State wird von start() zurueckgesetzt
        } else if (this._lastExitedStep >= 0 && this._lastExitedStep < this._steps.length - 1) {
            // Mittendrin abgebrochen — Schritt speichern
            API.saveTutorialState({ current_step: this._lastExitedStep }).catch(function() {});
        } else {
            // Komplett durchlaufen oder letzter Schritt
            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;
        if (index > this._highestStep) this._highestStep = index;
        this._enterStep(index);
    },

    // -----------------------------------------------------------------------
    // Step Enter/Exit
    // -----------------------------------------------------------------------
    async _enterStep(i) {
        var step = this._steps[i];

        // Bei Modal-Steps: Spotlight ausblenden (Modal hat eigene Abdunkelung)
        var isModalStep = step.target && (step.target.indexOf('#modal-') !== -1);

        // Wenn kein Modal-Step: offene Modals schliessen (z.B. bei Zurueck-Navigation)
        if (!isModalStep) {
            ['modal-new', 'modal-sources'].forEach(function(id) {
                var overlay = document.getElementById(id);
                if (overlay && overlay.classList.contains('active')) {
                    overlay.classList.remove('active');
                    overlay.style.zIndex = '';
                }
            });
        }

        if (step.deferSpotlight) {
            // Spotlight wird vom onEnter gesetzt (nach Layout-Aenderung)
            this._els.spotlight.style.opacity = '0';
        } else if (step.target && step.position !== 'center' && !isModalStep) {
            await this._scrollToTarget(step.target);
            this._spotlightElement(step.target);
        } else {
            this._els.spotlight.style.opacity = '0';
        }

        // Modal-Steps: zum bubbleTarget scrollen bevor Bubble positioniert wird
        if (isModalStep && step.bubbleTarget) {
            await this._scrollModalTo(step.bubbleTarget);
        }

        // Bubble konfigurieren und anzeigen
        this._showBubble(step, i);

        // Modal-Steps: Bubble nach Modal-Transition nochmal neu positionieren
        if (isModalStep) {
            var self2 = this;
            setTimeout(function() {
                if (self2._isActive && self2._currentStep === i) {
                    self2._positionBubble(step);
                    // Clamp + Arrow ausfuehren, DANN sichtbar machen
                    requestAnimationFrame(function() {
                        var bubble = self2._els.bubble;
                        var bRect = bubble.getBoundingClientRect();
                        var vH = window.innerHeight;
                        var cTop = parseFloat(bubble.style.top) || 0;
                        if (bRect.bottom > vH - 8) {
                            bubble.style.top = (cTop - (bRect.bottom - vH + 16)) + 'px';
                        }
                        bRect = bubble.getBoundingClientRect();
                        if (bRect.top < 8) {
                            bubble.style.top = (parseFloat(bubble.style.top) + (8 - bRect.top)) + 'px';
                        }
                        var at = step.bubbleTarget || step.target;
                        if (at && (bubble.className.indexOf('pos-left') !== -1 || bubble.className.indexOf('pos-right') !== -1)) {
                            var te = document.querySelector(at);
                            if (te) {
                                bRect = bubble.getBoundingClientRect();
                                var tR = te.getBoundingClientRect();
                                var ay = (tR.top + tR.height / 2) - bRect.top;
                                ay = Math.max(18, Math.min(ay, bRect.height - 18));
                                bubble.style.setProperty('--arrow-top', ay + 'px');
                            }
                        }
                        // Erst jetzt sichtbar machen - Position ist korrekt
                        bubble.classList.add('visible');
                    });
                }
            }, 450);
        }

        // onEnter-Callback
        if (step.onEnter) {
            step.onEnter();
        }
    },

    _exitStep(i) {
        var step = this._steps[i];
        this._clearStepTimers();
        // Laufende Demo abbrechen
        this._demoRunning = false;
        // Step-Index merken um verspaetete Highlight-Aufrufe zu ignorieren
        this._lastExitedStep = 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 vw = window.innerWidth;
        var vh = window.innerHeight;
        var pad = 8;

        // Sichtbaren Bereich berechnen (auf Viewport beschränkt)
        var top = Math.max(rect.top, 0) - pad;
        var left = Math.max(rect.left, 0) - pad;
        var bottom = Math.min(rect.bottom, vh) + pad;
        var right = Math.min(rect.right, vw) + pad;

        var s = this._els.spotlight.style;
        s.top = top + 'px';
        s.left = left + 'px';
        s.width = (right - left) + 'px';
        s.height = (bottom - top) + 'px';
        s.borderRadius = '8px';
        s.opacity = '1';
    },

    // -----------------------------------------------------------------------
    // Bubble
    // -----------------------------------------------------------------------
    _showBubble(step, index) {
        var bubble = this._els.bubble;
        var total = this._steps.length;

        // Inhalt
        var counterHtml = '<div class="tutorial-bubble-counter">Schritt ' + (index + 1) + ' von ' + total + '</div>';
        var titleHtml = '<div class="tutorial-bubble-title">' + step.title + '</div>';
        var textHtml = '<div class="tutorial-bubble-text">' + step.text + '</div>';

        // Fortschrittspunkte
        var dotsHtml = '<div class="tutorial-bubble-dots">';
        for (var d = 0; d < total; d++) {
            dotsHtml += '<span class="tutorial-dot' + (d === index ? ' active' : '') + (d < index ? ' done' : '') + '"></span>';
        }
        dotsHtml += '</div>';

        // Navigation
        var navHtml = '<div class="tutorial-bubble-nav">';
        if (step.disableNav) {
            // Automatische Demo läuft - keine Buttons, wird pulsieren
            navHtml += '<span class="tutorial-demo-hint">Demo läuft...</span>';
        } else {
            if (index > 0) {
                navHtml += '<button class="tutorial-btn tutorial-btn-back" onclick="Tutorial.prev()">Zur\u00fcck</button>';
            } else {
                navHtml += '<span></span>';
            }
            if (index < total - 1) {
                navHtml += '<button class="tutorial-btn tutorial-btn-next" onclick="Tutorial.next()">Weiter</button>';
            } else {
                navHtml += '<button class="tutorial-btn tutorial-btn-next" onclick="Tutorial.stop()">Fertig</button>';
            }
        }
        navHtml += '</div>';

        // Close-Button
        var closeHtml = '<button class="tutorial-bubble-close" onclick="Tutorial.stop()" title="Rundgang beenden">&times;</button>';

        bubble.innerHTML = closeHtml + counterHtml + titleHtml + textHtml + dotsHtml + navHtml;

        // Positionierung
        this._positionBubble(step);

        // Pulsieren bei automatischen Demos
        if (step.disableNav) {
            bubble.classList.add('tutorial-bubble-pulsing');
        } else {
            bubble.classList.remove('tutorial-bubble-pulsing');
        }

        // Bubble erst nach Clamp-Check sichtbar machen (verhindert Springen)
        var isModalBubbleStep = step.bubbleTarget && step.target && step.target.indexOf('#modal-') !== -1;

        // Sicherheitscheck: Bubble komplett im sichtbaren Bereich halten
        var self = this;
        requestAnimationFrame(function() {
            var bRect = bubble.getBoundingClientRect();
            var vHeight = window.innerHeight;
            var currentTop = parseFloat(bubble.style.top) || 0;
            // Transform beruecksichtigen (translateY(-100%) bei top-Position)
            var transform = bubble.style.transform || '';
            var actualTop = bRect.top;
            var actualBottom = bRect.bottom;
            // Unten abgeschnitten
            if (actualBottom > vHeight - 8) {
                var shift = actualBottom - vHeight + 16;
                bubble.style.top = (currentTop - shift) + 'px';
                bubble.style.transform = transform.replace('translateY(-100%)', '');
            }
            // Oben abgeschnitten
            bRect = bubble.getBoundingClientRect();
            if (bRect.top < 8) {
                currentTop = parseFloat(bubble.style.top) || 0;
                bubble.style.top = (currentTop + (8 - bRect.top)) + 'px';
                bubble.style.transform = bubble.style.transform.replace('translateY(-100%)', '');
            }
            // Pfeil dynamisch auf Ziel-Element ausrichten
            var arrowTarget = step.bubbleTarget || step.target;
            if (arrowTarget && (step.position === 'left' || step.position === 'right' || bubble.className.indexOf('pos-left') !== -1 || bubble.className.indexOf('pos-right') !== -1)) {
                var targetEl = document.querySelector(arrowTarget);
                if (targetEl) {
                    bRect = bubble.getBoundingClientRect();
                    var tRect = targetEl.getBoundingClientRect();
                    var arrowY = (tRect.top + tRect.height / 2) - bRect.top;
                    arrowY = Math.max(18, Math.min(arrowY, bRect.height - 18));
                    bubble.style.setProperty('--arrow-top', arrowY + 'px');
                }
            }
            // Jetzt sichtbar machen (nach Clamp + Arrow) - nur fuer nicht-Modal Steps
            if (!isModalBubbleStep) {
                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 bubbleSelector = step.bubbleTarget || step.target;
        var el = document.querySelector(bubbleSelector);
        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();
        // Fuer Modal-Steps: horizontale Positionierung am Modal, vertikale am Feld
        if (step.bubbleTarget && step.target && step.target.indexOf('#modal-') !== -1) {
            var modalEl = document.querySelector(step.target);
            if (modalEl) {
                var modalRect = modalEl.getBoundingClientRect();
                rect = { top: rect.top, bottom: rect.bottom, left: modalRect.left, right: modalRect.right,
                         width: modalRect.width, height: rect.height };
            }
        }
        var vw = window.innerWidth;
        var vh = window.innerHeight;
        var gap = 16;

        // Sichtbaren Bereich des Elements berechnen (auf Viewport beschraenkt)
        var visTop = Math.max(rect.top, 0);
        var visBottom = Math.min(rect.bottom, vh);
        var visLeft = Math.max(rect.left, 0);
        var visRight = Math.min(rect.right, vw);
        var visCenterY = (visTop + visBottom) / 2;
        var visCenterX = (visLeft + visRight) / 2;

        // Reset transform
        bubble.style.transform = '';
        bubble.style.width = bw + 'px';

        // Automatische Positionswahl falls nicht genug Platz
        var pos = step.position || 'bottom';
        var bubbleHeight = 300;
        var isModalBubble = step.bubbleTarget && step.target && step.target.indexOf('#modal-') !== -1;

        if (isModalBubble) {
            // Fuer Modal-Steps: Platz links/rechts vom Modal pruefen
            var modalEl2 = document.querySelector(step.target);
            var mRect = modalEl2 ? modalEl2.getBoundingClientRect() : rect;
            var spaceLeft = mRect.left;
            var spaceRight = vw - mRect.right;
            var bestSpace = Math.max(spaceLeft, spaceRight);
            // Bubble-Breite an verfuegbaren Platz rechts anpassen (min 260px)
            // Modal-Bubbles immer rechts - links ist die Sidebar
            var modalBw = Math.min(bw, spaceRight - gap - 8);
            modalBw = Math.max(modalBw, 260);
            bw = modalBw;
            bubble.style.width = bw + 'px';
            pos = 'right';
        } else {
            if (pos === 'bottom' && (visBottom + gap + bubbleHeight > vh)) pos = 'top';
            if (pos === 'top' && (visTop - gap - bubbleHeight < 0)) pos = 'bottom';
            if (pos === 'right' && (visRight + gap + bw > vw)) pos = 'left';
            if (pos === 'left' && (visLeft - gap - bw < 0)) pos = 'right';
        }

        bubble.className = 'tutorial-bubble visible tutorial-pos-' + pos;

        // Bubble-Top immer im sichtbaren Bereich halten
        var clampTop = function(t) { return Math.max(8, Math.min(t, vh - bubbleHeight - 8)); };

        switch (pos) {
            case 'bottom':
                bubble.style.top = Math.min(visBottom + gap, vh - bubbleHeight - 8) + 'px';
                bubble.style.left = Math.max(8, Math.min(visCenterX - bw / 2, vw - bw - 8)) + 'px';
                break;
            case 'top':
                bubble.style.top = Math.max(visTop - gap, 8) + 'px';
                bubble.style.left = Math.max(8, Math.min(visCenterX - bw / 2, vw - bw - 8)) + 'px';
                bubble.style.transform = 'translateY(-100%)';
                break;
            case 'right':
                bubble.style.top = clampTop(visCenterY - bubbleHeight / 2) + 'px';
                bubble.style.left = Math.min(visRight + gap, vw - bw - 8) + 'px';
                break;
            case 'left':
                bubble.style.top = clampTop(visCenterY - bubbleHeight / 2) + 'px';
                bubble.style.left = Math.max(visLeft - gap - bw, 8) + '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');
    },

    _clickAtCursor() {
        var c = this._els.cursor;
        c.classList.remove('clicking');
        void c.offsetWidth;
        c.classList.add('clicking');
        var self = this;
        return new Promise(function(resolve) {
            setTimeout(function() {
                c.classList.remove('clicking');
                resolve();
            }, 300);
        });
    },

    _cursorToToggle(checkboxSelector, fromX, fromY) {
        var cb = document.querySelector(checkboxSelector);
        if (!cb) return this._cursorToElement(checkboxSelector, fromX, fromY);
        var toggle = cb.parentElement ? cb.parentElement.querySelector('.toggle-switch') : null;
        if (!toggle) return this._cursorToElement(checkboxSelector, fromX, fromY);
        var rect = toggle.getBoundingClientRect();
        if (!rect.width && !rect.height) return this._cursorToElement(checkboxSelector, fromX, fromY);
        var tx = rect.left + rect.width / 2;
        var ty = rect.top + rect.height / 2;
        var self = this;
        if (fromX !== undefined && fromY !== undefined) {
            return this._animateCursor(fromX, fromY, tx, ty, 500).then(function() {
                return self._wait(200).then(function() { return { x: tx, y: ty }; });
            });
        } else {
            this._showCursor(tx, ty, 'default');
            return this._wait(200).then(function() { return { x: tx, y: ty }; });
        }
    },

    _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);
        });
    },

    // -----------------------------------------------------------------------
    // Karten-Demo: Vollbild mit Markern und Cursor-Demo
    // -----------------------------------------------------------------------
    _openDemoMapFullscreen() {
        var overlay = document.getElementById('map-fullscreen-overlay');
        var fsContainer = document.getElementById('map-fullscreen-container');
        var fsStats = document.getElementById('map-fullscreen-stats');
        if (!overlay || !fsContainer) return;

        // Overlay anzeigen
        overlay.classList.add('active');
        overlay.style.zIndex = '9998';
        if (fsStats) fsStats.textContent = '3 Orte / 9 Artikel';

        // Alte Demo-Map entfernen
        this._destroyDemoMap();

        // Map im Fullscreen-Container
        fsContainer.innerHTML = '';
        // Explizite Hoehe setzen damit Leaflet korrekt rendert
        fsContainer.style.flex = '1';
        fsContainer.style.minHeight = '0';
        var mapDiv = document.createElement('div');
        mapDiv.id = 'tutorial-fs-map';
        mapDiv.style.cssText = 'width:100%;height:100%;min-height:400px;';
        fsContainer.appendChild(mapDiv);

        // Start weit herausgezoomt (Europa)
        this._demoMap = L.map(mapDiv, {
            zoomControl: false, dragging: false, touchZoom: false, doubleClickZoom: false, scrollWheelZoom: false, boxZoom: false, keyboard: false,
            attributionControl: true,
        }).setView([51.0, 10.0], 5);

        // Gleiche Tile-Quelle wie die echte App (deutsche OSM-Kacheln)
        L.tileLayer('https://tile.openstreetmap.de/{z}/{x}/{y}.png', {
            attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>',
            maxZoom: 18,
            noWrap: true,
        }).addTo(this._demoMap);

        // Marker + Legende hinzufügen
        this._demoMapMarkers = this._createDemoMarkers(this._demoMap);
        this._demoMapLegend = this._addDemoLegend(this._demoMap);

        // Resize + animierter Zoom auf Hamburg
        var map = this._demoMap;
        // Mehrere invalidateSize-Aufrufe damit Leaflet die Container-Groesse erkennt
        [100, 300, 600].forEach(function(delay) {
            setTimeout(function() { if (map) map.invalidateSize(); }, delay);
        });
        // Warten bis Tiles geladen, dann sanft auf Hamburg zoomen
        setTimeout(function() {
            if (map) {
                map.invalidateSize();
                map.flyTo([53.54, 9.97], 13, { duration: 2.5 });
            }
            // Nach Zoom: Demo starten
            setTimeout(function() {
                self._runDemo(self._simulateMapDemo);
            }, 3200);
        }, 1200);
    },

    _closeDemoMapFullscreen() {
        var overlay = document.getElementById('map-fullscreen-overlay');
        if (overlay) {
            overlay.classList.remove('active');
            overlay.style.zIndex = '';
        }
        var fsContainer = document.getElementById('map-fullscreen-container');
        if (fsContainer) {
            fsContainer.innerHTML = '';
            fsContainer.style.flex = '';
            fsContainer.style.minHeight = '';
        }
        this._destroyDemoMap();
    },

    async _simulateMapDemo() {
        this._demoRunning = true;
        await this._wait(800);

        if (!this._demoMapMarkers.length || !this._demoMap) {
            this._demoRunning = false;
            this._enableNavAfterDemo();
            return;
        }

        var mapEl = document.getElementById('tutorial-fs-map');
        if (!mapEl) { this._demoRunning = false; this._enableNavAfterDemo(); return; }
        var mapRect = mapEl.getBoundingClientRect();

        // Marker kurz pulsieren lassen damit Nutzer sie sieht
        this._demoMapMarkers.forEach(function(m) {
            var icon = m._icon;
            if (icon) {
                icon.style.transition = 'transform 0.3s ease';
                icon.style.transform = 'scale(1.8)';
            }
        });
        await this._wait(600);
        this._demoMapMarkers.forEach(function(m) {
            var icon = m._icon;
            if (icon) {
                icon.style.transform = 'scale(1)';
            }
        });
        await this._wait(400);

        // Alle 3 Marker nacheinander besuchen
        var names = ['Burchardkai Terminal (Hauptereignisort)', 'Hamburg Innenstadt (Erwähnt)', 'Elbe / Hafengebiet (Kontext)'];
        var prevX, prevY;

        for (var i = 0; i < this._demoMapMarkers.length; i++) {
            if (!this._isActive) break;
            var marker = this._demoMapMarkers[i];
            var latLng = marker.getLatLng();
            var point = this._demoMap.latLngToContainerPoint(latLng);
            var mx = mapRect.left + point.x;
            var my = mapRect.top + point.y;

            if (prevX !== undefined) {
                await this._animateCursor(prevX, prevY, mx, my, 600);
            } else {
                this._showCursor(mx - 60, my - 50, 'default');
                await this._wait(200);
                await this._animateCursor(mx - 60, my - 50, mx, my, 500);
            }
            await this._wait(200);
            marker.openPopup();
            await this._wait(2500);
            marker.closePopup();
            prevX = mx;
            prevY = my;
        }

        this._hideCursor();
        this._demoRunning = false;
        this._enableNavAfterDemo();
    },

    // -----------------------------------------------------------------------
    // Tipp-Simulation: Text Zeichen fuer Zeichen eingeben
    // -----------------------------------------------------------------------
    _simulateTyping(input, text, ms) {
        var self = this;
        var charDelay = ms / text.length;
        return new Promise(function(resolve) {
            var i = 0;
            function typeNext() {
                if (!self._isActive || i >= text.length) { resolve(); return; }
                input.value += text[i];
                input.dispatchEvent(new Event('input', { bubbles: true }));
                i++;
                setTimeout(typeNext, charDelay);
            }
            typeNext();
        });
    },

    // Helfer: Cursor zu einem Element bewegen
    async _cursorToElement(selector, fromX, fromY) {
        var el = document.querySelector(selector);
        if (!el) return { x: fromX || 400, y: fromY || 300 };
        var rect = el.getBoundingClientRect();
        if (!rect.width && !rect.height) return { x: fromX || 400, y: fromY || 300 };
        var tx = rect.left + Math.min(rect.width / 2, 60);
        var ty = rect.top + rect.height / 2;
        if (fromX !== undefined && fromY !== undefined) {
            await this._animateCursor(fromX, fromY, tx, ty, 500);
        } else {
            this._showCursor(tx, ty, 'default');
        }
        await this._wait(200);
        return { x: tx, y: ty };
    },

    // Helfer: Modal-Body zu einem Element scrollen
    _scrollModalTo(selector) {
        var el = document.querySelector(selector);
        var modalBody = document.querySelector('#modal-new .modal-body');
        if (!el || !modalBody) return Promise.resolve();
        // scrollIntoView auf dem Element - scrollt den naechsten scrollbaren Container
        try { el.scrollIntoView({ behavior: 'smooth', block: 'center' }); } catch(e) {}
        // Fallback: manuell scrollTop setzen via offsetTop-Berechnung
        var elTop = 0;
        var node = el;
        while (node && node !== modalBody) {
            elTop += node.offsetTop || 0;
            node = node.offsetParent;
        }
        var targetScroll = elTop - modalBody.clientHeight / 2 + el.offsetHeight / 2;
        modalBody.scrollTop = Math.max(0, targetScroll);
        return new Promise(function(resolve) { setTimeout(resolve, 400); });
    },

    // Helfer: Modal scrollen UND Bubble neu positionieren
    async _scrollModalAndReposition(selector) {
        await this._scrollModalTo(selector);
        // Bubble neu positionieren nach Scroll
        if (this._currentStep >= 0) {
            var step = this._steps[this._currentStep];
            this._positionBubble(step);
        }
    },

    // -----------------------------------------------------------------------
    // Step 3: Titel + Beschreibung
    // -----------------------------------------------------------------------
    async _simulateFormTitleDesc() {
        this._demoRunning = true;
        // Warten bis Modal vollstaendig gerendert
        await this._wait(600);
        var titleInput = document.getElementById('inc-title');
        var descInput = document.getElementById('inc-description');
        if (!titleInput) { this._demoRunning = false; this._enableNavAfterDemo(); return; }

        // Cursor zum Titel
        this._highlightSub('#inc-title');
        var pos = await this._cursorToElement('#inc-title');
        titleInput.focus();
        await this._simulateTyping(titleInput, 'Explosion in Hamburger Hafen', 1200);
        await this._wait(400);
        this._clearSubHighlights();

        // Cursor zur Beschreibung
        if (descInput) {
            this._highlightSub('#inc-description');
            pos = await this._cursorToElement('#inc-description', pos.x, pos.y);
            descInput.focus();
            await this._simulateTyping(descInput, 'Schwere Explosion im Hafengebiet, Burchardkai-Terminal', 1200);
            await this._wait(400);
            this._clearSubHighlights();
        }

        this._hideCursor();
        this._demoRunning = false;
        this._enableNavAfterDemo();
    },

    // -----------------------------------------------------------------------
    // Step 4: Art der Lage (Typ-Wechsel)
    // -----------------------------------------------------------------------
    // Step 4: Art der Lage (simuliertes Dropdown)
    // -----------------------------------------------------------------------
    async _simulateTypeSwitch() {
        this._demoRunning = true;
        var sel = document.getElementById('inc-type');
        if (!sel) { this._demoRunning = false; this._enableNavAfterDemo(); return; }

        var pos = await this._cursorToElement('#inc-type');
        this._els.cursor.style.zIndex = '999999';
        await this._wait(300);

        // Simuliertes Dropdown
        var rect = sel.getBoundingClientRect();
        var dropdown = document.createElement('div');
        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;';

        var opt1 = document.createElement('div');
        opt1.textContent = 'Live-Monitoring \u2014 Ereignis beobachten';
        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 = 'Recherche \u2014 Thema analysieren';
        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 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';
        try { sel.dispatchEvent(new Event('change')); } catch(e) {}
        await this._wait(1000);

        dropdown.style.opacity = '0';
        dropdown.style.transform = 'translateY(-4px)';
        await this._wait(250);
        if (dropdown.parentNode) dropdown.remove();

        this._els.cursor.style.zIndex = '';
        this._hideCursor();
        this._demoRunning = false;
        this._enableNavAfterDemo();
    },

    // -----------------------------------------------------------------------
    // Step 5: Quellen (International + Telegram toggles)
    // -----------------------------------------------------------------------
    async _simulateFormSources() {
        this._demoRunning = true;
        await this._scrollModalAndReposition('#inc-international');
        await this._wait(400);

        // International-Toggle highlighten
        var intlCheckbox = document.getElementById('inc-international');
        var intlLabel = intlCheckbox ? intlCheckbox.closest('.toggle-label') : null;
        if (intlLabel) {
            this._highlightSub('#inc-international');
            var pos = await this._cursorToToggle('#inc-international');
            await this._wait(600);

            // Klick simulieren: International deaktivieren
            await this._clickAtCursor();
            intlCheckbox.checked = false;
            await this._wait(1200);

            // Wieder aktivieren
            await this._clickAtCursor();
            intlCheckbox.checked = true;
            await this._wait(800);
            this._clearSubHighlights();

            // Telegram-Toggle
            var telegramCheckbox = document.getElementById('inc-telegram');
            if (telegramCheckbox) {
                this._highlightSub('#inc-telegram');
                pos = await this._cursorToToggle('#inc-telegram', pos.x, pos.y);
                await this._wait(600);

                // Klick simulieren: Telegram aktivieren
                await this._clickAtCursor();
                telegramCheckbox.checked = true;
                await this._wait(1200);
                this._clearSubHighlights();
            }
        }

        this._hideCursor();
        this._demoRunning = false;
        this._enableNavAfterDemo();
    },

    // -----------------------------------------------------------------------
    // Step 6: Sichtbarkeit
    // -----------------------------------------------------------------------
    async _simulateFormVisibility() {
        this._demoRunning = true;
        await this._scrollModalAndReposition('#inc-visibility');
        await this._wait(400);

        var checkbox = document.getElementById('inc-visibility');
        if (checkbox) {
            this._highlightSub('#inc-visibility');
            var pos = await this._cursorToToggle('#inc-visibility');
            await this._wait(600);

            // Klick simulieren: Umschalten auf Privat
            await this._clickAtCursor();
            checkbox.checked = false;
            var textEl = document.getElementById('visibility-text');
            if (textEl) textEl.textContent = 'Privat — nur für dich sichtbar';
            await this._wait(1500);

            // Klick simulieren: Zurueck auf Oeffentlich
            await this._clickAtCursor();
            checkbox.checked = true;
            if (textEl) textEl.textContent = 'Öffentlich — für alle Nutzer sichtbar';
            await this._wait(800);
            this._clearSubHighlights();
        }

        this._hideCursor();
        this._demoRunning = false;
        this._enableNavAfterDemo();
    },

    // -----------------------------------------------------------------------
    // Step 7: Aktualisierung + Intervall
    // -----------------------------------------------------------------------
    async _simulateFormRefresh() {
        this._demoRunning = true;
        await this._scrollModalAndReposition('#inc-refresh-mode');
        await this._wait(400);

        var refreshSelect = document.getElementById('inc-refresh-mode');
        if (refreshSelect) {
            this._highlightSub('#inc-refresh-mode');
            var pos = await this._cursorToElement('#inc-refresh-mode');
            await this._wait(800);

            // Auf Auto wechseln
            refreshSelect.value = 'auto';
            try { refreshSelect.dispatchEvent(new Event('change')); } catch(e) {}
            await this._wait(1000);
            this._clearSubHighlights();

            // Intervall-Feld highlighten
            var intervalField = document.getElementById('refresh-interval-field');
            var intervalInput = document.getElementById('inc-refresh-value');
            if (intervalField && intervalInput) {
                this._highlightSub('#inc-refresh-value');
                pos = await this._cursorToElement('#inc-refresh-value', pos.x, pos.y);
                await this._wait(1500);
                this._clearSubHighlights();
            }
        }

        this._hideCursor();
        this._demoRunning = false;
        this._enableNavAfterDemo();
    },

    // -----------------------------------------------------------------------
    // Step 8: Aufbewahrung
    // -----------------------------------------------------------------------
    async _simulateFormRetention() {
        this._demoRunning = true;
        await this._scrollModalAndReposition('#inc-retention');
        await this._wait(400);

        var retentionInput = document.getElementById('inc-retention');
        if (retentionInput) {
            this._highlightSub('#inc-retention');
            var pos = await this._cursorToElement('#inc-retention');
            await this._wait(2000);
            this._clearSubHighlights();
        }

        this._hideCursor();
        this._demoRunning = false;
        this._enableNavAfterDemo();
    },

    // -----------------------------------------------------------------------
    // Step 9: E-Mail-Benachrichtigungen
    // -----------------------------------------------------------------------
    async _simulateFormNotifications() {
        this._demoRunning = true;
        await this._scrollModalAndReposition('#inc-notify-summary');
        await this._wait(400);

        var checks = ['#inc-notify-summary', '#inc-notify-new-articles', '#inc-notify-status-change'];
        var pos;
        for (var i = 0; i < checks.length; i++) {
            var cb = document.querySelector(checks[i]);
            if (!cb) continue;
            this._highlightSub(checks[i]);
            if (pos) {
                pos = await this._cursorToToggle(checks[i], pos.x, pos.y);
            } else {
                pos = await this._cursorToToggle(checks[i]);
            }
            await this._wait(400);
            await this._clickAtCursor();
            cb.checked = true;
            await this._wait(800);
            this._clearSubHighlights();
        }

        this._hideCursor();
        this._demoRunning = false;
        this._enableNavAfterDemo();
    },

    // -----------------------------------------------------------------------
    // Drag-Demo: Kachel visuell verschieben
    // -----------------------------------------------------------------------
    async _simulateDrag() {
        this._demoRunning = true;
        var tile = document.querySelector('[gs-id="lagebild"]');
        var header = tile ? tile.querySelector('.card-header') : null;
        if (!header || !tile) { this._demoRunning = false; this._enableNavAfterDemo(); return; }

        var rect = header.getBoundingClientRect();
        var startX = rect.left + rect.width / 2;
        var startY = rect.top + rect.height / 2;
        var moveX = 150;

        // Cursor erscheint
        this._showCursor(startX - 50, startY - 30, 'default');
        await this._wait(300);
        await this._animateCursor(startX - 50, startY - 30, startX, startY, 500);
        await this._wait(300);

        // Grabbing
        this._els.cursor.classList.remove('tutorial-cursor-default');
        this._els.cursor.classList.add('tutorial-cursor-grabbing');
        await this._wait(200);

        // Kachel visuell verschieben (CSS transform)
        tile.style.transition = 'none';
        tile.style.zIndex = '9002';
        var self = this;
        var start = null;
        await new Promise(function(resolve) {
            function frame(ts) {
                if (!self._isActive) { resolve(); return; }
                if (!start) start = ts;
                var progress = Math.min((ts - start) / 1200, 1);
                var eased = progress < 0.5 ? 4*progress*progress*progress : 1 - Math.pow(-2*progress+2,3)/2;
                var dx = moveX * eased;
                tile.style.transform = 'translateX(' + dx + 'px)';
                self._els.cursor.style.left = (startX + dx) + 'px';
                if (progress < 1) requestAnimationFrame(frame); else resolve();
            }
            requestAnimationFrame(frame);
        });
        await this._wait(300);

        // Zurück
        this._els.cursor.classList.remove('tutorial-cursor-grabbing');
        this._els.cursor.classList.add('tutorial-cursor-default');
        start = null;
        await new Promise(function(resolve) {
            function frame(ts) {
                if (!self._isActive) { resolve(); return; }
                if (!start) start = ts;
                var progress = Math.min((ts - start) / 800, 1);
                var eased = progress < 0.5 ? 4*progress*progress*progress : 1 - Math.pow(-2*progress+2,3)/2;
                var dx = moveX * (1 - eased);
                tile.style.transform = 'translateX(' + dx + 'px)';
                self._els.cursor.style.left = (startX + dx) + 'px';
                if (progress < 1) requestAnimationFrame(frame); else resolve();
            }
            requestAnimationFrame(frame);
        });

        // Aufräumen
        tile.style.transform = '';
        tile.style.transition = '';
        tile.style.zIndex = '';
        this._hideCursor();
        await this._wait(200);

        this._demoRunning = false;
        this._enableNavAfterDemo();
    },

    // -----------------------------------------------------------------------
    // Resize-Demo: Kachel visuell vergrößern
    // -----------------------------------------------------------------------
    async _simulateResize() {
        this._demoRunning = true;
        var tile = document.querySelector('[gs-id="faktencheck"]');
        if (!tile) { this._demoRunning = false; this._enableNavAfterDemo(); return; }

        var rect = tile.getBoundingClientRect();
        var startX = rect.right - 6;
        var startY = rect.bottom - 6;
        var expandX = 80;
        var expandY = 50;

        // Cursor am Rand
        this._showCursor(startX - 40, startY - 40, 'default');
        await this._wait(300);
        await this._animateCursor(startX - 40, startY - 40, startX, startY, 400);
        await this._wait(200);

        // Resize-Cursor
        this._els.cursor.classList.remove('tutorial-cursor-default');
        this._els.cursor.classList.add('tutorial-cursor-resize');
        await this._wait(200);

        // Rein visuelle Vergrößerung per CSS transform (kein width/height!)
        // So bleibt GridStack komplett unberührt
        tile.style.transition = 'none';
        tile.style.zIndex = '9002';
        tile.style.transformOrigin = 'top left';
        var self = this;
        var origW = rect.width;
        var origH = rect.height;
        var start = null;

        await new Promise(function(resolve) {
            function frame(ts) {
                if (!self._isActive) { resolve(); return; }
                if (!start) start = ts;
                var progress = Math.min((ts - start) / 1000, 1);
                var eased = progress < 0.5 ? 4*progress*progress*progress : 1 - Math.pow(-2*progress+2,3)/2;
                var scaleX = 1 + (expandX / origW) * eased;
                var scaleY = 1 + (expandY / origH) * eased;
                tile.style.transform = 'scale(' + scaleX + ', ' + scaleY + ')';
                self._els.cursor.style.left = (startX + expandX * eased) + 'px';
                self._els.cursor.style.top = (startY + expandY * eased) + 'px';
                if (progress < 1) requestAnimationFrame(frame); else resolve();
            }
            requestAnimationFrame(frame);
        });
        await this._wait(500);

        // Zurück
        start = null;
        await new Promise(function(resolve) {
            function frame(ts) {
                if (!self._isActive) { resolve(); return; }
                if (!start) start = ts;
                var progress = Math.min((ts - start) / 700, 1);
                var eased = progress < 0.5 ? 4*progress*progress*progress : 1 - Math.pow(-2*progress+2,3)/2;
                var scaleX = 1 + (expandX / origW) * (1 - eased);
                var scaleY = 1 + (expandY / origH) * (1 - eased);
                tile.style.transform = 'scale(' + scaleX + ', ' + scaleY + ')';
                self._els.cursor.style.left = (startX + expandX * (1 - eased)) + 'px';
                self._els.cursor.style.top = (startY + expandY * (1 - eased)) + 'px';
                if (progress < 1) requestAnimationFrame(frame); else resolve();
            }
            requestAnimationFrame(frame);
        });

        // Aufräumen
        tile.style.transform = '';
        tile.style.transformOrigin = '';
        tile.style.transition = '';
        tile.style.zIndex = '';
        this._hideCursor();
        await this._wait(200);

        this._demoRunning = false;
        this._enableNavAfterDemo();
    },

    // -----------------------------------------------------------------------
    // Quellenverwaltung-Demo: Info-Icon hover + Tooltip zeigen
    // -----------------------------------------------------------------------
    // Quellen-Demo Teil 1: Info-Icon + Tooltip, dann Weiter-Button
    async _simulateSourcesInfoIcon() {
        this._demoRunning = true;

        // Warten bis Quellen geladen sind
        var attempts = 0;
        while (attempts < 10) {
            var infoIcon = document.querySelector('#sources-list .info-icon');
            if (infoIcon) break;
            await this._wait(300);
            attempts++;
        }

        var infoIcon = document.querySelector('#sources-list .info-icon');
        if (!infoIcon) {
            this._demoRunning = false;
            this._enableNavAfterDemo();
            return;
        }

        // Cursor zum Info-Icon
        var rect = infoIcon.getBoundingClientRect();
        var targetX = rect.left + rect.width / 2;
        var targetY = rect.top + rect.height / 2;

        this._showCursor(targetX - 80, targetY - 60, 'default');
        await this._wait(300);
        await this._animateCursor(targetX - 80, targetY - 60, targetX, targetY, 600);
        await this._wait(200);

        // Info-Icon highlighten
        this._highlightSub('#sources-list .info-icon');

        // Tooltip manuell anzeigen
        var tooltipText = infoIcon.getAttribute('data-tooltip') || '';
        if (tooltipText) {
            var tooltip = document.createElement('div');
            tooltip.id = 'tutorial-tooltip';
            tooltip.style.cssText = 'position:fixed;z-index:9999;background:var(--bg-card);color:var(--text-primary);'
                + 'border:1px solid var(--accent);border-radius:var(--radius);padding:8px 12px;font-size:12px;'
                + 'line-height:1.5;max-width:250px;white-space:pre-line;box-shadow:var(--shadow-md);'
                + 'pointer-events:none;';
            tooltip.textContent = tooltipText;
            document.body.appendChild(tooltip);
            tooltip.style.left = Math.max(8, rect.left - 20) + 'px';
            tooltip.style.top = (rect.bottom + 8) + 'px';
        }

        // Demo fertig - Weiter-Button einblenden, Tooltip bleibt stehen
        this._demoRunning = false;
        this._enableNavAfterDemo();
    },

    // Quellen-Demo Teil 2: + Quelle und Ausschlie\u00dfen Buttons
    async _simulateSourcesActions() {
        this._demoRunning = true;

        // Zum "+ Quelle" Button
        var addBtn = document.querySelector('.sources-toolbar-actions .btn-primary');
        if (addBtn) {
            var addRect = addBtn.getBoundingClientRect();
            this._showCursor(addRect.left - 40, addRect.top - 30, 'default');
            await this._wait(300);
            await this._animateCursor(addRect.left - 40, addRect.top - 30,
                addRect.left + addRect.width / 2, addRect.top + addRect.height / 2, 500);
            this._highlightSub('.sources-toolbar-actions .btn-primary');
            await this._wait(2500);
            this._clearSubHighlights();
        }

        // Zum "Ausschlie\u00dfen" Button der ersten Quelle
        var excludeBtn = document.querySelector('#sources-list .source-group-actions .btn-secondary');
        if (excludeBtn) {
            var exRect = excludeBtn.getBoundingClientRect();
            var prevX = addBtn ? addBtn.getBoundingClientRect().left + addBtn.getBoundingClientRect().width / 2 : exRect.left;
            var prevY = addBtn ? addBtn.getBoundingClientRect().top + addBtn.getBoundingClientRect().height / 2 : exRect.top;
            await this._animateCursor(prevX, prevY,
                exRect.left + exRect.width / 2, exRect.top + exRect.height / 2, 500);
            this._highlightSub('#sources-list .source-group-actions .btn-secondary');
            await this._wait(2500);
            this._clearSubHighlights();
        }

        this._hideCursor();
        this._demoRunning = false;
        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)
    // -----------------------------------------------------------------------
    // 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 nach rechts (Lagebild <-> Faktencheck tauschen)
        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);

            // GridStack-Swap
            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, tmpW=ln.w, tmpH=ln.h;
                    grid.update(lbTile,{x:fn.x,y:fn.y});
                    grid.update(fcTile,{x:tmpX,y:tmpY});
                    grid.compact();
                }
            }
            await this._wait(600);

            // Phase 1b: Zurueck tauschen
            lbTile = document.querySelector('[gs-id="lagebild"]');
            fcTile = document.querySelector('[gs-id="faktencheck"]');
            if (lbTile && fcTile) {
                lbH = lbTile.querySelector('.card-header');
                if (lbH) {
                    lbR = lbH.getBoundingClientRect();
                    fcR = fcTile.getBoundingClientRect();
                    sx = lbR.left + lbR.width/2; sy = lbR.top + lbR.height/2;
                    ex = fcR.left + fcR.width/2; ey = fcR.top + lbR.height/2;
                    dx = ex - sx; dy = ey - sy;

                    await this._animateCursor(
                        parseFloat(this._els.cursor.style.left),
                        parseFloat(this._els.cursor.style.top), sx, sy, 400);
                    await this._wait(200);

                    lbTile.style.transition = 'none';
                    lbTile.style.zIndex = '9002';
                    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)/1000,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(300);

                    lbTile.style.transform = '';
                    lbTile.style.transition = '';
                    lbTile.style.zIndex = '';
                    if (grid) {
                        var ln2 = grid.engine.nodes.find(function(n){return n.el===lbTile;});
                        var fn2 = grid.engine.nodes.find(function(n){return n.el===fcTile;});
                        if (ln2 && fn2) {
                            var tx2=ln2.x, ty2=ln2.y;
                            grid.update(lbTile,{x:fn2.x,y:fn2.y});
                            grid.update(fcTile,{x:tx2,y:ty2});
                            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 Lagebild stark vergroessern
        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);

            // GridStack-Resize
            lbTile.style.transform = '';
            lbTile.style.transformOrigin = '';
            lbTile.style.transition = '';
            lbTile.style.zIndex = '';
            if (grid) {
                var ln3 = grid.engine.nodes.find(function(n){return n.el===lbTile;});
                if (ln3) { grid.update(lbTile,{w:12,h:Math.max(ln3.h+3,6)}); grid.compact(); }
            }
            await this._wait(600);
        }

        this._hideCursor();
        this._demoRunning = false;
        this._enableNavAfterDemo();
    },



    // -----------------------------------------------------------------------
    // Theme-Toggle Demo
    // -----------------------------------------------------------------------
    async _simulateThemeToggle() {
        this._demoRunning = true;
        await this._wait(400);

        var btn = document.getElementById('theme-toggle');
        if (!btn) { this._demoRunning = false; this._enableNavAfterDemo(); return; }

        var rect = btn.getBoundingClientRect();
        var tx = rect.left + rect.width / 2;
        var ty = rect.top + rect.height / 2;
        this._showCursor(tx - 40, ty - 30, 'default');
        await this._wait(300);
        await this._animateCursor(tx - 40, ty - 30, tx, ty, 400);
        await this._wait(300);

        // Klick - zum hellen Design wechseln
        btn.click();
        await this._wait(2500);

        // Zurueck zum dunklen Design
        rect = btn.getBoundingClientRect();
        tx = rect.left + rect.width / 2;
        ty = rect.top + rect.height / 2;
        await this._animateCursor(
            parseFloat(this._els.cursor.style.left),
            parseFloat(this._els.cursor.style.top), tx, ty, 300);
        await this._wait(300);
        btn.click();
        await this._wait(800);

        this._hideCursor();
        this._demoRunning = false;
        this._enableNavAfterDemo();
    },

    // -----------------------------------------------------------------------
    // Navigation nach Demo-Ende freigeben
    // -----------------------------------------------------------------------
    _enableNavAfterDemo() {
        var bubble = this._els.bubble;
        if (!bubble) return;

        // Pulsieren stoppen
        bubble.classList.remove('tutorial-bubble-pulsing');

        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 += '<button class="tutorial-btn tutorial-btn-back" onclick="Tutorial.prev()">Zurück</button>';
        } else {
            navHtml += '<span></span>';
        }
        if (index < total - 1) {
            navHtml += '<button class="tutorial-btn tutorial-btn-next" onclick="Tutorial.next()">Weiter</button>';
        } else {
            navHtml += '<button class="tutorial-btn tutorial-btn-next" onclick="Tutorial.stop()">Fertig</button>';
        }
        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(async function() {
            if (!self._isActive || self._currentStep < 0) return;
            var step = self._steps[self._currentStep];
            var isModalStep = step.target && step.target.indexOf('#modal-') !== -1;

            // Spotlight neu positionieren
            if (step.target && step.position !== 'center' && !isModalStep) {
                self._spotlightElement(step.target);
            }

            // Modal-Steps: zum Feld scrollen
            if (isModalStep && step.bubbleTarget) {
                await self._scrollModalTo(step.bubbleTarget);
            }

            // Bubble neu positionieren
            self._positionBubble(step);

            // Clamp + Arrow-Alignment (gleiche Logik wie in _showBubble)
            requestAnimationFrame(function() {
                var bubble = self._els.bubble;
                if (!bubble) return;
                var bRect = bubble.getBoundingClientRect();
                var vHeight = window.innerHeight;
                var currentTop = parseFloat(bubble.style.top) || 0;

                // Unten geclampt
                if (bRect.bottom > vHeight - 8) {
                    var shift = bRect.bottom - vHeight + 16;
                    bubble.style.top = (currentTop - shift) + 'px';
                    var transform = bubble.style.transform || '';
                    bubble.style.transform = transform.replace('translateY(-100%)', '');
                }
                // Oben geclampt
                bRect = bubble.getBoundingClientRect();
                if (bRect.top < 8) {
                    currentTop = parseFloat(bubble.style.top) || 0;
                    bubble.style.top = (currentTop + (8 - bRect.top)) + 'px';
                    bubble.style.transform = (bubble.style.transform || '').replace('translateY(-100%)', '');
                }
                // Pfeil-Alignment
                var arrowTarget = step.bubbleTarget || step.target;
                if (arrowTarget && (bubble.className.indexOf('pos-left') !== -1 || bubble.className.indexOf('pos-right') !== -1)) {
                    var targetEl = document.querySelector(arrowTarget);
                    if (targetEl) {
                        bRect = bubble.getBoundingClientRect();
                        var tRect = targetEl.getBoundingClientRect();
                        var arrowY = (tRect.top + tRect.height / 2) - bRect.top;
                        arrowY = Math.max(18, Math.min(arrowY, bRect.height - 18));
                        bubble.style.setProperty('--arrow-top', arrowY + 'px');
                    }
                }
            });
        }, 150);
    },

    // -----------------------------------------------------------------------
    // Persistenz
    // -----------------------------------------------------------------------
    _markSeen() {
        try { localStorage.setItem('osint_tutorial_seen', '1'); } catch(e) {}
        API.saveTutorialState({ completed: true, current_step: null }).catch(function() {});
    },

    _hasSeen() {
        try { return localStorage.getItem('osint_tutorial_seen') === '1'; } catch(e) { return false; }
    },
};
