Generative Assets · Industry Demos XXVI · site 343
Halftone Print Works — how it was made
A conversion-first landing page for a fictional Malaga commercial printer, built as the pattern Sapience Analytics would ship for a real Perth print shop: price the run on the page, then send the file.
Concept
Halftone Print Works prints business cards, flyers and posters for Perth businesses from a press room and trade counter in Malaga — digital under about two thousand pieces, offset above, same-day rush and next-day metro delivery. The audience is the tradie who needs 500 cards before the expo, the café owner with a seasonal menu flyer, the school P&C with a fete poster and the marketing coordinator ordering the quarterly run. The page has one job: a print-quote request with the product, quantity and finish already priced, so the first phone call is about the file and the day, not "how much".
Art direction is the press room at 3pm: uncoated-stock white ground, registration-black ink, press-magenta actions and process-cyan crop-mark linework. Bright, exact, a little loud — the whole page reads like a proof sheet with the marks still on.
Palette — measured, not assumed
The spec's contrast figures were hypotheses; every one was measured with WCAG relative-luminance arithmetic from the shipped hex values in Pass 1. Press magenta landed at 6.00:1 on the stock (spec ≈6) and 6.48:1 under white labels (spec ≈6.4), so no two-token remedy was needed for the accent — the pre-minted deep fill and text-ink pair still exist as hover and small-copy tokens. Process cyan carries no text anywhere: every cyan-coloured word uses cyan ink at 5.30:1. Secondary body copy uses #4A4D53 at 7.84:1.
Type
Space Grotesk (700) for the wordmark, headings, the display line, the money and the job-ticket total — a grotesque with a typesetter's ink traps, the face a print shop's own signage would carry. IBM Plex Sans for everything else, a workhorse that stays legible at 13px inside the ledger and at 10.5 units in the sheet's slug line. Body is 17px on mobile. The typographic moment is the display line above the sheet — "ONE THOUSAND FLYERS, ON THE PRESS BY FOUR" — set in caps at up to 34px, where the quantity is written in words from the live stepper (two hundred and fifty … fifteen hundred … two and a half thousand … fifty) and the second half follows the toggles: on the press by four at rest, same-day, collect from four with rush on, in your hands tomorrow with delivery on.
The signature — the Press Sheet
A drawn SVG press sheet built from JavaScript on every state change, so the product chips genuinely re-impose the sheet rather than toggling layers. Business cards impose 21-up (7 × 3 of 55 × 90 mm on a 450 × 320 mm SRA3, drawn landscape); A5 flyers impose 8-up (4 × 2 of 148 × 210 mm on a 640 × 450 mm SRA2 — eight A5s are an A2 of paper, which is why the flyer sheet is the bigger one and why the drawn sheet swaps size with the product); A2 posters draw 1-up on a 610 mm wide-format roll with the core at the left. Every item is drawn twice: a ghost keyline version (unprinted stock) and a full-colour version — a magenta halftone block, black headline bars, grey body lines, a cyan rule — clipped by a four-point <clipPath> polygon whose right edge is the roller line, leaning 3 units so it reads as a wet edge, with a 50-unit magenta wet-ink gradient behind the roller.
The roller rides that edge on a requestAnimationFrame loop: inkFrac = 0.5 + 0.15·sin(0.7t), so the inked region always covers between 35% and 65% of the sweep — the never-invisible floor is coverage rather than opacity: at any capture instant the sheet is visibly half ghost, half ink, at full opacity. The delta is clamped both ways (dt = min(max(now − last, 0)/1000, 0.05)), the loop pauses on document.hidden, and reduced-motion skips the loop and paints the 0.5 frame once. The halftone itself is a 4.2-unit <pattern> of white dots rotated 15°, laid over the magenta blocks so the screen shows in the artwork the way it does on a real proof.
Finish chips draw the sheet's surface without touching the price: gloss (cards gloss cello / flyers gloss) lays a hard diagonal specular sweep at 62% over the inked region; satin and silk lay the same sweep at 34%; matt cello and matt lay none; uncoated adds a fine grey paper-tooth pattern over the whole sheet. Setup draws a rotated cyan-ink "PROOF APPROVED · HALFTONE · MALAGA" stamp on the sheet's top-right corner; rush draws a clock at four o'clock on the press bed; delivery draws a courier satchel with a magenta flap and a cyan-edged label. Cyan crop marks sit at the sheet corners, registration targets in the side margins, a C/M/K colour bar along the gripper edge, and a slug line prints the imposition, the stock and — the second-read detail — the number of sheets the run actually needs (1,000 flyers 8-up = 125 sheets; 500 cards 21-up = 24 sheets; posters one print each).
The job ticket is drawn on the sheet like a real sticker — white plate, magenta header strip, "JOB TICKET · #0343", the total in Space Grotesk, a sub-line naming quantity, finish and whichever extras are on — tilted 2° and set inset from the bottom-right corner so it never covers the crop marks or the slug. Below 600px it ships a tight 160 × 62 variant (total only) so it never covers the imposed items at 375. Every control handler bumps a token before touching state; the rebuild is synchronous and the loop reads scene references refreshed by each render, so a rapid-fire chip sequence never leaves a stale closure alive. First draw staggers the drawn groups at 90 ms (1.3 s cap); every later redraw runs at 26 ms (460 ms cap) via a firstDraw flag. window.__printing exposes product, quantity, finish, toggle states, rate, tier flag, note, ledger lines, total, sheets, badge text and sub-line, run line, an integer-safety flag, ink fraction, frame count and a settled flag.
Pricing model — pinned anchors, measured outputs
| Line | Rate | Rule |
|---|---|---|
| Business cards, 350 gsm, double-sided | $22 per 100 | Stepper 250–2,000 ×250, rest 500. Volume tier $18 per 100 from 1,000, applied to the whole run, with the note "volume tier: $18 per 100 from a thousand cards". 750 → list, 1,000 → tier |
| A5 flyers, 150 gsm, double-sided | $18 per 100 | Stepper 500–5,000 ×500, rest 1,000. Tier $16 per 100 from 2,500 — "volume tier: $16 per 100 from two and a half thousand". 2,000 → list, 2,500 → tier |
| A2 posters, 200 gsm, single-sided | $14 each | Stepper 5–50 ×5, rest 10. Tier $11 each from 20 — "volume tier: $11 each from twenty". 15 → list, 20 → tier |
| Product chip | reset | Switching product RESETS the quantity to that product's rest quantity (the grids share no values, so restore has no meaning). Toggles are NOT reset. Each product remembers its own finish; re-clicking the selected product changes nothing |
| Finish chips | $0 | Cards: matt cello / gloss cello / uncoated. Flyers: gloss / silk / uncoated. Posters: satin / matt. Live at every combination, no price change — "the finish changes the feel, not the price at these runs" on the card and in the ledger; the drawing changes |
| Artwork & design setup | +$90 flat | Live everywhere; default ON at rest. Draws the proof stamp. Pre-sets the form's print-ready checkbox to unticked until the visitor touches it |
| Same-day rush | +$80 flat | Live everywhere. Draws the clock; the display line becomes "same-day, collect from four" |
| Metro delivery | +$15 flat | Live everywhere. Draws the satchel; OFF prints a "Collection from Malaga — free" ledger line instead |
| Minimums | stepper floors | 250 cards, 500 flyers, 5 posters — stated as the minimum run under the stepper; no minimum note needed |
| Booklets, signage, packaging, letterpress | no number | "Quoted on the file — send us the artwork and the run" — a designed price-less line in the grid, under the ledger and in the FAQ |
Toggle matrix
| Toggle | Cards (any finish, any qty) | Flyers (any finish, any qty) | Posters (any finish, any qty) |
|---|---|---|---|
| Artwork & design setup +$90 | live | live | live |
| Same-day rush +$80 | live | live | live |
| Metro delivery +$15 | live | live | live |
| Finish chips | live, $0 | live, $0 | live, $0 |
Worked examples — all measured from the running page and cross-checked against an independent oracle
| Scenario | Arithmetic | Measured |
|---|---|---|
| Rest: A5 flyers, 1,000, gloss, setup ON, rush OFF, delivery OFF | 10 × 18 + 90 | $270 |
| Business cards, 250, matt cello, no toggles | 2.5 × 22 | $55 |
| Cards 750 (list, no note) · 1,000 (tier + note) | 7.5 × 22 · 10 × 18 | $165 · $180 |
| Flyers 2,000 (list) · 2,500 (tier + note) · 5,000 | 20 × 18 · 25 × 16 · 50 × 16 | $360 · $400 · $800 |
| Posters 10 satin · + rush · 15 (list) · 20 (tier + note) | 140 · 140 + 80 · 15 × 14 · 20 × 11 | $140 · $220 · $210 · $220 |
| Rest + delivery · rest + rush + delivery | 270 + 15 · 270 + 80 + 15 | $285 · $365 |
| Product reset: rest → cards chip (500, setup stays ON) → posters chip (10) | 5 × 22 + 90 · 140 + 90 | $200 · $230 |
| Rest, finish silk / uncoated | unchanged, drawing changes | $270 |
| Grid floors: cards $55 per 250 · flyers $90 per 500 · posters $70 per 5 | stepper floor × list rate | match |
Zero spec corrections: every pinned figure landed exactly. Every quantity on every grid is a multiple of 250, 500 or 5 and every tier applies to the whole run, so every product line is a whole dollar by construction (250 cards = 2.5 hundreds × $22 = $55). The harness swept all 8 + 10 + 10 grid quantities against the oracle and asserted no decimal ever reaches the badge or the ledger; the probe carries an integerSafe flag that would go false if one ever did.
Conversion decisions
- Above the fold at 375: wordmark, the eyebrow trust line (Malaga · est. 2009 · same-day rush), the h1 naming the products, the suburb and the promise, and the magenta "Get a print quote" button — all inside the first 667px, CTA bottom measured at 520px.
- Two ways to act, always: click-to-call in the header (number hidden below 560px, the button stays), the ghost call button in the hero, and the fixed mobile bar with call + quote. A tradie ordering cards is a phone in one hand.
- The quoter sits beside the hero, not below it: the sheet and its job ticket are inside the 1200 × 750 thumbnail; the chips, stepper and ledger follow immediately, with the ledger sticky on desktop so the number stays in view while you change product.
- The quote hands off to the form: the run textarea pre-fills from the live state (quantity · product · finish · extras · total) and the print-ready checkbox pre-sets from the setup toggle, until the visitor edits them — so the enquiry arrives warm and the office knows whether artwork is coming.
- Form fields are what a printer actually needs: name, phone (v-flag-safe pattern), business name (the job goes on the invoice), suburb (delivery zone), the run, and the artwork checkbox. Front-end validation with visible focus rings; the confirmation panel is hidden on load and on an empty submit and appears only after a valid filled submit — no fake network round-trip, and it says so.
- Honest lines wherever the total appears: the tier note prints under the stepper and in the ledger; the finish line says "included" with its no-price statement; delivery OFF prints "collection from Malaga — free"; the quoted-on-the-file line sits under the ledger total, in the services grid and in the FAQ.
Local SEO we'd implement for the real business
The live page carries only WebPage, WebSite, CreativeWork and a BreadcrumbList — a fictional business must not put business-shaped structured data into a real index. For the real client we would ship Store: schema.org has no print-shop type, and a printer with a trade counter that sells finished goods over it is a retail LocalBusiness subtype — Store is the honest organisation type, more precise than bare LocalBusiness and not a specialty token. With the real ABN, address, hours, delivery area and a live review feed:
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Store",
"name": "Halftone Print Works",
"url": "https://example.com.au/",
"telephone": "+61 8 9000 0343",
"address": { "@type": "PostalAddress", "addressLocality": "Malaga", "addressRegion": "WA", "postalCode": "6090", "addressCountry": "AU" },
"areaServed": ["Malaga", "Balcatta", "Osborne Park", "Morley", "Joondalup", "Perth", "Fremantle", "Cannington"],
"openingHoursSpecification": [{ "@type": "OpeningHoursSpecification", "dayOfWeek": ["Monday","Tuesday","Wednesday","Thursday","Friday"], "opens": "08:00", "closes": "17:00" }],
"priceRange": "$$",
"hasOfferCatalog": { "@type": "OfferCatalog", "name": "Printing", "itemListElement": [
{ "@type": "Offer", "itemOffered": { "@type": "Product", "name": "Business cards, 350 gsm" } },
{ "@type": "Offer", "itemOffered": { "@type": "Product", "name": "A5 flyers, 150 gsm" } },
{ "@type": "Offer", "itemOffered": { "@type": "Product", "name": "A2 posters, 200 gsm" } },
{ "@type": "Offer", "itemOffered": { "@type": "Service", "name": "Artwork and design setup" } }
]},
"aggregateRating": { "@type": "AggregateRating", "ratingValue": "4.9", "reviewCount": "141" },
"review": [{
"@type": "Review",
"author": { "@type": "Person", "name": "Real customer name" },
"reviewRating": { "@type": "Rating", "ratingValue": "5" },
"reviewBody": "Pulled from the Google Business Profile feed, never hand-typed."
}]
}
</script>
Plus: a Google Business Profile with the delivery area set to the metro and "in-store pickup" on, product entries for cards, flyers and posters with the real from-prices, suburb landing sections for the industrial belt (Malaga, Osborne Park, Balcatta, Wangara), and the FAQ marked up as FAQPage once the answers are the client's own.
What would change for the real client
- Their sheet, not ours: the drawn press sheet stays as the quoter, but the artwork on the imposed items becomes the client's real sample jobs, and the reviews, trust strip and hero gain real photos, real Google reviews and their real FSC certificate and association membership.
- Quoting and job management: the quote request posts straight into printIQ as a new estimate with the run in the job notes, so the office turns it into a job ticket from the MIS instead of retyping — and the on-page rates read from the same price list printIQ uses.
- Web-to-print: for repeat runs (the café's seasonal flyer, the franchise's cards) the Press Sheet becomes a Shopify or WooCommerce product with the artwork upload and preflight on the product page, so the reorder is a click and a file, not a phone call.
- Invoicing: quotes accepted in printIQ sync to Xero; the deposit invoice on artwork approval and the final on collection follow without a spreadsheet.
- Their real numbers: the per-100 rates, tiers, grids, extras and imposition counts are a twelve-line config the client can change without a developer.
Iteration log
Pass 1 — Craft
- Measured every palette pair; press magenta cleared 6.00:1 on the stock and 6.48:1 under white labels, so it carries buttons, links and prices as spec'd, and the pre-minted deep fill / magenta ink pair went to hover and small accent copy. Cyan is lines-only; cyan ink at 5.30:1 carries every cyan-coloured word. Figures recorded above.
- First screenshot: the job ticket's bottom edge struck through the sheet's slug line, and the registration targets sat over the outer column of imposed items. Scaled the imposition grid to 93% of the sheet so the margins hold the targets and the colour bar clear of the artwork; shortened the slug to imposition · sheet · stock; moved the ticket up 30 units — no overlap.
- Poster scene: the roll core and the clock/satchel shared the same left margin. Moved the core right (128–172) so the extras sit on the press bed beside it, and dropped the ticket onto the poster's lower-right where it covers only filler lines, not the title bars.
- Service-area map: "Balcatta" ran across its own dot and "Osborne Park" into its dot; the delivery zone left Joondalup and Rockingham outside though the copy says metro-wide. Relabelled and enlarged the zone.
- Copy read: FAQ answers tightened, "quoted on the file" wording made identical in the grid, ledger and FAQ; "collection from Malaga is free" made identical on the toggle card, ledger line and services grid.
Pass 2 — Depth
- Second-read detail: the slug line now prints the number of sheets the run really needs from the imposition (125 sheets for 1,000 flyers 8-up, 24 for 500 cards 21-up, one print per poster) — the probe carries it and the harness asserts it.
- The display line now follows the toggles as well as the quantity: rush → "same-day, collect from four", delivery → "in your hands tomorrow".
- Wet-ink gradient behind the roller; magenta ink film on the roller's leading face; the halftone screen rotated 15° so the dots read as a screen, not a grid; specular sweep strengths split between gloss (62%) and satin/silk (34%); paper-tooth pattern for uncoated.
- Micro-interaction: stepper buttons compress 6% on press; chips and switches keep their border/fill transitions; the ledger is sticky on desktop.
Pass 3 — Hardening
- 375 masthead checked first: wordmark right edge 205px vs call button left edge 270px, no collision; scrollWidth 375 on both pages, zero offenders outside the SVG.
- Native-resolution label-collision sweep at 375: the clock label "SAME-DAY · 4PM" and the satchel label "METRO · NEXT DAY" clipped at the left edge of the scene at mobile label size. Shipped tight labels ("RUSH · 4PM", "NEXT DAY") below 600px and dropped the mobile label size to 13 units; re-shot clean for cards, flyers and posters with every extra drawn.
- Ticket tight variant checked at native 375: 160 × 62, total only, clear of the imposed items, crop marks and slug on all three products.
- Reduced motion: no rAF loop (frame count static), all drawn groups at opacity 1, settled flag true, rest total intact.
- Rapid-fire drive (thirteen control clicks in one evaluate — product, stepper, product, toggle, stepper, product, toggle, stepper, finish, toggle) ends on the asserted cards / 1,000 / uncoated / rush + delivery state at $275 with no console errors; every worked example with the full control vector set, all three tier boundaries both sides, the product-reset chain, the per-product finish memory and the empty/filled form submits asserted against the oracle — 329 asserts green.
- Chanel rule: removed the CSS crop-mark ornament from the scene card's corners — the drawn sheet already carries real crop marks, and the doubled marks read as decoration.
Rest-state price measured from the running page: $270 (1,000 A5 flyers, gloss, with artwork setup). Zero console errors, one <h1> per page, live JSON-LD limited to WebPage / WebSite / CreativeWork / BreadcrumbList / ListItem.