Generative Assets · Industry demo 279 · Barbershop
How the Crosscomb Barbers demo was built
A fictional two-chair barbershop on the Albany Highway strip, Victoria Park, built as a conversion demonstration for main-street personal-services businesses. The page has one job: put a visitor in a chair — build the service, see the honest maths, hold the slot.
Concept
Crosscomb Barbers sells twenty good minutes: the same cut every third Saturday, first cuts for kids, a beard lined properly. The audience is the customer first — a bloke on his phone deciding whether to book or walk in — so the signature element is the Chair Menu: cut chips and a beard toggle driving a procedural SVG fade chart (the real guard recipe), a Courier price ticket that does the arithmetic in front of you, and a demo day board that honestly greys the gaps your service won't fit.
Palette — oat & oxblood, argued from the shop
Deliberately not the banned cream-and-terracotta default: the ground is a warm oat (morning light on painted plaster), the red is a deep oxblood leather, the display face is a grotesque, and the marks are barber-pole geometry.
Type
- Bricolage Grotesque (display) — a characterful grotesque with just enough signwriter swagger for a heritage shopfront; carries the masthead and headings.
- Work Sans (body/UI) — legible workhorse at 16.5px, professional without coldness.
- Courier Prime (the till) — price ticket, day board and guard labels set in the receipt face, so every number reads as printed, not marketed.
The signature: Chair Menu, fade chart & day board
The fade chart is a procedural SVG scene — brass-framed mirror, side-profile head in espresso line-work, and clipper passes generated in JS as short oxblood strokes between per-band y-extents, with guard numbers pencilled in brass-ink at each band (skin · 0.5 · 1 · 2 · 3 · scissor). Each cut re-renders the chart honestly: classic is scissor-over-comb with a 2 taper, the skin fade lays all five bands with a cross-hatched blend zone, buzz is one guard all over, kids is a soft 3 taper, seniors is the classic with the sides tightened. The beard toggle draws the jaw outline, its own guard work and a "1.5 cheek · 3 body" mark. Strokes draw in via stroke-dashoffset with a dual-speed stagger — 26 ms/element (capped 1.5 s) on first render, 11 ms (capped 520 ms) on every subsequent change — and a window.__barber probe carries the live state with a settled flag. The blend hatch breathes between opacity 0.58 and 1 (never below the visibility floor); reduced motion renders everything complete and static.
The day board is a 16-slot 15-minute lattice (9:00–13:00) with fixed demo bookings. A service needs ceil(minutes/15) consecutive free slots before close: a 60-minute fade-plus-beard fits only 11:30/11:45/12:00, while a 15-minute buzz opens every free gap — so the board visibly responds to the menu, and the greyed slots say why. Picking a slot writes it to the ticket and pre-fills the booking form.
Pricing model — pinned anchors, measured outputs
All prices are pinned anchors from the wave spec; the bundle rule is uniform: beard sculpt with any cut saves $6 — one chair, one gowning, one clean-up; the saving is real overhead, not marketing. Durations sum honestly and drive the board. Measured from the running page (independent oracle in the QA harness, every combination driven):
| Service | Solo | + beard ($22, 15 min) | Bundle maths |
|---|---|---|---|
| Buzz cut · 15 min | $25 | $41 · 30 min | 25+22−6 |
| Kids u12 · 20 min | $28 | — disabled | "lollipop, not a beard service" |
| Seniors · 30 min | $30 | $46 · 45 min | 30+22−6 |
| Classic cut · 30 min | $38 | $54 · 45 min | 38+22−6 |
| Skin fade · 45 min | $48 | $64 · 60 min | 48+22−6 |
The rest state (skin fade + beard) measured $64 · 60 min — exactly the spec's pure-anchor arithmetic, no corrections. The menu's cheapest output ($25 buzz) equals the services grid's "cuts from $25" floor. The kids rule follows LEARNINGS W18: a control with no honest meaning is disabled with its reason on the page, never left computing nonsense. The apprentice's $20 Tuesday price lives outside the menu as its own quiet line, stated as supervised and disclosed before you sit down.
Conversion decisions
- Above the fold at 375px: name + trade + suburb in the single h1, primary "Book a chair" CTA, tel link, and three trust chips — all inside the first viewport, verified by rect probe and screenshot.
- CTA placement: header button, hero pair (book + call), a "Book this chair" button on the ticket itself (the moment the maths convinces), and a mobile sticky call/book bar with ≥48px targets.
- Form fields: name, phone, suburb, first-visit select, preferred day (
type=date, min today) and the service line pre-filled from the menu — the signature-to-form handoff that turns a demo interaction into a warmer lead. Confirmation is local and honest: it says plainly that nothing is sent, no fake network round-trip. - Walk-in honesty: the shop's defining rhythm — bookings win the chair, walk-ins ride the gaps, the board is the truth — is the hero note and a shop-rules card, because it's the question every walk-in trade gets asked first.
Local SEO we'd implement for the real business
This demo page ships WebPage schema only — fictional structured data in a real index is dishonest markup. For a real barbershop client, we'd ship the full local pack (rendered here as a code sample, not live markup):
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "BarberShop",
"name": "Crosscomb Barbers",
"image": "https://example.com.au/shopfront.jpg",
"telephone": "+61 8 XXXX XXXX",
"priceRange": "$25–$64",
"address": {
"@type": "PostalAddress",
"streetAddress": "Albany Highway",
"addressLocality": "Victoria Park",
"addressRegion": "WA",
"postalCode": "6100",
"addressCountry": "AU"
},
"geo": { "@type": "GeoCoordinates", "latitude": -31.9762, "longitude": 115.9048 },
"openingHoursSpecification": [
{ "@type": "OpeningHoursSpecification",
"dayOfWeek": ["Tuesday","Wednesday","Thursday","Friday"],
"opens": "09:00", "closes": "18:00" },
{ "@type": "OpeningHoursSpecification",
"dayOfWeek": "Saturday", "opens": "08:00", "closes": "14:00" }
],
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": "4.9", "reviewCount": "180"
},
"review": [{
"@type": "Review",
"author": { "@type": "Person", "name": "Real Customer" },
"reviewRating": { "@type": "Rating", "ratingValue": "5" },
"reviewBody": "Pulled from the live Google reviews feed."
}]
}
</script>
Plus a Google Business Profile tuned for "barber victoria park" / "skin fade near me", service-level pages fed from the booking system, and review markup sourced from the real feed — never written by hand.
What would change for the real client
- Fresha or Squire chair booking with barber selection — the demo day board becomes the live one, same visual, real lattice.
- SMS reminders & no-show protection wired to the booking flow (deposits on Saturday-morning slots if no-shows bite).
- Square POS with service-level reporting — the ticket's bundle maths reconciled against the till.
- Xero for the books; Google review flow replacing the demo quotes with the live feed.
- Walk-in board screen in the window fed from the booking system — the "board is the truth" promise, automated.
- Their photos, their barbers by name, their real ABN/licence details replacing the placeholder patterns.
Techniques
- Single-file vanilla build: all CSS/JS inline, Google Fonts the only external resource. No libraries.
- Procedural SVG everywhere a photo would be: the fade chart scene (JS-generated strokes, deterministic jitter), the Perth service-area map, the favicon pole.
- CSS-only barber pole (repeating diagonal gradient translated one full period, seamless loop), ticket-total bump micro-interaction, entrance choreography gated on
document.fonts.readywith an 900ms fallback and a no-JS-safe reveal class. - Second-read detail: the shopfront window lettering caught reversed in the mirror — an arc of ghosted brass "CROSSCOMB" you only notice on the second visit.
- DPR-independent (no canvas), rAF-free at rest except CSS loops; reduced-motion kills the pole, the hatch breathing, the dot pulse and all draw-ins, rendering the chart complete.
Iteration log
Pass 1 — Craft
Drove every cut chip × beard state through an independently reimplemented pricing oracle: 92 asserts — all bundle sums ($54/$64/$41/$46), all durations (fade+beard = 60 min), board fits per service, ticket text, chart recipe per cut — all measured green, zero console errors. The screenshot review (the part the asserts can't do) caught three real defects: the beard guard labels collided with the fade band labels and their leader lines crossed the bands — moved to a single "beard · 1.5 cheek · 3 body" caption under the chin; the ear read as a floating "O" — redrawn larger with an inner helix line; the clipper passes were cramped scribble — lengthened to fill their band rows, jitter halved, columns aligned. Also fixed a malformed x attribute on the map's Como label.
Pass 2 — Depth
Added the mirrored window-lettering second read (first placed mid-mirror where it collided with the crown scissor marks — raised to the mirror rim), the ticket-total bump on every price change, and trimmed the two-line recipe caption to one. Drove the whole conversion path: fresh-load empty submit raised 4 invalid fields (fresh navigation per W15, since the signature pre-fills the form at rest — verified the pre-fill separately), filled submit rendered the local confirmation in the lexicon family with the demo honesty line; FAQ driven by dispatched click and by keyboard; the menu driven by keyboard (Enter selects, toggles); drive-then-diff liveness passed; subsequent redraws settled well under the dual-speed cap (measured <1.1 s wall-clock including the settle margin, vs 1.5 s+ entrance).
Pass 3 — Hardening
375px masthead first (W18/W21): wordmark vs tel-link edges compared explicitly, no collision, screenshot eyeballed. Full-page 375 sweep: scrollWidth 375, zero partially-overflowing elements, every section stacking cleanly. Reduced-motion emulation: settled immediately, pole animation off, zero undrawn strokes. Focus-visible outline verified on chips. Honest 7-second 1200×750 thumbnail capture: the fade chart sits mid-frame — the signature shares the thumbnail viewport with the hero (W19), asserted via bounding rect. Chanel rule: removed one of the map's minor road lines. Structural sweep: exactly one h1 per page, canonical + OG, WebPage-only JSON-LD, reserved-phone positive sweep (every (08) 9xxx number on the 9000 exchange), date min = today.
Honesty rules
Crosscomb Barbers is fictional and the page says so plainly in the colophon. The phone number uses the reserved (08) 9000 0279 pattern; the address is suburb-level; the ABN is visibly placeholder. Reviews are demo copy, never structured data. Every price on the page is one internally-consistent model, measured from the running code. The one branch not exercisable headlessly is nothing — every interactive branch on this page (chips, toggle, disable rule, slots, form, FAQ) is driven in the harness.