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
34 lines
969 B
TypeScript
34 lines
969 B
TypeScript
"use client";
|
|
|
|
import { useEffect, useRef, useState } from "react";
|
|
|
|
/** True once no pointer/keyboard activity has been seen for `timeoutMs`. */
|
|
export function useIdleTimer(timeoutMs = 3000): boolean {
|
|
const [isIdle, setIsIdle] = useState(false);
|
|
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
|
|
useEffect(() => {
|
|
const reset = () => {
|
|
setIsIdle(false);
|
|
if (timerRef.current) clearTimeout(timerRef.current);
|
|
timerRef.current = setTimeout(() => setIsIdle(true), timeoutMs);
|
|
};
|
|
|
|
reset();
|
|
const events: Array<keyof WindowEventMap> = [
|
|
"mousemove",
|
|
"pointerdown",
|
|
"touchstart",
|
|
"keydown",
|
|
];
|
|
events.forEach((event) => window.addEventListener(event, reset, { passive: true }));
|
|
|
|
return () => {
|
|
if (timerRef.current) clearTimeout(timerRef.current);
|
|
events.forEach((event) => window.removeEventListener(event, reset));
|
|
};
|
|
}, [timeoutMs]);
|
|
|
|
return isIdle;
|
|
}
|