Generative Assets · Industry demo 282 · Wave 25

How Latitude South Travel was made

A fictional Subiaco travel agency for Perth travellers — families doing Bali properly, couples finally booking Europe, cruise regulars who want the good cabin. The page has one job: book a $180 trip-design session, with the great-circle Trip Sketcher doing the persuading. View the site · Collection index

Palette — horizon-white departure morning

The wave's cool blue-white light site, argued from the studio's wall chart: pale sky off the morning ocean, deep water, inked routes, and one lei-coloured pin on arrival. The line/mark accent (ultramarine) and the secondary (coral) both fail 4.5:1 as small text on this ground, so body copy never uses them — the ink/line split gives text its own tokens and keeps the bright colours for routes, rules and pins.

Horizon white #F2F6F9

Ground — pale sky off the morning ocean.

Deep-sea navy #22314E

Structure and primary ink — the ocean at depth on the chart.

Flightpath ultramarine #3D5AC8

Line/mark only — route arcs, tide-marks, CTAs. Never body text.

Frangipani coral #DC6B52

The pin on arrival — chips, focus rings, the destination pin. Chip-scale only.

Coral ink (derived) #A8402B

Text-safe coral for links — #DC6B52 measures ~3.0:1 on the ground, this ~5.6:1.

Body ink (derived) #3E4B66

Secondary copy — derived so the marks never have to carry paragraphs.

Type

The signature — the Trip Sketcher

Destination chips (Bali / Japan / Europe / South Pacific cruise ex-Fremantle), travellers and nights steppers, and comfort chips drive a procedural SVG chart of the Indian Ocean rim. The chart is built entirely in JS at runtime: a 20° graticule, ~19 hand-set coastline polylines, a dashed 32°S latitude line through Perth, and routes that are true great circles — each is a spherical slerp between Perth and the destination sampled at 64 points, projected equirectangular (lon −12°→158°, lat 62°→−46°). The route dash-draws with butt caps (the W15 round-cap dot lesson), one tide-mark per night is placed with getPointAtLength, and the coral pin drops with a one-shot ring pulse when the arc completes. The cruise renders the coastal leg north-west out of Fremantle instead of a flight arc — illustrative of "it leaves from home", with itineraries quoted exactly at booking.

The planning card prints the full maths line by line in mono under a declaration that is part of the card, always visible: "Planning figures from recent bookings, not quotes — fares move daily; we quote exact at booking." Dual-speed redraw per the W21 rule: the load entrance takes 1.4s and rests at two-thirds arced (pin not yet dropped, a marker patrolling the drawn portion so the demonstration is alive at any capture instant); every subsequent state change animates in 520ms. Reduced motion renders the route complete with the pin down, no patrol. State probe window.__travelagent carries dest, travellers, nights, comfort, total, rooms, drawn and a settled flag; QA gates on state, never on seconds. A second-read detail: all four routes Latitude South inks are on the chart wall faintly the whole time — choosing a destination just inks one of them boldly.

Pricing model — shape and derivation

Per-person flights + per-room accommodation + per-person daily allowance, each pinned per destination and comfort tier. Rooms are 1 room per 2 travellers, rounded up — stated on-card, so a family of three sees the second room appear in the maths, not in the fine print. The cruise has no flights line ("it leaves from home — that's the point"); fares are per person twin-share per night, the comfort chips relabel to inside / balcony / suite, and the shore allowance is deliberately flat at $45 pp/night whatever the cabin — a documented pricing statement (the ship feeds you either way), not an invented gradient. The $180 trip-design fee sits outside the band (credited in full at booking), which is why the sketcher's cheapest outputs equal the services grid's "from" floors exactly.

Every figure measured from the running page by a harness that reimplements the model independently from the spec (LEARNINGS W14).
ScenarioArithmeticSpecMeasured
Japan · 2 trav · 10 n · comfort (rest state)2×$1,150 + 1×10×$280 + 2×10×$140$7,900$7,900
Japan · 2 · 10 · luxe$2,300 + $5,200 + $4,800$12,300$12,300
Bali · 4 · 7 · comfort4×$580 + 2rm×7×$240 + 4×7×$110$8,760$8,760 (rooms=2)
Bali · 3 · 7 · comfort (round-up check)rooms = ceil(3/2) = 2rooms 2$7,410, rooms 2
Bali floor · 1 · 5 · essentials$580 + $650 + $350$1,580$1,580 = grid floor
Japan floor · 1 · 5 · essentials$1,150 + $900 + $450$2,500$2,500
Europe floor · 1 · 5 · essentials$1,850 + $1,100 + $550$3,500$3,500
Cruise floor · 2 · 5 · inside2×5×$210 + 2×5×$45$2,550$2,550
Cruise · 2 · 5 · suite2×5×$520 + 2×5×$45$5,650 vs oracle

Zero spec corrections — all worked examples were pure anchor arithmetic and every one matched the implemented model to the dollar.

Flagged, by design — the solo-cruise gap: cruise fares are per person twin-share, so with cruise selected the travellers stepper clamps to a minimum of 2, with the reason on-card: "solo cabins exist and we quote them honestly, on the phone." A solo cruise price is deliberately not computable in the sketcher — a real build would add a solo-supplement rate. Also declared on-card: the flat cruise shore allowance, and the rooms rounding rule.

Conversion decisions

Local SEO we'd implement for the real business

This demonstration page ships only WebPage schema naming it a demo — fictional structured data in a real index would be dishonest markup. A real Latitude South would ship TravelAgency + review schema like this:

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "TravelAgency",
  "name": "Latitude South Travel",
  "url": "https://latitudesouthtravel.com.au",
  "telephone": "+61 8 XXXX XXXX",
  "address": {
    "@type": "PostalAddress",
    "streetAddress": "…", "addressLocality": "Subiaco",
    "addressRegion": "WA", "postalCode": "6008", "addressCountry": "AU"
  },
  "geo": { "@type": "GeoCoordinates", "latitude": -31.949, "longitude": 115.826 },
  "openingHoursSpecification": [{ "@type": "OpeningHoursSpecification",
    "dayOfWeek": ["Monday","Tuesday","Wednesday","Thursday","Friday"],
    "opens": "09:00", "closes": "17:30" }],
  "priceRange": "$$",
  "areaServed": "Perth, Western Australia",
  "aggregateRating": { "@type": "AggregateRating", "ratingValue": "4.9", "reviewCount": "187" },
  "review": [{
    "@type": "Review",
    "author": { "@type": "Person", "name": "…" },
    "reviewRating": { "@type": "Rating", "ratingValue": "5" },
    "reviewBody": "…"
  }]
}
</script>

What would change for the real client

Iteration log

Pass 1 — Craft

The 48-assert harness (independent pricing oracle, fresh-load empty submit, filled submit, chip/stepper drives, cruise clamp, 375 probes, reduced motion) caught three ship-blockers: a specificity collision where a.cta's display beat .head-cta{display:none}, leaving the header CTA alive at 375px and pushing scrollWidth to 467px (the W6 shorthand-vs-specificity family); the reduced-motion rest state holding at two-thirds with no pin (spec: complete, pin down); and nights-changes while the arc was incomplete not rebuilding tide-marks. Screenshot review at 1200×750 and 375 then caught what asserts can't: hero copy said "the sketcher on the right" (wrong on mobile, now position-neutral) and the eyebrow duplicated the wordmark word-for-word.

Pass 2 — Depth

Added the chart-wall second read (all four routes inked faintly at all times, the chosen one drawn bold over them), the one-shot pin-drop ring pulse (JS-driven — the pin group is positioned by an SVG transform attribute, so CSS animation is off-limits per W16/W17), and the resting marker patrol along the drawn arc. Driven checks: +1 night from rest rebuilt to 11 tide-marks and completed the arc ($8,460, matching the oracle); the cruise pin tag had clipped past the chart's right edge in the Pass-1 screenshot — re-anchored left of the pin and re-shot to confirm. One harness false alarm documented: getBBox() reports the tag in the pin group's local frame, so a raw bounds assert "fails" while the screenshot shows it fully in-frame — trust the rendered image.

Pass 3 — Hardening

Masthead first at 375 (W18/W21): wordmark right edge vs call-button left edge compared explicitly (rect-edge assert) and eyeballed — clean, with the header CTA correctly gone below 560px. Re-ran the full suite after every change: 48/48, zero console errors (which also proves the form's v-flag pattern compiles), sticky bar fixed with a tel: link in the bottom 140px, FAQ opens by dispatched click, date min set to today. Chanel rule: removed the sketcher's "CHART · PERTH 32°S" sub-caption — the chart already says both. Final copy read fixed the Swan on the metro map to actually meet the ocean at Fremantle. Reduced motion re-verified: route complete, pin down, no patrol, totals intact.

Techniques