Custom caps — 3D configurator
A phone-first 3D studio where customers design rhinestone caps in real millimetres, with store and back-office.
- Astro 7
- React 19
- Three.js
- zustand + immer
- zod
- Drizzle ORM
- Cloudflare D1 + R2
- Vitest + Playwright
- 53named anchor points per cap model, each with a mirror twin
- 315 → 21 msper step with 6,000 stones along the edge, after tuning
- 26architecture decision records
One cap, measured in millimetres
Tap a block to see what it does.
- Logic
- People
- Data
- Infrastructure
- Output
The heart of the system
Cap studio in millimetres
Every part works on the same design: a procedural cap in real millimetres, edited on a phone, checked by one schema and turned into products, stock and a bill of materials.
Connected to Cap engine, Phone Studio, Offline drafts, Design document, Cloudflare Worker, D1 + R2, Store and cart, Back-office
Logic
Cap engine
A procedural Three.js cap in real millimetres: 5 models, 14 zones, 11 procedural fabrics, 53 named anchors with mirror twins.
- Three.js
Gets from Cap studio in millimetresHands to Phone Studio
People
Phone Studio
A React editor for phones: rhinestones from text, shape, image or brush; mirror, layers, undo and redo through immer patches.
- React 19
- zustand
- immer
Gets from Cap engine, Cap studio in millimetresHands to Offline drafts, Design document
Data
Offline drafts
Drafts saved in IndexedDB and synced with a revision number, so conflicting edits are detected instead of overwritten.
- IndexedDB
Gets from Phone Studio, Cap studio in millimetresHands to Cloudflare Worker
Logic
Design document
One zod schema on client and server, real rhinestone sizes, prices and a bill of materials computed from each design.
- zod
Gets from Phone Studio, Cap studio in millimetresHands to Cloudflare Worker
Infrastructure
Cloudflare Worker
Astro on one Worker; the API is split into public, studio and admin prefixes, with email codes, CSRF, CSP, rate limits and validated uploads.
- Astro 7
- Cloudflare Workers
Gets from Offline drafts, Design document, Cap studio in millimetresHands to D1 + R2
Data
D1 + R2
41 tables through Drizzle, amounts in cents; uploaded assets in R2, where private ones answer 404 to everyone else.
- Cloudflare D1
- Cloudflare R2
- Drizzle ORM
Gets from Cloudflare Worker, Cap studio in millimetresHands to Store and cart, Back-office
Output
Store and cart
Astro store pages and a server-side guest cart with atomic holds on limited drops, discounts and shipping. Stripe checkout is planned.
- Astro 7
- GSAP
Gets from D1 + R2, Cap studio in millimetres
People
Back-office
Stock with transactional moves, a shopping list, and materials deducted from each order's bill of materials exactly once.
- React 19
Gets from D1 + R2, Cap studio in millimetres
The problem
A maker of custom rhinestone caps had a 3D configurator with a low-poly model, one colour for the whole cap and a native colour picker that covered the model on iPhone. The client wanted placing text, logos, patches and rhinestones to be very easy, with many preset anchors, snapping and symmetry.
What I built
- Wrote a phased master spec with quality gates and bans, then ran Claude Code in a long autonomous loop and Codex on a separate branch, with every task checked again by an independent reviewer subagent.
- Specified a procedural cap engine in real millimetres instead of a 3D asset: 5 models, 14 zones, 11 procedural fabrics and 53 named anchors, each with a mirror twin.
- Shipped a phone-first Studio: rhinestones from text, shapes, images or a brush, mirror mode, undo and redo, and offline drafts that sync with conflict detection.
- Added a store with a server-side cart that holds limited pieces atomically, and a back-office where each design's bill of materials drives the stock.
- Locked down the Worker: one-time email codes, CSRF, CSP, per-client rate limits and validated uploads; private assets answer 404, never 403.
The hard part
Putting flat text and logos on a curved cap without distortion. The first surface map inverted with errors up to 53.7 mm; Newton iteration with periodic wrapping brought it to 0 errors on 400 test points, and a geodesic walker brought sheared elements back to square.
Result
A working local prototype, not deployed yet: Studio, store and back-office run on local D1 and R2 after 74 commits in 6 days. Placing 6,000 stones along the edge went from 315 to 21 ms per step after tuning; checkout, export and the final audit are still to do.
My role
Client project built by orchestrating agents: Claude Code and Codex wrote the code. I wrote and maintained the master spec, supplied the references and my design skills, asked for the technical comparisons that became ADRs, set the priorities and reviewed the results.