← Back to the Reefline Dive demo Index

Build guide · site 354 · Wave 39, Industry Demos XXVIII

How the Reefline Dive demo was built

A fictional dive shop and training centre on the Rockingham beachfront — try-dives, Open Water and Advanced courses, guided shore dives on the Shoalwater reefs, boat double-dives on the wrecks, gear hire, air and nitrox fills. The page has one job: a booking request with the trip or course, the divers and the extras already priced.

Concept

The audience is the couple who want to learn before their Ningaloo trip, the visiting diver with a card and no gear, the group of mates booking a boat day, and the parent booking a teenager's try-dive. Dive pricing is per person, mostly all-inclusive, and lives or dies on what's in the price — so the page declares "tanks, weights and your guide or instructor included" on the card, on the slate and in the ledger, and prices the only three things that genuinely move: the trip, the head-count and the extras.

The direction is ten metres down on the Shoalwater reef, looking up: deep-water ground, sunlit-foam ink, aqua CTAs, hose-orange linework. The signature is the Dive Planner — a drawn SVG cross-section of the water column with a bright surface at the top, sun shafts breathing down through it, a depth gauge down the left from 0 to 30 m, a reef ledge, and the divers descending along a drawn line toward it. Trip chips redraw the surface (a boat, a beach, a pool edge), the depth line and the ledge; the divers stepper draws the figures; the toggles draw their marks (a hire-kit rack, green-and-yellow nitrox bands on every tank, a camera housing on the guide); and the price is a dive slate hung on the gauge rule with an orange cord.

Palette — measured, not assumed

Every ratio below was computed by WCAG relative-luminance arithmetic from the shipped hex tokens in Pass 1 (the spec's figures were hypotheses). All four spec tokens cleared their grounds by a wide margin, so no two-token remedy was needed: aqua #4FC8BE measures 7.65:1 on the water and 6.74:1 on the lifted card, and carries dark labels on buttons at 7.65:1; hose orange #F2A65A is 7.68:1 / 6.77:1 and is used for lines and marks; the pre-minted lighter inks (#8FE0D8 10.23:1, #F7C289 9.65:1) carry aqua- and orange-coloured copy anyway, for headroom over gradients. The slate is the one light surface: deep ink 11.81:1, subtitle 8.55:1, note 6.05:1, name line 6.16:1 on the slate cream.

#0A2733
Deep waterPage ground, dark labels on aqua and orange buttons.ground for every ratio below
#E3F0F2
Sunlit foamBody ink, headings, the surface line, diver masks and the hull.13.33:1 on water · 11.74:1 on card
#B9CFD4
Foam softSecondary copy, ledger notes, form fine print.9.58:1 on water · 8.44:1 on card
#4FC8BE
AquaPrimary buttons and selected chips (dark labels), switch-on fill, seagrass, map dots.7.65:1 on water · water on it 7.65:1
#8FE0D8
Aqua inkLinks, the display line's second phrase, ledger totals, declaration lines.10.23:1 on water · 9.01:1 on card
#F2A65A
Hose orangeGauge rule, depth lines, chip outlines, hoses, the slate cord, sticky Call button ground.7.68:1 on water · water on it 7.68:1
#F7C289
Orange inkEyebrows, depth labels, tier note, count tag, disabled-toggle reasons.9.65:1 on water · 9.32:1 on the stage · 8.50:1 on card
#F4F1E4
Slate creamThe dive slate only — the one light surface, so the price reads first.ink 11.81:1 · sub 8.55:1 · note 6.05:1

Type

Four divers, two tanks, under by nine

Josefin Sans 700 for the wordmark, headings, the display line and the slate price — a geometric with a 1930s poster-diver feel. The display moment re-words itself with the controls: the count word from the stepper (One diver, … Eight divers,) and the trip's phrase (one tank, pool first · four days, eighteen metres · five dives, thirty metres · one tank, ten metres · two tanks, under by nine).

Manrope 400–700

Manrope for everything read: body at 17px on mobile and 18px from 760px, chips, ledger, form, scene labels. Weight carries hierarchy (700 legends and buttons, 600 chips, 500 fine print); tracking only on the uppercase eyebrows, the slate name line and the DEPTH title.

Techniques

Signature implementation

State is one object — trip, divers, three toggle memories — and compute() is a pure function over it: the boat rate is $170 at four or more divers else $190, other trips are flat per person; gear (+$75 pp) is live on shore and boat only, nitrox (+$20 pp) on boat only, photos (+$60 flat) on shore and boat; per-person lines multiply the actual count; the tier note fires only at boat × 3. render() writes the result to every surface in one pass — the display line, the slate (name, price, subtitle, note), the chips, the stepper, the three switches (disabled with the on-card reason where not live, aria-checked mirroring the effective state), the itemised ledger, the plain-text mirror under the scene and the booking textarea — then drawTrip() rebuilds the scene. Restore-vs-reset is by memory: a course chip never overwrites state.gear/nitrox/photos, so returning to a boat day restores the prior choices exactly. Every handler bumps the scene token before touching state; the first draw earns a 90 ms/layer stagger (1.2 s cap), every later draw 26 ms/layer (480 ms cap); the settle timer publishes window.__diveshop.settled only for the latest token, so a rapid-fire run of clicks ends on one definite, settled state.

Pricing model (pinned anchors)

Trip / coursePer personDepth drawnGearNitroxPhotos
Try-dive · Discover Scuba (pool + shore, half day)$19912 m line, pool edge, instructor holds the student's handdisableddisableddisabled
Open Water course (four days)$65018 m line, shore entry, skills matdisableddisableddisabled
Advanced course (two days, five dives)$55030 m line, boat, deep-blue gradient below 18 mdisableddisableddisabled
Guided shore dive (Shoalwater, single dive)$9510 m line, beach, Penguin Island, seagrasslive +$75 ppdisabledlive +$60
Boat double-dive (two dives)$190 · $170 at 4+ (everyone)18–30 m band, boat, two dive-flag markerslive +$75 pplive +$20 pplive +$60
RuleSemanticCopy
Boat × 3 diversNOTE only, never forced"at four divers the boat re-rates to $170 each for everyone" — mobile short form on the slate: "four divers → $170 each for all"; ledger and mirror always carry the full template
Boat × 4+ diversdeclaration line on badge, ledger and mirror; note ABSENT"$170 pp — group rate"
Gear on coursesDISABLED with reason; unlock RESTORES the prior choice"the course fee includes all your gear"
Nitrox on courses and shore divesDISABLED with reason; unlock RESTORES"nitrox needs the nitrox ticket and a boat-depth profile — add it to a boat day"
Photos on coursesDISABLED with reason; unlock RESTORES"course days are for skills — book photos on your first fun dive"
Rescue and pro courses, charters, servicing, salesno number"quoted" — grid, ledger footnote and FAQ; diver medical statement declared in the FAQ
Tanks, weights, guide/instructordeclaration line, $0on the card, the slate, the ledger and the mirror

Divers 1–8 on every trip (no empty state; the boat carries eight so the drawn cap equals the range). Every anchor and multiplier is an integer, so totals are whole dollars by construction. The estimator's cheapest outputs equal the services grid floors: shore dives from $95, try-dives $199, boat double-dives $170 pp (groups of four) / $190 pp, Open Water $650, Advanced $550.

Worked examples — measured from the running page against an independent oracle

Scenario (full control vector set explicitly)SpecMeasuredAlso asserted
Rest: boat double-dive · 4 divers · gear ON · nitrox OFF · photos OFF$980$980display "FOUR DIVERS, TWO TANKS, UNDER BY NINE"; group-rate line on slate, ledger and mirror; tier note absent on all three; four divers + guide drawn; slate and scene inside the first 750 px at 1200
Guided shore dive · 1 · all OFF$95$95grid floor; nitrox disabled with its reason
Guided shore dive · 1 · gear ON$170$170hire-kit rack drawn on the beach
Try-dive · 2$398$398gear, nitrox and photos all disabled, three reasons visible
Boat · 3 · all OFF$570$570tier note PRESENT on slate, ledger, mirror; group line absent
Boat · 4 · all OFF$680$680note ABSENT, group-rate line PRESENT (three surfaces)
Boat · 2 · nitrox ON$420$420nitrox bands drawn on every tank
Open Water · 1$650$650all three toggles disabled with reasons
Boat · 8 · gear + nitrox + photos$2,180$2,180eight divers drawn (cap), camera on the guide, rack + bands
Boat · nitrox ON → shore → boatrestore$420 → $190 → $420nitrox locked OFF on shore (price = shore), RESTORED ON on return; gear + photos memory also asserted across an Open Water hop
Rest + photos · rest gear OFF · rest divers 3$1,040 · $680 · $795samedivers 3 carries the tier note
Advanced · 2 · all OFF$1,100$1,100deep gradient below 18 m
Floors: try 1 · adv 1 · boat 1$199 · $550 · $190samematch the grid

Conversion decisions

Local SEO we'd implement for the real business

Would-ship structured data (not live on this demo)

The demo's live JSON-LD is only WebPage / WebSite / BreadcrumbList, because fictional business data in a real index is dishonest markup. For the real Reefline we'd ship a SportsActivityLocation (the schema.org LocalBusiness subtype for a dive centre; the retail counter and servicing arm would carry a separate SportingGoodsStore node) with real reviews from the shop's Google profile:

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "SportsActivityLocation",
  "name": "Reefline Dive",
  "description": "Dive shop and training centre in Rockingham: try-dives, Open Water and Advanced courses, guided shore dives on the Shoalwater reefs, boat double-dives on the wrecks, gear hire, air and nitrox fills.",
  "url": "https://www.example-reefline.com.au/",
  "telephone": "+61 8 9000 0354",
  "image": "https://www.example-reefline.com.au/photos/boat.jpg",
  "priceRange": "$95–$650",
  "address": { "@type": "PostalAddress", "streetAddress": "(real street address)", "addressLocality": "Rockingham", "addressRegion": "WA", "postalCode": "6168", "addressCountry": "AU" },
  "geo": { "@type": "GeoCoordinates", "latitude": -32.28, "longitude": 115.73 },
  "areaServed": ["Rockingham", "Shoalwater", "Safety Bay", "Warnbro", "Baldivis", "Kwinana", "Perth"],
  "openingHoursSpecification": [
    { "@type": "OpeningHoursSpecification", "dayOfWeek": ["Wednesday","Thursday","Friday"], "opens": "08:00", "closes": "17:00" },
    { "@type": "OpeningHoursSpecification", "dayOfWeek": ["Saturday","Sunday"], "opens": "06:30", "closes": "16:00" }
  ],
  "hasOfferCatalog": { "@type": "OfferCatalog", "name": "Courses and trips", "itemListElement": [
    { "@type": "Offer", "name": "Guided shore dive", "price": "95", "priceCurrency": "AUD" },
    { "@type": "Offer", "name": "Boat double-dive", "price": "190", "priceCurrency": "AUD" },
    { "@type": "Offer", "name": "Open Water course", "price": "650", "priceCurrency": "AUD" }
  ] },
  "aggregateRating": { "@type": "AggregateRating", "ratingValue": "4.9", "reviewCount": "184" },
  "review": [ { "@type": "Review", "author": { "@type": "Person", "name": "(real reviewer)" }, "reviewRating": { "@type": "Rating", "ratingValue": "5" }, "reviewBody": "(real review text, synced from Google)" } ]
}
</script>

Plus: a Google Business Profile with the boat, the classroom and the fill station as photos; a Course node per certification with the agency named; suburb landing content for Rockingham, Baldivis and Mandurah searches ("learn to dive Rockingham", "shore dive Shoalwater"); the FAQ marked up as FAQPage once the answers are the shop's own words.

What would change for the real client

Iteration log

Pass 1 — Craft

  • Contrast measured from the shipped tokens (see the palette section): every pair clears 4.5:1 with the lowest being the slate note ink at 6.05:1 and the selected-chip small text at 4.95:1. Recorded, nothing shifted.
  • Rest-state price measured from the running page: $980, matching the spec's 4 × 170 + 4 × 75.
  • Slate over the depth labels: the first slate covered the "10 m" label and its name line wrapped to three lines. Fixed by moving the depth labels to the left of the gauge rule, widening the slate to 25%, dropping the divers count from the name line (it lives in the subtitle and the count tag) and hanging the slate from the rule with an exact calc(6% + 24px) cord.
  • Descent line too steep under the boat: moved the boat right and the ledge left so the divers descend at ~45°; the shore-side trips now put the ledge on the right so the descent runs down-right from the beach or pool. Divers scaled up 1.28× (guide 1.5×) so they read at the 1200 thumbnail.
  • Boat composition: the mast and dive flag moved to the bow, the hire-kit rack to the aft deck, the boat name into the cabin — the rack and "REEFLINE" had collided.
  • Gauge rescaled to 12.4 px/m so the 30 m line leaves room for the ledge below it.

Pass 2 — Depth

  • The bank was a wall on the shallow trips (a 12 m ledge dropping vertically to the frame's floor); the ledge path now runs its bank at ~45° scaled to the drop, so a try-dive reef reads as a slope and the 30 m ledge as a shelf.
  • Second-read details: five aqua fish drifting and turning above the ledge; the guide's orange hood; nitrox bands on the rack cylinders as well as the divers' tanks; the sun bloom in the top-right corner of the water; a hand-line from instructor to student on try-dives.
  • Micro-interaction: the switch knob slides with the ease-out curve and swaps colour; disabled switches drop to 42% with a dashed card border and the reason line, so "off" and "not applicable" never look the same.
  • Count tag now anchors at the top of the descent line under the boat (left ledges) and at the midpoint above the line (right ledges), with a leader to the line, after it had sat over the divers.

Pass 3 — Hardening

  • 375px: scrollWidth 375, no offenders, masthead wordmark right edge clear of the call chip; h1, CTA and trust chips above the fold; the sticky bar fixed within the bottom 140 px with a tel: link. The tall layout keeps the ledge/entry logic and its own slate variant (48% cap, brief tier note); measured slate bottom 157 px on a 338 px stage — under the 10 m line's label. On the shore-side trips at 375 the slate hides the first third of the descent line (the divers nearest the beach); the count tag, guide, depth label and ledge stay clear — accepted and noted.
  • Label collisions at native resolution: "pool first, then the shore" had collided with the DEPTH title (moved right of it, end-anchored at the ladder); the "skills mat" caption collided with the "18 m · Open Water" label at 375 (caption moved left of the mat, depth labels on right-side ledges dropped 34 px below the line over the rock); the beach hire-kit rack collided with DEPTH (moved to x = 100 on the dune).
  • Mid-width (1000): slate 140 px wide on a 501 px stage, subtitle shortened, no compression of the boat or the ledge.
  • Reduced motion: all layers settle at opacity 1 with no transition, four static bubbles per emitter, no rAF loop; settled flag set within 30 ms.
  • Rapid-fire drive (eleven then five control clicks in one evaluate, no settle between): zero errors, one definite settled state, total consistent with the oracle.
  • Chanel rule: removed the SVG cord line drawn from the rule (the slate's own pseudo-element cord replaced it) and the "0 m" label's collision with the foam line (dropped below the surface).
  • Final copy read; guide tables in .table-scroll; both pages one <h1>; both carry the fictional-disclosure colophon.

This is a demonstration build by Sapience Analytics. Want one pointed at your customers?

Reefline Dive is a fictional business — a dive shop industry demonstration designed & built by Sapience Analytics, part of the Generative Assets collection. Back to the site · Index.