A fictional Nedlands dry cleaner, built as a conversion demonstration: one landing page whose job is a pickup booking, argued around the trade's most honest object — the counter ticket.
The Careful Press is a fictional dry cleaning & garment care business for Perth's western suburbs, aimed at shirt-cycling professionals, wedding parties and doona owners. The page has one job — book a pickup — and one signature: the Garment Rail, a conveyor that hangs your garments in lilac poly bags while a mono counter ticket prices them live, with the two rules that matter (the 5-shirt batch re-rate and the $40 free-delivery line) printed on the ticket itself, not in fine print.
The collection's first lilac-led industry site. Every colour is off the shop floor: the finished shirt, the press frame, the trade's own garment bags, the heated plates.
Ink/line split (LEARNINGS W2/W5): raw lilac measures ≈4.9:1 and raw copper ≈4.0:1 on pressed cotton, so both are marks-only. Derived inks for small text: lilac-ink #5A4287 (≈7.6:1) and copper-ink #8A4519 (≈6.6:1).
Fully procedural single file: SVG illustration (rail, press, map), CSS gradients, no raster assets, no libraries. The Garment Rail is an SVG conveyor: each stepper tap builds a hanger group in JS — hook curling over the rail line, wire shoulders, a translucent lilac bag with a garment line-drawing inside, a copper-strung price tag swinging beneath, and a mono hook number (#113 onward, because the SMS "your shirts are on hook 113" is the trade's real UX). New hangers glide in from the right on a 950ms entrance ease at load and a 300ms snappy transition thereafter (dual-speed, LEARNINGS W21 — the inline entrance transition is handed back to the class rule once landed). When more than eight hooks hang, the conveyor translates left and older garments disappear into the press with an "in · N" counter. The ticket re-renders per interaction from a single compute(); window.__drycleaning exposes counts, totals and a settled flag QA gates on. Reduced motion hangs everything instantly, settled and complete.
Garment subtotal = Σ qty × rate, where the shirt rate is $6.00 until the fifth shirt re-rates the lot to $5.50 (one uniform rule — the press runs in batches). Express multiplies garments only by 1.5. The delivery threshold reads the express-adjusted garment value — "everything on the ticket except the delivery itself, express included" — free at $40 and over, else +$8. Totals round to the nearest 5 cents (Math.round(v×20)/20); with the pinned price book the rounding almost never fires, but the rule is stated on-ticket so the arithmetic is auditable. The dress chip stays live at the $24 plain-dress rate with "beaded & formal quoted on sight" on the card (LEARNINGS W18 — a live control with a visible honesty statement); the wedding gown is its own $340 chip, not a dress variant.
The harness reimplements the price book independently from the spec (LEARNINGS W14) and drives the real steppers/toggles, asserting the displayed ticket at each boundary. All figures below are measured from the running page; zero corrections against the spec's worked examples.
| Scenario | Oracle | Displayed | Verifies |
|---|---|---|---|
| 4 shirts (walk-in) | $24.00 | $24.00 | base shirt rate |
| 5 shirts (walk-in) | $27.50 | $27.50 | uniform 5+ re-rate to $5.50 |
| Rest: 4 shirts + 2-pc suit + dress, pickup on | $69.00 | $69.00 · FREE | rest pin, delivery free ≥ $40 |
| Rest + express | $103.50 | $103.50 · FREE | +50% on garments only |
| 2 shirts + trousers, pickup on | $32.00 | $32.00 | $24 < $40 → +$8 |
| 2 shirts + trousers, express | $44.00 | $44.00 | $36 still < $40; express counts toward threshold |
| … add a 2-pc suit | $45.00 | $45.00 · FREE | crossing releases the $8 |
| 2 jackets + trousers | $40.00 | $40.00 · FREE | exact boundary — "at $40 and over" |
| 1 shirt, walk-in | $6.00 | $6.00 | cheapest output = services-grid floor |
type=date, min today), morning/afternoon window, optional address note. No email — the trade confirms by phone and SMS. Empty submit raises four visible field errors; the confirmation is local, demo-declared, and uses the "we'd call / received" family.WebPage only — the schema a real client would ship is below, as a code sample, not live markup.On a real client's site this page would carry DryCleaningOrLaundry schema (the tightest schema.org type for the trade) with genuine reviews wired to their Google profile. Shown here as a sample — never emitted live on a fictional page:
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "DryCleaningOrLaundry",
"name": "The Careful Press",
"telephone": "+61-8-XXXX-XXXX",
"priceRange": "$6–$340",
"address": {
"@type": "PostalAddress",
"streetAddress": "(real street address)",
"addressLocality": "Nedlands",
"addressRegion": "WA",
"postalCode": "6009",
"addressCountry": "AU"
},
"openingHoursSpecification": [
{ "@type": "OpeningHoursSpecification",
"dayOfWeek": ["Monday","Tuesday","Wednesday","Thursday","Friday"],
"opens": "07:30", "closes": "18:00" },
{ "@type": "OpeningHoursSpecification",
"dayOfWeek": "Saturday", "opens": "08:00", "closes": "13:00" }
],
"areaServed": ["Nedlands","Dalkeith","Claremont","Cottesloe","Subiaco","Mosman Park"],
"aggregateRating": { "@type": "AggregateRating",
"ratingValue": "4.9", "reviewCount": "87" },
"review": [{
"@type": "Review",
"author": { "@type": "Person", "name": "(real reviewer)" },
"reviewRating": { "@type": "Rating", "ratingValue": "5" },
"reviewBody": "(pulled from the client's live Google reviews)"
}]
}
</script>
Looked at the 1200×750 rest capture: the ticket's shirt line truncated to "4 × business shirt…", ellipsing away the unit rate — shortened ticket labels to "4 × shirts @ 6.00" (the rate is the point of the line). The rail read sparse right of centre, so hook spacing widened 66→74px and the first slot moved to 145px (max eight visible hooks before the press swallows overflow). Fixed a malformed hook path from the first build, and split marks from inks: raw lilac/copper demoted to lines and chips, with derived lilac-ink/copper-ink for all small text.
Added the second-read details: mono hook numbers on every hanger, the "in · N" press counter when the conveyor carries garments off-stage, and a counter-bell ding (500ms rotate keyframe) whenever the ticket total changes. Enforced dual-speed redraw — the 950ms entrance transition is handed back to the 300ms class rule after landing, removals fade in 180ms, and the settle cap after any interaction is 650ms. Re-drove every pinned crossing against the independent oracle: 4/5-shirt re-rate, the $32/$44/$45 delivery sequence, the exact-$40 boundary, rest and rest-express — all matched, zero corrections.
375px masthead first (LEARNINGS W18/W21): wordmark right edge 197px vs phone-link left edge 215px — no collision; nav and header CTA drop cleanly, scrollWidth holds 375 on both pages. Sticky call bar sits flush at the bottom with 48px tap targets. Fresh-load empty submit raises four invalid fields with visible messages; filled submit confirms in the demo lexicon. Reduced-motion path hangs all six rest garments instantly, settled at $69.00. No pattern attributes (v-flag risk avoided — phone length checked in JS with setCustomValidity). Chanel rule: removed the ticket's decorative perforation background that read as noise. Zero console errors across the full drive.