Generative Assets · site 405 · industry demo

Fallzone Play — how the Zone was drawn.

A landing page for a fictional Perth playground and certified-softfall installer, whose whole argument is one number a buyer cannot check: how much ground a fall zone actually takes. So the page draws it, to scale, against their boundary.

The concept

Fallzone Play sells to local-government parks officers, school business managers, childcare directors and strata managers. Every one of them arrives with the same question — will this fit, and what does it cost — and every one of them has been quoted on equipment before anybody measured the impact area. The page's single job is to turn "what can we fit in this space" into a booking, with the fall zone drawn to scale, the tested depth named, the area in square metres and the price already on screen.

The commercial hinge is the refusal. Below about 7.2 m of clear width nothing in the range can be certified, and the page says so — in the badge, with no dollar figure, and with the impact area drawn visibly crossing the boundary on both sides. A demo that only ever says yes is a brochure.

Palette — measured, not estimated

Argued from the two surfaces councils actually buy (wet-pour EPDM and pine bark) plus the red that marks a fall zone on every set-out drawing. Every ratio below was computed with WCAG arithmetic from the shipped hex by contrast-playgrounds.mjs — 26 rows, 0 fail — including the cross product of every ink against every scene fill it can land on.

Civic paper #F2F4F1page ground. Cards #FFFFFF, hairlines #DCE1DA. A cool paper, not a cream — this is a council drawing, not a nursery.
Ink #1A211C14.85:1 on paper · 16.43:1 on card · 12.38:1 on the hairline tint. Every scene label is this ink.
Secondary ink #4A54507.10:1 on paper · 7.85:1 on card · 5.92:1 on hairline. Minted so the accents never have to carry small type.
Wet-pour green #2E6B3A5.79 / 6.40 / 4.82. White on it 6.40:1 (hover #24552E, 8.70:1). Forbidden as text on every scene fill: bark 2.19, wet-pour 3.20, sand 4.29, grass 3.30, steel 3.36.
Rubber red #A83A525.60 / 6.19 / 4.67. Draws the impact-area outline, the hatch and the overrun. Forbidden as text on scene fills: bark 2.11, wet-pour 3.10, sand 4.15, grass 3.20, steel 3.25.
Scene fillsbark #B98F62 5.61:1 · wet-pour #9CC0A6 8.22 · sand #DDD3B8 11.02 · grass #A9C295 8.48 · overrun ground #EDE6E8 13.37 · steel #B8BDBA 8.63 — all measured against the body ink, which is the only thing that labels them.

The COLOUR chip needed two more fills, because a price-neutral control still has to change the drawing. Both were minted and measured with the rest: terracotta #D09A83 (6.75:1 under the body ink) and bark-natural #C6A986 (7.37:1). Forest green reuses the pinned wet-pour fill.

Service-area map labels are body-ink class with a civic-paper halo (paint-order: stroke), never an accent — the lines-only tokens never label a map.

Type

Bricolage Grotesque 700/800 for display. Its width variation gives headings a civic confidence with no toy-like curve anywhere — this is a page a parks officer pastes into a report. Manrope 400/600 carries the body at 17px. Roboto Mono 500 has exactly one role: dimensions. Every number that is a measurement — 2.4 m FHF · 110 mm · 95 m² — is set in it, on the page and inside the drawing, so the eye learns that mono means "measured".

The typographic moment is the display cut beneath the plan: the live impact area set at clamp(4.4rem, 17vw, 9rem) with OF CERTIFIED IMPACT AREA beneath it in mono at .28em tracking. It is the argument in one number — 95 m² of ground for 30 m² of equipment.

The signature — the Zone

One SVG panel, rebuilt synchronously on every state change, holding two drawings that share a 1 m grid: a plan and a section.

The plan is view-fit to max(yard width, zone width), so the boundary is always inside the frame — including in the refusal, where you have to be able to see the impact area cross it. The equipment footprint is drawn to scale in steel; the impact area is its true expanded outline in the rubber red with a drifting diagonal hatch; both axes are dimensioned; the yard boundary is a pair of hard rules with the clear width dimensioned beneath. Where the zone overruns, the overrun is filled with a dense red hatch on the pale overrun ground and the distance is dimensioned in metres.

The section strip beneath draws the free height of fall as a vertical dimension from the deck down to the top of the softfall, and the softfall depth to scale beneath it. That is deliberately unflattering: at the rest state 110 mm of wet-pour is a 7 px band under a 158 px fall. The thinness is the argument — that band is the only thing the standard is talking about, and its tested critical fall height is printed beside it.

No library. Plain createElementNS, laid out in real CSS pixels per width rather than a fixed viewBox, so a 20 m yard at 375 px still renders 11 px dimension type instead of the 4 px a fixed viewBox would give. The whole drawing is assembled synchronously and the entrance choreography is driven from the animation loop — never by post-hoc DOM insertion, which headless Chrome has been known to leave unpainted.

Liveness has a floor: after the entrance settles, the impact-area outline breathes between 0.62 and 1.00 stroke opacity and the hatch drifts one pattern step every two seconds. The signature is visibly alive at any capture instant, with no phase arithmetic against the seven-second thumbnail settle.

The model — integer millimetres, whole dollars

The impact-area offset comes from the AS 4685 series (following EN 1176): 1,500 mm up to 1.5 m of free height of fall, and above that ceil(2 × FHF / 3) + 500 mm. Swings carry a directional zone instead — 1,500 mm to the sides, 3,600 mm fore and aft. Everything is held in millimetres and whole dollars; Number.isInteger() is asserted on every field the price function returns, and a non-integer anywhere throws.

UnitFootprintFHFOffsetImpact zoneAreaSupply & install
Toddler unit, 0–54.2 × 3.0 m1.2 m1,500 mm7.2 × 6.0 m44 m²$14,800
Double swing bay6.0 × 2.4 m2.0 m1,500 / 3,6009.0 × 9.6 m87 m²$9,400
Junior climber & slide6.8 × 4.4 m2.4 m2,100 mm11.0 × 8.6 m95 m²$28,600
Senior tower & net8.2 × 6.0 m3.2 m2,634 mm13.468 × 11.268 m152 m²$46,500
offset   = FHF <= 1500 ? 1500 : ceil(2 * FHF / 3) + 500      (swings: 1,500 sides / 3,600 ends)
zoneW    = footprintW + 2 * sideOffset
zoneL    = footprintL + 2 * endOffset
area_m2  = ceil(zoneW * zoneL / 1,000,000)
depth    = smallest tested row whose CFH >= FHF, else NOT CERTIFIABLE (module disabled)

scope    = new equipment ? module price + $1,450 inspection : $480 audit + $980 drop test
subtotal = scope + area_m2 * surface rate + access [0 | 1,850 | 2,400]
total    = max(subtotal, $4,200 minimum)

The full control grid is 4 units × 29 yard widths × 4 surfaces × 3 colours × 2 scopes × 3 access options = 8,352 cells, of which 4,806 price. The $4,200 minimum binds in 156 of them. Cheapest reachable output is $4,200, which is exactly the services grid's "softfall replacement, from $4,200" floor; dearest is $93,518 (senior tower on 145 mm wet-pour over 152 m², new equipment, barrow-in). Zero non-integer cells.

The honesty clauses this page carries

Three of them are structural, not decorative.

Alongside those: the business is declared fictional on both pages, the phone number uses the reserved (08) 9000 0405 pattern, the address is suburb-level, the ABN and builder registration are visibly placeholder-patterned, and the live JSON-LD is WebPage/WebSite/CreativeWork only — nothing business-shaped, no fictional reviews marked up as structured data.

Where my oracle disagreed with the spec

The concept's numbers came from a spec-time enumeration. They were re-derived here from the prose in an independent oracle (oracle-playgrounds.mjs, 131 assertions, 0 fail) rather than trusted. Three disagreements surfaced. In each case the page ships the reading that reproduces the pinned endpoints, and the disagreement is reported here rather than quietly resolved either way.

1. The smallest subtotal is $2,956, not $3,132. The concept states "the smallest subtotal is $3,132 (softfall replacement only, toddler zone 44 m², pine bark at 200 mm, open site)". That cell is arithmetically correct — but sand is $34/m² against bark's $38/m², so the true enumerated minimum is the same vector on sand: 44 × $34 + $1,460 = $2,956. It is a "smallest X" claim read off a hand-picked cell rather than produced by a sweep. Nothing downstream moves: both cells clamp to the same $4,200 minimum, so the cheapest reachable total — the endpoint that binds against the services grid floor — is unchanged. The page ships both cells and the harness asserts both.

2. The narrowest bark-versus-wet-pour gap is $3,772 today and $692 at ten years, not $4,840 / $1,760. The concept quotes those figures as the extremes "across all 408 comparable bark-vs-wet-pour vectors". They are the extremes across the 204 new-equipment vectors. On the 204 softfall-replacement vectors the $4,200 minimum clamps the bark side of the comparison and the gap narrows further — to $3,772 to lay and $692 over ten years, at toddler / softfall replacement / open access. The headline claim survives both readings and is what the page prints: bark never overtakes wet-pour; the top-ups close the gap, they do not reverse it. The on-page note is computed live from the current vector, so it is right in every cell either way.

3. The pinned one-decimal boundary display collides with the pinned "exactly to the boundary" branch. The senior tower's impact area is 13,468 mm wide and the yard stepper moves in 500 mm steps, so at a 13.5 m clear width there are exactly 32 mm to spare — which toFixed(1) prints as "0.0 m", the same string the spec reserves for a true zero. Exact zero is reachable (swing at 9.0 m, junior at 11.0 m), so both states are live and needed distinguishing. Ruling shipped: the "exactly to the boundary" branch tests the exact millimetre value, and a spare under 50 mm prints to two decimals — the senior at 13.5 m reads "0.03 m of clear width to spare". No pinned string was changed; one was given a resolution the spec did not supply.

Two smaller gaps the spec left open and this build had to rule on, recorded so they are not mistaken for oversights:

Conversion decisions

Local SEO we'd implement for the real business

None of this ships on the demo — a fictional business in a real index is dishonest markup. This is the schema we would deploy for a real Fallzone Play, with the top-level type HomeAndConstructionBusiness, once the reviews were genuine and the address real.

{
  "@context": "https://schema.org",
  "@type": "HomeAndConstructionBusiness",
  "@id": "https://fallzoneplay.com.au/#business",
  "name": "Fallzone Play",
  "description": "Playground equipment supply and installation and certified softfall surfacing for councils, schools, childcare centres and strata across Perth.",
  "url": "https://fallzoneplay.com.au/",
  "telephone": "+61 8 XXXX XXXX",
  "email": "setout@fallzoneplay.com.au",
  "image": "https://fallzoneplay.com.au/img/malaga-yard.jpg",
  "priceRange": "$$",
  "address": {
    "@type": "PostalAddress",
    "streetAddress": "Unit N, XX Example Road",
    "addressLocality": "Malaga",
    "addressRegion": "WA",
    "postalCode": "6090",
    "addressCountry": "AU"
  },
  "geo": { "@type": "GeoCoordinates", "latitude": -31.86, "longitude": 115.89 },
  "openingHoursSpecification": [{
    "@type": "OpeningHoursSpecification",
    "dayOfWeek": ["Monday","Tuesday","Wednesday","Thursday","Friday"],
    "opens": "06:30", "closes": "16:00"
  }],
  "areaServed": [
    { "@type": "City", "name": "Malaga" },
    { "@type": "City", "name": "Ballajura" },
    { "@type": "City", "name": "Ellenbrook" },
    { "@type": "City", "name": "Midland" },
    { "@type": "City", "name": "Guildford" },
    { "@type": "City", "name": "Bassendean" },
    { "@type": "City", "name": "Bayswater" },
    { "@type": "City", "name": "Morley" }
  ],
  "hasOfferCatalog": {
    "@type": "OfferCatalog",
    "name": "Playground and softfall services",
    "itemListElement": [
      { "@type": "Offer", "itemOffered": { "@type": "Service", "name": "Wet-pour rubber softfall, supplied and laid" } },
      { "@type": "Offer", "itemOffered": { "@type": "Service", "name": "Pine bark softfall to AS 4422" } },
      { "@type": "Offer", "itemOffered": { "@type": "Service", "name": "Synthetic grass over shockpad" } },
      { "@type": "Offer", "itemOffered": { "@type": "Service", "name": "Playground equipment, supplied and installed" } },
      { "@type": "Offer", "itemOffered": { "@type": "Service", "name": "Independent post-installation inspection to AS 4685.0" } }
    ]
  },
  "aggregateRating": {
    "@type": "AggregateRating",
    "ratingValue": "4.9", "reviewCount": "38"
  },
  "review": [{
    "@type": "Review",
    "author": { "@type": "Person", "name": "Real verified customer name" },
    "datePublished": "2026-02-11",
    "reviewRating": { "@type": "Rating", "ratingValue": "5", "bestRating": "5" },
    "reviewBody": "Impact-area set-out drawn over our reserve plan inside two days, and the depth table went straight into the council report."
  }]
}

Beside the schema we would ship: a Google Business Profile with the Malaga yard and the eight service-area councils, a page per surface type (wet-pour / bark / synthetic grass) each carrying its own AS 4422 depth table, and one page per council named the way a parks officer searches ("playground softfall replacement, City of Bayswater").

What would change for the real client

Iteration log

Pass 1 — Craft

Measured every shipped token rather than trusting the spec's ratios: 26 rows, 0 fail, including both minted COLOUR-chip fills the spec did not anticipate. The secondary ink #4A5450 was minted here (7.10:1 on paper) so that no accent ever has to carry small type.

The costly find was vertical: the signature's stage opened at y = 940 at 1200 wide, with its badge at 940 — well outside the first 750 px, so the thumbnail would have shown copy and no drawing at all. Fixed by cutting the hero (padding 46/34 → 30/26, h1 ceiling 3.35 → 3.0 rem, the lede from four lines to three, the "or draw it yourself" jump link deleted) and by cutting the Zone section head from a six-line paragraph to two. Stage top now lands near 630, badge with it.

Also in this pass: the wordmark's sub-line was a nested <span> that the .brand span rule was styling as well as the wordmark itself — the brand would have rendered in 9 px mono. And the phone and suburb pattern attributes were written with unescaped ( and ), which Chrome's v-flag regex rejects at checkValidity time: the fields looked fine and validated nothing until the harness drove a deliberately invalid value through them. Both are now escaped and both are proved by driving, not by reading the attribute.

Pass 2 — Depth

Two z-order defects that only pixels could find. The 1 m grid was drawn under the surface fill, so the reference the whole drawing is built on was invisible everywhere it mattered; it now draws over the fills at 16 % ink opacity, the way a set-out sheet reads. And the overrun hatch lived in the boundary group, which paints after the labels — so in every does-not-fit state the surface caption lost its first two characters behind the hatch ("T-POUR 55 MM"). The overrun fills moved into the zone group and the caption is now clamped to start inside the boundary. Both states passed every DOM assert throughout.

The section strip was drawing the deck's handrail straight through its own caption. It now reserves a measured title band above the drawing, and the free-height caption and the section title each shorten by available room rather than clipping.

The second-read detail is a magnifier. At true scale 110 mm of wet-pour is a 7 px band under a 158 px fall — which is the honest picture and an unreadable one. A DETAIL ×N callout now magnifies the band beside the section, with a leader line back to it: ×3 at the rest state, ×11 on a 30 mm system. It appears only where the magnification earns its space (band under 13 px, room in the frame), and never at phone widths.

Micro-interaction: the badge's total and the display number each rise 3 px and fade in over ~0.35 s whenever their value actually changes — not on every redraw — and not at all under reduced motion. The liveness floor was tuned here too: the impact-area outline breathes between 0.62 and 1.00 and the hatch drifts one pattern step every two seconds, so there is no capture instant at which the signature looks dead.

Last: the display cut's lead sentence is state-aware, so the refusal reads "That is how far short the yard is — a 7.2 m impact area against 6.0 m of clear width" instead of claiming to surface ground it has just refused to surface.

Pass 3 — Hardening

375 px. The masthead is measured explicitly — the wordmark's right edge against the call button's left — rather than trusted to scrollWidth. The overflow probe (SVG descendants and the off-canvas skip link exempted) reports zero offenders. The badge docks beneath the artwork below 641 px and every short form is asserted from the probe, not read off a screenshot.

The widest state at the smallest width. A 20 m yard at 375 px is where a fixed viewBox would have produced 5 px dimension type. The drawing is laid out in real CSS pixels per width, and the pass asserts the minimum rendered font size in the whole scene (9.5 px) plus the presence of all four dimension labels and the section's depth and critical-fall-height captions.

The screenshot must not break the page. Entrance choreography runs on a cold-load firstDraw flag with a settle-timer backstop, and a resize renders with animation off — because a clipped puppeteer screenshot changes device metrics and fires the resize handler. The pass drives the controls, waits with nothing poking the page, and requires every animated group opaque; then repeats it after a resize to 1000 px and at DPR 2.

Rapid fire. Eight control clicks in one evaluate with no settle between them. Every handler bumps the render token before the state swap, so an in-flight entrance can never write into a rebuilt scene; the final vector, its $20,042 total and full group opacity are all asserted.

Keyboard. Chips and steppers focus and operate on Enter, FAQ summaries focus and toggle, and the global :focus-visible outline rule is proved present in the stylesheet rather than assumed. The plain-text mirror is an aria-live region and the disabled-unit reason is a role="status", so the two things a sighted user reads off the drawing are announced too.

One ornament removed. The service-area map carried a dashed hairline twin over the river band — two strokes saying one thing. One stroke says river. In the same spirit the "1 m GRID" caption now suppresses itself when the centred clear-width dimension leaves it no room, instead of crowding it.

The final copy read is a script. copy-playgrounds.mjs extracts every money, millimetre and metre token from the rendered prose of both pages — SVG drawing labels and the escaped schema panel excluded, since those are asserted elsewhere — and checks every one of them against a re-derived model. Zero unexplained tokens on either page. The only allowlisted figures are the $20m public liability cover and the "0.0 m" string the boundary ruling above exists to avoid printing.

Totals. Independent oracle 131 assertions / 0 fail. Contrast 26 rows / 0 fail. Headless harness 235 assertions / 0 fail across both pages at 375, 1000 and 1200, plus reduced motion and DPR 2. Zero console errors on either page.