<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>NROS — Browser-native OS (desktop icons)</title>
<style>
:root {
--accent: #3b82f6;
--accent-2: #9333ea;
--bg: #0f172a;
--panel: #111827cc;
--win: #1f2937cc;
--text: #e5e7eb;
--muted: #9ca3af;
--success: #22c55e;
--danger: #ef4444;
--shadow: 0 12px 30px rgba(0,0,0,.35);
--wallpaper: url('https://images.unsplash.com/photo-1517694712202-14dd9538aa97?q=80&w=1600&auto=format&fit=crop');
}
* { box-sizing: border-box; }
html, body { height: 100%; margin: 0; background: var(--bg); color: var(--text); font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif; }
#desktop {
position: fixed; inset: 0; background: center/cover no-repeat var(--wallpaper);
}
/* Desktop icon layer /
#icons {
position: absolute; inset: 0 0 48px 0; / leave room for taskbar /
pointer-events: none; / windows receive drag; individual icons re-enable pointer-events */
}
.desk-icon {
position: absolute; width: 110px; min-height: 100px; padding: 8px 6px;
display: grid; place-items: center; gap: 6px; text-align: center; color: #fff;
background: transparent; border-radius: 10px; user-select: none;
pointer-events: auto; cursor: default;
}
.desk-icon .glyph {
width: 60px; height: 60px; border-radius: 14px;
display: grid; place-items: center; font-size: 28px; font-weight: 700;
background: rgba(0,0,0,.25); box-shadow: inset 0 0 0 1px rgba(255,255,255,.12);
}
.desk-icon .label {
font-size: 13px; text-shadow: 0 1px 2px rgba(0,0,0,.7);
white-space: nowrap; overflow: hidden; text-overflow: ellipsis; width: 100%;
}
.desk-icon.selected { outline: 2px solid rgba(255,255,255,.35); background: rgba(255,255,255,.08); }
.desk-icon:hover .glyph { box-shadow: inset 0 0 0 2px rgba(255,255,255,.2); }
#taskbar {
position: fixed; left: 0; right: 0; bottom: 0; height: 48px;
background: linear-gradient( to top, rgba(0,0,0,.65), rgba(0,0,0,.35) );
display: flex; align-items: center; gap: 10px; padding: 6px 10px; backdrop-filter: blur(6px);
border-top: 1px solid rgba(255,255,255,.08);
}
.orb {
width: 36px; height: 36px; border-radius: 50%;
background: radial-gradient(circle at 30% 30%, var(--accent), var(--accent-2));
box-shadow: 0 0 0 2px rgba(255,255,255,.12), 0 6px 18px rgba(0,0,0,.5);
cursor: pointer; display: grid; place-items: center; font-weight: 700; color: #fff;
}
.task-items { display: flex; align-items: center; gap: 6px; flex: 1; }
.task-item {
min-width: 120px; max-width: 180px; padding: 6px 10px; border-radius: 8px;
background: rgba(255,255,255,.06); box-shadow: inset 0 0 0 1px rgba(255,255,255,.08);
display: flex; align-items: center; gap: 8px; cursor: pointer; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.task-item.active { outline: 2px solid var(--accent); background: rgba(59,130,246,.15); }
.tray { display: flex; align-items: center; gap: 10px; }
.clock { font-variant-numeric: tabular-nums; color: var(--muted); }
#launcher {
position: fixed; left: 8px; bottom: 58px; width: 380px; max-height: 70vh;
background: rgba(17,24,39,.9); border: 1px solid rgba(255,255,255,.08);
border-radius: 12px; box-shadow: var(--shadow); backdrop-filter: blur(8px); display: none; flex-direction: column;
}
#launcher header { padding: 12px; border-bottom: 1px solid rgba(255,255,255,.08); }
#search { width: 100%; padding: 10px; border-radius: 8px; border: 1px solid rgba(255,255,255,.15); background: rgba(0,0,0,.35); color: var(--text); }
#app-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; padding: 12px; }
.app-tile {
padding: 14px; border-radius: 10px; background: rgba(255,255,255,.06);
box-shadow: inset 0 0 0 1px rgba(255,255,255,.08); cursor: pointer; text-align: center;
}
.app-tile:hover { outline: 2px solid var(--accent); }
.window {
position: absolute; width: 640px; height: 420px; background: var(--win);
border: 1px solid rgba(255,255,255,.12); border-radius: 12px; box-shadow: var(--shadow);
display: flex; flex-direction: column; overflow: hidden;
}
.titlebar {
height: 40px; display: flex; align-items: center; gap: 10px; padding: 0 10px;
background: linear-gradient(to right, rgba(255,255,255,.06), rgba(255,255,255,.02));
cursor: grab; user-select: none;
}
.titlebar:active { cursor: grabbing; }
.title { flex: 1; font-weight: 600; }
.controls { display: flex; align-items: center; gap: 6px; }
.btn {
width: 28px; height: 28px; border-radius: 6px; display: grid; place-items: center; cursor: pointer;
background: rgba(255,255,255,.08); border: 1px solid rgba(255,255,255,.1);
}
.btn:hover { filter: brightness(1.2); }
.btn.close { background: rgba(239,68,68,.25); border-color: rgba(239,68,68,.4); }
.btn.max { background: rgba(34,197,94,.25); border-color: rgba(34,197,94,.4); }
.btn.min { background: rgba(59,130,246,.25); border-color: rgba(59,130,246,.4); }
.content { flex: 1; padding: 10px; overflow: auto; }
.status { font-size: 12px; color: var(--muted); padding: 6px 10px; border-top: 1px solid rgba(255,255,255,.08); background: rgba(0,0,0,.2); }
.toolbar { display: flex; gap: 8px; margin-bottom: 8px; }
.input, .button, .select {
padding: 8px 10px; border-radius: 8px; border: 1px solid rgba(255,255,255,.15);
background: rgba(0,0,0,.35); color: var(--text);
}
.button { cursor: pointer; }
.flex { display: flex; gap: 10px; }
.col { display: flex; flex-direction: column; gap: 6px; }
.center { display: grid; place-items: center; height: 100%; }
canvas { background: rgba(0,0,0,.35); border-radius: 8px; }
.note-area { width: 100%; height: 100%; padding: 12px; border-radius: 8px; background: rgba(0,0,0,.35); border: 1px solid rgba(255,255,255,.12); color: var(--text); }
.grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
.card { padding: 10px; border-radius: 8px; background: rgba(255,255,255,.06); border: 1px solid rgba(255,255,255,.08); }
.wallpaper-thumb { width: 100%; height: 80px; border-radius: 8px; background-size: cover; background-position: center; cursor: pointer; }
.hidden { display: none !important; }
</style>
</head>
<body>
<div id="desktop">
<div id="icons"></div>
</div>
<div id="launcher">
<header>
<input id="search" type="text" placeholder="Search apps…" />
</header>
<div id="app-grid"></div>
</div>
<div id="taskbar">
<div class="orb" id="orb">NR</div>
<div class="task-items" id="task-items"></div>
<div class="tray">
<span class="clock" id="clock"></span>
</div>
</div>
<script>
/* ===========================
NROS - window/app system
=========================== */
const state = {
z: 10,
windows: new Map(),
tasks: new Map(),
minimized: new Set(),
maximized: new Set(),
theme: localStorage.getItem('nros.theme') || 'dark',
wallpaper: localStorage.getItem('nros.wallpaper') || getComputedStyle(document.documentElement).getPropertyValue('--wallpaper'),
icons: JSON.parse(localStorage.getItem('nros.icons') || '{}') // positions by app id
};
function setTheme(t) {
state.theme = t;
document.documentElement.style.setProperty('--bg', t === 'dark' ? '#0f172a' : '#e5e7eb');
document.documentElement.style.setProperty('--text', t === 'dark' ? '#e5e7eb' : '#111827');
document.documentElement.style.setProperty('--panel', t === 'dark' ? '#111827cc' : '#ffffffcc');
document.documentElement.style.setProperty('--win', t === 'dark' ? '#1f2937cc' : '#ffffffcc');
localStorage.setItem('nros.theme', t);
}
function setWallpaper(url) {
state.wallpaper = `url('${url}')`;
document.documentElement.style.setProperty('--wallpaper', state.wallpaper);
localStorage.setItem('nros.wallpaper', state.wallpaper);
}
function clockTick() {
const d = new Date();
const hh = String(d.getHours()).padStart(2,'0');
const mm = String(d.getMinutes()).padStart(2,'0');
document.getElementById('clock').textContent = `${hh}:${mm}`;
}
setInterval(clockTick, 1000); clockTick();
/* ---------- window manager ---------- */
function createWindow({ id, title, width=680, height=440, x=100, y=80, content }) {
if (state.windows.has(id)) { focusWindow(id); return state.windows.get(id); }
const win = el('div', 'window'); win.style.width = width+'px'; win.style.height = height+'px'; win.style.left = x+'px'; win.style.top = y+'px';
win.dataset.id = id;
const tb = el('div', 'titlebar');
const icon = el('div'); icon.style.width='10px'; icon.style.height='10px'; icon.style.borderRadius='50%'; icon.style.background='var(--accent)';
const ttl = el('div', 'title', title);
const ctr = el('div', 'controls');
const btnMin = el('div', 'btn min', '—');
const btnMax = el('div', 'btn max', '▭');
const btnClose = el('div', 'btn close', '×');
ctr.append(btnMin, btnMax, btnClose);
tb.append(icon, ttl, ctr);
const cnt = el('div', 'content'); cnt.append(content);
const st = el('div', 'status', title);
win.append(tb, cnt, st);
document.getElementById('desktop').append(win);
// drag window
let dx=0, dy=0, dragging=false;
tb.addEventListener('mousedown', e => {
dragging = true;
const rect = win.getBoundingClientRect();
dx = e.clientX - rect.left;
dy = e.clientY - rect.top;
});
window.addEventListener('mousemove', e => {
if (!dragging || state.maximized.has(id)) return;
win.style.left = (e.clientX - dx) + 'px';
win.style.top = (e.clientY - dy) + 'px';
});
window.addEventListener('mouseup', () => dragging=false);
// controls
btnClose.onclick = () => closeWindow(id);
btnMin.onclick = () => minimizeWindow(id);
btnMax.onclick = () => toggleMaximize(id);
win.addEventListener('mousedown', () => focusWindow(id));
focusWindow(id);
// task button
const task = el('div', 'task-item', title);
task.onclick = () => {
if (state.minimized.has(id)) restoreWindow(id);
else focusWindow(id);
};
document.getElementById('task-items').append(task);
state.windows.set(id, win);
state.tasks.set(id, task);
return win;
}
function closeWindow(id) {
const win = state.windows.get(id);
if (!win) return;
win.remove();
const t = state.tasks.get(id); if (t) t.remove();
state.windows.delete(id); state.tasks.delete(id);
state.minimized.delete(id); state.maximized.delete(id);
}
function focusWindow(id) {
const win = state.windows.get(id); if (!win) return;
win.style.zIndex = ++state.z;
document.querySelectorAll('.task-item').forEach(t => t.classList.remove('active'));
const t = state.tasks.get(id); if (t) t.classList.add('active');
}
function minimizeWindow(id) {
const win = state.windows.get(id); if (!win) return;
win.classList.add('hidden'); state.minimized.add(id);
const t = state.tasks.get(id); if (t) t.classList.remove('active');
}
function restoreWindow(id) {
const win = state.windows.get(id); if (!win) return;
win.classList.remove('hidden'); state.minimized.delete(id);
focusWindow(id);
}
function toggleMaximize(id) {
const win = state.windows.get(id); if (!win) return;
if (state.maximized.has(id)) {
win.style.left = '100px'; win.style.top = '80px';
win.style.width = '680px'; win.style.height='440px';
state.maximized.delete(id);
} else {
win.style.left = '0px'; win.style.top = '0px';
win.style.width = '100vw'; win.style.height = 'calc(100vh - 48px)';
state.maximized.add(id);
}
focusWindow(id);
}
function el(tag, cls, text) {
const e = document.createElement(tag);
if (cls) e.className = cls;
if (text) e.textContent = text;
return e;
}
/* ---------- apps ---------- */
const apps = [
{
id: 'browser', name: 'NROS Browser', icon: '🌐',
open: () => {
const addr = el('input', 'input'); addr.placeholder = 'Enter URL (https://...)';
const go = el('button', 'button', 'Go');
const back = el('button', 'button', '⟵'); const fwd = el('button', 'button', '⟶');
const bar = el('div', 'toolbar'); bar.append(back, fwd, addr, go);
const frame = document.createElement('iframe'); frame.style.width='100%'; frame.style.height='100%'; frame.style.border='0'; frame.referrerPolicy='no-referrer';
const hist = []; let idx = -1;
function navigate(u) {
try {
if (!/^https?:\/\//i.test(u)) u = 'https://'+u;
frame.src = u; addr.value = u;
hist.splice(idx+1); hist.push(u); idx = hist.length-1;
} catch(e) { /* noop */ }
}
back.onclick = () => { if (idx>0) { idx--; frame.src = hist[idx]; addr.value = hist[idx]; } };
fwd.onclick = () => { if (idx<hist.length-1) { idx++; frame.src = hist[idx]; addr.value = hist[idx]; } };
go.onclick = () => navigate(addr.value.trim());
addr.addEventListener('keydown', e => { if (e.key==='Enter') navigate(addr.value.trim()); });
const wrap = el('div'); wrap.append(bar, frame);
createWindow({ id:'browser', title:'NROS Browser', content:wrap });
}
},
{
id: 'notepad', name: 'Notepad', icon: '📝',
open: () => {
const area = el('textarea', 'note-area'); area.value = localStorage.getItem('nros.notes') || '';
area.addEventListener('input', () => localStorage.setItem('nros.notes', area.value));
const wrap = el('div'); wrap.append(area);
createWindow({ id:'notepad', title:'Notepad', content:wrap });
}
},
{
id: 'files', name: 'Files', icon: '📁',
open: () => {
const root = el('div', 'grid');
['Documents','Pictures','Music','Videos','Downloads','Desktop'].forEach(f => {
const c = el('div','card'); c.innerHTML = `<strong>${f}</strong><br><span class="status">Mock folder</span>`;
root.append(c);
});
createWindow({ id:'files', title:'Files', content:root });
}
},
{
id: 'settings', name: 'Settings', icon: '⚙️',
open: () => {
const themeLabel = el('div'); themeLabel.innerHTML = '<strong>Theme</strong>';
const themeSelect = el('select','select'); ['dark','light'].forEach(t => { const o=document.createElement('option'); o.value=t; o.textContent=t; themeSelect.append(o); }); themeSelect.value = state.theme;
themeSelect.onchange = () => setTheme(themeSelect.value);
const wallLabel = el('div'); wallLabel.innerHTML = '<strong>Wallpaper</strong>';
const walls = [
'https://images.unsplash.com/photo-1517694712202-14dd9538aa97?q=80&w=1600&auto=format&fit=crop',
'https://images.unsplash.com/photo-1500530855697-b586d89ba3ee?q=80&w=1600&auto=format&fit=crop',
'https://images.unsplash.com/photo-1469474968028-56623f02e42e?q=80&w=1600&auto=format&fit=crop',
'https://images.unsplash.com/photo-1446776811953-b23d57bd21aa?q=80&w=1600&auto=format&fit=crop'
];
const wallGrid = el('div','grid');
walls.forEach(u => {
const w = el('div','wallpaper-thumb'); w.style.backgroundImage = `url('${u}')`;
w.onclick = () => setWallpaper(u);
wallGrid.append(w);
});
const wrap = el('div','col');
wrap.append(themeLabel, themeSelect, wallLabel, wallGrid);
createWindow({ id:'settings', title:'Settings', content:wrap });
}
},
{
id: 'snake', name: 'Snake', icon: '🐍',
open: () => {
const canvas = document.createElement('canvas'); canvas.width = 480; canvas.height = 320;
const ctx = canvas.getContext('2d');
let dir = {x:1,y:0}, speed = 120, grid = 16;
let snake = [{x:10,y:10}];
let food = {x: 5, y: 5};
let running = true;
function randFood() { food.x = Math.floor(Math.random()* (canvas.width/grid)); food.y = Math.floor(Math.random()* (canvas.height/grid)); }
function update() {
if (!running) return;
const head = { x: snake[0].x + dir.x, y: snake[0].y + dir.y };
head.x = (head.x + canvas.width/grid) % (canvas.width/grid);
head.y = (head.y + canvas.height/grid) % (canvas.height/grid);
snake.unshift(head);
if (head.x === food.x && head.y === food.y) { randFood(); } else { snake.pop(); }
for (let i=1;i<snake.length;i++) { if (snake[i].x===head.x && snake[i].y===head.y) running=false; }
draw();
setTimeout(update, speed);
}
function draw() {
ctx.fillStyle = 'rgba(0,0,0,.35)'; ctx.fillRect(0,0,canvas.width,canvas.height);
ctx.fillStyle = '#22c55e';
snake.forEach(s => ctx.fillRect(s.x*grid, s.y*grid, grid-2, grid-2));
ctx.fillStyle = '#f59e0b'; ctx.fillRect(food.x*grid, food.y*grid, grid-2, grid-2);
if (!running) { ctx.fillStyle='#ef4444'; ctx.font='20px system-ui'; ctx.fillText('Game Over - press R', 120, 160); }
}
window.addEventListener('keydown', e => {
if (e.key==='ArrowUp' && dir.y!==1) dir={x:0,y:-1};
if (e.key==='ArrowDown' && dir.y!==-1) dir={x:0,y:1};
if (e.key==='ArrowLeft' && dir.x!==1) dir={x:-1,y:0};
if (e.key==='ArrowRight' && dir.x!==-1) dir={x:1,y:0};
if (e.key.toLowerCase()==='r') { snake=[{x:10,y:10}]; dir={x:1,y:0}; running=true; }
});
const wrap = el('div','center'); wrap.append(canvas);
createWindow({ id:'snake', title:'Snake', content:wrap });
randFood(); draw(); setTimeout(update, speed);
}
},
{
id: 'tictactoe', name: 'Tic‑Tac‑Toe', icon: '⭕',
open: () => {
const board = Array(9).fill(null);
let turn = 'X', over = false;
const grid = el('div','grid');
const status = el('div','status','Your move: X');
function render() {
grid.innerHTML = '';
for (let i=0;i<9;i++) {
const cell = el('div','card'); cell.style.height='72px'; cell.style.fontSize='28px'; cell.style.display='grid'; cell.style.placeItems='center';
cell.textContent = board[i] || '';
cell.onclick = () => {
if (over || board[i]) return;
board[i] = turn; turn = turn==='X' ? 'O' : 'X';
const w = winner(board);
if (w) { status.textContent = `Winner: ${w}`; over=true; }
else if (board.every(Boolean)) { status.textContent = 'Draw'; over=true; }
else status.textContent = `Your move: ${turn}`;
render();
};
grid.append(cell);
}
}
function winner(b) {
const lines = [[0,1,2],[3,4,5],[6,7,8],[0,3,6],[1,4,7],[2,5,8],[0,4,8],[2,4,6]];
for (const [a,b2,c] of lines) { if (board[a] && board[a]===board[b2] && board[a]===board[c]) return board[a]; }
return null;
}
const reset = el('button','button','Reset');
reset.onclick = () => { for (let i=0;i<9;i++) board[i]=null; turn='X'; over=false; status.textContent='Your move: X'; render(); };
const wrap = el('div','col'); wrap.append(grid, reset, status);
createWindow({ id:'tictactoe', title:'Tic‑Tac‑Toe', content:wrap });
render();
}
}
];
/* ---------- launcher ---------- */
const gridEl = document.getElementById('app-grid');
function buildLauncher() {
gridEl.innerHTML = '';
apps.forEach(a => {
const tile = el('div','app-tile');
tile.innerHTML = `<div style="font-size:24px">${a.icon}</div><div style="margin-top:6px">${a.name}</div>`;
tile.onclick = () => { a.open(); toggleLauncher(false); };
gridEl.append(tile);
});
}
buildLauncher();
function toggleLauncher(show) {
const l = document.getElementById('launcher');
l.style.display = show ? 'flex' : 'none';
}
document.getElementById('orb').onclick = () => toggleLauncher(document.getElementById('launcher').style.display==='none');
document.getElementById('search').addEventListener('input', e => {
const q = e.target.value.toLowerCase();
Array.from(gridEl.children).forEach(tile => {
tile.style.display = tile.textContent.toLowerCase().includes(q) ? '' : 'none';
});
});
/* ---------- desktop icons ---------- */
const iconsLayer = document.getElementById('icons');
function layoutDefaults() {
const padX = 14, padY = 14, colW = 120, rowH = 120;
let x = padX, y = padY, maxH = iconsLayer.clientHeight;
const pos = {};
apps.forEach((a, i) => {
pos[a.id] = { x, y };
y += rowH;
if (y + rowH > maxH - 20) { y = padY; x += colW; }
});
return pos;
}
function saveIconPos(id, x, y) {
state.icons[id] = { x, y };
localStorage.setItem('nros.icons', JSON.stringify(state.icons));
}
function buildDesktopIcons() {
iconsLayer.innerHTML = '';
if (!Object.keys(state.icons).length) state.icons = layoutDefaults();
apps.forEach(a => {
const icon = el('div','desk-icon');
const g = el('div','glyph', a.icon);
const label = el('div','label', a.name);
icon.append(g, label);
icon.dataset.id = a.id;
// position
const p = state.icons[a.id] || { x: 20, y: 20 };
icon.style.left = p.x + 'px';
icon.style.top = p.y + 'px';
// selection + launch
let clickTimer = null;
icon.addEventListener('click', () => {
document.querySelectorAll('.desk-icon').forEach(i => i.classList.remove('selected'));
icon.classList.add('selected');
});
icon.addEventListener('dblclick', () => {
a.open();
icon.classList.remove('selected');
});
// drag to reposition
let drag = false, dx=0, dy=0;
icon.addEventListener('mousedown', e => {
drag = true;
const rect = icon.getBoundingClientRect();
dx = e.clientX - rect.left;
dy = e.clientY - rect.top;
e.preventDefault();
});
window.addEventListener('mousemove', e => {
if (!drag) return;
const bounds = iconsLayer.getBoundingClientRect();
let nx = e.clientX - bounds.left - dx;
let ny = e.clientY - bounds.top - dy;
// simple clamping
nx = Math.max(6, Math.min(bounds.width - 116, nx));
ny = Math.max(6, Math.min(bounds.height - 106, ny));
// light snap
nx = Math.round(nx / 10) * 10;
ny = Math.round(ny / 10) * 10;
icon.style.left = nx + 'px';
icon.style.top = ny + 'px';
});
window.addEventListener('mouseup', () => {
if (drag) {
drag = false;
const x = parseInt(icon.style.left,10);
const y = parseInt(icon.style.top,10);
saveIconPos(a.id, x, y);
}
});
iconsLayer.append(icon);
});
}
buildDesktopIcons();
/* ---------- boot ---------- */
setTheme(state.theme);
if (state.wallpaper) document.documentElement.style.setProperty('--wallpaper', state.wallpaper);
// Auto-open: Files + Notepad on boot
apps.find(a=>a.id==='files').open();
apps.find(a=>a.id==='notepad').open();
</script>
</body>
</html>