Changes carousel auto scroll to pause when interaction occurs

This commit is contained in:
2026-02-19 16:32:11 +00:00
parent d403e96d34
commit 3ddd4ecdbd
2 changed files with 37 additions and 19 deletions
+2 -2
View File
@@ -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) {
<motion.div
initial={prefersReducedMotion ? { opacity: 1 } : { opacity: 0, y: 6 }}
animate={{ opacity: 1, y: 0, transition: motionSafeTransition(0.25, 'easeOut') }}
exit={prefersReducedMotion ? { opacity: 1 } : { opacity: 0, y: 4, transition: { duration: 0.2, ease: 'easeIn' } }}
exit={{ opacity: 0, transition: { duration: 0 } }}
className="fixed z-[101] right-4 md:right-6 pointer-events-none"
style={{
/* Position above button: button-bottom + button-height + gap */
+35 -17
View File
@@ -539,9 +539,26 @@ function ContinuousScrollCarousel() {
: false,
)
const resumeTimeoutRef = useRef<number>(0)
const resumeTimestampRef = useRef<number | null>(null)
const containerRef = useRef<HTMLDivElement>(null)
const cardRefs = useRef<Map<number, HTMLDivElement>>(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() {
<div
ref={viewportRef}
style={{ overflow: 'hidden' }}
onMouseEnter={() => 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()
}
}}
>