"use client"; import Image from "next/image"; import { useEffect, useRef, useState } from "react"; import { clamp, DEFAULT_TIME, mapPointerYToTime, SMOOTHING, TOTAL_DURATION, } from "@/lib/cursor-portrait"; const VIDEO_SRC = "/media/cursor-portrait/poyraz-bottom-right.mp4"; const POSTER_SRC = "/media/cursor-portrait/poyraz-bottom-right-poster.webp"; const SEEK_INTERVAL_MS = 1000 / 60; const MIN_TIME_DELTA = 0.002; const MIN_SEEK_DELTA = 1 / 120; type DisplayMode = | "pending" | "interactive" | "poster-reduced" | "hidden-mobile" | "hidden-dark"; function subscribeToMediaQuery(query: MediaQueryList, listener: () => void) { if (typeof query.addEventListener === "function") { query.addEventListener("change", listener); return () => query.removeEventListener("change", listener); } query.addListener(listener); return () => query.removeListener(listener); } export function PoyrazBottomRightFollower() { const videoRef = useRef(null); const pointerYRef = useRef(null); const targetTimeRef = useRef(DEFAULT_TIME); const currentTimeRef = useRef(DEFAULT_TIME); const rafIdRef = useRef(null); const lastSeekTimestampRef = useRef(0); const metadataReadyRef = useRef(false); const primedRef = useRef(false); const [displayMode, setDisplayMode] = useState("pending"); const [videoFailed, setVideoFailed] = useState(false); useEffect(() => { const finePointerQuery = window.matchMedia("(pointer: fine)"); const desktopQuery = window.matchMedia("(min-width: 840px)"); const reducedMotionQuery = window.matchMedia("(prefers-reduced-motion: reduce)"); const updateDisplayMode = () => { if (document.documentElement.dataset.poyrazTheme === "dark") { setDisplayMode("hidden-dark"); return; } if (!desktopQuery.matches || !finePointerQuery.matches) { setDisplayMode("hidden-mobile"); return; } setDisplayMode(reducedMotionQuery.matches ? "poster-reduced" : "interactive"); }; updateDisplayMode(); const unsubscribeFinePointer = subscribeToMediaQuery( finePointerQuery, updateDisplayMode, ); const unsubscribeDesktop = subscribeToMediaQuery(desktopQuery, updateDisplayMode); const unsubscribeReducedMotion = subscribeToMediaQuery( reducedMotionQuery, updateDisplayMode, ); const themeObserver = new MutationObserver(updateDisplayMode); themeObserver.observe(document.documentElement, { attributes: true, attributeFilter: ["data-poyraz-theme"], }); return () => { unsubscribeFinePointer(); unsubscribeDesktop(); unsubscribeReducedMotion(); themeObserver.disconnect(); }; }, []); useEffect(() => { if (displayMode !== "interactive" || videoFailed) return; const video = videoRef.current; if (!video) return; let disposed = false; const stopAnimationLoop = () => { if (rafIdRef.current === null) return; window.cancelAnimationFrame(rafIdRef.current); rafIdRef.current = null; }; const seekVideo = (timestamp: number) => { if ( !metadataReadyRef.current || video.seeking || timestamp - lastSeekTimestampRef.current < SEEK_INTERVAL_MS ) { return; } const duration = Number.isFinite(video.duration) ? Math.min(video.duration, TOTAL_DURATION) : TOTAL_DURATION; const nextTime = clamp(currentTimeRef.current, 0, duration); if (Math.abs(video.currentTime - nextTime) < MIN_SEEK_DELTA) return; try { video.currentTime = nextTime; lastSeekTimestampRef.current = timestamp; } catch { // The poster remains visible until the browser exposes seekable metadata. } }; const runAnimationFrame = (timestamp: number) => { rafIdRef.current = null; if (disposed || document.hidden) return; const difference = targetTimeRef.current - currentTimeRef.current; const settled = Math.abs(difference) <= MIN_TIME_DELTA; currentTimeRef.current = settled ? targetTimeRef.current : currentTimeRef.current + difference * SMOOTHING; seekVideo(timestamp); const videoNeedsSeek = metadataReadyRef.current && (video.seeking || Math.abs(video.currentTime - targetTimeRef.current) >= MIN_SEEK_DELTA); if (!settled || videoNeedsSeek) { rafIdRef.current = window.requestAnimationFrame(runAnimationFrame); } }; const startAnimationLoop = () => { if ( disposed || document.hidden || !metadataReadyRef.current || rafIdRef.current !== null ) { return; } rafIdRef.current = window.requestAnimationFrame(runAnimationFrame); }; const returnToDefault = () => { pointerYRef.current = null; targetTimeRef.current = DEFAULT_TIME; startAnimationLoop(); }; const primeVideo = () => { if (primedRef.current || !metadataReadyRef.current) return; primedRef.current = true; const resumeTime = currentTimeRef.current; video.muted = true; void video .play() .then(() => { if (disposed) return; video.pause(); video.currentTime = resumeTime; }) .catch(() => { video.pause(); }); }; const handlePointerMove = (event: PointerEvent) => { if (event.pointerType !== "mouse") return; pointerYRef.current = event.clientY; targetTimeRef.current = mapPointerYToTime(event.clientY, window.innerHeight); primeVideo(); startAnimationLoop(); }; const handleResize = () => { if (pointerYRef.current === null) return; targetTimeRef.current = mapPointerYToTime( pointerYRef.current, window.innerHeight, ); startAnimationLoop(); }; const handleVisibilityChange = () => { if (document.hidden) { stopAnimationLoop(); return; } startAnimationLoop(); }; const handleLoadedMetadata = () => { video.pause(); metadataReadyRef.current = true; targetTimeRef.current = DEFAULT_TIME; currentTimeRef.current = DEFAULT_TIME; video.currentTime = clamp(DEFAULT_TIME, 0, video.duration); }; if (video.readyState >= HTMLMediaElement.HAVE_METADATA) { handleLoadedMetadata(); } video.addEventListener("loadedmetadata", handleLoadedMetadata); window.addEventListener("pointermove", handlePointerMove, { passive: true }); document.documentElement.addEventListener("pointerleave", returnToDefault); window.addEventListener("blur", returnToDefault); window.addEventListener("resize", handleResize, { passive: true }); document.addEventListener("visibilitychange", handleVisibilityChange); return () => { disposed = true; stopAnimationLoop(); video.pause(); metadataReadyRef.current = false; primedRef.current = false; pointerYRef.current = null; targetTimeRef.current = DEFAULT_TIME; currentTimeRef.current = DEFAULT_TIME; lastSeekTimestampRef.current = 0; video.removeEventListener("loadedmetadata", handleLoadedMetadata); window.removeEventListener("pointermove", handlePointerMove); document.documentElement.removeEventListener("pointerleave", returnToDefault); window.removeEventListener("blur", returnToDefault); window.removeEventListener("resize", handleResize); document.removeEventListener("visibilitychange", handleVisibilityChange); }; }, [displayMode, videoFailed]); const showVideo = displayMode === "interactive" && !videoFailed; if ( displayMode === "pending" || displayMode === "hidden-mobile" || displayMode === "hidden-dark" ) { return null; } return ( ); }