diff --git a/apps/www/components/Products/RealtimeVisual.tsx b/apps/www/components/Products/RealtimeVisual.tsx index cf6f783fb7c..64b3d622d98 100644 --- a/apps/www/components/Products/RealtimeVisual.tsx +++ b/apps/www/components/Products/RealtimeVisual.tsx @@ -1,6 +1,6 @@ import { isBrowser, useReducedMotion } from 'common' import Image from 'next/image' -import React, { useRef, useState } from 'react' +import React, { useEffect, useRef, useState } from 'react' import { cn } from 'ui' interface Props { @@ -8,15 +8,29 @@ interface Props { } const RealtimeVisual: React.FC = ({ className }) => { - const cardRef = useRef(null) + const figureRef = useRef(null) const [svgTransformSelf, setSvgTransformSelf] = useState('translate(0px, 0px)') const [svgTransform, setSvgTransform] = useState('translate(0px, 0px)') const [svgTransform2, setSvgTransform2] = useState('translate(0px, 0px)') const reduceMotion = isBrowser && useReducedMotion() - const handleMouseMove = (event: React.MouseEvent) => { - if (cardRef.current) { - const cardRect = cardRef.current.getBoundingClientRect() + useEffect(() => { + if (reduceMotion) return + + const figure = figureRef.current + if (!figure) return + + // Track the pointer across the whole card (the Link ancestor), not just the + // figure. The card's text content sits above the figure at a higher z-index, + // and it is a sibling of the figure rather than a descendant, so listening on + // the figure alone means the animation freezes whenever the pointer is over + // the text or other overlapping elements. Listening on the card lets the + // mousemove events bubble up from any child regardless of stacking order. + const card = figure.closest('a') ?? figure + + const handleMouseMove = (event: MouseEvent) => { + // Coordinates stay relative to the figure so the animation math is unchanged. + const cardRect = figure.getBoundingClientRect() const mouseX = event.clientX - cardRect.left // Mouse X relative to card const mouseY = event.clientY - cardRect.top // Mouse Y relative to card const cardWidth = cardRect.width @@ -34,24 +48,30 @@ const RealtimeVisual: React.FC = ({ className }) => { ) setSvgTransformSelf(`translate(${mouseX + 12}px, ${mouseY + 4}px)`) } - } - const handleMouseLeave = () => { - setSvgTransform('translate(0px, 0px)') - setSvgTransform2('translate(0px, 0px)') - } + const handleMouseLeave = () => { + setSvgTransform('translate(0px, 0px)') + setSvgTransform2('translate(0px, 0px)') + } + + card.addEventListener('mousemove', handleMouseMove) + card.addEventListener('mouseleave', handleMouseLeave) + + return () => { + card.removeEventListener('mousemove', handleMouseMove) + card.removeEventListener('mouseleave', handleMouseLeave) + } + }, [reduceMotion]) return (
= ({ className }) => { /> {/* User 1 */}
= ({ className }) => {
{/* User 2 */}
= ({ className }) => {
{/* Self */}