Guide · Industry demo 218 · Landscaping
How Tuart & Stone was made.
Tuart & Stone Landscapes is a fictional design-and-build landscaper based in Wembley Downs, serving Perth's western suburbs. The audience is a homeowner with a sand-patch backyard and a Pinterest board; the page has one job — book a $220 design consult. Everything on it, including the signature before/after reveal, is procedural SVG, CSS and vanilla JS in a single file.
Conversion decisions
- CTA placement. "Book a design consult — $220" is the primary CTA above the fold at 375px, with the phone number beside it. Naming the price in the button is deliberate: a landscaping consult is a considered purchase, and stating the fee (credited on build) filters tyre-kickers while signalling confidence. The same CTA repeats in the header, the sticky mobile bar, and the form section.
- Sticky mobile bar is call-first, consult second — most trade traffic is a phone in one hand. Both targets are 50px tall.
- Form fields — name, phone, suburb, project type, yard description. Suburb is a first-class field because the business is geography-bound; project type mirrors the six service cards so enquiries arrive pre-qualified. No email field: the promised follow-up is a call-back, and every extra field costs completions.
- Local confirmation, honestly. Submit renders an instant confirmation card ("we'd call you back within one business day") with an explicit note that nothing was sent — no fake spinner, no fake network round-trip.
- Prices agree everywhere. The reveal's scope tags quote the same anchors as the service cards ($380/m walls, $28/m² lawn, $2,800 retic, $340/m cedar, $6,500–$18,000 natives), and the full-reveal total ($30,000–$45,000 for this yard) sits inside the stated design-and-build band ($25,000–$80,000).
Palette — argued from the coastal plain
The ink/line split is enforced throughout: gold draws, green and umber speak. On the green review and footer grounds, small cream text runs at ≥.72 alpha to hold 4.5:1.
Type
Bricolage Grotesque (display, 700–800) has the organic irregularity the brand needs — grotesque bones with warm, slightly hand-cut terminals, set tight at −.02em. Public Sans carries body, forms and UI: plain-spoken, legible at 16px on a phone, appropriately governmental about prices. The one typographic flourish is the hero's gold underline swash on "western suburbs" — a mark, not text.
Techniques — the before/after reveal
The signature is two hand-drawn SVG scenes of the same backyard — identical fence geometry, identical 1200×750 viewBox — stacked in one stage. The "before" (sand, dead kikuyu patches, rusting hills hoist, cracked concrete, abandoned hose) sits on top, clipped with clip-path: inset(0 calc(100% − --pos) 0 0); the "after" (limestone retaining and terrace, kangaroo paw and grevillea beds, cedar screen, striped kikuyu lawn, pop-up retic) renders full underneath. A single CSS custom property --pos drives the clip, the divider line and the handle, so pointer, touch and keyboard all share one setPos() path. The handle is a real role="slider" button — arrows step 2, PageUp/Down 10, Home/End jump — with aria-valuetext reporting "N% of the finished garden revealed".
Five scope tags are positioned over the after-scene in viewBox coordinates, each with a trigger x; when the divider crosses a trigger the tag surfaces with its honest price anchor. At full reveal (≤12%) a total chip surfaces the whole-yard figure. The page parks at 42% so the static thumbnail already reads as a transformation with three tags up. Everything else is restrained: one entrance stagger gated on document.fonts.ready with a timeout fallback, CSS-only plant sway and sprinkler shimmer (killed under prefers-reduced-motion), no rAF loops anywhere, no libraries.
Live state is exposed at window.__landscaper — slider position, visible tags, total-chip state, form status and errors, open FAQ panels — so QA asserts on state, never on wall-clock.
Local SEO we'd implement for the real business
This demo deliberately ships no LocalBusiness, Review or AggregateRating structured data — fictional entities in a real search index would be dishonest markup. The page carries only a WebPage schema naming it a demonstration. For a real client, this is the block we'd ship, fed by their actual NAP, licence numbers and review feed:
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "LandscapeArchitect",
"name": "«Real Business» Landscapes",
"telephone": "«real phone»",
"url": "https://«realdomain».com.au",
"address": {
"@type": "PostalAddress",
"streetAddress": "«street»",
"addressLocality": "Wembley Downs",
"addressRegion": "WA",
"postalCode": "6019",
"addressCountry": "AU"
},
"geo": { "@type": "GeoCoordinates", "latitude": -31.9, "longitude": 115.77 },
"areaServed": ["Wembley Downs","City Beach","Floreat","Scarborough",
"Doubleview","Churchlands","Wembley","Subiaco","Claremont",
"Swanbourne","Cottesloe","Nedlands","Karrinyup"],
"openingHoursSpecification": [{
"@type": "OpeningHoursSpecification",
"dayOfWeek": ["Monday","Tuesday","Wednesday","Thursday","Friday"],
"opens": "07:00", "closes": "16:00"
}],
"priceRange": "$$$",
"aggregateRating": { "@type": "AggregateRating",
"ratingValue": "«from review feed»", "reviewCount": "«count»" },
"review": [{
"@type": "Review",
"author": { "@type": "Person", "name": "«real reviewer»" },
"reviewRating": { "@type": "Rating", "ratingValue": "5" },
"reviewBody": "«pulled from Google Business Profile via API»"
}]
}
</script>
Plus: Google Business Profile optimisation, suburb-level service pages, and review markup fed live from the client's actual Google reviews — never hand-typed.
What would change for the real client
- Their projects in the reveal. The illustrated scenes swap for real before/after photo pairs from their own jobs — the slider mechanic, scope tags and pricing stay, now anchored to genuine site photography. Multiple yards become a swipeable set.
- Live reviews from their Google Business Profile replacing the demo copy, with honest counts and dates.
- Job management integration — the enquiry form posts into their existing tooling (Tradify, AroFlo or ServiceM8 are the usual suspects for WA landscaping crews), so a call-back promise is backed by a real job card, with SMS confirmation.
- Design-tool handoff — consult bookings synced to their calendar, and concept presentations from whatever they draw in (many WA designers use SketchUp or Vectorworks Landmark) embedded in a client portal.
- Call tracking and analytics on the tel: links and sticky bar, so they can see which suburb pages and which scope tags actually produce enquiries.
Iteration log
Pass 1 — Craft
- Re-reading the after-scene SVG caught a corrupted paving rect (a mangled fill attribute) — removed; the scene renders clean with zero console errors.
- Contrast audit on the green grounds: six small-text tokens (review captions and demo note, footer legal, Sapience kicker, footer headings) computed below 4.5:1 at their original alphas (as low as ≈3.3:1 at .5). All raised to .72–.85 alpha, ≥4.8:1.
- Drove every interactive: empty form submit fires all five validations (probe reported
["name","phone","suburb","type","desc"], five fields visibly marked); filled submit renders the confirmation with the demo-honesty note and hides the form; FAQ item opens to a measured 92px panel and collapses back to 0; slider keyboard path steps exactly ±2 per arrow witharia-valuenowtracking.
Pass 2 — Depth
- Added the full-reveal scope total chip: drag past 12% and "A yard like this one — typically $30,000–$45,000" surfaces, tying the five itemised tags to the design-and-build band. Asserted: hidden at the 42% rest state, shown and readable at Home/full reveal.
- Micro-interaction on the handle — scale 1.07 on hover, 0.95 while dragging — and second-read details in the scenes: the rusting hills hoist and abandoned hose exist only in the before; the sprinkler's water arcs breathe on a 3.4s cycle; three kangaroo-paw and grevillea clumps sway on offset 5.9–7.8s periods.
- Drove the drag path end-to-end: mouse-drag to 20% measured
pos=20, clip-path atinset(0 80% 0 0), all five tags surfaced, and a before/after screenshot hash diff confirmed the pixels actually changed (drive-then-diff).
Pass 3 — Hardening
- 375px sweep:
scrollWidth=375(no overflow), wordmark and tel link fully inside the viewport, h1 + primary CTA + warranty trust line all above the fold (CTA bottom at 474px of 667), sticky bar fixed with two 50px targets, and a real touch-drag moved the slider to the expected 80. - Reduced-motion path: hero visible immediately (opacity 1, no stagger), plant sway and sprinkler animations report
animation: none, tag transitions removed — the reveal still works as a plain stepped control. - Chanel rule: removed the decorative arrows icon from the drag hint — the handle already says it. Confirmation panel verified invisible at rest via the
[hidden]{display:none !important}guard. Final copy read at 375 (tightened the hero lede to keep the trust line above the fold). Full harness: 44/44 assertions green, zero console errors across desktop, mobile, reduced-motion and guide passes.
Verified headlessly with puppeteer-core against Chrome — 7-second settle, console-error collection, state-probe assertions, drive-then-diff, 375px and reduced-motion passes.