How the Clearfigure demo was made
Clearfigure Bookkeeping Co. is a fictional bookkeeping & BAS practice in South Perth, built as an industry demonstration: one conversion-focused landing page showing a WA bookkeeper exactly what Sapience Analytics would build for them. The audience is the owner-operator doing their books at 11pm; the page's single job is to get them to book a free books review.
Palette
The colour of a reconciled ledger
Every colour is argued from the reconciliation screen. Coral is deliberately rationed: it is the enemy colour — it appears only on the unreconciled state (the receipt spill, unmatched totals, form errors) so that green resolving over coral literally enacts the service.
Per the collection's ink/line rule, neither accent is ever set as small text: #2E9E6B and #E4573F live on strokes, ticks and fills; body-sized text uses the derived green-ink/coral-ink tokens, both clearing 4.5:1.
Type
Familjen Grotesk + Karla, tabular everywhere
Familjen Grotesk carries the display voice — friendly Scandinavian precision, the tone of a bookkeeper who is calm because the books are done. Karla does the body work at 16.5px. The whole page runs font-variant-numeric: tabular-nums: every figure — estimator outputs, ledger amounts, service anchors, even the phone number — aligns like a ledger column. On a page that sells numeric tidiness, proportional figures would be a self-own.
Signature
The Tidy-Up Estimator
Three controls (transactions/month, staff on payroll, months behind) drive both the price model and a split SVG scene: a shoebox spilling coral receipt chits on the left, an eight-row ledger on the right. A rAF loop flies one chit at a time along an arced cubic-ease path; on landing, the row's ghost text solidifies, the tick draws itself (stroke-dashoffset), and the Unmatched footer — computed live as the sum of the remaining unreconciled row amounts — counts down until it flips to a green “Balanced ✓ $0.00”. The spill height scales with months behind (up to ~54 chits, seeded deterministic placement via mulberry32 so screenshots are stable). At rest the scene sits mid-sort — four of eight rows reconciled, the worked example (180 txns, 3 staff, 4 months) already priced — so the 7-second thumbnail settle reads as a premium demonstration, not a blank.
The pricing model is one lookup table shared by the estimator and (by construction) the services grid:
| Volume tier | Monthly | Per catch-up month |
|---|---|---|
| up to 100 txns | $330 | $200 |
| 101–200 | $440 | $260 |
| 201–350 | $550 | $330 |
| 351–500 | $660 | $400 |
| 500+ | $770 | $460 |
The floors reconcile with the services grid by design: lowest tier = $330/mo (grid: “from $330/mo”); payroll add-on $77/mo up to 5 staff, $132 for 6–10; catch-up at 6 months × $200 = $1,200 (grid: “from $1,200, 6 months behind”). Catch-up months price at roughly 60% of the live-month tier because backlog work has no ongoing correspondence load — that is also why the worked example (4 months at the $260 tier) reads $1,040, below the 6-month grid anchor, without the page arguing with itself.
Conversion decisions
Why the page is shaped like this
- Above the fold at 375px: brand, the promise (“Tidy books. Lodged BAS. No more 11pm spreadsheet shifts.”), the primary CTA and the BAS-agent trust chip all land inside the first viewport. The estimator is one scroll away, not competing with the CTA.
- The estimator is the hero's proof, not a toy: it answers the only two questions a behind-on-books owner has (“what will the mess cost to fix” and “what will staying tidy cost”) with no email gate — and its Use these numbers button pre-fills the review form, turning the interaction into a warmer lead.
- Form fields are the minimum a bookkeeper needs to prep a review call: name, phone, business type (chart-of-accounts context), months behind (scope), free-text “what's messy”. No email field — the promise is a call back within one business day, so phone is the honest primary key.
- Coral is rationed to the problem state. The only red on the page is the spill, the unmatched sum and validation errors — so the eye learns the grammar: coral = unreconciled, green = handled.
- Sticky mobile bar (call + free review) because most trade and hospitality owners will read this on a phone between jobs; both targets are ≥48px.
- The balance strip (bank feed = ledger = out by $0.00) is the second-read detail: it counts up on first scroll and the difference never leaves zero — the whole pitch in three cells.
Local SEO
What we'd ship for the real business
This demo deliberately carries no LocalBusiness/Review structured data — fictional entities in a real search index would be dishonest markup. The page emits only a WebPage schema naming it a demonstration. For a real client we would ship:
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "AccountingService",
"name": "…Real Bookkeeping Pty Ltd…",
"url": "https://example.com.au",
"telephone": "+61 8 …",
"address": {
"@type": "PostalAddress",
"addressLocality": "South Perth",
"addressRegion": "WA",
"postalCode": "6151",
"addressCountry": "AU"
},
"areaServed": ["Perth", "Western Australia"],
"openingHoursSpecification": [{
"@type": "OpeningHoursSpecification",
"dayOfWeek": ["Monday","Tuesday","Wednesday","Thursday","Friday"],
"opens": "08:30", "closes": "17:00"
}],
"priceRange": "$$",
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": "4.9", "reviewCount": "38" // from their live Google profile
},
"review": [{
"@type": "Review",
"author": { "@type": "Person", "name": "…real client…" },
"reviewRating": { "@type": "Rating", "ratingValue": "5" },
"reviewBody": "…their real words, with permission…"
}]
}
</script>
Plus: a Google Business Profile wired to the same NAP, suburb-level service pages only if genuinely serviced, and FAQ schema mirroring the on-page questions (bookkeeper vs accountant, BAS agent scope) — the queries WA owners actually type.
The real client
What would change for a paying bookkeeper
- Xero — practice branding as a real Xero partner (badge via their partner programme), and the estimator's tiers tuned to their actual rate card.
- Dext / Hubdoc — the “receipts by phone photo” claim becomes a live onboarding flow: the form's confirmation step links straight to their receipt-capture app invite.
- Employment Hero (or KeyPay) — payroll add-on copy aligned to the actual payroll platform, STP Phase 2 language included.
- Booking — the free-review form posts to their practice inbox or books directly via Calendly/HubSpot Meetings; the demo's local-only confirmation becomes a real round-trip with a server-side validation twin.
- Reviews feed — demo quotes replaced by their Google reviews, pulled and cached, with schema per the panel above.
Techniques
Under the hood
- Single-file vanilla HTML/CSS/JS — no libraries, no build. Google Fonts is the only external resource.
- All art procedural SVG: the shoebox scene, the Perth-metro + WA-inset service map, ticks and quote marks. Zero raster assets.
- Scene loop: rAF with a clamped delta accumulator, paused on
document.hiddenand (via IntersectionObserver) when the scene is off-screen. - Reduced motion:
matchMediacheck gates every loop; the scene renders fully reconciled (“Balanced ✓”), reveals are instant, and the count-up is skipped. - State probe
window.__bookkeeperexposes estimator inputs/outputs, scene reconcile state and form status — QA drives it headlessly and asserts displayed fees equal hand-computed values. [hidden]{display:none !important}guard so the form/confirmation swap can never double-render.
Iteration log
Three passes
Pass 1 — Craft
Drove the full flow headless (46-assert suite at 1440, 375 and reduced-motion, plus a real screenshot per viewport, all read by eye). Screenshot review caught two things the DOM asserts sailed past: the confirmation heading rendered “Thanks , Robyn” — the h3 is a flex container, so its bare text nodes were picking up the flex gap as phantom spacing (wrapped the sentence in one span); and the four-item trust strip wrapped into a lonely second line at 1440 (tightened to .88rem, centred the wrap). Verified every displayed fee against the hand-computed table: 180/3/4 → $440+$77 = $517/mo, 4×$260 = $1,040 ✓; 90/0/6 → $330/mo, $1,200 ✓ — both grid floors land exactly. Also confirmed the ledger footer stays internally honest mid-cycle: at 5/8 reconciled the screenshot showed Unmatched $386.55 = 129.00+187.55+70.00, the sum of the three remaining rows.
Pass 2 — Depth
Enriched the signature's feel: ledger row text and ticks now cross-fade over 450ms instead of snapping when a chit lands or the cycle resets; the two quote figures “bump” (a 320ms scale flash) only when their value actually changes — keyed on the computed output, so slider drags within a tier stay calm; slider thumbs grow on hover/press. The second-read moment was already in: the hero balance strip counts both cells up in lockstep so “Out by” never leaves $0.00 ✓. Re-ran the drive suite after each change — still green.
Pass 3 — Hardening
375px sweep: no horizontal overflow (scrollWidth 375), sticky call bar fixed in the bottom 140px with ≥48px tel target, hero value-prop + CTA + trust chip all inside the first 667px viewport, body 16.5px so iOS never zoom-jumps the form. Reduced-motion emulation renders the scene fully reconciled (“Balanced ✓”, zero flying chits) with no console errors. Empty submit flags all three required fields with visible messages and aria-invalid; filled submit renders the local confirmation with the explicit “nothing was sent anywhere” demo note. Chanel rule: removed the outermost of three dashed service rings on the metro map — two say “radius” just as well. Final suite: 46/46.
Honesty: Clearfigure Bookkeeping Co. is a fictional business. The phone number uses the reserved (08) 9000 0xxx pattern, credentials are visibly placeholder-patterned, reviews are demo copy, and the only structured data is a WebPage schema declaring the demonstration.