Tutorial Patch 2: Pfeile, Cursor-Z-Index, Modal-Scroll, Karteninteraktion, Layout-Demo, Theme-Toggle

- Schritt 3: Bubble repositioniert sich auf Modal nach Oeffnung
- Schritt 5: Cursor-Z-Index ueber Dropdown (999999)
- Schritt 7ff: Modal scrollt automatisch zu Feldern (async scrollModalTo)
- Schritt 20: Goldener Rahmen um gesamte Faktencheck-Kachel
- Schritt 21: Timeline-Kachel wird temporaer vergroessert
- Schritt 23: Alle Karteninteraktionen deaktiviert (kein Zoom/Click)
- Schritt 25: Drag nach rechts + zurueck, dann Resize vom Original
- Schritt 26: Theme-Toggle-Simulation (hell/dunkel/zurueck)
- Schritt 27: Button bleibt sichtbar nach Quellenverwaltung-Oeffnung
- Spotlight ausgeblendet waehrend Layout-Demo
Dieser Commit ist enthalten in:
Claude Dev
2026-03-23 21:48:14 +01:00
Ursprung 584183951f
Commit c6b154dbba

Datei anzeigen

@@ -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:<br><br>'
+ '<strong>Status-Symbol</strong> - Farbcodiert f\u00fcr schnelle Einordnung<br>'
@@ -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 = {
+ '<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')) {
@@ -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
// -----------------------------------------------------------------------