The concept
Jarrahline Carpentry & Decking sells decks the way a carpenter builds one: from the tape measure outwards. The signature Deck Planner draws a top-down deck board by board — joists showing through the gaps, grain strokes per board, a drawn tape measure with live dimension callouts — and prices it against pinned WA-market rates as you resize it. The estimate lives on the boards themselves, then walks itself into the quote form. Everything on the page is procedural SVG and CSS; there isn't a raster pixel anywhere.
Palette — argued from the timber rack
Contrast figures below are measured with real WCAG arithmetic from the shipped tokens (Pass 1), not estimated.
The spec's hypotheses (≈13, ≈8, ≈7, ≈6.5) measured 13.9, 9.2, 8.0 and 6.3 — the jarrah accent is deep enough that no W33 two-token remedy was needed; pine shaving never carries text, only linework, per the standing ink/line split.
Type
Zilla Slab 600 for display — a slab serif with a joinery squareness to it; the bracketed slabs read like dressed timber ends. It carries the wordmark, the section heads, the dimension callouts on the drawing, and the display moment: the deck's area set huge in words ("TWELVE SQUARE METRES, LAID STRAIGHT") that re-sets itself as the steppers move — "TWELVE AND A HALF…" arrives with the next half-metre. Public Sans does the workhorse body: legible, plain-spoken, nothing showroom about it.
Techniques — the Deck Planner
The planner is one hand-built SVG redrawn from state. Each redraw computes the deck rectangle at 58 px/m, lays an under-structure (dark base plus joist bands every 450 mm that show through the 2.5 px board gaps), then generates boards: count derived from the drawn width, each board's fill varied in lightness by a seeded pseudo-random (deterministic per board index, so redraws are stable), with one or two long cubic-Bézier grain strokes and occasional knots — composite gets a single flat stroke and near-uniform boards, which is honest to the material. Balustrade posts and twin stainless-wire runs walk a clockwise offset path around the deck edges to the stepper's length; the demolition toggle draws the old deck's boards lifting away at a corner in dashed jarrah ghosts. A decking-oil sheen — a skewed gradient band clipped to the deck — sweeps on a 7 s rAF loop with the delta clamped at zero and capped (W29), paused when the tab hides, and parked mid-deck under reduced motion, so the signature is visibly alive at any capture instant.
Redraws are dual-speed (W21): the first draw staggers boards in at 26 ms each (capped 1.5 s) as entrance choreography; every later change redraws at 8 ms per board (capped 450 ms) via a firstDraw flag, so stepper taps feel snappy, not laggy. Every control handler bumps a token and resets the settle timer before touching state (W25); the sheen loop reads a live deckRect object rather than closing over rebuilt geometry, so rapid-fire control bursts can't strand a stale animation. The page exposes window.__carpenter — scenario, computed figures and a settled flag — so QA gates on state, never on seconds.
The pricing model — pinned and honest
| Line | Rate | Rules |
|---|---|---|
| Decking, supply + install | $340/m² pine · $480/m² composite · $560/m² jarrah | Minimum billed area 6 m², billed at the selected timber's rate. At exactly 6.00 m² the minimum binds silently (no note); below it the "billed at the 6 m² minimum" note shows. |
| Balustrade | $290/lm, 0–12 lm × 0.5 | Kwila posts + stainless wire. Flat over any timber — stated on-card. 0 lm is the designed rest cell: draws nothing, adds $0. |
| Old-deck demolition | +$48/m² | Bills the drawn area, not the clamped billing area — the 6 m² minimum applies to the decking line only. Resizing the deck re-prices this line with it (derived-state repricing). |
| Framing-only, pergolas, built-ins | — | No estimator number by design — quoted from drawings; services-grid ranges only. |
Derivation notes
- Every area is a multiple of 0.25 m² (0.5 m steppers), and 0.25 × {340, 480, 560} = {85, 120, 140} — all totals are whole dollars by construction. Displayed totals are whole-dollar (W26).
- Worked figures, all measured from the running page: rest 4.0 × 3.0 m pine = $4,080; composite $5,760; jarrah $6,720; rest + 4 lm balustrade $5,240; 5.0 m² pine bills the minimum at $2,040 (with note), 6.0 m² also $2,040 (no note — boundary reachable exactly at 2.0 × 3.0); rest + demolition $4,656; demolition at 15 m² re-prices to $5,820.
- The estimator's cheapest output — the 6 m²-minimum pine deck at $2,040 — equals the services grid's "new decks from $2,040" floor (W12 consistency contract).
- The balustrade's drawn run tops out at the deck's perimeter (you can't draw 12 lm around a 2 × 2 m deck), but the price always follows the stepper — the label prints the priced length. Documented here so the drawing and the ledger can't be read as disagreeing.
Conversion decisions
- Planner beside the hero copy, price on the boards. The 1200 × 750 thumbnail is the commercial argument, so the $4,080 badge sits in the artwork inside the first 750 px — a visitor (or a prospect imagining their logo up top) sees a priced deck before they scroll.
- Planner → form handoff. "Send this deck to the quote form" carries size, timber, balustrade and price into the enquiry textarea — the demo interaction becomes a warmer lead, and the fields a real business needs (name, phone, suburb, job type) stay short.
- Click-to-call everywhere a thumb lands: header tel link, hero CTA, and a sticky call/quote bar at mobile widths with 44 px targets.
- Honest floors beat teaser rates. The 6 m² minimum is stated in the grid, in the planner note, and in the model — no scenario exists where the page's cheapest number can't actually be bought.
- Confirmation is a designed state: hidden on load and on invalid submit; a valid submit swaps the form for a confirmation panel with the received scenario echoed back. No fake network round-trip.
Local SEO we'd implement for the real business
This demo page ships only honest WebPage/BreadcrumbList structured data naming it a demonstration — fictional business schema in a real index would be dishonest markup. For a real carpentry client we'd ship the schema below. Carpentry has no exact schema.org subtype, so the honest top-level choice is the parent HomeAndConstructionBusiness:
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "HomeAndConstructionBusiness",
"name": "Jarrahline Carpentry & Decking",
"description": "Carpentry and decking across Perth — decks, doors,
skirting and built-ins from an Osborne Park workshop.",
"telephone": "+61-8-XXXX-XXXX",
"url": "https://example.com.au",
"address": {
"@type": "PostalAddress",
"addressLocality": "Osborne Park",
"addressRegion": "WA",
"addressCountry": "AU"
},
"areaServed": ["Osborne Park", "Karrinyup", "Joondalup", "Morley",
"Subiaco", "Fremantle", "Cannington", "Midland", "Rockingham"],
"openingHours": "Mo-Fr 07:00-16: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": "Fed from the client's live Google reviews."
}
}
</script>
For the real client this feeds from their actual NAP, licence numbers and live review stream — plus service-page schema per trade line. Job-management integrations we'd wire the form into: Tradify or Buildxact for quoting and job cards, Xero for invoicing. Their photos would replace nothing here — the procedural drawing stays as the estimator; their finished-deck gallery would sit beside it.
What would change for the real client
- Real phone number, ABN, licence and insurance details replacing the reserved placeholder patterns.
- The form posting to their inbox and Tradify/Buildxact pipeline, with spam protection and an SMS acknowledgement.
- Their reviews feed (Google) replacing the demo review cards, marked up as real Review schema.
- Their rates in the planner — the model is a config object; repricing it is a five-minute change.
- A finished-work gallery and per-service pages once the landing page proves itself.
Iteration log
Pass 1 — Craft
Measured every palette hypothesis with WCAG arithmetic: ash/charcoal 13.9:1, white-on-jarrah 9.2:1, jarrah-on-ash 8.0:1, shaving-ink 6.3:1, muted ink 6.7:1 — all clear of 4.5:1, no two-token remedy needed. Drove the full worked-example set against an independently reimplemented oracle with the complete control vector pinned per scenario (rest $4,080; jarrah $6,720; composite $5,760; +4 lm balustrade $5,240; 5.0 m² clamp $2,040 with note; 6.0 m² boundary $2,040 without; demolition $4,656; demolition × resize intersection $5,820) — every figure matched the pinned arithmetic first try. The screenshot review caught what the 52 green asserts couldn't: the demolition tag sat struck-through by the tape-measure dimension line, and the balustrade wires grazed the area callout. Both labels moved to a stacked caption row below the tape zone, where nothing else lives at any deck size.
Pass 2 — Depth
Reasoned about the badge at the extremes: the 172 px price badge would overhang a 2.0 m-long deck's boards entirely, so it became adaptive — under ~3.3 m of drawn length it compacts (narrower plate, tighter type, "6 m² minimum billed" as the short form), verified on the 5.0 m² clamp screenshot where badge, note and boards all fit inside the fascia. Added one micro-interaction: the badge does a 320 ms settle-pulse on every repricing, so the number visibly lands. Second-read details already in the boards: seeded knots on real timber but never on composite, whose grain honestly flattens to a single embossed stroke; the display line re-words itself ("SEVENTEEN AND A HALF SQUARE METRES…") rather than just re-numbering. A rapid-fire burst — seven control clicks in one evaluate, no settle between — left probe, badge and board count consistent with the final state: the token-in-handler pattern held.
Pass 3 — Hardening
375 px masthead checked first (W18): wordmark right edge vs call-pill left edge compared explicitly — clear air, no wrap; no horizontal overflow on either page, and the guide's pricing table scrolls inside its wrapper instead of stretching the page. Empty submit (on a fresh navigation, so the prefill can't mask it) raises three validation states and keeps the confirmation panel hidden; a filled submit swaps form for panel — and the screenshot of that panel caught a copy defect the asserts passed: "New deck — New deck — 4.0 × 3.0 m…", the job-type select duplicating the prefill's opening words. The echo now drops the job label whenever the details already lead with it. Reduced-motion parks the sheen mid-deck with the deck fully drawn; the rAF delta is clamped at zero and capped; the loop pauses on hidden tabs. Removed one ornament (a planned compass rose in the drawing's corner — the tape measure already carries the drawing's authority; Chanel rule).