Clearwell Pools ← Back to the site

How Clearwell Pools was made

Clearwell Pools is a fictional fibreglass pool builder based in Baldivis, selling to families and downsizers across Perth's southern corridor. The landing page has one job: a site-visit booking with the shell and options already priced — the Pool Fit estimator does the pricing, and everything else on the page walks the visitor toward the form.

The concept

Pool buying starts with two anxieties: "what does it actually cost?" and "will it fit?". The Pool Fit answers both at once — a top-down drawing of a backyard with the shell set in, resized live by three shell chips, with fencing, heating, mineral water and a crane lift toggling on and off as drawn objects with drawn prices. The estimate badge floats on the water itself, so the price and the pool are one picture. Whatever the visitor configures carries straight into the booking form.

Palette — limestone, pebble, filtered water

Argued from the materials of a WA pool build: limestone paving underfoot, the dark pebble of a pool interior, water with the sun filtered through it, and the sand of coping strokes. Contrast figures below are measured from the shipped tokens with WCAG arithmetic, not estimated.

#F3EFE6 limestone pavingpage ground
#16303A deep pebblebody ink — 12.06:1 on the ground
#0E6B84 filtered tealCTAs — white labels 6.08:1; 5.30:1 on the ground
#0A4E61 deep teal inklinks & teal small copy — 8.04:1 on the ground
#C9B78C sun sandcoping strokes, fence ticks, rules — lines and marks only, never small text

The spec hypothesised ≈5.6:1 for white-on-teal and ≈7:1 for the deep-teal ink; they measured 6.08:1 and 8.04:1, so the pre-minted two-token pair shipped as specced with no remedy needed. Support inks: #41565F soft ink measures 6.73:1 on the ground and 7.39:1 on cards; the barrier-note amber #5C4A18 measures 7.48:1 on its #F6EFDC field; footer copy #C6D4D1 measures 9.05:1 on the pebble ink.

Type

Sora 600/700 for display — its engineered rounds read like moulded fibreglass, and it carries the typographic moment: the shell length set huge ("SIX AND A HALF METRES OF SUMMER") that swaps with the shell chips. Albert Sans is the workhorse: open, legible at 16.5px body, comfortable in chips, ledgers and form labels.

The Pool Fit — how it works

The scene is a single inline SVG: lawn bands, a paver-pattern pad, boundary fence with procedural palings, then the pool — coping band, coping edge and water rect all centred on one point and resized by pure attribute arithmetic. A <canvas> overlay clipped to the water's rounded rect draws the shimmer: five layered sine caustic lines plus a drifting sun glint, with the global alpha riding 0.70 + 0.15·sin(t) so the animation never drops below a 0.55 visibility floor — the 7-second thumbnail always catches it mid-shimmer.

  • Dual-speed redraw: the first render earns a 900 ms entrance with fence ticks staggering in at 26 ms each; every subsequent chip or toggle redraws at a snappy 450 ms with an 11 ms stagger, via a firstDraw flag.
  • Stale-frame safety: every control handler bumps an invalidation token before touching state; in-flight tweens and staggered tick timers check the token and abandon silently, so rapid-fire clicking can never leave a stale closure driving the scene.
  • rAF hygiene: the shimmer's delta is clamped at zero and capped (dt = min(max(now−last,0)/1000, 0.05)), the loop idles when the tab is hidden, DPR is capped at 2, and a ResizeObserver re-sizes the canvas.
  • Derived-state pricing: fencing is priced at the shell's pinned perimeter allowance (16 / 19 / 22 lm × $390), so changing the shell with fencing on honestly re-prices the fencing line — 6.5 m → 8.0 m moves it $7,410 → $8,580. Heat, mineral and crane are flat at every shell, and the card says so.
  • Honest price-less line: site power and extra earthworks print no number anywhere — "quoted from the site visit — no two backyards dig the same" appears in the ledger beside every total, and abbreviated on the badge itself.
  • The barrier note: turning fencing off never blocks the estimate; it swaps in the pinned note — WA law requires a compliant barrier before the pool holds water, yours or ours.
  • State probe: window.__poolbuilder exposes shell, toggles, computed lines, total and a settled flag, so QA gates on state, never on seconds.

Pinned anchors

ItemPriceBehaviour
5.0 m plunge shell$42,500supply + standard install — the grid floor
6.5 m family shell$52,900rest state
8.0 m swim shell$63,800
Glass fencing$390/lm16 / 19 / 22 lm by shell — re-prices with the shell
Heat pump+$5,900flat, any shell
Mineral-water system+$2,400flat, any shell — tints the drawn water
Tight-access crane lift+$3,800flat, any shell — draws the crane over the boundary
Site power & extra earthworksno numberquoted from the site visit, declared beside every total

Rest state measured from the running page: 6.5 m + fencing (19 lm) = $60,310. All worked figures — $48,740, $78,280, $55,300, $72,380 — were verified against an independently reimplemented oracle in the QA harness.

Conversion decisions

  • Above the fold: trade + area + promise in the h1, the call button beside the booking CTA, and the estimator card sharing the hero — the $60,310 badge lands inside the 1200×750 thumbnail.
  • Estimator → form handoff: the configured shell, options and estimate pre-fill the booking textarea; the crane toggle also pre-selects "tight access" in the block-access field (only while the visitor hasn't touched it).
  • Form fields: name, phone, suburb, block access, and the pre-filled configuration — the minimum a pool builder needs to price a site visit. Empty submits mark fields inline; a valid submit swaps the form for a designed confirmation panel ("we'd call you back within one business day") that is plainly labelled a demonstration.
  • Mobile: a fixed call/book bar below 860px, 44px+ tap targets, and the masthead checked at 375 first.

Local SEO we'd implement for the real business

The live page carries only WebPage/CreativeWork/BreadcrumbList schema naming it a demonstration — fictional business data doesn't belong in a real search index. For a real Clearwell, we'd ship this (with their real reviews feed):

{
  "@context": "https://schema.org",
  "@type": "GeneralContractor",
  "name": "Clearwell Pools",
  "telephone": "+61-8-XXXX-XXXX",
  "address": {
    "@type": "PostalAddress",
    "addressLocality": "Baldivis",
    "addressRegion": "WA",
    "postalCode": "6171",
    "addressCountry": "AU"
  },
  "areaServed": ["Baldivis", "Wellard", "Secret Harbour",
                 "Rockingham", "Kwinana", "Mandurah", "Perth"],
  "priceRange": "$42,500+",
  "openingHoursSpecification": [{
    "@type": "OpeningHoursSpecification",
    "dayOfWeek": ["Monday","Tuesday","Wednesday","Thursday","Friday"],
    "opens": "07:00", "closes": "17:00"
  }],
  "aggregateRating": {
    "@type": "AggregateRating",
    "ratingValue": "4.9", "reviewCount": "87"
  },
  "review": [{
    "@type": "Review",
    "author": {"@type": "Person", "name": "…"},
    "reviewRating": {"@type": "Rating", "ratingValue": "5"},
    "reviewBody": "…"
  }]
}

Why GeneralContractor: schema.org has no pool-builder subtype, and WA pool installation legally runs under a registered building contractor — so GeneralContractor is the honest, most-specific organization type available. For the real client we'd also wire the booking form into their job software — Buildertrend or Tradify for scheduling and site-visit workflow, Xero for deposits and progress invoicing — and swap the demo reviews for their live Google reviews feed.

Iteration log

Pass 1 — Craft

  • Caught a shorthand-vs-longhand CSS collision: .mast{padding:12px 0} silently zeroed the wrapper's side padding, pushing the masthead CTA flush against the viewport edge at 375. Rebuilt as longhand top/bottom padding.
  • Measured every contrast pair from the shipped tokens (figures above). One failure found: the Sapience band's bright-teal button fill measured 2.89:1 under white labels — swapped to the core teal fill (6.08:1) and kept the bright hue as a border accent.
  • Screenshot review at native resolution caught the price badge swallowing the entire water at 375 — the nowrap subtitle forced a ~220px badge over a 117px pool. Shipped a short subtitle variant ("+ site works on visit") and a tighter badge under 560px; the water now shows around it.

Pass 2 — Depth

  • Second-read details: a striped towel and thongs on the paving that track the pool as it resizes, a gate swing drawn in the fence line, palings on the boundary fence, and a drifting sun glint in the shimmer.
  • Added the badge "bump" micro-interaction — a 500 ms overshoot scale each time the estimate changes, killed under reduced motion.
  • Tuned the dual-speed redraw (900 ms entrance / 450 ms subsequent) and the display-heading crossfade against the shell chips.

Pass 3 — Hardening

  • 375 sweep: scrollWidth exactly 375, masthead wordmark's right edge (176px) clears the actions block (208px), sticky call bar fixed in the bottom 140px, h1 + CTA above the fold.
  • Drove everything: all three shell chips, all four option toggles, empty submit (4 invalid fields, confirmation stays hidden) and filled submit (panel shows) via the submit button specifically, plus a rapid-fire drive — seven clicks in one evaluate with no settle between — ending on a pinned, asserted state. Five oracle scenarios matched an independently reimplemented pricing model exactly, including the fencing re-price intersection.
  • Reduced-motion path renders a settled static caustic frame and skips all tweens; rAF delta clamped at zero and capped. Chanel rule: removed a second mirrored sun glint that added motion without meaning. Zero console errors across every pass.