Building an AI-Powered 16-Bit Wall at WeAreDevelopers Berlin

Nov 3, 2026

In early July 2026 I was at WeAreDevelopers World Congress in Berlin. Instead of ending my session with a polite "any questions?", I wanted the ~1,000 people in the room to do something. So I built a wall.

The mechanic: I projected a QR code, attendees scanned it, took a selfie, and a few seconds later they had a collectible 16-bit trading card with their own pixel-art portrait — downloadable as a PNG and, if they opted in, published to a live wall projected on stage. By the end of the event, roughly 290 AI-generated portraits had been created for a total image-generation cost of about $35.

The project is called caritas ("little faces"), it's 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. It worked well enough that I kept re-skinning it for other events — 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.

What actually happened in Berlin

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 hotel-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 and an audience that left with a souvenir they made themselves — worth it.

Erasmo Hernández