Clearfigure — build guide ← Back to the site

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.

#FAF9F5 porcelainground — clean desk, nothing hiding
#232A33 ledger inkstructure, headings, the balance strip
#2E9E6B reconciled greenLINE/MARK only — ticks, fills, rings
#1B6B47 green-inkderived text token, 6.0:1 on porcelain
#E4573F out-of-balance coralthe enemy — spill chits, unmatched sums
#B23A28 coral-inkderived error text, 5.6:1 on white

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 tierMonthlyPer 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

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

Techniques

Under the hood

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.