๐Ÿ’Ž
Loading...
๐Ÿ’Ž
Smart Sheet Studio
๐Ÿ”„
Your net worth
$0
Loading...
Overview
๐Ÿ’ฐ
Total Assets
$0
๐Ÿ’ณ
Total Debt
$0
๐Ÿ“ˆ
Avg Growth
โ€”
๐ŸŽฏ
Freedom Score
0%
12-month growth
Net worth chart0 months
JanFebMarAprMayJunJulAugSepOctNovDec
Debt payoff
๐Ÿ† Debt Payoff Progress0%
Enter original debt in Settings to track progress
Recent monthsView all โ†’
MonthNet WorthChange
Log your first month โ†’
โš™๏ธ
Assets & Liabilities
Update balances. Saves to your Google Sheet.
Assets
๐ŸฆBank / CashSavings & checking
๐Ÿ Home / Real EstateEstimated value
๐Ÿ“ˆInvestmentsStocks, ETFs, 401k
โ‚ฟCrypto
๐Ÿš—VehiclesMarket value
๐Ÿ’ŽOther Assets
Total Assets$0
Liabilities
Current = today ยท Original = starting amount (once)
๐Ÿ  Mortgage
๐Ÿš— Car Loan
๐Ÿ’ณ Credit Cards
๐ŸŽ“ Student Loans
Total Debt$0

Saves directly to your private Google Sheet

๐Ÿ“…
Monthly Log
๏ผ‹
Tap + to log this month. Update once a month.
MonthNet WorthChange
Tap + to log your first month
โœ…
Monthly Checklist
0 of 5 tasks done
This month
Year tracker
๐Ÿ’ก Set a reminder on the 1st โ€” "Wealth Check-in"
๐Ÿ’ŽDashboard
โš™๏ธSettings
๐Ÿ“…Log
โœ…Checklist
async function formatSheet(sid){ const meta=await gapi('GET',`https://sheets.googleapis.com/v4/spreadsheets/${sid}`); const S={};meta.sheets.forEach(s=>{S[s.properties.title]=s.properties.sheetId;}); const SET=S['Settings'],LOG=S['Monthly Log'],DBD=S['Dashboard'],CHK=S['Monthly Checklist']; // Exact colors from original Excel const white ={red:1,green:1,blue:1}; const gray1 ={red:0.953,green:0.957,blue:0.965}; // F3F4F6 - outer margin const gray2 ={red:0.976,green:0.980,blue:0.984}; // F9FAFB - alt rows const gray3 ={red:0.973,green:0.980,blue:0.988}; // F8FAFC - subtle const navy ={red:0.118,green:0.161,blue:0.231}; // 1E293B - dark header const green_bg={red:0.925,green:0.992,blue:0.961};// ECFDF5 const red_bg ={red:1.0,green:0.945,blue:0.949}; // FFF1F2 const green_t ={red:0.020,green:0.588,blue:0.412};// 059669 const red_t ={red:0.863,green:0.149,blue:0.149};// DC2626 const black_t ={red:0.067,green:0.094,blue:0.153};// 111827 const muted_t ={red:0.612,green:0.639,blue:0.686};// 9CA3AF const gray_t ={red:0.420,green:0.447,blue:0.502};// 6B7280 const green_v ={red:0.063,green:0.725,blue:0.506};// 10B981 const red_v ={red:0.937,green:0.267,blue:0.267};// EF4444 const dk_green={red:0.024,green:0.373,blue:0.275};// 065F46 const dk_blue ={red:0.118,green:0.251,blue:0.686};// 1E40AF const dk_amber={red:0.573,green:0.251,blue:0.055};// 92400E const dk_purp ={red:0.235,green:0.204,blue:0.537};// 3C3489 const c1={red:0.820,green:0.980,blue:0.898}; // D1FAE5 step1 const c2={red:0.859,green:0.918,blue:0.996}; // DBEAFE step2 const c3={red:0.996,green:0.953,blue:0.780}; // FEF3C7 step3 const c4={red:0.929,green:0.914,blue:0.996}; // EDE9FE step4 const c5={red:0.800,green:0.984,blue:0.945}; // CCFBF1 step5 function cell(shId,r0,r1,c0,c1x,bg,fc,bold,fs){ const fmt={backgroundColor:bg}; if(fc||bold||fs){fmt.textFormat={};} if(fc)fmt.textFormat.foregroundColor=fc; if(bold!==undefined)fmt.textFormat.bold=bold; if(fs)fmt.textFormat.fontSize=fs; return {repeatCell:{range:{sheetId:shId,startRowIndex:r0,endRowIndex:r1,startColumnIndex:c0,endColumnIndex:c1x},cell:{userEnteredFormat:fmt},fields:'userEnteredFormat'}}; } function colW(shId,c0,c1x,px){return{updateDimensionProperties:{range:{sheetId:shId,dimension:'COLUMNS',startIndex:c0,endIndex:c1x},properties:{pixelSize:px},fields:'pixelSize'}};} function rowH(shId,r0,r1,px){return{updateDimensionProperties:{range:{sheetId:shId,dimension:'ROWS',startIndex:r0,endIndex:r1},properties:{pixelSize:px},fields:'pixelSize'}};} function freeze(shId,r){return{updateSheetProperties:{properties:{sheetId:shId,gridProperties:{frozenRowCount:r}},fields:'gridProperties.frozenRowCount'}};} const requests=[ // โ•โ• SETTINGS โ•โ• // Full sheet outer margin: col A and E+ cell(SET,0,25,0,1,gray1,null,false,null), cell(SET,0,25,4,6,gray1,null,false,null), // Section header: ASSETS (row 2, index 1) โ€” white bg, black text, size 14 cell(SET,1,2,1,4,white,black_t,true,14), // Empty row 3 (index 2) cell(SET,2,3,1,4,gray1,null,false,null), // Asset rows 4-9 (index 3-8): white bg, gray font ...[3,5,7].map(i=>cell(SET,i,i+1,1,4,white,gray_t,false,11)), ...[4,6,8].map(i=>cell(SET,i,i+1,1,4,white,gray_t,false,11)), // Total Assets row 10 (index 9): light green bg, green font cell(SET,9,10,1,2,green_bg,green_t,true,12), cell(SET,9,10,2,3,green_bg,green_t,true,14), cell(SET,9,10,3,4,green_bg,null,false,null), // Empty row 11 (index 10) cell(SET,10,11,1,4,gray1,null,false,null), // Liabilities header row 12 (index 11): white bg, black text, size 14 cell(SET,11,12,1,4,white,black_t,true,14), // Column labels row 13 (index 12): gray bg, muted font, size 10 cell(SET,12,13,1,4,gray2,muted_t,true,10), // Liability rows 14-18 (index 13-17): white bg, gray font ...[13,15,17].map(i=>cell(SET,i,i+1,1,4,white,gray_t,false,11)), ...[14,16].map(i=>cell(SET,i,i+1,1,4,white,gray_t,false,11)), // Total Liabilities row 19 (index 18): light red bg, red font cell(SET,18,19,1,2,red_bg,red_t,true,12), cell(SET,18,19,2,3,red_bg,red_t,true,14), cell(SET,18,19,3,4,red_bg,red_t,true,14), // Empty row 20 (index 19) cell(SET,19,20,1,4,gray1,null,false,null), // Net Worth row 21 (index 20): navy bg, white font, size 22 cell(SET,20,21,1,4,navy,white,true,22), // Column widths Settings colW(SET,0,1,20), colW(SET,1,2,220), colW(SET,2,3,160), colW(SET,3,4,160), colW(SET,4,5,20), rowH(SET,20,21,52), // โ•โ• MONTHLY LOG โ•โ• cell(LOG,0,16,0,1,gray1,null,false,null), cell(LOG,0,16,6,7,gray1,null,false,null), // Header row 2 (index 1): white bg, black bold, size 14 cell(LOG,1,2,1,6,white,black_t,true,14), // Column labels row 3 (index 2): gray bg, muted, size 10 cell(LOG,2,3,1,6,gray2,muted_t,true,10), // Month rows 4-15 (index 3-14): alternating white/gray, month name bold black ...[3,5,7,9,11,13].map(i=>cell(LOG,i,i+1,1,6,white,black_t,false,11)), ...[4,6,8,10,12,14].map(i=>cell(LOG,i,i+1,1,6,gray2,black_t,false,11)), // Net Worth column (E=col4) bold for formulas ...[3,5,7,9,11,13].map(i=>cell(LOG,i,i+1,4,5,white,black_t,true,12)), ...[4,6,8,10,12,14].map(i=>cell(LOG,i,i+1,4,5,gray2,black_t,true,12)), // Input cells C,D blue font ...[4,6,8,10,12,14].map(i=>cell(LOG,i,i+1,2,4,gray2,{red:0,green:0,blue:1},true,null)), freeze(LOG,3), colW(LOG,0,1,20), colW(LOG,1,2,130), colW(LOG,2,3,160), colW(LOG,3,4,160), colW(LOG,4,5,150), colW(LOG,5,6,150), // โ•โ• DASHBOARD โ•โ• cell(DBD,0,25,0,1,gray1,null,false,null), cell(DBD,0,25,6,7,gray1,null,false,null), // Header B2 (index 1): navy bg, white text, size 18 cell(DBD,1,2,1,5,navy,white,true,18), // Empty row 3-4 cell(DBD,2,4,1,6,gray1,null,false,null), // Metric labels row 5 (index 4): white bg, muted text cell(DBD,4,5,1,6,white,muted_t,true,10), // Metric values row 6 (index 5): white bg, green/red large font cell(DBD,5,6,1,4,white,green_t,true,24), cell(DBD,5,6,5,6,white,red_t,true,24), // Color bars row 8 (index 7) cell(DBD,7,8,1,4,{red:0.063,green:0.725,blue:0.506},null,false,null), cell(DBD,7,8,5,6,{red:0.937,green:0.267,blue:0.267},null,false,null), rowH(DBD,7,8,6), colW(DBD,0,1,20), colW(DBD,1,2,180), colW(DBD,2,3,60), colW(DBD,3,4,60), colW(DBD,4,5,20), colW(DBD,5,6,180), // โ•โ• CHECKLIST โ•โ• cell(CHK,0,26,0,1,gray1,null,false,null), cell(CHK,0,26,6,7,gray1,null,false,null), // Rows 2-4 (index 1-3): navy bg cell(CHK,1,4,1,5,navy,null,false,null), // Title row 3 (index 2): white font, size 16, bold cell(CHK,2,3,1,5,navy,white,true,16), // Green bar row 5 (index 4) cell(CHK,4,5,1,5,{red:0.063,green:0.725,blue:0.506},null,false,null), rowH(CHK,4,5,6), // Sub header row 6 (index 5): gray3 bg cell(CHK,5,6,1,5,gray3,gray_t,false,10), // Empty row 7 (index 6) cell(CHK,6,7,1,5,gray1,null,false,null), // Column labels row 8 (index 7): gray2 bg, muted font cell(CHK,7,8,1,5,gray2,muted_t,true,10), // Task rows 9-13 (index 8-12): step number colored, alternating content cell(CHK,8,9,1,2,c1,dk_green,true,12), // step 1 green cell(CHK,8,9,2,5,white,black_t,false,11), cell(CHK,9,10,1,2,c2,dk_blue,true,12), // step 2 blue cell(CHK,9,10,2,5,gray2,black_t,false,11), cell(CHK,10,11,1,2,c3,dk_amber,true,12), // step 3 yellow cell(CHK,10,11,2,5,white,black_t,false,11), cell(CHK,11,12,1,2,c4,dk_purp,true,12), // step 4 purple cell(CHK,11,12,2,5,gray2,black_t,false,11), cell(CHK,12,13,1,2,c5,dk_green,true,12), // step 5 teal cell(CHK,12,13,2,5,white,black_t,false,11), // Done column size 16 ...[8,9,10,11,12].map(i=>cell(CHK,i,i+1,4,5,i%2===0?white:gray2,muted_t,false,16)), // Tracker section cell(CHK,14,15,1,5,gray1,muted_t,true,9), colW(CHK,0,1,20), colW(CHK,1,2,40), colW(CHK,2,3,200), colW(CHK,3,4,140), colW(CHK,4,5,60), ]; await gapi('POST',`https://sheets.googleapis.com/v4/spreadsheets/${sid}:batchUpdate`,{requests}); } async function loadData(){ try{ const[a,l,log]=await Promise.all([sheetsRead(SHEET_ID,'Settings!C4:C9'),sheetsRead(SHEET_ID,'Settings!C14:D18'),sheetsRead(SHEET_ID,'Monthly Log!C4:D15')]); const g=(arr,r,c)=>parseFloat(arr[r]?.[c])||0; D={assets:{bank:g(a,0,0),home:g(a,1,0),investments:g(a,2,0),crypto:g(a,3,0),vehicles:g(a,4,0),other:g(a,5,0)},liab:{mortgage:{cur:g(l,0,0),orig:g(l,0,1)},car:{cur:g(l,1,0),orig:g(l,1,1)},cc:{cur:g(l,2,0),orig:g(l,2,1)},student:{cur:g(l,3,0),orig:g(l,3,1)},other:{cur:g(l,4,0),orig:g(l,4,1)}},log:MOS.map((m,i)=>({month:m,assets:g(log,i,0),liab:g(log,i,1)}))}; localStorage.setItem('wd_data_'+SHEET_ID,JSON.stringify(D)); bootApp(); }catch(e){TOKEN=null;document.getElementById('loading').style.display='none';document.getElementById('signin-screen').style.display='flex';toast('Session expired โ€” sign in again');} } async function syncData(){if(!TOKEN)await reAuth();toast('Syncing...');await loadData();renderAll();fillForm();toast('โœ“ Synced');} async function reAuth(){ return new Promise(res=>{ const c=google.accounts.oauth2.initTokenClient({client_id:CLIENT_ID,scope:SCOPES,prompt:'',callback:async r=>{if(!r.error){TOKEN=r.access_token;}res();}}); c.requestAccessToken(); }); } function getEmpty(){return{assets:{bank:0,home:0,investments:0,crypto:0,vehicles:0,other:0},liab:{mortgage:{cur:0,orig:0},car:{cur:0,orig:0},cc:{cur:0,orig:0},student:{cur:0,orig:0},other:{cur:0,orig:0}},log:MOS.map(m=>({month:m,assets:0,liab:0}))};} function bootApp(){document.getElementById('loading').style.display='none';document.getElementById('setup-screen').style.display='none';document.getElementById('signin-screen').style.display='none';document.getElementById('tbar').style.display='flex';renderAll();fillForm();go('dashboard');} function renderAll(){if(!D)return;renderDash();renderLog();renderChecklist();} function renderDash(){ const ta=Object.values(D.assets).reduce((a,b)=>a+b,0); const td=Object.values(D.liab).reduce((a,b)=>a+(b.cur||0),0); const to=Object.values(D.liab).reduce((a,b)=>a+(b.orig||0),0); const nw=ta-td,fr=to>0?Math.max(0,Math.min(1,(to-td)/to)):0; const fil=D.log.filter(e=>e.assets>0||e.liab>0); const nwArr=fil.map(e=>e.assets-e.liab); let avg=0;if(nwArr.length>1){const ch=nwArr.slice(1).map((v,i)=>v-nwArr[i]);avg=ch.reduce((a,b)=>a+b,0)/ch.length;} const el=document.getElementById('d-nw');el.textContent=fmt(nw);el.className='hdr-nw'+(nw<0?' neg':''); document.getElementById('d-badge').textContent=avg?fmtC(avg)+' avg/month':'Log months to see growth'; document.getElementById('d-assets').textContent=fmt(ta);document.getElementById('d-debt').textContent=fmt(td); document.getElementById('d-freedom').textContent=Math.round(fr*1000)/10+'%';document.getElementById('d-growth').textContent=avg?fmtC(Math.round(avg)):'โ€”'; const pct=Math.round(fr*1000)/10;document.getElementById('d-pct').textContent=pct+'%'; setTimeout(()=>{document.getElementById('d-pfill').style.width=pct+'%';},300); if(to>0)document.getElementById('d-psub').textContent=fmt(to-td)+' paid off ยท '+fmt(td)+' remaining of '+fmt(to); renderChart(D.log);document.getElementById('d-chart-badge').textContent=fil.length+' month'+(fil.length!==1?'s':''); const rec=fil.slice(-5).reverse();const logEl=document.getElementById('d-log'); if(!rec.length){logEl.innerHTML='
Log your first month โ†’
';return;} logEl.innerHTML=rec.map((e,i)=>{const nwv=e.assets-e.liab,chg=i'+e.month.slice(0,3)+''+fmt(nwv)+''+(chg!==null?fmtC(chg):'โ€”')+'โœ๏ธ';}).join(''); logEl.addEventListener('click',ev=>{const t=ev.target.closest('.log-edit');if(t&&t.dataset.month)editLog(t.dataset.month);}); } function renderChart(log){ const svg=document.getElementById('d-chart'),f=log.filter(e=>e.assets>0||e.liab>0); if(f.length<2){svg.innerHTML='Log 2+ months to see chart';return;} const v=f.map(e=>e.assets-e.liab),mn=Math.min(...v),mx=Math.max(...v),rng=mx-mn||1,W=340,H=100,P=6; const pts=v.map((val,i)=>[P+(i/(v.length-1))*(W-P*2),H-P-((val-mn)/rng)*(H-P*2)]); const path='M '+pts.map(p=>p[0]+','+p[1]).join(' L ');const last=pts[pts.length-1];const color=v[v.length-1]>=v[0]?'#10B981':'#EF4444'; svg.innerHTML=``; } function renderLog(){ if(!D)return;const f=D.log.filter(e=>e.assets>0||e.liab>0),el=document.getElementById('log-rows'); if(!f.length){el.innerHTML='
Tap + to log your first month
';return;} el.innerHTML=f.map((e,i)=>{const nwv=e.assets-e.liab,chg=i>0?nwv-(f[i-1].assets-f[i-1].liab):null;return'
'+e.month+''+fmt(nwv)+''+(chg!==null?fmtC(chg):'โ€”')+'โœ๏ธ
';}).join(''); el.addEventListener('click',ev=>{const t=ev.target.closest('.log-edit');if(t&&t.dataset.month)editLog(t.dataset.month);}); } function fillForm(){ if(!D)return;const a=D.assets,l=D.liab,s=(id,v)=>{const e=document.getElementById(id);if(e)e.value=v||'';}; s('f-bank',a.bank);s('f-home',a.home);s('f-invest',a.investments);s('f-crypto',a.crypto);s('f-vehicles',a.vehicles);s('f-other',a.other); s('f-mc',l.mortgage.cur);s('f-mo',l.mortgage.orig);s('f-cc2',l.car.cur);s('f-co',l.car.orig);s('f-ccc',l.cc.cur);s('f-cco',l.cc.orig);s('f-sc',l.student.cur);s('f-so',l.student.orig); calcTotals(); } function calcTotals(){ const ta=['f-bank','f-home','f-invest','f-crypto','f-vehicles','f-other'].reduce((s,id)=>s+num(id),0); const td=['f-mc','f-cc2','f-ccc','f-sc'].reduce((s,id)=>s+num(id),0); document.getElementById('f-ta').textContent=fmt(ta);document.getElementById('f-td').textContent=fmt(td); } async function saveSettings(){ if(!TOKEN)await reAuth(); const btn=document.getElementById('save-btn');btn.textContent='Saving...';btn.disabled=true; const a={bank:num('f-bank'),home:num('f-home'),investments:num('f-invest'),crypto:num('f-crypto'),vehicles:num('f-vehicles'),other:num('f-other')}; const l={mortgage:{cur:num('f-mc'),orig:num('f-mo')},car:{cur:num('f-cc2'),orig:num('f-co')},cc:{cur:num('f-ccc'),orig:num('f-cco')},student:{cur:num('f-sc'),orig:num('f-so')},other:{cur:0,orig:0}}; D.assets=a;D.liab=l;localStorage.setItem('wd_data_'+SHEET_ID,JSON.stringify(D)); try{ await sheetsWrite(SHEET_ID,'Settings!C4:C9',[[a.bank],[a.home],[a.investments],[a.crypto],[a.vehicles],[a.other]]); await sheetsWrite(SHEET_ID,'Settings!C14:D18',[[l.mortgage.cur,l.mortgage.orig],[l.car.cur,l.car.orig],[l.cc.cur,l.cc.orig],[l.student.cur,l.student.orig],[0,0]]); renderDash();btn.textContent='โœ“ Saved!';btn.style.background='var(--green2)'; setTimeout(()=>{btn.textContent='๐Ÿ’พ Save to My Google Sheet';btn.disabled=false;btn.style.background='';},2200); toast('โœ“ Saved to your Google Sheet'); }catch(e){btn.textContent='๐Ÿ’พ Save to My Google Sheet';btn.disabled=false;toast('Save failed');} } function openModal(){ const sel=document.getElementById('m-month');sel.innerHTML=MOS.map(m=>``).join(''); sel.selectedIndex=new Date().getMonth();document.getElementById('m-a').value='';document.getElementById('m-l').value=''; document.getElementById('m-nw').textContent='$0';setupPrefill();document.getElementById('modal').style.display='block'; } function editLog(month){ const e=D.log.find(x=>x.month===month);if(!e)return; const sel=document.getElementById('m-month');sel.innerHTML=MOS.map(m=>``).join(''); sel.value=month;document.getElementById('m-a').value=e.assets||'';document.getElementById('m-l').value=e.liab||'';calcM();setupPrefill();document.getElementById('modal').style.display='block'; } function setupPrefill(){ if(!D)return;const ta=Object.values(D.assets).reduce((a,b)=>a+b,0),td=Object.values(D.liab).reduce((a,b)=>a+(b.cur||0),0); const bar=document.getElementById('prefill-bar'),btn=document.getElementById('btn-use'); bar.style.display='block';btn.textContent=`Use current โ†’ Assets ${fmt(ta)} ยท Debt ${fmt(td)}`; btn.onclick=()=>{document.getElementById('m-a').value=ta;document.getElementById('m-l').value=td;calcM();hidePrefill();}; } function hidePrefill(){document.getElementById('prefill-bar').style.display='none';} function closeModal(){document.getElementById('modal').style.display='none';} function calcM(){const nw=(parseFloat(document.getElementById('m-a').value)||0)-(parseFloat(document.getElementById('m-l').value)||0);const el=document.getElementById('m-nw');el.textContent=fmt(nw);el.style.color=nw>=0?'var(--green)':'var(--red)';} async function saveLog(){ if(!TOKEN)await reAuth(); const month=document.getElementById('m-month').value; const assets=parseFloat(document.getElementById('m-a').value)||0; const liab=parseFloat(document.getElementById('m-l').value)||0; const idx=D.log.findIndex(e=>e.month===month); if(idx>=0){D.log[idx].assets=assets;D.log[idx].liab=liab;} localStorage.setItem('wd_data_'+SHEET_ID,JSON.stringify(D)); try{ const row=MOS.indexOf(month)+4; await sheetsWrite(SHEET_ID,`Monthly Log!C${row}:D${row}`,[[assets,liab]]); renderLog();renderDash();closeModal();toast('โœ“ '+month+' logged'); }catch(e){closeModal();toast('Save failed');} } function renderChecklist(){ const mo=MOS[new Date().getMonth()];document.getElementById('chk-mo-lbl').textContent=mo+"'s tasks"; document.getElementById('chk-prog').textContent=CHK.filter(Boolean).length+' of 5 tasks done'; const tasks=[['Check bank balance','Banking app'],['Check credit card','CC app'],['Update Settings','Settings'],['Log this month','Log tab'],['Review Dashboard','Dashboard']]; document.getElementById('chk-items').innerHTML=tasks.map((t,i)=>`
${t[0]}${t[1]}
`).join(''); document.getElementById('mon-grid').innerHTML=MOS.map((m,i)=>`
${m.slice(0,3)}
${MDONE.includes(i)?'โœ“':''}
`).join(''); } function toggleChk(i){CHK[i]=!CHK[i];localStorage.setItem('wd_chk_'+SHEET_ID,JSON.stringify(CHK));renderChecklist();} function toggleMonth(i){const idx=MDONE.indexOf(i);if(idx>=0)MDONE.splice(idx,1);else MDONE.push(i);localStorage.setItem('wd_md_'+SHEET_ID,JSON.stringify(MDONE));renderChecklist();} function go(tab){['dashboard','settings','log','checklist'].forEach(t=>{document.getElementById('screen-'+t).classList.remove('active');document.getElementById('tab-'+t).classList.remove('active');});document.getElementById('screen-'+tab).classList.add('active');document.getElementById('tab-'+tab).classList.add('active');if(tab==='settings')calcTotals();if(tab==='checklist')renderChecklist();} function toast(msg){const t=document.getElementById('toast');t.textContent=msg;t.classList.add('show');setTimeout(()=>t.classList.remove('show'),2800);} ['f-bank','f-home','f-invest','f-crypto','f-vehicles','f-other','f-mc','f-cc2','f-ccc','f-sc'].forEach(id=>{ window.addEventListener('DOMContentLoaded',()=>{const e=document.getElementById(id);if(e)e.addEventListener('input',calcTotals);}); }); // Make sign in button show loading state until GIS is ready function waitForGoogle(cb, tries=0){ if(typeof google!=='undefined'&&google.accounts){cb();} else if(tries<20){setTimeout(()=>waitForGoogle(cb,tries+1),300);} } window.addEventListener('DOMContentLoaded',async()=>{ document.getElementById('tbar').style.display='none'; document.getElementById('loading').style.display='none'; if(SHEET_ID&&D){ bootApp(); try{await reAuth();await loadData();renderAll();fillForm();} catch(e){ TOKEN=null; document.querySelectorAll('.screen').forEach(s=>s.classList.remove('active')); document.getElementById('tbar').style.display='none'; document.getElementById('signin-screen').style.display='flex'; } } else { document.getElementById('signin-screen').style.display='flex'; } });