Fairway Synthetic Turf

Guide · how it was made

A lawn you price by stepping it out

Fairway Synthetic Turf is a fictional Canning Vale synthetic-lawn installer, built as a conversion-first landing page demonstration. The audience is a Perth household staring at dead grass under sprinkler restrictions; the page's single job is a lawn-install enquiry with the lawn's dimensions already on the meter.

Palette — the pile and the sand infill

Kiln sand#F0EAD9 — page ground; the silica infill brushed into every lawn.
Shade ink#232B24 — body text, 13.2:1 on the ground.
Field green#2F6B3F — CTAs, the turf fill, the drawn lawn. 4.9:1 on the ground; carries buttons, never small text on dark.
Blade green#6FA05C — the second pile tone: mow bands, rules, dimension marks. Lines only, per the ink/line split.

Two greens because a real lawn has two: the pile colour and the band the mower leaves. Everything warm around them is the sand the installer actually broadcasts into the pile. Small text never sits in either green — a separate muted ink (#46504A, 6.7:1) does that work.

Type

Outfit (600/700) is the display face — geometric and landscape-brochure modern, the voice of a company that draws presentation sheets for backyards. Source Sans 3 carries body copy at 16.5px. The typographic moment is the ledger's square-metre readout: the area set huge in Outfit with "never watered / never mowed" stacked quietly beside it — the whole pitch in one glance.

The Lawn Planner — signature & implementation

A top-down SVG yard plan drawn like a landscape architect's sheet: paver-grid ground, the house wall with its sliding door and cast shade, a dashed boundary fence, and the lawn itself drawn live at 26 px per metre. Width (2–12 m) and length (2–20 m) steppers resize the drawn lawn; pile chips swap the fill grammar — 30 mm draws wide mow bands, 40 mm draws twice-dense bands, the 13 mm putting green swaps to sand-dressed rings with a cup, flag, and a ball mid-putt on a dotted roll line (the second read). The strip toggle rolls the old patchy lawn back at one corner — a pattern-filled region terminated by a drawn turf roll — and tight access opens a side gate with a hand-barrow path.

Everything is rebuilt into three SVG groups per state change. Mow bands are registered with a single persistent requestAnimationFrame loop that owns a shimmer clock (dt = min(max(now − last, 0)/1000, 0.05) — clamped at zero and capped) and drives band opacity as 0.75 + 0.2·sin(t·1.15 + i·0.65): a floor of 0.55, so the signature is visibly alive at any capture instant. The first draw earns entrance choreography (26 ms stagger, 1.5 s cap); every later redraw is snappy (8 ms, 500 ms cap) behind a firstDraw flag. Every control handler bumps a generation token before touching state, and rebuilds replace the loop's scene object atomically — so a rapid-fire burst of clicks can never leave a stale closure animating a dead lawn. State is exposed at window.__turf (scenario, area, rates, raw, billed, minApplied, settled) so QA gates on state, never on seconds.

The pricing model (all figures measured from the running page)

LineRateWorked exampleMeasured
30 mm family lawn$92/m²20 m² (4 × 5 m), no toggles$1,840 · no minimum note
40 mm premium lawn$105/m²40 m² (5 × 8 m) + strip$4,760 (rest state)
13 mm putting green$135/m²20 m² (4 × 5 m), no toggles$2,700
Strip & cart old lawn+$14/m²40 m² on 30 mm$4,240
Tight access, hand-barrow+$9/m²40 m² on 40 mm + strip$5,120
Minimum job$1,840 billed16 m² (4 × 4 m) on 30 mm = $1,472 rawbilled $1,840 · note shown
Concrete/paving removalNo number — quoted on inspection; thickness and access drive itprice-less by design

The minimum is a total-level clamp with its note shown only while it binds: at exactly 20 m² of 30 mm ($1,840 on the nose) the note stays hidden — the boundary is reachable from the integer stepper grid and was asserted there. Every figure above was verified against an independently reimplemented oracle in the QA harness, plus a rapid-fire drive (14 clicks across two no-settle bursts) whose settled state still matched the oracle ($4,050 at 6 × 5 m putting green).

Conversion decisions

The planner lives in the hero. It's the signature and the qualifier — a visitor who steps out 5 × 8 m has already told us the job size, so its bounding box shares the first 750 px with the headline instead of sitting below the fold.

Planner → form handoff. Dimensions, pile and the strip toggle pre-fill the enquiry form (and flow back the other way), so the enquiry arrives warm: name, phone, suburb is all we still ask. Phone validation is a JS digit-count check rather than a pattern attribute — Chrome now compiles patterns as v-flag regexes and a dead pattern fails silently.

Call first, form second. Trade traffic is a phone in one hand: tel: in the masthead, a sticky call/quote bar under 720 px, and the form's confirmation is a designed local panel — hidden on load and on empty submit, shown only after a valid submit, with a plain line saying no enquiry was sent anywhere.

Honest edges. The minimum-job note appears only while it binds; concrete removal prints no number at all ("quoted on inspection — thickness and access drive it"); the FAQ concedes the 40° underfoot heat and the enzyme rinse for small dog yards. A demo that argues honestly is the sales evidence.

Local SEO we'd implement for the real business

This demo page ships only WebPage/WebSite/BreadcrumbList structured data naming it a demonstration — fictional business schema in a real index is dishonest markup. For a real installer we'd ship HomeAndConstructionBusiness (schema.org has no landscaping/turf subtype — that's the correct family type a real SEO build would choose) plus a review feed:

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "HomeAndConstructionBusiness",
  "name": "Fairway Synthetic Turf",
  "telephone": "+61 8 9000 0307",
  "url": "https://fairwayturf.example.com.au",
  "address": {
    "@type": "PostalAddress",
    "addressLocality": "Canning Vale",
    "addressRegion": "WA",
    "postalCode": "6155",
    "addressCountry": "AU"
  },
  "geo": { "@type": "GeoCoordinates", "latitude": -32.058, "longitude": 115.918 },
  "areaServed": "Perth metropolitan area",
  "openingHours": ["Mo-Fr 07:00-17:00", "Sa 08:00-12:00"],
  "priceRange": "$$",
  "aggregateRating": {
    "@type": "AggregateRating",
    "ratingValue": "4.9",
    "reviewCount": "87"
  },
  "review": {
    "@type": "Review",
    "author": { "@type": "Person", "name": "A real customer" },
    "reviewRating": { "@type": "Rating", "ratingValue": "5" },
    "reviewBody": "Pulled from the live Google Business Profile feed, never hand-written."
  }
}
</script>

Ratings and reviews would be fed from the client's real Google Business Profile — never fabricated. Add suburb-level service pages, a Google Business Profile with photo cadence, and review-request automation off job completion.

What changes for a real client

Their install photography would replace nothing structural — the procedural plan stays as the quoting tool — but their finished-lawn gallery earns a strip above the reviews. The planner's anchors become their real rates, loaded from one place. Job management wires into what they already run: ServiceM8 or Tradify for the enquiry-to-job-card handoff (the form posts the planner scenario straight into the job description), AroFlo for the bigger multi-crew operators, a Google Business Profile reviews feed for live social proof, and Xero + Stripe deposits so the measured quote can be accepted and paid from the phone it was read on.

Iteration log