Guide · Industry demonstration № 329
A term of music, priced on the stave
Fernbrook School of Music is a fictional Applecross tuition studio — piano, guitar, voice, drums and violin, terms of ten weekly lessons. The page has one job: turn a parent's "how much would lessons be for the two of them?" into an enrolment enquiry with instruments, lengths and the family total already settled. The signature is a Term Planner drawn as a grand stave — every enrolment becomes a note, and the family total sits on the end bar.
Palette
The practice room at golden hour
Every colour is argued from the room: the ivory of piano keys and afternoon light, ebony from the case, plum from the recital curtain, brass from the metronome and the stands. Spec-time contrast figures were treated as hypotheses (W32) and measured by WCAG arithmetic from the shipped tokens during Pass 1 — measured values below.
#F7F3ECPage ground. Warm without being cream-and-terracotta; the light through the studio window at 5pm.
#201B24Body ink and the notes themselves. Measured 15.27:1 on ivory (spec hypothesis ≈14:1 — passed with room).
#5A3A6ECTAs, links, the price badge. White labels on plum measured 9.27:1; plum text on ivory 8.38:1. No two-token remedy needed.
#B8933FStave lines, metronome marks, rules — lines and marks only (2.61:1 on ivory, deliberately never used for copy).
#75601CThe pre-minted text twin for brass-coloured small copy: eyebrows, tempo markings, the family-discount note. Measured 5.51:1 on ivory (spec hypothesis ≈6:1 — under the guess, still comfortably past 4.5:1).
#D9BE7AMinted during Pass 1 for the discount line on the dark ledger — brass-ink vanished on ebony, this measures 9.32:1 there.
Type
Fraunces & Nunito Sans
Fraunces (600, optical sizing on) carries the display voice — a warm serif with recital manners, used for the wordmark, headings, the huge "TERM ONE, TEN LESSONS", and italic for the family total and the tempo-marking notes under the stave. Nunito Sans is the workhorse: rounded enough to feel kind to parents, set at ≥16px body with weights 400/700/800 doing the hierarchy work. The one moment of typographic bravery is the planner's display pair — the uppercase Fraunces line swapping live with the enrolment count, the italic total beneath it like a marking on a score.
Signature
The Term Planner on a stave
The planner is a single inline SVG: a grand stave ruled in brass with a curly brace, an end bar, and a metronome beside it. Each enrolment renders as a drawn note on the stave with its instrument's silhouette above it — and the note value is honest musical notation for the lesson length: 30 minutes draws a quarter note, 45 a half note, 60 a whole note, and a group lesson draws a beamed pair of quavers (a shared lesson, literally). The term total sits in a plum badge on the stave's end bar — inside the signature's artwork and inside the first 750px at 1200 wide, because the thumbnail is the commercial argument (W30). The family-discount note renders under the stave in Fraunces italic, like a tempo marking.
Engineering the redraw
- Dual-speed redraw (W21): the first render earns the entrance (glyphs stagger in at 230 ms per row behind the stave's dash-draw); every subsequent chip tap redraws at 70 ms per row via a
firstDrawflag, so the planner reads snappy, not laggy. - Token invalidation in the handler (W25): every control click bumps
redrawTokenbefore the state swap, so an in-flight settle timer from a previous redraw can never mark a stale frame as settled. The rapid-fire drive in QA (five clicks in one evaluate, no settle) passes clean. - Metronome loop: rAF with the W29 delta clamp (
dt = min(max(now−last,0),50)/1000), paused ondocument.hidden, and a beat dot whose opacity floor is 0.55 — the scene is visibly alive at any capture instant. Reduced motion renders a settled frame: pendulum parked at 9°, dot held at 0.8. - SVG discipline: positions live on
transformattributes of inner groups; CSS animates only the outer.glyphwrapper (opacity/translate), so no stylesheet rule ever discards a presentation attribute (W16/W17). Stave lines draw in viapathLength="1"dash with butt caps — no round-cap dot at zero length (W15). - State probe:
window.__musicschoolexposes the scenario (rows, instruments, lengths, modes, hire), the computed figures (tuition, hire, discount, total, per-row tuition) and asettledflag QA gates on — state, never seconds (W8).
Honest pricing
The family-discount engine
Every figure is pinned per term (ten weekly lessons) and the planner's output was verified against an independently reimplemented oracle across all six binding worked examples, plus a rapid-fire race drive.
| Line | Price | Availability |
|---|---|---|
| Private 30 min | $420 ($42 a lesson) | all five instruments |
| Private 45 min | $580 — AMEB prep included | all five instruments |
| Private 60 min | $740 — AMEB prep included | all five instruments |
| Group beginners | $280 (max 4, 30 min) | piano & guitar only; length locked to 30 min |
| Instrument hire | +$60 per student | guitar & violin, any length/mode |
| Ensembles & theory | timetabled each term — no number by design | ask for this term's sheet |
The discount rule, stated once and shipped everywhere
Two or more students: 10% off every enrolment's term tuition except the single most expensive. The note is a pinned template (W31) — "family discount — saves $X" where X = Σ 0.10 × (each tuition except one instance of the highest). Ties resolve by exactly one counting as highest (two group-piano enrolments at $280 each discount one of them: saves $28). Hire fees are never discounted — tuition only, stated on the ledger. 10% of every anchor {420, 580, 740, 280} is a whole dollar, so totals are integer-safe by construction.
The disable / lock matrix (W18/W31 — every cell pinned)
| Instrument | Group mode | Take-home hire |
|---|---|---|
| Piano | live ($280) | disabled — "the piano stays here — practice-room cards included" |
| Guitar | live ($280) | live (+$60) |
| Voice | disabled — "taught one-on-one — voices and drum kits don't share well" | disabled — "you already own the instrument" |
| Drums | disabled — same on-page reason | disabled — "studio kit provided — your neighbours said thanks" |
| Violin | disabled — same on-page reason | live (+$60) |
Selecting group forces the length to 30 minutes (the 45/60 chips disable with the lock note — 45/60 group combinations do not exist). Switching a group row to a one-on-one instrument falls the row back to private; switching a hired row to piano/voice/drums clears the hire. Every control either changes the price honestly or visibly says why it can't.
Worked examples — all measured from the running page against the oracle
| Scenario | Arithmetic | Measured |
|---|---|---|
| Rest: 1 student, piano private 30 | 420 | $420, no note |
| Piano 30 + guitar 45 | 1,000 − 42 | $958 · "saves $42" |
| Piano 30 + guitar 45 + drums 60 | 1,740 − (42 + 58) | $1,640 · "saves $100" |
| Two group-piano enrolments (tie rule) | 560 − 28 | $532 · "saves $28" |
| Violin private 45 + hire, one student | 580 + 60 | $640, no discount |
| Violin 45 + hire, piano 60 (hire excluded) | 1,320 − 58 + 60 | $1,322 · "saves $58" |
The cheapest plannable output — one group enrolment at $280 — equals the services grid's "group beginners $280 a term" line, and the privates floor $420 matches "from $420 a term ($42 a lesson)" (W12: the page never argues with itself).
Conversion decisions
Why the page is shaped like this
- Above the fold at 375px: name, "Music lessons in Applecross — ten weeks to your first song", the Plan-your-term CTA, the phone number, and the WWC/AMEB/recital trust line — all before the first scroll. The planner stage follows immediately; at 1200×750 the stave, the $420 badge and the display pair share the thumbnail with the headline.
- The planner is the qualifier: parents arrive asking "what would two kids cost?" — the stave answers it in four taps, and "Send this plan to the enquiry" carries the whole scenario into the form textarea (W12 handoff), so the enquiry lands pre-qualified.
- Form fields: name, phone, suburb (the studio is destination-based — suburb tells the school how far the family drives), optional student ages, and the plan textarea. Front-end validation only; the confirmation panel is hidden on load and on empty submit, shown only after a valid filled submit, and says plainly that this is a demonstration and nothing was sent.
- Sticky mobile bar: call + enrol, solid ebony (no backdrop-filter — W28), thumb-height targets, present only under 768px.
- Price-less lines are designed, not missing: ensembles & theory are timetabled each term, so the honest deliverable is "ask for this term's sheet" — printed in the services grid and the ledger's fine print (W25 family).
Local SEO
What we'd implement for the real business
This demo page ships only WebPage/WebSite/BreadcrumbList JSON-LD naming itself a demonstration — fictional structured data in a real index is dishonest markup. For a real school we would ship the schema below. schema.org has no MusicSchool subtype, so the honest choice is School (the EducationalOrganization family) with the offer catalogue carrying the tuition lines:
{
"@context": "https://schema.org",
"@type": "School",
"name": "Fernbrook School of Music",
"description": "Private and group music tuition — piano, guitar, voice,
drums and violin — terms of ten weekly lessons in Applecross, Perth.",
"url": "https://fernbrookmusic.com.au",
"telephone": "+61 8 XXXX XXXX",
"address": {
"@type": "PostalAddress",
"streetAddress": "(real street address)",
"addressLocality": "Applecross",
"addressRegion": "WA",
"postalCode": "6153",
"addressCountry": "AU"
},
"geo": { "@type": "GeoCoordinates", "latitude": -32.016, "longitude": 115.836 },
"openingHoursSpecification": [
{ "@type": "OpeningHoursSpecification",
"dayOfWeek": ["Monday","Tuesday","Wednesday","Thursday","Friday"],
"opens": "12:00", "closes": "20:00" },
{ "@type": "OpeningHoursSpecification",
"dayOfWeek": "Saturday", "opens": "09:00", "closes": "13:00" }
],
"areaServed": ["Applecross","Ardross","Mount Pleasant","Booragoon",
"Como","South Perth","Willetton"],
"makesOffer": [
{ "@type": "Offer", "name": "Private lessons — 30 min, term of 10",
"price": "420", "priceCurrency": "AUD" },
{ "@type": "Offer", "name": "Group beginners — 30 min, max 4",
"price": "280", "priceCurrency": "AUD" }
],
"aggregateRating": { "@type": "AggregateRating",
"ratingValue": "4.9", "reviewCount": "87" },
"review": [
{ "@type": "Review",
"author": { "@type": "Person", "name": "(real student's parent)" },
"reviewRating": { "@type": "Rating", "ratingValue": "5" },
"reviewBody": "(their real words, with permission)" }
]
}
Plus: a Google Business Profile wired to the same NAP, term-dates content refreshed each school term (search spikes in weeks 8–10 and the January rush), and suburb-level landing content for the riverside catchment.
What changes for the real client
Their photos of the studio and recitals replace the drawn scenes; their reviews feed (Google) replaces demo copy; the enquiry form posts to their booking stack — for music schools that's My Music Staff for scheduling/billing, Square for term payments, and Xero for the books. The planner keeps working exactly as shipped: it's front-end arithmetic on their real price list.
Process
Three passes, logged honestly
Pass 1 — Craft
- Measured every spec contrast hypothesis by WCAG arithmetic: ebony 15.27:1, white-on-plum 9.27:1, plum 8.38:1, brass-ink 5.51:1 (under the spec's ≈6:1 guess but comfortably compliant). Brass held at lines/marks only.
- The discount line on the dark ledger was first set in brass-ink (5.5:1 on ivory, ~2.8:1 on ebony) — minted ledger-gold
#D9BE7A(9.32:1 on ebony) for it and the stave-note family. - Removed a dead duplicated ledger loop left from drafting; the 375 screenshot showed anchor jumps tucking section headings under the sticky masthead — added
scroll-margin-topto every section. - Drove the full worked-example set with the complete control vector pinned per scenario — all six matched the oracle to the dollar on first run.
Pass 2 — Depth
- Second-read detail: the note values are honest notation — 30 min draws a quarter note, 45 a half, 60 a whole, group a beamed quaver pair. Nobody needs to notice; musicians will.
- Refined the redraw stagger (first draw 260+230i ms, subsequent 40+70i ms) and the glyph ease to a single crafted cubic-bezier(.22,.7,.2,1) shared with the buttons.
- Gave each enrolment row its own stave pitch so a three-student family reads as a rising phrase, and the metronome's beat dot pulses in time with the pendulum (2× swing frequency, floor 0.55).
- Micro-interaction: the price badge pulses (a 450 ms scale pop, CSS transform on a group carrying no transform attribute) whenever the total actually changes — silent on first draw and under reduced motion.
- Exercised every disable/lock cell: group on voice/drums/violin (reason shown), the 30-min lock under group, all three hire refusals, and the fall-backs when an instrument switch invalidates mode or hire.
Pass 3 — Hardening
- 375px masthead first (W18): wordmark right edge vs call/enrol cluster measured with clear air; phone number collapses to icon+label under 560px so nothing wraps.
- Empty submit: panel stays hidden, ≥4
:invalidsignals; filled submit: panel appears with the "received / we'd call" copy. Rapid-fire drive (five clicks, one evaluate, no settle) left state, figures and settled flag coherent. - Reduced-motion path renders the settled frame (pendulum parked, glyphs opaque, dash-draw complete); rAF pauses on
document.hidden; delta clamped at zero and capped. - Chanel rule: cut the service-area map's second ghost coastline stroke — one drawn coast reads cleaner and nothing else on the map is doubled.
- Guide tables wrapped in
.table-scrollat build time (W21); both pages screenshot-reviewed at 375 and 1200×750.