From 3ddd4ecdbd682514f48cfd8b54a71775b4a5d1cd Mon Sep 17 00:00:00 2001 From: Andy Charlwood Date: Thu, 19 Feb 2026 16:32:11 +0000 Subject: [PATCH] Changes carousel auto scroll to pause when interaction occurs --- src/components/ChatWidget.tsx | 4 +-- src/components/tiles/ProjectsTile.tsx | 52 ++++++++++++++++++--------- 2 files changed, 37 insertions(+), 19 deletions(-) diff --git a/src/components/ChatWidget.tsx b/src/components/ChatWidget.tsx index 1b73040..1fe6f9b 100644 --- a/src/components/ChatWidget.tsx +++ b/src/components/ChatWidget.tsx @@ -72,7 +72,7 @@ export function ChatWidget({ onAction }: ChatWidgetProps) { useEffect(() => { const timer = setTimeout(() => { if (!hasInteracted.current) setShowNudge(true) - }, 12_000) + }, 5_000) return () => clearTimeout(timer) }, []) @@ -722,7 +722,7 @@ export function ChatWidget({ onAction }: ChatWidgetProps) { (0) + const resumeTimestampRef = useRef(null) const containerRef = useRef(null) const cardRefs = useRef>(new Map()) + const RESUME_DELAY_MS = 10000 + const RAMP_DURATION_MS = 2000 + + const pauseCarousel = useCallback(() => { + isPausedRef.current = true + window.clearTimeout(resumeTimeoutRef.current) + }, []) + + const scheduleResume = useCallback(() => { + window.clearTimeout(resumeTimeoutRef.current) + resumeTimeoutRef.current = window.setTimeout(() => { + isPausedRef.current = false + resumeTimestampRef.current = performance.now() + }, RESUME_DELAY_MS) + }, []) + const jumpByCards = useCallback((direction: 1 | -1) => { const trackEl = trackRef.current const firstSetEl = firstSetRef.current @@ -553,9 +570,7 @@ function ContinuousScrollCarousel() { const cardWidth = (viewportWidth - totalGap) / cardsPerView const jumpPx = cardWidth + gap - // Pause auto-scroll - isPausedRef.current = true - window.clearTimeout(resumeTimeoutRef.current) + pauseCarousel() // Apply CSS transition for smooth jump if (!prefersReducedMotion) { @@ -579,11 +594,8 @@ function ContinuousScrollCarousel() { trackEl.addEventListener('transitionend', transitionEnd, { once: true }) } - // Resume auto-scroll after 6s - resumeTimeoutRef.current = window.setTimeout(() => { - isPausedRef.current = false - }, 6000) - }, [viewportWidth, prefersReducedMotion]) + scheduleResume() + }, [viewportWidth, prefersReducedMotion, pauseCarousel, scheduleResume]) useEffect(() => { return () => { @@ -629,9 +641,19 @@ function ContinuousScrollCarousel() { const deltaSeconds = (timestamp - lastTime) / 1000 lastTime = timestamp if (!isPausedRef.current) { + let speedMultiplier = 1 + if (resumeTimestampRef.current !== null) { + const elapsed = timestamp - resumeTimestampRef.current + if (elapsed < RAMP_DURATION_MS) { + const t = elapsed / RAMP_DURATION_MS + speedMultiplier = 1 - Math.pow(1 - t, 2) + } else { + resumeTimestampRef.current = null + } + } const setWidth = firstSetEl.offsetWidth if (setWidth > 0) { - offsetRef.current += speedPxPerSecond * deltaSeconds + offsetRef.current += speedPxPerSecond * speedMultiplier * deltaSeconds if (offsetRef.current >= setWidth) offsetRef.current -= setWidth trackEl.style.transform = `translate3d(-${offsetRef.current}px, 0, 0)` } @@ -655,10 +677,6 @@ function ContinuousScrollCarousel() { return 214 }, [viewportWidth]) - const setPaused = (value: boolean) => { - isPausedRef.current = value - } - const handleArrowKey = useCallback((currentIndex: number, direction: -1 | 1) => { const nextIndex = (currentIndex + direction + investigations.length) % investigations.length cardRefs.current.get(nextIndex)?.focus() @@ -702,12 +720,12 @@ function ContinuousScrollCarousel() {
setPaused(true)} - onMouseLeave={() => setPaused(false)} - onFocusCapture={() => setPaused(true)} + onMouseEnter={() => pauseCarousel()} + onMouseLeave={() => scheduleResume()} + onFocusCapture={() => pauseCarousel()} onBlurCapture={(event) => { if (!event.currentTarget.contains(event.relatedTarget as Node | null)) { - setPaused(false) + scheduleResume() } }} >