Generative Assets · Industry demo 376
How the Hose Run was built
Marlock Hydraulics is a fictional Naval Base hydraulic hose service. The page has one job: get the phone ringing with the hose spec already priced — hose, both ends and the callout named before the customer rings. Everything on it is drawn in the browser; there is not a single raster image on either page.
The concept
A hydraulic hose is bought in the worst ten minutes of somebody’s day: a forklift dead in a doorway, an excavator boom hose let go at half three on a Friday. The prospect isn’t browsing — they want to know it can be matched, what it costs, and how fast someone can get there. So the page opens on a working drawing of the exact assembly they need, priced, with the phone number one thumb away.
The art direction is a spec sheet on the swage bench: a cool zinc drafting ground, graphite ink, and the two colours a hydraulic circuit diagram actually uses — red for the pressure line, blue for the return. Technical, dry, precise: the aesthetic of a workshop that measures things. It is held off the three AI-default palettes deliberately — a cool zinc grey rather than cream-and-terracotta, a light page rather than near-black-plus-an-acid-accent, and no column rules anywhere, because the structure here is the circuit, not a broadsheet grid.
Palette
Argued from a zinc bench top, graphite pencil and hydraulic-circuit colour convention. Every ratio below was measured with WCAG arithmetic from the shipped hex values against the ground it actually sits on.
#EBEDEAThe page ground. Cards go pure white #FFFFFF; hairlines #D6D9D4. The drawing sheet itself is a half-step darker at #E3E6E2 so the instrument reads as a physical sheet laid on the page.
#1B2124Body ink and the primary CTA fill. 13.83:1 on the ground, 16.28:1 on white cards, 12.94:1 on the drawing sheet. White labels on the graphite button measure 16.28:1; the hover #141A1C measures 17.58:1.
#2F6E8FCircuit return lines, rule ticks, chip outlines and the secondary CTA fill (white labels, 5.60:1; hover #255A76, 7.49:1). As text on the ground it measures only 4.76:1, so it is a line-and-fill token, never small copy.
#1F5573The pre-minted text twin for the return blue: 6.85:1 on the ground, 8.06:1 on white, 6.40:1 on the drawing sheet. Every small blue word on both pages — eyebrows, the 300 mm rule label, the DETAIL A caption — uses this and not the line colour.
#B4342BThe pressure line, the travelling pulse, the crimp bands on each ferrule and the map’s Naval Base star. Scene and marks only (5.14:1 on the ground) — it is the colour of the fluid under load, so it never labels anything.
#96271FThe text twin for pressure red: 6.84:1 on the ground, 8.06:1 on white, 6.40:1 on the drawing sheet, 7.09:1 on the map ground. Carries the brand eyebrow, the lock reasons and the quoted-travel line.
#4C5458Secondary copy: 6.57:1 on the ground, 7.73:1 on white, 5.81:1 on the hose tag’s zinc plate, 6.45:1 in the drawing’s title block. Disabled chip labels use #5A6367 (5.22:1) plus a dashed border, so a locked control is still legible and still explains itself.
#232A2EThe single dark band on the page, under the trust strip. White on it measures 14.56:1, the zinc body copy #C9CFCB 9.20:1, the pale blue fine print #A8C6D6 8.12:1.
Type
Archivo 700/800 is the display face — an industrial grotesque with square counters, argued from stencilled plant labelling and machine data plates. It sets the h1, the section heads and the price on the hose tag, where its tight negative tracking makes $410 land like a stamped number rather than a marketing figure.
IBM Plex Sans is the workhorse: 16.5px body, 15px in cards, never below 13px anywhere a customer has to read. IBM Plex Mono 500 appears in exactly one role — spec strings. The hose tag’s 3/4" R13 · 3.0 m · JIC×JIC, every label inside the drawing, the price columns in the ledger and the pre-filled hose spec in the enquiry form. Restricting the mono to real specification data is what makes the page read like a workshop document instead of a website wearing a costume.
The typographic moment is the hose tag: the rest-state total set huge in Archivo 800 with the spec string running beneath it in tracked mono, hung on a wire loop over the drawing. That pairing — a big soft-edged number over a hard technical string — is the whole proposition in one object.
Techniques
No libraries, no build step, no images. Both pages are single self-contained HTML files. The drawing is SVG generated as a string on every state change; the motion is one requestAnimationFrame loop updating two attributes, plus one CSS keyframe for the oil sheen. That is the entire runtime.
The signature: the Hose Run
An SVG swage bench in elevation with the selected assembly drawn along it. The hose body carries its real braid construction — two-wire hose draws a crosshatch weave, four-spiral draws four straight-wound bands — and a crimped ferrule with three red crimp bands sits at each end, with the fitting drawn to type outboard of it: a hex nut and 37° seat cone for JIC, a longer nut with a threaded male end for BSP, a four-bolt plate for the SAE Code 62 flange.
The frame is zoom-to-fit: its width in scene units is always length + 0.6 m, so a 1 m assembly draws in a 1,600 mm frame and a 6 m assembly in a 6,600 mm frame. Because the frame grows with the hose, the honest constant reference is drawn beneath the assembly: a 300 mm steel rule, always exactly 300 mm in scene units, which visibly shrinks as the run gets longer. Everything in the drawing is at that one frame scale, including the outside diameters — 13, 18, 22, 34 and 42 mm for the five bores. At true frame scale a 34 mm hose in a 3,600 mm frame is a hairline, which is correct and also useless, so the sheet carries a drafting convention instead of a cheat: DETAIL A, a clipped circle showing a 220 mm window on the crimped end at whatever magnification the frame implies, with the drawn OD called out beneath it. The main assembly stays honest; the detail is where you can see the weave.
A slow pressure pulse travels the red line end to end on a 3.4-second cycle. Its delta is clamped at both ends (dt = min(max(now − last, 0)/1000, 0.05)) so a vsync timestamp that precedes the scheduling clock can never drive a negative length; the loop idles while the tab is hidden. The pulse is never invisible — it is always somewhere on the line and the base pressure line never drops below 55% alpha — so the drawing is visibly alive at any capture instant without any phase arithmetic against a screenshot schedule.
Redraws run at two speeds. The first draw earns the full entrance stagger (26 ms per element); every subsequent state change redraws snappy at 11 ms per element, so tapping through the hose chips reads as an instrument responding rather than a page reloading. Each redraw bumps a token in the event handler, before the state swap, so a control clicked mid-transition can never leave an animation closed over the previous scene’s geometry.
The scene’s height is computed, not fixed: the tag’s measured pixel height sets a minimum so the assembly, its labels and the steel rule can never end up underneath the badge at any viewport width. Below 641 px the tag docks directly beneath the drawing and switches to the short forms of its service line and both declarations; the ledger and the screen-reader mirror keep the full strings at every width.
Everything else that’s drawn
- The setting behind the bench changes with the service chip — a vice and swage press with a pegboard wall for a bench build, the van’s roller door and hose reel for a mobile callout, the same door under a work lamp with the light pooled on the bench for an after-hours breakdown.
- The service-area map is a schematic SVG of the coast with the core zone as a filled circle around Naval Base, the metro zone as a dashed radius, and both clipped to the land so a service boundary never floats out into the Indian Ocean. Labels are body ink with a ground-coloured halo drawn by paint-order: stroke, measured at 14.34:1 — a map label is body copy, not a decorative mark.
- The favicon is an inline SVG data URI of a hose with a ferrule at each end, a red pressure line and a blue return.
The pricing model
GST-inclusive, whole dollars, and internally consistent by construction — every rate is a whole dollar and every stepper grid is whole units, so no total can ever land on a fraction. Hose is priced per whole metre and multiplies only the hose line; fittings are a flat pair price and never multiply by length; the callout is flat; travel is added once, not per hour.
| Control | Option | Price | Drawn |
|---|---|---|---|
| Hose (per metre) | 1/4" 2-wire (SAE 100R2AT) | $18/m | 13 mm OD |
| 3/8" 2-wire (R2AT) | $22/m | 18 mm OD | |
| 1/2" 2-wire (R2AT) | $28/m | 22 mm OD | |
| 3/4" 4-spiral (R13) | $62/m | 34 mm OD | |
| 1" 4-spiral (R13) | $84/m | 42 mm OD | |
| Fittings (both ends) | JIC 37° both ends | $44 (2 × $22) | hex nut + 37° cone |
| BSP both ends | $52 (2 × $26) | hex nut + male thread | |
| JIC one end, BSP the other | $48 ($22 + $26) | one of each | |
| SAE Code 62 flange + JIC | $118 ($96 + $22) | four-bolt plate + cone | |
| Service (callout) | Bench, Naval Base | $0 | vice + swage press |
| Mobile callout — first 30 min on site in | $180 | van door + hose reel | |
| After-hours breakdown — first hour in | $340 | van door + work lamp | |
| Travel zone | Kwinana · Henderson · Rockingham | +$0 | — |
| Perth metro | +$60 | — | |
| Beyond metro | quoted | — |
Length steps in whole metres from 1 to 6. There is no empty state: one chip in every set is always selected and the length floor is 1 m, so the estimator always shows a real number.
Measured outputs
Every figure below was read off the running page and cross-checked against a pricing model reimplemented independently in the verification harness.
| Scenario | Arithmetic | Measured |
|---|---|---|
| Rest state — 3/4" R13, 3 m, JIC both ends, mobile, Kwinana | 62×3 + 44 + 180 + 0 | $410 |
| Cheapest output — bench, 1/4" 2-wire, 1 m, JIC both | 18 + 44 + 0 | $62 |
| After-hours, 1" 4-spiral, 6 m, SAE flange + JIC, Perth metro | 84×6 + 118 + 340 + 60 | $1,022 |
| Bench, 3/8" 2-wire, 4 m, JIC/BSP mixed | 22×4 + 48 + 0 | $136 |
| Mobile, 3/4" 4-spiral, 6 m, SAE flange + JIC, Perth metro | 62×6 + 118 + 180 + 60 | $730 |
| After-hours, 1/4" 2-wire, 1 m, BSP both, Kwinana | 18 + 52 + 340 | $410 |
| Mobile, 1/2" 2-wire, 2 m, BSP both, beyond metro | 28×2 + 52 + 180, travel quoted | $288 + travel quoted |
The sixth and first rows land on the same number by coincidence. The harness asserts that the badge’s second line differs — “after-hours breakdown — first hour on site included” against “mobile callout — first thirty minutes on site included” — rather than only checking the total, because a price that matches by accident is exactly where a state bug hides.
The cheapest output the estimator can produce is $62, and that is precisely the “from $62” floor printed on the services grid’s bench-built assembly card. The grid’s other floors — $18/m, $62/m, $22 an end, $96, $180, $340, $60 — are the same pinned anchors the estimator uses, so the page cannot argue with itself.
Locks, forces and memory
Two controls constrain each other, and both are declared on the page rather than silently computed.
- Bench lock. On a bench build there is nothing to travel to, so the whole travel-zone group is disabled with that reason printed beneath it, and travel bills $0. The zone the customer had chosen is remembered and restored the moment they put the van back on the road. Measured: mobile + Perth metro, 1/2" 2 m JIC = $340 → bench = $100 → mobile = $340, zone back on Perth metro.
- After-hours boundary. The after-hours truck stays inside the metro boundary, so “beyond metro” is disabled with that reason and, if it was already selected, the zone is forced to Perth metro and the old choice remembered. Measured: mobile + beyond ($288 + travel quoted) → after-hours forces Perth metro = $508 → mobile restores beyond = $288 + travel quoted.
- Partial unwind. With beyond metro remembered and Perth metro forced, chipping bench disables the zone group without overwriting the memory ($108); chipping after-hours again holds Perth metro, because the remembered value is still invalid ($508); only mobile, where beyond metro is valid again, restores it.
- Memory invalidation. Any explicit zone change while a zone is selectable clears the memory for good. Measured: mobile + beyond → after-hours (forced to metro) → explicitly chip Kwinana → mobile stays on Kwinana at $288, with the quoted-travel string gone from the instrument entirely.
What the page refuses to put a number on
Beyond the metro, travel is genuinely unknowable until we know the gate and the induction, so the badge prints the part it can compute plus the declaration — $288 + travel quoted — wherever the total would normally print, on the tag, in the ledger total, in the screen-reader mirror and in the pre-filled form. A bare “quoted” would throw away information the customer already has. Two more declarations ride along with every scenario on all three surfaces: crimping, the pressure test and the tag are in the price and never charged per end; and re-usable fittings salvaged off the old assembly are credited on the invoice, not in this estimate. The hose register is priced “per fleet, after we walk the yard” and carries no figure at all, because the size of the yard decides it and a rate card would be a guess.
Conversion decisions
- The phone is the primary CTA, everywhere. A dead machine in a doorway is a phone call, not a form. The number is a tel: link in the sticky header, the first hero button, inside the enquiry column and in the mobile sticky bar. At 375 px the value proposition, both CTAs and a trust signal all sit above the fold without scrolling.
- The estimator sits in the hero, not below it. The whole argument — “we can price your hose before you ring” — only works if the drawing and the price are the first thing visible. The drawing and its tag both sit inside the first 750 px at 1200 px wide, and the tag’s total is a real text node, not paint on a canvas, so it is selectable, searchable and readable by a screen reader.
- Form fields are the four things a hose fitter actually needs. Name, phone and suburb-or-site are required; the machine is optional because half of them don’t know the model and all of them know what broke. The hose spec field arrives pre-filled from the estimator and can be wiped and rewritten in plain English — typing over it stops the estimator overwriting it again.
- The confirmation is honest about being a demo. It is hidden on load and stays hidden on an empty submit, which instead marks the three required fields with visible messages and aria-invalid. A valid submit names the enquirer, quotes the number back, and says plainly that nothing was sent anywhere. There is no fake network round-trip.
- Every chip is keyboard-drivable. Arrow keys, Home and End move through each radio group, skipping disabled options, with focus following selection — because the customer standing in a workshop is often not the person who ends up filling in the form.
- Reviews are demo copy and are marked as such, in a line directly beneath them, and are deliberately not emitted as structured review data.
Local SEO we’d implement for the real business
The live page emits only WebPage, CreativeWork, WebSite, BreadcrumbList and ListItem schema, naming itself a demonstration. Putting a LocalBusiness block with fictional reviews and a fictional rating into a real search index would be dishonest markup, so it lives here as a code sample instead. This is what would ship on the real operator’s site, with their real address, real hours and a review feed wired to their Google Business Profile:
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "LocalBusiness",
"@id": "https://marlockhydraulics.com.au/#business",
"name": "Marlock Hydraulics",
"additionalType": "https://www.wikidata.org/wiki/Q1128564",
"description": "Mobile hydraulic hose service and on-site assembly across Kwinana, Henderson, Rockingham and the Perth metropolitan industrial belt.",
"url": "https://marlockhydraulics.com.au/",
"telephone": "+61-8-XXXX-XXXX",
"email": "bench@marlockhydraulics.com.au",
"image": "https://marlockhydraulics.com.au/img/swage-bench.jpg",
"logo": "https://marlockhydraulics.com.au/img/logo.png",
"priceRange": "$$",
"currenciesAccepted": "AUD",
"address": {
"@type": "PostalAddress",
"streetAddress": "Unit N, Example Road",
"addressLocality": "Naval Base",
"addressRegion": "WA",
"postalCode": "6165",
"addressCountry": "AU"
},
"geo": { "@type": "GeoCoordinates", "latitude": -32.19, "longitude": 115.78 },
"openingHoursSpecification": [
{ "@type": "OpeningHoursSpecification",
"dayOfWeek": ["Monday","Tuesday","Wednesday","Thursday","Friday"],
"opens": "06:30", "closes": "16:30" },
{ "@type": "OpeningHoursSpecification",
"dayOfWeek": "Saturday", "opens": "07:00", "closes": "12:00" }
],
"areaServed": [
{ "@type": "City", "name": "Kwinana" },
{ "@type": "City", "name": "Rockingham" },
{ "@type": "Place", "name": "Henderson" },
{ "@type": "Place", "name": "Bibra Lake" },
{ "@type": "Place", "name": "Welshpool" },
{ "@type": "Place", "name": "Canning Vale" },
{ "@type": "Place", "name": "Malaga" }
],
"knowsAbout": [
"SAE 100R2AT two-wire hose assemblies",
"SAE 100R13 four-spiral hose assemblies",
"JIC 37 degree flare fittings",
"BSP fittings",
"SAE Code 62 flange fittings",
"hydraulic hose registers and scheduled changeout"
],
"hasOfferCatalog": {
"@type": "OfferCatalog",
"name": "Hydraulic hose services",
"itemListElement": [
{ "@type": "Offer",
"itemOffered": { "@type": "Service", "name": "Bench-built hose assembly" },
"priceSpecification": { "@type": "PriceSpecification",
"minPrice": 62, "priceCurrency": "AUD", "valueAddedTaxIncluded": true } },
{ "@type": "Offer",
"itemOffered": { "@type": "Service", "name": "Mobile hose replacement" },
"priceSpecification": { "@type": "PriceSpecification",
"minPrice": 180, "priceCurrency": "AUD", "valueAddedTaxIncluded": true } },
{ "@type": "Offer",
"itemOffered": { "@type": "Service", "name": "After-hours breakdown callout" },
"priceSpecification": { "@type": "PriceSpecification",
"minPrice": 340, "priceCurrency": "AUD", "valueAddedTaxIncluded": true } }
]
},
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": "4.9", "reviewCount": "87",
"bestRating": "5", "worstRating": "1"
},
"review": [
{ "@type": "Review",
"author": { "@type": "Person", "name": "REAL CUSTOMER NAME" },
"datePublished": "2026-07-18",
"reviewRating": { "@type": "Rating", "ratingValue": "5", "bestRating": "5" },
"reviewBody": "Forklift died in the roller door at 10:20. Van was in the yard by 11:05
and the mast hose was swaged on the tailgate." }
]
}
</script>
Note the @type: schema.org has no hydraulic-service subtype and the trade sits outside AutoRepair, so the honest choice is the LocalBusiness parent with the specialty carried by additionalType, knowsAbout and hasOfferCatalog. Reaching for a more specific-sounding token that isn’t actually an organization subtype would be worse markup, not better.
Alongside the schema we would ship: a Google Business Profile with the service area drawn as the real van radius rather than a postcode list, service-page URLs for the three callout types and the two hose constructions, an emergency hydraulic hose Kwinana-shaped page targeting the breakdown query, and review requests fired by SMS the afternoon a job closes — the only reliable way a hose business gets reviews from people who were, by definition, having a bad day.
What would change for the real client
- Their photos. The procedural drawing is the collection’s signature and it proves the layout carries without stock imagery, but a real operator has better assets than any illustration: the bench, the van interior, the crimp dies, a wall of ferrules. Those replace the backdrop; the estimator’s drawing stays, because it does a job a photo can’t.
- Their job system. The enquiry posts into simPRO or AroFlo as a job with the hose spec in the description and the van stock checked before dispatch — both are already running in WA hose and hydraulics workshops. Invoicing syncs to Xero.
- After-hours acknowledgement. A Twilio SMS the second an after-hours enquiry lands, with the on-call fitter’s name and an honest ETA window, then a second message when the van rolls. At 2am, silence loses the job to whoever answers first.
- The hose register as a product. The tagging workflow is the highest-margin thing in the business and the hardest to sell cold. On a real build it becomes a logged-in area: each tag’s QR code resolves to the assembly’s build record, and the changeout schedule emails the workshop manager. That turns a one-off callout into a maintenance contract.
- Live run time instead of a promise. The FAQ deliberately refuses to promise a response time on a webpage. With vehicle telematics wired in, the page could show the honest current one — which is a far stronger claim than “fast, friendly service”.
- Google Business Profile as the primary local surface, with the review feed rendered on the page from live data rather than the demo copy shipped here.
Iteration log
Pass 1 — Craft
The first render passed 213 of 218 assertions, and every one of the five failures was a composition defect that no state assertion could have seen.
- At 1000 px the hose tag was covering the right-hand fitting label and the 3.0 m dimension label. The root cause was that the scene’s height was a fixed fraction of its width while the tag’s height was fixed in pixels, so at mid widths the badge occupied 63% of the drawing. Fixed properly rather than cosmetically: the whole vertical layout was rebuilt with the assembly at 57.5% of the sheet height, its labels and dimension line moved below it, and the sheet’s height now computed as max(base aspect, (tag height + margins) ÷ 0.575) so the badge can never reach the drawn subject at any width.
- The title block’s three text lines were overlapping each other at 375 and 1000 px, because line spacing was a fraction of the sheet height while the type was sized in pixels. It became a two-line block laid out from its own font metrics, with a short form at phone widths and the pressure/return legend moved out beside it.
- The 300 mm rule label was colliding with the title block; the rule now sits on the bench surface with its label to the right of it, which is also how a rule actually lies on a bench.
- Two labels were overflowing the drawing frame at 375 px. All centred scene labels now clamp their own half-width inside the sheet margins, and the DETAIL A caption carries a short form at phone widths.
- The hero trust line was breaking mid-sentence: its list items were flex containers, so the bold clause and the text after it became separate flex items and wrapped into two columns. Rebuilt as a grid of block items with the marker absolutely positioned.
- Contrast was measured from the shipped tokens rather than trusted: everything cleared 4.5:1 except the disabled chip label at 3.32:1, which was re-inked to #5A6367 (5.22:1) and given a dashed border so “locked” is signalled by shape as well as tone.
- Copy read: the lede lost a clause, and the h1 dropped to 39px at desktop and 29px at phone widths so the value proposition, both CTAs and a trust signal clear the fold at 375×667 with room rather than by a pixel.
Pass 2 — Depth
- DETAIL A earned a dimension. The magnified crimp detail now carries an OD 13 mm … OD 42 mm callout that tracks the hose chip — the one number a fitter would actually write on a drawing, and the number that explains why the main assembly is drawn as thin as it is.
- The steel rule grew minor graduations. Ten-millimetre ticks across its first 100 mm, alongside the 50 mm marks. At a 1 m frame they are crisp; at a 6 m frame they collapse into a grey smear — which is exactly what happens to a real rule as you back away from it, and makes the zoom-to-fit argument visible instead of merely stated. This is the second-read detail.
- Every scene label got a halo. Ground-coloured paint-order: stroke on every piece of type in the drawing, so a roller-door slat or a grid line can never strike through a label.
- Micro-interaction: the tag’s total lifts and fades in over 400 ms whenever the price actually changes — not on every redraw, only on a genuine change — so the badge acknowledges the tap. Suppressed entirely under reduced motion.
- Keyboard control of every radio group with arrow keys, Home and End, skipping disabled chips, focus following selection. Driven and asserted in the harness rather than assumed.
- The pressure pulse’s glow was rebuilt to hug the hose — its vertical radius is now derived from the drawn OD instead of the sheet height, so it reads as fluid in the bore rather than a smudge on the sheet.
Pass 3 — Hardening
- The desktop layout had a void. With the whole instrument in the hero’s right column, the control deck and ledger ran 600 px down the page beside an empty left column. The hero grid was re-cut: copy and drawing share row one, and the control deck spans both columns on row two, with chips and ledger side by side. The drawing keeps its exact width, so the thumbnail composition is untouched.
- The FAQ ran 1,180 px wide — unreadable measure. It became a two-column section with the heading and an invitation to ring on the left and the accordion on the right.
- The map’s zone circles were floating into the Indian Ocean. Both are now clipped to a land path, so a service boundary stops at the coast.
- Chanel rule. Two ornaments removed: the 01–08 numbering on the services grid, since a service catalogue is not a sequence and the numbers were structure pretending to be meaning; and an unlabelled red dashed route line across the top of the map that carried no information at all.
- 375 px re-checked at native resolution end to end: no horizontal overflow, no element escaping the viewport box, the masthead wordmark clearing the call button, every tap target at or above 44 px, the sticky bar inside the bottom 140 px, the tag docked beneath the drawing with the short forms while the ledger and screen-reader mirror keep the full ones.
- Reduced motion asserted, not assumed: the pulse parks mid-run, the sheen stops, the badge stops flashing, and every element of the scene renders at full opacity — a settled frame, never a blank one.
- An explicit label-collision sweep at 375, 1000 and 1200 px compares every pair of scene label bounding boxes and every label against the tag. It is part of the harness now, because assertions prove state and only pixels prove composition.
Verification
The final harness drives the real page in headless Chrome and asserts 228 times: the pinned rest state and every worked example against an independently reimplemented pricing oracle; all four lock, force, partial-unwind and memory-invalidation sequences; every hose, fittings, service and travel chip; both stepper bounds; two rapid-fire drives that fire several controls in a single evaluation with no settle between, to catch an animation closed over stale scene data; a fresh-navigation empty submit and a filled submit; the estimator-to-form handoff; the live JSON-LD type allowlist; the reserved phone pattern; the colophon on both pages; the reduced-motion settled frame; the 375 px layout; and screenshots at 375, 1000 and 1200 px at native resolution with the label-collision sweep. Zero console errors, zero page errors, zero failed requests.