Passion projects

Things I build for fun, outside client work.

Bookshelf Project

An online interactive bookshelf to store your reading material. Organize your ebooks and comics on shelves, then read them right here.

How it was built

Inspired by a Reddit post asking for a simple, interactive bookshelf for downloaded ebooks, I built a browser based library where you create shelves, add EPUB and CBZ files, drag books into any order, and read them without leaving the app. Everything runs client side with no backend: books and covers are stored in IndexedDB, and shelf layouts and reading progress persist in localStorage. Along the way I practiced React state design (lifting state, derived values, immutable updates), drag and drop with dnd-kit, URL driven navigation with React Router, and async file handling with Promises. I integrated third party libraries for each format (epubjs for EPUB, fflate for CBZ) and kept each behind its own module, so the reader components stay independent of storage and parsing. Features include metadata and cover extraction, spine colors generated from cover art, a CBZ reader with double-page and right-to-left modes, touch swipe support, and cleanup of orphaned data when books are removed. Deployed on Vercel.

Room Cleaner Game

A top down room tidying puzzle game built with React and Vite, where you race the clock to drag furniture into place.

How it was built

A browser based puzzle game built from scratch with React and Vite. Players drag furniture pieces onto matching outlines to tidy a randomly generated room before a sixty second timer runs out, with a new room loading automatically after each successful clear. The drag system is built directly on the Pointer Events API rather than native HTML drag and drop, using pointer capture to keep dragging reliable even during fast movements or releases outside the drop target. Room layouts are separated from the furniture catalog so new designs can be added as pure data, starting positions are generated with a collision check so furniture never spawns overlapping, and the player's best score persists locally between visits.