Concept
Flakestone coats garage and workshop floors across metro Perth. The page has one job: a quote enquiry with the area, finish and prep already priced. The audience is a new-build owner whose garage is the last unfinished room, a car enthusiast who wants a floor worth the car, and a workshop operator sick of dusting concrete — all of them price-shopping on a phone. So the estimator isn't a widget beside the pitch; it is the pitch, drawn as the thing being bought: a top-down slab with a squeegee laying the finish in real time.
Palette — measured, not hoped
Argued from the subject: cured concrete, graphite screed, and the wet pigment chip. The page stays matte; the only "wet look" lives inside the drawn floor. Contrast ratios below are measured by WCAG arithmetic from the shipped tokens (the spec's figures were hypotheses; two shifted slightly under measurement — ≈12.6 → 12.55, ≈5.6 → 5.40).
Supporting measurements: footer text #C9C6BE on graphite 8.83:1; badge text #FCFBF8 on the graphite price badge 14.57:1.
Type
Space Grotesk 700 for display — it has the set-out-drawing precision of a coatings spec sheet, and it carries the page's one typographic bravery moment: the square-metre figure set huge on the drawn slab ("36 m²"), swapping live as the garage chips resize the floor. Source Sans 3 does the body work — legible at 16px+ on a phone, which is where trade traffic lives.
The Garage Plan — signature & implementation
An SVG top-down slab with the roller-door opening drawn at the front edge. Garage chips (single 18 m² / double 36 m² / triple 54 m² / workshop 72 m²) resize the slab with set-out dimensions; finish chips restyle the fill procedurally — solid lays flat colour, full flake broadcasts a seeded-random speckle (~7 flakes per m², three tones per colour), metallic drags bezier swirl bands with pearl highlights. A squeegee bar sweeps the slab on a loop, revealing the gloss layer through a clip-path that follows it — the concrete ahead of the wet edge never drops below full visibility, so the floor is alive at any capture instant. The computed price is badged on the slab, inside the thumbnail frame.
Add-ons draw themselves: the oil-stain toggle marks a treated patch on the concrete that the coat visibly buries as the squeegee passes; the steps stepper (0–4) draws coated step edges off the rear wall; the removal toggle lays an old flaking coat ahead of the squeegee with grind swirls in the strip just before the wet edge — the old floor being ground off in front, the new one laid behind. Second-read detail: a chalked "moisture 3.8% — ok to coat" reading on the bare concrete, buried by the coat like the real note would be.
Engineering: one persistent rAF loop drives the sweep with the delta clamped at zero and capped (dt = min(max(now−last,0)/1000, .05)); every control handler bumps a token before the state swap so in-flight flake-batch timeouts invalidate cleanly under rapid-fire input; redraws are dual-speed (staggered entrance on first draw, ~560 ms snappy after); the loop pauses on document.hidden and reduced motion renders a settled mid-sweep frame with no loop at all. State is exposed on window.__epoxyflooring for QA.
Pricing model — honest by construction
The coating line is rate × m². The $1,400 minimum applies to the coating line before add-ons: only solid-colour on a single garage (18 × $75 = $1,350) can fall under it, and when it does the ledger shows the billed $1,400 with the note "small floors carry the setup — the minimum covers it". Every other rate × area cell clears the minimum. The removal note is a template, not a literal — "grinding off the old coat adds $<18 × m²>" — so it scales with the selected slab. Colour is price-inert by design and says so on-card ("every colour cures at the same price"). Warehouses and food-grade systems print no number at all: substrate moisture and downtime drive them, so the declaration is the honest deliverable. The services grid floor ("garage floors from $1,400") equals the plan's cheapest billed output.
| Scenario | Coating line | Billed |
|---|---|---|
| Single 18 m² · solid $75 | $1,350 | $1,400 (minimum, note shown) |
| Single 18 m² · full flake $95 | $1,710 | $1,710 (clean) |
| Single 18 m² · metallic $130 | $2,340 | $2,340 |
| Double 36 m² · solid $75 | $2,700 | $2,700 (clean) |
| Double 36 m² · full flake (rest state) | $3,420 | $3,420 |
| Double 36 m² · flake + removal | $3,420 + $648 | $4,068 |
| Workshop 72 m² · flake + oil + 2 steps | $6,840 + $180 + $120 | $7,140 |
All figures above were driven in headless Chrome and matched against an independently reimplemented oracle — including the boundary cells either side of the minimum (solid-18 binds, flake-18 and solid-36 run clean) and minimum-before-add-ons (solid-18 + oil bills $1,400 + $180 = $1,580).
Conversion decisions
- Plan in the hero, price on the slab. The 1200×750 thumbnail is the commercial argument, so the signature and its $3,420 badge both sit inside the first 750px — the capture sells even before the copy is read.
- Click-to-call twice above the fold (header tel + ghost CTA), and a sticky call/quote bar on mobile — most trade traffic is a phone in one hand.
- Plan → form handoff: "Send this plan to the quote form" carries the configured scenario into the enquiry, and the garage-size select stays synced with the chips — the demo interaction becomes a warmer lead.
- Form fields match the trade: name, phone, suburb, garage size, "what's on the floor now?" (bare concrete / old paint / old epoxy / not sure — the answer that changes the quote), and timing. Confirmation is a designed local panel, hidden until a valid submit; no fake network round-trip, and it says plainly that nothing was sent.
- FAQ answers are the honest phone answers — park in three days (not "tomorrow"), crack lines can ghost, warehouses get walked before they get priced. Honesty is the trust signal.
Iteration log
Pass 1 — Craft
Measured every palette hypothesis: graphite on ground 12.55:1 (spec guessed ≈12.6), epoxy blue 5.40:1 (≈5.6), flake grey 4.41:1 — demoted to lines/marks only as specced, with body copy on the darker graphite-soft ink. Screenshot review caught the squeegee vanishing during the end-of-sweep hold phase — a thumbnail captured in that window would miss the wet edge — so the squeegee now parks at the far edge during the hold and the hold shortened 1.6 s → 1.0 s. Refactored the sweep from rebuilding the squeegee/grind-band DOM every frame to building once per redraw and translating per frame. Fixed the settled-flag timer reading the firstDraw flag after it had been cleared.
Pass 2 — Depth
Added the second-read detail: the installer's chalked "moisture 3.8% — ok to coat" on the bare concrete, which the coat buries as the squeegee passes — it ties the trust strip's moisture-test claim into the drawing itself. Added a price-pulse micro-interaction on the ledger total (suppressed under reduced motion). Verified the metallic swirl and solid/dune renders by screenshot at driven states ($4,680 metallic double, $3,000 solid double with oil + 2 steps — both oracle-exact), and confirmed the oil stain correctly disappears behind the wet edge once treated and coated.
Pass 3 — Hardening
Tap targets raised to ≥44px (chips, steppers, swatches). Chanel rule: cut the hero's third bullet — it repeated the $95/m² anchor the finish chips and services grid already carry. Re-ran the full 60-assert harness: 375px clean (no horizontal overflow, masthead wordmark clears the call link, sticky bar in the bottom 140px, h1 + CTA + trust signal above the fold), empty submit keeps the confirmation panel hidden while raising validation, filled submit (clicking button[type=submit] specifically) shows it, two rapid-fire volleys with no settle between land oracle-exact, reduced motion renders the static mid-sweep frame, and zero console errors across index and guide.
Local SEO we'd implement for the real business
This demo page ships only honest WebPage/BreadcrumbList structured data naming itself a demonstration — fictional business schema in a real index is dishonest markup. For a real Flakestone we'd ship HomeAndConstructionBusiness (schema.org has no floor-coating subtype; it's the correct organization family for a coatings contractor), with real reviews fed from their Google profile:
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "HomeAndConstructionBusiness",
"name": "Flakestone Epoxy Floors",
"description": "Garage and workshop epoxy floor coatings across metro Perth.",
"telephone": "+61-8-9000-0325",
"address": {
"@type": "PostalAddress",
"addressLocality": "Malaga",
"addressRegion": "WA",
"postalCode": "6090",
"addressCountry": "AU"
},
"areaServed": "Perth metropolitan area",
"priceRange": "$1,400–$9,500",
"openingHours": "Mo-Fr 07:00-16:30",
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": "4.9",
"reviewCount": "87"
},
"review": {
"@type": "Review",
"reviewRating": { "@type": "Rating", "ratingValue": "5" },
"author": { "@type": "Person", "name": "A real customer" },
"reviewBody": "Pulled from the live reviews feed, never invented."
}
}
</script>
What would change for the real client
- Their floors, not our procedural ones — a photographed job gallery per suburb, with the drawn Garage Plan kept as the estimator beside it.
- Job management integration — the quote form pushing straight into Tradify or ServiceM8 as a lead with the plan's scenario attached, so the measure visit is booked from the enquiry; Buildxact where the client quotes builders on multi-slab new-build packages.
- Live reviews — the demo's written testimonials replaced by their Google reviews feed, which also powers the real schema above.
- A real rate card — the $75/$95/$130 anchors swapped for theirs, with the same minimum-job and template-note honesty rules kept intact.