// solar-calc-calculator.jsx // The 4-step calculator card. Bright white card on cream. // Sage green for progress + UI. Amber appears only on the incentive band. // // Step flow: // 0 — Electricity Basics (province, monthly bill) // 1 — Facility Profile (industry, size, roof type) // 2 — Teaser + Lead Gate (visible savings range, blurred metrics, form) // 3 — Calculating animation → Results // // State held by parent component . // ----- Reusable bits ----------------------------------------- const ProgressRail = ({ step, labels }) => (
{labels.map((label, i) => { const active = i <= step; const current = i === step; return (
{i + 1} {label}
); })}
); const FieldLabel = ({ children, hint }) => (
{hint && ( {hint} )}
); const inputStyle = (focus) => ({ width: '100%', padding: '14px 16px', background: '#fff', border: `1px solid ${focus ? SC_SAGE : 'rgba(15,33,51,0.16)'}`, borderRadius: 2, fontFamily: 'var(--ff-sans)', fontSize: 15, color: 'var(--gi-deep-ink)', outline: 'none', transition: 'border-color 180ms cubic-bezier(.2,0,0,1)', boxShadow: focus ? `0 0 0 3px ${SC_SAGE_SOFT}` : 'none' }); const TextInput = ({ value, onChange, placeholder, type = 'text', ...rest }) => { const [focus, setFocus] = React.useState(false); return ( onChange(e.target.value)} placeholder={placeholder} onFocus={() => setFocus(true)} onBlur={() => setFocus(false)} style={inputStyle(focus)} {...rest} /> ); }; const Select = ({ value, onChange, options, placeholder }) => { const [focus, setFocus] = React.useState(false); return (
); }; // ----- Step 1: Electricity Basics ---------------------------- const Step1 = ({ data, setData, onNext, error }) => { return (

Your facility’s electricity costs

Two numbers get us started — your province sets local utility rates and irradiance; your bill anchors the whole estimate.

Province setData({ ...data, monthlyBill: e.target.value === '' ? '' : Number(e.target.value) })} placeholder="e.g. 9,500" style={{ ...inputStyle(false), paddingLeft: 56 }} />
{error && (
{error}
)}
); }; // ----- Step 2: Facility Profile ------------------------------- const Step2 = ({ data, setData, onNext, onBack, error }) => { return (

Tell us about your facility

Industry, size, and roof type help us frame the system — and route your estimate to the right specialist for first contact.

{/* Industry tiles */} Industry
{INDUSTRIES.map(ind => { const active = data.industry === ind.id; return ( ); })}
Facility size setData({ ...data, roof: v })} placeholder="Select roof type" options={ROOF_TYPES} />
{error && (
{error}
)}
); }; // ----- Step 3: Teaser + Lead Gate ---------------------------- const Step3 = ({ data, setData, estimates, onSubmit, onBack, error }) => { return (

Here’s your savings range — unlock the full estimate.

{/* Teaser cards: savings visible, two others blurred */}
{/* Lead gate explainer */}
Unlock your full estimate — including project cost range and incentive breakdown.
Your address lets us pull satellite roof data and precise local irradiance — so the number you get is specific to your building.
{/* Lead form — lean: business name + address required, plus reach-out */}
Business name setData({ ...data, businessName: v })} placeholder="e.g. Acme Manufacturing Inc." />
Contact name setData({ ...data, contactName: v })} placeholder="Optional" />
Facility address setData({ ...data, address: v })} placeholder="Street, city, province, postal code" />
Work email setData({ ...data, email: v })} placeholder="name@company.ca" />
Phone setData({ ...data, phone: v })} placeholder="(000) 000-0000" />

Your information is used only to generate and deliver your site-specific estimate. No spam. No third-party sharing.

{error && (
{error}
)}
); }; const TeaserCard = ({ label, value, blurred = false, highlight = false }) => (
{highlight && ( ); // ----- Step 4: Calculating + Results ------------------------- const Calculating = () => { const lines = [ 'Pulling provincial irradiance', 'Sizing system to annual profile', 'Applying incentive programs' ]; return (
); }; const Results = ({ estimates, data, onReset }) => { const province = estimates.province; return (

Your estimated savings — full picture.

Directional ranges for {data.businessName || 'your facility'}. A specialist will review these numbers with you within one business day.

{/* 4 result cards */}
{/* Incentive band — this is the ONE amber moment on the page */}
Up to {fmt$k(estimates.incentive.high)} in incentives
Applicable in {province.name}: {province.programs}. CCA Class 43.2 accelerated depreciation also applies.
{/* Next step CTA */}
A Green Integrations specialist will contact you within 1 business day to review this estimate and discuss a site-specific analysis.
{/* Disclaimer */}

This estimate is directional only. Actual savings depend on your consumption profile, roof conditions, shading, utility rate structure, and project design. A site-specific analysis is required to produce a bankable projection. All figures in CAD.

); }; const ResultCard = ({ label, value, sub, highlight = false }) => (
{highlight && ( ); // ----- Nav row ------------------------------------------------- const NavRow = ({ onBack, onNext, nextLabel }) => (
{onBack ? ( ) : }
); // ----- Top-level: ScCalculator section ------------------------ const ScCalculator = React.forwardRef((props, ref) => { const [step, setStep] = React.useState(0); const [error, setError] = React.useState(''); const [calculating, setCalculating] = React.useState(false); const [done, setDone] = React.useState(false); const [data, setData] = React.useState({ province: '', monthlyBill: '', industry: '', size: '', roof: '', businessName: '', contactName: '', address: '', email: '', phone: '' }); const estimates = React.useMemo(() => calculateEstimates({ provinceCode: data.province, monthlyBill: Number(data.monthlyBill) || 0 }), [data.province, data.monthlyBill]); const handleStep1 = () => { setError(''); if (!data.province) return setError('Please select your province.'); if (!data.monthlyBill) return setError('Please enter your average monthly electricity bill.'); if (Number(data.monthlyBill) < 500) return setError('This calculator is built for commercial facilities — monthly bills typically start at $500. For residential, please consult a residential installer.'); setStep(1); }; const handleStep2 = () => { setError(''); if (!data.industry) return setError('Please choose an industry.'); if (!data.size) return setError('Please select a facility size.'); if (!data.roof) return setError('Please select a roof type.'); setStep(2); }; const handleSubmit = () => { setError(''); if (!data.businessName.trim()) return setError('Business name is required.'); if (!data.address.trim()) return setError('Facility address is required — it’s how we generate a building-specific estimate.'); if (!data.email.trim() || !/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(data.email)) return setError('Please enter a valid work email.'); if (!data.phone.trim()) return setError('Please enter a phone number.'); setCalculating(true); setStep(3); setTimeout(() => { setCalculating(false); setDone(true); // GI Link Audit (Part 3, Key Event #3): solar_calc_complete. // Fires when the simulated calculation finishes and the user lands on // the results panel. Also doubles as a soft lead signal — by this point // the user has submitted business name, email, and phone in Step 3. if (window.GIGtm && typeof window.GIGtm.solarCalcComplete === 'function') { window.GIGtm.solarCalcComplete({ location: 'res-solar-calculator', province: data.province || undefined, industry: data.industry || undefined, facility_size: data.size || undefined, roof_type: data.roof || undefined, monthly_bill: Number(data.monthlyBill) || undefined, estimated_savings: estimates && estimates.annualSavings || undefined, estimated_system_kw: estimates && estimates.systemKw || undefined }); // The form on Step 3 collects business email + phone, which the // audit doc also classifies as a `generate_lead` moment. if (typeof window.GIGtm.generateLead === 'function') { window.GIGtm.generateLead({ form_id: 'solar-calculator', form_location: 'res-solar-calculator', industry: data.industry || undefined, province: data.province || undefined }); } } }, 2200); }; const handleReset = () => { setStep(0); setError(''); setCalculating(false); setDone(false); setData({ province: '', monthlyBill: '', industry: '', size: '', roof: '', businessName: '', contactName: '', address: '', email: '', phone: '' }); if (ref && ref.current) ref.current.scrollIntoView({ behavior: 'smooth', block: 'start' }); }; return (
{/* Section running header */}
Estimate Step {Math.min(step + 1, 4)} of 04
{/* Card */}
{step === 0 && } {step === 1 && setStep(0)} error={error} />} {step === 2 && estimates && setStep(1)} error={error} />} {step === 3 && calculating && } {step === 3 && done && estimates && }
); }); Object.assign(window, { ScCalculator });