diff --git a/app/layout.tsx b/app/layout.tsx
index f49c460..93f83cf 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -69,6 +69,7 @@ export const metadata: Metadata = {
import { TooltipProvider } from "@/components/ui/tooltip";
import { Toaster } from "sonner";
import { ThemeProvider } from "@/components/theme-provider";
+import { PageProgress } from "@/components/page-progress";
export default function RootLayout({
children,
@@ -82,6 +83,7 @@ export default function RootLayout({
suppressHydrationWarning
>
+
{
+ const handleClick = (e: MouseEvent) => {
+ const target = (e.target as HTMLElement).closest("a")
+ if (!target) return
+
+ const href = target.getAttribute("href")
+ if (!href) return
+
+ // Check if it's an internal link
+ const isInternal = href.startsWith("/") || href.startsWith(window.location.origin)
+ const isSamePage = href === window.location.pathname || href.startsWith(window.location.pathname + "#") || href === window.location.href
+
+ // Only show progress for actual page transitions
+ if (isInternal && !isSamePage && target.getAttribute("target") !== "_blank") {
+ setIsVisible(true)
+ setProgress(15)
+
+ // Simulate progress while waiting for route change
+ // Uses a decaying approach so it slows down as it gets closer to 95%
+ const interval = setInterval(() => {
+ setProgress((prev) => {
+ if (prev >= 95) {
+ clearInterval(interval)
+ return prev
+ }
+ return prev + (95 - prev) * 0.1
+ })
+ }, 100)
+
+ // Store interval so we can clear it on route change
+ ;(window as any).__page_progress_interval = interval
+ }
+ }
+
+ document.addEventListener("click", handleClick)
+ return () => document.removeEventListener("click", handleClick)
+ }, [])
+
+ // Finish progress on route change
+ useEffect(() => {
+ if (typeof window !== "undefined" && (window as any).__page_progress_interval) {
+ clearInterval((window as any).__page_progress_interval)
+ }
+
+ if (isVisible) {
+ setProgress(100)
+ const timeout = setTimeout(() => {
+ setIsVisible(false)
+ setTimeout(() => setProgress(0), 300) // Reset after hiding
+ }, 400)
+ return () => clearTimeout(timeout)
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [pathname, searchParams])
+
+ if (!isVisible && progress === 0) return null
+
+ return (
+
+ )
+}
+
+export function PageProgress() {
+ return (
+
+
+
+ )
+}
diff --git a/components/ui/progress.tsx b/components/ui/progress.tsx
new file mode 100644
index 0000000..777e627
--- /dev/null
+++ b/components/ui/progress.tsx
@@ -0,0 +1,31 @@
+"use client"
+
+import * as React from "react"
+import { Progress as ProgressPrimitive } from "radix-ui"
+
+import { cn } from "@/lib/utils"
+
+function Progress({
+ className,
+ value,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+ )
+}
+
+export { Progress }