Ashfield Planning View the site  ·  Index

How this page was made

A lot plan on tracing paper, priced to the whole dollar

Ashfield Planning is a fictional West Perth town planning practice. Its customer is the Bayswater couple with a 500 m² back yard, the Victoria Park café owner taking over a shopfront last approved as a hairdresser, and the family cutting a lot off the rear. The page has one job: get a fixed-fee proposal booked with the application type, the density, the count and the advertising already named — and the council’s own fee declared at cost, never estimated.

The signature: the Envelope

A 30 × 30 m plan window, drawn in SVG, that re-draws itself as you change the application.

The whole drawing lives inside one constant frame: a 30 × 30 metre plan window on a 5 m grid with a 10 m scale bar, at a fixed 15 units-per-metre scale. Nothing zooms and nothing re-fits — every application type is drawn at the same scale in the same window, so the setback lines mean the same thing on every chip. The subject lot is 20 × 25 m (500 m²) with the street frontage at the bottom, faint neighbours numbered 41 and 43 either side, and the north point inside the frame’s top-right corner.

Four controls redraw it. The application type chip swaps the subject entirely — a dwelling, a dwelling with a hatched business room and a single bay, a commercial tenancy in a row of three shopfronts with four car bays, n grouped-dwelling footprints against a common-property driveway, or n survey-strata lots with the access leg drawn. The R-Code chip re-hatches the buildable envelope and re-dimensions the front, side and rear setbacks. The count stepper adds or removes footprints and lots. The advertising toggle plants a council-red site sign on the frontage.

The price lives inside the artwork as a title block in the plan margin — drawing number, revision letter, the total set large, the application line beneath it in tracking-wide caps, and the declarations underneath as footnotes. That is deliberate: the drawing and the fee are the same object, and a screenshot of the page has to carry the commercial argument on its own.

Techniques

  • Hand-built SVG, no library. Every element is created with createElementNS from a metre-based coordinate helper (mx(m), my(m)), so the drawing code reads in metres and the geometry can be checked against the setback table by eye.
  • A dimension sweep on requestAnimationFrame. Three overlay segments grow along the setback dimension lines over 55% of a 6.2 s cycle, then hold complete for the rest — a measuring pass that is never invisible at any capture instant, so the seven-second thumbnail always lands on a drawn or drawing state. The delta is clamped at both ends (min(max(now − last, 0)/1000, 0.05)) so a vsync timestamp that precedes the scheduling clock can never drive a negative length.
  • Dual-speed redraw. The first draw earns the full entrance stagger (26 ms per element, capped at 1.5 s); every state change afterwards redraws snappy (11 ms, capped at 520 ms) via a firstDraw flag, so a chip tap reads as a pen moving, not as lag.
  • Token invalidation in the handler. Every control bumps the sweep token and cancels the in-flight frame before the state swap, so a rapid burst of clicks can never leave an animation closed over the previous scene’s line set.
  • Procedural tracing-paper grain. 190 hair-thin fibres and 60 pale flecks from a seeded linear-congruential generator, in a group that drifts 6 × 5 px over 26 seconds. The group carries no transform attribute, only a CSS animation — SVG presentation attributes and CSS rules for the same property never share an element on this page.
  • A phone-width viewBox crop. Below 641 px the SVG swaps to a 498-unit viewBox that drops the title-block margin, the title block docks beneath the drawing as a real block, and every badge string switches to its short form. The sheet border is a second rect toggled by attribute — SVG elements have no hidden IDL property, which is exactly the bug the first verification pass caught.
  • Container-query type. The title block’s three type sizes scale against the drawing container’s own width in cqw, not the viewport, because the stage is 644 px inside a two-column hero at 1200 and 940 px full-width at 1000. Plain pixel fallbacks sit above each declaration.

The second read. Tracing paper keeps what was traced before it. Every time you change the drawing, the previous subject stays behind as a 13%-opacity ghost of its own outlines — and the revision letter in the title block advances, A, B, C, so the drawing quietly records how many times you changed your mind.

The fee model

Fixed professional fees, GST included. Whole dollars everywhere — every rate and every step is an integer, so no rounding rule is needed.

Ashfield Planning — fixed professional fees
ApplicationBasePer extraCountTitle-block line
Home business approval$1,600lockedhome business approval
Single house (development application)$2,400lockeddevelopment application, single house
Change of use (commercial tenancy)$3,200lockedchange of use, commercial tenancy
Subdivision advice + WAPC application$3,600+$800 / lot2–5subdivision advice and WAPC application, n lots
Grouped dwellings (development application)$4,800+$900 / dwelling2–4development application, n grouped dwellings
We run the 14-day advertising period+$550flat

What the controls do to the money

  • The base fee is flat per application. The extra-dwelling loading multiplies n − 2 on grouped dwellings; the extra-lot loading multiplies n − 2 on subdivision. Advertising is flat. The R-Code multiplies nothing at all — and the page says so rather than letting a live control silently no-op: “$0 — the R-Code changes the drawing and the assessment, not our fee.”
  • On a change of use the R-Code chips are disabled with a reason (“a commercial tenancy isn’t assessed against the R-Codes — the use table is”), the value is retained, and the price-neutral declaration is replaced by a use-table note. On single house, home business and change of use the count stepper is disabled with its own reason and the number is held.
  • Chipping change of use forces advertising on and locks the toggle — the council requires it. The previous on/off value is remembered and restored when you pick another type. Because the toggle is locked while forced, no explicit change is possible during the lock, so the memory always survives it.
  • Grouped dwellings caps at four. Coming from a five-lot subdivision, the count is forced to 4 and 5 is remembered; chipping subdivision restores it. Any explicit press of the stepper while forced clears the memory. On a partial unwind — grouped, then single house, then grouped again — the forced 4 is held, because 5 is still invalid; only subdivision restores it.
  • The council’s application fee prints no number, ever. It is set by regulation from the estimated cost of development, so any figure we printed would be the dishonest part. It is declared at cost on all three surfaces wherever the total appears.

Worked examples, measured from the running page

ScenarioArithmeticTotal
Rest state — grouped, 3 dwellings, R40, advertising off4,800 + 900 × 1$5,700
Home business, R30, advertising off (cheapest output in the model)1,600$1,600
Single house, R20, advertising off2,400$2,400
Change of use — advertising forced on3,200 + 550$3,750
Subdivision, 5 lots, advertising off3,600 + 800 × 3$6,000
Grouped, 4 dwellings, advertising chosen4,800 + 900 × 2 + 550$7,150
Subdivision, 2 lots, advertising on3,600 + 550$4,150
Count force / restore: subdivision @5 → grouped (forced 4) → subdivision6,000 → 6,600 → 6,000$6,000
Memory invalidation: subdivision @5 → grouped (forced 4) → step to 3 → subdivision3,600 + 800 × 1$4,400

The cheapest output the estimator can produce is $1,600, which is exactly the services grid’s “home business approval from $1,600” floor. A page whose calculator undercuts its own price list argues with itself.

The drawn setbacks

R20 front 6.0 / side 1.5 / rear 6.0 · R30 4.5 / 1.2 / 4.0 · R40 4.0 / 1.0 / 3.0 · R60 3.0 / 1.0 / 2.0, in metres, on the 500 m² lot. These are indicative, for the drawing, and the page says so beside the drawing and again in the FAQ: real deemed-to-comply setbacks under the Residential Design Codes move with wall height, wall length, boundary walls and major openings, and a local planning scheme can override them. Drawing a number we can’t stand behind without the caveat would be the dishonest choice.

Palette

Argued from survey tracing paper, a blue-black drafting pen, the mauve of a scheme map, and the red pen that marks an advertised application. Every ratio below was measured from the shipped tokens in the running page with WCAG arithmetic.

Tracing paper#ECEAE1

The page ground. Warm enough to read as drafting stock, pale enough to carry a 12.35:1 ink.

Drawing sheet#F7F5EF

The plan sheet inside the card, a half-step lighter than the page so the drawing sits forward. Body ink measures 13.65:1 on it.

Survey blue-black#232734

Body ink and the primary CTA. 12.35:1 on the ground, 14.88:1 on white cards; white labels on the fill measure 14.88:1, and 16.83:1 on the #191D28 hover.

Soft ink#4E5566

Secondary copy, scene annotations and quiet footnotes. Measured 6.19:1 on the ground and 7.46:1 on white — a real body-copy ink, not a tinted accent.

Scheme mauve#5A4A8C

The envelope hatch, the dimension lines, chip outlines and the secondary CTA. 6.24:1 as text on the ground, 7.52:1 for white labels on the fill (9.72:1 on the #493B74 hover).

Mauve ink#4A3C77

The pre-minted deeper twin for mauve copy under 14 px — the h1 subline, the title-block application line (measured 9.53:1 on white) and the map’s river label.

Council red#B03A2E

Marks only: the advertising sign on the frontage and the note rule beside a forced advertising line. 4.99:1 on the ground — strong enough for a mark, never used for small copy.

Red ink#932A20

The two-token remedy: any small red copy (the ADVERTISED label on the plan, the “West Perth” map pin, form errors) uses this at 6.72:1 on the ground and 7.43:1 on the sheet.

The ink/line split is the rule that keeps the accents honest: mauve and red draw lines and marks; text gets its own ink token. The service-area map obeys the same rule — every place name is body ink with a sheet-coloured halo (paint-order: stroke), never a line colour, because those labels cross a mauve river and a pale sea.

Deck copy 6.19:1H1 12.35:1H1 subline 7.91:1Title-block notes 14.88:1Quiet footnotes 7.46:1Application line 9.53:1Service copy 7.46:1FAQ answers 7.46:1

Type

Spectral 600 for display — a document serif drawn for screen reading, which is exactly what a statutory planning report is. It carries the h1, the section headings, the fee figures and the review quotes. Karla does the work: a compact grotesque whose figures set cleanly at dimension-label sizes, so “front 4.0” on the drawing and “$5,700” in the ledger belong to the same document.

The typographic moment is the masthead pair: the practice name at up to 2.78 rem in Spectral, with the promise set immediately beneath it in Karla caps at 0.14 em tracking — a nameplate and its subtitle, the way a drawing sheet names itself. The title block repeats the device at a quarter of the size, which is what ties the fee to the drawing.

Structure is used only where the content is genuinely sequential or categorical: the services grid is numbered 01–08 because it is a fee schedule, and the iteration log is numbered because the passes happened in order. Nothing else gets a rule or a number.

Conversion decisions

  • The drawing is the hero, not a section further down. At 1200 px the hero is a two-column grid with the copy at left and the plan sheet at right, so the drawing and the price badge both sit inside the first 750 px — the width and height a preview screenshot captures. A signature that only appears after a scroll does not make the argument.
  • Controls sit below the drawing, not above it. Putting four control groups above the plan would have pushed the drawing out of the fold. Below the fold the desk splits into the controls at left and the ledger at right, and the controls panel ends with its own CTA pair — the moment a visitor has configured their application is the moment to ask for the enquiry.
  • Everything you set pre-fills the form. The application type, the density, the count, the advertising choice and the computed fee are written into the job description and the type select, and the lot size defaults to 500 m². A visitor who has played with the Envelope arrives at the form with the brief already written; the fields stop being homework.
  • The form asks five things. Name, phone, suburb, application type and the job. Email is not asked for because the promise is a phone call; lot size is optional because half the people who need us don’t know it yet. Every extra required field is a person who leaves.
  • Two FAQ answers lose us work on purpose. “Do I even need approval for a patio or a shed” answers often not, and we’ll tell you on the phone for free, and “can you guarantee approval” answers no, and nobody may. In a trade where the customer is braced for a sales job, the answer that costs the business a fee is the one that earns the call.
  • The confirmation is local and says so. Nothing is sent. Submitting a valid form reveals a designed panel that repeats the application, the suburb, the fee and the at-cost council line, with a plain statement that this is a demonstration page. Empty submits show inline errors and the panel stays hidden — there is no fake network round-trip anywhere on the page.
  • The mobile bar is a call and a booking. A person standing on a lot with a builder needs the phone, not a menu. The sticky bar carries a tel: and the enquiry anchor at 50 px tall; the masthead keeps a click-to-call at every width.

Iteration log

Three passes, each one driving the real controls in headless Chrome and asserting the output — not loading the page and looking at it. The harness finished at 402 assertions against an independently reimplemented fee oracle.

  1. Craft — a dead toggle, and five labels in the wrong place

    • The first driven run went 298/300 and the two failures were a real defect: the phone-width sheet border was toggled with element.hidden = true on an SVG rect, which has no such IDL property, so the assignment landed as a silent expando. The viewBox cropped correctly while the wrong border stayed on screen. Replaced with setAttribute/removeAttribute against the page’s [hidden]{display:none !important} guard.
    • Screenshot review at native resolution found the dimension figures fighting the drawing: the side dimension was measured against the left boundary, where the common-property driveway is hatched, so a 1 m line was invisible under it. Moved the side dimension to the right boundary and the figure into the neighbour margin; moved the front dimension to x = 11 and the rear to x = 21, the two corridors that stay clear on all five application types.
    • On change of use the frontage dimension was landing on the car bays and the “4 BAYS” tag was sitting in the road. Frontage moved above the tenancy row, the bay tag to its left.
    • The garage division and the home-business room were drawn on the same side of the dwelling, so “GARAGE” rendered underneath the hatched room and read as a single letter. Garage moved to the right, under the crossover; the business room labelled inside itself.
    • The subdivision drew the rear lot at the width of the front lots, leaving a wedge of bare envelope hatch above the access leg that read as a rendering fault. The rear lot now owns the leg and keeps the full frontage width, which is what a battleaxe actually is.
    • The h1 was breaking as “Ashfield Planning / — town planners / in West Perth”, an orphaned em dash on line two. Restructured into a nameplate and a tracked subline. Dropped the duplicated “scale 1:200 @ A3” caption — a responsive SVG has no paper size, so the claim was untrue as well as redundant.
  2. Depth — a drawing that records its own history

    • Added the tracing-paper ghost: the previous subject persists behind the new one as 13%-opacity outlines with all text and dashes stripped. The first attempt kept fills and labels and made the change-of-use scene unreadable; strokes only, one weight, no text is the version that reads as paper rather than clutter.
    • Added the revision letter to the title block — DWG AP-42 · REV A advancing one letter per redraw. It costs nothing, it is invisible on a first visit, and it is the detail a second visitor notices.
    • Added crossovers: two ink ticks and a paper-coloured gap where every driveway, bay and access leg meets the frontage, so the lot boundary breaks where a vehicle actually crosses it. Added a dashed alfresco return under the main roof of the dwelling.
    • Rebalanced the desk: the controls panel had 250 px of dead white space beside the taller ledger. Set the grid to align-items:start and gave the controls their own closing CTA pair, so the empty space became the second-best place on the page to ask for the enquiry.
    • Retuned the title-block type to container-query units after finding it set at 10.2 px inside a 314 px-wide block at a 1000 px viewport — the stage width and the viewport width are not proportional across the layout’s breakpoints, so vw was the wrong ruler. Softened the dimension sweep from 3.4 px to 2.6 px so it reads as a measuring pass rather than an ink blot, and gave the subdivision a paler envelope hatch so the survey-strata lines stay on top.
  3. Hardening — 60 collision sweeps, a resize, and a hidden tab

    • Wrote a programmatic label-collision sweep: every scene text node’s bounding box against every other, plus the title block against every label and against the drawn subject — run over five application types × two R-Codes × advertising on/off at 375, 1000 and 1200 px. Sixty combinations, zero overlaps. Asserts prove state; only boxes and pixels prove composition.
    • The same sweep over the service-area map caught three collisions from a newly-added “SWAN RIVER” label whose rotated bounding box swallowed the Bayswater node. Moved it downstream. Also rebalanced Vincent and Bayswater, which had been overlapping into one unreadable word, and capped the map at 740 px so it stopped being 811 px tall.
    • Drove a resize across the dock breakpoint in both directions: at 600 px the badge docks static beneath the drawing with short-form strings and a cropped viewBox; back at 1200 it returns to the plan margin with the full templates and the wide sheet border. The ledger and the plain-text mirror carry the full strings at every width.
    • Verified the rAF loop freezes on a hidden document (one in-flight frame, 0.03 scene units of drift) and resumes with a fresh clock — no accumulated jump when a background tab comes forward.
    • Measured contrast from the shipped tokens in the running page rather than from the spec: thirteen text/ground pairs, lowest 6.19:1. Checked a focus indicator on every interactive class, every tap target ≥ 44 px at 375, the masthead wordmark’s right edge against the call button’s left edge, and zero elements crossing the 375 px viewport.
    • Two rapid-fire bursts — four type chips in one evaluate, then a chip plus three stepper presses plus a toggle plus an R-Code with no settle between — land on $5,700 and $6,550 with the forced advertising correctly unwound and the sweep rebuilt exactly once.
    • Removed one ornament (the redundant scale caption), confirmed the reduced-motion path parks the sweep complete and stops the grain drift, and re-read every number in this guide against the running page.

Local SEO we’d implement for the real business

The live page carries only WebPage, WebSite and BreadcrumbList structured data, naming itself a demonstration. A fictional practice must not mark itself up as a real one, and demo reviews must never become structured review data. For a real client we would ship the block below. schema.org has no town-planning subtype, so the top-level type is ProfessionalService — an actual organisation type in the LocalBusiness family — with the specialty named through additionalType, knowsAbout and a hasOfferCatalog. Reviews would come from the live Google Business Profile feed, with the client’s consent, never hand-written.

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "ProfessionalService",
  "additionalType": "https://www.wikidata.org/wiki/Q1002697",
  "name": "Ashfield Planning",
  "description": "Town planning consultants in West Perth — development applications, change of use, grouped dwellings and subdivision advice.",
  "url": "https://www.example-ashfieldplanning.com.au/",
  "telephone": "+61 8 9000 0378",
  "image": "https://www.example-ashfieldplanning.com.au/studio.jpg",
  "priceRange": "$$",
  "address": {
    "@type": "PostalAddress",
    "streetAddress": "Suite 000, 000 Hay Street",
    "addressLocality": "West Perth",
    "addressRegion": "WA",
    "postalCode": "6005",
    "addressCountry": "AU"
  },
  "geo": { "@type": "GeoCoordinates", "latitude": -31.947, "longitude": 115.842 },
  "areaServed": [
    "City of Vincent", "City of Bayswater", "City of Stirling", "City of Melville",
    "City of Fremantle", "City of Canning", "City of Swan", "City of Joondalup",
    "Perth", "Western Australia"
  ],
  "openingHoursSpecification": [
    {
      "@type": "OpeningHoursSpecification",
      "dayOfWeek": ["Monday","Tuesday","Wednesday","Thursday","Friday"],
      "opens": "08:30", "closes": "17:00"
    }
  ],
  "knowsAbout": [
    "development applications", "change of use", "grouped dwellings",
    "Residential Design Codes", "survey-strata subdivision",
    "Western Australian Planning Commission applications", "local planning schemes"
  ],
  "memberOf": {
    "@type": "Organization",
    "name": "Planning Institute of Australia"
  },
  "hasOfferCatalog": {
    "@type": "OfferCatalog",
    "name": "Town planning services",
    "itemListElement": [
      {
        "@type": "Offer",
        "itemOffered": { "@type": "Service", "name": "Home business approval" },
        "priceSpecification": {
          "@type": "PriceSpecification",
          "price": "1600", "priceCurrency": "AUD",
          "valueAddedTaxIncluded": true
        }
      },
      {
        "@type": "Offer",
        "itemOffered": { "@type": "Service", "name": "Grouped dwellings development application" },
        "priceSpecification": {
          "@type": "PriceSpecification",
          "price": "4800", "priceCurrency": "AUD",
          "valueAddedTaxIncluded": true
        }
      }
    ]
  },
  "aggregateRating": {
    "@type": "AggregateRating",
    "ratingValue": "4.9", "reviewCount": "38"
  },
  "review": [
    {
      "@type": "Review",
      "author": { "@type": "Person", "name": "From the live Google Business Profile feed" },
      "reviewRating": { "@type": "Rating", "ratingValue": "5" },
      "reviewBody": "Real review text, pulled with the client's consent — never written by us."
    }
  ]
}
</script>

Alongside the schema we’d ship a Google Business Profile with the primary category Urban planning department or Consultant, per-local-government landing pages (“development applications in the City of Bayswater”) written from the practice’s actual lodgement history, and the Envelope re-pointed at each council’s own R-Code map — a calculator that knows the visitor’s scheme is a stronger local ranking signal than any amount of copy.

What would change for a real practice

  • Their fees, their scope. Every anchor here is a demonstration figure. The first hour of a real build is spent turning the practice’s own fee proposal template into the estimator’s model, including the jobs it should refuse to price.
  • The WAPC’s Planning Online portal and council e-lodgement for the actual lodgement path, with the reference number written back so a client can follow their own file.
  • HubSpot (or Pipedrive) for the enquiry pipeline, so the Envelope’s configured scenario lands on the deal record instead of in an inbox — the planner opens the call already knowing the lot, the density and the count.
  • Dropbox Sign for the fee proposal, so the fixed fee the visitor saw is the document they sign, and Xero behind it for the invoice and the at-cost council fee as a separate disbursement line.
  • Google Business Profile for the review feed, replacing the demonstration testimonials with real ones under the schema above.
  • Their drawings. A real practice has approved site plans and photographs of what got built. Those replace nothing on this page — the Envelope stays, because a plan a visitor can change is worth more than a plan they can only look at — but they would fill the reviews and the local landing pages.