{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"stopwatch","type":"registry:component","title":"Stopwatch Timer","description":"Elapsed time with pause, resume, and lap splits.","author":"Notion Arena","categories":["time"],"tags":["widget","free","stopwatch","timer","laps","focus","elapsed time"],"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":false,"writesToNotion":false,"needsNetwork":false},"embedUrl":"https://notionarena.vercel.app/embed/stopwatch","fileUrl":"https://notionarena.vercel.app/widgets/stopwatch.html","downloadUrl":"https://notionarena.vercel.app/api/widget/stopwatch","height":260,"minHeight":190,"maxHeight":400,"params":[{"key":"label","label":"Stopwatch title","type":"text","default":"Stopwatch"},{"key":"maxLaps","label":"Laps to keep","type":"number","default":6,"min":1,"max":10},{"key":"accent","label":"Accent","type":"color","default":"#d9730d"},{"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/stopwatch.html","type":"registry:file","target":"notion/widgets/stopwatch.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>Stopwatch</title>\n<style>\n:root{--bg:#fff;--fg:#37352f;--muted:#787774;--surface:#f7f7f5;--border:#e9e9e7;--accent:#d9730d;--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}\n@media(prefers-color-scheme:dark){html[data-theme=\"auto\"]{--bg:#191919;--fg:#e6e6e4;--muted:#9b9a97;--surface:#252525;--border:#343434}}\n*{box-sizing:border-box}html,body{margin:0;height:100%}body{background:var(--bg);color:var(--fg);font-family:var(--font);-webkit-font-smoothing:antialiased;overflow:hidden}\n.wrap{height:100%;min-height:210px;display:flex;flex-direction:column;gap:12px;padding:15px;overflow:auto}\n.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}\n.time{flex:1;display:grid;place-items:center;min-height:60px;color:var(--accent);font-size:clamp(34px,10vw,54px);font-weight:650;letter-spacing:-.055em;font-variant-numeric:tabular-nums}\n.status{min-height:14px;text-align:center;color:var(--muted);font-size:11px}.controls{display:flex;justify-content:center;gap:7px;flex-wrap:wrap}\nbutton{min-height:32px;border:1px solid var(--border);border-radius:8px;background:var(--surface);color:var(--fg);font:inherit;font-size:11.5px;font-weight:550;padding:6px 12px;cursor:pointer;transition:border-color .15s,background .15s,opacity .15s}\nbutton:hover{border-color:color-mix(in oklab,var(--accent) 45%,var(--border))}button:focus-visible{outline:2px solid var(--accent);outline-offset:2px}button:disabled{opacity:.45;cursor:not-allowed}\nbutton.primary{border-color:var(--accent);background:var(--accent);color:#fff}.laps{display:flex;flex-direction:column;gap:3px;max-height:90px;overflow:auto;margin:0;padding:0;list-style:none}\n.laps li{display:flex;justify-content:space-between;gap:10px;padding:4px 7px;border-radius:6px;background:var(--surface);font-size:10.5px;color:var(--muted);font-variant-numeric:tabular-nums}.laps b{color:var(--fg);font-weight:600}\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\">Time & focus</div><div class=\"title\" id=\"title\">Stopwatch</div></div><div class=\"status\" id=\"state\">Ready</div></header>\n  <div class=\"time\" id=\"time\" role=\"timer\" aria-live=\"off\">00:00.00</div>\n  <div class=\"controls\"><button class=\"primary\" id=\"start\" type=\"button\">Start</button><button id=\"lap\" type=\"button\" disabled>Lap</button><button id=\"reset\" type=\"button\">Reset</button></div>\n  <ol class=\"laps\" id=\"laps\" aria-label=\"Recorded laps\"></ol>\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 num(k,d){var n=parseInt(c[k],10);return isFinite(n)?n:d;}\n  var theme=['auto','light','dark'].indexOf(String(c.theme||'auto'))>-1?String(c.theme||'auto'):'auto';\n  var ACCENT=safeColor(c.accent,'#d9730d'),LABEL=String(c.label||'Stopwatch').slice(0,48),MAX_LAPS=Math.max(1,Math.min(10,num('maxLaps',6)));\n  document.documentElement.setAttribute('data-theme',theme);document.documentElement.style.setProperty('--accent',ACCENT);\n  document.getElementById('title').textContent=LABEL;document.title=LABEL;\n  var timeEl=document.getElementById('time'),stateEl=document.getElementById('state'),startBtn=document.getElementById('start'),lapBtn=document.getElementById('lap'),lapsEl=document.getElementById('laps');\n  var elapsed=0,startedAt=0,running=false,timer=null,laps=[],lapIndex=0;\n  function pad(n){return String(n).padStart(2,'0');}\n  function current(){return elapsed+(running?Date.now()-startedAt:0);}\n  function format(ms){var cs=Math.floor(Math.max(0,ms)/10),cent=cs%100,sec=Math.floor(cs/100)%60,min=Math.floor(cs/6000)%60,hour=Math.floor(cs/360000);return (hour?pad(hour)+':':'')+pad(min)+':'+pad(sec)+'.'+pad(cent);}\n  function paint(){timeEl.textContent=format(current());stateEl.textContent=running?'Running':(elapsed?'Paused':'Ready');startBtn.textContent=running?'Pause':(elapsed?'Resume':'Start');lapBtn.disabled=!running;}\n  function drawLaps(){while(lapsEl.firstChild)lapsEl.removeChild(lapsEl.firstChild);laps.forEach(function(l){var li=document.createElement('li'),label=document.createElement('span'),value=document.createElement('b');label.textContent='Lap '+l.number;value.textContent=l.time;li.appendChild(label);li.appendChild(value);lapsEl.appendChild(li);});}\n  startBtn.addEventListener('click',function(){if(running){elapsed=current();running=false;if(timer){clearInterval(timer);timer=null;}paint();return;}startedAt=Date.now();running=true;timer=setInterval(paint,30);paint();});\n  lapBtn.addEventListener('click',function(){if(!running)return;lapIndex++;var now=current(),previous=laps.length?laps[0].total:0;laps.unshift({number:lapIndex,time:format(now-previous),total:now});if(laps.length>MAX_LAPS)laps.pop();drawLaps();});\n  document.getElementById('reset').addEventListener('click',function(){running=false;elapsed=0;startedAt=0;lapIndex=0;laps=[];if(timer){clearInterval(timer);timer=null;}drawLaps();paint();});\n  paint();\n})();\n</script>\n</body>\n</html>\n"}]}