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
36 lines
1.0 KiB
TypeScript
36 lines
1.0 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useState } from "react";
|
|
|
|
/** Self-contained rAF-based FPS counter — the engine exposes no perf API of its own. */
|
|
export default function StatsReadout() {
|
|
const [fps, setFps] = useState<number | null>(null);
|
|
|
|
useEffect(() => {
|
|
let frameCount = 0;
|
|
let windowStart = performance.now();
|
|
let raf = 0;
|
|
|
|
const tick = (now: number) => {
|
|
frameCount += 1;
|
|
const elapsed = now - windowStart;
|
|
if (elapsed >= 500) {
|
|
setFps(Math.round((frameCount * 1000) / elapsed));
|
|
frameCount = 0;
|
|
windowStart = now;
|
|
}
|
|
raf = requestAnimationFrame(tick);
|
|
};
|
|
raf = requestAnimationFrame(tick);
|
|
|
|
return () => cancelAnimationFrame(raf);
|
|
}, []);
|
|
|
|
return (
|
|
<div className="flex items-baseline gap-2 rounded border border-chrome/30 bg-black/40 px-3 py-1.5 font-mono">
|
|
<span className="text-xs tracking-widest text-chrome">FPS</span>
|
|
<span className="text-lg tabular-nums text-arc">{fps ?? "--"}</span>
|
|
</div>
|
|
);
|
|
}
|