import { animate } from "motion"; /** * Step transition animations - In */ export function animateStepIn(stepElement: HTMLElement, duration = 0.5) { return animate(stepElement, // @ts-ignore { opacity: [0, 1], scale: [0.98, 1] }, { duration: duration, easing: [0.22, 0.03, 0.26, 1] } ).finished; } /** * Step transition animations - Out */ export function animateStepOut(stepElement: HTMLElement, duration = 0.3) { return animate(stepElement, // @ts-ignore { opacity: [1, 0], scale: [1, 0.98] }, { duration: duration, easing: [0.22, 0.03, 0.26, 1] } ).finished; } /** * Updates the feedback component with a message and status. */ export function updateFeedback(message: string, status: 'feedback-success' | 'feedback-error', duration: number = 5000): void { const feedbackElement = document.querySelector('.feedback') as HTMLElement; const messageElement = feedbackElement.querySelector('.feedback-message') as HTMLElement; if (!feedbackElement || !messageElement) return; // Update text and status messageElement.innerHTML = message; feedbackElement.classList.remove('feedback-success', 'feedback-error'); feedbackElement.classList.add(status); showFeedback(feedbackElement); if (duration > 0) { setTimeout(() => { hideFeedback(feedbackElement); }, duration); } } /** * Shows the feedback element. Animation is driven by CSS (transition on .showing). */ export function showFeedback(feedbackElement: HTMLElement): void { feedbackElement.style.visibility = 'visible'; feedbackElement.style.pointerEvents = 'auto'; feedbackElement.classList.add('showing'); } /** Duration of the hide transition in ms (must match CSS). */ const FEEDBACK_HIDE_TRANSITION_MS = 400; /** * Hides the feedback element. Animation is driven by CSS; visibility/pointer-events * are reset after the transition so the next show starts from a clean state. */ export function hideFeedback(feedbackElement: HTMLElement): void { feedbackElement.classList.remove('showing', 'feedback-success', 'feedback-error'); let done = false; const onTransitionEnd = (): void => { if (done) return; done = true; feedbackElement.removeEventListener('transitionend', onTransitionEnd); feedbackElement.style.visibility = 'hidden'; feedbackElement.style.pointerEvents = 'none'; }; feedbackElement.addEventListener('transitionend', onTransitionEnd); // Fallback if transitionend doesn't fire (e.g. element detached or no transition) setTimeout(onTransitionEnd, FEEDBACK_HIDE_TRANSITION_MS); } /** * Opens pop-up with animation. */ export function openPopupBtn(overlayElement: HTMLElement): void { if (!overlayElement) return; overlayElement.style.display = 'flex'; overlayElement.style.opacity = '0'; const popupBox = overlayElement.querySelector('.popup-box') as HTMLElement; animate(overlayElement, { opacity: [0, 1] }, { duration: 0.3 }); if (popupBox) { animate(popupBox, { opacity: [0, 1], scale: [0.9, 1] }, { duration: 0.3 }); } } /** * Closes pop-up with animation. */ export async function closePopup(overlays: HTMLElement[]): Promise { const animations = overlays.map(overlay => { const popupBox = overlay.querySelector('.popup-box') as HTMLElement; const anims = [animate(overlay, { opacity: [1, 0] }, { duration: 0.3 }).finished]; if (popupBox) { anims.push(animate(popupBox, { opacity: [1, 0], scale: [1, 0.9] }, { duration: 0.3 }).finished); } return Promise.all(anims); }); await Promise.all(animations); overlays.forEach(overlay => { overlay.style.display = 'none'; }); }