Generative Assets · Wave 31 · Industry Demos XX

How the Stonewater Bathrooms demo was made

Stonewater Bathrooms is a fictional Joondalup renovation firm built as an industry demonstration: one conversion-focused landing page showing a WA bathroom renovator exactly what their site could be. The page has a single job — a renovation enquiry with package, size and price already chosen — and every design decision below serves it.

Concept

Owners of tired 90s bathrooms, pre-sale renovators, and downsizers wanting one good bathroom done properly. The direction is a stone showroom on paper: warm stone-white ground, procedural terrazzo flecks in the panels, brass tapware line-art, sea-glass tile lines — calm, magazine-spread spacing, so the page reads the way the finished room feels. The signature is the Reno Planner: an SVG bathroom elevation that redraws by package and size, with the computed price badged on the drawing itself.

Palette — argued from the stone bench and the brass tap

#F1EEE9 honed stonepage ground — the benchtop the room is sold on
#2B2A28 slate inkbody text, 12.9:1 on the ground
#9A6B33 brassCTAs, marks, tapware linework — never small text
#3E7C74 sea-glassrules and tile hatching; links use the darker #31635C at 5.9:1

The ink/line split is deliberate: brass and sea-glass carry lines, fills and marks; small text always sits in slate ink (#2B2A28) or muted ink (#5C564C, 6.3:1). Where sea-glass appears as link text it is deepened to #31635C to clear 4.5:1.

Type

Cormorant Garamond 600/700 for display — the elegance of a stone showroom, and the face that sets the package price huge inside the drawn elevation ("$23,800 / fully finished" is the page's typographic moment). Mulish for everything workaday: labels, controls, body. Professional and legible beats expressive on a trade page; the serif carries all the personality the page needs.

The Reno Planner — signature implementation

The elevation is a single SVG rebuilt from state on every change: room shell, tile grid, fittings in brass line-art (vanity, mirror, shower), and the price badge — all generated as SVG nodes in JS, no raster anywhere. The refresh package keeps the layout: a half-height tile band at reduced opacity with a dashed brass "reseal" line, fittings swapped in brass. Full renovation retiles floor-to-ceiling: the tile columns run a perpetual sweep whose opacity floor is 0.55 — the hatching is visibly alive at any capture instant, never invisible. Premium adds the diagonal feature band, a terrazzo-pattern stone top on the vanity, and pulsing underfloor-heating glow lines beneath the slab. The freestanding bath is drawn with an adaptive width so it always fits between vanity and shower zone at any size from 4–10 m²; when it's off, a toilet takes the middle of the wall.

Pricing model (binding anchors)

ScenarioArithmeticDisplayed
Rest: full renovation, 8 m², bath ON, heating OFF19,500 + 2×950 + 2,400$23,800
Refresh, 8 m²8,900 + 2×250$9,400
Refresh, 5 m²8,900 flat — note shown$8,900
Premium, 10 m² (no bath)28,500 + 4×950 — heating locked ON, $0 itemised$32,300
Full, 6 m², no toggles19,500 — boundary point, no uplift, no note$19,500
Moving drainage / walls / windowsno number by design — "quoted on inspection"at cost

All figures are pure anchor arithmetic and integer-safe; every scenario above was measured from the running page and cross-checked against a reimplemented oracle in the harness. The planner's cheapest output ($8,900) equals the services grid floor. One builder-derived decision: the spec pins underfloor heating on full (+$1,600) and premium (locked ON, $0) but is silent for refresh — since heating lays under new floor tiles and a refresh keeps the floor, the toggle is disabled on refresh with the on-page reason "heating lays under new floor tiles, so it comes with a full or premium renovation" (the W18 disable-with-reason semantic, flagged for wave QA).

Conversion decisions

Local SEO we’d implement for the real business

This demo page deliberately ships only WebPage/CreativeWork schema naming it a demonstration — fictional structured data in a real index is dishonest markup. For a real client we’d ship HomeAndConstructionBusiness (schema.org has no bathroom-renovation subtype; this is the correct organisation type for a renovation builder) with real NAP, geo, hours and genuine review markup:

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "HomeAndConstructionBusiness",
  "name": "Stonewater Bathrooms",
  "description": "Bathroom renovations across the Perth metro — refresh packages,
    full renovations and premium renovations, one crew start to finish.",
  "url": "https://www.stonewaterbathrooms.com.au",
  "telephone": "+61 8 XXXX XXXX",
  "address": {
    "@type": "PostalAddress",
    "addressLocality": "Joondalup",
    "addressRegion": "WA",
    "postalCode": "6027",
    "addressCountry": "AU"
  },
  "geo": { "@type": "GeoCoordinates", "latitude": -31.7444, "longitude": 115.7683 },
  "areaServed": "Perth metropolitan area",
  "openingHours": "Mo-Fr 07:00-16:00",
  "priceRange": "$8,900–$35,000",
  "hasOfferCatalog": {
    "@type": "OfferCatalog",
    "name": "Renovation packages",
    "itemListElement": [
      { "@type": "Offer", "name": "Bathroom refresh", "price": "8900", "priceCurrency": "AUD" },
      { "@type": "Offer", "name": "Full renovation", "price": "19500", "priceCurrency": "AUD" },
      { "@type": "Offer", "name": "Premium renovation", "price": "28500", "priceCurrency": "AUD" }
    ]
  },
  "review": {
    "@type": "Review",
    "reviewRating": { "@type": "Rating", "ratingValue": "5" },
    "author": { "@type": "Person", "name": "— real customer, real review feed —" },
    "reviewBody": "— pulled from the client's Google Business Profile, never invented —"
  },
  "aggregateRating": {
    "@type": "AggregateRating",
    "ratingValue": "— measured —",
    "reviewCount": "— measured —"
  }
}
</script>

Plus: Google Business Profile optimisation for "bathroom renovations joondalup / perth northern suburbs", suburb-level landing sections fed by real job photos, and review harvesting wired to the client’s actual feed.

What changes for the real client

Iteration log

Pass 1 — Craft

  • Drove every control against the binding arithmetic: rest $23,800; refresh 8 m² $9,400; refresh 5 m² $8,900 with the flat note; premium 10 m² $32,300 with heating locked at $0 itemised; full 6 m² exactly $19,500 with no uplift line and no note at the boundary; full + heating $21,100. Empty submit kept the confirmation hidden and raised six validation signals; filled submit showed the panel with the scenario figure.
  • Screenshot review (the ground truth): the shower head sat half-hidden behind the price badge — moved the shower fittings below the badge card. The freestanding bath collided with the toilet and would have spilt past the wall at small sizes — gave the bath an adaptive width centred between vanity and shower zone, and the toilet now yields the wall middle to the bath (it's "around the corner" in elevation terms).
  • 375px masthead checked first (wordmark right edge vs call-chip left edge): the tel chip overflowed the viewport by 4px — added a ≤420px masthead tier (icon hidden, tighter chip). scrollWidth returned to 375 with the wordmark clearing the chip by 47px.
  • With heating on, the dimension label clipped below the viewBox — the viewBox height now adapts (412 / 460) to the heating glow's extra rows.

Pass 2 — Depth

  • Added the badge price count-up (380 ms cubic ease-out, token-guarded, off under reduced motion) so package taps read as the price "re-quoting" rather than snapping.
  • Added the second-read detail: with the bath on, its faint reflection appears in the mirror.
  • Fixed a map label collision (Joondalup's "home base" ran into Wanneroo) found on the full-page screenshot.
  • Re-drove the full worked-example suite plus the rapid-fire drive (ten clicks in one evaluate) — settled true, totals matching the oracle, zero console errors.

Pass 3 — Hardening

  • Reduced-motion path: settles immediately, hero never strands at opacity 0, two frames 1.3 s apart are pixel-identical (sweep and tweens fully off), and a chip drive still updates the price instantly. Motion path re-checked at the 7-second thumbnail settle: 12 sweep columns alive, minimum opacity 0.557 — the never-invisible floor holds.
  • 375px: all planner controls ≥42px tall; keyboard drive (Tab + Enter on a package chip) works with a visible focus ring.
  • Found and fixed anchor targets tucking under the sticky header (scroll-margin-top on all section anchors).
  • Chanel rule: removed the mirror's decorative glint line — the bath reflection is the one mirror detail that earns its place.
  • Final copy read for banned filler and Americanisms; v-flag-validated the phone pattern (every literal special escaped); confirmed zero console errors across every driven state on both pages.