diff --git a/components/pinball/PinballCanvasImpl.tsx b/components/pinball/PinballCanvasImpl.tsx
index e1229bb..8686ef5 100644
--- a/components/pinball/PinballCanvasImpl.tsx
+++ b/components/pinball/PinballCanvasImpl.tsx
@@ -74,6 +74,13 @@ export default function PinballCanvasImpl({ tableData, rom, tableInfo }: Pinball
)}
+
+ {status === "starting" && (
+
+ )}
);
}
diff --git a/lib/pinball/usePinballInstance.ts b/lib/pinball/usePinballInstance.ts
index 352b88c..d5eee96 100644
--- a/lib/pinball/usePinballInstance.ts
+++ b/lib/pinball/usePinballInstance.ts
@@ -10,7 +10,7 @@ import { hasWebGL2 } from "./webglSupport";
// outside webpack's module graph (see scripts/copy-engine-assets.mjs).
const ENGINE_BASE_URL = "/vendor/vpinball-wasm";
-export type PinballStatus = "idle" | "loading" | "ready" | "running" | "error";
+export type PinballStatus = "idle" | "loading" | "ready" | "starting" | "running" | "error";
interface EngineModule {
loadPinball(options: LoadPinballOptions): Promise;
@@ -114,8 +114,18 @@ export function usePinballInstance(
const start = useCallback(() => {
if (!instanceRef.current) return;
- instanceRef.current.start();
- setStatus("running");
+ setStatus("starting");
+ // instance.start() runs synchronously and blocks the main thread for a
+ // noticeable moment (heavy WASM init) — a nested rAF lets the browser
+ // paint the "starting" spinner from the state update above before that
+ // block hits, instead of the UI freezing on the old screen mid-click.
+ requestAnimationFrame(() => {
+ requestAnimationFrame(() => {
+ if (disposedRef.current || !instanceRef.current) return;
+ instanceRef.current.start();
+ setStatus("running");
+ });
+ });
}, []);
return { status, progress, error, loadTimeMs, instanceRef, start };