← Back to the Ember & Rye demo Index

Build guide · site 357 · Wave 40, Industry Demos XXIX

How the Ember & Rye food truck demo was built

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.

Concept

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.

Palette — measured, not assumed

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.

#241C2B
Dusk auberginePage ground — the sky just after the sun's gone.ground for every ratio below
#2E2536
Card aubergineCards, planner, form fields' surrounds.paper on it 12.16:1
#F0E9E0
Warm paperBody ink, headings, chalk text on the A-frame.13.67:1 on the ground · 14.80:1 on the board
#C4B8CB
Soft paperSecondary copy, ledger declarations, fine print — minted in-build.8.67:1 on the ground · 7.72:1 on cards
#D24A35
TomatoMarks, rules, the drawn awning's family, large display accents only.white on it 4.40:1 — demoted from button fill (measured)
#C23C29
Tomato fillPrimary buttons — minted via the two-token remedy.white on it 5.29:1 · hover #A83322 6.64:1
#E8836F
Tomato inkSmall tomato-coloured copy: badge notes, ledger notes, h1 accent.6.20:1 on the ground · 5.51:1 on cards
#F0B440
Festoon amberBulbs, flame licks, chip outlines, leader lines, links, the chalk price.8.86:1 on the ground · 7.88:1 on cards · 9.60:1 on the board

Type

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.

Techniques

Signature implementation

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.

Pricing model (pinned anchors)

LineAmountHow 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,400package 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+$150flat, never multiplies
Freo & inner metroincludeddeclared on the badge, the ledger and the mirror
Outer metro+$120flat
Beyond the metrono numberdesigned 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+ guestsnote 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 partnersquotedFAQ + 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).

Worked examples — measured from the running page against an independent oracle

Scenario (full control vector set explicitly)SpecMeasuredAlso asserted
Rest: The Feast · 80 · Freo & inner · all extras off$3,040$3,040no 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,400minimum note on badge, ledger and mirror
Classic · 40 + dessert$1,720$1,720dessert line $320 = 8 × ACTUAL 40, asserted ≠ $400
Classic · 50 / 60 (boundary three ways)$1,400 / $1,680$1,400 / $1,680note ABSENT at exactly 50 and at 60
Feast · 30 / 40 (meter contract — boundary not on the grid)$1,400 / $1,520$1,400 / $1,520note present at 30, absent at 40
Feast · 200 · dessert + drinks + third hour · outer$10,670$10,6707,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,700note 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,160third hour flips the display to "by ten" and the declaration to "three hours of service"
Zone → beyond the metro → back to innerquoted → $3,040quoted → $3,040badge 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,400equals the services grid's "from $1,400"
Rapid-fire: nine clicks in one dispatch, no settle$5,350$5,350Feast · 100 · inner · all extras on; a second five-click burst lands back on the rest $3,040; zero console errors

Conversion decisions

Local SEO we'd implement for the real business

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.

What would change for the real client

Iteration log

Pass 1 — Craft

  • Measured every palette pair with WCAG arithmetic (table above). White-on-tomato measured 4.40:1 against the spec's 5.3 hypothesis — minted the deeper button fill #C23C29 (5.29:1) per the two-token remedy; tomato demoted to marks and large display. All other pinned figures landed exactly (amber 8.86/7.88, tomato-ink 6.20, paper 13.67/12.16).
  • First harness run: 153/160. The one real layout defect was the classic fieldset min-width bug — the planner's fieldsets refused to shrink below their chips' content and blew the page to 521px at a 375px viewport while every card looked fine at desktop. fieldset{min-width:0} fixed it; the 375 probe then came back clean.
  • Screenshot review at 1200: the drawn queue was nearly invisible — near-black silhouettes on near-black bitumen. Gave every figure an amber rim-light stroke, scaled the rows up (1.12 / 0.9), and boosted the warm light pool under the queue.
  • The container crane was drawn behind the cab where the A-frame hides it at 1200 — moved it behind the truck's box and raised the gantry above the roofline so the Freo backdrop actually reads at every width. Shortened the badge's top line so it sets on one line; extended the count tag's leader down to the queue's heads; stopped the "dates go fast" pill wrapping inside its own label at 1000px.

Pass 2 — Depth

  • Ember sparks now rise off the flat-top inside the window glow — four phase-offset motes with wind wobble, the thing you only notice on the second look. The exhaust smoke was reading as grey balloons above the truck; made it smaller, slower and fainter so it drifts instead of floats.
  • Second-read details in place: the gull on the left festoon pole, the moon climbing imperceptibly with time-on-page, the phone-glow guest in the queue, the kid with the tomato balloon.
  • Micro-interaction: the chalk price bumps on every honest change (scale pulse, suppressed for reduced motion); chip swaps redraw the scene at the snappy 26 ms stagger so the planner reads as instant, not as a replayed entrance.
  • The second window's scene response got its own queue: at 150+ three of the twelve drawn figures form a second line at the new window — the note's "faster queue" claim is drawn, not just stated.

Pass 3 — Hardening

  • 375px: masthead measured first (wordmark right edge 206px vs call button left edge 222px — clear), scrollWidth 375, zero overflow offenders on both pages, h1 and primary CTA inside the first viewport, sticky call bar in the bottom 140px. Badge folds flat below the scene with the pinned SHORT note forms at ≤640px; the 960–1100 band keeps the FULL forms (both asserted from computed styles).
  • 1200: badge and stage bounding boxes both intersect the first 750px (thumbnail contract); the A-frame's left edge measured clear of the drawn cab. Label-collision sweep at native resolution across rest, minimum, 150, max and quoted states: count tag, 3 HRS tag, wordmark, second window and A-frame all clear.
  • Drove every control in every pass: both package chips, all three zone chips, all three toggles, the stepper across its range, keyboard arrows on the chip groups, empty submit (panel stays hidden, five field errors) and filled submit (panel appears) after a hard reload, plus two rapid-fire bursts with no settle between clicks — token invalidation held, zero console errors anywhere.
  • Reduced motion renders the settled dusk frame with everything visible and the probe settling immediately; the rAF delta is clamped at zero and capped; the loop pauses on 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.