diff --git a/src/static/dashboard.html b/src/static/dashboard.html
index a5b0deb..43bfc79 100644
--- a/src/static/dashboard.html
+++ b/src/static/dashboard.html
@@ -764,7 +764,7 @@
-
+
diff --git a/src/static/js/tutorial.js b/src/static/js/tutorial.js
index c164f30..43e38e8 100644
--- a/src/static/js/tutorial.js
+++ b/src/static/js/tutorial.js
@@ -187,22 +187,12 @@ const Tutorial = {
timeline.innerHTML = tlHtml;
}
- // Karte: "Keine Orte" ausblenden, Platzhalter einsetzen
+ // Karte: Leaflet-Map mit Demo-Markern initialisieren
var mapEmpty = document.getElementById('map-empty');
if (mapEmpty) mapEmpty.style.display = 'none';
- var mapContainer = document.getElementById('map-container');
- if (mapContainer) {
- var mapPlaceholder = document.createElement('div');
- mapPlaceholder.className = 'tutorial-demo tutorial-map-placeholder';
- mapPlaceholder.style.cssText = 'width:100%;height:100%;display:flex;align-items:center;justify-content:center;'
- + 'background:var(--bg-secondary);color:var(--text-secondary);font-size:13px;';
- mapPlaceholder.innerHTML = '
'
- + '
🌎
'
- + '
3 Orte erkannt: Hamburg, Burchardkai, Elbe
';
- mapContainer.appendChild(mapPlaceholder);
- }
var mapStats = document.getElementById('map-stats');
- if (mapStats) mapStats.textContent = '3 Orte';
+ if (mapStats) mapStats.textContent = '3 Orte / 9 Artikel';
+ this._initDemoMap();
// Meta
var metaUpdated = document.getElementById('meta-updated');
@@ -250,9 +240,8 @@ const Tutorial = {
var mapStats = document.getElementById('map-stats');
if (mapStats) mapStats.innerHTML = s.mapStats;
- // Map-Platzhalter entfernen
- var mapPlaceholder = document.querySelector('.tutorial-map-placeholder');
- if (mapPlaceholder) mapPlaceholder.remove();
+ // Demo-Map entfernen
+ this._destroyDemoMap();
// Meta
var metaUpdated = document.getElementById('meta-updated');
@@ -267,6 +256,127 @@ const Tutorial = {
this._savedState = null;
},
+ // -----------------------------------------------------------------------
+ // Demo-Karte mit Leaflet
+ // -----------------------------------------------------------------------
+ _demoMap: null,
+ _demoMapMarkers: [],
+
+ _initDemoMap() {
+ 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 headerEl = container.closest('.map-card');
+ var hdr = headerEl ? headerEl.querySelector('.card-header') : null;
+ var headerH = hdr ? hdr.offsetHeight : 40;
+ var available = gsItem.offsetHeight - headerH - 4;
+ container.style.height = Math.max(available, 200) + 'px';
+ } else if (container.offsetHeight < 50) {
+ container.style.height = '300px';
+ }
+
+ // Falls UI._map existiert, vorher sichern
+ if (typeof UI !== 'undefined' && UI._map) {
+ this._savedUIMap = true;
+ }
+
+ this._demoMap = L.map(container, {
+ zoomControl: true,
+ attributionControl: true,
+ }).setView([53.545, 9.98], 13);
+
+ // Tile-Layer (Theme-abhängig)
+ var isDark = !document.documentElement.getAttribute('data-theme') || document.documentElement.getAttribute('data-theme') !== 'light';
+ if (isDark) {
+ L.tileLayer('https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png', {
+ attribution: '\u00a9 OpenStreetMap, \u00a9 CARTO',
+ maxZoom: 19,
+ }).addTo(this._demoMap);
+ } else {
+ L.tileLayer('https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png', {
+ attribution: '\u00a9 OpenStreetMap, \u00a9 CARTO',
+ maxZoom: 19,
+ }).addTo(this._demoMap);
+ }
+
+ // Demo-Marker
+ var locations = [
+ { lat: 53.5325, lon: 9.9275, name: 'Burchardkai Terminal', articles: 6, cat: 'primary' },
+ { lat: 53.5460, lon: 9.9690, name: 'Hamburg Innenstadt', articles: 2, cat: 'secondary' },
+ { lat: 53.5380, lon: 9.9400, name: 'Elbe / Hafengebiet', articles: 1, cat: 'tertiary' },
+ ];
+
+ var catColors = { primary: '#EF4444', secondary: '#F59E0B', tertiary: '#3B82F6' };
+ var catLabels = { primary: 'Hauptereignisort', secondary: 'Erw\u00e4hnt', tertiary: 'Kontext' };
+ var self = this;
+
+ locations.forEach(function(loc) {
+ var color = catColors[loc.cat] || '#7b7b7b';
+ var icon = L.divIcon({
+ className: 'tutorial-map-marker',
+ html: '',
+ iconSize: [14, 14],
+ iconAnchor: [7, 7],
+ });
+ var marker = L.marker([loc.lat, loc.lon], { icon: icon });
+ var popupHtml = '';
+ marker.bindPopup(popupHtml, { maxWidth: 250, className: 'map-popup-container' });
+ marker.addTo(self._demoMap);
+ self._demoMapMarkers.push(marker);
+ });
+
+ // Legende
+ var legend = L.control({ position: 'bottomright' });
+ legend.onAdd = function() {
+ var div = L.DomUtil.create('div', 'map-legend-ctrl');
+ L.DomEvent.disableClickPropagation(div);
+ var html = 'Legende';
+ ['primary', 'secondary', 'tertiary'].forEach(function(cat) {
+ html += ''
+ + ''
+ + '' + catLabels[cat] + '
';
+ });
+ div.innerHTML = html;
+ return div;
+ };
+ legend.addTo(this._demoMap);
+ this._demoMapLegend = legend;
+
+ // Resize-Fix
+ var map = this._demoMap;
+ [100, 300, 800].forEach(function(delay) {
+ setTimeout(function() {
+ if (map) map.invalidateSize();
+ }, delay);
+ });
+
+ // Hauptereignisort-Popup nach kurzer Verz\u00f6gerung \u00f6ffnen
+ var mainMarker = this._demoMapMarkers[0];
+ if (mainMarker) {
+ setTimeout(function() {
+ if (map && mainMarker) mainMarker.openPopup();
+ }, 1500);
+ }
+ },
+
+ _destroyDemoMap() {
+ if (this._demoMap) {
+ this._demoMap.remove();
+ this._demoMap = null;
+ this._demoMapMarkers = [];
+ this._demoMapLegend = null;
+ }
+ },
+
// -----------------------------------------------------------------------
// Highlight-Helfer: Einzelnes Sub-Element innerhalb einer Kachel markieren
// -----------------------------------------------------------------------
@@ -632,19 +742,23 @@ const Tutorial = {
id: 'karte',
target: '[gs-id="karte"]',
title: 'Geografische Verteilung',
- text: 'Die Karte zeigt automatisch erkannte Orte aus den gesammelten Artikeln (Geoparsing).
'
- + 'Marker - Klicken Sie auf einen Marker für Details zum Ort und verknüpfte Artikel
'
- + 'Cluster - Bei vielen Markern werden nahe Orte gruppiert
'
- + 'Orte einlesen - Startet das Geoparsing manuell neu
'
- + 'Vollbild - Vergrößert die Karte auf den gesamten Bildschirm
'
- + 'Besonders bei internationalen Lagen bietet die Karte einen schnellen Überblick über die räumliche Verteilung.',
+ text: 'Die Karte zeigt per Geoparsing automatisch erkannte Orte aus den Quellen.
'
+ + '● Hauptereignisort - Zentraler Ort des Geschehens
'
+ + '● Erwähnt - In Artikeln genannte Orte
'
+ + '● Kontext - Relevante Umgebung
'
+ + 'Klicken Sie auf Marker für Details und verknüpfte Artikel. '
+ + 'Bei vielen Markern werden nahe Orte zu Clustern gruppiert.',
position: 'top',
+ disableNav: true,
onEnter: function() {
- Tutorial._highlightSub('#geoparse-btn');
- setTimeout(function() {
- Tutorial._clearSubHighlights();
- Tutorial._highlightSub('#map-expand-btn');
- }, 3000);
+ // Demo-Map Popup öffnen + invalidateSize
+ if (Tutorial._demoMap) {
+ Tutorial._demoMap.invalidateSize();
+ setTimeout(function() {
+ if (Tutorial._demoMap) Tutorial._demoMap.setView([53.545, 9.98], 13);
+ }, 300);
+ }
+ Tutorial._simulateMapDemo();
},
onExit: function() {
Tutorial._clearSubHighlights();
@@ -1097,6 +1211,49 @@ const Tutorial = {
});
},
+ // -----------------------------------------------------------------------
+ // Karten-Demo (Step 17): Marker klicken, Geoparse + Vollbild highlighten
+ // -----------------------------------------------------------------------
+ async _simulateMapDemo() {
+ this._demoRunning = true;
+
+ // 1. Auf Marker zeigen und klicken
+ if (this._demoMapMarkers.length > 0 && this._demoMap) {
+ var mainMarker = this._demoMapMarkers[0];
+ var latLng = mainMarker.getLatLng();
+ var point = this._demoMap.latLngToContainerPoint(latLng);
+ var mapContainer = document.getElementById('map-container');
+ if (mapContainer) {
+ var mapRect = mapContainer.getBoundingClientRect();
+ var markerX = mapRect.left + point.x;
+ var markerY = mapRect.top + point.y;
+
+ this._showCursor(markerX - 40, markerY - 40, 'default');
+ await this._wait(400);
+ await this._animateCursor(markerX - 40, markerY - 40, markerX, markerY, 600);
+ await this._wait(300);
+ mainMarker.openPopup();
+ await this._wait(2000);
+ mainMarker.closePopup();
+ }
+ }
+
+ // 2. Geoparse-Button highlighten
+ this._hideCursor();
+ this._highlightSub('#geoparse-btn');
+ await this._wait(2000);
+ this._clearSubHighlights();
+
+ // 3. Vollbild-Button highlighten
+ this._highlightSub('#map-expand-btn');
+ await this._wait(2000);
+ this._clearSubHighlights();
+
+ this._hideCursor();
+ this._demoRunning = false;
+ this._enableNavAfterDemo();
+ },
+
// -----------------------------------------------------------------------
// Typ-Wechsel-Demo (Step 4)
// -----------------------------------------------------------------------