Inspiration
The idea started with a message I never sent. I typed it three times, deleted it three times, then sat with the version still in my drafts for a year. At some point I realized the draft itself had become a kind of object. It wasn't a message anymore. It was a feeling I had given a shape to.
That's the whole museum.
The Unsent Museum is built on one question: what if the messages we couldn't send didn't have to just sit in our drafts? What if writing them somewhere, anywhere, was the act of placing them down instead of carrying them around? I wanted a place that took those messages seriously, treated them as art instead of mistakes, and let strangers sit with each other's unsent feelings without anyone having to be brave enough to send them.
What it does
You walk up to five doors: Love, Grief, Hope, Regret, Closure. Each one is for a different shape of unsent thing. You pick a room, watch a short video of the room breathing, then tap ENTER MEMORY and write the thing you couldn't send. The input is capped at 180 characters, because brevity is what unsent messages already are.
Your message becomes a generative artifact: a shader-rendered visual seeded by the text itself, so the same message always renders the same way. You can keep it, like it, share it, or download it as a poster. Then you wander the gallery and read what other people couldn't say either. The gallery has a 3D coverflow carousel and a grid view, and you can filter by room or browse all of them at once.
How I built it
The stack:
- React + TypeScript + Vite for the app shell
- WebGL + GLSL for the artifact rendering
- Radix UI + Tailwind for accessible primitives and styling
- Framer Motion for the door peel and room transitions
- Cloudinary for streaming the room ambience videos
- localStorage for likes and saved artifacts, so contribution is anonymous and the visitor owns their own gallery
Each room has its own family of shaders with cultural visual roots: ink and sumi-e for Grief, kintsugi for healing, adinkra symbols, enso circles for Closure, ascension rays for Hope. The text a visitor types gets hashed and normalized into a deterministic seed that feeds every random branch in the shader:
$$u_{\text{seed}} = \frac{\text{hash}(\text{text}) \bmod 10000}{100}$$
That gives a value in $[0, 100]$. Determinism is the part that makes the artifact feel like your message and not a slot machine.
The 3D coverflow gallery places each card at angle
$$\theta_i = \frac{2\pi i}{N} + \theta_{\text{cursor}}$$
with depth and opacity falling off as $|\cos(\theta_i)|$, so the focused card pops and the others recede.
The bigger architectural decision was a shared WebGL engine. Instead of giving every card its own GL context, there's one renderer that paints into an offscreen canvas and then 2D-blits the result into each card's <canvas> per frame. Twenty-five animated artifacts cost roughly one context's worth of GPU instead of twenty-five.
Challenges I ran into
- WebGL context limits. Browsers cap live WebGL contexts at around 16. The first version gave every card its own context, and the page silently died at card 17. The fix was the shared-engine architecture above. It took two rewrites.
- Shader determinism vs. user input. Early on, visitor-submitted artifacts rendered as blank squares. The seed wasn't being normalized at the GLSL boundary, so the hash overflowed the float precision the fragment shader had to work with. Pinning the seed to $[0, 100]$ before upload fixed it.
- Tone. The hardest part wasn't technical. Generative art tends to look cold. A grief room should not feel like a Vercel demo. I rewrote every room tagline, every empty-state line, every loading message until it sounded like something a person would actually leave behind. The Grief tagline, "I still set the table for two," took longer than the shader engine did.
- Mobile. Five autoplay videos, twenty-five live shaders, and a 3D carousel did not love a phone. I code-split the gallery route, lazy-decoded door images, gated animation on card visibility so off-screen cards stop running, and preloaded doors from the document
<head>before JS boots. The landing now fits one screen with no scroll on the worst device I could find.
Accomplishments that I'm proud of
- The shared-engine WebGL pipeline runs twenty-five animated artifacts on a single GPU context, smoothly, on a mid-tier laptop. I didn't know if that was possible when I started.
- Every room ships with its own culturally-rooted shader family, not a recoloured generic one. Adinkra, sumi-e, kintsugi, enso, and ascension rays each have dedicated fragment shaders.
- The product reads like writing, not like UI. A first-time visitor lands, reads three lines of room copy, and knows what to do. No tooltips, no onboarding modal, no "Get Started."
- It feels fast on mobile despite the WebGL and video payload, because the doors preload from
<head>before JS boots and the heavier routes are split out. - Most of all: the museum is quiet. There are no follower counts, no comment threads, no trending. Just rooms, doors, and other people's unsaid things.
What I learned
I went in thinking this was a frontend project. It turned out to be a writing project with a frontend attached. The shaders are the body, but the room taglines and the empty-state copy are the soul. If I had treated the words as filler, the whole thing would have read as a tech demo no matter how good the rendering was.
I also learned how much performance you can claw back by sharing one expensive resource across many cheap surfaces. The "one WebGL context, many canvases" pattern works for almost any animated grid, and I'll reach for it again.
And I learned, mostly, that I am not the only one with a drafts folder full of things I couldn't send. The first thing testers did was write one of their own.
What's next for Unsent Museum
- Audio rooms. A say it out loud mode where you record what you couldn't say and it becomes a waveform sculpture instead of a shader.
- Time release. Letters that don't appear in the public gallery for thirty, sixty, three hundred and sixty-five days. The act of sending it to the future, not to a person.
- Printable artifacts. Export your message as a poster-sized print so the museum lives off-screen too.
- Gentle moderation. Once the rooms are public they need careful hands. I am researching the lightest possible system that keeps them safe without flattening what people are brave enough to write.
- More rooms. Apology, gratitude, the things you would say to your younger self. There is no shortage of unsent shapes.
Built With
- claude
- codex
- figma
- figmamake
- figmaweave
- vercel
Log in or sign up for Devpost to join the conversation.