Generative Assets · Wave 15 · Industry Demos IV
Design and build notes for a demonstration landing page: a fictional Perth commercial cleaning company, one conversion job (book a walkthrough quote), and a floor plan that cleans itself while you scope it.
Brightwork Commercial Cleaning is a fictional after-hours commercial cleaner based in Belmont — offices, medical suites, gyms and strata common areas across the Perth metro. The audience is an office or practice manager whose current contractor keeps no-showing, and a strata council comparing quotes. The page has a single job: get a walkthrough quote booked, because commercial cleaning is never honestly priced off a webpage — and saying so out loud is the trust play.
The art direction is "the building at 9pm": nightshift navy, warm lit windows, the quiet pride of a floor done before anyone arrives. Premium facilities services, not a mop-and-bucket flyer.
Supporting tokens: card navy #1B2534, raised navy #233047, hairline #2A3850, muted ink #ABA89D (6.7:1 on ground — captions and secondary copy only, never below 0.85rem body scale).
Outfit (500–700) carries the display voice — geometric facilities-services confidence, warm enough not to read corporate-cold. It sets the nameplate, headings, prices and the estimate figure. Karla carries body and UI: a humanist grotesque that stays friendly at small sizes, with font-variant-numeric: tabular-nums on every quote figure, caption and slider readout so the estimate never jitters as it updates. Body is 17px, and nothing interactive drops below 16px on mobile.
Space-type chips, an 80–1,200 m² area slider and frequency chips drive a procedural SVG floor plan. The plan is a serpentine grid of tiles — the route a cleaner actually walks — and tiles flip to citrus as the sweep dot passes, with room outlines (meeting room, treatment rooms, lobby, studio) and furniture blocks redrawn per space type. The grid's column count scales with the slider (6 columns at 80 m² up to 16 at 1,200 m²), so size is something you see, not just read. A task manifest updates per frequency and space type, and the estimate renders live. At rest the page is already mid-demonstration: a 300 m² office at three visits a week, two-thirds swept — and when the floor completes, a "signed off — 11:42pm" stamp holds for a beat before the next set begins.
Everything is vanilla: no libraries, one rAF loop that pauses on document.hidden, tiles painted incrementally (only newly-cleaned cells touch the DOM each frame), and prefers-reduced-motion renders the plan fully swept and settled with the sign-off stamp showing. "Use this scope in the quote" copies the configured scenario straight into the walkthrough form — the demo interaction becomes a warmer lead.
| Space type | Base / visit | Rate / m² | Floor (80 m², weekly) |
|---|---|---|---|
| Office suite | $55 | $0.50 | $95 / visit |
| Medical & allied | $85 | $0.75 | $145 / visit |
| Gym & studio | $60 | $0.75 | $120 / visit |
| Strata common areas | $48 | $0.50 | $380 / month |
per-visit = base + rate × m² · monthly = round-to-$5(per-visit × visits/week × 52 ÷ 12). Strata is presented monthly (that is how strata contracts are actually let), so its card leads with the monthly figure. Worked anchors: 300 m² office × 3 visits/week → $55 + 0.50×300 = $205/visit; $205 × 3 × 52÷12 = $2,665 → $2,665/month. Every "from" price in the services grid is the exact floor of the matching builder scenario — office $95/visit, medical $145/visit, gym $120/visit, strata $380/month — so the page can never argue with itself. Frequency deliberately doesn't move the per-visit rate: that variation is real but building-specific, which is exactly why the copy says the final price is set at the walkthrough.
document.fonts.ready with a 900ms fallback timer; content is never left at opacity 0 if JS fails, and reduced-motion skips the transition entirely.window.__cleaning carries space, m², frequency, per-visit, monthly, sweep progress and form status, updated on every interaction, so QA gates on state rather than wall-clock.This demo deliberately ships only WebPage schema naming itself a demonstration — fictional LocalBusiness or Review markup in a real search index would be dishonest. For a real client, we'd ship this (with their real details, real review feed, and per-suburb service pages):
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "LocalBusiness",
"@id": "https://brightworkcleaning.com.au/#business",
"name": "Brightwork Commercial Cleaning",
"description": "After-hours commercial cleaning for offices,
medical suites, gyms and strata across Perth.",
"telephone": "+61-8-XXXX-XXXX",
"address": {
"@type": "PostalAddress",
"streetAddress": "Unit X, XX Example St",
"addressLocality": "Belmont",
"addressRegion": "WA",
"postalCode": "6104",
"addressCountry": "AU"
},
"geo": { "@type": "GeoCoordinates", "latitude": -31.96, "longitude": 115.93 },
"areaServed": ["Perth CBD","West Perth","East Perth","Subiaco",
"Belmont","Victoria Park","South Perth","Osborne Park","Cannington"],
"openingHoursSpecification": [{
"@type": "OpeningHoursSpecification",
"dayOfWeek": ["Monday","Tuesday","Wednesday","Thursday","Friday"],
"opens": "18:00", "closes": "02:00"
}],
"priceRange": "$$",
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": "4.9", "reviewCount": "87"
},
"review": [{
"@type": "Review",
"author": { "@type": "Person", "name": "Real Customer" },
"reviewRating": { "@type": "Rating", "ratingValue": "5" },
"reviewBody": "Pulled live from the client's Google Business Profile."
}]
}
</script>
Plus: Google Business Profile with service categories per suburb, review acquisition wired into job completion, and location pages for the suburbs that actually convert.
aria-invalid ×5, no confirmation, probe form:'invalid'); filled submit rendered the "Thanks — request received / we'd call you back within one business day" state; every chip, the slider, the FAQ accordion, the sticky bar and the scope→form handoff driven as dispatched MouseEvents, never el.click().perVisit/monthly updated in the same interaction.scrollWidth = 375, wordmark and call button fully inside the viewport box with no overlap, h1 + primary CTA above the fold, sticky bar fixed at the bottom with both tap targets at 56px.