dbaltaza.pt
TypeScript project · featured · web, performance, TypeScript
A game-console portfolio where every project is a pre-rendered disc: Vite + TypeScript, no framework, and a paint budget the tests measure.
- 30 mspaint budget per scenario
- 0 msof paint while the backdrop idles
- 300+unit tests
- 75+smoke tests in headless Chrome
The problem
Turn my portfolio into a PSP/PS2-era game console where every project is a disc, without giving up what a portfolio is for: loading fast on a phone, reading well for recruiters and search engines, and working by keyboard, gamepad and screen reader. Smooth had to be something a test can measure, not a feeling.
What I built
- Disc labels drawn as SVG by a script (
tools/disc-art), rendered in headless Chrome and saved as WebP at 600 and 1000 px. A disc on screen is two images: the printed label, the only part that turns, and one shared overlay for the hub, mirror band and light, which never moves. - A carousel, a game case with a back cover and a Manual, and a full-screen overlay for the games that run in the browser, in strict TypeScript with no UI framework. Every transition is a transform or an opacity, and a unit test keeps it that way.
- One input layer for keyboard, mouse wheel, touch and the Gamepad API, with a wheel stepper that turns a whole trackpad gesture, momentum included, into exactly one disc.
- A hash router that still opens the old site's links (
#metro,#resume, in any capitalisation), GitHub numbers fetched at build time with a daily rebuild, and plain HTML pages and a sitemap generated from the same typed content for search engines. - A Puppeteer smoke test that serves the build the way GitHub Pages does and checks every view, every disc, the deep links, axe on desktop and phone, and the paint budget from Chrome traces. GitHub Actions runs it on every push.
- A few things for the curious: an attract mode after a minute without input, a 404 that is a disc that won't read, and a disc that isn't on the shelf.
The hard part
Holding the paint budget. The backdrop takes the colour of the selected disc and fades to the next one over 1.2 s; written as a transition of one CSS colour, it repainted the whole screen on every frame, and stepping through five discs cost 44 ms of paint against a budget of 30. It is now a cross-fade: the new colour is painted once on a layer above and only that layer's opacity animates, on the compositor. The gradient is linear in the colour, so it shows exactly the same shades. The hover tilt had the same kind of bug: a perspective on the disc put its shadows in 3D and made Chrome re-raster every disc in the carousel on every frame, until the tilt carried its own perspective().
Built with
- TypeScript (strict)
- Vite
- Vitest + jsdom
- Puppeteer + axe-core
- GitHub Actions
- GitHub Pages
More projects
- metro · Lisbon metro sim in pixel art: watch the four lines run, step into any station, board a train and ride it.
- pac-man · Pac-Man in Python, built as a duo with sylvzzz: a new maze every level and four ghosts that each hunt you differently.
- call_me_maybe · Turns a sentence into a JSON function call with a 0.6B model using constrained decoding — parseable by construction, not by luck.
- codexion · Multithreaded twist on the Dining Philosophers: coders compete for shared dongles, with FIFO/EDF scheduling.
- fly-in · Drone fleet routing: Dijkstra pathfinding, turn-based scheduling and a pygame replay.
- a-maze-ing · Maze generator with multiple algorithms, pathfinding solvers and terminal visualization.
- push_swap · Highly optimized sorting with two stacks and a tiny set of allowed operations.
- ft_printf · printf rebuilt from scratch: chars, strings, pointers, decimals, unsigned and hex.
- get_next_line · Reads a file descriptor one line at a time with careful dynamic memory handling.
- Libft · My own C standard library — the foundation every later 42 project is built on.