client work active-build

NeatoFX Play

A player identity and loyalty layer for shooting galleries: passkey signup, attendant-scanned payment, and leaderboards across every location.

ViteReactTypeScriptCloudflare Pages FunctionsCloudflare D1WebAuthnStripe ConnectPlaywrightVitest
The landing page: the promise, the tour, and a live product loop in the first viewport.
The landing page: the promise, the tour, and a live product loop in the first viewport.
The same landing page on a phone, which is where most of this pitch gets read.
The same landing page on a phone, which is where most of this pitch gets read.
A player's dashboard on the demo store: their points, offers and scores across every location they have played.
A player's dashboard on the demo store: their points, offers and scores across every location they have played.
The attendant's admin screen on the demo store, confirming a player's identity and payment from a scanned code.
The attendant's admin screen on the demo store, confirming a player's identity and payment from a scanned code.
The store owner's dashboard on the demo store: takings and games, broken down to the stall.
The store owner's dashboard on the demo store: takings and games, broken down to the stall.
The public leaderboards, on the demo store.
The public leaderboards, on the demo store.

A game at a shooting gallery costs about $5. The lane counts it, and that is the whole record. Nobody knows who played, so nobody comes back for a rank, and the operator cannot tell a regular from a walk-in.

What It Is

NeatoFX builds the hardware inside those galleries: IR hit detection with an onboard prop controller, event-triggered audio, touchscreen scoring displays, and a motor controller built for animatronics and moving props. Its founder is a friend of mine, and his lanes already count the games. NeatoFX Play is the identity layer that sits next to that count.

A player scans a printed QR code at the stall and signs up with a passkey. Email is optional. They get a dashboard of their points, offers and scores across every location they have played. At the counter they show a code. The attendant, signed in on the admin side, scans it to confirm who they are and that they paid. The lane does not know who is shooting. The scan is what attaches a name.

Who Sees What

The sale credits that store’s account, down to the individual stall. A store owner sees their own takings and games broken down the same way. A super admin sees every store. The public site shows leaderboards.

Payments are Stripe Connect destination charges through Stripe-hosted Checkout, so the money lands in each store’s own Stripe account and Play never touches a card. Sign-in is WebAuthn passkeys. A passkey is bound to one domain, so attaching a custom domain later means every player and every staff member registers again. That is a product constraint, and it decides the order the launch happens in. A player who may be under 13 gets a stable public alias instead of the name they typed, and cannot give an email.

Four Demos, Because the Hardware Is Not in the Room

The founder had to be able to see this without shipping a lane anywhere. So: a 56-second self-running tour on one screen, a fleet view across five fictional venues, a live in-browser sandbox running the real matching code, and a hardware lane driven by a USB arcade button through the Gamepad API. An ESP32 sketch and a Home Assistant package are the two integration paths. His installs report to his own server, and the bridge from that server to Play is specified rather than built.

One Seam, Three Backends

The domain logic runs behind a single storage interface with three implementations: Firestore, D1, and in-memory. That is why the backend could move from Firebase to Cloudflare without rewriting the product, and why the tests and the in-browser demo run the real matching code instead of a stub.

Verification: 203 unit tests pass. Seven Firestore rules tests have never run at all, because they need Java and I do not have it installed. Plus a six-role walk that passes 27 of 27 against the deployed backend rather than a mock. Passkey signup, attendant scan and cash payment, hardware ingest through Play’s own Node client, all three dashboards, leaderboards, and the negative cases.

Honest Constraints

Real Stripe has never run. There are no keys yet, so a purchase answers 503 by design. The camera scan has been proven against a fake video feed, not a real phone camera in a real hand. The ESP32 sketch has not been compiled. The bridge from the founder’s server to Play is still unbuilt, so nothing on his hardware makes Play’s request today.

And nothing is signed. This is a pitch with a working product behind it, and still not a partnership.

Stack

LayerToolRole
FrontendVite + React + TypeScriptOne app, player and staff surfaces behind role checks
APICloudflare Pages FunctionsRoute contract written first, so five workstreams could build against it at once
DataCloudflare D1Ledger rows for every sale and every game: store, stall, lane, staff, player, offer
AuthWebAuthn passkeysDomain-bound, fails closed if the origin is unset
PaymentsStripe ConnectDestination charges, Stripe-hosted Checkout, no card data here
TestingVitest + Playwright203 passing unit tests (7 more need Java and have never run), and role walks driven against the deployed backend