Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added public/images/hero-palm-gradient-loop.mp4
Binary file not shown.
Binary file added public/images/hero-palm-gradient-loop.webm
Binary file not shown.
Binary file added public/images/hero-palm-gradient-loop.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
58 changes: 41 additions & 17 deletions src/routes/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,18 @@ function Index() {
}
}

const heroVideoRef = React.useRef<HTMLVideoElement>(null)

// Autoplay is declarative so the loop starts without waiting for hydration;
// this only walks it back for users who asked for less motion.
React.useEffect(() => {
if (!window.matchMedia('(prefers-reduced-motion: reduce)').matches) return
const video = heroVideoRef.current
if (!video) return
video.pause()
video.currentTime = 0
}, [])
Comment on lines +67 to +77

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Gate autoplay on the reduced-motion preference.

The effect runs only once, after the initial autoPlay/preload="auto" markup has already been processed, so reduced-motion users can briefly see motion and still download the video. Preference changes after mount are also ignored. Use the existing usePrefersReducedMotion hook, keep autoplay disabled while its value is unknown, and react to changes.

Suggested direction
+import { usePrefersReducedMotion } from '~/utils/usePrefersReducedMotion'
...
+  const prefersReducedMotion = usePrefersReducedMotion()
   const heroVideoRef = React.useRef<HTMLVideoElement>(null)

   React.useEffect(() => {
-    if (!window.matchMedia('(prefers-reduced-motion: reduce)').matches) return
+    if (prefersReducedMotion === null) return
     const video = heroVideoRef.current
     if (!video) return
-    video.pause()
-    video.currentTime = 0
-  }, [])
+    if (prefersReducedMotion) {
+      video.pause()
+      video.currentTime = 0
+    } else {
+      void video.play().catch(() => {})
+    }
+  }, [prefersReducedMotion])
...
-                  autoPlay
+                  autoPlay={prefersReducedMotion === false}
...
-                  preload="auto"
+                  preload={prefersReducedMotion === false ? 'auto' : 'none'}

Also applies to: 104-112

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/routes/index.tsx` around lines 67 - 77, Update the hero video logic
around heroVideoRef to use the existing usePrefersReducedMotion hook instead of
a one-time matchMedia effect. Keep autoplay disabled while the preference is
unknown and whenever reduced motion is enabled, and update the video playback
behavior when the preference changes so reduced-motion users do not start
downloading or seeing motion prematurely.


return (
<>
<div className="max-w-full z-10 space-y-24">
Expand All @@ -74,31 +86,43 @@ function Index() {
rather than a theme-flipping semantic. */}
<div className="w-full">
<div className="relative isolate flex h-[calc(100dvh-var(--navbar-height))] max-h-[720px] min-h-[560px] flex-col justify-between gap-8 px-6 py-10 sm:px-10 xl:flex-row xl:items-end xl:justify-between xl:gap-12 xl:px-16 xl:py-16">
{/* Wrapper carries the 8px inset + squircle clip so the <img>
{/* Wrapper carries the 8px inset + squircle clip so the <video>
(which has intrinsic width/height) fills it exactly instead of
overflowing the right/bottom. Plain <img> (not OptimizedImage):
the Cloudflare transform resolves against the production origin,
so a newly-added asset 404s until deployed. */}
overflowing the right/bottom. Assets are served straight from
/public: the Cloudflare transform resolves against the
production origin, so a newly-added asset 404s until
deployed. */}
<div
aria-hidden
className="absolute inset-2 -z-10 overflow-hidden rounded-[2rem] [corner-shape:squircle]"
>
<picture className="contents">
{/* The poster is frame 0 of the loop, so the still and the
video are the same picture and there is no pop when playback
starts. Under `prefers-reduced-motion` we hold on that frame.
Codecs are spelled out so Safari skips the 10-bit VP9 and
picks the HEVC file instead of failing on it. */}
<video
ref={heroVideoRef}
autoPlay
loop
muted
playsInline
disableRemotePlayback
preload="auto"
poster="/images/hero-palm-gradient-loop.webp"
width={1168}
height={768}
className="h-full w-full object-cover object-center"
>
<source
type="image/webp"
srcSet="/images/hero-palm-gradient-960.webp 960w, /images/hero-palm-gradient-1600.webp 1600w, /images/hero-palm-gradient-2400.webp 2400w"
sizes="100vw"
src="/images/hero-palm-gradient-loop.webm"
type='video/webm; codecs="vp09.02.30.10"'
/>
<img
src="/images/hero-palm-gradient.jpg"
alt=""
width={2400}
height={1600}
loading="eager"
fetchPriority="high"
className="h-full w-full object-cover object-center"
<source
src="/images/hero-palm-gradient-loop.mp4"
type='video/mp4; codecs="hvc1.2.4.L93.B0"'
/>
</picture>
</video>
</div>
<h1 className="max-w-[613px] font-ds-display text-ds-display-sm font-bold text-ds-neutral-500 sm:text-ds-display-md lg:text-ds-display-lg xl:text-ds-display-xl">
The{' '}
Expand Down