Add vpinball: a browser Visual Pinball player
Next.js app that plays real .vpx tables via @valknar/vpinball-wasm (WebAssembly Visual Pinball). Demo mode with the bundled default table, drag-and-drop custom table upload, a cabinet-styled UI with a coin-door HUD (fullscreen/stats/info/eject), a custom 404 page, PWA support (hand-rolled service worker), and a static Docker Compose deployment behind nginx. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012hQoM3jJT1Lx7CMTciMzvD
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect } from "react";
|
||||
import type { attachTouchControls as AttachTouchControls } from "@valknar/vpinball-wasm";
|
||||
|
||||
const ENGINE_BASE_URL = "/vendor/vpinball-wasm";
|
||||
|
||||
function isTouchDevice(): boolean {
|
||||
return typeof window !== "undefined" && ("ontouchstart" in window || navigator.maxTouchPoints > 0);
|
||||
}
|
||||
|
||||
/** Attaches the engine's on-screen touch overlay to `containerRef` while `active` is true. */
|
||||
export function useTouchControls(
|
||||
containerRef: React.RefObject<HTMLElement | null>,
|
||||
active: boolean,
|
||||
): void {
|
||||
useEffect(() => {
|
||||
if (!active || !isTouchDevice()) return;
|
||||
const container = containerRef.current;
|
||||
if (!container) return;
|
||||
|
||||
let handle: { detach: () => void } | undefined;
|
||||
let cancelled = false;
|
||||
|
||||
(async () => {
|
||||
const { attachTouchControls } = (await import(
|
||||
/* webpackIgnore: true */ `${ENGINE_BASE_URL}/index.js`
|
||||
)) as { attachTouchControls: typeof AttachTouchControls };
|
||||
if (cancelled) return;
|
||||
handle = attachTouchControls({ container });
|
||||
})();
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
handle?.detach();
|
||||
};
|
||||
}, [containerRef, active]);
|
||||
}
|
||||
Reference in New Issue
Block a user