Tutorial: Formular Feld fuer Feld erklaeren + Klicks blockieren

Formular-Steps komplett ueberarbeitet (Steps 2-9):
- Step 3: Titel + Beschreibung mit Tipp-Animation
- Step 4: Art der Lage (Live vs Recherche) mit Cursor-Demo + Erklaerung
- Step 5: Quellen (International + Telegram) einzeln gehighlightet + erklaert
- Step 6: Sichtbarkeit (Oeffentlich/Privat) mit Toggle-Demo
- Step 7: Aktualisierung + Intervall, Hinweis auf Creditverbrauch
- Step 8: Aufbewahrung erklaert
- Step 9: E-Mail-Benachrichtigungen (alle 3 Optionen einzeln gehighlightet)

Jedes Feld wird mit Cursor angesteuert, gehighlightet und erklaert.
Modal-Body scrollt automatisch zu den jeweiligen Feldern.

Klick-Blockierung: Waehrend des Tutorials sind alle Dashboard-Elemente
nicht anklickbar (pointer-events:none auf body.tutorial-active).
Nur die Tutorial-Bubble mit Navigation bleibt bedienbar.

Duplikate der alten Methoden entfernt.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Dieser Commit ist enthalten in:
Claude Dev
2026-03-16 16:05:17 +01:00
Ursprung 37d7addd5b
Commit 6d09c0a5fa
3 geänderte Dateien mit 579 neuen und 425 gelöschten Zeilen

Datei anzeigen

@@ -4943,266 +4943,266 @@ a.map-popup-article:hover {
position: relative; position: relative;
z-index: 100; z-index: 100;
} }
/* ================================================================ /* ================================================================
Tutorial System Tutorial System
================================================================ */ ================================================================ */
/* Overlay (Hintergrund-Abdunkelung) */ /* Overlay (Hintergrund-Abdunkelung) */
.tutorial-overlay { .tutorial-overlay {
display: none; display: none;
position: fixed; position: fixed;
inset: 0; inset: 0;
z-index: 9000; z-index: 9000;
pointer-events: none; pointer-events: none;
} }
.tutorial-overlay.active { .tutorial-overlay.active {
display: block; display: block;
} }
/* Spotlight */ /* Spotlight */
.tutorial-spotlight { .tutorial-spotlight {
position: fixed; position: fixed;
z-index: 9001; z-index: 9001;
box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.65); box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.65);
border: 2px solid var(--accent); border: 2px solid var(--accent);
border-radius: var(--radius-lg); border-radius: var(--radius-lg);
transition: top 0.4s ease, left 0.4s ease, width 0.4s ease, height 0.4s ease, opacity 0.3s ease; transition: top 0.4s ease, left 0.4s ease, width 0.4s ease, height 0.4s ease, opacity 0.3s ease;
opacity: 0; opacity: 0;
pointer-events: none; pointer-events: none;
} }
/* Target-Element klickbar machen */ /* Target-Element klickbar machen */
.tutorial-overlay.active ~ * [data-tutorial-target] { .tutorial-overlay.active ~ * [data-tutorial-target] {
position: relative; position: relative;
z-index: 9002; z-index: 9002;
} }
/* Bubble (Sprechblase) */ /* Bubble (Sprechblase) */
.tutorial-bubble { .tutorial-bubble {
position: fixed; position: fixed;
z-index: 9003; z-index: 9003;
width: 340px; width: 340px;
background: var(--bg-card); background: var(--bg-card);
border: 1px solid var(--accent); border: 1px solid var(--accent);
border-radius: var(--radius-lg); border-radius: var(--radius-lg);
box-shadow: var(--shadow-lg), 0 0 20px rgba(150, 121, 26, 0.15); box-shadow: var(--shadow-lg), 0 0 20px rgba(150, 121, 26, 0.15);
padding: var(--sp-xl); padding: var(--sp-xl);
pointer-events: auto; pointer-events: auto;
opacity: 0; opacity: 0;
transition: opacity 0.3s ease, top 0.4s ease, left 0.4s ease, transform 0.4s ease; transition: opacity 0.3s ease, top 0.4s ease, left 0.4s ease, transform 0.4s ease;
font-family: var(--font-body); font-family: var(--font-body);
} }
.tutorial-bubble.visible { .tutorial-bubble.visible {
opacity: 1; opacity: 1;
} }
/* Bubble-Pfeil */ /* Bubble-Pfeil */
.tutorial-bubble::before { .tutorial-bubble::before {
content: ''; content: '';
position: absolute; position: absolute;
width: 12px; width: 12px;
height: 12px; height: 12px;
background: var(--bg-card); background: var(--bg-card);
border: 1px solid var(--accent); border: 1px solid var(--accent);
transform: rotate(45deg); transform: rotate(45deg);
} }
.tutorial-pos-bottom::before { .tutorial-pos-bottom::before {
top: -7px; top: -7px;
left: 50%; left: 50%;
margin-left: -6px; margin-left: -6px;
border-right: none; border-right: none;
border-bottom: none; border-bottom: none;
} }
.tutorial-pos-top::before { .tutorial-pos-top::before {
bottom: -7px; bottom: -7px;
left: 50%; left: 50%;
margin-left: -6px; margin-left: -6px;
border-left: none; border-left: none;
border-top: none; border-top: none;
} }
.tutorial-pos-right::before { .tutorial-pos-right::before {
left: -7px; left: -7px;
top: 30px; top: 30px;
border-top: none; border-top: none;
border-right: none; border-right: none;
} }
.tutorial-pos-left::before { .tutorial-pos-left::before {
right: -7px; right: -7px;
top: 30px; top: 30px;
border-bottom: none; border-bottom: none;
border-left: none; border-left: none;
} }
.tutorial-pos-center::before { .tutorial-pos-center::before {
display: none; display: none;
} }
/* Bubble-Inhalt */ /* Bubble-Inhalt */
.tutorial-bubble-counter { .tutorial-bubble-counter {
font-size: 11px; font-size: 11px;
color: var(--accent); color: var(--accent);
font-weight: 600; font-weight: 600;
text-transform: uppercase; text-transform: uppercase;
letter-spacing: 0.5px; letter-spacing: 0.5px;
margin-bottom: var(--sp-sm); margin-bottom: var(--sp-sm);
} }
.tutorial-bubble-title { .tutorial-bubble-title {
font-family: var(--font-title); font-family: var(--font-title);
font-size: 16px; font-size: 16px;
font-weight: 600; font-weight: 600;
color: var(--text-primary); color: var(--text-primary);
margin-bottom: var(--sp-md); margin-bottom: var(--sp-md);
} }
.tutorial-bubble-text { .tutorial-bubble-text {
font-size: 13px; font-size: 13px;
color: var(--text-secondary); color: var(--text-secondary);
line-height: 1.6; line-height: 1.6;
margin-bottom: var(--sp-lg); margin-bottom: var(--sp-lg);
} }
/* Close-Button */ /* Close-Button */
.tutorial-bubble-close { .tutorial-bubble-close {
position: absolute; position: absolute;
top: var(--sp-md); top: var(--sp-md);
right: var(--sp-md); right: var(--sp-md);
width: 24px; width: 24px;
height: 24px; height: 24px;
border: none; border: none;
background: transparent; background: transparent;
color: var(--text-secondary); color: var(--text-secondary);
font-size: 18px; font-size: 18px;
cursor: pointer; cursor: pointer;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
border-radius: var(--radius); border-radius: var(--radius);
transition: color 0.15s, background 0.15s; transition: color 0.15s, background 0.15s;
line-height: 1; line-height: 1;
} }
.tutorial-bubble-close:hover { .tutorial-bubble-close:hover {
color: var(--text-primary); color: var(--text-primary);
background: var(--bg-hover); background: var(--bg-hover);
} }
/* Fortschrittspunkte */ /* Fortschrittspunkte */
.tutorial-bubble-dots { .tutorial-bubble-dots {
display: flex; display: flex;
gap: 5px; gap: 5px;
justify-content: center; justify-content: center;
margin-bottom: var(--sp-lg); margin-bottom: var(--sp-lg);
flex-wrap: wrap; flex-wrap: wrap;
} }
.tutorial-dot { .tutorial-dot {
width: 6px; width: 6px;
height: 6px; height: 6px;
border-radius: 50%; border-radius: 50%;
background: var(--border); background: var(--border);
transition: background 0.2s; transition: background 0.2s;
} }
.tutorial-dot.active { .tutorial-dot.active {
background: var(--accent); background: var(--accent);
width: 18px; width: 18px;
border-radius: 3px; border-radius: 3px;
} }
.tutorial-dot.done { .tutorial-dot.done {
background: var(--accent-hover); background: var(--accent-hover);
} }
/* Nav-Buttons */ /* Nav-Buttons */
.tutorial-bubble-nav { .tutorial-bubble-nav {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
gap: var(--sp-md); gap: var(--sp-md);
} }
.tutorial-btn { .tutorial-btn {
border: none; border: none;
border-radius: var(--radius); border-radius: var(--radius);
padding: var(--sp-md) var(--sp-xl); padding: var(--sp-md) var(--sp-xl);
font-size: 13px; font-size: 13px;
font-weight: 500; font-weight: 500;
cursor: pointer; cursor: pointer;
transition: background 0.15s, color 0.15s; transition: background 0.15s, color 0.15s;
font-family: var(--font-body); font-family: var(--font-body);
} }
.tutorial-btn-back { .tutorial-btn-back {
background: var(--bg-hover); background: var(--bg-hover);
color: var(--text-secondary); color: var(--text-secondary);
} }
.tutorial-btn-back:hover { .tutorial-btn-back:hover {
background: var(--bg-elevated); background: var(--bg-elevated);
color: var(--text-primary); color: var(--text-primary);
} }
.tutorial-btn-next { .tutorial-btn-next {
background: var(--accent); background: var(--accent);
color: #fff; color: #fff;
} }
.tutorial-btn-next:hover { .tutorial-btn-next:hover {
background: var(--accent-hover); background: var(--accent-hover);
} }
/* Virtueller Cursor */ /* Virtueller Cursor */
.tutorial-cursor { .tutorial-cursor {
position: fixed; position: fixed;
z-index: 9500; z-index: 9500;
width: 24px; width: 24px;
height: 24px; height: 24px;
pointer-events: none; pointer-events: none;
opacity: 0; opacity: 0;
transition: opacity 0.3s ease; transition: opacity 0.3s ease;
} }
.tutorial-cursor.visible { .tutorial-cursor.visible {
opacity: 1; opacity: 1;
} }
.tutorial-cursor-default { .tutorial-cursor-default {
background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M5 3l14 8-6 2 4 8-3 1-4-8-5 4z' fill='%23fff' stroke='%23000' stroke-width='1'/%3E%3C/svg%3E") no-repeat center/contain; background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M5 3l14 8-6 2 4 8-3 1-4-8-5 4z' fill='%23fff' stroke='%23000' stroke-width='1'/%3E%3C/svg%3E") no-repeat center/contain;
} }
.tutorial-cursor-grabbing { .tutorial-cursor-grabbing {
background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M8 10V8a1 1 0 112 0v2h1V7a1 1 0 112 0v3h1V8a1 1 0 112 0v2h.5a1.5 1.5 0 011.5 1.5V16a5 5 0 01-5 5h-2a5 5 0 01-5-5v-3.5A1.5 1.5 0 017.5 11H8z' fill='%23fff' stroke='%23000' stroke-width='0.8'/%3E%3C/svg%3E") no-repeat center/contain; background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M8 10V8a1 1 0 112 0v2h1V7a1 1 0 112 0v3h1V8a1 1 0 112 0v2h.5a1.5 1.5 0 011.5 1.5V16a5 5 0 01-5 5h-2a5 5 0 01-5-5v-3.5A1.5 1.5 0 017.5 11H8z' fill='%23fff' stroke='%23000' stroke-width='0.8'/%3E%3C/svg%3E") no-repeat center/contain;
} }
.tutorial-cursor-resize { .tutorial-cursor-resize {
background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M22 22H20V20H22V22ZM22 18H18V22H16V16H22V18ZM18 18V14H22V12H16V18H18ZM14 22H12V16H18V14H10V22H14Z' fill='%23fff' stroke='%23000' stroke-width='0.3'/%3E%3C/svg%3E") no-repeat center/contain; background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M22 22H20V20H22V22ZM22 18H18V22H16V16H22V18ZM18 18V14H22V12H16V18H18ZM14 22H12V16H18V14H10V22H14Z' fill='%23fff' stroke='%23000' stroke-width='0.3'/%3E%3C/svg%3E") no-repeat center/contain;
} }
/* Chat Tutorial-Hinweis */ /* Chat Tutorial-Hinweis */
.chat-tutorial-hint { .chat-tutorial-hint {
background: var(--bg-card); background: var(--bg-card);
border: 1px solid var(--accent); border: 1px solid var(--accent);
border-radius: var(--radius); border-radius: var(--radius);
padding: var(--sp-lg); padding: var(--sp-lg);
margin: var(--sp-md) var(--sp-md) 0; margin: var(--sp-md) var(--sp-md) 0;
cursor: pointer; cursor: pointer;
transition: background 0.15s; transition: background 0.15s;
font-size: 13px; font-size: 13px;
color: var(--text-secondary); color: var(--text-secondary);
line-height: 1.5; line-height: 1.5;
} }
.chat-tutorial-hint:hover { .chat-tutorial-hint:hover {
background: var(--tint-accent-subtle); background: var(--tint-accent-subtle);
} }
.chat-tutorial-hint strong { .chat-tutorial-hint strong {
color: var(--accent); color: var(--accent);
} }
/* Sub-Element Highlight innerhalb von Tutorial-Steps */ /* Sub-Element Highlight innerhalb von Tutorial-Steps */
.tutorial-sub-highlight { .tutorial-sub-highlight {
outline: 2px solid var(--accent) !important; outline: 2px solid var(--accent) !important;
outline-offset: 3px; outline-offset: 3px;
border-radius: var(--radius); border-radius: var(--radius);
animation: tutorial-sub-pulse 1.5s ease-in-out infinite; animation: tutorial-sub-pulse 1.5s ease-in-out infinite;
position: relative; position: relative;
z-index: 9002; z-index: 9002;
} }
@keyframes tutorial-sub-pulse { @keyframes tutorial-sub-pulse {
0%, 100% { outline-color: var(--accent); } 0%, 100% { outline-color: var(--accent); }
50% { outline-color: rgba(150, 121, 26, 0.4); } 50% { outline-color: rgba(150, 121, 26, 0.4); }
} }
/* Chat Tutorial-Hint Layout */ /* Chat Tutorial-Hint Layout */
.chat-tutorial-hint { .chat-tutorial-hint {
@@ -5228,3 +5228,17 @@ a.map-popup-article:hover {
.chat-tutorial-hint-close:hover { .chat-tutorial-hint-close:hover {
color: var(--text-primary); color: var(--text-primary);
} }
/* Tutorial: Klicks auf Dashboard blockieren */
body.tutorial-active .dashboard,
body.tutorial-active .modal-overlay,
body.tutorial-active .chat-toggle-btn,
body.tutorial-active #chat-window {
pointer-events: none !important;
}
/* Bubble und Cursor bleiben klickbar */
body.tutorial-active .tutorial-bubble,
body.tutorial-active .tutorial-cursor {
pointer-events: auto !important;
}

Datei anzeigen

@@ -17,7 +17,7 @@
<link rel="stylesheet" href="/static/vendor/MarkerCluster.css"> <link rel="stylesheet" href="/static/vendor/MarkerCluster.css">
<link rel="stylesheet" href="/static/vendor/MarkerCluster.Default.css"> <link rel="stylesheet" href="/static/vendor/MarkerCluster.Default.css">
<link rel="stylesheet" href="/static/css/network.css?v=20260316a"> <link rel="stylesheet" href="/static/css/network.css?v=20260316a">
<link rel="stylesheet" href="/static/css/style.css?v=20260316e"> <link rel="stylesheet" href="/static/css/style.css?v=20260316f">
</head> </head>
<body> <body>
<a href="#main-content" class="skip-link">Zum Hauptinhalt springen</a> <a href="#main-content" class="skip-link">Zum Hauptinhalt springen</a>
@@ -764,7 +764,7 @@
<script src="/static/js/api_network.js?v=20260316a"></script> <script src="/static/js/api_network.js?v=20260316a"></script>
<script src="/static/js/network-graph.js?v=20260316a"></script> <script src="/static/js/network-graph.js?v=20260316a"></script>
<script src="/static/js/app_network.js?v=20260316a"></script> <script src="/static/js/app_network.js?v=20260316a"></script>
<script src="/static/js/tutorial.js?v=20260316g"></script> <script src="/static/js/tutorial.js?v=20260316h"></script>
<script src="/static/js/chat.js?v=20260316f"></script> <script src="/static/js/chat.js?v=20260316f"></script>
<script>document.addEventListener("DOMContentLoaded",function(){Chat.init();Tutorial.init()});</script> <script>document.addEventListener("DOMContentLoaded",function(){Chat.init();Tutorial.init()});</script>

Datei anzeigen

@@ -460,14 +460,13 @@ const Tutorial = {
id: 'new-incident-btn', id: 'new-incident-btn',
target: '#new-incident-btn', target: '#new-incident-btn',
title: 'Neue Lage anlegen', title: 'Neue Lage anlegen',
text: 'Mit diesem Button erstellen Sie eine neue Lage. Wir simulieren jetzt die Eingabe f\u00fcr Sie.', text: 'Mit diesem Button öffnen Sie das Formular zur Erstellung einer neuen Lage. '
+ 'Wir gehen jetzt gemeinsam alle Felder durch.',
position: 'right', position: 'right',
onEnter: function() { onEnter: function() {
Tutorial._stepTimeout(function() { Tutorial._stepTimeout(function() {
var overlay = document.getElementById('modal-new'); var overlay = document.getElementById('modal-new');
if (overlay && !overlay.classList.contains('active')) { if (overlay && !overlay.classList.contains('active')) overlay.classList.add('active');
overlay.classList.add('active');
}
}, 1500); }, 1500);
}, },
onExit: function() { onExit: function() {
@@ -475,51 +474,45 @@ const Tutorial = {
if (overlay) overlay.classList.remove('active'); if (overlay) overlay.classList.remove('active');
}, },
}, },
// 3 - Formular ausf\u00fcllen (Cursor-Demo) // 3 - Titel und Beschreibung (Cursor-Demo)
{ {
id: 'new-incident-modal', id: 'form-title-desc',
target: '#modal-new .modal', target: '#modal-new .modal',
title: 'Lage konfigurieren', title: 'Titel und Beschreibung',
text: 'Beobachten Sie, wie das Formular Schritt f\u00fcr Schritt ausgef\u00fcllt wird. ' text: 'Geben Sie einen aussagekräftigen <strong>Titel</strong> ein, der das Ereignis klar beschreibt. '
+ 'So legen Sie eine neue Lage an.', + 'Die <strong>Beschreibung</strong> liefert zusätzlichen Kontext für die Recherche.<br><br>'
+ 'Beobachten Sie die Eingabe:',
position: 'left', position: 'left',
disableNav: true, disableNav: true,
onEnter: function() { onEnter: function() {
var overlay = document.getElementById('modal-new'); var overlay = document.getElementById('modal-new');
if (overlay && !overlay.classList.contains('active')) { if (overlay && !overlay.classList.contains('active')) overlay.classList.add('active');
overlay.classList.add('active');
}
if (overlay) overlay.style.zIndex = '9002'; if (overlay) overlay.style.zIndex = '9002';
// Formular zuruecksetzen var modalBody = document.querySelector('#modal-new .modal-body');
if (modalBody) modalBody.scrollTop = 0;
var t = document.getElementById('inc-title'); if (t) t.value = ''; var t = document.getElementById('inc-title'); if (t) t.value = '';
var d = document.getElementById('inc-description'); if (d) d.value = ''; var d = document.getElementById('inc-description'); if (d) d.value = '';
var r = document.getElementById('inc-refresh-mode'); if (r) { r.value = 'manual'; try { r.dispatchEvent(new Event('change')); } catch(e) {} } Tutorial._simulateFormTitleDesc();
Tutorial._simulateFormFill();
}, },
onExit: function() { onExit: function() {
// Werte stehen lassen (werden in stop() aufgeraeumt) Tutorial._clearSubHighlights();
}, },
}, },
// 4 - Lage-Typ: Live vs Recherche (mit Cursor-Demo) // 4 - Art der Lage (Cursor-Demo)
{ {
id: 'incident-type', id: 'form-type',
target: '#modal-new .modal', target: '#modal-new .modal',
title: 'Live-Monitoring vs. Recherche', title: 'Art der Lage',
text: '<strong>Live-Monitoring</strong> beobachtet ein Ereignis in Echtzeit. ' text: '<strong>Live-Monitoring</strong> beobachtet ein Ereignis in Echtzeit. Hunderte Quellen werden '
+ 'Hunderte Nachrichtenquellen werden automatisch durchsucht. ' + 'laufend durchsucht. Ideal für aktuelle Krisen und sich entwickelnde Lagen.<br><br>'
+ 'Ideal f\u00fcr aktuelle Vorf\u00e4lle, Krisen oder sich entwickelnde Lagen.<br><br>' + '<strong>Analyse/Recherche</strong> untersucht ein Thema tiefergehend ohne automatische Updates. '
+ '<strong>Analyse/Recherche</strong> untersucht ein Thema tiefergehend. ' + 'Ideal für Hintergrundanalysen und Lageberichte.',
+ 'Keine automatischen Updates, stattdessen gezielte Recherche mit eigenen Suchbegriffen. '
+ 'Ideal f\u00fcr Hintergrundanalysen und Lageberichte.',
position: 'left', position: 'left',
disableNav: true, disableNav: true,
onEnter: function() { onEnter: function() {
var overlay = document.getElementById('modal-new'); var overlay = document.getElementById('modal-new');
if (overlay && !overlay.classList.contains('active')) { if (overlay && !overlay.classList.contains('active')) overlay.classList.add('active');
overlay.classList.add('active');
}
if (overlay) overlay.style.zIndex = '9002'; if (overlay) overlay.style.zIndex = '9002';
// Modal-Body nach oben scrollen zum Typ-Feld
var modalBody = document.querySelector('#modal-new .modal-body'); var modalBody = document.querySelector('#modal-new .modal-body');
if (modalBody) modalBody.scrollTo({ top: 0, behavior: 'smooth' }); if (modalBody) modalBody.scrollTo({ top: 0, behavior: 'smooth' });
Tutorial._stepTimeout(function() { Tutorial._stepTimeout(function() {
@@ -527,19 +520,124 @@ const Tutorial = {
Tutorial._simulateTypeSwitch(); Tutorial._simulateTypeSwitch();
}, 500); }, 500);
}, },
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',
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._simulateFormSources();
},
onExit: function() {
Tutorial._clearSubHighlights();
},
},
// 6 - Sichtbarkeit
{
id: 'form-visibility',
target: '#modal-new .modal',
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._simulateFormVisibility();
},
onExit: function() {
Tutorial._clearSubHighlights();
},
},
// 7 - Aktualisierung und Intervall
{
id: 'form-refresh',
target: '#modal-new .modal',
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._simulateFormRefresh();
},
onExit: function() {
Tutorial._clearSubHighlights();
},
},
// 8 - Aufbewahrung
{
id: 'form-retention',
target: '#modal-new .modal',
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._simulateFormRetention();
},
onExit: function() {
Tutorial._clearSubHighlights();
},
},
// 9 - E-Mail-Benachrichtigungen
{
id: 'form-notifications',
target: '#modal-new .modal',
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._simulateFormNotifications();
},
onExit: function() { onExit: function() {
var overlay = document.getElementById('modal-new'); var overlay = document.getElementById('modal-new');
if (overlay) { if (overlay) {
overlay.classList.remove('active'); overlay.classList.remove('active');
overlay.style.zIndex = ''; overlay.style.zIndex = '';
} }
// Select zur\u00fccksetzen
var sel = document.getElementById('inc-type');
if (sel) { sel.value = 'adhoc'; sel.dispatchEvent(new Event('change')); }
Tutorial._clearSubHighlights(); Tutorial._clearSubHighlights();
}, },
}, },
// 5 - Sidebar Filter // 5 - Sidebar Filter
{ {
id: 'sidebar-filters', id: 'sidebar-filters',
target: '.sidebar-filter', target: '.sidebar-filter',
@@ -905,8 +1003,9 @@ const Tutorial = {
// Chat schließen falls offen // Chat schließen falls offen
if (typeof Chat !== 'undefined' && Chat._isOpen) Chat.close(); if (typeof Chat !== 'undefined' && Chat._isOpen) Chat.close();
// Overlay einblenden // Overlay einblenden + Klicks blockieren
this._els.overlay.classList.add('active'); this._els.overlay.classList.add('active');
document.body.classList.add('tutorial-active');
// Keyboard // Keyboard
this._keyHandler = this._onKey.bind(this); this._keyHandler = this._onKey.bind(this);
@@ -929,8 +1028,9 @@ const Tutorial = {
this._currentStep = -1; this._currentStep = -1;
this._demoRunning = false; this._demoRunning = false;
// Overlay ausblenden // Overlay ausblenden + Klicks freigeben
this._els.overlay.classList.remove('active'); this._els.overlay.classList.remove('active');
document.body.classList.remove('tutorial-active');
this._els.spotlight.style.opacity = '0'; this._els.spotlight.style.opacity = '0';
this._els.bubble.classList.remove('visible'); this._els.bubble.classList.remove('visible');
this._hideCursor(); this._hideCursor();
@@ -1307,146 +1407,117 @@ const Tutorial = {
}); });
}, },
// ----------------------------------------------------------------------- // Helfer: Cursor zu einem Element bewegen
// Formular-Ausf\u00fcll-Demo (Step 3) async _cursorToElement(selector, fromX, fromY) {
// ----------------------------------------------------------------------- var el = document.querySelector(selector);
async _simulateFormFill() { if (!el) return { x: fromX, y: fromY };
this._demoRunning = true; var rect = el.getBoundingClientRect();
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;
var elTop = el.offsetTop - modalBody.offsetTop;
modalBody.scrollTo({ top: Math.max(0, elTop - 20), behavior: 'smooth' });
},
// -----------------------------------------------------------------------
// Step 3: Titel + Beschreibung
// -----------------------------------------------------------------------
async _simulateFormTitleDesc() {
this._demoRunning = true;
var titleInput = document.getElementById('inc-title'); var titleInput = document.getElementById('inc-title');
var descInput = document.getElementById('inc-description'); var descInput = document.getElementById('inc-description');
var refreshSelect = document.getElementById('inc-refresh-mode'); if (!titleInput) { this._demoRunning = false; this._enableNavAfterDemo(); return; }
var modal = document.querySelector('#modal-new .modal');
if (!titleInput || !modal) { // Cursor zum Titel
this._demoRunning = false;
this._enableNavAfterDemo();
return;
}
// Modal-Body scrollen wir nach oben
var modalBody = modal.querySelector('.modal-body');
if (modalBody) modalBody.scrollTop = 0;
// 1. Cursor zum Titel-Feld
var titleRect = titleInput.getBoundingClientRect();
var startX = titleRect.left - 60;
var startY = titleRect.top - 40;
this._showCursor(startX, startY, 'default');
await this._wait(300);
await this._animateCursor(startX, startY, titleRect.left + 20, titleRect.top + titleRect.height / 2, 600);
await this._wait(200);
// Fokus + Tippen
titleInput.focus();
this._highlightSub('#inc-title'); this._highlightSub('#inc-title');
var pos = await this._cursorToElement('#inc-title');
titleInput.focus();
await this._simulateTyping(titleInput, 'Explosion in Hamburger Hafen', 1200); await this._simulateTyping(titleInput, 'Explosion in Hamburger Hafen', 1200);
await this._wait(500); await this._wait(400);
this._clearSubHighlights(); this._clearSubHighlights();
// 2. Cursor zur Beschreibung // Cursor zur Beschreibung
if (descInput) { if (descInput) {
var descRect = descInput.getBoundingClientRect();
await this._animateCursor(titleRect.left + 20, titleRect.top + titleRect.height / 2, descRect.left + 20, descRect.top + 12, 500);
await this._wait(200);
descInput.focus();
this._highlightSub('#inc-description'); this._highlightSub('#inc-description');
await this._simulateTyping(descInput, 'Schwere Explosion im Hafengebiet', 1000); pos = await this._cursorToElement('#inc-description', pos.x, pos.y);
await this._wait(500); descInput.focus();
await this._simulateTyping(descInput, 'Schwere Explosion im Hafengebiet, Burchardkai-Terminal', 1200);
await this._wait(400);
this._clearSubHighlights(); this._clearSubHighlights();
} }
// 3. Modal-Body runterscrollen zum Refresh-Feld
if (refreshSelect && modalBody) {
// Sanft scrollen
modalBody.scrollTo({ top: modalBody.scrollHeight, behavior: 'smooth' });
await this._wait(600);
var refRect = refreshSelect.getBoundingClientRect();
var prevRect = descInput ? descInput.getBoundingClientRect() : titleRect;
await this._animateCursor(prevRect.left + 20, prevRect.top + 12, refRect.left + refRect.width / 2, refRect.top + refRect.height / 2, 600);
await this._wait(200);
this._highlightSub('#inc-refresh-mode');
refreshSelect.value = 'auto';
try { refreshSelect.dispatchEvent(new Event('change')); } catch(e) {}
await this._wait(1200);
this._clearSubHighlights();
}
// 4. Cursor verschwindet
this._hideCursor(); this._hideCursor();
await this._wait(300);
this._demoRunning = false; this._demoRunning = false;
this._enableNavAfterDemo(); this._enableNavAfterDemo();
}, },
// ----------------------------------------------------------------------- // -----------------------------------------------------------------------
// Typ-Wechsel-Demo (Step 4) // Step 4: Art der Lage (Typ-Wechsel)
// ----------------------------------------------------------------------- // -----------------------------------------------------------------------
async _simulateTypeSwitch() { async _simulateTypeSwitch() {
this._demoRunning = true; this._demoRunning = true;
var sel = document.getElementById('inc-type'); var sel = document.getElementById('inc-type');
if (!sel) { this._demoRunning = false; this._enableNavAfterDemo(); return; } if (!sel) { this._demoRunning = false; this._enableNavAfterDemo(); return; }
var rect = sel.getBoundingClientRect(); var pos = await this._cursorToElement('#inc-type');
var targetX = rect.left + rect.width / 2;
var targetY = rect.top + rect.height / 2;
// 1. Cursor erscheint oben links im Modal, f\u00e4hrt zum Select
var startX = rect.left - 80;
var startY = rect.top - 60;
this._showCursor(startX, startY, 'default');
await this._wait(400);
await this._animateCursor(startX, startY, targetX, targetY, 800);
await this._wait(300); await this._wait(300);
// 2. Klick - wechselt zu Recherche // Wechsel zu Recherche
sel.value = 'research'; sel.value = 'research';
sel.dispatchEvent(new Event('change')); sel.dispatchEvent(new Event('change'));
this._highlightSub('#inc-type');
await this._wait(2000); await this._wait(2000);
// 3. Zur\u00fcck zu Live-Monitoring // Zur\u00fcck zu Live-Monitoring
sel.value = 'adhoc'; sel.value = 'adhoc';
sel.dispatchEvent(new Event('change')); sel.dispatchEvent(new Event('change'));
await this._wait(1000); await this._wait(800);
// 4. Cursor verschwindet
this._hideCursor(); this._hideCursor();
this._demoRunning = false; this._demoRunning = false;
this._enableNavAfterDemo(); this._enableNavAfterDemo();
}, },
// ----------------------------------------------------------------------- // -----------------------------------------------------------------------
// Drag-Demo // Step 5: Quellen (International + Telegram toggles)
// ----------------------------------------------------------------------- // -----------------------------------------------------------------------
async _simulateDrag() { async _simulateFormSources() {
this._demoRunning = true; this._demoRunning = true;
var el = document.querySelector('[gs-id="lagebild"] .card-header'); this._scrollModalTo('#inc-international');
if (!el) { this._demoRunning = false; this._enableNavAfterDemo(); return; } await this._wait(400);
var rect = el.getBoundingClientRect(); // International-Toggle highlighten
var startX = rect.left + rect.width / 2; var intlCheckbox = document.getElementById('inc-international');
var startY = rect.top + rect.height / 2; var intlLabel = intlCheckbox ? intlCheckbox.closest('.toggle-label') : null;
var endX = startX + 200; if (intlLabel) {
var endY = startY; this._highlightSub('#inc-international');
var pos = await this._cursorToElement('#inc-international');
await this._wait(1500);
this._clearSubHighlights();
this._showCursor(startX, startY, 'default'); // Telegram-Toggle
await this._wait(500); var telegramCheckbox = document.getElementById('inc-telegram');
if (telegramCheckbox) {
this._els.cursor.classList.remove('tutorial-cursor-default'); this._highlightSub('#inc-telegram');
this._els.cursor.classList.add('tutorial-cursor-grabbing'); pos = await this._cursorToElement('#inc-telegram', pos.x, pos.y);
await this._wait(300); // Aktivieren
telegramCheckbox.checked = true;
await this._animateCursor(startX, startY, endX, endY, 1500); await this._wait(1500);
await this._wait(200); this._clearSubHighlights();
}
this._els.cursor.classList.remove('tutorial-cursor-grabbing'); }
this._els.cursor.classList.add('tutorial-cursor-default');
await this._animateCursor(endX, endY, startX, startY, 800);
await this._wait(300);
this._hideCursor(); this._hideCursor();
this._demoRunning = false; this._demoRunning = false;
@@ -1454,52 +1525,121 @@ const Tutorial = {
}, },
// ----------------------------------------------------------------------- // -----------------------------------------------------------------------
// Resize-Demo // Step 6: Sichtbarkeit
// ----------------------------------------------------------------------- // -----------------------------------------------------------------------
async _simulateResize() { async _simulateFormVisibility() {
this._demoRunning = true; this._demoRunning = true;
var el = document.querySelector('[gs-id="faktencheck"]'); this._scrollModalTo('#inc-visibility');
if (!el) { this._demoRunning = false; this._enableNavAfterDemo(); return; } await this._wait(400);
var rect = el.getBoundingClientRect(); var checkbox = document.getElementById('inc-visibility');
var startX = rect.right - 4; if (checkbox) {
var startY = rect.bottom - 4; this._highlightSub('#inc-visibility');
var endX = startX + 100; var pos = await this._cursorToElement('#inc-visibility');
var endY = startY + 60; await this._wait(1000);
this._showCursor(startX, startY, 'resize'); // Umschalten auf Privat
await this._wait(500); checkbox.checked = false;
var textEl = document.getElementById('visibility-text');
if (textEl) textEl.textContent = 'Privat \u2014 nur f\u00fcr dich sichtbar';
await this._wait(1500);
await this._animateCursor(startX, startY, endX, endY, 1200); // Zur\u00fcck auf \u00d6ffentlich
await this._wait(200); checkbox.checked = true;
if (textEl) textEl.textContent = '\u00d6ffentlich \u2014 f\u00fcr alle Nutzer sichtbar';
await this._animateCursor(endX, endY, startX, startY, 800); await this._wait(800);
await this._wait(300); this._clearSubHighlights();
}
this._hideCursor(); this._hideCursor();
this._demoRunning = false; this._demoRunning = false;
this._enableNavAfterDemo(); this._enableNavAfterDemo();
}, },
_enableNavAfterDemo() { // -----------------------------------------------------------------------
var bubble = this._els.bubble; // Step 7: Aktualisierung + Intervall
var nav = bubble.querySelector('.tutorial-bubble-nav'); // -----------------------------------------------------------------------
if (!nav) return; async _simulateFormRefresh() {
var index = this._currentStep; this._demoRunning = true;
var total = this._steps.length; this._scrollModalTo('#inc-refresh-mode');
await this._wait(400);
var navHtml = ''; var refreshSelect = document.getElementById('inc-refresh-mode');
if (index > 0) { if (refreshSelect) {
navHtml += '<button class="tutorial-btn tutorial-btn-back" onclick="Tutorial.prev()">Zurück</button>'; this._highlightSub('#inc-refresh-mode');
} else { var pos = await this._cursorToElement('#inc-refresh-mode');
navHtml += '<span></span>'; 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();
}
} }
if (index < total - 1) {
navHtml += '<button class="tutorial-btn tutorial-btn-next" onclick="Tutorial.next()">Weiter</button>'; this._hideCursor();
} else { this._demoRunning = false;
navHtml += '<button class="tutorial-btn tutorial-btn-next" onclick="Tutorial.stop()">Fertig</button>'; this._enableNavAfterDemo();
},
// -----------------------------------------------------------------------
// Step 8: Aufbewahrung
// -----------------------------------------------------------------------
async _simulateFormRetention() {
this._demoRunning = true;
this._scrollModalTo('#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();
} }
nav.innerHTML = navHtml;
this._hideCursor();
this._demoRunning = false;
this._enableNavAfterDemo();
},
// -----------------------------------------------------------------------
// Step 9: E-Mail-Benachrichtigungen
// -----------------------------------------------------------------------
async _simulateFormNotifications() {
this._demoRunning = true;
this._scrollModalTo('#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._cursorToElement(checks[i], pos.x, pos.y);
} else {
pos = await this._cursorToElement(checks[i]);
}
cb.checked = true;
await this._wait(1000);
this._clearSubHighlights();
}
this._hideCursor();
this._demoRunning = false;
this._enableNavAfterDemo();
}, },
// ----------------------------------------------------------------------- // -----------------------------------------------------------------------