

Sappa
Lets online shoppers see furniture in 3D and in their own room before buying
The problem
People buy furniture with their eyes, and a static product photo cannot answer the question they actually have: what does this look like in my size, my fabric, my room. Retailers know this. What stops them is that every existing answer demands a 3D team, a long integration, and a rebuild of the product page.
What I built
Sappa is an embeddable visualisation widget for home-goods retailers, plus the self-serve tooling that gets a catalog into it. A shopper picks size, colour, material and options, and sees the product update live. The retailer sends a catalog and goes live on their existing store, typically within a day, with no developers on their side.

One embed, three modes
The whole shopper-facing surface is a single Vite app deployed once at /configurator/, which boots into one of three modes chosen at runtime from partner config and URL params:
- 3D configurator for three-dimensional goods like modular sofas. Real-time procedural Three.js.
- 2D visualiser for a full-page photo catalog with room rendering.
- 2D product widget, an inline strip that swaps colours and materials on real product photography, with no 3D model required at all.
All three share the catalog layer, internationalisation, analytics and the embed SDK. Only the 3D path pays for the engine assets. That decision is why a rug retailer and a sofa retailer can run the same product without either carrying the other's weight.
The 3D engine
Geometry is procedural, generated in code from a resolved BuildSpec, not authored per SKU. That is what makes dimension ranges possible: a shopper drags width from 180cm to 240cm and the mesh rebuilds, rather than snapping between pre-modelled variants.
The layering is strictly one-way:
host page -> embed/sdk.ts -> iframe -> configurator app
ui/ (React)
constraints/ + pricing/ (pure functions)
engine/ (framework-agnostic Three.js)
engine/ knows nothing about React and consumes metres and hex colours. constraints/ is a pure resolve(config, model) returning a clamped config, disabled options and validation notes. pricing/ is additive option deltas. The host page owns cart and checkout; the iframe owns geometry, constraints and price, and emits the selection back over a small postMessage contract.
Modular furniture has geometry rules that are easy to get subtly wrong. Each module owns its own parts, so legs sit at each footprint rectangle's corners rather than on the overall bounding box, otherwise an L-shaped sofa grows a leg floating in the notch. The back stack orders rear to front (back frame, back cushion, seat cushion) with the seat starting in front of the back so they never intersect. A chaise removes the arm on its side, and cushions size to usable width so they never pierce an armrest.
The AI render pipeline
Studio 3D answers "what does it look like". It does not answer "what does it look like in a room". A separate variation pipeline takes the configured product and produces photorealistic room renders, so the site can show the same sofa as clean geometry and as a furnished living room side by side.

Getting catalogs in
The part that decides whether this is a product or a consultancy is onboarding. A partner Studio takes a retailer's catalog in whatever format they already have, and a Cloud Run chat orchestrator drives the pipeline that turns products, variants, materials and product rules into a configured widget. Constraints matter commercially, not just visually: a shopper can only pick combinations the retailer actually sells, so every order that comes through is one they can fulfil.
Distribution is through embed plugins for Shopify, WooCommerce, Wix and Magento, so the install is a script tag rather than a project. Everything is bilingual, English and Hebrew, with full right-to-left support.
Building something in this territory? Start a conversation →
