The concept
Latchford Locksmiths is a fictional emergency & security locksmith based in Victoria Park, Perth — built for the person standing on their own dark porch at 11pm, and for the landlord booking a settlement-day rekey. The page has one job split by urgency: call now if you’re locked out, book a visit for everything else. The art direction is the porch light on when you finally get home — night-calm competence, zero fear-mongering, and the honest price said before the van starts.
Palette — the front step at 11pm
This is the wave’s first dark site. Per the ink/line split (LEARNINGS W2/W5), brass never sets body copy — it measures ~6.5:1 on navy so it’s legal for short marks and the mono price, but all running text is moonlit steel (12.6:1) or the derived dim steel (7.4:1). Both clear 4.5:1 on every surface they sit on, including the raised card blues.
Type
- Red Hat Display 700/800 — masthead, headings, buttons. Round-cut terminals like a freshly milled key; sturdy without shouting at midnight.
- Red Hat Text 400/500/700 — body and UI. Same family, so the whole page reads as one cut of metal.
- Red Hat Mono 400/500/700 — every number that matters: prices, the dial readout, licence strings, suburb pills. Mono is the “this is the actual figure” signal.
The signature — the Callout Estimator
A procedural SVG 24-hour clock dial (6am at the top, 15° per hour) is a real accessible control: role="slider" with aria-valuenow in minutes, draggable via Pointer Events (atan2 from the dial centre → minutes, snapped to 30-minute steps) and keyboard-steppable (arrows ±30 min, PageUp/Down ±3 h, Home = 7am, End = 10pm). Three band arcs are drawn on the face — standard in deadbolt blue, after-hours in dim brass, late-night in full brass — so the price structure is visible before you touch anything. The hand’s rotation lives in an SVG transform attribute set by JS, with no CSS transform anywhere near the group (LEARNINGS W16: a stylesheet transform silently discards the attribute).
The dial drives two things at once. The scene — a Victoria Park porch drawn in SVG — interpolates its sky gradient stops between day and night in JS, fades the stars and moon in, warms the porch-light halo and window glow after dusk, and parks the Latchford van at the kerb. The price card re-renders from a single pricing object, and every estimator change pre-fills the booking form’s job select and details field (the W12 calculator→form handoff) — required fields stay empty so the fresh-load empty-submit validation probe remains honest (W15). At rest the page is mid-demonstration: 11pm, locked-out selected, $290 showing, porch light lit — a premium static thumbnail with no interaction required. Second-read detail: between midnight and 4am a cat appears on the front step.
The pricing model — shape and derivation
| Job | Price | Dial-sensitive? |
|---|---|---|
| Emergency lockout — standard (7am–6pm) | from $165 | yes — band-priced |
| Emergency lockout — after hours (6pm–10pm) | from $240 | yes |
| Emergency lockout — late night (10pm–6am) | from $290 | yes |
| Rekey after moving (callout + 3 cylinders, then $25/cyl) | from $180 | no — booked work |
| Lock repair & replace (+ parts at cost) | from $145 | no — booked work |
| Security upgrade visit (quoted per opening) | from $190 | no — booked work |
Two deliberate shape decisions, documented so QA has a source of truth:
- Booked work is flat against the dial. The spec pins after-hours pricing only for lockouts. Rekeys, repairs and upgrades are booked jobs done 7am–6pm, so moving the dial to 2am doesn’t surcharge them — the card instead says we’d book the next standard-hours slot. Shipping this flatness as a stated policy (“after-hours pricing applies to emergency lockouts only”) is the honest model per LEARNINGS W15, not an invented gradient.
- The 6–7am hour bills as after-hours. The spec’s bands (7am–6pm / 6pm–10pm / 10pm–6am) leave 6–7am unpinned on a dial that sweeps 6am–6am. An early start isn’t a late night, but it isn’t standard hours either — it’s priced at the $240 after-hours rate.
The estimator’s cheapest possible output — lock repair at $145 — equals the services grid’s “from $145 + parts at cost” floor, so the page never argues with itself (W12/W14 cross-surface rule). The three lockout bands appear verbatim in the estimator, the services grid, and the “what does a lockout actually cost” FAQ, all rendered from one PRICES object in the page and cross-checked by the harness against an independently reimplemented oracle.
Conversion decisions
- Call outranks form, everywhere. A locksmith’s highest-value visitor is locked out on a phone. The header carries a brass
tel:button, the hero’s primary CTA is the number itself, the mobile sticky bar leads with “Call now”, and the estimator’s lockout card says plainly: don’t fill a form — call. - The form books daylight work only. Name, phone, suburb, job, details — five fields, nothing a tired person can’t answer. Front-end validation with inline messages; the confirmation is local and honest (“this is a demonstration, nothing was sent”) with no fake network delay.
- The signature is the price list. Instead of burying callout bands in fine print, the dial makes the after-hours structure the interactive centrepiece — the thing a competitor’s site hides is the thing this page demonstrates.
- Trust is licensing, not adjectives. WA locksmiths operate under Police security-agent licensing — the trust strip leads with the (placeholder) licence number and the ask-to-see-it police-clearance line, plus the no-damage-first policy.
Local SEO we’d implement for the real business
This demo page deliberately ships only WebPage schema naming it a demonstration — fictional structured data in a real index would be dishonest markup. For a real Latchford, we’d ship:
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Locksmith",
"name": "Latchford Locksmiths",
"telephone": "+61-8-XXXX-XXXX",
"url": "https://latchfordlocksmiths.com.au",
"address": {
"@type": "PostalAddress",
"addressLocality": "Victoria Park",
"addressRegion": "WA",
"postalCode": "6100",
"addressCountry": "AU"
},
"areaServed": ["Victoria Park","East Victoria Park","Lathlain",
"Carlisle","Burswood","Rivervale","Kensington","South Perth",
"Como","St James","Bentley","Welshpool"],
"openingHoursSpecification": [
{ "@type": "OpeningHoursSpecification",
"dayOfWeek": ["Monday","Tuesday","Wednesday","Thursday",
"Friday","Saturday","Sunday"],
"opens": "00:00", "closes": "23:59",
"description": "24-hour emergency line" }
],
"priceRange": "$$",
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": "4.9", "reviewCount": "187"
},
"review": [{
"@type": "Review",
"author": { "@type": "Person", "name": "Real Customer" },
"reviewRating": { "@type": "Rating", "ratingValue": "5" },
"reviewBody": "Fed from the live Google Business Profile."
}]
}
</script>
— plus suburb-level service pages, a Google Business Profile wired to the review flow, and tel: call-tracking numbers per channel.
What would change for the real client
- ServiceM8 job dispatch with on-the-way SMS — the “20–40 minutes” promise becomes a live tracked ETA.
- Square or Stripe tap-to-pay at the door — the phone-price-equals-invoice promise settled on the spot.
- Xero invoicing synced from job cards; no re-keying (of the data kind).
- Geotab GPS on the vans — honest dispatch of whichever van is genuinely closest at 2am.
- Podium review flow into the Google Business Profile — the three demo reviews become a live feed.
- Their photos, their licence numbers, their real bands — the dial stays; the numbers become theirs.
Techniques
- Procedural SVG throughout — porch scene, clock dial, service-area map, hero key, favicon. No raster images anywhere.
- JS-interpolated SVG gradients — the scene’s sky lerps its two gradient stop colours per dial move; stars, moon, sun, porch halo and window glow fade on derived curves of a single darkness function.
- Pointer Events + slider semantics — one code path for mouse, touch and pen drag; full keyboard operation with
aria-valuetextannouncing time and band. - CSS-only motion elsewhere — star twinkle and porch-glow pulse are opacity keyframes (phase-safe at the 7s thumbnail: mid-animation frames are always fully composed); entrance is a fonts-gated staggered rise with a 1.2s fallback timer and a no-JS visible default.
prefers-reduced-motionkills all of it and renders the settled night scene. - State probe —
window.__locksmithcarries minutes, band, job, price, scene darkness, form status and FAQ state, updated on every interaction, so QA gates on state rather than wall-clock.
Iteration log
Pass 1 — Craft (57 driven asserts)
- Drove the full page headlessly against an independently reimplemented pricing oracle: rest state 11pm / locked-out / $290; keyboard-stepped the dial (ArrowRight ×2 → midnight $290, Home → 7am $165, PageUp/arrows → 2pm $165, End/PageDown → 8pm $240, 6:30am early-start → $240); pointer-dragged the hand to noon; drove every job chip via dispatched MouseEvents; the model’s cheapest output measured $145, matching the services-grid floor. Empty submit on a fresh load raised 3 invalid fields with inline errors and focus on the first; filled submit rendered the “Thanks — request received” confirmation.
- Real bug found by the drive: the phone field’s
patternattribute used unescaped parens in a character class — valid in classic regex, a SyntaxError under thev-flag compilation HTML pattern now uses, throwing on every validity check. Escaped the class; console went to zero. - Screenshot review: the dial’s cardinal labels (6am / noon / 6pm / 12am) sat on top of the band arcs. Moved them inward to a clean inner radius.
Pass 2 — Depth
- The screenshot caught what 28 green asserts sailed past: at noon the porch-light halo still glowed on screen while its
opacityattribute read 0 — theglow-pulseCSS opacity animation was overriding the SVG presentation attribute (the W16 family: a stylesheet rule silently beats the attribute). Fixed by splitting layers — the animation lives on a wrapper<g>, JS drives the circle’s own opacity — and re-verified computed opacity 0 at noon. - Drove the scene sweep to known states: noon (sun out, stars 0, porch off, caption flips to “porch light off”), dusk 6:30pm (darkness mid-ramp 0.625), 2am (cat on the step — the second-read detail — porch lit), and the exact 6pm band boundary (5:30pm standard $165 / 6:00pm after $240). Estimator pixels diffed before/after the drive (liveness).
- Added the price-tick micro-interaction — the mono figure dips in from below when the number changes, disabled under reduced motion — and verified it fires on a driven change.
- Swept honesty mechanics: all four (08) 9xxx occurrences use the reserved 9000 0242 exchange, all eight
tel:hrefs identical; live JSON-LD is WebPage-only naming the demonstration; cross-surface grep tied the bands, $180/$25 rekey, $145 floor, $190 visit and $420 smart-lock figures across estimator, services grid, FAQ and reviews.
Pass 3 — Hardening
- 375px: scrollWidth exactly 375, wordmark and header call button inside the viewport; h1 + call CTA (48px) + licence trust line all above the fold; sticky call/book bar fixed in the bottom 140px with both tap targets 48px; estimator chips all ≥44px; chip drive re-verified at mobile ($145).
- Reduced-motion emulation: hero visible immediately, twinkle/pulse animations report
none, the 11pm rest demonstration intact — screenshot verified settled. - Focus path: skip link surfaces at top-left on first Tab; dial focusable with visible
:focus-visiblering (it’s in the Pass 1 estimator capture). - Chanel rule, from the fold screenshot: the brass “LATCHFORD LOCKSMITHS” eyebrow inside the h1 duplicated the header wordmark 60px above it. The brand stays in the h1 for search and screen readers but is now visually hidden — the hero opens straight on the promise.
- Guide page held to the same floor: exactly one h1, no live JSON-LD (the Locksmith schema is an escaped text panel), 375px clean. Zero console errors across every page and drive; guide numbers re-read against final code before commit.
Honesty footnote
Latchford Locksmiths is fictional. The phone number uses the reserved (08) 9000 0242 pattern, the licence and ABN strings are visibly placeholders, the address stays at suburb level, and the reviews are demonstration copy carrying no structured data. Prices are argued from the 2026 Perth market: metro lockout callouts commonly run $150–$200 in hours and $250–$350 late at night; rekeys land $150–$250 plus per-cylinder charges. Every figure shown is computed by the page’s single pricing object and verified against an independent oracle in the QA harness.