Box Populi

Live and deployed on Vercel — the collective's public face for sets, roster, and bookings.
Client project · Live
Box Populi
A site for a live techno improvisation collective out of New York City. The interesting part was never the landing page. It was the constraints. boxpopuli.live
What this is
Box Populi (“Box of the People”) is a rotating cast of NYC artists who play continuous, multi-hour improvised techno sets. The site is the collective's public face: a club-flavored landing page, a full roster with a profile page for every member, an in-page set player, and a booking form. It is roster-driven, so the crew can grow without anyone touching layout code.
How it's built
The site is a thin presentation layer over a typed data model. Everything the public sees is generated from typed files in data/: network constants, the artist roster and lookup helpers, shows, role labels, and shared types. Adding an artist or swapping the featured set is a one-line data change, and the per-artist pages generate themselves from the roster. Pages stay declarative.
- Structure. A multi-page Next.js App Router app, not a single landing page: a home page (hero, manifesto, Listen player, crew grid), a roster index, a dynamically generated profile page for each artist, a Resend-backed booking form, and an unlisted, non-indexed sandbox for work-in-progress pieces and client conversations. Around two dozen routes in all.
- Stack. Next.js 16 (App Router, React Server Components), TypeScript 5 end to end, React 19, Tailwind CSS v4 with Radix UI primitives and lucide-react. Geist plus a VCR display face for headings. Resend for the booking form, the official SoundCloud Widget API for audio, Vercel with CI/CD on push to main.
- SEO. The Next.js Metadata API drives titles, Open Graph, and Twitter cards, alongside robots and sitemap routes. The sandbox is excluded from indexing.
Notable engineering
- One-at-a-time audio coordination. Several players can be on screen at once: a persistent header player that keeps playing across navigation, the Listen playlist, and per-artist related tracks. A small module-level registry (no React context) lets every player join a shared bus, and whoever starts playing pauses the rest. Because it is a module singleton, the coordination survives client-side navigation. The header keeps playing as you move between pages, but starting a track elsewhere still pauses it.
- Custom players over the SoundCloud Widget. The players are fully custom and on-brand (dark glass cards, a bespoke transport and tracklist) but driven by the official SoundCloud Widget API behind a hidden iframe. A single module centralizes embed-URL building and one-time script loading, so SoundCloud stays the sanctioned host while the interface stays on theme.
- The iOS first-tap reality, handled honestly. iOS Safari only starts audio inside a synchronous user gesture. A custom button calling
play()across a cross-origin iframe via postMessage is asynchronous, so the gesture is lost and iOS needs a second tap. Rather than hide this, the site shows a one-time “Double Tap” hint on touch devices that flips to the track name once playback starts. An overlay workaround was prototyped and rejected after on-device testing. An unofficial-API approach was rejected on terms-of-service and reliability grounds. Both calls are documented in the commit history. - No device sniffing. All responsive and capability behavior is CSS: width breakpoints for layout, input-capability queries (
pointer-coarse,hover) for touch-specific UI. NoisMobile, no user-agent checks. A narrow desktop window never sees the touch hint.
Status
Live and deployed on Vercel. A git-backed CMS (Keystatic) is built and parked on a branch, ready to let the crew edit copy and roster content without code. Not yet merged.
Skill set, end to end
Brand-faithful visual design, typed data modeling, Next.js App Router and React Server Components, Tailwind CSS v4, third-party API integration (SoundCloud Widget, Resend), cross-origin audio coordination, capability-query responsive design, and SEO plumbing. Plus the judgment calls, documented in the commit history, about which workarounds to ship and which to reject.
The interesting part
It was never the landing page. It was the constraints. Multiple live players that cannot talk over each other. A sanctioned but clunky audio API kept behind an on-brand interface. An iOS limitation handled by telling the user the truth instead of hiding it. A roster-driven data model that lets the collective grow without touching a line of layout code.