{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"reading-tracker","type":"registry:component","title":"Reading Progress Tracker","description":"Track pages read in one book, with progress saved on this device.","author":"Notion Arena","categories":["docs"],"tags":["widget","free","reading","book","pages","progress","study"],"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/reading-tracker","fileUrl":"https://notionarena.vercel.app/widgets/reading-tracker.html","downloadUrl":"https://notionarena.vercel.app/api/widget/reading-tracker","height":250,"minHeight":205,"maxHeight":380,"params":[{"key":"book","label":"Book title","type":"text","default":"Reading progress"},{"key":"pages","label":"Total pages","type":"number","default":320,"min":1,"max":100000},{"key":"step","label":"Quick step","type":"number","default":5,"min":1,"max":1000},{"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/reading-tracker.html","type":"registry:file","target":"notion/widgets/reading-tracker.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>Reading Progress</title>\n<style>\n:root{--bg:#fff;--fg:#37352f;--muted:#787774;--surface:#f7f7f5;--border:#e9e9e7;--accent:#0f7b6c;--track:#e9e9e7;--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;--track:#343434}\n@media(prefers-color-scheme:dark){html[data-theme=\"auto\"]{--bg:#191919;--fg:#e6e6e4;--muted:#9b9a97;--surface:#252525;--border:#343434;--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}\n.wrap{height:100%;min-height:205px;display:flex;flex-direction:column;gap:11px;padding:14px;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}.badge{font-size:10px;color:var(--muted);font-variant-numeric:tabular-nums}\n.progress{display:flex;align-items:baseline;justify-content:space-between;gap:8px}.current{font-size:25px;font-weight:650;letter-spacing:-.04em;font-variant-numeric:tabular-nums;color:var(--accent)}.total{font-size:11px;color:var(--muted);font-variant-numeric:tabular-nums}.bar{height:7px;border-radius:99px;background:var(--track);overflow:hidden}.bar i{display:block;height:100%;width:0;border-radius:inherit;background:var(--accent);transition:width .3s ease}\n.controls{display:flex;align-items:center;gap:6px;flex-wrap:wrap}.page-input{width:82px;height:31px;border:1px solid var(--border);border-radius:7px;background:var(--surface);color:var(--fg);padding:4px 7px;font:inherit;font-size:11px;font-variant-numeric:tabular-nums}.page-input:focus-visible{outline:2px solid var(--accent);outline-offset:1px}\nbutton{min-height:30px;border:1px solid var(--border);border-radius:7px;background:var(--surface);color:var(--fg);font:inherit;font-size:11px;font-weight:550;padding:5px 9px;cursor:pointer;transition:border-color .15s,opacity .15s}button:hover{border-color:color-mix(in oklab,var(--accent) 45%,var(--border))}button:focus-visible{outline:2px solid var(--accent);outline-offset:2px}.step{font-variant-numeric:tabular-nums}.spacer{flex:1}.status{font-size:10px;color:var(--muted);min-height:14px}\n@media(max-width:340px){.wrap{padding:10px}.controls{gap:4px}.page-input{width:68px}}\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\">Reading progress</div><div class=\"title\" id=\"title\">Book title</div></div><div class=\"badge\" id=\"percent\">0%</div></header>\n  <div class=\"progress\"><span class=\"current\" id=\"currentDisplay\">0</span><span class=\"total\" id=\"totalDisplay\">of 320 pages</span></div>\n  <div class=\"bar\" id=\"bar\" role=\"progressbar\" aria-label=\"Book reading progress\" aria-valuemin=\"0\" aria-valuemax=\"320\" aria-valuenow=\"0\"><i id=\"fill\"></i></div>\n  <div class=\"controls\"><button class=\"step\" id=\"back\" type=\"button\" aria-label=\"Move back by the configured step\">−5</button><label class=\"sr-only\" for=\"currentInput\">Current page</label><input class=\"page-input\" id=\"currentInput\" type=\"number\" min=\"0\" value=\"0\" /><button class=\"step\" id=\"forward\" type=\"button\" aria-label=\"Advance by the configured step\">+5</button><span class=\"spacer\"></span><button id=\"reset\" type=\"button\">Start over</button></div>\n  <div class=\"status\" id=\"status\" aria-live=\"polite\">Progress saves only in this browser.</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 num(k,d){var n=parseFloat(c[k]);return isFinite(n)?n:d;}\n  function safeColor(v,d){return /^#[0-9a-f]{6}$/i.test(String(v||''))?String(v):d;}\n  var theme=['auto','light','dark'].indexOf(String(c.theme||'auto'))>-1?String(c.theme||'auto'):'auto',ACCENT=safeColor(c.accent,'#0f7b6c');\n  var BOOK=String(c.book||'Reading progress').slice(0,60),TOTAL=Math.max(1,Math.min(100000,Math.round(num('pages',320)))),STEP=Math.max(1,Math.min(1000,Math.round(num('step',5))));\n  document.documentElement.setAttribute('data-theme',theme);document.documentElement.style.setProperty('--accent',ACCENT);document.getElementById('title').textContent=BOOK;document.title=BOOK;\n  document.getElementById('back').textContent='−'+STEP;document.getElementById('forward').textContent='+'+STEP;\n  var KEY='notion-arena.reading.v1',store={books:{}};\n  function hash(s){var h=5381;for(var i=0;i<s.length;i++)h=((h<<5)-h+s.charCodeAt(i))|0;return 'b'+(h>>>0).toString(36);}\n  var bookId=hash(BOOK+'|'+TOTAL),page=0;\n  try{var parsed=JSON.parse(localStorage.getItem(KEY)||'{}');if(parsed&&parsed.books)store=parsed;}catch(e){}\n  if(store.books&&isFinite(Number(store.books[bookId])))page=Math.max(0,Math.min(TOTAL,Math.floor(Number(store.books[bookId]))));\n  function save(){if(!store.books)store.books={};store.books[bookId]=page;var ids=Object.keys(store.books);while(ids.length>12){var old=ids.shift();if(old===bookId){ids.push(old);continue;}delete store.books[old];}try{localStorage.setItem(KEY,JSON.stringify(store));}catch(e){}}\n  var input=document.getElementById('currentInput');input.max=String(TOTAL);document.getElementById('totalDisplay').textContent='of '+TOTAL+' pages';\n  function render(){page=Math.max(0,Math.min(TOTAL,Math.floor(page)||0));input.value=String(page);document.getElementById('currentDisplay').textContent=String(page);var pct=Math.round(page/TOTAL*100);document.getElementById('percent').textContent=pct+'%';document.getElementById('fill').style.width=pct+'%';var bar=document.getElementById('bar');bar.setAttribute('aria-valuemax',String(TOTAL));bar.setAttribute('aria-valuenow',String(page));document.getElementById('status').textContent=page>=TOTAL?'Finished. Nice work. Progress is saved in this browser.':(TOTAL-page)+' pages left. Progress is saved in this browser.';}\n  function setPage(value){var n=parseInt(value,10);if(!isFinite(n))n=page;page=Math.max(0,Math.min(TOTAL,n));save();render();}\n  document.getElementById('back').addEventListener('click',function(){setPage(page-STEP);});document.getElementById('forward').addEventListener('click',function(){setPage(page+STEP);});input.addEventListener('change',function(){setPage(input.value);});document.getElementById('reset').addEventListener('click',function(){page=0;save();render();});\n  render();save();\n})();\n</script>\n</body>\n</html>\n"}]}