Brightmark Tutoring

Index / tutoring / guide

Site 319 · Wave 32 · Industry Demos XXI

Making Brightmark Tutoring

A fictional Willetton tutoring centre for parents in Perth’s southern school belt — sliding year 9s, ATAR students six months out, and year 3s who won’t read aloud. The page has one job: an enrolment enquiry with the program, sessions-per-week and 10-week term price already chosen before anyone picks up a phone.

The concept

The art direction is the exercise book, taken seriously: ruled-paper ground, faint blue rule lines, a red margin line used as literal page furniture, graphite ink and marking-red ticks. No children’s clip-art, no primary-colour confetti — the page reads the way careful handwriting looks, because that’s what the brand sells.

The signature is the Term Planner: a 10-week term drawn as an open exercise-book spread in SVG. Each scheduled session lands as a marking-red tick on its week line — one session a week fills one column, three fill three. The tick currently being “marked” (week 4, mid-term) draws itself with a looping pen-stroke animation, and the term price sits as a badge on the spread itself: rate × sessions/week × 10, minus the $60 sibling discount when it’s on. Week 5’s line carries progress report home in small caps — the second-read detail, and a real promise from the trust strip.

Palette — argued from the ruled page and the marking pen

Ruled paper#F7F5EF — page ground
Graphite ink#26262B — body text, 13.4:1
Marking red#C2402F — CTAs, ticks, margin lines
Rule blue#5B7FB4 — ruled lines & linework only
Link ink#3E6390 — links & small blue text, 5.7:1
Blackboard#232327 — footer & demo band

The ink/line split is deliberate: rule blue #5B7FB4 measures ~3.8:1 on the paper ground, so it draws lines — ruled lines, the river, suburb linework — and never sets small text. Text-sized blue uses the darker link ink #3E6390 (5.7:1); marking red carries buttons and marks at 4.8–5.2:1 against its grounds.

Type

Display — Fraunces 600/700

Marks follow method.

Body — Lexend 400–700

A term to change habits, two to change marks.

Fraunces has bookish warmth with a spine — it reads like a well-set textbook rather than a toy. Lexend is the readability-first body choice: it was designed around reading-proficiency research (variable width and spacing tuned to reduce visual crowding for struggling readers), which is exactly the right argument on a tutoring page where some of the audience are parents of kids who find reading hard. The typographic bravery moment is the term price set huge in Fraunces on the spread with a hand-drawn red tick sweeping through it.

The Term Planner — implementation

The spread is a single SVG: two page rects, a ruled-line <pattern> aligned so every week’s baseline lands exactly on a rule line, red margin lines, week numbers 1–5 down the left margin and 6–10 down the right. Ticks are generated into one <g> from the planner state: weeks before the current week render as solid marking-red ticks, future weeks render as graphite “pencilled-in” ticks at reduced opacity, and the current week’s newest tick is the pen: a dash-draw animation (dasharray = path length, offset animated in a rAF loop with the delta clamped at zero and capped) that hides its stroke until the drawn length is > 0, layered over a ghost tick at opacity 0.55 so the mark never fully disappears between loops.

Chip, stepper and sibling handlers bump an invalidation token before swapping state, then rebuild the tick group synchronously — so a rapid-fire burst of clicks can never leave an animation closed over stale geometry. The first render gets entrance choreography (marked ticks draw in with a 90 ms stagger); every later state change redraws instantly and only re-sweeps the badge tick, so the planner feels snappy rather than theatrical. Price arithmetic is one line — rate × sessions/week × 10 − (sibling ? 60 : 0) — and the same state object feeds the badge, the plain-text plan line under the spread (the accessibility mirror), the enquiry-form summary and the window.__tutoring QA probe.

Honest pricing — the anchors

ProgramRateSessions/weekTerm (10 weeks)
Primary & lower-secondary group (max 4)$60 a sessionlocked at 1 — the timetable sets it$600 · $540 with sibling
ATAR small group (max 3)$75 a sessionlocked at 1$750
Private one-on-one$95 a session1–3$950–$2,850 · 2/week = $1,900
ATAR private specialist$110 a session1–3$1,100–$3,300 (rest state: $1,100)
Learning support & dyslexiano number by design — planned (and priced) after an assessment

The sibling discount is a flat −$60 off the term, live for every program, with the fairness rule stated on-card: one discount per family, applied to the cheaper enrolment. The sessions-per-week stepper is genuinely locked for both group programs — with the reason on the card (“group classes run once a week — the timetable sets it”) rather than a silently dead control. The services grid floor (“group tutoring from $60 a session”) equals the planner’s cheapest per-session rate, so the page never argues with itself. All figures were verified against an independently reimplemented oracle across the full program × sessions × sibling matrix.

Conversion decisions

  • Planner as the hero visual. The signature shares the first screen with the value proposition — at 1200×750 the spread and its $1,100 price badge are both inside the fold, because the thumbnail is the commercial argument.
  • Price before phone. Tutoring enquiries stall on “how much is this going to be a term?” — so the term figure, not the hourly rate, is the number set huge. The badge also translates it back (“that’s $110 a session”).
  • Planner → form handoff. The enquiry form carries a live summary of the planned term and its own program chips bound to the same state — the enquiry arrives pre-qualified with program, schedule and price.
  • The free placement session is the CTA, not “enrol now” — it’s the low-stakes ask a hesitant parent will actually click, and it’s how the real business closes.
  • Form fields: name, phone, suburb, year-level select, program, goal textarea. No email field — this trade calls back; the confirmation panel promises a callback within one school day and appears only after a valid local submit (no fake network round-trip).
  • Honesty furniture: reserved (08) 9000 0319 number, placeholder ABN/WWC strings, the fictional-business colophon, and a price-less learning-support card that explains itself in the FAQ.

Local SEO we’d implement for the real business

The demo page itself carries only WebPage/CreativeWork structured data naming it a demonstration — fictional business schema in a real index would be dishonest markup. For a real tutoring centre we’d ship EducationalOrganization (the correct organization type — schema.org has no tutoring-specific LocalBusiness subtype), with genuine reviews fed from Google:

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "EducationalOrganization",
  "name": "Brightmark Tutoring",
  "description": "Maths and English tutoring in Willetton — groups of four,
    ATAR specialists, and private one-on-one, in-centre and online.",
  "url": "https://brightmarktutoring.com.au",
  "telephone": "+61890000319",
  "address": {
    "@type": "PostalAddress",
    "addressLocality": "Willetton",
    "addressRegion": "WA",
    "postalCode": "6155",
    "addressCountry": "AU"
  },
  "areaServed": ["Willetton","Bull Creek","Leeming","Riverton",
    "Rossmoyne","Shelley","Canning Vale","Murdoch"],
  "openingHoursSpecification": [
    { "@type": "OpeningHoursSpecification",
      "dayOfWeek": ["Monday","Tuesday","Wednesday","Thursday","Friday"],
      "opens": "15:30", "closes": "19:30" },
    { "@type": "OpeningHoursSpecification",
      "dayOfWeek": "Saturday", "opens": "09:00", "closes": "12:30" }
  ],
  "makesOffer": [
    { "@type": "Offer", "name": "Primary & lower-secondary group tutoring",
      "price": "60", "priceCurrency": "AUD" },
    { "@type": "Offer", "name": "ATAR private specialist tutoring",
      "price": "110", "priceCurrency": "AUD" }
  ],
  "aggregateRating": { "@type": "AggregateRating",
    "ratingValue": "4.9", "reviewCount": "87" },
  "review": [
    { "@type": "Review",
      "author": { "@type": "Person", "name": "Rachael T." },
      "reviewRating": { "@type": "Rating", "ratingValue": "5" },
      "reviewBody": "Two terms in the Tuesday group and he put the last
        maths test on the fridge himself." }
  ]
}
</script>

Panel note: EducationalOrganization is the pinned top-level type — an actual organization type, not a specialty token. aggregateRating/review ship only with the real client’s genuine, verifiable reviews; the demo page’s reviews are declared demo copy and carry no markup.

What changes for the real client

  • Enrolment & scheduling: the form posts into their tutor-management system — TutorCruncher, Teachworks or TutorBird all take a webhook lead and hold the timetable that the “locked at 1/week” rule reads from.
  • Reviews: the demo testimonials swap for a live Google Reviews feed (Places API), which also feeds the would-ship aggregateRating.
  • Payments: term invoices via Stripe or direct debit, reconciled in Xero — the planner’s term figure becomes the actual invoice line.
  • Their numbers: real phone, real ABN, real WWC registrations, their photos of the centre if they want them — though this page is proof the design carries without stock photography.

Iteration log

Pass 1 — Craft

  • Drove every control headlessly (all four chips, stepper 1→3, sibling toggle on every program, locked-stepper reason state, empty and filled submits) against an independently reimplemented pricing oracle — all 16 reachable program×sessions×sibling cells matched, and the rest state measured $1,100 from the running page.
  • Screenshot review at 1200×750 caught the price badge overlapping the right page’s header text (“no surprises at week ten”) — moved the badge down into clear ruled space.
  • The week-5 progress report home caption ran into the gutter shadow at the left page’s edge — moved it under the week-5 rule line, aligned with the tick column, and dropped its underline.
  • The hero trust line wrapped with a stranded “·” opening a line — each claim is now a no-break segment so separators can’t lead a line.

Pass 2 — Depth

  • Layered the ghost tick under the pen stroke so the marked tick never drops below 0.55 opacity between loops — the thumbnail can land at any instant and the signature is alive.
  • Future weeks render as graphite “pencilled-in” ticks (opacity .34) against the marked red weeks — the spread reads as a term in progress, week 4 under the pen, not a static chart.
  • Driving private × 3/week × sibling showed $2,790 overflowing the badge card and the sibling note wrapping to three lines — tightened the price size with no-wrap and shortened the note to “−$60 sibling discount applied”.
  • Rapid-fire drive (nine clicks, then six more, each burst in a single evaluate with no settle between) confirmed the handler-first token invalidation holds — zero console errors, settled state matched the oracle. Second-read detail: week 5’s progress report home matches the trust strip’s promise, so the artwork and the copy vouch for each other.

Pass 3 — Hardening

  • 375px sweep: masthead measured explicitly — wordmark right edge 260px vs call-button left edge 274px — and scrollWidth 375 on both pages. The sticky bar’s call label wrapped to two lines, fixed with no-wrap at 14px; tap targets measured 62px.
  • The overlaid badge crowded the shrunken spread at phone widths, covering the right page’s header — at ≤700px it now flows as a full-width card under the spread, and its residual tilt came off with it (the removed ornament).
  • Reduced-motion renders the week-4 tick fully drawn and static and skips the entrance stagger and pen loop; the rAF loop pauses on document.hidden and clamps its delta to [0, 50 ms] (a negative first-frame delta is real Chrome behaviour).
  • Phone pattern written v-flag-safe (every literal special escaped) — verified by the zero-console-errors gate including at checkValidity time; empty submit raises aria-invalid on all four required fields with the confirmation panel still hidden, and only a valid filled submit reveals it.