Build notes · Industry demo
How the Gatherwell Party Hire page was made
Gatherwell is a fictional Balcatta marquee & party hire operator built to show what Sapience Analytics would ship for a real one: a landing page whose signature — the Marquee Planner — turns "how big a marquee do I need?" into a priced, itemised weekend plan that walks itself into the enquiry form. Every visual is procedural SVG and CSS; there isn't a raster pixel on the page.
Palette — golden hour on white canvas
Argued from the subject: white marquee canvas, dusk plum for the hour the festoon takes over, berry punch for the celebration, brass for the bulb wire. All contrast figures below are measured WCAG ratios from the shipped tokens (the spec's figures were treated as hypotheses and verified in Pass 1).
#F6F5F1 — page ground; folded-tablecloth crispness.#2B2630 — body ink, measured 13.53:1 on linen.#6A6070 — secondary ink, measured 5.47:1 on linen, 5.97:1 on white cards.#A83A57 — CTAs and links; measured 5.65:1 on linen, white button text 6.16:1 on berry.#A98A45 — measured 3.0:1, so it is a lines-and-marks token only: marquee linework, bulb wire, valance. Never small text.Type
DM Serif Display is the occasion invitation — high-contrast, a little formal, exactly the register of an engagement party. It carries the headings and the planner's typographic bravery: the guest count set huge in words (FORTY GUESTS) that re-spells itself as the stepper runs. Mulish is the workhorse — a warm humanist sans that stays legible at 13px ledger rows and 16.5px body on a phone.
The Marquee Planner
An SVG backyard — fence palings, lawn, golden-hour sun — with a marquee redrawn from data on every state change. The planner always quotes the smallest marquee that fits the guest count for the selected mode (seated capacities 24/48/96; cocktail 40/80/150), states it on-card, and lets an upsize toggle step one size up for weather room. Seated mode computes and draws trestle tables (one per eight guests) and every chair; cocktail clears them for bar tables — "cocktail means standing, that's the point". The dance floor is disabled on the 6×3 with its reason printed, and the upsize toggle disables at the 10×6 ("the biggest on the truck"). The computed weekend price lives inside the artwork as a badge on the marquee roof, with the $200 refundable bond declared under it — the bond prints wherever the total does and is never added to it.
The festoon glow sweep
Bulbs are sampled along the swag path with getPointAtLength, and a single persistent requestAnimationFrame loop drives a warm pulse bulb-to-bulb. Each bulb's opacity is 0.55 + 0.45·pulse — a never-invisible floor of 0.55 so the string reads lit at any screenshot instant — and the bulb nearest the sweep phase is pinned to full 1.0. The frame delta is clamped at zero and capped (dt = min(max(now−last,0)/1000, .05)), the loop pauses on document.hidden, and every control handler bumps an animation token before the scene swap so a mid-sweep rebuild can never leave a stale closure driving removed nodes. Festoon off leaves the string drawn and the bulbs unlit; reduced motion renders the string statically lit with no sweep.
Dual-speed redraw
The first render earns the entrance choreography — scene elements fade in on a stagger capped at ~1.05 s. Every subsequent stepper tap or toggle rebuilds the scene instantly, with only the price badge given a 280 ms settle-pop so the number lands with weight instead of lag.
The pricing model (pinned anchors)
| Item | Rate | Rule |
|---|---|---|
| 6×3 m marquee | $480 weekend | seats 24 · cocktail 40 |
| 8×4 m marquee | $760 weekend | seats 48 · cocktail 80 |
| 10×6 m marquee | $1,250 weekend | seats 96 · cocktail 150 |
| Trestle tables | $16 each | 1 per 8 guests, rounded up (seated) |
| Chairs | $4 each | 1 per guest (seated) |
| Bar tables | $20 each | 1 per 10 guests, rounded up (cocktail) |
| Festoon lighting | +$120 / $160 / $220 | priced by the marquee delivered |
| Parquet dance floor 4×4 m | +$380 | fits from the 8×4 up — disabled on the 6×3 |
| Mushroom heaters | +$95 each | 0–4 |
| Security bond | $200 flat | refundable — declared with every total, never added to it |
| Crockery, linen & styling | no number | quoted from the runsheet — a designed price-less line |
All prices are weekend hire, delivered, rigged and collected metro-wide. Every worked example in the spec was measured on the running page and cross-checked against an independently reimplemented oracle: rest state $1,160 (40 seated, festoon on); the seated boundary pair $624 at 24 guests (6×3, no step-up) and $924 at 25 (8×4, with the size note); the cocktail boundary pair $560 at 40 (6×3 exactly) and the 8×4 step at 41; $2,616 for the full 96-guest build; $904 for 24 seated upsized (which brings the dance floor live); and the $500 grid floor (10 guests cocktail) that equals the services grid's "from $500". The festoon string is priced by the marquee actually delivered, so an upsize re-prices the string honestly.
Conversion decisions
- Planner in the hero. The question every caller opens with is "what size do I need?" — answering it in the first viewport, with the price on the marquee roof, is the whole sales argument. At 1200×750 the scene and badge both sit inside the thumbnail frame.
- Planner → form handoff. The enquiry textarea and guest count arrive pre-filled with the planner state (edits are respected via a dirty flag), so the demo interaction becomes a warmer lead, not a toy.
- Honest controls. Nothing silently no-ops: the dance floor and upsize toggles disable with their reasons printed, the bond is a declaration rather than a line item, and styling is a designed price-less state — "plates are counted, styling is designed".
- Mobile-first plumbing. Click-to-call in the header, a sticky call/quote bar at 375px, 44px tap targets, and a confirmation panel that is hidden on load, stays hidden on an empty submit, and never fakes a network round-trip.
Iteration log
Pass 1 — Craft
Measured every contrast hypothesis from the spec: dusk plum landed 13.53:1 (spec ≈13), berry 5.65:1 (spec ≈5.6), and a muted plum ink was minted at #6A6070 (5.47:1) rather than letting brass near text — brass measured 3.0:1 and stays a line token. The screenshot review caught the roof price badge still faded out at the capture instant: the entrance stagger totalled ~2.4 s across ~90 scene elements, so the badge — drawn last — risked missing the 7-second thumbnail; the stagger is now capped at ~1.05 s. At 375px the masthead "Get a quote" button wrapped to two lines and broke the header rhythm — fixed with nowrap and tighter padding. Removed a dead conditional left in the scene-draw path.
Pass 2 — Depth
Gave the price badge a settle-pop (scale .92→1, 280 ms) on subsequent redraws only, so a stepper tap reads as the number landing rather than the scene lagging. Stopped the glow rAF loop entirely when festoon is off instead of letting it idle. Widened the badge 160→176 and dropped its figure to 20px after the rest-state text sat tight against the plate edges. The second-read detail: the marquee valance carries a tiny letter-spaced GATHERWELL wordmark, the way real hire canvas is branded.
Pass 3 — Hardening
The map's Scarborough label collided with the Balcatta depot star — moved clear. Chanel rule: the service-area map had two dashed delivery rings; one says it better, the outer ring went. Verified the reduced-motion path (static lit festoon, no sweep, hero visible), the empty/filled submit pair driven on button[type=submit] specifically, a rapid-fire burst of mode/stepper/toggle clicks with no settle between (token invalidation held — zero errors), and the full worked-example suite against the oracle. Final run: 61 probes green, zero console errors on desktop, 375, reduced-motion and guide passes; no horizontal overflow at 375 on either page.
Local SEO we'd implement for the real business
This demo page deliberately ships no business structured data — fictional LocalBusiness/Review markup in a real index would be dishonest. For a real Gatherwell, we'd ship this. schema.org has no party-hire subtype, so the honest top-level type is LocalBusiness with an additionalType pointing at an equipment-rental concept:
{
"@context": "https://schema.org",
"@type": "LocalBusiness",
"additionalType": "http://www.productontology.org/id/Equipment_rental",
"name": "Gatherwell Party Hire",
"description": "Marquee and party equipment hire — 6x3, 8x4 and 10x6 marquees,
seating, festoon lighting and dance floors, delivered across Perth.",
"slogan": "The party, delivered.",
"telephone": "+61-8-9000-0324",
"address": {
"@type": "PostalAddress",
"addressLocality": "Balcatta",
"addressRegion": "WA",
"postalCode": "6021",
"addressCountry": "AU"
},
"areaServed": "Perth metropolitan area",
"openingHoursSpecification": [
{ "@type": "OpeningHoursSpecification",
"dayOfWeek": ["Monday","Tuesday","Wednesday","Thursday","Friday"],
"opens": "07:00", "closes": "17:00" },
{ "@type": "OpeningHoursSpecification",
"dayOfWeek": "Saturday", "opens": "07:00", "closes": "12:00" }
],
"priceRange": "$500-$2,600",
"aggregateRating": {
"@type": "AggregateRating", "ratingValue": "4.9", "reviewCount": "87"
},
"review": [{
"@type": "Review",
"author": { "@type": "Person", "name": "Priya M." },
"reviewRating": { "@type": "Rating", "ratingValue": "5" },
"reviewBody": "Rain came in sideways and the marquee didn't move.
Bond back by Tuesday."
}]
}
What would change for the real client
The planner's rate card would read from the client's live hire inventory — Booqable or HireHop both expose availability and pricing APIs, so "is the 8×4 free on the 14th?" becomes a real answer, not a callback. The enquiry form would write straight into the same system's booking pipeline, invoicing and the $200 bond refund would run through Xero, and the reviews would be a live feed instead of demo copy. Their photos would join the drawn scene, not replace it — the planner stays, because it's the thing a competitor's template can't do.