Build notes · site 456 · Wave 60
How Bluecone Gas Fitting was built
Bluecone Gas Fitting is a fictional Morley gas fitter servicing heaters, cooktops, gas hot water and ducted gas heating across Perth’s north-eastern suburbs. Its customer is a homeowner lighting the heater on the first cold night, or a property manager with a list of rentals, and neither knows when the thing was last looked at. The page has one job: say in ten seconds whether it is actually due, what the visit costs, and take the booking — or say plainly that it isn’t due.
Palette: a clean flame and a brass fitting
A correctly burning gas flame is blue, and the fittings it runs through are brass. Flame blue carries every action on the page; brass marks what the regulator says and anything overdue. The yellow-orange of a faulty flame appears once, as a drawn fill in the FAQ, and never behind a word. Declared tokens are quoted from contrast-w60.log; colours minted during the build were measured by gf-check.mjs.
#F4F6FAPage ground. Ink 16.39:1, muted 7.13:1, primary 7.73:1, brass 5.85:1.#FFFFFFCards, form, footer. Ink 17.73:1, muted 7.72:1, brass 6.33:1.#121826Headings, the verdict, figures. Forbidden on band (1.04:1).#4A5366Body copy and ledger labels. Forbidden on band (2.22:1) and primary (1.08:1).#1F47A6Every primary button, white text at 8.37:1. The last-service mark and the not-due dot.#8A5300Eyebrows, quote rules, “Due now” pin, form errors. 5.85:1 on ground; never on band (2.70:1).#101B33The Sapience band with #EEF2F9 at 15.25:1, and the heater’s window behind the flames.#3F74F0Minted. Drawn fill only, 4.05:1 against the glass; no text sits on it.#B7862FMinted. Burner bar, bayonet and pipe. 5.27:1 against the glass.#E9962BMinted. FAQ illustration only, 7.22:1 against the glass; labelled in ink below it on white.#7A8499Minted. Inputs, steppers, chips, map boundary: 3.76:1 on surface, 3.47:1 on ground.#EDF1F8Minted. The wait box, map land, the Now column. Muted 6.81:1, primary 7.39:1.Also minted and measured: hover blue #183A8A (white 10.44:1), error tint #FFF5E8 (brass 5.87:1), placeholder grey #667085 (4.97:1 on white), flame core #BCD0FF (drawn, 11.09:1 on the glass), heater cabinet #E6EBF3 (the sticker sits on white above it), the disabled stepper glyph #8C95A8 (3.01:1 — an inactive control, deliberately quieter), and three decorative lines that never carry meaning: hairline #D5DBE6 (1.39:1), strip baseline #C3CBD9 (1.63:1) and the Swan River #BFD0EA (1.38:1 on map land; its label is muted at 6.81:1).
Type
Red Hat Display 700 for headings, the wordmark, the verdict and every price. It has the rounded, even stroke of lettering on an appliance badge, and at 2.4 rem “Not due for 12 months” still reads as one confident statement. Red Hat Text 400/500 for everything read at length — the quoted Building and Energy sentences, the ledger, the FAQ — designed as the same family’s small-size cut, so the page never switches voice. Tabular numerals throughout, so $189.00 and $190.00 line up in the ledger.
The proof element: “Is it due?”
Three inputs: gas appliances in the home (a 1–5 stepper), age of the oldest (a 0–25 range with the 10 marked on its track), and when it was last serviced (five chips, from “This year” to “Never, or not sure”). The rule is the one sentence on wa.gov.au: at least every two years, and annually once the appliance is more than 10 years old — so ten exactly stays on two years, and the card says so in words at that exact age. “3+ years” is held as three and “never” as no date, which is due on either interval. Prices are the business’s own: $189 for the first appliance, $95 for each extra at the same visit.
The model is eleven lines between /*MODEL*/ markers. gf-check.mjs lifts that block out of the page and compares it with the gas() function in figures-w60.mjs on every reachable cell — 5 appliance counts × 26 ages × 5 answers, 650 cells, six fields each — before any browser opens.
The drawing is inline SVG rebuilt on every change: a gas space heater with a dark glass window, five blue flames on a brass burner bar, two knobs, a brass bayonet and pipe, and a service sticker that reads the answer you gave (“1 yr ago”, or a dashed “NO STICKER”). Beside it, six service years from −3 to +2 with Now shaded: a filled mark at the last service, hollow marks in every service year from the next one on, a black “Last service” pin, a brass “Next due” or “Due now” pin, and a hatched brass span for the years it has been overdue. Two hand-set layouts, 640×236 and a portrait 340×350, are chosen by the drawing’s own width. The entrance — flames lick up, marks drop in — is CSS keyframes on a cold-load flag, so it is wall-clock by nature, finishes by 1.5 s and is absent under reduced motion. There is no animation loop to pause.
| Worked example (figures-w60.mjs) | What the card prints | Today |
|---|---|---|
| 1 heater, 6 years old, serviced 1 year ago | Not due for 12 months · every two years · $189 when it’s due | $0 |
| 3 appliances, oldest 12, serviced 1 year ago (default) | Due now · every year · $189.00 + 2 × $95.00 = $379 | $379 |
| 2 appliances, oldest 8, never or not sure | Due now · every two years · no date to wait from | $284 |
| 1 heater, exactly 10, serviced 1 year ago | Not due for 12 months · “ten exactly is not more than 10” | $0 |
| Full domain, 650 cells | matches gas() cell by cell; floor equals the services grid’s $189 | $189 – $569 |
Where the page argues against its own sale
The cheaper option is to wait, and it is printed in every state, in the same box, at the same size. When the appliance isn’t due the card leads with it: $0 today — it isn’t due. Note the month and we’ll be here
, the price it will be when it is, and a button that reads “Ring me when it’s due” and sets the booking form to exactly that. The probe carries isSubstitute: true. When it is due, waiting is still shown — Or wait: $0 today. Not a substitute.
— with the regulator’s reason for this appliance and isSubstitute: false.
With more than one appliance the card also volunteers the number a gas fitter could quietly keep: three separate visits would be $567, and one visit is $188 less.
Verified at source, 14 September 2026
Every quotation on the site was copied from Building and Energy’s page Gas safety at home on wa.gov.au, re-fetched before a sentence was written. That includes the service-interval sentence exactly as published (“as is outlined the manufacturer’s instructions”), the leak steps and “Do not use your heater again until you have had it checked by a licensed gas fitter.”, the three reasons to have it serviced (a discoloured case, trouble re-lighting, a yellow flame) and the sticker sentence.
What this page declined to publish
- Sale or lease certificates. No claim that WA requires a gas certificate when a home is sold or leased, and no form names.
- A landlord’s legal servicing duty. The rental FAQ says to check the lease and ask the property manager, and states no law.
- Carbon monoxide testing. No test standard, no interval, no claim that a service includes a particular test.
- Emergency numbers. No gas supplier’s line. The safety block quotes Building and Energy’s own steps and ends in our number.
- Rebates. None.
- Market rates. Perth gas fitters charge roughly $105–$210 an hour with call-outs of $84–$190; that band informed the prices and appears only here.
Why this converts
For the owner deciding whether to hire Sapience
The phone number and a “Book a service” button sit in the header, in the hero and in a sticky bar on every phone, because this customer is standing next to a cold heater and wants a person, not a menu. The checker answers the question they can’t answer themselves — is it actually due — with the regulator’s own rule quoted beside it, so the answer reads as a fact rather than a sales line. When the answer is “not yet”, the page says so, prices the wait at $0 and offers to ring when it is due, which is exactly the moment a sceptical homeowner decides this business can be trusted with the next one. The trust line leads with the licence reference and a promise that the price on the page is held at the door, because the fear with a trade is the number that changes once they’re inside. The form asks only what a gas fitter needs to book the run — suburb, appliances, a time — and carries the checker’s answer with it, so nobody explains the heater twice.
Conversion decisions in brief: the hero CTA names the smallest yes, “from $189”, because a first appliance is the whole invoice for most homes; the card CTA carries the exact figure it just computed; the sticky bar splits call and book because property managers book and homeowners ring; the suburb is a select, not free text, so every lead lands on a run; and “Not due yet — ring me when it is” is a real option in “When suits”, so an honest answer is still a lead.
Local SEO we would implement for the real business
The live page carries only WebPage markup naming it a demonstration. For the real gas fitter we would ship business markup like this — shown as a code sample, deliberately not live, every value a placeholder until the client supplies it.
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Plumber",
"additionalType": "https://schema.org/HVACBusiness",
"name": "[Client business name]",
"description": "Licensed gas fitters servicing gas heaters, cooktops, gas hot water and ducted gas heating.",
"telephone": "[client phone]",
"url": "https://[client-domain]/",
"address": {
"@type": "PostalAddress",
"streetAddress": "[client street address]",
"addressLocality": "Morley",
"addressRegion": "WA",
"postalCode": "6062",
"addressCountry": "AU"
},
"areaServed": ["Bayswater", "Bassendean", "Dianella", "Inglewood", "Maylands",
"Nollamara", "Mirrabooka", "Beechboro", "Ballajura", "Morley"],
"openingHours": ["Mo-Fr 07:00-17:00", "Sa 08:00-12:00"],
"hasOfferCatalog": {
"@type": "OfferCatalog",
"name": "Gas appliance servicing",
"itemListElement": [
{ "@type": "Offer", "itemOffered": { "@type": "Service", "name": "Gas heater service" }, "price": "[price]", "priceCurrency": "AUD" },
{ "@type": "Offer", "itemOffered": { "@type": "Service", "name": "Additional appliance, same visit" }, "price": "[price]", "priceCurrency": "AUD" }
]
},
"review": [
{
"@type": "Review",
"author": { "@type": "Person", "name": "[verified customer name]" },
"reviewRating": { "@type": "Rating", "ratingValue": "[1-5]" },
"reviewBody": "[verbatim review from the client's Google Business Profile]"
}
]
}
</script>
Alongside it: a Google Business Profile set up as a service-area business with no public street address, one FAQ page answering “how often should a gas heater be serviced” that quotes Building and Energy rather than paraphrasing it, and an autumn post each year pointing at the checker, when the searches arrive.
What changes for the real client
- Their licence and stickers. The real gas fitter licence replaces
GF000000, and a photo of their actual service sticker can sit beside the drawn one. - Their job management. The booking lands as a job in ServiceM8, Tradify, simPRO or AroFlo with the appliances as job lines and the checker’s answer in the notes, and the confirmation text comes from that system instead of this page.
- Their reminders. “Ring me when it’s due” becomes a recurring job or reminder in the same system, set to the month the checker computed — the not-due branch turns into next year’s booking.
- Property managers. A second form path that accepts a list of addresses and groups them by suburb, feeding a single multi-site quote.
- Their reviews. A live feed from their Google Business Profile replaces the demo copy, with the same suburb-and-appliance framing.
- Their accounts. Invoices raised in Xero from the job, with the same $189 and $95 lines the ledger printed.
Iteration log
Every pass re-read the code, then drove the page in headless Chrome: the checker to all four worked examples and both ends of its range, the form through empty, junk and filled submissions, asserting the printed text rather than the model. The final run was 40 of 40 in the page harness, 7 of 7 in the hardening script, and 650 of 650 model cells against figures-w60.mjs.
Pass 1 · Craft
- The service-area map had never drawn. The projection was handed to
Array.map, which passes each coordinate pair and its index as longitude and latitude, so the boundary and the river were NaN paths and the only symptom was a console error. The first fix projected the points a second time and still failed; the second removed the pre-projection. The map now asserts three paths with no NaN and nine suburb dots. - The hero quote card sat 40 px in from the column. A browser default margin on
blockquote; reset. - A wrapped verdict centred its dot on two lines. “Not due for 12 months” wraps in the answer column at 1200, and the status dot floated between the lines. Aligned to the first line, and the desktop verdict stepped down to 2.15 rem.
- The strip legend explained a pattern that wasn’t there. “Overdue” now appears in the legend only when the hatched span is drawn.
- Two first-run failures were the check, not the page: a dollar regex that kept the comma in “$189,”, and an honesty scan that failed this guide for listing what the page declined to publish.
- Driven and asserted: 1 heater, 6 years, 1 year ago → Not due for 12 months, $0 today, $189 when it’s due; 3 appliances, oldest 12 → Due now, $189.00 + $190.00, $379; 2 appliances, oldest 8, never → Due now, $284; exactly 10 → two years; 11 → due; 5 appliances → $569 with the stepper disabled. Empty submit flags five fields and focuses the name; “ring me maybe” in the phone is refused while the rest clears; a filled form confirms locally with the checker’s $379.
Pass 2 · Depth
- The rule’s one subtlety became something to press. The FAQ on service intervals now carries two buttons, “Try a heater that’s exactly ten” and “Now one that’s eleven”, which set the checker and scroll to it; the first prints “ten exactly is not more than 10”, the second flips to Due now. Both are driven and asserted.
- Only what changed moves. Changing an answer pops in only the marks that are new on the strip, identified by kind and year rather than by index; moving from “1 year ago” to “This year” on an annual interval is asserted to pop exactly one mark. Ledger lines whose amount changed, and the today figure, take a brief blue tint.
- Second reads in the drawing. The sticker on the heater reads your answer back (“1 yr ago”), and with no record it turns dashed and reads “NO STICKER”; years overdue are hatched in brass between the missed due year and Now; the SVG description is rewritten on every change so a screen reader hears the same verdict.
- The not-due branch ends in an action. Its button reads “Ring me when it’s due” and presets the form’s “When suits” to exactly that, so declining the visit is still a lead; the confirmation then says it will ring in about 12 months.
Pass 3 · Hardening
- The sticky bar’s Book button landed the form under the masthead. At 375 the heading finished at 44 px behind a 67 px sticky header, and the FAQ buttons scrolled the checker the same way.
scroll-padding-topon the page fixes every anchor andscrollIntoViewat once; the landing is asserted. - Map labels were 9 px at 375. Suburb names went from 13 to 16 in the drawing’s units and the map is capped at 460 px, so they read at about 11 px on a phone without growing past 16 px on a desktop.
- The headline set in four lines at 1440. Its size cap came down from 3.4 to 3 rem; it is two lines at 1200, where the thumbnail is taken.
- One ornament removed. A faint reflection streak across the heater’s glass. It read as a smudge at phone size and the flames carry the window without it.
- Keyboard and motion. Tab reaches the age range with a 3 px flame-blue ring; two presses of the left arrow take 12 years to 10 and flip the verdict. Under reduced motion the probe is settled at load and no entrance class is applied. A resize from 1200 to 375 and back swaps the drawing between its two layouts, keeps the answers and never replays the entrance.
- 375 and the fold. No horizontal overflow on either page; the headline, primary button and trust line finish above the sticky bar with 100 px to spare at 375×667; the bar is a
navowning a realtel:link and an anchor to the form, and every tap target in it, the checker and the FAQ is 44 px or more. The fold rule was negative-tested by growing the bar to 260 px, and it fired. - Rest and honesty. The probe is identical at 7 s and 15 s and the proof card’s computed grid column is 2 at 1200. Both files were scanned for curly-quoted attributes, street-type nouns, phone numbers outside
(08) 9000 0601, AI attribution and unverified claims: clean. The phone field’s pattern compiles under thevflag, and zero console errors at 375, 768, 1200 and 1440.
Techniques
One self-contained HTML file per page, no libraries, Google Fonts only. Inline SVG built with createElementNS for the heater and service strip, the FAQ flame comparison and the service-area map (suburbs projected from longitude and latitude at render time, the Swan River along the south). CSS keyframes for a single cold-load entrance. A state probe, window.__gasfitter, exposes the inputs, the interval, due and months-until, every ledger figure, today’s price, the wait alternative with isSubstitute and its reason, the same-visit saving, the range, and what the drawing placed.