Chat / index.html
darkc0de's picture
Update index.html
1f0fe92 verified
raw
history blame
83.3 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>XortronOS v8 // CyberDesktop Enhanced</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
/* --- Base Styles (Keep previous styles, add/modify as needed) --- */
@import url('https://fonts.googleapis.com/css2?family=Share+Tech+Mono&family=Orbitron:wght@400;700&display=swap');
:root { /* ... keep color variables ... */ }
body { /* ... keep body styles ... */ }
.cyber-font { /* ... */ }
.window { /* ... keep window styles, ensure position: fixed/absolute logic is solid ... */ }
.window-header { /* ... keep header styles ... */ cursor: grab; }
.window-header:active { cursor: grabbing; }
.window-content { /* ... keep content flex/scroll styles ... */ }
.terminal { /* ... keep terminal styles ... */ }
.glow-text { /* ... */ }
.taskbar { /* ... keep taskbar styles ... */ }
.icon { /* ... keep icon styles ... */ }
.file { /* ... keep file styles ... */ cursor: pointer; }
.cursor-blink { /* ... */ }
.pulse { /* ... */ }
::-webkit-scrollbar { /* ... */ }
::-webkit-scrollbar-track { /* ... */ }
::-webkit-scrollbar-thumb { /* ... */ }
.matrix-bg { /* ... */ }
.browser-nav { /* ... */ }
.url-bar { /* ... */ }
.theme-button { /* ... */ }
/* --- New Styles --- */
/* System Monitor */
.progress-bar-container {
background-color: rgba(255, 255, 255, 0.1);
border-radius: 2px; overflow: hidden; height: 16px; border: 1px solid rgba(255, 255, 255, 0.2);
}
.progress-bar {
background: linear-gradient(90deg, var(--primary), var(--secondary));
height: 100%; transition: width 0.5s ease-out; text-align: right; padding-right: 5px; font-size: 10px; line-height: 14px; color: #000;
}
.process-list-item {
background-color: rgba(0,0,0,0.2); padding: 3px 6px; margin-bottom: 4px; border-radius: 2px; display: flex; justify-content: space-between; align-items: center;
}
/* Text Editor */
#editor-textarea {
width: 100%; height: 100%; background: rgba(0,0,0,0.7); border: none; outline: none;
color: var(--light); font-family: 'Share Tech Mono', monospace; padding: 10px; font-size: 14px;
resize: none;
}
.editor-statusbar {
background: rgba(0,0,0,0.5); padding: 3px 10px; font-size: 12px; border-top: 1px solid var(--primary); flex-shrink: 0;
}
/* Code Breaker Game */
.codebreaker-board { display: flex; flex-direction: column; gap: 10px; }
.codebreaker-row { display: flex; align-items: center; gap: 8px; }
.codebreaker-guess-peg { width: 30px; height: 30px; border: 1px solid #ccc; border-radius: 50%; cursor: pointer; transition: background-color 0.2s; }
.codebreaker-feedback-area { display: grid; grid-template-columns: repeat(2, 10px); gap: 3px; width: 23px; height: 23px; border: 1px dashed #555; padding: 1px;}
.codebreaker-feedback-peg { width: 10px; height: 10px; border-radius: 50%; border: 1px solid #333; }
.feedback-black { background-color: #FFF; border-color: #FFF;} /* Correct color and position */
.feedback-white { background-color: #888; border-color: #888;} /* Correct color, wrong position */
.codebreaker-palette { display: flex; gap: 5px; margin-top: 15px; }
.palette-color { width: 25px; height: 25px; border: 2px solid transparent; border-radius: 50%; cursor: pointer; }
.palette-color.selected { border-color: var(--primary); transform: scale(1.1); }
/* File Explorer Enhancements */
.file-explorer-toolbar { padding: 5px; border-bottom: 1px solid var(--primary); flex-shrink: 0; display: flex; gap: 5px; }
.file-explorer-pathbar { padding: 3px 8px; background: rgba(0,0,0,0.3); margin-bottom: 5px; font-size: 12px; }
.file-item .fa-folder { color: #FFCA28; } /* Yellow folder */
.file-item .fa-file-alt { color: #B0BEC5; } /* Grey text file */
.file-item .fa-file-code { color: #42A5F5; } /* Blue code file */
.file-item .fa-lock { color: #EF5350; margin-left: 4px; font-size: 0.8em; } /* Red lock for encrypted */
</style>
</head>
<body class="flex flex-col md:h-screen">
<canvas id="matrix" class="matrix-bg"></canvas>
<div id="desktop-area" class="flex-grow relative overflow-hidden">
<div class="absolute left-0 top-0 p-2 md:p-4 flex flex-col space-y-1 md:space-y-0">
<div class="icon" onclick="openApp('terminal')">
<div class="text-3xl text-center mb-1"><i class="fas fa-terminal" style="color: var(--green);"></i></div> <div class="text-xs text-center">Terminal</div>
</div>
<div class="icon" onclick="openApp('file-explorer')">
<div class="text-3xl text-center mb-1"><i class="fas fa-folder-open text-yellow-500"></i></div> <div class="text-xs text-center">Files</div>
</div>
<div class="icon" onclick="openApp('editor')"> <div class="text-3xl text-center mb-1"><i class="fas fa-edit text-blue-400"></i></div> <div class="text-xs text-center">Editor</div>
</div>
<div class="icon" onclick="openApp('monitor')"> <div class="text-3xl text-center mb-1"><i class="fas fa-tachometer-alt text-red-400"></i></div> <div class="text-xs text-center">Monitor</div>
</div>
<div class="icon" onclick="openApp('hack-tools')">
<div class="text-3xl text-center mb-1"><i class="fas fa-user-secret text-red-500"></i></div> <div class="text-xs text-center">Hack Tools</div>
</div>
<div class="icon" onclick="openApp('network')">
<div class="text-3xl text-center mb-1"><i class="fas fa-network-wired text-blue-500"></i></div> <div class="text-xs text-center">Network</div>
</div>
<div class="icon" onclick="openApp('browser')">
<div class="text-3xl text-center mb-1"><i class="fas fa-globe text-purple-500"></i></div> <div class="text-xs text-center">DarkBrowser</div>
</div>
<div class="icon" onclick="openApp('codebreaker')"> <div class="text-3xl text-center mb-1"><i class="fas fa-puzzle-piece text-green-400"></i></div> <div class="text-xs text-center">CodeBreaker</div>
</div>
<div class="icon" onclick="openApp('settings')">
<div class="text-3xl text-center mb-1"><i class="fas fa-cog text-gray-400"></i></div> <div class="text-xs text-center">Settings</div>
</div>
</div>
<div id="windows-container">
<div id="terminal-window" class="window hidden" style="/* initial desktop styles */">
<div class="window-content p-0">
<div class="terminal p-2 h-full overflow-auto" id="terminal-content">
<div class="output-area mb-2"></div>
<div class="prompt-line flex items-center">
<span class="text-green-500" id="terminal-prompt">root@xortron-os:~#</span>
<input type="text" class="bg-transparent border-none outline-none flex-1 ml-2 text-green-500 terminal-input-field" autofocus autocomplete="off" spellcheck="false">
<span class="cursor-blink"></span>
</div>
</div>
</div>
</div>
<div id="file-explorer-window" class="window hidden flex flex-col" style="/* initial desktop styles */">
<div class="file-explorer-toolbar">
<button onclick="FileExplorer.navigateBack()" class="px-2 py-1 bg-gray-700 hover:bg-gray-600 text-xs"><i class="fas fa-arrow-left mr-1"></i> Back</button>
<button onclick="FileExplorer.createNew('folder')" class="px-2 py-1 bg-gray-700 hover:bg-gray-600 text-xs"><i class="fas fa-folder-plus mr-1"></i> New Folder</button>
<button onclick="FileExplorer.createNew('file')" class="px-2 py-1 bg-gray-700 hover:bg-gray-600 text-xs"><i class="fas fa-file-plus mr-1"></i> New File</button>
<button onclick="FileExplorer.deleteSelected()" class="px-2 py-1 bg-red-800 hover:bg-red-700 text-xs"><i class="fas fa-trash mr-1"></i> Delete</button>
</div>
<div class="file-explorer-pathbar" id="file-explorer-path">/</div>
<div class="window-content p-2 flex-grow"> <div class="grid grid-cols-3 sm:grid-cols-4 md:grid-cols-3 lg:grid-cols-4 gap-3" id="file-explorer-grid">
</div>
</div>
</div>
<div id="editor-window" class="window hidden flex flex-col" style="z-index: 11; top: 6rem; left: 10rem; width: 60%; height: 65%;">
<div class="window-header flex justify-between items-center">
<div class="flex items-center"><div class="w-3 h-3 r-f bg-r-500 mr-2"></div><div class="w-3 h-3 r-f bg-y-500 mr-2"></div><div class="w-3 h-3 r-f bg-g-500 mr-2"></div> <span class="cyber-font ml-2 text-sm md:text-base" id="editor-title">Editor</span></div>
<div class="flex"><button class="px-2" onclick="Editor.save()">Save</button><button class="px-2" onclick="minimizeWindow('editor')">_</button><button class="px-2" onclick="maximizeWindow('editor')"></button><button class="px-2" onclick="closeWindow('editor')">×</button></div>
</div>
<div class="window-content p-0 flex-grow"> <textarea id="editor-textarea" spellcheck="false"></textarea>
</div>
<div class="editor-statusbar" id="editor-statusbar">Ready.</div>
</div>
<div id="monitor-window" class="window hidden flex flex-col" style="z-index: 8; top: 14rem; left: 18rem; width: 450px; height: 350px;">
<div class="window-header flex justify-between items-center">
<div class="flex items-center"><div class="w-3 h-3 r-f bg-r-500 mr-2"></div><div class="w-3 h-3 r-f bg-y-500 mr-2"></div><div class="w-3 h-3 r-f bg-g-500 mr-2"></div> <span class="cyber-font ml-2 text-sm md:text-base">System Monitor</span></div>
<div class="flex"><button class="px-2" onclick="minimizeWindow('monitor')">_</button><button class="px-2" onclick="maximizeWindow('monitor')"></button><button class="px-2" onclick="closeWindow('monitor')">×</button></div>
</div>
<div class="window-content p-4 overflow-auto">
<div class="mb-4">
<div class="flex justify-between items-center mb-1"><span>CPU Usage</span><span id="monitor-cpu-text">0%</span></div>
<div class="progress-bar-container"><div id="monitor-cpu-bar" class="progress-bar" style="width: 0%;"></div></div>
</div>
<div class="mb-4">
<div class="flex justify-between items-center mb-1"><span>RAM Usage</span><span id="monitor-ram-text">0 MB / 16384 MB</span></div>
<div class="progress-bar-container"><div id="monitor-ram-bar" class="progress-bar" style="width: 0%;"></div></div>
</div>
<div>
<h3 class="cyber-font text-lg mb-2 glow-text">Running Processes</h3>
<div id="monitor-process-list" class="space-y-1 text-sm">
</div>
</div>
</div>
</div>
<div id="codebreaker-window" class="window hidden flex flex-col" style="z-index: 9; top: 10rem; left: 22rem; width: 400px; height: 550px;">
<div class="window-header flex justify-between items-center">
<div class="flex items-center"><div class="w-3 h-3 r-f bg-r-500 mr-2"></div><div class="w-3 h-3 r-f bg-y-500 mr-2"></div><div class="w-3 h-3 r-f bg-g-500 mr-2"></div> <span class="cyber-font ml-2 text-sm md:text-base">Code Breaker v1.0</span></div>
<div class="flex"><button class="px-2" onclick="minimizeWindow('codebreaker')">_</button><button class="px-2" onclick="maximizeWindow('codebreaker')"></button><button class="px-2" onclick="closeWindow('codebreaker')">×</button></div>
</div>
<div class="window-content p-4 overflow-auto flex flex-col items-center">
<h3 class="cyber-font text-lg mb-2 glow-text">Break the 4-Digit Code</h3>
<div class="text-xs mb-3 text-gray-400">Colors: Red, Green, Blue, Yellow, Magenta, Cyan</div>
<div id="codebreaker-game-board" class="codebreaker-board mb-4">
</div>
<div class="codebreaker-palette mb-4" id="codebreaker-color-palette">
</div>
<button id="codebreaker-submit" class="px-4 py-2 bg-[--primary] text-black hover:opacity-80 disabled:opacity-50 disabled:cursor-not-allowed" disabled>Submit Guess</button>
<div id="codebreaker-message" class="mt-4 text-center font-bold"></div>
<button id="codebreaker-restart" class="mt-2 px-3 py-1 bg-gray-600 hover:bg-gray-500 text-xs hidden">New Game</button>
</div>
</div>
<div id="hack-tools-window" class="window hidden" style="/* ... */">...</div>
<div id="network-window" class="window hidden" style="/* ... */">...</div>
<div id="browser-window" class="window hidden flex flex-col" style="/* ... */">...</div>
<div id="settings-window" class="window hidden" style="/* ... */">
<div class="window-content p-4">
<div class="mt-6">
<h3 class="cyber-font text-lg mb-2 glow-text text-red-500">System Reset</h3>
<button class="theme-button bg-red-800 hover:bg-red-700" onclick="resetXortronOS()">Reset File System & Settings</button>
<p class="text-xs text-gray-400 mt-1">Warning: This will clear all saved files, history, and settings.</p>
</div>
</div>
</div>
</div> </div> <div class="taskbar fixed bottom-0 left-0 right-0 h-10 flex justify-between items-center px-2 md:px-4">
<div class="flex items-center taskbar-app-icons">
<button class="cyber-font text-lg md:text-xl mr-2 md:mr-4 glow-text">XORTRON</button>
<button class="mx-1 md:mx-2 text-sm hover:text-[--primary]" onclick="openApp('terminal')"><i class="fas fa-terminal mr-1"></i> <span class="hidden sm:inline">Terminal</span></button>
<button class="mx-1 md:mx-2 text-sm hover:text-[--primary]" onclick="openApp('file-explorer')"><i class="fas fa-folder-open mr-1"></i> <span class="hidden sm:inline">Files</span></button>
<button class="mx-1 md:mx-2 text-sm hover:text-[--primary]" onclick="openApp('editor')"><i class="fas fa-edit mr-1"></i> <span class="hidden sm:inline">Editor</span></button>
<button class="mx-1 md:mx-2 text-sm hover:text-[--primary]" onclick="openApp('browser')"><i class="fas fa-globe mr-1"></i> <span class="hidden sm:inline">Browser</span></button>
<button class="mx-1 md:mx-2 text-sm hover:text-[--primary]" onclick="openApp('codebreaker')"><i class="fas fa-puzzle-piece mr-1"></i> <span class="hidden sm:inline">Game</span></button>
</div>
</div>
<div id="notification" class="fixed bottom-12 md:bottom-16 right-4 bg-gray-900 border border-[--primary] p-3 text-sm max-w-xs rounded hidden z-[6000]">...</div>
<audio id="audio-open" src="https://cdn.pixabay.com/download/audio/2022/03/15/audio_00f666c791.mp3" preload="auto"></audio>
<audio id="audio-close" src="https://cdn.pixabay.com/download/audio/2021/08/04/audio_a4723ae51a.mp3" preload="auto"></audio>
<audio id="audio-notify" src="https://cdn.pixabay.com/download/audio/2022/11/17/audio_73659bf4de.mp3" preload="auto"></audio>
<audio id="audio-error" src="https://cdn.pixabay.com/download/audio/2022/03/10/audio_c4cc4c47ce.mp3" preload="auto"></audio> <audio id="audio-success" src="https://cdn.pixabay.com/download/audio/2022/03/10/audio_17cc2a1796.mp3" preload="auto"></audio> <script>
// --- VFS (Virtual File System using localStorage) ---
const VFS = (() => {
const STORAGE_KEY = 'xortron_vfs';
const CWD_KEY = 'xortron_cwd';
let fsData = {};
let currentDirectory = '/';
const load = () => {
try {
const storedFs = localStorage.getItem(STORAGE_KEY);
const storedCwd = localStorage.getItem(CWD_KEY);
if (storedFs) {
fsData = JSON.parse(storedFs);
} else {
// Initialize default FS
fsData = {
'/': { type: 'dir', content: ['root'] },
'/root': { type: 'dir', content: ['Documents', 'Downloads', 'exploit.py', 'targets.txt.enc'] },
'/root/Documents': { type: 'dir', content: ['notes.txt'] },
'/root/Downloads': { type: 'dir', content: [] },
'/root/exploit.py': { type: 'file', content: '#!/usr/bin/env python\nprint("Running exploit...")\n# TODO: Add actual exploit logic simulation' },
'/root/Documents/notes.txt': { type: 'file', content: 'Meeting notes:\n- Project Xortron funding secured.\n- Need more neon.\n- Remember to buy synthwave.' },
'/root/targets.txt.enc': { type: 'file', content: 'U2FsdGVkX1+ABC... [Simulated Encrypted Data]' } // Fake encrypted file
};
save();
}
currentDirectory = storedCwd || '/root'; // Default to /root
// Ensure current directory exists, otherwise reset to root
if (!exists(currentDirectory) || !isDirectory(currentDirectory)) {
currentDirectory = '/root';
localStorage.setItem(CWD_KEY, currentDirectory);
}
} catch (e) {
console.error("Error loading VFS:", e);
reset(); // Reset if corrupted
}
};
const save = () => {
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(fsData));
localStorage.setItem(CWD_KEY, currentDirectory);
} catch (e) {
console.error("Error saving VFS:", e);
showNotification("Error saving file system state!");
}
};
const reset = () => {
localStorage.removeItem(STORAGE_KEY);
localStorage.removeItem(CWD_KEY);
fsData = {}; // Clear in-memory cache
load(); // Reload initial state
FileExplorer.render(); // Update file explorer display after reset
Terminal.updatePrompt(); // Update terminal prompt
showNotification("XortronOS state reset.");
};
const normalizePath = (path) => {
if (!path) return currentDirectory;
let newPath;
if (path.startsWith('/')) {
newPath = path; // Absolute path
} else {
// Relative path: join with current directory
newPath = joinPath(currentDirectory, path);
}
// Resolve .. and . components
const parts = newPath.split('/').filter(p => p !== '');
const resolvedParts = [];
for (const part of parts) {
if (part === '..') {
resolvedParts.pop();
} else if (part !== '.') {
resolvedParts.push(part);
}
}
// Handle edge case of resolving back to root or beyond
if (resolvedParts.length === 0) return '/';
return '/' + resolvedParts.join('/');
};
const joinPath = (base, part) => {
if (base === '/') return '/' + part;
return base + '/' + part;
};
const getParentPath = (path) => {
if (path === '/') return '/';
const parts = path.split('/');
parts.pop();
return parts.join('/') || '/';
};
const getItemName = (path) => {
if (path === '/') return '/';
return path.substring(path.lastIndexOf('/') + 1);
};
const exists = (path) => fsData.hasOwnProperty(normalizePath(path));
const isDirectory = (path) => exists(path) && fsData[normalizePath(path)].type === 'dir';
const isFile = (path) => exists(path) && fsData[normalizePath(path)].type === 'file';
const readFile = (path) => {
const normPath = normalizePath(path);
if (isFile(normPath)) {
return fsData[normPath].content;
}
return null; // Or throw error
};
const writeFile = (path, content) => {
const normPath = normalizePath(path);
const parentPath = getParentPath(normPath);
const itemName = getItemName(normPath);
if (!isDirectory(parentPath)) return false; // Parent must exist and be a directory
// If file exists, just update content
if (isFile(normPath)) {
fsData[normPath].content = content;
save();
return true;
}
// If it doesn't exist or is a directory, create/replace
else if (isDirectory(parentPath)) {
fsData[normPath] = { type: 'file', content: content };
// Add to parent directory's content list if not already there
if (!fsData[parentPath].content.includes(itemName)) {
fsData[parentPath].content.push(itemName);
}
save();
return true;
}
return false; // Should not happen if parent is dir
};
const createDirectory = (path) => {
const normPath = normalizePath(path);
const parentPath = getParentPath(normPath);
const itemName = getItemName(normPath);
if (exists(normPath)) return false; // Already exists
if (!isDirectory(parentPath)) return false; // Parent must exist and be directory
fsData[normPath] = { type: 'dir', content: [] };
fsData[parentPath].content.push(itemName);
save();
return true;
};
const createFile = (path, content = '') => {
const normPath = normalizePath(path);
const parentPath = getParentPath(normPath);
const itemName = getItemName(normPath);
if (exists(normPath)) return false; // Already exists
if (!isDirectory(parentPath)) return false;
fsData[normPath] = { type: 'file', content: content };
fsData[parentPath].content.push(itemName);
save();
return true;
};
const listDirectory = (path) => {
const normPath = normalizePath(path);
if (isDirectory(normPath)) {
// Return objects with name and type
return fsData[normPath].content.map(name => {
const itemPath = joinPath(normPath, name);
return { name, type: fsData[itemPath]?.type || 'unknown' };
}).sort((a, b) => { // Sort folders first, then alphabetically
if (a.type === 'dir' && b.type !== 'dir') return -1;
if (a.type !== 'dir' && b.type === 'dir') return 1;
return a.name.localeCompare(b.name);
});
}
return null; // Or throw error
};
const deleteItem = (path) => {
const normPath = normalizePath(path);
if (!exists(normPath) || normPath === '/') return false; // Cannot delete root
const parentPath = getParentPath(normPath);
const itemName = getItemName(normPath);
// Check if directory is empty before deleting (simple check)
if (isDirectory(normPath) && fsData[normPath].content.length > 0) {
// Could implement recursive delete later (rm -r)
return 'dir_not_empty'; // Special return value
}
// Remove from parent listing
if (fsData[parentPath] && fsData[parentPath].content) {
fsData[parentPath].content = fsData[parentPath].content.filter(name => name !== itemName);
}
// Remove the item itself
delete fsData[normPath];
save();
return true;
};
const getCurrentDirectory = () => currentDirectory;
const setCurrentDirectory = (path) => {
const normPath = normalizePath(path);
if (isDirectory(normPath)) {
currentDirectory = normPath;
save(); // Persist CWD change
Terminal.updatePrompt(); // Update terminal prompt
FileExplorer.render(currentDirectory); // Update file explorer
return true;
}
return false;
};
// Initial load
load();
return {
load, save, reset, normalizePath, joinPath, exists, isDirectory, isFile,
readFile, writeFile, createDirectory, createFile, listDirectory, deleteItem,
getCurrentDirectory, setCurrentDirectory, getParentPath, getItemName
};
})();
// --- Global App Management ---
const AppManager = (() => {
let highestZ = 10; // Keep track of highest z-index
let openWindows = {}; // Track open window elements { appId: windowElement }
const getOpenWindows = () => Object.keys(openWindows);
const openApp = (appId, data = null) => {
const windowEl = document.getElementById(`${appId}-window`);
if (!windowEl) {
console.error(`Window element not found for app: ${appId}`);
return;
}
windowEl.classList.remove('hidden');
bringToFront(appId);
playSound('audio-open');
openWindows[appId] = windowEl;
if (window.innerWidth < 768 && !windowEl.classList.contains('maximized')) {
maximizeWindow(appId, false);
}
showNotification(`${appId} application launched`);
// App-specific opening logic
switch (appId) {
case 'terminal': Terminal.focusInput(); break;
case 'browser': Browser.loadInitial(); break;
case 'editor': Editor.open(data); break; // Pass file path if provided
case 'file-explorer': FileExplorer.render(); break; // Render current dir
case 'monitor': Monitor.start(); break;
case 'codebreaker': CodeBreaker.initGame(); break;
}
};
const closeWindow = (appId) => {
const windowEl = document.getElementById(`${appId}-window`);
if (windowEl) {
windowEl.classList.add('hidden');
windowEl.classList.remove('maximized');
delete openWindows[appId]; // Remove from tracking
if (window.innerWidth >= 768) restoreOriginalStyle(windowEl);
playSound('audio-close');
// App-specific closing logic
if (appId === 'monitor') Monitor.stop();
if (appId === 'editor') Editor.close(); // Handle unsaved changes?
}
};
const minimizeWindow = (appId) => {
// Simple close on mobile for now
if (window.innerWidth < 768) {
closeWindow(appId);
} else {
showNotification(`${appId} minimized (simulation)`);
// Could hide and add to taskbar state here
}
};
const maximizeWindow = (appId, toggle = true) => {
const windowEl = document.getElementById(`${appId}-window`);
if (!windowEl) return;
if (toggle && windowEl.classList.contains('maximized')) {
// Restore
windowEl.classList.remove('maximized');
if (window.innerWidth >= 768) restoreOriginalStyle(windowEl);
else windowEl.classList.remove('hidden'); // Ensure visible on mobile restore
} else {
// Maximize
if (toggle || !windowEl.classList.contains('maximized')) {
if (window.innerWidth >= 768) storeOriginalStyle(windowEl);
windowEl.classList.add('maximized');
windowEl.style.top = '0'; windowEl.style.left = '0';
windowEl.style.width = '100%'; windowEl.style.height = `calc(100% - 40px)`;
windowEl.style.position = 'fixed';
bringToFront(appId);
}
}
};
const bringToFront = (appId) => {
const windowEl = document.getElementById(`${appId}-window`);
if (windowEl) {
highestZ++;
windowEl.style.zIndex = highestZ;
windowEl.dataset.zIndex = highestZ;
}
};
// --- Style Helpers for Maximize ---
const storeOriginalStyle = (windowEl) => {
const style = windowEl.getAttribute('style');
if (style && !windowEl.dataset.originalStyle) { // Only store if not already stored
windowEl.dataset.originalStyle = style;
}
};
const restoreOriginalStyle = (windowEl) => {
if (windowEl.dataset.originalStyle) {
windowEl.setAttribute('style', windowEl.dataset.originalStyle);
// Ensure z-index is restored correctly from dataset or highestZ
windowEl.style.zIndex = windowEl.dataset.zIndex || highestZ;
delete windowEl.dataset.originalStyle; // Clear stored style after restoring
} else { // Fallback needed if style wasn't stored
windowEl.style.position = 'absolute';
windowEl.style.top = '5rem'; windowEl.style.left = '5rem';
windowEl.style.width = '50%'; windowEl.style.height = '50%';
windowEl.style.zIndex = windowEl.dataset.zIndex || highestZ;
}
};
// --- Dragging Logic (Keep existing touch/mouse handler) ---
const makeWindowsDraggable = () => {
document.querySelectorAll('.window-header').forEach(header => {
// ... (Existing mousedown/touchstart -> mousemove/touchmove -> mouseup/touchend logic) ...
// Ensure bringToFront is called inside onDragStart
function onDragStart(e) {
const windowEl = this.parentElement;
if (window.innerWidth >= 768 && windowEl.classList.contains('maximized')) return; // Don't drag maximized
// ... rest of drag start logic ...
bringToFront(windowEl.id.split('-')[0]); // Critical line
// ... rest of drag start logic ...
}
// Remove previous listeners before adding new ones to prevent duplicates
header.removeEventListener('mousedown', header._dragStartHandler);
header.removeEventListener('touchstart', header._dragStartHandler);
header._dragStartHandler = onDragStart; // Store handler reference
header.addEventListener('mousedown', header._dragStartHandler);
header.addEventListener('touchstart', header._dragStartHandler, { passive: false });
// ... rest of drag logic with onDragMove, onDragEnd ...
});
};
// --- Public API ---
return {
openApp, closeWindow, minimizeWindow, maximizeWindow, bringToFront,
makeWindowsDraggable, getOpenWindows
};
})();
// --- Terminal Module ---
const Terminal = (() => {
const terminalContent = document.getElementById('terminal-content');
const outputArea = terminalContent?.querySelector('.output-area');
let currentInput = null;
let commandHistory = [];
let historyIndex = -1;
const createNewPrompt = () => {
if (!terminalContent) return;
const newPromptLine = document.createElement('div');
newPromptLine.className = 'prompt-line flex items-center mt-1';
newPromptLine.innerHTML = `
<span class="text-green-500" id="terminal-prompt">${getPromptText()}</span>
<input type="text" class="bg-transparent border-none outline-none flex-1 ml-2 text-green-500 terminal-input-field" autofocus autocomplete="off" spellcheck="false">
<span class="cursor-blink">█</span>`;
terminalContent.appendChild(newPromptLine);
currentInput = newPromptLine.querySelector('.terminal-input-field');
currentInput.addEventListener('keydown', handleKeyDown);
currentInput.focus();
terminalContent.scrollTop = terminalContent.scrollHeight;
};
const getPromptText = () => {
const cwd = VFS.getCurrentDirectory();
// Replace /root with ~ for classic look
const displayPath = cwd.startsWith('/root') ? '~' + cwd.substring(5) : cwd;
return `root@xortron-os:<span class="text-blue-400">${displayPath}</span># `;
};
const updatePrompt = () => {
const promptSpan = terminalContent?.querySelector('.prompt-line:last-child #terminal-prompt');
if (promptSpan) {
promptSpan.innerHTML = getPromptText();
}
};
const handleKeyDown = (e) => {
if (e.key === 'Enter' && currentInput.value.trim() !== '') {
const command = currentInput.value.trim();
commandHistory.push(command);
historyIndex = -1;
currentInput.disabled = true; // Disable old input
// Display command entered
const promptText = currentInput.previousElementSibling.innerHTML; // Get the prompt span HTML
const commandDiv = document.createElement('div');
commandDiv.innerHTML = `${promptText}<span class="ml-2">${escapeHtml(command)}</span>`; // Use escaped command
outputArea.appendChild(commandDiv);
processCommand(command); // Process
createNewPrompt(); // Create new input line
} else if (e.key === 'ArrowUp') { /* History Up */ }
else if (e.key === 'ArrowDown') { /* History Down */ }
else if (e.key === 'Tab') {
e.preventDefault(); // Prevent focus change
handleTabCompletion(currentInput);
}
};
const handleTabCompletion = (inputElement) => {
const text = inputElement.value;
const lastSpaceIndex = text.lastIndexOf(' ') + 1;
const currentWord = text.substring(lastSpaceIndex);
if (!currentWord) return; // Nothing to complete
const isPathCompletion = text.trim().split(' ').length > 1 || currentWord.includes('/'); // Crude check if likely a path
const currentPath = VFS.normalizePath(currentWord);
const parentPath = VFS.getParentPath(currentPath);
const partialName = VFS.getItemName(currentPath);
const itemsInDir = VFS.listDirectory(parentPath);
if (!itemsInDir) return;
const matches = itemsInDir.filter(item => item.name.startsWith(partialName));
if (matches.length === 1) {
// Complete single match
const completion = matches[0].name + (matches[0].type === 'dir' ? '/' : ' ');
inputElement.value = text.substring(0, lastSpaceIndex) + VFS.joinPath(parentPath === '/' ? '' : parentPath, completion); // Build full path for completion
inputElement.setSelectionRange(inputElement.value.length, inputElement.value.length);
} else if (matches.length > 1) {
// Show multiple matches
const output = document.createElement('div');
output.className = 'text-gray-400';
output.textContent = matches.map(m => m.name + (m.type === 'dir' ? '/' : '')).join(' ');
outputArea.appendChild(output);
playSound('audio-notify'); // Hint sound
}
};
const processCommand = (command) => {
const output = document.createElement('div');
output.className = 'mb-2 whitespace-pre-wrap';
const args = command.match(/(?:[^\s"]+|"[^"]*")+/g) || []; // Handle quoted args basic
const cmd = args[0]?.toLowerCase();
const cleanArgs = args.map(arg => arg.startsWith('"') && arg.endsWith('"') ? arg.slice(1, -1) : arg); // Remove quotes
const writeOutput = (text) => {
output.innerHTML = text; // Use innerHTML for potential formatting
outputArea.appendChild(output);
};
const commands = {
'help': () => writeOutput(`Available: clear, ls, cd, pwd, cat, touch, mkdir, rm, echo, neo, nmap, decrypt, browser, theme, date, whoami, exit`),
'clear': () => { outputArea.innerHTML = ''; return; }, // Special case, no output div
'ls': (path = '.') => {
const targetPath = VFS.normalizePath(path || '.');
const items = VFS.listDirectory(targetPath);
if (items) {
const listOutput = items.map(item => {
const itemPath = VFS.joinPath(targetPath, item.name);
const isDir = item.type === 'dir';
const isEnc = item.name.endsWith('.enc');
const color = isDir ? 'text-blue-400' : (isEnc ? 'text-red-500' : 'text-gray-300');
const icon = isDir ? '<i class="fas fa-folder"></i>' : (isEnc ? '<i class="fas fa-lock"></i>' : '<i class="fas fa-file-alt"></i>');
return `<span class="${color}">${icon} ${escapeHtml(item.name)}</span>`;
}).join('\n');
writeOutput(listOutput || '(empty directory)');
} else {
writeOutput(`ls: cannot access '${escapeHtml(path)}': No such file or directory`);
playSound('audio-error');
}
},
'cd': (path) => {
if (!path) { path = '/root'; } // Default cd goes to ~ (/root)
const targetPath = VFS.normalizePath(path);
if (VFS.setCurrentDirectory(targetPath)) {
// Success, prompt updates automatically via setCurrentDirectory
} else {
writeOutput(`cd: no such file or directory: ${escapeHtml(path)}`);
playSound('audio-error');
}
},
'pwd': () => writeOutput(VFS.getCurrentDirectory()),
'cat': (path) => {
if (!path) { writeOutput("cat: missing file operand"); playSound('audio-error'); return; }
const normPath = VFS.normalizePath(path);
if (VFS.isFile(normPath)) {
if (normPath.endsWith('.enc')) {
writeOutput(`cat: ${escapeHtml(VFS.getItemName(normPath))} is encrypted. Use 'decrypt'.`);
playSound('audio-error');
} else {
writeOutput(escapeHtml(VFS.readFile(normPath)));
}
} else if (VFS.isDirectory(normPath)) {
writeOutput(`cat: ${escapeHtml(VFS.getItemName(normPath))}: Is a directory`);
playSound('audio-error');
} else {
writeOutput(`cat: ${escapeHtml(path)}: No such file or directory`);
playSound('audio-error');
}
},
'touch': (path) => {
if (!path) { writeOutput("touch: missing file operand"); playSound('audio-error'); return; }
const normPath = VFS.normalizePath(path);
if (VFS.createFile(normPath)) {
// Silent success
} else if (VFS.exists(normPath)) {
// File exists, update timestamp (simulation - do nothing here)
} else {
writeOutput(`touch: cannot touch '${escapeHtml(path)}': No such file or directory or invalid path`);
playSound('audio-error');
}
FileExplorer.render(); // Update file explorer view
},
'mkdir': (path) => {
if (!path) { writeOutput("mkdir: missing operand"); playSound('audio-error'); return; }
const normPath = VFS.normalizePath(path);
if (VFS.createDirectory(normPath)) {
// Silent success
} else if (VFS.exists(normPath)) {
writeOutput(`mkdir: cannot create directory ‘${escapeHtml(path)}’: File exists`);
playSound('audio-error');
} else {
writeOutput(`mkdir: cannot create directory ‘${escapeHtml(path)}’: No such file or directory or invalid path`);
playSound('audio-error');
}
FileExplorer.render(); // Update file explorer view
},
'rm': (path) => {
if (!path) { writeOutput("rm: missing operand"); playSound('audio-error'); return; }
const normPath = VFS.normalizePath(path);
if (!VFS.exists(normPath)) {
writeOutput(`rm: cannot remove '${escapeHtml(path)}': No such file or directory`);
playSound('audio-error'); return;
}
if (VFS.isDirectory(normPath)) {
writeOutput(`rm: cannot remove '${escapeHtml(path)}': Is a directory (use rmdir or rm -r)`);
playSound('audio-error'); return;
}
if (VFS.deleteItem(normPath)) {
// Silent success
} else {
writeOutput(`rm: cannot remove '${escapeHtml(path)}': Operation failed`); // Generic failure
playSound('audio-error');
}
FileExplorer.render(); // Update file explorer view
},
'echo': () => writeOutput(escapeHtml(cleanArgs.slice(1).join(' '))),
'neo': () => writeOutput(`<pre class="text-sm leading-tight">
<span class="text-cyan-400">/////////////</span> <span class="text-green-500">root@xortron-os</span>
<span class="text-cyan-400">/////////////////</span> -----------------
<span class="text-cyan-400">///////</span><span class="text-purple-400">///////</span><span class="text-cyan-400">///////</span> OS: <span class="text-white">XortronOS v8.1 Cyberpunk</span>
<span class="text-cyan-400">//////</span><span class="text-purple-400">///////////</span><span class="text-cyan-400">//////</span> Kernel: <span class="text-white">7.7.7-xanmod-xos</span>
<span class="text-cyan-400">//////</span><span class="text-purple-400">///////////</span><span class="text-cyan-400">//////</span> Uptime: <span class="text-white">42d 13h 37m</span>
<span class="text-cyan-400">///////</span><span class="text-purple-400">///////</span><span class="text-cyan-400">///////</span> Shell: <span class="text-white">zsh (hax0r edition)</span>
<span class="text-cyan-400">/////////////////</span> Resolution: <span class="text-white">${window.screen.width}x${window.screen.height}</span>
<span class="text-cyan-400">/////////////</span> WM: <span class="text-white">XorgWM (GPU Accelerated)</span>
Theme: <span class="text-white">CyberNeon [GTK3]</span>
Icons: <span class="text-white">XOS-Icons [GTK3]</span>
CPU: <span class="text-white">Quantum Entangler @ 10 THz (16 Cores)</span>
GPU: <span class="text-white">Nvidia RTX 9090 Ti CyberDrive</span>
Memory: <span class="text-white">128GiB / 256GiB DDR9</span></pre>`),
'nmap': (target = 'scanme.nmap.org') => {
writeOutput(`Starting Nmap 9.99 ( https://nmap.org ) at ${new Date().toISOString()}
Nmap scan report for ${escapeHtml(target)} (192.168.1.X)
Host is up (0.01s latency).
Not shown: 990 filtered tcp ports (no-response)
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 9.8p1 (protocol 2.0)
80/tcp open http nginx 1.27.1
135/tcp open msrpc Microsoft Windows RPC
443/tcp open ssl/http nginx 1.27.1
445/tcp open microsoft-ds Windows 10 SMB (unsafe!)
3306/tcp open mysql MySQL 8.0.32 (Bruteforce possible)
3389/tcp open ms-wbt-server Microsoft Terminal Services (RDP)
5900/tcp open vnc RealVNC Enterprise 6.x (Weak Auth)
8080/tcp closed http-proxy
Nmap done: 1 IP address (1 host up) scanned in 3.50 seconds`);
},
'decrypt': (path) => {
if (!path) { writeOutput("decrypt: missing file operand"); playSound('audio-error'); return; }
const normPath = VFS.normalizePath(path);
if (VFS.isFile(normPath) && normPath.endsWith('.enc')) {
let progress = 0;
const interval = setInterval(() => {
progress += Math.random() * 15;
if (progress >= 100) {
clearInterval(interval);
const decryptedName = normPath.replace('.enc', '');
VFS.writeFile(decryptedName, `--- DECRYPTED DATA ---\nOriginal File: ${VFS.getItemName(normPath)}\nContent simulation: Access codes, coordinates, secrets...\n--- END DECRYPTION ---`);
VFS.deleteItem(normPath); // Delete encrypted version
writeOutput(`Decrypting ${escapeHtml(VFS.getItemName(normPath))}... [##########] 100% Done.\nDecrypted content saved to ${escapeHtml(VFS.getItemName(decryptedName))}`);
playSound('audio-success');
FileExplorer.render(); // Update file explorer
} else {
writeOutput(`Decrypting ${escapeHtml(VFS.getItemName(normPath))}... [${'#'.repeat(Math.floor(progress / 10)).padEnd(10, ' ')}] ${Math.floor(progress)}%`);
}
}, 200);
} else {
writeOutput(`decrypt: file '${escapeHtml(path)}' not found or not encrypted (.enc)`);
playSound('audio-error');
}
},
// Keep other commands: browser, theme, date, whoami, exit
'browser': () => { AppManager.openApp('browser'); return writeOutput('Launching DarkBrowser...'); },
'theme': (color) => { /* Keep theme logic */ },
'date': () => writeOutput(new Date().toString()),
'whoami': () => writeOutput('root'),
'exit': () => { setTimeout(() => AppManager.closeWindow('terminal'), 300); return writeOutput('Closing terminal session...'); }
};
if (commands[cmd]) {
commands[cmd](cleanArgs[1], cleanArgs.slice(2)); // Pass args
} else {
writeOutput(`XortronOS: command not found: ${escapeHtml(cmd)}`);
playSound('audio-error');
}
// Scroll after potential output
terminalContent.scrollTop = terminalContent.scrollHeight;
};
const focusInput = () => {
setTimeout(() => currentInput?.focus(), 50);
};
const escapeHtml = (unsafe) => {
if (typeof unsafe !== 'string') return unsafe;
return unsafe.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
}
// Initial setup
const init = () => {
const firstInput = terminalContent?.querySelector('.terminal-input-field');
if (firstInput) {
currentInput = firstInput;
currentInput.addEventListener('keydown', handleKeyDown);
} else {
createNewPrompt(); // Create if not present initially
}
updatePrompt(); // Set initial prompt text
};
return { init, focusInput, updatePrompt };
})();
// --- Editor Module ---
const Editor = (() => {
const windowEl = document.getElementById('editor-window');
const textarea = document.getElementById('editor-textarea');
const titleEl = document.getElementById('editor-title');
const statusbar = document.getElementById('editor-statusbar');
let currentFilePath = null;
let originalContent = '';
let unsavedChanges = false;
const open = (filePath) => {
if (!windowEl || !textarea || !titleEl || !statusbar) return;
currentFilePath = filePath ? VFS.normalizePath(filePath) : null;
if (currentFilePath && VFS.isFile(currentFilePath)) {
originalContent = VFS.readFile(currentFilePath) || '';
textarea.value = originalContent;
titleEl.textContent = `Editor - ${VFS.getItemName(currentFilePath)}`;
statusbar.textContent = `Opened: ${currentFilePath}`;
} else {
// New file or invalid path
originalContent = '';
textarea.value = '';
currentFilePath = null; // Explicitly null for new file
titleEl.textContent = 'Editor - New File';
statusbar.textContent = 'New file. Use Save button or terminal "touch" first.';
}
unsavedChanges = false;
textarea.oninput = () => {
unsavedChanges = (textarea.value !== originalContent);
statusbar.textContent = currentFilePath ? `${currentFilePath} ${unsavedChanges ? '[Modified]' : '[Saved]'}` : `New File ${unsavedChanges ? '[Modified]' : ''}`;
};
};
const save = () => {
if (!currentFilePath) {
// Handle saving a new file - needs a prompt or default location
const newPath = prompt("Save As (full path, e.g., /root/Documents/new.txt):", VFS.getCurrentDirectory() + '/untitled.txt');
if (!newPath) return; // User cancelled
currentFilePath = VFS.normalizePath(newPath);
if (VFS.exists(currentFilePath) && !confirm("File exists. Overwrite?")) {
currentFilePath = null; // Reset if overwrite cancelled
return;
}
// Attempt to create parent directories if needed (basic)
const parentDir = VFS.getParentPath(currentFilePath);
if (!VFS.exists(parentDir)) VFS.createDirectory(parentDir); // Naive creation
}
if (VFS.writeFile(currentFilePath, textarea.value)) {
originalContent = textarea.value;
unsavedChanges = false;
statusbar.textContent = `Saved: ${currentFilePath}`;
titleEl.textContent = `Editor - ${VFS.getItemName(currentFilePath)}`;
showNotification(`File saved: ${currentFilePath}`);
playSound('audio-success');
FileExplorer.render(); // Update file explorer in case it's a new file
} else {
statusbar.textContent = `Error saving: ${currentFilePath}`;
showNotification(`Error: Could not save file to ${currentFilePath}`);
playSound('audio-error');
}
};
const close = () => {
// Basic close, could add unsaved changes check later
if (unsavedChanges) {
// Simple alert for now
// alert("You have unsaved changes!");
// A more integrated dialog would be better
}
currentFilePath = null;
textarea.value = '';
originalContent = '';
unsavedChanges = false;
};
return { open, save, close };
})();
// --- File Explorer Module ---
const FileExplorer = (() => {
const windowEl = document.getElementById('file-explorer-window');
const gridEl = document.getElementById('file-explorer-grid');
const pathEl = document.getElementById('file-explorer-path');
let currentPath = '/root'; // Start in user's home
let selectedItem = null;
const render = (path = currentPath) => {
if (!windowEl || !gridEl || !pathEl) return;
currentPath = VFS.normalizePath(path);
pathEl.textContent = currentPath;
gridEl.innerHTML = ''; // Clear current view
selectedItem = null; // Clear selection
const items = VFS.listDirectory(currentPath);
if (!items) {
gridEl.innerHTML = '<p class="text-gray-400 col-span-full">Error listing directory.</p>';
return;
}
if (items.length === 0) {
gridEl.innerHTML = '<p class="text-gray-400 col-span-full">(Directory is empty)</p>';
}
items.forEach(item => {
const itemPath = VFS.joinPath(currentPath, item.name);
const div = document.createElement('div');
div.className = 'file-item file text-center p-2 rounded hover:bg-[--primary]/20 focus:bg-[--primary]/30 outline-none';
div.setAttribute('tabindex', '0'); // Make focusable
div.dataset.path = itemPath;
div.dataset.type = item.type;
let iconClass = 'fa-file-alt text-gray-400'; // Default file
if (item.type === 'dir') iconClass = 'fa-folder text-yellow-500';
else if (item.name.endsWith('.py') || item.name.endsWith('.js')) iconClass = 'fa-file-code text-blue-400';
else if (item.name.endsWith('.zip') || item.name.endsWith('.tar')) iconClass = 'fa-file-archive text-purple-400';
else if (item.name.endsWith('.png') || item.name.endsWith('.jpg')) iconClass = 'fa-file-image text-pink-400';
else if (item.name.endsWith('.enc')) iconClass = 'fa-lock text-red-500';
div.innerHTML = `
<div class="text-3xl mb-1"><i class="fas ${iconClass}"></i></div>
<div class="text-xs truncate">${Terminal.escapeHtml(item.name)}</div>
`;
// --- Event Listeners ---
div.addEventListener('dblclick', () => handleItemOpen(itemPath, item.type));
div.addEventListener('click', () => handleItemSelect(div, itemPath));
div.addEventListener('keydown', (e) => { // Keyboard nav/open
if (e.key === 'Enter') handleItemOpen(itemPath, item.type);
// Add arrow key navigation later if needed
});
gridEl.appendChild(div);
});
};
const handleItemSelect = (element, path) => {
// Remove selection from previous item
if (selectedItem && selectedItem.element) {
selectedItem.element.classList.remove('bg-[--primary]/30');
}
// Set new selection
element.classList.add('bg-[--primary]/30');
selectedItem = { element, path };
};
const handleItemOpen = (path, type) => {
if (type === 'dir') {
render(path); // Navigate into directory
} else if (type === 'file') {
if (path.endsWith('.txt') || path.endsWith('.py') || path.endsWith('.js') || path.endsWith('.md') || !path.includes('.')) { // Open text-like files in editor
AppManager.openApp('editor', path);
} else if (path.endsWith('.enc')) {
showNotification(`File ${VFS.getItemName(path)} is encrypted. Use terminal 'decrypt' command.`);
playSound('audio-error');
} else {
showNotification(`No application available to open ${VFS.getItemName(path)}.`);
playSound('audio-notify');
}
}
};
const navigateBack = () => {
const parentPath = VFS.getParentPath(currentPath);
render(parentPath);
};
const createNew = (type) => {
const name = prompt(`Enter name for new ${type}:`);
if (!name || !name.trim()) return;
const newPath = VFS.joinPath(currentPath, name.trim());
if (VFS.exists(newPath)) {
showNotification(`${type} '${name}' already exists.`);
playSound('audio-error'); return;
}
let success = false;
if (type === 'folder') {
success = VFS.createDirectory(newPath);
} else if (type === 'file') {
success = VFS.createFile(newPath, ''); // Create empty file
}
if (success) {
render(); // Refresh view
showNotification(`${type} '${name}' created.`);
playSound('audio-success');
} else {
showNotification(`Failed to create ${type}.`);
playSound('audio-error');
}
};
const deleteSelected = () => {
if (!selectedItem) {
showNotification("No item selected to delete.");
playSound('audio-notify'); return;
}
const path = selectedItem.path;
const itemName = VFS.getItemName(path);
const itemType = VFS.isDirectory(path) ? 'directory' : 'file';
if (!confirm(`Are you sure you want to delete ${itemType} "${itemName}"?`)) {
return;
}
const result = VFS.deleteItem(path);
if (result === true) {
render(); // Refresh view
showNotification(`${itemType} '${itemName}' deleted.`);
playSound('audio-success');
} else if (result === 'dir_not_empty') {
showNotification(`Directory '${itemName}' is not empty. Cannot delete.`);
playSound('audio-error');
} else {
showNotification(`Failed to delete '${itemName}'.`);
playSound('audio-error');
}
};
return { render, navigateBack, createNew, deleteSelected };
})();
// --- System Monitor Module ---
const Monitor = (() => {
let intervalId = null;
const cpuText = document.getElementById('monitor-cpu-text');
const cpuBar = document.getElementById('monitor-cpu-bar');
const ramText = document.getElementById('monitor-ram-text');
const ramBar = document.getElementById('monitor-ram-bar');
const processList = document.getElementById('monitor-process-list');
const update = () => {
if (!cpuText || !cpuBar || !ramText || !ramBar || !processList) { stop(); return; } // Stop if elements missing
// Simulate CPU (more spikes)
const cpuUsage = Math.min(100, Math.max(5, Math.random() * 20 + (Math.random() > 0.8 ? Math.random() * 60 : 0)));
cpuText.textContent = `${cpuUsage.toFixed(1)}%`;
cpuBar.style.width = `${cpuUsage}%`;
// Simulate RAM
const ramUsage = Math.min(16384, Math.max(2048, Math.random() * 8192 + 2048));
const ramPercent = (ramUsage / 16384) * 100;
ramText.textContent = `${ramUsage.toFixed(0)} MB / 16384 MB`;
ramBar.style.width = `${ramPercent}%`;
// Update Process List
processList.innerHTML = ''; // Clear previous list
const openApps = AppManager.getOpenWindows();
openApps.forEach(appId => {
const procDiv = document.createElement('div');
procDiv.className = 'process-list-item';
const nameSpan = document.createElement('span');
nameSpan.textContent = `${appId}.exe`; // Simulate process name
const cpuSpan = document.createElement('span');
cpuSpan.textContent = `CPU: ${(Math.random() * (cpuUsage / openApps.length)).toFixed(1)}%`; // Distribute some CPU
cpuSpan.className = 'text-xs text-gray-400';
procDiv.appendChild(nameSpan);
procDiv.appendChild(cpuSpan);
processList.appendChild(procDiv);
});
// Add some fake system processes
['xkernel.sys', 'svchost.exe', 'dwm.exe', 'explorer.xos'].forEach(p => {
const procDiv = document.createElement('div');
procDiv.className = 'process-list-item';
procDiv.innerHTML = `<span>${p}</span><span class="text-xs text-gray-400">CPU: ${(Math.random() * 2).toFixed(1)}%</span>`;
processList.appendChild(procDiv);
});
};
const start = () => {
if (!intervalId) {
update(); // Initial update
intervalId = setInterval(update, 2000); // Update every 2 seconds
}
};
const stop = () => {
clearInterval(intervalId);
intervalId = null;
};
return { start, stop };
})();
// --- Code Breaker Game Module ---
const CodeBreaker = (() => {
const windowEl = document.getElementById('codebreaker-window');
const gameBoard = document.getElementById('codebreaker-game-board');
const palette = document.getElementById('codebreaker-color-palette');
const submitButton = document.getElementById('codebreaker-submit');
const messageEl = document.getElementById('codebreaker-message');
const restartButton = document.getElementById('codebreaker-restart');
const COLORS = ['#EF5350', '#66BB6A', '#42A5F5', '#FFEE58', '#EC407A', '#26C6DA']; // Red, Green, Blue, Yellow, Magenta, Cyan
const CODE_LENGTH = 4;
const MAX_GUESSES = 10;
let secretCode = [];
let guesses = [];
let currentGuess = Array(CODE_LENGTH).fill(null);
let currentRowIndex = 0;
let selectedColor = COLORS[0];
let gameOver = false;
const initGame = () => {
if (!gameBoard || !palette || !submitButton || !messageEl) return;
secretCode = generateSecretCode();
guesses = [];
currentRowIndex = 0;
gameOver = false;
messageEl.textContent = '';
restartButton.classList.add('hidden');
// Setup Palette
palette.innerHTML = '';
COLORS.forEach((color, index) => {
const colorDiv = document.createElement('div');
colorDiv.className = 'palette-color';
colorDiv.style.backgroundColor = color;
if (index === 0) {
colorDiv.classList.add('selected');
selectedColor = color;
}
colorDiv.onclick = () => selectColor(color, colorDiv);
palette.appendChild(colorDiv);
});
// Setup Board
gameBoard.innerHTML = '';
for (let i = 0; i < MAX_GUESSES; i++) {
gameBoard.appendChild(createGuessRow(i));
}
updateSubmitButtonState();
// Add event listener for restart
restartButton.onclick = initGame;
console.log("Secret Code (for debugging):", secretCode); // Debugging
};
const generateSecretCode = () => {
const code = [];
for (let i = 0; i < CODE_LENGTH; i++) {
code.push(COLORS[Math.floor(Math.random() * COLORS.length)]);
}
return code;
};
const createGuessRow = (rowIndex) => {
const rowDiv = document.createElement('div');
rowDiv.className = 'codebreaker-row';
rowDiv.dataset.rowIndex = rowIndex;
// Guess Pegs
for (let i = 0; i < CODE_LENGTH; i++) {
const peg = document.createElement('div');
peg.className = 'codebreaker-guess-peg';
peg.dataset.pegIndex = i;
peg.onclick = () => placeColor(rowIndex, i, peg);
rowDiv.appendChild(peg);
}
// Feedback Area
const feedbackArea = document.createElement('div');
feedbackArea.className = 'codebreaker-feedback-area';
// Add placeholder feedback pegs
for (let i = 0; i < CODE_LENGTH; i++) {
const fbPeg = document.createElement('div');
fbPeg.className = 'codebreaker-feedback-peg';
feedbackArea.appendChild(fbPeg);
}
rowDiv.appendChild(feedbackArea);
return rowDiv;
};
const selectColor = (color, element) => {
if (gameOver) return;
selectedColor = color;
document.querySelectorAll('.palette-color.selected').forEach(el => el.classList.remove('selected'));
element.classList.add('selected');
};
const placeColor = (rowIndex, pegIndex, pegElement) => {
if (gameOver || rowIndex !== currentRowIndex) return; // Only allow current row
currentGuess[pegIndex] = selectedColor;
pegElement.style.backgroundColor = selectedColor;
updateSubmitButtonState();
};
const updateSubmitButtonState = () => {
submitButton.disabled = gameOver || currentGuess.some(color => color === null);
};
const submitGuess = () => {
if (gameOver || submitButton.disabled) return;
guesses.push([...currentGuess]); // Store a copy
const feedback = checkGuess(currentGuess);
displayFeedback(currentRowIndex, feedback);
// Disable current row pegs
document.querySelectorAll(`.codebreaker-row[data-row-index="${currentRowIndex}"] .codebreaker-guess-peg`).forEach(peg => peg.onclick = null);
if (feedback.black === CODE_LENGTH) {
// Win condition
messageEl.textContent = `Code Cracked in ${currentRowIndex + 1} guesses!`;
messageEl.className = 'mt-4 text-center font-bold text-green-400';
gameOver = true;
restartButton.classList.remove('hidden');
playSound('audio-success');
} else if (currentRowIndex >= MAX_GUESSES - 1) {
// Lose condition
messageEl.textContent = `Game Over! Code was:`;
messageEl.className = 'mt-4 text-center font-bold text-red-400';
displaySecretCode(); // Show the answer
gameOver = true;
restartButton.classList.remove('hidden');
playSound('audio-error');
} else {
// Continue to next row
currentRowIndex++;
currentGuess = Array(CODE_LENGTH).fill(null); // Reset for next guess
}
updateSubmitButtonState();
};
const checkGuess = (guess) => {
let blackPegs = 0;
let whitePegs = 0;
let secretCopy = [...secretCode];
let guessCopy = [...guess];
// First pass for black pegs (correct color and position)
for (let i = CODE_LENGTH - 1; i >= 0; i--) {
if (guessCopy[i] === secretCopy[i]) {
blackPegs++;
secretCopy.splice(i, 1); // Remove matched item
guessCopy.splice(i, 1);
}
}
// Second pass for white pegs (correct color, wrong position)
for (let i = guessCopy.length - 1; i >= 0; i--) {
const indexInSecret = secretCopy.indexOf(guessCopy[i]);
if (indexInSecret !== -1) {
whitePegs++;
secretCopy.splice(indexInSecret, 1); // Remove matched item from secret
}
}
return { black: blackPegs, white: whitePegs };
};
const displayFeedback = (rowIndex, feedback) => {
const feedbackArea = document.querySelector(`.codebreaker-row[data-row-index="${rowIndex}"] .codebreaker-feedback-area`);
const pegs = feedbackArea.querySelectorAll('.codebreaker-feedback-peg');
let pegIndex = 0;
for (let i = 0; i < feedback.black; i++) {
if (pegs[pegIndex]) pegs[pegIndex].className = 'codebreaker-feedback-peg feedback-black';
pegIndex++;
}
for (let i = 0; i < feedback.white; i++) {
if (pegs[pegIndex]) pegs[pegIndex].className = 'codebreaker-feedback-peg feedback-white';
pegIndex++;
}
};
const displaySecretCode = () => {
const secretDiv = document.createElement('div');
secretDiv.className = 'flex gap-2 mt-1';
secretCode.forEach(color => {
const peg = document.createElement('div');
peg.style.width = '20px'; peg.style.height = '20px';
peg.style.backgroundColor = color; peg.style.borderRadius = '50%';
secretDiv.appendChild(peg);
});
messageEl.appendChild(secretDiv);
};
// Attach submit listener
submitButton.onclick = submitGuess;
return { initGame };
})();
// --- Browser Module (Simple Enhancements) ---
const Browser = (() => {
const iframe = document.getElementById('browser-frame');
const urlInput = document.getElementById('browser-url');
const loadInitial = () => {
if (iframe && iframe.src === 'about:blank') {
// Load a custom internal start page instead
iframe.srcdoc = `
<html style="background-color: #050508; color: #e0e0ff; font-family: 'Share Tech Mono', monospace; padding: 20px;">
<head><title>XOS DarkBrowser</title></head>
<body>
<h1 style="font-family: 'Orbitron', sans-serif; color: var(--primary); text-shadow: 0 0 5px var(--primary);">Xortron DarkBrowser</h1>
<p>Welcome, agent. Use the URL bar above.</p>
<p>Bookmarks:</p>
<ul>
<li><a href="#" onclick="parent.Browser.navigate('https://github.com'); return false;">GitHub (Clearnet)</a></li>
<li><a href="#" onclick="parent.Browser.navigate('xos://intel-feed'); return false;">XOS Intel Feed (Internal)</a></li>
<li><a href="#" onclick="parent.Browser.navigate('xos://black-market-hub'); return false;">Black Market Hub (Simulated)</a></li>
</ul>
</body></html>`;
urlInput.value = 'xos://darkbrowser/home';
}
};
const navigate = (url) => {
if (!iframe || !urlInput) return;
// Simple internal page simulation
if (url === 'xos://intel-feed') {
iframe.srcdoc = `<html style="background-color: black; color: lime; font-family: monospace; padding: 10px; font-size: 12px;"><head><title>Intel Feed</title></head><body><h1>[CLASSIFIED] Intel Feed</h1><p>Timestamp: ${new Date().toISOString()}</p><pre>TARGET ACQUIRED: Project Chimera server farm (Coordinates: XX.XXX, YY.YYY)\nVULNERABILITY DETECTED: Zero-day exploit in firewall firmware v3.1\nASSET TRANSFER: Package ZETA en route to safe house GAMMA.\nCOUNTER-INTEL: Operation NIGHTFALL compromised. Agent SILAS extracted.</pre></body></html>`;
urlInput.value = url;
} else if (url === 'xos://black-market-hub') {
iframe.srcdoc = `<html style="background-color: #1a001a; color: #ff00ff; font-family: monospace; padding: 10px;"><head><title>Black Market</title></head><body><h1>The Shadow Market</h1><p>Listings:</p><ul><li>Zero-Day Exploits (Inquire)</li><li>Stolen Credentials (Bulk Available)</li><li>Botnet Rental (DDOS Services)</li><li>Custom Malware Development</li></ul><p style="color: red;">WARNING: Monitored Network. Use caution.</p></body></html>`;
urlInput.value = url;
} else {
// Treat as external URL
if (!url.includes('://')) url = 'https://' + url;
iframe.src = url; // Navigate external
urlInput.value = url;
}
};
// Expose navigate for the srcdoc links
window.Browser = { navigate };
return { loadInitial, navigate };
})();
// --- Utility Functions ---
function playSound(id) { /* ... keep existing sound function ... */ }
function showNotification(message) { /* ... keep existing notification function ... */ }
function hideNotification() { /* ... keep existing notification function ... */ }
function updateClock() { /* ... keep existing clock function ... */ }
function changeTheme(color) { /* ... keep existing theme function ... */ }
function toggleMatrix(enable) { /* ... keep existing matrix function ... */ }
function setupMatrix() { /* ... keep existing matrix setup ... */ }
function resetXortronOS() {
if (confirm("Confirm Reset? This will erase all files and settings.")) {
VFS.reset(); // This handles clearing localStorage and reloading defaults
// Optionally close all windows except settings?
// AppManager.getOpenWindows().forEach(id => { if (id !== 'settings') AppManager.closeWindow(id); });
showNotification("System Reset Complete.");
}
}
// --- Initialization ---
document.addEventListener('DOMContentLoaded', () => {
// F: Restore theme from localStorage
const savedTheme = localStorage.getItem('themeColor');
if (savedTheme) changeTheme(savedTheme);
// F: Restore matrix state from localStorage
const matrixEnabled = localStorage.getItem('matrixEnabled');
toggleMatrix(matrixEnabled !== 'false'); // Enable by default
Terminal.init(); // Initialize Terminal first as VFS relies on it implicitly via CWD
FileExplorer.render(); // Initial render
AppManager.makeWindowsDraggable(); // Setup dragging for all window headers
setInterval(updateClock, 1000); updateClock(); // Start clock
// Start random notifications (moved here)
const systemMessages = [ /* ... */ ];
setInterval(() => { /* ... random notification logic ... */ }, 45000);
// Debounced resize handler (keep existing logic)
let resizeTimeout;
window.addEventListener('resize', () => { /* ... existing resize logic ... */ });
// Open browser by default (optional)
// AppManager.openApp('browser');
console.log("XortronOS v8 Initialized. Welcome, agent.");
showNotification("XortronOS v8 Boot Complete.");
});
</script>
</body>
</html>