Build guide · Industry demo

Latchford Locksmiths — how the midnight porch-light page was made

Back to the site · Collection index

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

#111B2B

Night navy — the ground; the street after the porch light.

#D7DEE8

Moonlit steel — body ink on navy (12.6:1); the keyway’s milled edge.

#C9973F

Key brass — LINE/MARK only: dial marks, key silhouettes, CTAs. Never long body text.

#7FA3C4

Deadbolt blue — links, focus rings, active chips; the night sky’s last blue.

#9FACC0

Derived secondary ink (7.4:1 on navy) — the spec names brass as a mark token, so a proper dim steel was minted for secondary copy.

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

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

JobPriceDial-sensitive?
Emergency lockout — standard (7am–6pm)from $165yes — band-priced
Emergency lockout — after hours (6pm–10pm)from $240yes
Emergency lockout — late night (10pm–6am)from $290yes
Rekey after moving (callout + 3 cylinders, then $25/cyl)from $180no — booked work
Lock repair & replace (+ parts at cost)from $145no — booked work
Security upgrade visit (quoted per opening)from $190no — booked work

Two deliberate shape decisions, documented so QA has a source of truth:

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

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

Techniques

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 pattern attribute used unescaped parens in a character class — valid in classic regex, a SyntaxError under the v-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 opacity attribute read 0 — the glow-pulse CSS 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-visible ring (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.