Generative Assets · industry demonstration 424

How the Bluebone Fish & Chips page was built

A Coogee takeaway lead-generation page whose whole job is one thing: a parent on a phone at twenty to six knows what dinner costs and how long it takes, before they ring. Every price on it is licensed by an arithmetic check that runs before a line of the page is written.

The conceptA shop that answers the question you were going to ring and ask

Bluebone Fish & Chips is a fictional shop on the highway side of Coogee, frying to order from 4pm to 8:30pm, Tuesday to Sunday, for Coogee, Munster, Beeliar, Success, Hamilton Hill, Spearwood, North Coogee and Bibra Lake. The audience is a parent at 5:40pm on a Friday with a phone in one hand. The page has one job — a pickup order placed direct, on the page or on the phone, not through an app — so everything on it is arranged around a total and a wait.

Every other page in this trade makes you ring to find out what a family feed costs. This one prices its own board in public and puts a line on the page naming that practice: “Most shops make you ring to find out what a family feed costs. Build it here, then ring.”

For the prospectWhy this converts

The call button is in the masthead at every width and in a fixed bar at the bottom of the phone screen, because on a takeaway page the phone is the conversion and it should never be more than a thumb away. The form asks four things — name, mobile, pickup time and the order — and the order box arrives already filled in from the card the customer just built, so the work of writing the order out is work they have already done. The order card itself is the argument: it answers in a total and a wait in minutes, the two things the customer actually wants, and it draws the parcel so they can see what they are buying before they commit. The trust strip under the headline carries the three facts that decide a takeaway — how long the shop has been frying, the hours it is open, and the cheapest thing on the board — so a stranger can qualify themselves in one line without scrolling. And the page tells the truth against its own interest in two places, publishing that grilled costs the same as fried and that four on hake is $13.20 cheaper than four on snapper, which is what makes the rest of the numbers believable.

The proof elementThe order card

Four controls and a toggle: how many are eating (which sets the pieces), the fish, the chips size, fried or grilled, and the scallops-and-calamari pack. The result card answers with a total and a wait, and beside it a parcel is drawn open on butcher paper — the fillets fan out and re-centre as the count changes, the chip heap gets wider and taller with the serve, the order is written on the paper like a docket, and a lemon wedge at the bottom right is replaced by the scallop tray when the pack goes on.

Why the wait sits under the total

Because the customer is deciding two things at once and the second one is time. A shop that publishes a price and hides the wait has answered half the question, and the half it left out is the one that decides whether dinner is here or somewhere else. The wait is a real term in the model — twelve minutes for two pieces, one more for every extra piece, four more for the pack — so it moves with the order rather than being a slogan. It is set at half the size of the total because the money is still the headline.

The arithmetic, and where it is licensed

The order card's rate card
LineRateNote
Hake$6.50a piece
Barramundi$8.60a piece
Snapper$9.80a piece
Chips$5.50 / $8.00 / $11.50regular / large / family
Scallops & calamari pack$9.40adds four minutes
Cooking method$0.00a real per-piece term, zero for fried and for grilled
Wait12 min+1 a piece over two, +4 for the pack
Rest state$34.00 · 14 minfour people, hake, large chips
Floor$18.50two, hake, regular — equals the board’s “from $”
Dearest$79.70 · 20 minsix, snapper, family, with the pack

Money is held in whole cents and divided once at display, so nothing ever lands on a floating-point cent. The domain is 72 cells and the two extremes above are swept for — argmin and argmax over the whole grid — not read off a cell somebody chose by hand. The page’s own harness re-implements the rate card independently, drives all 72 cells in a real browser and checks the displayed string, the docket written on the paper, the state probe, the drawn piece count, the drawn heap, the tray and the fan’s re-centring on every one of them.

The two sentences that cost the shop money

  • Grilled is priced exactly the same as fried — every fish, every size, every order. This is swept rather than asserted: the cooking method is a real per-piece term in the model and it is zero for both, and the browser drive flips the control on all 72 vectors and checks the total, the wait and the probe do not move while the flag genuinely flips. The drawing changes even though the price does not — batter ribs become bars off the flat top — which is what makes the claim visible instead of merely stated.
  • Feeding four on hake is $13.20 less than on snapper. Driven across every head count and every chips size; hake is cheaper on all of them, and the four-person gap is exactly $13.20.

HonestyWhat this page declined to publish

The shop’s own policy is on the page. Nothing that belongs to somebody else’s business, or to a regulator, is.

  • Any delivery-app commission rate, and any claim about what a named aggregator charges a shop or adds to a menu. These are commercial terms we cannot verify and they differ by shop and by contract. The order-direct flag states Bluebone’s own policy only: “Order here and you pay our price. We don’t add a delivery-app margin to it.” The FAQ says the same thing the same way — it explains why the shop isn’t on the apps without printing anybody’s percentage.
  • Any health claim about grilled versus fried. The page states the price fact and stops. Grilled “comes off the flat top with lemon instead of out of the fryer, and it’s the same price” — there is no better-for-you, no calorie comparison, no nutrition claim anywhere on either page.
  • Any food-standards clause, allergen rule or regulator figure. The gluten-free answer describes this kitchen — wheat batter, shared fryers, a flat top in the same room — and offers a conversation, rather than quoting a code we would have to get exactly right.
  • Any local-government food-business registration number or fee. The footer carries a visibly patterned FB0000 and ABN 00 000 000 000, both declared as placeholders on the page itself.
  • Any provenance claim about a real fishery, market or supplier. The species are named on the board because naming them is the point; where the fish comes from is a claim we would have to verify, so the FAQ answers “fresh or frozen” with what a shop honestly knows and offers to tell you on the day.
  • A real phone number and a street address. The number is the reserved (08) 9000 0424 pattern and the address is suburb-level: the highway side of Coogee.

ColourButcher paper, deep marine, paprika

A coastal shop, deliberately not a red-and-yellow one. Every hex and every ratio below is quoted from the same row of the wave’s contrast sweep — measured, never estimated — and the sweep covers the scene fills and the part-opacity tokens as well as the page grounds.

  • Paper #F7F4ECThe page ground. Warm, slightly off-white, the colour of the sheet the parcel is wrapped in.
  • Panel #ECE5D5Cards, tiles, the order card and the map’s land fill.
  • Ink #1A1A18Body copy. 15.86:1 on paper, 13.89:1 on panel, 14.15:1 on the butcher paper.
  • Muted #54524ASecondary copy. 7.12:1 on paper, 6.24:1 on panel — and nowhere else: it is not licensed on the butcher paper, where the drawn labels use the ink instead.
  • Marine ink #173D4CFigures and links. 10.57:1 on paper, 9.25:1 on panel, 9.43:1 on the butcher paper.
  • Marine #1D4757The CTA ground, with paper reversed on it at 9.14:1. Doubles as the drawing’s outline: 8.15:1 on the paper, 6.2:1 on the chips, 4.48:1 on the batter.
  • Marine hover #163846Paper reversed at 11.32:1.
  • Band #12303CThe reviews band, the footer and the sticky bar. Paper reversed at 12.62:1.
  • Paprika #8E3120The order-direct flag and the step numbers, nothing else. 7.32:1 on paper, 6.41:1 on panel, 6.53:1 on the butcher paper.
  • Field #67655BInput and chip borders. 5.32:1 on paper, 4.66:1 on panel — a mark, never type.
  • Rule #CFC7B4Hairlines only. 1.53:1 on paper, 1.34:1 on panel.
  • Butcher paper #EFE7D4The sheet the parcel is drawn on. The docket written on it is the ink at 80% — blended #45433E, 8.02:1.
  • Batter #D9A44EThe fried fillet. Ink on it measures 7.78:1; the marine outline 4.48:1.
  • Chips #E8C878The chips, and the lemon wedge. Ink on it 10.76:1, marine 6.2:1, paprika 4.97:1.
  • Batter edge #A8762FA hairline only token — 1.77:1 on the batter, 3.22:1 on the paper, 2.45:1 on the chips. It draws the batter ribs and the fold border and never carries a word.
  • Lemon mark #A87400A mark at 3.7:1 on paper and 3.24:1 on panel, and forbidden on every other ground — which is why the wedge in the drawing is painted in the chips fill and only the little wedge beside “Tonight’s board” uses this token.
  • Map water #BFD2D8Cockburn Sound, with the coast line at #476871 — 3.85:1 on the water, 4.8:1 on the land. No label crosses the water.
  • Chip sub-label #F7F4EC at 86%The second line inside a selected chip, blended to #D8DCD7: 7.24:1 on marine, 8.83:1 on the hover. It is not taken lower.

TypeA shop, not a chain

Bricolage Grotesque at 600 and 700 carries the headline, the price figures and every label. It has a slight hand-cut wobble in the curves — the terminals do not all agree with each other — which reads as a shop with one owner rather than a franchise style guide, and it holds up at the size the total is set. DM Sans at 400 and 500 does the body work: it is quiet, it has a large x-height, and it stays legible at 17px on a phone in a car park.

The one typographic move is the result card. The total is set in Bricolage 700 at clamp(3rem, 10.5vw, 4.4rem) with a tightened -0.045em track, and the wait sits directly beneath it at a little under a third of that size in the marine ink. Nothing else on the page comes near either size. Those are the only two things the customer wants, so they are the only two things set large.

TechniqueHow the page is put together

No libraries at all

One HTML file, all CSS and JavaScript inline, two Google Fonts. No framework, no build step, no raster images — the parcel, the map, the lemon and the phone glyph are all SVG geometry written into the markup. The page is about 70 KB.

The layout trick, and why it exists

The lead-generation stack is fixed: masthead, hero, price anchors, proof element, how it works, reviews, service area, FAQ, form, footer. But the thumbnail that sells this page is 1200×750, and a hero plus an eight-tile board spends the whole frame before the drawing appears. So the hero copy, the board and the order card live in one CSS grid with two area maps. On a phone it reads copy → board → card, exactly as specified. Above 1000px it becomes copy | card on the first row with the board spanning underneath. One block of markup, no duplication, and the mandated order is asserted at 375 by reading the rendered offsets rather than trusting the CSS.

The drawing

The parcel is assembled synchronously in the markup and only ever mutated by attribute — six fillets, three chip heaps, a tray, a lemon and three steam wisps, with everything unused carrying the hidden attribute against a [hidden]{display:none!important} guard. SVG elements have no .hidden property in the DOM, so every toggle goes through setAttribute; assigning the property would leave the state correct and the pixels wrong. The chip heaps are generated from a small table of row positions and angles, and the drawn top of each heap is computed from that geometry (224, 198 and 186 scene units) so the steam always leaves the pile it belongs to instead of floating above it. The fillet fan is re-centred on the piece count, so two pieces and six pieces both sit in the middle of the sheet.

The animation

Three steam wisps sway and bob, and that is the only motion on the page after the load. Each wisp’s offset is read straight off performance.now() — a wall clock, never a per-frame accumulator — so a headless renderer servicing fifty frames in seven seconds lands on exactly the same phase as a browser servicing four hundred. The loop pauses on document.hidden, prefers-reduced-motion stops it before it starts and paints one settled frame instead, and the state probe carries no time field, so nothing in it moves between the seven-second thumbnail instant and eight seconds later.

The service area

A drawn sketch of the coast with Cockburn Sound on the west, the shop marked at Coogee and seven suburbs around it. Labels are body ink on the land fill at 13.89:1 and none of them crosses the water; two of them swap to shorter forms below 560px, and the selectors that do the swapping are asserted to match a real element, because a compound selector that matches nothing is syntactically perfect, semantically dead and invisible to every state probe.

The buildThree passes

Pass 1 · Craft

The drawing was three separate lies about food

The first cut drew the fish as flat ovals, the chips as a single dome with a few lines on it, and the steam as a dashed stroke whose gaps rendered as stray punctuation floating over the paper. Every state assert was green. Rebuilt from scratch: six battered fillets with a tapered tail, fanned and re-centred on the count; three chip heaps whose chips span ±70° so a bigger serve is visibly wider and taller (12, 19 and 26 chips); three continuous wisps that sway on the wall clock instead of dashing.

Then three measurement errors, all found with getBBox rather than by reading the path: the lemon wedge’s arc carried sweep-flag 1, so it bulged up — local box y −62…0 where the composition assumed 0…62 — and sat straight over the $34.00 written on the paper. The family chip heap measured x2 = 478, which is exactly the docket’s left edge. And the “+ scallops & calamari” docket line measured 148 units wide from x 490, running to 638 — past the sheet’s own fold border at 620 and almost off the paper. All three fixed by moving measured boxes, not by eye. The little lemon glyph beside “Tonight’s board” read as a leaf at 21px and was redrawn as the same half-disc wedge as the scene.

Pass 2 · Depth

The page’s best claim was invisible, and the drawing was contradicting it

The headline honesty claim is that grilled costs the same as fried. Pick grilled and the docket said grilled while the drawing still put batter ribs on six battered fillets — the page arguing with its own picture. Each fillet now carries two rib layers: warm batter ribs for fried, navy bars off the flat top for grilled, toggled by attribute. Now the claim is watchable — the picture changes and the total does not — and the harness asserts it on all 72 vectors in the browser: six grill bars drawn, zero batter ribs, and the total, the wait and the probe all identical.

Then a layout problem that only exists at 1440. The order card is tall and the hero copy is not, so the desktop grid left roughly 800px of empty column beside it. Rather than padding it, the two sentences that argue against the shop — hake feeding four for $13.20 less, and grilled costing the same — were moved out of the middle of the page and set beside the headline, where they are read. The card’s five control groups run in two columns above 1160px, which takes about 250px off its height; the control labels were then measured at 1440 and one of them (“Fried or grilled” against “same price either way”) wrapped to two lines, so the hint was cut to “same price” and a standing assert now fails if any label or hint wraps.

Two smaller depth moves. The steam is anchored to the measured top of whichever heap is drawn, computed from the chip geometry rather than typed, so it leaves the pile at every size. And the form learned to behave: “Send this order for pickup” re-syncs the order box even after the customer has typed over it, the confirmation panel clears itself the moment the order card changes underneath it, and the confirmation ticket branches honestly — if the customer rewrote the order in their own words it says the shop would price it when they ring, and still shows what the card had.

Pass 3 · Hardening

Junk in the phone box, a tray over the edge of the paper, and one ornament removed

The enquiry form is driven three ways — empty, junk in the phone field, good data. The shipped pattern is compiled with the v flag in the checker, because browsers compile pattern that way and the obvious [0-9 ()+-]{8,20} is perfectly valid in Node and a parse error in Chrome, which silently leaves the one field that has to be right accepting anything. The shipped form is proven to compile under v, to accept 0400 000 000, (08) 9000 0424 and +61 400 000 000, and to reject @@@invalid@@@, a word and a six-digit number — and the empty and junk submits are proven not to confirm.

Every selector whose job is to hide or move something at a breakpoint is asserted to match at least one element — an empty NodeList is syntactically perfect, semantically dead, and invisible to every probe that reads state instead of pixels. The mandated mobile stack is measured off the rendered document at 375 rather than read off the CSS, and the headline, both CTAs and the trust line all finish above 667px.

Then the service-area map, which had been passing every assert and was plainly broken in a screenshot: at 375 the label bump made “North Coogee” run straight through “Hamilton Hill”. The two page-level swaps (“Bluebone · Coogee” and “Cockburn Sound”) had short forms; the suburb labels just got bigger. North Coogee now sits centred above its own dot at every width, the narrow bump came down a point, and there is a standing check that intersects every visible map label’s box with every other one at 375 and at 1200 and fails on any overlap. Two smaller ones from the same look: four head-count chips wrapped 3 + 1 at 375 and now sit two-up, and the pickup note under the total was right-aligned against a left-aligned column on a phone.

On a 667 px-tall phone the fixed call bar sat straight across the middle of the order-direct flag, cutting a live paragraph in half — the one measurement nobody takes because the page scrolls. The subline and the flag were both cut back until every block in the hero column sits wholly above the bar’s top edge or wholly below it, with 29 px to spare, and that is now a standing assert at 375×667 rather than a thing that happened to be true once.

Every drawn group is checked against the sheet’s safe box: the scallop tray’s right edge measured 640 against a 636 limit and crossed the paper’s own fold line, so it came in to 628. One ornament was removed — the two diagonal creases that ran corner to corner across the sheet read as a cross drawn over the food, and the single fold border does that job on its own.

SearchLocal SEO we would implement for the real shop

The live page emits only WebPage, CreativeWork and BreadcrumbList schema, naming itself a demonstration, because putting a fictional shop’s opening hours and invented reviews into a real search index would be dishonest markup. For a real client we would ship the block below — a code sample here, not a live script.

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "FastFoodRestaurant",
  "name": "REAL SHOP NAME",
  "servesCuisine": ["Fish and chips", "Seafood"],
  "priceRange": "$$",
  "url": "https://example.com.au/",
  "telephone": "+61 8 XXXX XXXX",
  "image": ["https://example.com.au/img/shopfront.jpg"],
  "address": {
    "@type": "PostalAddress",
    "streetAddress": "REAL STREET ADDRESS",
    "addressLocality": "Coogee",
    "addressRegion": "WA",
    "postalCode": "6166",
    "addressCountry": "AU"
  },
  "geo": { "@type": "GeoCoordinates", "latitude": -32.11, "longitude": 115.76 },
  "openingHoursSpecification": [{
    "@type": "OpeningHoursSpecification",
    "dayOfWeek": ["Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday"],
    "opens": "16:00", "closes": "20:30"
  }],
  "areaServed": [
    { "@type": "Place", "name": "Coogee" },
    { "@type": "Place", "name": "Munster" },
    { "@type": "Place", "name": "Spearwood" }
  ],
  "acceptsReservations": "False",
  "hasMenu": {
    "@type": "Menu",
    "hasMenuSection": [{
      "@type": "MenuSection",
      "name": "From the fryer",
      "hasMenuItem": [{
        "@type": "MenuItem",
        "name": "Hake",
        "offers": { "@type": "Offer", "price": "6.50", "priceCurrency": "AUD" }
      }]
    }]
  },
  "aggregateRating": {
    "@type": "AggregateRating",
    "ratingValue": "REAL AVERAGE",
    "reviewCount": "REAL COUNT"
  },
  "review": [{
    "@type": "Review",
    "author": { "@type": "Person", "name": "REAL REVIEWER" },
    "reviewRating": { "@type": "Rating", "ratingValue": "5" },
    "reviewBody": "REAL REVIEW TEXT, WITH PERMISSION"
  }]
}
</script>

Alongside it: a Google Business Profile with the real hours and holiday hours kept current, the menu published as text on the page rather than as a PDF so it can be read and indexed, and one page per suburb only where the shop genuinely serves that suburb differently — not eight thin pages with the name swapped.

For a real clientWhat would change

  • The prices come out of the till, not out of the HTML. A real shop runs Square or Lightspeed Restaurant on the counter, and the board on this page would read its item list and prices from that, so a price change is made once, at the till, and the website follows. The order card’s arithmetic stays exactly where it is — it is the shop’s own rate card — but the rates stop being typed twice.
  • The order goes through, not into the browser. The confirmation here is drawn locally and says so. On the real site the same form posts into Square Online Ordering or Lightspeed’s ordering module: the docket prints in the kitchen, the pickup time goes on the board, and the customer gets a text. Payment stays optional — plenty of shops want the cash at the counter, and forcing prepayment costs orders.
  • Stock and specials. Snapper is on the board when it is on. A live item-availability flag from the POS would grey out a fish that has run out rather than selling a wait for something that is not there.
  • Their photographs. Everything drawn here is procedural, which proves the layout carries without stock imagery — but a real shop should shoot its own counter, its own parcel and its own people, and those photographs would replace the parcel drawing above the fold.
  • Their reviews. The three on this page are demonstration copy and are deliberately not marked up. A real shop’s Google reviews would be pulled in with the ratings markup above, which is only honest once the reviews are real.