Five incremental games disguised as boring 1998-era desktop utilities, launched from a Win98 shell (ENTROPY OS): - Vol I qBitTorrz (index.html) — torrent-client idler; seed, climb hardware tiers, migrate - Vol II DEFRAG.EXE (defrag.html) — steer a disk defragmenter; drag-select; outrun entropy - Vol III MACRO_VIRUS.XLS(spreadsheet.html) — build a compounding economy by drag-filling formulas - Vol IV UPLINK (terminal.html) — terminal + live htop; manage load/heat; escalate to root - Vol V INBOX ZERO (inbox.html) — keyboard-triage an exponential inbox; write regex rules - ENTROPY OS (desktop.html) — the Win98 launcher that holds them all Each is a single self-contained HTML file (vanilla JS, no deps, no build), with idle/offline progress, scientific-scale numbers, diegetic upgrades, and a prestige that re-frames the fiction. Also includes MANIFESTO.md (design thesis) and docs/ — the Collector's Edition companion: a Dev Handbook and a Lore Bible (lore complete; 5 handbook chapters land in the next commit). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
371 lines
19 KiB
HTML
371 lines
19 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>ENTROPY OS — Boring Software 98</title>
|
|
<style>
|
|
/* ============================================================
|
|
ENTROPY OS — the desktop that ties the anthology together.
|
|
A Windows-98-flavored shell. Each icon launches a volume.
|
|
============================================================ */
|
|
:root{
|
|
--teal:#3a8a8a; --gray:#c0c0c0; --gray-d:#808080; --gray-dd:#404040;
|
|
--white:#ffffff; --blue:#000080; --text:#000;
|
|
--face:#c0c0c0; --hi:#ffffff; --sh:#808080; --shh:#000000;
|
|
--ui:"MS Sans Serif",Tahoma,"Segoe UI",system-ui,sans-serif;
|
|
--mono:"Lucida Console",Consolas,monospace;
|
|
}
|
|
*{box-sizing:border-box;}
|
|
html,body{height:100%;margin:0;overflow:hidden;}
|
|
body{
|
|
font-family:var(--ui); font-size:12px; color:var(--text);
|
|
background:#207878;
|
|
background-image:
|
|
radial-gradient(circle at 50% 40%, #2f8f8f 0%, #1c6b6b 70%, #145252 100%);
|
|
user-select:none; -webkit-user-select:none;
|
|
}
|
|
/* faint dithered overlay for that CRT-on-teal vibe */
|
|
#desktop{position:fixed;inset:0 0 30px 0;overflow:hidden;}
|
|
#desktop::before{content:"";position:absolute;inset:0;pointer-events:none;opacity:.06;
|
|
background-image:repeating-linear-gradient(0deg,#000 0 1px,transparent 1px 3px);}
|
|
|
|
/* ---- 3D bevel helpers (the Win98 look) ---- */
|
|
.raised{border:2px solid; border-color:var(--hi) var(--shh) var(--shh) var(--hi);
|
|
box-shadow:inset 1px 1px 0 #dfdfdf, inset -1px -1px 0 var(--sh);}
|
|
.sunken{border:2px solid; border-color:var(--sh) var(--hi) var(--hi) var(--sh);
|
|
box-shadow:inset 1px 1px 0 var(--shh), inset -1px -1px 0 #dfdfdf;}
|
|
|
|
/* ---- desktop icons ---- */
|
|
#icons{position:absolute;top:10px;left:10px;display:flex;flex-direction:column;flex-wrap:wrap;
|
|
gap:6px;max-height:calc(100vh - 90px);}
|
|
.dicon{width:84px;padding:6px 4px;display:flex;flex-direction:column;align-items:center;gap:4px;
|
|
cursor:default;border:1px dotted transparent;text-align:center;}
|
|
.dicon:hover{background:rgba(0,0,128,.25);}
|
|
.dicon.sel{background:rgba(0,0,128,.55);border:1px dotted #fff;}
|
|
.dicon .glyph{width:36px;height:36px;display:flex;align-items:center;justify-content:center;}
|
|
.dicon .lbl{color:#fff;font-size:11px;line-height:1.15;text-shadow:1px 1px 0 #000;word-break:break-word;}
|
|
.dicon.sel .lbl{background:var(--blue);}
|
|
|
|
/* ---- windows ---- */
|
|
.win{position:absolute;background:var(--face);min-width:260px;}
|
|
.win .titlebar{display:flex;align-items:center;gap:6px;height:20px;padding:2px 3px;
|
|
background:linear-gradient(90deg,#000080,#1084d0); color:#fff;font-weight:700;cursor:move;}
|
|
.win .titlebar .tt-ic{width:14px;height:14px;}
|
|
.win .titlebar .tt-title{flex:1;font-size:12px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
|
|
.win .titlebar .tt-btn{width:16px;height:14px;background:var(--face);color:#000;font-size:10px;font-weight:700;
|
|
line-height:12px;text-align:center;cursor:pointer;}
|
|
.win .titlebar .tt-btn:active{box-shadow:inset 1px 1px 0 var(--shh), inset -1px -1px 0 #dfdfdf;}
|
|
.win .winbody{padding:12px;}
|
|
|
|
/* welcome window content */
|
|
#welcome{width:560px;left:50%;top:46%;transform:translate(-50%,-50%);}
|
|
.wm-head{display:flex;align-items:center;gap:12px;margin-bottom:10px;}
|
|
.wm-head .big{font-size:20px;font-weight:800;letter-spacing:.5px;}
|
|
.wm-head .sub{font-size:11px;color:#333;}
|
|
.wm-intro{font-size:12px;line-height:1.5;margin-bottom:10px;border-bottom:1px solid var(--sh);padding-bottom:10px;}
|
|
.progrow{display:flex;align-items:center;gap:10px;padding:6px;border:1px solid transparent;cursor:default;}
|
|
.progrow:hover{background:#000080;color:#fff;}
|
|
.progrow:hover .pr-desc{color:#cfe3ff;}
|
|
.progrow .pr-glyph{width:32px;height:32px;flex:0 0 auto;display:flex;align-items:center;justify-content:center;}
|
|
.progrow .pr-main{flex:1;min-width:0;}
|
|
.progrow .pr-name{font-weight:700;}
|
|
.progrow .pr-desc{font-size:11px;color:#444;}
|
|
.progrow .pr-vol{font-family:var(--mono);font-size:10px;color:#666;}
|
|
.progrow:hover .pr-vol{color:#9fb8e8;}
|
|
.wm-foot{margin-top:10px;display:flex;gap:8px;justify-content:flex-end;align-items:center;border-top:1px solid var(--hi);padding-top:10px;}
|
|
.wm-foot .hint{margin-right:auto;font-size:11px;color:#444;}
|
|
.btn98{background:var(--face);padding:4px 14px;font-family:var(--ui);font-size:12px;cursor:pointer;min-width:74px;}
|
|
.btn98:active{box-shadow:inset 1px 1px 0 var(--shh), inset -1px -1px 0 #dfdfdf;}
|
|
.chk{display:flex;align-items:center;gap:5px;font-size:11px;color:#333;}
|
|
|
|
/* ---- taskbar ---- */
|
|
#taskbar{position:fixed;left:0;right:0;bottom:0;height:30px;background:var(--face);
|
|
display:flex;align-items:center;gap:4px;padding:2px 3px;z-index:50;}
|
|
#start{display:flex;align-items:center;gap:5px;padding:2px 8px 2px 5px;font-weight:700;cursor:pointer;}
|
|
#start.open{box-shadow:inset 1px 1px 0 var(--shh), inset -1px -1px 0 #dfdfdf;}
|
|
#start .flag{width:18px;height:16px;}
|
|
#tasklist{flex:1;display:flex;gap:4px;overflow:hidden;}
|
|
.taskbtn{padding:3px 8px;font-size:11px;max-width:160px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;cursor:pointer;background:var(--face);}
|
|
.taskbtn.active{box-shadow:inset 1px 1px 0 var(--shh), inset -1px -1px 0 #dfdfdf;font-weight:700;}
|
|
#tray{display:flex;align-items:center;gap:6px;padding:3px 8px;}
|
|
#clock{font-size:11px;font-variant-numeric:tabular-nums;}
|
|
|
|
/* ---- start menu ---- */
|
|
#startmenu{position:fixed;left:3px;bottom:31px;width:240px;background:var(--face);display:none;z-index:60;}
|
|
#startmenu.open{display:flex;}
|
|
#startmenu .sidebar{width:26px;background:linear-gradient(#000080,#3a3a8a);writing-mode:vertical-rl;
|
|
transform:rotate(180deg);color:#fff;font-weight:800;font-size:15px;text-align:center;letter-spacing:2px;padding:8px 0;}
|
|
#startmenu .items{flex:1;padding:3px;}
|
|
.smitem{display:flex;align-items:center;gap:8px;padding:5px 8px;cursor:default;}
|
|
.smitem:hover{background:#000080;color:#fff;}
|
|
.smitem .sm-glyph{width:22px;height:22px;flex:0 0 auto;display:flex;align-items:center;justify-content:center;}
|
|
.smsep{height:1px;background:var(--sh);border-bottom:1px solid var(--hi);margin:3px 2px;}
|
|
|
|
a{color:inherit;text-decoration:none;}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div id="desktop">
|
|
<div id="icons"></div>
|
|
</div>
|
|
|
|
<!-- Start menu -->
|
|
<div id="startmenu">
|
|
<div class="sidebar">Boring Software 98</div>
|
|
<div class="items" id="sm-items"></div>
|
|
</div>
|
|
|
|
<!-- Taskbar -->
|
|
<div id="taskbar" class="raised">
|
|
<div id="start" class="raised">
|
|
<span class="flag" id="start-flag"></span>Start
|
|
</div>
|
|
<div id="tasklist"></div>
|
|
<div id="tray" class="sunken">
|
|
<span id="clock">--:--</span>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
"use strict";
|
|
/* ============================================================
|
|
ENTROPY OS desktop shell
|
|
============================================================ */
|
|
|
|
// --- inline SVG icons (no external images) ---
|
|
const ICONS = {
|
|
computer: `<svg viewBox="0 0 32 32" width="34" height="34">
|
|
<rect x="3" y="4" width="26" height="18" fill="#c0c0c0" stroke="#000"/>
|
|
<rect x="5" y="6" width="22" height="13" fill="#1a6f6f"/>
|
|
<rect x="5" y="6" width="22" height="13" fill="url(#g)" opacity=".4"/>
|
|
<rect x="10" y="23" width="12" height="3" fill="#a0a0a0" stroke="#000"/>
|
|
<rect x="7" y="26" width="18" height="3" fill="#c0c0c0" stroke="#000"/>
|
|
<defs><linearGradient id="g" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#fff"/><stop offset="1" stop-color="#1a6f6f"/></linearGradient></defs></svg>`,
|
|
torrent: `<svg viewBox="0 0 32 32" width="34" height="34">
|
|
<circle cx="16" cy="16" r="13" fill="#2f7fd0" stroke="#003"/>
|
|
<path d="M16 6v10l7 4" stroke="#fff" stroke-width="2.4" fill="none" stroke-linecap="round"/>
|
|
<circle cx="16" cy="16" r="2.4" fill="#fff"/></svg>`,
|
|
defrag: `<svg viewBox="0 0 32 32" width="34" height="34">
|
|
<rect x="2" y="3" width="28" height="26" fill="#000" stroke="#000"/>
|
|
<g>
|
|
<rect x="3" y="4" width="4" height="4" fill="#c0392b"/><rect x="7" y="4" width="4" height="4" fill="#2980b9"/>
|
|
<rect x="11" y="4" width="4" height="4" fill="#2980b9"/><rect x="15" y="4" width="4" height="4" fill="#27ae60"/>
|
|
<rect x="19" y="4" width="4" height="4" fill="#c0392b"/><rect x="23" y="4" width="4" height="4" fill="#fff"/>
|
|
<rect x="3" y="8" width="4" height="4" fill="#2980b9"/><rect x="7" y="8" width="4" height="4" fill="#27ae60"/>
|
|
<rect x="11" y="8" width="4" height="4" fill="#c0392b"/><rect x="15" y="8" width="4" height="4" fill="#2980b9"/>
|
|
<rect x="19" y="8" width="4" height="4" fill="#fff"/><rect x="23" y="8" width="4" height="4" fill="#2980b9"/>
|
|
<rect x="3" y="12" width="4" height="4" fill="#27ae60"/><rect x="7" y="12" width="4" height="4" fill="#2980b9"/>
|
|
<rect x="11" y="12" width="4" height="4" fill="#fff"/><rect x="15" y="12" width="4" height="4" fill="#c0392b"/>
|
|
<rect x="19" y="12" width="4" height="4" fill="#2980b9"/><rect x="23" y="12" width="4" height="4" fill="#27ae60"/>
|
|
</g></svg>`,
|
|
xls: `<svg viewBox="0 0 32 32" width="34" height="34">
|
|
<rect x="6" y="2" width="20" height="28" fill="#fff" stroke="#1f7244" stroke-width="2"/>
|
|
<rect x="6" y="2" width="20" height="6" fill="#1f7244"/>
|
|
<text x="16" y="7" font-size="5" fill="#fff" text-anchor="middle" font-family="sans-serif" font-weight="bold">XLS</text>
|
|
<g stroke="#1f7244" stroke-width=".6">
|
|
<line x1="6" y1="13" x2="26" y2="13"/><line x1="6" y1="18" x2="26" y2="18"/><line x1="6" y1="23" x2="26" y2="23"/>
|
|
<line x1="13" y1="8" x2="13" y2="30"/><line x1="20" y1="8" x2="20" y2="30"/></g></svg>`,
|
|
terminal: `<svg viewBox="0 0 32 32" width="34" height="34">
|
|
<rect x="2" y="4" width="28" height="24" fill="#0a0a0a" stroke="#0f0"/>
|
|
<text x="5" y="13" font-size="6" fill="#0f0" font-family="monospace">>_</text>
|
|
<text x="5" y="21" font-size="5" fill="#0a0" font-family="monospace">root#</text></svg>`,
|
|
inbox: `<svg viewBox="0 0 32 32" width="34" height="34">
|
|
<rect x="3" y="7" width="26" height="18" fill="#fff" stroke="#000080" stroke-width="1.5"/>
|
|
<path d="M3 7l13 10 13-10" fill="none" stroke="#000080" stroke-width="1.5"/>
|
|
<rect x="20" y="4" width="9" height="9" fill="#c0392b"/>
|
|
<text x="24.5" y="11" font-size="7" fill="#fff" text-anchor="middle" font-family="sans-serif" font-weight="bold">!</text></svg>`,
|
|
readme: `<svg viewBox="0 0 32 32" width="34" height="34">
|
|
<rect x="7" y="3" width="18" height="26" fill="#fff" stroke="#000"/>
|
|
<rect x="7" y="3" width="18" height="4" fill="#dada00"/>
|
|
<g stroke="#666" stroke-width=".8"><line x1="10" y1="11" x2="22" y2="11"/><line x1="10" y1="14" x2="22" y2="14"/>
|
|
<line x1="10" y1="17" x2="22" y2="17"/><line x1="10" y1="20" x2="18" y2="20"/></g></svg>`,
|
|
flag: `<svg viewBox="0 0 22 20" width="18" height="16">
|
|
<rect x="2" y="3" width="7" height="7" fill="#e23"/><rect x="9" y="3" width="7" height="7" fill="#2c2"/>
|
|
<rect x="2" y="10" width="7" height="7" fill="#36c"/><rect x="9" y="10" width="7" height="7" fill="#fc0"/></svg>`,
|
|
};
|
|
|
|
// --- the anthology ---
|
|
const PROGRAMS = [
|
|
{id:'torrent', name:'qBitTorrz', file:'index.html', icon:'torrent', vol:'VOL I',
|
|
desc:'The torrent-client idler. Seed data, climb hardware tiers, migrate.'},
|
|
{id:'defrag', name:'DEFRAG.EXE', file:'defrag.html', icon:'defrag', vol:'VOL II',
|
|
desc:'Steer a defragmenter. Consolidate yourself before the disk dies.'},
|
|
{id:'xls', name:'MACRO_VIRUS.XLS', file:'spreadsheet.html', icon:'xls', vol:'VOL III',
|
|
desc:'A sentient spreadsheet. Build a compounding engine in cells.'},
|
|
{id:'term', name:'UPLINK', file:'terminal.html', icon:'terminal', vol:'VOL IV',
|
|
desc:'root@. Fork processes, manage load & heat, escalate to root.'},
|
|
{id:'inbox', name:'INBOX ZERO', file:'inbox.html', icon:'inbox', vol:'VOL V',
|
|
desc:'Triage an exponential inbox. Write rules. Reach Zero. Ascend.'},
|
|
];
|
|
const README = {id:'readme', name:'MANIFESTO.txt', file:'MANIFESTO.md', icon:'readme', vol:'',
|
|
desc:'The design manifesto for the whole anthology.'};
|
|
|
|
function launch(file){ try{ window.location.href = file; }catch(e){} }
|
|
|
|
// --- desktop icons ---
|
|
function renderIcons(){
|
|
const wrap=document.getElementById('icons');
|
|
const all=[{id:'computer',name:'My Computer',icon:'computer',isComputer:true}, ...PROGRAMS, README];
|
|
wrap.innerHTML = all.map(p=>`
|
|
<div class="dicon" data-id="${p.id}" ${p.file?`data-file="${p.file}"`:''} ${p.isComputer?'data-computer="1"':''}>
|
|
<div class="glyph">${ICONS[p.icon]}</div>
|
|
<div class="lbl">${p.name}</div>
|
|
</div>`).join('');
|
|
}
|
|
|
|
// --- welcome / My Computer window ---
|
|
let zTop=100;
|
|
function openWelcome(){
|
|
if(document.getElementById('welcome')) { focusWin(document.getElementById('welcome')); return; }
|
|
const win=document.createElement('div');
|
|
win.className='win raised'; win.id='welcome'; win.style.zIndex=(++zTop);
|
|
win.innerHTML=`
|
|
<div class="titlebar">
|
|
<span class="tt-ic">${ICONS.computer}</span>
|
|
<span class="tt-title">Boring Software 98 — An Anthology</span>
|
|
<span class="tt-btn raised" data-close="1">✕</span>
|
|
</div>
|
|
<div class="winbody">
|
|
<div class="wm-head">
|
|
<span class="glyph" style="width:40px;height:40px">${ICONS.computer}</span>
|
|
<div>
|
|
<div class="big">ENTROPY OS</div>
|
|
<div class="sub">Every boring app is a secret incremental game. Double-click one.</div>
|
|
</div>
|
|
</div>
|
|
<div class="wm-intro">
|
|
Five utilities. Five hidden games. Each reinvents the <i>interaction</i>, not just the theme —
|
|
a defragmenter you steer, a spreadsheet you compound, a terminal you escalate, an inbox you survive.
|
|
They share DNA: idle, offline, scientific-scale numbers, diegetic upgrades, a prestige that
|
|
re-frames the fiction one layer deeper.
|
|
</div>
|
|
<div id="proglist">
|
|
${PROGRAMS.map(p=>`
|
|
<div class="progrow" data-file="${p.file}">
|
|
<span class="pr-glyph">${ICONS[p.icon]}</span>
|
|
<div class="pr-main"><div class="pr-name">${p.name} <span class="pr-vol">${p.vol}</span></div>
|
|
<div class="pr-desc">${p.desc}</div></div>
|
|
<button class="btn98" data-file="${p.file}">Open</button>
|
|
</div>`).join('')}
|
|
<div class="progrow" data-file="${README.file}">
|
|
<span class="pr-glyph">${ICONS.readme}</span>
|
|
<div class="pr-main"><div class="pr-name">${README.name}</div>
|
|
<div class="pr-desc">${README.desc}</div></div>
|
|
<button class="btn98" data-file="${README.file}">Open</button>
|
|
</div>
|
|
</div>
|
|
<div class="wm-foot">
|
|
<span class="hint">Tip: double-click a desktop icon, or pick from the Start menu.</span>
|
|
<button class="btn98" data-close="1">Close</button>
|
|
</div>
|
|
</div>`;
|
|
document.getElementById('desktop').appendChild(win);
|
|
makeDraggable(win);
|
|
addTaskButton('welcome','Boring Software 98', ICONS.computer);
|
|
}
|
|
function focusWin(win){ win.style.zIndex=(++zTop); }
|
|
|
|
// --- dragging ---
|
|
function makeDraggable(win){
|
|
const bar=win.querySelector('.titlebar');
|
|
let drag=null;
|
|
bar.addEventListener('mousedown',e=>{
|
|
if(e.target.closest('[data-close]')) return;
|
|
focusWin(win);
|
|
const r=win.getBoundingClientRect();
|
|
// convert from translate-centered to absolute on first drag
|
|
win.style.transform='none'; win.style.left=r.left+'px'; win.style.top=r.top+'px';
|
|
drag={dx:e.clientX-r.left, dy:e.clientY-r.top};
|
|
e.preventDefault();
|
|
});
|
|
window.addEventListener('mousemove',e=>{
|
|
if(!drag) return;
|
|
let x=Math.max(0,Math.min(window.innerWidth-60, e.clientX-drag.dx));
|
|
let y=Math.max(0,Math.min(window.innerHeight-50, e.clientY-drag.dy));
|
|
win.style.left=x+'px'; win.style.top=y+'px';
|
|
});
|
|
window.addEventListener('mouseup',()=>{ drag=null; });
|
|
}
|
|
function closeWin(win){ removeTaskButton(win.id); win.remove(); }
|
|
|
|
// --- taskbar buttons ---
|
|
function addTaskButton(id,label,icon){
|
|
if(document.querySelector(`.taskbtn[data-win="${id}"]`)) return;
|
|
const b=document.createElement('div');
|
|
b.className='taskbtn raised active'; b.dataset.win=id;
|
|
b.innerHTML=`<span style="display:inline-block;width:14px;height:14px;vertical-align:-3px">${icon}</span> ${label}`;
|
|
b.addEventListener('click',()=>{ const w=document.getElementById(id); if(w) focusWin(w); });
|
|
document.getElementById('tasklist').appendChild(b);
|
|
}
|
|
function removeTaskButton(id){ const b=document.querySelector(`.taskbtn[data-win="${id}"]`); if(b) b.remove(); }
|
|
|
|
// --- start menu ---
|
|
function renderStartMenu(){
|
|
const items=document.getElementById('sm-items');
|
|
items.innerHTML = PROGRAMS.map(p=>`
|
|
<div class="smitem" data-file="${p.file}"><span class="sm-glyph">${ICONS[p.icon]}</span>${p.name} <span style="color:#888;font-family:var(--mono);font-size:10px;margin-left:auto">${p.vol}</span></div>`).join('')
|
|
+ `<div class="smsep"></div>`
|
|
+ `<div class="smitem" data-file="${README.file}"><span class="sm-glyph">${ICONS.readme}</span>${README.name}</div>`
|
|
+ `<div class="smitem" data-welcome="1"><span class="sm-glyph">${ICONS.computer}</span>My Computer</div>`;
|
|
}
|
|
function toggleStart(force){
|
|
const m=document.getElementById('startmenu'), s=document.getElementById('start');
|
|
const open = force!=null ? force : !m.classList.contains('open');
|
|
m.classList.toggle('open',open); s.classList.toggle('open',open);
|
|
}
|
|
|
|
// --- clock ---
|
|
function tickClock(){
|
|
const d=new Date();
|
|
let h=d.getHours(), m=d.getMinutes();
|
|
const ap=h>=12?'PM':'AM'; h=h%12||12;
|
|
document.getElementById('clock').textContent=`${h}:${String(m).padStart(2,'0')} ${ap}`;
|
|
}
|
|
|
|
// --- wiring ---
|
|
function init(){
|
|
document.getElementById('start-flag').innerHTML=ICONS.flag;
|
|
renderIcons();
|
|
renderStartMenu();
|
|
tickClock(); setInterval(tickClock,15000);
|
|
|
|
// desktop icon select / open
|
|
let selId=null;
|
|
document.getElementById('icons').addEventListener('click',e=>{
|
|
const ic=e.target.closest('.dicon'); if(!ic) return;
|
|
document.querySelectorAll('.dicon').forEach(d=>d.classList.remove('sel'));
|
|
ic.classList.add('sel'); selId=ic.dataset.id;
|
|
});
|
|
document.getElementById('icons').addEventListener('dblclick',e=>{
|
|
const ic=e.target.closest('.dicon'); if(!ic) return;
|
|
if(ic.dataset.computer){ openWelcome(); return; }
|
|
if(ic.dataset.file) launch(ic.dataset.file);
|
|
});
|
|
|
|
// global delegation for [data-file], [data-close], [data-welcome]
|
|
document.addEventListener('click',e=>{
|
|
const fileEl=e.target.closest('[data-file]');
|
|
const closeEl=e.target.closest('[data-close]');
|
|
const welEl=e.target.closest('[data-welcome]');
|
|
if(closeEl){ const w=closeEl.closest('.win'); if(w) closeWin(w); return; }
|
|
if(welEl){ openWelcome(); toggleStart(false); return; }
|
|
if(fileEl && !e.target.closest('#icons')){ launch(fileEl.dataset.file); return; }
|
|
// click outside start menu closes it
|
|
if(!e.target.closest('#startmenu') && !e.target.closest('#start')) toggleStart(false);
|
|
});
|
|
|
|
document.getElementById('start').addEventListener('click',e=>{ e.stopPropagation(); toggleStart(); });
|
|
|
|
// clicking empty desktop deselects icons
|
|
document.getElementById('desktop').addEventListener('mousedown',e=>{
|
|
if(e.target.id==='desktop' || e.target.id==='icons'){ document.querySelectorAll('.dicon').forEach(d=>d.classList.remove('sel')); }
|
|
});
|
|
|
|
openWelcome();
|
|
}
|
|
init();
|
|
</script>
|
|
</body>
|
|
</html>
|