Generative Assets · Industry demo 268 · Upholstery
Second Sitting Upholstery is a fictional East Victoria Park re-upholstery workshop, built as a single conversion-focused landing page. Its audience is the owner of a good frame in tired fabric; the page's single job is a quote request — and the Recover Studio exists to make that request warmer, by letting the visitor watch their own piece recovered before they ask what it costs.
#F6F2EAThe ground. The fitting cloth every piece wears first — the workshop's neutral.#3E3229Structure and primary ink — the show-wood under the fabric. 11.1:1 on calico.#1F6E63Line/mark accent — the recover fabric on the bench. Seams, marks, CTA. Never body text.#A9803CSecondary — the nailhead trim. Chips, focus rings, marks. 3.3:1, so never type.#7A5A24Derived text twin of the tack — 5.8:1 on calico for links, eyebrows, labels.A calico ground with a warm accent sits one step from the banned cream-and-terracotta default, so the line was held deliberately: the accent duty is carried by velvet teal, not terracotta; the display face is a soft plump serif chosen for upholstered warmth, not a high-contrast editorial serif; and brass stays secondary — tacks and focus rings, with a darker derived ink (#7A5A24) doing any actual reading work. Secondary body copy uses a derived walnut-soft #665649 (6.3:1).
Fraunces (display) — soft-shouldered, plump, slightly wonky letterforms that read like upholstered furniture; its italic carries the brand line "every good chair deserves a second sitting". Albert Sans (body, UI) — a warm workhorse that stays out of the way at 16.5px. Fragment Mono — metres, counts and prices; every figure on the page is set in it so the numbers read as measured, not decorated.
The signature is a procedural SVG workshop bench (viewBox 920×560, zero raster) on which the chosen piece is drawn in walnut line-work and then re-covered live, panel by panel. Each piece is generated from rounded-rect path geometry (armchair 6 panels, sofas 8–10, dining 1 + one mini-seat per chair in the set, banquette one segment per linear metre). Every panel exists twice: a calico-fitted outline path, and a fabric path filled with a procedural <pattern> per grade — fine cross-hatch weave for house linen, tight 7px grain for performance weave, directional 52px sheen bands for velvet. Wrapping is one scalar W (panels-done + fraction): each panel's fabric sits under a clipPath rect that a rAF loop grows bottom-up, so the cloth pulls over the calico exactly the way a cover goes on. Velvet additionally takes a light sweep — a shared userSpaceOnUse linear gradient whose gradientTransform is translated each frame, clipped to the wrapped panels only. Brass tacks seat along the arm rails after their panel wraps, each with a 220ms overshoot "tap". All animation is JS attribute work — no CSS rule ever animates a property an element also sets as a presentation attribute (the W16/W17 trap).
Dual-speed redraw: the first render earns the entrance — line-work, then W tweens to two-thirds over 2.1s and an ambient drift finishes the wrap slowly, so the 7-second thumbnail catches the armchair mid-recover with the sheen mid-pass (measured wrap 0.84 at settle). Every subsequent chip or stepper tap rebuilds and re-wraps completely in 550ms. Reduced motion renders everything settled and fully wrapped, sweep parked. The loop pauses on document.hidden; state is exposed at window.__upholsterer.
| Piece | Anchor | Performance weave | Velvet | Count / notes |
|---|---|---|---|---|
| Armchair | from $1,250 | +$180 | +$320 | fixed piece — stepper disabled with on-page reason |
| 2-seat sofa | from $1,950 | +$260 | +$450 | fixed piece |
| 3-seat sofa | from $2,450 | +$260 | +$450 | fixed piece |
| Dining chairs | $95/chair | grade chips disabled — commercial range only | 4–12 chairs, min 4 = $380 | |
| Banquette | $480/lm | restricted to performance weave / vinyl | 2–6 lm by 0.5, min 2 lm = $960 | |
| New seat foam | +$120/cushion | toggle, armchair (×1) and sofas (×2 / ×3) only — foam is part of the rate for dining and banquette | ||
The card splits every fixed-piece figure into labour and fabric lines. The fabric line is a house-grade allowance derived at roughly $45/m across the piece's stated metres — armchair (5–6 m) $250, 2-seat (8–10 m) $400, 3-seat (11–13 m) $540 — and labour is the anchor minus that allowance, so the split always re-adds to the pinned anchor exactly. Grade surcharges land on the fabric line, where they belong.
Measured from the running page against an independently re-implemented oracle: rest state armchair + velvet = $1,570; armchair + performance + foam = $1,550; 2-seat linen $1,950, + foam ×2 $2,190; 3-seat velvet $2,900; dining ×6 $570; banquette 3.5 lm $1,680; cheapest possible output $380 = the services grid's dining floor. Every worked example in the CONCEPTS spec matched the implemented model with no correction needed.
This demonstration page ships only WebPage schema naming it a demo — fictional structured data in a real index would be dishonest markup. For a real upholsterer we'd ship LocalBusiness + Review schema like this:
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "LocalBusiness",
"name": "Second Sitting Upholstery",
"description": "Re-upholstery and recovering — armchairs, sofas,
dining seats and banquettes. East Victoria Park workshop,
Perth metro pickup and delivery.",
"telephone": "+61 8 9000 0268",
"address": {
"@type": "PostalAddress",
"addressLocality": "East Victoria Park",
"addressRegion": "WA",
"postalCode": "6101",
"addressCountry": "AU"
},
"areaServed": ["East Victoria Park", "Victoria Park", "Como",
"South Perth", "Bentley", "Cannington", "Perth"],
"openingHoursSpecification": [{
"@type": "OpeningHoursSpecification",
"dayOfWeek": ["Tuesday","Wednesday","Thursday","Friday","Saturday"],
"opens": "08:00", "closes": "16:00"
}],
"priceRange": "$$",
"review": {
"@type": "Review",
"reviewRating": { "@type": "Rating", "ratingValue": "5" },
"author": { "@type": "Person", "name": "— real customer —" },
"reviewBody": "— pulled live from the review platform —"
},
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": "— live —", "reviewCount": "— live —"
}
}
</script>
Plus a Google Business Profile wired to the same NAP, suburb landing content for the pickup run, and review schema fed from a live platform — never hand-typed.
Drove the whole page headlessly (Chrome + puppeteer-core, port 6803): 42 asserts green, zero console errors. Rest state measured $1,570 against an independently re-implemented oracle; wrap 0.842 at the 7s settle; empty submit on a fresh load raised 8 validation signals; filled submit confirmed in the lexicon; every chip, stepper and the FAQ driven with dispatched MouseEvents.
The screenshot review (thumbnail, 375 fold, reduced-motion) found what the asserts couldn't: the webbing roll read as a lollipop on a stick — redrawn as a flat roll with a cross hub; seat-cushion seams vanished into the fabric — panel strokes darkened to #124A42 at 2px; armchair arms widened 84→92 with a seventh tack per rail so the piece reads as furniture, not diagram.
Added the second-read details: brass tacks now "tap" in with a 220ms overshoot as their rail wraps; the parked shears snip once every ~14 seconds; the price total pops 6% on change. Measured sofa scenarios against the oracle: 2-seat velvet $2,400, linen $1,950, linen + foam ×2 $2,190; banquette 4.0 lm $1,920.
The eyeball review caught a real honesty defect no assert modelled: switching to dining chairs kept rendering the previously chosen velvet pattern while the card said "commercial-grade range" — the dining scene is now locked to the commercial weave regardless of prior selection (and can never carry the velvet sheen). One harness expectation was corrected the other way: foam honestly persists between fixed pieces, so armchair + performance + foam = $1,550 was the right figure — fix the harness, never the site.
Masthead first at 375: screenshot + measured edges (wordmark right 153.6px, call chip 293–353px — 59.9px clear). Above-fold at 375×720: h1 at 143px, primary CTA 55.6px tall at 468px, trust row 550px — all inside the first screen. Sticky bar fixed with 48px tel target in the bottom 140px. scrollWidth 375 and 768 both clean; an offender sweep found zero non-SVG elements past the viewport edge. Keyboard drive: chips, stepper and FAQ all operate on Enter with visible brass focus rings (screenshot reviewed). Reduced motion renders wrap = 1, sheen parked, entrance skipped. Chanel rule: removed the outermost dashed radius ring from the service-area map — two rings say "near ring", three said clutter.