fix loading workflow 3
This commit is contained in:
@@ -129,6 +129,7 @@ function useLabelFade(i: number, progress: React.RefObject<number>, awake: boole
|
|||||||
// at the stage boundary where `focused` flips and the labels unmount, so the
|
// 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).
|
// 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);
|
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);
|
for (const el of labels.current) el.style.opacity = String(op);
|
||||||
});
|
});
|
||||||
return register;
|
return register;
|
||||||
@@ -788,7 +789,7 @@ function Floor() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function SceneReadySignal({ onReady }: { onReady?: () => void }) {
|
function SceneReadySignal({ onReady }: { onReady?: () => void }) {
|
||||||
const { active, progress } = useProgress();
|
const { active, progress, errors = [] } = useProgress();
|
||||||
const gl = useThree((s) => s.gl);
|
const gl = useThree((s) => s.gl);
|
||||||
const scene = useThree((s) => s.scene);
|
const scene = useThree((s) => s.scene);
|
||||||
const camera = useThree((s) => s.camera);
|
const camera = useThree((s) => s.camera);
|
||||||
@@ -796,35 +797,66 @@ function SceneReadySignal({ onReady }: { onReady?: () => void }) {
|
|||||||
const compiling = useRef(false);
|
const compiling = useRef(false);
|
||||||
const compiled = useRef(false);
|
const compiled = useRef(false);
|
||||||
const meaningfulFrames = useRef(0);
|
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(() => {
|
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;
|
compiling.current = true;
|
||||||
const renderer = gl as THREE.WebGLRenderer & {
|
const renderer = gl as THREE.WebGLRenderer & {
|
||||||
compileAsync?: (scene: THREE.Scene, camera: THREE.Camera) => Promise<void>;
|
compileAsync?: (scene: THREE.Scene, camera: THREE.Camera) => Promise<void>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
const compile = renderer.compileAsync
|
const compile = renderer.compileAsync
|
||||||
? renderer.compileAsync(scene, camera)
|
? renderer.compileAsync(scene, camera)
|
||||||
: Promise.resolve(renderer.compile(scene, camera));
|
: Promise.resolve().then(() => renderer.compile(scene, camera));
|
||||||
|
|
||||||
compile
|
compile
|
||||||
.catch(() => undefined)
|
.catch((err) => {
|
||||||
|
console.warn("StrategyCanvas compileAsync rejected:", err);
|
||||||
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
compiled.current = true;
|
compiled.current = true;
|
||||||
});
|
});
|
||||||
}, [active, camera, gl, progress, scene]);
|
} catch (e) {
|
||||||
|
console.warn("StrategyCanvas compile sync error:", e);
|
||||||
|
compiled.current = true;
|
||||||
|
}
|
||||||
|
}, [isReadyToCompile, camera, gl, scene]);
|
||||||
|
|
||||||
useFrame(() => {
|
useFrame(() => {
|
||||||
if (fired.current || active || progress < 100 || !compiled.current) return;
|
if (fired.current || !compiled.current) return;
|
||||||
|
|
||||||
|
totalFramesAfterCompile.current++;
|
||||||
|
|
||||||
const renderInfo = gl.info.render;
|
const renderInfo = gl.info.render;
|
||||||
const hasMeaningfulScene = renderInfo.calls >= 12 && renderInfo.triangles >= 500;
|
const hasMeaningfulScene = renderInfo.calls >= 12 && renderInfo.triangles >= 500;
|
||||||
meaningfulFrames.current = hasMeaningfulScene ? meaningfulFrames.current + 1 : 0;
|
meaningfulFrames.current = hasMeaningfulScene ? meaningfulFrames.current + 1 : 0;
|
||||||
if (meaningfulFrames.current < 4) return;
|
|
||||||
|
|
||||||
|
if (meaningfulFrames.current >= 4 || totalFramesAfterCompile.current >= 30 || bypassLoading) {
|
||||||
fired.current = true;
|
fired.current = true;
|
||||||
requestAnimationFrame(() => onReady?.());
|
requestAnimationFrame(() => onReady?.());
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return null;
|
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
|
// page load / while WF3 is still below the fold — without re-allocating them
|
||||||
// (and hitching) every time the user scrolls past and back.
|
// (and hitching) every time the user scrolls past and back.
|
||||||
const [everActive, setEverActive] = useState(false);
|
const [everActive, setEverActive] = useState(false);
|
||||||
|
// eslint-disable-next-line react-hooks/set-state-in-effect
|
||||||
useEffect(() => { if (active) setEverActive(true); }, [active]);
|
useEffect(() => { if (active) setEverActive(true); }, [active]);
|
||||||
const heavyFx = everActive && !reduced && !isMobile;
|
const heavyFx = everActive && !reduced && !isMobile;
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -149,6 +149,31 @@ export default function StrategySection({ connected = false }: { connected?: boo
|
|||||||
return () => { clearTimeout(refresh); st.kill(); };
|
return () => { clearTimeout(refresh); st.kill(); };
|
||||||
}, [scroll]);
|
}, [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.
|
// Intro hint fades out as the journey begins.
|
||||||
const introOpacity = useTransform(scroll, [0, 0.03, 0.06], [1, 1, 0]);
|
const introOpacity = useTransform(scroll, [0, 0.03, 0.06], [1, 1, 0]);
|
||||||
// Persistent header fades in after the intro.
|
// Persistent header fades in after the intro.
|
||||||
@@ -182,11 +207,9 @@ export default function StrategySection({ connected = false }: { connected?: boo
|
|||||||
role="status"
|
role="status"
|
||||||
aria-live="polite"
|
aria-live="polite"
|
||||||
aria-label="Loading MileTruth Strategy Engine"
|
aria-label="Loading MileTruth Strategy Engine"
|
||||||
onTransitionEnd={(e) => {
|
|
||||||
if (e.propertyName === "opacity" && sceneReady) setShowLoader(false);
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<span className="dm-st-loader__ring" />
|
<span className="dm-st-loader__ring" />
|
||||||
|
<span className="dm-st-loader__text">Loading strategy engine...</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -310,25 +333,29 @@ const styles = `
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dm-st-canvas { position: absolute; inset: 0; z-index: 1; opacity: 0; visibility: hidden;
|
.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.is-ready { opacity: 1; visibility: visible; transition-delay: 0s; }
|
||||||
.dm-st-canvas canvas { display: block; }
|
.dm-st-canvas canvas { display: block; }
|
||||||
|
|
||||||
.dm-st-ui { position: absolute; inset: 0; z-index: 4; pointer-events: none;
|
.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;
|
font-family: var(--font-space-grotesk), var(--font-manrope), system-ui, sans-serif; color: #0f172a;
|
||||||
opacity: 0; visibility: hidden;
|
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-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;
|
.dm-st-loader { position: absolute; top: 24px; left: 50%; transform: translateX(-50%); z-index: 6;
|
||||||
background: transparent; opacity: 1; pointer-events: none;
|
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;
|
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.is-hiding { opacity: 0; pointer-events: none; }
|
||||||
.dm-st-loader__ring { width: 18px; height: 18px; border-radius: 50%;
|
.dm-st-loader__ring { width: 14px; height: 14px; border-radius: 50%;
|
||||||
border: 2px solid rgba(255,255,255,0.48); border-top-color: #ffffff;
|
border: 2px solid rgba(255,255,255,0.2); border-top-color: #00E5FF;
|
||||||
box-shadow: 0 8px 22px rgba(15,23,42,0.14);
|
animation: dm-hiw-spin 0.8s linear infinite; box-sizing: border-box; }
|
||||||
animation: dm-hiw-spin 0.8s linear infinite; }
|
.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 dmStLoaderFadeIn { from { opacity: 0; } to { opacity: 1; } }
|
||||||
@keyframes dm-hiw-spin { to { transform: rotate(360deg); } }
|
@keyframes dm-hiw-spin { to { transform: rotate(360deg); } }
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user