Bellrose Home Care

Build notes · Industry demonstration

How Bellrose Home Care was made

A fictional Bayswater in-home aged care provider, built as a single conversion page. The audience is the daughter who has just been told to “get an assessment” and doesn't know what that means. The page has one job: turn a worried phone call into a rostered week with a price on it.

The concept

Every in-home care provider's website looks like a portal: stock photography of a smiling carer, a wall of accreditation logos, and a “contact us” form with no prices anywhere. Families don't want a portal. They want to know what three showers a week costs, whether it's the same person each time, and what the funding words mean.

So the page is a home's morning, not a clinic. The signature — the Week at Home — is a linen roster strip laid across a kitchen table with the visits folded onto it, and the price is a card pegged to the strip. Pick the support, the number of visits, the length and the weekend, and the week redraws with a weekly figure on it. That figure travels into the enquiry form, so the call back starts from a costed roster instead of a blank page.

The honesty constraints do real work here. My Aged Care and the Support at Home program are real Australian Government schemes, so the page names them correctly and modestly: assessment comes through My Aged Care, not from us; the quoted rates are private rates; funded clients are billed against their plan; and we never charge to help someone start an assessment. Nothing on the page implies we can grant, guarantee or expedite funding, because no provider can.

Palette

Argued from an ivory wall in morning light, a garden through the window and jarrah floorboards. It deliberately sits near — and then walks away from — the cream-and-terracotta default: the accent is a garden green, not a terracotta, and the warm jarrah is confined to a floor and table fill that never carries text. There are no column rules anywhere; the structure is the seven days.

Every ratio below was measured with WCAG arithmetic against the shipped tokens.

Ivory wall#F1F0EAPage ground. Cards are pure white #FFFFFF, hairlines #DEDCD2.
Olive black#2A2F2ABody ink — 11.95:1 on the ground, 13.65:1 on white. Also the primary CTA fill under white labels (13.65:1; hover #1D211D, 16.31:1).
Garden green#2F6B52The week's rules, visit-block outlines, chip fills and the secondary CTA under white labels (6.28:1; hover #25553F, 8.57:1). As text on the ground it measures 5.50:1.
Green ink#26583FThe pre-minted deeper twin for small green copy — 7.21:1 on the ground, 8.24:1 on white. Eyebrows, prices and reasons use this, never the mid-tone green.
Jarrah floor#C2733FFloorboards, table and chair. Scene fill only — 3.16:1 on the ground. No text of any size sits on it (olive ink measures 3.78:1 on it, white 3.61:1 — both forbidden).
Muted ink#565C56Secondary copy — 6.01:1 on the ground, 6.86:1 on white, 6.46:1 on the linen. The map labels use the body ink with a white halo, never a line token.
Weekend tint#DCE7E0Saturday and Sunday columns when the weekend is in the roster. Olive ink on it measures 10.76:1.
Linen#FAF8F2The week strip itself. Olive ink 12.85:1, green ink 7.75:1, muted 6.46:1.

Type

Fraunces at 600 carries the display — a warm humanist serif with enough wonk that the page reads as though a person wrote it. That is the argument against the clinical-portal look every provider defaults to; a geometric sans here would have made the page feel like a claims form. Nunito Sans does the work: body at 16.5px, uppercase 11–12px eyebrows at 0.15em tracking for structure, tabular numerals everywhere a dollar figure appears.

The typographic moment is the roster card: $342 a week set large in Fraunces with the support line beneath it in tracked caps. It is the biggest thing on the page after the headline, on purpose — the price is the reassurance.

The unit a week is welded to every printed total, on every surface: the badge, the ledger, the screen-reader mirror and the display copy. A weekly figure with no unit is the single most common way a care quote gets misread.

The Week at Home — how the signature works

The scene is one inline SVG on a 660 × 680 viewBox: an ivory wall with a window onto a garden, a kettle on the bench, a wall calendar, a jarrah table and a ladder-back chair. Laid across the table is the linen week strip — seven columns Monday to Sunday, a four-hour column scale with a one-hour tick rule down the left as the honest constant reference mark, and a 10pm–6am night band beneath it.

Visits are drawn as folded cards dropped onto their days. Block height is strictly proportional to the visit length against that constant four-hour scale (1 h = 34 units, 1½ h = 51, 2 h = 68, 3 h = 102), so no zoom-to-fit and no overflow unit is ever needed — all four lengths fit inside the same frame. Each card carries a small clock face whose green hand sweeps the visit's duration from twelve, plus the length in words below it. The drawn cap is seven: seven columns, at most one block per day, and the stepper maxes at seven, so every reachable value is drawable and no “+ more” honest-count text is ever needed. One folded card is one visit; on overnight, one night.

Overnight draws differently. Each block becomes a night bar spanning the 10pm–6am band under the strip with a moon mark and a greyed-out clock face, the daytime grid empties, and the disabled reason is tagged in the empty grid where the blocks would have been.

A soft morning light traverses the strip left to right on a seventeen-second cycle — a day passing. Its band is sized so at least 160 scene units of it are inside the strip at every instant and its opacity never drops below 0.55, so the signature is visibly alive at any capture moment rather than depending on loop-phase arithmetic. Reduced motion parks the light mid-week and stops the kettle's steam. The rAF delta is clamped at both ends (min(max(now - last, 0)/1000, 0.05)) so a vsync timestamp that precedes the scheduling call can never drive the traverse backwards.

Placement is pinned, not computed

Which days a roster lands on is a fixed table rather than a spread algorithm, because a real care roster is negotiated, not evenly divided. Weekdays only: 1 → Wed, 2 → Mon + Thu, 3 → Mon + Wed + Fri, and so on. With the weekend in: 1 → Sat, 2 → Sat + Sun, then weekdays fill in around them. A single Saturday and at most one Sunday a week, always.

Second read

The calendar on the wall is not decoration. Its four rows of dots are the same seven days as the strip, filled for whichever days the current roster uses — a month of the week you just built, hanging on the kitchen wall. Change the roster and the calendar changes with it.

Two redraw speeds

The first render earns the full entrance choreography (34 ms per block, 1.2 s cap). Every subsequent state change redraws snappy (13 ms, 480 ms cap) behind a firstDraw flag, so tapping a stepper reads as a response, not a replay. Every handler bumps a draw token before it swaps state, so a rapid-fire burst of clicks can never leave an in-flight animation closed over a stale day list.

The pricing model

Private rates, GST-free, per hour unless stated. Every rate and every loading is an even whole dollar, which is what makes the 1½-hour multiplier integer-safe: no product anywhere in the model is a fraction of a dollar, so there is no rounding rule to invent. Totals display to the whole dollar and a non-integer intermediate would be a bug, not a display decision.

SupportRateNotes
Domestic help$68/hrCleaning, laundry, the shopping.
Social support & transport$72/hrTwo-hour minimum — the 1 h and 1½ h chips are disabled with a reason.
Personal care$76/hrShowering, dressing, medication prompts.
Respite for a family carer$76/hrSame rate as personal care; sits in the services grid, not the estimator.
Nursing$124/hrWound care, medication management.
Overnight sleepover$420/nightFlat. 10pm–6am, a carer in the house.
Saturday loading+$18/hr+$60 on a sleepover night.
Sunday loading+$34/hr+$110 on a sleepover night.
Weekly minimum$150Billed minimum, not a forced control.

The weekly total for the hourly types is length × [ rate × weekdays + (rate + 18) × Saturdays + (rate + 34) × Sundays ]; for sleepovers it is 420 × weeknights + 480 × Saturday + 530 × Sunday.

The $150 weekly minimum

If the computed roster comes to less than $150, the week is billed at $150. It is a billed minimum, not a control that moves: the per-hour lines still multiply the actual visits and hours, and the shortfall is named out loud in a note rather than hidden — your roster comes to $68 a week — billed at our $150 weekly minimum. That makes $150 the cheapest output the estimator can produce, which is exactly the “$150 a week minimum” floor printed in the services grid.

Worth stating because it is a designed edge: no reachable combination of support, visits, length and weekend lands on exactly $150. Personal care once for two hours is $152 and does not lift; social support once for two hours is $144 and does. The boundary is therefore always a strict lift, and the QA harness enumerates all fourteen below-floor states and drives every one of them.

Two forces, and what happens on the way back

  • The weekend cap. There are five weekdays, so with the weekend off the visits stepper ceiling is five. Turning the weekend off while the count is six or seven forces it to five and remembers the explicit value; turning the weekend back on restores it. An explicit stepper change while forced clears the memory for good.
  • The transport minimum. Choosing social support & transport while the length is 1 h or 1½ h forces the length to 2 h and remembers what it was; choosing any other hourly type restores it. An explicit length chip while forced clears the memory.
  • Partial unwind. Overnight disables the length chip entirely but the memory survives it. Coming back to social support holds the forced two hours — only an hourly type other than social support restores the remembered one. The rule is: hold the forced value while it is still required, and restore only when the remembered value becomes valid again.

Worked examples, all measured from the running page

ScenarioArithmeticTotal
Personal care, 3 visits, 1½ h, weekdays (rest state)76 × 1.5 = 114 × 3$342 a week
Domestic help, 1 visit, 1 h, weekdays68 → lifted to the minimum$150 a week
Domestic help, 2 visits, 2 h, weekdays68 × 2 × 2$272 a week
Nursing, 2 visits, 1 h, weekend in(124 + 18) + (124 + 34)$300 a week
Personal care, 7 visits, 3 h, weekend in3 × [76 × 5 + 94 + 110]$1,752 a week
Social support, 2 visits, 2 h, weekdays72 × 2 × 2$288 a week
Social support, 1 visit, 2 h, weekdays144 → lifted to the minimum$150 a week
Overnight sleepover, 3 nights, weekdays420 × 3$1,260 a week
Overnight sleepover, 2 nights, weekend in480 + 530$1,010 a week
Nursing, 5 visits, 1½ h, weekdays124 × 1.5 = 186 × 5$930 a week
Weekend-cap force: 7 → weekend off → back on876 → 570 → 876$876 a week
Transport force: domestic 3 × 1 h → social → back204 → 432 → 204$204 a week
Partial unwind: … → overnight → social → personal432 → 1,260 → 432 → 228$228 a week
Memory cleared: social forced, then 3 h chosen, then domestic68 × 3 × 3$612 a week

What every surface has to agree on

Four declarations and notes live on three surfaces at once — the roster card, the itemised ledger and the plain-text mirror a screen reader gets. Two of them are always present (the funding path and the no-lock-in promise); the minimum-lift note, the weekend loading note, the overnight price-neutral declaration and the transport minimum appear only in their own states, and the harness asserts their absence everywhere else, not just their presence. On the roster card the full templates run at 641px and up, including the awkward 900–1100px tablet band; below 641px the card docks beneath the artwork and swaps to the pinned short forms (funded? we bill the plan, + weekend loading, 2 h minimum on transport). The ledger and the mirror always carry the full string.

Conversion decisions

  • The phone number is a button in the header, not a line of text. Aged care enquiries skew to phone calls from people who are already stressed. It repeats in the hero, in the enquiry column, in the footer, and in the mobile sticky bar — four tap targets on one number, all reserved (08) 9000 0380.
  • Above the fold at 375px: the business name, the value proposition naming the trade and the area, the tagline that is the differentiator (“the same two or three faces, every week”), both CTAs and three trust signals — before a single scroll. The signature sits immediately below, because the price is the second question, not the first.
  • The estimator hands off to the form. “Send this roster with my details” writes the whole scenario — support, visits, length, weekend, days and the weekly figure — into the enquiry textarea and moves focus to the name field. A care manager starts the call back from a costed roster.
  • Form fields are the four a care manager actually needs to ring back usefully: name, phone, suburb, and who the care is for. “Who are we helping” is there because the person filling the form is almost never the person receiving the care, and getting that wrong on the first call is a bad start.
  • The confirmation is local and honest. It is hidden on load, stays hidden on an empty submit, and appears only after a valid filled submit — with the roster and the figure read back, and a plain line saying nothing was sent anywhere. No fake network round-trip.
  • Prices are on the page. Providers hide rates because the funded conversation is complicated. The result is a phone call that starts with suspicion. Publishing the private rates and stating the funded path in the same breath is the whole conversion argument here.

Iteration log

Pass 1 — Craft

Spacing, contrast, and the things a state assert cannot see

  • The masthead broke at 375px. The phone button and the “Get a call back” button both wrapped to three lines and the row was 100px tall. Fixed by hiding the secondary CTA below 559px (the sticky bar already carries it), adding white-space:nowrap to every button, and tightening the wordmark and button type below 420px. The wordmark's right edge is now asserted against the call button's left edge at 375.
  • The above-the-fold trust signal fell off the 375px screen by 34px — measured, not eyeballed. The lede was five lines of copy doing work the services grid already does; cut to three, hero padding trimmed and the trust row tightened, which brought the last trust item to roughly 628px.
  • The scene container collapsed to 2 × 2px between 641 and 1099px. margin-left:auto on a grid item makes it shrink-to-fit, and the item's content was an SVG sized at width:100% — a circular dependency that resolved to zero. Every mid-width badge assert was failing against a scene that was not being drawn at all. Adding an explicit width:100% alongside the max-width fixed it.
  • Two stray hairlines on the linen at x = 228 and 424 read as misaligned column rules, because the real columns fall at 168.6, 245.1, 321.7… They were meant as fold marks. Removed — the strip has enough structure.
  • Contrast measured from the shipped tokens rather than assumed. Every figure in the palette section above is the measured one; the mid-tone green failed nothing but sits at 5.50:1 as text, so all small green copy uses the pre-minted #26583F at 7.21:1 instead.
Pass 2 — Depth

Enriching the signature

  • The wall calendar. The upper-left wall was empty and the scene had no second read. The calendar now mirrors the roster: four rows of seven dots, filled for whichever days the current week uses. It is the same information told twice, once as a plan and once as a month, and it moves whenever the strip does.
  • The night band became a real band. It was previously an unmarked white strip at the bottom of the linen that read as dead space, with the “4 h” tick label crowding its edge. It now carries a permanent cool tint with a hairline top rule, moved down six units to clear the tick, and deepens further when sleepovers are the selected support.
  • The chair was redrawn. A top rail with two stub legs read as a radiator. It is now a ladder-back chair — top rail, two stiles, three slats — pulled up to the near edge of the table, and it sits entirely below the strip so it can never crowd the roster.
  • The morning light stopped disappearing at the ends of its traverse. The band originally ran from fully off-strip to fully off-strip, so at either end of the seventeen-second cycle almost none of it was visible — a thumbnail captured at the wrong instant would have shown a dead signature. The range is now clamped so at least 160 scene units of the band are always inside the strip, on top of the existing 0.55 opacity floor.
  • The roster card's folded corner was a lopsided notch built from a gradient and two borders. Rebuilt as a proper fold: a ground-coloured triangle with a hairline crease, plus the green peg at the top-left that ties it to the strip.
Pass 3 — Hardening

375px, reduced motion, and proving the numbers

  • A 673-assert harness drives the page against an independently reimplemented pricing oracle: every one of the 180 reachable matrix cells (five supports × both weekend states × every visit count × every legal length), all ten pinned worked examples, both forces, the partial unwind, the memory invalidation, all fourteen below-minimum states, and two rapid-fire bursts that fire several controls inside one evaluate with no settle between them. It runs green at 673/673.
  • The tap-target sweep caught a 42px masthead button at 375. Raised to 44.
  • The service-area map was unreadable on a phone. Its labels are set in scene units, so at 375px they rendered at about 6.7px. The map card now goes full-bleed below 641px and the label sizes step up below 760px — and because bigger labels collide, four of them (Maylands, Bassendean, Guildford, Midland) were re-anchored and the collision sweep now covers the map as well as the roster scene.
  • A section label was silently not a section label. “Your week, costed” sat outside the fieldset the uppercase rule was scoped to, so it rendered in sentence case beside three tracked-caps siblings. One selector.
  • Label-collision sweep at native resolution across 375, 1000 and 1200, in the busiest states (seven three-hour blocks with the weekend in, and seven sleepover nights): every pair of scene text boxes is checked for intersection, and the roster card is checked against every label in the scene. Asserts prove state; only pixels prove composition.
  • Two capture artifacts were ruled out rather than “fixed”. Element-scoped screenshots caught the block entrance mid-transition and made a healthy scene look half-drawn; and a downscaled full-page capture hid the sleepover night bars entirely. Both were re-checked at native resolution with computed page-relative clips before anything was touched, and the DOM's computed opacity confirmed the scene was correct all along.
  • Reduced motion parks the light mid-week, stops the kettle, and marks the roster settled on the first frame — the figures are identical, only the movement stops.
  • The phone pattern attribute is v-flag safe — the parentheses and the hyphen are escaped, because modern Chrome compiles pattern as a v-flag regex and an unescaped literal throws at parse time, leaving a form that looks fine while its validation is dead. The harness calls checkValidity() on a good and a bad value and asserts zero console errors.
  • Ornament removed (the Chanel rule): the two decorative fold hairlines on the linen, which were noise pretending to be structure.

Local SEO we'd implement for the real business

The live page emits only WebPage, WebSite and BreadcrumbList schema, and says in it that this is a demonstration. Marking up a fictional provider as a real one — with fictional reviews and a fictional rating — would put dishonest data into a real search index, so we don't. For an actual client we would ship the block below.

The type is MedicalBusiness. Schema.org has no in-home-aged-care subtype, and NursingHome would be wrong — a nursing home is a residential facility and this is care in someone's own house. So the organization type stays at MedicalBusiness and the specialisation is carried by additionalType, knowsAbout and a real hasOfferCatalog. Reviews are marked up only once they are genuine, collected reviews from real clients — never demo copy.

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "MedicalBusiness",
  "@id": "https://bellrosehomecare.com.au/#business",
  "name": "Bellrose Home Care",
  "additionalType": "https://www.wikidata.org/wiki/Q1339733",
  "description": "In-home aged care across Perth's eastern and northern suburbs — domestic help, personal care, nursing, social support and transport, and overnight sleepovers.",
  "url": "https://bellrosehomecare.com.au/",
  "telephone": "+61 8 9000 0380",
  "priceRange": "$$",
  "image": "https://bellrosehomecare.com.au/img/bellrose-team.jpg",
  "logo": "https://bellrosehomecare.com.au/img/logo.png",
  "address": {
    "@type": "PostalAddress",
    "streetAddress": "12 King William Street",
    "addressLocality": "Bayswater",
    "addressRegion": "WA",
    "postalCode": "6053",
    "addressCountry": "AU"
  },
  "geo": { "@type": "GeoCoordinates", "latitude": -31.9192, "longitude": 115.9186 },
  "areaServed": [
    { "@type": "City", "name": "Bayswater" },
    { "@type": "City", "name": "Morley" },
    { "@type": "City", "name": "Bassendean" },
    { "@type": "City", "name": "Maylands" },
    { "@type": "City", "name": "Mount Lawley" },
    { "@type": "City", "name": "Inglewood" },
    { "@type": "City", "name": "Dianella" },
    { "@type": "City", "name": "Guildford" },
    { "@type": "City", "name": "Midland" },
    { "@type": "City", "name": "Ellenbrook" }
  ],
  "knowsAbout": [
    "In-home aged care",
    "Support at Home program",
    "My Aged Care assessment referrals",
    "Personal care and showering assistance",
    "Community nursing and wound care",
    "Overnight respite for family carers"
  ],
  "openingHoursSpecification": [
    { "@type": "OpeningHoursSpecification",
      "dayOfWeek": ["Monday","Tuesday","Wednesday","Thursday","Friday"],
      "opens": "08:00", "closes": "17:00" }
  ],
  "hasOfferCatalog": {
    "@type": "OfferCatalog",
    "name": "In-home care services",
    "itemListElement": [
      { "@type": "Offer",
        "itemOffered": { "@type": "Service", "name": "Personal care" },
        "priceSpecification": {
          "@type": "UnitPriceSpecification",
          "price": "76.00", "priceCurrency": "AUD",
          "unitCode": "HUR", "referenceQuantity": { "@type": "QuantitativeValue", "value": 1, "unitCode": "HUR" }
        } },
      { "@type": "Offer",
        "itemOffered": { "@type": "Service", "name": "Overnight sleepover" },
        "priceSpecification": {
          "@type": "UnitPriceSpecification",
          "price": "420.00", "priceCurrency": "AUD", "unitCode": "DAY"
        } }
    ]
  },
  "aggregateRating": {
    "@type": "AggregateRating",
    "ratingValue": "4.9", "reviewCount": "38", "bestRating": "5"
  },
  "review": [
    { "@type": "Review",
      "author": { "@type": "Person", "name": "Kath R." },
      "datePublished": "2026-05-14",
      "reviewRating": { "@type": "Rating", "ratingValue": "5", "bestRating": "5" },
      "reviewBody": "Three mornings a week and I stopped driving over before work. Same two carers for fourteen months." }
  ]
}
</script>

Alongside it: a Google Business Profile in the aged-care-service category with the service areas above, suburb landing pages that actually differ (Morley, Bassendean, Midland, Ellenbrook — each with its own travel realities and its own carers), and an FAQ block matching the questions on this page word for word, because that is what gets pulled into an answer panel.

What would change for a real client

  • Their photography replaces the drawn scene — carers, not stock. The Week at Home stays, because no photograph answers “what does three showers a week cost”.
  • Care management: the enquiry writes straight into AlayaCare or Lumary as a prospective client record with the roster attached, so the care manager's call back opens on a half-built care plan.
  • Rostering: Deputy holds the shifts and the availability, so “can you start Thursday” is answered from real coverage in the suburb rather than a guess.
  • Invoicing: Xero for private clients. Funded clients are claimed through the My Aged Care provider portal against their Support at Home plan — two billing paths, one roster, and the website has to be honest about which one the visitor is on.
  • Shift confirmations: Twilio SMS the night before with the carer's name, because “who is coming tomorrow” is the single most common phone call an office takes.
  • Reviews: a live Google Business Profile feed replaces the demo testimonials, and only then does the review schema go live.