This portfolio
A portfolio that's also a small product: a visual editor I share with Claude Code, with saves built so neither of us overwrites the other.

01 – The idea
Makes the invisible legible.
That's the homepage headline—and the through-line of my work. Design is where the instinct started, and this site makes it literal. The little ticks, exposed grids, ruler-like link underlines, and elevation markers on images are visual jokes about the structure designers work with every day: columns, measurements, alignment, constraints. The machinery that makes something feel intuitive, even when nobody notices it.
Product work runs on the same machinery. I want to zoom from the big-picture architecture down to the smallest interaction without losing the coherence of the whole.
Structure, but somewhere you'd want to spend time
The colors soften the precision. Sky blue, deep green, and warm paper are meant to feel like sky, trees, and the materials of making things.
- Role
- Concept, art direction, product requirements
- Team
- Me + Claude Code
- Editor
- Puck, running only on my machine
- Built with
- Next.js, published on Vercel
The apostrophe hops over MO'B when you hover, and the footer insists apostrophes are valid in last names. Plenty of forms still disagree.
02 – The editor
I've worked in WYSIWYG editors for decades, and I wanted to compose pages visually: columns, grids, images, and text where they go. Writing pages in MDX turned every layout change into a coding task, and I didn't want to run a hosted CMS to avoid it. So I built a small editor, treating it as a product with one demanding user.
What I ruled out
TinaCMS: form-based editing in a sidebar, not on the page.
Squarespace: it leaves the codebase behind.
WordPress with Beaver Builder: the strongest alternative (I built Verblio's marketing site on it), but a second system to run.
A custom editor from scratch, Craft.js, and hosted builders (Builder.io, Plasmic, Makeswift).

What the editor does
- Compose
- columns, grids, images and text from one shared set of blocks, on preset layouts.
- Edit in place
- type on the page itself; header fields write back to each page's frontmatter.
- Save to Git
- pages are files in the repository, so every change is a reviewable diff.
- Stay local
- the editor exists only on my machine. Every deployment returns 404 for it.
03 – Three decisions and what they taught me
Decision 01
A CMS without CMS infrastructure
Pages are files in Git, edited with Puck on my machine and prerendered at build time.
The full story
The call
No database, no hosted editor, and no monthly cost. The live site stays fully static, and every change is a reviewable diff.
What made it work
Layouts are presets pinned in a lock file and checked before every build, so authors pick layouts rather than invent them, and a code change can't silently re-lay-out a saved page. Saves write stable, sorted JSON, so a diff shows only what changed.
Decision 02
Fixed composition where it matters
Most pages are composed freely from shared blocks. The homepage isn't.
The full story
What prompted it
An earlier, fully editable homepage felt too sleepy.
The call
The homepage's character comes from how the parts overlap: the name crossing the horizon, the portrait pulled up to cross it too, the ordered entrance, the numbers counting up. Making those draggable would mean guardrails on every knob, and one stray drag could break the composition. So its layout lives in code, and the editor exposes only its copy.
Decision 03
Safe for two editors
I edit pages visually while Claude Code edits the same files in code, so every save has to be trustworthy.
The full story
The guarantees
Saves refuse to overwrite files changed since the page opened. Unsaved edits are backed up in the browser. Edits made during a save stay unsaved. A failed save puts its files back.
What happened
Early on, the editor seemed to wipe work in progress every few minutes. The likeliest cause was Chrome discarding a background tab, but the real gap was that unsaved edits lived only in the tab's memory. They now back up to the browser, and reopening offers Restore or Discard.
Rejected
Autosave to disk. It would write files constantly, make noisy diffs, and race Claude Code's edits.
Deliberately not built
An editor on the live site.
Routes for content that doesn't exist yet.
Results so far
14
pages, every one composed in the editor
4
case studies migrated from MDX in a single day
15
block types, shared by every page
04 – How
I developed the visual concept, art-directed its execution, and defined the product and interaction requirements; Claude Code implemented and iterated on it. The decision logs record the alternatives I rejected and the times I overruled Claude's proposals.
- Concept
- the visual language, from "makes the invisible legible" down to the apostrophe.
- Art direction
- how every page and block should look and behave.
- Requirements
- the product and interaction requirements for the site and its editor.
- Review
- evaluated approaches, challenged proposals, and reviewed the results.
Tools, constraints & technical notes
Where to look
The editor's guard: it exists only under
npm run dev, and its write routes refuse cross-site requests.What a save guarantees, including its one known gap: the two file writes aren't a single atomic step.
Content checks that run before every build. If they fail, the deploy fails.
Pinned layouts, so a change can't silently re-lay-out saved pages.
Caught along the way
Comments were content. Converting the case studies from MDX, a word-for-word diff of old and new text caught a dozen design-artifact notes that a naive conversion would have published. They became "Note to self" blocks, visible in the editor and never rendered.
Tailwind drops CSS it can't see. Class names built from template strings were silently removed, so they're written out in full.
Stack: Next.js 16, TypeScript, Tailwind CSS, and Puck, prerendered and hosted on Vercel.
Design · AI