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();