el.addEventListener('input', () => saveAll()); wrap.appendChild(label); wrap.appendChild(el); return wrap; } function renderResults(r){ lastResult = r; $('shareText').textContent = r.summary; const k = $('kpis'); k.innerHTML = ''; r.kpis.forEach(item => { const div = document.createElement('div'); div.className = 'kpi'; div.innerHTML = `
${item.name}
${item.val}
${item.hint || ''}
`; k.appendChild(div); }); const hasChart = !!r.chart && Array.isArray(r.chart.series); $('chartWrap').classList.toggle('hidden', !hasChart); if (hasChart){ $('chartNote').textContent = r.chart.note || ''; drawChart(r.chart.series); } refreshProUI(); } function drawChart(series){ const c = $('chart'); const ctx = c.getContext('2d'); const w = c.width, h = c.height; ctx.clearRect(0,0,w,h); const pad = 18; const ys = series; const minY = Math.min(...ys, 0); const maxY = Math.max(...ys, 0); const span = (maxY - minY) || 1; ctx.globalAlpha = .28; ctx.beginPath(); for(let i=0;i<=4;i++){ const y = pad + (h-2*pad)*(i/4); ctx.moveTo(pad,y); ctx.lineTo(w-pad,y); } ctx.stroke(); ctx.globalAlpha = 1; const y0 = pad + (h-2*pad) * ((maxY - 0)/span); ctx.globalAlpha = .55; ctx.beginPath(); ctx.moveTo(pad,y0); ctx.lineTo(w-pad,y0); ctx.stroke(); ctx.globalAlpha = 1; ctx.lineWidth = 2; ctx.beginPath(); for(let i=0;i { const el = document.getElementById(id); if (el) el.value = val; }); } function persistCategory(){ const st = loadState() || { currentKey:'roi', values:{}, theme:'light', isPro:false, category:'Small Business' }; st.category = currentCategory; saveState(st); } function navigate(key){ currentKey = key; location.hash = `#/${key}`; } function autoSelectFirstInCategory(){ const keys = filteredKeys(); if (!keys.length) return; // If current key not in category, switch. if (CALCS[currentKey]?.category !== currentCategory){ navigate(keys[0]); } else { mountMenu(); } } function onRoute(){ const hash = location.hash || '#/roi'; const key = (hash.startsWith('#/') ? hash.slice(2) : 'roi').split('?')[0]; if (!CALCS[key]) return; currentKey = key; currentCategory = CALCS[currentKey].category; mountChips(); mountMenu(); mountInputs(); restoreValues(); doCalc(); } // ============================ // ACTIONS // ============================ function doCalc(){ saveAll(); const c = CALCS[currentKey]; const raw = getValues(); const v = {}; for (const f of c.fields){ const val = raw[f.id]; if (f.type === 'select') v[f.id] = Number(val); else if (f.type === 'number') v[f.id] = Number(val); else v[f.id] = val; } const res = c.compute(v); renderResults(res); } async function copySummary(){ if (!lastResult) return alert('Calculate first.'); try{ await navigator.clipboard.writeText(lastResult.summary); $('savePill').textContent = 'Copied ✓'; setTimeout(()=> $('savePill').textContent = 'Autosaves', 1200); } catch { alert('Copy failed.'); } } function downloadJSON(){ const c = CALCS[currentKey]; if (!lastResult) return alert('Calculate first.'); if (c.proExports && !isPro()) return openModal(); downloadBlob(JSON.stringify(lastResult, null, 2), `${currentKey}-results.json`, 'application/json'); } function downloadCSV(){ const c = CALCS[currentKey]; if (!lastResult) return alert('Calculate first.'); if (!c.csv) return alert('No table export for this calculator yet.'); if (c.proExports && !isPro()) return openModal(); downloadBlob(c.csv(lastResult), `${currentKey}-table.csv`, 'text/csv'); } function resetAll(){ localStorage.removeItem(STORAGE_KEY); location.reload(); } // ============================ // THEME // ============================ function setTheme(theme){ document.documentElement.setAttribute('data-theme', theme); const st = loadState() || { currentKey:'roi', values:{}, theme:'light', isPro:false, category:'Small Business' }; st.theme = theme; saveState(st); $('themeSwitch').setAttribute('aria-checked', theme === 'dark' ? 'true' : 'false'); } function toggleTheme(){ const cur = document.documentElement.getAttribute('data-theme') || 'light'; setTheme(cur === 'dark' ? 'light' : 'dark'); } // ============================ // MODAL // ============================ function openModal(){ $('modalBackdrop').style.display = 'flex'; $('modalBackdrop').setAttribute('aria-hidden','false'); } function closeModal(){ $('modalBackdrop').style.display = 'none'; $('modalBackdrop').setAttribute('aria-hidden','true'); } // ============================ // INIT // ============================ $('calcBtn').addEventListener('click', doCalc); $('copyBtn').addEventListener('click', copySummary); $('downloadBtn').addEventListener('click', downloadJSON); $('csvBtn').addEventListener('click', downloadCSV); $('resetBtn').addEventListener('click', resetAll); $('startBtn').addEventListener('click', () => document.getElementById('main').scrollIntoView({behavior:'smooth'})); $('seeProBtn').addEventListener('click', () => document.getElementById('pro').scrollIntoView({behavior:'smooth'})); $('upgradeBtn').addEventListener('click', openModal); $('upgradeTopBtn').addEventListener('click', () => { if (!isPro()) openModal(); }); $('learnBtn').addEventListener('click', openModal); $('modalClose').addEventListener('click', closeModal); $('modalBackdrop').addEventListener('click', (e) => { if (e.target === $('modalBackdrop')) closeModal(); }); $('unlockLocalBtn').addEventListener('click', () => { setPro(true); closeModal(); }); $('lockLocalBtn').addEventListener('click', () => { setPro(false); closeModal(); }); $('themeSwitch').addEventListener('click', toggleTheme); $('themeSwitch').addEventListener('keydown', (e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); toggleTheme(); } }); window.addEventListener('hashchange', onRoute); // Restore state const st = loadState(); if (st?.theme) setTheme(st.theme); else setTheme('light'); if (st?.category && CATEGORIES.includes(st.category)) currentCategory = st.category; if (st?.currentKey && CALCS[st.currentKey]) location.hash = `#/${st.currentKey}`; else location.hash = location.hash || '#/roi'; $('year').textContent = String(new Date().getFullYear()); onRoute(); refreshProUI();