Develop · Build
How it came together.
- 01
The map is the starting point
Vantage opens on a map. The first question it answers is what has been scanned near you, which is the question a buyer actually arrives with. Search runs in plain language.
Product Strategy - 02
Reusing the shipped viewer, not forking one
The scan viewer moved to PlayCanvas Streamed-LOD (SOG-native) for lighter, faster streaming, embedded per residence so a map point drops you straight into the full walk.
3D & Spatial A scan ships only if it passes. Coverage, collision and a spawn point computed from the room's own geometry, checked against a written standard before a client ever opens it. - 03
An internal standard before a scan ships
Every scan is checked against a written internal standard, coverage, collision, a spawn point computed from the room's geometry, before it earns a Verified by Vantage badge. A point-cloud minimap replaced floor plans, which misread furniture as walls.
3D & Spatial - 04
Co-browse over a self-hosted relay
A broker drives the tour; the client's browser follows over a self-hosted WebSocket relay, not a third-party vendor. Verified live: join, presence, camera state, and reactions round-trip correctly.
Backend Engineering - 05
Design matters because trust is visual
Gold-on-black, Cormorant headings, the BARNES system throughout, with a light/dark/auto toggle on client-facing pages (the 3D viewer stays dark). A brokerage surface in front of a buyer has to read as finished.
Develop · Forks
Decisions on the record.
The few calls worth defending. Each one is a fork; the other branch would have been a different project.
Decision · 01
Collapsing three broker apps into one map
One map, modes rather than separate apps, means the listing a broker shows and the residence they walk are the same object, one click apart.
Decision · 02
A self-hosted relay for co-browse
A broker-led tour needs low-latency camera sync between broker and client, but that traffic doesn't need to leave infrastructure already hosting the scans. A self-hosted WebSocket relay, co-located with the splat viewer, avoids a recurring realtime-vendor bill and keeps the stack in one place to operate.
Develop · System
The design system.
Tokens and treatments as they exist in the repo - values verbatim, nothing aspirational.
Gold on black, built for a surface where the content is someone's home and the interface has to stay out of its way. The viewer is dark-only on purpose, and the accent does its work as a hairline rather than a fill: the signature move here is an inset ring, not a coloured button.
Colour
--bg-base / -elevated / -card / -surface / -hover#0A0A0A / #111111 / #161616 / #1A1A1A / #222222- Five near-black steps. A scan fills the frame, so the chrome around it needs fine-grained separation without ever introducing a lighter plane that would compete with the render.
--gold and --gold-rgb#C8A96E, plus the raw triple 200, 169, 110- The hex for solid use, the triple so opacity can be composed inline. Every ring, hover border and divider in the app is that one colour at a different alpha.
--champagne#E8DFD0- A lighter warm neutral for the rare element that needs to read as brand without being gold.
--text-primary / -secondary / -dim#F5F5F5 / #A0A0A0 / #808080- Neutral greys, notably NOT the warm off-whites Data Centre uses with the same gold. Over photographic content a warm text ramp picks up whatever colour the scan happens to be.
--border / -light / -subtle#2A2A2A / #333333 / rgba(255,255,255,0.06)- Two solid steps for structure plus a translucent one for edges that sit over the 3D canvas.
Type
- Interface
AaBb Handgloves 0123
Inter Variable, then InterNot the Hanken Grotesk the Dev Portal and Data Centre share. A viewer that ships inside other people's pages is better off in a face that disappears than one carrying house identity.
- Editorial moments
AaBb Handgloves 0123
Cormorant GaramondHeld back for the embed page, the share page and the loading state, the three places a client meets the product without a broker present. A section-header comment in the CSS makes the rule explicit: sans keeps card and section titles in one system, serif only where it earns the moment.
Implementation
CSS custom properties in src/app/globals.css under Tailwind, dark-only with no theme queries at all in this repo. Gold is stored twice on purpose, once as a hex and once as a bare RGB triple, so rgba(var(--gold-rgb), 0.2) can compose an alpha at the call site without a second token per opacity step.
Elements
- The inset ring
.lux-ring is a 1px inset box-shadow of gold at 20% opacity, with .lux-ring-strong at 42% for the selected state.
An inset shadow rather than a border keeps the ring inside the element's box, so adding or removing a selection never shifts layout by a pixel in a grid of scan cards.
- Luxury card
A 145-degree gradient between the elevated and surface planes, a solid hairline, 12px radius, and a 300ms ease into a 30%-gold border on hover.
The gradient is barely perceptible and doing real work: it separates a card from the plane behind it without a shadow, which over a dark 3D canvas would read as haze.
Process · 01
Gold as a hairline, never a fill
The obvious way to use a brand colour is on buttons. In a viewer, that puts the strongest colour in the interface next to a photoreal render of a room, and the room loses. Confining gold to inset rings, hover borders and dividers keeps the accent present on every surface while leaving the largest, most saturated thing on screen to be the property. It is also why the selected state changes ring opacity rather than filling anything.
Process · 02
Neutral greys under the same gold
Data Centre pairs this exact gold with warm off-whites, and that is right for a dense data surface where every pixel is the interface. It is wrong here. The viewer's text sits over photographs, and a warm ramp shifts perceptibly depending on whether the scan behind it is a marble lobby or a green-lit showroom. Neutral greys hold still. Same brand colour, opposite decision, because the thing underneath the text is different.
