Longtable Catering Co.

How the Longtable demo was built

Longtable Catering Co. is a fictional Perth caterer — corporate, weddings and wakes out of a Leederville kitchen. The page has one job: turn "how much per head, and what's actually included?" into a priced, dated enquiry. Everything on it, from the golden-hour pass illustration to the map, is procedural SVG and CSS — no photography, no raster assets.

Signature

The Menu Builder

Event chips (office lunch / cocktail party / wedding / wake), a 10–150 guest slider, and four service chips drive two things at once: a procedural SVG pass scene and an honest estimate card. Drop-off mode lines labelled trays along the bench and multiplies them with the guest count; grazing lengthens the table plank by plank; staffed canapés sprout picked trays with apron-silhouette staff standing behind the pass; plated sets cloched covers in rows. The scene is rebuilt per state change from primitive shapes (a mk() helper creating namespaced SVG elements into one group), with a mono caption stating the count — and a second-read detail: pick "wedding" and a small cake waits at the end of the pass; pick "wake" and a jar of rosemary appears, for remembrance.

The card shows the working, not just the answer: rate × guests, the derived staff count for staffed services, the inclusions for that service, and — whenever a minimum bites — a wine-barred note explaining why. The builder state pre-fills the enquiry form's guests and event-description fields (a calculator-to-form handoff), and the whole state is exposed on window.__caterer for verification.

Honest numbers

The pricing model and its shape

All four per-head rates and their minimums are the spec's pinned anchors; the model around them is deliberately simple and fully disclosed:

ServiceRateMinimumStaffed
Drop-off trays$18/head$220 order floor
Grazing table$26/head20 guests
Staffed canapés$42/head30 guests1 per 25
Plated (3 courses)$78/head40 guests1 per 25

Measured from the running page against an independently re-implemented oracle: rest state 60 × $42 = $2,520 with 3 staff; plated clamp scenario $3,120 at 40 guests / 2 staff; canapés at 100 $4,200 / 4 staff; wedding default 90 × $78 = $7,020 / 4 staff. All card figures matched the oracle exactly.

Palette

Argued from the pass at golden hour

Butter linen#F7F1E4

The ground — pressed tablecloth in late sun. Warm butter, held deliberately away from gallery cream.

Roasted walnut#3A322C

Structure and body ink — cast iron and toasted crust. Carries the estimate card and footer.

Beetroot wine#8E3B55

Line/mark accent — the beet-stained board and the shiraz pour. CTAs, garnish marks, underlines. Never body text.

Olive branch#7A7C4E

Secondary — the sprig on the tray. Marks, focus rings, the delivery ring on the map.

Olive ink (derived)#5A5C34

Olive branch fails 4.5:1 as small text on linen (≈3.9:1), so links, eyebrows and active chips use this derived darker ink (≈6.2:1).

Wine lift (derived)#E1A0B4

Wine is too dark to accent on walnut, so totals and card highlights use this lifted tint (≈6.4:1 on walnut).

The spec flags this direction as living near the banned cream-serif-terracotta default. The line is held three ways: the accent stays wine-and-olive (nothing terracotta anywhere), the grounds stay warm butter rather than gallery cream, and the serif is strictly display-only over a sans body.

Type

A menu card, set properly

Conversion decisions

Why the page is shaped this way

Local SEO

What we'd ship for the real business

This demo page emits only a WebPage schema naming it a demonstration — fictional structured data in a real index is dishonest markup. For a real client, the page would carry LocalBusiness + Review schema like this:

Would-ship JSON-LD (sample)

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "FoodEstablishment",
  "name": "Longtable Catering Co.",
  "description": "Corporate, wedding and wake catering across Perth from Leederville.",
  "telephone": "+61 8 XXXX XXXX",
  "address": {
    "@type": "PostalAddress",
    "addressLocality": "Leederville",
    "addressRegion": "WA",
    "postalCode": "6007",
    "addressCountry": "AU"
  },
  "areaServed": ["Leederville","Perth","Subiaco","Mount Lawley","Fremantle","Swan Valley"],
  "priceRange": "$18–$78 per head",
  "openingHours": "Mo-Fr 08:00-16:00",
  "aggregateRating": {
    "@type": "AggregateRating",
    "ratingValue": "4.9",
    "reviewCount": "187"
  },
  "review": [{
    "@type": "Review",
    "author": { "@type": "Person", "name": "Real client name" },
    "reviewRating": { "@type": "Rating", "ratingValue": "5" },
    "reviewBody": "Pulled from the live Google / Podium review feed."
  }]
}
</script>

Real client

What would change

Techniques

Under the linen

Process

Iteration log

Pass 1 — Craft

A 57-check headless suite (independent pricing oracle re-derived from the spec, fresh-load empty submit, filled submit, clamp scenarios, sticky bar, FAQ, masthead geometry, reserved-phone sweep, JSON-LD scoping) ran green first time — then the screenshot review earned its keep. At 1280 the hero quote chip's label spans were rendering inline (missing display:block) and the chip covered the tray illustration entirely; recomposed the bench 56px higher, moved tongs and the olive sprig onto the bench face, lidded the jars. At 375 the slider's "40 ≈ a typical office floor" landmark collided with the "10" tick — moved to its own row with a tick stem. Anchor targets were hiding under the sticky header — scroll-margin-top on every id. Two write-time defects also caught: an invalid --wine-deep token and a dead branch in the minimum-note logic.

Pass 2 — Depth

Drove all four services and looked at each render: drop-off trays read undersized against the canvas — scaled to 86px with two-line labels; the hero's chevron "canapés" read as arrows — replaced with dolloped rounds. Added the second-read event garnish (wedding cake / rosemary jar), the estimate-total bump micro-interaction (re-triggered CSS animation, killed under reduced motion), and nudged the map's Leederville label clear of its own halo.

Pass 3 — Hardening

Chanel rule: removed the builder scene's light-shaft polygon — sun glow and window disc already carry the hour. Masthead-first at 375: wordmark and call pill measured inside the viewport with no overlap, sticky bar fixed with ≥48px tel target, h1 + CTA + trust chip all above the fold. Fresh-load empty submit raises four field errors with aria-invalid (guests arrives valid from the builder sync); filled submit confirms inside the "thanks / received / we'd call" lexicon with the 7-day pencil promise. Reduced-motion renders everything settled. Final suite: 57/57 with zero console errors; every guide figure above re-measured from the shipped code.

← Back to Longtable Catering Co.