Steadfast — build notes

Generative Assets · Industry demo 313 · dog training

Making Steadfast Dog Training

Steadfast is a fictional Balcatta dog trainer — puppy school, group obedience and private behaviour work for Perth's northern suburbs. The landing page has one job: a program enquiry with the program, session count and price already chosen. Everything on the page, including the kelpie, is procedural SVG, CSS and vanilla JS in a single file.

Palette — the oval at 7am

Every colour argues from the morning training session: the cream of early light, the oval itself, and the kelpie on it. The tan is a line-and-mark token only — body text always sits in the near-black heel ink (13.0:1 on the ground).

Morning cream#F2F1E7 — page ground, badge fill, boundary line.
Heel ink#242B20 — body text, trust strip, footer. 13.0:1 on cream.
Oval green#4C7A3F — CTAs, buttons, the field. 4.7:1 — buttons, never small text.
Kelpie tan#A5522F — the dog linework, lead trace, marks. Lines only.

Type

Baloo 2 (700–800) carries the display work — a rounded face with real structure, friendly without tipping into children's-book territory, which is the brand's whole argument: warm methods, disciplined outcomes. Asap handles body and UI at 17px. The typographic moment is “SIT. STAY. FINALLY.” set huge with the tan landing on the payoff word, and the program name swapping in beneath it as the planner chips change.

The signature — the Training Planner

An SVG oval scene: boundary line, mowing stripes clipped to the ellipse, a kelpie and handler in tan linework with cream halo understrokes (tan alone dies against the green — the double-stroke keeps the pinned linework legible). Each program redraws the course: a cone circle for puppy school, a down-and-back heel lane for obedience, a figure-eight for private work, and a long recall line for bootcamp that the dog runs out-and-back (ping-pong, not loop). In private modes a 1–6 stepper lights one numbered marker per session; the dog runs the course on a rAF loop with a lead-line trace rendered as a moving dash pair (tan over cream halo, opacity floor 0.55, so the thumbnail can never catch it invisible).

The dog's position comes from getPointAtLength on the course path; the delta is clamped at zero and capped (dt = min(max(now − last, 0)/1000, .05)) per the W29 lesson. Every control bumps a run token as its first act, before any state swap, so in-flight marker staggers and the reward-star timeout die instantly under rapid-fire input. First draw earns the full entrance stagger (110 ms per marker); every later redraw runs snappy at 55 ms with a 520 ms settle cap, and window.__dogtraining.settled flips true when the redraw lands. Second-read details: the kelpie sits for a beat at the last lit marker each lap and a reward star pops above it — the force-free method drawn into the animation — plus a tennis ball parked on the boundary.

The pricing model

Pure anchor arithmetic, measured from the running page: price = 480 × ⌊n/3⌋ + 180 × (n mod 3) for private sessions and bootcamp; group courses are fixed. The 3-session package applies automatically and surfaces as a note, never a control.

ScenarioArithmeticDisplayedNote
Puppy school (8–16 wks)fixed, 5-week course$240“one price for the five weeks”
Basic obedience (5 mo+)fixed, 5-week course$260“one price for the five weeks”
Private · 2 sessions2 × 180$360no note
Private · 3 sessions (rest state)480 × 1$480“3-session package applied — saves $60”
Private · 4 sessions480 + 180$660“saves $60”
Private · 6 sessions480 × 2$960“2 × 3-session packages applied — saves $120”
Recall bootcampsame meter as privatefrom $180“a curriculum, not a surcharge”
Reactive-dog programpriced after consultdesigned price-less state
One honest-numbers refinement: the spec pins the note string “3-session package applied — saves $60”. At six sessions the true saving is $120 (2 × $60), so the note text scales with the package count — the pinned string ships verbatim for one package, and “2 × 3-session packages applied — saves $120” at six. Captioning $60 against a $120 saving would have been the dishonest choice (the W10 rule: fix the claim, not the caption).

Conversion decisions

Local SEO we'd implement for the real business

The live demo page carries only WebPage/WebSite/BreadcrumbList schema naming it a demonstration — fictional structured data in a real index would be dishonest markup. For a real client we'd ship ProfessionalService (schema.org has no dog-training subtype; it's the correct LocalBusiness-family choice) with a live review feed:

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "ProfessionalService",
  "name": "Steadfast Dog Training",
  "description": "Puppy school, group obedience and private behaviour
    training across Perth's northern suburbs.",
  "telephone": "+61-8-9000-0313",
  "address": {
    "@type": "PostalAddress",
    "addressLocality": "Balcatta",
    "addressRegion": "WA",
    "addressCountry": "AU"
  },
  "areaServed": ["Balcatta", "Stirling", "Karrinyup", "Scarborough",
    "Duncraig", "Hillarys", "Joondalup", "Wanneroo", "Dianella", "Morley"],
  "openingHoursSpecification": [{
    "@type": "OpeningHoursSpecification",
    "dayOfWeek": ["Tuesday", "Wednesday", "Thursday", "Friday"],
    "opens": "06:30", "closes": "18:30"
  }, {
    "@type": "OpeningHoursSpecification",
    "dayOfWeek": "Saturday", "opens": "07:00", "closes": "13:00"
  }],
  "priceRange": "$180-$480",
  "aggregateRating": {
    "@type": "AggregateRating",
    "ratingValue": "4.9",
    "reviewCount": "87"
  },
  "review": [{
    "@type": "Review",
    "author": { "@type": "Person", "name": "— real customer —" },
    "reviewRating": { "@type": "Rating", "ratingValue": "5" },
    "reviewBody": "— pulled live from the Google Business Profile —"
  }]
}
</script>

Plus: a Google Business Profile wired to the reviews feed above, suburb-level landing sections targeting “puppy school + [suburb]” queries, and tel: call tracking so ad spend reports in booked programs, not clicks.

What changes for a real client

Iteration log

Pass 1 — Craft

Drove every control against the pinned arithmetic: stepper to 2 → $360 no note, 3 → $480 note shown, 4 → $660 “saves $60”, 6 → $960 “saves $120”; puppy $240, basic $260, bootcamp inheriting the session count at the same meter — all measured from the badge text, all matching the anchor arithmetic. Empty submit raised 5 invalid fields with the confirmation panel still hidden; a filled submit showed it. Screenshot review found two defects the asserts sailed past: unlit course markers at 0.32 opacity vanished into the green (raised to 0.5), and the distant tree line rendered as grey slugs against the morning sky (recoloured to a eucalypt #7C9468). Measured the 375px masthead explicitly: wordmark right edge 145px vs call-button left edge 284px — clear.

Pass 2 — Depth

Added ground shadows under the dog and handler, a badge price pop on state change (transform-box: fill-box so the CSS animation never fights an SVG attribute), and the reward star over the kelpie's sit at the last lit marker — the second-read detail that draws the force-free method into the loop. Re-drove the full matrix plus the deliberate rapid-fire drive: nine control dispatches in a single evaluate with no settle between (chip → stepper → chip → chip → stepper → chip → chip → stepper → stepper). Token invalidation held — the run ended on private · 4 sessions with the badge reading the self-consistent $660 and zero console errors.

Pass 3 — Hardening

Removed the centre-circle ellipse that cluttered the figure-eight crossing (the excess-ornament rule). Re-verified: reduced-motion renders a settled static frame (dog parked at 42% of the course, all six markers shown, settled:true, no rAF loop); 375px scrollWidth exactly 375 on both pages with the sticky tel: bar ≥48px in the bottom 140px; the phone pattern is v-flag-valid (every literal special escaped — rejects letters, accepts “(08) 9000 0313”, zero parse errors on the console); live JSON-LD collects recursively to [WebSite, WebPage, BreadcrumbList, ListItem] — allowlist clean; fresh-navigation empty-submit re-probed after the planner handoff pre-filled the form. Final full-suite run: 48 checks green, zero console errors on both pages.