{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"product-roadmap","type":"registry:component","title":"Product Roadmap","description":"Arrange product milestones by horizon, area, month, and status.","author":"Notion Arena","categories":["planning"],"tags":["widget","free","product roadmap","milestones","planning","product","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/product-roadmap","fileUrl":"https://notionarena.vercel.app/widgets/product-roadmap.html","downloadUrl":"https://notionarena.vercel.app/api/widget/product-roadmap","height":410,"minHeight":315,"maxHeight":600,"params":[{"key":"label","label":"Widget title","type":"text","default":"Product roadmap"},{"key":"accent","label":"Accent","type":"color","default":"#455dd3"},{"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/product-roadmap.html","type":"registry:file","target":"notion/widgets/product-roadmap.html","content":"<!doctype html>\n<html lang=\"en\" data-theme=\"auto\">\n<head><meta charset=\"utf-8\" /><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\" /><title>Product Roadmap</title>\n<style>\n:root{--bg:#fff;--fg:#37352f;--muted:#777672;--surface:#f7f7f5;--border:#e9e9e7;--accent:#2383e2;--accent-text:#1765a8;--accent-ink:#fff;--danger:#c24136;--soft:color-mix(in srgb,var(--accent) 10%,var(--bg))}\nhtml[data-theme=\"dark\"]{--bg:#191919;--fg:#e6e6e4;--muted:#aaa9a5;--surface:#222;--border:#363636;--danger:#ff8177}\n@media(prefers-color-scheme:dark){html[data-theme=\"auto\"]{--bg:#191919;--fg:#e6e6e4;--muted:#aaa9a5;--surface:#222;--border:#363636;--danger:#ff8177}}\n*{box-sizing:border-box}html,body{margin:0;height:100%}body{overflow:hidden;background:var(--bg);color:var(--fg);font-family:ui-sans-serif,-apple-system,BlinkMacSystemFont,\"Segoe UI\",Helvetica,Arial,sans-serif;-webkit-font-smoothing:antialiased;font-size:12px}\nbutton,input,select,textarea{font:inherit;color:inherit}button{cursor:pointer}.sr{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}\n.wrap{height:100%;min-height:275px;display:flex;flex-direction:column;gap:9px;padding:13px;overflow:auto}.head{display:flex;align-items:center;justify-content:space-between;gap:8px;min-width:0}.brand{display:flex;align-items:center;gap:8px;min-width:0}.mark{width:30px;height:30px;display:grid;place-items:center;flex:none;border-radius:8px;background:var(--soft);color:var(--accent-text)}.mark svg{width:18px;height:18px;fill:none;stroke:currentColor;stroke-width:1.65;stroke-linecap:round;stroke-linejoin:round}.kicker{font-size:9px;text-transform:uppercase;letter-spacing:.08em;color:var(--muted);font-weight:650}.title{margin-top:2px;font-size:14px;line-height:1.25;font-weight:650;overflow-wrap:anywhere}.metric{flex:none;min-width:52px;padding:5px 7px;border-radius:7px;background:var(--soft);color:var(--accent-text);text-align:center;font-size:10px;font-weight:650;font-variant-numeric:tabular-nums}\n.toolbar{display:flex;align-items:center;gap:6px;min-width:0}.toolbar>*{min-width:0}.form-grid{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:6px;align-items:end}.field{grid-column:span 4;min-width:0;display:flex;flex-direction:column;gap:3px}.field.wide{grid-column:span 6}.field.full{grid-column:1/-1}.field.third{grid-column:span 3}.field label{font-size:9px;color:var(--muted);font-weight:600}.field input,.field select,.field textarea,.search{width:100%;min-width:0;border:1px solid var(--border);border-radius:7px;background:var(--surface);color:var(--fg);padding:6px 7px;font-size:10px;outline:none}.field input,.field select,.search{height:31px}.field textarea{min-height:46px;line-height:1.4;resize:vertical}.field input:focus-visible,.field select:focus-visible,.field textarea:focus-visible,.search:focus-visible{outline:2px solid var(--accent-text);outline-offset:1px}.btn{min-height:30px;border:1px solid var(--border);border-radius:7px;background:var(--surface);color:var(--fg);padding:5px 9px;font-size:10px;font-weight:600}.btn:hover,.icon-btn:hover{border-color:var(--accent-text)}.btn.primary{border-color:var(--accent);background:var(--accent);color:var(--accent-ink)}.btn.primary:hover{filter:brightness(.96)}.btn.small{min-height:25px;padding:3px 7px;font-size:9px}.icon-btn{min-width:28px;min-height:27px;border:1px solid var(--border);border-radius:6px;background:var(--bg);color:var(--muted);padding:3px 6px;font-size:10px}.icon-btn:hover{color:var(--danger);border-color:var(--danger)}\n.list{display:flex;flex:1;min-height:55px;flex-direction:column;gap:5px;overflow:auto}.entry{display:flex;align-items:flex-start;justify-content:space-between;gap:8px;min-width:0;padding:8px;border:1px solid var(--border);border-radius:8px;background:var(--surface)}.entry-main{flex:1;min-width:0}.entry-title{font-size:11px;font-weight:650;overflow-wrap:anywhere;line-height:1.35}.sub{margin-top:3px;color:var(--muted);font-size:9px;line-height:1.4;overflow-wrap:anywhere}.details{margin-top:5px;color:var(--fg);font-size:10px;line-height:1.45;overflow-wrap:anywhere;white-space:pre-wrap}.row{display:flex;align-items:center;gap:5px;flex-wrap:wrap}.actions{display:flex;align-items:center;gap:4px;flex:none}.select-mini{max-width:125px;height:26px;border:1px solid var(--border);border-radius:6px;background:var(--bg);color:var(--fg);padding:3px 5px;font-size:9px}.chip{display:inline-flex;align-items:center;min-height:19px;padding:2px 6px;border-radius:999px;background:var(--soft);color:var(--accent-text);font-size:8px;font-weight:650;white-space:nowrap}.empty{padding:13px 8px;border:1px dashed var(--border);border-radius:8px;color:var(--muted);text-align:center;font-size:10px;line-height:1.45}.notice{color:var(--muted);font-size:9px;line-height:1.4}.footer{display:flex;align-items:flex-start;justify-content:space-between;gap:8px;border-top:1px solid var(--border);padding-top:6px}.message{color:var(--muted);font-size:9px;line-height:1.35;text-align:right}.search{flex:1}.weekbar{display:flex;align-items:center;justify-content:space-between;gap:6px}.week-label{font-size:10px;font-weight:650;text-align:center}.days{display:grid;grid-template-columns:repeat(7,minmax(0,1fr));gap:4px}.day-btn{min-width:0;border:1px solid var(--border);border-radius:7px;background:var(--surface);color:var(--muted);padding:5px 2px;font-size:8px;line-height:1.3}.day-btn strong{display:block;margin-top:2px;color:var(--fg);font-size:10px}.day-btn[aria-pressed=\"true\"]{border-color:var(--accent);background:var(--soft);color:var(--accent-text)}.day-btn[aria-pressed=\"true\"] strong{color:var(--accent-text)}.task{display:flex;align-items:flex-start;gap:7px;min-width:0}.task input{accent-color:var(--accent);margin:2px 0 0}.task.done .entry-title{text-decoration:line-through;color:var(--muted)}.progress{display:flex;align-items:center;justify-content:space-between;gap:8px;color:var(--muted);font-size:9px}.bar{height:4px;flex:1;overflow:hidden;border-radius:99px;background:var(--border)}.bar span{display:block;height:100%;background:var(--accent);border-radius:inherit}.avatar{width:28px;height:28px;display:grid;place-items:center;flex:none;border-radius:8px;background:var(--soft);color:var(--accent-text);font-size:9px;font-weight:700}.score-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:5px}.score-grid .field{grid-column:auto}.totals{display:grid;grid-template-columns:1fr auto;gap:3px 10px;align-items:center;padding:8px;border:1px solid var(--border);border-radius:8px;background:var(--surface);font-variant-numeric:tabular-nums}.total-label{color:var(--muted);font-size:9px}.total-value{font-size:10px;text-align:right}.grand{padding-top:5px;border-top:1px solid var(--border);font-weight:700;color:var(--fg)}\n@media(max-width:410px){.wrap{padding:10px;gap:7px}.field{grid-column:span 6}.field.third{grid-column:span 6}.field.full{grid-column:1/-1}.score-grid{grid-template-columns:1fr 1fr}.entry{gap:5px;padding:7px}.select-mini{max-width:110px}.footer{flex-direction:column}.message{text-align:left}.days{gap:2px}.day-btn{font-size:7px;padding:4px 1px}}\n@media(max-width:290px){.metric{min-width:42px;padding:4px;font-size:9px}.title{font-size:12px}.field{grid-column:1/-1}.score-grid{grid-template-columns:1fr}.actions{gap:2px}.btn{padding:4px 6px}}\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 class=\"brand\"><span class=\"mark\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\"><path d=\"M4 19h16M5 16l4-4 3 2 6-7\"/><circle cx=\"5\" cy=\"16\" r=\"1.5\"/><circle cx=\"9\" cy=\"12\" r=\"1.5\"/><circle cx=\"12\" cy=\"14\" r=\"1.5\"/><circle cx=\"18\" cy=\"7\" r=\"1.5\"/></svg></span><div><div class=\"kicker\">PROJECT PLANNING</div><div class=\"title\" id=\"title\">Product Roadmap</div></div></div><div class=\"metric\" id=\"metric\">0</div></header>\n<form class=\"form-grid\" id=\"form\"><div class=\"field wide\"><label for=\"milestone\">Milestone</label><input id=\"milestone\" maxlength=\"90\" placeholder=\"Outcome or release milestone\" required /></div><div class=\"field third\"><label for=\"area\">Area</label><select id=\"area\"><option>Product</option><option>Design</option><option>Engineering</option><option>Operations</option><option>Other</option></select></div><div class=\"field third\"><label for=\"horizon\">Horizon</label><select id=\"horizon\"><option>Now</option><option>Next</option><option>Later</option></select></div><div class=\"field third\"><label for=\"target\">Target month</label><input id=\"target\" type=\"month\" /></div><button class=\"btn primary field full\" type=\"submit\">Add milestone</button></form>\n<div class=\"toolbar\"><span class=\"notice\">Move status as plans change</span><span class=\"notice\" id=\"summary\"></span></div><section class=\"list\" id=\"list\" aria-label=\"Roadmap milestones\"></section>\n<footer class=\"footer\"><span class=\"notice\">A manual planning view. It does not sync with issues, releases, or external dates.</span><span class=\"message\" id=\"status\" role=\"status\" aria-live=\"polite\">Saved only in this browser.</span></footer>\n</main>\n<script>(function(){\nvar cfgEl=document.getElementById('bk-config'),base={};try{base=cfgEl?JSON.parse(cfgEl.textContent||'{}'):{};}catch(e){}\nvar q=new URLSearchParams(location.search),c={};for(var k in base)c[k]=base[k];q.forEach(function(v,k){c[k]=v;});\nfunction safeColor(v,d){return /^#[0-9a-f]{6}$/i.test(String(v||''))?String(v):d;}\nvar theme=['auto','light','dark'].indexOf(String(c.theme||'auto'))>=0?String(c.theme||'auto'):'auto';\nvar ACCENT=safeColor(c.accent,'#455dd3'),LABEL=String(c.label||'Product roadmap').slice(0,48),KEY='notion-arena.product-roadmap.v1';\ndocument.documentElement.setAttribute('data-theme',theme);document.documentElement.style.setProperty('--accent',ACCENT);document.getElementById('title').textContent=LABEL;document.title=LABEL;\nfunction rgb(h){var s=h.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(sum,v,i){return sum+v*[.2126,.7152,.0722][i];},0);}function ratio(a,b){var x=lum(a),y=lum(b);return(Math.max(x,y)+.05)/(Math.min(x,y)+.05);}function hex(a){return'#'+a.map(function(v){return Math.max(0,Math.min(255,v)).toString(16).padStart(2,'0');}).join('');}function mix(a,b,t){return a.map(function(v,i){return Math.round(v+(b[i]-v)*t);});}function setAccentText(){var dark=theme==='dark'||(theme==='auto'&&window.matchMedia&&window.matchMedia('(prefers-color-scheme: dark)').matches),bg=rgb(dark?'#191919':'#f7f7f5'),a=rgb(ACCENT),soft=mix(bg,a,.1),target=dark?[255,255,255]:[0,0,0],text=a;for(var t=.02;t<=1;t+=.02){if(ratio(text,soft)>=4.5)break;text=mix(a,target,t);}var black=[0,0,0],white=[255,255,255],ink=ratio(a,black)>=ratio(a,white)?black:white;document.documentElement.style.setProperty('--accent-text',hex(text));document.documentElement.style.setProperty('--accent-ink',hex(ink));}setAccentText();if(theme==='auto'&&window.matchMedia){var mq=window.matchMedia('(prefers-color-scheme: dark)');if(mq.addEventListener)mq.addEventListener('change',setAccentText);else if(mq.addListener)mq.addListener(setAccentText);}\nvar savedData={},items=[];try{var raw=JSON.parse(localStorage.getItem(KEY)||'{}');if(raw&&typeof raw==='object'){savedData=Array.isArray(raw)?{items:raw}:raw;}if(Array.isArray(savedData.items))items=savedData.items.filter(function(x){return x&&typeof x.id==='string';}).slice(0,200);}catch(e){}\nfunction announce(text){var n=document.getElementById('status');if(n)n.textContent=text;}\nfunction save(text){savedData.items=items;try{localStorage.setItem(KEY,JSON.stringify(savedData));announce(text||'Saved in this browser.');}catch(e){announce('Browser storage is unavailable. Changes may not persist.');}}\nfunction el(tag,cls,text){var n=document.createElement(tag);if(cls)n.className=cls;if(text!==undefined&&text!==null)n.textContent=String(text);return n;}\nfunction clean(s,max){return String(s||'').trim().slice(0,max);}\nfunction uid(){return Date.now().toString(36)+Math.random().toString(36).slice(2,8);}\nfunction bindDelete(id){var root=document.getElementById(id);root.addEventListener('click',function(e){var node=e.target;while(node&&node!==root){if(node.getAttribute){var key=node.getAttribute('data-remove');if(key){items=items.filter(function(x){return x.id!==key;});save('Entry removed.');render();return;}}node=node.parentNode;}});}\nvar order={Now:0,Next:1,Later:2};function render(){var list=document.getElementById('list');list.textContent='';var shown=items.slice().sort(function(a,b){return (order[a.horizon]||0)-(order[b.horizon]||0)||String(a.target||'9999-99').localeCompare(String(b.target||'9999-99'))||String(a.title).localeCompare(String(b.title));});document.getElementById('metric').textContent=items.length+' milestones';document.getElementById('summary').textContent=items.filter(function(x){return x.state==='Active';}).length+' active';if(!shown.length){list.appendChild(el('div','empty','Add a milestone to sketch the roadmap.'));return;}shown.forEach(function(item){var row=el('article','entry'),main=el('div','entry-main');main.appendChild(el('div','entry-title',item.title));var meta=el('div','row');meta.appendChild(el('span','chip',item.horizon));meta.appendChild(el('span','sub',item.area));meta.appendChild(el('span','sub',item.target||'No target month'));main.appendChild(meta);row.appendChild(main);var actions=el('div','actions'),sel=el('select','select-mini');['Planned','Active','Shipped','Paused'].forEach(function(v){var o=el('option','',v);o.value=v;sel.appendChild(o);});sel.value=item.state;sel.setAttribute('aria-label','Status for '+item.title);sel.setAttribute('data-state',item.id);actions.appendChild(sel);var del=el('button','icon-btn','Remove');del.type='button';del.setAttribute('data-remove',item.id);del.setAttribute('aria-label','Remove '+item.title);actions.appendChild(del);row.appendChild(actions);list.appendChild(row);});}document.getElementById('form').addEventListener('submit',function(e){e.preventDefault();var title=clean(document.getElementById('milestone').value,90);if(!title)return;if(items.length>=80){announce('Roadmap limit reached.');return;}items.push({id:uid(),title:title,area:document.getElementById('area').value,horizon:document.getElementById('horizon').value,target:document.getElementById('target').value,state:'Planned'});this.reset();save();render();document.getElementById('milestone').focus();});document.getElementById('list').addEventListener('change',function(e){var id=e.target.getAttribute('data-state');if(!id)return;var item=items.find(function(x){return x.id===id;});if(item){item.state=e.target.value;save();render();}});bindDelete('list');render();\n})();</script>\n</body></html>\n"}]}