Skip to content

jdcodes1/tv-game-browser

Repository files navigation

TV Game Browser

A 10-foot, TV-style games browse UI driven entirely by a D-pad, gamepad, or arrow keys — no mouse, no touch. Built as a focused demonstration of the spatial-navigation and rendering engineering that a smooth living-room UI requires on low-power hardware.

▶ Live demo: https://tv-game-browser.vercel.app

Browse grid Detail modal

The interesting engineering problem

A TV remote emits directions, not Tab. The tile that should receive focus when you press "right" is the one physically to the right on screen — which, in a virtualized carousel, is usually not the next element in DOM order. So focus can't be a tabindex sequence; it has to be resolved from on-screen geometry.

On top of that, it has to stay at 60fps on a cheap TV SoC, where a single synchronous image decode or a scrollLeft reflow inside a frame is enough to drop frames visibly.

This project solves both:

  • A geometry-based spatial navigation engine — every focusable element registers its rectangle; a pure scoring function picks the nearest candidate in the pressed direction, with focus memory per row and a modal focus trap.
  • A transform-only, virtualized render path plus an off-main-thread image pipeline (img.decode(), 4-way concurrency cap, spatially-bounded LRU) so navigation never triggers layout or synchronous decode.

Full write-up in ARCHITECTURE.md.

Measured performance

Under 6× CPU throttling (CDP, emulating a low-power TV SoC), driving 190 key presses across the whole grid:

Metric Threshold Measured
Dropped frames ≤ 5% 0%
Longest long task < 100ms 0ms (0 observed)
JS heap ≤ 100MB 9.5MB

Sustained ~60fps (990 frames / 16.5s). These are real, reproducible numbers — see BENCHMARK.md for methodology and caveats. Nothing here is hand-waved; npm run benchmark fails the build if any threshold regresses.

Stack

  • React 19 + TypeScript (strict, verbatimModuleSyntax)
  • Vite 8 build / dev
  • Vitest for unit + integration tests
  • Playwright + CDP for the performance benchmark
  • Deployed on Vercel
  • Gamepad API + keyboard for input; no UI framework, no virtualization library — the engine is ~500 lines of first-party code

Running locally

npm install
npm run dev          # http://localhost:5173

Navigate with arrow keys (or a connected gamepad's D-pad). Enter/A opens a tile's detail modal; Esc/B closes it.

Game data

Tiles are real games with real box art. src/data/games.ts is generated from Steam's public store API (titles, release years, genres, ratings, descriptions) with cover art from Steam's library capsules — committed as static data, so the app ships with real games and needs no API key at runtime. To refresh or recurate:

node scripts/fetch-games.mjs   # edit the curated app-ID list at the top first

Testing

npm test             # 46 unit + integration tests (vitest)
npm run build        # type-check (tsc -b) + production build
npm run benchmark    # Playwright perf run under 6x CPU throttle

The integration tests mock getBoundingClientRect to synthesize grid geometry, then drive real keydown events through the live engine — asserting within-row and cross-row movement, focus memory, aria-live announcements, and the modal focus trap.

Accessibility

Roving tabindex, an aria-live announcer for every focus change, a role="dialog" modal with trapped/restored focus, 48px overscan-safe margins, 24px minimum type, a high-contrast focus ring, and no hover-dependent UI.

About

React + TypeScript TV games-browse UI with custom spatial navigation engine, virtualized carousel rows, and Playwright performance benchmarks

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors