← Back to work

bible-viewer

A Bible reader that puts two Korean translations side by side

PersonalSole designer, developer, and operator2026.01 — Present

ReactTypeScriptViteTailwindFirebase

Why I built it

I often compare two Korean Bible translations verse by verse, but every reader I tried made switching translations a tab change. Reading both at once meant opening two windows. I wanted a viewer that lines the two texts up against the same verse.

What I did

Everything — data pipeline, search indexing, the column layout, Firestore sync, deployment.

What I learned

Importing a 30MB scripture JSON through resolveJsonModule kills tsc with a heap OOM: the type checker expands the entire literal into a type. Switching to ?url plus a runtime fetch fixed it.

I also spent a while deciding whether translations belonged in static JSON or Firestore. Scripture is immutable and large, so it stayed static; only per-user state (bookmarks, reading position) went to Firestore. That is where I learned that Firestore’s last-write-wins semantics will resurrect a deleted document from a stale write on another device unless deletions are modeled as tombstones.

The interesting part

I built column drag-and-reorder with raw Pointer Events, no dnd-kit. The layout is a row-based grid, so a “column” is not one DOM node. I laid a single absolutely-positioned pillar card behind transparent cells and wrote CSS custom properties (--drag-x) straight onto an ancestor, so a pointermove triggers zero re-renders. The other columns yield with translateX(±pitch). It ends up feeling like rearranging icons on an iPhone home screen.

What I’d do differently

To run integration tests without a browser I executed the Firestore logic in Node through Vite’s ssrLoadModule. Honest as that is, it still does not cover IndexedDB persistence in a real browser.