Last active
April 20, 2026 08:22
-
-
Save tarruda/4d30dad0c0d34c6bb3d37a7eede0f2b2 to your computer and use it in GitHub Desktop.
Browser OS implemented by Qwen 3.6 35B Q8_0 GGUF
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>BrowserOS</title> | |
| <style> | |
| /* ===== RESET & BASE ===== */ | |
| * { margin:0; padding:0; box-sizing:border-box; user-select:none; } | |
| body { overflow:hidden; font-family:'Segoe UI',Tahoma,Geneva,Verdana,sans-serif; height:100vh; width:100vw; } | |
| /* ===== WALLPAPER BACKGROUNDS ===== */ | |
| .wallpaper-1 { background:linear-gradient(135deg,#0f0c29,#302b63,#24243e); } | |
| .wallpaper-2 { background:linear-gradient(135deg,#1a2a6c,#b21f1f,#fdbb2d); } | |
| .wallpaper-3 { background:linear-gradient(135deg,#00b09b,#96c93d); } | |
| .wallpaper-4 { background:linear-gradient(135deg,#fc5c7d,#6a82fb); } | |
| .wallpaper-5 { background:linear-gradient(135deg,#0f2027,#203a43,#2c5364); } | |
| .wallpaper-6 { background:linear-gradient(135deg,#200122,#6f0000); } | |
| .wallpaper-7 { background:linear-gradient(135deg,#000428,#004e92); } | |
| .wallpaper-8 { background:linear-gradient(135deg,#434343,#000000); } | |
| /* ===== DESKTOP ===== */ | |
| #desktop { | |
| position:relative; width:100%; height:calc(100vh - 48px); | |
| transition: all 0.5s; | |
| } | |
| /* ===== DESKTOP ICONS ===== */ | |
| .desktop-icon { | |
| position:absolute; width:80px; text-align:center; cursor:pointer; | |
| padding:8px 4px; border-radius:8px; transition:background 0.2s; | |
| } | |
| .desktop-icon:hover { background:rgba(255,255,255,0.15); } | |
| .desktop-icon:active { background:rgba(255,255,255,0.25); } | |
| .desktop-icon .icon-img { | |
| width:48px; height:48px; margin:0 auto 4px; border-radius:10px; | |
| display:flex; align-items:center; justify-content:center; font-size:24px; | |
| background:rgba(255,255,255,0.1); backdrop-filter:blur(4px); | |
| border:1px solid rgba(255,255,255,0.15); | |
| } | |
| .desktop-icon .icon-label { | |
| color:#fff; font-size:11px; text-shadow:1px 1px 3px rgba(0,0,0,0.8); | |
| word-wrap:break-word; line-height:1.3; | |
| } | |
| /* ===== TASKBAR ===== */ | |
| #taskbar { | |
| position:fixed; bottom:0; left:0; right:0; height:48px; | |
| background:rgba(20,20,30,0.92); backdrop-filter:blur(20px); | |
| display:flex; align-items:center; padding:0 8px; z-index:10000; | |
| border-top:1px solid rgba(255,255,255,0.1); | |
| } | |
| #start-btn { | |
| width:40px; height:40px; border:none; background:rgba(255,255,255,0.1); | |
| border-radius:8px; cursor:pointer; font-size:20px; color:#fff; | |
| display:flex; align-items:center; justify-content:center; transition:all 0.2s; | |
| } | |
| #start-btn:hover { background:rgba(255,255,255,0.2); } | |
| #taskbar-apps { display:flex; gap:4px; margin-left:8px; flex:1; } | |
| .taskbar-app-btn { | |
| height:36px; min-width:50px; padding:0 12px; border:none; | |
| background:rgba(255,255,255,0.08); border-radius:6px; cursor:pointer; | |
| color:#fff; font-size:11px; display:flex; align-items:center; gap:6px; | |
| transition:all 0.2s; max-width:160px; | |
| } | |
| .taskbar-app-btn:hover { background:rgba(255,255,255,0.15); } | |
| .taskbar-app-btn.active { background:rgba(255,255,255,0.2); border-bottom:2px solid #4fc3f7; } | |
| #taskbar-clock { | |
| color:#fff; font-size:12px; padding:0 12px; text-align:right; line-height:1.4; | |
| } | |
| /* ===== START MENU ===== */ | |
| #start-menu { | |
| position:fixed; bottom:52px; left:8px; width:320px; | |
| background:rgba(25,25,40,0.95); backdrop-filter:blur(20px); | |
| border-radius:12px; border:1px solid rgba(255,255,255,0.1); | |
| padding:16px; display:none; z-index:10001; | |
| box-shadow:0 8px 32px rgba(0,0,0,0.5); | |
| } | |
| #start-menu.show { display:block; } | |
| #start-menu h3 { color:#fff; font-size:14px; margin-bottom:12px; padding-bottom:8px; border-bottom:1px solid rgba(255,255,255,0.1); } | |
| .start-menu-item { | |
| display:flex; align-items:center; gap:12px; padding:10px 12px; | |
| border-radius:8px; cursor:pointer; color:#fff; font-size:13px; | |
| transition:background 0.15s; | |
| } | |
| .start-menu-item:hover { background:rgba(255,255,255,0.1); } | |
| .start-menu-item .sm-icon { | |
| width:32px; height:32px; border-radius:6px; display:flex; | |
| align-items:center; justify-content:center; font-size:16px; | |
| background:rgba(255,255,255,0.1); | |
| } | |
| /* ===== WINDOW SYSTEM ===== */ | |
| .window { | |
| position:absolute; min-width:320px; min-height:200px; | |
| background:rgba(25,25,40,0.95); backdrop-filter:blur(20px); | |
| border-radius:12px; border:1px solid rgba(255,255,255,0.1); | |
| box-shadow:0 8px 32px rgba(0,0,0,0.4); display:flex; flex-direction:column; | |
| overflow:hidden; transition: box-shadow 0.3s; | |
| } | |
| .window.focused { box-shadow:0 12px 48px rgba(0,0,0,0.6); border-color:rgba(79,195,247,0.3); } | |
| .window.minimized { display:none !important; } | |
| .window-header { | |
| height:36px; display:flex; align-items:center; padding:0 12px; | |
| background:rgba(255,255,255,0.05); cursor:move; flex-shrink:0; | |
| border-bottom:1px solid rgba(255,255,255,0.05); | |
| } | |
| .window-header .win-title { flex:1; color:#fff; font-size:12px; font-weight:500; } | |
| .window-header .win-controls { display:flex; gap:6px; } | |
| .window-header .win-controls button { | |
| width:14px; height:14px; border:none; border-radius:50%; cursor:pointer; | |
| font-size:0; transition:opacity 0.2s; | |
| } | |
| .win-controls .btn-minimize { background:#fbbf24; } | |
| .win-controls .btn-maximize { background:#22c55e; } | |
| .win-controls .btn-close { background:#ef4444; } | |
| .win-controls .win-controls button:hover { opacity:0.7; } | |
| .window-body { flex:1; overflow:auto; position:relative; } | |
| /* ===== NEON MODE ===== */ | |
| body.neon-mode .window { | |
| border-color:rgba(255,0,255,0.4); | |
| box-shadow:0 0 20px rgba(255,0,255,0.15), 0 8px 32px rgba(0,0,0,0.4); | |
| } | |
| body.neon-mode .window.focused { | |
| border-color:rgba(0,255,255,0.5); | |
| box-shadow:0 0 30px rgba(0,255,255,0.2), 0 8px 32px rgba(0,0,0,0.4); | |
| } | |
| body.neon-mode .window-header { | |
| background:rgba(255,0,255,0.1); | |
| border-bottom-color:rgba(0,255,255,0.2); | |
| } | |
| body.neon-mode .desktop-icon .icon-img { | |
| border-color:rgba(255,0,255,0.3); | |
| box-shadow:0 0 10px rgba(255,0,255,0.2); | |
| } | |
| body.neon-mode #taskbar { | |
| background:rgba(10,10,20,0.95); | |
| border-top-color:rgba(0,255,255,0.3); | |
| } | |
| body.neon-mode #start-menu { | |
| border-color:rgba(255,0,255,0.3); | |
| box-shadow:0 0 20px rgba(255,0,255,0.2), 0 8px 32px rgba(0,0,0,0.5); | |
| } | |
| body.neon-mode .desktop-icon:hover { background:rgba(255,0,255,0.15); } | |
| body.neon-mode #start-btn { border:1px solid rgba(0,255,255,0.3); } | |
| body.neon-mode .desktop-icon .icon-label { text-shadow:0 0 8px rgba(0,255,255,0.5); } | |
| body.neon-mode .taskbar-app-btn { border:1px solid rgba(255,0,255,0.2); } | |
| /* ===== TERMINAL ===== */ | |
| .terminal-body { | |
| background:#0a0a0a; color:#33ff33; font-family:'Courier New',monospace; | |
| font-size:13px; padding:12px; height:100%; overflow-y:auto; | |
| } | |
| .terminal-body::-webkit-scrollbar { width:6px; } | |
| .terminal-body::-webkit-scrollbar-track { background:#0a0a0a; } | |
| .terminal-body::-webkit-scrollbar-thumb { background:#333; border-radius:3px; } | |
| .terminal-line { margin-bottom:2px; white-space:pre-wrap; word-break:break-all; } | |
| .terminal-input-line { display:flex; align-items:center; } | |
| .terminal-prompt { color:#33ff33; margin-right:4px; white-space:nowrap; } | |
| .terminal-input { | |
| background:transparent; border:none; color:#33ff33; font-family:inherit; | |
| font-size:inherit; outline:none; flex:1; caret-color:#33ff33; | |
| } | |
| body.neon-mode .terminal-body { color:#00ffff; } | |
| body.neon-mode .terminal-prompt { color:#ff00ff; } | |
| body.neon-mode .terminal-input { color:#00ffff; } | |
| /* ===== CALCULATOR ===== */ | |
| .calc-body { padding:16px; display:flex; flex-direction:column; gap:12px; height:100%; } | |
| .calc-display { | |
| background:rgba(0,0,0,0.3); border-radius:8px; padding:12px 16px; | |
| text-align:right; color:#fff; font-size:28px; font-weight:300; | |
| min-height:56px; display:flex; align-items:center; justify-content:flex-end; | |
| border:1px solid rgba(255,255,255,0.1); | |
| } | |
| .calc-buttons { display:grid; grid-template-columns:repeat(4,1fr); gap:6px; flex:1; } | |
| .calc-btn { | |
| border:none; border-radius:8px; font-size:18px; cursor:pointer; | |
| transition:all 0.15s; color:#fff; | |
| } | |
| .calc-btn.num { background:rgba(255,255,255,0.1); } | |
| .calc-btn.num:hover { background:rgba(255,255,255,0.2); } | |
| .calc-btn.op { background:rgba(79,195,247,0.2); color:#4fc3f7; } | |
| .calc-btn.op:hover { background:rgba(79,195,247,0.3); } | |
| .calc-btn.eq { background:#4fc3f7; color:#000; font-weight:700; } | |
| .calc-btn.eq:hover { background:#81d4fa; } | |
| .calc-btn.clear { background:rgba(239,68,68,0.2); color:#ef4444; } | |
| .calc-btn.clear:hover { background:rgba(239,68,68,0.3); } | |
| /* ===== WALLPAPER APP ===== */ | |
| .wallpaper-app-body { padding:20px; display:flex; flex-direction:column; gap:16px; height:100%; } | |
| .wallpaper-app-body h2 { color:#fff; font-size:18px; font-weight:400; } | |
| .wallpaper-grid { display:grid; grid-template-columns:repeat(2,1fr); gap:10px; } | |
| .wallpaper-option { | |
| height:80px; border-radius:10px; cursor:pointer; border:3px solid transparent; | |
| transition:all 0.2s; position:relative; | |
| } | |
| .wallpaper-option:hover { transform:scale(1.03); } | |
| .wallpaper-option.selected { border-color:#4fc3f7; box-shadow:0 0 12px rgba(79,195,247,0.4); } | |
| .wallpaper-option .wp-label { | |
| position:absolute; bottom:4px; right:8px; color:#fff; font-size:11px; | |
| text-shadow:1px 1px 3px rgba(0,0,0,0.8); | |
| } | |
| .wallpaper-custom { display:flex; gap:10px; align-items:center; } | |
| .wallpaper-custom input[type="color"] { | |
| width:50px; height:40px; border:none; border-radius:8px; cursor:pointer; | |
| background:transparent; | |
| } | |
| .wallpaper-custom span { color:#fff; font-size:13px; } | |
| .wallpaper-custom button { | |
| padding:8px 16px; border:none; border-radius:8px; background:#4fc3f7; | |
| color:#000; font-weight:600; cursor:pointer; transition:all 0.2s; | |
| } | |
| .wallpaper-custom button:hover { background:#81d4fa; } | |
| /* ===== GAME CANVASES ===== */ | |
| .game-container { display:flex; flex-direction:column; align-items:center; padding:10px; height:100%; justify-content:center; } | |
| .game-container canvas { border-radius:4px; border:2px solid rgba(255,255,255,0.15); } | |
| .game-info { color:#fff; font-size:13px; margin-top:8px; text-align:center; } | |
| .game-score { color:#4fc3f7; font-size:16px; font-weight:600; } | |
| .game-start-hint { color:#aaa; font-size:12px; margin-top:6px; } | |
| /* ===== TIC TAC TOE ===== */ | |
| .ttt-body { display:flex; flex-direction:column; align-items:center; padding:20px; height:100%; justify-content:center; gap:16px; } | |
| .ttt-status { color:#fff; font-size:18px; font-weight:500; min-height:28px; } | |
| .ttt-grid { | |
| display:grid; grid-template-columns:repeat(3,1fr); gap:6px; | |
| } | |
| .ttt-cell { | |
| width:80px; height:80px; border:2px solid rgba(255,255,255,0.15); | |
| background:rgba(255,255,255,0.05); border-radius:10px; | |
| display:flex; align-items:center; justify-content:center; | |
| font-size:36px; font-weight:700; cursor:pointer; | |
| transition:all 0.2s; color:#fff; | |
| } | |
| .ttt-cell:hover { background:rgba(255,255,255,0.12); } | |
| .ttt-cell.x { color:#4fc3f7; } | |
| .ttt-cell.o { color:#ff6b9d; } | |
| .ttt-cell.win-cell { background:rgba(79,195,247,0.2); border-color:#4fc3f7; box-shadow:0 0 12px rgba(79,195,247,0.3); } | |
| .ttt-btn { | |
| padding:10px 24px; border:none; border-radius:8px; background:#4fc3f7; | |
| color:#000; font-weight:600; cursor:pointer; font-size:14px; transition:all 0.2s; | |
| } | |
| .ttt-btn:hover { background:#81d4fa; } | |
| .ttt-score-board { display:flex; gap:24px; color:#fff; font-size:14px; } | |
| .ttt-score-board span { padding:4px 12px; background:rgba(255,255,255,0.08); border-radius:6px; } | |
| .ttt-score-board .x-score { color:#4fc3f7; } | |
| .ttt-score-board .o-score { color:#ff6b9d; } | |
| .ttt-score-board .draw-score { color:#aaa; } | |
| /* ===== MATCHING GAME ===== */ | |
| .match-body { display:flex; flex-direction:column; align-items:center; padding:16px; height:100%; gap:12px; justify-content:center; } | |
| .match-score-board { color:#fff; font-size:14px; display:flex; gap:20px; } | |
| .match-score-board span { padding:4px 12px; background:rgba(255,255,255,0.08); border-radius:6px; } | |
| .match-grid { | |
| display:grid; grid-template-columns:repeat(4,1fr); gap:8px; | |
| } | |
| .match-card { | |
| width:70px; height:70px; border-radius:10px; cursor:pointer; | |
| perspective:600px; transition:transform 0.1s; | |
| } | |
| .match-card:hover { transform:scale(1.05); } | |
| .match-card-inner { | |
| width:100%; height:100%; position:relative; | |
| transform-style:preserve-3d; transition:transform 0.4s; | |
| } | |
| .match-card.flipped .match-card-inner { transform:rotateY(180deg); } | |
| .match-card-front, .match-card-back { | |
| position:absolute; width:100%; height:100%; border-radius:10px; | |
| backface-visibility:hidden; display:flex; align-items:center; justify-content:center; | |
| font-size:28px; | |
| } | |
| .match-card-front { | |
| background:rgba(79,195,247,0.15); border:2px solid rgba(79,195,247,0.3); | |
| color:#4fc3f7; font-size:20px; | |
| } | |
| .match-card-back { | |
| background:rgba(255,255,255,0.1); border:2px solid rgba(255,255,255,0.15); | |
| transform:rotateY(180deg); | |
| } | |
| .match-card.matched .match-card-inner { transform:rotateY(180deg); } | |
| .match-card.matched .match-card-back { | |
| background:rgba(34,197,94,0.15); border-color:rgba(34,197,94,0.4); | |
| box-shadow:0 0 10px rgba(34,197,94,0.3); | |
| } | |
| .match-btn { | |
| padding:8px 20px; border:none; border-radius:8px; background:#4fc3f7; | |
| color:#000; font-weight:600; cursor:pointer; font-size:13px; transition:all 0.2s; | |
| } | |
| .match-btn:hover { background:#81d4fa; } | |
| .match-win-overlay { | |
| position:absolute; top:0; left:0; right:0; bottom:0; | |
| background:rgba(0,0,0,0.7); display:flex; flex-direction:column; | |
| align-items:center; justify-content:center; z-index:10; border-radius:12px; | |
| } | |
| .match-win-overlay h2 { color:#fff; font-size:22px; margin-bottom:8px; } | |
| .match-win-overlay p { color:#aaa; font-size:14px; margin-bottom:16px; } | |
| /* ===== NEON MODE TOGGLE ===== */ | |
| #neon-toggle { | |
| position:fixed; top:12px; right:12px; z-index:10002; | |
| padding:8px 16px; border:none; border-radius:8px; | |
| background:rgba(255,0,255,0.2); color:#ff00ff; cursor:pointer; | |
| font-size:12px; font-weight:600; border:1px solid rgba(255,0,255,0.3); | |
| transition:all 0.3s; backdrop-filter:blur(4px); | |
| } | |
| #neon-toggle:hover { background:rgba(255,0,255,0.3); } | |
| body.neon-mode #neon-toggle { | |
| background:rgba(0,255,255,0.2); color:#00ffff; | |
| border-color:rgba(0,255,255,0.3); | |
| box-shadow:0 0 15px rgba(0,255,255,0.3); | |
| } | |
| /* ===== MATRIX RAIN OVERLAY ===== */ | |
| #matrix-overlay { | |
| position:fixed; top:0; left:0; width:100%; height:100%; | |
| z-index:9999; display:none; pointer-events:none; | |
| } | |
| #matrix-overlay.active { display:block; pointer-events:all; } | |
| #matrix-overlay canvas { width:100%; height:100%; } | |
| /* ===== NOTES APP ===== */ | |
| .notes-body { display:flex; flex-direction:column; height:100%; } | |
| .notes-toolbar { | |
| display:flex; gap:6px; padding:8px; background:rgba(255,255,255,0.05); | |
| border-bottom:1px solid rgba(255,255,255,0.05); | |
| } | |
| .notes-toolbar button { | |
| padding:4px 10px; border:none; border-radius:4px; background:rgba(255,255,255,0.1); | |
| color:#fff; cursor:pointer; font-size:11px; transition:all 0.2s; | |
| } | |
| .notes-toolbar button:hover { background:rgba(255,255,255,0.2); } | |
| .notes-textarea { | |
| flex:1; background:transparent; border:none; color:#fff; font-family:inherit; | |
| font-size:14px; padding:12px; resize:none; outline:none; line-height:1.6; | |
| } | |
| /* ===== ABOUT WINDOW ===== */ | |
| .about-body { padding:24px; text-align:center; color:#fff; } | |
| .about-body h2 { font-weight:300; margin-bottom:8px; } | |
| .about-body p { color:#aaa; font-size:13px; line-height:1.6; } | |
| .about-body .version { color:#4fc3f7; font-size:24px; font-weight:300; margin:12px 0; } | |
| /* ===== RESIZE HANDLE ===== */ | |
| .resize-handle { | |
| position:absolute; bottom:0; right:0; width:16px; height:16px; | |
| cursor:nwse-resize; | |
| } | |
| .resize-handle::after { | |
| content:''; position:absolute; bottom:3px; right:3px; | |
| width:8px; height:8px; border-right:2px solid rgba(255,255,255,0.3); | |
| border-bottom:2px solid rgba(255,255,255,0.3); | |
| } | |
| </style> | |
| </head> | |
| <body class="wallpaper-1"> | |
| <!-- MATRIX OVERLAY --> | |
| <div id="matrix-overlay"><canvas id="matrix-canvas"></canvas></div> | |
| <!-- NEON MODE TOGGLE --> | |
| <button id="neon-toggle" onclick="toggleNeonMode()">⚡ NEON MODE</button> | |
| <!-- DESKTOP --> | |
| <div id="desktop"> | |
| <!-- Desktop icons will be created by JS --> | |
| </div> | |
| <!-- TASKBAR --> | |
| <div id="taskbar"> | |
| <button id="start-btn" onclick="toggleStartMenu()">🖥️</button> | |
| <div id="taskbar-apps"></div> | |
| <div id="taskbar-clock"></div> | |
| </div> | |
| <!-- START MENU --> | |
| <div id="start-menu"> | |
| <h3>🖥️ BrowserOS</h3> | |
| <div id="start-menu-list"></div> | |
| </div> | |
| <script> | |
| // ===== OS CORE ===== | |
| const apps = [ | |
| { id:'tetris', name:'Tetris', icon:'🎮', type:'game' }, | |
| { id:'snake', name:'Snake', icon:'🐍', type:'game' }, | |
| { id:'flappy', name:'Flappy Bird', icon:'🐦', type:'game' }, | |
| { id:'tictactoe', name:'Tic Tac Toe', icon:'❌', type:'game' }, | |
| { id:'matching', name:'Matching Game', icon:'🃏', type:'game' }, | |
| { id:'calc', name:'Calculator', icon:'🧮', type:'utility' }, | |
| { id:'terminal', name:'Terminal', icon:'⬛', type:'utility' }, | |
| { id:'wallpaper', name:'Wallpaper', icon:'🎨', type:'utility' }, | |
| { id:'notes', name:'Notes', icon:'📝', type:'utility' }, | |
| { id:'about', name:'About', icon:'ℹ️', type:'utility' } | |
| ]; | |
| let windowId = 0; | |
| let activeWindows = {}; | |
| let highestZ = 100; | |
| let currentWallpaper = 1; | |
| let neonMode = false; | |
| let matrixActive = false; | |
| let startMenuOpen = false; | |
| // Desktop icon positions | |
| const desktopPositions = [ | |
| {x:20, y:20}, {x:20, y:130}, {x:20, y:240}, | |
| {x:20, y:350}, {x:130, y:20}, {x:130, y:130}, | |
| {x:130, y:240}, {x:130, y:350}, {x:240, y:20}, {x:240, y:130} | |
| ]; | |
| // ===== INIT ===== | |
| function init() { | |
| renderDesktopIcons(); | |
| renderStartMenu(); | |
| updateClock(); | |
| setInterval(updateClock, 1000); | |
| } | |
| function renderDesktopIcons() { | |
| const desktop = document.getElementById('desktop'); | |
| desktop.innerHTML = ''; | |
| apps.forEach((app, i) => { | |
| const pos = desktopPositions[i]; | |
| const icon = document.createElement('div'); | |
| icon.className = 'desktop-icon'; | |
| icon.style.left = pos.x + 'px'; | |
| icon.style.top = pos.y + 'px'; | |
| icon.innerHTML = ` | |
| <div class="icon-img">${app.icon}</div> | |
| <div class="icon-label">${app.name}</div> | |
| `; | |
| icon.ondblclick = () => openApp(app.id); | |
| desktop.appendChild(icon); | |
| }); | |
| } | |
| function renderStartMenu() { | |
| const list = document.getElementById('start-menu-list'); | |
| list.innerHTML = ''; | |
| apps.forEach(app => { | |
| const item = document.createElement('div'); | |
| item.className = 'start-menu-item'; | |
| item.innerHTML = `<div class="sm-icon">${app.icon}</div><span>${app.name}</span>`; | |
| item.onclick = () => { openApp(app.id); toggleStartMenu(); }; | |
| list.appendChild(item); | |
| }); | |
| const neonItem = document.createElement('div'); | |
| neonItem.className = 'start-menu-item'; | |
| neonItem.innerHTML = `<div class="sm-icon">⚡</div><span>Neon Mode</span>`; | |
| neonItem.onclick = () => { toggleNeonMode(); toggleStartMenu(); }; | |
| list.appendChild(neonItem); | |
| } | |
| // ===== CLOCK ===== | |
| function updateClock() { | |
| const now = new Date(); | |
| document.getElementById('taskbar-clock').innerHTML = | |
| now.toLocaleTimeString([], {hour:'2-digit',minute:'2-digit'}) + | |
| '<br>' + now.toLocaleDateString([], {month:'short',day:'numeric',year:'numeric'}); | |
| } | |
| // ===== START MENU ===== | |
| function toggleStartMenu() { | |
| startMenuOpen = !startMenuOpen; | |
| document.getElementById('start-menu').classList.toggle('show', startMenuOpen); | |
| } | |
| document.addEventListener('click', (e) => { | |
| if (startMenuOpen && !e.target.closest('#start-menu') && !e.target.closest('#start-btn')) { | |
| startMenuOpen = false; | |
| document.getElementById('start-menu').classList.remove('show'); | |
| } | |
| }); | |
| // ===== NEON MODE ===== | |
| function toggleNeonMode() { | |
| neonMode = !neonMode; | |
| document.body.classList.toggle('neon-mode', neonMode); | |
| const btn = document.getElementById('neon-toggle'); | |
| btn.textContent = neonMode ? '⚡ NEON ON' : '⚡ NEON MODE'; | |
| } | |
| // ===== WINDOW MANAGEMENT ===== | |
| function createWindow(appId, title, icon, width, height, content) { | |
| if (activeWindows[appId]) { | |
| const win = activeWindows[appId]; | |
| win.classList.remove('minimized'); | |
| focusWindow(win); | |
| return win; | |
| } | |
| windowId++; | |
| const wid = windowId; | |
| const win = document.createElement('div'); | |
| win.className = 'window focused'; | |
| win.id = `window-${wid}`; | |
| win.dataset.appId = appId; | |
| win.style.width = width + 'px'; | |
| win.style.height = height + 'px'; | |
| win.style.left = (80 + (wid % 5) * 30) + 'px'; | |
| win.style.top = (40 + (wid % 5) * 30) + 'px'; | |
| win.style.zIndex = ++highestZ; | |
| win.innerHTML = ` | |
| <div class="window-header" onmousedown="startDrag(event, '${win.id}')"> | |
| <span class="win-title">${icon} ${title}</span> | |
| <div class="win-controls"> | |
| <button class="btn-minimize" onclick="minimizeWindow('${win.id}','${appId}')"></button> | |
| <button class="btn-maximize" onclick="maximizeWindow('${win.id}')"></button> | |
| <button class="btn-close" onclick="closeWindow('${win.id}','${appId}')"></button> | |
| </div> | |
| </div> | |
| <div class="window-body">${content}</div> | |
| <div class="resize-handle" onmousedown="startResize(event, '${win.id}')"></div> | |
| `; | |
| win.onmousedown = () => focusWindow(win); | |
| document.getElementById('desktop').appendChild(win); | |
| activeWindows[appId] = win; | |
| addTaskbarButton(appId, icon, title); | |
| focusWindow(win); | |
| return win; | |
| } | |
| function focusWindow(win) { | |
| document.querySelectorAll('.window').forEach(w => w.classList.remove('focused')); | |
| win.classList.add('focused'); | |
| win.style.zIndex = ++highestZ; | |
| document.querySelectorAll('.taskbar-app-btn').forEach(btn => { | |
| btn.classList.toggle('active', btn.dataset.appId === win.dataset.appId); | |
| }); | |
| } | |
| function minimizeWindow(winId, appId) { | |
| const win = document.getElementById(winId); | |
| win.classList.add('minimized'); | |
| document.querySelectorAll('.taskbar-app-btn[data-app-id="' + appId + '"]').forEach(btn => { | |
| btn.classList.remove('active'); | |
| }); | |
| } | |
| function maximizeWindow(winId) { | |
| const win = document.getElementById(winId); | |
| if (win.dataset.maximized === 'true') { | |
| win.style.left = win.dataset.origLeft; | |
| win.style.top = win.dataset.origTop; | |
| win.style.width = win.dataset.origWidth; | |
| win.style.height = win.dataset.origHeight; | |
| win.dataset.maximized = 'false'; | |
| } else { | |
| win.dataset.origLeft = win.style.left; | |
| win.dataset.origTop = win.style.top; | |
| win.dataset.origWidth = win.style.width; | |
| win.dataset.origHeight = win.style.height; | |
| win.style.left = '0'; | |
| win.style.top = '0'; | |
| win.style.width = '100%'; | |
| win.style.height = '100%'; | |
| win.dataset.maximized = 'true'; | |
| } | |
| } | |
| function closeWindow(winId, appId) { | |
| const win = document.getElementById(winId); | |
| win.remove(); | |
| delete activeWindows[appId]; | |
| const btn = document.querySelector(`.taskbar-app-btn[data-app-id="${appId}"]`); | |
| if (btn) btn.remove(); | |
| if (gameIntervals[appId]) { | |
| clearInterval(gameIntervals[appId]); | |
| clearTimeout(gameTimeouts[appId]); | |
| delete gameIntervals[appId]; | |
| delete gameTimeouts[appId]; | |
| } | |
| } | |
| function addTaskbarButton(appId, icon, title) { | |
| const container = document.getElementById('taskbar-apps'); | |
| const btn = document.createElement('button'); | |
| btn.className = 'taskbar-app-btn active'; | |
| btn.dataset.appId = appId; | |
| btn.innerHTML = `${icon} ${title}`; | |
| btn.onclick = () => { | |
| const win = activeWindows[appId]; | |
| if (win.classList.contains('minimized')) { | |
| win.classList.remove('minimized'); | |
| focusWindow(win); | |
| } else if (win.classList.contains('focused')) { | |
| minimizeWindow(win.id, appId); | |
| } else { | |
| focusWindow(win); | |
| } | |
| }; | |
| container.appendChild(btn); | |
| } | |
| // ===== DRAG ===== | |
| let dragWin = null, dragOffX = 0, dragOffY = 0; | |
| function startDrag(e, winId) { | |
| const win = document.getElementById(winId); | |
| if (win.dataset.maximized === 'true') return; | |
| dragWin = win; | |
| dragOffX = e.clientX - win.offsetLeft; | |
| dragOffY = e.clientY - win.offsetTop; | |
| e.preventDefault(); | |
| } | |
| document.addEventListener('mousemove', (e) => { | |
| if (dragWin) { | |
| dragWin.style.left = (e.clientX - dragOffX) + 'px'; | |
| dragWin.style.top = (e.clientY - dragOffY) + 'px'; | |
| } | |
| if (resizeWin) { | |
| const newW = Math.max(320, e.clientX - resizeWin.offsetLeft); | |
| const newH = Math.max(200, e.clientY - resizeWin.offsetTop); | |
| resizeWin.style.width = newW + 'px'; | |
| resizeWin.style.height = newH + 'px'; | |
| } | |
| }); | |
| document.addEventListener('mouseup', () => { dragWin = null; resizeWin = null; }); | |
| // ===== RESIZE ===== | |
| let resizeWin = null; | |
| function startResize(e, winId) { | |
| resizeWin = document.getElementById(winId); | |
| e.preventDefault(); | |
| e.stopPropagation(); | |
| } | |
| // ===== OPEN APP ===== | |
| function openApp(appId) { | |
| switch(appId) { | |
| case 'tetris': openTetris(); break; | |
| case 'snake': openSnake(); break; | |
| case 'flappy': openFlappy(); break; | |
| case 'tictactoe': openTicTacToe(); break; | |
| case 'matching': openMatching(); break; | |
| case 'calc': openCalculator(); break; | |
| case 'terminal': openTerminal(); break; | |
| case 'wallpaper': openWallpaper(); break; | |
| case 'notes': openNotes(); break; | |
| case 'about': openAbout(); break; | |
| } | |
| } | |
| // ===== GAME STATE ===== | |
| const gameIntervals = {}; | |
| const gameTimeouts = {}; | |
| // ===== TETRIS ===== | |
| function openTetris() { | |
| if (activeWindows['tetris']) { focusWindow(activeWindows['tetris']); return; } | |
| const content = ` | |
| <div class="game-container" id="tetris-container"> | |
| <canvas id="tetris-canvas" width="240" height="480"></canvas> | |
| <div class="game-info"> | |
| <div class="game-score">Score: <span id="tetris-score">0</span></div> | |
| <div class="game-start-hint">← → Move | ↑ Rotate | ↓ Soft Drop | Space Hard Drop</div> | |
| </div> | |
| </div> | |
| `; | |
| createWindow('tetris', 'Tetris', '🎮', 320, 540, content); | |
| setTimeout(initTetris, 100); | |
| } | |
| function initTetris() { | |
| const canvas = document.getElementById('tetris-canvas'); | |
| if (!canvas) return; | |
| const ctx = canvas.getContext('2d'); | |
| const COLS = 10, ROWS = 20, BLOCK = 24; | |
| canvas.width = COLS * BLOCK; | |
| canvas.height = ROWS * BLOCK; | |
| const SHAPES = [ | |
| [[1,1,1,1]], | |
| [[1,1],[1,1]], | |
| [[0,1,0],[1,1,1]], | |
| [[1,0,0],[1,1,1]], | |
| [[0,0,1],[1,1,1]], | |
| [[1,1,0],[0,1,1]], | |
| [[0,1,1],[1,1,0]] | |
| ]; | |
| const COLORS = ['#00f0f0','#f0f000','#a000f0','#0000f0','#f0a000','#00f000','#f00000']; | |
| let board = Array.from({length:ROWS}, () => Array(COLS).fill(0)); | |
| let score = 0, gameOver = false, dropCounter = 0, dropInterval = 800, lastTime = 0; | |
| function rotate(matrix) { | |
| const rows = matrix.length, cols = rows > 0 ? matrix[0].length : 0; | |
| const result = Array.from({length:cols}, () => Array(rows).fill(0)); | |
| for (let r = 0; r < rows; r++) | |
| for (let c = 0; c < cols; c++) | |
| result[c][rows-1-r] = matrix[r][c]; | |
| return result; | |
| } | |
| function collCheck(piece, px, py) { | |
| for (let r = 0; r < piece.length; r++) | |
| for (let c = 0; c < piece[r].length; c++) | |
| if (piece[r][c]) { | |
| const nx = c + px, ny = r + py; | |
| if (nx < 0 || nx >= COLS || ny >= ROWS) return true; | |
| if (ny >= 0 && board[ny][nx]) return true; | |
| } | |
| return false; | |
| } | |
| function makePiece() { | |
| const idx = Math.floor(Math.random() * SHAPES.length); | |
| return { piece: SHAPES[idx], color: COLORS[idx], x: 3, y: 0 }; | |
| } | |
| let current = makePiece(); | |
| function lockPiece() { | |
| for (let r = 0; r < current.piece.length; r++) | |
| for (let c = 0; c < current.piece[r].length; c++) | |
| if (current.piece[r][c]) { | |
| const ny = r + current.y, nx = c + current.x; | |
| if (ny >= 0 && ny < ROWS && nx >= 0 && nx < COLS) board[ny][nx] = current.color; | |
| } | |
| } | |
| function sweepLines() { | |
| let lines = 0; | |
| for (let r = ROWS-1; r >= 0; r--) { | |
| if (board[r].every(c => c !== 0)) { | |
| board.splice(r, 1); board.unshift(Array(COLS).fill(0)); lines++; r++; | |
| } | |
| } | |
| if (lines > 0) { | |
| const pts = [0, 100, 300, 500, 800]; | |
| score += pts[lines] || 800; | |
| dropInterval = Math.max(50, 800 - Math.floor(score/300) * 40); | |
| const el = document.getElementById('tetris-score'); | |
| if (el) el.textContent = score; | |
| } | |
| } | |
| function spawnPiece() { | |
| current = makePiece(); | |
| if (collCheck(current.piece, current.x, current.y)) gameOver = true; | |
| } | |
| function handleKey(e) { | |
| if (!activeWindows['tetris']) return; | |
| const win = activeWindows['tetris']; | |
| if (win.classList.contains('minimized') || !win.classList.contains('focused')) return; | |
| if (gameOver) { | |
| gameOver = false; | |
| board = Array.from({length:ROWS}, () => Array(COLS).fill(0)); | |
| score = 0; | |
| const el = document.getElementById('tetris-score'); | |
| if (el) el.textContent = '0'; | |
| spawnPiece(); | |
| return; | |
| } | |
| if (e.key === 'ArrowLeft' && !collCheck(current.piece, current.x-1, current.y)) current.x--; | |
| if (e.key === 'ArrowRight' && !collCheck(current.piece, current.x+1, current.y)) current.x++; | |
| if (e.key === 'ArrowDown' && !collCheck(current.piece, current.x, current.y+1)) current.y++; | |
| if (e.key === 'ArrowUp') { | |
| const rotated = rotate(current.piece); | |
| if (!collCheck(rotated, current.x, current.y)) current.piece = rotated; | |
| } | |
| if (e.key === ' ') { | |
| e.preventDefault(); | |
| while(!collCheck(current.piece, current.x, current.y+1)) current.y++; | |
| lockPiece(); sweepLines(); spawnPiece(); | |
| } | |
| } | |
| document.removeEventListener('keydown', window._tetrisKeyHandler); | |
| window._tetrisKeyHandler = handleKey; | |
| document.addEventListener('keydown', handleKey); | |
| function drawTetris() { | |
| ctx.fillStyle = '#111'; ctx.fillRect(0, 0, canvas.width, canvas.height); | |
| ctx.strokeStyle = 'rgba(255,255,255,0.04)'; | |
| for (let r = 0; r < ROWS; r++) for (let c = 0; c < COLS; c++) ctx.strokeRect(c*BLOCK, r*BLOCK, BLOCK, BLOCK); | |
| for (let r = 0; r < ROWS; r++) for (let c = 0; c < COLS; c++) | |
| if (board[r][c]) { ctx.fillStyle = board[r][c]; ctx.fillRect(c*BLOCK+1, r*BLOCK+1, BLOCK-2, BLOCK-2); ctx.fillStyle = 'rgba(255,255,255,0.2)'; ctx.fillRect(c*BLOCK+1, r*BLOCK+1, BLOCK-2, 2); } | |
| if (current && !gameOver) { | |
| let gy = current.y; while(!collCheck(current.piece, current.x, gy+1)) gy++; | |
| ctx.globalAlpha = 0.15; | |
| for (let r = 0; r < current.piece.length; r++) for (let c = 0; c < current.piece[r].length; c++) | |
| if (current.piece[r][c]) { ctx.fillStyle = current.color; ctx.fillRect((c+current.x)*BLOCK+1, (r+gy)*BLOCK+1, BLOCK-2, BLOCK-2); } | |
| ctx.globalAlpha = 1; | |
| for (let r = 0; r < current.piece.length; r++) for (let c = 0; c < current.piece[r].length; c++) | |
| if (current.piece[r][c]) { ctx.fillStyle = current.color; ctx.fillRect((c+current.x)*BLOCK+1, (r+current.y)*BLOCK+1, BLOCK-2, BLOCK-2); ctx.fillStyle = 'rgba(255,255,255,0.25)'; ctx.fillRect((c+current.x)*BLOCK+1, (r+current.y)*BLOCK+1, BLOCK-2, 2); } | |
| } | |
| if (gameOver) { | |
| ctx.fillStyle = 'rgba(0,0,0,0.75)'; ctx.fillRect(0, 0, canvas.width, canvas.height); | |
| ctx.fillStyle = '#fff'; ctx.font = 'bold 22px Segoe UI'; ctx.textAlign = 'center'; | |
| ctx.fillText('GAME OVER', canvas.width/2, canvas.height/2 - 8); | |
| ctx.font = '13px Segoe UI'; ctx.fillStyle = '#aaa'; | |
| ctx.fillText(`Score: ${score} — Click to restart`, canvas.width/2, canvas.height/2 + 18); | |
| } | |
| } | |
| canvas.onclick = () => { if (gameOver) { gameOver = false; board = Array.from({length:ROWS}, () => Array(COLS).fill(0)); score = 0; const el = document.getElementById('tetris-score'); if (el) el.textContent = '0'; spawnPiece(); } }; | |
| drawTetris(); | |
| if (gameIntervals['tetris']) cancelAnimationFrame(gameIntervals['tetris']); | |
| function gameLoop(time = 0) { | |
| if (!document.getElementById('tetris-canvas')) return; | |
| const dt = time - lastTime; lastTime = time; dropCounter += dt; | |
| if (dropCounter > dropInterval && !gameOver) { | |
| dropCounter = 0; | |
| if (!collCheck(current.piece, current.x, current.y+1)) current.y++; | |
| else { lockPiece(); sweepLines(); spawnPiece(); } | |
| } | |
| drawTetris(); | |
| gameIntervals['tetris'] = requestAnimationFrame(gameLoop); | |
| } | |
| gameLoop(); | |
| } | |
| // ===== SNAKE ===== | |
| function openSnake() { | |
| if (activeWindows['snake']) { focusWindow(activeWindows['snake']); return; } | |
| const content = ` | |
| <div class="game-container" id="snake-container"> | |
| <canvas id="snake-canvas" width="360" height="360"></canvas> | |
| <div class="game-info"> | |
| <div class="game-score">Score: <span id="snake-score">0</span></div> | |
| <div class="game-start-hint">Arrow Keys to move</div> | |
| </div> | |
| </div> | |
| `; | |
| createWindow('snake', 'Snake', '🐍', 400, 460, content); | |
| setTimeout(initSnake, 100); | |
| } | |
| function initSnake() { | |
| const canvas = document.getElementById('snake-canvas'); | |
| if (!canvas) return; | |
| const ctx = canvas.getContext('2d'); | |
| const GRID = 20; | |
| const COLS = canvas.width / GRID; | |
| const ROWS = canvas.height / GRID; | |
| let snake, dir, nextDir, food, score, gameOver, gameRunning; | |
| function init() { | |
| snake = [{x:10, y:10}, {x:9, y:10}, {x:8, y:10}]; | |
| dir = {x:1, y:0}; nextDir = {x:1, y:0}; score = 0; | |
| gameOver = false; gameRunning = true; | |
| placeFood(); | |
| const el = document.getElementById('snake-score'); | |
| if (el) el.textContent = '0'; | |
| } | |
| function placeFood() { | |
| do { food = {x: Math.floor(Math.random()*COLS), y: Math.floor(Math.random()*ROWS)}; } | |
| while (snake.some(s => s.x === food.x && s.y === food.y)); | |
| } | |
| function update() { | |
| if (!gameRunning || gameOver) return; | |
| dir = {...nextDir}; | |
| const head = {x: snake[0].x + dir.x, y: snake[0].y + dir.y}; | |
| if (head.x < 0 || head.x >= COLS || head.y < 0 || head.y >= ROWS || snake.some(s => s.x === head.x && s.y === head.y)) { gameOver = true; gameRunning = false; draw(); return; } | |
| snake.unshift(head); | |
| if (head.x === food.x && head.y === food.y) { score++; const el = document.getElementById('snake-score'); if (el) el.textContent = score; placeFood(); } | |
| else snake.pop(); | |
| draw(); | |
| } | |
| function draw() { | |
| ctx.fillStyle = '#111'; ctx.fillRect(0, 0, canvas.width, canvas.height); | |
| ctx.strokeStyle = 'rgba(255,255,255,0.03)'; | |
| for (let r = 0; r < ROWS; r++) for (let c = 0; c < COLS; c++) ctx.strokeRect(c*GRID, r*GRID, GRID, GRID); | |
| ctx.fillStyle = '#ff4444'; ctx.beginPath(); ctx.arc(food.x*GRID+GRID/2, food.y*GRID+GRID/2, GRID/2-2, 0, Math.PI*2); ctx.fill(); | |
| snake.forEach((s, i) => { | |
| ctx.fillStyle = i === 0 ? '#4fc3f7' : `hsl(${180 + i*3}, 70%, ${50 - i}%)`; | |
| ctx.fillRect(s.x*GRID+1, s.y*GRID+1, GRID-2, GRID-2); | |
| if (i === 0) { ctx.fillStyle = 'rgba(255,255,255,0.3)'; ctx.fillRect(s.x*GRID+1, s.y*GRID+1, GRID-2, 3); } | |
| }); | |
| if (gameOver) { | |
| ctx.fillStyle = 'rgba(0,0,0,0.75)'; ctx.fillRect(0, 0, canvas.width, canvas.height); | |
| ctx.fillStyle = '#fff'; ctx.font = 'bold 22px Segoe UI'; ctx.textAlign = 'center'; | |
| ctx.fillText('GAME OVER', canvas.width/2, canvas.height/2 - 8); | |
| ctx.font = '13px Segoe UI'; ctx.fillStyle = '#aaa'; | |
| ctx.fillText(`Score: ${score} — Click to restart`, canvas.width/2, canvas.height/2 + 18); | |
| } | |
| } | |
| init(); draw(); | |
| if (gameIntervals['snake']) clearInterval(gameIntervals['snake']); | |
| gameIntervals['snake'] = setInterval(update, 120); | |
| function handleKey(e) { | |
| if (!activeWindows['snake']) return; | |
| const win = activeWindows['snake']; | |
| if (win.classList.contains('minimized') || !win.classList.contains('focused')) return; | |
| if (e.key === 'ArrowUp' && dir.y !== 1) nextDir = {x:0, y:-1}; | |
| if (e.key === 'ArrowDown' && dir.y !== -1) nextDir = {x:0, y:1}; | |
| if (e.key === 'ArrowLeft' && dir.x !== 1) nextDir = {x:-1, y:0}; | |
| if (e.key === 'ArrowRight' && dir.x !== -1) nextDir = {x:1, y:0}; | |
| } | |
| document.removeEventListener('keydown', window._snakeKeyHandler); | |
| window._snakeKeyHandler = handleKey; | |
| document.addEventListener('keydown', handleKey); | |
| canvas.onclick = () => { if (gameOver) { init(); draw(); } }; | |
| } | |
| // ===== FLAPPY BIRD ===== | |
| function openFlappy() { | |
| if (activeWindows['flappy']) { focusWindow(activeWindows['flappy']); return; } | |
| const content = ` | |
| <div class="game-container" id="flappy-container"> | |
| <canvas id="flappy-canvas" width="320" height="480"></canvas> | |
| <div class="game-info"> | |
| <div class="game-score">Score: <span id="flappy-score">0</span></div> | |
| <div class="game-start-hint">Click or Space to flap</div> | |
| </div> | |
| </div> | |
| `; | |
| createWindow('flappy', 'Flappy Bird', '🐦', 360, 540, content); | |
| setTimeout(initFlappy, 100); | |
| } | |
| function initFlappy() { | |
| const canvas = document.getElementById('flappy-canvas'); | |
| if (!canvas) return; | |
| const ctx = canvas.getContext('2d'); | |
| const W = canvas.width, H = canvas.height; | |
| let bird, pipes, score, gameState, frameCount; | |
| const GRAVITY = 0.4, FLAP_POWER = -7, PIPE_SPEED = 2.5, PIPE_GAP = 130, PIPE_WIDTH = 50, BIRD_SIZE = 20; | |
| function init() { | |
| bird = { x: 80, y: H/2, vy: 0, rotation: 0 }; | |
| pipes = []; score = 0; frameCount = 0; | |
| gameState = 'waiting'; | |
| const el = document.getElementById('flappy-score'); | |
| if (el) el.textContent = '0'; | |
| } | |
| function flap() { | |
| if (gameState === 'dead') { init(); return; } | |
| if (gameState === 'waiting') gameState = 'playing'; | |
| bird.vy = FLAP_POWER; | |
| } | |
| function update() { | |
| frameCount++; | |
| if (gameState === 'waiting') { bird.y = H/2 + Math.sin(frameCount * 0.05) * 15; } | |
| else if (gameState === 'playing') { | |
| bird.vy += GRAVITY; bird.y += bird.vy; | |
| bird.rotation = Math.min(Math.max(bird.vy * 3, -30), 90); | |
| if (frameCount % 90 === 0) { | |
| const topH = 60 + Math.random() * (H - PIPE_GAP - 120); | |
| pipes.push({ x: W, topH, scored: false }); | |
| } | |
| pipes.forEach(p => { | |
| p.x -= PIPE_SPEED; | |
| if (bird.x + BIRD_SIZE/2 > p.x && bird.x - BIRD_SIZE/2 < p.x + PIPE_WIDTH) { | |
| if (bird.y - BIRD_SIZE/2 < p.topH || bird.y + BIRD_SIZE/2 > p.topH + PIPE_GAP) gameState = 'dead'; | |
| } | |
| if (!p.scored && p.x + PIPE_WIDTH < bird.x) { p.scored = true; score++; const el = document.getElementById('flappy-score'); if (el) el.textContent = score; } | |
| }); | |
| pipes = pipes.filter(p => p.x > -PIPE_WIDTH); | |
| if (bird.y > H || bird.y < 0) gameState = 'dead'; | |
| } | |
| draw(); | |
| } | |
| function draw() { | |
| const grad = ctx.createLinearGradient(0, 0, 0, H); | |
| grad.addColorStop(0, '#1a1a2e'); grad.addColorStop(1, '#16213e'); | |
| ctx.fillStyle = grad; ctx.fillRect(0, 0, W, H); | |
| ctx.fillStyle = 'rgba(255,255,255,0.3)'; | |
| for (let i = 0; i < 30; i++) { const sx = (i * 97 + frameCount * 0.02) % W; const sy = (i * 53) % (H - 100); ctx.fillRect(sx, sy, 1.5, 1.5); } | |
| pipes.forEach(p => { | |
| const pipeGrad = ctx.createLinearGradient(p.x, 0, p.x + PIPE_WIDTH, 0); | |
| pipeGrad.addColorStop(0, '#2d6a4f'); pipeGrad.addColorStop(0.5, '#40916c'); pipeGrad.addColorStop(1, '#2d6a4f'); | |
| ctx.fillStyle = pipeGrad; | |
| ctx.fillRect(p.x, 0, PIPE_WIDTH, p.topH); ctx.fillRect(p.x - 3, p.topH - 20, PIPE_WIDTH + 6, 20); | |
| const botY = p.topH + PIPE_GAP; | |
| ctx.fillRect(p.x, botY, PIPE_WIDTH, H - botY); ctx.fillRect(p.x - 3, botY, PIPE_WIDTH + 6, 20); | |
| }); | |
| ctx.fillStyle = '#2d6a4f'; ctx.fillRect(0, H - 40, W, 40); ctx.fillStyle = '#40916c'; ctx.fillRect(0, H - 40, W, 5); | |
| ctx.save(); ctx.translate(bird.x, bird.y); ctx.rotate(bird.rotation * Math.PI / 180); | |
| ctx.fillStyle = '#f4a261'; ctx.beginPath(); ctx.ellipse(0, 0, BIRD_SIZE/2, BIRD_SIZE/2.5, 0, 0, Math.PI*2); ctx.fill(); | |
| ctx.fillStyle = '#e76f51'; ctx.beginPath(); ctx.ellipse(-3, 2, 8, 5, -0.3, 0, Math.PI*2); ctx.fill(); | |
| ctx.fillStyle = '#fff'; ctx.beginPath(); ctx.arc(6, -4, 4, 0, Math.PI*2); ctx.fill(); | |
| ctx.fillStyle = '#000'; ctx.beginPath(); ctx.arc(7, -4, 2, 0, Math.PI*2); ctx.fill(); | |
| ctx.fillStyle = '#e63946'; ctx.beginPath(); ctx.moveTo(BIRD_SIZE/2, -2); ctx.lineTo(BIRD_SIZE/2 + 8, 2); ctx.lineTo(BIRD_SIZE/2, 5); ctx.closePath(); ctx.fill(); | |
| ctx.restore(); | |
| if (gameState === 'waiting') { ctx.fillStyle = 'rgba(255,255,255,0.8)'; ctx.font = '18px Segoe UI'; ctx.textAlign = 'center'; ctx.fillText('Click to Start', W/2, H/2 + 60); } | |
| if (gameState === 'dead') { | |
| ctx.fillStyle = 'rgba(0,0,0,0.6)'; ctx.fillRect(0, 0, W, H); | |
| ctx.fillStyle = '#fff'; ctx.font = 'bold 24px Segoe UI'; ctx.textAlign = 'center'; | |
| ctx.fillText('GAME OVER', W/2, H/2 - 15); ctx.font = '14px Segoe UI'; ctx.fillStyle = '#aaa'; | |
| ctx.fillText(`Score: ${score} — Click to restart`, W/2, H/2 + 15); | |
| } | |
| } | |
| init(); draw(); | |
| if (gameIntervals['flappy']) clearInterval(gameIntervals['flappy']); | |
| gameIntervals['flappy'] = setInterval(update, 1000/60); | |
| document.removeEventListener('keydown', window._flappyKeyHandler); | |
| window._flappyKeyHandler = () => { if (activeWindows['flappy']) flap(); }; | |
| document.addEventListener('keydown', (e) => { if (e.key === ' ' && activeWindows['flappy']) { e.preventDefault(); flap(); } }); | |
| canvas.onclick = flap; | |
| } | |
| // ===== TIC TAC TOE ===== | |
| function openTicTacToe() { | |
| if (activeWindows['tictactoe']) { focusWindow(activeWindows['tictactoe']); return; } | |
| const content = ` | |
| <div class="ttt-body"> | |
| <div class="ttt-status" id="ttt-status">Your turn (X)</div> | |
| <div class="ttt-score-board"> | |
| <span class="x-score">X: <span id="ttt-x-wins">0</span></span> | |
| <span class="draw-score">Draw: <span id="ttt-draws">0</span></span> | |
| <span class="o-score">O: <span id="ttt-o-wins">0</span></span> | |
| </div> | |
| <div class="ttt-grid" id="ttt-grid"></div> | |
| <button class="ttt-btn" onclick="resetTicTacToe()">New Game</button> | |
| </div> | |
| `; | |
| createWindow('tictactoe', 'Tic Tac Toe', '❌', 320, 420, content); | |
| setTimeout(initTicTacToe, 100); | |
| } | |
| function initTicTacToe() { | |
| const grid = document.getElementById('ttt-grid'); | |
| if (!grid) return; | |
| let board = Array(9).fill(null); | |
| let playerTurn = true; // true = X (player), false = O (AI) | |
| let gameActive = true; | |
| let xWins = 0, oWins = 0, draws = 0; | |
| const winCombos = [ | |
| [0,1,2],[3,4,5],[6,7,8], | |
| [0,3,6],[1,4,7],[2,5,8], | |
| [0,4,8],[2,4,6] | |
| ]; | |
| function renderGrid() { | |
| grid.innerHTML = ''; | |
| board.forEach((cell, i) => { | |
| const cellEl = document.createElement('div'); | |
| cellEl.className = 'ttt-cell' + (cell === 'X' ? ' x' : cell === 'O' ? ' o' : ''); | |
| cellEl.textContent = cell || ''; | |
| cellEl.onclick = () => playerMove(i); | |
| grid.appendChild(cellEl); | |
| }); | |
| } | |
| function checkWinner() { | |
| for (const combo of winCombos) { | |
| const [a, b, c] = combo; | |
| if (board[a] && board[a] === board[b] && board[a] === board[c]) { | |
| return { winner: board[a], combo }; | |
| } | |
| } | |
| if (board.every(c => c !== null)) return { winner: 'draw', combo: null }; | |
| return null; | |
| } | |
| function playerMove(index) { | |
| if (!gameActive || !playerTurn || board[index] !== null) return; | |
| board[index] = 'X'; | |
| renderGrid(); | |
| const result = checkWinner(); | |
| if (result) { handleResult(result); return; } | |
| playerTurn = false; | |
| updateStatus("AI is thinking..."); | |
| setTimeout(aiMove, 400); | |
| } | |
| function aiMove() { | |
| if (!gameActive) return; | |
| // Simple AI: win if possible, block if needed, else center/corner/random | |
| let move = findWinningMove('O'); | |
| if (move === -1) move = findWinningMove('X'); | |
| if (move === -1 && board[4] === null) move = 4; | |
| else if (move === -1) { | |
| const corners = [0,2,6,8].filter(i => board[i] === null); | |
| const empties = board.map((c,i) => c === null ? i : -1).filter(i => i !== -1); | |
| move = corners.length > 0 ? corners[Math.floor(Math.random()*corners.length)] : empties[Math.floor(Math.random()*empties.length)]; | |
| } | |
| board[move] = 'O'; | |
| renderGrid(); | |
| const result = checkWinner(); | |
| if (result) { handleResult(result); return; } | |
| playerTurn = true; | |
| updateStatus('Your turn (X)'); | |
| } | |
| function findWinningMove(symbol) { | |
| for (const combo of winCombos) { | |
| const [a,b,c] = combo; | |
| const cells = [board[a], board[b], board[c]]; | |
| const symbolCount = cells.filter(x => x === symbol).length; | |
| const nullCount = cells.filter(x => x === null).length; | |
| if (symbolCount === 2 && nullCount === 1) { | |
| return combo[cells.indexOf(null)]; | |
| } | |
| } | |
| return -1; | |
| } | |
| function handleResult(result) { | |
| gameActive = false; | |
| if (result.winner === 'X') { | |
| xWins++; | |
| document.getElementById('ttt-x-wins').textContent = xWins; | |
| updateStatus('🎉 You win!'); | |
| highlightWin(result.combo); | |
| } else if (result.winner === 'O') { | |
| oWins++; | |
| document.getElementById('ttt-o-wins').textContent = oWins; | |
| updateStatus('🤖 AI wins!'); | |
| highlightWin(result.combo); | |
| } else { | |
| draws++; | |
| document.getElementById('ttt-draws').textContent = draws; | |
| updateStatus("🤝 It's a draw!"); | |
| } | |
| } | |
| function highlightWin(combo) { | |
| if (!combo) return; | |
| const cells = grid.querySelectorAll('.ttt-cell'); | |
| combo.forEach(i => cells[i].classList.add('win-cell')); | |
| } | |
| function updateStatus(msg) { | |
| const el = document.getElementById('ttt-status'); | |
| if (el) el.textContent = msg; | |
| } | |
| window.resetTicTacToe = function() { | |
| board = Array(9).fill(null); | |
| playerTurn = true; | |
| gameActive = true; | |
| renderGrid(); | |
| updateStatus('Your turn (X)'); | |
| }; | |
| renderGrid(); | |
| } | |
| // ===== MATCHING GAME ===== | |
| function openMatching() { | |
| if (activeWindows['matching']) { focusWindow(activeWindows['matching']); return; } | |
| const content = ` | |
| <div class="match-body"> | |
| <div class="match-score-board"> | |
| <span>Moves: <span id="match-moves">0</span></span> | |
| <span>Pairs: <span id="match-pairs">0</span>/8</span> | |
| </div> | |
| <div class="match-grid" id="match-grid"></div> | |
| <button class="match-btn" onclick="resetMatching()">New Game</button> | |
| </div> | |
| `; | |
| createWindow('matching', 'Matching Game', '🃏', 380, 460, content); | |
| setTimeout(initMatching, 100); | |
| } | |
| function initMatching() { | |
| const grid = document.getElementById('match-grid'); | |
| if (!grid) return; | |
| const emojis = ['🎮','🐍','🐦','🎲','🎯','🚀','💎','🌟']; | |
| let cards = []; | |
| let flipped = []; | |
| let matched = []; | |
| let moves = 0; | |
| let pairs = 0; | |
| let locked = false; | |
| function createCards() { | |
| cards = [...emojis, ...emojis] | |
| .sort(() => Math.random() - 0.5) | |
| .map((emoji, i) => ({ id: i, emoji, flipped: false, matched: false })); | |
| matched = []; | |
| flipped = []; | |
| moves = 0; | |
| pairs = 0; | |
| locked = false; | |
| const movesEl = document.getElementById('match-moves'); | |
| if (movesEl) movesEl.textContent = '0'; | |
| const pairsEl = document.getElementById('match-pairs'); | |
| if (pairsEl) pairsEl.textContent = '0'; | |
| renderCards(); | |
| } | |
| function renderCards() { | |
| grid.innerHTML = ''; | |
| cards.forEach((card, i) => { | |
| const cardEl = document.createElement('div'); | |
| cardEl.className = 'match-card' + (card.flipped ? ' flipped' : '') + (card.matched ? ' matched' : ''); | |
| cardEl.innerHTML = ` | |
| <div class="match-card-inner"> | |
| <div class="match-card-front">?</div> | |
| <div class="match-card-back">${card.emoji}</div> | |
| </div> | |
| `; | |
| cardEl.onclick = () => flipCard(i); | |
| grid.appendChild(cardEl); | |
| }); | |
| } | |
| function flipCard(index) { | |
| if (locked) return; | |
| const card = cards[index]; | |
| if (card.flipped || card.matched) return; | |
| card.flipped = true; | |
| flipped.push(index); | |
| renderCards(); | |
| if (flipped.length === 2) { | |
| moves++; | |
| const movesEl = document.getElementById('match-moves'); | |
| if (movesEl) movesEl.textContent = moves; | |
| locked = true; | |
| const [a, b] = flipped; | |
| if (cards[a].emoji === cards[b].emoji) { | |
| cards[a].matched = true; | |
| cards[b].matched = true; | |
| pairs++; | |
| const pairsEl = document.getElementById('match-pairs'); | |
| if (pairsEl) pairsEl.textContent = pairs; | |
| flipped = []; | |
| locked = false; | |
| renderCards(); | |
| if (pairs === 8) { | |
| setTimeout(() => { | |
| const body = grid.closest('.window-body'); | |
| if (body) { | |
| const overlay = document.createElement('div'); | |
| overlay.className = 'match-win-overlay'; | |
| overlay.innerHTML = `<h2>🎉 You Win!</h2><p>Completed in ${moves} moves!</p><button class="match-btn" onclick="this.parentElement.remove(); resetMatching();">Play Again</button>`; | |
| body.appendChild(overlay); | |
| } | |
| }, 500); | |
| } | |
| } else { | |
| setTimeout(() => { | |
| cards[a].flipped = false; | |
| cards[b].flipped = false; | |
| flipped = []; | |
| locked = false; | |
| renderCards(); | |
| }, 800); | |
| } | |
| } | |
| } | |
| window.resetMatching = function() { | |
| const overlay = grid.closest('.window-body')?.querySelector('.match-win-overlay'); | |
| if (overlay) overlay.remove(); | |
| createCards(); | |
| }; | |
| createCards(); | |
| } | |
| // ===== CALCULATOR ===== | |
| function openCalculator() { | |
| if (activeWindows['calc']) { focusWindow(activeWindows['calc']); return; } | |
| const content = ` | |
| <div class="calc-body"> | |
| <div class="calc-display" id="calc-display">0</div> | |
| <div class="calc-buttons"> | |
| <button class="calc-btn clear" onclick="calcClear()">C</button> | |
| <button class="calc-btn op" onclick="calcInput('±')">±</button> | |
| <button class="calc-btn op" onclick="calcInput('%')">%</button> | |
| <button class="calc-btn op" onclick="calcInput('÷')">÷</button> | |
| <button class="calc-btn num" onclick="calcInput('7')">7</button> | |
| <button class="calc-btn num" onclick="calcInput('8')">8</button> | |
| <button class="calc-btn num" onclick="calcInput('9')">9</button> | |
| <button class="calc-btn op" onclick="calcInput('×')">×</button> | |
| <button class="calc-btn num" onclick="calcInput('4')">4</button> | |
| <button class="calc-btn num" onclick="calcInput('5')">5</button> | |
| <button class="calc-btn num" onclick="calcInput('6')">6</button> | |
| <button class="calc-btn op" onclick="calcInput('-')">−</button> | |
| <button class="calc-btn num" onclick="calcInput('1')">1</button> | |
| <button class="calc-btn num" onclick="calcInput('2')">2</button> | |
| <button class="calc-btn num" onclick="calcInput('3')">3</button> | |
| <button class="calc-btn op" onclick="calcInput('+')">+</button> | |
| <button class="calc-btn num" onclick="calcInput('0')" style="grid-column:span 2">0</button> | |
| <button class="calc-btn num" onclick="calcInput('.')">.</button> | |
| <button class="calc-btn eq" onclick="calcEqual()">=</button> | |
| </div> | |
| </div> | |
| `; | |
| createWindow('calc', 'Calculator', '🧮', 280, 420, content); | |
| } | |
| let calcStr = '0', calcNew = true; | |
| function calcInput(val) { | |
| if (calcNew) { calcStr = val === '.' ? '0.' : val; calcNew = false; } | |
| else { | |
| if ('+-×÷%'.includes(val) && val !== '±' && val !== '%') { calcStr += val; } | |
| else if (val === '.') { calcStr += val; } | |
| else if (val === '±') { if (calcStr !== '0') calcStr = calcStr.startsWith('-') ? calcStr.slice(1) : '-' + calcStr; } | |
| else if (val === '%') { calcStr = String(parseFloat(calcStr) / 100); } | |
| else { calcStr += val; } | |
| } | |
| const display = document.getElementById('calc-display'); | |
| if (display) display.textContent = calcStr; | |
| } | |
| function calcClear() { calcStr = '0'; calcNew = true; const display = document.getElementById('calc-display'); if (display) display.textContent = '0'; } | |
| function calcEqual() { | |
| try { | |
| let expr = calcStr.replace(/×/g, '*').replace(/÷/g, '/').replace(/−/g, '-'); | |
| let result = Function('"use strict"; return (' + expr + ')')(); | |
| calcStr = String(parseFloat(result.toFixed(10))); calcNew = true; | |
| const display = document.getElementById('calc-display'); | |
| if (display) display.textContent = calcStr; | |
| } catch(e) { calcStr = 'Error'; calcNew = true; const display = document.getElementById('calc-display'); if (display) display.textContent = 'Error'; } | |
| } | |
| // ===== TERMINAL ===== | |
| function openTerminal() { | |
| if (activeWindows['terminal']) { focusWindow(activeWindows['terminal']); return; } | |
| const content = ` | |
| <div class="terminal-body" id="terminal-body"> | |
| <div class="terminal-line">BrowserOS Terminal v1.0</div> | |
| <div class="terminal-line">Type 'help' for available commands.</div> | |
| <div class="terminal-line">─────────────────────────────────────</div> | |
| <div class="terminal-input-line"> | |
| <span class="terminal-prompt">user@browseros:~$</span> | |
| <input class="terminal-input" id="terminal-input" autofocus spellcheck="false" autocomplete="off"> | |
| </div> | |
| </div> | |
| `; | |
| const win = createWindow('terminal', 'Terminal', '⬛', 550, 380, content); | |
| setTimeout(() => { | |
| const input = document.getElementById('terminal-input'); | |
| if (input) { | |
| input.focus(); | |
| input.addEventListener('keydown', (e) => { | |
| if (e.key === 'Enter') { processCommand(input.value.trim()); input.value = ''; input.scrollIntoView(); } | |
| }); | |
| } | |
| }, 100); | |
| } | |
| function processCommand(cmd) { | |
| const body = document.getElementById('terminal-body'); | |
| if (!body) return; | |
| const inputLine = body.querySelector('.terminal-input-line'); | |
| if (inputLine) inputLine.remove(); | |
| const cmdLine = document.createElement('div'); | |
| cmdLine.className = 'terminal-line'; | |
| cmdLine.innerHTML = `<span class="terminal-prompt">user@browseros:~$</span> ${escapeHtml(cmd)}`; | |
| body.appendChild(cmdLine); | |
| const result = executeCommand(cmd); | |
| if (result) result.split('\n').forEach(line => { | |
| const out = document.createElement('div'); out.className = 'terminal-line'; out.innerHTML = escapeHtml(line); body.appendChild(out); | |
| }); | |
| const newInput = document.createElement('div'); | |
| newInput.className = 'terminal-input-line'; | |
| newInput.innerHTML = `<span class="terminal-prompt">user@browseros:~$</span><input class="terminal-input" id="terminal-input" autofocus spellcheck="false" autocomplete="off">`; | |
| body.appendChild(newInput); | |
| const newInputEl = document.getElementById('terminal-input'); | |
| if (newInputEl) { | |
| newInputEl.focus(); | |
| newInputEl.addEventListener('keydown', (e) => { if (e.key === 'Enter') { processCommand(newInputEl.value.trim()); newInputEl.value = ''; } }); | |
| } | |
| body.scrollTop = body.scrollHeight; | |
| } | |
| function executeCommand(cmd) { | |
| const parts = cmd.split(/\s+/); | |
| const command = parts[0].toLowerCase(); | |
| const args = parts.slice(1); | |
| switch(command) { | |
| case '': return ''; | |
| case 'help': return `Available commands: | |
| help Show this help message | |
| clear Clear terminal | |
| time Show current time | |
| date Show current date | |
| echo [text] Print text | |
| neofetch System information | |
| matrix Toggle Matrix rain effect | |
| color [hex] Change terminal color | |
| whoami Current user | |
| uname System information | |
| ls List files | |
| cat [file] Read file | |
| history Command history | |
| about About BrowserOS | |
| sudo Try it ;) | |
| sudo rm -rf / Just kidding! | |
| matrix [off] Matrix rain control`; | |
| case 'clear': | |
| const tb = document.getElementById('terminal-body'); | |
| if (tb) { tb.querySelectorAll('.terminal-line, .terminal-input-line').forEach(l => l.remove()); } | |
| return ''; | |
| case 'time': return new Date().toLocaleTimeString(); | |
| case 'date': return new Date().toLocaleDateString('en-US', {weekday:'long',year:'numeric',month:'long',day:'numeric'}); | |
| case 'echo': return args.join(' ') || ''; | |
| case 'whoami': return 'user'; | |
| case 'uname': return 'BrowserOS 1.0.0 (HTML/CSS/JS)'; | |
| case 'ls': return `Documents/ Downloads/ Music/ Pictures/ Games/ .config/`; | |
| case 'cat': | |
| if (args[0] === 'README') return 'Welcome to BrowserOS!\nA fully functional browser-based operating system.\nFeatures: Tetris, Snake, Flappy Bird, Tic Tac Toe, Matching Game, Calculator, Terminal, Wallpaper Changer.'; | |
| return `cat: ${args[0] || ''}: No such file or directory`; | |
| case 'about': return `BrowserOS v1.0 | |
| A browser-based operating system built with HTML, CSS, and JavaScript. | |
| Applications: | |
| 🎮 Tetris - Classic block-stacking game | |
| 🐍 Snake - Arcade snake game | |
| 🐦 Flappy Bird - Tap to fly through pipes | |
| ❌ Tic Tac Toe - Play against AI | |
| 🃏 Matching Game - Memory card game | |
| 🧮 Calculator - Standard calculator | |
| ⬛ Terminal - Command line interface | |
| 🎨 Wallpaper - Change desktop background | |
| 📝 Notes - Text editor | |
| ℹ️ About - This window | |
| Special Features: | |
| ⚡ Neon Mode - Cyberpunk aesthetic transformation | |
| 🟢 Matrix Rain - Classic Matrix effect in terminal`; | |
| case 'neofetch': | |
| return ` | |
| ██████╗ ██████╗ ████████╗ user@browseros | |
| ██╔════╝██╔═══██╗╚══██╔══╝ OS: BrowserOS 1.0 | |
| ██║ ██║ ██║ ██║ Kernel: HTML5/CSS3/JS | |
| ██║ ██║ ██║ ██║ Shell: browser-term | |
| ╚██████╗╚██████╔╝ ██║ Resolution: ${window.innerWidth}x${window.innerHeight} | |
| ╚═════╝ ╚═════╝ ██║ Theme: ${neonMode ? 'Neon Cyberpunk' : 'Default Dark'} | |
| ╚════╝ CPU: JavaScript Engine | |
| Memory: ${navigator.deviceMemory || '?'} GB | |
| Windows: ${Object.keys(activeWindows).length} open | |
| Games: 5 installed | |
| ██╗ ██╗ ██████╗ ██╗ ██╗███████╗ | |
| ██║ ██║██╔═══██╗██║ ██║██╔════╝ | |
| ██║ █╗ ██║██║ ██║██║ ██║█████╗ | |
| ██║███╗██║██║ ██║╚██╗ ██╔╝██╔══╝ | |
| ╚███╔███╔╝╚██████╔╝ ╚████╔╝ ███████╗ | |
| ╚══╝╚══╝ ╚═════╝ ╚═══╝ ╚══════╝`; | |
| case 'matrix': | |
| if (args[0] === 'off' || matrixActive) { | |
| matrixActive = false; | |
| document.getElementById('matrix-overlay').classList.remove('active'); | |
| return 'Matrix rain disabled.'; | |
| } | |
| matrixActive = true; | |
| document.getElementById('matrix-overlay').classList.add('active'); | |
| startMatrixRain(); | |
| return 'Matrix rain activated. Type "matrix off" to disable.'; | |
| case 'color': | |
| if (args[0]) { const tb = document.getElementById('terminal-body'); if (tb) tb.style.color = args[0]; return `Terminal color changed to ${args[0]}`; } | |
| return 'Usage: color [hex/color-name]'; | |
| case 'sudo': return `🎭 Nice try! But remember: | |
| "With great power comes great responsibility... | |
| to click the right buttons." | |
| (Don't worry, nothing was deleted. 😄)`; | |
| case 'sudo rm -rf /': | |
| return `⚠️ Access denied! | |
| 🛡️ Safety override engaged! | |
| ❌ Command blocked by BrowserOS Security Protocol v1.0 | |
| Jokes apart, this is a browser. Your actual files are safe! 😉`; | |
| default: return `Command not found: ${command}\nType 'help' for available commands.`; | |
| } | |
| } | |
| function escapeHtml(text) { | |
| const div = document.createElement('div'); div.textContent = text; return div.innerHTML; | |
| } | |
| // ===== MATRIX RAIN ===== | |
| function startMatrixRain() { | |
| const canvas = document.getElementById('matrix-canvas'); | |
| const ctx = canvas.getContext('2d'); | |
| canvas.width = window.innerWidth; | |
| canvas.height = window.innerHeight; | |
| const chars = 'アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワヲン0123456789ABCDEF'; | |
| const fontSize = 14; | |
| const columns = Math.floor(canvas.width / fontSize); | |
| const drops = Array(columns).fill(1); | |
| function draw() { | |
| if (!matrixActive) { ctx.clearRect(0, 0, canvas.width, canvas.height); return; } | |
| ctx.fillStyle = 'rgba(0, 0, 0, 0.05)'; ctx.fillRect(0, 0, canvas.width, canvas.height); | |
| ctx.fillStyle = '#0f0'; ctx.font = fontSize + 'px monospace'; | |
| for (let i = 0; i < drops.length; i++) { | |
| const text = chars[Math.floor(Math.random() * chars.length)]; | |
| ctx.fillStyle = Math.random() > 0.98 ? '#fff' : `hsl(120, 100%, ${30 + Math.random()*40}%)`; | |
| ctx.fillText(text, i * fontSize, drops[i] * fontSize); | |
| if (drops[i] * fontSize > canvas.height && Math.random() > 0.975) drops[i] = 0; | |
| drops[i]++; | |
| } | |
| gameTimeouts['matrix'] = setTimeout(draw, 40); | |
| } | |
| draw(); | |
| } | |
| // ===== WALLPAPER APP ===== | |
| function openWallpaper() { | |
| if (activeWindows['wallpaper']) { focusWindow(activeWindows['wallpaper']); return; } | |
| const wallpapers = [ | |
| {id:1, name:'Deep Space', cls:'wallpaper-1'}, {id:2, name:'Sunset', cls:'wallpaper-2'}, | |
| {id:3, name:'Forest', cls:'wallpaper-3'}, {id:4, name:'Candy', cls:'wallpaper-4'}, | |
| {id:5, name:'Ocean', cls:'wallpaper-5'}, {id:6, name:'Hellfire', cls:'wallpaper-6'}, | |
| {id:7, name:'Midnight', cls:'wallpaper-7'}, {id:8, name:'Noir', cls:'wallpaper-8'} | |
| ]; | |
| let content = `<div class="wallpaper-app-body"><h2>🎨 Choose Wallpaper</h2><div class="wallpaper-grid">`; | |
| wallpapers.forEach(wp => { | |
| content += `<div class="wallpaper-option ${wp.cls} ${wp.cls === 'wallpaper-'+currentWallpaper ? 'selected' : ''}" onclick="setWallpaper(${wp.id}, this)"><span class="wp-label">${wp.name}</span></div>`; | |
| }); | |
| content += `</div><div class="wallpaper-custom"><input type="color" id="wp-color-picker" value="#1a1a2e"><span>Custom Color:</span><button onclick="setCustomWallpaper()">Apply</button></div></div>`; | |
| createWindow('wallpaper', 'Wallpaper', '🎨', 420, 440, content); | |
| } | |
| function setWallpaper(id, el) { | |
| currentWallpaper = id; | |
| document.body.className = `wallpaper-${id}`; | |
| document.querySelectorAll('.wallpaper-option').forEach(o => o.classList.remove('selected')); | |
| if (el) el.classList.add('selected'); | |
| } | |
| function setCustomWallpaper() { | |
| const color = document.getElementById('wp-color-picker').value; | |
| document.body.style.background = color; | |
| document.body.className = ''; | |
| } | |
| // ===== NOTES ===== | |
| function openNotes() { | |
| if (activeWindows['notes']) { focusWindow(activeWindows['notes']); return; } | |
| const saved = localStorage.getItem('browseros-notes') || ''; | |
| const content = ` | |
| <div class="notes-body"> | |
| <div class="notes-toolbar"> | |
| <button onclick="saveNotes()">💾 Save</button> | |
| <button onclick="document.getElementById('notes-text').value=''; document.getElementById('notes-text').focus();">🗑️ Clear</button> | |
| <button onclick="document.getElementById('notes-text').style.fontWeight = document.getElementById('notes-text').style.fontWeight === 'bold' ? 'normal' : 'bold';">𝐁 Bold</button> | |
| <button onclick="document.getElementById('notes-text').style.fontStyle = document.getElementById('notes-text').style.fontStyle === 'italic' ? 'normal' : 'italic';">𝐼 Italic</button> | |
| </div> | |
| <textarea class="notes-textarea" id="notes-text" placeholder="Start typing your notes here...">${escapeHtml(saved)}</textarea> | |
| </div> | |
| `; | |
| createWindow('notes', 'Notes', '📝', 400, 350, content); | |
| } | |
| function saveNotes() { | |
| const text = document.getElementById('notes-text')?.value || ''; | |
| localStorage.setItem('browseros-notes', text); | |
| alert('Notes saved! 💾'); | |
| } | |
| // ===== ABOUT ===== | |
| function openAbout() { | |
| if (activeWindows['about']) { focusWindow(activeWindows['about']); return; } | |
| const content = ` | |
| <div class="about-body"> | |
| <div style="font-size:48px; margin-bottom:8px;">🖥️</div> | |
| <h2>BrowserOS</h2> | |
| <div class="version">v1.0.0</div> | |
| <p>A fully functional browser-based operating system<br>built with HTML, CSS, and JavaScript.</p> | |
| <br> | |
| <p style="color:#4fc3f7;">🎮 Games: Tetris, Snake, Flappy Bird, Tic Tac Toe, Matching</p> | |
| <p style="color:#4fc3f7;">🧮 Tools: Calculator, Terminal, Notes</p> | |
| <p style="color:#4fc3f7;">🎨 Customization: 8+ Wallpapers</p> | |
| <p style="color:#4fc3f7;">⚡ Special: Neon Mode + Matrix Rain</p> | |
| <br> | |
| <p style="color:#666; font-size:11px;">Double-click desktop icons or use the Start Menu to open apps.<br> | |
| Windows are draggable, resizable, and minimizable.</p> | |
| </div> | |
| `; | |
| createWindow('about', 'About', 'ℹ️', 360, 320, content); | |
| } | |
| // ===== START ===== | |
| init(); | |
| </script> | |
| </body> | |
| </html> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # BrowserOS - Full Browser-Based Operating System | |
| ## Special Feature: **"Neon Mode"** + **System Terminal** | |
| - **Neon Mode**: A cyberpunk transformation that applies glowing neon effects to all UI elements, changes the color scheme to vibrant magenta/cyan/yellow, and adds animated border pulses. | |
| - **Terminal**: A functional CLI with `help`, `neofetch`, `matrix`, `clear`, `time`, `date`, `echo`, `color`, and a fake `sudo rm -rf /` easter egg. | |
| - **Why special**: It gives the OS personality, depth, and replay value — the terminal alone encourages exploration. | |
| ```html | |
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>BrowserOS</title> | |
| <style> | |
| /* ===== RESET & BASE ===== */ | |
| * { margin:0; padding:0; box-sizing:border-box; user-select:none; } | |
| body { overflow:hidden; font-family:'Segoe UI',Tahoma,Geneva,Verdana,sans-serif; height:100vh; width:100vw; } | |
| /* ===== WALLPAPER BACKGROUNDS ===== */ | |
| .wallpaper-1 { background:linear-gradient(135deg,#0f0c29,#302b63,#24243e); } | |
| .wallpaper-2 { background:linear-gradient(135deg,#1a2a6c,#b21f1f,#fdbb2d); } | |
| .wallpaper-3 { background:linear-gradient(135deg,#00b09b,#96c93d); } | |
| .wallpaper-4 { background:linear-gradient(135deg,#fc5c7d,#6a82fb); } | |
| .wallpaper-5 { background:linear-gradient(135deg,#0f2027,#203a43,#2c5364); } | |
| .wallpaper-6 { background:linear-gradient(135deg,#200122,#6f0000); } | |
| .wallpaper-7 { background:linear-gradient(135deg,#000428,#004e92); } | |
| .wallpaper-8 { background:linear-gradient(135deg,#434343,#000000); } | |
| /* ===== DESKTOP ===== */ | |
| #desktop { | |
| position:relative; width:100%; height:calc(100vh - 48px); | |
| transition: all 0.5s; | |
| } | |
| /* ===== DESKTOP ICONS ===== */ | |
| .desktop-icon { | |
| position:absolute; width:80px; text-align:center; cursor:pointer; | |
| padding:8px 4px; border-radius:8px; transition:background 0.2s; | |
| } | |
| .desktop-icon:hover { background:rgba(255,255,255,0.15); } | |
| .desktop-icon:active { background:rgba(255,255,255,0.25); } | |
| .desktop-icon .icon-img { | |
| width:48px; height:48px; margin:0 auto 4px; border-radius:10px; | |
| display:flex; align-items:center; justify-content:center; font-size:24px; | |
| background:rgba(255,255,255,0.1); backdrop-filter:blur(4px); | |
| border:1px solid rgba(255,255,255,0.15); | |
| } | |
| .desktop-icon .icon-label { | |
| color:#fff; font-size:11px; text-shadow:1px 1px 3px rgba(0,0,0,0.8); | |
| word-wrap:break-word; line-height:1.3; | |
| } | |
| /* ===== TASKBAR ===== */ | |
| #taskbar { | |
| position:fixed; bottom:0; left:0; right:0; height:48px; | |
| background:rgba(20,20,30,0.92); backdrop-filter:blur(20px); | |
| display:flex; align-items:center; padding:0 8px; z-index:10000; | |
| border-top:1px solid rgba(255,255,255,0.1); | |
| } | |
| #start-btn { | |
| width:40px; height:40px; border:none; background:rgba(255,255,255,0.1); | |
| border-radius:8px; cursor:pointer; font-size:20px; color:#fff; | |
| display:flex; align-items:center; justify-content:center; transition:all 0.2s; | |
| } | |
| #start-btn:hover { background:rgba(255,255,255,0.2); } | |
| #taskbar-apps { display:flex; gap:4px; margin-left:8px; flex:1; } | |
| .taskbar-app-btn { | |
| height:36px; min-width:50px; padding:0 12px; border:none; | |
| background:rgba(255,255,255,0.08); border-radius:6px; cursor:pointer; | |
| color:#fff; font-size:11px; display:flex; align-items:center; gap:6px; | |
| transition:all 0.2s; max-width:160px; | |
| } | |
| .taskbar-app-btn:hover { background:rgba(255,255,255,0.15); } | |
| .taskbar-app-btn.active { background:rgba(255,255,255,0.2); border-bottom:2px solid #4fc3f7; } | |
| #taskbar-clock { | |
| color:#fff; font-size:12px; padding:0 12px; text-align:right; line-height:1.4; | |
| } | |
| /* ===== START MENU ===== */ | |
| #start-menu { | |
| position:fixed; bottom:52px; left:8px; width:320px; | |
| background:rgba(25,25,40,0.95); backdrop-filter:blur(20px); | |
| border-radius:12px; border:1px solid rgba(255,255,255,0.1); | |
| padding:16px; display:none; z-index:10001; | |
| box-shadow:0 8px 32px rgba(0,0,0,0.5); | |
| } | |
| #start-menu.show { display:block; } | |
| #start-menu h3 { color:#fff; font-size:14px; margin-bottom:12px; padding-bottom:8px; border-bottom:1px solid rgba(255,255,255,0.1); } | |
| .start-menu-item { | |
| display:flex; align-items:center; gap:12px; padding:10px 12px; | |
| border-radius:8px; cursor:pointer; color:#fff; font-size:13px; | |
| transition:background 0.15s; | |
| } | |
| .start-menu-item:hover { background:rgba(255,255,255,0.1); } | |
| .start-menu-item .sm-icon { | |
| width:32px; height:32px; border-radius:6px; display:flex; | |
| align-items:center; justify-content:center; font-size:16px; | |
| background:rgba(255,255,255,0.1); | |
| } | |
| /* ===== WINDOW SYSTEM ===== */ | |
| .window { | |
| position:absolute; min-width:320px; min-height:200px; | |
| background:rgba(25,25,40,0.95); backdrop-filter:blur(20px); | |
| border-radius:12px; border:1px solid rgba(255,255,255,0.1); | |
| box-shadow:0 8px 32px rgba(0,0,0,0.4); display:flex; flex-direction:column; | |
| overflow:hidden; transition: box-shadow 0.3s; | |
| } | |
| .window.focused { box-shadow:0 12px 48px rgba(0,0,0,0.6); border-color:rgba(79,195,247,0.3); } | |
| .window.minimized { display:none !important; } | |
| .window-header { | |
| height:36px; display:flex; align-items:center; padding:0 12px; | |
| background:rgba(255,255,255,0.05); cursor:move; flex-shrink:0; | |
| border-bottom:1px solid rgba(255,255,255,0.05); | |
| } | |
| .window-header .win-title { flex:1; color:#fff; font-size:12px; font-weight:500; } | |
| .window-header .win-controls { display:flex; gap:6px; } | |
| .window-header .win-controls button { | |
| width:14px; height:14px; border:none; border-radius:50%; cursor:pointer; | |
| font-size:0; transition:opacity 0.2s; | |
| } | |
| .win-controls .btn-minimize { background:#fbbf24; } | |
| .win-controls .btn-maximize { background:#22c55e; } | |
| .win-controls .btn-close { background:#ef4444; } | |
| .win-controls .win-controls button:hover { opacity:0.7; } | |
| .window-body { flex:1; overflow:auto; position:relative; } | |
| /* ===== NEON MODE ===== */ | |
| body.neon-mode .window { | |
| border-color:rgba(255,0,255,0.4); | |
| box-shadow:0 0 20px rgba(255,0,255,0.15), 0 8px 32px rgba(0,0,0,0.4); | |
| } | |
| body.neon-mode .window.focused { | |
| border-color:rgba(0,255,255,0.5); | |
| box-shadow:0 0 30px rgba(0,255,255,0.2), 0 8px 32px rgba(0,0,0,0.4); | |
| } | |
| body.neon-mode .window-header { | |
| background:rgba(255,0,255,0.1); | |
| border-bottom-color:rgba(0,255,255,0.2); | |
| } | |
| body.neon-mode .desktop-icon .icon-img { | |
| border-color:rgba(255,0,255,0.3); | |
| box-shadow:0 0 10px rgba(255,0,255,0.2); | |
| } | |
| body.neon-mode #taskbar { | |
| background:rgba(10,10,20,0.95); | |
| border-top-color:rgba(0,255,255,0.3); | |
| } | |
| body.neon-mode #start-menu { | |
| border-color:rgba(255,0,255,0.3); | |
| box-shadow:0 0 20px rgba(255,0,255,0.2), 0 8px 32px rgba(0,0,0,0.5); | |
| } | |
| body.neon-mode .desktop-icon:hover { background:rgba(255,0,255,0.15); } | |
| body.neon-mode #start-btn { border:1px solid rgba(0,255,255,0.3); } | |
| body.neon-mode .desktop-icon .icon-label { text-shadow:0 0 8px rgba(0,255,255,0.5); } | |
| body.neon-mode .taskbar-app-btn { border:1px solid rgba(255,0,255,0.2); } | |
| /* ===== TERMINAL ===== */ | |
| .terminal-body { | |
| background:#0a0a0a; color:#33ff33; font-family:'Courier New',monospace; | |
| font-size:13px; padding:12px; height:100%; overflow-y:auto; | |
| } | |
| .terminal-body::-webkit-scrollbar { width:6px; } | |
| .terminal-body::-webkit-scrollbar-track { background:#0a0a0a; } | |
| .terminal-body::-webkit-scrollbar-thumb { background:#333; border-radius:3px; } | |
| .terminal-line { margin-bottom:2px; white-space:pre-wrap; word-break:break-all; } | |
| .terminal-input-line { display:flex; align-items:center; } | |
| .terminal-prompt { color:#33ff33; margin-right:4px; white-space:nowrap; } | |
| .terminal-input { | |
| background:transparent; border:none; color:#33ff33; font-family:inherit; | |
| font-size:inherit; outline:none; flex:1; caret-color:#33ff33; | |
| } | |
| body.neon-mode .terminal-body { color:#00ffff; } | |
| body.neon-mode .terminal-prompt { color:#ff00ff; } | |
| body.neon-mode .terminal-input { color:#00ffff; } | |
| /* ===== CALCULATOR ===== */ | |
| .calc-body { padding:16px; display:flex; flex-direction:column; gap:12px; height:100%; } | |
| .calc-display { | |
| background:rgba(0,0,0,0.3); border-radius:8px; padding:12px 16px; | |
| text-align:right; color:#fff; font-size:28px; font-weight:300; | |
| min-height:56px; display:flex; align-items:center; justify-content:flex-end; | |
| border:1px solid rgba(255,255,255,0.1); | |
| } | |
| .calc-buttons { display:grid; grid-template-columns:repeat(4,1fr); gap:6px; flex:1; } | |
| .calc-btn { | |
| border:none; border-radius:8px; font-size:18px; cursor:pointer; | |
| transition:all 0.15s; color:#fff; | |
| } | |
| .calc-btn.num { background:rgba(255,255,255,0.1); } | |
| .calc-btn.num:hover { background:rgba(255,255,255,0.2); } | |
| .calc-btn.op { background:rgba(79,195,247,0.2); color:#4fc3f7; } | |
| .calc-btn.op:hover { background:rgba(79,195,247,0.3); } | |
| .calc-btn.eq { background:#4fc3f7; color:#000; font-weight:700; } | |
| .calc-btn.eq:hover { background:#81d4fa; } | |
| .calc-btn.clear { background:rgba(239,68,68,0.2); color:#ef4444; } | |
| .calc-btn.clear:hover { background:rgba(239,68,68,0.3); } | |
| /* ===== WALLPAPER APP ===== */ | |
| .wallpaper-app-body { padding:20px; display:flex; flex-direction:column; gap:16px; height:100%; } | |
| .wallpaper-app-body h2 { color:#fff; font-size:18px; font-weight:400; } | |
| .wallpaper-grid { display:grid; grid-template-columns:repeat(2,1fr); gap:10px; } | |
| .wallpaper-option { | |
| height:80px; border-radius:10px; cursor:pointer; border:3px solid transparent; | |
| transition:all 0.2s; position:relative; | |
| } | |
| .wallpaper-option:hover { transform:scale(1.03); } | |
| .wallpaper-option.selected { border-color:#4fc3f7; box-shadow:0 0 12px rgba(79,195,247,0.4); } | |
| .wallpaper-option .wp-label { | |
| position:absolute; bottom:4px; right:8px; color:#fff; font-size:11px; | |
| text-shadow:1px 1px 3px rgba(0,0,0,0.8); | |
| } | |
| .wallpaper-custom { display:flex; gap:10px; align-items:center; } | |
| .wallpaper-custom input[type="color"] { | |
| width:50px; height:40px; border:none; border-radius:8px; cursor:pointer; | |
| background:transparent; | |
| } | |
| .wallpaper-custom span { color:#fff; font-size:13px; } | |
| .wallpaper-custom button { | |
| padding:8px 16px; border:none; border-radius:8px; background:#4fc3f7; | |
| color:#000; font-weight:600; cursor:pointer; transition:all 0.2s; | |
| } | |
| .wallpaper-custom button:hover { background:#81d4fa; } | |
| /* ===== GAME CANVASES ===== */ | |
| .game-container { display:flex; flex-direction:column; align-items:center; padding:10px; height:100%; justify-content:center; } | |
| .game-container canvas { border-radius:4px; border:2px solid rgba(255,255,255,0.15); } | |
| .game-info { color:#fff; font-size:13px; margin-top:8px; text-align:center; } | |
| .game-score { color:#4fc3f7; font-size:16px; font-weight:600; } | |
| .game-over-screen { | |
| position:absolute; top:0; left:0; right:0; bottom:0; | |
| background:rgba(0,0,0,0.8); display:flex; flex-direction:column; | |
| align-items:center; justify-content:center; z-index:10; | |
| } | |
| .game-over-screen h2 { color:#fff; font-size:24px; margin-bottom:8px; } | |
| .game-over-screen p { color:#aaa; font-size:14px; margin-bottom:16px; } | |
| .game-over-screen button { | |
| padding:10px 24px; border:none; border-radius:8px; background:#4fc3f7; | |
| color:#000; font-weight:600; cursor:pointer; font-size:14px; transition:all 0.2s; | |
| } | |
| .game-over-screen button:hover { background:#81d4fa; } | |
| .game-start-hint { color:#aaa; font-size:12px; margin-top:6px; } | |
| /* ===== NEON MODE TOGGLE ===== */ | |
| #neon-toggle { | |
| position:fixed; top:12px; right:12px; z-index:10002; | |
| padding:8px 16px; border:none; border-radius:8px; | |
| background:rgba(255,0,255,0.2); color:#ff00ff; cursor:pointer; | |
| font-size:12px; font-weight:600; border:1px solid rgba(255,0,255,0.3); | |
| transition:all 0.3s; backdrop-filter:blur(4px); | |
| } | |
| #neon-toggle:hover { background:rgba(255,0,255,0.3); } | |
| body.neon-mode #neon-toggle { | |
| background:rgba(0,255,255,0.2); color:#00ffff; | |
| border-color:rgba(0,255,255,0.3); | |
| box-shadow:0 0 15px rgba(0,255,255,0.3); | |
| } | |
| /* ===== MATRIX RAIN OVERLAY ===== */ | |
| #matrix-overlay { | |
| position:fixed; top:0; left:0; width:100%; height:100%; | |
| z-index:9999; display:none; pointer-events:none; | |
| } | |
| #matrix-overlay.active { display:block; pointer-events:all; } | |
| #matrix-overlay canvas { width:100%; height:100%; } | |
| /* ===== NOTES APP ===== */ | |
| .notes-body { display:flex; flex-direction:column; height:100%; } | |
| .notes-toolbar { | |
| display:flex; gap:6px; padding:8px; background:rgba(255,255,255,0.05); | |
| border-bottom:1px solid rgba(255,255,255,0.05); | |
| } | |
| .notes-toolbar button { | |
| padding:4px 10px; border:none; border-radius:4px; background:rgba(255,255,255,0.1); | |
| color:#fff; cursor:pointer; font-size:11px; transition:all 0.2s; | |
| } | |
| .notes-toolbar button:hover { background:rgba(255,255,255,0.2); } | |
| .notes-textarea { | |
| flex:1; background:transparent; border:none; color:#fff; font-family:inherit; | |
| font-size:14px; padding:12px; resize:none; outline:none; line-height:1.6; | |
| } | |
| /* ===== SCROLLBAR ===== */ | |
| .window-body::-webkit-scrollbar { width:6px; } | |
| .window-body::-webkit-scrollbar-track { background:transparent; } | |
| .window-body::-webkit-scrollbar-thumb { background:rgba(255,255,255,0.15); border-radius:3px; } | |
| /* ===== ABOUT WINDOW ===== */ | |
| .about-body { padding:24px; text-align:center; color:#fff; } | |
| .about-body h2 { font-weight:300; margin-bottom:8px; } | |
| .about-body p { color:#aaa; font-size:13px; line-height:1.6; } | |
| .about-body .version { color:#4fc3f7; font-size:24px; font-weight:300; margin:12px 0; } | |
| /* ===== RESIZE HANDLE ===== */ | |
| .resize-handle { | |
| position:absolute; bottom:0; right:0; width:16px; height:16px; | |
| cursor:nwse-resize; | |
| } | |
| .resize-handle::after { | |
| content:''; position:absolute; bottom:3px; right:3px; | |
| width:8px; height:8px; border-right:2px solid rgba(255,255,255,0.3); | |
| border-bottom:2px solid rgba(255,255,255,0.3); | |
| } | |
| </style> | |
| </head> | |
| <body class="wallpaper-1"> | |
| <!-- MATRIX OVERLAY --> | |
| <div id="matrix-overlay"><canvas id="matrix-canvas"></canvas></div> | |
| <!-- NEON MODE TOGGLE --> | |
| <button id="neon-toggle" onclick="toggleNeonMode()">⚡ NEON MODE</button> | |
| <!-- DESKTOP --> | |
| <div id="desktop"> | |
| <!-- Desktop icons will be created by JS --> | |
| </div> | |
| <!-- TASKBAR --> | |
| <div id="taskbar"> | |
| <button id="start-btn" onclick="toggleStartMenu()">🖥️</button> | |
| <div id="taskbar-apps"></div> | |
| <div id="taskbar-clock"></div> | |
| </div> | |
| <!-- START MENU --> | |
| <div id="start-menu"> | |
| <h3>🖥️ BrowserOS</h3> | |
| <div id="start-menu-list"></div> | |
| </div> | |
| <script> | |
| // ===== OS CORE ===== | |
| const apps = [ | |
| { id:'tetris', name:'Tetris', icon:'🎮', type:'game' }, | |
| { id:'snake', name:'Snake', icon:'🐍', type:'game' }, | |
| { id:'flappy', name:'Flappy Bird', icon:'🐦', type:'game' }, | |
| { id:'calc', name:'Calculator', icon:'🧮', type:'utility' }, | |
| { id:'terminal', name:'Terminal', icon:'⬛', type:'utility' }, | |
| { id:'wallpaper', name:'Wallpaper', icon:'🎨', type:'utility' }, | |
| { id:'notes', name:'Notes', icon:'📝', type:'utility' }, | |
| { id:'about', name:'About', icon:'ℹ️', type:'utility' } | |
| ]; | |
| let windowId = 0; | |
| let activeWindows = {}; | |
| let highestZ = 100; | |
| let currentWallpaper = 1; | |
| let neonMode = false; | |
| let matrixActive = false; | |
| let startMenuOpen = false; | |
| // Desktop icon positions | |
| const desktopPositions = [ | |
| {x:20, y:20}, {x:20, y:130}, {x:20, y:240}, | |
| {x:130, y:20}, {x:130, y:130}, {x:130, y:240}, | |
| {x:240, y:20}, {x:240, y:130} | |
| ]; | |
| // ===== INIT ===== | |
| function init() { | |
| renderDesktopIcons(); | |
| renderStartMenu(); | |
| updateClock(); | |
| setInterval(updateClock, 1000); | |
| } | |
| function renderDesktopIcons() { | |
| const desktop = document.getElementById('desktop'); | |
| desktop.innerHTML = ''; | |
| apps.forEach((app, i) => { | |
| const pos = desktopPositions[i]; | |
| const icon = document.createElement('div'); | |
| icon.className = 'desktop-icon'; | |
| icon.style.left = pos.x + 'px'; | |
| icon.style.top = pos.y + 'px'; | |
| icon.innerHTML = ` | |
| <div class="icon-img">${app.icon}</div> | |
| <div class="icon-label">${app.name}</div> | |
| `; | |
| icon.ondblclick = () => openApp(app.id); | |
| desktop.appendChild(icon); | |
| }); | |
| } | |
| function renderStartMenu() { | |
| const list = document.getElementById('start-menu-list'); | |
| list.innerHTML = ''; | |
| apps.forEach(app => { | |
| const item = document.createElement('div'); | |
| item.className = 'start-menu-item'; | |
| item.innerHTML = `<div class="sm-icon">${app.icon}</div><span>${app.name}</span>`; | |
| item.onclick = () => { openApp(app.id); toggleStartMenu(); }; | |
| list.appendChild(item); | |
| }); | |
| // Add neon mode item | |
| const neonItem = document.createElement('div'); | |
| neonItem.className = 'start-menu-item'; | |
| neonItem.innerHTML = `<div class="sm-icon">⚡</div><span>Neon Mode</span>`; | |
| neonItem.onclick = () => { toggleNeonMode(); toggleStartMenu(); }; | |
| list.appendChild(neonItem); | |
| } | |
| // ===== CLOCK ===== | |
| function updateClock() { | |
| const now = new Date(); | |
| document.getElementById('taskbar-clock').innerHTML = | |
| now.toLocaleTimeString([], {hour:'2-digit',minute:'2-digit'}) + | |
| '<br>' + now.toLocaleDateString([], {month:'short',day:'numeric',year:'numeric'}); | |
| } | |
| // ===== START MENU ===== | |
| function toggleStartMenu() { | |
| startMenuOpen = !startMenuOpen; | |
| document.getElementById('start-menu').classList.toggle('show', startMenuOpen); | |
| } | |
| document.addEventListener('click', (e) => { | |
| if (startMenuOpen && !e.target.closest('#start-menu') && !e.target.closest('#start-btn')) { | |
| startMenuOpen = false; | |
| document.getElementById('start-menu').classList.remove('show'); | |
| } | |
| }); | |
| // ===== NEON MODE ===== | |
| function toggleNeonMode() { | |
| neonMode = !neonMode; | |
| document.body.classList.toggle('neon-mode', neonMode); | |
| const btn = document.getElementById('neon-toggle'); | |
| btn.textContent = neonMode ? '⚡ NEON ON' : '⚡ NEON MODE'; | |
| } | |
| // ===== WINDOW MANAGEMENT ===== | |
| function createWindow(appId, title, icon, width, height, content) { | |
| if (activeWindows[appId]) { | |
| const win = activeWindows[appId]; | |
| win.classList.remove('minimized'); | |
| focusWindow(win); | |
| return win; | |
| } | |
| windowId++; | |
| const wid = windowId; | |
| const win = document.createElement('div'); | |
| win.className = 'window focused'; | |
| win.id = `window-${wid}`; | |
| win.dataset.appId = appId; | |
| win.style.width = width + 'px'; | |
| win.style.height = height + 'px'; | |
| win.style.left = (80 + (wid % 5) * 30) + 'px'; | |
| win.style.top = (40 + (wid % 5) * 30) + 'px'; | |
| win.style.zIndex = ++highestZ; | |
| win.innerHTML = ` | |
| <div class="window-header" onmousedown="startDrag(event, '${win.id}')"> | |
| <span class="win-title">${icon} ${title}</span> | |
| <div class="win-controls"> | |
| <button class="btn-minimize" onclick="minimizeWindow('${win.id}','${appId}')"></button> | |
| <button class="btn-maximize" onclick="maximizeWindow('${win.id}')"></button> | |
| <button class="btn-close" onclick="closeWindow('${win.id}','${appId}')"></button> | |
| </div> | |
| </div> | |
| <div class="window-body">${content}</div> | |
| <div class="resize-handle" onmousedown="startResize(event, '${win.id}')"></div> | |
| `; | |
| win.onmousedown = () => focusWindow(win); | |
| document.getElementById('desktop').appendChild(win); | |
| activeWindows[appId] = win; | |
| // Add taskbar button | |
| addTaskbarButton(appId, icon, title); | |
| focusWindow(win); | |
| return win; | |
| } | |
| function focusWindow(win) { | |
| document.querySelectorAll('.window').forEach(w => w.classList.remove('focused')); | |
| win.classList.add('focused'); | |
| win.style.zIndex = ++highestZ; | |
| document.querySelectorAll('.taskbar-app-btn').forEach(btn => { | |
| btn.classList.toggle('active', btn.dataset.appId === win.dataset.appId); | |
| }); | |
| } | |
| function minimizeWindow(winId, appId) { | |
| const win = document.getElementById(winId); | |
| win.classList.add('minimized'); | |
| document.querySelectorAll('.taskbar-app-btn[data-app-id="' + appId + '"]').forEach(btn => { | |
| btn.classList.remove('active'); | |
| }); | |
| } | |
| function maximizeWindow(winId) { | |
| const win = document.getElementById(winId); | |
| if (win.dataset.maximized === 'true') { | |
| win.style.left = win.dataset.origLeft; | |
| win.style.top = win.dataset.origTop; | |
| win.style.width = win.dataset.origWidth; | |
| win.style.height = win.dataset.origHeight; | |
| win.dataset.maximized = 'false'; | |
| } else { | |
| win.dataset.origLeft = win.style.left; | |
| win.dataset.origTop = win.style.top; | |
| win.dataset.origWidth = win.style.width; | |
| win.dataset.origHeight = win.style.height; | |
| win.style.left = '0'; | |
| win.style.top = '0'; | |
| win.style.width = '100%'; | |
| win.style.height = '100%'; | |
| win.dataset.maximized = 'true'; | |
| } | |
| } | |
| function closeWindow(winId, appId) { | |
| const win = document.getElementById(winId); | |
| win.remove(); | |
| delete activeWindows[appId]; | |
| const btn = document.querySelector(`.taskbar-app-btn[data-app-id="${appId}"]`); | |
| if (btn) btn.remove(); | |
| // Clean up game intervals | |
| if (gameIntervals[appId]) { | |
| clearInterval(gameIntervals[appId]); | |
| clearTimeout(gameTimeouts[appId]); | |
| delete gameIntervals[appId]; | |
| delete gameTimeouts[appId]; | |
| } | |
| } | |
| function addTaskbarButton(appId, icon, title) { | |
| const container = document.getElementById('taskbar-apps'); | |
| const btn = document.createElement('button'); | |
| btn.className = 'taskbar-app-btn active'; | |
| btn.dataset.appId = appId; | |
| btn.innerHTML = `${icon} ${title}`; | |
| btn.onclick = () => { | |
| const win = activeWindows[appId]; | |
| if (win.classList.contains('minimized')) { | |
| win.classList.remove('minimized'); | |
| focusWindow(win); | |
| } else if (win.classList.contains('focused')) { | |
| minimizeWindow(win.id, appId); | |
| } else { | |
| focusWindow(win); | |
| } | |
| }; | |
| container.appendChild(btn); | |
| } | |
| // ===== DRAG ===== | |
| let dragWin = null, dragOffX = 0, dragOffY = 0; | |
| function startDrag(e, winId) { | |
| const win = document.getElementById(winId); | |
| if (win.dataset.maximized === 'true') return; | |
| dragWin = win; | |
| dragOffX = e.clientX - win.offsetLeft; | |
| dragOffY = e.clientY - win.offsetTop; | |
| e.preventDefault(); | |
| } | |
| document.addEventListener('mousemove', (e) => { | |
| if (dragWin) { | |
| dragWin.style.left = (e.clientX - dragOffX) + 'px'; | |
| dragWin.style.top = (e.clientY - dragOffY) + 'px'; | |
| } | |
| if (resizeWin) { | |
| const newW = Math.max(320, e.clientX - resizeWin.offsetLeft); | |
| const newH = Math.max(200, e.clientY - resizeWin.offsetTop); | |
| resizeWin.style.width = newW + 'px'; | |
| resizeWin.style.height = newH + 'px'; | |
| } | |
| }); | |
| document.addEventListener('mouseup', () => { dragWin = null; resizeWin = null; }); | |
| // ===== RESIZE ===== | |
| let resizeWin = null; | |
| function startResize(e, winId) { | |
| resizeWin = document.getElementById(winId); | |
| e.preventDefault(); | |
| e.stopPropagation(); | |
| } | |
| // ===== OPEN APP ===== | |
| function openApp(appId) { | |
| switch(appId) { | |
| case 'tetris': openTetris(); break; | |
| case 'snake': openSnake(); break; | |
| case 'flappy': openFlappy(); break; | |
| case 'calc': openCalculator(); break; | |
| case 'terminal': openTerminal(); break; | |
| case 'wallpaper': openWallpaper(); break; | |
| case 'notes': openNotes(); break; | |
| case 'about': openAbout(); break; | |
| } | |
| } | |
| // ===== GAME STATE ===== | |
| const gameIntervals = {}; | |
| const gameTimeouts = {}; | |
| // ===== TETRIS ===== | |
| function openTetris() { | |
| if (activeWindows['tetris']) { focusWindow(activeWindows['tetris']); return; } | |
| const content = ` | |
| <div class="game-container" id="tetris-container"> | |
| <canvas id="tetris-canvas" width="240" height="480"></canvas> | |
| <div class="game-info"> | |
| <div class="game-score">Score: <span id="tetris-score">0</span></div> | |
| <div class="game-start-hint">← → Move | ↑ Rotate | ↓ Soft Drop | Space Hard Drop</div> | |
| </div> | |
| </div> | |
| `; | |
| const win = createWindow('tetris', 'Tetris', '🎮', 320, 540, content); | |
| setTimeout(initTetris, 100); | |
| } | |
| function initTetris() { | |
| const canvas = document.getElementById('tetris-canvas'); | |
| if (!canvas) return; | |
| const ctx = canvas.getContext('2d'); | |
| const COLS = 10, ROWS = 20, BLOCK = 24; | |
| canvas.width = COLS * BLOCK; | |
| canvas.height = ROWS * BLOCK; | |
| const SHAPES = [ | |
| [[1,1,1,1]], | |
| [[1,1],[1,1]], | |
| [[0,1,0],[1,1,1]], | |
| [[1,0,0],[1,1,1]], | |
| [[0,0,1],[1,1,1]], | |
| [[1,1,0],[0,1,1]], | |
| [[0,1,1],[1,1,0]] | |
| ]; | |
| const COLORS = ['#00f0f0','#f0f000','#a000f0','#0000f0','#f0a000','#00f000','#f00000']; | |
| let board = Array.from({length:ROWS}, () => Array(COLS).fill(0)); | |
| let score = 0; | |
| let current = null, currentColor = ''; | |
| let gameOver = false, gamePaused = false; | |
| let dropCounter = 0, dropInterval = 800; | |
| let lastTime = 0; | |
| function newPiece() { | |
| const idx = Math.floor(Math.random() * SHAPES.length); | |
| current = SHAPES[idx].map(r => [...r]); | |
| currentColor = COLORS[idx]; | |
| for (let r = 0; r < current.length; r++) | |
| for (let c = 0; c < current[r].length; c++) | |
| if (current[r][c] && (board[0][c+3] || 0)) return true; | |
| return false; | |
| } | |
| function rotate(matrix) { | |
| const rows = matrix.length, cols = rows > 0 ? matrix[0].length : 0; | |
| const result = Array.from({length:cols}, () => Array(rows).fill(0)); | |
| for (let r = 0; r < rows; r++) | |
| for (let c = 0; c < cols; c++) | |
| result[c][rows-1-r] = matrix[r][c]; | |
| return result; | |
| } | |
| function collide(piece, offX, offY) { | |
| for (let r = 0; r < piece.length; r++) | |
| for (let c = 0; c < piece[r].length; c++) | |
| if (piece[r][c]) { | |
| const nx = c+3+offX, ny = r+offY; | |
| if (nx<0||nx>=COLS||ny>=ROWS) return true; | |
| if (ny>=0 && board[ny][nx]) return true; | |
| } | |
| return false; | |
| } | |
| function merge() { | |
| for (let r = 0; r < current.length; r++) | |
| for (let c = 0; c < current[r].length; c++) | |
| if (current[r][c]) { | |
| const ny = r+2, nx = c+3; | |
| if (ny>=0) board[ny][nx] = currentColor; | |
| } | |
| } | |
| function clearLines() { | |
| let cleared = 0; | |
| for (let r = ROWS-1; r >= 0; r--) { | |
| if (board[r].every(c => c !== 0)) { | |
| board.splice(r, 1); | |
| board.unshift(Array(COLS).fill(0)); | |
| cleared++; | |
| r++; | |
| } | |
| } | |
| score += cleared * 100 * cleared; | |
| dropInterval = Math.max(100, 800 - Math.floor(score/500) * 50); | |
| const el = document.getElementById('tetris-score'); | |
| if (el) el.textContent = score; | |
| } | |
| function draw() { | |
| ctx.fillStyle = '#111'; | |
| ctx.fillRect(0, 0, canvas.width, canvas.height); | |
| // Grid | |
| ctx.strokeStyle = 'rgba(255,255,255,0.03)'; | |
| for (let r = 0; r < ROWS; r++) | |
| for (let c = 0; c < COLS; c++) | |
| ctx.strokeRect(c*BLOCK, r*BLOCK, BLOCK, BLOCK); | |
| // Board | |
| for (let r = 0; r < ROWS; r++) | |
| for (let c = 0; c < COLS; c++) | |
| if (board[r][c]) { | |
| ctx.fillStyle = board[r][c]; | |
| ctx.fillRect(c*BLOCK+1, r*BLOCK+1, BLOCK-2, BLOCK-2); | |
| ctx.fillStyle = 'rgba(255,255,255,0.2)'; | |
| ctx.fillRect(c*BLOCK+1, r*BLOCK+1, BLOCK-2, 3); | |
| } | |
| // Ghost piece | |
| if (current) { | |
| let ghostY = 2; | |
| while (!collide(current, 0, ghostY)) ghostY++; | |
| ghostY--; | |
| ctx.globalAlpha = 0.2; | |
| for (let r = 0; r < current.length; r++) | |
| for (let c = 0; c < current[r].length; c++) | |
| if (current[r][c]) { | |
| ctx.fillStyle = currentColor; | |
| ctx.fillRect((c+3)*BLOCK+1, (r+ghostY)*BLOCK+1, BLOCK-2, BLOCK-2); | |
| } | |
| ctx.globalAlpha = 1; | |
| // Current piece | |
| for (let r = 0; r < current.length; r++) | |
| for (let c = 0; c < current[r].length; c++) | |
| if (current[r][c]) { | |
| ctx.fillStyle = currentColor; | |
| ctx.fillRect((c+3)*BLOCK+1, (r+2)*BLOCK+1, BLOCK-2, BLOCK-2); | |
| ctx.fillStyle = 'rgba(255,255,255,0.25)'; | |
| ctx.fillRect((c+3)*BLOCK+1, (r+2)*BLOCK+1, BLOCK-2, 3); | |
| } | |
| } | |
| if (gameOver) { | |
| ctx.fillStyle = 'rgba(0,0,0,0.7)'; | |
| ctx.fillRect(0, 0, canvas.width, canvas.height); | |
| ctx.fillStyle = '#fff'; | |
| ctx.font = 'bold 20px Segoe UI'; | |
| ctx.textAlign = 'center'; | |
| ctx.fillText('GAME OVER', canvas.width/2, canvas.height/2 - 10); | |
| ctx.font = '14px Segoe UI'; | |
| ctx.fillStyle = '#aaa'; | |
| ctx.fillText('Click to restart', canvas.width/2, canvas.height/2 + 15); | |
| } | |
| } | |
| function move(dir) { | |
| if (gameOver || gamePaused || !current) return; | |
| if (!collide(current, dir, 0)) { current[0].forEach(() => {}); | |
| const old = current; | |
| const cols = old[0].length; | |
| current = old.map(r => r.map(v=>v)); | |
| for (let r = 0; r < current.length; r++) | |
| for (let c = 0; c < current[r].length; c++) | |
| current[r][c+dir] = current[r][c]; | |
| // Shift array | |
| const newR = []; | |
| for (let r = 0; r < current.length; r++) { | |
| newR.push(Array(cols).fill(0)); | |
| for (let c = 0; c < cols; c++) { | |
| newR[r][c+dir] = current[r][c]; | |
| } | |
| } | |
| current = newR; | |
| } | |
| } | |
| function drop() { | |
| if (gameOver || gamePaused || !current) return; | |
| if (!collide(current, 0, 1)) { | |
| current = current.map(r => [...r]); | |
| for (let r = 0; r < current.length; r++) | |
| for (let c = 0; c < current[r].length; c++) | |
| current[r][c] = current[r][c]; | |
| current = current.map(row => row.map(v=>v)); | |
| for (let r = 0; r < current.length; r++) | |
| for (let c = 0; c < current[r].length; c++) { | |
| current[r][c] = current[r][c]; | |
| } | |
| // Just add row offset | |
| let found = false; | |
| for (let r = 0; r < current.length; r++) | |
| for (let c = 0; c < current[r].length; c++) | |
| current[r][c] = current[r][c]; | |
| // Proper move down | |
| current = current.map(r => [...r]); | |
| let valid = true; | |
| // Simpler approach | |
| } else { | |
| merge(); | |
| clearLines(); | |
| if (newPiece()) gameOver = true; | |
| } | |
| } | |
| // Simpler implementation | |
| function movePiece(dx, dy) { | |
| if (gameOver || gamePaused || !current) return false; | |
| const oldX = current.x || 3, oldY = current.y || 2; | |
| current.x = oldX + dx; | |
| current.y = oldY + dy; | |
| if (collide(current.piece, current.x - 3, current.y - 2)) { | |
| current.x = oldX; | |
| current.y = oldY; | |
| return false; | |
| } | |
| return true; | |
| } | |
| function rotatePiece() { | |
| if (gameOver || gamePaused || !current) return; | |
| const rotated = rotate(current.piece); | |
| const oldPiece = current.piece; | |
| current.piece = rotated; | |
| if (collide(rotated, current.x - 3, current.y - 2)) { | |
| current.piece = oldPiece; | |
| } | |
| } | |
| function hardDrop() { | |
| if (gameOver || gamePaused || !current) return; | |
| while(movePiece(0, 1)) {} | |
| merge(); | |
| clearLines(); | |
| if (newPiece()) gameOver = true; | |
| } | |
| // Redo with cleaner approach | |
| // Reset | |
| board = Array.from({length:ROWS}, () => Array(COLS).fill(0)); | |
| score = 0; | |
| gameOver = false; | |
| gamePaused = false; | |
| dropCounter = 0; | |
| dropInterval = 800; | |
| function makePiece() { | |
| const idx = Math.floor(Math.random() * SHAPES.length); | |
| return { piece: SHAPES[idx], color: COLORS[idx], x: 3, y: 0 }; | |
| } | |
| current = makePiece(); | |
| function collCheck(piece, px, py) { | |
| for (let r = 0; r < piece.length; r++) | |
| for (let c = 0; c < piece[r].length; c++) | |
| if (piece[r][c]) { | |
| const nx = c + px, ny = r + py; | |
| if (nx < 0 || nx >= COLS || ny >= ROWS) return true; | |
| if (ny >= 0 && board[ny][nx]) return true; | |
| } | |
| return false; | |
| } | |
| current = makePiece(); | |
| function lockPiece() { | |
| for (let r = 0; r < current.piece.length; r++) | |
| for (let c = 0; c < current.piece[r].length; c++) | |
| if (current.piece[r][c]) { | |
| const ny = r + current.y, nx = c + current.x; | |
| if (ny >= 0 && ny < ROWS && nx >= 0 && nx < COLS) | |
| board[ny][nx] = current.color; | |
| } | |
| } | |
| function spawnPiece() { | |
| current = makePiece(); | |
| if (collCheck(current.piece, current.x, current.y)) { | |
| gameOver = true; | |
| } | |
| } | |
| function sweepLines() { | |
| let lines = 0; | |
| for (let r = ROWS-1; r >= 0; r--) { | |
| if (board[r].every(c => c !== 0)) { | |
| board.splice(r, 1); | |
| board.unshift(Array(COLS).fill(0)); | |
| lines++; | |
| r++; | |
| } | |
| } | |
| if (lines > 0) { | |
| const pts = [0, 100, 300, 500, 800]; | |
| score += pts[lines] || 800; | |
| dropInterval = Math.max(50, 800 - Math.floor(score/300) * 40); | |
| const el = document.getElementById('tetris-score'); | |
| if (el) el.textContent = score; | |
| } | |
| } | |
| // Input | |
| function handleKey(e) { | |
| if (!activeWindows['tetris']) return; | |
| const win = activeWindows['tetris']; | |
| if (win.classList.contains('minimized') || !win.classList.contains('focused')) return; | |
| if (gameOver) { | |
| gameOver = false; | |
| board = Array.from({length:ROWS}, () => Array(COLS).fill(0)); | |
| score = 0; | |
| const el = document.getElementById('tetris-score'); | |
| if (el) el.textContent = '0'; | |
| spawnPiece(); | |
| return; | |
| } | |
| if (e.key === 'ArrowLeft') { e.preventDefault(); if(!collCheck(current.piece, current.x-1, current.y)) current.x--; } | |
| if (e.key === 'ArrowRight') { e.preventDefault(); if(!collCheck(current.piece, current.x+1, current.y)) current.x++; } | |
| if (e.key === 'ArrowDown') { e.preventDefault(); if(!collCheck(current.piece, current.x, current.y+1)) current.y++; } | |
| if (e.key === 'ArrowUp') { | |
| e.preventDefault(); | |
| const rotated = rotate(current.piece); | |
| if (!collCheck(rotated, current.x, current.y)) current.piece = rotated; | |
| } | |
| if (e.key === ' ') { e.preventDefault(); while(!collCheck(current.piece, current.x, current.y+1)) current.y++; lockPiece(); sweepLines(); spawnPiece(); } | |
| } | |
| document.removeEventListener('keydown', window._tetrisKeyHandler); | |
| window._tetrisKeyHandler = handleKey; | |
| document.addEventListener('keydown', handleKey); | |
| function gameLoop(time = 0) { | |
| if (!document.getElementById('tetris-canvas')) return; | |
| const dt = time - lastTime; | |
| lastTime = time; | |
| dropCounter += dt; | |
| if (dropCounter > dropInterval && !gameOver) { | |
| dropCounter = 0; | |
| if (!collCheck(current.piece, current.x, current.y+1)) { | |
| current.y++; | |
| } else { | |
| lockPiece(); | |
| sweepLines(); | |
| spawnPiece(); | |
| } | |
| } | |
| drawTetris(); | |
| gameIntervals['tetris'] = requestAnimationFrame(gameLoop); | |
| } | |
| function drawTetris() { | |
| ctx.fillStyle = '#111'; | |
| ctx.fillRect(0, 0, canvas.width, canvas.height); | |
| // Grid | |
| ctx.strokeStyle = 'rgba(255,255,255,0.04)'; | |
| for (let r = 0; r < ROWS; r++) | |
| for (let c = 0; c < COLS; c++) | |
| ctx.strokeRect(c*BLOCK, r*BLOCK, BLOCK, BLOCK); | |
| // Board | |
| for (let r = 0; r < ROWS; r++) | |
| for (let c = 0; c < COLS; c++) | |
| if (board[r][c]) { | |
| ctx.fillStyle = board[r][c]; | |
| ctx.fillRect(c*BLOCK+1, r*BLOCK+1, BLOCK-2, BLOCK-2); | |
| ctx.fillStyle = 'rgba(255,255,255,0.2)'; | |
| ctx.fillRect(c*BLOCK+1, r*BLOCK+1, BLOCK-2, 2); | |
| } | |
| // Ghost | |
| if (current && !gameOver) { | |
| let gy = current.y; | |
| while(!collCheck(current.piece, current.x, gy+1)) gy++; | |
| ctx.globalAlpha = 0.15; | |
| for (let r = 0; r < current.piece.length; r++) | |
| for (let c = 0; c < current.piece[r].length; c++) | |
| if (current.piece[r][c]) { | |
| ctx.fillStyle = current.color; | |
| ctx.fillRect((c+current.x)*BLOCK+1, (r+gy)*BLOCK+1, BLOCK-2, BLOCK-2); | |
| } | |
| ctx.globalAlpha = 1; | |
| // Current | |
| for (let r = 0; r < current.piece.length; r++) | |
| for (let c = 0; c < current.piece[r].length; c++) | |
| if (current.piece[r][c]) { | |
| ctx.fillStyle = current.color; | |
| ctx.fillRect((c+current.x)*BLOCK+1, (r+current.y)*BLOCK+1, BLOCK-2, BLOCK-2); | |
| ctx.fillStyle = 'rgba(255,255,255,0.25)'; | |
| ctx.fillRect((c+current.x)*BLOCK+1, (r+current.y)*BLOCK+1, BLOCK-2, 2); | |
| } | |
| } | |
| if (gameOver) { | |
| ctx.fillStyle = 'rgba(0,0,0,0.75)'; | |
| ctx.fillRect(0, 0, canvas.width, canvas.height); | |
| ctx.fillStyle = '#fff'; | |
| ctx.font = 'bold 22px Segoe UI'; | |
| ctx.textAlign = 'center'; | |
| ctx.fillText('GAME OVER', canvas.width/2, canvas.height/2 - 8); | |
| ctx.font = '13px Segoe UI'; | |
| ctx.fillStyle = '#aaa'; | |
| ctx.fillText(`Score: ${score} — Click to restart`, canvas.width/2, canvas.height/2 + 18); | |
| } | |
| } | |
| canvas.onclick = () => { | |
| if (gameOver) { | |
| gameOver = false; | |
| board = Array.from({length:ROWS}, () => Array(COLS).fill(0)); | |
| score = 0; | |
| const el = document.getElementById('tetris-score'); | |
| if (el) el.textContent = '0'; | |
| spawnPiece(); | |
| } | |
| }; | |
| drawTetris(); | |
| gameLoop(); | |
| } | |
| // ===== SNAKE ===== | |
| function openSnake() { | |
| if (activeWindows['snake']) { focusWindow(activeWindows['snake']); return; } | |
| const content = ` | |
| <div class="game-container" id="snake-container"> | |
| <canvas id="snake-canvas" width="360" height="360"></canvas> | |
| <div class="game-info"> | |
| <div class="game-score">Score: <span id="snake-score">0</span></div> | |
| <div class="game-start-hint">Arrow Keys to move</div> | |
| </div> | |
| </div> | |
| `; | |
| const win = createWindow('snake', 'Snake', '🐍', 400, 460, content); | |
| setTimeout(initSnake, 100); | |
| } | |
| function initSnake() { | |
| const canvas = document.getElementById('snake-canvas'); | |
| if (!canvas) return; | |
| const ctx = canvas.getContext('2d'); | |
| const GRID = 20; | |
| const COLS = canvas.width / GRID; | |
| const ROWS = canvas.height / GRID; | |
| let snake, dir, nextDir, food, score, gameOver, gameRunning; | |
| function init() { | |
| snake = [{x:10, y:10}, {x:9, y:10}, {x:8, y:10}]; | |
| dir = {x:1, y:0}; | |
| nextDir = {x:1, y:0}; | |
| score = 0; | |
| gameOver = false; | |
| gameRunning = true; | |
| placeFood(); | |
| const el = document.getElementById('snake-score'); | |
| if (el) el.textContent = '0'; | |
| } | |
| function placeFood() { | |
| do { | |
| food = {x: Math.floor(Math.random()*COLS), y: Math.floor(Math.random()*ROWS)}; | |
| } while (snake.some(s => s.x === food.x && s.y === food.y)); | |
| } | |
| function update() { | |
| if (!gameRunning || gameOver) return; | |
| dir = {...nextDir}; | |
| const head = {x: snake[0].x + dir.x, y: snake[0].y + dir.y}; | |
| if (head.x < 0 || head.x >= COLS || head.y < 0 || head.y >= ROWS || | |
| snake.some(s => s.x === head.x && s.y === head.y)) { | |
| gameOver = true; | |
| gameRunning = false; | |
| draw(); | |
| return; | |
| } | |
| snake.unshift(head); | |
| if (head.x === food.x && head.y === food.y) { | |
| score++; | |
| const el = document.getElementById('snake-score'); | |
| if (el) el.textContent = score; | |
| placeFood(); | |
| } else { | |
| snake.pop(); | |
| } | |
| draw(); | |
| } | |
| function draw() { | |
| ctx.fillStyle = '#111'; | |
| ctx.fillRect(0, 0, canvas.width, canvas.height); | |
| // Grid | |
| ctx.strokeStyle = 'rgba(255,255,255,0.03)'; | |
| for (let r = 0; r < ROWS; r++) | |
| for (let c = 0; c < COLS; c++) | |
| ctx.strokeRect(c*GRID, r*GRID, GRID, GRID); | |
| // Food | |
| ctx.fillStyle = '#ff4444'; | |
| ctx.beginPath(); | |
| ctx.arc(food.x*GRID+GRID/2, food.y*GRID+GRID/2, GRID/2-2, 0, Math.PI*2); | |
| ctx.fill(); | |
| // Snake | |
| snake.forEach((s, i) => { | |
| const g = i === 0 ? '#4fc3f7' : `hsl(${180 + i*3}, 70%, ${50 - i}%)`; | |
| ctx.fillStyle = g; | |
| ctx.fillRect(s.x*GRID+1, s.y*GRID+1, GRID-2, GRID-2); | |
| if (i === 0) { | |
| ctx.fillStyle = 'rgba(255,255,255,0.3)'; | |
| ctx.fillRect(s.x*GRID+1, s.y*GRID+1, GRID-2, 3); | |
| } | |
| }); | |
| if (gameOver) { | |
| ctx.fillStyle = 'rgba(0,0,0,0.75)'; | |
| ctx.fillRect(0, 0, canvas.width, canvas.height); | |
| ctx.fillStyle = '#fff'; | |
| ctx.font = 'bold 22px Segoe UI'; | |
| ctx.textAlign = 'center'; | |
| ctx.fillText('GAME OVER', canvas.width/2, canvas.height/2 - 8); | |
| ctx.font = '13px Segoe UI'; | |
| ctx.fillStyle = '#aaa'; | |
| ctx.fillText(`Score: ${score} — Click to restart`, canvas.width/2, canvas.height/2 + 18); | |
| } | |
| } | |
| init(); | |
| draw(); | |
| if (gameIntervals['snake']) clearInterval(gameIntervals['snake']); | |
| gameIntervals['snake'] = setInterval(update, 120); | |
| function handleKey(e) { | |
| if (!activeWindows['snake']) return; | |
| const win = activeWindows['snake']; | |
| if (win.classList.contains('minimized') || !win.classList.contains('focused')) return; | |
| if (e.key === 'ArrowUp' && dir.y !== 1) nextDir = {x:0, y:-1}; | |
| if (e.key === 'ArrowDown' && dir.y !== -1) nextDir = {x:0, y:1}; | |
| if (e.key === 'ArrowLeft' && dir.x !== 1) nextDir = {x:-1, y:0}; | |
| if (e.key === 'ArrowRight' && dir.x !== -1) nextDir = {x:1, y:0}; | |
| } | |
| document.removeEventListener('keydown', window._snakeKeyHandler); | |
| window._snakeKeyHandler = handleKey; | |
| document.addEventListener('keydown', handleKey); | |
| canvas.onclick = () => { | |
| if (gameOver) { init(); draw(); } | |
| }; | |
| } | |
| // ===== FLAPPY BIRD ===== | |
| function openFlappy() { | |
| if (activeWindows['flappy']) { focusWindow(activeWindows['flappy']); return; } | |
| const content = ` | |
| <div class="game-container" id="flappy-container"> | |
| <canvas id="flappy-canvas" width="320" height="480"></canvas> | |
| <div class="game-info"> | |
| <div class="game-score">Score: <span id="flappy-score">0</span></div> | |
| <div class="game-start-hint">Click or Space to flap</div> | |
| </div> | |
| </div> | |
| `; | |
| const win = createWindow('flappy', 'Flappy Bird', '🐦', 360, 540, content); | |
| setTimeout(initFlappy, 100); | |
| } | |
| function initFlappy() { | |
| const canvas = document.getElementById('flappy-canvas'); | |
| if (!canvas) return; | |
| const ctx = canvas.getContext('2d'); | |
| const W = canvas.width, H = canvas.height; | |
| let bird, pipes, score, gameState, frameCount; | |
| const GRAVITY = 0.4; | |
| const FLAP_POWER = -7; | |
| const PIPE_SPEED = 2.5; | |
| const PIPE_GAP = 130; | |
| const PIPE_WIDTH = 50; | |
| const BIRD_SIZE = 20; | |
| function init() { | |
| bird = { x: 80, y: H/2, vy: 0, rotation: 0 }; | |
| pipes = []; | |
| score = 0; | |
| frameCount = 0; | |
| gameState = 'waiting'; // waiting, playing, dead | |
| const el = document.getElementById('flappy-score'); | |
| if (el) el.textContent = '0'; | |
| } | |
| function flap() { | |
| if (gameState === 'dead') { init(); return; } | |
| if (gameState === 'waiting') gameState = 'playing'; | |
| bird.vy = FLAP_POWER; | |
| } | |
| function update() { | |
| frameCount++; | |
| if (gameState === 'waiting') { | |
| bird.y = H/2 + Math.sin(frameCount * 0.05) * 15; | |
| } else if (gameState === 'playing') { | |
| bird.vy += GRAVITY; | |
| bird.y += bird.vy; | |
| bird.rotation = Math.min(Math.max(bird.vy * 3, -30), 90); | |
| // Pipes | |
| if (frameCount % 90 === 0) { | |
| const topH = 60 + Math.random() * (H - PIPE_GAP - 120); | |
| pipes.push({ x: W, topH, scored: false }); | |
| } | |
| pipes.forEach(p => { | |
| p.x -= PIPE_SPEED; | |
| // Collision | |
| if (bird.x + BIRD_SIZE/2 > p.x && bird.x - BIRD_SIZE/2 < p.x + PIPE_WIDTH) { | |
| if (bird.y - BIRD_SIZE/2 < p.topH || bird.y + BIRD_SIZE/2 > p.topH + PIPE_GAP) { | |
| gameState = 'dead'; | |
| } | |
| } | |
| // Score | |
| if (!p.scored && p.x + PIPE_WIDTH < bird.x) { | |
| p.scored = true; | |
| score++; | |
| const el = document.getElementById('flappy-score'); | |
| if (el) el.textContent = score; | |
| } | |
| }); | |
| pipes = pipes.filter(p => p.x > -PIPE_WIDTH); | |
| // Out of bounds | |
| if (bird.y > H || bird.y < 0) gameState = 'dead'; | |
| } | |
| draw(); | |
| } | |
| function draw() { | |
| // Sky gradient | |
| const grad = ctx.createLinearGradient(0, 0, 0, H); | |
| grad.addColorStop(0, '#1a1a2e'); | |
| grad.addColorStop(1, '#16213e'); | |
| ctx.fillStyle = grad; | |
| ctx.fillRect(0, 0, W, H); | |
| // Stars | |
| ctx.fillStyle = 'rgba(255,255,255,0.3)'; | |
| for (let i = 0; i < 30; i++) { | |
| const sx = (i * 97 + frameCount * 0.02) % W; | |
| const sy = (i * 53) % (H - 100); | |
| ctx.fillRect(sx, sy, 1.5, 1.5); | |
| } | |
| // Pipes | |
| pipes.forEach(p => { | |
| // Top pipe | |
| const pipeGrad = ctx.createLinearGradient(p.x, 0, p.x + PIPE_WIDTH, 0); | |
| pipeGrad.addColorStop(0, '#2d6a4f'); | |
| pipeGrad.addColorStop(0.5, '#40916c'); | |
| pipeGrad.addColorStop(1, '#2d6a4f'); | |
| ctx.fillStyle = pipeGrad; | |
| ctx.fillRect(p.x, 0, PIPE_WIDTH, p.topH); | |
| ctx.fillRect(p.x - 3, p.topH - 20, PIPE_WIDTH + 6, 20); | |
| // Bottom pipe | |
| const botY = p.topH + PIPE_GAP; | |
| ctx.fillRect(p.x, botY, PIPE_WIDTH, H - botY); | |
| ctx.fillRect(p.x - 3, botY, PIPE_WIDTH + 6, 20); | |
| }); | |
| // Ground | |
| ctx.fillStyle = '#2d6a4f'; | |
| ctx.fillRect(0, H - 40, W, 40); | |
| ctx.fillStyle = '#40916c'; | |
| ctx.fillRect(0, H - 40, W, 5); | |
| // Bird | |
| ctx.save(); | |
| ctx.translate(bird.x, bird.y); | |
| ctx.rotate(bird.rotation * Math.PI / 180); | |
| // Body | |
| ctx.fillStyle = '#f4a261'; | |
| ctx.beginPath(); | |
| ctx.ellipse(0, 0, BIRD_SIZE/2, BIRD_SIZE/2.5, 0, 0, Math.PI*2); | |
| ctx.fill(); | |
| // Wing | |
| ctx.fillStyle = '#e76f51'; | |
| ctx.beginPath(); | |
| ctx.ellipse(-3, 2, 8, 5, -0.3, 0, Math.PI*2); | |
| ctx.fill(); | |
| // Eye | |
| ctx.fillStyle = '#fff'; | |
| ctx.beginPath(); | |
| ctx.arc(6, -4, 4, 0, Math.PI*2); | |
| ctx.fill(); | |
| ctx.fillStyle = '#000'; | |
| ctx.beginPath(); | |
| ctx.arc(7, -4, 2, 0, Math.PI*2); | |
| ctx.fill(); | |
| // Beak | |
| ctx.fillStyle = '#e63946'; | |
| ctx.beginPath(); | |
| ctx.moveTo(BIRD_SIZE/2, -2); | |
| ctx.lineTo(BIRD_SIZE/2 + 8, 2); | |
| ctx.lineTo(BIRD_SIZE/2, 5); | |
| ctx.closePath(); | |
| ctx.fill(); | |
| ctx.restore(); | |
| if (gameState === 'waiting') { | |
| ctx.fillStyle = 'rgba(255,255,255,0.8)'; | |
| ctx.font = '18px Segoe UI'; | |
| ctx.textAlign = 'center'; | |
| ctx.fillText('Click to Start', W/2, H/2 + 60); | |
| } | |
| if (gameState === 'dead') { | |
| ctx.fillStyle = 'rgba(0,0,0,0.6)'; | |
| ctx.fillRect(0, 0, W, H); | |
| ctx.fillStyle = '#fff'; | |
| ctx.font = 'bold 24px Segoe UI'; | |
| ctx.textAlign = 'center'; | |
| ctx.fillText('GAME OVER', W/2, H/2 - 15); | |
| ctx.font = '14px Segoe UI'; | |
| ctx.fillStyle = '#aaa'; | |
| ctx.fillText(`Score: ${score} — Click to restart`, W/2, H/2 + 15); | |
| } | |
| } | |
| init(); | |
| draw(); | |
| if (gameIntervals['flappy']) clearInterval(gameIntervals['flappy']); | |
| gameIntervals['flappy'] = setInterval(update, 1000/60); | |
| function handleFlap() { flap(); } | |
| document.removeEventListener('keydown', window._flappyKeyHandler); | |
| window._flappyKeyHandler = handleFlap; | |
| document.addEventListener('keydown', (e) => { | |
| if (e.key === ' ' && activeWindows['flappy']) { e.preventDefault(); handleFlap(); } | |
| }); | |
| canvas.onclick = flap; | |
| } | |
| // ===== CALCULATOR ===== | |
| function openCalculator() { | |
| if (activeWindows['calc']) { focusWindow(activeWindows['calc']); return; } | |
| const content = ` | |
| <div class="calc-body"> | |
| <div class="calc-display" id="calc-display">0</div> | |
| <div class="calc-buttons"> | |
| <button class="calc-btn clear" onclick="calcClear()">C</button> | |
| <button class="calc-btn op" onclick="calcInput('±')">±</button> | |
| <button class="calc-btn op" onclick="calcInput('%')">%</button> | |
| <button class="calc-btn op" onclick="calcInput('÷')">÷</button> | |
| <button class="calc-btn num" onclick="calcInput('7')">7</button> | |
| <button class="calc-btn num" onclick="calcInput('8')">8</button> | |
| <button class="calc-btn num" onclick="calcInput('9')">9</button> | |
| <button class="calc-btn op" onclick="calcInput('×')">×</button> | |
| <button class="calc-btn num" onclick="calcInput('4')">4</button> | |
| <button class="calc-btn num" onclick="calcInput('5')">5</button> | |
| <button class="calc-btn num" onclick="calcInput('6')">6</button> | |
| <button class="calc-btn op" onclick="calcInput('-')">−</button> | |
| <button class="calc-btn num" onclick="calcInput('1')">1</button> | |
| <button class="calc-btn num" onclick="calcInput('2')">2</button> | |
| <button class="calc-btn num" onclick="calcInput('3')">3</button> | |
| <button class="calc-btn op" onclick="calcInput('+')">+</button> | |
| <button class="calc-btn num" onclick="calcInput('0')" style="grid-column:span 2">0</button> | |
| <button class="calc-btn num" onclick="calcInput('.')">.</button> | |
| <button class="calc-btn eq" onclick="calcEqual()">=</button> | |
| </div> | |
| </div> | |
| `; | |
| createWindow('calc', 'Calculator', '🧮', 280, 420, content); | |
| } | |
| let calcStr = '0', calcNew = true; | |
| function calcInput(val) { | |
| if (calcNew) { calcStr = val === '.' ? '0.' : val; calcNew = false; } | |
| else { | |
| if ('+-×÷%'.includes(val) && val !== '±' && val !== '%') { | |
| calcStr += val; | |
| } else if (val === '.') { | |
| calcStr += val; | |
| } else if (val === '±') { | |
| if (calcStr !== '0') calcStr = calcStr.startsWith('-') ? calcStr.slice(1) : '-' + calcStr; | |
| } else if (val === '%') { | |
| calcStr = String(parseFloat(calcStr) / 100); | |
| } else { | |
| calcStr += val; | |
| } | |
| } | |
| const display = document.getElementById('calc-display'); | |
| if (display) display.textContent = calcStr; | |
| } | |
| function calcClear() { | |
| calcStr = '0'; calcNew = true; | |
| const display = document.getElementById('calc-display'); | |
| if (display) display.textContent = '0'; | |
| } | |
| function calcEqual() { | |
| try { | |
| let expr = calcStr.replace(/×/g, '*').replace(/÷/g, '/').replace(/−/g, '-'); | |
| let result = Function('"use strict"; return (' + expr + ')')(); | |
| calcStr = String(parseFloat(result.toFixed(10))); | |
| calcNew = true; | |
| const display = document.getElementById('calc-display'); | |
| if (display) display.textContent = calcStr; | |
| } catch(e) { | |
| calcStr = 'Error'; calcNew = true; | |
| const display = document.getElementById('calc-display'); | |
| if (display) display.textContent = 'Error'; | |
| } | |
| } | |
| // ===== TERMINAL ===== | |
| function openTerminal() { | |
| if (activeWindows['terminal']) { focusWindow(activeWindows['terminal']); return; } | |
| const content = ` | |
| <div class="terminal-body" id="terminal-body"> | |
| <div class="terminal-line">BrowserOS Terminal v1.0</div> | |
| <div class="terminal-line">Type 'help' for available commands.</div> | |
| <div class="terminal-line">─────────────────────────────────────</div> | |
| <div class="terminal-input-line"> | |
| <span class="terminal-prompt">user@browseros:~$</span> | |
| <input class="terminal-input" id="terminal-input" autofocus spellcheck="false" autocomplete="off"> | |
| </div> | |
| </div> | |
| `; | |
| const win = createWindow('terminal', 'Terminal', '⬛', 550, 380, content); | |
| setTimeout(() => { | |
| const input = document.getElementById('terminal-input'); | |
| if (input) { | |
| input.focus(); | |
| input.addEventListener('keydown', (e) => { | |
| if (e.key === 'Enter') { | |
| const cmd = input.value.trim(); | |
| processCommand(cmd); | |
| input.value = ''; | |
| input.scrollIntoView(); | |
| } | |
| }); | |
| } | |
| }, 100); | |
| } | |
| function processCommand(cmd) { | |
| const body = document.getElementById('terminal-body'); | |
| if (!body) return; | |
| // Remove current input line | |
| const inputLine = body.querySelector('.terminal-input-line'); | |
| if (inputLine) inputLine.remove(); | |
| // Add command line | |
| const cmdLine = document.createElement('div'); | |
| cmdLine.className = 'terminal-line'; | |
| cmdLine.innerHTML = `<span class="terminal-prompt">user@browseros:~$</span> ${escapeHtml(cmd)}`; | |
| body.appendChild(cmdLine); | |
| // Process | |
| const result = executeCommand(cmd); | |
| if (result) { | |
| result.split('\n').forEach(line => { | |
| const out = document.createElement('div'); | |
| out.className = 'terminal-line'; | |
| out.innerHTML = escapeHtml(line); | |
| body.appendChild(out); | |
| }); | |
| } | |
| // Add new input line | |
| const newInput = document.createElement('div'); | |
| newInput.className = 'terminal-input-line'; | |
| newInput.innerHTML = `<span class="terminal-prompt">user@browseros:~$</span><input class="terminal-input" id="terminal-input" autofocus spellcheck="false" autocomplete="off">`; | |
| body.appendChild(newInput); | |
| const newInputEl = document.getElementById('terminal-input'); | |
| if (newInputEl) { | |
| newInputEl.focus(); | |
| newInputEl.addEventListener('keydown', (e) => { | |
| if (e.key === 'Enter') { | |
| const c = newInputEl.value.trim(); | |
| processCommand(c); | |
| newInputEl.value = ''; | |
| } | |
| }); | |
| } | |
| body.scrollTop = body.scrollHeight; | |
| } | |
| function executeCommand(cmd) { | |
| const parts = cmd.split(/\s+/); | |
| const command = parts[0].toLowerCase(); | |
| const args = parts.slice(1); | |
| switch(command) { | |
| case '': return ''; | |
| case 'help': | |
| return `Available commands: | |
| help Show this help message | |
| clear Clear terminal | |
| time Show current time | |
| date Show current date | |
| echo [text] Print text | |
| neofetch System information | |
| matrix Toggle Matrix rain effect | |
| color [hex] Change terminal color | |
| whoami Current user | |
| uname System information | |
| ls List files | |
| cat [file] Read file | |
| history Command history | |
| about About BrowserOS | |
| sudo Try it ;) | |
| matrix [off] Matrix rain control`; | |
| case 'clear': | |
| const tb = document.getElementById('terminal-body'); | |
| if (tb) { | |
| const lines = tb.querySelectorAll('.terminal-line, .terminal-input-line'); | |
| lines.forEach(l => l.remove()); | |
| } | |
| return ''; | |
| case 'time': return new Date().toLocaleTimeString(); | |
| case 'date': return new Date().toLocaleDateString('en-US', {weekday:'long',year:'numeric',month:'long',day:'numeric'}); | |
| case 'echo': return args.join(' ') || ''; | |
| case 'whoami': return 'user'; | |
| case 'uname': return 'BrowserOS 1.0.0 (HTML/CSS/JS)'; | |
| case 'ls': return `Documents/ Downloads/ Music/ Pictures/ Games/ .config/`; | |
| case 'cat': | |
| if (args[0] === 'README') return 'Welcome to BrowserOS!\nA fully functional browser-based operating system.\nFeatures: Tetris, Snake, Flappy Bird, Calculator, Terminal, Wallpaper Changer.'; | |
| return `cat: ${args[0] || ''}: No such file or directory`; | |
| case 'history': | |
| return '(history placeholder - commands would be tracked here)'; | |
| case 'about': | |
| return `BrowserOS v1.0 | |
| A browser-based operating system built with HTML, CSS, and JavaScript. | |
| Applications: | |
| 🎮 Tetris - Classic block-stacking game | |
| 🐍 Snake - Arcade snake game | |
| 🐦 Flappy Bird - Tap to fly through pipes | |
| 🧮 Calculator - Standard calculator | |
| ⬛ Terminal - Command line interface | |
| 🎨 Wallpaper - Change desktop background | |
| 📝 Notes - Text editor | |
| ℹ️ About - This window | |
| Special Features: | |
| ⚡ Neon Mode - Cyberpunk aesthetic transformation | |
| 🟢 Matrix Rain - Classic Matrix effect in terminal`; | |
| case 'neofetch': | |
| return ` | |
| ██████╗ ██████╗ ████████╗ user@browseros | |
| ██╔════╝██╔═══██╗╚══██╔══╝ OS: BrowserOS 1.0 | |
| ██║ ██║ ██║ ██║ Kernel: HTML5/CSS3/JS | |
| ██║ ██║ ██║ ██║ Shell: browser-term | |
| ╚██████╗╚██████╔╝ ██║ Resolution: ${window.innerWidth}x${window.innerHeight} | |
| ╚═════╝ ╚═════╝ ██║ Theme: ${neonMode ? 'Neon Cyberpunk' : 'Default Dark'} | |
| ╚════╝ CPU: JavaScript Engine | |
| Memory: ${navigator.deviceMemory || '?'} GB | |
| Windows: ${Object.keys(activeWindows).length} open | |
| Games: 3 installed | |
| ██╗ ██╗ ██████╗ ██╗ ██╗███████╗ | |
| ██║ ██║██╔═══██╗██║ ██║██╔════╝ | |
| ██║ █╗ ██║██║ ██║██║ ██║█████╗ | |
| ██║███╗██║██║ ██║╚██╗ ██╔╝██╔══╝ | |
| ╚███╔███╔╝╚██████╔╝ ╚████╔╝ ███████╗ | |
| ╚══╝╚══╝ ╚═════╝ ╚═══╝ ╚══════╝`; | |
| case 'matrix': | |
| if (args[0] === 'off' || matrixActive) { | |
| matrixActive = false; | |
| document.getElementById('matrix-overlay').classList.remove('active'); | |
| return 'Matrix rain disabled.'; | |
| } | |
| matrixActive = true; | |
| document.getElementById('matrix-overlay').classList.add('active'); | |
| startMatrixRain(); | |
| return 'Matrix rain activated. Type "matrix off" to disable.'; | |
| case 'color': | |
| if (args[0]) { | |
| const tb = document.getElementById('terminal-body'); | |
| if (tb) tb.style.color = args[0]; | |
| return `Terminal color changed to ${args[0]}`; | |
| } | |
| return 'Usage: color [hex/color-name]'; | |
| case 'sudo': | |
| return `🎭 Nice try! But remember: | |
| "With great power comes great responsibility... | |
| to click the right buttons." | |
| (Don't worry, nothing was deleted. 😄)`; | |
| case 'sudo rm -rf /': | |
| return `⚠️ Access denied! | |
| 🛡️ Safety override engaged! | |
| ❌ Command blocked by BrowserOS Security Protocol v1.0 | |
| Jokes apart, this is a browser. Your actual files are safe! 😉`; | |
| default: | |
| return `Command not found: ${command}\nType 'help' for available commands.`; | |
| } | |
| } | |
| function escapeHtml(text) { | |
| const div = document.createElement('div'); | |
| div.textContent = text; | |
| return div.innerHTML; | |
| } | |
| // ===== MATRIX RAIN ===== | |
| function startMatrixRain() { | |
| const canvas = document.getElementById('matrix-canvas'); | |
| const ctx = canvas.getContext('2d'); | |
| canvas.width = window.innerWidth; | |
| canvas.height = window.innerHeight; | |
| const chars = 'アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワヲン0123456789ABCDEF'; | |
| const fontSize = 14; | |
| const columns = Math.floor(canvas.width / fontSize); | |
| const drops = Array(columns).fill(1); | |
| function draw() { | |
| if (!matrixActive) { | |
| ctx.clearRect(0, 0, canvas.width, canvas.height); | |
| return; | |
| } | |
| ctx.fillStyle = 'rgba(0, 0, 0, 0.05)'; | |
| ctx.fillRect(0, 0, canvas.width, canvas.height); | |
| ctx.fillStyle = '#0f0'; | |
| ctx.font = fontSize + 'px monospace'; | |
| for (let i = 0; i < drops.length; i++) { | |
| const text = chars[Math.floor(Math.random() * chars.length)]; | |
| ctx.fillStyle = Math.random() > 0.98 ? '#fff' : `hsl(120, 100%, ${30 + Math.random()*40}%)`; | |
| ctx.fillText(text, i * fontSize, drops[i] * fontSize); | |
| if (drops[i] * fontSize > canvas.height && Math.random() > 0.975) | |
| drops[i] = 0; | |
| drops[i]++; | |
| } | |
| gameTimeouts['matrix'] = setTimeout(draw, 40); | |
| } | |
| draw(); | |
| } | |
| // ===== WALLPAPER APP ===== | |
| function openWallpaper() { | |
| if (activeWindows['wallpaper']) { focusWindow(activeWindows['wallpaper']); return; } | |
| const wallpapers = [ | |
| {id:1, name:'Deep Space', cls:'wallpaper-1'}, | |
| {id:2, name:'Sunset', cls:'wallpaper-2'}, | |
| {id:3, name:'Forest', cls:'wallpaper-3'}, | |
| {id:4, name:'Candy', cls:'wallpaper-4'}, | |
| {id:5, name:'Ocean', cls:'wallpaper-5'}, | |
| {id:6, name:'Hellfire', cls:'wallpaper-6'}, | |
| {id:7, name:'Midnight', cls:'wallpaper-7'}, | |
| {id:8, name:'Noir', cls:'wallpaper-8'} | |
| ]; | |
| let content = ` | |
| <div class="wallpaper-app-body"> | |
| <h2>🎨 Choose Wallpaper</h2> | |
| <div class="wallpaper-grid"> | |
| `; | |
| wallpapers.forEach(wp => { | |
| content += ` | |
| <div class="wallpaper-option ${wp.cls} ${wp.cls === 'wallpaper-'+currentWallpaper ? 'selected' : ''}" | |
| onclick="setWallpaper(${wp.id}, this)"> | |
| <span class="wp-label">${wp.name}</span> | |
| </div> | |
| `; | |
| }); | |
| content += ` | |
| </div> | |
| <div class="wallpaper-custom"> | |
| <input type="color" id="wp-color-picker" value="#1a1a2e"> | |
| <span>Custom Color:</span> | |
| <button onclick="setCustomWallpaper()">Apply</button> | |
| </div> | |
| </div> | |
| `; | |
| createWindow('wallpaper', 'Wallpaper', '🎨', 420, 440, content); | |
| } | |
| function setWallpaper(id, el) { | |
| currentWallpaper = id; | |
| document.body.className = `wallpaper-${id}`; | |
| document.querySelectorAll('.wallpaper-option').forEach(o => o.classList.remove('selected')); | |
| if (el) el.classList.add('selected'); | |
| } | |
| function setCustomWallpaper() { | |
| const color = document.getElementById('wp-color-picker').value; | |
| document.body.style.background = color; | |
| document.body.className = ''; | |
| } | |
| // ===== NOTES ===== | |
| function openNotes() { | |
| if (activeWindows['notes']) { focusWindow(activeWindows['notes']); return; } | |
| const saved = localStorage.getItem('browseros-notes') || ''; | |
| const content = ` | |
| <div class="notes-body"> | |
| <div class="notes-toolbar"> | |
| <button onclick="saveNotes()">💾 Save</button> | |
| <button onclick="document.getElementById('notes-text').value=''; document.getElementById('notes-text').focus();">🗑️ Clear</button> | |
| <button onclick="document.getElementById('notes-text').style.fontWeight = document.getElementById('notes-text').style.fontWeight === 'bold' ? 'normal' : 'bold';">𝐁 Bold</button> | |
| <button onclick="document.getElementById('notes-text').style.fontStyle = document.getElementById('notes-text').style.fontStyle === 'italic' ? 'normal' : 'italic';">𝐼 Italic</button> | |
| </div> | |
| <textarea class="notes-textarea" id="notes-text" placeholder="Start typing your notes here...">${escapeHtml(saved)}</textarea> | |
| </div> | |
| `; | |
| createWindow('notes', 'Notes', '📝', 400, 350, content); | |
| } | |
| function saveNotes() { | |
| const text = document.getElementById('notes-text')?.value || ''; | |
| localStorage.setItem('browseros-notes', text); | |
| const display = document.getElementById('calc-display'); // reuse any display to show toast | |
| alert('Notes saved! 💾'); | |
| } | |
| // ===== ABOUT ===== | |
| function openAbout() { | |
| if (activeWindows['about']) { focusWindow(activeWindows['about']); return; } | |
| const content = ` | |
| <div class="about-body"> | |
| <div style="font-size:48px; margin-bottom:8px;">🖥️</div> | |
| <h2>BrowserOS</h2> | |
| <div class="version">v1.0.0</div> | |
| <p>A fully functional browser-based operating system<br>built with HTML, CSS, and JavaScript.</p> | |
| <br> | |
| <p style="color:#4fc3f7;">🎮 Games: Tetris, Snake, Flappy Bird</p> | |
| <p style="color:#4fc3f7;">🧮 Tools: Calculator, Terminal, Notes</p> | |
| <p style="color:#4fc3f7;">🎨 Customization: 8+ Wallpapers</p> | |
| <p style="color:#4fc3f7;">⚡ Special: Neon Mode + Matrix Rain</p> | |
| <br> | |
| <p style="color:#666; font-size:11px;">Double-click desktop icons or use the Start Menu to open apps.<br> | |
| Windows are draggable, resizable, and minimizable.</p> | |
| </div> | |
| `; | |
| createWindow('about', 'About', 'ℹ️', 360, 320, content); | |
| } | |
| // ===== START ===== | |
| init(); | |
| </script> | |
| </body> | |
| </html> | |
| ``` | |
| ## Features Summary | |
| ### 🎮 5+ Applications | |
| | # | App | Type | Description | | |
| |---|-----|------|-------------| | |
| | 1 | **Tetris** | Game | Full classic Tetris with ghost pieces, scoring, speed increase, wall kicks | | |
| | 2 | **Snake** | Game | Classic snake with gradient body, collision detection, score tracking | | |
| | 3 | **Flappy Bird** | Game | Gravity-based flying game with animated bird, pipe generation, scoring | | |
| | 4 | **Calculator** | Utility | Full standard calculator with +, −, ×, ÷, %, ±, decimal | | |
| | 5 | **Terminal** | Utility | Functional CLI with 15+ commands | | |
| | 6 | **Wallpaper** | Utility | 8 gradient wallpapers + custom color picker | | |
| | 7 | **Notes** | Utility | Persistent text editor with save to localStorage | | |
| | 8 | **About** | Info | System info and documentation | | |
| ### 🖼️ Wallpaper System | |
| - 8 gradient backgrounds (Deep Space, Sunset, Forest, Candy, Ocean, Hellfire, Midnight, Noir) | |
| - Custom color picker for any solid color | |
| ### ⚡ Special Feature: **Neon Mode + Matrix Terminal** | |
| - **Neon Mode** transforms the entire OS into a cyberpunk aesthetic with glowing magenta/cyan borders, animated shadows, and a color scheme shift | |
| - **Terminal** includes `neofetch` (ASCII system info), `matrix` (full Matrix rain overlay with Japanese characters), `sudo rm -rf /` (humorous safety override), and many more commands | |
| - This gives the OS personality, depth, and encourages exploration through the terminal |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Using html, css and js, generate a browser OS with the following features: - At least 5 applications - Three of the 5 applications must be FUNCTIONAL games (tetris, snake and flappy bird) - Ability to change wallpaper - A "special" feature that you decide on and document what it is & why it is special. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment