Generative Assets · Industry Demos XXVII · site 349
Cornerstone Picture Framing — how it was made
A conversion-first landing page for a fictional Fremantle custom picture framer, built as the pattern Sapience Analytics would ship for a real framing workshop: size the piece and pick the moulding, mat and glass on the page, see the price on a gallery wall label, then send the request.
Concept
Cornerstone Picture Framing is a two-framer workshop on Wray Avenue, Fremantle: custom framing, mat cutting, UV and museum glass, float mounting and object framing. The audience is the couple with a print from the markets, the family with a grandfather's medals, the artist framing twelve works for a group show, and the buyer who paid too much for a poster to hang it with clips. The page has one job: a framing request with the size, moulding, mat and glass already priced, so the first phone call is about the piece and the drop-off day, not "roughly how much".
Cornerstone Picture Framing is a fictional business — a picture framing industry demonstration designed & built by Sapience Analytics, part of the Generative Assets collection. The phone number uses the reserved (08) 9000 0349 pattern, the ABN is a placeholder, and the address stays at suburb level.
Palette — measured, not assumed
Argued from a gallery wall, charcoal mount card, museum-green baize and a gilt moulding. Every ratio below was measured from the shipped tokens with WCAG arithmetic in Pass 1; the spec's figures were hypotheses.
Type
Playfair Display (600, with an italic moment) — the serif on a gallery label — carries the wordmark, headings, the wall label's title and price, and the display line set huge above the frame: "SIXTY BY EIGHTY, OAK AND A SINGLE MAT", which re-sets from the steppers and chips (dimensions in words for pieces under a metre). Work Sans does the body, controls and ledger; the SVG dimension tags use it too so the scene and the page share one voice. Body is 17px on desktop and 16px in form fields, never under 16 on mobile.
The signature — the Frame Studio
A single SVG stage (a warm gallery wall under a drawn track light, with a hook and hanging cord) holds a drawn moulding, mat and artwork that resize to the width and height steppers. The artwork is a procedural landscape swatch — a nested <svg viewBox="0 0 200 200" preserveAspectRatio="xMidYMid slice"> with a sky gradient, a gilt sun and four layered hill paths in the palette — so any aspect ratio crops the same picture rather than stretching it. Geometry is computed in centimetres (artwork + float gap + mat border + moulding profile), scaled to fit a fixed frame box, and capped so a 20 × 20 cm piece reads honestly small while a 120 × 120 fills the wall; the dimension tags always print the true centimetres. Moulding chips swap the frame's fill (an oak gradient with a rotated grain pattern clipped to the band, matte ash, a gilt gradient with a heavier bevel highlight, chalk white) and its profile width (4.2 / 3.2 / 5.2 / 3.6 cm). Mat chips resize the border (6 cm single, 7 cm double with a thin gilt reveal); the float toggle opens a 2 cm gap around the artwork and paints a blurred drop shadow under it. Glass chips change the reflection: standard is a skewed white sheen band, UV adds a faint violet edge band, museum drops the sheen to 7% so the picture reads clearest.
Motion: state changes tween the displayed centimetres over 380 ms (900 ms on the first draw with a scale-in — the dual-speed redraw), and a token bumped in every handler before the state swap invalidates any in-flight tween so rapid-fire clicks can never index a stale frame. The gallery light drifts across the glass on a 9-second sinusoid whose range (0.22–0.78 of the glass width) never lets the sheen leave the pane — a never-invisible floor rather than phase arithmetic. The rAF loop clamps dt at zero and 50 ms, pauses on document.hidden, and under prefers-reduced-motion draws the settled frame once with the sheen parked mid-glass. The price is drawn as a gallery wall label beside the frame — an HTML overlay so the type stays crisp — that moves into the flow beneath the stage when the scene card is under 600px wide, and the stage itself switches to a tighter 480 × 430 viewBox with larger dimension tags under 480px.
Pricing model — pinned anchors, measured outputs
GST-inclusive. Foam-core backing, D-rings and cord are included and declared on the ledger, the wall label and the a11y mirror — never a line item. Moulding is priced per linear metre of the artwork's perimeter (2 × (w + h) / 100 — the mat border sits outside those numbers and is priced in the mat line, stated on-card); mat and glass by the artwork's area in two bands (≤ 0.5 m² / > 0.5 m²). On the ×10 cm grid, 2(w + h) is a multiple of 20, so the perimeter is a multiple of 0.2 lm and every rate × 0.2 is a whole dollar (12 / 10 / 17 / 9) — integer-safe by construction; the probe carries an integerSafe flag and the harness asserts it on every scenario. Totals display to the whole dollar.
| Line | Rate | Notes |
|---|---|---|
| Raw oak moulding | $60 / lm | 4.2 cm profile drawn |
| Black ash moulding | $50 / lm | 3.2 cm profile |
| Gilt moulding | $85 / lm | 5.2 cm profile, bevel highlight |
| White moulding | $45 / lm | 3.6 cm profile |
| Mat — none / single / double | $0 · $35/$55 · $55/$85 | small band / large band |
| Glass — standard / UV / museum | $30/$55 · $60/$110 · $120/$220 | small band / large band |
| Float mount | +$40 | flat, live at every combination |
| Oversize (w or h ≥ 100 cm) | note only | "over a metre we glaze in acrylic — same price band"; badge, ledger, stepper card and mirror all carry it |
| Canvas stretching, block mounts, medals, jerseys, objects | no number | "quoted on the piece" — grid and FAQ |
Worked examples — all measured from the running page against an independent oracle
| Scenario | Arithmetic | Measured | Note |
|---|---|---|---|
| Rest: 60 × 80, raw oak, single mat, UV, no float | 2.8 lm × 60 = 168 + 35 + 60 | $263 | none |
| 20 × 20, white, no mat, standard | 0.8 × 45 = 36 + 0 + 30 | $66 | grid floor |
| 50 × 100, gilt, double, museum | 3.0 × 85 = 255 + 55 + 120 (0.50 m² — small band) | $430 | oversize note present |
| 60 × 90, gilt, double, museum | 3.0 × 85 = 255 + 85 + 220 (0.54 m² — large band) | $560 | note absent — band flip asserted both ways |
| 100 × 100, black ash, no mat, standard | 4.0 × 50 = 200 + 0 + 55 | $255 | oversize note |
| Rest + float | 263 + 40 | $303 | none |
| Rest, black ash | 2.8 × 50 = 140 + 35 + 60 | $235 | none |
| Rest, museum glass | 168 + 35 + 120 | $323 | none |
| 120 × 120, gilt, double, museum, float | 4.8 × 85 = 408 + 85 + 220 + 40 | $753 | grid ceiling, oversize note |
| 90 × 20 → 100 × 20, white, no mat, standard | $129 → $138 | note absent → present | oversize boundary both sides |
| Grid floors | 20 × 20 white + single mat / UV / museum | $35 · $60 · $120 lines | services "from" prices match |
Fourteen further pseudo-random control vectors were driven against the oracle in the harness, plus a rapid-fire drive (ten clicks in one evaluate, then a second burst back to rest) — every total, band, note state and integer-safety flag matched. The rest-state figure measured from the running page is $263, exactly the spec's pinned figure; no spec correction was needed.
Conversion decisions
- Above the fold at 375: eyebrow (Wray Avenue, est. 2009), the h1 naming the trade and the suburb with the promise in italic, a two-line value proposition, "Request a framing quote" (48px tall) and three trust chips — all inside the first 740px. The Frame Studio follows immediately.
- Click-to-call:
tel:in the header (collapsing to a bordered "Call" chip at 375, wordmark right edge 214px vs call chip left edge 267px), and a fixed call/quote bar under 760px. No emergency treatment — nobody needs a framer at 9pm. - The wall label is the price: the studio estimate lives on the art (a gallery label beside the frame, in the flow beneath it on phones) so the 1200 × 750 thumbnail shows a frame with a price, and again in the charcoal ledger with every line, the included-fittings declaration and a "send this as a request" CTA.
- Studio → form handoff: size, moulding, mat, glass and float pre-fill the request textarea with the estimate; a manual edit sets a dirty flag so later chip taps don't overwrite what the customer typed.
- Form fields: name, phone (v-flag-safe pattern), suburb, "what is it?" select (print or poster / original on paper / canvas / photograph / object or textile — the answer decides whether it's a Studio price or a quoted-on-the-piece job) and the pre-filled spec. Front-end validation with per-field messages; the confirmation panel is hidden on load and on an empty submit and shown only after a valid submit, phrased in the "we'd call you back" family with a demo tag saying nothing was sent.
- Honest declarations: "quoted on the piece" lines carry no number in the grid, FAQ and ledger footnote; the oversize acrylic note is a note, never a price change; the included fittings are a declaration on three surfaces.
Local SEO we'd implement for the real business
The live page emits only WebPage, CreativeWork and a BreadcrumbList naming it a demonstration — fictional LocalBusiness data in a real index would be dishonest markup. For the real workshop we'd ship a Store record (the schema.org organization subtype for a retail workshop) with real hours, geo, price range and a reviews feed:
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Store",
"name": "Cornerstone Picture Framing",
"image": "https://example.com.au/workshop.jpg",
"url": "https://example.com.au/",
"telephone": "+61 8 9000 0349",
"priceRange": "$$",
"address": {
"@type": "PostalAddress",
"streetAddress": "Wray Avenue",
"addressLocality": "Fremantle",
"addressRegion": "WA",
"postalCode": "6160",
"addressCountry": "AU"
},
"geo": { "@type": "GeoCoordinates", "latitude": -32.06, "longitude": 115.75 },
"openingHoursSpecification": [
{ "@type": "OpeningHoursSpecification", "dayOfWeek": ["Tuesday","Wednesday","Thursday","Friday"], "opens": "09:00", "closes": "17:00" },
{ "@type": "OpeningHoursSpecification", "dayOfWeek": "Saturday", "opens": "09:00", "closes": "13:00" }
],
"areaServed": ["Fremantle","South Fremantle","East Fremantle","Cottesloe","Melville"],
"hasOfferCatalog": {
"@type": "OfferCatalog",
"name": "Framing services",
"itemListElement": [
{ "@type": "Offer", "itemOffered": { "@type": "Service", "name": "Custom picture framing" }, "priceSpecification": { "@type": "PriceSpecification", "minPrice": 66, "priceCurrency": "AUD" } },
{ "@type": "Offer", "itemOffered": { "@type": "Service", "name": "Museum glass glazing" }, "priceSpecification": { "@type": "PriceSpecification", "minPrice": 120, "priceCurrency": "AUD" } }
]
},
"aggregateRating": { "@type": "AggregateRating", "ratingValue": "4.9", "reviewCount": "112" },
"review": [
{
"@type": "Review",
"author": { "@type": "Person", "name": "Real customer name" },
"datePublished": "2026-05-14",
"reviewRating": { "@type": "Rating", "ratingValue": "5" },
"reviewBody": "Pulled from the Google Business Profile reviews feed — never typed by hand."
}
]
}
</script>
Alongside it: a Google Business Profile with the workshop pinned on Wray Avenue and hours synced, service pages per line (custom framing, mat cutting, museum glass, float mounting, object framing) with the "from" prices as on-page text, suburb mentions that match the areas actually served, and review-request links printed on the collection docket.
What would change for the real client
- Their photos: the procedural landscape swatch would become the customer's own artwork uploaded into the Studio (or a gallery of finished jobs on the workshop wall), and the moulding chips would draw from the supplier ranges they actually stock, with corner samples photographed.
- Their prices: the anchors are plausible for the Fremantle market, but the real matrix comes from their moulding cost sheet — the model is a table, so swapping rates is a data change, not a rebuild.
- Their reviews: live from the Google Business Profile feed with the schema above.
- Their systems: the request posts to the workshop inbox and their job board; the natural integrations are Square or Lightspeed POS for the deposit and invoice, Xero for the books, and a framing job-management package (FrameReady, LifeSaver or similar) whose job exports carry the size, moulding SKU, mat and glass so a Studio request becomes a work order without re-keying.
Iteration log
Pass 1 — Craft
- Measured every palette pair (figures above). Gilt ink landed at 5.12:1 against a ≈6 hypothesis — kept, restricted to bold labels ≥ 12.5px; the accent gilt itself never carries text.
- The header wrapped at 1000px (nav items and the CTA broke onto two lines): nav now hides under 1080px and the CTA and nav links are
nowrap. - The display line at 31px broke "MAT" onto its own line at 1200: capped at 27px so the rest line sets on one line, with two-line wrapping reserved for the long "one hundred and twenty" states.
- The width dimension tag sat between the hook and the frame, and the hanging cord ran straight through it — moved the width tag under the frame with the caption beneath, height tag stays on the left, badge on the right: no leader crosses a label at any size (label-collision sweep at 20 × 20, 60 × 80, 100 × 40, 120 × 120).
- The display-line variant assert caught the ash chip printing "ASH" where the spec's moulding names read "black ash".
- Ledger copy read: "included" for the fittings line, "quoted on the piece" in the fine print, no lorem anywhere.
Pass 2 — Depth
- The first light sweep glided edge-to-edge on a 7 s loop and rested off-glass — at the 7.5 s capture the sheen was invisible. Redesigned as a 9 s sinusoidal drift over 0.22–0.78 of the pane so the light is on the glass at every instant (never-invisible floor, W18), with UV's violet band riding a third of a pane behind it.
- Second-read details: the drawn track light whose spot lights the wall, the hook and two-line hanging cord that shortens for a small frame, the oak grain pattern clipped to the moulding band, the gilt bevel highlight, the float mount's blurred shadow.
- Scale cap raised so a 20 × 20 reads as a small square on a big wall while a 60 × 80 fills the frame box; the frame box was narrowed 12 units so a 100 cm-wide piece clears the wall label at 1200.
- Wall-label title switches from words to numerals at a metre ("120 × 120 cm") — the words version wrapped to four lines.
- Micro-interactions: the label pops 2% on every price change, stepper values bump, chips carry an arrow-key radio pattern.
Pass 3 — Hardening
- 375px: masthead measured (wordmark right 214 < call chip left 267), no overflow, sticky call/quote bar present and its quote tap lands on
#quote. The dimension tags rendered at 5.7px in the 720-unit stage on a 331px card — added a narrow stage (480 × 430 viewBox, 15px tags) under 480px and the badge-in-flow layout under 600px; the 1000px mid-width check uses the in-flow label as well, so the scene is never compressed by an overlay. - Reduced motion: no rAF loop, the frame draws once at $263 with the sheen parked mid-glass; the CSS side kills the reveal transitions.
- Drives: every stepper, chip and the toggle; empty submit (validation on ≥ 3 fields, panel stays hidden) and filled submit (panel shown, lexicon matched); two rapid-fire bursts; 16 pinned scenarios plus 14 random vectors against the oracle; probe serialised in-page. 284 asserts, zero console errors, one h1 on each page.
- Two harness artifacts ruled out before touching the site: puppeteer
clipis page-relative (the scrolled 375 captures showed the top of the page), and the second rapid-fire burst stepped height past its 120 cm ceiling so the "back to rest" assert compared against the wrong vector. - Chanel rule: dropped the sweep’s glide-and-rest choreography (and its easing helper) — one slow drift says "gallery light" with less.
Verified headless with puppeteer-core on port 8404: state probe window.__framing (scenario, lm, area band, per-line prices, total, note, badge text and mode, settled flag), screenshots at 375 / 1000 / 1200 inspected at native resolution.