atlas-os/web/os.html
Chaib Aarab 449133f470 feat: real remote desktop (Webtop) + OS v5 launcher integration
- Full Linux XFCE desktop in the browser at desktop.atlascorporation.nl
  (Selkies/WebRTC, Ubuntu 26.04), gated by Caddy basic_auth + TLS
- Claude Code CLI + Chromium pre-installed; one-click desktop launchers
- runs on loopback only; auth enforced at the Caddy edge
- web/os.html: OS v5 adds Remote Desktop app (39 apps total)
- docs/remote-desktop.md + deploy/webtop-docker-compose.yml
2026-06-22 20:50:24 +02:00

258 lines
32 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<meta name="theme-color" content="#0b1020">
<title>Atlas OS</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/tabler-icons/2.47.0/iconfont/tabler-icons.min.css">
<style>
*{box-sizing:border-box;margin:0;padding:0}
html,body{height:100%;background:#0b1020;color:#e8edf7;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;overflow:hidden;user-select:none}
#boot,#login{position:fixed;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:18px;background:#0b1020;z-index:2000}
#login{display:none}
.bar{width:220px;height:4px;background:#1b2742;border-radius:4px;overflow:hidden}
.bar>div{width:0;height:100%;background:#e0a234;transition:width .3s}
.mono{font-family:ui-monospace,Menlo,Consolas,monospace;font-size:12px;color:#7e8db0}
#desktop{position:fixed;inset:0;display:none;opacity:0;transition:opacity .28s ease;background:radial-gradient(1200px 800px at 78% -10%, #16213f 0%, transparent 60%),radial-gradient(900px 700px at -5% 110%, #14233a 0%, transparent 55%),#0b1020}
#desktop.in{opacity:1}
#topbar{position:fixed;top:0;left:0;right:0;height:calc(38px + env(safe-area-inset-top));padding-top:env(safe-area-inset-top);padding-left:calc(14px + env(safe-area-inset-left));padding-right:calc(14px + env(safe-area-inset-right));background:#11182b;border-bottom:.5px solid #233252;display:flex;align-items:center;gap:10px;z-index:1000}
#icons{position:fixed;top:calc(54px + env(safe-area-inset-top));left:calc(14px + env(safe-area-inset-left));display:flex;flex-direction:column;gap:15px;z-index:10;max-height:calc(100vh - 120px);overflow-y:auto;overflow-x:hidden;width:188px}
.cathead{font-size:10px;font-weight:600;color:#6c7a9c;text-transform:uppercase;letter-spacing:.09em;padding-left:3px;margin-bottom:6px}
.catgrid{display:grid;grid-template-columns:repeat(2,80px);gap:12px}
.ico{display:flex;flex-direction:column;align-items:center;gap:5px;cursor:pointer;width:80px;border-radius:12px;padding:4px 0}
.ico .b{width:48px;height:48px;border-radius:12px;background:#141d33;border:.5px solid #2a3a5e;display:flex;align-items:center;justify-content:center;font-size:23px;transition:transform .12s ease,border-color .12s ease,background .12s ease}
.ico span{font-size:11px;color:#cfd9ee}
.ico:hover .b{transform:translateY(-2px);border-color:#3c5286;background:#19243f}
.ico:active .b{transform:scale(.94)}
.win{position:fixed;width:330px;background:#141d33;border:.5px solid #2a3a5e;border-radius:11px;overflow:hidden;z-index:20;box-shadow:0 8px 30px rgba(0,0,0,.4)}
.tb{display:flex;align-items:center;gap:8px;padding:9px 11px;background:#1b2742;cursor:move;touch-action:none}
.tb i:first-child{font-size:15px}
.tb span{font-size:13px;font-weight:500;flex:1}
.tb .x{cursor:pointer;color:#9fb0cc;font-size:16px;border-radius:6px;transition:color .12s ease,background .12s ease}
.tb .x:hover{color:#fff;background:#e2655f}
.tb .r{cursor:pointer;color:#9fb0cc;font-size:15px;border-radius:6px;transition:color .12s ease,background .12s ease}
.tb .r:hover{color:#fff;background:#2a3a5e}
.tb .r:focus-visible{outline:2px solid #e0a234;outline-offset:2px}
.body{padding:12px;max-height:60vh;overflow:auto;font-size:13px}
.card{background:#0f1730;border-radius:8px;padding:9px 11px}
.card .l{font-size:11px;color:#7e8db0}.card .v{font-size:19px;font-weight:500}
.row{display:flex;justify-content:space-between;padding:7px 4px;border-bottom:.5px solid #1b2742;font-size:12px}
a.btn,button.btn{display:inline-block;background:#e0a234;color:#0b1020;text-decoration:none;padding:8px 20px;border-radius:8px;font-size:13px;font-weight:500;cursor:pointer;transition:filter .12s ease,transform .12s ease}
a.btn:hover,button.btn:hover{filter:brightness(1.08)}
a.btn:active,button.btn:active{transform:scale(.97)}
#taskbar{position:fixed;bottom:0;left:0;right:0;height:calc(30px + env(safe-area-inset-bottom));padding-bottom:env(safe-area-inset-bottom);padding-left:calc(12px + env(safe-area-inset-left));padding-right:calc(12px + env(safe-area-inset-right));background:#11182b;border-top:.5px solid #233252;display:flex;align-items:center;gap:8px;font-size:11px;color:#5e6e92;z-index:1000}
#tbtag{flex:0 0 auto;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:50%}
#tbwins{flex:1 1 auto;display:flex;align-items:center;gap:6px;overflow-x:auto;overflow-y:hidden;white-space:nowrap}
#tbwins::-webkit-scrollbar{height:0}
.tbchip{flex:0 0 auto;display:inline-flex;align-items:center;gap:5px;background:#1b2742;border:.5px solid #2a3a5e;color:#cfd9ee;font-size:11px;padding:3px 9px;border-radius:7px;cursor:pointer;font-family:inherit;line-height:1.4;transition:border-color .12s ease,background .12s ease}
.tbchip:hover{border-color:#3c5286;background:#19243f}
.tbchip.active{border-color:#e0a234;color:#fff}
.tbchip i{font-size:13px}
#home{transition:background .12s ease}
#home:hover{background:#1b2742}
#acct{display:flex;align-items:center;gap:6px;background:none;border:none;color:inherit;cursor:pointer;font-family:inherit;padding:2px 4px;border-radius:7px;transition:background .12s ease}
#acct:hover{background:#1b2742}
#acctav{width:22px;height:22px;border-radius:50%;background:#1b2742;border:.5px solid #e0a234;display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:500;color:#e0a234;flex:0 0 auto}
#acctmenu{position:fixed;top:calc(46px + env(safe-area-inset-top));right:calc(14px + env(safe-area-inset-right));background:#141d33;border:.5px solid #2a3a5e;border-radius:9px;box-shadow:0 8px 30px rgba(0,0,0,.4);overflow:hidden;z-index:1001;display:none;min-width:150px}
#acctmenu button{display:flex;align-items:center;gap:8px;width:100%;background:none;border:none;color:#cfd9ee;font-family:inherit;font-size:13px;text-align:left;padding:10px 14px;cursor:pointer}
#acctmenu button:hover{background:#1b2742}
#acctmenu i{font-size:15px;color:#9fb0cc}
#enter[disabled]{opacity:.6;cursor:progress}
@keyframes pulse{0%,100%{opacity:.55}50%{opacity:1}}
.skel{display:flex;flex-direction:column;gap:8px}
.skel>div{border-radius:8px;background:#0f1730;animation:pulse 1.1s ease-in-out infinite}
@media (prefers-reduced-motion: reduce){*,*::before,*::after{animation-duration:.001ms!important;animation-iteration-count:1!important;transition-duration:.001ms!important;scroll-behavior:auto!important}}
:focus-visible{outline:2px solid #e0a234;outline-offset:2px}
.ico:focus-visible,.tb .x:focus-visible,a.btn:focus-visible,button.btn:focus-visible{outline:2px solid #e0a234;outline-offset:2px}
@media (max-width:560px){
#icons{position:static;left:auto;top:auto;display:flex;flex-direction:column;gap:16px;padding:calc(54px + env(safe-area-inset-top)) calc(14px + env(safe-area-inset-right)) 14px calc(14px + env(safe-area-inset-left));width:100%;max-height:none;overflow:visible}
.ico{width:auto}.catgrid{grid-template-columns:repeat(4,1fr)}
.win{left:0!important;right:0!important;top:calc(38px + env(safe-area-inset-top))!important;bottom:calc(30px + env(safe-area-inset-bottom));width:auto!important;border-radius:0;border-left:none;border-right:none;display:flex;flex-direction:column}
.body{max-height:none;flex:1 1 auto;min-height:0}
#taskbar{font-size:10px}
.tb .x{font-size:20px;padding:4px}
.tb .r{font-size:19px;padding:4px}
#tbtag{display:none}
}
</style>
</head>
<body>
<div id="boot">
<svg width="68" height="68" viewBox="0 0 64 64"><path d="M32 8 L56 56 L40 56 L32 38 L24 56 L8 56 Z" fill="#e0a234"/><path d="M32 26 L38 40 L26 40 Z" fill="#0b1020"/></svg>
<div style="font-size:24px;font-weight:500;letter-spacing:2px">ATLAS OS</div>
<div class="bar"><div id="bbar"></div></div>
<div class="mono" id="bmsg">initializing kernel…</div>
</div>
<div id="login">
<div id="lclock" style="font-size:42px;font-weight:500"></div>
<div style="font-size:13px;color:#9fb0cc;margin-bottom:6px" id="ldate"></div>
<div id="lava" style="width:66px;height:66px;border-radius:50%;background:#1b2742;border:.5px solid #e0a234;display:flex;align-items:center;justify-content:center;font-size:23px;font-weight:500;color:#e0a234">··</div>
<div id="lname" style="font-size:16px;font-weight:500"></div>
<div id="lsso" style="font-size:12px;color:#7e8db0">Atlas Corporation · connecting to Authentik SSO…</div>
<button id="enter" style="margin-top:10px;background:#e0a234;color:#0b1020;border:none;padding:10px 28px;border-radius:8px;font-size:14px;font-weight:500;cursor:pointer">Enter desktop</button>
</div>
<div id="desktop">
<div id="topbar">
<button id="home" aria-label="Home — show app grid" title="Home" style="display:flex;align-items:center;gap:10px;background:none;border:none;color:inherit;cursor:pointer;font-family:inherit;padding:2px 4px;border-radius:7px"><svg width="17" height="17" viewBox="0 0 64 64"><path d="M32 8 L56 56 L40 56 L32 38 L24 56 L8 56 Z" fill="#e0a234"/></svg><span style="font-size:13px;font-weight:500">Atlas OS</span></button>
<span style="flex:1"></span>
<i id="tbwifi" class="ti ti-wifi" style="color:#4ec98a" aria-hidden="true" title="Live data connected"></i>
<i class="ti ti-shield-check" style="color:#4ec98a" aria-hidden="true" title="Identity verified via SSO"></i>
<span id="tclock" style="font-size:12px;color:#cfd9ee"></span>
<button id="acct" aria-haspopup="menu" aria-expanded="false" aria-label="Account"><span id="acctav">··</span><span id="acctname" style="font-size:12px;color:#cfd9ee"></span><i class="ti ti-chevron-down" style="font-size:13px;color:#7e8db0"></i></button>
</div>
<div id="acctmenu" role="menu" aria-label="Account">
<button id="acctlock" role="menuitem"><i class="ti ti-lock"></i>Lock</button>
<button id="acctout" role="menuitem"><i class="ti ti-logout"></i>Sign out</button>
</div>
<div id="icons"></div>
<div id="taskbar"><span id="tbtag">Atlas OS 1.0 · your business on one box · live behind your identity login</span><span id="tbwins"></span></div>
</div>
<script>
var COCK="/os/cockpit";
var apps=[
{id:"money",name:"Money",icon:"ti-coin",c:"#e0a234",api:"/api/money",render:function(j){var shown=(j.tx||[]).slice(0,8);var rows=shown.map(function(t){var neg=t.amount<0;return '<div class="row"><span style="color:#9fb0cc">'+esc(t.date)+'</span><span style="flex:1;margin:0 8px;color:#cfd9ee">'+esc(t.cp)+'</span><span style="color:'+(neg?"#e2655f":"#4ec98a")+'">'+eur(t.amount)+'</span></div>';}).join("");
var net=shown.reduce(function(s,t){return s+(+t.amount||0);},0);
return '<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:10px"><div class="card"><div class="l">Net (recent)</div><div class="v" style="color:'+(net<0?"#e2655f":"#4ec98a")+'">'+eur(net)+'</div></div><div class="card"><div class="l">Transactions</div><div class="v">'+(j.count||0)+' <i class="ti ti-check" style="font-size:13px;color:#4ec98a" title="Bank synced"></i></div></div></div>'+rows;}},
{id:"inbox",name:"Inbox",icon:"ti-mail",c:"#4a9fe2",api:"/api/inbox",render:function(j){var d=(j.drafts||[]);if(!d.length)return '<div style="color:#9fb0cc">Inbox clear.</div>';return d.slice(0,8).map(function(m){return '<div style="padding:8px;border-left:2px solid #4a9fe2;background:#0f1730;border-radius:0 8px 8px 0;margin-bottom:6px"><div style="font-size:12px;font-weight:500">'+esc(m.target||m.subject||m.channel)+'</div><div style="font-size:11px;color:#7e8db0">'+esc(m.channel||"")+'</div></div>';}).join("");}},
{id:"pipeline",name:"Pipeline",icon:"ti-chart-arrows",c:"#4ec98a",api:"/api/pipeline",render:function(j){var l=(j.leads||[]);if(!l.length)return '<div style="color:#9fb0cc">No open leads.</div>';return l.slice(0,8).map(function(x){return '<div style="padding:8px;background:#0f1730;border-radius:8px;margin-bottom:6px"><div style="font-size:12px;font-weight:500">'+esc(x.name)+'</div><div style="font-size:11px;color:#7e8db0">'+esc(x.contact||"")+'</div></div>';}).join("");}},
{id:"today",name:"Today",icon:"ti-sun",c:"#e0a234",api:"/api/state",render:function(j){var bt=String(j.brief==null?"":j.brief).replace(/\*/g,"");var head=bt.split("\n")[0]||"Atlas Daily Brief";var ni=bt.indexOf("\n");var rest=(ni<0?"":bt.slice(ni+1)).trim().slice(0,600);var bodyHtml=rest?'<div style="font-size:12px;color:#9fb0cc;line-height:1.6;white-space:pre-wrap">'+esc(rest)+'</div>':'';return '<div style="font-size:13px;font-weight:500;margin-bottom:6px">'+esc(head)+'</div>'+bodyHtml;}},
{id:"fleet",name:"Fleet",icon:"ti-devices",c:"#9b8cf0",api:"/api/fleet",render:function(j){var c=(j.customers||[]);return c.map(function(x){return '<div style="padding:8px;background:#0f1730;border-radius:8px;margin-bottom:6px"><div style="font-size:12px;font-weight:500">'+esc(x.name)+'</div><div style="font-size:11px;color:#7e8db0">'+((x.terminals||[]).length)+' terminal(s)</div></div>';}).join("")||'<div style="color:#9fb0cc">No devices.</div>';}},
{id:"control",name:"Control",icon:"ti-adjustments",c:"#e2655f",api:"/api/control",render:function(j){return '<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px"><div class="card"><div class="l">Autonomy</div><div class="v" style="color:#4ec98a">'+esc(j.level||"L0")+'</div></div><div class="card"><div class="l">Kill switch</div><div class="v" style="color:'+(j.kill_switch?"#e2655f":"#4ec98a")+'">'+(j.kill_switch?"ON":"OFF")+'</div></div><div class="card"><div class="l">Canonical</div><div class="v" style="font-size:14px">'+esc(j.canonical||"")+'</div></div><div class="card"><div class="l">Spend cap</div><div class="v">'+eur(j.spend_cap_day||0)+'/d</div></div></div>';}},
{id:"files",name:"Nextcloud",icon:"ti-folder",c:"#4a9fe2",url:"https://cloud.atlascorporation.nl"},
{id:"cal",name:"Calendar",icon:"ti-calendar",c:"#4ec98a",url:"https://cloud.atlascorporation.nl/apps/calendar"},
{id:"odoo",name:"Odoo",icon:"ti-building-store",c:"#9b8cf0",url:"https://odoo.atlascorporation.nl"},
{id:"git",name:"Git",icon:"ti-git-branch",c:"#e0a234",url:"https://git.atlascorporation.nl"},
{id:"n8n",name:"n8n",icon:"ti-route",c:"#e2655f",url:"https://n8n.atlascorporation.nl"},
{id:"vault",name:"Vault",icon:"ti-lock",c:"#4a9fe2",url:"https://vault.atlascorporation.nl"},
{id:"uptime",name:"Uptime",icon:"ti-activity",c:"#4ec98a",url:"https://uptime.atlascorporation.nl"},
{id:"map",name:"Command",icon:"ti-map-2",c:"#4a9fe2",url:"https://command.atlascorporation.nl"},
{id:"chat",name:"Atlas Chat",icon:"ti-message-chatbot",c:"#4ec98a",url:"https://chat.atlascorporation.nl"},
{id:"cfo",name:"CFO",icon:"ti-calculator",c:"#e0a234",url:"https://cfo.atlascorporation.nl"},
{id:"bank",name:"Bank",icon:"ti-building-bank",c:"#4ec98a",url:"https://fidi.atlascorporation.nl"},
{id:"paperless",name:"Paperless",icon:"ti-files",c:"#e0a234",url:"https://paperless.atlascorporation.nl"},
{id:"mesh",name:"MeshCentral",icon:"ti-screen-share",c:"#9b8cf0",url:"https://mesh.atlascorporation.nl"},
{id:"mindmap",name:"Mindmap",icon:"ti-bulb",c:"#e0a234",url:"https://mindmap.atlascorporation.nl"},
{id:"llm",name:"AI Gateway",icon:"ti-cpu",c:"#9b8cf0",url:"https://llm.atlascorporation.nl"},
{id:"leer",name:"Leeragent",icon:"ti-school",c:"#4a9fe2",url:"https://leeragent.atlascorporation.nl"},
{id:"relay",name:"Relay",icon:"ti-share",c:"#4ec98a",url:"https://relay.atlascorporation.nl"},
{id:"mailadmin",name:"Mail Admin",icon:"ti-server-cog",c:"#9b8cf0",url:"https://stalwart.atlascorporation.nl"},
{id:"arch",name:"Architecture",icon:"ti-sitemap",c:"#4a9fe2",url:"/os/sitemap.html"},
{id:"auth",name:"Identity",icon:"ti-shield-lock",c:"#e2655f",url:"https://auth.atlascorporation.nl"},
{id:"office",name:"Office",icon:"ti-file-text",c:"#4a9fe2",url:"https://office.atlascorporation.nl"},
{id:"s_works",name:"AtlasWorks",icon:"ti-shopping-cart",c:"#e0a234",url:"https://atlasworks.nl"},
{id:"s_corp",name:"Atlas Corp",icon:"ti-world",c:"#4a9fe2",url:"https://atlascorporation.nl"},
{id:"s_agency",name:"Atlas Agency",icon:"ti-briefcase",c:"#9b8cf0",url:"https://atlasagency.nl"},
{id:"s_academy",name:"Academy",icon:"ti-school",c:"#4ec98a",url:"https://atlasacademy.nl"},
{id:"s_pos",name:"AtlasPOS",icon:"ti-cash",c:"#e0a234",url:"https://atlaspos.nl"},
{id:"s_hub",name:"Atlas Hub",icon:"ti-layout-grid",c:"#4a9fe2",url:"https://atlashub.nl"},
{id:"s_bicos",name:"Bicos",icon:"ti-building",c:"#9b8cf0",url:"https://bicosagency.nl"},
{id:"s_bec",name:"Budget Energie",icon:"ti-bolt",c:"#e0a234",url:"https://budgetenergiecoach.nl"},
{id:"s_hki",name:"HKinterCare",icon:"ti-heart",c:"#e2655f",url:"https://hkintercare.nl"},
{id:"s_jea",name:"Jouw Energie",icon:"ti-sun",c:"#e0a234",url:"https://jouwenergieadvies.nl"},
{id:"s_rm",name:"ReclameMans",icon:"ti-speakerphone",c:"#4ec98a",url:"https://reclamemans.nl"},
{id:"desktop",name:"Remote Desktop",icon:"ti-device-desktop",c:"#9b8cf0",url:"https://desktop.atlascorporation.nl"}
];
var CATS={today:"Cockpit",money:"Cockpit",inbox:"Cockpit",pipeline:"Cockpit",fleet:"Cockpit",control:"Cockpit",auth:"Identity",vault:"Identity",chat:"AI",llm:"AI",mindmap:"AI",leer:"AI",odoo:"Work",cfo:"Work",bank:"Work",paperless:"Work",office:"Work",cal:"Work",files:"Files & Comms",mailadmin:"Files & Comms",relay:"Files & Comms",git:"Ops",n8n:"Ops",uptime:"Ops",mesh:"Ops",map:"Ops",arch:"Ops",s_works:"Sites",s_corp:"Sites",s_agency:"Sites",s_academy:"Sites",s_pos:"Sites",s_hub:"Sites",s_bicos:"Sites",s_bec:"Sites",s_hki:"Sites",s_jea:"Sites",s_rm:"Sites",desktop:"Ops"};
function eur(n){try{return new Intl.NumberFormat("nl-NL",{style:"currency",currency:"EUR"}).format(n);}catch(e){return "€"+n;}}
function esc(s){return String(s==null?"":s).replace(/[&<>]/g,function(c){return{"&":"&amp;","<":"&lt;",">":"&gt;"}[c];});}
function netHealth(ok){var w=document.getElementById("tbwifi");if(!w)return;w.style.color=ok?"#4ec98a":"#e2655f";w.className="ti "+(ok?"ti-wifi":"ti-wifi-off");w.title=ok?"Live data connected":"Live data unavailable";}
var MOBILE=function(){return window.matchMedia("(max-width:560px)").matches;};
function bringToFront(d){var BASE=12;var ws=[].slice.call(document.querySelectorAll(".win"));ws.sort(function(a,b){return (parseInt(a.style.zIndex,10)||BASE)-(parseInt(b.style.zIndex,10)||BASE);});var i=0;ws.forEach(function(w){if(w!==d){w.style.zIndex=BASE+(i++);}});if(d){d.style.zIndex=BASE+ws.length;}renderTaskbar();}
function topH(){var t=document.getElementById("topbar");return t?t.offsetHeight:38;}
function botH(){var t=document.getElementById("taskbar");return t?t.offsetHeight:30;}
function maxTop(){return Math.max(topH(),window.innerHeight-botH()-38);}
function clampWin(el){if(MOBILE())return;var mL=Math.max(0,window.innerWidth-el.offsetWidth),mT=maxTop();el.style.left=Math.min(mL,Math.max(0,el.offsetLeft))+"px";el.style.top=Math.min(mT,Math.max(topH(),el.offsetTop))+"px";}
function focusIcon(name){var ic=document.querySelector('.ico[aria-label="'+name+'"]');if(ic)ic.focus();}
function closeWin(d,a){if(d&&d._ac){try{d._ac.abort();}catch(e){}}d.remove();renderTaskbar();focusIcon(a.name);}
function destroyAllWindows(){document.querySelectorAll(".win").forEach(function(w){if(w._ac){try{w._ac.abort();}catch(e){}}w.remove();});renderTaskbar();}
function raiseWin(d){bringToFront(d);clampWin(d);try{d.focus();}catch(e){}}
function renderTaskbar(){var c=document.getElementById("tbwins");if(!c)return;var ws=document.querySelectorAll(".win"),topEl=null,mx=-1;for(var i=0;i<ws.length;i++){var zi=parseInt(ws[i].style.zIndex,10)||0;if(zi>=mx){mx=zi;topEl=ws[i];}}apps.forEach(function(a){var d=document.getElementById("w-"+a.id);var chip=document.getElementById("tc-"+a.id);if(!d){if(chip)chip.remove();return;}if(!chip){chip=document.createElement("button");chip.id="tc-"+a.id;chip.className="tbchip";chip.setAttribute("aria-label","Switch to "+a.name);chip.innerHTML='<i class="ti '+a.icon+'" style="color:'+a.c+'"></i>'+esc(a.name);chip.onclick=function(){raiseWin(d);};c.appendChild(chip);}var active=(d===topEl);if(chip.classList.contains("active")!==active)chip.classList.toggle("active",active);});}
function win(a){
if(a.url&&MOBILE()){window.open(a.url,"_blank","noopener");return;}
var ex=document.getElementById("w-"+a.id); if(ex){raiseWin(ex);if(!a.url&&(ex.dataset.s==="error"||ex.dataset.s==="loading")){load(a,ex.querySelector(".body"));}return;}
var d=document.createElement("div");d.className="win";d.id="w-"+a.id;
d.setAttribute("role","dialog");d.setAttribute("aria-label",a.name);d.setAttribute("tabindex","-1");
d.style.left=(60+Math.round(Math.random()*60))+"px";d.style.top=(60+Math.round(Math.random()*40))+"px";d.style.zIndex=20;
d.innerHTML='<div class="tb"><i class="ti '+a.icon+'" style="color:'+a.c+'"></i><span>'+a.name+'</span>'+(a.api?'<i class="ti ti-refresh r" role="button" tabindex="0" aria-label="Refresh '+a.name+'"></i>':'')+'<i class="ti ti-x x" role="button" tabindex="0" aria-label="Close '+a.name+'"></i></div><div class="body" id="b-'+a.id+'" role="status" aria-live="polite">'+(a.api?SKEL:'')+'</div>';
document.getElementById("desktop").appendChild(d);
clampWin(d);
var xb=d.querySelector(".x");xb.onclick=function(){closeWin(d,a);};xb.addEventListener("keydown",function(e){if(e.key==="Enter"||e.key===" "){e.preventDefault();closeWin(d,a);}});
var rf=d.querySelector(".r");if(rf){rf.onclick=function(){load(a,d.querySelector(".body"));};rf.addEventListener("keydown",function(e){if(e.key==="Enter"||e.key===" "){e.preventDefault();load(a,d.querySelector(".body"));}});}
d.addEventListener("pointerdown",function(){bringToFront(d);});
drag(d,d.querySelector(".tb"));
bringToFront(d);
try{d.focus();}catch(e){}
var b=d.querySelector(".body");
if(a.url){b.innerHTML='<div style="text-align:center;padding:18px"><i class="ti '+a.icon+'" style="font-size:38px;color:'+a.c+'"></i><div style="margin:10px 0;color:#cfd9ee">'+a.name+'</div><a class="btn" href="'+a.url+'" target="_blank" rel="noopener">Open '+a.name+' ↗</a></div>';return;}
load(a,b);
}
var SKEL='<div class="skel"><div style="height:46px"></div><div style="height:14px;width:70%"></div><div style="height:14px;width:50%"></div></div>';
function load(a,b,silent){
var w=b.closest(".win");if(w)w.dataset.s="loading";
if(w&&w._ac){try{w._ac.abort();}catch(e){}}
if(!silent)b.innerHTML=SKEL;
var ac=new AbortController();if(w)w._ac=ac;var gen=(w?(w._g=(w._g||0)+1):0);var to=setTimeout(function(){ac.abort();},8000);
fetch(COCK+a.api,{credentials:"include",signal:ac.signal}).then(function(r){
clearTimeout(to);
if(!r.ok){var e=new Error("HTTP "+r.status);e.status=r.status;throw e;}
var ct=r.headers.get("content-type")||"";if(ct.indexOf("application/json")<0){var ne=new Error("non-json");ne.auth=true;throw ne;}
return r.json();
}).then(function(j){if(w&&w._g!==gen)return;netHealth(true);var html;try{html=render(a,j);}catch(re){if(silent&&w&&w.dataset.t)return;if(w)w.dataset.s="error";b.innerHTML='<div style="color:#e2655f">This view could not render the latest data.</div>';return;}var sc=b.scrollTop;b.innerHTML=html;if(silent)b.scrollTop=sc;if(w){w.dataset.s="ok";w.dataset.t=Date.now();}}).catch(function(err){
clearTimeout(to);
if(err&&(err.name==="AbortError"||ac.signal.aborted))return;
if(w&&w._g!==gen)return;
if(w)w.dataset.s="error";
var st=err&&err.status;var authFail=(err&&err.auth)||st===401||st===403;
if(!authFail)netHealth(false);
var msg=authFail?"Session expired — reload to sign in.":"Could not load live data. The cockpit may be waking up.";
if(silent&&w&&w.dataset.t){w.dataset.s="ok";return;}
b.innerHTML='<div><div style="color:#e2655f">'+msg+'</div><div style="margin-top:8px"><button class="btn" id="rt-'+a.id+'" style="border:none;cursor:pointer;font-family:inherit">Retry</button></div></div>';
var rb=document.getElementById("rt-"+a.id);if(rb){rb.onclick=function(){load(a,b);};if(!silent)rb.focus();}
});
}
function stamp(ts){var n=new Date(ts||Date.now()),h=String(n.getHours()).padStart(2,"0"),m=String(n.getMinutes()).padStart(2,"0");return '<div style="font-size:11px;color:#7e8db0;margin-top:10px">updated '+h+':'+m+'</div>';}
function render(a,j){
var body=a&&a.render?a.render(j):'<pre style="font-size:11px;white-space:pre-wrap">'+esc(JSON.stringify(j,null,2).slice(0,800))+'</pre>';
return body+stamp();
}
var dragTarget=null,dragOX=0,dragOY=0;
document.addEventListener("pointermove",function(e){if(!dragTarget)return;var maxL=Math.max(0,window.innerWidth-dragTarget.offsetWidth);var maxT=maxTop();dragTarget.style.left=Math.min(maxL,Math.max(0,e.clientX-dragOX))+"px";dragTarget.style.top=Math.min(maxT,Math.max(topH(),e.clientY-dragOY))+"px";});
document.addEventListener("pointerup",function(){dragTarget=null;});
document.addEventListener("pointercancel",function(){dragTarget=null;});
function drag(w,h){h.addEventListener("pointerdown",function(e){if(MOBILE())return;dragTarget=w;dragOX=e.clientX-w.offsetLeft;dragOY=e.clientY-w.offsetTop;e.preventDefault();});}
function clampAll(){var ws=document.querySelectorAll(".win");for(var i=0;i<ws.length;i++){clampWin(ws[i]);}}
var rT;window.addEventListener("resize",function(){clearTimeout(rT);rT=setTimeout(clampAll,120);});
function icons(){var g=document.getElementById("icons");if(g.childElementCount)return;var ORDER=["Cockpit","Identity","AI","Work","Files & Comms","Ops","Sites"];var groups={};apps.forEach(function(a){var c=(typeof CATS!=="undefined"&&CATS[a.id])||"More";(groups[c]=groups[c]||[]).push(a);});var cats=ORDER.slice();Object.keys(groups).forEach(function(k){if(cats.indexOf(k)<0)cats.push(k);});cats.forEach(function(cat){var list=groups[cat];if(!list||!list.length)return;var head=document.createElement("div");head.className="cathead";head.textContent=cat;g.appendChild(head);var grid=document.createElement("div");grid.className="catgrid";list.forEach(function(a){var d=document.createElement("div");d.className="ico";d.setAttribute("role","button");d.setAttribute("tabindex","0");d.setAttribute("aria-label",a.name);d.innerHTML='<div class="b"><i class="ti '+a.icon+'" style="color:'+a.c+'"></i></div><span>'+esc(a.name)+'</span>';d.onclick=function(){win(a);};d.addEventListener("keydown",function(e){if(e.key==="Enter"||e.key===" "){e.preventDefault();win(a);}});grid.appendChild(d);});g.appendChild(grid);});}
document.addEventListener("keydown",function(e){if(e.key==="Escape"){var am=document.getElementById("acctmenu");if(am&&am.style.display==="block"){closeAcctMenu();var at=document.getElementById("acct");if(at){try{at.focus();}catch(e){}}return;}var ws=document.querySelectorAll(".win"),top=null,mx=-1;for(var i=0;i<ws.length;i++){var zi=parseInt(ws[i].style.zIndex,10)||0;if(zi>=mx){mx=zi;top=ws[i];}}if(top){var aid=top.id.slice(2);var ap=null;for(var k=0;k<apps.length;k++){if(apps[k].id===aid){ap=apps[k];break;}}if(ap){closeWin(top,ap);}else{top.remove();renderTaskbar();}}}});
function tick(){var n=new Date(),h=String(n.getHours()).padStart(2,"0"),m=String(n.getMinutes()).padStart(2,"0");["lclock","tclock"].forEach(function(i){var e=document.getElementById(i);if(e)e.textContent=h+":"+m;});var dt=document.getElementById("ldate");if(dt)dt.textContent=n.toLocaleDateString("nl-NL",{weekday:"long",day:"numeric",month:"long"});}
setInterval(tick,1000);tick();
function autoRefresh(){if(document.hidden)return;apps.forEach(function(a){if(!a.api)return;var d=document.getElementById("w-"+a.id);if(d&&d.dataset.s==="ok"){load(a,d.querySelector(".body"),true);}});}
setInterval(autoRefresh,60000);
var who={user:"Operator",initials:"··"};
var whoReady=false;
function readyEnter(){whoReady=true;var en=document.getElementById("enter");if(en){en.disabled=false;en.removeAttribute("aria-busy");en.textContent="Enter desktop";}}
fetch("/os/whoami",{credentials:"include"}).then(function(r){if(!r.ok)throw new Error("HTTP "+r.status);var ct=r.headers.get("content-type")||"";if(ct.indexOf("application/json")<0)throw new Error("non-json");return r.json();}).then(function(j){if(j&&(j.user||j.username)){who.user=j.user||j.username;var p=who.user.trim().split(/\s+/);who.initials=((p[0]||"")[0]||"")+((p[1]||"")[0]||"");document.getElementById("lname").textContent=who.user;document.getElementById("lava").textContent=who.initials.toUpperCase()||"··";document.getElementById("acctav").textContent=who.initials.toUpperCase()||"··";document.getElementById("acctname").textContent=who.user;var ls=document.getElementById("lsso");if(ls)ls.textContent="Atlas Corporation · identity verified via Authentik SSO";}else{document.getElementById("lname").textContent="Operator";var ls2=document.getElementById("lsso");if(ls2){ls2.textContent="Atlas Corporation · identity not verified";ls2.style.color="#e2655f";}}readyEnter();}).catch(function(){document.getElementById("lname").textContent="Operator";var ls=document.getElementById("lsso");if(ls){ls.textContent="Atlas Corporation · identity not verified";ls.style.color="#e2655f";}readyEnter();});
var msgs=["initializing kernel…","mounting /opt/atlas services…","connecting bank…","loading mind · daily brief…","verifying identity…","ready."];var p=0;var bi=null;
function finishBoot(){if(bi){clearInterval(bi);bi=null;}try{sessionStorage.setItem("atlasBooted","1");}catch(e){}document.getElementById("boot").style.display="none";document.getElementById("login").style.display="flex";var en=document.getElementById("enter");if(en){if(!whoReady){en.disabled=true;en.setAttribute("aria-busy","true");en.textContent="Resolving identity…";}try{en.focus();}catch(e){}}}
var booted=false;try{booted=sessionStorage.getItem("atlasBooted")==="1";}catch(e){}
if(booted){finishBoot();}else{
bi=setInterval(function(){p++;document.getElementById("bbar").style.width=Math.min(100,p*20)+"%";document.getElementById("bmsg").textContent=msgs[Math.min(p,msgs.length-1)];if(p>=5){clearInterval(bi);bi=null;setTimeout(finishBoot,500);}},420);
document.getElementById("boot").addEventListener("click",finishBoot);
document.addEventListener("keydown",function bskip(e){if(document.getElementById("boot").style.display!=="none"){e.preventDefault();document.removeEventListener("keydown",bskip);finishBoot();}});
}
function closeAcctMenu(){var m=document.getElementById("acctmenu");m.style.display="none";document.getElementById("acct").setAttribute("aria-expanded","false");}
function openAcctMenu(){var m=document.getElementById("acctmenu");m.style.display="block";document.getElementById("acct").setAttribute("aria-expanded","true");var items=m.querySelectorAll('button[role="menuitem"]');if(items.length)items[0].focus();}
document.getElementById("acctmenu").addEventListener("keydown",function(e){var m=this;if(m.style.display!=="block")return;var items=[].slice.call(m.querySelectorAll('button[role="menuitem"]'));if(!items.length)return;var i=items.indexOf(document.activeElement);if(e.key==="ArrowDown"){e.preventDefault();items[(i+1+items.length)%items.length].focus();}else if(e.key==="ArrowUp"){e.preventDefault();items[(i-1+items.length)%items.length].focus();}else if(e.key==="Home"){e.preventDefault();items[0].focus();}else if(e.key==="End"){e.preventDefault();items[items.length-1].focus();}});
document.getElementById("home").onclick=function(){destroyAllWindows();};
document.getElementById("acct").onclick=function(e){e.stopPropagation();var m=document.getElementById("acctmenu");if(m.style.display==="block"){closeAcctMenu();}else{openAcctMenu();}};
document.addEventListener("click",function(e){var m=document.getElementById("acctmenu");if(m.style.display==="block"&&!m.contains(e.target)&&e.target.id!=="acct"&&!document.getElementById("acct").contains(e.target)){closeAcctMenu();}});
document.getElementById("acctlock").onclick=function(){closeAcctMenu();destroyAllWindows();var dk=document.getElementById("desktop");dk.classList.remove("in");dk.style.display="none";document.getElementById("login").style.display="flex";var en=document.getElementById("enter");if(en){try{en.focus();}catch(e){}}};
document.getElementById("acctout").onclick=function(){closeAcctMenu();window.location.href="https://command.atlascorporation.nl/outpost.goauthentik.io/sign_out";};
document.getElementById("enter").onclick=function(){document.getElementById("login").style.display="none";var dk=document.getElementById("desktop");dk.style.display="block";icons();requestAnimationFrame(function(){dk.classList.add("in");});if(!MOBILE())win(apps[0]);};
</script>
</body>
</html>