UI: Hardware-aware video reveal with click-to-play bridge for iPhone XR

This commit is contained in:
Avery Felts 2026-01-31 19:59:43 -07:00
parent 3c3f803a1c
commit d166e92f8c

View File

@ -287,44 +287,62 @@ export function UserHeader({ user, preferences, onModalStateChange }: UserHeader
}} }}
/> />
{/* Static Poster Image Layer (Safe Fallback) */} {/* Wrapper for background image/video to allow click-to-play */}
<img <div
src="/videos/smoke-poster.jpg" className="absolute inset-0 w-full h-full pointer-events-auto"
alt="" onClick={() => {
className={cn( if (videoRef.current && videoRef.current.paused) {
"absolute inset-0 w-full h-full object-cover scale-110 transition-opacity duration-1000", videoRef.current.play().catch((err) => {
isVideoPlaying ? "opacity-30" : "opacity-70 dark:opacity-50" console.warn("User interaction play failed:", err);
)} });
aria-hidden="true" }
/> }}
{/* Smoke Video background - Only visible when MOVING */}
<video
ref={videoRef}
autoPlay
loop
muted
playsInline
{...({
"webkit-playsinline": "true",
"x-webkit-airplay": "deny",
"disableRemotePlayback": true
} as any)}
preload="auto"
controls={false}
disablePictureInPicture
onContextMenu={(e) => e.preventDefault()}
onLoadedData={() => setIsVideoLoaded(true)}
onPlay={() => setIsVideoPlaying(true)}
className={cn(
"absolute inset-0 w-full h-full object-cover scale-110 transition-opacity duration-[1500ms] ease-in-out",
isVideoPlaying
? "opacity-70 dark:opacity-50"
: "opacity-0"
)}
> >
<source src="/videos/smoke.mp4" type="video/mp4" /> {/* Static Poster Image Layer (Safe Fallback) */}
</video> <img
src="/videos/smoke-poster.jpg"
alt=""
className={cn(
"absolute inset-0 w-full h-full object-cover scale-110 transition-opacity duration-1000",
isVideoPlaying ? "opacity-30" : "opacity-70 dark:opacity-50"
)}
aria-hidden="true"
/>
{/* Smoke Video background - Only visible when MOVING */}
<video
ref={videoRef}
autoPlay
loop
muted
playsInline
{...({
"webkit-playsinline": "true",
"x-webkit-airplay": "deny",
"disableRemotePlayback": true
} as any)}
preload="auto"
controls={false}
disablePictureInPicture
onContextMenu={(e) => e.preventDefault()}
onLoadedData={() => setIsVideoLoaded(true)}
onPlay={() => setIsVideoPlaying(true)}
onPlaying={() => setIsVideoPlaying(true)}
onTimeUpdate={() => {
if (!isVideoPlaying && videoRef.current && videoRef.current.currentTime > 0) {
setIsVideoPlaying(true);
}
}}
className={cn(
"absolute inset-0 w-full h-full object-cover scale-110 transition-opacity duration-[1500ms] ease-in-out",
isVideoPlaying
? "opacity-70 dark:opacity-50"
: "opacity-[0.01]" // Keep very slightly visible so OS doesn't throttle it
)}
>
<source src="/videos/smoke.mp4" type="video/mp4" />
</video>
</div>
{/* Vignette/Readability Overlay - Reinforced for contrast */} {/* Vignette/Readability Overlay - Reinforced for contrast */}
<div className="absolute inset-0 bg-gradient-to-b from-black/30 via-transparent to-black/50 dark:from-black/60 dark:via-transparent dark:to-black/70 mix-blend-multiply" /> <div className="absolute inset-0 bg-gradient-to-b from-black/30 via-transparent to-black/50 dark:from-black/60 dark:via-transparent dark:to-black/70 mix-blend-multiply" />