{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"savings-goal","type":"registry:component","title":"Savings Goal Tracker","description":"Compare local contributions with a target amount you set.","author":"Notion Arena","categories":["trackers"],"tags":["widget","free","savings","goal","progress","personal finance","offline"],"meta":{"kind":"widget","tier":"free","version":"1.0.0","updatedAt":"2026-10-11","notionPlan":"Free","emoji":"🎯","delivery":["embed","html-block","download"],"capabilities":{"interactive":true,"offlineSafe":true,"persistsLocally":true,"writesToNotion":false,"needsNetwork":false},"embedUrl":"https://notionarena.vercel.app/embed/savings-goal","fileUrl":"https://notionarena.vercel.app/widgets/savings-goal.html","downloadUrl":"https://notionarena.vercel.app/api/widget/savings-goal","height":360,"minHeight":275,"maxHeight":500,"params":[{"key":"label","label":"Widget title","type":"text","default":"Savings goal"},{"key":"currency","label":"Currency label","type":"text","default":"$"},{"key":"accent","label":"Accent","type":"color","default":"#0f7b6c"},{"key":"theme","label":"Theme","type":"select","default":"auto","options":[{"value":"auto","label":"Match Notion"},{"value":"light","label":"Light"},{"value":"dark","label":"Dark"}]}]},"files":[{"path":"widgets/savings-goal.html","type":"registry:file","target":"notion/widgets/savings-goal.html","content":"<!doctype html>\n<html lang=\"en\" data-theme=\"auto\">\n<head>\n<meta charset=\"utf-8\" /><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\" />\n<title>Savings Goal Tracker</title>\n<style>\n:root{--bg:#fff;--fg:#37352f;--muted:#787774;--surface:#f7f7f5;--border:#e9e9e7;--accent:#0f7b6c;--accent-ink:#fff;--accent-text:var(--accent);--danger:#c24136;--track:#e9e9e7}\nhtml[data-theme=\"dark\"]{--bg:#191919;--fg:#e6e6e4;--muted:#9b9a97;--surface:#252525;--border:#343434;--danger:#ff7369;--track:#343434}\n@media(prefers-color-scheme:dark){html[data-theme=\"auto\"]{--bg:#191919;--fg:#e6e6e4;--muted:#9b9a97;--surface:#252525;--border:#343434;--danger:#ff7369;--track:#343434}}\n*{box-sizing:border-box}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}html,body{margin:0;height:100%}body{background:var(--bg);color:var(--fg);font-family:ui-sans-serif,-apple-system,BlinkMacSystemFont,\"Segoe UI\",Helvetica,Arial,sans-serif;-webkit-font-smoothing:antialiased;overflow:hidden}.wrap{height:100%;min-height:275px;display:flex;flex-direction:column;gap:8px;padding:13px;overflow:auto}.head{display:flex;align-items:center;justify-content:space-between;gap:8px}.kicker{font-size:9px;text-transform:uppercase;letter-spacing:.1em;color:var(--muted);font-weight:650}.title{font-size:14px;font-weight:650;margin-top:2px}.totals{display:flex;gap:10px}.total{font-size:8.5px;color:var(--muted);text-align:right}.total b{display:block;color:var(--accent-text);font-size:12px;font-variant-numeric:tabular-nums}.goal-fields{display:grid;grid-template-columns:minmax(0,1fr) 120px;gap:5px}.goal-fields input,.form input{min-width:0;height:30px;border:1px solid var(--border);border-radius:7px;background:var(--surface);color:var(--fg);padding:4px 7px;font:inherit;font-size:10px}.goal-fields input:focus-visible,.form input:focus-visible{outline:2px solid var(--accent);outline-offset:1px}.progress{height:8px;border-radius:99px;background:var(--track);overflow:hidden}.progress i{display:block;width:0;height:100%;border-radius:inherit;background:var(--accent);transition:width .2s ease}.label-row{display:flex;justify-content:space-between;gap:8px;font-size:9px;color:var(--muted)}.form{display:grid;grid-template-columns:minmax(0,1fr) 92px auto;gap:5px}.form button{height:30px;border:1px solid var(--accent);border-radius:7px;background:var(--accent);color:var(--accent-ink);font:inherit;font-size:9.5px;padding:4px 8px;cursor:pointer}.form button:focus-visible,.remove:focus-visible,.clear:focus-visible{outline:2px solid var(--accent);outline-offset:2px}.list{display:flex;min-height:46px;flex:1;flex-direction:column;gap:4px;overflow:auto}.row{display:grid;grid-template-columns:minmax(0,1fr) auto 24px;align-items:center;gap:6px;padding:5px 7px;border:1px solid var(--border);border-radius:7px;background:var(--surface)}.entry-name{min-width:0;overflow-wrap:anywhere;font-size:9.5px}.entry-amount{font-size:9.5px;color:var(--accent-text);font-variant-numeric:tabular-nums}.remove{width:23px;height:23px;border:1px solid var(--border);border-radius:6px;background:var(--bg);color:var(--muted);cursor:pointer}.remove:hover{border-color:var(--danger);color:var(--danger)}.foot{display:flex;align-items:center;justify-content:space-between;gap:8px}.status,.note{font-size:9px;color:var(--muted);line-height:1.35}.clear{min-height:25px;border:1px solid var(--border);border-radius:7px;background:var(--surface);color:var(--fg);font:inherit;font-size:9px;padding:3px 7px;cursor:pointer}.empty{text-align:center;padding:8px;color:var(--muted);font-size:9.5px}@media(max-width:340px){.wrap{padding:10px}.goal-fields{grid-template-columns:minmax(0,1fr) 100px}.totals{gap:5px}}@media(prefers-reduced-motion:reduce){*,*::before,*::after{animation-duration:.01ms!important;transition-duration:.01ms!important;scroll-behavior:auto!important}}\n</style>\n</head>\n<body><main class=\"wrap\">\n  <header class=\"head\"><div><div class=\"kicker\">Personal savings estimate</div><div class=\"title\" id=\"title\">Savings goal</div></div><div class=\"totals\"><div class=\"total\">Saved<b id=\"saved\">$0.00</b></div><div class=\"total\">Remaining<b id=\"remaining\">$0.00</b></div></div></header>\n  <section class=\"goal-fields\"><label class=\"sr-only\" for=\"goalName\">Goal name</label><input id=\"goalName\" maxlength=\"48\" value=\"Savings goal\" aria-label=\"Goal name\" /><label class=\"sr-only\" for=\"target\">Target amount</label><input id=\"target\" type=\"number\" min=\"0\" max=\"100000000\" step=\"0.01\" value=\"1000\" aria-label=\"Target amount\" /></section>\n  <div class=\"label-row\"><span id=\"goalLabel\">Progress</span><span id=\"percent\">0%</span></div><div class=\"progress\" id=\"progressMeter\" role=\"progressbar\" aria-label=\"Savings goal progress\" aria-valuemin=\"0\" aria-valuemax=\"100\" aria-valuenow=\"0\"><i id=\"bar\"></i></div>\n  <form class=\"form\" id=\"form\"><label class=\"sr-only\" for=\"amount\">Contribution amount</label><input id=\"amount\" type=\"number\" min=\"0.01\" max=\"100000000\" step=\"0.01\" placeholder=\"Add contribution\" required /><label class=\"sr-only\" for=\"note\">Contribution note</label><input id=\"note\" maxlength=\"48\" placeholder=\"Note (optional)\" /><button type=\"submit\">Add</button></form>\n  <section class=\"list\" id=\"list\" aria-label=\"Savings contributions\"></section>\n  <footer class=\"foot\"><span class=\"status\" id=\"status\" aria-live=\"polite\">Values save only in this browser. No account link.</span><button class=\"clear\" id=\"clear\" type=\"button\">Clear contributions</button></footer>\n  <div class=\"note\">A manual progress calculation, not financial advice or a savings recommendation.</div>\n</main>\n<script>\n(function(){\n  var cfgEl=document.getElementById('bk-config'),base={};try{base=cfgEl?JSON.parse(cfgEl.textContent||'{}'):{};}catch(e){}\n  var q=new URLSearchParams(location.search),c={};for(var k in base)c[k]=base[k];q.forEach(function(v,k){c[k]=v;});\n  function safeColor(v,d){return /^#[0-9a-f]{6}$/i.test(String(v||''))?String(v):d;}function rgb(hex){var s=String(hex).slice(1);return[0,2,4].map(function(i){return parseInt(s.slice(i,i+2),16);});}function lum(a){return a.map(function(v){v/=255;return v<=.04045?v/12.92:Math.pow((v+.055)/1.055,2.4);}).reduce(function(s,v,i){return s+v*[.2126,.7152,.0722][i];},0);}function contrast(a,b){var x=lum(a),y=lum(b);return(Math.max(x,y)+.05)/(Math.min(x,y)+.05);}function accentInk(hex){var a=rgb(hex);return contrast(a,[255,255,255])>=contrast(a,[0,0,0])?'#fff':'#000';}function accentText(hex){var dark=theme==='dark'||(theme==='auto'&&window.matchMedia&&window.matchMedia('(prefers-color-scheme: dark)').matches),bg=rgb(dark?'#252525':'#f7f7f5'),a=rgb(hex),to=dark?[255,255,255]:[0,0,0];if(contrast(a,bg)>=4.5)return hex;for(var t=.02;t<=1;t+=.02){var m=a.map(function(v,i){return Math.round(v+(to[i]-v)*t);});if(contrast(m,bg)>=4.5)return'#'+m.map(function(v){return v.toString(16).padStart(2,'0');}).join('');}return dark?'#fff':'#191919';}function setAccentText(){document.documentElement.style.setProperty('--accent-text',accentText(ACCENT));}\n  var theme=['auto','light','dark'].indexOf(String(c.theme||'auto'))>-1?String(c.theme||'auto'):'auto',ACCENT=safeColor(c.accent,'#0f7b6c'),LABEL=String(c.label||'Savings goal').slice(0,48),CURRENCY=String(c.currency||'$').slice(0,6),KEY='notion-arena.savings-goal.v1',state={name:'Savings goal',target:1000,contributions:[]};\n  document.documentElement.setAttribute('data-theme',theme);document.documentElement.style.setProperty('--accent',ACCENT);document.documentElement.style.setProperty('--accent-ink',accentInk(ACCENT));setAccentText();if(theme==='auto'&&window.matchMedia){var scheme=window.matchMedia('(prefers-color-scheme: dark)');if(scheme.addEventListener)scheme.addEventListener('change',setAccentText);else if(scheme.addListener)scheme.addListener(setAccentText);}document.getElementById('title').textContent=LABEL;document.title=LABEL;\n  try{var saved=JSON.parse(localStorage.getItem(KEY)||'null');if(saved&&typeof saved==='object'){state.name=typeof saved.name==='string'?saved.name.slice(0,48):state.name;state.target=Math.max(0,Math.min(100000000,Number(saved.target)||0));state.contributions=Array.isArray(saved.contributions)?saved.contributions.filter(function(x){return x&&isFinite(Number(x.amount));}).slice(0,100):[];}}catch(e){}\n  var goalName=document.getElementById('goalName'),target=document.getElementById('target'),list=document.getElementById('list'),status=document.getElementById('status');goalName.value=state.name;target.value=String(state.target);function money(n){return CURRENCY+' '+Number(n||0).toLocaleString(undefined,{minimumFractionDigits:2,maximumFractionDigits:2});}function save(message){state.name=goalName.value.trim().slice(0,48)||'Savings goal';state.target=Math.max(0,Math.min(100000000,Number(target.value)||0));try{localStorage.setItem(KEY,JSON.stringify(state));status.textContent=message||'Values save only in this browser. No account link.';}catch(e){status.textContent='Storage is unavailable in this browser session.';}}\n  function render(){var total=state.contributions.reduce(function(s,x){return s+Number(x.amount||0);},0),pct=state.target>0?Math.min(100,total/state.target*100):0;document.getElementById('saved').textContent=money(total);document.getElementById('remaining').textContent=money(Math.max(0,state.target-total));document.getElementById('percent').textContent=Math.floor(pct)+'%';document.getElementById('goalLabel').textContent=state.name+' progress';document.getElementById('bar').style.width=pct+'%';document.getElementById('progressMeter').setAttribute('aria-valuenow',String(Math.round(pct)));list.textContent='';if(!state.contributions.length){var empty=document.createElement('div');empty.className='empty';empty.textContent='Add a contribution to see progress here.';list.appendChild(empty);return;}state.contributions.slice().reverse().forEach(function(item){var index=state.contributions.indexOf(item),row=document.createElement('div');row.className='row';var name=document.createElement('span');name.className='entry-name';name.textContent=item.note||'Contribution';var amount=document.createElement('span');amount.className='entry-amount';amount.textContent=money(item.amount);var remove=document.createElement('button');remove.className='remove';remove.type='button';remove.textContent='×';remove.setAttribute('aria-label','Remove '+(item.note||'contribution'));remove.addEventListener('click',function(){state.contributions.splice(index,1);save();render();});row.append(name,amount,remove);list.appendChild(row);});}\n  goalName.addEventListener('input',function(){save();render();});target.addEventListener('input',function(){save();render();});document.getElementById('form').addEventListener('submit',function(e){e.preventDefault();var amount=Number(document.getElementById('amount').value),note=document.getElementById('note').value.trim().slice(0,48);if(!isFinite(amount)||amount<=0||amount>100000000)return;if(state.contributions.length>=100){status.textContent='Contribution limit reached.';return;}state.contributions.push({amount:Math.round(amount*100)/100,note:note});document.getElementById('amount').value='';document.getElementById('note').value='';save();render();document.getElementById('amount').focus();});document.getElementById('clear').addEventListener('click',function(){state.contributions=[];save('Contributions cleared.');render();});render();\n})();\n</script>\n</body>\n</html>\n"}]}