{
    "id": "bhaskar/active-sessions",
    "kind": "component",
    "title": "Active Sessions",
    "summary": "Signed-in devices list with live status, per-device sign out, a confirm step for signing out everywhere and smooth row exits.",
    "author": "Admin",
    "stars": 0,
    "url": "https://ownlyplans.com/bhaskar/active-sessions/",
    "stack": [
        "CSS",
        "HTML",
        "JavaScript"
    ],
    "tools": [
        "claude-code",
        "cursor",
        "lovable"
    ],
    "difficulty": "beginner",
    "license": "mit",
    "version": "1.0",
    "overview": "Signed-in devices list with live status, per-device sign out, a confirm step for signing out everywhere and smooth row exits. 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.",
    "steps": [],
    "files": [],
    "install": "",
    "how_to_use": "Use the component code (html, css, js, or react) directly, or run the install command.",
    "component": {
        "category": "Feedback",
        "html": "<!--\n  Ported to plain HTML/CSS/JS for OwnlyPlans from ObsidianUI (https://github.com/Atharvsinh-codez/ObsidianUI)\n  MIT License — Copyright (c) 2026 ObsidianUI\n  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated\n  documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation\n  the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n  permit persons to whom the Software is furnished to do so, subject to the following conditions:\n  The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n  THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED.\n-->\n<div class=\"op-as-stage\">\n  <div class=\"op-as-wrap\">\n    <section class=\"op-active-sessions\" aria-labelledby=\"op-as-h\">\n      <header class=\"op-as-header\">\n        <div class=\"op-as-titles\"><h3 id=\"op-as-h\" tabindex=\"-1\">Active sessions</h3><p>Devices signed in to your account. Sign out any you don’t recognize.</p></div>\n        <div class=\"op-as-action\"></div>\n      </header>\n      <p class=\"op-as-alert\" role=\"alert\" hidden></p>\n      <div class=\"op-as-card\"><ul class=\"op-as-list\" aria-labelledby=\"op-as-h\"></ul><p class=\"op-as-empty\" hidden><span></span></p></div>\n      <span class=\"op-as-sr\" role=\"status\" aria-live=\"polite\"></span>\n    </section>\n    <button type=\"button\" class=\"op-as-restore\" hidden><svg viewBox=\"0 0 24 24\" width=\"13\" height=\"13\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M3 12a9 9 0 1 0 3-6.7L3 8\"/><path d=\"M3 3v5h5\"/></svg>Restore demo sessions</button>\n  </div>\n</div>",
        "css": "/* Ported from ObsidianUI (MIT License, Copyright (c) 2026 ObsidianUI) */\n.op-as-stage{min-height:320px;display:grid;place-items:center;padding:2.5rem 1.25rem;background:#fff;color:#0a0a0a;font-family:Inter,system-ui,sans-serif;--fg:#0a0a0a;--muted:#737373;--border:#e5e5e5;--card:#fff;--accent:#f5f5f5}\n.op-as-wrap{display:flex;width:100%;max-width:36rem;flex-direction:column;align-items:center;gap:.75rem}\n.op-active-sessions{--success:oklch(.52 .13 152);--warning:oklch(.52 .12 65);--danger:oklch(.55 .2 27);--danger-solid:oklch(.577 .215 27);container-type:inline-size;display:flex;width:100%;min-width:0;flex-direction:column;gap:.75rem}\n.op-as-header{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:10px 16px;padding:0 2px}\n@container (max-width:30rem){.op-as-header{grid-template-columns:1fr}.op-as-action{justify-self:start!important}.op-as-sep-status{display:none}.op-as-status{flex-basis:100%}}\n.op-as-titles{display:flex;min-width:0;flex-direction:column;gap:2px}\n.op-as-titles h3{margin:0;border-radius:2px;font-size:14px;font-weight:500;line-height:20px;letter-spacing:-.015em;outline:none}\n.op-as-titles p{margin:0;font-size:12.5px;line-height:18px;color:var(--muted);text-wrap:pretty}\n.op-as-action{display:flex;min-width:0;align-items:center;gap:8px;justify-self:end}\n.op-as-action>*{animation:op-as-in .2s cubic-bezier(.22,1,.36,1)}\n@keyframes op-as-in{from{opacity:0;transform:translateY(4px);filter:blur(2px)}to{opacity:1;transform:none;filter:none}}\n.op-as-confirm{display:flex;flex-wrap:wrap;align-items:center;gap:8px}\n.op-as-confirm span{font-size:12.5px;color:var(--muted)}\n.op-as-alert{margin:0;border-radius:8px;background:color-mix(in oklab,var(--danger) 10%,transparent);padding:8px 12px;font-size:12.5px;line-height:18px;color:var(--danger)}\n.op-as-card{overflow:hidden;border-radius:12px;border:1px solid var(--border);background:var(--card);box-shadow:0 1px 2px 0 rgb(0 0 0/.05)}\n.op-as-list{display:flex;flex-direction:column;margin:0;padding:0;list-style:none}\n.op-as-list li{overflow:hidden;border-top:1px solid var(--border)}\n.op-as-list li:first-child{border-top:0}\n.op-as-row{display:flex;min-width:0;align-items:flex-start;gap:12px;padding:12px 16px}\n.op-as-icon{position:relative;margin-top:2px;display:grid;width:32px;height:32px;flex-shrink:0;place-items:center;border-radius:8px;background:color-mix(in oklab,var(--fg) 4%,transparent);color:color-mix(in oklab,var(--fg) 75%,transparent);box-shadow:inset 0 0 0 1px var(--border)}\n.op-as-icon svg{width:16px;height:16px}\n.op-as-main{display:flex;min-width:0;flex:1;flex-direction:column;gap:2px}\n.op-as-title{margin:0;display:flex;min-width:0;flex-wrap:wrap;align-items:center;gap:4px 8px;font-size:13px;font-weight:500;line-height:20px}\n.op-as-pill{border-radius:999px;background:color-mix(in oklab,var(--fg) 7%,transparent);padding:1px 6px;font-size:11px;font-weight:500;line-height:16px;color:color-mix(in oklab,var(--fg) 75%,transparent)}\n.op-as-flag{border-radius:999px;background:color-mix(in oklab,var(--warning) 12%,transparent);padding:1px 6px;font-size:11px;font-weight:500;line-height:16px;color:var(--warning)}\n.op-as-meta{margin:0;display:flex;min-width:0;flex-wrap:wrap;align-items:center;gap:0 6px;font-size:12px;line-height:16px;color:var(--muted)}\n.op-as-place{display:flex;min-width:0;max-width:100%;align-items:center;gap:0 6px;overflow:hidden;white-space:nowrap}\n.op-as-ip{min-width:0;overflow:hidden;text-overflow:ellipsis;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;letter-spacing:.01em}\n.op-as-sep{color:color-mix(in oklab,var(--muted) 60%,transparent)}\n.op-as-status{display:inline-flex;align-items:center;gap:6px;color:var(--success)}\n.op-as-dot{position:relative;display:grid;width:6px;height:6px;place-items:center}\n.op-as-dot i{position:absolute;inset:0;border-radius:999px;background:color-mix(in oklab,var(--success) 70%,transparent);animation:op-as-ping 1.8s cubic-bezier(0,0,.2,1) infinite}\n.op-as-dot b{width:6px;height:6px;border-radius:999px;background:var(--success)}\n.op-as-time{font-variant-numeric:tabular-nums}\n.op-as-err{margin:0;padding-top:4px;font-size:12px;line-height:16px;color:var(--danger)}\n.op-as-btn{position:relative;display:inline-grid;height:28px;flex-shrink:0;user-select:none;place-items:center;border-radius:6px;padding:0 10px;font:inherit;font-size:12.5px;font-weight:500;outline:none;border:0;background:none;cursor:pointer;transition:background-color .15s,border-color .15s,color .15s,scale .15s}\n.op-as-btn:active{scale:.97;transition-duration:75ms}.op-as-btn[aria-busy]:active{scale:1}\n.op-as-btn:focus-visible{outline:2px solid #a3a3a3;outline-offset:2px}\n.op-as-btn>*{grid-column:1;grid-row:1}\n.op-as-btn .lbl{transition:opacity .15s}.op-as-btn[data-slow] .lbl{opacity:0}\n.op-as-btn .spin{display:none;place-items:center}.op-as-btn[data-slow] .spin{display:grid}\n.op-as-btn .spin svg{width:14px;height:14px;animation:op-as-spin 1s linear infinite}\n.op-as-secondary{border:1px solid var(--border);background:var(--card);color:var(--fg);box-shadow:0 1px 2px 0 rgb(0 0 0/.05)}\n.op-as-secondary:enabled:hover{background:var(--accent)}.op-as-secondary:disabled{opacity:.5;cursor:default}\n.op-as-ghost{margin:2px -6px 0 0;color:var(--muted)}\n.op-as-ghost:not([aria-busy]):hover{background:color-mix(in oklab,var(--danger) 10%,transparent);color:var(--danger)}\n.op-as-danger{background:var(--danger-solid);color:#fff;box-shadow:0 1px 2px 0 rgb(0 0 0/.05)}\n.op-as-danger:hover{background:color-mix(in oklab,var(--danger-solid) 90%,transparent)}.op-as-danger:disabled{opacity:.6}\n.op-as-empty{margin:0;overflow:hidden;font-size:12.5px;color:var(--muted)}\n.op-as-empty span{display:block;padding:12px 16px}.op-as-empty[data-border] span{border-top:1px solid var(--border)}\n.op-as-wrap [hidden]{display:none!important}\n.op-as-sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}\n.op-as-restore{display:inline-flex;height:28px;align-items:center;gap:6px;border-radius:6px;border:0;background:none;padding:0 10px;font:inherit;font-size:12.5px;font-weight:500;color:var(--muted);cursor:pointer;transition:color .15s,background-color .15s}\n.op-as-restore:hover{background:color-mix(in oklab,var(--fg) 6%,transparent);color:var(--fg)}\n@keyframes op-as-ping{0%{opacity:.6;transform:scale(1)}75%,100%{opacity:0;transform:scale(2.4)}}\n@keyframes op-as-spin{to{transform:rotate(360deg)}}\n@media (prefers-reduced-motion:reduce){.op-as-dot i{animation:none}.op-as-action>*{animation:none}}",
        "js": "(function(){\nvar MIN=6e4,ICONS={laptop:'<path d=\"M20 16V7a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v9m16 0H4m16 0 1.28 2.55a1 1 0 0 1-.9 1.45H3.62a1 1 0 0 1-.9-1.45L4 16\"/>',desktop:'<rect width=\"20\" height=\"14\" x=\"2\" y=\"3\" rx=\"2\"/><path d=\"M8 21h8M12 17v4\"/>',phone:'<rect width=\"14\" height=\"20\" x=\"5\" y=\"2\" rx=\"2\"/><path d=\"M12 18h.01\"/>',tablet:'<rect width=\"16\" height=\"20\" x=\"4\" y=\"2\" rx=\"2\"/><path d=\"M12 18h.01\"/>'};\nvar SPIN='<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" aria-hidden=\"true\"><path d=\"M21 12a9 9 0 1 1-6.22-8.56\"/></svg>';\nvar NETS=['192.0.2','198.51.100','203.0.113'];function ip(){return NETS[Math.floor(Math.random()*3)]+'.'+(1+Math.floor(Math.random()*254))}\nfunction demo(now){return[{id:'macbook',device:'laptop',browser:'Arc',os:'macOS',location:'Ahmedabad, India',ip:ip(),last:now,current:true},{id:'win',device:'desktop',browser:'Chrome',os:'Windows 11',location:'Mumbai, India',ip:ip(),last:now-2*MIN},{id:'linux',device:'laptop',browser:'Firefox',os:'Linux',location:'Berlin, Germany',ip:ip(),last:now-1560*MIN,flag:'New location'},{id:'android',device:'phone',browser:'Chrome',os:'Android 15',location:'Pune, India',ip:ip(),last:now-180*MIN}]}\nvar rtf=new Intl.RelativeTimeFormat(undefined,{numeric:'auto'}),sd=new Intl.DateTimeFormat(undefined,{day:'numeric',month:'short'}),fd=new Intl.DateTimeFormat(undefined,{dateStyle:'medium',timeStyle:'short'});\nfunction rel(t,now){var s=Math.round((t-now)/1000),d=Math.abs(s);if(d<3600)return rtf.format(Math.round(s/60),'minute');if(d<86400)return rtf.format(Math.round(s/3600),'hour');if(d<604800)return rtf.format(Math.round(s/86400),'day');return sd.format(t)}\nfunction esc(s){return String(s).replace(/[&<>\"]/g,function(c){return{'&':'&amp;','<':'&lt;','>':'&gt;','\"':'&quot;'}[c]})}\nfunction label(s){return s.name||[s.browser,s.os].filter(Boolean).join(' on ')||'Unknown device'}\nfunction noun(n){return n===1?'session':'sessions'}\nvar wait=function(ms){return new Promise(function(r){setTimeout(r,ms)})};\ndocument.querySelectorAll('.op-as-wrap').forEach(function(wrap){\n  var root=wrap.querySelector('.op-active-sessions'),list=root.querySelector('.op-as-list'),action=root.querySelector('.op-as-action'),alertEl=root.querySelector('.op-as-alert'),empty=root.querySelector('.op-as-empty'),sr=root.querySelector('.op-as-sr'),heading=root.querySelector('h3'),restore=wrap.querySelector('.op-as-restore');\n  var reduce=matchMedia('(prefers-reduced-motion: reduce)').matches,sessions=demo(Date.now()),pending={},confirming=false,revokingOthers=false;\n  function sorted(){return sessions.slice().sort(function(a,b){return (+!!b.current)-(+!!a.current)||b.last-a.last})}\n  function others(){return sessions.filter(function(s){return!s.current})}\n  function btn(variant,text,attrs){return '<button type=\"button\" class=\"op-as-btn op-as-'+variant+'\" '+(attrs||'')+'><span class=\"lbl\">'+text+'</span><span class=\"spin\">'+SPIN+'</span></button>'}\n  function setPending(b,on){if(!b)return;if(on){b.setAttribute('aria-busy','true');b._t=setTimeout(function(){b.setAttribute('data-slow','')},150)}else{clearTimeout(b._t);b.removeAttribute('aria-busy');b.removeAttribute('data-slow')}}\n  function rowHTML(s){var now=Date.now(),active=s.current||now-s.last<5*MIN,place=s.location||s.ip,lab=label(s);\n    return '<div class=\"op-as-row\"><span class=\"op-as-icon\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linecap=\"round\" stroke-linejoin=\"round\">'+ICONS[s.device]+'</svg></span><div class=\"op-as-main\"><p class=\"op-as-title\"><span>'+esc(lab)+'</span>'+(s.current?'<span class=\"op-as-pill\">This device</span>':'')+(s.flag?'<span class=\"op-as-flag\">'+esc(s.flag)+'</span>':'')+'</p>'+\n    '<p class=\"op-as-meta\">'+(place?'<span class=\"op-as-place\" title=\"'+esc([s.location,s.ip].filter(Boolean).join(' · '))+'\">'+(s.location?'<span>'+esc(s.location)+'</span>':'')+(s.location&&s.ip?'<span class=\"op-as-sep\">·</span>':'')+(s.ip?'<span class=\"op-as-ip\">'+esc(s.ip)+'</span>':'')+'</span><span class=\"op-as-sep op-as-sep-status\">·</span>':'')+\n    (active?'<span class=\"op-as-status\"><span class=\"op-as-dot\" aria-hidden=\"true\"><i></i><b></b></span>Active now</span>':'<time class=\"op-as-time op-as-status-time\" datetime=\"'+new Date(s.last).toISOString()+'\" title=\"'+fd.format(s.last)+'\">Last active '+rel(s.last,now)+'</time>')+'</p><div class=\"op-as-errs\"></div></div>'+\n    (s.current?'':btn('ghost','Sign out','aria-label=\"Sign out '+esc(lab)+(s.location?', '+esc(s.location):'')+'\" data-revoke=\"'+s.id+'\"'))+'</div>'}\n  function renderList(){list.innerHTML='';sorted().forEach(function(s){var li=document.createElement('li');li.dataset.session=s.id;li.innerHTML=rowHTML(s);list.appendChild(li)});renderEmpty()}\n  function renderEmpty(){var o=others().length;if(o===0){empty.hidden=false;empty.querySelector('span').textContent=sessions.length?'No other sessions. You’re only signed in here.':'No active sessions.';if(sessions.length)empty.setAttribute('data-border','');restore.hidden=false}else{empty.hidden=true;restore.hidden=true}}\n  function renderAction(focusConfirm){var o=others().length;if(o===0){action.innerHTML='';return}\n    if(confirming){action.innerHTML='<div class=\"op-as-confirm\" role=\"group\" aria-label=\"Confirm signing out other sessions\"><span>Sign out '+o+' other '+noun(o)+'?</span>'+btn('secondary','Cancel','data-cancel')+btn('danger','Sign out','data-confirm')+'</div>';var c=action.querySelector('[data-confirm]');c.focus({preventScroll:true})}\n    else{action.innerHTML=btn('secondary','Sign out other sessions','data-others'+(Object.keys(pending).length?' disabled':''));if(focusConfirm)action.querySelector('button').focus({preventScroll:true})}}\n  function exitRow(li,delay){return new Promise(function(res){if(reduce){li.animate([{opacity:1},{opacity:0}],{duration:120,delay:delay*500,fill:'forwards'}).onfinish=function(){li.remove();res()};return}\n    var h=li.offsetHeight;li.animate([{opacity:1},{opacity:0}],{duration:140,delay:delay*1000,fill:'forwards'});li.animate([{transform:'none'},{transform:'translateX(8px)'}],{duration:180,delay:delay*1000,easing:'cubic-bezier(.55,0,1,.45)',fill:'forwards'});\n    li.animate([{height:h+'px'},{height:'0px'}],{duration:260,delay:(delay+.06)*1000,easing:'cubic-bezier(.65,0,.35,1)',fill:'forwards'}).onfinish=function(){li.remove();res()}})}\n  function revealEmpty(){if(others().length)return;renderEmpty();if(!reduce)empty.animate([{opacity:0,height:'0px'},{opacity:1,height:empty.scrollHeight+'px'}],{duration:440,easing:'cubic-bezier(.65,0,.35,1)',delay:200})}\n  list.addEventListener('click',function(e){var b=e.target.closest('[data-revoke]');if(!b||b.hasAttribute('aria-busy'))return;var id=b.dataset.revoke,s=sessions.find(function(x){return x.id===id});var li=b.closest('li');li.querySelector('.op-as-errs').innerHTML='';\n    pending[id]=1;setPending(b,true);renderAction();\n    wait(650).then(function(){delete pending[id];var wasFocused=document.activeElement===b;var o=sorted().filter(function(x){return!x.current}),i=o.indexOf(s),next=(o[i+1]||o[i-1]);\n      sessions=sessions.filter(function(x){return x.id!==id});sr.textContent='Signed out '+label(s);renderAction();\n      exitRow(li,0).then(function(){revealEmpty();if(wasFocused){var nb=next&&list.querySelector('[data-revoke=\"'+next.id+'\"]');(nb||heading).focus({preventScroll:true})}})})});\n  action.addEventListener('click',function(e){\n    if(e.target.closest('[data-others]')){confirming=true;renderAction();return}\n    if(e.target.closest('[data-cancel]')){if(revokingOthers)return;confirming=false;renderAction(true);return}\n    var c=e.target.closest('[data-confirm]');if(c&&!revokingOthers){revokingOthers=true;setPending(c,true);action.querySelector('[data-cancel]').disabled=true;\n      var targets=sorted().filter(function(x){return!x.current});targets.forEach(function(s){var rb=list.querySelector('[data-revoke=\"'+s.id+'\"]');if(rb)setPending(rb,true)});\n      wait(900).then(function(){sessions=sessions.filter(function(x){return x.current});sr.textContent='Signed out '+targets.length+' other '+noun(targets.length);revokingOthers=false;confirming=false;\n        var fades=action.firstElementChild;if(fades&&!reduce)fades.animate([{opacity:1},{opacity:0,transform:'translateY(-4px)',filter:'blur(2px)'}],{duration:120,fill:'forwards'});setTimeout(function(){renderAction()},120);\n        Promise.all(targets.map(function(s,i){var li=list.querySelector('[data-session=\"'+s.id+'\"]');return li?exitRow(li,Math.min(i,8)*.045):null})).then(function(){revealEmpty();heading.focus({preventScroll:true})})})}});\n  action.addEventListener('keydown',function(e){if(e.key==='Escape'&&confirming&&!revokingOthers){e.preventDefault();confirming=false;renderAction(true)}});\n  restore.addEventListener('click',function(){sessions=demo(Date.now());confirming=false;renderList();renderAction()});\n  setInterval(function(){if(!document.hidden)list.querySelectorAll('li').forEach(function(li){var s=sessions.find(function(x){return x.id===li.dataset.session});var t=li.querySelector('.op-as-status-time');if(s&&t)t.textContent='Last active '+rel(s.last,Date.now())})},30000);\n  renderList();renderAction();\n});\n})();",
        "react": "",
        "dependencies": [],
        "install": "",
        "licence_notice": ""
    }
}