diff --git a/apps/website/components/AppNavbar.vue b/apps/website/components/AppNavbar.vue index 3b2646a..e064bc4 100644 --- a/apps/website/components/AppNavbar.vue +++ b/apps/website/components/AppNavbar.vue @@ -65,11 +65,11 @@ const handleSmoothScroll = (event: Event, href: string) => { } event.preventDefault() - scrollToElement(href, 1800) // 1.8 seconds for smoother scrolling + scrollToElement(href, 1200) // Smooth scrolling with sine easing } const scrollToTop = () => { - smoothScrollToTop(1800) // 1.8 seconds for smoother scrolling + smoothScrollToTop(1200) // Smooth scrolling with sine easing } onMounted(() => { diff --git a/apps/website/components/HeroSection.vue b/apps/website/components/HeroSection.vue index d50a6c7..d64082e 100644 --- a/apps/website/components/HeroSection.vue +++ b/apps/website/components/HeroSection.vue @@ -101,11 +101,11 @@ const handlePhoneClick = () => { } const handleServicesClick = () => { - scrollToElement('#services', 1800) // 1.8 seconds for smoother scrolling + scrollToElement('#services', 1200) // Smooth scrolling with sine easing } const handleScrollToExplore = () => { - scrollToElement('#services', 1800) // Scroll to services section + scrollToElement('#services', 1200) // Smooth scroll to services section } const handleVideoVisibility = () => { @@ -136,7 +136,7 @@ const initSmoothScroll = () => { if (!href) { return } - scrollToElement(href, 1800) // 1.8 seconds for smoother scrolling + scrollToElement(href, 1200) // Smooth scrolling with sine easing }) }) } diff --git a/apps/website/composables/useSmoothScroll.ts b/apps/website/composables/useSmoothScroll.ts index 6e1a6fd..09139dc 100644 --- a/apps/website/composables/useSmoothScroll.ts +++ b/apps/website/composables/useSmoothScroll.ts @@ -1,8 +1,40 @@ export const useSmoothScroll = () => { - const smoothScrollTo = (target: number | Element, duration: number = 1800) => { + let animationFrameId: number | null = null + + // Cancel any ongoing scroll animation + const cancelScroll = () => { + if (animationFrameId) { + cancelAnimationFrame(animationFrameId) + animationFrameId = null + } + // Clean up event listeners + window.removeEventListener('wheel', cancelScroll) + window.removeEventListener('touchstart', cancelScroll) + window.removeEventListener('keydown', cancelScroll) + } + + // Smooth sine-based easing function for natural motion + const easeInOutSine = (t: number): number => { + return -(Math.cos(Math.PI * t) - 1) / 2 + } + + const smoothScrollTo = (target: number | Element, duration: number = 1200) => { + // Check for reduced motion preference + const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches + const targetPosition = typeof target === 'number' ? target : target.getBoundingClientRect().top + window.pageYOffset + + if (prefersReducedMotion) { + // Instant scroll for accessibility + window.scrollTo(0, targetPosition) + return + } + + // Cancel any previously running scroll animation + cancelScroll() + const startPosition = window.pageYOffset const distance = targetPosition - startPosition let startTime: number | null = null @@ -11,32 +43,33 @@ export const useSmoothScroll = () => { if (startTime === null) startTime = currentTime const timeElapsed = currentTime - startTime const progress = Math.min(timeElapsed / duration, 1) + const easedProgress = easeInOutSine(progress) - // Easing function for smoother, more consistent motion - const easeInOutQuad = (t: number) => { - return t < 0.5 - ? 2 * t * t - : 1 - Math.pow(-2 * t + 2, 2) / 2 - } - - window.scrollTo(0, startPosition + distance * easeInOutQuad(progress)) + window.scrollTo(0, startPosition + distance * easedProgress) if (progress < 1) { - requestAnimationFrame(animation) + animationFrameId = requestAnimationFrame(animation) + } else { + cancelScroll() // Clean up when animation completes } } - requestAnimationFrame(animation) + // Add listeners to detect user interruption + window.addEventListener('wheel', cancelScroll, { passive: true }) + window.addEventListener('touchstart', cancelScroll, { passive: true }) + window.addEventListener('keydown', cancelScroll, { once: true }) + + animationFrameId = requestAnimationFrame(animation) } - const scrollToElement = (selector: string, duration: number = 1800) => { + const scrollToElement = (selector: string, duration: number = 1200) => { const element = document.querySelector(selector) if (element) { smoothScrollTo(element, duration) } } - const scrollToTop = (duration: number = 1800) => { + const scrollToTop = (duration: number = 1200) => { smoothScrollTo(0, duration) }