Build guide · site 357 · Wave 40, Industry Demos XXIX
A fictional flame-grill food truck out of Fremantle doing private events across Perth — weddings, birthdays, corporate lunches and street-food nights. The page has one job: an event quote request with the package, guest count, zone and extras already priced.
The audience is the couple pricing their wedding's late-night service, the EA booking a site lunch for forty, the thirtieth birthday in a Hamilton Hill backyard, the brewery booking a Friday residency. Event catering is priced per head with a hard floor — the truck, the crew and the fire cost the same for twenty as for fifty — so the page makes that arithmetic visible instead of hiding it behind "get in touch": pick a package, count heads, name the zone, flick the extras, and the quote chalks itself onto the A-frame beside the truck. It's the wave's second dark site: dusk, festoon light, a little smoky — the page should feel like standing in the queue while the grill flares.
The signature is the Service Window — a drawn SVG scene of the truck side-on at dusk. The open serving window glows warm; a drawn cook works a spatula loop over the flat-top while flame licks breathe and ember sparks rise; festoon strings sag from the roof to poles either side; and a drawn queue follows the guests stepper (drawn cap twelve — the tag prints the true count). The package chip redraws the pass (The Classic slides out a burger-and-chips tray; The Feast adds a second tray and a charred corn cob); the zone chip redraws the backdrop (the Fremantle container crane and Norfolk pines for Freo & inner, the scarp and a line of lit rooftops for outer metro, open paddocks with a windmill for beyond); the toggles draw their marks (marshmallow skillet on the pass, iced drinks tub beside the truck, a "3 HRS" tag on the window frame). At 150 guests the second serving window opens — awning props out, a second cook appears, and a short second queue forms at it.
Every ratio below was computed with WCAG relative-luminance arithmetic from the shipped hex tokens (the spec's figures were computed at authoring; Pass 1 re-measured from the shipped tokens). One correction: the spec paired white labels with tomato #D24A35 at a claimed 5.3:1, but the measured figure is 4.40:1 — under the 4.5 floor. Per the standing two-token remedy the argued hue was kept for marks and large display type, and a deeper tomato #C23C29 was minted as the button fill (white on it 5.29:1 — almost certainly the pairing the spec's 5.3 figure was computed from). The pre-minted hover #A83322 (6.64:1) and lighter tomato-ink #E8836F (6.20:1 on the ground) shipped as pinned.
Eighty plates off the pass by nine
Bricolage Grotesque 800 for the wordmark, headings, the display moment and the chalk price — chalkboard-confident with a grin, exactly the hand a good truck paints its own menu in. The display line re-words itself with the controls: Forty plates off the pass by nine · Two hundred plates off two windows by ten.
Nunito Sans 400 / 600 / 700
Nunito Sans for everything read — round, warm and legible at dusk-palette contrast, body at 17px. Weight carries hierarchy; tracking only on the uppercase eyebrows, chalk labels and the count tag.
dt = min(max(now − last, 0)/1000, .05) — clamped at zero and capped; the loop pauses on document.hidden; reduced motion renders one settled frame.aria-live region. Three width treatments: absolute in-scene above 1100px and from 641–959px; folded flat below the scene at ≤640px (short note forms) and in the 960–1100px band (full forms) so it never compresses the scene at tablet-landscape widths.firstDraw flag. Every control handler bumps a token before touching state; entrance timers and the settle timer check it, so rapid-fire clicks end on one definite settled state.<style>, Google Fonts the only external request; the favicon is an inline SVG data-URI.State is one object — package, guests, zone, three toggles — and compute() is a pure function over it: package line = max(rate × guests, $1,400) with the minimum note flagged when the raw figure is under the floor; dessert and drinks multiply the actual guest count even when the package line bills the minimum; the extra hour and outer-metro fee are flat; "beyond the metro" computes no total at all — the badge swaps to the designed quoted-state prompt with the food-and-service subtotal declared beside it, and the same declaration prints on the ledger total line and the mirror. render() rebuilds the scene synchronously and writes every text surface in one pass: display line, badge (price, zone, declaration, notes in full and short forms), ledger, mirror, control states and the form pre-fill. window.__foodtruck publishes state, every computed line and a token-gated settled flag.
| Line | Amount | How it bills |
|---|---|---|
| The Classic — smash burger, chips, slaw | $28 pp | × guests (20–200 in tens; every product a whole dollar) |
| The Feast — double smash, charred corn, chips, slaw, pickles | $38 pp | × guests |
| Event minimum | $1,400 | package line ONLY — when pp × guests < $1,400 the package line bills $1,400 with the note "billed at the $1,400 event minimum — the truck, crew and fire cost the same for twenty as for fifty" (short form at ≤640px: "billed at the $1,400 event minimum"). Classic crosses exactly at 50; the Feast's boundary (≈36.8) is not on the ×10 grid, so the meter-level contract is the assert: Feast × 30 bills the minimum, Feast × 40 doesn't |
| Dessert skillet | +$8 pp | × ACTUAL guests, even when the package line bills the minimum — live at every combination |
| Drinks station (unlicensed — BYO bar beside us is fine) | +$6 pp | × ACTUAL guests — live at every combination |
| Extra service hour | +$150 | flat, never multiplies |
| Freo & inner metro | included | declared on the badge, the ledger and the mirror |
| Outer metro | +$120 | flat |
| Beyond the metro | no number | designed price-less state — "quoted by the kilometre"; the badge total swaps for the prompt, the food-and-service subtotal is declared wherever the total would print, and the form still pre-fills and submits |
| 150+ guests | note only | "over one-fifty we open the second window — same price, faster queue" — no price change; the scene opens the drawn second window at the same threshold (absent at 140, present at 150) |
| 200+ guests, weddings with table service, licensed bar partners | quoted | FAQ + form notes — never a number on this page |
All rates × any guest count on the ×10 grid are whole dollars — integer-safe by construction; totals display to the whole dollar. The estimator's cheapest output equals the services grid floor: private events from $1,400 (Classic × 20 = $560 → billed $1,400).
| Scenario (full control vector set explicitly) | Spec | Measured | Also asserted |
|---|---|---|---|
| Rest: The Feast · 80 · Freo & inner · all extras off | $3,040 | $3,040 | no notes on any surface; second window closed (painted panel drawn); 8 queue figures; "EIGHTY PLATES OFF THE PASS BY NINE" |
| Classic · 40 | $1,400 | $1,400 | minimum note on badge, ledger and mirror |
| Classic · 40 + dessert | $1,720 | $1,720 | dessert line $320 = 8 × ACTUAL 40, asserted ≠ $400 |
| Classic · 50 / 60 (boundary three ways) | $1,400 / $1,680 | $1,400 / $1,680 | note ABSENT at exactly 50 and at 60 |
| Feast · 30 / 40 (meter contract — boundary not on the grid) | $1,400 / $1,520 | $1,400 / $1,520 | note present at 30, absent at 40 |
| Feast · 200 · dessert + drinks + third hour · outer | $10,670 | $10,670 | 7,600 + 1,600 + 1,200 + 150 + 120; second-window note + drawn second window open; drawn cap 12 with the tag counting 200 |
| Guests 140 / 150 | $5,320 / $5,700 | $5,320 / $5,700 | note AND drawn window absent at 140, both present at 150; display flips to "two windows" |
| Rest + drinks / + third hour / outer | $3,520 / $3,190 / $3,160 | $3,520 / $3,190 / $3,160 | third hour flips the display to "by ten" and the declaration to "three hours of service" |
| Zone → beyond the metro → back to inner | quoted → $3,040 | quoted → $3,040 | badge swaps to "Quoted by the km" with "food & service $3,040 + travel"; the declaration prints on ledger total and mirror; asserted designed, not missing |
| Floor: Classic · 20 | $1,400 | $1,400 | equals the services grid's "from $1,400" |
| Rapid-fire: nine clicks in one dispatch, no settle | $5,350 | $5,350 | Feast · 100 · inner · all extras on; a second five-click burst lands back on the rest $3,040; zero console errors |
type=date — the date is the scarce resource, so it's a first-class field), event type (wedding / birthday / corporate / community), a site-and-crowd textarea (access, power, dietaries — the three things a truck actually needs to know) and the pre-filled quote from the Service Window. Front-end validation with per-field messages and aria-invalid, no pattern attributes; the confirmation panel is hidden on load and on empty submit and appears only after a valid filled submit.The live page carries only WebPage + BreadcrumbList JSON-LD naming it a demonstration — fictional business data in a real search index would be dishonest markup. For the real Ember & Rye we'd ship a FoodEstablishment entity — the exact schema.org organization type; there is no food-truck subtype, so the food-establishment type carries the NAP, the service area and the reviews feed:
{
"@context": "https://schema.org",
"@type": "FoodEstablishment",
"@id": "https://emberandrye.example/#truck",
"name": "Ember & Rye",
"image": "https://emberandrye.example/truck-at-dusk.jpg",
"url": "https://emberandrye.example/",
"telephone": "+61 8 9000 0357",
"servesCuisine": ["Burgers", "Barbecue"],
"priceRange": "$28–$38 pp",
"address": {
"@type": "PostalAddress",
"addressLocality": "Fremantle",
"addressRegion": "WA",
"postalCode": "6160",
"addressCountry": "AU"
},
"areaServed": ["Perth", "Fremantle", "Cockburn", "Melville", "Joondalup", "Mandurah"],
"aggregateRating": { "@type": "AggregateRating", "ratingValue": "4.9", "reviewCount": "168" },
"review": [
{
"@type": "Review",
"author": { "@type": "Person", "name": "P. — wedding, Bicton" },
"reviewRating": { "@type": "Rating", "ratingValue": "5" },
"reviewBody": "Midnight service at our wedding — the dance floor emptied into the queue and came back happier."
}
]
}
Alongside the markup: a Google Business Profile as a service-area business anchored to Fremantle, an events-catering landing section per event type (buyers search "wedding food truck Perth", not "catering"), the find-the-truck strip fed from the booking calendar so the page always shows real upcoming spots, and a reviews feed synced from Google.
fieldset{min-width:0} fixed it; the 375 probe then came back clean.document.hidden. Chanel rule: removed a planned second smoke source over the second window — one chimney is enough. Final harness: all asserts green across index and guide.This is a demonstration build by Sapience Analytics. Want one pointed at your customers?
Ember & Rye is a fictional business — a food truck industry demonstration designed & built by Sapience Analytics, part of the Generative Assets collection. Back to the site · Index.