"use client"; import { Suspense, useCallback, useEffect, useRef, useState, type MutableRefObject, } from "react"; import { usePathname, useSearchParams } from "next/navigation"; import { motion, AnimatePresence, useReducedMotion } from "framer-motion"; const REVEAL_DELAY_MS = 120; const COMPLETION_MS = 140; const FAILSAFE_MS = 10_000; type LoaderPhase = "idle" | "pending" | "completing"; function clearTimer(timer: MutableRefObject) { if (timer.current === null) return; window.clearTimeout(timer.current); timer.current = null; } export function isSameDocumentNavigation(anchor: HTMLAnchorElement): boolean { if (anchor.target && anchor.target !== "_self") return false; if (anchor.hasAttribute("download")) return false; const next = new URL(anchor.href, window.location.href); const current = new URL(window.location.href); if (next.origin !== current.origin) return false; return `${next.pathname}${next.search}` !== `${current.pathname}${current.search}`; } function PageTransitionLoaderInner() { const pathname = usePathname(); const searchParams = useSearchParams(); const routeKey = `${pathname}?${searchParams.toString()}`; const reduceMotion = useReducedMotion(); const [phase, setPhase] = useState("idle"); const isFirstPaint = useRef(true); const navigationArmed = useRef(false); const revealTimer = useRef(null); const completionTimer = useRef(null); const failsafeTimer = useRef(null); const beginNavigation = useCallback(() => { clearTimer(revealTimer); clearTimer(completionTimer); clearTimer(failsafeTimer); navigationArmed.current = true; revealTimer.current = window.setTimeout(() => { setPhase("pending"); revealTimer.current = null; }, REVEAL_DELAY_MS); failsafeTimer.current = window.setTimeout(() => { navigationArmed.current = false; setPhase("idle"); failsafeTimer.current = null; }, FAILSAFE_MS); }, []); useEffect(() => { function handleDocumentClick(event: MouseEvent) { if ( event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey ) { return; } const target = event.target; if (!(target instanceof Element)) return; const anchor = target.closest("a[href]"); if (!anchor || !isSameDocumentNavigation(anchor)) return; beginNavigation(); } window.addEventListener("popstate", beginNavigation); document.addEventListener("click", handleDocumentClick, true); return () => { window.removeEventListener("popstate", beginNavigation); document.removeEventListener("click", handleDocumentClick, true); clearTimer(revealTimer); clearTimer(completionTimer); clearTimer(failsafeTimer); }; }, [beginNavigation]); useEffect(() => { if (isFirstPaint.current) { isFirstPaint.current = false; return; } if (!navigationArmed.current) return; navigationArmed.current = false; clearTimer(revealTimer); clearTimer(failsafeTimer); setPhase("completing"); completionTimer.current = window.setTimeout(() => { setPhase("idle"); completionTimer.current = null; }, COMPLETION_MS); return () => clearTimer(completionTimer); }, [routeKey]); return ( {phase !== "idle" ? ( ) : null} ); } export function PageTransitionLoader() { return ( ); }