diff --git a/public/js/app.js b/public/js/app.js
index 8df37cd..15ec4d6 100644
--- a/public/js/app.js
+++ b/public/js/app.js
@@ -955,6 +955,11 @@ function submitLogin() {
document.cookie = 'webgis_user=' + encodeURIComponent(name) + ';path=/;max-age=31536000;SameSite=Lax';
document.getElementById('login-modal').style.display = 'none';
+ // Starts Onboarding Tour on first Login
+ if (!localStorage.getItem('webgis_onboarding_done')) {
+ setTimeout(function () { startTour(); }, 500);
+ }
+
// Open Create Modal if Geometry is pending
if (drawnGeometry) {
document.getElementById('create-geom').value = JSON.stringify(drawnGeometry);
@@ -965,6 +970,11 @@ function submitLogin() {
function skipLogin() {
document.getElementById('login-modal').style.display = 'none';
+
+ // Starts Onboarding Tour on first Login
+ if (!localStorage.getItem('webgis_onboarding_done')) {
+ setTimeout(function () { startTour(); }, 500);
+ }
}
// Info Modal
diff --git a/public/js/onboarding.js b/public/js/onboarding.js
index eff5ccd..fce1845 100644
--- a/public/js/onboarding.js
+++ b/public/js/onboarding.js
@@ -9,12 +9,6 @@
// Block 1: Onboarding Configuration
// =================================================================
-// ONBOARDING_MODE — Controls when the Tutorial is shown:
-const ONBOARDING_MODE = 'once';
-// 'once' — Shown on first Visit, stored in localStorage
-// 'session' — Shown per Browser Session, stored in sessionStorage
-// 'always' — Shows always, nothing stored
-
// Prevents double Initialization
let onboardingStarted = false;
@@ -25,57 +19,10 @@ function isMobile() {
// =================================================================
-// Block 2: Tour Initialization
+// Block 2: Tour Definition
// =================================================================
-function initOnboardingTour() {
-
- // Checks if Tutorial should be shown based on Onboarding Mode
- if (ONBOARDING_MODE === 'once' && localStorage.getItem('webgis_onboarding_done')) {
- return;
- }
- if (ONBOARDING_MODE === 'session' && sessionStorage.getItem('webgis_onboarding_done')) {
- return;
- }
-
- // Waits for Welcome and Login Modals to be closed
- waitForModalsToClose(function () {
- setTimeout(startTour, 600);
- });
-}
-
-
-// =================================================================
-// Block 3: Modal Watcher — Starts Tour after Welcome and Login Modals closed
-// =================================================================
-
-function waitForModalsToClose(callback) {
- const welcomeModal = document.getElementById('welcome-modal');
- const loginModal = document.getElementById('login-modal');
-
- const checkInterval = setInterval(function () {
- const welcomeHidden = !welcomeModal || welcomeModal.style.display === 'none' || welcomeModal.style.display === '';
- const loginHidden = !loginModal || loginModal.style.display === 'none' || loginModal.style.display === '';
-
- if (welcomeHidden && loginHidden) {
- clearInterval(checkInterval);
- callback();
- }
- }, 300);
-
- // Safety Timeout
- setTimeout(function () {
- clearInterval(checkInterval);
- callback();
- }, 30000);
-}
-
-
-// =================================================================
-// Block 4: Tour Definition
-// =================================================================
-
-function startTour() {
+function startTour(manual) {
// Prevents double Start
if (onboardingStarted) return;
onboardingStarted = true;
@@ -97,26 +44,45 @@ function startTour() {
});
+// -----------------------------------------------------------------
+ // Step 1: Welcome — Skip Timer at automatic Start
// -----------------------------------------------------------------
- // Step 1: Welcome (identical on Mobile and Desktop)
- // -----------------------------------------------------------------
+ var welcomeButtons = [
+ {
+ text: 'Überspringen',
+ action: tour.cancel,
+ classes: 'shepherd-button-secondary' + (manual ? '' : ' skip-btn-locked')
+ },
+ {
+ text: 'Los geht\'s ',
+ action: tour.next,
+ classes: 'shepherd-button-primary'
+ }
+ ];
+
tour.addStep({
id: 'welcome',
title: ' Willkommen bei der Mitmachkarte!',
text: 'Dieses interaktive Tutorial zeigt Ihnen die Kernfunktionen der Mitmachkarte.' +
- '
Sie können das Tutorial jederzeit über den Hilfe-Tab der Seitenleiste wiederholen.',
- buttons: [
- {
- text: 'Überspringen',
- action: tour.cancel,
- classes: 'shepherd-button-secondary'
- },
- {
- text: 'Los geht\'s ',
- action: tour.next,
- classes: 'shepherd-button-primary'
+ '
Sie können das Tutorial jederzeit über den Hilfe-Tab der Seitenleiste wiederholen.',
+ buttons: welcomeButtons,
+ when: {
+ show: function () {
+ if (manual) return;
+
+ // Locks Skip Button with Progress Bar for 5 Seconds
+ var skipBtn = document.querySelector('.skip-btn-locked');
+ if (!skipBtn) return;
+ skipBtn.disabled = true;
+ skipBtn.style.pointerEvents = 'none';
+
+ setTimeout(function () {
+ skipBtn.disabled = false;
+ skipBtn.style.pointerEvents = '';
+ skipBtn.classList.remove('skip-btn-locked');
+ }, 5000);
}
- ]
+ }
});
@@ -233,7 +199,7 @@ function startTour() {
classes: 'shepherd-button-secondary'
},
{
- text: 'Beenden ',
+ text: 'Abschließen ',
action: tour.next,
classes: 'shepherd-button-primary'
}
@@ -259,51 +225,80 @@ function startTour() {
// -----------------------------------------------------------------
- // Completion and Cancellation
+ // Completion and Cancellation — shows Drawing Arrow
// -----------------------------------------------------------------
- tour.on('complete', function () {
- markOnboardingDone();
+ function onTourEnd() {
onboardingStarted = false;
if (mobile) sidebar.close();
- });
- tour.on('cancel', function () {
- markOnboardingDone();
- onboardingStarted = false;
- if (mobile) sidebar.close();
- });
+ // Shows Arrow Hint
+ if (!localStorage.getItem('webgis_onboarding_done')) {
+ localStorage.setItem('webgis_onboarding_done', 'true');
+ showDrawingArrow();
+ }
+ }
+
+ tour.on('complete', onTourEnd);
+ tour.on('cancel', onTourEnd);
tour.start();
}
// =================================================================
-// Marks Onboarding as completed
+// Drawing Arrow — Points to Geoman Toolbar after Tour
// =================================================================
-function markOnboardingDone() {
- if (ONBOARDING_MODE === 'once') {
- localStorage.setItem('webgis_onboarding_done', 'true');
- } else if (ONBOARDING_MODE === 'session') {
- sessionStorage.setItem('webgis_onboarding_done', 'true');
+function showDrawingArrow() {
+ var hint = document.createElement('div');
+ hint.id = 'drawing-hint-arrow';
+ hint.innerHTML = '' +
+ ' Beitrag hinzufügen' +
+ '' +
+ '' +
+ '' +
+ '' +
+ '';
+ document.body.appendChild(hint);
+
+ // Positions Hint centered on Geoman Toolbar
+ function positionHint() {
+ var toolbar = document.querySelector('.leaflet-pm-toolbar');
+ if (!toolbar) { removeDrawingArrow(); return; }
+
+ var rect = toolbar.getBoundingClientRect();
+ var hintHeight = hint.offsetHeight || 32;
+ hint.style.top = (rect.top + (rect.height / 2) - (hintHeight / 2)) + 'px';
+ hint.style.right = (window.innerWidth - rect.left + 10) + 'px';
+ }
+
+ positionHint();
+ window.addEventListener('resize', positionHint);
+
+ var timeout = setTimeout(removeDrawingArrow, 60000);
+
+ map.on('pm:globaldrawmodetoggled', function onDraw() {
+ clearTimeout(timeout);
+ removeDrawingArrow();
+ map.off('pm:globaldrawmodetoggled', onDraw);
+ window.removeEventListener('resize', positionHint);
+ });
+}
+
+function removeDrawingArrow() {
+ var arrow = document.getElementById('drawing-hint-arrow');
+ if (arrow) {
+ arrow.classList.add('fade-out');
+ setTimeout(function () { arrow.remove(); }, 300);
}
}
// =================================================================
-// Manual Tour Restart
+// Manual Tour Restart (from Info Modal or Help Tab)
// =================================================================
function restartOnboarding() {
- localStorage.removeItem('webgis_onboarding_done');
- sessionStorage.removeItem('webgis_onboarding_done');
onboardingStarted = false;
- startTour();
-}
-
-
-// =================================================================
-// Auto-Start on Page Load
-// =================================================================
-
-initOnboardingTour();
\ No newline at end of file
+ startTour(true);
+}
\ No newline at end of file
diff --git a/public/styles.css b/public/styles.css
index 32c9935..9116e55 100644
--- a/public/styles.css
+++ b/public/styles.css
@@ -875,6 +875,97 @@ select.form-input { cursor: pointer; }
transform: translate(-50%, -50%) !important;
}
+
+/* -----------------------------------------------------------------
+ 4.10 Skip Button Progress Bar (Onboarding)
+ ----------------------------------------------------------------- */
+.skip-btn-locked {
+ position: relative;
+ overflow: hidden;
+ opacity: 0.6;
+}
+
+.skip-btn-locked::after {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ height: 100%;
+ width: 0%;
+ background: rgba(0, 0, 0, 0.12);
+ border-radius: 6px;
+ animation: skip-fill 5s linear forwards;
+ pointer-events: none;
+}
+
+@keyframes skip-fill {
+ from { width: 0%; }
+ to { width: 100%; opacity: 0; }
+}
+
+
+/* -----------------------------------------------------------------
+ 4.11 Drawing Hint Arrow after Onboarding
+ ----------------------------------------------------------------- */
+#drawing-hint-arrow {
+ position: fixed;
+ z-index: 1500;
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ pointer-events: none;
+ animation: hint-fade-in 400ms ease;
+ transition: opacity 300ms ease;
+}
+
+#drawing-hint-arrow.fade-out {
+ opacity: 0;
+}
+
+.drawing-hint-label {
+ background: var(--color-primary);
+ color: white;
+ font-family: var(--font-body);
+ font-size: 0.78rem;
+ font-weight: 600;
+ padding: 6px 14px;
+ border-radius: 20px;
+ white-space: nowrap;
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
+ display: flex;
+ align-items: center;
+ gap: 6px;
+}
+
+.drawing-hint-chevrons {
+ display: flex;
+ gap: 0;
+ color: var(--color-primary);
+ font-size: 1.2rem;
+ font-weight: 900;
+ filter: drop-shadow(0 1px 3px rgba(0, 0, 0, 0.2));
+}
+
+.drawing-hint-chevrons i:nth-child(1) {
+ animation: chevron-pulse 1.2s ease-in-out infinite;
+ opacity: 0.4;
+}
+
+.drawing-hint-chevrons i:nth-child(2) {
+ animation: chevron-pulse 1.2s ease-in-out 0.3s infinite;
+ opacity: 0.7;
+}
+
+@keyframes chevron-pulse {
+ 0%, 100% { transform: translateX(0); opacity: 0.3; }
+ 50% { transform: translateX(3px); opacity: 1; }
+}
+
+@keyframes hint-fade-in {
+ from { opacity: 0; transform: translateX(-10px); }
+ to { opacity: 1; transform: translateX(0); }
+}
+
/* Mobile */
@media (max-width: 768px) {
.shepherd-element { max-width: 300px !important; }