Building a Reusable AI-Powered 16-Bit Wall for Developer Events

Jul 12, 2026

Developer events have a participation problem. Too many sessions end with a polite "any questions?" answered by silence — I wanted the audience to do something instead of passively sitting through another talk. So I built a wall.

caritas ("little faces") is a reusable interactive experience I designed and built for developer meetups, workshops, and conferences. The mechanic: I project a QR code, attendees scan it, take a selfie, and a few seconds later they have a collectible 16-bit trading card with their own pixel-art portrait — downloadable as a PNG and, if they opt in, published to a shared live wall projected at the venue.

The app was built to travel: it runs on Next.js + TypeScript, Supabase for the shared wall, and OpenAI's image-generation API for the enhanced portraits, with each event getting its own re-branded fork and palette. Its biggest test so far was WeAreDevelopers World Congress in Berlin in July 2026, where roughly 290 AI-generated portraits were created for a total image-generation cost of about $35.

The project is MIT-licensed on GitHub, and this post is about how it actually works — the pipeline, the cost guardrails, and what broke.

The origin

Caritas started in April 2026 for a JS Chile community meetup: I wanted a souvenir people would actually keep. A PNG card you can post on social media beats a slide full of bullet points. From day one it was MIT-licensed so any meetup, hackathon, or community could fork it and adapt it — and that's exactly what happened: a Devin meetup in Cúcuta, Buenos Aires, Santiago, Montevideo, Lima, CaribeConf, Platzi Conf, Nerdearla. Each event gets its own palette and branding; Berlin was the biggest room it had ever faced.

Architecture

The app is a mobile-first Next.js + TypeScript app deployed on Vercel, with Supabase for storage, database, and realtime — plus an AI image-generation step that's entirely optional at runtime.

Three design decisions carried the whole thing:

1. The card always works without AI. The selfie is pixelated into a 16-bit-style card locally on a <canvas> — zero cost, zero latency, zero network. The AI step is an enhancement: it takes the selfie through an image-edit call with a style-constrained prompt. If no AI key is configured, the API returns 501 and the user keeps the local card. During a live event, "the feature degrades" beats "the feature is down".

2. A boring, reliable backend. Sharing a card is a POST to /api/wall, which uploads the PNG to a public Supabase Storage bucket (wall-images, organized by date) and inserts a row into wall_images with a hash for deduplication. Row Level Security is on: public SELECT, open INSERT — for a public event wall, that's the right trade. The wall page subscribes to the supabase_realtime publication, so every insert lands on the projected screen in about a second. One detail I like: the server talks to Supabase over plain REST (/rest/v1/, /storage/v1/), no SDK — one less dependency to reason about when you're debugging at a venue.

3. Cost is a product decision. At roughly $0.12 per generated image (290 images, ~$35 total), a crowd can still surprise you — so the API enforces a 60-second cooldown per IP and a hard gate that only opens when the event starts (NEXT_PUBLIC_EVENT_UNLOCK_AT, with a bypass cookie for development). The prompt itself is also a cost/quality lever:

const prompt = `
Transform the provided selfie into a premium 16-bit pixel portrait
for a vertical collectible trading card.
Preserve the person's likeness, face shape, hair, expression, pose...
Use chunky pixel shapes, crisp stair-stepped edges, simplified
facial features, and controlled dithering.
Use a constrained palette: navy, white, warm gray, event accent color.
...no text, no logos, no extra people.
`.trim();

Constraining the palette and forbidding text/logos did more for visual consistency than any post-processing I tried.

Generation fallback chain. The endpoint tries Vercel AI Gateway first (multi-model routing) and falls back to OpenAI's image edit endpoint (gpt-image-1 at 1024×1536) — whichever key exists. Response parsing is deliberately defensive: providers return images as b64_json, nested objects, or URLs, so a small recursive walker finds the image in whatever shape it arrives, and remote URLs get fetched back into data URLs.

Berlin: the biggest test so far

What worked: the QR mechanic is unbeatable for adoption — no app store, no signup, camera-to-card in seconds. Realtime on the wall held up fine at that scale; the dedup hash prevented double-submits when people tapped twice on venue-grade wifi.

What I underestimated: image generation takes seconds, not milliseconds. In a live audience, the wait is part of the experience — I ended up leaning into it ("the AI is drawing you") — but a queue position or a progress affordance would have been better. The per-IP cooldown is also crude: behind venue NAT, many attendees share one IP and can accidentally rate-limit each other. A per-device token would be fairer. And the fork-per-event model I use for rebranding (each event gets its own repo with its palette) is fast but drifts; if I keep running this at bigger events, it deserves a config-driven multi-event setup instead of twelve cousins.

For ~$35 at its biggest event and an audience that left with a souvenir they made themselves — worth it.

Erasmo Hernández