Status Bars
Ninety-day uptime strip with a gliding tooltip, incident details and keyboard navigation.
Preview and code
index.html
<!--
Ported to plain HTML/CSS/JS for OwnlyPlans from ObsidianUI (https://github.com/Atharvsinh-codez/ObsidianUI)
MIT License — Copyright (c) 2026 ObsidianUI
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED.
-->
<div class="op-sb-stage">
<div class="op-sb-list">
<div class="op-status-bars" data-label="Component registry" data-incidents='[{"ago":70,"degraded":14,"title":"Slow registry responses"},{"ago":57,"downtime":23,"title":"Registry deploy rolled back"},{"ago":43,"degraded":9,"title":"Elevated error rate"},{"ago":24,"degraded":6,"title":"Elevated error rate"}]'></div>
<div class="op-status-bars" data-label="Docs and previews" data-incidents='[{"ago":64,"degraded":32,"title":"Search index rebuilding"},{"ago":31,"downtime":11,"title":"CDN cache purge failed"},{"ago":16,"degraded":18,"title":"Preview images loading slowly"}]'></div>
</div>
</div>
styles.css
/* Ported from ObsidianUI (MIT License, Copyright (c) 2026 ObsidianUI) */
.op-sb-stage{min-height:320px;display:grid;place-items:center;padding:3rem 1.5rem;background:#fff;color:#0a0a0a;font-family:Inter,system-ui,sans-serif;--muted:#737373;--border:#e5e5e5;--pop:#fff}
.op-sb-list{width:100%;max-width:640px;display:flex;flex-direction:column;gap:2rem}
.op-status-bars{--up:oklch(.7 .16 152);--degraded:oklch(.8 .15 78);--down:oklch(.63 .21 25);--empty:color-mix(in oklab,currentColor 9%,transparent);display:flex;flex-direction:column;width:100%;min-width:0}
.op-sb-head{margin-bottom:10px;display:flex;align-items:center;justify-content:space-between;gap:12px}
.op-sb-label{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:13px;font-weight:500;letter-spacing:-.01em}
.op-sb-now{display:flex;flex-shrink:0;align-items:center;gap:6px;font-size:12px;color:var(--muted)}
.op-sb-dotwrap{position:relative;display:grid;width:8px;height:8px;place-items:center}
.op-sb-dot{width:6px;height:6px;border-radius:999px}
.op-sb-ping{position:absolute;inset:0;border-radius:999px;animation:op-sb-ping 1.8s cubic-bezier(0,0,.2,1) infinite}
.op-sb-wrap{position:relative}
.op-sb-strip{display:flex;height:32px;gap:2px;border-radius:3px;outline:none}
.op-sb-strip:focus-visible{outline:1px solid #a3a3a3;outline-offset:3px}
.op-sb-bar{min-width:0;flex:1;transform-origin:bottom;border-radius:2px;transition:opacity 150ms ease-out,transform 150ms ease-out}
.op-sb-strip[data-hovering] .op-sb-bar{opacity:.35}
.op-sb-strip[data-hovering] .op-sb-bar[data-active]{opacity:1;transform:scaleY(1.08)}
.s-up{background:color-mix(in oklab,var(--up) 75%,transparent)}.s-degraded{background:var(--degraded)}.s-down{background:var(--down)}.s-none{background:var(--empty)}
.d-up{background:var(--up)}.d-degraded{background:var(--degraded)}.d-down{background:var(--down)}.d-none{background:color-mix(in oklab,var(--muted) 50%,transparent)}
.op-sb-tip{pointer-events:none;position:absolute;left:0;top:0;z-index:20;opacity:0;transition:opacity 140ms cubic-bezier(.22,1,.36,1),translate 140ms cubic-bezier(.22,1,.36,1);translate:0 3px}
.op-sb-tip[data-show]{opacity:1;translate:0 0}
.op-sb-tip-inner{transform:translateY(-100%);padding-bottom:10px}
.op-sb-card{width:max-content;max-width:16rem;border-radius:8px;border:1px solid var(--border);background:var(--pop);padding:8px 10px;font-size:12px;line-height:16px;box-shadow:0 10px 15px -3px rgb(0 0 0/.1),0 4px 6px -4px rgb(0 0 0/.1)}
.op-sb-row{display:flex;align-items:baseline;justify-content:space-between;gap:16px}
.op-sb-pct{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;font-variant-numeric:tabular-nums;color:var(--muted)}
.op-sb-st{margin-top:4px;display:flex;align-items:center;gap:6px;color:var(--muted)}
.op-sb-sub{color:color-mix(in oklab,var(--muted) 80%,transparent)}
.op-sb-inc{margin-top:6px;border-top:1px solid var(--border);padding-top:6px;color:var(--muted)}
.op-sb-axis{margin-top:8px;display:flex;align-items:center;gap:12px;font-size:11px;color:color-mix(in oklab,var(--muted) 80%,transparent)}
.op-sb-axis span{flex-shrink:0;font-variant-numeric:tabular-nums}.op-sb-axis i{height:1px;flex:1;background:var(--border)}
.op-sb-axis b{font-weight:400;color:var(--muted)}
@keyframes op-sb-ping{0%{opacity:.55;transform:scale(1)}75%,100%{opacity:0;transform:scale(2.2)}}
@media (prefers-reduced-motion:reduce){.op-sb-ping{animation:none}.op-sb-bar,.op-sb-tip{transition:none}}
script.js
(function(){
var DAY=864e5,WORD={up:'Operational',degraded:'Degraded',down:'Outage',none:'No data'},CUR={up:'Operational',degraded:'Degraded performance',down:'Outage',none:'No data'};
function iso(t){return new Date(t).toISOString().slice(0,10)}
function parse(s){return Date.UTC(+s.slice(0,4),+s.slice(5,7)-1,+s.slice(8,10))}
function status(d){if(d.status)return d.status;if((d.downtime||0)>0)return 'down';if((d.degraded||0)>0)return 'degraded';return 'up'}
function up(d){return d.uptime!=null?d.uptime:Math.max(0,100-((d.downtime||0)/1440)*100)}
function dur(m){return m>=60?Math.floor(m/60)+'h'+(m%60?' '+(m%60)+'m':''):Math.max(1,Math.round(m))+'m'}
var loc=Intl.DateTimeFormat().resolvedOptions().locale;
var fShort=new Intl.DateTimeFormat(loc,{weekday:'short',month:'short',day:'numeric',timeZone:'UTC'}),fAxis=new Intl.DateTimeFormat(loc,{month:'short',day:'numeric',timeZone:'UTC'}),fLong=new Intl.DateTimeFormat(loc,{weekday:'long',month:'long',day:'numeric',timeZone:'UTC'}),fPct=new Intl.NumberFormat(loc,{maximumFractionDigits:2});
function pct(v){return fPct.format(Math.floor(v*100)/100)+'%'}
document.querySelectorAll('.op-status-bars').forEach(function(root){
var label=root.dataset.label,today=parse(iso(Date.now())),inc={};
JSON.parse(root.dataset.incidents||'[]').forEach(function(i){var d=iso(today-i.ago*DAY);inc[d]={date:d,degraded:i.degraded,downtime:i.downtime,incidents:[{title:i.title,minutes:i.degraded||i.downtime}]}});
var days=[];for(var t=today-89*DAY;t<=today;t+=DAY){var d=iso(t);days.push(inc[d]||{date:d})}
var now=status(days[days.length-1]);
root.innerHTML='<div class="op-sb-head"><span class="op-sb-label"></span><span class="op-sb-now"><span class="op-sb-dotwrap" aria-hidden="true">'+((now==='down'||now==='degraded')?'<span class="op-sb-ping d-'+now+'"></span>':'')+'<span class="op-sb-dot d-'+now+'"></span></span>'+CUR[now]+'</span></div><div class="op-sb-wrap"><div class="op-sb-strip" role="grid" tabindex="0"></div><div class="op-sb-tip" aria-hidden="true"><div class="op-sb-tip-inner"><div class="op-sb-card"></div></div></div></div><div class="op-sb-axis"><span class="a1"></span><i></i><b class="a2"></b><i></i><span>Today</span></div>';
root.querySelector('.op-sb-label').textContent=label;
var wrap=root.querySelector('.op-sb-wrap'),strip=root.querySelector('.op-sb-strip'),tip=root.querySelector('.op-sb-tip'),tipIn=root.querySelector('.op-sb-tip-inner'),card=root.querySelector('.op-sb-card');
var shown=[],active=null,pinned=false,kb=false,tx=0,cur=0,vel=0,raf=0,visible=false,width=0,gap=2;
function render(){
width=wrap.clientWidth;var fits=width?Math.floor((width+gap)/(3+gap)):90;var max=days.length;
var count=[max,60,30].filter(function(n){return n<=max}).find(function(n){return n<=fits})||Math.max(1,fits);
var tail=days.slice(-count);shown=[];for(var i=0;i<count-tail.length;i++)shown.push({date:'',status:'none'});shown=shown.concat(tail);
strip.innerHTML='';shown.forEach(function(d,i){var b=document.createElement('div');var s=d.date?status(d):'none';b.className='op-sb-bar s-'+s;b.setAttribute('role','gridcell');b.setAttribute('aria-label',describe(d));strip.appendChild(b)});
var m=shown.filter(function(d){return d.date&&status(d)!=='none'}),u=m.length?m.reduce(function(a,d){return a+up(d)},0)/m.length:null;
strip.setAttribute('aria-label',label+', last '+count+' days'+(u!==null?', '+pct(u)+' uptime':''));
root.querySelector('.a1').textContent=shown[0].date?fAxis.format(parse(shown[0].date)):count+' days ago';
root.querySelector('.a2').textContent=u===null?'No data yet':pct(u)+' uptime';
}
function describe(d){if(!d.date)return 'No data';var s=status(d),p=[fLong.format(parse(d.date)),WORD[s]];if(s!=='none')p.push(pct(up(d))+' uptime');(d.incidents||[]).forEach(function(i){p.push(i.minutes?i.title+' for '+dur(i.minutes):i.title)});return p.join(', ')}
function paintTip(){var sh=-Math.min(1,Math.max(0,cur/(width||1)))*100;tip.style.transform='translateX('+cur+'px)';tipIn.style.transform='translate('+sh+'%,-100%)'}
function spring(){var k=520,c=42,m=.6,dt=1/60;var a=(-k*(cur-tx)-c*vel)/m;vel+=a*dt;cur+=vel*dt;paintTip();if(Math.abs(cur-tx)>.1||Math.abs(vel)>.1)raf=requestAnimationFrame(spring);else{cur=tx;paintTip();raf=0}}
function point(i,instant){tx=((i+.5)*(width+gap))/shown.length-gap/2;if(instant||!visible||matchMedia('(prefers-reduced-motion: reduce)').matches){cur=tx;vel=0;paintTip()}else if(!raf)raf=requestAnimationFrame(spring);visible=true;setActive(i)}
function setActive(i){if(active!==null&&strip.children[active])strip.children[active].removeAttribute('data-active');active=i;
if(i===null){strip.removeAttribute('data-hovering');tip.removeAttribute('data-show');return}
strip.setAttribute('data-hovering','');strip.children[i].setAttribute('data-active','');
var d=shown[i],s=d.date?status(d):'none',h='<div class="op-sb-row"><span style="font-weight:500">'+(d.date?fShort.format(parse(d.date)):'Before tracking')+'</span>'+(s!=='none'?'<span class="op-sb-pct">'+pct(up(d))+'</span>':'')+'</div><div class="op-sb-st"><span class="op-sb-dot d-'+s+'" style="flex-shrink:0"></span>'+WORD[s]+'</div>';
if(s==='up'&&!(d.incidents||[]).length)h+='<div class="op-sb-sub" style="margin-top:4px">No incidents</div>';
(d.incidents||[]).forEach(function(x){h+='<div class="op-sb-inc">'+x.title.replace(/</g,'<')+(x.minutes?'<span class="op-sb-sub"> · '+dur(x.minutes)+'</span>':'')+'</div>'});
card.innerHTML=h;tip.setAttribute('data-show','');}
function hide(){visible=false;setActive(null);pinned=false;kb=false}
function idx(e){var r=strip.getBoundingClientRect();return Math.min(shown.length-1,Math.max(0,Math.floor(((e.clientX-r.left)/r.width)*shown.length)))}
strip.addEventListener('pointermove',function(e){if(e.pointerType!=='mouse'||pinned)return;var i=idx(e);if(i!==active)point(i,false)});
strip.addEventListener('pointerdown',function(e){if(e.pointerType==='mouse')return;var i=idx(e);if(pinned&&i===active)return hide();point(i,true);pinned=true});
wrap.addEventListener('pointerleave',function(e){if(e.pointerType==='mouse'&&!pinned&&!kb)hide()});
document.addEventListener('pointerdown',function(e){if(pinned&&!wrap.contains(e.target)){visible=false;setActive(null);pinned=false}});
strip.addEventListener('focus',function(){if(!strip.matches(':focus-visible'))return;kb=true;point(active!=null?active:shown.length-1,true)});
strip.addEventListener('blur',hide);
strip.addEventListener('keydown',function(e){if(e.key==='Escape')return hide();var l=shown.length-1,f=active!=null?active:l,to={ArrowLeft:Math.max(0,f-1),ArrowRight:Math.min(l,f+1),Home:0,End:l,PageUp:Math.max(0,f-7),PageDown:Math.min(l,f+7)}[e.key];if(to==null)return;e.preventDefault();kb=true;point(to,true)});
new ResizeObserver(function(){var a=active;render();if(a!=null&&a<shown.length)point(a,true)}).observe(wrap);
});
})();
About
Ninety-day uptime strip with a gliding tooltip, incident details and keyboard navigation. Ported to plain HTML, CSS and JavaScript from ObsidianUI (github.com/Atharvsinh-codez/ObsidianUI), which is released under the MIT License. The original copyright notice is kept in the code.