window.initInteractions = function () { var reduce = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches; // Back-to-top button (attach once globally) if (!window.__bttInit) { window.__bttInit = true; var onBttScroll = function () { var b = document.getElementById('backtotop'); if (b) { if (window.scrollY > 600) b.classList.add('show'); else b.classList.remove('show'); } }; window.addEventListener('scroll', onBttScroll, { passive: true }); onBttScroll(); } // Local stories carousel var car = document.querySelector('[data-stories-carousel]'); if (car && !car.__carouselInit) { car.__carouselInit = true; var track = car.querySelector('.stories-track'); var cards2 = track.children; var dotsWrap = car.querySelector('.carousel-dots'); var cIdx = 0; var paint = function () { Array.prototype.forEach.call(dotsWrap.children, function (d, i) { d.className = i === cIdx ? 'active' : ''; }); }; var goTo = function (i) { cIdx = Math.max(0, Math.min(cards2.length - 1, i)); track.scrollTo({ left: cards2[cIdx].offsetLeft - track.offsetLeft, behavior: reduce ? 'auto' : 'smooth' }); paint(); }; for (var ci = 0; ci < cards2.length; ci++) { (function (i) { var b = document.createElement('button'); b.type = 'button'; b.setAttribute('aria-label', 'Story ' + (i + 1)); b.addEventListener('click', function () { goTo(i); }); dotsWrap.appendChild(b); })(ci); } car.querySelector('.carousel-prev').addEventListener('click', function () { goTo(cIdx - 1); }); car.querySelector('.carousel-next').addEventListener('click', function () { goTo(cIdx + 1); }); track.addEventListener('scroll', function () { var w = cards2[0].offsetWidth + 24; var ni = Math.round(track.scrollLeft / w); ni = Math.max(0, Math.min(cards2.length - 1, ni)); if (ni !== cIdx) { cIdx = ni; paint(); } }, { passive: true }); paint(); } // 1. Scroll-reveal on major blocks only (a few per view) if (!reduce && 'IntersectionObserver' in window) { var blocks = document.querySelectorAll('.jumpnav, .section-head, .two-col, .container > .prose, .story-card, .download-card, .chart-wrap'); var io = new IntersectionObserver(function (entries) { entries.forEach(function (e) { if (e.isIntersecting) { e.target.classList.add('in'); io.unobserve(e.target); } }); }, { threshold: 0.12, rootMargin: '0px 0px -8% 0px' }); Array.prototype.forEach.call(blocks, function (el) { el.classList.add('reveal'); io.observe(el); }); } // 2. Active jump-nav (scroll-spy) var cards = Array.prototype.slice.call(document.querySelectorAll('.jump-card')); var byId = {}; var sections = []; cards.forEach(function (c) { var id = (c.getAttribute('href') || '').slice(1); var target = document.getElementById(id); var sec = target && target.closest('.section'); if (sec) { sec.setAttribute('data-spy', id); byId[id] = c; sections.push(sec); } }); if ('IntersectionObserver' in window && sections.length) { var current = null; var spy = new IntersectionObserver(function (entries) { entries.forEach(function (e) { if (e.isIntersecting) { var id = e.target.getAttribute('data-spy'); if (current !== id) { current = id; cards.forEach(function (c) { c.classList.remove('is-active'); }); if (byId[id]) byId[id].classList.add('is-active'); } } }); }, { rootMargin: '-45% 0px -45% 0px', threshold: 0 }); sections.forEach(function (s) { spy.observe(s); }); } }; /* ─── RegionReportPage boot (district fixed per page) ─── */ (function () { var report = document.getElementById('report'); if (!report) return; var TEMPLATE = report.innerHTML; var slug = report.getAttribute('data-district') || 'new-zealand'; var DATA = (window.DISTRICTS && window.DISTRICTS.data) || {}; function sub(tpl, fields) { return tpl.replace(/\{\{\s*(\w+)\s*\}\}/g, function (m, k) { return (k in fields) ? fields[k] : ''; }); } window.renderLocation = function (s) { var d = DATA[s] || DATA['new-zealand']; if (!d) return; window.__CURRENT__ = d; report.innerHTML = sub(TEMPLATE, d.fields); if (window.renderCharts) window.renderCharts(); if (window.initInteractions) window.initInteractions(); }; window.__bootRender = function () { window.renderLocation(slug); }; if (window.__chartsReady) window.__bootRender(); })(); const { BarChart, Bar, XAxis, YAxis, CartesianGrid, ResponsiveContainer, LabelList, Tooltip } = Recharts; const { useState } = React; /* ─── Shared chart helpers ─── */ const fmt = (prefix, suffix) => (n) => (prefix || '') + Number(n).toLocaleString('en-NZ') + (suffix || ''); const MultiLineTick = ({ x, y, payload }) => { const lines = String(payload.value).split('\n'); return ( {lines.map((ln, i) => ( {ln} ))} ); }; const TotalLabel = ({ x, y, width, index, data }) => { const d = data[index]; if (!d || !d.total) return null; return ( {d.total} ); }; /* ─── BILL COMPARISON CHART ─── */ const BILL_CFG = { titleHtml: "Energy bills", subtitle: "Excludes upfront costs. Based on 2026 energy prices. Average household for the selected location.", hideSegLabels: true, yMin: 0, yMax: 12500, yTicks: [0, 2500, 5000, 7500, 10000, 12500], valuePrefix: "$", valueSuffix: "", segments: [ { key: "base", label: "", color: "transparent" }, { key: "maintenance", label: "Car maintenance", color: "#b7b7b7" }, { key: "electricity", label: "Electricity bills", color: "#6d9eeb" }, { key: "gas", label: "Gas bills", color: "#c27ba0" }, { key: "petrol", label: "Petrol bills", color: "#e06666" }, { key: "rucs", label: "RUCs", color: "#ea9999" }, { key: "savings", label: "Savings", color: "#93c47d" }, ], legendOrder: ["petrol", "gas", "electricity", "maintenance", "rucs", "savings"], data: [ { name: "Fossil fuel\nhome", base: 0, maintenance: 2300, electricity: 1900, gas: 2100, petrol: 4300, rucs: 0, savings: 0, total: "$10,600 /yr" }, { name: "Electric\nhome", base: 0, maintenance: 900, electricity: 2500, gas: 0, petrol: 0, rucs: 900, savings: 0, total: "$4,300 /yr" }, { name: "Savings", base: 4300, maintenance: 0, electricity: 0, gas: 0, petrol: 0, rucs: 0, savings: 6300, total: "$6,300 /yr" }, ], }; function StackedBarChart({ cfg, id }) { const [activeKey, setActiveKey] = useState(null); const [sticky, setSticky] = useState(false); const COLORS = Object.fromEntries(cfg.segments.map(s => [s.key, s.color])); const LABELS = Object.fromEntries(cfg.segments.map(s => [s.key, s.label])); const STACK = cfg.segments.map(s => s.key); const fmtVal = fmt(cfg.valuePrefix, cfg.valueSuffix); const topKey = (row) => [...STACK].reverse().find(k => row[k] > 0); const rowTotal = (row) => STACK.reduce((a, k) => a + (k === 'base' ? 0 : (row[k] || 0)), 0); const clear = () => { setActiveKey(null); setSticky(false); }; const dim = (key) => activeKey == null ? 1 : activeKey === key ? 1 : 0.35; const SegTip = ({ active, payload }) => { if (!active || !payload?.length || !activeKey) return null; const row = payload[0].payload; const val = row[activeKey]; if (!val) return null; const pct = Math.round((val / rowTotal(row)) * 100); return (
{String(row.name).replace('\n', ' ')}
{LABELS[activeKey]} {fmtVal(val)}
{row.total &&
{pct}% of total
}
); }; return (
{cfg.titleHtml ?
:
{cfg.title}
}
{cfg.legendOrder.map(k => (
{ if (activeKey === k && sticky) clear(); else { setActiveKey(k); setSticky(true); } }}> {LABELS[k]}
))}
{ if (e.target === e.currentTarget && sticky) clear(); }}>
} height={60} /> } /> {cfg.segments.map((s, i) => ( { if (!sticky) setActiveKey(s.key); }} onMouseLeave={() => { if (!sticky) clear(); }} onClick={() => { if (sticky && activeKey === s.key) clear(); else { setActiveKey(s.key); setSticky(true); } }} shape={(props) => { const { x, y, width, height, index } = props; if (!height || s.key === 'base') return null; const isTop = topKey(cfg.data[index]) === s.key; const r = isTop ? 6 : 0; const opacity = dim(s.key); const val = cfg.data[index][s.key]; const cx = x + width / 2; const cy = y + height / 2; const showLabel = !cfg.hideSegLabels && height > 60 && width > 100 && val > 0; const showValue = height > 30 && val > 0; const rectEl = r ? : ; return ( {rectEl} {showLabel && {LABELS[s.key]}} {showValue && {fmtVal(val)}} ); }} > {i === cfg.segments.length - 1 && } />} ))}

{cfg.subtitle}

); } /* ─── MACHINE SAVINGS CHART ─── */ const SAVINGS_CFG = { title: "Lifetime savings by electrification switch", subtitle: "Net savings including upfront costs. Solar: 30 years, 9kW system at 5.5% finance. All others: 15 years. Vehicle and cooking figures use bill savings (not net) as upfront costs vary.", yMin: 0, yMax: 25000, yTicks: [0, 5000, 10000, 15000, 20000, 25000], valuePrefix: "$", valueSuffix: "", segments: [ { key: "savings", label: "Net savings / bill savings", color: "#93c47d" }, ], legendOrder: ["savings"], data: [ { name: "EV\n(petrol SUV)", savings: 22837, total: "$22,837" }, { name: "Solar\n(9kW, 30yr)", savings: 18656, total: "$18,656" }, { name: "Heat pump\n(LPG fire)", savings: 19300, total: "$19,300" }, { name: "Hot water HP\n(LPG instant)", savings: 10000, total: "$10,000" }, { name: "EV\n(diesel ute)", savings: 14170, total: "$14,170" }, { name: "Induction\n(LPG cooktop)", savings: 1638, total: "$1,638" }, ], }; /* ─── HORIZONTAL BAR CHART (for machine comparisons) ─── */ function HorizBarChart({ data, segments, xMax, xTicks, valuePrefix, valueSuffix, subtitle }) { const fmtVal = fmt(valuePrefix, valueSuffix); const COLORS = Object.fromEntries(segments.map(s => [s.key, s.color])); const LABELS = Object.fromEntries(segments.map(s => [s.key, s.label])); const STACK = segments.map(s => s.key); const rightKey = (row) => [...STACK].reverse().find(k => row[k] > 0); const rowTotal = (row) => STACK.reduce((a, k) => a + (row[k] || 0), 0); const chartH = Math.max(280, data.length * 62 + 50); return (
{segments.map(s => (
{s.label}
))}
{segments.map((s, i) => ( { const { x, y, width, height } = props; if (!width) return null; const idx = props.index; const isRight = rightKey(data[idx]) === s.key; const r = isRight ? 4 : 0; if (r) { const path = `M${x},${y} L${x + width - r},${y} Q${x + width},${y} ${x + width},${y + r} L${x + width},${y + height - r} Q${x + width},${y + height} ${x + width - r},${y + height} L${x},${y + height} Z`; return ; } return ; }} > {i === segments.length - 1 && ( { const d = data[index]; if (!d || !d.total) return null; return {d.total}; }} /> )} ))}
{subtitle &&

{subtitle}

}
); } /* ─── TABBED CHART WRAPPER ─── */ function TabbedCharts({ title, tabs }) { const [active, setActive] = useState(0); return (
{title}
{tabs.map((t, i) => ( ))}
{tabs.map((t, i) => (
))}
); } /* ─── SPACE HEATING DATA ─── */ const HEATING_TABS = { title: "Space heating savings", tabs: [ { label: "15 Year Lifetime", chart: { segments: [ { key: "upfront", label: "Upfront costs", color: "#434343" }, { key: "elec", label: "Electricity costs", color: "#6fa8dc" }, { key: "wood", label: "Wood costs", color: "#ac9e76" }, { key: "gasVol", label: "Gas costs (volume)", color: "#a64d79" }, { key: "gasDaily", label: "Gas costs (daily)", color: "#c27ba0" }, { key: "lpgVol", label: "LPG costs (volume)", color: "#674ea7" }, { key: "lpgDaily", label: "LPG costs (daily)", color: "#8e7cc3" }, ], xMax: 35000, xTicks: [0, 5000, 10000, 15000, 20000, 25000, 30000, 35000], valuePrefix: "$", valueSuffix: "", subtitle: "Based on average home heating needs (RBS 2021), 2026 energy prices with forward inflation (real) based on historic averages, 15 year appliance lifetime.", data: [ { name: "Heat pump", upfront: 3500, elec: 5600, wood: 0, gasVol: 0, gasDaily: 0, lpgVol: 0, lpgDaily: 0, total: "$9,100" }, { name: "Wood fire", upfront: 4500, elec: 0, wood: 13700, gasVol: 0, gasDaily: 0, lpgVol: 0, lpgDaily: 0, total: "$18,200" }, { name: "Resistive heater", upfront: 500, elec: 20500, wood: 0, gasVol: 0, gasDaily: 0, lpgVol: 0, lpgDaily: 0, total: "$21,000" }, { name: "Gas heater flued", upfront: 3000, elec: 0, wood: 0, gasVol: 15500, gasDaily: 5600, lpgVol: 0, lpgDaily: 0, total: "$24,100" }, { name: "Gas heater fire", upfront: 3000, elec: 0, wood: 0, gasVol: 17800, gasDaily: 5600, lpgVol: 0, lpgDaily: 0, total: "$26,400" }, { name: "LPG heater flued", upfront: 3000, elec: 0, wood: 0, gasVol: 0, gasDaily: 0, lpgVol: 22500, lpgDaily: 3000, total: "$28,500" }, { name: "LPG heater fire", upfront: 3000, elec: 0, wood: 0, gasVol: 0, gasDaily: 0, lpgVol: 25400, lpgDaily: 3000, total: "$31,400" }, ], }}, { label: "Yearly Bills", chart: { segments: [ { key: "elec", label: "Electricity costs", color: "#6fa8dc" }, { key: "wood", label: "Wood costs", color: "#ac9e76" }, { key: "gasVol", label: "Gas costs (volume)", color: "#a64d79" }, { key: "gasDaily", label: "Gas costs (daily)", color: "#c27ba0" }, { key: "lpgVol", label: "LPG costs (volume)", color: "#674ea7" }, { key: "lpgDaily", label: "LPG costs (daily)", color: "#8e7cc3" }, ], xMax: 1750, xTicks: [0, 250, 500, 750, 1000, 1250, 1500, 1750], valuePrefix: "$", valueSuffix: "", subtitle: "Based on 2026 energy prices.", data: [ { name: "Heat pump", elec: 330, wood: 0, gasVol: 0, gasDaily: 0, lpgVol: 0, lpgDaily: 0, total: "$330 /yr" }, { name: "Wood fire", elec: 0, wood: 740, gasVol: 0, gasDaily: 0, lpgVol: 0, lpgDaily: 0, total: "$740 /yr" }, { name: "Resistive heater", elec: 1220, wood: 0, gasVol: 0, gasDaily: 0, lpgVol: 0, lpgDaily: 0, total: "$1,220 /yr" }, { name: "Gas heater flued", elec: 0, wood: 0, gasVol: 870, gasDaily: 320, lpgVol: 0, lpgDaily: 0, total: "$1,190 /yr" }, { name: "Gas heater fire", elec: 0, wood: 0, gasVol: 950, gasDaily: 320, lpgVol: 0, lpgDaily: 0, total: "$1,270 /yr" }, { name: "LPG heater flued", elec: 0, wood: 0, gasVol: 0, gasDaily: 0, lpgVol: 1210, lpgDaily: 200, total: "$1,410 /yr" }, { name: "LPG heater fire", elec: 0, wood: 0, gasVol: 0, gasDaily: 0, lpgVol: 1350, lpgDaily: 200, total: "$1,550 /yr" }, ], }}, { label: "Yearly Emissions", chart: { segments: [ { key: "emissions", label: "Yearly emissions (kg CO2e/yr)", color: "#e06666" }, ], xMax: 1250, xTicks: [0, 250, 500, 750, 1000, 1250], valuePrefix: "", valueSuffix: "", subtitle: "Based on average home heating needs (RBS 2021), 2023 emissions factors (Ministry for the Environment 2023).", data: [ { name: "Heat pump", emissions: 80, total: "80" }, { name: "Wood fire", emissions: 100, total: "100" }, { name: "Resistive heater", emissions: 290, total: "290" }, { name: "Gas heater flued", emissions: 1000, total: "1,000" }, { name: "Gas heater fire", emissions: 1100, total: "1,100" }, { name: "LPG heater flued", emissions: 1090, total: "1,090" }, { name: "LPG heater fire", emissions: 1200, total: "1,200" }, ], }}, ], }; /* ─── WATER HEATING DATA ─── */ const WATER_TABS = { title: "Water heating savings", tabs: [ { label: "15 Year Lifetime", chart: { segments: [ { key: "upfront", label: "Upfront costs", color: "#434343" }, { key: "solar", label: "Solar financed", color: "#fbbc04" }, { key: "ripple", label: "Ripple", color: "#6d9eeb" }, { key: "elec", label: "Electricity costs", color: "#3c78d8" }, { key: "gasVol", label: "Gas costs (volume)", color: "#a64d79" }, { key: "gasDaily", label: "Gas costs (daily)", color: "#c27ba0" }, { key: "lpgVol", label: "LPG costs (volume)", color: "#674ea7" }, { key: "lpgDaily", label: "LPG costs (daily)", color: "#8e7cc3" }, ], xMax: 25000, xTicks: [0, 5000, 10000, 15000, 20000, 25000], valuePrefix: "$", valueSuffix: "", subtitle: "Based on average home water heating needs (RBS 2021), 2026 energy prices with forward inflation, 15 year appliance lifetime.", data: [ { name: "Hot water heat pump (solar)", upfront: 5000, solar: 3600, ripple: 0, elec: 0, gasVol: 0, gasDaily: 0, lpgVol: 0, lpgDaily: 0, total: "$8,600" }, { name: "Hot water heat pump (night)", upfront: 5000, solar: 0, ripple: 4500, elec: 0, gasVol: 0, gasDaily: 0, lpgVol: 0, lpgDaily: 0, total: "$9,500" }, { name: "Hot water heat pump (grid)", upfront: 5000, solar: 0, ripple: 0, elec: 6000, gasVol: 0, gasDaily: 0, lpgVol: 0, lpgDaily: 0, total: "$11,000" }, { name: "Resistive on solar", upfront: 1500, solar: 8350, ripple: 0, elec: 0, gasVol: 0, gasDaily: 0, lpgVol: 0, lpgDaily: 0, total: "$9,850" }, { name: "Resistive night rate", upfront: 1500, solar: 0, ripple: 11950, elec: 0, gasVol: 0, gasDaily: 0, lpgVol: 0, lpgDaily: 0, total: "$13,450" }, { name: "Resistive avg grid", upfront: 1500, solar: 0, ripple: 0, elec: 17950, gasVol: 0, gasDaily: 0, lpgVol: 0, lpgDaily: 0, total: "$19,450" }, { name: "Gas instant", upfront: 3000, solar: 0, ripple: 0, elec: 0, gasVol: 10000, gasDaily: 4500, lpgVol: 0, lpgDaily: 0, total: "$17,500" }, { name: "Gas tank", upfront: 3000, solar: 0, ripple: 0, elec: 0, gasVol: 12900, gasDaily: 4500, lpgVol: 0, lpgDaily: 0, total: "$20,400" }, { name: "LPG instant", upfront: 3000, solar: 0, ripple: 0, elec: 0, gasVol: 0, gasDaily: 0, lpgVol: 13700, lpgDaily: 3000, total: "$19,700" }, ], }}, { label: "Yearly Bills", chart: { segments: [ { key: "solar", label: "Solar financed", color: "#fbbc04" }, { key: "ripple", label: "Ripple", color: "#6d9eeb" }, { key: "elec", label: "Electricity costs", color: "#3c78d8" }, { key: "gasVol", label: "Gas costs (volume)", color: "#a64d79" }, { key: "gasDaily", label: "Gas costs (daily)", color: "#c27ba0" }, { key: "lpgVol", label: "LPG costs (volume)", color: "#674ea7" }, { key: "lpgDaily", label: "LPG costs (daily)", color: "#8e7cc3" }, ], xMax: 1250, xTicks: [0, 250, 500, 750, 1000, 1250], valuePrefix: "$", valueSuffix: "", subtitle: "Based on 2026 energy prices.", data: [ { name: "Hot water heat pump (solar)", solar: 90, ripple: 0, elec: 0, gasVol: 0, gasDaily: 0, lpgVol: 0, lpgDaily: 0, total: "$90 /yr" }, { name: "Hot water heat pump (night)", solar: 0, ripple: 130, elec: 0, gasVol: 0, gasDaily: 0, lpgVol: 0, lpgDaily: 0, total: "$130 /yr" }, { name: "Hot water heat pump (grid)", solar: 0, ripple: 0, elec: 220, gasVol: 0, gasDaily: 0, lpgVol: 0, lpgDaily: 0, total: "$220 /yr" }, { name: "Resistive on solar", solar: 360, ripple: 0, elec: 0, gasVol: 0, gasDaily: 0, lpgVol: 0, lpgDaily: 0, total: "$360 /yr" }, { name: "Resistive night rate", solar: 0, ripple: 540, elec: 0, gasVol: 0, gasDaily: 0, lpgVol: 0, lpgDaily: 0, total: "$540 /yr" }, { name: "Resistive avg grid", solar: 0, ripple: 0, elec: 910, gasVol: 0, gasDaily: 0, lpgVol: 0, lpgDaily: 0, total: "$910 /yr" }, { name: "Gas instant", solar: 0, ripple: 0, elec: 0, gasVol: 540, gasDaily: 300, lpgVol: 0, lpgDaily: 0, total: "$840 /yr" }, { name: "Gas tank", solar: 0, ripple: 0, elec: 0, gasVol: 640, gasDaily: 300, lpgVol: 0, lpgDaily: 0, total: "$940 /yr" }, { name: "LPG instant", solar: 0, ripple: 0, elec: 0, gasVol: 0, gasDaily: 0, lpgVol: 750, lpgDaily: 200, total: "$950 /yr" }, ], }}, { label: "Yearly Emissions", chart: { segments: [ { key: "emissions", label: "Yearly emissions (kg CO2e/yr)", color: "#e06666" }, ], xMax: 800, xTicks: [0, 200, 400, 600, 800], valuePrefix: "", valueSuffix: "", subtitle: "Based on average water heating needs (RBS 2021), 2023 emissions factors (Ministry for the Environment 2023).", data: [ { name: "Hot water heat pump", emissions: 50, total: "50" }, { name: "Resistive", emissions: 220, total: "220" }, { name: "Gas instant", emissions: 620, total: "620" }, { name: "Gas tank", emissions: 740, total: "740" }, { name: "LPG instant", emissions: 670, total: "670" }, ], }}, ], }; /* ─── COOKTOP DATA ─── */ const COOKTOP_TABS = { title: "Cooking savings", tabs: [ { label: "15 Year Lifetime", chart: { segments: [ { key: "upfront", label: "Upfront costs", color: "#434343" }, { key: "elec", label: "Electricity costs", color: "#6fa8dc" }, { key: "gasVol", label: "Gas costs (volume)", color: "#a64d79" }, { key: "gasDaily", label: "Gas costs (daily)", color: "#c27ba0" }, { key: "lpgVol", label: "LPG costs (volume)", color: "#674ea7" }, { key: "lpgDaily", label: "LPG costs (daily)", color: "#8e7cc3" }, ], xMax: 5000, xTicks: [0, 1000, 2000, 3000, 4000, 5000], valuePrefix: "$", valueSuffix: "", subtitle: "Based on average cooktop energy needs (RBS 2021), 2026 energy prices with forward inflation (real), 15 year appliance lifetime.", data: [ { name: "Induction cooktop", upfront: 1200, elec: 2127, gasVol: 0, gasDaily: 0, lpgVol: 0, lpgDaily: 0, total: "$3,327" }, { name: "Resistive cooktop", upfront: 500, elec: 2565, gasVol: 0, gasDaily: 0, lpgVol: 0, lpgDaily: 0, total: "$3,065" }, { name: "Gas cooktop", upfront: 800, elec: 0, gasVol: 1824, gasDaily: 1000, lpgVol: 0, lpgDaily: 0, total: "$3,624" }, { name: "LPG cooktop", upfront: 800, elec: 0, gasVol: 0, gasDaily: 0, lpgVol: 2977, lpgDaily: 1000, total: "$4,777" }, ], }}, { label: "Yearly Bills", chart: { segments: [ { key: "elec", label: "Electricity costs", color: "#6fa8dc" }, { key: "gasVol", label: "Gas costs (volume)", color: "#a64d79" }, { key: "gasDaily", label: "Gas costs (daily)", color: "#c27ba0" }, { key: "lpgVol", label: "LPG costs (volume)", color: "#674ea7" }, { key: "lpgDaily", label: "LPG costs (daily)", color: "#8e7cc3" }, ], xMax: 200, xTicks: [0, 50, 100, 150, 200], valuePrefix: "$", valueSuffix: "", subtitle: "Based on 2026 energy prices.", data: [ { name: "Induction cooktop", elec: 80, gasVol: 0, gasDaily: 0, lpgVol: 0, lpgDaily: 0, total: "$80 /yr" }, { name: "Resistive cooktop", elec: 89, gasVol: 0, gasDaily: 0, lpgVol: 0, lpgDaily: 0, total: "$89 /yr" }, { name: "Gas cooktop", elec: 0, gasVol: 50, gasDaily: 68, lpgVol: 0, lpgDaily: 0, total: "$118 /yr" }, { name: "LPG cooktop", elec: 0, gasVol: 0, gasDaily: 0, lpgVol: 120, lpgDaily: 67, total: "$187 /yr" }, ], }}, { label: "Yearly Emissions", chart: { segments: [ { key: "emissions", label: "Yearly emissions (kg CO2e/yr)", color: "#e06666" }, ], xMax: 150, xTicks: [0, 50, 100, 150], valuePrefix: "", valueSuffix: "", subtitle: "Based on average cooktop energy needs (RBS 2021), 2023 emissions factors (Ministry for the Environment 2023).", data: [ { name: "Induction cooktop", emissions: 19, total: "19" }, { name: "Resistive cooktop", emissions: 21, total: "21" }, { name: "Gas cooktop", emissions: 136, total: "136" }, { name: "LPG cooktop", emissions: 147, total: "147" }, ], }}, ], }; /* ─── SOLAR & BATTERIES DATA ─── */ const SOLAR_TABS = { title: "Solar & battery savings", tabs: [ { label: "Effective Price", chart: { segments: [ { key: "price", label: "Effective electricity price (c/kWh)", color: "#3c78d8" }, ], xMax: 50, xTicks: [0, 10, 20, 30, 40, 50], valuePrefix: "", valueSuffix: "c", subtitle: "Effective price per kWh paid by the household, including financing at 5.5%. Lower is better. Placeholder figures — to confirm against the model.", data: [ { name: "Grid only", price: 40, total: "40c" }, { name: "Solar only", price: 28, total: "28c" }, { name: "Solar + battery", price: 23, total: "23c" }, ], }}, { label: "15 Year Electricity Cost", chart: { segments: [ { key: "cost", label: "Electricity cost over 15 years", color: "#3c78d8" }, ], xMax: 50000, xTicks: [0, 10000, 20000, 30000, 40000, 50000], valuePrefix: "$", valueSuffix: "", subtitle: "Total household electricity spend over 15 years, including solar and battery financing at 5.5%. Placeholder figures — to confirm against the model.", data: [ { name: "Grid only", cost: 48000, total: "$48,000" }, { name: "Solar only", cost: 26000, total: "$26,000" }, { name: "Solar + battery", cost: 13700, total: "$13,700" }, ], }}, ], }; /* ─── ELECTRIC VEHICLE DATA ─── */ const EV_TABS = { title: "Vehicle savings", tabs: [ { label: "15 Year Lifetime", chart: { segments: [ { key: "elec", label: "Electricity costs", color: "#3c78d8" }, { key: "petrol", label: "Petrol costs", color: "#e06666" }, { key: "diesel", label: "Diesel costs", color: "#cc4125" }, { key: "rucs", label: "Road user charges", color: "#b7b7b7" }, { key: "maintenance", label: "Maintenance", color: "#76a5af" }, ], xMax: 55000, xTicks: [0, 10000, 20000, 30000, 40000, 50000], valuePrefix: "$", valueSuffix: "", subtitle: "Running costs only (excludes vehicle purchase price). Medium vehicle, 188km/week, 2026 prices with forward inflation, 15 year ownership. Solar charging at the effective solar electricity price (23c/kWh vs 40c/kWh grid). Placeholder figures — to confirm against the model.", data: [ { name: "Electric (grid charged)", elec: 9500, petrol: 0, diesel: 0, rucs: 13400, maintenance: 3800, total: "$26,700" }, { name: "Electric (solar charged)", elec: 5500, petrol: 0, diesel: 0, rucs: 13400, maintenance: 3800, total: "$22,700" }, { name: "Hybrid SUV", elec: 0, petrol: 25200, diesel: 0, rucs: 0, maintenance: 7800, total: "$33,000" }, { name: "Petrol SUV", elec: 0, petrol: 41000, diesel: 0, rucs: 0, maintenance: 8500, total: "$49,500" }, { name: "Diesel ute", elec: 0, petrol: 0, diesel: 31500, rucs: 13400, maintenance: 9000, total: "$53,900" }, ], }}, { label: "Yearly Bills", chart: { segments: [ { key: "elec", label: "Electricity costs", color: "#3c78d8" }, { key: "petrol", label: "Petrol costs", color: "#e06666" }, { key: "diesel", label: "Diesel costs", color: "#cc4125" }, { key: "rucs", label: "Road user charges", color: "#b7b7b7" }, { key: "maintenance", label: "Maintenance", color: "#76a5af" }, ], xMax: 4000, xTicks: [0, 1000, 2000, 3000, 4000], valuePrefix: "$", valueSuffix: "", subtitle: "Based on 2026 energy prices, 188km/week. Placeholder figures — to confirm against the model.", data: [ { name: "Electric (grid charged)", elec: 530, petrol: 0, diesel: 0, rucs: 745, maintenance: 300, total: "$1,575 /yr" }, { name: "Electric (solar charged)", elec: 300, petrol: 0, diesel: 0, rucs: 745, maintenance: 300, total: "$1,345 /yr" }, { name: "Hybrid SUV", elec: 0, petrol: 1400, diesel: 0, rucs: 0, maintenance: 600, total: "$2,000 /yr" }, { name: "Petrol SUV", elec: 0, petrol: 2600, diesel: 0, rucs: 0, maintenance: 700, total: "$3,300 /yr" }, { name: "Diesel ute", elec: 0, petrol: 0, diesel: 2000, rucs: 750, maintenance: 800, total: "$3,550 /yr" }, ], }}, { label: "Yearly Emissions", chart: { segments: [ { key: "emissions", label: "Yearly emissions (kg CO2e/yr)", color: "#e06666" }, ], xMax: 3000, xTicks: [0, 1000, 2000, 3000], valuePrefix: "", valueSuffix: "", subtitle: "Tailpipe and electricity emissions, 2023 emissions factors (Ministry for the Environment 2023). Placeholder figures — to confirm against the model.", data: [ { name: "Electric (EV)", emissions: 130, total: "130" }, { name: "Hybrid SUV", emissions: 1400, total: "1,400" }, { name: "Petrol SUV", emissions: 2300, total: "2,300" }, { name: "Diesel ute", emissions: 2600, total: "2,600" }, ], }}, ], }; /* ─── Bills: two-tab wrapper (annual bills / incl. upfront) ─── */ function BillTabs({ cfgA, cfgB }) { const tabs = ["Bill savings", "Bill savings including upfront costs"]; const [active, setActive] = useState(0); const a = cfgA || BILL_CFG; const b = cfgB || SAVINGS_CFG; return (
{tabs.map((t, i) => ( ))}
{active === 0 ? : }
); } /* ─── Render all charts (re-runnable per location; model chart-blocks with national fallback) ─── */ window.renderCharts = function () { var D = window.__CURRENT__; if (!D) return; var title = function (sav) { return "The average " + D.fields.location + " home could save " + sav + " on their bills every year by electrifying"; }; var cfgA = null, cfgB = null; if (D.billTabs) { cfgA = D.billTabs.a; cfgA.titleHtml = title(D.billTabs.billSavings); cfgB = D.billTabs.b; cfgB.titleHtml = title(D.billTabs.netSavings) + " (incl. upfront costs)"; } else { BILL_CFG.data = D.bill; BILL_CFG.yMax = D.billYMax; BILL_CFG.yTicks = D.billYTicks; BILL_CFG.titleHtml = title(D.fields.bill_savings); SAVINGS_CFG.data = D.savings; SAVINGS_CFG.yMax = D.savingsYMax; SAVINGS_CFG.yTicks = D.savingsYTicks; cfgA = BILL_CFG; cfgB = SAVINGS_CFG; } var MT = D.machineTabs || {}; var mergeTabs = function (base, locTabs) { if (!locTabs || !locTabs.length) return base; var t = base.tabs.slice(); for (var i = 0; i < locTabs.length && i < t.length; i++) t[i] = locTabs[i]; return { title: base.title, tabs: t }; }; var mount = function (id, el) { var n = document.getElementById(id); if (n) ReactDOM.createRoot(n).render(el); }; mount('bill-chart', ); mount('solar-tabs', ); mount('ev-tabs', ); mount('heating-tabs', ); mount('water-tabs', ); mount('cooktop-tabs', ); }; window.__chartsReady = true; if (window.__bootRender) window.__bootRender();