Generative Assets · Industry demonstration · 388
A schedule that will draw you nothing.
Ledgerline Quantity Surveyors is a fictional West Perth practice. The page has one job: turn “is a depreciation schedule worth it for me” into a phone call — including the calls where the honest answer is no. Everything on it is drawn in the browser; there is not a pixel of photography anywhere.
The concept
A quantity surveyor’s working paper, ruled and quiet, with a forty-year tax depreciation schedule drawn on it. The visitor sets four things about a property — what report they need, when it was built, its floor area, and whether they bought it new or established — and watches forty years of schedule redraw. The fee in the stamp beside it does not move.
That refusal is the whole site. Three of the six controls are deliberately price-neutral: building age, floor area, and how you bought it change what can be claimed and change nothing about what Ledgerline charges. A practice whose fee moved with the size of the deduction would have a reason to make the deduction look bigger, and the page says so in as many words.
The showpiece is the empty state. In exactly one cell of the control grid — a pre-1987 building bought established — Division 43 is nil and Division 40 is nil, and the schedule draws empty, with a refusal set across the blank grid: “we’d rather lose the $770 than sell you an empty schedule.” The fee still reads $770, because that is what the report costs. It is the schedule that goes to zero, not the price list.
The signature: forty year-columns on a fixed axis
The schedule is a single inline SVG, assembled synchronously into one #sceneRoot group and animated entirely from the requestAnimationFrame loop that owns it — never by inserting nodes or toggling classes after the loop has started. Eighty rectangles (a capital-works bar and a plant bar per year) are created once per layout; every state change re-targets their heights and the loop eases the drawn heights toward the targets. Two speeds: the first draw earns a 13 ms-per-column entrance stagger with a 150 ms time constant, and every subsequent change redraws snappy at 80 ms, so tapping a chip reads as response rather than as replay.
The axis is fixed, on purpose
Every other interactive drawing in this wave zooms to fit its subject. This one refuses. The dollar axis is pinned at $48,000 with $8,000 gridlines and never rescales, because an axis that rescaled would draw an 80 m² apartment exactly as tall as a 400 m² house, and the single most useful thing this page can tell a small-unit owner is that there genuinely is not much in it.
The argument only survives if the smallest state stays readable, so both ends were measured off the running page at 1200 px:
- Largest schedule anywhere on the grid — 400 m², built 2025, bought new: $46,300 in year one, drawn at 96.4% of the plot height. It fits under the fixed axis with visible headroom, by construction rather than by luck.
- Smallest non-empty schedule — 80 m², bought established: $4,300 in year one, drawn at 8.95% of the plot, which is 31 px tall at 1200 px wide. That is enough to carry its own white-on-oxblood callout inside the band.
- The honest constant reference is the $8,000 gridline set, which never changes value or spacing. Nothing on the sheet silently rescales under you.
The forty-year clock
Division 43 runs forty years from the date construction began, so the oxblood band stops partway across: 33 columns for a 2019 build, 16 for 2002, 39 for a 2025 new build, and none at all before 17 September 1987. The plant curve keeps going, so the stacked line drops sharply at the year the band ends. Until the second pass, that drop read as a rendering artifact; now a dashed oxblood rule marks it and captions it — FORTY-YEAR CLOCK ENDS AT YEAR 33 — and the most confusing shape on the chart became the thing it is best at explaining.
Second read: the schedule you were just looking at
Change a chip and the schedule you were looking at stays on the paper for 1.15 seconds as a dashed stair, fading quadratically while the new one grows underneath it. It exists to make the argument visible rather than merely stated: the drawing moves a long way, and the stamp two inches to its right does not move at all. It is suppressed on the first draw and under prefers-reduced-motion, and the scene’s settled flag waits for it, so a screenshot taken “at rest” is genuinely at rest.
Where the price badge goes
The fee stamp is an HTML element sitting on the SVG, so the sheet’s height is computed from the badge’s measured height, never from a fixed aspect ratio — a pixel-height badge over a scene whose height is a fraction of its width collides only at middle widths, and passes every state assertion while doing it. Measured at four widths: the badge is 92 px tall at 700 and the head band is 112 px; 105 px at 1000 against a 125 px band; 108 px at 1200 against a 128 px band. Below 641 px the badge docks directly beneath the artwork instead, where it can never cover the drawing.
The pinned formula — (badge + 20) ÷ 0.20 — asks for a sheet three times taller than the drawing wants once the sheet column narrows, so above 1000 px the band grows past a fifth of the sheet (to 22–26%) rather than the sheet growing. The contract that actually binds is the rectangle: the badge’s box never intersects a drawn mark. That is asserted at 375, 640, 641, 1000 and 1200 px, at device pixel ratio 2, across all seven pinned scenarios — 21 state-and-geometry checks, plus a label-collision sweep over every text node on the sheet.
Palette
Argued from a working paper: warm stock, black ink, and the two divisions of the tax act the schedule is made of. Every ratio below was measured from the shipped tokens with WCAG arithmetic in the verification harness, not estimated.
Warm working paper
The page ground. Cards lift to #FFFFFF, ledger rules are #DFD9CB, ruled bands #E9E4D8.
Black ink
Body copy and the primary call button.
14.04:1 on paper · 16.14:1 on card · 12.72:1 on the ruled tint · 11.47:1 on hairlineDivision 43 oxblood
The capital-works band, its callouts, the fee stamp and the clock marker.
7.95:1 on paper · 9.13:1 on card · white on oxblood 9.13:1 · hover #5F2530 11.71:1Division 40 ledger green
The plant-and-equipment curve, its callout and the confirmation panel.
5.50:1 on paper · 6.32:1 on card · white on green 6.32:1 · hover #215348 8.77:1Secondary ink
Supporting copy and captions.
6.66:1 on paper · 7.65:1 on cardMono ink
Eyebrows, year numbers and the permanent declaration ruled under the axis.
5.11:1 on paper · 5.87:1 on cardThis is the only site in its wave with no minted accent ink. Both accents were chosen dark enough to serve as body text on paper before anything else was drawn, because on a working paper the colour is the notation: the oxblood and the green have to be readable at 13 px inside a table, not just legible as marks. Oxblood clears 7.95:1 and green 5.50:1 on the ground, so neither needed the usual two-token remedy. Large tinted fills carry the ink instead — #E4CFD2 at 10.88:1 and #CFE0DA at 11.78:1 — and the stamp’s second line uses a pale oxblood (#F3DCDE) that measures 7.00:1 on its own fill. The service-area map labels are body ink with a land-coloured halo, measured at 12.36:1 where they cross the drawn suburbs.
Type
- Bricolage Grotesque 800 for display. A grotesque, not a high-contrast serif — an accountant’s paper set in a bookish serif reads as a brochure about accounting rather than as the document itself. Bricolage has enough idiosyncrasy in its bowls and terminals to keep the page from reading as a template, and it holds up at the 96 px the fee is set at.
- Source Sans 3 for body, 17 px on desktop and 16 px minimum on mobile. It is a workhorse that does not argue with the display face, and it sets long tax explanations without fatigue.
- Roboto Mono 500 in exactly one role: every figure. The year columns, the axis, the fee ledger, the eyebrows and the on-sheet callouts. Tabular numerals are the entire point — a schedule whose figures do not line up vertically is not a schedule.
- The typographic bravery is the schedule itself, and the display moment beside it: the fee at 96 px in the display face over a mono line at wide tracking reading YR1 $20,835 · DIV43 33Y · DIV40 DV20%. Two units of information, one enormous and one tiny, sharing a hairline.
The fee model
Flat professional fees, exclusive of GST, quoted before work starts. Two of the five reports carry a $120 multi-property discount because a second unit in the same block genuinely saves a trip and a set of plans; the other three do not, because a second cost estimate saves the practice nothing and there is nothing to pass on. The estimator’s cheapest reachable output is $520, which is exactly the services grid’s floor.
| Report | Fee | Unit | Each after the first | No-inspection |
|---|---|---|---|---|
| Residential tax depreciation schedule | $770 | property | $650 | not available |
| Commercial & industrial schedule | $1,650 | property | $1,530 | not available |
| Replacement cost (insurance) valuation | $880 | property | $880 | not available |
| Progress-claim certification | $520 | inspection | $520 | not available |
| Construction cost estimate & feasibility | $1,980 | stage | $1,980 | −$180 → $1,800 |
| Regional WA travel — Bunbury, Geraldton, Kalgoorlie | +$340 | engagement | added once | — |
Only the report chip and the quantity stepper multiply; the travel adjustment is flat and added once whatever the quantity. Nothing multiplies by area, age or how the property was bought.
The illustration arithmetic
Capital works: area × $2,150/m² as a typical Perth project-home construction cost, at the statutory 2.5% a year, drawn flat for the remaining years on the forty-year clock. Plant: area × $310/m² as a typical rental fit-out, at 20% diminishing value. Both rates are illustrative Perth averages and the page says so wherever a figure appears. Every reachable cell of the grid is a whole dollar by construction — $2,150 × 2.5% is $53.75 per m² and $310 × 20% is $62 per m², both exact against a 20 m² stepper — so there is no rounding rule to invent.
| Building | Division 43, year one | Years | Division 40, year one | Year-one illustration |
|---|---|---|---|---|
| 2019, bought new, 180 m² (rest state) | $9,675 | 33 | $11,160 | $20,835 |
| 2002, bought established, 300 m² | $16,125 | 16 | nil | $16,125 |
| 2025 new build, bought new, 400 m² | $21,500 | 39 | $24,800 | $46,300 |
| 2019, bought new, 80 m² | $4,300 | 33 | $4,960 | $9,260 |
| 1975, bought new, 180 m² | nil | 0 | $11,160 | $11,160 |
| 1975, bought established, 180 m² | nil | 0 | nil | $0 — the schedule draws empty |
The model was reimplemented from scratch in a separate oracle and run over the full control grid — five reports × ten quantities × three visit states, and four build years × two purchase states × seventeen floor areas — then compared cell by cell against the figures the page actually renders. 54 oracle checks and 395 driven browser assertions, all green. Two figures in the first draft of the oracle were wrong; both were the author’s own guesses about how many distinct year-one values the grid can produce, and the enumeration caught them. Nothing in the specification needed correcting.
Forced states, and what happens on the way back
Two rules force the purchase toggle, and one forces the inspection. Each one remembers what it overrode, and each remembers it honestly.
- A 2025 new build forces “bought new.” You cannot buy a new build established — if nobody lived in it before you, the plant is yours to claim. “Bought established” is disabled with that reason on the control, and remembered.
- A feasibility forces “bought new” too, because the building does not exist yet. Crucially this force does not overwrite a memory already held by the first one — a control that becomes compulsory for a second reason must not clobber what the first reason parked.
- Partial unwind. Take a 2002 building bought established, switch to 2025 (forced new, established remembered), switch the report to a feasibility (still forced, memory untouched), then switch the age back to 2002. The age constraint has lifted but the feasibility has not, so the toggle holds at bought-new rather than stepping through an intermediate. Switch the report back to residential and the memory finally lands: established restored, fee back to $770, year one back to $16,125.
- “No inspection” is only offered on the construction cost estimate, with the reason on every other report: a schedule or a valuation without an inspection is a guess. Selecting any other report forces a Perth metro inspection and remembers the choice; going back to the feasibility restores it.
- Memory invalidation: explicitly choosing the purchase state while it is forced clears the memory for good. The page will not silently restore something you have since overruled by hand.
All five chains are driven by real clicks in the verification harness and asserted at every step, including the intermediate cells — not just the endpoints.
Three iteration passes
The page was built, then audited three times against the running result. Everything below is a defect that was actually found and fixed, not a description of intent.
Craft
- The schedule was deleting its own accessible description. The redraw cleared the SVG by child count — while (childNodes.length > 2) — which counts whitespace text nodes, so the <desc> referenced by aria-labelledby was removed on the very first redraw and never came back. The chart was announcing itself to a screen reader by a description that no longer existed. Replaced with a single scene-root group that is removed and rebuilt, leaving the title and description untouched.
- The trust strip read “AIQSmember”. Each item was inline-flex, which collapses the whitespace between an inline element and the text after it, so “AIQS member” and “Schedules in 5–7 days” lost their spaces entirely. Fixed with a flex gap.
- The sheet’s head line ran underneath the fee stamp. It picked its longest fitting form from a character count using a 0.602 em advance and ignored the 0.13 em tracking on the mono face — an 18% under-estimate. Replaced with a real getComputedTextLength() fit, and given a four-step ladder of forms so middle widths shorten gracefully instead of dropping straight to the shortest one.
- The axis origin and the year row were crowding each other. The $0 label shared a corner with the YEAR caption, and the year numbers sat a fraction of a line above the permanent declaration. The origin label now sits above its own rule and the year band was grown from 1.9 to 3.9 line-heights.
- The badge docked at 1000 px. The dock test also measured the sheet’s own column, so a two-column hero at 1000 px produced a 485 px column and docked the badge below the artwork — 360 px above the breakpoint the wave pins. Removed the column test; narrow columns are carried by the clearance arithmetic instead.
- A global em { font-style: normal }, written for the oxblood clause in the headline, was silently killing the emphasis in the FAQ’s “assets you buy and install yourself”. Scoped to the headline.
- The refusal state printed the same 240-character paragraph twice, once across the blank grid and once in the stamp two inches away. The stamp now takes the pinned short form in that one state; the sheet, the ledger and the plain-text mirror all still carry the full string.
Depth
- The forty-year clock marker. The capital-works band stopped mid-chart and the plant line dropped to the axis with no explanation — correct for a stacked chart and unreadable as one. A dashed oxblood rule now marks the year the clock runs out and captions it with the year number, and the AGE chip’s effect became legible instead of merely correct.
- The ghost. The schedule you were just looking at now stays on the paper for 1.15 seconds as a dashed stair, fading quadratically while the new one grows underneath. It is the site’s argument as a motion: the drawing moves, the stamp does not.
- The Division 40 callout ran off the sheet. Its fallback position — used whenever the stack leaves no headroom above it, which is exactly the 400 m² new-build case — was a fixed fraction across the plot with no width check, so the word “DIMINISHING” was sitting on the sheet’s right edge. Re-anchored to the plot’s right edge so a long callout cannot overflow at any width.
- The settle flag was lying by 1.15 seconds. The scene reported itself at rest while the ghost was still fading, which meant a screenshot taken on the flag could catch a transient. The flag now waits for the ghost, and the column-breathing amplitude waits with it.
Hardening
- The focus ring was invisible on half the controls. It was oxblood — the same colour as every selected chip and the primary quote button, so keyboard focus on a selected chip drew an oxblood ring around an oxblood fill. Changed to ink, with the paper colour inside the dark Sapience band where ink would vanish instead.
- The chip groups announced themselves as radio groups and did not behave like them. Four role="radiogroup" sets with no arrow-key support at all. Added the full pattern: roving tabindex, Arrow keys, Home and End, all selecting as they move and skipping any option that is disabled-with-reason — so the locked “bought established” chip cannot be reached by arrow while a force is holding it.
- Removed one ornament. A disabled chip was carrying three simultaneous signals — grey fill, strikethrough and a not-allowed cursor. The strikethrough went; the reason is written on the card underneath, where it can actually be read.
- Verified rather than assumed: the badge rectangle never intersects a drawn mark, checked at 375 / 640 / 641 / 1000 / 1200 px and again at device pixel ratio 2, across all seven pinned scenarios; no scene text escapes the sheet in any of them; the masthead wordmark clears the call button at every width; every form pattern proved by driving checkValidity() on a deliberately invalid value rather than by reading the attribute; a rapid-fire drive firing seven chip changes and thirty-two stepper taps in a single evaluation with no settle between them, landing on $770 and $20,835 with exactly forty columns drawn and the ruling loop settled once.
Conversion decisions
- The fee is above the fold and the schedule is beside it. At 1200 px the $770 appears twice inside the first 750 px — once at 96 px in the hero column and once stamped on the sheet — because the page’s thumbnail is its first commercial argument and a price below the fold does not make the picture.
- The call button leads, the calculator supports. The primary call-to-action is a phone number, not the estimator: this trade converts on a two-minute conversation about a build year. Below 641 px a sticky bar carries a call button and a jump to the form at all times.
- The form asks the two questions that decide the answer. Name, phone, suburb, the year you settled, the report you think you need, and a property description that arrives pre-filled from whatever the visitor set on the schedule — so the enquiry lands already carrying the scenario and the practice can open the conversation where the visitor left it. Editing the textarea marks it touched and the page stops overwriting it.
- Every control either changes the price honestly or says why it can’t. The three neutral controls carry the neutrality statement on the card itself. “No inspection” is disabled on four of the five reports with the reason written out, not hidden in a tooltip.
- The refusal is a conversion feature, not a disclaimer. Telling a Victoria Park duplex owner not to order anything is the most persuasive thing this page does; one of the three demonstration reviews is about exactly that call. The FAQ answers “is it worth it on my place” first, and answers it with “sometimes no”.
- No fake network round-trip. The confirmation panel is hidden on load and on an empty submit, appears only after a valid submit, and says plainly that nothing was sent and what the real build would do instead.
Local SEO we’d implement for the real business
The demonstration page itself carries only WebPage and CreativeWork structured data describing it as a demonstration — marking up a fictional practice as a real local business would put invented data into a real search index. Below is the markup a real Ledgerline would ship, as a code sample rather than live JSON-LD.
The top-level type is ProfessionalService — an actual schema.org organization type. Quantity surveying has no dedicated subtype, so the specialty is carried by additionalType, knowsAbout and a hasOfferCatalog naming each report at its real fee, which is the honest way to say “quantity surveyor” to a search engine rather than borrowing a type that means something else.
This panel deliberately ships no review or rating markup, even as a sample. Star ratings attached to tax work invite a practice to compete on the size of the number it produces, which is the exact incentive this page is built to refuse — and rating markup on a professional service whose fee is fixed adds nothing a prospect can act on. Testimonials belong on the page as copy. For a trade where ratings genuinely help a customer choose, we would ship them; here we would not, and we would tell the client why.
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "ProfessionalService",
"@id": "https://ledgerline.com.au/#practice",
"name": "Ledgerline Quantity Surveyors",
"additionalType": "https://en.wikipedia.org/wiki/Quantity_surveyor",
"description": "Tax depreciation schedules, replacement-cost valuations, progress-claim
certification and construction cost estimates for Perth property.",
"url": "https://ledgerline.com.au/",
"telephone": "+61 8 9XXX XXXX",
"email": "office@ledgerline.com.au",
"image": "https://ledgerline.com.au/img/west-perth-office.jpg",
"logo": "https://ledgerline.com.au/img/ledgerline-mark.svg",
"priceRange": "$$",
"currenciesAccepted": "AUD",
"address": {
"@type": "PostalAddress",
"streetAddress": "Level 2, 000 Colin Street",
"addressLocality": "West Perth",
"addressRegion": "WA",
"postalCode": "6005",
"addressCountry": "AU"
},
"geo": { "@type": "GeoCoordinates", "latitude": -31.9469, "longitude": 115.8395 },
"openingHoursSpecification": [{
"@type": "OpeningHoursSpecification",
"dayOfWeek": ["Monday","Tuesday","Wednesday","Thursday","Friday"],
"opens": "08:30",
"closes": "17:00"
}],
"areaServed": [
{ "@type": "City", "name": "Perth" },
{ "@type": "AdministrativeArea", "name": "Western Australia" }
],
"memberOf": {
"@type": "Organization",
"name": "Australian Institute of Quantity Surveyors"
},
"knowsAbout": [
"Division 43 capital works deductions",
"Division 40 plant and equipment decline in value",
"TR 2022/1 effective lives",
"Replacement cost insurance valuation",
"Progress claim certification"
],
"hasOfferCatalog": {
"@type": "OfferCatalog",
"name": "Reports",
"itemListElement": [
{ "@type": "Offer", "priceCurrency": "AUD", "price": "770",
"itemOffered": { "@type": "Service",
"name": "Residential tax depreciation schedule" } },
{ "@type": "Offer", "priceCurrency": "AUD", "price": "1650",
"itemOffered": { "@type": "Service",
"name": "Commercial and industrial depreciation schedule" } },
{ "@type": "Offer", "priceCurrency": "AUD", "price": "880",
"itemOffered": { "@type": "Service",
"name": "Replacement cost (insurance) valuation" } },
{ "@type": "Offer", "priceCurrency": "AUD", "price": "520",
"itemOffered": { "@type": "Service",
"name": "Progress-claim certification, per inspection" } },
{ "@type": "Offer", "priceCurrency": "AUD", "price": "1980",
"itemOffered": { "@type": "Service",
"name": "Construction cost estimate and feasibility" } }
]
},
"sameAs": [
"https://www.linkedin.com/company/ledgerline-qs",
"https://www.aiqs.com.au/"
]
}
</script>
Alongside it: a verified Google Business Profile at the West Perth address with the practice’s real category, an FAQPage block generated from the six questions already on the page, per-suburb landing content only where the practice genuinely works, and BreadcrumbList markup once there is more than one page.
What would change for the real client
- Their measurements, not an illustration. The $2,150/m² and $310/m² rates are Perth averages standing in for real work. A live build would draw the schedule from the practice’s own takeoff — Cubit or Buildsoft — so the page could still show a worked illustration while every signed report came off measured quantities.
- The enquiry becomes an engagement. The form posts into Karbon or FYI as a job with the scenario attached, an engagement letter goes out through DocuSign, and time and fees run through Xero Practice Manager so the fixed fee on the page is the fee that gets invoiced.
- Their photography and their reviews. A real inspection photo set replaces nothing here — the drawings stay, because the schedule is the product — but the trust strip picks up the practice’s actual registration numbers, its real professional indemnity certificate, and a reviews feed from Google Business Profile rather than demonstration copy.
- Booking. An inspection scheduler pointed at the surveyors’ real calendars, so “usually within three business days” is a bookable slot rather than a promise.
- The numbers stay honest. Whatever the source, the rule this page is built on does not change: the fee is quoted before the work, it does not move with the deduction, and the schedule is applied to a return by the client’s accountant — not by the quantity surveyor.
Honesty notes
- The practice, its reviews, its ABN, its tax agent number and its phone number are invented, and the phone number uses a reserved pattern that cannot reach anyone.
- The institutions and the law are real. Division 40 and Division 43 of the ITAA 1997, the second-hand plant measure that took effect at 7:30pm on 9 May 2017, the 17 September 1987 capital-works date, TR 2022/1 effective lives, and the Australian Institute of Quantity Surveyors are all genuine — getting those wrong to protect a fiction would be the dishonest choice.
- Nothing on the page is advice, and nothing on it is anyone’s deduction. The chart carries a permanent, non-dismissible declaration under its own axis at every width, repeated in the fee ledger and in a plain-text mirror for screen readers. The words “estimate”, “deduction” and “return” never appear as a promise, and the page states plainly in three places that the schedule is applied to a return by the client’s accountant, not by the quantity surveyor.
- The illustration deliberately does not model renovations on a pre-1987 property, and says so — a previous owner’s 2006 kitchen can genuinely still be claimed, and guessing at it on a marketing page would be exactly the kind of number a reader should not trust.
- Demonstration reviews are written as demonstration copy, are not attributed to real people, and are deliberately not marked up as structured review data.