{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"grocery-list","type":"registry:component","title":"Grocery List","description":"Check off a small shopping list saved in this browser.","author":"Notion Arena","categories":["planning"],"tags":["widget","free","grocery","shopping list","checklist","household","food"],"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/grocery-list","fileUrl":"https://notionarena.vercel.app/widgets/grocery-list.html","downloadUrl":"https://notionarena.vercel.app/api/widget/grocery-list","height":340,"minHeight":240,"maxHeight":480,"params":[{"key":"label","label":"List title","type":"text","default":"Grocery list"},{"key":"items","label":"Starter items","type":"text","default":"Apples||Rice||Coffee","help":"Separate up to 40 item names with ||."},{"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/grocery-list.html","type":"registry:file","target":"notion/widgets/grocery-list.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>Grocery List</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;--font:ui-sans-serif,-apple-system,BlinkMacSystemFont,\"Segoe UI\",Helvetica,Arial,sans-serif}\nhtml[data-theme=\"dark\"]{--bg:#191919;--fg:#e6e6e4;--muted:#9b9a97;--surface:#252525;--border:#343434;--danger:#ff7369}\n@media(prefers-color-scheme:dark){html[data-theme=\"auto\"]{--bg:#191919;--fg:#e6e6e4;--muted:#9b9a97;--surface:#252525;--border:#343434;--danger:#ff7369}}\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}\n.wrap{height:100%;min-height:240px;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:13.5px;font-weight:650;margin-top:2px}.count{font-size:9px;color:var(--muted)}.form{display:flex;gap:5px}.form input{flex:1;min-width:0;height:30px;border:1px solid var(--border);border-radius:7px;background:var(--surface);color:var(--fg);padding:5px 7px;font:inherit;font-size:10.5px}.form input:focus-visible{outline:2px solid var(--accent);outline-offset:1px}.form button{height:30px;border:1px solid var(--accent);border-radius:7px;background:var(--accent);color:var(--accent-ink);font:inherit;font-size:10px;padding:4px 8px;cursor:pointer}.list{display:flex;min-height:70px;flex:1;flex-direction:column;gap:4px;overflow:auto}.row{display:flex;align-items:center;gap:7px;min-height:30px;padding:4px 6px;border:1px solid var(--border);border-radius:7px;background:var(--surface)}.row input{width:14px;height:14px;margin:0;accent-color:var(--accent)}.row span{flex:1;min-width:0;overflow-wrap:anywhere;font-size:10.5px}.row.done span{color:var(--muted);text-decoration:line-through}.remove{width:24px;height:23px;border:1px solid transparent;border-radius:5px;background:transparent;color:var(--muted);cursor:pointer}.remove:hover{color:var(--danger);border-color:var(--danger)}.foot{display:flex;align-items:center;justify-content:space-between;gap:8px}.status{font-size:9px;color:var(--muted)}.foot button{min-height:27px;border:1px solid var(--border);border-radius:7px;background:var(--surface);color:var(--fg);font:inherit;font-size:9px;padding:4px 7px;cursor:pointer}.foot button:hover{border-color:var(--accent)}.empty{text-align:center;padding:10px;color:var(--muted);font-size:10px}\n@media(max-width:330px){.wrap{padding:10px}.row{gap:5px}}\n@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\">Shopping list</div><div class=\"title\" id=\"title\">Grocery list</div></div><div class=\"count\" id=\"count\">0 items</div></header>\n  <form class=\"form\" id=\"form\"><label class=\"sr-only\" for=\"newItem\">Grocery item</label><input id=\"newItem\" maxlength=\"60\" placeholder=\"Add an item\" required /><button type=\"submit\">Add</button></form>\n  <section class=\"list\" id=\"list\" aria-label=\"Grocery items\"></section>\n  <div class=\"foot\"><span class=\"status\" id=\"status\" aria-live=\"polite\">List saves only in this browser.</span><button id=\"clearDone\" type=\"button\">Clear checked</button><button id=\"restore\" type=\"button\">Restore starter list</button></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;}\n  function rgb(hex){var s=String(hex).slice(1);return [0,2,4].map(function(i){return parseInt(s.slice(i,i+2),16);});}\n  function luminance(values){return values.map(function(v){v/=255;return v<=.04045?v/12.92:Math.pow((v+.055)/1.055,2.4);}).reduce(function(a,v,i){return a+v*[.2126,.7152,.0722][i];},0);}\n  function contrast(a,b){var x=luminance(a),y=luminance(b);return (Math.max(x,y)+.05)/(Math.min(x,y)+.05);}\n  function accentInk(hex){var c=rgb(hex);return contrast(c,[255,255,255])>=contrast(c,[0,0,0])?'#fff':'#000';}\n  function accentText(hex){var dark=theme==='dark'||(theme==='auto'&&window.matchMedia&&window.matchMedia('(prefers-color-scheme: dark)').matches),bg=rgb(dark?'#252525':'#f7f7f5'),base=rgb(hex),target=dark?[255,255,255]:[0,0,0];if(contrast(base,bg)>=4.5)return hex;for(var t=.02;t<=1;t+=.02){var mix=base.map(function(v,i){return Math.round(v+(target[i]-v)*t);});if(contrast(mix,bg)>=4.5)return '#'+mix.map(function(v){return v.toString(16).padStart(2,'0');}).join('');}return dark?'#fff':'#191919';}\n  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||'Grocery list').slice(0,48),defaults=String(c.items||'Apples||Rice||Coffee').split('||').map(function(x){return x.trim().slice(0,60);}).filter(Boolean).slice(0,40);\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  function hash(s){var h=5381;for(var i=0;i<s.length;i++)h=((h<<5)-h+s.charCodeAt(i))|0;return 'g'+(h>>>0).toString(36);}\n  var id=hash(LABEL+'|'+defaults.join('|')),KEY='notion-arena.grocery-list.v1',store={lists:{}},items=defaults.map(function(name){return{name:name,done:false};});\n  try{var saved=JSON.parse(localStorage.getItem(KEY)||'{}');if(saved&&saved.lists)store=saved;if(store.lists&&Array.isArray(store.lists[id]))items=store.lists[id].filter(function(x){return x&&typeof x.name==='string';}).slice(0,60).map(function(x){return{name:String(x.name).slice(0,60),done:!!x.done};});}catch(e){}\n  function save(){if(!store.lists)store.lists={};store.lists[id]=items;var keys=Object.keys(store.lists);while(keys.length>8){var old=keys.shift();if(old===id){keys.push(old);continue;}delete store.lists[old];}try{localStorage.setItem(KEY,JSON.stringify(store));}catch(e){}}\n  function render(){var list=document.getElementById('list');while(list.firstChild)list.removeChild(list.firstChild);var checked=0;items.forEach(function(item,index){if(item.done)checked++;var row=document.createElement('label'),box=document.createElement('input'),name=document.createElement('span'),remove=document.createElement('button');row.className='row'+(item.done?' done':'');box.type='checkbox';box.checked=item.done;box.setAttribute('aria-label','Mark '+item.name+' as '+(item.done?'not purchased':'purchased'));name.textContent=item.name;remove.className='remove';remove.type='button';remove.textContent='×';remove.setAttribute('aria-label','Remove '+item.name);box.addEventListener('change',function(){item.done=box.checked;save();render();});remove.addEventListener('click',function(e){e.preventDefault();e.stopPropagation();items.splice(index,1);save();render();});row.appendChild(box);row.appendChild(name);row.appendChild(remove);list.appendChild(row);});document.getElementById('count').textContent=items.length+' items · '+checked+' checked';document.getElementById('status').textContent='List saves only in this browser. No store account or sync.';if(!items.length){var empty=document.createElement('div');empty.className='empty';empty.textContent='No items. Add something above or restore the starter list.';list.appendChild(empty);}}\n  document.getElementById('form').addEventListener('submit',function(e){e.preventDefault();var input=document.getElementById('newItem'),name=input.value.trim();if(!name)return;if(items.length>=60){document.getElementById('status').textContent='List limit reached.';return;}items.push({name:name.slice(0,60),done:false});input.value='';save();render();});\n  document.getElementById('clearDone').addEventListener('click',function(){items=items.filter(function(x){return !x.done;});save();render();});\n  document.getElementById('restore').addEventListener('click',function(){items=defaults.map(function(name){return{name:name,done:false};});save();render();});render();save();\n})();\n</script>\n</body>\n</html>\n"}]}