Unknown app.
";
}
// Switcher MVP
function renderSwitcherCards(){
const sw=$("#switcher"); sw.innerHTML="";
state.apps.forEach((rec,id)=>{
const c=document.createElement("div"); c.className="card"; c.textContent=id;
c.onclick=()=>{ focusWin(rec.node); toggleSwitcher(false); };
sw.appendChild(c);
});
}
function toggleSwitcher(on){
$("#switcher").classList.toggle("active", !!on);
$("#switcher").setAttribute("aria-hidden", on?"false":"true");
}
// -------- CC / NC / Hotkeys / Gestures --------
function toggleCC(force){ const on = force!==undefined ? force : !$("#cc").classList.contains("active"); $("#cc").classList.toggle("active", on); }
function toggleNC(force){ const on = force!==undefined ? force : !$("#nc").classList.contains("active"); $("#nc").classList.toggle("active", on); }
document.addEventListener("keydown",(e)=>{
if((e.ctrlKey||e.metaKey)&&e.key.toLowerCase()==="q") toggleCC();
if((e.ctrlKey||e.metaKey)&&e.key.toLowerCase()==="n") toggleNC();
if(e.key==="Escape"){ toggleCC(false); toggleNC(false); toggleSwitcher(false); hideSpot(); $$(".icon-wrap").forEach(w=>w.classList.remove("jiggle")); }
if(e.key===" " && !state.locked){ showSpot(); e.preventDefault(); }
if(e.key==="Enter" && state.locked){ unlockFlow(); }
});
(function gestureTopPull(){
let pulling=false, fromRight=false, startY=0;
const nearTop=(y)=> y<50;
const right=(x)=> x>window.innerWidth*0.66;
document.addEventListener("touchstart",e=>{
const t=e.touches[0];
if(nearTop(t.clientY)){ pulling=true; fromRight=right(t.clientX); startY=t.clientY; }
}, {passive:true});
document.addEventListener("touchmove",e=>{
if(!pulling) return;
const t=e.touches[0], dy=t.clientY-startY;
if(dy>28){ if(fromRight) toggleCC(true); else toggleNC(true); pulling=false; }
}, {passive:true});
document.addEventListener("touchend",()=>{ pulling=false; });
})();
// CC sliders
$("#cc-bright")?.addEventListener("input",e=>{
const v=parseFloat(e.target.value);
state.brightness=v; localStorage.setItem(K.bright,String(v));
document.body.style.filter=`brightness()`;
});
// -------- Spotlight --------
function showSpot(){ $("#spotlight").classList.add("active"); $("#spot-input").focus(); renderSpot(""); }
function hideSpot(){ $("#spotlight").classList.remove("active"); }
$("#spot-input")?.addEventListener("input", e=>renderSpot(e.target.value));
function renderSpot(q){
q=(q||"").toLowerCase(); const box=$("#spot-results"); box.innerHTML="";
const items=[];
// apps
CATALOG.forEach(a=>{ if(a.name.toLowerCase().includes(q)) items.push({label:a.name, act:()=>openApp(a.id,a.name)}); });
// settings sections
[{label:"Appearance โ Settings",sec:"appearance"},
{label:"Security โ Settings",sec:"security"},
{label:"System โ Settings",sec:"system"},
{label:"About โ Settings",sec:"about"}]
.forEach(s=>{ if(s.label.toLowerCase().includes(q)) items.push({label:s.label, act:()=>{openApp("settings","Settings"); setTimeout(()=>document.querySelector(\`.set-item[data-sec=""]\`)?.click(),0); }}); });
// notes
try{
const notes=JSON.parse(localStorage.getItem("hq.notes")||"[]");
notes.forEach(n=>{ if((n.t||"").toLowerCase().includes(q)) items.push({label:` โ Note`, act:()=>openApp("notes","Notes")}); });
}catch{}
// files
try{
const files=JSON.parse(localStorage.getItem("hq.files")||"[]");
files.forEach(f=>{ if((f.name||"").toLowerCase().includes(q)) items.push({label:` โ File`, act:()=>openApp("files","Files")}); });
}catch{}
items.forEach(it=>{ const row=document.createElement("div"); row.className="spot-item"; row.textContent=it.label; row.onclick=()=>{ it.act(); hideSpot(); }; box.appendChild(row); });
}
// -------- Lock / Security --------
function setLastOK(){ localStorage.setItem(K.lastOK, String(nowMs())); }
function getLastOK(){ return parseInt(localStorage.getItem(K.lastOK)||"0",10); }
function shouldRelock(){
if(!state.relockSecs) return false;
const last=getLastOK(); if(!last) return true;
return (nowMs()-last) > secs(state.relockSecs);
}
function lockScreen(){
state.locked=true;
$("#homescreen").classList.remove("active");
$("#lockscreen").classList.add("active");
}
async function unlockFlow(){
// If passcode not set, run first setup
if(!localStorage.getItem(K.passcode)) return await firstSetupPasscode();
// If Face ID enabled, try quick
if(state.faceOn){
const ok = await tryFaceAuth();
await snapshotAttempt(ok);
if(ok){ state.locked=false; setLastOK(); uiUnlocked(); toast("Unlocked (Face ID)"); return; }
// fallthrough to passcode
}
const pass = await promptPasscode("Enter Passcode");
if(!pass) return;
const ok = await verifyPass(pass);
await snapshotAttempt(ok);
if(ok){ state.locked=false; setLastOK(); uiUnlocked(); toast("Unlocked"); }
else toast("Incorrect passcode");
}
function uiUnlocked(){
$("#lockscreen").classList.remove("active");
$("#homescreen").classList.add("active");
renderPages();
}
async function firstSetupPasscode(){
const newCode = await promptPasscode("Create Passcode");
if(!newCode) return;
const hash = await sha256Hex(newCode);
localStorage.setItem(K.passcode, hash);
await setupRecoveryQA();
toast("Passcode set");
const enable = await confirmDialog("Enable Face ID?", "Weโll store a local camera snapshot to help unlock.");
if(enable) await enrollFaceTemplate();
state.locked=false; setLastOK(); uiUnlocked();
}
async function verifyPass(pass){
const want = localStorage.getItem(K.passcode)||"";
if(!want) return false;
const got = await sha256Hex(pass);
return got===want;
}
// Modals
function modalFromTemplate(tplId){
const tpl = $(tplId);
const frag = tpl.content.cloneNode(true);
document.body.appendChild(frag);
const overlay = document.body.lastElementChild;
return { overlay, destroy:()=>overlay.remove() };
}
async function promptPasscode(title){
const m = modalFromTemplate("#tpl-passcode-modal");
$(".modal-title", m.overlay).textContent = title;
const input = $("#pc-in",m.overlay);
const okBtn = $("#pc-ok",m.overlay);
const cancel = $("#pc-cancel",m.overlay);
const forgot = $("#pc-forgot",m.overlay);
input.focus();
return await new Promise(res=>{
cancel.onclick=()=>{ m.destroy(); res(null); };
okBtn.onclick=()=>{ const v=input.value.trim(); m.destroy(); res(v||null); };
forgot.onclick=async()=>{ m.destroy(); await forgotFlow(); res(null); };
input.addEventListener("keydown",e=>{ if(e.key==="Enter"){ okBtn.click(); } });
});
}
async function forgotFlow(){
const q = localStorage.getItem(K.question)||"What is your favorite pet?";
const m = modalFromTemplate("#tpl-forgot-modal");
$("#recov-q",m.overlay).textContent = q;
const a = $("#recov-a",m.overlay);
const n = $("#recov-new",m.overlay);
$("#recov-cancel",m.overlay).onclick=()=> m.destroy();
$("#recov-ok",m.overlay).onclick= async ()=>{
const want = localStorage.getItem(K.answer)||"";
const got = await sha256Hex(a.value.trim());
if(!want || got!==want){ toast("Recovery answer incorrect"); return; }
if(!n.value.trim()){ toast("Enter new passcode"); return; }
const newHash = await sha256Hex(n.value.trim());
localStorage.setItem(K.passcode,newHash);
toast("Passcode updated");
m.destroy();
};
}
async function setupRecoveryQA(){
const q = prompt("Set recovery question", "What is your favorite pet?");
const a = prompt("Answer (used to reset passcode)", "");
if(!a) return;
localStorage.setItem(K.question, q||"What is your favorite pet?");
const ah = await sha256Hex(a.trim());
localStorage.setItem(K.answer, ah);
}
async function confirmDialog(title,msg){
return window.confirm(`\n\n`);
}
// Face enroll/auth (naive template similarity)
async function enrollFaceTemplate(){
const ok = await confirmDialog("Face ID", "Capture a local face snapshot (single frame) to enroll?");
if(!ok) return false;
const stream = await navigator.mediaDevices?.getUserMedia?.({video:true}).catch(()=>null);
if(!stream){ toast("Camera unavailable"); return false; }
const snap = await captureFrame(stream);
localStorage.setItem(K.faceTpl, snap);
localStorage.setItem(K.faceOn, "1"); state.faceOn=true;
toast("Face ID enrolled");
stream.getTracks().forEach(t=>t.stop());
return true;
}
async function tryFaceAuth(){
try{
const tpl = localStorage.getItem(K.faceTpl); if(!tpl) return false;
const stream = await navigator.mediaDevices?.getUserMedia?.({video:true}).catch(()=>null);
if(!stream) return false;
const snap = await captureFrame(stream);
stream.getTracks().forEach(t=>t.stop());
const ok = similarityScore(tpl,snap) > 0.85;
return ok;
}catch{ return false; }
}
function similarityScore(a,b){
if(!a||!b) return 0;
const L=Math.max(a.length,b.length);
let same=0, M=Math.min(a.length,b.length);
for(let i=0;inull);
if(!stream) return;
const img = await captureFrame(stream);
stream.getTracks().forEach(t=>t.stop());
fetch("/api/snapshot",{ method:"POST", headers:{"Content-Type":"application/json"}, body:JSON.stringify({ data:img, success:Boolean(success), ts: Date.now() }) }).catch(()=>{});
if(state.webhookOn && state.webhook){
fetch("/api/alert",{ method:"POST", headers:{"Content-Type":"application/json"}, body:JSON.stringify({ url:state.webhook, text:`Unlock @ ` }) }).catch(()=>{});
}
}catch{}
}
// Autosave heartbeat
setInterval(()=>{
if(!state.autosave) return;
localStorage.setItem("hq.autosave.hb", String(Date.now()));
}, 5000);
// -------- Apps --------
function appNotes(sel){
$(sel).innerHTML=``;
$("#sec-set-pass").onclick=async()=>{
const pc = await promptPasscode("Enter new passcode"); if(!pc) return;
const h = await sha256Hex(pc); localStorage.setItem(K.passcode,h); toast("Passcode saved");
};
$("#sec-recover").onclick=async()=>{ await setupRecoveryQA(); toast("Recovery saved"); };
$("#sec-face-on").checked=state.faceOn;
$("#sec-face-on").onchange=(e)=>{ state.faceOn=e.target.checked; localStorage.setItem(K.faceOn, state.faceOn?"1":""); toast("Face ID "+(state.faceOn?"enabled":"disabled")); };
$("#sec-enroll-face").onclick=()=>enrollFaceTemplate();
$("#sec-relock").value=String(state.relockSecs||0);
$("#sec-relock").onchange=(e)=>{ state.relockSecs=parseInt(e.target.value,10)||0; localStorage.setItem(K.relock, String(state.relockSecs)); toast("Relock updated"); };
$("#sec-alert-on").checked=state.webhookOn;
$("#sec-alert-url").value=state.webhook||"";
$("#sec-alert-on").onchange=(e)=>{ state.webhookOn = e.target.checked; localStorage.setItem(K.webhookOn, state.webhookOn?"1":""); };
$("#sec-alert-url").onchange=(e)=>{ state.webhook = e.target.value.trim(); localStorage.setItem(K.webhook, state.webhook); };
}
function secSystem(){
body.innerHTML=`