From fe62cbbaee8cb4cf4dc63495b7bfc571ab54c3d2 Mon Sep 17 00:00:00 2001 From: Claude Dev Date: Mon, 23 Mar 2026 22:28:07 +0100 Subject: [PATCH] Tutorial: Vollstaendige Neupositionierung bei Fenstergroessen-Aenderung _onResize fuehrt jetzt alle Positionierungsschritte durch: - Spotlight-Update - Modal-Scroll zum bubbleTarget - Bubble-Positionierung mit dynamischer Breite - Viewport-Clamping (oben + unten) - Pfeil-Alignment auf Ziel-Element --- src/static/js/tutorial.js | 50 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 48 insertions(+), 2 deletions(-) diff --git a/src/static/js/tutorial.js b/src/static/js/tutorial.js index bc64f90..aa32180 100644 --- a/src/static/js/tutorial.js +++ b/src/static/js/tutorial.js @@ -2828,13 +2828,59 @@ const Tutorial = { if (!this._isActive) return; clearTimeout(this._resizeTimer); var self = this; - this._resizeTimer = setTimeout(function() { + this._resizeTimer = setTimeout(async function() { if (!self._isActive || self._currentStep < 0) return; var step = self._steps[self._currentStep]; - if (step.target && step.position !== 'center') { + var isModalStep = step.target && step.target.indexOf('#modal-') !== -1; + + // Spotlight neu positionieren + if (step.target && step.position !== 'center' && !isModalStep) { self._spotlightElement(step.target); } + + // Modal-Steps: zum Feld scrollen + if (isModalStep && step.bubbleTarget) { + await self._scrollModalTo(step.bubbleTarget); + } + + // Bubble neu positionieren self._positionBubble(step); + + // Clamp + Arrow-Alignment (gleiche Logik wie in _showBubble) + requestAnimationFrame(function() { + var bubble = self._els.bubble; + if (!bubble) return; + var bRect = bubble.getBoundingClientRect(); + var vHeight = window.innerHeight; + var currentTop = parseFloat(bubble.style.top) || 0; + + // Unten geclampt + if (bRect.bottom > vHeight - 8) { + var shift = bRect.bottom - vHeight + 16; + bubble.style.top = (currentTop - shift) + 'px'; + var transform = bubble.style.transform || ''; + bubble.style.transform = transform.replace('translateY(-100%)', ''); + } + // Oben geclampt + bRect = bubble.getBoundingClientRect(); + if (bRect.top < 8) { + currentTop = parseFloat(bubble.style.top) || 0; + bubble.style.top = (currentTop + (8 - bRect.top)) + 'px'; + bubble.style.transform = (bubble.style.transform || '').replace('translateY(-100%)', ''); + } + // Pfeil-Alignment + var arrowTarget = step.bubbleTarget || step.target; + if (arrowTarget && (bubble.className.indexOf('pos-left') !== -1 || bubble.className.indexOf('pos-right') !== -1)) { + var targetEl = document.querySelector(arrowTarget); + if (targetEl) { + bRect = bubble.getBoundingClientRect(); + var tRect = targetEl.getBoundingClientRect(); + var arrowY = (tRect.top + tRect.height / 2) - bRect.top; + arrowY = Math.max(18, Math.min(arrowY, bRect.height - 18)); + bubble.style.setProperty('--arrow-top', arrowY + 'px'); + } + } + }); }, 150); },