Industry demo · plumbing · Perth
How Swanline was built
Swanline Plumbing & Gas is a fictional 24/7 emergency plumber based in Bayswater, built as a plumbing-industry demonstration: one landing page whose only job is to get a panicking homeowner — standing in water at 9pm, phone in one hand — to press call. Every pixel is procedural (SVG, gradients, CSS); no photos, no libraries, no build step.
Conversion decisions
- Call-first, everywhere. This is an emergency trade: the primary CTA is the phone number, repeated in the header, hero, triage panel, quote section and a mobile sticky bar where call takes the wider, copper-filled slot. The quote form exists for the non-emergency visitor and is deliberately second.
- Above the fold at 375px: brand, "burst pipe at midnight — we answer", the glowing call button and the licence trust line all land inside the first screen, verified headlessly at 375×667 with the sticky bar's height accounted for.
- The signature earns its keep. The Emergency Triage strip is the conversion device: it meets the visitor at their exact problem, quotes the honest callout figure before they ask, and gives real damage-limitation advice (mains valve clockwise; gas → evacuate and call ATCO 13 13 52 first). Usefulness is the trust signal.
- Form fields: name, phone, suburb, problem type, optional description — the minimum the on-call plumber needs to triage and give an ETA. Suburb is required because ETA is the caller's first question. Submission confirms locally and says plainly that nothing is sent — no fake network round-trip.
- Honest price anchors: $99 weekday / $249 after-hours callout, drains from $220, hot water $1,650–$3,800 fitted — plausible 2026 Perth figures, and the triage panel quotes the same numbers as the services grid so the page never argues with itself.
Palette — copper pipe at night
Argued from the trade: water and copper pipe under a night sky. The navy makes the emergency CTA glow; copper is the trade's own metal, reserved for lines and marks; teal is moving water.
Type
Barlow Condensed for display — it has van-signage DNA, the condensed uppercase of a trade name painted on a Hiace door, and it lets a long emergency headline land in two lines at 375px. Barlow (the wide sibling) carries body copy at 17px so the two voices share one skeleton. The typographic move is scale contrast: a 4.6rem condensed shout over quiet 17px explanations — the page raises its voice exactly once.
Techniques — the Emergency Triage strip
Four chips re-render one panel from a static data object: honest price anchor, three-step "what we'll do", three-step "do this right now", and one of four hand-drawn SVG diagrams. The burst-pipe diagram is a mains isolation-valve locator: street main → meter box at the front boundary → valve → house, with the burst spraying at the wall.
The animation is a single coordinated 8-second CSS timeline across five keyframe tracks: the valve wheel turns 540° across the first 55% of the loop, the teal stroke-dashoffset water flow runs and then drains away as the wheel closes, the burst spray fades on the same cue, a copper CLOSED tag stamps in at 55%, and — the second-read detail — the water-meter dial spins while water flows and stops the moment the valve shuts, because that is literally what a meter does. At rest the page sits on burst-pipe mid-animation, so the 7-second thumbnail catches the valve mid-turn. Everything else is restrained: a shared zero-size SVG holds the ground gradient, [hidden]{display:none !important} guards the figure toggling, and prefers-reduced-motion collapses the whole timeline to a settled diagram.
Elsewhere: the Perth service-area map is a procedural SVG (coastline, Swan River, 12 named suburbs, a dashed 45–60-minute ring pulsing on Bayswater); the hero's pipe curve is a single stroked path; the load choreography is four staggered rise keyframes gated on a JS class so a no-JS visitor sees everything instantly. State lives in window.__plumber (active triage, form status, open FAQ) so QA gates on state, not seconds.
Local SEO we'd implement for the real business
This demo deliberately ships only WebPage schema naming it a demonstration — fictional review markup in a real index would be dishonest. For a real Swanline, we'd ship LocalBusiness + Review structured data wired to their actual details and live Google reviews:
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Plumber",
"name": "…real business name…",
"telephone": "…real phone…",
"url": "https://…",
"address": { "@type": "PostalAddress",
"addressLocality": "Bayswater", "addressRegion": "WA",
"postalCode": "6053", "addressCountry": "AU" },
"areaServed": "Perth metropolitan area",
"openingHoursSpecification": [{ "@type": "OpeningHoursSpecification",
"dayOfWeek": ["Monday","Tuesday","Wednesday","Thursday",
"Friday","Saturday","Sunday"],
"opens": "00:00", "closes": "23:59" }],
"aggregateRating": { "@type": "AggregateRating",
"ratingValue": "4.9", "reviewCount": "187" },
"review": [{ "@type": "Review",
"author": { "@type": "Person", "name": "…from GBP feed…" },
"reviewRating": { "@type": "Rating", "ratingValue": "5" },
"reviewBody": "…synced from Google Business Profile…" }]
}
</script>
Plus: suburb-level service pages for the high-intent "emergency plumber [suburb]" queries, GBP posts synced to jobs, and honest review acquisition via post-job SMS.
What would change for the real client
- Job management integration: the quote form posts into ServiceM8 or Tradify (the two we see most in WA plumbing; Simpro or AroFlo for larger crews) so a 9pm enquiry becomes a job card with suburb and problem type already triaged — no re-keying.
- Live reviews: the demo testimonials swap for a Google Business Profile feed with the real star count.
- Call tracking: a tracked number (e.g. WildJar) so the client sees exactly which jobs the page produced — the demo's honesty rule becomes their ROI report.
- Real details: actual PL/GF licence numbers, ABN, insurance certificates, and their van/team photography if they want it — though this page proves the design carries without stock photos.
- After-hours routing: the call button targets the on-call roster's number via their VoIP schedule, not a single mobile.
Iteration log
Pass 1 — Craft
Re-read every rule and measured contrast. Found and fixed: the shared ground gradient was defined inside the burst figure's SVG but referenced by the three initially-hidden figures — hidden-ancestor paint-server references are unreliable, so it moved to an always-present zero-size defs SVG. Removed a stray zero-width path from the valve diagram. Measured copper #C0703C at 4.7:1 on navy and held it to lines, borders and large condensed type only; body copy stays ivory (≈14:1), secondary mist (8.3:1). Driven check: all four triage chips clicked headlessly — each asserted the panel price anchor, the 3+3 step lists, aria-pressed, and that exactly one figure had computed display ≠ none. All four matched their expected copy exactly.
Pass 2 — Depth
Enriched the signature: chip changes now run a 0.4s eased swap animation on both the info column and the diagram, so the panel visibly answers the tap; the meter-dial/valve coupling (dial spins while water flows, stops at shutoff) stays as the second-read detail. Driven checks: form submitted empty — all 4 validations fired, error copy became visible, focus moved to the first invalid field, probe read invalid. Submitted filled (Test Resident / 0400 000 000 / Morley / burst) — the local confirmation rendered with all three values interpolated, the form hid, and a network listener confirmed zero POST requests. Reset returned a clean idle form. FAQ driven: opening item 2 then item 5 asserted exclusivity (one open at a time) and probe indices 1 → 4.
Pass 3 — Hardening
Headless 375×667: scrollWidth = 375, wordmark/nav/tel inside the viewport, hero value-prop + call CTA + licence trust line all above the fold, sticky bar present with 58px targets — but the header tel pill measured 37px tall, under the 44px floor; fixed with min-height:44px. Drive-then-diff: screenshots before/after a chip drive differ; the valve wheel's computed transform advances at rest (mid-animation thumbnail confirmed). Reduced-motion emulation: no anim class, valve animation-name none, everything visible and settled, zero console errors. Screenshot review (by eye, not just asserts) caught what the DOM probes sailed past: the triage figure was a flex row, so the caption sat beside the diagram and squeezed the SVG tiny — switched to column; label collisions in three diagrams (the gas panel's "get to the street" ran through the ATCO 13 13 52 box, the hot-water tap/pilot/relief labels crowded each other, the drain diagram carried a redundant label) — all re-plotted and re-shot clean. Chanel rule: removed the copper highlight stroke from the hero's background pipe — it competed with the CTA glow. Final run: 30/30 asserts green, zero console errors on both pages.