Hello, World!

import React, { useMemo, useState } from "react"; // Planet Electrical — Solar ROI Calculator (Embed) // Single-file React component. Production-ready, clean Tailwind UI. // Assumptions are NZ‑centric and editable in the "Defaults" section. export default function SolarRoiCalculator() { // ========= Defaults (you can tweak these) ========= const DEFAULTS = { pricePerKWh: 0.35, // $/kWh retail feedInTariff: 0.08, // $/kWh export credit baselineYield: 1350, // kWh/kW/year (Auckland-ish, north @ ~30° tilt) sysCostPerKW: 2800, // NZD/kW installed (PV only) batteryInstalledCost: 16000, // NZD (e.g., Tesla Powerwall as placeholder) evChargerCost: 2000, // NZD for smart EVSE install baseline } as const; // ========= State ========= const [dailyUse, setDailyUse] = useState(22); // kWh/day const [daytimeShare, setDaytimeShare] = useState(55); // % of use that happens 8am–6pm const [systemSize, setSystemSize] = useState(7.2); // kW DC const [panelWatt, setPanelWatt] = useState(440); // for roof fit calc only const [roofArea, setRoofArea] = useState(70); // m² usable area const [orientation, setOrientation] = useState("N"); // N, NE/NW, E/W, SE/SW, S const [tilt, setTilt] = useState(25); // degrees const [shading, setShading] = useState(5); // % loss const [pricePerKWh, setPricePerKWh] = useState(DEFAULTS.pricePerKWh); const [feedIn, setFeedIn] = useState(DEFAULTS.feedInTariff); const [sysCostPerKW, setSysCostPerKW] = useState(DEFAULTS.sysCostPerKW); const [hasBattery, setHasBattery] = useState(false); const [batteryKWh, setBatteryKWh] = useState(13.5); const [batteryCost, setBatteryCost] = useState(DEFAULTS.batteryInstalledCost); const [hasEv, setHasEv] = useState(false); const [evChargerCost, setEvChargerCost] = useState(DEFAULTS.evChargerCost); // ========= Helpers ========= const fmtNZD = (n: number) => n.toLocaleString("en-NZ", { style: "currency", currency: "NZD", maximumFractionDigits: 0 }); const fmtKWh = (n: number) => `${n.toLocaleString("en-NZ", { maximumFractionDigits: 0 })} kWh`; const clamp = (v: number, a: number, b: number) => Math.max(a, Math.min(b, v)); // Orientation multipliers (simple heuristic) const orientationFactor = useMemo(() => { switch (orientation) { case "N": return 1.0; case "NE/NW": return 0.95; case "E/W": return 0.9; case "SE/SW": return 0.85; case "S": return 0.75; default: return 1.0; } }, [orientation]); // Tilt factor — gentle parabolic penalty away from ~30° const tiltFactor = useMemo(() => { const d = Math.abs(tilt - 30); // ~1% loss per 5° away from 30°, capped const penalty = Math.min(0.12, (d / 5) * 0.01); return 1 - penalty; }, [tilt]); const shadingFactor = useMemo(() => 1 - clamp(shading, 0, 60) / 100, [shading]); // Roof fit estimate (kW) from area + panel W const roofFitKW = useMemo(() => { const panelAreaM2 = 1.94; // typical 72/108 cell module footprint const panelsFit = Math.floor(roofArea / panelAreaM2); const kW = (panelsFit * panelWatt) / 1000; return Math.max(0, kW); }, [roofArea, panelWatt]); const suggestedSizeKW = useMemo(() => { // Rule of thumb: aim to cover ~90–110% of annual usage if feed-in is low, // but not exceed roof/budget limits. Provide a nudge only. const annualUse = dailyUse * 365; const yieldPerKW = DEFAULTS.baselineYield * orientationFactor * tiltFactor * shadingFactor; const sizeForUsage = annualUse / yieldPerKW; // kW to match usage // Budget limit const budgetCapKW = Infinity; // You could add a budget input and cap by budget/sysCostPerKW. return clamp(Number((Math.min(sizeForUsage * 0.95, roofFitKW, budgetCapKW)).toFixed(2)), 0, 50); }, [dailyUse, roofFitKW, orientationFactor, tiltFactor, shadingFactor]); // ========= Core calcs ========= const results = useMemo(() => { const annualUse = dailyUse * 365; // kWh const yieldPerKW = DEFAULTS.baselineYield * orientationFactor * tiltFactor * shadingFactor; const annualGen = systemSize * yieldPerKW; // kWh/yr // Self-consumption model (simple heuristic): // Start with daytime share of annual use as a cap for direct consumption const dayFrac = clamp(daytimeShare / 100, 0, 1); const directUseCap = annualUse * dayFrac; // Direct self-consumption (bounded by generation) const directSelf = Math.min(annualGen, directUseCap); // Battery & EV improve self-consumption by shifting some of the exported energy const exportedBeforeShift = Math.max(0, annualGen - directSelf); let extraFrac = 0; if (hasBattery) { // Battery can increase self-consumption up to +25pp depending on surplus // and size; rough cap scaled by battery size vs system size. const battScale = clamp(batteryKWh / (systemSize * 2), 0, 1); // 0–1 heuristic extraFrac += 0.25 * battScale; } if (hasEv) { // Smart EV charging behaviour can add ~5pp extraFrac += 0.05; } const targetSelfFrac = clamp(dayFrac + extraFrac, 0, 0.98); const targetSelf = Math.min(annualGen, annualUse * targetSelfFrac); // Additional shifted self-consumption is limited by exported surplus const shifted = Math.min(exportedBeforeShift, Math.max(0, targetSelf - directSelf)); const selfConsumed = directSelf + shifted; const exported = Math.max(0, annualGen - selfConsumed); // $ figures const billBefore = annualUse * pricePerKWh; const valueSelf = selfConsumed * pricePerKWh; const valueExport = exported * feedIn; const annualSavings = valueSelf + valueExport; const pvCost = systemSize * sysCostPerKW; const capex = pvCost + (hasBattery ? batteryCost : 0) + (hasEv ? evChargerCost : 0); const billAfter = Math.max(0, billBefore - annualSavings); const paybackYears = annualSavings > 0 ? capex / annualSavings : Infinity; // ROI (simple) — year 1 savings / capex const roiPct = annualSavings > 0 && capex > 0 ? (annualSavings / capex) * 100 : 0; return { annualUse, annualGen, yieldPerKW, selfConsumed, exported, billBefore, billAfter, annualSavings, capex, paybackYears, roiPct, directSelf, shifted, targetSelfFrac, }; }, [ dailyUse, daytimeShare, systemSize, orientationFactor, tiltFactor, shadingFactor, pricePerKWh, feedIn, sysCostPerKW, hasBattery, batteryKWh, batteryCost, hasEv, evChargerCost, ]); const copyEmbed = async () => { const html = `\n
\n`; try { await navigator.clipboard.writeText(html); alert("Embed code copied. Paste into a Squarespace code block."); } catch (e) { alert("Couldn't copy — select & copy manually:\"); console.log(html); } }; return (

Solar savings & ROI estimator

Quick, NZ‑centric guidance. Edit inputs to match your home — outputs are estimates only.

{/* Controls */}
Household & usage
Roof & system
onChange(Number(e.target.value))} className="w-full" /> onChange(Number(e.target.value))} className="w-28 input" /> {suffix && {suffix}} {!suffix && prefix && {prefix}}
); } function Select({ label, value, onChange, options }: { label: string; value: string; onChange: (v: string) => void; options: string[] }) { return (
); } function Toggle({ label, checked, onChange }: { label: string; checked: boolean; onChange: (b: boolean) => void }) { return ( ); } // Utility classes (lightweight replacements for shadcn/ui) // Tailwind shortcuts // .input, .btn, .btn-outline defined here to keep it single-file const _style = ( );