There are 3 root causes causing the UI to flash before/during the loader:
LoadingWrapper uses isHydrated state (starts false). Until React hydrates, shouldShowInitialLoading = false, so the loader is invisible for ~200–500ms while the page renders below it.
isHydrated: false → shouldShowInitialLoading = false → loader not rendered
React hydrates → isHydrated = true → loader NOW appears ← UI already visible
onComplete in LoadingWrapper calls setVideoComplete(true) when the video ends or the fallback timeout fires — but onModelLoaded in Hero.tsx never connects to the loading context. The loader can disappear before Charizard is ready.
LoadingWrapper always renders {children} first (intentional, for 3D preloading), then overlays the loader. But because of Bug 1, there's a window where children are visible and the loader isn't mounted yet.
- Remove the
isHydratedguard — instead render the loader overlay withopacity: 0 → 1CSS immediately on mount (avoids hydration mismatch and the flash). - Add
suppressHydrationWarningon the loader div so SSR/client mismatch is silently accepted. - Replace
handleVideoComplete → setVideoCompletewith finishInitialLoading — the loader should complete when the model is loaded, not when the video ends. - Expose a
onModelReadycallback prop that Hero.tsx can call when Charizard finishes loading. - Wire it into LoadingContext via finishInitialLoading.
- Merge
isVideoCompletelogic intoisModelLoaded: the loader should finish when setModelLoaded(true) is called. - Keep
isVideoCompletestate so nothing else breaks. - Add a safety timeout (6 s) — if the model never fires
onLoaded, dismiss the loader anyway (prevents infinite load on WebGL errors).
- Call setModelLoaded(true) via useLoading() inside the existing
onModelLoadedcallback — one line added. - This directly connects model-ready signal to the loading context.
- No structural changes needed — just update the
fallbackTimeoutMsdefault to6000to align with context safety timeout (was3000, which could dismiss the loader before the model is ready on slow connections).
cd "c:\N Drive\Github projects\portfolio-charizardop" && npx tsc --noEmitExpected: exit code 0, no errors.
- Run
npm run devand openhttp://localhost:3000in an incognito window (clears sessionStorage so the "starter" loader path is triggered). - Expected: Loader appears instantly — no flash of page content underneath.
- Expected: The loader stays up until Charizard finishes flying into frame.
- Expected: After ~1–2 s of Charizard being visible, loader fades out smoothly.
- Reload the page (non-incognito,
hasVisitedset) — the returning-visitor fast loader (~1.5 s) should show immediately with no flash.