Generative Assets · Industry demo 246 · Painting · Perth

How the Cutline Painting Co. demo was built.

A fictional Mount Hawthorn painting contractor, built as a single conversion-focused landing page — every visual procedural, every price argued from the WA market, one signature interaction doing the selling.

Concept

Cutline Painting Co. paints interiors and exteriors across Mount Hawthorn and Perth's inner north — character homes, rentals between tenants, pre-sale refreshes. The brand's whole personality is the trade's own point of pride: a cut line you can't fault from a foot away. The page has one job: book a free colour-and-quote visit. Everything on it — the estimator, the prep honesty, the two-coat line — exists to make a visitor with a scrappy card-quote in their pocket think "these are the careful ones", and then call.

Palette — argued from a freshly painted room

#F7F5F1 · primer whiteThe ground — a prepped wall waiting for colour. Every section sits on it.
#33302B · scaffold inkStructure and primary type — pencil marks on a doorframe. Also the estimate card's ground.
#5A7258 · gum greenLINE/MARK accent — the feature wall drying. CTAs, tape lines, swatch marks. Never body text on primer (it fails 4.5:1 small).
#C7952E · masking goldThe tape itself — focus rings, active chips, the cut line through the masthead. Link text uses a derived darker ink.
#8A6414 · gold inkDerived body-ink for links — masking gold demoted to marks only, per the collection's ink/line split rule.
#5C564C · soft inkSecondary copy at ≥4.5:1 on primer — derived, not specced, so small text never rides an accent.

Type

Fraunces (display) — a serif with brush-drawn warmth, set tight; it carries the craftsman-calm register without tipping into heritage pastiche. The one moment of typographic bravery is the masthead's cut clean. — the word is half-filled gum green, half scaffold ink, split by a 3px masking-gold vertical bar: a wall mid-coat, the cut line dead straight through the letterforms. (The first attempt ran the tape line horizontally and read as strikethrough — caught in Pass 1's screenshot review and rotated vertical.)

Inter (body, UI) does the legibility work at 16px+. Space Mono carries everything numeric — prices, the estimate card, suburb chips, coat counts — so figures read like a written quote, not marketing.

Signature — the Repaint Estimator & Colour Studio

Scope chips (feature wall / single room / whole 3-bed interior / exterior single-storey), a finish toggle (walls only / walls + ceilings + trims) and three curated scheme chips drive a procedural SVG room elevation — cornice, skirting, doorframe, window, a taped feature wall, drop sheets and a ladder, all vector. Choosing a scheme repaints the room live: fills are set via element.style.fill from JS with a CSS transition on fill (never a CSS rule fighting an SVG presentation attribute — position and paint state live on separate elements per the collection's W16/W17 rule). The exterior scope swaps to a second SVG group (a rendered single-storey elevation) toggled with setAttribute('hidden','') under a [hidden]{display:none !important} guard. A second-read detail: three painter's swatch cards taped to the wall, with a dashed pencil ring that moves to the active scheme.

The estimate card renders in Space Mono like a written quote — scope, finish, scheme, itemised lines, a "from" total — and a handoff button pre-fills the enquiry form with the configured scenario and price, so the demo interaction becomes a warmer lead. At rest the studio sits mid-demonstration: whole 3-bed interior, full finish, "Limewash & Gum" applied, the room painted and $6,900 showing — a premium static frame at the 7-second thumbnail settle (no looping animation to phase; colour transitions complete in under a second).

The three schemes

Colour never changes the price in the model — stated plainly on the card, with the honest caveat that very deep bases sometimes want a third coat, called at the visit rather than after.

The pricing model — derivation

The spec pins five anchors: feature walls from $290, single rooms from $440 walls-only, $690 with ceilings and trims, whole 3-bed interiors from $4,900 walls-only, exteriors from $7,900. Everything else is derived and lives in one place in the page's JS (and is exposed at window.__painter.model for QA).

Per-space schedule (whole 3-bed, walls only)

The $440 room anchor is a standard bedroom. A whole home is that bedroom re-weighted across every painted space — bigger living areas, a door-heavy hallway, small wet areas — summing exactly to the pinned $4,900 floor:

Space schedule — walls only
SpaceWalls-onlyCeiling-bearing?
Bedroom 2$440yes
Bedroom 3$440yes
Master suite$560yes
Living & family$900yes
Dining & meals$520yes
Kitchen$440yes
Hallway, entry & passage$900yes
Laundry & WC$460yes
Robes & linen ×3$240no
Whole 3-bed, walls only$4,9008 spaces

The ceilings-and-trims uplift — deliberately flat

The room anchors fix the bundled uplift: $690 − $440 = $250 per space when ceilings and trims are booked with the walls. That's cheaper than the standalone $340/room ceiling anchor on purpose — the room is already sheeted, cut in and under protection, so the set-up cost is paid once. The uplift is flat per space as a pricing statement, not an invented gradient: bigger ceilings pair with smaller trim runs and vice versa, and it averages true across a home. Robes and linen take no uplift (no ceiling work booked in them).

Measured worked example

Whole 3-bed interior, walls + ceilings + trims = $4,900 + 8 × $250 = $6,900. The spec's ≈$6,900 hypothesis was verified by measurement: the QA harness re-derives this model in independent oracle code and asserts the displayed figure at rest and across all eight scope × finish combinations. The estimator's cheapest output ($290 feature wall) equals the services grid floor, and the whole-home walls-only output equals the grid's $4,900 — the page never argues with itself.

Conversion decisions

Local SEO we'd implement for the real business

This demo page ships only WebPage schema naming it a demonstration — fictional structured data in a real index would be dishonest markup. For a real painting contractor we'd ship LocalBusiness + Review schema like this (shown here as a code sample, not emitted):

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "HousePainter",
  "name": "Cutline Painting Co.",
  "telephone": "+61 8 XXXX XXXX",
  "url": "https://cutlinepainting.com.au",
  "address": {
    "@type": "PostalAddress",
    "addressLocality": "Mount Hawthorn",
    "addressRegion": "WA",
    "postalCode": "6016",
    "addressCountry": "AU"
  },
  "areaServed": ["Mount Hawthorn", "Leederville", "North Perth",
                 "Mount Lawley", "Joondanna", "Yokine", "Wembley"],
  "openingHours": "Mo-Fr 07:00-16:30",
  "priceRange": "$$",
  "aggregateRating": {
    "@type": "AggregateRating",
    "ratingValue": "4.9",
    "reviewCount": "87"
  },
  "review": [{
    "@type": "Review",
    "author": { "@type": "Person", "name": "Real customer" },
    "reviewRating": { "@type": "Rating", "ratingValue": "5" },
    "reviewBody": "Fed from the live review platform, never hand-typed."
  }]
}
</script>

Plus: Google Business Profile with service-area suburbs, per-suburb service pages only where genuine work history exists, and review markup fed from the live review platform so ratings stay true.

What would change for the real client

Techniques

Iteration log

Pass 1 · Craft

Drive everything, then look at it

  • Harness (puppeteer, headless Chrome, port 6401): 61 asserts driving every scope × finish × scheme against an independently re-derived pricing oracle; empty submit on a fresh load raised all five validation messages; filled submit rendered the lexicon confirmation; FAQ, sticky bar, prefill handoff all driven. One real FAIL: 375px scrollWidth 406 — the header's quote button pushed .head-actions 31px off-edge. Fixed by hiding the header button under 900px (the sticky bar owns the quote CTA on phones).
  • Screenshot review caught what no assert modelled: the masthead's cut clean. with a horizontal gold line read as strikethrough — exactly wrong for a brand about precision. Rotated the treatment 90°: left of the cut line painted green, right still ink, tape bar vertical. Now it reads as a wall mid-coat.
  • Header tel wrapped to two lines at 375 — white-space:nowrap plus a smaller wordmark under 420px.
  • Code craft: removed a redundant double-write of the finish chips' aria-pressed state.

Pass 2 · Depth

Second-read details, then a screenshot catch

  • Added the taped swatch cards to the room wall — three fixed scheme samples with a dashed pencil ring that follows the active scheme (toggled via setAttribute('hidden'), never the SVG-less .hidden property). Asserted the ring follows a driven scheme change.
  • Micro-interaction: the estimate total pulses (0.4s, custom bezier) only when the figure actually changes; suppressed under reduced motion.
  • Desktop hero's empty right side got the roller-stroke backdrop — one soft green band with a gold tape line at its edge, behind the content stack.
  • The pass's screenshot review caught a ship-blocker every state assert sailed past: at desktop the finish chips stretched into ~150px ovals — flex items stretching to the tallest sibling fieldset's height. Fixed with align-items:flex-start on the chip rows and align-content:start on fieldsets. The rendered image is ground truth.

Pass 3 · Hardening

Keyboard, honesty sweeps, one ornament removed

  • Keyboard path driven, not just dispatched clicks: focused a scheme chip and pressed Enter (state changed), focused an FAQ summary and pressed Enter (opened). Focus outlines verified present.
  • Honesty sweeps: every (08) 9xxx number on the page uses the reserved 9000 exchange (only 0246 appears); zero LocalBusiness/Review/AggregateRating types in live JSON-LD — only the WebPage demonstration schema.
  • Chanel rule: removed the horizontal tape strip along the feature wall's top edge — one crisp vertical cut line is the story; two tape lines were clutter at the cornice junction.
  • Final state: 65/65 asserts green across desktop, fresh-load form flows, 375px, and reduced-motion emulation; zero console errors on every run; rest state verified painted and showing $6,900 at the 7-second settle screenshot.