Generative Assets · Industry Demos XXX · site 364
How the Swan Bend Brewing Co. demo was made.
A fictional Bassendean brewery and taproom, built as a single conversion landing page for function enquiries. This guide covers the concept, the palette and type, how the Function Booker is drawn and priced, the conversion decisions, the local SEO we would ship for a real brewery, and what the three iteration passes found.
Concept
Swan Bend Brewing Co. is a fictional working brewery on the bend of the Swan at Bassendean that hosts functions in its barrel room, on the taproom floor or across the whole venue — 20 to 120 guests on tasting paddles, shared feasts or a plated long table. The audience is the Guildford couple planning a 40th, the Midland office Christmas organiser and the Maylands wedding party; the page has one job: a function enquiry that arrives with the space, guest count, day, package and extras already priced, and the weekend whole-venue minimum spend declared honestly before anyone rings.
The signature element is the Function Booker: a drawn SVG taproom that lights the zone you pick, fills the tables with guests as you step the count, changes the windows with the day, draws what's on the tables for each package, and chalks the estimate onto a board hanging over the bar — the board carrying a second line only when the weekend minimum actually applies.
Palette — copper and stout
Argued from a copper brew kettle, a pint of stout, a paper beer label and fresh hop cones: a brown-black ground, paper-cream ink, copper for the calls to action and hop-green for lines, tap handles and chip outlines. Every figure below was measured from the shipped tokens with WCAG arithmetic during Pass 1 (not estimated) and matches the spec's computed hypotheses to the decimal.
Because this is a dark site, the two-token remedy runs the other way: the argued copper stays for fills and marks, and the lighter copper-ink carries small copper text. The copper CTA carries dark, ground-coloured labels rather than white (white on #C8803A would measure under 3:1).
Type
Bricolage Grotesque 700/800 for display — a characterful grotesk with the slightly squared bowls of a can label, set tight (−0.015em) for the headline, the chalkboard figure and the display line over the taproom. Karla for everything else: a workhorse humanist sans that stays legible at 13–15px in the ledger and chips and holds 17px body comfortably on a dark ground. Display moment: the rest-state line "60 GUESTS · BREWER'S TABLE · $5,950" set uppercase over the drawn room — it is live, re-deriving from the booker state, so the figure can never drift from the board.
Techniques
- Inline SVG taproom (800×470 viewBox) — walls, clerestory windows, a barrel pyramid, the bar with eight tap handles, the copper kettle behind glass (linear-gradient copper, glass gradient, diagonal highlight), three pendants with radial-gradient glow discs, long tables drawn as shallow trapezoids with a front edge so back-row guests sit behind and front-row guests sit over the table.
- Procedural guests — seats are generated per table (centre-out order so small groups sit together), zone lists round-robin across tables, and whole-venue interleaves the floor and barrel lists. The scene draws at most 40 figures; the tag prints the true count and, when capped, "40 drawn".
- State via CSS classes, never presentation attributes — day/night swaps pane gradients and wall fills through
.scene.dayrules; zone dimming is a class-toggled opacity on overlay rects; figure entrances transition on an inner group while the outer group carries the SVGtransform(the W16/W17 split). - Ambient rAF loop — kettle steam (two re-parameterised cubic paths plus two rising puffs) and pendant pulses, driven by a zero-clamped, capped delta; paused on
document.hidden; not started under reduced motion (which renders a settled room with the glows at their base level). - Dual-speed redraw — first draw staggers figures at 26 ms (1.5 s cap); every later change rebuilds at 11 ms (520 ms cap). A token is bumped in the control handler before the state swap, so an in-flight reveal or settle timer from a previous click can never land on a rebuilt scene.
- HTML chalkboard over the SVG — sized with container-query units (
cqw/cqh) so it scales with the scene; on-scene at ≥641 px, docked beneath the room (with the figure tag) at ≤640 px. - Procedural service-area map — SVG river on a grid pattern, labelled suburbs, a dashed reach ring and a textPath river label.
- No libraries; CSS and JS inline; Google Fonts only.
How the Function Booker is priced
State is a single object (guests, space, explicit space, day, package, tour, acoustic). resolveSpace() applies the capacity locks: a space is disabled when guests exceed its cap; if the current space no longer fits, the selection is forced to the smallest fitting space; and the user's last explicit chip choice is restored the moment it fits again — a forced intermediate is never restored, which is what makes the 20 → 120 → 60 rapid-fire end on the whole venue and only return to the barrel room at 40. compute() then bills package = guests × per-head, clamps that line alone to $6,000 for Friday–Saturday whole-venue bookings, and adds the room fee, the tour (actual guests × $15, outside the minimum) and the acoustic set. The badge, the ledger, the display line, the visually-hidden text mirror, the form pre-fill and the window.__brewery probe all read from that one result.
| Anchor | Value | Notes |
|---|---|---|
| Guests | 20–120, ×10 | Drawn cap 40 figures; tag prints the true count. |
| Barrel room | ≤40 · $200 / $350 | Room fee Sun–Thu / Fri–Sat. Disabled "seats up to 40" above 40. |
| Taproom floor | ≤80 · $450 / $700 | Disabled "seats up to 80" above 80. |
| Whole venue | ≤120 · $1,000 / $1,500 | Fri–Sat: package line billed at a $6,000 minimum when guests × per-head is under it. |
| Paddle & platters | $45 a head | 2 h — paddle + platters, then on consumption. |
| Brewer's table | $65 a head | 3 h drinks package + shared feast. |
| Long table | $85 a head | 4 h drinks package + three-course feast. |
| Brewery tour & paddle | +$15 a guest | Multiplies the actual count; outside the minimum; live at every combination. |
| Live acoustic set | +$450 flat | Live at every combination. |
Worked examples asserted against an independent oracle
| Scenario | Arithmetic | Total | Note line |
|---|---|---|---|
| Rest: taproom floor · 60 · brewer's table · Fri–Sat · tour · acoustic | 3,900 + 700 + 900 + 450 | $5,950 | absent |
| Barrel room · 20 · paddle · Sun–Thu · nothing | 900 + 200 | $1,100 | absent (grid floor) |
| Whole venue · Fri–Sat · 90 · brewer's table | 5,850 → billed 6,000 + 1,500 | $7,500 | present — "… your 90 guests on brewer's table come to $5,850 …" |
| … step to 100 | 6,500 + 1,500 | $8,000 | absent |
| Whole venue · Fri–Sat · 70 · long table · tour | 5,950 → 6,000 + 1,050 + 1,500 | $8,550 | present; tour on the actual 70 |
| … step to 80 | 6,800 + 1,200 + 1,500 | $9,500 | absent |
| Whole venue · Sun–Thu · 90 · brewer's table | 5,850 + 1,000 | $6,850 | absent (no weekday minimum) |
| Barrel · Fri–Sat · 40 · paddle → 50 → 40 | 2,150 → forced floor 2,950 → restored 2,150 | $2,150 / $2,950 / $2,150 | absent |
| Floor · Fri–Sat · 80 · long → 90 | 7,500 → forced whole 7,650 + 1,500 | $7,500 / $9,150 | absent (7,650 ≥ 6,000) |
| Whole · Fri–Sat · 120 · long · tour · acoustic | 10,200 + 1,500 + 1,800 + 450 | $13,950 | absent |
| Rapid-fire 20 → 120 → 60 (barrel explicit) | whole stays; barrel disabled, floor re-enabled | $7,500 at 60 | present (3,900 < 6,000); → 40 restores barrel, $2,950 |
The harness reimplements the pricing from the anchors above (not from the page's code) and asserts the badge, ledger, display line and text mirror for every row, including note absence. Grid floors cross-check: barrel $1,100, taproom floor $1,350, whole venue $1,900 — the same figures the services grid prints.
Conversion decisions
- Above the fold at 375 px: business name in the masthead, the value proposition naming trade and area as the h1, the "Enquire about a date" primary CTA, a tel: button and three trust chips — all visible without scrolling; the sticky call/enquire bar sits beneath.
- Two CTAs, one primary. Function organisers want a date held, so "Enquire about a date" leads and the phone is secondary — but the number is a tap away in the header, the hero, the enquiry section and the sticky bar, because a Friday-night organiser rings.
- The booker sits beside the hero, not below it, so the 1200×750 capture shows the drawn room and the $5,950 chalkboard with the headline. Its "Enquire with this function" button pre-fills the form and moves focus to the name field.
- Form fields: name, phone, suburb, occasion (birthday / work function / wedding / other), preferred date as free text (organisers have "a Friday in March", not an ISO date), and the function textarea pre-filled from the booker and left editable. Front-end validation with per-field messages,
aria-invalid, focus to the first error, v-flag-safe patterns. The confirmation panel is hidden on load and on an empty submit and appears only after a valid submit; copy stays in the "thanks / received / we'd call" family and says plainly that the demo sends nothing. - Honest minimum spend. The $6,000 weekend whole-venue minimum is on the booker (badge + ledger + mirror), in the whole-venue space card, in the services grid and in the FAQ — the same sentence everywhere, so the page never argues with itself. Public holidays are a designed price-less "quoted" line in the FAQ only.
- Locks that explain themselves. Over-capacity spaces disable with "seats up to 40 — 90 guests won't fit" rather than silently no-op, and the forced selection is visible on the chips, the tag and the ledger line.
Local SEO we'd implement for the real business
The demo page ships only WebPage / WebSite / BreadcrumbList JSON-LD naming itself a demonstration — fictional business data in a real index would be dishonest markup. For the real brewery we would ship a Brewery entity (the exact schema.org subtype under FoodEstablishment) with opening hours, geo, the function-enquiry action, and real reviews fed from the review platform, plus Google Business Profile categories "Brewery" and "Function room facility", an events feed and a tap-list page. The shape:
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Brewery",
"name": "Swan Bend Brewing Co.",
"image": "https://www.swanbendbrewing.example/taproom.jpg",
"url": "https://www.swanbendbrewing.example/",
"telephone": "+61 8 9000 0364",
"priceRange": "$$",
"servesCuisine": "Modern Australian",
"address": {
"@type": "PostalAddress",
"streetAddress": "(real street address)",
"addressLocality": "Bassendean",
"addressRegion": "WA",
"postalCode": "6054",
"addressCountry": "AU"
},
"geo": { "@type": "GeoCoordinates", "latitude": -31.9, "longitude": 115.94 },
"openingHoursSpecification": [
{ "@type": "OpeningHoursSpecification", "dayOfWeek": ["Wednesday","Thursday"], "opens": "16:00", "closes": "22:00" },
{ "@type": "OpeningHoursSpecification", "dayOfWeek": "Friday", "opens": "12:00", "closes": "00:00" },
{ "@type": "OpeningHoursSpecification", "dayOfWeek": "Saturday", "opens": "11:00", "closes": "00:00" },
{ "@type": "OpeningHoursSpecification", "dayOfWeek": "Sunday", "opens": "11:00", "closes": "20:00" }
],
"areaServed": ["Bassendean","Guildford","Midland","Maylands","Bayswater","Morley","Ascot","Eden Hill"],
"amenityFeature": [
{ "@type": "LocationFeatureSpecification", "name": "Function room", "value": true },
{ "@type": "LocationFeatureSpecification", "name": "Brewery tours", "value": true }
],
"potentialAction": {
"@type": "ReserveAction",
"target": "https://www.swanbendbrewing.example/functions#enquire",
"name": "Enquire about a function"
},
"aggregateRating": { "@type": "AggregateRating", "ratingValue": "4.9", "reviewCount": "184" },
"review": [
{
"@type": "Review",
"author": { "@type": "Person", "name": "(real reviewer)" },
"datePublished": "2026-06-14",
"reviewRating": { "@type": "Rating", "ratingValue": "5" },
"reviewBody": "(real review text pulled from the review platform)"
}
]
}
</script>
Alongside the schema: a functions landing page per space with its own h1 and FAQ, suburb pages only where the brewery genuinely draws from (Guildford, Midland, Maylands), Core Web Vitals kept green by keeping the scene SVG inline and fonts to two families, and the review feed wired so the on-page reviews and the markup come from the same source.
What changes for the real client
- Their photos and their room. The drawn taproom is the demo's way of showing the spaces without stock photography; the real build would redraw it to the actual floor plan (their capacities, their table count) and pair it with their photography.
- Function management: the enquiry would post into iVvy or Tripleseat (the function-management platforms venues of this size run), carrying the booker's structured scenario — space, guests, day, package, extras, estimate — so the functions manager replies with a proposal, not a question.
- Taproom tables: Now Book It for walk-in and small-group reservations, kept separate from the functions flow.
- The bar tab and the minimum spend: Square or Lightspeed POS reporting the night's spend against the package, with the minimum-spend gap reconciled on the tab.
- Accounts: Xero for invoicing the room fee and package deposit.
- The tap list: an Untappd feed for the eight taps, so the "what's a tasting paddle" answer always names what's actually pouring.
- Reviews: their Google review feed replaces the three demo reviews, and the same feed populates the Review markup above.
Iteration log
Pass 1 — Craft
- Found: the h1 at
clamp(31px, 4.5vw, 56px)ran to six lines in its 5/12 column at 1200×750 and pushed the primary CTA to 770 px — below the fold. Changed: display scale toclamp(30px, 3.7vw, 50px); CTA now lands at ~580 px, trust chips at ~650 px. - Found: the chalkboard's percentage padding (5.5%) resolved against the scene width and made the rest-state board 147 px tall with the subtitle wrapping to three lines. Changed: padding 3%, subtitle 1.7cqw, board 31% wide — rest-state board ~100 px, subtitle on two lines.
- Found: at 1000 px with the minimum-spend note present the board ran down over the bar front, the tour group and the left seats of the right-hand table; at 375 px the absolutely-positioned figure tag sat on top of the docked board. Changed: split the stage (SVG + tag) from the board wrapper; moved the tour group to stand in front of the kettle glass (x ≥ 696, clear of the board's 432–680 reach); dropped the floor tables 20 units; re-pitched the taps to 312–424 and moved the second pendant to x 400 so nothing sits behind the board; top of the board 10% with a tighter note line-height. At 375 both the tag and the board dock beneath the room.
- Found: the display line "90 GUESTS · BREWER'S TABLE · $7,500" wrapped mid-phrase at 1000 px with a viewport-relative size. Changed: the booker is a container and the line sizes in
cqw— one line from 961 px up, a balanced two-line wrap on phones. - Found: the barrel-room wall sign sat under the barrel pendant's cord. Changed: sign moved left; the "BARREL ROOM" label reads clean.
- Found: in the services grid the "20 guests · Sun–Thu" caption wrapped inline after the price and broke "Sun–Thu" across lines. Changed: the caption is its own non-wrapping line under the figure.
- Found (map): the river's textPath label landed under the Bassendean caption, the "Swan Bend Brewing Co." caption sat on the river stroke, and the header line crossed the reach ring. Changed: river label moved to the Guildford reach of the path (27%), both Bassendean captions lifted above the star, header line raised clear of the ring.
- Contrast measured from the shipped tokens: every pair in the palette section is ≥ 5.37:1 (hop on cards) with body ink at 12.85–14.33:1; no token moved.
Pass 2 — Depth
- Second-read detail: on the long-table package the candle flames flicker in the ambient loop (a per-flame sine jitter on opacity), and the steam puffs rise from the kettle chimney through the clerestory window above it — neither is noticed on the first look.
- Micro-interaction: the chalk figure "bumps" (a 420 ms scale ease) on every price change, and the tag reads "40 drawn" only once the true count passes the drawn cap — a small honesty cue rather than a silent cap.
- Motion timing: the first figure reveal staggers at 26 ms per guest capped at 1.5 s (the entrance), every later change at 11 ms capped at 520 ms; pendants pulse at three unrelated frequencies so the room never strobes in unison; on Sun–Thu the glows drop to half strength and the warm late light comes from the panes instead.
- Rapid-fire drive: ten "+" and six "−" clicks in one evaluate from the barrel room, then a mixed burst across package, day, toggles and space — both settle to the oracle's figure with the forced/restored space exactly as pinned; the token bump in the handler is what keeps the in-flight reveal off the rebuilt scene.
Pass 3 — Hardening
- 375 px masthead first: wordmark right edge vs call button left edge compared explicitly (clear by > 100 px with the short "Call" label below 768 px and the Enquire button dropped below 420 px); scrollWidth 375; no element outside the viewport; body 17 px.
- Sticky bar: fixed, contains
tel:, inside the bottom 140 px at 375×740; its caption shortened to one line. - Reduced motion: CSS media query calms every transition and the JS
matchMediacheck skips the ambient loop and the staggered entrance — the room renders settled with forty guests and the glows at base level; probe settles immediately. - Label-collision sweep at native 375/1000/1200: the board is clear of the tap handles and the kettle glass in both rest and note states; the tag is clear of the front-row figures (B2 moved up so its front row ends at 418 units, the tag starts ~426); the pendant cords pass between window bays; the "BARREL ROOM" sign is clear of the cord.
- Found: with the barrel room booked, the tour group and the acoustic set were drawn under the floor's dim overlay — the extras you'd just paid for sat in the dark. Changed: the extras now render above the zone dims, so the tour by the kettle and the set in the corner are lit whichever space is booked.
- Chanel rule: removed the three loose glasses on the bar top — at phone widths they read as noise beside the board, and the paddles on the tables already say "beer".
- Focus states: every chip, stepper, switch, summary, field and link shows the hop-ink double ring on
:focus-visible; the form moves focus to the first invalid field on an empty submit. - Zero console errors across all drives; form
patternattributes compile under thevflag (the phone pattern escapes(,)and-); live JSON-LD types ∈ {WebPage, WebSite, BreadcrumbList, ListItem}; reserved phone pattern only; exactly one h1 on each page; guide tables in.table-scrolland the guide itself clean at 375.