Repository
Margin
Turns a folder of photographs into a written chapter about the day they were taken, with artwork made for it.
A scrapbook that remembers more than you did.
Private repository. Everything below is written from the code; none of it is a link you can follow.
What it is
You upload a batch of photos and it builds a chapter. Every photo is enriched with what can be inferred — time, place, weather, faces, scene emotion, subjects — and the chapter is written in one of four voices, given a visual identity generated for it, and rendered as an editorial scroll, a paged book, or a scrapbook. Chapters accumulate into a library with people memory, maps and year portraits.
Why it exists
Everyone has ten thousand photos and no story. The products that address that either generate slop — Your Week in Review — or demand hours of manual scrapbooking. The bet is that enrichment plus generation closes the gap: if the system knows it was raining, that these three people recur, and that this was the last photo of a trip, it can write something worth keeping.
As a business
Consumer subscription is the obvious shape, and the bindable books point at print as an upsell. I did not pursue it — there is no billing, no pricing, no paywall. The economics are the hard part and I know it: a chapter costs a photo-enrichment pass plus a writing pass plus image generation, which is closer to a print product's margins than a software product's.
Stack
- Client
- Next.js 14 · TypeScript · Tailwind
- Backend
- Supabase · Postgres · Auth · Storage
- AI
- Claude · OpenAI · Nano Banana 2 · Recraft
The decision
The problem
A template makes every chapter look the same, which undercuts the whole premise that each chapter is a distinct memory. But freely-generated design is usually worse than a good template — unconstrained generation produces the clip-art look that makes a product feel cheap.
What I did
The theme is a structured output — palette, fonts, frames, motifs, layout — generated per chapter and then rendered by the app's own components. The model chooses within a system rather than drawing the page, and rendering is constrained to three formats, so novelty lives in the palette-and-motif layer while typography and layout stay under the app's control.
What it costs
A narrower design space than free generation, and a real risk that four voices times three formats still converges on a house style. It also multiplies what has to look right: every theme the model can emit must render acceptably in all three layouts, and there is no way to test that exhaustively — which is why the repository is full of kept output directories from generation runs.
Where it ended
Parked rather than abandoned. Five hundred and nineteen of its seven hundred and seventy-one commits land in a single month, then it tapers. It is deployed and reachable, and the last substantial push was an editorial redesign of how it reads rather than what it does.
A piece of the code
margin-web/lib/theme/types.ts
/** A ThemeKit — high-level vibe. Built-in kits live in design_space.ts * as const data; AI-generated kits live in the `theme_kits` table and * are loaded at theme-generation time. Same shape. */export type ThemeKit = { short_id: string label: string /** One short sentence the LLM uses to decide whether the kit fits * the chapter. Keep concrete: places, occasions, moods. */ use_when: string palette: PaletteFamily font_pair: FontPairId motif_set: MotifSetId frame: FrameKindId texture: TextureId density: DensityId /** "builtin" for ones we ship with the app; "ai" for ones invented * during generation; "user" for ones the user hand-authored. */ source: "builtin" | "ai" | "user"}Excerpt — trimmed for reading, not a full file.