2026 · Solo · Static web app
The Reinhardt Family Recipe Box
My grandmother has a recipe box our family has cooked out of for decades. There's only one of it, so I scanned every card and put the whole thing online, built to look and open like the box itself.
The brief
The goal was simple: everyone in the family should be able to find a recipe from their phone, and the site should never need looking after. So there's no server, no database, and no dependencies — a single HTML file reads a JSON manifest and two folders of images.
Just as important, it should feel like her box, not a recipe database. The front page is the box: a wooden case with its lid open and a manila divider for every category. Click a tab to pull that category, or search across all of it.
- 701
- Recipes
- 926
- Card scans, 215 recipes spanning more than one card
- 22
- Categories, one divider tab each
- 1
- HTML file — the entire application
- 0
- Servers, databases, or dependencies to keep alive
01A box made of divs
The box is plain HTML and CSS 3D transforms — no canvas, no WebGL, no images for the wood. A scene
with perspective holds the box tilted back and to the side; every wall, the floor, and the lid are
ordinary elements placed in 3D space with translateZ and rotateY, and the
wood grain is layered gradients. The front carries a thumb notch and an embossed name plate whose
screw heads are pseudo-elements.
The lid is a hollow tray hinged at the back: an outer shell, rim walls, and four inner walls, each darkened to a slightly different brightness to fake the way light falls into it. Its depth and wall thickness are CSS variables, which made it quick to tune by eye.
The dividers are generated from the manifest — one per category, spaced evenly through the depth of the box, alternating between two manila shades, with tabs cycling through staggered positions the way real dividers do. The last tab position is computed so it sits exactly flush with the card's edge.
02Making the tabs feel right
Hovering a divider lifts it out of the box. The catch is that a lifted tab moves out from under the
cursor, which fires mouseleave, which drops it back down — an infinite flicker. The fix
tracks the pointer instead: a lifted tab stays up until the pointer leaves its rectangle plus a
margin, with a deeper margin below to cover the path the tab just travelled. The whole lifted divider
is clickable, so the target is generous, and clicking the open tab again closes it.
On phones and other devices without hover, the box becomes a scaled-down illustration and a grid of manila category buttons with recipe counts takes over the navigation.
03Browsing and search
Opening a category deals its cards into a masonry gallery. Cards are dealt round-robin across the columns so each visual row still reads left to right, and the layout rebuilds only when the column count actually changes on resize. Thumbnails load lazily, multi-card recipes are labelled "page 1 of 2," and clicking one opens every page stacked at full size.
Search is deliberately simple: the manifest is flattened into one list on load, and titles are matched as you type. Each result carries a small badge tinted from its category's divider color, so results still feel like they came out of the box.
04The build script
A Python script with Pillow turns a folder of original scans into the site. Categories are subfolders; titles, page order, and multi-card recipes all come from the filenames:
Recipes/Cookies/Cookies - Capt. Tweedie Cookies (1).jpeg
Recipes/Cookies/Cookies - Capt. Tweedie Cookies (2).jpeg
└──────── title ────────┘ └ page
- Every scan becomes a 1600px web image and a 560px thumbnail — progressive, optimized JPEGs, with phone-camera rotation baked in so nothing appears sideways.
- Pages of the same recipe are grouped and ordered; apostrophes, which were kept out of filenames, are restored in titles.
- It's incremental: a scan is only reprocessed if it's newer than its outputs, and the manifest is rebuilt every run.
- The originals are never modified. Adding a recipe means dropping a scan in a folder and running the script again.
05Deployment
The output is one static folder with relative paths, so it can live anywhere. It's served from
/recipe-box/ on this site, with a handful of friendlier short links
(/recipes, /cookbook) redirecting to it.
Stack
HTML · CSS 3D transforms · vanilla JavaScript · Python · Pillow