Palette — argued from the counter
Butcher's paper, the block, the cool room and the sage of twine. All ratios below were measured with WCAG arithmetic from the shipped tokens, not assumed (the spec's figures were hypotheses; measurement confirmed all of them, no two-token remedy needed).
Type
Alfa Slab One for display — the slab a signwriter paints on a butcher's window, used for the wordmark, headings, prices and the pack-weight line ("8.7 KILOS, WRAPPED IN PAPER"). Source Sans 3 carries the body: legible at 16px+ on a phone in one hand, which is where trade traffic lives. The typographic bravery is the weight line — the biggest thing on the page after the h1 is a number of kilograms, because that's what a pack customer is actually buying.
Techniques
- The Pack Builder — one inline SVG scene (960×560): a side-profile steer with seven cut sections (chuck, blade, rib, loin, rump, brisket, shank) drawn as rects clipped to the body silhouette with a
clipPath, so section edges follow the animal. Pack chips light the sections that pack draws from and rebuild the parcel stack — one twine-tied paper parcel per line item, built withcreateElementNS, dropped in with CSS transitions. Loin and rib never light: packs are built from the working cuts, and the scene says so. - Dual-speed redraw — first render staggers parcels at 110 ms each (entrance choreography); every later state change redraws at 34 ms per parcel, capped ~420 ms, via a
firstDrawflag. A render token is bumped in every control handler before the state swap, so an in-flight stagger or settle timer can never act on a stale parcel list (rapid-fire clicking is asserted in QA, twice over). - The glint — a gradient bar sweeping the knife rack on rAF with
dt = min(max(now−last,0)/1000, .05)(clamped at zero and capped) and an opacity floor of 0.55, so the thumbnail capture can never catch it invisible. Paused ondocument.hidden; a static 0.8-opacity frame underprefers-reduced-motion. - Everything procedural — steer, parcels, van, chook, knives and the metro map are hand-coordinated SVG; no raster anywhere. The state probe
window.__butcherexposes the full pricing state plus asettledflag, and QA gates on state, never on seconds.
Honest numbers — the pricing contract
Every figure is pinned arithmetic from the pack anchors, verified in QA against an independently reimplemented oracle. The add-on arithmetic is the services-grid per-kilo pricing: extra mince +$18/kg, wings $24 for 2 kg = $12/kg, biltong $32 for 500 g = $64/kg — asserted, not coincidental.
| Scenario | Arithmetic | Total |
|---|---|---|
| Rest — Family pack, pickup | 185 | $185 |
| Family + delivery | 185 ≥ 160 → free | $185 · "free delivery applied" |
| Weeknight + delivery | 110 + 10 | $120 · "free from $160" note |
| Boundary — Weeknight + 1 kg mince + biltong + delivery | 110+18+32 = 160 → free | $160 (exact threshold) |
| Nearest below — Weeknight + 2 kg mince + free-range + delivery | 158 + 10 | $168 + note |
| Freezer-filler + 4 kg mince + delivery | 422 → free | $422 |
| Cheapest output — Weeknight, pickup | 110 | $110 = grid floor |
- One clamp semantic: the $160 free-delivery threshold applies to the goods subtotal before the delivery fee, and the boundary is reachable exactly ($160 on the nose → free).
- Disabled with a reason: the free-range chicken upgrade is disabled on the Freezer-filler — "the freezer-filler is straight beef — nothing to upgrade" — and is stripped (not remembered) when you step into that pack, so the ledger never carries a dishonest line.
- The price-less line: whole-beast and custom carcase breaks print no number anywhere — "quoted by the kilo on the day's carcase — the market sets it, we pass it through." The declaration is the deliverable; a figure would be the dishonesty.
- Spec correction (measured): the concept's illustrative weight line read "SEVEN KILOS"; the pinned pack contents sum to 5.3 / 8.7 / 15 kg, so the display is computed from the real contents — the rest state honestly reads 8.7 kilos and re-weighs with every add-on.
Conversion decisions
- CTA order: "Build your pack" outranks "Call" in the hero — the builder is the qualifier; a caller who has already priced a Family pack plus biltong is a warmer lead than a cold ring. On mobile the sticky bar flips that: call first, because Saturday-morning intent is a phone call.
- Form fields: name, phone, suburb (delivery is suburb-gated), a pickup-day select matching real shop days (Tue–Sat), and a pre-filled pack summary the builder writes into the form on every change — the W12 signature→form handoff, so the enquiry arrives already speaking the shop's language.
- Price badge in the scene: the total lives on a butcher's ticket tied to the parcel stack, inside the 1200×750 thumbnail, because the capture is the commercial argument. Below 600px the ticket drops its subtitle so it never covers the parcels at 375.
- Confirmation: a designed panel, hidden until a valid submit, that echoes name, phone, day and fulfilment — plus a plain line admitting no order was sent. Never a fake network round-trip.
Local SEO we'd implement for the real business
This demo ships only WebPage/CreativeWork/BreadcrumbList schema, because fictional structured data in a real index is dishonest markup. For a real Hawthorn Cut we'd ship the block below. Schema.org has no butcher subtype — no ButcherShop exists — so the honest top-level type is GroceryStore, the nearest food-retail parent, sharpened with a precise description and keywords rather than a made-up type.
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "GroceryStore",
"name": "Hawthorn Cut Butchery",
"description": "Whole-carcase butcher shop. Beef broken down by hand on site; meat packs, house sausages and dry-aged steak.",
"keywords": "butcher, meat packs, whole carcase, dry aged beef",
"url": "https://hawthorncut.com.au",
"telephone": "+61 8 XXXX XXXX",
"priceRange": "$$",
"address": {
"@type": "PostalAddress",
"addressLocality": "Mount Hawthorn",
"addressRegion": "WA",
"postalCode": "6016",
"addressCountry": "AU"
},
"geo": { "@type": "GeoCoordinates", "latitude": -31.92, "longitude": 115.83 },
"openingHoursSpecification": [
{ "@type": "OpeningHoursSpecification",
"dayOfWeek": ["Tuesday","Wednesday","Thursday","Friday"],
"opens": "08:00", "closes": "17:30" },
{ "@type": "OpeningHoursSpecification",
"dayOfWeek": "Saturday", "opens": "07:00", "closes": "13:00" }
],
"hasOfferCatalog": {
"@type": "OfferCatalog",
"name": "Meat packs",
"itemListElement": [
{ "@type": "Offer", "name": "Weeknight pack", "price": "110", "priceCurrency": "AUD" },
{ "@type": "Offer", "name": "Family pack", "price": "185", "priceCurrency": "AUD" },
{ "@type": "Offer", "name": "Freezer-filler", "price": "350", "priceCurrency": "AUD" }
]
},
"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 Business Profile feed)"
}]
}
</script>
Beyond markup: a Google Business Profile with weekly photo posts of the actual cabinet (butchers win local search on photos), suburb-level service pages only if delivery genuinely differs by suburb, and review acquisition wired to the counter — a QR on the wrap.
What would change for the real client
- Square at the counter and behind the order form — the pack builder's output becomes a real order with payment on pickup, and the in-shop catalogue stays the single price source so the site can never argue with the till.
- Shopify click-and-collect if they outgrow form-and-phone: packs as products, the builder as a themed product configurator, collection slots per shop day.
- Xero reconciliation off Square's daily settlement, so Saturday's packs land in the books without a bookkeeping session on Sunday (the knives rest, so should the owner).
- Their photos where the drawn steer supports them, their real Google reviews replacing the demo copy, real ABN/HACCP details, and delivery-run postcodes wired to the suburb field for instant "we deliver to you" feedback.
Iteration log
Pass 1 — Craft
Measured every contrast hypothesis with WCAG arithmetic: smokehouse ink 13.9:1, oxblood on ground 7.3:1, white on oxblood 8.2:1, sage-ink 5.3:1 — all clear, sage itself kept to lines and twine only. Removed a duplicated counter rect with a corrupted stroke attribute caught on code re-read. Tightened the ledger's dashed rules and gave the docket its own ink-dark card so the total reads like a handwritten chit. Rewrote three service-card blurbs that had drifted into filler ("quality cuts daily") toward counter language ("they rarely make it home intact").
Pass 2 — Depth
Added the chook standing by the counter with its "chooks from the next shed" tag — it re-labels to "free-range chooks, as asked" when the upgrade is on, so the toggle answers in the scene, not just the docket. Gave parcels a per-parcel ±2° rotation off a hash so the stack reads hand-stacked, and phased the entrance stagger against the snappy redraw (110 ms/parcel first draw, 34 ms after). The scene caption ("loin and rib stay on the counter") turns the two permanently-unlit sections from an oversight into the shop's actual argument.
Pass 3 — Hardening
The thumbnail probe measured the price ticket at y=804 against the 750px fold at 1200 wide — compacted the hero and builder rhythm and lifted the ticket in the scene; it now lands at ~706 with the steer. The native-resolution label-collision sweep caught four real defects no assert saw: the twine line striking through every one-line parcel label (fixed with drawn hand-label plates), the chook's tag hidden behind the bottom-left parcel in 4-wide stacks (the chook now stands under the counter lip), the vertical SHANK label buried behind top-row parcels (now horizontal, above the stack), and the van wordmark clipping its own cab. Masthead checked first at 375: wordmark right edge clears the compacted call button; the ticket's tight variant drops its subtitle below 600px and parcel labels step up to 15.5px. Re-ran the full suite after the fixes — 120/120: exact $160 boundary and $158 below-cell against the independent oracle, every control plus two rapid-fire rounds, zero console errors, reduced-motion static glint. Chanel rule: deleted a planned sawdust-speckle counter texture — the scene carries without it.