</yunmakai>
An indie label's store inside a walkable 3D motel room.

<5 MB
enforced GLB budget
120 s
signed audio URL TTL
2-vendor
Shopify + Supabase split
SPECIFICATIONS
| ROLE | FRONTEND LEAD |
|---|---|
| YEAR | 2026 |
| TYPE | WEB |
| STATUS | IN DEVELOPMENT |
| STACK | next.js 16 · react 19 · typescript · react three fiber · three.js · tailwind v4 · supabase · shopify storefront api +3 more |
| LINKS | — |
| AVAILABILITY | in development |
“Indie music projects sell merchandise through generic Shopify themes that look identical to every other artist storefront.”
Immersive digital universe for an indie music project where releases and physical/digital products are experienced inside an interactive 3D environment.Walk through a Southern Gothic Noir motel room, browse the TV-as-product-grid, hover product posters, play tracks through a global audio player, and check out via Shopify · all without leaving the scene.
== WHAT IS THIS ==
Immersive digital universe for an indie music project where releases and physical/digital products are experienced inside an interactive 3D environment. Walk through a Southern Gothic Noir motel room, browse the TV-as-product-grid, hover product posters, play tracks through a global audio player, and check out via Shopify · all without leaving the scene.
== </the problem> ==
Indie music projects sell merchandise through generic Shopify themes that look identical to every other artist storefront. There is no spatial identity, no narrative, no reason to linger. Listeners arrive, buy, leave · and never return between releases.
role & context
Frontend lead for an indie music client. I own the 3D scene, commerce integration, and design system; hosting and content are the client’s.
== </my approach> ==
Built a single interactive 3D environment where the entire catalog lives. The room IS the storefront. Music plays continuously through a global audio player. Products are objects you interact with, not SKUs in a grid. Commerce splits cleanly: Shopify owns checkout and inventory, Supabase owns identity and content.
== </the story> ==
Yunmakai is an immersive digital universe for an indie music project · a single interactive 3D environment where music releases and physical/digital products live together as objects in a scene. Open the site, drop into a Southern Gothic Noir motel room (Ozark / Twin Peaks / motel photography as the visual reference), and the room is the store. The TV is the product grid. The posters are listings. The lamp lights drive the mood. Music streams through a persistent global audio player that survives navigation.
The brief from the client was unusual: replace a traditional storefront with a place. Buying a record should feel like picking it up off a shelf, not adding a SKU to a cart. So every interaction is anchored to a 3D object · hover a poster outline, the cursor sharpens; tap the TV, the camera auto-zooms; tap a product, the scene transitions into a detail page without breaking spatial continuity. Round-trip back to the room and you land exactly where you left.
The cart is a floating panel that overlays the scene. Checkout hands off to Shopify hosted checkout for PCI scope reasons. Auth, music library, and admin live behind Supabase with RLS and role-gated routes for the artist to upload tracks, manage product images, and seed releases.
== </architecture> ==
Next.js 16 with the App Router and React 19, TypeScript strict mode, Tailwind 4 with custom design tokens in globals.css. The 3D layer is React Three Fiber on Three.js 0.172 with drei utilities and a postprocessing stack (Bloom, Vignette, ChromaticAberration, custom SplitToningEffect). GLB models load via useGLTF with a Draco decoder. Hotspot mesh names in the motel room are load-bearing · Room, TV, TV_Screen, Poster, Lamp · and drive every interaction.
State runs on Zustand stores: auth, cart, music, env. The music store wraps a singleton HTMLAudioElement and survives route transitions for uninterrupted playback. The cart store persists to localStorage by cart_id and uses a mutex queue for cart mutations to prevent race conditions during rapid add/remove.
Backend is Supabase: PostgreSQL with RLS as the primary access layer, Auth with cookie-based sessions, and Storage for audio files. Middleware refreshes sessions and gates /admin behind role checks. An API route at /api/tracks/[id]/stream-url generates signed Storage URLs with a 120s TTL, rate-limited to 30 req/min per IP, and the music store auto-refreshes the URL 30s before expiry.
Commerce is Shopify Storefront API (GraphQL, 2025-01) via a typed client. Cart mutations go through GraphQL with optimistic UI; checkout redirects to Shopify hosted checkout. Stripe is wired but dormant. Hosting is handled by the client; the public portfolio embeds only a sandboxed scene viewer.
== </key features> ==
3D motel room as storefront
Hotspot-driven navigation. TV is the product grid, posters are listings, lamps drive mood lighting.
Persistent global audio player
Music survives route transitions via a singleton audio element. Signed Storage URLs with auto-refresh.
Spatial continuity
Round-trip navigation preserves exact camera position. TV auto-zoom replaces hard cuts.
Shopify + Supabase split
Shopify owns money and inventory via hosted checkout. Supabase owns auth, music library, and admin uploads.
Privacy-respecting auth
Cookie-based Supabase sessions with middleware refresh. Admin routes gated by role checks at the edge.
Production performance
GLB budget enforced. Sequenced loader doubles as boot sequence. Draco decoded, postprocessing stack tuned per scene.
== </key decisions> ==
DECISION 01
The hardest decision was committing to the scene as the storefront rather than bolting 3D onto a conventional grid. It would have been faster to ship a normal Shopify-themed Next.js store and embed a 3D hero. But the client wanted a place, not a marketing element · so every navigation step had to preserve spatial continuity. Round-trip back to the room lands you at the exact camera position you left. The TV auto-zooms instead of cutting. Hover outlines are precise polygon-level, not bounding-box.
DECISION 02
GLB budget is enforced (under 5MB, 50-100k triangles, PBR metallic-roughness) so the room loads fast enough to feel native. The landing loader is a sequenced reveal that doubles as the boot sequence · by the time the loader finishes, the room is fully resident and ready to interact.
DECISION 03
Splitting commerce between Shopify (PCI scope, hosted checkout, inventory) and Supabase (auth, music, admin) was a deliberate two-vendor call. Shopify owns money + product catalog; Supabase owns identity + content. Each one is best in class for its slice, and the boundary is clean: products flow Shopify → frontend, cart state lives in Zustand, tracks flow Supabase → frontend.
DECISION 04
The Southern Gothic Noir aesthetic is preserved everywhere · IBM Plex Sans + JetBrains Mono, void/abyss/soot/charcoal neutrals with amber lamp light and teal neon accents. Custom easing curves and glow shadows are tokenized so the artist (and future designers) can extend without drift.
== </what i learned> ==
3D as storefront is a UX commitment, not a visual flourish. Every transition has to preserve spatial continuity or the illusion breaks.
Splitting commerce between Shopify and Supabase is the right boundary for indie clients · best in class on both sides with a clean integration seam.
Mesh names in GLB files are load-bearing infrastructure. Treat them as a versioned contract between the 3D artist and the engineer.
next.js 16 · react 19 · typescript · react three fiber · three.js · tailwind v4 · supabase · shopify storefront api · zustand · postprocessing · vercel
interactive demo · loads on demand
IN DEVELOPMENT