Fairbank ← Back to the site Index

Generative Assets · site 385 · industry demo

How the Fairbank page was made

A schedule of cover, set properly — and a pricing model that refuses to quote three things on purpose.

01 · THE CONCEPT

The brief in three sentences

Fairbank Insurance Brokers is a fictional small commercial broking firm in Osborne Park that places business insurance for WA trades, builders, hospitality operators, couriers and allied health practices. The audience is the Wangara sparky whose builder client just asked for a $20M certificate of currency this afternoon, and the Joondalup physio setting up her own practice who is not sure what "PI" actually covers. The page has one job: turn "what does business insurance cost for someone like me" into a phone call with the covers, the limits and an indicative annual figure already on the table.

Everything about the art direction follows from one decision — this is a policy schedule, not a comparison site. Orderly, quiet, adult. Every section is a block on the schedule with its own rule and its own reference number in mono, and the signature is literally a schedule of cover drawn to scale.

02 · PALETTE

Policy paper, a broker's stamp, and the slate of a schedule table

Argued from the physical object the page imitates: warm policy paper for the ground, near-black warm ink for type, a deep desaturated oxblood that reads as a stamp colour rather than a decorative terracotta, and a slate blue doing most of the visual work in the cover bands. Every ratio below was computed with WCAG arithmetic from the shipped hex, then re-measured against the ground it actually sits on.

#EFEDE7policy paper — page ground

#FFFFFFcard / sheet

#252220body ink · 13.50:1 on ground, 15.81:1 on card

#7A2E33oxblood · white 9.27:1 · as text 7.92:1 on ground

#41586Eslate · white 7.38:1 · as text 6.30:1 on ground

#5C5750muted ink · 6.11:1 on ground

#6B6459mono reference ink · 4.99:1 on ground

#DCD8CFhairline / rule

Eight band fills, eight measured label inks

The stack draws up to eight bands and each one prints a figure inside itself, so the label ink is chosen per band, not once for the whole chart. Every pair below was measured, not assumed.

SectionCoverBand fillLabel inkMeasured
SEC 01Public liability#41586E#FFFFFF7.38:1
SEC 02Tools & equipment#6E6152#FFFFFF6.01:1
SEC 03Commercial motor#7A2E33#FFFFFF9.27:1
SEC 04Professional indemnity#5B6E80#FFFFFF5.27:1
SEC 05Contents & stock#D8D2C4#25222010.49:1
SEC 06Business interruption#E2DDD1#25222011.67:1
SEC 07Cyber#2E4152#FFFFFF10.53:1
SEC 08Management liability#A8B6C2#2522207.63:1

Map labels are body-ink class, not accent: the service-area suburbs are set in #252220 with a land-coloured halo drawn through paint-order: stroke — 11.90:1 over the land fill and 9.91:1 where a label crosses the water. The oxblood is reserved for marks and rules; it never becomes small text on a coloured fill.

03 · TYPE

Two faces, and a third in exactly one role

Sora (600/700) carries the display — a geometric grotesque whose high-waisted figures make a large dollar amount look engineered rather than decorative, which is precisely the register a schedule of cover wants. Figtree is the workhorse: open apertures, 16px minimum on mobile, comfortable at 14px in dense ledger rows.

DM Mono (500) appears in exactly one role and never leaks out of it: policy and reference strings. Section numbers, limits, excesses, gridline values, the sheet header and the stamp's basis line. If it is a number that would be printed on a real policy schedule, it is mono; if it is prose, it is not.

The typographic moment is the stamp: the annual total set in Sora 700 with −0.035em tracking at clamp(28px, 2.7vw, 33px) — 32.4px at 1200, 28px at 1000 and on a phone — sitting on the oxblood in the sheet's upper-right margin with the basis line beneath it in 9.5px mono. It reads as something a broker pressed onto the page after the arithmetic was done — which is exactly what it is.

04 · THE SIGNATURE

The Cover Stack

An SVG schedule of cover drawn as a vertical stacked bar. Each active cover is a band whose height is proportional to that cover's annual premium, labelled inside the band with its section number, its cover name and its premium in mono. Behind the stack, gridlines every $1,000 run across the sheet and are labelled — they are the honest constant reference, and they visibly compress as the frame grows. Beside the stack, a schedule column lists each cover's section, limit of indemnity, excess and premium, aligned to its band by a leader line.

The second-read detail is that the schedule column and the stack are the same data set twice — and the band you hover lifts its schedule row (and its ledger row) with it. Once you notice that, the sheet stops being a chart and starts being a document.

The frame is measured in dollars, not pixels

The stack's frame is max($3,000, ⌈total ÷ $1,000⌉ × $1,000). At rest that is a $3,000 frame with four gridline values; at the largest reachable total in the model ($8,405) it is a $9,000 frame with ten. The gridline value never changes — only its pixel spacing does, which is the entire point of drawing it.

Drawn floor. A band draws at max(14 px, premium ÷ frame × plot height) so a thin band's label stays legible, and the band prints its real premium either way. The plot resolves to 320 px at 375, 480 px at 1000 and 500 px at 1200, so the 14 px floor is 4.4% of the frame on a phone and 2.8% on a desktop — that is the band below which the guard takes over. At rest the smallest band (tools & equipment, $180 against a $3,000 frame) is 6.0% and draws proportionally: the floor is a guard, not the normal case. It does bind for the same $180 line at the $9,000 ceiling frame, where the proportional height measures 6.4 px at 375 and 10.0 px at 1200.

Reconciling a dollar frame with a pixel badge

Because the stack's frame is set in dollars and the stamp's height is set in pixels, the two had to be reconciled explicitly rather than left to a fixed aspect ratio. The sheet's height is computed each draw as max(base aspect, (measured stamp height + 24 px) ÷ 0.18) — the stamp is measured with getBoundingClientRect() after its text is written, never assumed, so a longer basis line for a longer trade name grows the header strip instead of colliding with the top gridline. In practice the clearance term wins at every desktop width: at 1200 the stamp measures 94 px and the sheet resolves to 658 px against a base aspect of only 360 px; at 1000 it is 90 px and 634 px. A fixed aspect ratio would have put the stamp through the top of the stack at exactly those widths. Every sheet string is then fitted with getComputedTextLength() against the stamp's measured left edge, choosing the longest of two or three pinned candidates that actually fits, so the header never runs under the stamp at any width.

Motion

Bands tween toward their targets with an exponential ease, a = 1 − e^(−dt/τ), at two speeds: τ = 0.155 s with a 60 ms per-band stagger on the first draw (the entrance choreography), τ = 0.085 s with no stagger for every subsequent state change, so a toggle tap reads as snappy rather than as a replayed intro. At rest the settled stack breathes by less than a pixel and the stamp's ink flickers between 94% and 100% opacity like a stamp that has not quite dried. The rAF delta is clamped at both ends — dt = min(max(now − last, 0)/1000, 0.05) — because a vsync timestamp can precede the performance.now() that scheduled the frame, and a negative delta feeding a height is an error waiting for load. The loop pauses on document.hidden. Under prefers-reduced-motion the stack snaps to its settled frame and nothing moves at all.

05 · THE MODEL

What the numbers actually are

Every premium on the page is indicative. The declaration — "indicative annual premiums for a business with no claims in the last five years. The underwriter sets the actual premium once they've seen your claims history and your work mix — this is general information, not personal advice, and you get a Financial Services Guide before anything is placed." — appears on the stamp (short form), in the ledger, in the services grid, in the enquiry panel and in the plain-text accessibility mirror. It is never reduced to fine print, because on a page about insurance the basis of the number is the product.

The model is additive per cover, with two scaling drivers.

TradePublic liabilityProfessional indemnityCommercial motorContents & stockTools
Electrical contractor$760$980$1,180$340$180
Residential builder$1,240$1,460$1,320$380$180
Café & restaurant$940not available$860$1,240not available
Courier & light transport$880not available$2,480$260$180
Allied health practice$620$1,340$780$520not available

Turnover runs $250k–$3,000k in $250k steps and drives a band index b = turnover ÷ $250k − 1 (b = 0 at $250k, b = 11 at $3m). Added to the base: public liability +$40 × b, professional indemnity +$60 × b, contents & stock +$25 × b, business interruption $240 + $50 × b, cyber $420 + $20 × b. Staff runs 0–20 and drives management liability at $520 + $25 × employees — multiplying the actual headcount, since nothing in this model bills a minimum count.

Commercial motor and tools & equipment do not scale with turnover, and the page says so where you can see it happen: "a van is a van — motor and tools are rated on the asset, not your turnover". Turning the turnover stepper visibly grows five bands while those two hold perfectly still. A control that quietly moved a number it has no business moving would be the dishonest choice; a control that visibly declines to move it is the trust signal.

All eight band factors are whole dollars against whole band indices, so every reachable cell is a whole dollar by construction. That was not assumed — the verification harness enumerated the full trade × turnover × staff × cover-subset grid (322,560 cells) and found zero non-integer premiums and zero non-integer totals.

Locks, disables and the memory model

Public liability is locked on for every trade — "no insurer will write the rest of the pack without it" — which is also why this signature has no empty state: the stack always draws at least one band. Professional indemnity is locked on for the allied health practice ("you can't practise without it") and forced off for the café and the courier ("PI covers advice and design — it isn't a cover a café or a courier buys"). Commercial motor is locked on for the courier ("the vehicle is the business"). Tools & equipment is forced off for the café and the allied health practice ("no tools cover here — your equipment sits under contents & stock"). Every locked or disabled toggle stays in the tab order, is marked aria-disabled, and prints its reason on the chip — a control that silently no-ops reads as a broken page.

Each toggle keeps one remembered value. When a trade change makes a toggle forced, the value immediately before that change is written to memory — but a toggle that was already forced writes nothing, so a force that makes a control compulsory never overwrites a memory held from an earlier force. When the toggle becomes free again the memory is restored and cleared, and any explicit click on a free toggle clears its memory. The four-step drive is asserted end to end:

StepToolsProfessional indemnityMemory heldTotal
Electrical, $750k, 3 staff — PL + tools + PIonon$2,120
→ Café & restaurantforced offforced offtools ON, PI ON$1,020
→ Allied health practicestill forced offforced ONtools ON, PI ON (untouched)$2,160
→ Electrical contractorrestored onrestored oncleared$2,120

The third row is the whole point: professional indemnity is made compulsory there, and the memory that already says "the user had it on" survives that force untouched — so the fourth step restores the user's original choice rather than an intermediate one the user never made.

Measured figures

Three things this page refuses to price

06 · CONVERSION

Why the page is built this way

CTA placement. At 375px the phone number, the value proposition and the trust line are above the fold before the schedule is even mentioned — a sparky standing on site with a builder waiting wants a number, not a calculator. The calculator earns its place a scroll later, for the person who is comparing rather than panicking. The sticky bar carries both jobs (call / quote) and never leaves the bottom 140px.

Form fields. Name, phone, suburb, renewal date, trade, turnover, and what you need covered. Renewal date is the one field a broker genuinely needs and nobody else asks for — it decides whether this is a live opportunity this month or a diary note for October. Trade and turnover are selects rather than free text because they map straight onto the rating question. The brief textarea is pre-filled from the schedule you just built and marked as editable, so the enquiry arrives with the covers, the limits and the indicative total already in it: the signature is not decoration, it is lead qualification.

The confirmation is honestly a demo. It is hidden on load and on an empty submit, appears only after a valid filled submit, promises a callback within one business hour, and says plainly that nothing was sent. No fake network round-trip.

Trust before price. The trust strip sits between the covers and the reviews because for a financial-services page the licence, the association and the dispute scheme are the product's substrate. NIBA, AFCA, the General Insurance Brokers Code of Practice and the AFS licensing regime are real; the ABN, the AFSL number and the phone number are visibly patterned placeholders, and the page says once, plainly, that the business is fictional.

07 · LOCAL SEO

What we'd implement for the real business

The live page ships no business-shaped structured data at all — only a WebPage node that declares itself a demonstration. Publishing LocalBusiness and Review markup for a business that does not exist would be dishonest markup in a real index, so the schema we would ship for the real client lives here as a code sample instead. The top-level type is InsuranceAgency, a real schema.org LocalBusiness subtype and exactly right for a broking firm.

{
  "@context": "https://schema.org",
  "@type": "InsuranceAgency",
  "name": "Fairbank Insurance Brokers",
  "url": "https://fairbank.com.au/",
  "image": "https://fairbank.com.au/img/office.jpg",
  "telephone": "+61 8 9000 0385",
  "priceRange": "$$",
  "address": {
    "@type": "PostalAddress",
    "streetAddress": "Unit 4, 120 Hutton Street",
    "addressLocality": "Osborne Park",
    "addressRegion": "WA",
    "postalCode": "6017",
    "addressCountry": "AU"
  },
  "geo": { "@type": "GeoCoordinates", "latitude": -31.9021, "longitude": 115.8175 },
  "openingHoursSpecification": [{
    "@type": "OpeningHoursSpecification",
    "dayOfWeek": ["Monday","Tuesday","Wednesday","Thursday","Friday"],
    "opens": "08:00", "closes": "17:00"
  }],
  "areaServed": [
    { "@type": "City", "name": "Perth" },
    { "@type": "AdministrativeArea", "name": "Western Australia" }
  ],
  "hasOfferCatalog": {
    "@type": "OfferCatalog",
    "name": "Business insurance",
    "itemListElement": [
      { "@type": "Offer", "itemOffered": { "@type": "Service", "name": "Public liability" } },
      { "@type": "Offer", "itemOffered": { "@type": "Service", "name": "Professional indemnity" } },
      { "@type": "Offer", "itemOffered": { "@type": "Service", "name": "Commercial motor" } },
      { "@type": "Offer", "itemOffered": { "@type": "Service", "name": "Workers compensation" } }
    ]
  },
  "aggregateRating": {
    "@type": "AggregateRating", "ratingValue": "4.9", "reviewCount": "68"
  },
  "review": [{
    "@type": "Review",
    "author": { "@type": "Person", "name": "Dean R." },
    "reviewRating": { "@type": "Rating", "ratingValue": "5" },
    "reviewBody": "Certificate of currency with the right limit in my inbox inside 35 minutes."
  }]
}

Alongside it, for the real client: a Google Business Profile with the Osborne Park address and service-area suburbs, one location page per suburb only where there is genuine local content to put on it, and FAQPage markup on the FAQ block — which is the one piece of structured data on this page that would be honest to ship as-is if the answers were the real firm's.

08 · INTEGRATIONS

What would change for the real client

09 · ITERATION LOG

Three passes

Pass 1 — Craft

  • The memory model was subtly wrong. The first implementation wrote a memory whenever a toggle was forced after a trade change, not only when it became forced. Because public liability is locked on for every trade, it accumulated a memory on the first chip click that could never be cleared — the four-step drive ended with a stale memory held. Rewritten to write memory only on the free → forced transition, which is also the faithful reading of "a force that makes a toggle compulsory does not overwrite a memory already held".
  • The sheet header ran under the stamp at 1000px and 1200px. The header's trade/turnover/staff line was a single long mono string starting at x=14; at mid widths the stamp's left edge came back past it and the harness's stamp-versus-label collision check caught two overlapping labels at 1000 and one at 1200. Fixed by measuring the stamp's left edge and fitting every sheet string with getComputedTextLength() against it, choosing the longest of a few pinned candidates that fits; the turnover and headcount moved to the sheet's footer rule, where there is width for them, and each trade carries a pinned short label so a narrow header never falls back to a bare first word.
  • Band labels collided with their own premiums. "03 · COMMERCIAL MOTOR · 1 ve$1,180" — the left-hand name ran straight into the right-aligned figure at both 1000 and 1200. The premium is now measured first and the name is fitted into what is left of the band, falling back to a pinned abbreviation and then to the section number alone. The schedule column got the same treatment after its detail line ran off the right edge of the sheet at 1000.
  • The footer's right-hand mark now measures the left string's actual width, and reserves space for itself before the left string claims the row.
  • The cover toggles were unreadable. Eight chips inside a one-third-width control card gave each one about 130px, and the limit/excess/premium line wrapped over four lines. The control bar became two steppers on one row with the covers spanning the full width beneath, and each chip now separates its limit and excess (body copy) from its live premium (mono, oxblood) instead of running them together.
  • Map fixes. The Swan River was drawn through the Osborne Park star and its "OUR OFFICE" tag; the river now runs south of the office where it belongs, the graticule was moved off every label baseline, and the suburb labels scale up at phone widths so nothing renders under 8.5px — all three caught by looking at native-resolution captures, then locked down with a label-overlap assert at 375, 1000 and 1200.
  • Contrast re-measured from the shipped tokens rather than the spec: the mono reference ink lands at 4.99:1 on the ground, which passes but has no headroom, so it is used only for reference strings at 10–11px and never for body copy. All eight band label pairs measured between 5.27:1 and 11.67:1.
  • Type and spacing: the h1 was capping at 42px and running to seven lines in its column, so the scale was retuned to clamp(25px, 3.3vw, 38px); the trust line's separator dots were dangling at wrap points and became per-item markers; the enquiry textarea was clipping its own pre-filled third line.
  • Copy pass on the FAQ: the public-liability answer now names the specific expensive misunderstanding (that it covers rectifying your own faulty work) rather than describing the cover in the abstract.

Pass 2 — Depth

  • Enriched the signature into a document. The schedule column beside the stack now carries a leader line to each band's centre, de-collided with a minimum 32px row spacing that preserves order and clamps inside the plot — so eight covers can be listed against eight bands, two of which may be at the 14px drawn floor, without a single label collision.
  • The running total became a mark on the sheet. An oxblood rule now sits on the stack's live top edge with the total printed against it in the gridline gutter — so the figure on the stamp and the figure the stack actually reaches are visibly the same number. The label suppresses itself when the total lands within 10px of a $1,000 gridline, where it would just double the gridline's own label.
  • Headroom hatch. The space between the top of the stack and the top of the frame is filled with a fine 45° hatch, so the un-drawn part of the frame reads as headroom rather than as an unfinished sheet — and it visibly shrinks as covers are added until, at the ceiling, it disappears.
  • The steppers now say what they move. Each stepper card carries a one-line statement of exactly which covers respond to it — turnover moves five, headcount moves one — which turns the "rated on the asset, not your turnover" argument from a footnote into something you can watch happen.
  • Second-read detail: hovering (or keyboard-focusing) a cover toggle lifts three things at once — the band shifts 3px right, its schedule row lifts 2px, and the matching ledger row tints. The point being made is that the stack, the schedule column and the ledger are one data set rendered three ways, and you only notice on the second visit.
  • Dual-speed redraw added: the entrance stagger (60ms per band, τ = 0.155s) runs once, and every subsequent change redraws at τ = 0.085s with no stagger, so tapping four toggles in a row reads as responsive rather than as four replayed intros.
  • The stamp's ink now flickers between 94% and 100% opacity once settled — a wet-stamp tell that keeps the rest state visibly alive at any capture instant without ever dropping below a legibility floor.
  • Micro-interaction: the cover chips print their own live premium (· $840/YR) in the meta line, so the price of a cover is visible before you add it to the stack.

Pass 3 — Hardening

  • 375px. Masthead checked first: the wordmark's right edge measured against the call button's left edge explicitly, with room to spare. Zero elements overflow the viewport box, scrollWidth is exactly 375, and the h1, the primary CTA and the trust line all sit above the fold at 375×667. The masthead's action button also swaps from "Get a quote" to the phone number below 641px, so a click-to-call target is in the header at every width and not only in the sticky bar.
  • The badge docks below the artwork at ≤640px — still inside the signature container, measured to sit below the sheet's bottom edge — and switches to the pinned short basis line, while the ledger and the plain-text accessibility mirror keep the full templates at every width.
  • Keyboard. Cover toggles are real <button role="switch"> elements with an aria-label that includes the cover's live premium; Enter and Space both flip them and the harness asserts the state change. The trade chips are a proper radiogroup with arrow-key navigation. Focus outlines are 3px oxblood at 2px offset.
  • Patterns proved by driving, not by reading. Every pattern attribute is v-flag safe (literal specials escaped inside every character class) and is verified by setting a deliberately invalid value and asserting checkValidity() returns false — a dead regex looks identical to a live one in the markup.
  • Rapid-fire. Four toggle clicks dispatched in a single evaluate with no settle between them land on $6,045 with exactly six bands; a second burst mixing two trade chips with a cover toggle lands on the café's forced state. Every handler bumps the draw token before the state swap, so no in-flight tween can be left closed over a stale band set.
  • Resize. Crossing 640px in either direction re-docks the badge, swaps the basis line between its full and short forms, re-measures the stamp and recomputes the sheet's height from it — driven twice in the harness at two device pixel ratios, with the clearance rule and the $2,200 total asserted on the way back.
  • Chanel rule: the band labels were printing the limit inside the artwork ("01 · PUBLIC LIABILITY · $20M") while the schedule column beside them, the ledger below them, the cover chip above them and the accessibility mirror all printed the same limit. It came out of the bands, which now carry only the section number, the cover name and the premium. The trust line under the hero lost its chip borders in the same pass — three bordered pills sitting directly above five bordered trade chips made the page's one real control set harder to find.
  • Final copy read: "indicative" now appears on the stamp, in the ledger, in the services grid header, on the enquiry panel and in the mirror. On a page about insurance, the basis of the number is not fine print.

10 · VERIFICATION

How the numbers were checked

An independent oracle re-implements the pinned base premiums and band factors in separate code — it never calls the page's own pricing function — and the harness asserts the displayed figures against it at rest and across every worked example, the four-step trade force drive, the turnover drive, the staff drive and the frame drive. It also enumerates the entire trade × turnover × staff × cover-subset grid (322,560 cells) rather than spot-checking: every cover floor, the overall cheapest total, the largest reachable total, and the whole-dollar property of every reachable premium come out of that enumeration, and the prose above quotes it rather than the other way round.

Headless Chrome drives every chip, every stepper to both of its bounds, every toggle, the keyboard path, an empty submit, a filled submit and two rapid-fire bursts, with native-resolution screenshots reviewed at 375, 1000 and 1200 for label collisions and badge coverage. Zero console errors, zero failed requests.