diff --git a/src/static/js/tutorial.js b/src/static/js/tutorial.js
index 8dfc032..853eaf2 100644
--- a/src/static/js/tutorial.js
+++ b/src/static/js/tutorial.js
@@ -554,18 +554,36 @@ const Tutorial = {
id: 'new-incident-btn',
target: '#new-incident-btn',
title: 'Neue Lage anlegen',
- text: 'Mit diesem Button öffnen Sie das Formular zur Erstellung einer neuen Lage. '
+ text: 'Mit diesem Button \u00f6ffnen Sie das Formular zur Erstellung einer neuen Lage. '
+ 'Wir gehen jetzt gemeinsam alle Felder durch.',
position: 'right',
onEnter: function() {
Tutorial._stepTimeout(function() {
var overlay = document.getElementById('modal-new');
if (overlay && !overlay.classList.contains('active')) overlay.classList.add('active');
+ // Bubble auf Modal umpositionieren nach Oeffnung
+ Tutorial._stepTimeout(function() {
+ var modal = document.querySelector('#modal-new .modal');
+ if (modal) {
+ var step = Tutorial._steps[Tutorial._currentStep];
+ step._origTarget = step.target;
+ step.target = '#modal-new .modal';
+ step.position = 'left';
+ Tutorial._positionBubble(step);
+ }
+ }, 400);
}, 1500);
},
onExit: function() {
var overlay = document.getElementById('modal-new');
if (overlay) overlay.classList.remove('active');
+ // Target zuruecksetzen
+ var step = Tutorial._steps[2];
+ if (step && step._origTarget) {
+ step.target = step._origTarget;
+ step.position = 'right';
+ delete step._origTarget;
+ }
},
},
// 3 - Titel und Beschreibung (Cursor-Demo)
@@ -883,7 +901,7 @@ const Tutorial = {
// Faktencheck: Status-Demo
{
id: 'faktencheck-detail',
- target: '.factcheck-item[data-fc-status="confirmed"]',
+ target: '[gs-id="faktencheck"]',
title: 'Faktencheck-Eintrag',
text: 'Jeder Faktencheck-Eintrag besteht aus:
'
+ 'Status-Symbol - Farbcodiert f\u00fcr schnelle Einordnung
'
@@ -913,9 +931,26 @@ const Tutorial = {
position: 'top',
disableNav: true,
onEnter: function() {
+ // Timeline-Kachel vergroessern damit Detail-Panel 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, 4) });
+ }
+ }
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();
},
@@ -1004,6 +1039,8 @@ const Tutorial = {
position: 'right',
disableNav: true,
onEnter: function() {
+ // Spotlight ausblenden waehrend Drag-Demo
+ Tutorial._els.spotlight.style.opacity = '0';
Tutorial._runDemo(Tutorial._simulateLayoutDemo);
},
onExit: function() {
@@ -1013,14 +1050,26 @@ const Tutorial = {
Tutorial._hideCursor();
},
},
- // 20 - Theme
+ // 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ächsten Besuch beibehalten.',
+ + '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
{
@@ -1034,6 +1083,9 @@ const Tutorial = {
+ 'Quellen deaktivieren - 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')) {
@@ -1044,6 +1096,8 @@ const Tutorial = {
}, 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');
@@ -1607,7 +1661,7 @@ const Tutorial = {
// Start weit herausgezoomt (Europa)
this._demoMap = L.map(mapDiv, {
- zoomControl: true,
+ zoomControl: false, dragging: false, touchZoom: false, doubleClickZoom: false, scrollWheelZoom: false, boxZoom: false, keyboard: false,
attributionControl: true,
}).setView([51.0, 10.0], 5);
@@ -1742,12 +1796,20 @@ const Tutorial = {
_scrollModalTo(selector) {
var el = document.querySelector(selector);
var modalBody = document.querySelector('#modal-new .modal-body');
- if (!el || !modalBody) return;
+ if (!el || !modalBody) return Promise.resolve();
+ // Element-Position relativ zum sichtbaren Modal-Bereich
var elRect = el.getBoundingClientRect();
var bodyRect = modalBody.getBoundingClientRect();
+ // Pruefen ob Element bereits sichtbar
+ if (elRect.top >= bodyRect.top && elRect.bottom <= bodyRect.bottom) {
+ return Promise.resolve();
+ }
+ // Scroll-Ziel: Element mit Abstand oben im sichtbaren Bereich
var offset = elRect.top - bodyRect.top + modalBody.scrollTop;
- var targetScroll = offset - Math.min(40, bodyRect.height * 0.2);
+ var targetScroll = offset - 30;
modalBody.scrollTo({ top: Math.max(0, targetScroll), behavior: 'smooth' });
+ // Warten bis Scroll abgeschlossen
+ return new Promise(function(resolve) { setTimeout(resolve, 500); });
},
// -----------------------------------------------------------------------
@@ -1795,6 +1857,7 @@ const Tutorial = {
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
@@ -1859,6 +1922,7 @@ const Tutorial = {
await this._wait(250);
if (dropdown.parentNode) dropdown.remove();
+ this._els.cursor.style.zIndex = '';
this._hideCursor();
this._demoRunning = false;
this._enableNavAfterDemo();
@@ -1869,7 +1933,7 @@ const Tutorial = {
// -----------------------------------------------------------------------
async _simulateFormSources() {
this._demoRunning = true;
- this._scrollModalTo('#inc-international');
+ await this._scrollModalTo('#inc-international');
await this._wait(400);
// International-Toggle highlighten
@@ -1903,7 +1967,7 @@ const Tutorial = {
// -----------------------------------------------------------------------
async _simulateFormVisibility() {
this._demoRunning = true;
- this._scrollModalTo('#inc-visibility');
+ await this._scrollModalTo('#inc-visibility');
await this._wait(400);
var checkbox = document.getElementById('inc-visibility');
@@ -1935,7 +1999,7 @@ const Tutorial = {
// -----------------------------------------------------------------------
async _simulateFormRefresh() {
this._demoRunning = true;
- this._scrollModalTo('#inc-refresh-mode');
+ await this._scrollModalTo('#inc-refresh-mode');
await this._wait(400);
var refreshSelect = document.getElementById('inc-refresh-mode');
@@ -1971,7 +2035,7 @@ const Tutorial = {
// -----------------------------------------------------------------------
async _simulateFormRetention() {
this._demoRunning = true;
- this._scrollModalTo('#inc-retention');
+ await this._scrollModalTo('#inc-retention');
await this._wait(400);
var retentionInput = document.getElementById('inc-retention');
@@ -1992,7 +2056,7 @@ const Tutorial = {
// -----------------------------------------------------------------------
async _simulateFormNotifications() {
this._demoRunning = true;
- this._scrollModalTo('#inc-notify-summary');
+ await this._scrollModalTo('#inc-notify-summary');
await this._wait(400);
var checks = ['#inc-notify-summary', '#inc-notify-new-articles', '#inc-notify-status-change'];
@@ -2423,6 +2487,8 @@ const Tutorial = {
this._enableNavAfterDemo();
},
+ // -----------------------------------------------------------------------
+ // Layout Demo (Drag + Resize kombiniert)
// -----------------------------------------------------------------------
// Layout Demo (Drag + Resize kombiniert)
// -----------------------------------------------------------------------
@@ -2434,17 +2500,17 @@ const Tutorial = {
var grid = typeof LayoutManager !== 'undefined' ? LayoutManager._grid : null;
var self = this;
- // Phase 1: Drag
+ // 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;
+ 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');
+ this._showCursor(sx - 40, sy - 30, 'default');
await this._wait(300);
- await this._animateCursor(sx-40, sy-30, sx, sy, 400);
+ 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');
@@ -2452,7 +2518,7 @@ const Tutorial = {
lbTile.style.transition = 'none';
lbTile.style.zIndex = '9002';
- var dx = ex-sx, dy = ey-sy;
+ var dx = ex - sx, dy = ey - sy;
var ms = null;
await new Promise(function(resolve) {
function f(ts) {
@@ -2469,6 +2535,7 @@ const Tutorial = {
});
await this._wait(400);
+ // GridStack-Swap
lbTile.style.transform = '';
lbTile.style.transition = '';
lbTile.style.zIndex = '';
@@ -2476,25 +2543,78 @@ const Tutorial = {
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;
+ 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
+ // 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;
+ 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);
+ 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');
@@ -2504,7 +2624,7 @@ const Tutorial = {
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 epx = ow * 0.6, epy = oh * 0.5;
var rs = null;
await new Promise(function(resolve) {
function f(ts) {
@@ -2521,13 +2641,14 @@ const Tutorial = {
});
await this._wait(600);
+ // GridStack-Resize
lbTile.style.transform = '';
lbTile.style.transformOrigin = '';
lbTile.style.transition = '';
lbTile.style.zIndex = '';
if (grid) {
- var ln2 = grid.engine.nodes.find(function(n){return n.el===lbTile;});
- if (ln2) { grid.update(lbTile,{w:12,h:Math.max(ln2.h+3,6)}); grid.compact(); }
+ 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);
}
@@ -2538,6 +2659,45 @@ const Tutorial = {
},
+
+ // -----------------------------------------------------------------------
+ // 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
// -----------------------------------------------------------------------