How it was made · site 401
A drawing that refuses the sale.
Sandline Drainage is a fictional Wangara stormwater contractor built as an industry demonstration. Its single job is to turn “how many soakwells do I need” into a booking with the volume, the count, the winter water table and the price already drawn on the screen — including the ground where the honest answer is that a soakwell is the wrong thing to buy.
The concept
The drawing is the argument.
Every soakwell quote in Perth sells a number the customer cannot check: how much water the hole holds. The trade’s own answer is arithmetic — a millimetre of rain on a square metre is a litre — but it is arithmetic nobody shows you, and the second variable, the depth to the winter groundwater, is invisible from the surface and decides whether the hole works at all.
So the page draws it. One SVG cross-section, true to scale on a fixed 0–7 metre depth axis, showing the surface, 300 mm of cover, the precast row filling with the storm the calculator just computed, the aggregate surround, the sand profile and the winter water table as a hard blue line with its clearance dimensioned under the base. Change the ground to clay and the page draws the hole it will not dig, and the badge stops printing a price.
Palette
Argued from what is actually in the hole.
Bassendean sand, Perth limestone aggregate, precast concrete, and the storm the whole thing exists for. Every ratio below was computed with WCAG arithmetic from the shipped hexes by contrast-soakwells.mjs — role, hex and ratio pasted from one row. 39 rows, 0 fail.
Scene fills, each measured against the ink that labels it
| Fill | Hex | Body ink on it | What it draws |
|---|---|---|---|
| Dry sand | #E3D9C3 | 11.81:1 | the profile above the water table |
| Saturated sand | #BFC4BC | 9.33:1 | below it — where the water will not go |
| Clay | #B5836C | 5.07:1 | the refusal ground |
| Precast concrete | #C9C7C0 | 9.79:1 | walls, base slab and lid |
| Aggregate surround | #D6CDBB | 10.50:1 | the clean stone the unit sits in |
| Stormwater | #8FB4D2 | 7.59:1 | the computed volume, inside the units |
| Turf | #A8B58C | 7.61:1 | the surface finish over the lids |
Forbidden grounds. The accents draw LINES on the section; they never label one. Measured as text, storm blue lands at 3.81 on saturated sand, 2.07 on clay, 4.00 on precast, 4.29 on aggregate, 3.10 on stormwater and 3.11 on turf; limestone gold at 4.25, 2.31, 4.46, 3.46 and 3.47 on the same fills. Every label on the section, and every label on the service-area map, is the body ink — with a paper-coloured halo (paint-order: stroke) wherever it crosses a fill.
Type
A grotesque that dimensions well, and a mono that only ever holds a measurement.
Archivo 700/800 carries the display: a wide, flat cap height with engineering neutrality, which is what a drawing title block wants and what a serif would fight. IBM Plex Sans 400/600 is the workhorse — 16.5 px base, never below 16 on a phone. IBM Plex Mono 500 has exactly one role: quantities. Volumes, depths, dimensions, sizes, the phone number. If it is a measurement it is mono; if it is a sentence it is not. That single rule is what makes the section read as a drawing rather than a diagram.
The typographic moment is the rest-state volume set enormous beside the section — 3,300 L at display scale with TO HOLD, ON SITE beneath it in mono at wide tracking, the price and the count stamped alongside. It is re-derived from the running model on every state change, not typed into the markup.
The signature
The Section.
One SVG, assembled synchronously in JavaScript and animated only by attribute writes from a single requestAnimationFrame loop. The viewBox is recomputed on resize so that one SVG user unit equals one CSS pixel — which means the labels stay at a real, readable size at every width instead of shrinking with a fixed viewBox.
The axis is fixed, and that is the argument
The depth axis runs 0–7 m and never rescales. It has to: the whole question is how far down the winter groundwater sits, and that only means anything if the four grounds are directly comparable. A zooming axis would let a 1.4 m water table and a 6.0 m one draw the same. The smallest drawn value — the 600 mm-deep unit — is 8.6 % of the axis, and it carries its dimension on a leader outside its own fill.
One isotropic scale, and the one thing allowed to breathe
Diameters, depths, the cover, the aggregate surround and the water table are all drawn on a single scale, so the drawing is true in both directions. The only quantity permitted to vary with the sheet is the spacing between units, clamped between 0.8 m and 3.5 m — which is the range a real run is set out at anyway. The pixel scale itself falls with the viewport (53 px/m at desktop, ~36 px/m at 375) so the row always fits without breaking the axis.
The drawn cap
The enumerated maximum is 55 soakwells — 400 m² of roof, a 1-in-100 storm, coastal dune sand, the 600. The scene draws at most eight and prints the honest count beside it: 8 drawn of 55. Below 900 px the cap drops to six, and at phone width to four, still printing the true count — a legibility trade documented rather than hidden.
Second reads
- The last unit in a run holds the remainder, not a full share. It draws at its true partial level, captioned last unit holds 600 L of its 1,350. That is
required − capacity × (count − 1), and it is the detail a drainer notices first. - On clay the section draws the hole it will not dig: a dashed ghost cutting straight through the clay layer, labelled where the hole would have gone. No aggregate, no lid, no price.
- The downpipe is drawn at 90 mm on the same scale, with droplets that never stop — the liveness floor, so a screenshot taken at any instant catches a page that is visibly alive.
- The sand profile carries a procedural stipple pattern and a faint metre grid, so the ground reads as ground and the fixed axis reads across the whole sheet.
The things that keep it from breaking
- Entrance choreography is gated on a cold-load
firstDrawflag with a 4.2 s settle-timer backstop, and a resize never re-enters it. Taking a clipped headless screenshot changes device metrics and fires the resize handler; a page that re-animates from zero on resize photographs as an empty shed while every state assert stays green. - The entrance is driven from the loop by writing
opacityattributes on groups that were already in the document. No DOM is inserted while the loop is running, and no CSS rule touches a property the same element sets as an SVG presentation attribute. - The rAF delta is clamped at zero as well as capped:
dt = min(max(now − last, 0) / 1000, 0.05). A negative first-frame delta would drive the water wobble’s phase backwards. - Every control change bumps a scene token before the state swap, and the loop reads only current globals — so a rapid-fire burst of eleven clicks with no settle between them cannot leave a callback closed over a stale row.
- Reduced motion renders a settled static frame — strata opaque, every unit opaque, water at its computed level, droplets parked mid-pipe — not a blank.
- SVG visibility is never toggled through the
.hiddenIDL (SVG elements have no such property); the drawing is rebuilt from state instead, and the page carries the[hidden]{display:none !important}guard for the HTML panels that do use it.
The model
Integer arithmetic, end to end.
Every rate is a whole dollar, so every total is a whole dollar and total % 1 === 0 is an invariant rather than a rounding decision. The runoff coefficient is held in whole percent, so the volume chain never sees a float either. Number.isInteger() is asserted on every field the price function returns.
required_L = ceil(area_m2 × depth_mm × c_pct / 100)
count = ceil(required_L / capacity_L)
if ground is clay → REFUSAL, no price
subtotal = $480 site set-up, spoil removal & compaction
+ count × (size price + access surcharge + reinstatement)
+ connections × $145
total = max(subtotal, $1,650 minimum)
Capacities are floored from πr²h to the whole 10 litres — the number the unit actually holds, not the one on the invoice. A 1200 × 1200 computes to 1,357 L and is sold as 1,350.
Clearance, and the boundary that is reachable on purpose
A soakwell works by losing water sideways into dry sand, so the rule drawn and dimensioned on the section is depth + 300 cover + 500 clearance ≤ depth to the winter water table. The 600 needs exactly 1,400 mm; the coastal-flats ground gives exactly 1,400 mm. It fits, with nothing to spare — and the 900 (1,700) and the 1200/1500 (2,000) do not. That is why one suburb gets twenty-one small holes where the next gets three big ones.
What the enumeration says
| Claim | Figure | How it was produced |
|---|---|---|
| Control grid | 93,312 cells | 18 areas × 2 storms × 3 surfaces × 4 grounds × 4 sizes × 3 access × 3 reinstatements × 6 connections |
| Priced cells | 44,712 | clay refuses; the water table and hand-dig block the rest |
| Non-integer cells | 0 | every returned field asserted with Number.isInteger |
| Cheapest reachable | $1,650 | 60 m², 1-in-20, roof only, dune sand, 1200, excavator, lawn, 1 connection: subtotal $1,605, billed at the minimum — and equal to the services-grid floor |
| Dearest reachable | $53,050 | 400 m², 1-in-100, roof only, dune sand, the 600, hand dig, concrete cut & patch, 6 connections — 55 soakwells |
| Minimum charge binds | 20 cells | every line sits inside it; it is a floor on the job, never an addition |
| Ground is price-neutral | 19,440 vectors, 0 disagreements | stepping the ground across all three sand options at a fixed size leaves the total unchanged |
| Wrong-shape note fires | 15,714 priced cells | wherever the count exceeds twelve |
The refusal
Clay over sand does not price. The badge prints a bare string — no soakwell on this ground — you need a piped connection or a tank — never “$0”, never an empty badge, and the harness asserts the absence of any dollar figure across the badge, the display block and the ledger. The ledger explains why: a soakwell in clay is a bucket with a lid, and the job wants a piped connection to the council drain or a detention tank and a pump, both of which are as much another trade’s work as Sandline’s.
The forced-size chain
The size chip is capped by either of two controls — the ground’s water table, or hand-dig access. It is forced to the largest currently-valid size whenever the remembered value is invalid, restores the moment it becomes valid again, and the memory is only ever set by an explicit pick made while all four sizes are free. Driven in full, and asserted:
| Step | Sizes allowed | Result |
|---|---|---|
| Bassendean · excavator, user picks the 1200 | 600 900 1200 1500 | 1200 (memory := 1200) |
| → coastal flats | 600 | forced 600 |
| → hand dig | 600 | holds 600 |
| → Bassendean | 600 900 | forced 900 — partial unwind: the water table lifted, hand dig still caps at the 900, and the remembered 1200 is still invalid |
| → excavator | 600 900 1200 1500 | restore 1200 |
| → clay | — | refusal, all sizes disabled |
| → Bassendean | 600 900 1200 1500 | restore 1200 |
Honesty
Where our oracle disagreed with the spec.
The concept’s figures came from a spec-time enumeration. We re-derived all of them in a separate implementation (oracle-soakwells.mjs, 47 checks, 47 pass) rather than trusting them. Every price, count, cell count, boundary and chain step reproduced exactly — 93,312 cells, 44,712 priced, 0 non-integer, $1,650 cheapest, $53,050 dearest, 55 soakwells, 20 minimum-binding cells, 19,440 price-neutral ground vectors, 15,714 note cells, and all four capacities floored from πr²h. The 26 contrast ratios quoted in the spec reproduced to the second decimal.
One disagreement, reported rather than resolved silently. The wrong-shape note is pinned as a literal template that names the 1200: “{n} × 600 mm soakwells is the wrong shape of answer here — the 1200 holds the same {required} L in {m} holes and saves {delta}”. Its trigger, count > 12, is size-agnostic and fires in 15,714 priced cells — but our sweep over those same cells found that in 3,204 of them the 1200 is disabled by the very state that caused the note, and in a further 5,238 no larger soakwell is selectable at all. The spec’s own worked example is one of them: on the coastal flats the 600 is forced, twenty-one holes bill $8,200, the note fires — and the literal string would recommend a size the page has just refused two panels above.
What we shipped. The trigger is unchanged, so the firing set is still exactly 15,714 cells. The comparison target prefers the 1200 wherever the 1200 is selectable, which reproduces the pinned endpoints exactly — at the rest area the 600 gives 21 holes for $8,200 against the 1200’s 3 for $4,000, delta $4,200, and the pinned short form 21 holes where 3 would do — save $4,200. Where the 1200 is blocked but something larger is not, it names that size instead (hand dig with the 600 selected points at the 900). Where nothing larger fits at all, it says so and points at the alternative: “… nothing deeper fits under a 1.4 m water table, so this block is worth pricing against a piped connection or a soakage tank before you dig.” Neither the pinned trigger nor the pinned figures were changed; the string was split so it can never advertise a disabled control.
Two deviations we chose, and why
- The drawn cap varies with width. The spec pins eight drawn units. Eight 1200s at true scale need 11 metres of sheet, which is 583 px at desktop and does not exist at 375. Below 900 px the cap is six, and at phone width four — with the honest count printed either way (4 drawn of 21). The alternative was to shrink the pixel scale until the labels were unreadable, which last wave’s notes call out by name.
- Spacing between units is the only quantity fitted to the sheet. Everything a customer could measure — diameter, depth, cover, clearance, water-table depth — is on one isotropic scale. Spacing is clamped to a real installation range and stated here rather than implied to be surveyed.
What is real on that page, and what is not
Sandline Drainage does not exist. The phone number sits in a reserved (08) 9000 0xxx block, the ABN and the plumbing-contractor licence are visibly placeholder-patterned, the address is suburb-level only, and the three reviews are demonstration copy attributed to nobody real. There is no LocalBusiness, Review or AggregateRating structured data on the live page — fictional structured data in a real index is dishonest markup, so the only live types are WebPage, WebSite, CreativeWork, BreadcrumbList and ListItem.
What is real: the NCC Volume Three (Plumbing Code) Part B1 stormwater provisions, AS/NZS 3500.3, AS 4058 for the precast units, the Bureau of Meteorology’s IFD grid as the source of a design-storm intensity, the Perth superficial (Gnangara) aquifer and DWER’s maximum-groundwater-level mapping, and the fact that on-site stormwater disposal is a local-government requirement whose nominated design storm varies by council. Those are the honest choice: a made-up standard number would be the dishonest one.
Conversion
Why the page is shaped like this.
Above the fold at 375 px, without scrolling
Business name, a value proposition that names the trade and the area, a tel: button and an in-page CTA at 46 px tall, and two trust signals — licensed plumbing contractor since 2009, and installed to AS/NZS 3500.3 with the drainage detail lodged at the council. The masthead quote button is hidden below 600 px because the sticky bar already carries it and three items in one row is the collision that keeps shipping.
The estimator is the hero, not a section further down
Most trade pages put the calculator below the fold and the stock photo above it. Here the section drawing sits in the hero grid at desktop and the computed price appears twice above 750 px — once as the on-scene badge, once in the display stamp beside it — because the page’s screenshot is its commercial argument.
Every control changes the price honestly, or says why it can’t
The ground chip is price-neutral at a fixed size, and the page says so in plain words: a wet block doesn’t cost more to dig; the water table changes what we’re allowed to put in the ground, not what putting it there costs. The sizes it forbids are disabled with the computed reason on the chip, not silently dropped. Hand-dig access disables the 1200 and the 1500 with the reason a drainer would actually give.
Form fields
Name, phone, suburb and job type are required; roof area and a message are not. Suburb is required because it is the field that gives the soil and the winter groundwater level before anyone drives anywhere — it is worth more to the quote than the roof area is. The phone pattern is written with every literal special escaped and is proved by driving checkValidity() on a deliberately invalid value, because a pattern Chrome rejects at parse time leaves the field silently validating nothing.
The confirmation panel is hidden on load, stays hidden on an empty submit, and appears only after a valid filled submit — carrying the live estimate with it. There is no network round-trip and the panel says so.
Three surfaces, always
Every figure the model computes appears in the badge on the artwork, in the itemised ledger, and in a plain-text accessibility mirror written as a sentence. The badge carries short forms below 640 px (3,300 L · 220 m² · 1:20, water table 3.2 m); the ledger and the mirror carry the full templates at every width.
The three passes
What each pass actually found.
Pass 1 — Craft
- A false claim in the title block. The scene tag read 1:50 · depths true. A paper scale ratio is meaningless on a screen whose pixel scale changes with the viewport — at 53 px/m the drawing is nearer 1:71, and at 375 px it is 1:105. Replaced with True scale · fixed 0–7 m axis, which is the claim the page can actually keep.
- The first paint threw and the state probe never appeared. The scene clear-down was
while (svg.childNodes.length > 2), which counts whitespace text nodes, so it deleted the<title>and<desc>the drawing is named by. Replaced with an id-keyed clear. - The restore memory overwrote deliberate picks. Reconciliation ran on every state change, so a size chosen while the set was constrained was immediately replaced by the remembered value. It now runs only on ground and access changes — the two controls that change what is allowed.
- 375 px masthead overflowed by 32 px (wordmark + phone chip + quote button in one row). The quote button now hides below 600 px.
- Above the fold at 375, the trust line landed at y = 681 in a 667 px viewport. Rewrote the h1 to name the trade and the corridor, moved the tagline into the lede, and tightened the hero rhythm; the trust line now ends around y = 560.
- Measured every shipped token with WCAG arithmetic rather than trusting the spec: 39 rows, 0 fail, and the forbidden-grounds table above is the output.
Pass 2 — Depth
- The bottom two-thirds of the section was flat colour. Added a procedural stipple pattern, a faint one-metre grid across the full width, and strata labels in the body ink — so the fixed axis reads all the way down and the profile reads as ground rather than as a swatch.
- The last unit now draws its true partial fill with the mono caption, which is the second read a drainer notices first.
- Redesigned the downpipe from a floating grey stub into a 90 mm pipe drawn on the same scale, elbowing under the cover into the first lid, with droplets that never stop — a visibility floor that beats phase-matching a screenshot schedule.
- Built the refusal state as a drawing, not an empty frame: the ghost hole cut through the clay with its own label, no aggregate, no lid, gold badge.
- Micro-interaction: each unit carries an SVG
<title>, so hovering any hole in the run names its size and its litres.
Pass 3 — Hardening
- Three label collisions, all found by looking at native-resolution captures and invisible to every assert. “1.7 m clear” and “300 cover” ran off the right edge; and the dimension column ran through the last soakwell, because centring the row inside the available width pushed it past the column that had been reserved for the dimension. Fixed by deriving the row’s origin from explicit reserved columns for the downpipe and the dimension stack, instead of centring and hoping.
- The water-table label collided with the row on the shallow ground. It sat above the line, which is fine at 3.2 m and impossible at 1.4 m. It now sits below the line, and the saturated-sand label moved down to clear it.
- Removed an ornament that was also a fabrication: an unlabelled blue watercourse drawn across the service-area map. There is no such river on that corridor. The sand-to-clay transition is the map’s only feature now, which is also the only feature the business cares about.
- A pass whose only job was re-deriving every number in body copy found a wrong causal claim: “a 900 mm side gate is the most common reason a job goes from three holes to twenty-one”. It isn’t — access changes the rate, not the volume. What forces twenty-one holes is a 1.4 m water table. Rewritten to say so, and cross-checked against the running model.
- Added a no-JavaScript fallback inside the scene card carrying the rest-state figures in prose, and
scroll-margin-topso the sticky masthead never hides an anchor target. - Final harness: 120 assertions on the site and 17 on this page, all passing — the rest state and six worked vectors against the independent oracle, every chip and both steppers, the full six-step unwind chain, the refusal’s absent dollar figure, the drawn cap, an eleven-click rapid-fire burst with no settle between clicks, the FAQ and a keyboard-driven chip, empty and filled submits driven at
button[type=submit]specifically, the pattern proved bycheckValidity, a comes-to-rest-visible check with nothing poking the page, the badge-versus-subject rectangle at 375 / 1000 / 1200, the reduced-motion settled frame, and zero console errors on both pages.
For the real business
What would change on day one.
- Their photos. Every visual here is procedural because the collection forbids raster assets — which is a useful proof that the design carries without stock photography, but a real drainer has better material than we do: the collapsed 1970s unit coming out in one piece, the aggregate going in, the driveway that used to flood.
- Their pricing, from their system. The six anchors are hard-coded here. On a live build they come from the job-management system so the page can never drift from the quote: ServiceM8 or Tradify for a two-crew operator, Simpro or AroFlo once there are subcontractors and a stores function. The enquiry creates the job directly rather than sending an email someone re-types.
- Real groundwater data. The four ground presets stand in for what should be a lookup: DWER’s maximum-groundwater-level mapping and the council’s nominated design storm, keyed off the address, so the section draws the customer’s actual block instead of the nearest archetype.
- Their reviews feed. Google Business Profile reviews pulled live — at which point the Review and AggregateRating structured data below becomes honest to ship, because it would describe real reviews of a real business.
- Xero for the invoice out of the completed job, and call tracking on the header number so the marketing spend can be argued from bookings rather than clicks.
Local SEO we’d implement for the real business
This is the structured data we would ship for a real Perth drainage contractor — and exactly what is not on the live demonstration page, because the business is fictional. It is printed here as a code sample only.
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Plumber",
"@id": "https://example.com.au/#business",
"name": "Sandline Drainage",
"description": "Soakwell installation, replacement and stormwater drainage across Perth's northern and eastern corridor.",
"url": "https://example.com.au/",
"telephone": "+61 8 9xxx xxxx",
"priceRange": "$$",
"image": "https://example.com.au/img/soakwell-install-wangara.jpg",
"logo": "https://example.com.au/img/logo.png",
"address": {
"@type": "PostalAddress",
"streetAddress": "Unit x, xx Prestige Parade",
"addressLocality": "Wangara",
"addressRegion": "WA",
"postalCode": "6065",
"addressCountry": "AU"
},
"geo": { "@type": "GeoCoordinates", "latitude": -31.79, "longitude": 115.83 },
"areaServed": [
{ "@type": "City", "name": "Yanchep" },
{ "@type": "City", "name": "Quinns Rocks" },
{ "@type": "City", "name": "Joondalup" },
{ "@type": "City", "name": "Wangara" },
{ "@type": "City", "name": "Bayswater" },
{ "@type": "City", "name": "Bassendean" }
],
"openingHoursSpecification": [
{ "@type": "OpeningHoursSpecification",
"dayOfWeek": ["Monday","Tuesday","Wednesday","Thursday","Friday"],
"opens": "06:30", "closes": "16:00" },
{ "@type": "OpeningHoursSpecification",
"dayOfWeek": "Saturday", "opens": "07:00", "closes": "12:00" }
],
"hasOfferCatalog": {
"@type": "OfferCatalog",
"name": "Stormwater & soakwells",
"itemListElement": [
{ "@type": "Offer",
"itemOffered": { "@type": "Service", "name": "Soakwell supply & install, 1200 x 1200" },
"priceSpecification": { "@type": "PriceSpecification",
"price": "980", "priceCurrency": "AUD", "valueAddedTaxIncluded": true } },
{ "@type": "Offer",
"itemOffered": { "@type": "Service", "name": "Soakwell replacement, collapsed unit removed" },
"priceSpecification": { "@type": "PriceSpecification",
"minPrice": "1650", "priceCurrency": "AUD", "valueAddedTaxIncluded": true } },
{ "@type": "Offer",
"itemOffered": { "@type": "Service", "name": "Downpipe or pit connection, to 6 m" },
"priceSpecification": { "@type": "PriceSpecification",
"price": "145", "priceCurrency": "AUD", "valueAddedTaxIncluded": true } }
]
},
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": "4.8", "reviewCount": "127", "bestRating": "5"
},
"review": [
{ "@type": "Review",
"author": { "@type": "Person", "name": "Peter M." },
"datePublished": "2026-07-02",
"reviewRating": { "@type": "Rating", "ratingValue": "5", "bestRating": "5" },
"reviewBody": "Two winters of sandbagging the garage door. Three new 1200s off the front downpipes and the driveway just didn't." }
]
}
</script>
Beside the schema: a Google Business Profile with the service area set to the suburbs above rather than a radius, suburb landing pages that name the actual ground (“soakwells in Bayswater” is a different technical page from “soakwells in Yanchep”, because the water table is), the calculator’s arithmetic written out as FAQPage markup, and reviews requested at handover while the driveway is still dry.