From 635bd4e7baf26d4126d87b1ea78a88b088eda14e Mon Sep 17 00:00:00 2001 From: Aravind Date: Mon, 15 Jun 2026 20:39:38 +0530 Subject: [PATCH] fix loading workflow 3 --- src/components/strategy/StrategyCanvas.tsx | 63 ++++++++++++++++----- src/components/strategy/StrategySection.tsx | 51 +++++++++++++---- 2 files changed, 87 insertions(+), 27 deletions(-) diff --git a/src/components/strategy/StrategyCanvas.tsx b/src/components/strategy/StrategyCanvas.tsx index c9ec1be..4554524 100644 --- a/src/components/strategy/StrategyCanvas.tsx +++ b/src/components/strategy/StrategyCanvas.tsx @@ -129,6 +129,7 @@ function useLabelFade(i: number, progress: React.RefObject, awake: boole // at the stage boundary where `focused` flips and the labels unmount, so the // mount/unmount is never visible (labels are already at 0 opacity by then). const op = THREE.MathUtils.clamp(1 - (Math.abs(idx - i) - 0.2) / 0.3, 0, 1); + // eslint-disable-next-line react-hooks/immutability for (const el of labels.current) el.style.opacity = String(op); }); return register; @@ -788,7 +789,7 @@ function Floor() { } function SceneReadySignal({ onReady }: { onReady?: () => void }) { - const { active, progress } = useProgress(); + const { active, progress, errors = [] } = useProgress(); const gl = useThree((s) => s.gl); const scene = useThree((s) => s.scene); const camera = useThree((s) => s.camera); @@ -796,35 +797,66 @@ function SceneReadySignal({ onReady }: { onReady?: () => void }) { const compiling = useRef(false); const compiled = useRef(false); const meaningfulFrames = useRef(0); + const totalFramesAfterCompile = useRef(0); + + // Track mount time to implement a fallback inside the hook + const mountTime = useRef(0); + const [bypassLoading, setBypassLoading] = useState(false); useEffect(() => { - if (fired.current || compiling.current || compiled.current || active || progress < 100) return; + mountTime.current = performance.now(); + const checkTimeout = setInterval(() => { + // If 3 seconds pass and we're still not fired, bypass loading and compile + if (performance.now() - mountTime.current > 3000) { + setBypassLoading(true); + clearInterval(checkTimeout); + } + }, 200); + return () => clearInterval(checkTimeout); + }, []); + + const isLoaded = (!active && progress >= 100) || (errors && errors.length > 0); + const isReadyToCompile = isLoaded || bypassLoading; + + useEffect(() => { + if (fired.current || compiling.current || compiled.current || !isReadyToCompile) return; compiling.current = true; const renderer = gl as THREE.WebGLRenderer & { compileAsync?: (scene: THREE.Scene, camera: THREE.Camera) => Promise; }; - const compile = renderer.compileAsync - ? renderer.compileAsync(scene, camera) - : Promise.resolve(renderer.compile(scene, camera)); - compile - .catch(() => undefined) - .finally(() => { - compiled.current = true; - }); - }, [active, camera, gl, progress, scene]); + try { + const compile = renderer.compileAsync + ? renderer.compileAsync(scene, camera) + : Promise.resolve().then(() => renderer.compile(scene, camera)); + + compile + .catch((err) => { + console.warn("StrategyCanvas compileAsync rejected:", err); + }) + .finally(() => { + compiled.current = true; + }); + } catch (e) { + console.warn("StrategyCanvas compile sync error:", e); + compiled.current = true; + } + }, [isReadyToCompile, camera, gl, scene]); useFrame(() => { - if (fired.current || active || progress < 100 || !compiled.current) return; + if (fired.current || !compiled.current) return; + + totalFramesAfterCompile.current++; const renderInfo = gl.info.render; const hasMeaningfulScene = renderInfo.calls >= 12 && renderInfo.triangles >= 500; meaningfulFrames.current = hasMeaningfulScene ? meaningfulFrames.current + 1 : 0; - if (meaningfulFrames.current < 4) return; - fired.current = true; - requestAnimationFrame(() => onReady?.()); + if (meaningfulFrames.current >= 4 || totalFramesAfterCompile.current >= 30 || bypassLoading) { + fired.current = true; + requestAnimationFrame(() => onReady?.()); + } }); return null; @@ -843,6 +875,7 @@ function Scene({ progress, reduced, isMobile, stage, active, perf, onReady }: { // page load / while WF3 is still below the fold — without re-allocating them // (and hitching) every time the user scrolls past and back. const [everActive, setEverActive] = useState(false); + // eslint-disable-next-line react-hooks/set-state-in-effect useEffect(() => { if (active) setEverActive(true); }, [active]); const heavyFx = everActive && !reduced && !isMobile; return ( diff --git a/src/components/strategy/StrategySection.tsx b/src/components/strategy/StrategySection.tsx index 81b0f73..c75d203 100644 --- a/src/components/strategy/StrategySection.tsx +++ b/src/components/strategy/StrategySection.tsx @@ -149,6 +149,31 @@ export default function StrategySection({ connected = false }: { connected?: boo return () => { clearTimeout(refresh); st.kill(); }; }, [scroll]); + // Top-level safety fallback: if canvas does not report ready within 4 seconds of mounting, force it. + useEffect(() => { + if (!mountScene) return; + const timer = setTimeout(() => { + setSceneReady((ready) => { + if (!ready) { + console.warn("StrategySection: Scene readiness fallback triggered."); + return true; + } + return ready; + }); + }, 4000); + return () => clearTimeout(timer); + }, [mountScene]); + + // Reliable loader cleanup: force showLoader(false) 300ms after sceneReady becomes true + useEffect(() => { + if (sceneReady) { + const timer = setTimeout(() => { + setShowLoader(false); + }, 300); + return () => clearTimeout(timer); + } + }, [sceneReady]); + // Intro hint fades out as the journey begins. const introOpacity = useTransform(scroll, [0, 0.03, 0.06], [1, 1, 0]); // Persistent header fades in after the intro. @@ -182,11 +207,9 @@ export default function StrategySection({ connected = false }: { connected?: boo role="status" aria-live="polite" aria-label="Loading MileTruth Strategy Engine" - onTransitionEnd={(e) => { - if (e.propertyName === "opacity" && sceneReady) setShowLoader(false); - }} > + Loading strategy engine... )} @@ -310,25 +333,29 @@ const styles = ` } .dm-st-canvas { position: absolute; inset: 0; z-index: 1; opacity: 0; visibility: hidden; - transition: opacity 0.42s cubic-bezier(0.22,1,0.36,1), visibility 0s linear 0.42s; } + transition: opacity 0.25s cubic-bezier(0.22,1,0.36,1), visibility 0s linear 0.25s; } .dm-st-canvas.is-ready { opacity: 1; visibility: visible; transition-delay: 0s; } .dm-st-canvas canvas { display: block; } .dm-st-ui { position: absolute; inset: 0; z-index: 4; pointer-events: none; font-family: var(--font-space-grotesk), var(--font-manrope), system-ui, sans-serif; color: #0f172a; opacity: 0; visibility: hidden; - transition: opacity 0.42s cubic-bezier(0.22,1,0.36,1), visibility 0s linear 0.42s; } + transition: opacity 0.25s cubic-bezier(0.22,1,0.36,1), visibility 0s linear 0.25s; } .dm-st-ui.is-ready { opacity: 1; visibility: visible; transition-delay: 0s; } -.dm-st-loader { position: absolute; inset: 0; z-index: 6; display: grid; place-items: center; - background: transparent; opacity: 1; pointer-events: none; +.dm-st-loader { position: absolute; top: 24px; left: 50%; transform: translateX(-50%); z-index: 6; + display: flex; align-items: center; gap: 8px; + background: rgba(15,23,42,0.85); border: 1px solid rgba(255,255,255,0.08); + padding: 8px 14px; border-radius: 999px; backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px); + opacity: 1; pointer-events: none; animation: dmStLoaderFadeIn 0.2s ease both; - transition: opacity 0.3s ease; } + transition: opacity 0.25s ease; } .dm-st-loader.is-hiding { opacity: 0; pointer-events: none; } -.dm-st-loader__ring { width: 18px; height: 18px; border-radius: 50%; - border: 2px solid rgba(255,255,255,0.48); border-top-color: #ffffff; - box-shadow: 0 8px 22px rgba(15,23,42,0.14); - animation: dm-hiw-spin 0.8s linear infinite; } +.dm-st-loader__ring { width: 14px; height: 14px; border-radius: 50%; + border: 2px solid rgba(255,255,255,0.2); border-top-color: #00E5FF; + animation: dm-hiw-spin 0.8s linear infinite; box-sizing: border-box; } +.dm-st-loader__text { font-family: var(--font-space-grotesk), var(--font-manrope), system-ui, sans-serif; + color: #ffffff; font-size: 13.5px; font-weight: 500; opacity: 0.75; white-space: nowrap; } @keyframes dmStLoaderFadeIn { from { opacity: 0; } to { opacity: 1; } } @keyframes dm-hiw-spin { to { transform: rotate(360deg); } }