Pricing comes from one function, estimateProject(), near the top of the script below. It sums rate_per_mm × length_mm for every piece across every room the lead added (one room for Kitchen / Wardrobe & Bedroom / Study / Living, several for Full Home Renovation), using the rate tables in PIECE_CATALOG. "Something Else" is the one exception — it has no defined room, so it sums a package range per selected area from AREA_PACKAGE_RANGE instead.
These rates are placeholders derived from the public vsbuild.com.my example (rows flagged REF below trace directly to a real line item there; the rest are interpolated to fill gaps). Replace every rate value in PIECE_CATALOG and every range in AREA_PACKAGE_RANGE with your real figures once the Excel rate list is available — the structure and the rest of the funnel don't need to change.
Multi-room navigation (Full Home Renovation) is driven by a flat, dynamically-built list of steps in state.virtualOrder — built once the lead picks their rooms, then walked forward and back by state.vPos. If you change which project types get itemised vs. the lighter area-based path, look at buildOrderForType() and extendOrderForRooms(). The "quickcontact" step (name + WhatsApp) is always inserted right after "type", so it's always virtualOrder[1] — that's what the "not you?" link on the results screen jumps back to.
The floor plan / reference design file inputs and the "Choose file" buttons are UI-only in this static prototype — they confirm a filename client-side (rFloorPlanName / rRefDesignName) but nothing uploads anywhere. Wire a real upload endpoint before launch, or swap them for a note asking leads to attach files over WhatsApp instead. The sticky "running estimate" bar shown during the pieces/measurements/areas steps, and the itemized docket with its per-line "Remove" button on the results screen, both just re-read estimateProject() against whatever is currently in state.answers — no separate cart state to keep in sync.
Perfex CRM handoff: set CRM_ENDPOINT_URL near the top of the script to the URL of your own receiver script (see the companion perfex-lead-intake.php file) — leave it blank to disable. sendLeadToCRM(tier) fires a fire-and-forget POST of buildLeadPayload() (full itemized brief: contact info, every room/piece/length/finish, budget, timeline, estimate range) at four points: the "quickcontact" step submit and the exit-intent modal submit (both "warm"), the final "Request Formal Quotation" submit ("hot"), and "Request a Site Visit" ("hot" again, with siteVisitRequested: true). This page never talks to Perfex directly or holds an API token — that stays in your server-side receiver script. Because the same WhatsApp number can submit more than once as a lead moves from warm to hot, have your receiver upsert on whatsapp (or email) rather than always creating a new lead.
| Piece | Standard spec | Variant | RM / mm |
| Base Cabinet | D600 · H850 | Standard laminate top | 1.378 |
| Base Cabinet | D600 · H850 | Quartz stone top REF | 1.912 |
| Wall Cabinet | D350 · H1200 | Standard | 0.722 |
| Wall Cabinet | D350 · H1200 | With LED lighting REF | 0.886 |
| Full-Height Wardrobe | D600 · H3000 | Standard swing door | 1.476 |
| Full-Height Wardrobe | D600 · H3000 | With end panel & drawer set REF | 1.877 |
| Base/Shoe Cabinet | D400 · H850 | Standard REF | 0.706 |
| Base/Shoe Cabinet | D400 · H850 | With curve corner REF | 1.290 |
Full rate list, all rooms/pieces/variants, is in PIECE_CATALOG in the script.