TypeScript records
Projects, selected collections, service definitions, and image metadata.
work-data.ts · content.tsWhimsy · Website design & engineering
A home for a creative business, from the first impression to the first conversation.
I designed and built Whimsy’s website to bring consulting, media, marketing, and web development into one experience. The work has room to show its character, services explain what comes next, and a durable inquiry system supports the conversation.
Next.js / React / TypeScript / Cloudflare


Product thinking
Someone looking for a website, an ad campaign, or business advice arrives with a different problem. The site connects examples, service details, and project context so each visitor can find a useful next step.
A curated gallery brings 20 featured projects into five categories. Visitors can narrow the collection before opening a full case study.
Category filters → Project storiesService guides connect the examples to scope, pricing, required inputs, and deliverables. The creative work has a practical business context.
Examples → Scope & expectationsThe contact page asks about goals, audience, timeline, budget, and existing materials. A “Not sure yet” option leaves room to work out the right service together.
Project context → An informed inquiryVisual system
Whimsy’s campaigns range from seasonal events to food photography and business branding. I built shared navigation and page framing around layouts that adapt to each subject. Click an image below to see more of the project and how it was built.

Campaign artwork · Partner spotlights · Event coverage
The campaign ties together the public invitation, individual partner spotlights, and photography from the event itself. Cranberry, mint, and seasonal illustrations connect the pieces.
The page moves from the public invitation to individual partner spotlights, then closes with photographs from the event. That order shows how one visual campaign carried through promotion and into the real experience.
Selected artwork and photography from Whimsy’s project collection.
Application architecture
The content-heavy site exports HTML and assets for Cloudflare Pages. React handles the parts visitors interact with, while a separate Cloudflare Worker owns inquiry processing and email delivery.
Projects, selected collections, service definitions, and image metadata.
work-data.ts · content.tsShared framing with dedicated layouts for individual stories.
generateStaticParams()Prebuilt pages served from Cloudflare Pages, without a runtime Next.js server.
output: "export"A curated selection maps project slugs into gallery categories. Dedicated React components give selected case studies their own composition while reusing captions, navigation, and calls to action.
Gallery filters use local React state. The contact form manages sending, saved, delayed, and error feedback. These behaviors sit alongside pages composed at build time.
Grid layouts and breakpoints reflow imagery and text. Native radio filters, labeled inputs, live status regions, and project-specific metadata support navigation and discovery.
Full-stack implementation
A contact form is part of the business’s front door. I separated saving the inquiry from delivering the notification, so an email delay does not have to become a lost conversation.

The checklist asks for goals and existing materials. Service guidance and pricing help visitors frame the conversation before they send.
The form distinguishes a saved inquiry from delayed delivery or a failed request. A reference number gives the submission an identity.
The browser reuses its submission ID on a retry. The backend stores delivery state and has separate paths for retrying and retaining failures.
Check origin, required fields, lengths, email format, and the honeypot.
Write to D1 with a client ID and reference before confirming it is saved.
Saved response → BrowserSend an inquiry ID to the queue. The consumer loads the stored record.
Store the message ID, then record delivery, deferral, or failure events.
Recovery path: delayed retries for transient send errors, a scheduled job for stranded inquiries, and a dead-letter queue for terminal send failures.
Built for interruption
Retry delays
The browser creates a UUID and retains it when a request fails. The Worker looks up that client ID before inserting another inquiry. A D1 write precedes a successful saved response; a queueing failure can therefore return “saved, delivery delayed” without asking the visitor to start again.
The consumer retries temporary send errors after 1, 5, 15, and 60 minutes. A five-minute scheduled job republishes pending or queue-failed records and queued or sending records that have stalled for more than 15 minutes. Provider acceptance and actual delivery are recorded separately, making delayed or failed delivery visible in the stored inquiry state.
Request boundaries
The Worker accepts JSON POST requests from allowed origins, validates required fields and lengths, and uses a hidden honeypot field to discard basic automated submissions. Prepared D1 statements bind submitted values, and HTML email content is escaped before delivery.
The email binding restricts the sending and destination addresses, keeping inquiry notifications within the business’s configured delivery path.
Build-time decisions
Next.js generates the project and service routes at build time. Project metadata comes from the same records used for presentation, while specialized components own each story’s layout. Content updates require a new build and deployment.
The export uses Next.js images with unoptimized: true, so there is no runtime image-resizing service. Source dimensions, chosen assets, CSS composition, and loading priority matter. The gallery’s filters run locally in the browser; the contact Worker remains a separately deployed service.
From first impression to first inquiry