| 116 | }; |
| 117 | |
| 118 | const useAnimationLoop = ( |
| 119 | trackRef: React.RefObject<HTMLDivElement | null>, |
| 120 | targetVelocity: number, |
| 121 | seqWidth: number, |
| 122 | seqHeight: number, |
| 123 | isHovered: boolean, |
| 124 | hoverSpeed: number | undefined, |
| 125 | isVertical: boolean |
| 126 | ) => { |
| 127 | const rafRef = useRef<number | null>(null); |
| 128 | const lastTimestampRef = useRef<number | null>(null); |
| 129 | const offsetRef = useRef(0); |
| 130 | const velocityRef = useRef(0); |
| 131 | |
| 132 | useEffect(() => { |
| 133 | const track = trackRef.current; |
| 134 | if (!track) return; |
| 135 | |
| 136 | const prefersReduced = |
| 137 | typeof window !== 'undefined' && |
| 138 | window.matchMedia && |
| 139 | window.matchMedia('(prefers-reduced-motion: reduce)').matches; |
| 140 | |
| 141 | const seqSize = isVertical ? seqHeight : seqWidth; |
| 142 | |
| 143 | if (seqSize > 0) { |
| 144 | offsetRef.current = ((offsetRef.current % seqSize) + seqSize) % seqSize; |
| 145 | const transformValue = isVertical |
| 146 | ? `translate3d(0, ${-offsetRef.current}px, 0)` |
| 147 | : `translate3d(${-offsetRef.current}px, 0, 0)`; |
| 148 | track.style.transform = transformValue; |
| 149 | } |
| 150 | |
| 151 | if (prefersReduced) { |
| 152 | track.style.transform = isVertical ? 'translate3d(0, 0, 0)' : 'translate3d(0, 0, 0)'; |
| 153 | return () => { |
| 154 | lastTimestampRef.current = null; |
| 155 | }; |
| 156 | } |
| 157 | |
| 158 | const animate = (timestamp: number) => { |
| 159 | if (lastTimestampRef.current === null) { |
| 160 | lastTimestampRef.current = timestamp; |
| 161 | } |
| 162 | |
| 163 | const deltaTime = Math.max(0, timestamp - lastTimestampRef.current) / 1000; |
| 164 | lastTimestampRef.current = timestamp; |
| 165 | |
| 166 | const target = isHovered && hoverSpeed !== undefined ? hoverSpeed : targetVelocity; |
| 167 | |
| 168 | const easingFactor = 1 - Math.exp(-deltaTime / ANIMATION_CONFIG.SMOOTH_TAU); |
| 169 | velocityRef.current += (target - velocityRef.current) * easingFactor; |
| 170 | |
| 171 | if (seqSize > 0) { |
| 172 | let nextOffset = offsetRef.current + velocityRef.current * deltaTime; |
| 173 | nextOffset = ((nextOffset % seqSize) + seqSize) % seqSize; |
| 174 | offsetRef.current = nextOffset; |
| 175 | |