Spaces:
Running
Running
| "use client"; | |
| import React, { useState, useEffect } from 'react'; | |
| import { usePathname } from 'next/navigation'; | |
| export default function PageLoader({ children }: { children: React.ReactNode }) { | |
| const pathname = usePathname(); | |
| const [isTransitioning, setIsTransitioning] = useState(false); | |
| const [loadProgress, setLoadProgress] = useState(0); | |
| const [isLoaded, setIsLoaded] = useState(true); | |
| const initialMount = React.useRef(true); | |
| useEffect(() => { | |
| if (initialMount.current && pathname === '/') { | |
| initialMount.current = false; | |
| return; | |
| } | |
| initialMount.current = false; | |
| setIsTransitioning(true); | |
| setIsLoaded(false); | |
| setLoadProgress(0); | |
| let progress = 0; | |
| // Safety net: force complete after 2500ms even if __PAGE_LOADED never fires | |
| // (e.g. the 404 page, or any page that doesn't set the flag) | |
| const safetyTimeout = setTimeout(() => { | |
| (window as any).__PAGE_LOADED = true; | |
| }, 2500); | |
| const interval = setInterval(() => { | |
| if (progress < 90) { | |
| progress += Math.random() * 15 + 5; | |
| } else if ((window as any).__PAGE_LOADED) { | |
| progress = 100; | |
| } | |
| if (progress >= 100) { | |
| progress = 100; | |
| clearInterval(interval); | |
| setIsLoaded(true); | |
| setTimeout(() => setIsTransitioning(false), 800); | |
| } | |
| setLoadProgress(progress); | |
| }, 80); | |
| return () => { | |
| clearInterval(interval); | |
| clearTimeout(safetyTimeout); | |
| // Always reset the flag on cleanup so next page starts fresh | |
| (window as any).__PAGE_LOADED = false; | |
| }; | |
| }, [pathname]); | |
| return ( | |
| <> | |
| <style dangerouslySetInnerHTML={{ | |
| __html: ` | |
| #global-loader { position: fixed; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; z-index: 9999; overflow: hidden; background: transparent; pointer-events: none; } | |
| .global-loader-panel { position: absolute; left: 0; width: 100%; height: 50%; background: var(--theme-bg); transition: transform 0.8s cubic-bezier(0.85, 0, 0.15, 1); z-index: 10000; will-change: transform; pointer-events: auto; } | |
| .global-loader-panel.top { top: 0; margin-bottom: -1px; } | |
| .global-loader-panel.bottom { bottom: 0; margin-top: -1px; } | |
| .global-loaded .global-loader-panel.top { transform: translateY(-101%); } | |
| .global-loaded .global-loader-panel.bottom { transform: translateY(101%); } | |
| .global-loader-content { position: relative; z-index: 10001; transition: opacity 0.4s ease; pointer-events: auto; } | |
| .global-loaded .global-loader-content { opacity: 0; pointer-events: none; } | |
| #global-progress-container { width: 200px; height: 1px; background: rgba(253, 232, 214, 0.1); margin-top: 20px; position: relative; overflow: hidden; } | |
| #global-progress-fill { position: absolute; top: 0; left: 0; height: 100%; background: var(--theme-text); transition: width 0.1s linear; } | |
| `}} /> | |
| {isTransitioning && ( | |
| <div id="global-loader" className={isLoaded ? 'global-loaded' : ''}> | |
| <div className="global-loader-panel top"></div> | |
| <div className="global-loader-panel bottom"></div> | |
| <div className="global-loader-content flex flex-col items-center"> | |
| <div className="text-[10px] font-mono tracking-[0.5em] text-[var(--theme-text)] uppercase mb-4">Initializing Module</div> | |
| <div id="global-progress-container"> | |
| <div id="global-progress-fill" style={{ width: `${Math.min(100, loadProgress)}%` }}></div> | |
| </div> | |
| <div className="text-[10px] font-mono text-[var(--theme-text)] mt-4 opacity-50">{Math.floor(Math.min(100, loadProgress))}%</div> | |
| </div> | |
| </div> | |
| )} | |
| <div className={`transition-all duration-1000 ${isTransitioning && !isLoaded ? 'opacity-0 blur-sm scale-95' : 'opacity-100 blur-0'}`}> | |
| {children} | |
| </div> | |
| </> | |
| ); | |
| } | |