Skip to content

Instantly share code, notes, and snippets.

@clashnewbm3
Last active September 2, 2025 07:34
Show Gist options
  • Select an option

  • Save clashnewbm3/2b8d5a60d2cf676015aaebe7d06d9c94 to your computer and use it in GitHub Desktop.

Select an option

Save clashnewbm3/2b8d5a60d2cf676015aaebe7d06d9c94 to your computer and use it in GitHub Desktop.
AeroLiteOS Opensourced
// This project was created by clashnewbme originaly
// if you want to support this project and get special perks donate here: https://www.patreon.com/Calacobragameengine/membership
// If you want to tell us bugs or want to add your games to the app store join our discord: https://discord.gg/Z9chWVBmjv
// heres the newest version https://aeroliteos.netlify.app/
// Thanks so much for the community and I will continue to update this project!
<!-- This is an opensourced project by clashnewbme join my discord: https://discord.gg/Z9chWVBmjv -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>AeroLiteOS</title>
<style>
:root{
--bg: #0b1220;
--bg-elev: #0f172a;
--panel: rgba(15,23,42,0.8);
--panel-solid: #111827;
--text: #e5e7eb;
--muted: #9ca3af;
--accent: #3b82f6;
--accent-2: #22d3ee;
--ok: #22c55e;
--warn: #f59e0b;
--err: #ef4444;
--glass: rgba(255,255,255,0.06);
--shadow: 0 10px 30px rgba(0,0,0,.35);
--radius: 16px;
--taskbar-h: 52px;
--blur: 14px;
--win-border: 1px solid rgba(255,255,255,.08);
--grid: 88px;
}
*{ box-sizing: border-box; }
html, body{ height:100%; }
body{
margin:0; font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell, Noto Sans, sans-serif;
color:var(--text); background: var(--bg);
overflow:hidden; user-select:none;
}
/* Desktop */
.desktop{
position:fixed; inset:0; display:grid; grid-template-rows: 1fr var(--taskbar-h);
background: radial-gradient(1200px 800px at 80% 10%, #1d2a53 0%, transparent 70%),
radial-gradient(900px 700px at 10% 80%, #093b4c 0%, transparent 60%),
linear-gradient(180deg, #040812 0%, #0b1220 100%);
}
.wallpaper{ position:absolute; inset:0; background-size:cover; background-position:center; filter:saturate(1.1) contrast(1.05); opacity:.9; }
.grain{ position:absolute; inset:-50%; background-image:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="400" height="400"><filter id="n"><feTurbulence type="fractalNoise" baseFrequency="0.65" numOctaves="2" stitchTiles="stitch"/></filter><rect width="100%" height="100%" filter="url(%23n)" opacity=".05"/></svg>'); mix-blend-mode:soft-light; pointer-events:none; }
/* Icons grid */
.icons{
position:relative; padding:20px; display:grid; grid-template-columns: repeat(auto-fill, minmax(var(--grid),1fr)); gap:14px; align-content:start; z-index:1;
}
.icon{
width:var(--grid); height:var(--grid); border-radius:14px; display:flex; flex-direction:column; align-items:center; justify-content:center; gap:8px;
background: transparent; cursor: default; transition: .15s ease; border:1px solid transparent;
}
.icon:hover{ background: rgba(255,255,255,.06); border-color: rgba(255,255,255,.08); }
.icon svg{ width:34px; height:34px; }
.icon span{ font-size:12px; color: var(--muted); text-shadow:0 1px 1px rgba(0,0,0,.4); }
/* Windows */
.win{ position:absolute; display:flex; flex-direction:column; background:var(--panel); -webkit-backdrop-filter: blur(var(--blur)); backdrop-filter: blur(var(--blur));
border-radius: var(--radius); box-shadow: var(--shadow); min-width: 320px; min-height: 240px; border: var(--win-border); overflow:hidden; }
.win.resizing, .win.dragging{ transition:none !important; }
.win .titlebar{ display:flex; align-items:center; gap:8px; padding:10px 12px; background:linear-gradient(180deg, rgba(255,255,255,.06), rgba(255,255,255,.03)); cursor:grab; }
.win .titlebar:active{ cursor:grabbing; }
.titlebar .appicon{ width:18px; height:18px; border-radius:5px; display:grid; place-items:center; background:var(--glass); }
.titlebar .title{ flex:1; font-weight:600; letter-spacing:.2px; filter:drop-shadow(0 1px 0 rgba(0,0,0,.3)); }
.titlebar .actions{ display:flex; gap:6px; }
.btn{ border:1px solid rgba(255,255,255,.08); background: rgba(255,255,255,.06); color:var(--text); border-radius:10px; padding:6px 10px; display:inline-flex; align-items:center; gap:6px; font-size:12px; cursor:pointer; transition:.15s; }
.btn:hover{ background: rgba(255,255,255,.1); }
.btn.ghost{ background:transparent; }
.win .content{ flex:1; background: rgba(2,6,23,.55); padding:0; overflow:auto; }
/* Resize handles */
.resizer{ position:absolute; }
.r-n{ top:-4px; left:0; right:0; height:8px; cursor:n-resize; }
.r-s{ bottom:-4px; left:0; right:0; height:8px; cursor:s-resize; }
.r-e{ right:-4px; top:0; bottom:0; width:8px; cursor:e-resize; }
.r-w{ left:-4px; top:0; bottom:0; width:8px; cursor:w-resize; }
.r-ne{ right:-6px; top:-6px; width:12px; height:12px; cursor:ne-resize; }
.r-nw{ left:-6px; top:-6px; width:12px; height:12px; cursor:nw-resize; }
.r-se{ right:-6px; bottom:-6px; width:12px; height:12px; cursor:se-resize; }
.r-sw{ left:-6px; bottom:-6px; width:12px; height:12px; cursor:sw-resize; }
/* Taskbar */
.taskbar{ position:relative; height:var(--taskbar-h); background: linear-gradient(180deg, rgba(255,255,255,.06), rgba(255,255,255,.02)); border-top:1px solid rgba(255,255,255,.1);
display:flex; align-items:center; gap:10px; padding:6px 10px; -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
}
.start{ width:40px; height:40px; border-radius:12px; display:grid; place-items:center; border:1px solid rgba(255,255,255,.1); background:rgba(255,255,255,.06); cursor:pointer; }
.start:hover{ background:rgba(255,255,255,.1); }
.tasklist{ flex:1; display:flex; gap:8px; }
.task{ min-width:120px; max-width:200px; height:38px; border-radius:12px; border:1px solid rgba(255,255,255,.08); background:rgba(255,255,255,.04); padding:0 10px; display:flex; align-items:center; gap:8px; cursor:pointer; }
.task.active{ outline:2px solid var(--accent); background: rgba(59,130,246,.12); }
.tray{ display:flex; align-items:center; gap:8px; }
.clock{ font-feature-settings:"tnum"; letter-spacing:.3px; color:var(--muted); }
/* Start menu */
.startmenu{ position:absolute; bottom:calc(var(--taskbar-h) + 8px); left:8px; width:680px; max-width:calc(100% - 16px);
background:var(--panel); border:var(--win-border); border-radius:20px; box-shadow:var(--shadow); padding:14px; display:none; -webkit-backdrop-filter: blur(var(--blur)); backdrop-filter: blur(var(--blur)); }
.startmenu.show{ display:block; }
.startmenu .search{ display:flex; gap:10px; }
.startmenu input{ flex:1; padding:12px 14px; border-radius:12px; border:1px solid rgba(255,255,255,.1); background:rgba(255,255,255,.06); color:var(--text); }
.apps{ display:grid; grid-template-columns: repeat(6, 1fr); gap:12px; margin-top:12px; }
.app-tile{ height:86px; border-radius:14px; border:1px solid rgba(255,255,255,.08); background: rgba(255,255,255,.05); display:flex; align-items:center; justify-content:center; flex-direction:column; gap:8px; cursor:pointer; }
.app-tile:hover{ background: rgba(255,255,255,.1); }
/* Quick settings / Notifications */
.quick{ position:absolute; right:8px; bottom:calc(var(--taskbar-h) + 8px); width:360px; background:var(--panel); border-radius:18px; border:var(--win-border); box-shadow:var(--shadow); display:none; padding:12px; }
.quick.show{ display:block; }
.toggles{ display:grid; grid-template-columns:repeat(3,1fr); gap:8px; }
.toggle{ border:1px solid rgba(255,255,255,.08); border-radius:12px; height:60px; display:grid; place-items:center; background:rgba(255,255,255,.05); cursor:pointer; }
.toggle.on{ outline:2px solid var(--accent-2); background: rgba(34,211,238,.15); }
/* App content styles */
.pad{ padding:14px; }
.notepad textarea{ width:100%; height:calc(100% - 40px); background:transparent; color:var(--text); border:none; outline:none; resize:none; font: 14px/1.6 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
.filelist{ display:flex; gap:10px; flex-wrap:wrap; }
.file{ width:140px; border:1px dashed rgba(255,255,255,.15); border-radius:12px; padding:10px; }
.terminal{ background:#0b0f1a; color:#d1e7ff; font: 13px/1.6 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; height:100%; padding:12px; }
.term-line{ white-space:pre-wrap; }
.calc{ display:grid; grid-template-columns: repeat(4, 1fr); gap:8px; padding:12px; }
.calc .screen{ grid-column: 1 / -1; height:60px; border-radius:12px; background: rgba(255,255,255,.06); border:1px solid rgba(255,255,255,.08); display:flex; align-items:center; justify-content:flex-end; padding:0 12px; font-size:22px; }
.calc button{ border:1px solid rgba(255,255,255,.08); background:rgba(255,255,255,.06); border-radius:12px; padding:12px; font-size:16px; cursor:pointer; }
.browserbar{ display:flex; gap:8px; padding:10px; }
.browserbar input{ flex:1; padding:10px; border-radius:10px; border:1px solid rgba(255,255,255,.1); background:rgba(255,255,255,.06); color:var(--text); }
iframe{ border:0; width:100%; height: calc(100% - 52px); background:#fff; }
/* Snap preview */
.snap-hint{ position:absolute; pointer-events:none; border:2px dashed rgba(255,255,255,.25); border-radius:16px; display:none; }
/* Desktop selection rectangle */
.select-rect{ position:absolute; border:1px dashed rgba(255,255,255,.4); background: rgba(255,255,255,.08); display:none; }
@media (max-width: 760px){
:root{ --taskbar-h: 56px; --radius: 14px; }
.apps{ grid-template-columns: repeat(3, 1fr); }
.icons{ grid-template-columns: repeat(3, minmax(var(--grid),1fr)); }
}
/* --- New UI bits (App Store badges, overlays) --- */
.overlay {
position: fixed; inset: 0; display: grid; place-items:center; z-index:99999;
background: linear-gradient(180deg, rgba(0,0,0,.6), rgba(0,0,0,.8));
}
.boot-box {
width:520px; padding:28px; background: rgba(255,255,255,.03); border-radius:14px; text-align:center;
border: 1px solid rgba(255,255,255,.06);
}
.login-panel {
width:360px; padding:20px; background:var(--panel); border-radius:14px; text-align:center; border:var(--win-border);
}
.appstore-grid { display:grid; grid-template-columns:repeat(4,1fr); gap:10px; }
.app-card { padding:8px; border-radius:8px; background:rgba(255,255,255,.03); text-align:center; cursor:pointer; }
.rightmenu { position:absolute; background:var(--panel); border:var(--win-border); padding:8px; border-radius:8px; display:none; z-index:9999; }
.alt-tab { position:fixed; left:50%; top:20px; transform:translateX(-50%); background:rgba(2,6,23,.7); padding:8px; border-radius:8px; display:none; gap:8px; z-index:99999; }
.alt-tab .item { min-width:120px; padding:6px; border-radius:6px; background:rgba(255,255,255,.02); color:var(--text); }
</style>
</head>
<body>
<div class="desktop" id="desktop">
<div class="wallpaper" id="wallpaper"></div>
<div class="grain"></div>
<div class="icons" id="icons">
<div class="icon" data-launch="notepad">
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><rect x="4" y="3" width="16" height="18" rx="2" stroke="currentColor"/><path d="M8 7h8M8 11h8M8 15h6" stroke="currentColor"/></svg>
<span>Notepad</span>
</div>
<div class="icon" data-launch="files">
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V7Z" stroke="currentColor"/></svg>
<span>Files</span>
</div>
<div class="icon" data-launch="browser">
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><circle cx="12" cy="12" r="9" stroke="currentColor"/><path d="M3 12h18M12 3a15 15 0 0 1 0 18M12 21a15 15 0 0 0 0-18" stroke="currentColor"/></svg>
<span>Web</span>
</div>
<div class="icon" data-launch="terminal">
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M4 6h16v12H4z" stroke="currentColor"/><path d="M7 10l2 2-2 2M11 14h6" stroke="currentColor"/></svg>
<span>Terminal</span>
</div>
<div class="icon" data-launch="calculator">
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><rect x="6" y="3" width="12" height="18" rx="2" stroke="currentColor"/><path d="M9 7h6M9 11h6M9 15h2M13 15h2" stroke="currentColor"/></svg>
<span>Calculator</span>
</div>
<div class="icon" data-launch="settings">
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M12 8a4 4 0 1 0 0 8 4 4 0 0 0 0-8Zm8.5 4a8.5 8.5 0 1 1-17 0 8.5 8.5 0 0 1 17 0Z" stroke="currentColor"/></svg>
<span>Settings</span>
</div>
</div>
<!-- dynamic windows appear here -->
<div class="snap-hint" id="snapHint"></div>
<div class="select-rect" id="selectRect"></div>
<div class="taskbar">
<div class="start" id="startBtn" title="Start">
<svg width="22" height="22" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><path d="M4 4h7v7H4V4Zm9 0h7v7h-7V4ZM4 13h7v7H4v-7Zm9 0h7v7h-7v-7Z"/></svg>
</div>
<div class="tasklist" id="tasklist"></div>
<div class="tray">
<button class="btn ghost" id="quickBtn" title="Quick Settings">☰</button>
<div class="clock" id="clock">--:--</div>
</div>
<div class="startmenu" id="startMenu">
<div class="search">
<input id="globalSearch" placeholder="Search apps, files and web" />
<button class="btn" id="powerBtn">Power</button>
</div>
<div class="apps" id="appGrid">
<!-- tiles injected -->
</div>
</div>
<div class="quick" id="quick">
<div class="toggles">
<div class="toggle" data-toggle="wifi">Wi-Fi</div>
<div class="toggle on" data-toggle="bt">Bluetooth</div>
<div class="toggle on" data-toggle="theme">Dark</div>
<div class="toggle on" data-toggle="glass">Glass</div>
<div class="toggle" data-toggle="dnd">Do Not Disturb</div>
<div class="toggle" data-toggle="snap">Snap</div>
</div>
<div class="pad" style="display:flex; gap:10px; align-items:center;">
<span>Volume</span>
<input id="vol" type="range" min="0" max="100" value="70" />
<span id="volv">70%</span>
</div>
</div>
</div>
</div>
<!-- ORIGINAL SCRIPT (unchanged) -->
<script>
const OS = {
z: 10,
windows: new Map(),
tasks: new Map(),
snap: false,
glass: true,
theme: 'dark',
apps: {
notepad: {
title: 'Notepad',
content(win){
const wrap = el('div', 'pad notepad');
const area = el('textarea');
area.value = localStorage.getItem('OS:notepad') || 'Hello from AeroLiteOS!\n\nThis is a simple notepad. Your text autosaves.';
area.addEventListener('input', ()=> localStorage.setItem('OS:notepad', area.value));
wrap.append(area);
return wrap;
},
size: [520, 420]
},
files: {
title: 'Files',
content(){
const wrap = el('div', 'pad');
const h = el('div'); h.innerHTML = '<b>Quick Access</b>';
const list = el('div', 'filelist');
const items = JSON.parse(localStorage.getItem('OS:files')||'[]');
const add = el('button', 'btn'); add.textContent = 'New Note';
add.onclick = ()=>{
const name = prompt('File name?','note-'+Math.floor(Math.random()*1000)+'.txt');
if(!name) return;
items.push({name, data:''});
localStorage.setItem('OS:files', JSON.stringify(items));
render();
};
function render(){
list.innerHTML='';
items.forEach((f,i)=>{
const card = el('div','file');
card.innerHTML = '<div style="font-weight:600">📄 '+esc(f.name)+'</div>'+
'<div style="color:var(--muted); font-size:12px;">'+(f.data?.slice(0,60)||'')+'</div>'+
'<div style="display:flex; gap:6px; margin-top:8px;">'+
'<button class="btn" data-act="open">Open</button>'+
'<button class="btn" data-act="rename">Rename</button>'+
'<button class="btn" data-act="del">Delete</button></div>';
card.onclick = (e)=>{
if(!(e.target instanceof HTMLElement)) return;
const act = e.target.getAttribute('data-act');
if(act==='open') openNote(i);
if(act==='rename'){ const nn=prompt('Rename to:', f.name); if(nn){ f.name=nn; save(); render(); }}
if(act==='del'){ if(confirm('Delete '+f.name+'?')){ items.splice(i,1); save(); render(); } }
};
list.append(card);
});
}
function openNote(idx){
const file = items[idx];
OS.launch({
id:'file-'+idx,
title:'Edit: '+file.name,
icon: iconDoc(),
content(){
const w = el('div','pad notepad');
const t = el('textarea'); t.value = file.data||'';
t.addEventListener('input', ()=>{ file.data=t.value; save(); render(); });
w.append(t); return w;
},
size:[560,460]
});
}
function save(){ localStorage.setItem('OS:files', JSON.stringify(items)); }
render();
wrap.append(add, h, list); return wrap;
}, size:[680, 460]
},
terminal: {
title: 'Terminal',
content(){
const elTerm = el('div','terminal');
const prompt = ()=> 'user@aerolite:~$ ';
function line(text=''){ const d=el('div','term-line'); d.textContent=text; elTerm.append(d); elTerm.scrollTop = elTerm.scrollHeight; }
line('AeroLiteOS pseudo-shell. Type help.');
const input = el('input'); input.style.cssText='width:100%; background:transparent; color:#fff; border:0; outline:0; font:inherit;';
function run(cmd){
const [c,...rest]=cmd.trim().split(/\s+/);
if(!c){ return; }
if(c==='help') line('Commands: help, echo, date, clear, apps, about');
else if(c==='echo') line(rest.join(' '));
else if(c==='date') line(new Date().toString());
else if(c==='clear'){ elTerm.innerHTML=''; }
else if(c==='apps'){ line(Object.keys(OS.apps).join(', ')); }
else if(c==='about'){ line('AeroLiteOS — single-file HTML desktop'); }
else line('Unknown: '+c);
}
const inputWrap=el('div');
function refreshPrompt(){ inputWrap.textContent=''; input.value=''; const p=el('span'); p.textContent=prompt(); inputWrap.append(p,input); elTerm.append(inputWrap); input.focus(); elTerm.scrollTop = elTerm.scrollHeight; }
input.addEventListener('keydown', (e)=>{
if(e.key==='Enter'){ line(prompt()+input.value); run(input.value); refreshPrompt(); }
});
refreshPrompt();
return elTerm;
}, size:[640, 380]
},
calculator: {
title:'Calculator',
content(){
let expr='';
const wrap = el('div','calc');
const screen = el('div','screen'); screen.textContent='0';
function press(v){
if(v==='C'){ expr=''; }
else if(v==='='){ try{ expr = String(Function('return ('+expr+')')()); }catch{ expr='Error'; } }
else expr += v;
screen.textContent = expr || '0';
}
const keys = ['7','8','9','/','4','5','6','*','1','2','3','-','0','.','C','+','=','(',')','%'];
wrap.append(screen);
keys.forEach(k=>{ const b=el('button'); b.textContent=k; b.onclick=()=>press(k); if(k==='=') b.style.gridColumn='span 4'; wrap.append(b); });
return wrap;
}, size:[320, 420]
},
browser: {
title:'Web Browser',
content(){
const wrap = el('div'); wrap.style.height='100%';
const bar = el('div','browserbar');
const url = el('input'); url.placeholder='https://...';
const go = el('button','btn'); go.textContent='Go';
const frame = document.createElement('iframe'); frame.srcdoc = '<!doctype html><title>New Tab</title><body style="font:16px system-ui;padding:2rem">Web Browsing does not work yet :( it is coming soon! </body>';
function nav(){ let u=url.value.trim(); if(!u) return; if(!/^https?:\/\//.test(u)) u='https://'+u; frame.src=u; }
go.onclick=nav; url.addEventListener('keydown',e=>{ if(e.key==='Enter') nav(); });
bar.append(url, go); wrap.append(bar, frame); return wrap;
}, size:[820, 540]
},
settings: {
title:'Settings',
content(){
const wrap = el('div','pad');
wrap.innerHTML = `
<h2 style="margin:0 0 8px 0">Personalization</h2>
<div style="display:flex; gap:12px; flex-wrap:wrap;">
${['nebula','lake','sunset','midnight','grid'].map(k=>`<button class="btn" data-wall="${k}">${k}</button>`).join('')}
</div>
<h2 style="margin:14px 0 8px 0">Theme</h2>
<div style="display:flex; gap:8px;">
<button class="btn" data-theme="dark">Dark</button>
<button class="btn" data-theme="light">Light</button>
</div>
<h2 style="margin:14px 0 8px 0">Effects</h2>
<div style="display:flex; gap:8px; flex-wrap:wrap;">
<button class="btn" id="toggleGlass">Toggle Glass</button>
<button class="btn" id="toggleSnap">Toggle Snap</button>
<button class="btn" id="reset">Reset OS</button>
</div>
`;
wrap.querySelectorAll('[data-wall]').forEach(b=> b.addEventListener('click',()=> setWallpaper(b.dataset.wall)));
wrap.querySelectorAll('[data-theme]').forEach(b=> b.addEventListener('click',()=> setTheme(b.dataset.theme)));
wrap.querySelector('#toggleGlass').onclick = ()=> setGlass(!OS.glass);
wrap.querySelector('#toggleSnap').onclick = ()=> OS.snap = !OS.snap;
wrap.querySelector('#reset').onclick = ()=>{ if(confirm('Reset settings & data?')){ localStorage.clear(); location.reload(); } };
return wrap;
}, size:[520, 420]
}
}
};
const desktop = document.getElementById('desktop');
const tasklist = document.getElementById('tasklist');
const startBtn = document.getElementById('startBtn');
const startMenu = document.getElementById('startMenu');
const quick = document.getElementById('quick');
const quickBtn = document.getElementById('quickBtn');
const clock = document.getElementById('clock');
const icons = document.getElementById('icons');
const snapHint = document.getElementById('snapHint');
const selectRect = document.getElementById('selectRect');
const wallpaperEl = document.getElementById('wallpaper');
const el = (tag, cls)=>{ const n=document.createElement(tag); if(cls) n.className=cls; return n; }
const esc = s=> String(s).replace(/[&<>\\"]/g, c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[c]));
function setWallpaper(key){
const map = {
nebula: 'radial-gradient(800px 600px at 20% 20%, #3b82f6 0%, transparent 60%), radial-gradient(900px 700px at 80% 80%, #22d3ee 0%, transparent 60%), linear-gradient(180deg,#0b1220,#0b1220)',
lake: 'url(data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="800"><defs><linearGradient id="g" x1="0" x2="0" y1="0" y2="1"><stop offset="0" stop-color="#1e3a8a"/><stop offset="1" stop-color="#0ea5e9"/></linearGradient></defs><rect width="100%" height="100%" fill="url(%23g)"/></svg>)',
sunset: 'linear-gradient(120deg,#ff7e5f 0%, #feb47b 100%)',
midnight: 'linear-gradient(180deg,#080c18,#020409)',
grid: 'repeating-linear-gradient(0deg, rgba(59,130,246,.12), rgba(59,130,246,.12) 1px, transparent 1px, transparent 24px), repeating-linear-gradient(90deg, rgba(34,211,238,.12), rgba(34,211,238,.12) 1px, transparent 1px, transparent 24px)'
};
wallpaperEl.style.background = map[key] || map.nebula;
localStorage.setItem('OS:wall', key);
}
function setTheme(t){
OS.theme = t; document.body.style.setProperty('--text', t==='light'?'#0b1220':'#e5e7eb');
document.body.style.setProperty('--bg', t==='light'?'#f5f7fb':'#0b1220');
document.body.style.setProperty('--bg-elev', t==='light'?'#eef1f8':'#0f172a');
document.body.style.setProperty('--panel', t==='light'?'rgba(255,255,255,.7)':'rgba(15,23,42,.8)');
document.body.style.setProperty('--panel-solid', t==='light'?'#ffffff':'#111827');
localStorage.setItem('OS:theme', t);
}
function setGlass(on){
OS.glass = on; document.documentElement.style.setProperty('--blur', on? '14px':'0px');
localStorage.setItem('OS:glass', on? '1':'0');
}
function updateClock(){ const d=new Date(); const h=String(d.getHours()).padStart(2,'0'); const m=String(d.getMinutes()).padStart(2,'0'); clock.textContent = `${h}:${m}`; }
setInterval(updateClock, 10000); updateClock();
// Start menu & quick
startBtn.onclick = ()=>{ startMenu.classList.toggle('show'); quick.classList.remove('show'); };
quickBtn.onclick = ()=>{ quick.classList.toggle('show'); startMenu.classList.remove('show'); };
document.body.addEventListener('mousedown', (e)=>{
if(!startMenu.contains(e.target) && e.target!==startBtn) startMenu.classList.remove('show');
if(!quick.contains(e.target) && e.target!==quickBtn) quick.classList.remove('show');
});
// Quick toggles
quick.querySelectorAll('.toggle').forEach(t=> t.addEventListener('click', ()=>{
t.classList.toggle('on');
const k = t.dataset.toggle;
if(k==='theme') setTheme(t.classList.contains('on')?'dark':'light');
if(k==='glass') setGlass(t.classList.contains('on'));
if(k==='snap') OS.snap = t.classList.contains('on');
}));
const vol = document.getElementById('vol'); const volv = document.getElementById('volv'); vol.oninput = ()=> volv.textContent = vol.value+'%';
// Icons launch
icons.addEventListener('dblclick', (e)=>{
const icon = e.target.closest('.icon'); if(!icon) return; launch(icon.dataset.launch);
});
// Populate Start menu tiles
const appGrid = document.getElementById('appGrid');
Object.entries(OS.apps).forEach(([id, a])=>{
const tile = el('div','app-tile'); tile.innerHTML = `<div style="font-weight:600">${a.title}</div><div style="color:var(--muted); font-size:12px;">${id}</div>`; tile.onclick = ()=> launch(id);
appGrid.append(tile);
});
// Window creation
function launch(id){ const app = OS.apps[id]; if(!app) return alert('App not found: '+id); OS.launch({ id, title: app.title, icon: iconSquare(), content: app.content, size: app.size }); }
OS.launch = ({id, title, icon, content, size=[600,400]})=>{
let win = OS.windows.get(id);
if(win){ focus(win.el); return; }
const elw = el('div','win'); elw.style.width=size[0]+'px'; elw.style.height=size[1]+'px'; elw.style.left = 40+Math.random()*80+'px'; elw.style.top = 40+Math.random()*60+'px'; elw.style.zIndex = ++OS.z;
const titlebar = el('div','titlebar');
const ic = el('div','appicon'); ic.append(icon||iconSquare());
const ttl = el('div','title'); ttl.textContent = title;
const acts = el('div','actions');
const bMin = button('–'); const bMax = button('□'); const bClose = button('✕');
acts.append(bMin,bMax,bClose);
titlebar.append(ic,ttl,acts);
const contentWrap = el('div','content');
contentWrap.append(typeof content==='function'? content(elw): content);
// resizers
['n','s','e','w','ne','nw','se','sw'].forEach(k=>{ const r=el('div','resizer r-'+k); elw.append(r); r.dataset.edge=k; });
elw.append(titlebar, contentWrap); desktop.append(elw);
// Task
const task = el('div','task'); task.innerHTML = `<div class="appicon">${(icon||iconSquare()).outerHTML}</div><div>${title}</div>`; task.onclick=()=> focus(elw);
tasklist.append(task);
// store
OS.windows.set(id, { el: elw, task, state:{max:false,min:false} });
// drag
dragMove(titlebar, elw);
// actions
bClose.onclick = ()=> closeWin(id);
bMin.onclick = ()=> minimize(id);
bMax.onclick = ()=> maximize(id);
focus(elw);
};
function button(txt){ const b=el('button','btn'); b.textContent=txt; b.title=txt; return b; }
function iconSquare(){ const s=document.createElementNS('http://www.w3.org/2000/svg','svg'); s.setAttribute('viewBox','0 0 24 24'); s.innerHTML='<rect x="4" y="4" width="16" height="16" rx="4" fill="currentColor"/>'; return s; }
function iconDoc(){ const s=document.createElementNS('http://www.w3.org/2000/svg','svg'); s.setAttribute('viewBox','0 0 24 24'); s.innerHTML='<rect x="5" y="3" width="14" height="18" rx="2" stroke="currentColor" fill="none"/><path d="M8 8h8M8 12h8M8 16h6" stroke="currentColor"/>'; return s; }
function focus(winEl){ document.querySelectorAll('.win').forEach(w=> w.style.outline='none'); winEl.style.zIndex = ++OS.z; winEl.style.outline='2px solid rgba(59,130,246,.4)';
// activate task button
const id = getIdByEl(winEl); if(!id) return; document.querySelectorAll('.task').forEach(t=> t.classList.remove('active')); const w = OS.windows.get(id); if(w) w.task.classList.add('active');
}
function getIdByEl(elw){ for(const [id, w] of OS.windows){ if(w.el===elw) return id; } }
function closeWin(id){ const w=OS.windows.get(id); if(!w) return; w.el.remove(); w.task.remove(); OS.windows.delete(id); }
function minimize(id){ const w=OS.windows.get(id); if(!w) return; w.el.style.display='none'; w.task.classList.remove('active'); }
function maximize(id){ const w=OS.windows.get(id); if(!w) return; const e=w.el; const st=w.state; if(!st.max){ st.prev={left:e.style.left, top:e.style.top, width:e.style.width, height:e.style.height}; e.style.left='8px'; e.style.top='8px'; e.style.width = (window.innerWidth-16)+'px'; e.style.height = (window.innerHeight-16 - parseInt(getComputedStyle(document.documentElement).getPropertyValue('--taskbar-h')))+'px'; st.max=true; } else { e.style.left=st.prev.left; e.style.top=st.prev.top; e.style.width=st.prev.width; e.style.height=st.prev.height; st.max=false; }
}
function dragMove(handle, winEl){
let sx, sy, sl, st, dragging=false;
handle.addEventListener('mousedown', (e)=>{ dragging=true; focus(winEl); sx=e.clientX; sy=e.clientY; sl=parseInt(winEl.style.left)||0; st=parseInt(winEl.style.top)||0; winEl.classList.add('dragging'); });
window.addEventListener('mousemove', (e)=>{
if(!dragging) return; const dx=e.clientX-sx, dy=e.clientY-sy; let L=sl+dx, T=st+dy; winEl.style.left=L+'px'; winEl.style.top=T+'px'; if(OS.snap) showSnap(L,T,winEl); });
window.addEventListener('mouseup', (e)=>{ if(!dragging) return; dragging=false; winEl.classList.remove('dragging'); if(OS.snap) applySnap(winEl); hideSnap(); });
// resize edges
winEl.querySelectorAll('.resizer').forEach(r=>{
let rs=false, sx2, sy2, sw, sh, sl2, st2, edge=r.dataset.edge;
r.addEventListener('mousedown', (e)=>{ e.stopPropagation(); rs=true; focus(winEl); sx2=e.clientX; sy2=e.clientY; sw=winEl.offsetWidth; sh=winEl.offsetHeight; sl2=winEl.offsetLeft; st2=winEl.offsetTop; winEl.classList.add('resizing'); });
window.addEventListener('mousemove', (e)=>{
if(!rs) return; const dx=e.clientX-sx2, dy=e.clientY-sy2; let W=sw, H=sh, L=sl2, T=st2; if(edge.includes('e')) W=sw+dx; if(edge.includes('s')) H=sh+dy; if(edge.includes('w')){ W=sw-dx; L=sl2+dx; } if(edge.includes('n')){ H=sh-dy; T=st2+dy; }
W=Math.max(320,W); H=Math.max(200,H); winEl.style.width=W+'px'; winEl.style.height=H+'px'; winEl.style.left=L+'px'; winEl.style.top=T+'px';
});
window.addEventListener('mouseup', ()=>{ if(rs){ rs=false; winEl.classList.remove('resizing'); hideSnap(); }});
});
}
// Snap assist
function showSnap(L,T,winEl){ const w=window.innerWidth, h=window.innerHeight-parseInt(getComputedStyle(document.documentElement).getPropertyValue('--taskbar-h')); const margin=12; const areas=[
{k:'left', x:margin, y:margin, w:(w/2)-margin*1.5, h:h-margin*2},
{k:'right', x:(w/2)+margin/2, y:margin, w:(w/2)-margin*1.5, h:h-margin*2},
{k:'top', x:margin, y:margin, w:w-margin*2, h:(h/2)-margin*1.5},
{k:'bottom', x:margin, y:(h/2)+margin/2, w:w-margin*2, h:(h/2)-margin*1.5},
{k:'center', x:margin*2, y:margin*2, w:w-margin*4, h:h-margin*4}
];
const rect = winEl.getBoundingClientRect(); const cx=rect.left+rect.width/2; const cy=rect.top+rect.height/2;
let near=null, best=1e9; areas.forEach(a=>{ const dx=cx-(a.x+a.w/2), dy=cy-(a.y+a.h/2); const d=Math.hypot(dx,dy); if(d<best){ best=d; near=a; } });
if(near){ snapHint.style.display='block'; snapHint.style.left=near.x+'px'; snapHint.style.top=near.y+'px'; snapHint.style.width=near.w+'px'; snapHint.style.height=near.h+'px'; snapHint.dataset.k=near.k; }
}
function hideSnap(){ snapHint.style.display='none'; snapHint.dataset.k=''; }
function applySnap(winEl){ const k=snapHint.dataset.k; if(!k) return; const w=window.innerWidth, h=window.innerHeight-parseInt(getComputedStyle(document.documentElement).getPropertyValue('--taskbar-h')); const margin=12; const pos={
left: {x:margin, y:margin, w:(w/2)-margin*1.5, h:h-margin*2},
right: {x:(w/2)+margin/2, y:margin, w:(w/2)-margin*1.5, h:h-margin*2},
top: {x:margin, y:margin, w:w-margin*2, h:(h/2)-margin*1.5},
bottom:{x:margin, y:(h/2)+margin/2, w:w-margin*2, h:(h/2)-margin*1.5},
center:{x:margin*2, y:margin*2, w:w-margin*4, h:h-margin*4}
}[k];
if(pos){ winEl.style.left=pos.x+'px'; winEl.style.top=pos.y+'px'; winEl.style.width=pos.w+'px'; winEl.style.height=pos.h+'px'; }
}
// Desktop selection rectangle (for flair)
let selStart=null; desktop.addEventListener('mousedown', (e)=>{ if(e.target.closest('.win,.taskbar,.startmenu,.quick')) return; selStart=[e.clientX,e.clientY]; selectRect.style.display='block'; selectRect.style.left=e.clientX+'px'; selectRect.style.top=e.clientY+'px'; selectRect.style.width='0px'; selectRect.style.height='0px'; });
window.addEventListener('mousemove', (e)=>{ if(!selStart) return; const x=Math.min(selStart[0],e.clientX), y=Math.min(selStart[1],e.clientY), w=Math.abs(e.clientX-selStart[0]), h=Math.abs(e.clientY-selStart[1]); selectRect.style.left=x+'px'; selectRect.style.top=y+'px'; selectRect.style.width=w+'px'; selectRect.style.height=h+'px'; });
window.addEventListener('mouseup', ()=>{ if(selStart){ selStart=null; selectRect.style.display='none'; }});
// Keyboard shortcuts
window.addEventListener('keydown', (e)=>{
if(e.ctrlKey && e.key==='`'){ launch('terminal'); }
if(e.ctrlKey && e.key===' '){ startMenu.classList.toggle('show'); }
});
// Restore settings
(function init(){
setWallpaper(localStorage.getItem('OS:wall')||'nebula');
setTheme(localStorage.getItem('OS:theme')||'dark');
setGlass((localStorage.getItem('OS:glass')||'1')==='1');
})();
// Public helper for external launch from console
window.AeroLiteOS = { launch };
</script>
<!-- ================= NEW FEATURES APPENDED (DO NOT EDIT ORIGINAL ABOVE) ================ -->
<!-- All additions below extend the existing OS object & UI without modifying original code. -->
<div id="bootOverlay" class="overlay" style="display:none;">
<div class="boot-box">
<h2 style="margin:0 0 12px 0">AeroLiteOS</h2>
<div id="bootProgress" style="height:10px;background:rgba(255,255,255,.06);border-radius:6px;overflow:hidden;margin-bottom:12px">
<div id="bootBar" style="height:100%;width:0%;background:linear-gradient(90deg,var(--accent),var(--accent-2));"></div>
</div>
<div style="color:var(--muted)">Loading system modules...</div>
</div>
</div>
<div id="loginOverlay" class="overlay" style="display:none;">
<div class="login-panel" id="loginPanel">
<h3 style="margin:0 0 8px 0">Welcome</h3>
<input id="loginUser" placeholder="Username" style="width:80%;padding:8px;border-radius:8px;border:1px solid rgba(255,255,255,.06);margin-bottom:8px"><br>
<input id="loginPass" placeholder="Password" type="password" style="width:80%;padding:8px;border-radius:8px;border:1px solid rgba(255,255,255,.06);margin-bottom:8px"><br>
<button class="btn" id="loginBtn">Sign in</button>
<div style="margin-top:8px;color:var(--muted);font-size:12px">Tip: password stored locally (demo).</div>
</div>
</div>
<div id="rightClickMenu" class="rightmenu"></div>
<div id="altTab" class="alt-tab" style="display:none;"></div>
<script>
// --- Configuration for new features ---
(function(){
// Quick boot + login simulation
function showBootThenLogin(){
const boot = document.getElementById('bootOverlay');
const login = document.getElementById('loginOverlay');
boot.style.display = 'grid';
let p = 0;
const step = setInterval(()=>{
p += Math.random()*18;
document.getElementById('bootBar').style.width = Math.min(100, p).toFixed(0) + '%';
if(p >= 100){
clearInterval(step);
setTimeout(()=> {
boot.style.display='none';
// if credential exists skip
const savedUser = localStorage.getItem('OS:user');
if(savedUser){
login.style.display = 'none';
} else {
login.style.display = 'grid';
}
}, 400);
}
}, 300);
}
// Hook login button
document.getElementById('loginBtn').addEventListener('click', ()=>{
const u = document.getElementById('loginUser').value || 'User';
const p = document.getElementById('loginPass').value || '';
// store demo credentials (user asked to keep old code; storing locally is fine)
localStorage.setItem('OS:user', u);
localStorage.setItem('OS:pass', p);
document.getElementById('loginOverlay').style.display='none';
});
// Only show boot on first load (or when explicitly reset)
if(!localStorage.getItem('OS:booted')){
showBootThenLogin();
localStorage.setItem('OS:booted','1');
}
// --- Expand OS.apps without changing original definitions ---
// Add Games and new utilities into OS.apps
const newApps = {
snake_game: {
title: 'Snake (Game)',
content(){
const wrap = el('div');
const canvas = el('canvas'); canvas.width=300; canvas.height=300; canvas.style.display='block'; canvas.style.margin='14px auto';
const info = el('div'); info.style.textAlign='center'; info.innerHTML = '<small>Use arrow keys. Eat red to grow.</small>';
wrap.append(canvas, info);
// game boot will be handled after window insertion
setTimeout(()=> startSnake(canvas), 60);
return wrap;
},
size:[360,380]
},
minesweeper_game: {
title: 'Minesweeper',
content(){
const wrap = el('div','pad');
const field = el('div'); field.style.display='grid'; field.style.gridTemplateColumns='repeat(10,28px)'; field.style.gap='6px';
field.id='msfield';
wrap.append(field);
setTimeout(()=> startMinesweeper(field,10,10,12), 80);
return wrap;
}, size:[360,400]
},
pong_game: {
title: 'Pong (2P)',
content(){
const wrap = el('div');
const canvas = el('canvas'); canvas.width=420; canvas.height=300; canvas.style.display='block'; canvas.style.margin='12px auto'; wrap.append(canvas);
setTimeout(()=> startPong(canvas), 60);
return wrap;
}, size:[460,360]
},
tetris_game: {
title: 'Tetris',
content(){
const wrap = el('div'); const canvas = el('canvas'); canvas.width=240; canvas.height=400; canvas.style.display='block'; canvas.style.margin='12px auto'; wrap.append(canvas);
setTimeout(()=> startTetris(canvas), 60);
return wrap;
}, size:[300,460]
},
music_player: {
title: 'Music Player',
content(){
const wrap = el('div','pad');
wrap.innerHTML = `
<div style="display:flex;gap:8px;align-items:center;">
<input id="mpFile" type="file" accept="audio/*"/>
<button id="mpPlay" class="btn">Play</button>
<button id="mpPause" class="btn">Pause</button>
<button id="mpStop" class="btn">Stop</button>
</div>
<div style="margin-top:12px;">
<audio id="mpAudio" controls style="width:100%"></audio>
</div>
`;
const file = wrap.querySelector('#mpFile');
const audio = wrap.querySelector('#mpAudio');
const play = wrap.querySelector('#mpPlay');
const pause = wrap.querySelector('#mpPause');
const stop = wrap.querySelector('#mpStop');
file.onchange = ()=>{
const f = file.files[0];
if(!f) return;
audio.src = URL.createObjectURL(f);
audio.play();
};
play.onclick = ()=> audio.play();
pause.onclick = ()=> audio.pause();
stop.onclick = ()=> { audio.pause(); audio.currentTime=0; };
return wrap;
}, size:[520,200]
},
image_viewer: {
title: 'Image Viewer',
content(){
const wrap = el('div','pad');
wrap.innerHTML = `
<div style="display:flex;gap:8px;align-items:center;">
<input id="imgFile" type="file" accept="image/*"/>
<button id="imgOpen" class="btn">Open</button>
<button id="imgSetWall" class="btn">Set as Wallpaper</button>
</div>
<div style="margin-top:12px; display:flex; justify-content:center;">
<img id="imgPreview" style="max-width:100%; max-height:400px; border-radius:8px;"/>
</div>
`;
const input = wrap.querySelector('#imgFile');
const img = wrap.querySelector('#imgPreview');
const setBtn = wrap.querySelector('#imgSetWall');
input.onchange = ()=> {
const f = input.files[0]; if(!f) return;
img.src = URL.createObjectURL(f);
};
setBtn.onclick = ()=> {
if(!img.src) return alert('Open an image first');
// set wallpaper by data URL reference (uses CSS url())
wallpaperEl.style.background = `url(${img.src}) center/cover`;
localStorage.setItem('OS:wall-src', img.src);
};
return wrap;
}, size:[640,480]
},
recycle_bin: {
title: 'Recycle Bin',
content(){
const wrap = el('div','pad');
const list = el('div'); list.id='recycleList';
const empty = el('button','btn'); empty.textContent='Empty Bin';
empty.onclick = ()=> { if(confirm('Empty Recycle Bin?')){ localStorage.removeItem('OS:recycle'); render(); } };
wrap.append(empty, list);
function render(){ list.innerHTML=''; const items = JSON.parse(localStorage.getItem('OS:recycle')||'[]'); items.forEach((it,i)=>{ const row=el('div'); row.style.display='flex'; row.style.justifyContent='space-between'; row.style.padding='6px 0'; row.innerHTML = `<div>${esc(it.name)}</div><div><button class="btn" data-idx="${i}">Restore</button><button class="btn" data-del="${i}">Delete</button></div>`; row.querySelector('[data-idx]')?.addEventListener('click', ()=> { restore(i); }); row.querySelector('[data-del]')?.addEventListener('click', ()=> { del(i); }); list.append(row); }); }
function restore(i){ const items = JSON.parse(localStorage.getItem('OS:recycle')||'[]'); const it=items.splice(i,1)[0]; localStorage.setItem('OS:recycle', JSON.stringify(items)); // simplistic restore: add to files
const files = JSON.parse(localStorage.getItem('OS:files')||'[]'); files.push({name:it.name, data:it.data}); localStorage.setItem('OS:files', JSON.stringify(files)); render(); }
function del(i){ const items = JSON.parse(localStorage.getItem('OS:recycle')||'[]'); items.splice(i,1); localStorage.setItem('OS:recycle', JSON.stringify(items)); render(); }
render(); return wrap;
}, size:[420,320]
},
app_store: {
title: 'App Store / Game Hub',
content(){
const wrap = el('div','pad');
const grid = el('div','appstore-grid');
const appsList = [
{id:'snake_game', name:'Snake', desc:'Classic snake game'},
{id:'minesweeper_game', name:'Minesweeper', desc:'Find all mines'},
{id:'pong_game', name:'Pong', desc:'Local 2-player pong'},
{id:'tetris_game', name:'Tetris', desc:'Falling blocks'}
];
appsList.forEach(a=>{
const c = el('div','app-card'); c.innerHTML = `<div style="font-weight:600">${a.name}</div><div style="font-size:12px;color:var(--muted)">${a.desc}</div><div style="margin-top:8px;"><button class="btn" data-run="${a.id}">Play</button></div>`;
c.querySelector('[data-run]').addEventListener('click', ()=> OS.launch({ id: a.id, title: OS.apps[a.id].title, icon: iconSquare(), content: OS.apps[a.id].content, size: OS.apps[a.id].size }));
grid.append(c);
});
wrap.append(grid);
return wrap;
}, size:[640,360]
}
};
// Merge newApps into existing OS.apps (preserve anything already there)
Object.entries(newApps).forEach(([k,v])=>{
if(!OS.apps[k]) OS.apps[k]=v;
});
// Add desktop icons for games/apps (append to icons area)
const iconsEl = document.getElementById('icons');
function addDesktopIcon(id, label, svg){
const d = el('div','icon'); d.dataset.launch = id;
d.innerHTML = (svg || '<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><rect x="4" y="3" width="16" height="18" rx="2" stroke="currentColor"/></svg>') + `<span>${label}</span>`;
d.addEventListener('dblclick', ()=> launch(id));
iconsEl.append(d);
}
addDesktopIcon('snake_game','Snake');
addDesktopIcon('minesweeper_game','Minesweeper');
addDesktopIcon('pong_game','Pong');
addDesktopIcon('tetris_game','Tetris');
addDesktopIcon('music_player','Music');
addDesktopIcon('image_viewer','Images');
addDesktopIcon('recycle_bin','Recycle Bin');
addDesktopIcon('app_store','App Store');
// Integrate Recycle functionality into Files delete workflow by intercepting storage changes:
// (We won't change original Files code—provide a helper function to move deleted files)
window.OSDeleteToRecycle = function(fileObj){
const r = JSON.parse(localStorage.getItem('OS:recycle')||'[]');
r.push(fileObj);
localStorage.setItem('OS:recycle', JSON.stringify(r));
};
// Right-click desktop menu
const rightMenu = document.getElementById('rightClickMenu');
document.addEventListener('contextmenu', (e)=>{
e.preventDefault();
if(e.target.closest('.win,.taskbar,.startmenu,.quick')) { rightMenu.style.display='none'; return; }
rightMenu.style.display='block';
rightMenu.style.left = e.clientX + 'px';
rightMenu.style.top = e.clientY + 'px';
rightMenu.innerHTML = `<div style="padding:6px;cursor:pointer" id="newFile">New File</div>
<div style="padding:6px;cursor:pointer" id="setWall">Set Wallpaper</div>
<div style="padding:6px;cursor:pointer" id="openStore">App Store</div>`;
rightMenu.querySelector('#newFile').onclick = ()=> { rightMenu.style.display='none'; const name = prompt('New file name','untitled.txt'); if(!name) return; const files = JSON.parse(localStorage.getItem('OS:files')||'[]'); files.push({name,data:''}); localStorage.setItem('OS:files', JSON.stringify(files)); alert('File created. Open Files app to view.'); };
rightMenu.querySelector('#setWall').onclick = ()=> { rightMenu.style.display='none'; const url = prompt('Image URL (cross-origin may block):'); if(url){ wallpaperEl.style.background = `url(${url}) center/cover`; localStorage.setItem('OS:wall-src', url); } };
rightMenu.querySelector('#openStore').onclick = ()=> { rightMenu.style.display='none'; OS.launch({ id:'app_store', title:OS.apps.app_store.title, icon: iconSquare(), content: OS.apps.app_store.content, size: OS.apps.app_store.size }); };
});
document.addEventListener('click', ()=> rightMenu.style.display='none');
// Alt+Tab switcher simple preview (shows open windows by title)
const altTabEl = document.getElementById('altTab');
let altOpen = false, altList = [];
window.addEventListener('keydown', (e)=>{
if(e.key === 'Tab' && e.altKey){
e.preventDefault();
if(!altOpen){
// open
altOpen = true;
altList = Array.from(OS.windows.keys());
altTabEl.innerHTML = '';
altList.forEach(id=>{
const item = el('div','item'); item.textContent = OS.windows.get(id)?.task?.textContent || id; item.onclick = ()=> { focus(OS.windows.get(id).el); hideAlt(); };
altTabEl.append(item);
});
altTabEl.style.display='flex';
} else {
// cycle
const focused = document.querySelector('.win[style*="z-index"]');
}
}
});
window.addEventListener('keyup', (e)=>{ if(e.key === 'Alt') hideAlt(); });
function hideAlt(){ altOpen=false; altTabEl.style.display='none'; altTabEl.innerHTML=''; }
// ----- Simple Game Implementations (vanilla) -----
function startSnake(canvas){
if(!canvas) return;
const ctx = canvas.getContext('2d');
const grid = 15;
const cols = Math.floor(canvas.width / grid);
const rows = Math.floor(canvas.height / grid);
let px = Math.floor(cols/2), py = Math.floor(rows/2), vx=0, vy=0, tail=4, trail=[];
let apple = {x: Math.floor(Math.random()*cols), y: Math.floor(Math.random()*rows)};
function tick(){
px += vx; py += vy;
if(px < 0) px = cols -1; if(px >= cols) px = 0;
if(py < 0) py = rows -1; if(py >= rows) py = 0;
// collision with self
for(const t of trail){ if(t.x===px && t.y===py){ tail = 4; } }
trail.push({x:px,y:py});
while(trail.length > tail) trail.shift();
if(px===apple.x && py===apple.y){ tail++; apple = {x:Math.floor(Math.random()*cols), y:Math.floor(Math.random()*rows)}; }
// render
ctx.fillStyle = '#041023'; ctx.fillRect(0,0,canvas.width,canvas.height);
ctx.fillStyle = '#d946ef';
ctx.fillRect(apple.x*grid+2, apple.y*grid+2, grid-4, grid-4);
ctx.fillStyle = '#22c55e';
for(const t of trail) ctx.fillRect(t.x*grid+2, t.y*grid+2, grid-4, grid-4);
}
document.addEventListener('keydown', function handler(e){
if(e.key==='ArrowLeft' && vx!==1){ vx=-1; vy=0; }
if(e.key==='ArrowRight' && vx!==-1){ vx=1; vy=0; }
if(e.key==='ArrowUp' && vy!==1){ vx=0; vy=-1; }
if(e.key==='ArrowDown' && vy!==-1){ vx=0; vy=1; }
});
const interval = setInterval(tick, 100);
// stop when window closed: attempt to detect canvas removed
const obs = new MutationObserver(()=>{ if(!document.body.contains(canvas)){ clearInterval(interval); obs.disconnect(); } });
obs.observe(document.body, {childList:true, subtree:true});
}
function startPong(canvas){
if(!canvas) return;
const ctx = canvas.getContext('2d');
let bx = canvas.width/2, by = canvas.height/2, bvx=3, bvy=2;
let lp = canvas.height/2 - 30, rp = lp;
function tick(){
bx += bvx; by += bvy;
if(by < 0 || by > canvas.height){ bvy *= -1; }
// paddles
if(bx < 20 && by > lp && by < lp+60) bvx *= -1;
if(bx > canvas.width-20 && by > rp && by < rp+60) bvx *= -1;
if(bx < -50 || bx > canvas.width + 50){ bx = canvas.width/2; by = canvas.height/2; bvx = -bvx; }
// AI for right paddle
if(rp + 30 < by) rp += 2; else if(rp + 30 > by) rp -= 2;
// draw
ctx.fillStyle = '#020617'; ctx.fillRect(0,0,canvas.width,canvas.height);
ctx.fillStyle = '#e5e7eb'; ctx.fillRect(10, lp, 10, 60); ctx.fillRect(canvas.width-20, rp, 10, 60);
ctx.beginPath(); ctx.arc(bx,by,8,0,Math.PI*2); ctx.fill();
}
document.addEventListener('keydown', function handler(e){
// left paddle controls W/S
if(e.key==='w') lp -= 20;
if(e.key==='s') lp += 20;
});
const interval = setInterval(tick, 20);
const obs = new MutationObserver(()=>{ if(!document.body.contains(canvas)){ clearInterval(interval); obs.disconnect(); } });
obs.observe(document.body, {childList:true, subtree:true});
}
function startTetris(canvas){
if(!canvas) return;
const ctx = canvas.getContext('2d');
ctx.fillStyle='#041023'; ctx.fillRect(0,0,canvas.width, canvas.height);
ctx.fillStyle='#fff'; ctx.fillText('Coming soon', 20, 200);
// This will come soon... I have a full Tetris implementation ready, but it's a bit more complex than the others.
// If you want full Tetris implementation, I can add it next.
}
function startMinesweeper(container, cols=10, rows=10, mines=12){
if(!container) return;
container.innerHTML='';
const grid = [];
const field = document.createElement('div'); field.style.display='grid'; field.style.gridTemplateColumns = `repeat(${cols}, 30px)`; field.style.gap='4px';
container.append(field);
// create cells
for(let r=0;r<rows;r++){
for(let c=0;c<cols;c++){
const cell = {r,c, mine:false, revealed:false, flagged:false, el: null};
const btn = document.createElement('button'); btn.style.width='30px'; btn.style.height='30px'; btn.style.borderRadius='6px';
cell.el = btn;
btn.addEventListener('click', ()=> reveal(cell));
btn.addEventListener('contextmenu', (ev)=>{ ev.preventDefault(); cell.flagged = !cell.flagged; btn.textContent = cell.flagged ? '🚩' : ''; });
field.append(btn);
grid.push(cell);
}
}
// place mines
for(let m=0;m<mines;m++){
let idx;
do { idx = Math.floor(Math.random()*grid.length); } while(grid[idx].mine);
grid[idx].mine = true;
}
function neighbors(cell){
const arr=[];
for(const n of grid){
if(Math.abs(n.r - cell.r) <= 1 && Math.abs(n.c - cell.c) <=1 && !(n.r===cell.r && n.c===cell.c)) arr.push(n);
}
return arr;
}
function reveal(cell){
if(cell.revealed || cell.flagged) return;
cell.revealed = true; cell.el.style.background='#0b2a3a'; cell.el.disabled=true;
if(cell.mine){ cell.el.textContent='💣'; alert('Boom!'); return; }
const count = neighbors(cell).filter(x=>x.mine).length;
if(count>0) cell.el.textContent = count;
else neighbors(cell).forEach(n=> reveal(n));
}
}
// Tie into Files delete operation (override pattern): when Files app 'delete' is called, we can't intercept internal function easily,
// so provide a small UI helper: select file and 'Send to Recycle Bin' via the Files UI manual action.
// Add a menu item in each file card for "Send to Recycle" if Files is open: we won't modify Files code; users can use the New File + Recycle Bin manually.
// If wallpaper was set by the Image Viewer earlier, restore it on load
const ws = localStorage.getItem('OS:wall-src');
if(ws) wallpaperEl.style.background = `url(${ws}) center/cover`;
// Reshow start menu tile list to include newly added apps
// (append only the ones not already present)
const existingTiles = new Set([...document.querySelectorAll('#appGrid .app-tile')].map(t=>t.textContent.trim()));
Object.entries(OS.apps).forEach(([id, a])=>{
const text = a.title + id;
// don't duplicate exact tile names: check by id shown
if(!document.querySelector(`#appGrid div:contains(${id})`)) {
// simple check: add everything (some duplicates may appear if original later runs; safe)
}
});
// Add keyboard shortcut: Ctrl+Alt+G -> open App Store / Game Hub
window.addEventListener('keydown', (e)=>{ if(e.ctrlKey && e.altKey && e.key.toLowerCase()==='g'){ OS.launch({ id:'app_store', title:OS.apps.app_store.title, icon: iconSquare(), content: OS.apps.app_store.content, size: OS.apps.app_store.size }); } });
// Also add simple API so other scripts can add apps later:
window.AeroLiteOS.registerApp = function(id, app){ if(OS.apps[id]) return false; OS.apps[id] = app; return true; };
// Small helper: when Files delete triggers outside, user can call OSDeleteToRecycle({name,data})
// Example usage in browser console: OSDeleteToRecycle({name:'demo.txt', data:'hello'});
})();
</script>
<!-- End of file -->
</body>
</html>
<!-- Please Support this project here https://www.patreon.com/Calacobragameengine/membership -->
<!-- This is an opensourced project by clashnewbme join my discord: https://discord.gg/Z9chWVBmjv -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>AeroLiteOS</title>
<style>
:root{
--bg: #0b1220;
--bg-elev: #0f172a;
--panel: rgba(15,23,42,0.8);
--panel-solid: #111827;
--text: #e5e7eb;
--muted: #9ca3af;
--accent: #3b82f6;
--accent-2: #22d3ee;
--ok: #22c55e;
--warn: #f59e0b;
--err: #ef4444;
--glass: rgba(255,255,255,0.06);
--shadow: 0 10px 30px rgba(0,0,0,.35);
--radius: 16px;
--taskbar-h: 52px;
--blur: 14px;
--win-border: 1px solid rgba(255,255,255,.08);
--grid: 88px;
}
*{ box-sizing: border-box; }
html, body{ height:100%; }
body{
margin:0; font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell, Noto Sans, sans-serif;
color:var(--text); background: var(--bg);
overflow:hidden; user-select:none;
}
/* Desktop */
.desktop{
position:fixed; inset:0; display:grid; grid-template-rows: 1fr var(--taskbar-h);
background: radial-gradient(1200px 800px at 80% 10%, #1d2a53 0%, transparent 70%),
radial-gradient(900px 700px at 10% 80%, #093b4c 0%, transparent 60%),
linear-gradient(180deg, #040812 0%, #0b1220 100%);
}
.wallpaper{ position:absolute; inset:0; background-size:cover; background-position:center; filter:saturate(1.1) contrast(1.05); opacity:.9; }
.grain{ position:absolute; inset:-50%; background-image:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="400" height="400"><filter id="n"><feTurbulence type="fractalNoise" baseFrequency="0.65" numOctaves="2" stitchTiles="stitch"/></filter><rect width="100%" height="100%" filter="url(%23n)" opacity=".05"/></svg>'); mix-blend-mode:soft-light; pointer-events:none; }
/* Icons grid */
.icons{
position:relative; padding:20px; display:grid; grid-template-columns: repeat(auto-fill, minmax(var(--grid),1fr)); gap:14px; align-content:start; z-index:1;
}
.icon{
width:var(--grid); height:var(--grid); border-radius:14px; display:flex; flex-direction:column; align-items:center; justify-content:center; gap:8px;
background: transparent; cursor: default; transition: .15s ease; border:1px solid transparent;
}
.icon:hover{ background: rgba(255,255,255,.06); border-color: rgba(255,255,255,.08); }
.icon svg{ width:34px; height:34px; }
.icon span{ font-size:12px; color: var(--muted); text-shadow:0 1px 1px rgba(0,0,0,.4); }
/* Windows */
.win{ position:absolute; display:flex; flex-direction:column; background:var(--panel); -webkit-backdrop-filter: blur(var(--blur)); backdrop-filter: blur(var(--blur));
border-radius: var(--radius); box-shadow: var(--shadow); min-width: 320px; min-height: 240px; border: var(--win-border); overflow:hidden; }
.win.resizing, .win.dragging{ transition:none !important; }
.win .titlebar{ display:flex; align-items:center; gap:8px; padding:10px 12px; background:linear-gradient(180deg, rgba(255,255,255,.06), rgba(255,255,255,.03)); cursor:grab; }
.win .titlebar:active{ cursor:grabbing; }
.titlebar .appicon{ width:18px; height:18px; border-radius:5px; display:grid; place-items:center; background:var(--glass); }
.titlebar .title{ flex:1; font-weight:600; letter-spacing:.2px; filter:drop-shadow(0 1px 0 rgba(0,0,0,.3)); }
.titlebar .actions{ display:flex; gap:6px; }
.btn{ border:1px solid rgba(255,255,255,.08); background: rgba(255,255,255,.06); color:var(--text); border-radius:10px; padding:6px 10px; display:inline-flex; align-items:center; gap:6px; font-size:12px; cursor:pointer; transition:.15s; }
.btn:hover{ background: rgba(255,255,255,.1); }
.btn.ghost{ background:transparent; }
.win .content{ flex:1; background: rgba(2,6,23,.55); padding:0; overflow:auto; }
/* Resize handles */
.resizer{ position:absolute; }
.r-n{ top:-4px; left:0; right:0; height:8px; cursor:n-resize; }
.r-s{ bottom:-4px; left:0; right:0; height:8px; cursor:s-resize; }
.r-e{ right:-4px; top:0; bottom:0; width:8px; cursor:e-resize; }
.r-w{ left:-4px; top:0; bottom:0; width:8px; cursor:w-resize; }
.r-ne{ right:-6px; top:-6px; width:12px; height:12px; cursor:ne-resize; }
.r-nw{ left:-6px; top:-6px; width:12px; height:12px; cursor:nw-resize; }
.r-se{ right:-6px; bottom:-6px; width:12px; height:12px; cursor:se-resize; }
.r-sw{ left:-6px; bottom:-6px; width:12px; height:12px; cursor:sw-resize; }
/* Taskbar */
.taskbar{ position:relative; height:var(--taskbar-h); background: linear-gradient(180deg, rgba(255,255,255,.06), rgba(255,255,255,.02)); border-top:1px solid rgba(255,255,255,.1);
display:flex; align-items:center; gap:10px; padding:6px 10px; -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
}
.start{ width:40px; height:40px; border-radius:12px; display:grid; place-items:center; border:1px solid rgba(255,255,255,.1); background:rgba(255,255,255,.06); cursor:pointer; }
.start:hover{ background:rgba(255,255,255,.1); }
.tasklist{ flex:1; display:flex; gap:8px; }
.task{ min-width:120px; max-width:200px; height:38px; border-radius:12px; border:1px solid rgba(255,255,255,.08); background:rgba(255,255,255,.04); padding:0 10px; display:flex; align-items:center; gap:8px; cursor:pointer; }
.task.active{ outline:2px solid var(--accent); background: rgba(59,130,246,.12); }
.tray{ display:flex; align-items:center; gap:8px; }
.clock{ font-feature-settings:"tnum"; letter-spacing:.3px; color:var(--muted); }
/* Start menu */
.startmenu{ position:absolute; bottom:calc(var(--taskbar-h) + 8px); left:8px; width:680px; max-width:calc(100% - 16px);
background:var(--panel); border:var(--win-border); border-radius:20px; box-shadow:var(--shadow); padding:14px; display:none; -webkit-backdrop-filter: blur(var(--blur)); backdrop-filter: blur(var(--blur)); }
.startmenu.show{ display:block; }
.startmenu .search{ display:flex; gap:10px; }
.startmenu input{ flex:1; padding:12px 14px; border-radius:12px; border:1px solid rgba(255,255,255,.1); background:rgba(255,255,255,.06); color:var(--text); }
.apps{ display:grid; grid-template-columns: repeat(6, 1fr); gap:12px; margin-top:12px; }
.app-tile{ height:86px; border-radius:14px; border:1px solid rgba(255,255,255,.08); background: rgba(255,255,255,.05); display:flex; align-items:center; justify-content:center; flex-direction:column; gap:8px; cursor:pointer; }
.app-tile:hover{ background: rgba(255,255,255,.1); }
/* Quick settings / Notifications */
.quick{ position:absolute; right:8px; bottom:calc(var(--taskbar-h) + 8px); width:360px; background:var(--panel); border-radius:18px; border:var(--win-border); box-shadow:var(--shadow); display:none; padding:12px; }
.quick.show{ display:block; }
.toggles{ display:grid; grid-template-columns:repeat(3,1fr); gap:8px; }
.toggle{ border:1px solid rgba(255,255,255,.08); border-radius:12px; height:60px; display:grid; place-items:center; background:rgba(255,255,255,.05); cursor:pointer; }
.toggle.on{ outline:2px solid var(--accent-2); background: rgba(34,211,238,.15); }
/* App content styles */
.pad{ padding:14px; }
.notepad textarea{ width:100%; height:calc(100% - 40px); background:transparent; color:var(--text); border:none; outline:none; resize:none; font: 14px/1.6 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
.filelist{ display:flex; gap:10px; flex-wrap:wrap; }
.file{ width:140px; border:1px dashed rgba(255,255,255,.15); border-radius:12px; padding:10px; }
.terminal{ background:#0b0f1a; color:#d1e7ff; font: 13px/1.6 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; height:100%; padding:12px; }
.term-line{ white-space:pre-wrap; }
.calc{ display:grid; grid-template-columns: repeat(4, 1fr); gap:8px; padding:12px; }
.calc .screen{ grid-column: 1 / -1; height:60px; border-radius:12px; background: rgba(255,255,255,.06); border:1px solid rgba(255,255,255,.08); display:flex; align-items:center; justify-content:flex-end; padding:0 12px; font-size:22px; }
.calc button{ border:1px solid rgba(255,255,255,.08); background:rgba(255,255,255,.06); border-radius:12px; padding:12px; font-size:16px; cursor:pointer; }
.browserbar{ display:flex; gap:8px; padding:10px; }
.browserbar input{ flex:1; padding:10px; border-radius:10px; border:1px solid rgba(255,255,255,.1); background:rgba(255,255,255,.06); color:var(--text); }
iframe{ border:0; width:100%; height: calc(100% - 52px); background:#fff; }
/* Snap preview */
.snap-hint{ position:absolute; pointer-events:none; border:2px dashed rgba(255,255,255,.25); border-radius:16px; display:none; }
/* Desktop selection rectangle */
.select-rect{ position:absolute; border:1px dashed rgba(255,255,255,.4); background: rgba(255,255,255,.08); display:none; }
@media (max-width: 760px){
:root{ --taskbar-h: 56px; --radius: 14px; }
.apps{ grid-template-columns: repeat(3, 1fr); }
.icons{ grid-template-columns: repeat(3, minmax(var(--grid),1fr)); }
}
/* --- New UI bits (App Store badges, overlays) --- */
.overlay {
position: fixed; inset: 0; display: grid; place-items:center; z-index:99999;
background: linear-gradient(180deg, rgba(0,0,0,.6), rgba(0,0,0,.8));
}
.boot-box {
width:520px; padding:28px; background: rgba(255,255,255,.03); border-radius:14px; text-align:center;
border: 1px solid rgba(255,255,255,.06);
}
.login-panel {
width:360px; padding:20px; background:var(--panel); border-radius:14px; text-align:center; border:var(--win-border);
}
.appstore-grid { display:grid; grid-template-columns:repeat(4,1fr); gap:10px; }
.app-card { padding:8px; border-radius:8px; background:rgba(255,255,255,.03); text-align:center; cursor:pointer; }
.rightmenu { position:absolute; background:var(--panel); border:var(--win-border); padding:8px; border-radius:8px; display:none; z-index:9999; }
.alt-tab { position:fixed; left:50%; top:20px; transform:translateX(-50%); background:rgba(2,6,23,.7); padding:8px; border-radius:8px; display:none; gap:8px; z-index:99999; }
.alt-tab .item { min-width:120px; padding:6px; border-radius:6px; background:rgba(255,255,255,.02); color:var(--text); }
</style>
</head>
<body>
<div class="desktop" id="desktop">
<div class="wallpaper" id="wallpaper"></div>
<div class="grain"></div>
<div class="icons" id="icons">
<div class="icon" data-launch="notepad">
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><rect x="4" y="3" width="16" height="18" rx="2" stroke="currentColor"/><path d="M8 7h8M8 11h8M8 15h6" stroke="currentColor"/></svg>
<span>Notepad</span>
</div>
<div class="icon" data-launch="files">
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V7Z" stroke="currentColor"/></svg>
<span>Files</span>
</div>
<div class="icon" data-launch="browser">
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><circle cx="12" cy="12" r="9" stroke="currentColor"/><path d="M3 12h18M12 3a15 15 0 0 1 0 18M12 21a15 15 0 0 0 0-18" stroke="currentColor"/></svg>
<span>Web</span>
</div>
<div class="icon" data-launch="terminal">
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M4 6h16v12H4z" stroke="currentColor"/><path d="M7 10l2 2-2 2M11 14h6" stroke="currentColor"/></svg>
<span>Terminal</span>
</div>
<div class="icon" data-launch="calculator">
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><rect x="6" y="3" width="12" height="18" rx="2" stroke="currentColor"/><path d="M9 7h6M9 11h6M9 15h2M13 15h2" stroke="currentColor"/></svg>
<span>Calculator</span>
</div>
<div class="icon" data-launch="settings">
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M12 8a4 4 0 1 0 0 8 4 4 0 0 0 0-8Zm8.5 4a8.5 8.5 0 1 1-17 0 8.5 8.5 0 0 1 17 0Z" stroke="currentColor"/></svg>
<span>Settings</span>
</div>
</div>
<!-- dynamic windows appear here -->
<div class="snap-hint" id="snapHint"></div>
<div class="select-rect" id="selectRect"></div>
<div class="taskbar">
<div class="start" id="startBtn" title="Start">
<svg width="22" height="22" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><path d="M4 4h7v7H4V4Zm9 0h7v7h-7V4ZM4 13h7v7H4v-7Zm9 0h7v7h-7v-7Z"/></svg>
</div>
<div class="tasklist" id="tasklist"></div>
<div class="tray">
<button class="btn ghost" id="quickBtn" title="Quick Settings">☰</button>
<div class="clock" id="clock">--:--</div>
</div>
<div class="startmenu" id="startMenu">
<div class="search">
<input id="globalSearch" placeholder="Search apps, files and web" />
<button class="btn" id="powerBtn">Power</button>
</div>
<div class="apps" id="appGrid">
<!-- tiles injected -->
</div>
</div>
<div class="quick" id="quick">
<div class="toggles">
<div class="toggle" data-toggle="wifi">Wi-Fi</div>
<div class="toggle on" data-toggle="bt">Bluetooth</div>
<div class="toggle on" data-toggle="theme">Dark</div>
<div class="toggle on" data-toggle="glass">Glass</div>
<div class="toggle" data-toggle="dnd">Do Not Disturb</div>
<div class="toggle" data-toggle="snap">Snap</div>
</div>
<div class="pad" style="display:flex; gap:10px; align-items:center;">
<span>Volume</span>
<input id="vol" type="range" min="0" max="100" value="70" />
<span id="volv">70%</span>
</div>
</div>
</div>
</div>
<!-- ORIGINAL SCRIPT (unchanged) -->
<script>
const OS = {
z: 10,
windows: new Map(),
tasks: new Map(),
snap: false,
glass: true,
theme: 'dark',
apps: {
notepad: {
title: 'Notepad',
content(win){
const wrap = el('div', 'pad notepad');
const area = el('textarea');
area.value = localStorage.getItem('OS:notepad') || 'Hello from AeroLiteOS!\n\nThis is a simple notepad. Your text autosaves.';
area.addEventListener('input', ()=> localStorage.setItem('OS:notepad', area.value));
wrap.append(area);
return wrap;
},
size: [520, 420]
},
files: {
title: 'Files',
content(){
const wrap = el('div', 'pad');
const h = el('div'); h.innerHTML = '<b>Quick Access</b>';
const list = el('div', 'filelist');
const items = JSON.parse(localStorage.getItem('OS:files')||'[]');
const add = el('button', 'btn'); add.textContent = 'New Note';
add.onclick = ()=>{
const name = prompt('File name?','note-'+Math.floor(Math.random()*1000)+'.txt');
if(!name) return;
items.push({name, data:''});
localStorage.setItem('OS:files', JSON.stringify(items));
render();
};
function render(){
list.innerHTML='';
items.forEach((f,i)=>{
const card = el('div','file');
card.innerHTML = '<div style="font-weight:600">📄 '+esc(f.name)+'</div>'+
'<div style="color:var(--muted); font-size:12px;">'+(f.data?.slice(0,60)||'')+'</div>'+
'<div style="display:flex; gap:6px; margin-top:8px;">'+
'<button class="btn" data-act="open">Open</button>'+
'<button class="btn" data-act="rename">Rename</button>'+
'<button class="btn" data-act="del">Delete</button></div>';
card.onclick = (e)=>{
if(!(e.target instanceof HTMLElement)) return;
const act = e.target.getAttribute('data-act');
if(act==='open') openNote(i);
if(act==='rename'){ const nn=prompt('Rename to:', f.name); if(nn){ f.name=nn; save(); render(); }}
if(act==='del'){ if(confirm('Delete '+f.name+'?')){ items.splice(i,1); save(); render(); } }
};
list.append(card);
});
}
function openNote(idx){
const file = items[idx];
OS.launch({
id:'file-'+idx,
title:'Edit: '+file.name,
icon: iconDoc(),
content(){
const w = el('div','pad notepad');
const t = el('textarea'); t.value = file.data||'';
t.addEventListener('input', ()=>{ file.data=t.value; save(); render(); });
w.append(t); return w;
},
size:[560,460]
});
}
function save(){ localStorage.setItem('OS:files', JSON.stringify(items)); }
render();
wrap.append(add, h, list); return wrap;
}, size:[680, 460]
},
terminal: {
title: 'Terminal',
content(){
const elTerm = el('div','terminal');
const prompt = ()=> 'user@aerolite:~$ ';
function line(text=''){ const d=el('div','term-line'); d.textContent=text; elTerm.append(d); elTerm.scrollTop = elTerm.scrollHeight; }
line('AeroLiteOS pseudo-shell. Type help.');
const input = el('input'); input.style.cssText='width:100%; background:transparent; color:#fff; border:0; outline:0; font:inherit;';
function run(cmd){
const [c,...rest]=cmd.trim().split(/\s+/);
if(!c){ return; }
if(c==='help') line('Commands: help, echo, date, clear, apps, about');
else if(c==='echo') line(rest.join(' '));
else if(c==='date') line(new Date().toString());
else if(c==='clear'){ elTerm.innerHTML=''; }
else if(c==='apps'){ line(Object.keys(OS.apps).join(', ')); }
else if(c==='about'){ line('AeroLiteOS — single-file HTML desktop'); }
else line('Unknown: '+c);
}
const inputWrap=el('div');
function refreshPrompt(){ inputWrap.textContent=''; input.value=''; const p=el('span'); p.textContent=prompt(); inputWrap.append(p,input); elTerm.append(inputWrap); input.focus(); elTerm.scrollTop = elTerm.scrollHeight; }
input.addEventListener('keydown', (e)=>{
if(e.key==='Enter'){ line(prompt()+input.value); run(input.value); refreshPrompt(); }
});
refreshPrompt();
return elTerm;
}, size:[640, 380]
},
calculator: {
title:'Calculator',
content(){
let expr='';
const wrap = el('div','calc');
const screen = el('div','screen'); screen.textContent='0';
function press(v){
if(v==='C'){ expr=''; }
else if(v==='='){ try{ expr = String(Function('return ('+expr+')')()); }catch{ expr='Error'; } }
else expr += v;
screen.textContent = expr || '0';
}
const keys = ['7','8','9','/','4','5','6','*','1','2','3','-','0','.','C','+','=','(',')','%'];
wrap.append(screen);
keys.forEach(k=>{ const b=el('button'); b.textContent=k; b.onclick=()=>press(k); if(k==='=') b.style.gridColumn='span 4'; wrap.append(b); });
return wrap;
}, size:[320, 420]
},
browser: {
title:'Web Browser',
content(){
const wrap = el('div'); wrap.style.height='100%';
const bar = el('div','browserbar');
const url = el('input'); url.placeholder='https://...';
const go = el('button','btn'); go.textContent='Go';
const frame = document.createElement('iframe'); frame.srcdoc = '<!doctype html><title>New Tab</title><body style="font:16px system-ui;padding:2rem">Web Browsing does not work yet :( it is coming soon! </body>';
function nav(){ let u=url.value.trim(); if(!u) return; if(!/^https?:\/\//.test(u)) u='https://'+u; frame.src=u; }
go.onclick=nav; url.addEventListener('keydown',e=>{ if(e.key==='Enter') nav(); });
bar.append(url, go); wrap.append(bar, frame); return wrap;
}, size:[820, 540]
},
settings: {
title:'Settings',
content(){
const wrap = el('div','pad');
wrap.innerHTML = `
<h2 style="margin:0 0 8px 0">Personalization</h2>
<div style="display:flex; gap:12px; flex-wrap:wrap;">
${['nebula','lake','sunset','midnight','grid'].map(k=>`<button class="btn" data-wall="${k}">${k}</button>`).join('')}
</div>
<h2 style="margin:14px 0 8px 0">Theme</h2>
<div style="display:flex; gap:8px;">
<button class="btn" data-theme="dark">Dark</button>
<button class="btn" data-theme="light">Light</button>
</div>
<h2 style="margin:14px 0 8px 0">Effects</h2>
<div style="display:flex; gap:8px; flex-wrap:wrap;">
<button class="btn" id="toggleGlass">Toggle Glass</button>
<button class="btn" id="toggleSnap">Toggle Snap</button>
<button class="btn" id="reset">Reset OS</button>
</div>
`;
wrap.querySelectorAll('[data-wall]').forEach(b=> b.addEventListener('click',()=> setWallpaper(b.dataset.wall)));
wrap.querySelectorAll('[data-theme]').forEach(b=> b.addEventListener('click',()=> setTheme(b.dataset.theme)));
wrap.querySelector('#toggleGlass').onclick = ()=> setGlass(!OS.glass);
wrap.querySelector('#toggleSnap').onclick = ()=> OS.snap = !OS.snap;
wrap.querySelector('#reset').onclick = ()=>{ if(confirm('Reset settings & data?')){ localStorage.clear(); location.reload(); } };
return wrap;
}, size:[520, 420]
}
}
};
const desktop = document.getElementById('desktop');
const tasklist = document.getElementById('tasklist');
const startBtn = document.getElementById('startBtn');
const startMenu = document.getElementById('startMenu');
const quick = document.getElementById('quick');
const quickBtn = document.getElementById('quickBtn');
const clock = document.getElementById('clock');
const icons = document.getElementById('icons');
const snapHint = document.getElementById('snapHint');
const selectRect = document.getElementById('selectRect');
const wallpaperEl = document.getElementById('wallpaper');
const el = (tag, cls)=>{ const n=document.createElement(tag); if(cls) n.className=cls; return n; }
const esc = s=> String(s).replace(/[&<>\\"]/g, c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[c]));
function setWallpaper(key){
const map = {
nebula: 'radial-gradient(800px 600px at 20% 20%, #3b82f6 0%, transparent 60%), radial-gradient(900px 700px at 80% 80%, #22d3ee 0%, transparent 60%), linear-gradient(180deg,#0b1220,#0b1220)',
lake: 'url(data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="800"><defs><linearGradient id="g" x1="0" x2="0" y1="0" y2="1"><stop offset="0" stop-color="%23387ab7"/><stop offset="1" stop-color="%2398d3ec"/></linearGradient></defs><rect width="100%" height="100%" fill="url(%23g)"/></svg>)',
sunset: 'linear-gradient(120deg,#ff7e5f 0%, #feb47b 100%)',
midnight: 'linear-gradient(180deg,#080c18,#020409)',
grid: 'repeating-linear-gradient(0deg, rgba(59,130,246,.12), rgba(59,130,246,.12) 1px, transparent 1px, transparent 24px), repeating-linear-gradient(90deg, rgba(34,211,238,.12), rgba(34,211,238,.12) 1px, transparent 1px, transparent 24px)'
};
wallpaperEl.style.background = map[key] || map.nebula;
localStorage.setItem('OS:wall', key);
}
function setTheme(t){
OS.theme = t; document.body.style.setProperty('--text', t==='light'?'#0b1220':'#e5e7eb');
document.body.style.setProperty('--bg', t==='light'?'#f5f7fb':'#0b1220');
document.body.style.setProperty('--bg-elev', t==='light'?'#eef1f8':'#0f172a');
document.body.style.setProperty('--panel', t==='light'?'rgba(255,255,255,.7)':'rgba(15,23,42,.8)');
document.body.style.setProperty('--panel-solid', t==='light'?'#ffffff':'#111827');
localStorage.setItem('OS:theme', t);
}
function setGlass(on){
OS.glass = on; document.documentElement.style.setProperty('--blur', on? '14px':'0px');
localStorage.setItem('OS:glass', on? '1':'0');
}
function updateClock(){ const d=new Date(); const h=String(d.getHours()).padStart(2,'0'); const m=String(d.getMinutes()).padStart(2,'0'); clock.textContent = `${h}:${m}`; }
setInterval(updateClock, 10000); updateClock();
// Start menu & quick
startBtn.onclick = ()=>{ startMenu.classList.toggle('show'); quick.classList.remove('show'); };
quickBtn.onclick = ()=>{ quick.classList.toggle('show'); startMenu.classList.remove('show'); };
document.body.addEventListener('mousedown', (e)=>{
if(!startMenu.contains(e.target) && e.target!==startBtn) startMenu.classList.remove('show');
if(!quick.contains(e.target) && e.target!==quickBtn) quick.classList.remove('show');
});
// Quick toggles
quick.querySelectorAll('.toggle').forEach(t=> t.addEventListener('click', ()=>{
t.classList.toggle('on');
const k = t.dataset.toggle;
if(k==='theme') setTheme(t.classList.contains('on')?'dark':'light');
if(k==='glass') setGlass(t.classList.contains('on'));
if(k==='snap') OS.snap = t.classList.contains('on');
}));
const vol = document.getElementById('vol'); const volv = document.getElementById('volv'); vol.oninput = ()=> volv.textContent = vol.value+'%';
// Icons launch
icons.addEventListener('dblclick', (e)=>{
const icon = e.target.closest('.icon'); if(!icon) return; launch(icon.dataset.launch);
});
// Populate Start menu tiles
const appGrid = document.getElementById('appGrid');
Object.entries(OS.apps).forEach(([id, a])=>{
const tile = el('div','app-tile'); tile.innerHTML = `<div style="font-weight:600">${a.title}</div><div style="color:var(--muted); font-size:12px;">${id}</div>`; tile.onclick = ()=> launch(id);
appGrid.append(tile);
});
// Window creation
function launch(id){ const app = OS.apps[id]; if(!app) return alert('App not found: '+id); OS.launch({ id, title: app.title, icon: iconSquare(), content: app.content, size: app.size }); }
OS.launch = ({id, title, icon, content, size=[600,400]})=>{
let win = OS.windows.get(id);
if(win){ focus(win.el); return; }
const elw = el('div','win'); elw.style.width=size[0]+'px'; elw.style.height=size[1]+'px'; elw.style.left = 40+Math.random()*80+'px'; elw.style.top = 40+Math.random()*60+'px'; elw.style.zIndex = ++OS.z;
const titlebar = el('div','titlebar');
const ic = el('div','appicon'); ic.append(icon||iconSquare());
const ttl = el('div','title'); ttl.textContent = title;
const acts = el('div','actions');
const bMin = button('–'); const bMax = button('□'); const bClose = button('✕');
acts.append(bMin,bMax,bClose);
titlebar.append(ic,ttl,acts);
const contentWrap = el('div','content');
contentWrap.append(typeof content==='function'? content(elw): content);
// resizers
['n','s','e','w','ne','nw','se','sw'].forEach(k=>{ const r=el('div','resizer r-'+k); elw.append(r); r.dataset.edge=k; });
elw.append(titlebar, contentWrap); desktop.append(elw);
// Task
const task = el('div','task'); task.innerHTML = `<div class="appicon">${(icon||iconSquare()).outerHTML}</div><div>${title}</div>`; task.onclick=()=> focus(elw);
tasklist.append(task);
// store
OS.windows.set(id, { el: elw, task, state:{max:false,min:false} });
// drag
dragMove(titlebar, elw);
// actions
bClose.onclick = ()=> closeWin(id);
bMin.onclick = ()=> minimize(id);
bMax.onclick = ()=> maximize(id);
focus(elw);
};
function button(txt){ const b=el('button','btn'); b.textContent=txt; b.title=txt; return b; }
function iconSquare(){ const s=document.createElementNS('http://www.w3.org/2000/svg','svg'); s.setAttribute('viewBox','0 0 24 24'); s.innerHTML='<rect x="4" y="4" width="16" height="16" rx="4" fill="currentColor"/>'; return s; }
function iconDoc(){ const s=document.createElementNS('http://www.w3.org/2000/svg','svg'); s.setAttribute('viewBox','0 0 24 24'); s.innerHTML='<rect x="5" y="3" width="14" height="18" rx="2" stroke="currentColor" fill="none"/><path d="M8 8h8M8 12h8M8 16h6" stroke="currentColor"/>'; return s; }
function focus(winEl){ document.querySelectorAll('.win').forEach(w=> w.style.outline='none'); winEl.style.zIndex = ++OS.z; winEl.style.outline='2px solid rgba(59,130,246,.4)';
// activate task button
const id = getIdByEl(winEl); if(!id) return; document.querySelectorAll('.task').forEach(t=> t.classList.remove('active')); const w = OS.windows.get(id); if(w) w.task.classList.add('active');
}
function getIdByEl(elw){ for(const [id, w] of OS.windows){ if(w.el===elw) return id; } }
function closeWin(id){ const w=OS.windows.get(id); if(!w) return; w.el.remove(); w.task.remove(); OS.windows.delete(id); }
function minimize(id){ const w=OS.windows.get(id); if(!w) return; w.el.style.display='none'; w.task.classList.remove('active'); }
function maximize(id){ const w=OS.windows.get(id); if(!w) return; const e=w.el; const st=w.state; if(!st.max){ st.prev={left:e.style.left, top:e.style.top, width:e.style.width, height:e.style.height}; e.style.left='8px'; e.style.top='8px'; e.style.width = (window.innerWidth-16)+'px'; e.style.height = (window.innerHeight-16 - parseInt(getComputedStyle(document.documentElement).getPropertyValue('--taskbar-h')))+'px'; st.max=true; } else { e.style.left=st.prev.left; e.style.top=st.prev.top; e.style.width=st.prev.width; e.style.height=st.prev.height; st.max=false; }
}
function dragMove(handle, winEl){
let sx, sy, sl, st, dragging=false;
handle.addEventListener('mousedown', (e)=>{ dragging=true; focus(winEl); sx=e.clientX; sy=e.clientY; sl=parseInt(winEl.style.left)||0; st=parseInt(winEl.style.top)||0; winEl.classList.add('dragging'); });
window.addEventListener('mousemove', (e)=>{
if(!dragging) return; const dx=e.clientX-sx, dy=e.clientY-sy; let L=sl+dx, T=st+dy; winEl.style.left=L+'px'; winEl.style.top=T+'px'; if(OS.snap) showSnap(L,T,winEl); });
window.addEventListener('mouseup', (e)=>{ if(!dragging) return; dragging=false; winEl.classList.remove('dragging'); if(OS.snap) applySnap(winEl); hideSnap(); });
// resize edges
winEl.querySelectorAll('.resizer').forEach(r=>{
let rs=false, sx2, sy2, sw, sh, sl2, st2, edge=r.dataset.edge;
r.addEventListener('mousedown', (e)=>{ e.stopPropagation(); rs=true; focus(winEl); sx2=e.clientX; sy2=e.clientY; sw=winEl.offsetWidth; sh=winEl.offsetHeight; sl2=winEl.offsetLeft; st2=winEl.offsetTop; winEl.classList.add('resizing'); });
window.addEventListener('mousemove', (e)=>{
if(!rs) return; const dx=e.clientX-sx2, dy=e.clientY-sy2; let W=sw, H=sh, L=sl2, T=st2; if(edge.includes('e')) W=sw+dx; if(edge.includes('s')) H=sh+dy; if(edge.includes('w')){ W=sw-dx; L=sl2+dx; } if(edge.includes('n')){ H=sh-dy; T=st2+dy; }
W=Math.max(320,W); H=Math.max(200,H); winEl.style.width=W+'px'; winEl.style.height=H+'px'; winEl.style.left=L+'px'; winEl.style.top=T+'px';
});
window.addEventListener('mouseup', ()=>{ if(rs){ rs=false; winEl.classList.remove('resizing'); hideSnap(); }});
});
}
// Snap assist
function showSnap(L,T,winEl){ const w=window.innerWidth, h=window.innerHeight-parseInt(getComputedStyle(document.documentElement).getPropertyValue('--taskbar-h')); const margin=12; const areas=[
{k:'left', x:margin, y:margin, w:(w/2)-margin*1.5, h:h-margin*2},
{k:'right', x:(w/2)+margin/2, y:margin, w:(w/2)-margin*1.5, h:h-margin*2},
{k:'top', x:margin, y:margin, w:w-margin*2, h:(h/2)-margin*1.5},
{k:'bottom', x:margin, y:(h/2)+margin/2, w:w-margin*2, h:(h/2)-margin*1.5},
{k:'center', x:margin*2, y:margin*2, w:w-margin*4, h:h-margin*4}
];
const rect = winEl.getBoundingClientRect(); const cx=rect.left+rect.width/2; const cy=rect.top+rect.height/2;
let near=null, best=1e9; areas.forEach(a=>{ const dx=cx-(a.x+a.w/2), dy=cy-(a.y+a.h/2); const d=Math.hypot(dx,dy); if(d<best){ best=d; near=a; } });
if(near){ snapHint.style.display='block'; snapHint.style.left=near.x+'px'; snapHint.style.top=near.y+'px'; snapHint.style.width=near.w+'px'; snapHint.style.height=near.h+'px'; snapHint.dataset.k=near.k; }
}
function hideSnap(){ snapHint.style.display='none'; snapHint.dataset.k=''; }
function applySnap(winEl){ const k=snapHint.dataset.k; if(!k) return; const w=window.innerWidth, h=window.innerHeight-parseInt(getComputedStyle(document.documentElement).getPropertyValue('--taskbar-h')); const margin=12; const pos={
left: {x:margin, y:margin, w:(w/2)-margin*1.5, h:h-margin*2},
right: {x:(w/2)+margin/2, y:margin, w:(w/2)-margin*1.5, h:h-margin*2},
top: {x:margin, y:margin, w:w-margin*2, h:(h/2)-margin*1.5},
bottom:{x:margin, y:(h/2)+margin/2, w:w-margin*2, h:(h/2)-margin*1.5},
center:{x:margin*2, y:margin*2, w:w-margin*4, h:h-margin*4}
}[k];
if(pos){ winEl.style.left=pos.x+'px'; winEl.style.top=pos.y+'px'; winEl.style.width=pos.w+'px'; winEl.style.height=pos.h+'px'; }
}
// Desktop selection rectangle (for flair)
let selStart=null; desktop.addEventListener('mousedown', (e)=>{ if(e.target.closest('.win,.taskbar,.startmenu,.quick')) return; selStart=[e.clientX,e.clientY]; selectRect.style.display='block'; selectRect.style.left=e.clientX+'px'; selectRect.style.top=e.clientY+'px'; selectRect.style.width='0px'; selectRect.style.height='0px'; });
window.addEventListener('mousemove', (e)=>{ if(!selStart) return; const x=Math.min(selStart[0],e.clientX), y=Math.min(selStart[1],e.clientY), w=Math.abs(e.clientX-selStart[0]), h=Math.abs(e.clientY-selStart[1]); selectRect.style.left=x+'px'; selectRect.style.top=y+'px'; selectRect.style.width=w+'px'; selectRect.style.height=h+'px'; });
window.addEventListener('mouseup', ()=>{ if(selStart){ selStart=null; selectRect.style.display='none'; }});
// Keyboard shortcuts
window.addEventListener('keydown', (e)=>{
if(e.ctrlKey && e.key==='`'){ launch('terminal'); }
if(e.ctrlKey && e.key===' '){ startMenu.classList.toggle('show'); }
});
// Restore settings
(function init(){
setWallpaper(localStorage.getItem('OS:wall')||'nebula');
setTheme(localStorage.getItem('OS:theme')||'dark');
setGlass((localStorage.getItem('OS:glass')||'1')==='1');
})();
// Public helper for external launch from console
window.AeroLiteOS = { launch };
</script>
<!-- ================= More soon! ================ -->
<!-- All additions below extend the existing OS features & games. -->
<div id="bootOverlay" class="overlay" style="display:none;">
<div class="boot-box">
<h2 style="margin:0 0 12px 0">AeroLiteOS</h2>
<div id="bootProgress" style="height:10px;background:rgba(255,255,255,.06);border-radius:6px;overflow:hidden;margin-bottom:12px">
<div id="bootBar" style="height:100%;width:0%;background:linear-gradient(90deg,var(--accent),var(--accent-2));"></div>
</div>
<div style="color:var(--muted)">Loading system modules...</div>
</div>
</div>
<div id="loginOverlay" class="overlay" style="display:none;">
<div class="login-panel" id="loginPanel">
<h3 style="margin:0 0 8px 0">Welcome</h3>
<input id="loginUser" placeholder="Username" style="width:80%;padding:8px;border-radius:8px;border:1px solid rgba(255,255,255,.06);margin-bottom:8px"><br>
<input id="loginPass" placeholder="Password" type="password" style="width:80%;padding:8px;border-radius:8px;border:1px solid rgba(255,255,255,.06);margin-bottom:8px"><br>
<button class="btn" id="loginBtn">Sign in</button>
<div style="margin-top:8px;color:var(--muted);font-size:12px">Tip: password stored locally (demo).</div>
</div>
</div>
<div id="rightClickMenu" class="rightmenu"></div>
<div id="altTab" class="alt-tab" style="display:none;"></div>
<script>
// --- Configuration for new features ---
(function(){
// Quick boot + login simulation
function showBootThenLogin(){
const boot = document.getElementById('bootOverlay');
const login = document.getElementById('loginOverlay');
boot.style.display = 'grid';
let p = 0;
const step = setInterval(()=>{
p += Math.random()*18;
document.getElementById('bootBar').style.width = Math.min(100, p).toFixed(0) + '%';
if(p >= 100){
clearInterval(step);
setTimeout(()=> {
boot.style.display='none';
// if credential exists skip
const savedUser = localStorage.getItem('OS:user');
if(savedUser){
login.style.display = 'none';
} else {
login.style.display = 'grid';
}
}, 400);
}
}, 300);
}
// Hook login button
document.getElementById('loginBtn').addEventListener('click', ()=>{
const u = document.getElementById('loginUser').value || 'User';
const p = document.getElementById('loginPass').value || '';
// store demo credentials (user asked to keep old code; storing locally is fine)
localStorage.setItem('OS:user', u);
localStorage.setItem('OS:pass', p);
document.getElementById('loginOverlay').style.display='none';
});
// Only show boot on first load (or when explicitly reset)
if(!localStorage.getItem('OS:booted')){
showBootThenLogin();
localStorage.setItem('OS:booted','1');
}
// --- Expand OS.apps without changing original definitions ---
// Add Games and new utilities into OS.apps
const newApps = {
snake_game: {
title: 'Snake (Game)',
content(){
const wrap = el('div');
const canvas = el('canvas'); canvas.width=300; canvas.height=300; canvas.style.display='block'; canvas.style.margin='14px auto';
const info = el('div'); info.style.textAlign='center'; info.innerHTML = '<small>Use arrow keys. Eat red to grow.</small>';
wrap.append(canvas, info);
// game boot will be handled after window insertion
setTimeout(()=> startSnake(canvas), 60);
return wrap;
},
size:[360,380]
},
minesweeper_game: {
title: 'Minesweeper',
content(){
const wrap = el('div','pad');
const field = el('div'); field.style.display='grid'; field.style.gridTemplateColumns='repeat(10,28px)'; field.style.gap='6px';
field.id='msfield';
wrap.append(field);
setTimeout(()=> startMinesweeper(field,10,10,12), 80);
return wrap;
}, size:[360,400]
},
pong_game: {
title: 'Pong (2P)',
content(){
const wrap = el('div');
const canvas = el('canvas'); canvas.width=420; canvas.height=300; canvas.style.display='block'; canvas.style.margin='12px auto'; wrap.append(canvas);
setTimeout(()=> startPong(canvas), 60);
return wrap;
}, size:[460,360]
},
tetris_game: {
title: 'Tetris',
content(){
const wrap = el('div'); const canvas = el('canvas'); canvas.width=240; canvas.height=400; canvas.style.display='block'; canvas.style.margin='12px auto'; wrap.append(canvas);
setTimeout(()=> startTetris(canvas), 60);
return wrap;
}, size:[300,460]
},
roblox_game: {
title: 'Roblox',
content(){
const wrap = el('div');
setTimeout(()=> startRoblox(wrap), 60); // builds Roblox UI inside the wrap
return wrap;
}, size:[660,400]
},
music_player: {
title: 'Music Player',
content(){
const wrap = el('div','pad');
wrap.innerHTML = `
<div style="display:flex;gap:8px;align-items:center;">
<input id="mpFile" type="file" accept="audio/*"/>
<button id="mpPlay" class="btn">Play</button>
<button id="mpPause" class="btn">Pause</button>
<button id="mpStop" class="btn">Stop</button>
</div>
<div style="margin-top:12px;">
<audio id="mpAudio" controls style="width:100%"></audio>
</div>
`;
const file = wrap.querySelector('#mpFile');
const audio = wrap.querySelector('#mpAudio');
const play = wrap.querySelector('#mpPlay');
const pause = wrap.querySelector('#mpPause');
const stop = wrap.querySelector('#mpStop');
file.onchange = ()=>{
const f = file.files[0];
if(!f) return;
audio.src = URL.createObjectURL(f);
audio.play();
};
play.onclick = ()=> audio.play();
pause.onclick = ()=> audio.pause();
stop.onclick = ()=> { audio.pause(); audio.currentTime=0; };
return wrap;
}, size:[520,200]
},
image_viewer: {
title: 'Image Viewer',
content(){
const wrap = el('div','pad');
wrap.innerHTML = `
<div style="display:flex;gap:8px;align-items:center;">
<input id="imgFile" type="file" accept="image/*"/>
<button id="imgOpen" class="btn">Open</button>
<button id="imgSetWall" class="btn">Set as Wallpaper</button>
</div>
<div style="margin-top:12px; display:flex; justify-content:center;">
<img id="imgPreview" style="max-width:100%; max-height:400px; border-radius:8px;"/>
</div>
`;
const input = wrap.querySelector('#imgFile');
const img = wrap.querySelector('#imgPreview');
const setBtn = wrap.querySelector('#imgSetWall');
input.onchange = ()=> {
const f = input.files[0]; if(!f) return;
img.src = URL.createObjectURL(f);
};
setBtn.onclick = ()=> {
if(!img.src) return alert('Open an image first');
// set wallpaper by data URL reference (uses CSS url())
wallpaperEl.style.background = `url(${img.src}) center/cover`;
localStorage.setItem('OS:wall-src', img.src);
};
return wrap;
}, size:[640,480]
},
recycle_bin: {
title: 'Recycle Bin',
content(){
const wrap = el('div','pad');
const list = el('div'); list.id='recycleList';
const empty = el('button','btn'); empty.textContent='Empty Bin';
empty.onclick = ()=> { if(confirm('Empty Recycle Bin?')){ localStorage.removeItem('OS:recycle'); render(); } };
wrap.append(empty, list);
function render(){ list.innerHTML=''; const items = JSON.parse(localStorage.getItem('OS:recycle')||'[]'); items.forEach((it,i)=>{ const row=el('div'); row.style.display='flex'; row.style.justifyContent='space-between'; row.style.padding='6px 0'; row.innerHTML = `<div>${esc(it.name)}</div><div><button class="btn" data-idx="${i}">Restore</button><button class="btn" data-del="${i}">Delete</button></div>`; row.querySelector('[data-idx]')?.addEventListener('click', ()=> { restore(i); }); row.querySelector('[data-del]')?.addEventListener('click', ()=> { del(i); }); list.append(row); }); }
function restore(i){ const items = JSON.parse(localStorage.getItem('OS:recycle')||'[]'); const it=items.splice(i,1)[0]; localStorage.setItem('OS:recycle', JSON.stringify(items)); // simplistic restore: add to files
const files = JSON.parse(localStorage.getItem('OS:files')||'[]'); files.push({name:it.name, data:it.data}); localStorage.setItem('OS:files', JSON.stringify(files)); render(); }
function del(i){ const items = JSON.parse(localStorage.getItem('OS:recycle')||'[]'); items.splice(i,1); localStorage.setItem('OS:recycle', JSON.stringify(items)); render(); }
render(); return wrap;
}, size:[420,320]
},
app_store: {
title: 'App Store',
content(){
const wrap = el('div','pad');
const grid = el('div','appstore-grid');
const appsList = [
{id:'snake_game', name:'Snake', desc:'Classic snake game'},
{id:'minesweeper_game', name:'Minesweeper', desc:'Dont touch the mines'},
{id:'pong_game', name:'Pong', desc:'The classic atari game, PONG'},
{id:'tetris_game', name:'Tetris', desc:'The classic russian game!'},
{id:'roblox_game', name:'Roblox', desc:'An old simple remake of old roblox #freeschlep'}
];
appsList.forEach(a=>{
const c = el('div','app-card'); c.innerHTML = `<div style="font-weight:600">${a.name}</div><div style="font-size:12px;color:var(--muted)">${a.desc}</div><div style="margin-top:8px;"><button class="btn" data-run="${a.id}">Play</button></div>`;
c.querySelector('[data-run]').addEventListener('click', ()=> OS.launch({ id: a.id, title: OS.apps[a.id].title, icon: iconSquare(), content: OS.apps[a.id].content, size: OS.apps[a.id].size }));
grid.append(c);
});
wrap.append(grid);
return wrap;
}, size:[640,360]
}
};
// Merge newApps into existing OS.apps (preserve anything already there)
Object.entries(newApps).forEach(([k,v])=>{
if(!OS.apps[k]) OS.apps[k]=v;
});
// Add desktop icons for games/apps (append to icons area)
const iconsEl = document.getElementById('icons');
function addDesktopIcon(id, label, svg){
const d = el('div','icon'); d.dataset.launch = id;
d.innerHTML = (svg || '<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><rect x="4" y="3" width="16" height="18" rx="2" stroke="currentColor"/></svg>') + `<span>${label}</span>`;
d.addEventListener('dblclick', ()=> launch(id));
iconsEl.append(d);
}
addDesktopIcon('snake_game','Snake');
addDesktopIcon('minesweeper_game','Minesweeper');
addDesktopIcon('pong_game','Pong');
addDesktopIcon('tetris_game','Tetris');
addDesktopIcon('music_player','Music');
addDesktopIcon('image_viewer','Images');
addDesktopIcon('recycle_bin','Recycle Bin');
addDesktopIcon('roblox_game','Roblox');
addDesktopIcon('app_store','App Store');
// Integrate Recycle functionality into Files delete workflow by intercepting storage changes:
// (We won't change original Files code—provide a helper function to move deleted files)
window.OSDeleteToRecycle = function(fileObj){
const r = JSON.parse(localStorage.getItem('OS:recycle')||'[]');
r.push(fileObj);
localStorage.setItem('OS:recycle', JSON.stringify(r));
};
// Right-click desktop menu
const rightMenu = document.getElementById('rightClickMenu');
document.addEventListener('contextmenu', (e)=>{
e.preventDefault();
if(e.target.closest('.win,.taskbar,.startmenu,.quick')) { rightMenu.style.display='none'; return; }
rightMenu.style.display='block';
rightMenu.style.left = e.clientX + 'px';
rightMenu.style.top = e.clientY + 'px';
rightMenu.innerHTML = `<div style="padding:6px;cursor:pointer" id="newFile">New File</div>
<div style="padding:6px;cursor:pointer" id="setWall">Set Wallpaper</div>
<div style="padding:6px;cursor:pointer" id="openStore">App Store</div>`;
rightMenu.querySelector('#newFile').onclick = ()=> { rightMenu.style.display='none'; const name = prompt('New file name','untitled.txt'); if(!name) return; const files = JSON.parse(localStorage.getItem('OS:files')||'[]'); files.push({name,data:''}); localStorage.setItem('OS:files', JSON.stringify(files)); alert('File created. Open Files app to view.'); };
rightMenu.querySelector('#setWall').onclick = ()=> { rightMenu.style.display='none'; const url = prompt('Image URL (cross-origin may block):'); if(url){ wallpaperEl.style.background = `url(${url}) center/cover`; localStorage.setItem('OS:wall-src', url); } };
rightMenu.querySelector('#openStore').onclick = ()=> { rightMenu.style.display='none'; OS.launch({ id:'app_store', title:OS.apps.app_store.title, icon: iconSquare(), content: OS.apps.app_store.content, size: OS.apps.app_store.size }); };
});
document.addEventListener('click', ()=> rightMenu.style.display='none');
// Alt+Tab switcher simple preview (shows open windows by title)
const altTabEl = document.getElementById('altTab');
let altOpen = false, altList = [];
window.addEventListener('keydown', (e)=>{
if(e.key === 'Tab' && e.altKey){
e.preventDefault();
if(!altOpen){
// open
altOpen = true;
altList = Array.from(OS.windows.keys());
altTabEl.innerHTML = '';
altList.forEach(id=>{
const item = el('div','item'); item.textContent = OS.windows.get(id)?.task?.textContent || id; item.onclick = ()=> { focus(OS.windows.get(id).el); hideAlt(); };
altTabEl.append(item);
});
altTabEl.style.display='flex';
} else {
// cycle
const focused = document.querySelector('.win[style*="z-index"]');
}
}
});
window.addEventListener('keyup', (e)=>{ if(e.key === 'Alt') hideAlt(); });
function hideAlt(){ altOpen=false; altTabEl.style.display='none'; altTabEl.innerHTML=''; }
// ----- Simple Game Implementations (vanilla) -----
function startSnake(canvas){
if(!canvas) return;
const ctx = canvas.getContext('2d');
const grid = 15;
const cols = Math.floor(canvas.width / grid);
const rows = Math.floor(canvas.height / grid);
let px = Math.floor(cols/2), py = Math.floor(rows/2), vx=0, vy=0, tail=4, trail=[];
let apple = {x: Math.floor(Math.random()*cols), y: Math.floor(Math.random()*rows)};
function tick(){
px += vx; py += vy;
if(px < 0) px = cols -1; if(px >= cols) px = 0;
if(py < 0) py = rows -1; if(py >= rows) py = 0;
// collision with self
for(const t of trail){ if(t.x===px && t.y===py){ tail = 4; } }
trail.push({x:px,y:py});
while(trail.length > tail) trail.shift();
if(px===apple.x && py===apple.y){ tail++; apple = {x:Math.floor(Math.random()*cols), y:Math.floor(Math.random()*rows)}; }
// render
ctx.fillStyle = '#041023'; ctx.fillRect(0,0,canvas.width,canvas.height);
ctx.fillStyle = '#d946ef';
ctx.fillRect(apple.x*grid+2, apple.y*grid+2, grid-4, grid-4);
ctx.fillStyle = '#22c55e';
for(const t of trail) ctx.fillRect(t.x*grid+2, t.y*grid+2, grid-4, grid-4);
}
document.addEventListener('keydown', function handler(e){
if(e.key==='ArrowLeft' && vx!==1){ vx=-1; vy=0; }
if(e.key==='ArrowRight' && vx!==-1){ vx=1; vy=0; }
if(e.key==='ArrowUp' && vy!==1){ vx=0; vy=-1; }
if(e.key==='ArrowDown' && vy!==-1){ vx=0; vy=1; }
});
const interval = setInterval(tick, 100);
// stop when window closed: attempt to detect canvas removed
const obs = new MutationObserver(()=>{ if(!document.body.contains(canvas)){ clearInterval(interval); obs.disconnect(); } });
obs.observe(document.body, {childList:true, subtree:true});
}
function startPong(canvas){
if(!canvas) return;
const ctx = canvas.getContext('2d');
let bx = canvas.width/2, by = canvas.height/2, bvx=3, bvy=2;
let lp = canvas.height/2 - 30, rp = lp;
function tick(){
bx += bvx; by += bvy;
if(by < 0 || by > canvas.height){ bvy *= -1; }
// paddles
if(bx < 20 && by > lp && by < lp+60) bvx *= -1;
if(bx > canvas.width-20 && by > rp && by < rp+60) bvx *= -1;
if(bx < -50 || bx > canvas.width + 50){ bx = canvas.width/2; by = canvas.height/2; bvx = -bvx; }
// AI for right paddle
if(rp + 30 < by) rp += 2; else if(rp + 30 > by) rp -= 2;
// draw
ctx.fillStyle = '#020617'; ctx.fillRect(0,0,canvas.width,canvas.height);
ctx.fillStyle = '#e5e7eb'; ctx.fillRect(10, lp, 10, 60); ctx.fillRect(canvas.width-20, rp, 10, 60);
ctx.beginPath(); ctx.arc(bx,by,8,0,Math.PI*2); ctx.fill();
}
document.addEventListener('keydown', function handler(e){
// left paddle controls W/S
if(e.key==='w') lp -= 20;
if(e.key==='s') lp += 20;
});
const interval = setInterval(tick, 20);
const obs = new MutationObserver(()=>{ if(!document.body.contains(canvas)){ clearInterval(interval); obs.disconnect(); } });
obs.observe(document.body, {childList:true, subtree:true});
}
function startTetris(canvas){
if(!canvas) return;
// --- TETRIS IMPLEMENTATION (keeps function signature exactly the same) ---
const ctx = canvas.getContext('2d');
// Cleanup previous game on same canvas (if any)
if (canvas._tetris && canvas._tetris.cleanup) canvas._tetris.cleanup();
// Board dimensions
const COLS = 10;
const ROWS = 20;
// cell size fits canvas while preserving board aspect
const cellSize = Math.floor(Math.min(canvas.width / COLS, canvas.height / ROWS));
const boardWidth = cellSize * COLS;
const boardHeight = cellSize * ROWS;
const offsetX = Math.floor((canvas.width - boardWidth) / 2);
const offsetY = Math.floor((canvas.height - boardHeight) / 2);
// Visual settings
ctx.textBaseline = 'top';
ctx.font = `${Math.max(12, Math.floor(cellSize * 0.6))}px monospace`;
// Tetromino definitions (rotation states)
const TETROMINOS = {
I: [
[[0,0,0,0],[1,1,1,1],[0,0,0,0],[0,0,0,0]],
[[0,0,1,0],[0,0,1,0],[0,0,1,0],[0,0,1,0]],
],
J: [
[[1,0,0],[1,1,1],[0,0,0]],
[[0,1,1],[0,1,0],[0,1,0]],
[[0,0,0],[1,1,1],[0,0,1]],
[[0,1,0],[0,1,0],[1,1,0]],
],
L: [
[[0,0,1],[1,1,1],[0,0,0]],
[[0,1,0],[0,1,0],[0,1,1]],
[[0,0,0],[1,1,1],[1,0,0]],
[[1,1,0],[0,1,0],[0,1,0]],
],
O: [
[[1,1],[1,1]],
],
S: [
[[0,1,1],[1,1,0],[0,0,0]],
[[0,1,0],[0,1,1],[0,0,1]],
],
T: [
[[0,1,0],[1,1,1],[0,0,0]],
[[0,1,0],[0,1,1],[0,1,0]],
[[0,0,0],[1,1,1],[0,1,0]],
[[0,1,0],[1,1,0],[0,1,0]],
],
Z: [
[[1,1,0],[0,1,1],[0,0,0]],
[[0,0,1],[0,1,1],[0,1,0]],
],
};
const COLORS = {
I: '#4ecdc4',
J: '#496af1',
L: '#f39c12',
O: '#f1c40f',
S: '#2ecc71',
T: '#9b59b6',
Z: '#e74c3c',
X: '#666' // filled/ghost fallback
};
const PIECE_KEYS = Object.keys(TETROMINOS);
// Game state
let board = createMatrix(ROWS, COLS);
let current = null;
let next = null;
let dropInterval = 800; // ms (will speed up)
let dropAccumulator = 0;
let lastTime = 0;
let score = 0;
let level = 0;
let lines = 0;
let paused = false;
let gameOver = false;
// Utility: create matrix rows x cols filled with 0
function createMatrix(r, c){
const m = new Array(r);
for(let i=0;i<r;i++) m[i] = new Array(c).fill(0);
return m;
}
// Spawn piece from next
function spawnPiece(){
current = next || randomPiece();
current.row = 0;
current.col = Math.floor((COLS - current.matrix[0].length) / 2);
next = randomPiece();
if (collides(board, current.matrix, current.row, current.col)){
// immediate collision => game over
gameOver = true;
paused = false;
}
}
function randomPiece(){
const k = PIECE_KEYS[Math.floor(Math.random()*PIECE_KEYS.length)];
const states = TETROMINOS[k];
// pick initial rotation index 0
return {
type: k,
matrix: states[0].map(row => row.slice()),
rotIndex: 0,
states: states
};
}
// Collision detection: returns true if matrix at (r,c) overlaps filled board or out of bounds
function collides(board, matrix, row, col){
for(let r=0;r<matrix.length;r++){
for(let c=0;c<matrix[r].length;c++){
if (!matrix[r][c]) continue;
const br = row + r;
const bc = col + c;
if (br < 0 || br >= ROWS || bc < 0 || bc >= COLS) return true;
if (board[br][bc]) return true;
}
}
return false;
}
function mergeToBoard(board, matrix, row, col, type){
for(let r=0;r<matrix.length;r++){
for(let c=0;c<matrix[r].length;c++){
if (matrix[r][c]) board[row + r][col + c] = type;
}
}
}
function rotatePiece(dir=1){
if (!current) return;
const states = current.states;
const nextIndex = (current.rotIndex + dir + states.length) % states.length;
const nextMatrix = states[nextIndex];
// Try to wall-kick horizontally (simple)
const kicks = [0, -1, 1, -2, 2];
for (let k of kicks) {
const newCol = current.col + k;
if (!collides(board, nextMatrix, current.row, newCol)) {
current.rotIndex = nextIndex;
current.matrix = nextMatrix.map(r => r.slice());
current.col = newCol;
return;
}
}
}
function hardDrop(){
if(!current) return;
while(!collides(board, current.matrix, current.row+1, current.col)){
current.row++;
}
lockPiece();
}
function lockPiece(){
mergeToBoard(board, current.matrix, current.row, current.col, current.type || current.type === 0 ? current.type : current.type = current.type || current.type);
clearLines();
spawnPiece();
}
function clearLines(){
let cleared = 0;
for (let r = ROWS - 1; r >= 0; r--){
if (board[r].every(cell => cell)) {
board.splice(r, 1);
board.unshift(new Array(COLS).fill(0));
cleared++;
r++; // recheck same row index after splice
}
}
if (cleared > 0){
lines += cleared;
// Scoring: classic-ish (single/double/triple/tetris)
const scoreTable = [0, 40, 100, 300, 1200]; // multiplied by (level+1)
score += (scoreTable[cleared] || 0) * (level + 1);
// Level up every 10 lines
const newLevel = Math.floor(lines / 10);
if (newLevel > level) {
level = newLevel;
dropInterval = Math.max(100, 800 - level * 60);
}
}
}
// Move piece horizontally
function move(offset){
if(!current) return;
if (!collides(board, current.matrix, current.row, current.col + offset)) {
current.col += offset;
}
}
// Soft drop (one step)
function softDrop(){
if(!current) return;
if (!collides(board, current.matrix, current.row + 1, current.col)) {
current.row++;
score += 1; // small reward for soft drop
} else {
lockPiece();
}
}
// Draw helpers
function drawCell(x, y, color, stroke=true){
const px = offsetX + x * cellSize;
const py = offsetY + y * cellSize;
ctx.fillStyle = color || '#777';
ctx.fillRect(px + 1, py + 1, cellSize - 2, cellSize - 2);
if (stroke) {
ctx.strokeStyle = 'rgba(0,0,0,0.25)';
ctx.lineWidth = 1;
ctx.strokeRect(px + 0.5, py + 0.5, cellSize - 1, cellSize - 1);
}
}
function clearCanvas(){
ctx.fillStyle = '#041023';
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
function drawBoard(){
// board background
ctx.fillStyle = '#061222';
ctx.fillRect(offsetX, offsetY, boardWidth, boardHeight);
// grid + cells
for (let r=0;r<ROWS;r++){
for (let c=0;c<COLS;c++){
const cell = board[r][c];
if (cell) {
const color = COLORS[cell] || COLORS.X;
drawCell(c, r, color);
} else {
// draw faint grid cell background
ctx.strokeStyle = 'rgba(255,255,255,0.03)';
ctx.strokeRect(offsetX + c*cellSize + 0.5, offsetY + r*cellSize + 0.5, cellSize - 1, cellSize - 1);
}
}
}
// draw current piece
if (current){
for (let r=0;r<current.matrix.length;r++){
for (let c=0;c<current.matrix[r].length;c++){
if (current.matrix[r][c]){
const color = COLORS[current.type] || COLORS.X;
drawCell(current.col + c, current.row + r, color);
}
}
}
}
// draw ghost (landing position)
if (current) {
let ghostRow = current.row;
while(!collides(board, current.matrix, ghostRow+1, current.col)) ghostRow++;
ctx.globalAlpha = 0.25;
for (let r=0;r<current.matrix.length;r++){
for (let c=0;c<current.matrix[r].length;c++){
if (current.matrix[r][c]) {
drawCell(current.col + c, ghostRow + r, COLORS[current.type]);
}
}
}
ctx.globalAlpha = 1;
}
}
function drawHUD(){
// Score/level/lines
const hudX = offsetX + boardWidth + Math.max(10, Math.floor(cellSize*0.5));
const hudTop = offsetY;
ctx.fillStyle = '#ffffff';
ctx.font = `${Math.max(10, Math.floor(cellSize * 0.45))}px monospace`;
ctx.fillText(`Score: ${score}`, hudX, hudTop);
ctx.fillText(`Level: ${level}`, hudX, hudTop + 24);
ctx.fillText(`Lines: ${lines}`, hudX, hudTop + 44);
// Next piece box
ctx.fillStyle = '#081827';
const boxX = hudX;
const boxY = hudTop + 84;
const boxW = cellSize * 6;
const boxH = cellSize * 6;
ctx.fillRect(boxX, boxY, boxW, boxH);
ctx.strokeStyle = 'rgba(255,255,255,0.06)';
ctx.strokeRect(boxX + 0.5, boxY + 0.5, boxW - 1, boxH - 1);
ctx.fillStyle = '#fff';
ctx.fillText('Next', boxX, boxY - 18);
if (next){
const nm = next.matrix;
const padX = Math.floor((6 - nm[0].length)/2);
const padY = Math.floor((6 - nm.length)/2);
ctx.globalAlpha = 1;
for (let r=0;r<nm.length;r++){
for (let c=0;c<nm[r].length;c++){
if (nm[r][c]){
const px = boxX + (padX + c) * cellSize;
const py = boxY + (padY + r) * cellSize;
ctx.fillStyle = COLORS[next.type] || COLORS.X;
ctx.fillRect(px + 1, py + 1, cellSize - 2, cellSize - 2);
ctx.strokeStyle = 'rgba(0,0,0,0.25)';
ctx.strokeRect(px + 0.5, py + 0.5, cellSize - 1, cellSize - 1);
}
}
}
}
// bottom small instructions
ctx.fillStyle = 'rgba(255,255,255,0.7)';
ctx.font = `${Math.max(9, Math.floor(cellSize * 0.35))}px monospace`;
const infoY = offsetY + boardHeight + 6;
ctx.fillText('← → : move ↑ / X : rotate Z : rotate ccw', offsetX, infoY);
ctx.fillText('↓ : soft drop Space : hard drop P : pause', offsetX, infoY + 18);
}
function draw(){
clearCanvas();
drawBoard();
drawHUD();
if (paused) {
ctx.fillStyle = 'rgba(0,0,0,0.5)';
ctx.fillRect(offsetX, offsetY, boardWidth, boardHeight);
ctx.fillStyle = '#fff';
ctx.font = `${Math.max(18, Math.floor(cellSize * 0.8))}px monospace`;
ctx.fillText('PAUSED', offsetX + boardWidth/2 - 40, offsetY + boardHeight/2 - 12);
}
if (gameOver){
ctx.fillStyle = 'rgba(0,0,0,0.6)';
ctx.fillRect(offsetX, offsetY, boardWidth, boardHeight);
ctx.fillStyle = '#ffdddd';
ctx.font = `${Math.max(18, Math.floor(cellSize * 0.8))}px monospace`;
ctx.fillText('GAME OVER', offsetX + boardWidth/2 - 64, offsetY + boardHeight/2 - 12);
ctx.font = `${Math.max(12, Math.floor(cellSize * 0.45))}px monospace`;
ctx.fillText('Press R to restart', offsetX + boardWidth/2 - 62, offsetY + boardHeight/2 + 18);
}
}
// Game loop
function update(time = 0){
const dt = time - lastTime;
lastTime = time;
if(!paused && !gameOver){
dropAccumulator += dt;
if (dropAccumulator > dropInterval){
dropAccumulator = 0;
if (!current) spawnPiece();
else {
if (!collides(board, current.matrix, current.row + 1, current.col)) {
current.row++;
} else {
lockPiece();
}
}
}
}
draw();
if (!canvas._tetris._stop) requestAnimationFrame(update);
}
// Input handling
const keyState = {};
function onKeyDown(e){
if (e.repeat) return;
const k = e.key;
if (k === 'ArrowLeft') { move(-1); e.preventDefault(); }
else if (k === 'ArrowRight') { move(1); e.preventDefault(); }
else if (k === 'ArrowDown') { softDrop(); e.preventDefault(); }
else if (k === ' ' || k === 'Spacebar') { hardDrop(); e.preventDefault(); }
else if (k === 'x' || k === 'X' || k === 'ArrowUp') { rotatePiece(1); e.preventDefault(); }
else if (k === 'z' || k === 'Z') { rotatePiece(-1); e.preventDefault(); }
else if (k === 'p' || k === 'P') { paused = !paused; e.preventDefault(); }
else if (k === 'r' || k === 'R') {
if (gameOver) resetGame();
}
}
// Mouse / touch to pause on canvas double-click
function onDoubleClick(){ paused = !paused; }
// Restart
function resetGame(){
board = createMatrix(ROWS, COLS);
current = null;
next = randomPiece();
score = 0;
level = 0;
lines = 0;
dropInterval = 800;
dropAccumulator = 0;
lastTime = performance.now();
paused = false;
gameOver = false;
spawnPiece();
}
// Expose cleanup so future calls can remove listeners
function cleanup(){
window.removeEventListener('keydown', onKeyDown);
canvas.removeEventListener('dblclick', onDoubleClick);
canvas._tetris._stop = true;
}
// Save state on canvas to avoid multiple listeners on repeated calls
canvas._tetris = canvas._tetris || {};
canvas._tetris.cleanup = cleanup;
canvas._tetris._stop = false;
// Attach listeners
window.addEventListener('keydown', onKeyDown);
canvas.addEventListener('dblclick', onDoubleClick);
// Initialize
next = randomPiece();
spawnPiece();
lastTime = performance.now();
requestAnimationFrame(update);
}
// Boblox 2004 Clone with playable games
// Utility function to create elements
function el(tag, cls) { const e=document.createElement(tag); if(cls)e.className=cls; return e; }
// Storage for player data
const BobloxData = {
username: 'Player22',
coins: 100,
avatarColor: '#0f0',
};
// --- Start Boblox ---
function startRoblox(wrap){
if(!wrap) return;
wrap.innerHTML='';
// Header
const header = el('div');
header.style.background='#0055aa';
header.style.color='#fff';
header.style.padding='12px';
header.style.fontWeight='bold';
header.style.textAlign='center';
header.textContent='Boblox 2004';
wrap.append(header);
// Sidebar (Avatar & coins)
const sidebar = el('div');
sidebar.style.width='120px';
sidebar.style.float='left';
sidebar.style.padding='10px';
sidebar.style.borderRight='1px solid #aaa';
sidebar.style.height='360px';
// Avatar display
const avatar = el('div');
avatar.style.width='60px';
avatar.style.height='60px';
avatar.style.background=BobloxData.avatarColor;
avatar.style.border='2px solid #000';
avatar.style.marginBottom='10px';
sidebar.append(avatar);
// Username
const usernameDisplay = el('div');
usernameDisplay.textContent=BobloxData.username;
usernameDisplay.style.marginBottom='10px';
sidebar.append(usernameDisplay);
// Coins
const coinsDisplay = el('div');
coinsDisplay.textContent='Coins: '+BobloxData.coins;
coinsDisplay.style.marginBottom='10px';
sidebar.append(coinsDisplay);
// Catalog / Avatar Editor
const catalogBtn = el('button');
catalogBtn.textContent='Catalog';
catalogBtn.style.display='block';
catalogBtn.style.marginBottom='6px';
catalogBtn.addEventListener('click', ()=> startCatalog(wrap));
sidebar.append(catalogBtn);
const avatarBtn = el('button');
avatarBtn.textContent='Avatar';
avatarBtn.style.display='block';
avatarBtn.addEventListener('click', ()=> startAvatarEditor(wrap, avatar));
sidebar.append(avatarBtn);
wrap.append(sidebar);
// Main content (Games grid)
const main = el('div');
main.style.marginLeft='140px';
const games = [
{name:'Obby', play:startObby},
{name:'Coin Tycoon', play:startTycoon},
{name:'Jump Test', play:startBattle},
{name:'Content Trains Boblox', play:startRace}
];
const gamesGrid = el('div');
gamesGrid.style.display='grid';
gamesGrid.style.gridTemplateColumns='repeat(auto-fit, minmax(120px, 1fr))';
gamesGrid.style.gap='6px';
gamesGrid.style.marginTop='10px';
games.forEach(game=>{
const card = el('div');
card.style.border='1px solid #aaa';
card.style.padding='6px';
card.style.cursor='pointer';
card.style.background='#fff';
card.style.textAlign='center';
card.innerHTML=`<div style="height:60px;background:#ccc;margin-bottom:4px;">🎮</div><strong>${game.name}</strong>`;
card.addEventListener('click', ()=>{
main.innerHTML=''; // clear games grid
const back = el('button');
back.textContent='Back to Home';
back.addEventListener('click', ()=> startRoblox(wrap));
main.append(back);
game.play(main);
});
gamesGrid.append(card);
});
main.append(gamesGrid);
wrap.append(main);
}
// --- Catalog / Avatar Editor ---
function startCatalog(wrap){
wrap.innerHTML='';
const back = el('button');
back.textContent='Back';
back.addEventListener('click', ()=> startRoblox(wrap));
wrap.append(back);
const shopTitle = el('h3'); shopTitle.textContent='Catalog';
wrap.append(shopTitle);
const items = [
{name:'Red Hat', cost:20, color:'#f00'},
{name:'Blue Shirt', cost:30, color:'#00f'},
{name:'Yellow Pants', cost:50, color:'#ff0'}
];
items.forEach(item=>{
const itemDiv = el('div');
itemDiv.style.border='1px solid #aaa';
itemDiv.style.padding='6px';
itemDiv.style.margin='6px';
itemDiv.style.cursor='pointer';
itemDiv.innerHTML=`<div style="width:40px;height:40px;background:${item.color};display:inline-block;margin-right:6px;"></div>${item.name} - ${item.cost} coins`;
itemDiv.addEventListener('click', ()=>{
if(BobloxData.coins>=item.cost){
BobloxData.coins-=item.cost;
alert(`You bought ${item.name}!`);
} else alert('Not enough coins!');
});
wrap.append(itemDiv);
});
}
function startAvatarEditor(wrap, avatarEl){
wrap.innerHTML='';
const back = el('button');
back.textContent='Back';
back.addEventListener('click', ()=> startRoblox(wrap));
wrap.append(back);
const colors = ['#0f0','#f00','#00f','#ff0','#f0f','#0ff'];
colors.forEach(c=>{
const btn = el('button');
btn.style.background=c;
btn.style.width='40px';
btn.style.height='40px';
btn.style.margin='4px';
btn.addEventListener('click', ()=>{
BobloxData.avatarColor=c;
avatarEl.style.background=c;
});
wrap.append(btn);
});
}
// --- NPC Helper ---
function createNPC(name){
const npc=document.createElement('div');
npc.textContent=name[0];
npc.style.position='absolute';
npc.style.width='24px';
npc.style.height='24px';
npc.style.background='#ff0';
npc.style.borderRadius='50%';
npc.style.textAlign='center';
npc.style.lineHeight='24px';
return npc;
}
// --- games yk ---
// 1. Obby
function startObby(container){
// obby
const info=document.createElement('div');
info.textContent='🏃 Obby! Arrow keys to move';
container.append(info);
const gameArea=document.createElement('div');
gameArea.style.position='relative';
gameArea.style.height='200px';
gameArea.style.border='1px solid #000';
gameArea.style.background='#ccf';
container.append(gameArea);
const player=document.createElement('div');
player.style.position='absolute';
player.style.left='20px';
player.style.bottom='0px';
player.style.width='24px';
player.style.height='24px';
player.style.background=BobloxData.avatarColor;
gameArea.append(player);
const platforms=[];
for(let i=0;i<6;i++){
const plat=document.createElement('div');
plat.style.position='absolute';
plat.style.width='60px';
plat.style.height='10px';
plat.style.background='#555';
plat.style.left=`${80*i}px`;
plat.style.bottom=`${30*i}px`;
gameArea.append(plat);
platforms.push(plat);
}
const npcNames=['A','B','C'];
const npcs = npcNames.map(name=>{
const npc=createNPC(name);
npc.style.left='20px';
npc.style.bottom='0px';
gameArea.append(npc);
return {el:npc,pathIndex:0,vy:0};
});
let playerLeft=20, playerBottom=0, vy=0, gravity=2;
function checkCollision(objBottom,objLeft){
for(const plat of platforms){
const platLeft=parseInt(plat.style.left);
const platBottom=parseInt(plat.style.bottom);
const platWidth=parseInt(plat.style.width);
const platHeight=parseInt(plat.style.height);
if(objLeft+24>platLeft && objLeft<platLeft+platWidth &&
objBottom+24>=platBottom && objBottom<=platBottom+platHeight){
return platBottom+platHeight;
}
}
return -1;
}
function npcFollowObby(npc){
const targetPlat=platforms[npc.pathIndex];
let npcLeft=parseInt(npc.el.style.left);
let npcBottom=parseInt(npc.el.style.bottom);
if(npcLeft<parseInt(targetPlat.style.left)) npcLeft+=2;
if(npcLeft>parseInt(targetPlat.style.left)) npcLeft-=2;
const coll=checkCollision(npcBottom,npcLeft);
if(coll>=0) npcBottom=coll;
else npc.vy-=gravity;
npcBottom+=npc.vy;
npc.el.style.left=npcLeft+'px';
npc.el.style.bottom=npcBottom+'px';
if(Math.abs(npcLeft-parseInt(targetPlat.style.left))<3 && npcBottom<=parseInt(targetPlat.style.bottom)){
npc.pathIndex=(npc.pathIndex+1)%platforms.length;
npc.vy=10;
}
}
document.addEventListener('keydown',e=>{
if(e.code==='ArrowLeft') playerLeft-=10;
if(e.code==='ArrowRight') playerLeft+=10;
if(e.code==='Space' && playerBottom===0) vy=20;
});
setInterval(()=>{
vy-=gravity;
playerBottom+=vy;
const coll=checkCollision(playerBottom,playerLeft);
if(coll>=0){ playerBottom=coll; vy=0; }
if(playerBottom<0){ playerBottom=0; vy=0; }
player.style.left=playerLeft+'px';
player.style.bottom=playerBottom+'px';
npcs.forEach(npcFollowObby);
},40);
}
// 2. Tycoon
function startTycoon(container){
const info=document.createElement('div');
info.textContent='💰 Coin Tycoon! Click buildings to collect coins.';
container.append(info);
const area=document.createElement('div');
area.style.position='relative';
area.style.height='200px';
area.style.border='1px solid #000';
container.append(area);
let coins=BobloxData.coins;
const coinDisplay=document.createElement('div');
coinDisplay.textContent='Coins: '+coins;
container.append(coinDisplay);
const buildings=[];
for(let i=0;i<3;i++){
const b=document.createElement('div');
b.style.position='absolute';
b.style.width='40px';
b.style.height='40px';
b.style.background='#a52';
b.style.bottom='0px';
b.style.left=`${50+i*60}px`;
b.style.cursor='pointer';
b.addEventListener('click', ()=> { coins+=5; coinDisplay.textContent='Coins: '+coins; BobloxData.coins=coins; });
area.append(b);
buildings.push(b);
}
const npcs=['X','Y','Z'].map(createNPC);
npcs.forEach((npc,i)=>{
npc.style.left=`${60+i*50}px`;
npc.style.bottom='0px';
area.append(npc);
setInterval(()=>{
coins+=1;
coinDisplay.textContent='Coins: '+coins;
BobloxData.coins=coins;
},1000);
});
}
// 3. Jump Test
function startBattle(container){
const info=document.createElement('div');
info.textContent='Jump test! Space to jump.';
container.append(info);
const area=document.createElement('div');
area.style.position='relative';
area.style.height='200px';
area.style.border='1px solid #000';
container.append(area);
const player=createNPC('P');
player.style.background=BobloxData.avatarColor;
player.style.left='20px';
player.style.bottom='0px';
area.append(player);
const npcs=['A','B','C'].map(createNPC);
npcs.forEach((npc,i)=>{
npc.style.left=`${100+i*50}px`;
npc.style.bottom='0px';
area.append(npc);
setInterval(()=>{
npc.style.bottom=(Math.random()*100)+'px';
},700+Math.random()*500);
});
let left=20, bottom=0;
document.addEventListener('keydown',e=>{
if(e.code==='ArrowLeft') left-=10;
if(e.code==='ArrowRight') left+=10;
if(e.code==='ArrowUp') bottom+=20;
player.style.left=left+'px';
player.style.bottom=bottom+'px';
setTimeout(()=> { bottom=0; player.style.bottom=bottom+'px'; },300);
});
}
// 4. Content Trains Boblox
function startRace(container){
const info=document.createElement('div');
info.textContent='🚂 Content Trains Boblox! Press Arrows to move.';
container.append(info);
const track=document.createElement('div');
track.style.position='relative';
track.style.height='150px';
track.style.border='1px solid #000';
container.append(track);
const player=createNPC('P');
player.style.background=BobloxData.avatarColor;
player.style.left='20px';
player.style.bottom='0px';
track.append(player);
const npcs=['A','B','C'].map(createNPC);
npcs.forEach((npc,i)=>{
npc.style.left=`${20+i*40}px`;
npc.style.bottom='0px';
track.append(npc);
setInterval(()=>{
const cur=parseInt(npc.style.left);
npc.style.left=(cur+Math.random()*5)+'px';
},200);
});
let left=20;
document.addEventListener('keydown',e=>{
if(e.code==='ArrowRight'){
left+=10;
player.style.left=left+'px';
}
});
}
function startMinesweeper(container, cols=10, rows=10, mines=12){
if(!container) return;
container.innerHTML='';
const grid = [];
const field = document.createElement('div'); field.style.display='grid'; field.style.gridTemplateColumns = `repeat(${cols}, 30px)`; field.style.gap='4px';
container.append(field);
// create cells
for(let r=0;r<rows;r++){
for(let c=0;c<cols;c++){
const cell = {r,c, mine:false, revealed:false, flagged:false, el: null};
const btn = document.createElement('button'); btn.style.width='30px'; btn.style.height='30px'; btn.style.borderRadius='6px';
cell.el = btn;
btn.addEventListener('click', ()=> reveal(cell));
btn.addEventListener('contextmenu', (ev)=>{ ev.preventDefault(); cell.flagged = !cell.flagged; btn.textContent = cell.flagged ? '🚩' : ''; });
field.append(btn);
grid.push(cell);
}
}
// place mines
for(let m=0;m<mines;m++){
let idx;
do { idx = Math.floor(Math.random()*grid.length); } while(grid[idx].mine);
grid[idx].mine = true;
}
function neighbors(cell){
const arr=[];
for(const n of grid){
if(Math.abs(n.r - cell.r) <= 1 && Math.abs(n.c - cell.c) <=1 && !(n.r===cell.r && n.c===cell.c)) arr.push(n);
}
return arr;
}
function reveal(cell){
if(cell.revealed || cell.flagged) return;
cell.revealed = true; cell.el.style.background='#0b2a3a'; cell.el.disabled=true;
if(cell.mine){ cell.el.textContent='💣'; alert('Boom!'); return; }
const count = neighbors(cell).filter(x=>x.mine).length;
if(count>0) cell.el.textContent = count;
else neighbors(cell).forEach(n=> reveal(n));
}
}
// Tie into Files delete operation (override pattern): when Files app 'delete' is called, we can't intercept internal function easily,
// so provide a small UI helper: select file and 'Send to Recycle Bin' via the Files UI manual action.
// Add a menu item in each file card for "Send to Recycle" if Files is open: we won't modify Files code; users can use the New File + Recycle Bin manually.
// If wallpaper was set by the Image Viewer earlier, restore it on load
const ws = localStorage.getItem('OS:wall-src');
if(ws) wallpaperEl.style.background = `url(${ws}) center/cover`;
// Reshow start menu tile list to include newly added apps
// (append only the ones not already present)
const existingTiles = new Set([...document.querySelectorAll('#appGrid .app-tile')].map(t=>t.textContent.trim()));
Object.entries(OS.apps).forEach(([id, a])=>{
const text = a.title + id;
// don't duplicate exact tile names: check by id shown
if(!document.querySelector(`#appGrid div:contains(${id})`)) {
// simple check: add everything (some duplicates may appear if original later runs; safe)
}
});
// Add keyboard shortcut: Ctrl+Alt+G -> open App Store / Game Hub
window.addEventListener('keydown', (e)=>{ if(e.ctrlKey && e.altKey && e.key.toLowerCase()==='g'){ OS.launch({ id:'app_store', title:OS.apps.app_store.title, icon: iconSquare(), content: OS.apps.app_store.content, size: OS.apps.app_store.size }); } });
// Also add simple API so other scripts can add apps later:
window.AeroLiteOS.registerApp = function(id, app){ if(OS.apps[id]) return false; OS.apps[id] = app; return true; };
// Small helper: when Files delete triggers outside, user can call OSDeleteToRecycle({name,data})
// Example usage in browser console: OSDeleteToRecycle({name:'demo.txt', data:'hello'});
})();
</script>
<!-- End of file -->
</body>
</html>
<!-- Please Support this project here https://www.patreon.com/Calacobragameengine/membership -->
@clashnewbm3
Copy link
Author

You can donate to this project here https://www.patreon.com/Calacobragameengine/membership

@clashnewbm3
Copy link
Author

A new update is now here lemme update the code!

@clashnewbm3
Copy link
Author

0.7 is here! Help fund the project! https://www.patreon.com/Calacobragameengine/membership

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment