Generative Assets · Industry Demos XXV · site 337 · how it was made
Tamala Line Retaining Walls — build notes
A retaining-wall contractor's landing page for Perth's northern and eastern sand corridors, built around one drawn instrument: a Wall Planner that prices a limestone or sleeper wall by length and height and hands the scenario to the site-measure form.
Concept
Tamala Line Retaining Walls is a fictional Wangara operator building reconstituted-limestone-block, natural-cut Tamala limestone and concrete-sleeper retaining walls for new-block owners in Alkimos and Brabham, established owners replacing a leaning pine wall, and pool builders' clients who need the level pad first. The page has one job: a site-measure booking that already carries the wall type, length and height — priced — so the first phone call is a confirmation, not a discovery.
The audience is a phone in one hand on a sand block. So the conversion path leads (name, promise, call, book, one trust line all inside 375×667), the drawing supports, and every number on the page comes from the same six pinned anchors.
Palette — a cut limestone bench, late afternoon
Argued from the materials: Tamala limestone for the ground, jarrah for the ink, the red-earth of a fresh cut for the calls to action, coastal scrub for the linework. Contrast was measured from the shipped tokens with WCAG 2.x arithmetic in Pass 1 — the spec's figures were hypotheses, these are the measurements.
Tamala cream#F6EFE0 — page ground
Jarrah dark#2B2A26 — body ink, 12.55:1 on cream, 14.36:1 on white
Red earth#8A5A2B — marks, plate rim, stars; white on it 5.87:1; on cream 5.13:1
Red-earth button fill#744A22 — white labels 7.65:1
Red-earth ink#67401D — links and accent copy, 7.88:1 on cream, 9.02:1 on white
Coastal scrub#7C8F6B — lines and marks only (3.06:1 on cream — never small text)
Scrub ink#48573C — eyebrows and drawing labels, 6.78:1 on cream, 7.77:1 on white
Soft ink#5A554A — secondary copy, 6.47:1 on cream
The ink/line split in practice: scrub #7C8F6B draws the string line, hatch and rules; anything you read in that hue is the darker scrub-ink. Red earth passes at 5.13:1 on cream, but buttons still use the pre-minted deeper fill (7.65:1 under white) and small accent copy uses red-earth ink (7.88:1) — the two-token remedy applied before it was needed, so the accent never sits near the 4.5 line. Trust strip on jarrah: cream 12.55:1, muted #D9CFB4 9.26:1.
Type
One metre high, twelve metres long
Bitter 700/800 — a slab serif with a mason's shoulders. Wordmark, headings, the display line over the drawing, the plate price, and every figure in the ledger. The display line is generated from the planner state ("750 millimetres high, twenty-one metres long") so the typographic moment is live, not decorative.
Holds the block, keeps the sand — reconstituted limestone, natural-cut Tamala and concrete-sleeper walls, priced by length and height.
Karla 400–700 — the workhorse: body at 17px (16px in the form fields at 375), labels, chips, the drawing annotations. Its slightly geometric shapes sit well against Bitter's slabs without competing.
Scale: 13/15/17/19/27 body-and-label rungs, then Bitter at 38 (ledger total), 40 (section heads) and 56 (h1) on desktop. Uppercase with tracking is used exactly twice — eyebrows and the display line — because both are categorical.
The signature — Wall Planner
An SVG cutaway of a retaining wall in elevation and section, drawn procedurally from the planner state. Left to right: the height dimension, the string line with drawn stakes and flags, a spirit level sitting on the top course, the wall face itself, then the cut end showing wall thickness, the blue-metal drainage column, the ag-line at the base, the hatched retained sand rising behind, and the compacted base and footing under everything. The length dimension runs below the footing and reads the course count.
How it is built
- Geometry. One coordinate system: ground at y=380, vertical scale 100 units per metre (0.5–1.5 m → 50–150 units), horizontal scale
min(90, 440 / length) so a 2 m wall is drawn at 90 units/m and a 40 m wall at 11 units/m. Both dimensions are labelled, so the drawing is honest at every aspect ratio. Block joints stop being drawn below 9 units of block width rather than turning into moiré.
- Wall types. Blocks: 250 mm courses in running bond with a 500 mm module. Natural-cut: each course split into seeded irregular quads (widths 0.32–0.87 of a metre, jittered top edges, three fill tones) — seeded, so redraws are stable and the QA screenshots are reproducible. Sleepers: 250 mm rows between galvanised H-posts every 2 m, footed below ground.
- Steppers. Length 2–40 m by 1 m extends the elevation; height 0.5–1.5 m by 0.25 m adds or removes one course (2–6 courses). Both re-solve every dependent element (fence bays, ghost wall rows, dimension text, plate position, viewBox crop).
- Toggles. Fence draws 1.8 m Colorbond panels on posts set into the top course; removal draws a skewed, dashed ghost of the old pine wall inside the new face with an italic tag; the permit toggle is owned by the height (see pricing) and renders locked-on with a lock glyph or off-and-dimmed with its reason.
- Price plate. The total lives on the drawing as a survey plate straddling the top course (jarrah plate, red-earth rim, four rivets). Below 640px the plate switches to a tight variant — shorter subtitle, lifted onto a post above the top course — so it never covers the coursing at 375. Its bounding box and the scene's both intersect the first 750px at 1200 wide.
- Bubble. The level's bubble drifts and settles on a 7-second loop driven by requestAnimationFrame with
dt = min(max(now − last, 0)/1000, 0.05); its opacity floor is 0.55 so the level is visibly alive at any capture instant, and the vial glows when the bubble is centred (second-read detail). Reduced motion: no loop, static centred bubble, no entrance animation.
- Dual-speed redraw. First draw earns the entrance (110 ms per course, capped 1.3 s, then string line, level, dimensions, plate); every later change redraws at 34 ms per course capped at 420 ms with a plate pop. Handlers bump the draw token before the state swap so an in-flight settle timer can never mark a stale frame settled;
window.__retainingwalls exposes scenario, figures, settled and the token.
- ViewBox crop. The SVG keeps a fixed aspect box (800×310 desktop, 640×300 mobile,
preserveAspectRatio="xMidYMax meet") and the viewBox is bottom-anchored and cropped to the tallest drawn element each redraw — a 0.5 m sleeper wall and a 1.5 m wall with a fence both fill the box without a layout shift.
Pricing model — the pinned anchors
Supply and build per square metre of wall face (length × height). Excavation to footing depth, compacted base, 100 mm ag-line and blue-metal drainage are included in every wall. All three rates divide by four, so every 0.25 m step prices to a whole dollar; totals display to the whole dollar by construction, not rounding.
Worked examples (all measured from the running page and matched against an independent oracle)
The harness set the full control vector for every scenario (type chip, both steppers, both toggles) before reading the plate, ledger and probe, then swept the whole grid — 3 types × 5 heights × 10 lengths × fence/removal on and off — against the oracle: zero mismatches. Rapid-fire drives (15 and 14 control clicks in one evaluate, no settle between) ended settled and consistent with the oracle.
Conversion decisions
- Above the fold at 375×667: wordmark, "Call" and "Site measure" in a 60px masthead (wordmark right edge measured 125px against the call button's left edge at 164px); the h1 names the trade and the corridors; one primary CTA; three trust signals in a single wrapped line, the first ending at 588px. The ghost phone button hides on mobile because the masthead and the sticky bar both carry
tel:.
- Sticky bar — call and book, fixed bottom, only below 760px; the footer carries 110px of bottom padding so the colophon is never under it.
- Planner → form handoff. Every planner change rewrites the wall field ("Reconstituted limestone blocks, 12 m long × 1.0 m high (12 m² face) — Wall Planner estimate $4,010 incl. engineer's design & permit") until the visitor edits it, and the confirmation copy names the same wall. The "Book the site measure for this wall" button under the ledger is the primary CTA a second time.
- Form fields: name, phone (v-flag-safe pattern), suburb, a purpose select (levelling a new block / replacing a leaning timber wall / pool or shed pad / driveway cut / terrace / other — it tells the crew which engineer and which drawing), plus the pre-filled wall field. Front-end validation only; error copy appears under each field on the first submit attempt; the confirmation panel is hidden on load and on an empty submit and shown only after a valid filled submit, with an explicit "nothing was sent anywhere" demo note.
- Services grid floors equal the planner's cheapest outputs by type; the permit, fence and steps lines repeat the planner's own rules so the page never argues with itself.
- Reviews are the three spec stories (the Alkimos pool pad, the Kingsley pine wall in two days, the Brabham natural-cut the neighbours asked about) and are labelled as demonstration copy.
Local SEO we'd implement for the real business
The live page emits only WebPage / WebSite / BreadcrumbList / ListItem schema naming it a demonstration — fictional business data in a real index would be dishonest markup. For the real client we would ship the block below (top-level type GeneralContractor, the schema.org organisation subtype for a wall builder), fed by their real reviews, hours and service area:
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "GeneralContractor",
"name": "Tamala Line Retaining Walls",
"description": "Limestone block, natural-cut Tamala limestone and concrete-sleeper retaining walls across Perth's northern and eastern suburbs.",
"url": "https://www.example-client-domain.com.au/",
"telephone": "+61 8 9000 0337",
"address": { "@type": "PostalAddress", "addressLocality": "Wangara", "addressRegion": "WA", "postalCode": "6065", "addressCountry": "AU" },
"geo": { "@type": "GeoCoordinates", "latitude": -31.79, "longitude": 115.83 },
"areaServed": ["Alkimos", "Butler", "Joondalup", "Kingsley", "Wanneroo", "Landsdale", "Ellenbrook", "Brabham", "Aveley", "Midland", "Bassendean", "Forrestfield"],
"openingHoursSpecification": [
{ "@type": "OpeningHoursSpecification", "dayOfWeek": ["Monday","Tuesday","Wednesday","Thursday","Friday"], "opens": "06:30", "closes": "16:00" }
],
"priceRange": "$$",
"hasOfferCatalog": {
"@type": "OfferCatalog",
"name": "Retaining walls",
"itemListElement": [
{ "@type": "Offer", "itemOffered": { "@type": "Service", "name": "Limestone block retaining walls" }, "priceSpecification": { "@type": "UnitPriceSpecification", "price": "280", "priceCurrency": "AUD", "unitText": "per m² of wall face" } },
{ "@type": "Offer", "itemOffered": { "@type": "Service", "name": "Natural-cut Tamala limestone walls" }, "priceSpecification": { "@type": "UnitPriceSpecification", "price": "360", "priceCurrency": "AUD", "unitText": "per m² of wall face" } },
{ "@type": "Offer", "itemOffered": { "@type": "Service", "name": "Concrete sleeper retaining walls" }, "priceSpecification": { "@type": "UnitPriceSpecification", "price": "240", "priceCurrency": "AUD", "unitText": "per m² of wall face" } }
]
},
"aggregateRating": { "@type": "AggregateRating", "ratingValue": "4.9", "reviewCount": "63" },
"review": [
{
"@type": "Review",
"author": { "@type": "Person", "name": "Real customer name (from the client's Google profile)" },
"datePublished": "2026-03-14",
"reviewRating": { "@type": "Rating", "ratingValue": "5" },
"reviewBody": "Real review text pulled from the client's Google Business Profile."
}
]
}
</script>
Alongside it: a Google Business Profile with the Wangara service-area setting (not a storefront pin), suburb landing sections for the two corridors, and an FAQ block mirroring the permit / drainage / boundary questions above.
What would change for the real client
- Their photos and their walls — the drawn cutaway stays as the estimator, but finished-wall photography (with suburb captions) replaces the illustrated service cards, and the reviews become their live Google feed.
- Tradify for the job pipeline: the site-measure form posts straight into Tradify as an enquiry with the planner's wall type, length, height and estimate in the notes, so the measure gets scheduled without re-keying.
- Buildxact for the real quote: the measured wall becomes a takeoff (blocks, sleepers, posts, blue metal, ag-line, road base, engineer's fee) with their supplier pricing, so the planner's rates can be recalibrated from actual margins each quarter.
- Xero for the money: deposits invoiced from the accepted quote, permit disbursements passed through, warranty jobs tracked against the original invoice.
- Council-specific permit copy (Wanneroo, Joondalup, Swan) once the client confirms which councils they lodge with, and a real 24-hour "wall's leaning" callback line if they want one.
Iteration log
Pass 1 — Craft
- Measured every palette pair (table above). Red earth cleared 4.5:1 on cream at 5.13:1, so it stays for marks; buttons and accent copy still use the deeper pair so nothing sits near the line.
- First render put the wall in a 920×520 box at 80 units/m — roughly 40% empty sky and 11px annotation text at 1200 wide. Moved to an 800-wide box, 100 units/m, 13–14px annotations, and a bottom-anchored viewBox crop inside a fixed aspect box so the drawing fills its frame at every height without layout shift.
- The string line at yTop−30 touched the plate's top edge; lifted it to yTop−38 and moved the "string line" label above the stakes. Section labels moved from following the cut end to a fixed legend column with scrims and leader lines, so a 0.5 m wall doesn't stack four labels into 50 units.
- 375 masthead: the wordmark wrapped to two lines and "Book a site measure" wrapped in the header — nowrap wordmark, "Site measure" short label, subtitle hidden. Wordmark right edge now measures 125px against the call button's left edge at 164px.
- 375 fold: the trust line ended at 710px (below 667). Dropped the ghost phone button on mobile, tightened the lede and hero padding — first trust signal now ends at 588px, primary CTA at 497px.
- Copy read: replaced "we handle it" throughout with what actually happens ("we design and lodge it", "you sign, we chase").
Pass 2 — Depth
- Mobile plate variant: at ≤640px the plate takes a shorter subtitle and lifts onto a drawn post above the top course, so the coursing stays visible at 375 (plate 81px wide against a 345px scene).
- Locked permit toggle no longer reads as a broken 50% dimmed control — it holds full opacity in red earth with a lock glyph in the knob; the 0.5 m state dims with its reason.
- Second-read details: the level's vial glows green when the bubble passes centre; natural-cut coursing is seeded so the same wall always draws the same stones; the "old pine wall — out and tipped" tag sits inside the ghost on the bottom course, clear of the level and the plate.
- Micro-interaction: subsequent redraws pop the plate (scale .92 → 1.03 → 1) — on an inner group, because the outer group carries the SVG transform attribute and a CSS transform animation would discard it.
- Chips at 375 became a three-column grid so the wall types read as one row instead of three stacked pills.
Pass 3 — Hardening
- Tap-target sweep at 375 measured the toggle switch at 28px tall — rebuilt the toggle as a 52×44 button with the 46×28 track drawn as a pseudo-element; minimum tap target now 44px across chips, steppers, toggles, buttons, sticky bar and header call.
- Reduced motion: no rAF loop, no entrance or pop animations, courses render at opacity 1 on the first frame with the plate at $4,010; asserted headlessly.
- Harness artifact caught and ruled out: clipped screenshots after a scroll captured every CSS-animated element at its pre-delay frame (opacity 0) — puppeteer's beyond-viewport capture re-renders the page; the same frames captured with
captureBeyondViewport:false show the settled scene. Diagnosed as capture, not site (the settled flag and course counts agreed).
- Label-collision sweep at native resolution (1200×1 and 375×2) across five driven states (rest; sleepers 2 m with removal; natural-cut 10 m with fence and removal; blocks 40 m with fence; blocks 3 m × 1.5 m with fence and removal). Two collisions found and fixed: on a 2 m wall the straddling plate covered the cut end, the drainage column and the legend's leader dots — walls under 300 drawing units now use the elevated plate-on-a-post variant with a minimum offset that clears the level and the "string line" label; and with the fence on, the "string line" label ran across the fence's left post — the label is dropped when the fence is drawn (the stakes and flags still read). The ghost pine wall was also too faint against natural-cut stone at 0.42 — now 0.5 with a darker outline.
- Chanel rule: removed the scrub-green gradient wash under the hero — the drawing already carries the colour.
- Final: zero console errors, both pages at 375 scroll-width clean, guide tables in
.table-scroll, one h1 per page, live JSON-LD allowlist clean, reserved phone pattern only.
This is a demonstration build by Sapience Analytics. Want one pointed at your customers?