function setMessagePosition(message, x, y) { message.style.left = `${x}px`; message.style.top = `${y}px`; } function animateMessageBetween(message, fromX, fromY, toX, toY, duration, callback) { const startTime = performance.now(); function step(currentTime) { const elapsed = currentTime - startTime; const progress = Math.min(elapsed / duration, 1); const eased = 1 - Math.pow(1 - progress, 3); setMessagePosition( message, fromX + (toX - fromX) * eased, fromY + (toY - fromY) * eased ); if (progress < 1) { requestAnimationFrame(step); } else if (callback) { callback(); } } requestAnimationFrame(step); } function animatePathBetween(message, waypoints, totalDuration, callback) { if (waypoints.length < 2) { if (callback) { callback(); } return; } let totalDist = 0; for (let i = 0; i < waypoints.length - 1; i++) { const dx = waypoints[i + 1].x - waypoints[i].x; const dy = waypoints[i + 1].y - waypoints[i].y; totalDist += Math.sqrt(dx * dx + dy * dy); } const durations = []; for (let i = 0; i < waypoints.length - 1; i++) { const dx = waypoints[i + 1].x - waypoints[i].x; const dy = waypoints[i + 1].y - waypoints[i].y; const legDist = Math.sqrt(dx * dx + dy * dy); durations.push((legDist / totalDist) * totalDuration); } let i = 0; function nextLeg() { if (i < waypoints.length - 1) { animateMessageBetween( message, waypoints[i].x, waypoints[i].y, waypoints[i + 1].x, waypoints[i + 1].y, durations[i], () => { i++; nextLeg(); } ); } else if (callback) { callback(); } } nextLeg(); } function configureMessage(elements, { type, arrow, method, detail, sessionText, sessionError = false, direction = 'right', } = {}) { const container = elements.container; const header = elements.header || container.querySelector('.message-header'); container.className = `message ${type} visible`; elements.type.textContent = method; elements.arrow.textContent = arrow; elements.detail.textContent = detail; if (elements.session) { if (sessionText) { elements.session.innerHTML = sessionText; elements.session.style.display = 'block'; elements.session.className = 'message-session' + (sessionError ? ' error' : ''); } else { elements.session.style.display = 'none'; } } if (header) { header.style.flexDirection = direction === 'left' ? 'row-reverse' : 'row'; } } function runStepSequence(steps, { initialDelay = 100, stepPause, startOnLoad = true } = {}) { if (!steps || steps.length === 0) { return; } const pause = stepPause ?? (typeof ANIMATION !== 'undefined' ? ANIMATION.STEP_PAUSE : 1500); let currentStep = 0; let animationTimer = null; let runId = 0; function runStep(activeRunId) { if (activeRunId !== runId) { return; } if (currentStep >= steps.length) { currentStep = 0; } const step = steps[currentStep]; step.setup(); animationTimer = setTimeout(() => { if (activeRunId !== runId) { return; } step.animate(() => { if (activeRunId !== runId) { return; } if (step.after) { step.after(); } currentStep++; animationTimer = setTimeout(() => runStep(activeRunId), pause); }); }, initialDelay); } const start = () => { runId++; if (animationTimer) { clearTimeout(animationTimer); animationTimer = null; } currentStep = 0; runStep(runId); }; if (startOnLoad) { if (document.readyState === 'complete') { start(); } else { window.addEventListener('load', start, { once: true }); } // Restart only for bfcache restores to avoid duplicate starts. window.addEventListener('pageshow', (event) => { if (event.persisted) { start(); } }); } else { start(); } }