Generative Assets · Industry demo 227

How the True North Solar demo was made

A conversion-focused landing page for a fictional Perth solar & battery installer whose whole pitch is arithmetic you can check. One page, one job: book a free roof assessment — and every number on the way there computed honestly. View the site · Index

Concept

True North Solar sells to the Perth homeowner watching a $700 quarterly bill, half-decided on a battery since the rebates, and wary of door-knockers. The design direction is the day curve — morning-sky clarity, engineering not evangelism, a page that shows its maths. Everything on the page argues one honest thesis: in Perth, with DEBS export credits at 2–10c against 31c grid power, self-consumption is where solar pays — so the system should be sized from your bill and your household pattern, not from a sales script.

Palette

#F4F7F9Horizon white — the ground; a 9am Perth sky, not cream.
#16283ESilicon navy — structure and primary ink; the blue-black of the cell itself.
#DE9B26Panel gold — LINE/MARK only: generation curve, panel fills, CTA ground. Never small text on white.
#3A8E5FExport green — delivered-to-grid, form focus, savings marks.
#A96F0EDerived gold-ink for gold-toned type on white (eyebrows, chart labels) — the raw accent fails 4.5:1.
#2C7A4EDerived green-ink for green type (prices, links) — #3A8E5F alone reads ~4.3:1 at small sizes.
#54677DMuted slate for secondary copy; ~5.5:1 on the ground.
#D8E1E8Hairline — borders and gridlines, quiet against both inks.

The ink/line split (LEARNINGS W2/W5) is applied twice: panel gold and export green each get a darker derived ink for any run of text, while the bright originals are reserved for curves, fills, dots and buttons with navy type.

Type

Space Grotesk (500/600/700) for display — technical warmth, the confidence of a spec sheet rather than a brochure; its slightly squared counters suit a page full of kilowatt figures. Public Sans for body and UI — a workhorse that stays legible at 16px on a phone in sunlight. Every kWh and dollar is set with font-variant-numeric: tabular-nums so readouts don't jitter as the slider moves — the typographic bravery here is restraint plus the oversized bill figure in the control rail.

The signature: Day-Curve Saver

A quarterly-bill slider ($250–$1,500), three household-pattern chips and a battery toggle drive a two-part procedural SVG scene. Above, a north-facing roof fills with 440 W panels — 15 at 6.6 kW up to 30 at 13.2 kW — each panel a bilinear-interpolated quad on the roof plane with a white catch-light strip, drawn in with a 40 ms stagger. Below, a day-curve chart plots hourly generation (a cosine bell normalised to the system's daily yield) against the household's 24-bucket usage profile, and shades the honest bands: self-consumed (gold), exported (green), still-from-grid (navy), and — with the battery on — a hatched from-battery band that visibly eats the evening deficit. The curves are cosine-interpolated at six samples per hour purely for drawing; all figures come from the 24 hourly buckets.

The arithmetic is deliberately checkable: usage is inferred from the bill at ~31c/kWh less a ~$100/quarter supply charge; yield is 1,520 kWh per kW per year (a 6.6 kW array ≈ 10,032 kWh/yr, inside the quoted 9,500–10,500 band); DEBS credits are 10c in the 3–9pm window and 2c otherwise, applied hour-by-hour to whatever is actually exported; the battery is a 13.5 kWh store simulated across the day from 6am with 90% round-trip efficiency. Annual saving = self-consumption × 31c + battery discharge × 31c + hourly export credits, × 365. Payback is cost ÷ saving, presented as a ±15% range for orientation and shading. System cost interpolates from the services grid's own anchors — $3,900 at 6.6 kW, landing exactly on $7,900 at the first 10 kW+ configuration — so the saver and the price list can never argue with each other. At rest the saver sits mid-demonstration: $650 bill, out-weekdays, 6.6 kW, curve drawn, bands shaded, ≈$900/yr saving and a 3.8–5.1 yr payback showing. Live state is exposed at window.__solar.

Second-read details: the chart quietly marks the DEBS 3–9pm peak window; pushing the slider to the top earns a "roof full — 30 panels is our single-phase cap" note; and with the battery on, an honesty line computes the battery's marginal saving (≈$1,290/yr at the rest configuration) and admits it pays back slower than the panels.

Conversion decisions

Local SEO we'd implement for the real business

This demo ships only WebPage schema naming it a demonstration — fictional structured data in a real index would be dishonest markup. For a real installer we'd ship LocalBusiness + Review schema fed by their actual details and review stream:

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "LocalBusiness",
  "@id": "https://truenorthsolar.example/#business",
  "name": "True North Solar",
  "description": "Solar and battery installation across Perth's northern suburbs.",
  "telephone": "+61-8-XXXX-XXXX",
  "address": { "@type": "PostalAddress", "addressLocality": "Balcatta",
               "addressRegion": "WA", "addressCountry": "AU" },
  "geo": { "@type": "GeoCoordinates", "latitude": -31.86, "longitude": 115.82 },
  "areaServed": ["Balcatta","Stirling","Karrinyup","Joondalup","Wanneroo"],
  "openingHoursSpecification": [{ "@type": "OpeningHoursSpecification",
    "dayOfWeek": ["Monday","Tuesday","Wednesday","Thursday","Friday"],
    "opens": "07:00", "closes": "16:00" }],
  "aggregateRating": { "@type": "AggregateRating",
    "ratingValue": "4.9", "reviewCount": "187" },
  "review": [{ "@type": "Review",
    "author": { "@type": "Person", "name": "…from the live review feed…" },
    "reviewRating": { "@type": "Rating", "ratingValue": "5" },
    "reviewBody": "…" }]
}
</script>

Plus: Google Business Profile with service-area suburbs, suburb-level landing sections fed from the one template, and review markup wired to the live feed (never hand-typed).

What would change for the real client

Techniques

SVGVanilla JSCSS choreography Everything is procedural SVG and CSS — no libraries, no rasters. The roof panels are bilinear quads on a perspective plane; the chart is built as path strings from the shared 24-bucket model; the service-area map is hand-drawn coastline, river and freeway with a dashed service ring. Entrance choreography is IntersectionObserver + staggered CSS transitions gated on document.fonts.ready with a timeout fallback and a no-JS guard; prefers-reduced-motion renders everything settled and complete. SVG visibility (battery, legend entries) toggles via the hidden attribute — never the .hidden IDL property, which silently no-ops on SVG elements (LEARNINGS W13) — backed by the [hidden]{display:none !important} guard.

Iteration log

Pass 1 — Craft

Drove every control headlessly against an independent reimplementation of the arithmetic: rest state ($650 / out weekdays / no battery) asserted at 6.6 kW · 15 panels, $3,900 (equal to the services-grid floor), ≈$900/yr displayed saving and 3.8–5.1 yr payback — all matching the oracle to the dollar. Screenshot review then caught what the 45 green asserts couldn't: the header CTA wrapped to two lines at 375px and pushed scrollWidth to 389 (fixed with a short-label swap and a ≤430px header tighten), the booking bullet points split around an <em> because the flex li made every text node a flex item (wrapped in a span), the Dianella map label crowded Balcatta's HQ caption (nudged), and the Sapience band's buttons wrapped at 1440 (flex-basis fix). Also re-tuned the Dianella review's figures ($684 → $326) to match what the saver itself computes for that bill.

Pass 2 — Depth

Drove the battery path and the extremes: $940 / home / battery asserted at 10.12 kW · 23 panels with solar cost exactly $7,900 (the "10 kW+ from" anchor), $17,800 all-in and $3,480/yr — again equal to the hand-computed value; $1,500 / evening-heavy asserted the 30-panel cap and its "roof full" note. Added the second-read layer: the DEBS peak-window strip on the chart, the battery marginal-saving honesty line, and a value-tick micro-interaction on the readouts (a 0.4s settle animation that only fires when a figure actually changes, suppressed under reduced motion). Bumped all chart label sizes ~15% after the 375px screenshot showed them fading at phone scale.

Pass 3 — Hardening

Full-suite re-run: 47/47 — zero console errors on desktop and mobile, one h1 per page, 375px scrollWidth exactly 375 with wordmark/CTA/h1 inside the viewport box, above-fold h1 + ≥40px CTA + trust chips in the first mobile viewport, sticky call bar with 56px tap targets in the bottom 140px, empty submit raising three aria-invalid fields, filled submit rendering the local confirmation in the required lexicon, drive-then-diff pixel change on the saver, and the reduced-motion pass rendering all reveals and all 15 panels settled at full opacity. Honesty sweeps: every (08) number on the page uses the reserved 9000 exchange, JSON-LD is WebPage-only, colophon and demo band present. Chanel cut: removed the map's second dashed radius ring — ring + freeway + river was one ornament too many. Final screenshot read of every section, then this log was checked against the shipped constants.

← Back to True North Solar · Generative Assets index