How the Kelvin & Coil appliance-repair demo was made
The concept. Kelvin & Coil Appliance Repairs is a fictional Balcatta whitegoods repairer — the name is the two things every whitegood runs on, temperature and a wound coil. The audience is a household standing in front of a dead washer (or a property manager who needs the fix documented), and the page's single job is to book a repair callout. The demo's argument: an appliance repairer's whole reputation is the moment the tech says "here's what it is, here's what it costs, here's whether it's worth it" — so the site performs exactly that moment, live.
Palette
Enamel-and-teal clinical light
Argued from the bench: white appliance enamel under kitchen light, the steel chassis behind it, a multimeter's LCD, and the amber tag on the live wire. Deliberately clinical rather than fresh — the diagnostic instrument is the brand. The bright meter teal is the LINE/MARK accent (traces, dial, CTA); body copy never sets in it — a darker teal-ink was derived for text duties, and an amber-ink likewise (both clear 4.5:1 on enamel).
Type
Manrope · Source Sans 3 · Chivo Mono
Manrope (700/800) is the display voice — an engineered rounded grotesque, precise without coldness, right for a trade that measures things. Source Sans 3 carries body and UI at ≥16px. Chivo Mono is the instrument face: every price, range, percentage, meter reading and the diagnostic caption strip set in it, so the numbers read as measurements, not marketing.
Signature
The Repair-or-Replace Adviser
Five appliance chips, a 1–15 year age stepper and a per-appliance fault select drive a procedural SVG cutaway built entirely in JS (line-work in graphite, ~40–60 elements per scene). The selected fault glows fault-amber with a pulsing halo, a dashed meter-teal probe line runs from a drawn multimeter to the part, and the meter's caption strip prints a per-fault reading ("OL — open circuit", "drum play 2.1 mm"). A verdict dial sweeps a needle between REPAIR and REPLACE: the needle angle maps the fault midpoint against the applicable threshold (60% of typical-new under ten years; 40% at ten-plus), so the dial genuinely shows how close the maths is to tipping. Scene redraws are dual-speed — 26 ms/element stagger on first paint (capped 1.5 s), 11 ms capped ~520 ms on every subsequent change — with a settled flag on the window.__appliancerepair probe. Second-read details: the energy-rating sticker prints the purchase year (2026 − age) and tilts, fades and gathers scuffs as the stepper climbs — rust spots appear at 12+ years — and each fault has its own meter reading.
Honest numbers
The pricing model, derived
All ranges are all-in (parts, labour, GST, with the $129 callout credited when the repair proceeds). Typical-new figures are comparable mid-range replacements. The verdict rule, printed on the page because it IS the product: replace when the fault's midpoint repair cost is ≥ 60% of typical-new, or when the appliance is ten-plus years old and the midpoint is ≥ 40%. Otherwise repair.
| Appliance | Typical new | Fault | Range | Midpoint | % of new | Verdict <10 yr / ≥10 yr |
|---|---|---|---|---|---|---|
| Washing machine | $899 | Drum bearings | $360–$420 | $390 | 43.4% | REPAIR / REPLACE |
| Drain pump | $220–$260 | $240 | 26.7% | REPAIR / REPAIR | ||
| Door seal | $180–$220 | $200 | 22.2% | REPAIR / REPAIR | ||
| Fridge-freezer | $1,199 | Thermostat | $210–$250 | $230 | 19.2% | REPAIR / REPAIR |
| Door seal | $190–$230 | $210 | 17.5% | REPAIR / REPAIR | ||
| Compressor | $520–$620 | $570 | 47.5% | REPAIR / REPLACE | ||
| Oven & cooktop | $1,049 | Heating element | $190–$230 | $210 | 20.0% | REPAIR / REPAIR |
| Thermostat | $230–$270 | $250 | 23.8% | REPAIR / REPAIR | ||
| Door hinges | $170–$210 | $190 | 18.1% | REPAIR / REPAIR | ||
| Dishwasher | $849 | Drain pump | $210–$250 | $230 | 27.1% | REPAIR / REPAIR |
| Heating element | $240–$280 | $260 | 30.6% | REPAIR / REPAIR | ||
| Inlet valve | $180–$220 | $200 | 23.6% | REPAIR / REPAIR | ||
| Dryer | $749 | Heating element | $200–$240 | $220 | 29.4% | REPAIR / REPAIR |
| Drive belt | $170–$200 | $185 | 24.7% | REPAIR / REPAIR | ||
| Thermostat | $190–$220 | $205 | 27.4% | REPAIR / REPAIR |
Measured from the running page (verified against an independently reimplemented oracle across all 30 appliance × fault × {8, 10} year scenarios): the rest state — washing machine, 8 years, drum bearings — displays $360–$420 against typical-new $899, 43% of new, verdict REPAIR; stepping the age to 10 arms the second rule and the dial crosses to REPLACE ("the next fault is already queued"). The fridge compressor (displayed 48%, raw 47.5%) crosses identically: REPAIR at 8 years, REPLACE at 10. Both crossings match the spec's pinned examples with zero corrections; on-page percentages are rounded to the nearest whole (43.4 → 43, 47.5 → 48). The adviser's cheapest range floor — $170 (oven door hinges, dryer drive belt) — equals the services grid's "from $170" floor, so the page never argues with itself.
Documented model shape: no pinned fault reaches the 60%-of-new line on its own, so the first rule's REPLACE branch is unreachable through the UI's own data — it exists in code (and in the printed rule) for the real build, where a quoted repair on a cheap unit can cross it. Age below ten years honestly no-ops on price and verdict, and the card says so ("age hasn't tipped the maths yet — at ten years it starts to") while the cutaway visibly weathers — a live control that never silently ignores input.
Conversion decisions
Why the page is shaped like this
- The adviser shares the first viewport with the hero — the signature is the sales pitch, so it must be in the 1200×750 thumbnail and reachable in one thumb-scroll at 375px. Primary CTA ("Book a repair callout") and the tel: link sit above the fold at every width; mobile gets a sticky call/book bar with ≥44px targets.
- The adviser pre-fills the booking form (appliance, age, fault, range, verdict) — the demo interaction becomes a warmer lead. Editing the description detaches the sync so the visitor's own words always win.
- Form fields are the trade's triage questions: name, phone, suburb (travel), make (parts on the van), preferred day (
type=date, min today), and the symptom. Validation is inline with visible focus states; the confirmation is a designed local state ("we'd call you back within one business hour") that plainly says nothing was sent — never a faked network round-trip. - The $129 callout-credit is a full-width band, not a footnote — it's the trade's biggest trust objection ("do I pay twice?") answered in one sentence: credited in full when you proceed; charged only when you decline or we call it not worth fixing.
- Real WA licensing stated plainly: hardwired ovens, cooktops and some dryers are licensed electrical work in WA — the demo carries a placeholder EC 000000 and puts the licence on the invoice line, because being right about the rules is the credibility.
Local SEO
The schema we'd ship for the real business
This demo page emits only a WebPage schema naming itself a demonstration — fictional LocalBusiness/Review markup in a real index would be dishonest. The real client's build would ship:
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "LocalBusiness",
"name": "Kelvin & Coil Appliance Repairs",
"telephone": "+61-8-XXXX-XXXX",
"areaServed": ["Balcatta", "Stirling", "Karrinyup", "Osborne Park", "Joondalup", "Perth"],
"address": { "@type": "PostalAddress", "addressLocality": "Balcatta",
"addressRegion": "WA", "postalCode": "6021", "addressCountry": "AU" },
"openingHoursSpecification": [
{ "@type": "OpeningHoursSpecification",
"dayOfWeek": ["Monday","Tuesday","Wednesday","Thursday","Friday"],
"opens": "07:00", "closes": "18:00" },
{ "@type": "OpeningHoursSpecification", "dayOfWeek": "Saturday",
"opens": "08:00", "closes": "14:00" }
],
"priceRange": "$129–$620",
"aggregateRating": { "@type": "AggregateRating",
"ratingValue": "4.9", "reviewCount": "214" },
"review": [{
"@type": "Review",
"author": { "@type": "Person", "name": "Real Customer" },
"reviewRating": { "@type": "Rating", "ratingValue": "5" },
"reviewBody": "Pulled from the live review feed, never hand-written."
}]
}
</script>
— fed from the client's actual review platform, with per-service Service schema (washing machine repair, fridge repair…) and suburb-level FAQ markup from the questions the phone actually gets.
Real client
What would change for the real business
- ServiceM8 job cards behind the form — the booking lands on the tech's phone with the adviser's scenario attached, photos and job history in one record.
- Parts-supplier ordering feeds so "is it on the van?" is answered from live stock, and overnight orders trigger from the job card.
- Property-manager approval workflows (PropertyMe / PropertyTree-style): the repair-or-replace maths, photos and quote in one owner-approval email — the exact flow the demo's trust strip promises.
- Square tap-to-pay at the door, Xero reconciliation behind it, Podium review requests after every closed job feeding the schema above.
- The fault table's ranges and typical-new figures maintained from the client's own invoice history — the adviser stays honest because it reads real numbers.
Process
Iteration log
Pass 1 — Craft
Drove the whole page headlessly (puppeteer, port 7003): 62 asserts green — rest state ($360–$420 / 43% / REPAIR), the full 30-scenario oracle sweep, both spec crossings, empty-submit validation on a fresh load (10 signals), filled-submit confirmation, FAQ, sticky bar, fold composition. Screenshot review then caught what the asserts couldn't: the energy-label wear sticker rendered at fixed coordinates that missed every appliance cabinet (floating in mid-air beside the washer) — replaced with a per-scene anchor; the dial's "% of new" caption sat mid-dial where the needle crossed it — moved to the baseline between the REPAIR/REPLACE labels; scuff marks could land off the narrower fridge cabinet — re-ranged to the shared body zone.
Pass 2 — Depth
Screenshot-reviewed all five cutaways plus both REPLACE states at 12 and 10 years. Moved the fridge's sticker off the compressor fault zone (it crowded the glowing part) and the dryer's sticker off the belt ring. Added the verdict pop — a 500 ms overshoot scale on the REPAIR/REPLACE word only when the call actually flips, so the moment the maths tips is felt. Confirmed the second-read layer: purchase year on the sticker (2026 − age), per-fault meter readings, rust spots at 12+ years.
Pass 3 — Hardening
375px masthead first (W18/W21): wordmark right edge vs call-button left edge compared explicitly — clear, button fully in viewport, and the header collapses to icon + "Call" below 430px. Above-fold assert at 375 (h1 + ≥44px CTA + trust line all visible), sticky bar fixed in the bottom band with a 48px tel target, both pages' scrollWidth ≤ 375 (the guide's pricing table ships in a .table-scroll wrapper). Reduced-motion renders the scene settled with a static needle and no probe-line flow. Removed the hero's pinstripe background overlay (the one excess ornament). Zero console errors across every run; QA gates on the state probe's settled flag, never wall-clock waits.
Techniques
Under the enamel
- Zero libraries. All five cutaways are JS-built SVG (namespace-correct
createElementNS), staggered by inline transition delays; the wear layer, fault halo, probe path and meter re-render as one atomic scene per state change. - CSS animations never touch a property the same element sets as an SVG presentation attribute — halo opacity and probe dash-flow live in classes on elements whose attributes stay geometric (LEARNINGS W16/W17).
- The needle is a CSS transform transition on an SVG group with an explicit transform-origin; the dial zones are two static arcs, so the moving part is one cheap composite.
- Procedural service-area map: stylised coastline, Swan River, dashed travel-band rings from Balcatta — all inline SVG in the palette.
- Entrance choreography gates on
document.fonts.readywith a 900 ms fallback; content is never stranded at opacity 0 without JS.