diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 8f5d0ab2..1e76b5bd 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -5,8 +5,9 @@ import Script from "next/script"; import { NextIntlClientProvider } from "next-intl"; import { getLocale, getMessages } from "next-intl/server"; -import type { ReactNode } from "react"; +import { type ReactNode, Suspense } from "react"; import { Toaster } from "sonner"; +import { GlobalProgressBar } from "@/components/global-progress-bar"; import { PwaRegister } from "@/components/pwa-register"; import { ScopedThemeVars } from "@/components/scoped-theme-vars"; import { ThemeScopeDetector } from "@/components/theme-scope-detector"; @@ -96,6 +97,9 @@ export default async function RootLayout({ className="flex min-h-screen flex-col site-bg" style={{ backgroundColor: "var(--color-background)" }} > + + + diff --git a/src/components/global-progress-bar.tsx b/src/components/global-progress-bar.tsx new file mode 100644 index 00000000..5d2918ea --- /dev/null +++ b/src/components/global-progress-bar.tsx @@ -0,0 +1,39 @@ +"use client"; + +import { motion, useIsPresent } from "motion/react"; +import { usePathname, useSearchParams } from "next/navigation"; +import { useEffect, useState } from "react"; + +/** + * Global smooth progress indicator for page transitions. + * Uses motion/react for high-performance 60fps animations. + */ +export function GlobalProgressBar() { + const pathname = usePathname(); + const searchParams = useSearchParams(); + const [isLoading, setIsLoading] = useState(false); + const isPresent = useIsPresent(); + + useEffect(() => { + setIsLoading(true); + const timer = setTimeout(() => { + setIsLoading(false); + }, 400); + + return () => clearTimeout(timer); + }, [pathname, searchParams]); + + if (!isLoading && isPresent) return null; + + return ( +
+ +
+ ); +}