Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> | |
| <title>Mios - Ultimate Web OS</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> | |
| @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap'); | |
| :root { | |
| --glass-border: rgba(255, 255, 255, 0.2); | |
| --glass-bg: rgba(255, 255, 255, 0.75); | |
| --glass-dark: rgba(28, 28, 30, 0.75); | |
| --accent: #007AFF; | |
| } | |
| body { | |
| font-family: 'Inter', sans-serif; | |
| overflow: hidden; | |
| user-select: none; | |
| background: #000; | |
| } | |
| /* Wallpaper */ | |
| #wallpaper { | |
| position: absolute; | |
| inset: 0; | |
| background-size: cover; | |
| background-position: center; | |
| background-image: url('https://images.unsplash.com/photo-1618005182384-a83a8bd57fbe?q=80&w=2564&auto=format&fit=crop'); | |
| transition: transform 0.3s; | |
| z-index: 0; | |
| } | |
| /* Glassmorphism */ | |
| .glass { | |
| background: var(--glass-bg); | |
| backdrop-filter: blur(20px); | |
| -webkit-backdrop-filter: blur(20px); | |
| border: 1px solid var(--glass-border); | |
| box-shadow: 0 15px 35px rgba(0,0,0,0.2); | |
| } | |
| .glass-dark { | |
| background: var(--glass-dark); | |
| backdrop-filter: blur(20px); | |
| -webkit-backdrop-filter: blur(20px); | |
| border: 1px solid rgba(255,255,255,0.1); | |
| color: white; | |
| } | |
| /* Icons */ | |
| .app-icon { | |
| transition: all 0.2s cubic-bezier(0.25, 0.46, 0.45, 0.94); | |
| } | |
| .app-icon:hover { transform: scale(1.15) translateY(-8px); } | |
| .app-icon:active { transform: scale(0.95); filter: brightness(0.8); } | |
| /* Window Animations */ | |
| .window { | |
| transition: transform 0.2s, opacity 0.2s, width 0.05s, height 0.05s; /* Faster width/height for resizing */ | |
| opacity: 0; | |
| transform: scale(0.95); | |
| display: flex; | |
| flex-direction: column; | |
| overflow: hidden; | |
| } | |
| .window.active { opacity: 1; transform: scale(1); } | |
| .window.minimized { | |
| opacity: 0; | |
| transform: scale(0.5) translate(0, 500px); | |
| pointer-events: none; | |
| } | |
| /* Context Menu & Dropdowns */ | |
| .context-menu { | |
| animation: fadeScale 0.1s ease-out; | |
| transform-origin: top left; | |
| } | |
| @keyframes fadeScale { | |
| from { opacity: 0; transform: scale(0.95); } | |
| to { opacity: 1; transform: scale(1); } | |
| } | |
| /* Utilities */ | |
| .resize-handle { | |
| position: absolute; | |
| bottom: 0; | |
| right: 0; | |
| width: 15px; | |
| height: 15px; | |
| cursor: nwse-resize; | |
| z-index: 50; | |
| } | |
| /* Flash Animation for Camera */ | |
| @keyframes flash { | |
| 0% { opacity: 0; } | |
| 10% { opacity: 1; background: white; } | |
| 100% { opacity: 0; } | |
| } | |
| .flash-overlay { | |
| animation: flash 0.5s ease-out; | |
| pointer-events: none; | |
| } | |
| /* Scrollbars */ | |
| ::-webkit-scrollbar { width: 6px; height: 6px; } | |
| ::-webkit-scrollbar-track { background: transparent; } | |
| ::-webkit-scrollbar-thumb { background: rgba(128,128,128,0.4); border-radius: 3px; } | |
| ::-webkit-scrollbar-thumb:hover { background: rgba(128,128,128,0.6); } | |
| </style> | |
| </head> | |
| <body class="h-screen w-screen overflow-hidden text-gray-900" oncontextmenu="return false;"> | |
| <!-- Desktop Wallpaper --> | |
| <div id="wallpaper" onclick="handleDesktopClick(event)" oncontextmenu="handleRightClick(event)"></div> | |
| <!-- Boot Screen --> | |
| <div id="boot-screen" class="absolute inset-0 bg-black flex flex-col items-center justify-center text-white z-[9999]"> | |
| <i class="fab fa-apple text-7xl mb-10"></i> | |
| <div class="w-56 h-1.5 bg-gray-800 rounded-full overflow-hidden"> | |
| <div class="h-full bg-white w-0 transition-all duration-[2500ms] ease-out" id="boot-bar"></div> | |
| </div> | |
| </div> | |
| <!-- Top Bar --> | |
| <div class="absolute top-0 left-0 right-0 h-8 glass-dark z-50 flex justify-between items-center px-4 text-xs font-medium select-none"> | |
| <div class="flex items-center space-x-4 relative"> | |
| <div class="hover:text-gray-300 cursor-pointer px-1 relative group" onclick="toggleMenu('apple-menu')"> | |
| <i class="fab fa-apple text-base"></i> | |
| <!-- Apple Menu Dropdown --> | |
| <div id="apple-menu" class="hidden absolute top-6 left-0 w-48 bg-gray-800/90 backdrop-blur-xl border border-gray-600 rounded-lg shadow-xl py-1 text-white flex-col"> | |
| <div class="px-4 py-1 hover:bg-blue-600 cursor-pointer" onclick="openApp('settings')">System Preferences...</div> | |
| <div class="h-[1px] bg-gray-600 my-1"></div> | |
| <div class="px-4 py-1 hover:bg-blue-600 cursor-pointer" onclick="location.reload()">Restart...</div> | |
| <div class="px-4 py-1 hover:bg-blue-600 cursor-pointer" onclick="shutdown()">Shut Down...</div> | |
| </div> | |
| </div> | |
| <span class="font-bold" id="active-app-name">Finder</span> | |
| <!-- Top Menu Items --> | |
| <div class="relative group cursor-pointer hover:text-gray-300" onclick="toggleMenu('file-menu')"> | |
| File | |
| <div id="file-menu" class="hidden absolute top-6 left-0 w-40 bg-gray-800/90 backdrop-blur-xl border border-gray-600 rounded-lg shadow-xl py-1 text-white flex-col font-normal"> | |
| <div class="px-4 py-1 hover:bg-blue-600" onclick="alertMsg('New Window')">New Window</div> | |
| <div class="px-4 py-1 hover:bg-blue-600" onclick="alertMsg('New Folder')">New Folder</div> | |
| </div> | |
| </div> | |
| <span class="hidden md:inline hover:text-gray-300 cursor-pointer" onclick="alertMsg('Edit Menu')">Edit</span> | |
| <span class="hidden md:inline hover:text-gray-300 cursor-pointer" onclick="alertMsg('View Menu')">View</span> | |
| <span class="hidden md:inline hover:text-gray-300 cursor-pointer" onclick="alertMsg('Go Menu')">Go</span> | |
| <span class="hidden md:inline hover:text-gray-300 cursor-pointer" onclick="alertMsg('Help Menu')">Help</span> | |
| </div> | |
| <!-- Right Status Icons --> | |
| <div class="flex items-center space-x-3"> | |
| <div class="flex items-center space-x-3 px-2 py-1 rounded hover:bg-white/10 cursor-pointer transition" onclick="toggleControlCenter()"> | |
| <i id="status-wifi" class="fas fa-wifi text-sm"></i> | |
| <i class="fas fa-battery-full text-sm text-green-400"></i> | |
| <span id="clock" class="text-sm font-medium ml-2">12:00</span> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Control Center --> | |
| <div id="control-center" class="absolute top-10 right-2 w-80 glass-dark rounded-2xl p-4 translate-y-[-150%] transition-transform duration-300 z-50 shadow-2xl text-white"> | |
| <div class="grid grid-cols-2 gap-3 mb-3"> | |
| <div class="bg-white/10 rounded-xl p-3 flex flex-col gap-2"> | |
| <div class="flex items-center gap-3 cursor-pointer group" onclick="toggleSystemState('wifi')"> | |
| <div id="cc-wifi-icon" class="w-8 h-8 rounded-full bg-blue-500 flex items-center justify-center transition"><i class="fas fa-wifi"></i></div> | |
| <div class="flex flex-col"> | |
| <span class="text-xs font-bold">Wi-Fi</span> | |
| <span id="cc-wifi-text" class="text-[10px] text-gray-300">Home_5G</span> | |
| </div> | |
| </div> | |
| <div class="flex items-center gap-3 cursor-pointer group" onclick="toggleSystemState('bluetooth')"> | |
| <div id="cc-bt-icon" class="w-8 h-8 rounded-full bg-blue-500 flex items-center justify-center transition"><i class="fab fa-bluetooth-b"></i></div> | |
| <div class="flex flex-col"> | |
| <span class="text-xs font-bold">Bluetooth</span> | |
| <span id="cc-bt-text" class="text-[10px] text-gray-300">On</span> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="bg-white/10 rounded-xl p-3 flex flex-col justify-between"> | |
| <div class="flex items-center space-x-2"> | |
| <div class="w-8 h-8 rounded-full bg-orange-500 flex items-center justify-center"><i class="fas fa-music"></i></div> | |
| <span class="text-xs font-bold">Music</span> | |
| </div> | |
| <div class="text-xs text-gray-400 text-center mt-2">Not Playing</div> | |
| <div class="flex justify-center text-lg space-x-4 mt-1"> | |
| <i class="fas fa-backward hover:text-white cursor-pointer"></i> | |
| <i class="fas fa-play hover:text-white cursor-pointer"></i> | |
| <i class="fas fa-forward hover:text-white cursor-pointer"></i> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Sliders --> | |
| <div class="bg-white/10 rounded-xl p-3 mb-3"> | |
| <div class="text-[10px] font-bold mb-1 uppercase text-gray-400">Display</div> | |
| <input type="range" min="0" max="100" value="80" class="w-full h-6 rounded-full overflow-hidden appearance-none bg-gray-700 cursor-pointer accent-white" oninput="document.getElementById('wallpaper').style.filter = `brightness(${this.value}%)`"> | |
| </div> | |
| <div class="bg-white/10 rounded-xl p-3"> | |
| <div class="text-[10px] font-bold mb-1 uppercase text-gray-400">Sound</div> | |
| <input type="range" min="0" max="100" value="60" class="w-full h-6 rounded-full overflow-hidden appearance-none bg-gray-700 cursor-pointer accent-white"> | |
| </div> | |
| </div> | |
| <!-- Desktop Context Menu --> | |
| <div id="context-menu" class="hidden absolute w-48 bg-white/90 backdrop-blur-md rounded-lg shadow-xl py-1 z-[100] text-sm border border-gray-200 context-menu"> | |
| <div class="px-4 py-1.5 hover:bg-blue-500 hover:text-white cursor-pointer" onclick="openApp('settings'); switchSettingsTab('wallpaper')">Change Wallpaper...</div> | |
| <div class="px-4 py-1.5 hover:bg-blue-500 hover:text-white cursor-pointer" onclick="createNewFolder()">New Folder</div> | |
| <div class="h-[1px] bg-gray-300 my-1"></div> | |
| <div class="px-4 py-1.5 hover:bg-blue-500 hover:text-white cursor-pointer" onclick="alertMsg('Sorted by Name')">Sort By</div> | |
| <div class="px-4 py-1.5 hover:bg-blue-500 hover:text-white cursor-pointer" onclick="location.reload()">Refresh Simulation</div> | |
| </div> | |
| <!-- Window Area --> | |
| <div id="window-container" class="absolute inset-0 z-10 pointer-events-none overflow-hidden pb-20 pt-8"> | |
| <!-- Dynamic Windows --> | |
| </div> | |
| <!-- Dock --> | |
| <div class="absolute bottom-4 left-1/2 transform -translate-x-1/2 h-20 glass rounded-3xl flex items-center px-4 space-x-3 z-40 transition-all duration-300 hover:scale-105"> | |
| <div class="dock-item group relative" onclick="openApp('finder')"> | |
| <div class="w-14 h-14 bg-gradient-to-b from-blue-400 to-blue-600 rounded-xl flex items-center justify-center text-3xl text-white shadow-lg app-icon"><i class="far fa-face-smile"></i></div> | |
| <div class="absolute -bottom-12 left-1/2 transform -translate-x-1/2 bg-gray-800 text-white text-xs px-2 py-1 rounded opacity-0 group-hover:opacity-100 pointer-events-none transition">Finder</div> | |
| </div> | |
| <div class="dock-item group relative" onclick="openApp('browser')"> | |
| <div class="w-14 h-14 bg-white rounded-xl flex items-center justify-center text-3xl shadow-lg overflow-hidden app-icon"> | |
| <i class="fab fa-safari text-blue-500 text-5xl"></i> | |
| </div> | |
| <div class="absolute -bottom-12 left-1/2 transform -translate-x-1/2 bg-gray-800 text-white text-xs px-2 py-1 rounded opacity-0 group-hover:opacity-100 pointer-events-none transition">Safari</div> | |
| </div> | |
| <div class="dock-item group relative" onclick="openApp('ai')"> | |
| <div class="w-14 h-14 bg-gradient-to-tr from-purple-600 to-pink-500 rounded-xl flex items-center justify-center text-2xl text-white shadow-lg app-icon"><i class="fas fa-sparkles"></i></div> | |
| <div class="absolute -bottom-12 left-1/2 transform -translate-x-1/2 bg-gray-800 text-white text-xs px-2 py-1 rounded opacity-0 group-hover:opacity-100 pointer-events-none transition">Qwen AI</div> | |
| </div> | |
| <div class="dock-item group relative" onclick="openApp('notes')"> | |
| <div class="w-14 h-14 bg-yellow-400 rounded-xl flex items-center justify-center text-2xl text-white shadow-lg overflow-hidden app-icon relative"> | |
| <div class="absolute top-0 left-0 w-full h-4 bg-yellow-300"></div> | |
| <i class="fas fa-sticky-note mt-2 text-yellow-100"></i> | |
| </div> | |
| <div class="absolute -bottom-12 left-1/2 transform -translate-x-1/2 bg-gray-800 text-white text-xs px-2 py-1 rounded opacity-0 group-hover:opacity-100 pointer-events-none transition">Notes</div> | |
| </div> | |
| <div class="w-[1px] h-10 bg-gray-400/50 mx-2"></div> | |
| <div class="dock-item group relative" onclick="openApp('camera')"> | |
| <div class="w-14 h-14 bg-gray-200 rounded-xl flex items-center justify-center shadow-lg app-icon relative"> | |
| <div class="w-8 h-8 bg-black rounded-full border-2 border-gray-600"></div> | |
| <div class="absolute top-2 right-2 w-2 h-2 bg-red-500 rounded-full animate-pulse"></div> | |
| </div> | |
| <div class="absolute -bottom-12 left-1/2 transform -translate-x-1/2 bg-gray-800 text-white text-xs px-2 py-1 rounded opacity-0 group-hover:opacity-100 pointer-events-none transition">Camera</div> | |
| </div> | |
| <div class="dock-item group relative" onclick="openApp('settings')"> | |
| <div class="w-14 h-14 bg-gray-300 rounded-xl flex items-center justify-center text-3xl text-gray-700 shadow-lg app-icon"><i class="fas fa-cog"></i></div> | |
| <div class="absolute -bottom-12 left-1/2 transform -translate-x-1/2 bg-gray-800 text-white text-xs px-2 py-1 rounded opacity-0 group-hover:opacity-100 pointer-events-none transition">Settings</div> | |
| </div> | |
| <div class="dock-item group relative" onclick="openApp('bin')"> | |
| <div class="w-14 h-14 bg-white/20 rounded-xl flex items-center justify-center text-2xl text-gray-200 shadow-lg app-icon"><i class="fas fa-trash"></i></div> | |
| <div class="absolute -bottom-12 left-1/2 transform -translate-x-1/2 bg-gray-800 text-white text-xs px-2 py-1 rounded opacity-0 group-hover:opacity-100 pointer-events-none transition">Bin</div> | |
| </div> | |
| </div> | |
| <!-- JAVASCRIPT LOGIC --> | |
| <script> | |
| // --- GLOBAL STATE --- | |
| const system = { | |
| wifi: true, | |
| bluetooth: true, | |
| volume: 60, | |
| brightness: 100, | |
| wallpaper: 'https://images.unsplash.com/photo-1618005182384-a83a8bd57fbe?q=80&w=2564&auto=format&fit=crop', | |
| files: { | |
| 'Desktop': [ | |
| { name: 'Welcome.txt', type: 'text', content: 'Welcome to Mios!\n\nThis is a fully functional simulation. Try opening apps, changing settings, or taking a photo!' }, | |
| { name: 'Project_Specs.pdf', type: 'file' } | |
| ], | |
| 'Documents': [ | |
| { name: 'Resume.docx', type: 'file' }, | |
| { name: 'Budget.xlsx', type: 'file' }, | |
| { name: 'Notes_Idea.txt', type: 'text', content: 'Idea for app: A hybrid OS that runs in the browser.' } | |
| ], | |
| 'Pictures': [ | |
| { name: 'Vacation.jpg', type: 'image', src: 'https://images.unsplash.com/photo-1507525428034-b723cf961d3e?w=400' }, | |
| { name: 'Cat.png', type: 'image', src: 'https://images.unsplash.com/photo-1514888286974-6c03e2ca1dba?w=400' } | |
| ], | |
| 'Downloads': [] | |
| } | |
| }; | |
| // --- BOOT & INIT --- | |
| window.onload = () => { | |
| // Boot Animation | |
| setTimeout(() => document.getElementById('boot-bar').style.width = '100%', 100); | |
| setTimeout(() => { | |
| const screen = document.getElementById('boot-screen'); | |
| screen.style.opacity = '0'; | |
| setTimeout(() => screen.remove(), 1000); | |
| }, 2800); | |
| startClock(); | |
| }; | |
| // --- UTILS --- | |
| function startClock() { | |
| setInterval(() => { | |
| const now = new Date(); | |
| document.getElementById('clock').innerText = now.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); | |
| }, 1000); | |
| } | |
| function alertMsg(msg) { | |
| // Use a custom toast instead of alert() | |
| const toast = document.createElement('div'); | |
| toast.className = 'fixed top-12 left-1/2 transform -translate-x-1/2 bg-gray-800 text-white px-4 py-2 rounded-full text-sm shadow-lg z-[9999] animate-fade-in-down'; | |
| toast.innerHTML = `<i class="fas fa-info-circle mr-2"></i> ${msg}`; | |
| document.body.appendChild(toast); | |
| setTimeout(() => { | |
| toast.style.opacity = '0'; | |
| setTimeout(() => toast.remove(), 500); | |
| }, 2000); | |
| } | |
| function shutdown() { | |
| document.body.innerHTML = '<div class="h-screen w-screen bg-black flex items-center justify-center text-white"><i class="fas fa-power-off text-4xl animate-pulse"></i></div>'; | |
| } | |
| // --- SYSTEM UI INTERACTIONS --- | |
| // Context Menu | |
| function handleRightClick(e) { | |
| e.preventDefault(); | |
| const menu = document.getElementById('context-menu'); | |
| menu.style.left = e.clientX + 'px'; | |
| menu.style.top = e.clientY + 'px'; | |
| menu.classList.remove('hidden'); | |
| closeAllDropdowns(); | |
| } | |
| function handleDesktopClick(e) { | |
| document.getElementById('context-menu').classList.add('hidden'); | |
| closeAllDropdowns(); | |
| } | |
| function toggleMenu(id) { | |
| const menu = document.getElementById(id); | |
| const isHidden = menu.classList.contains('hidden'); | |
| closeAllDropdowns(); | |
| if (isHidden) menu.classList.remove('hidden'); | |
| } | |
| function closeAllDropdowns() { | |
| document.querySelectorAll('[id$="-menu"]').forEach(el => { | |
| if(el.id !== 'context-menu') el.classList.add('hidden'); | |
| }); | |
| document.getElementById('context-menu').classList.add('hidden'); | |
| } | |
| // Control Center | |
| function toggleControlCenter() { | |
| const cc = document.getElementById('control-center'); | |
| const current = cc.style.transform; | |
| cc.style.transform = current === 'translateY(0px)' ? 'translateY(-150%)' : 'translateY(0px)'; | |
| } | |
| function toggleSystemState(type) { | |
| if (type === 'wifi') { | |
| system.wifi = !system.wifi; | |
| const icon = document.getElementById('cc-wifi-icon'); | |
| const text = document.getElementById('cc-wifi-text'); | |
| const status = document.getElementById('status-wifi'); | |
| icon.className = `w-8 h-8 rounded-full flex items-center justify-center transition ${system.wifi ? 'bg-blue-500 text-white' : 'bg-gray-600 text-gray-400'}`; | |
| text.innerText = system.wifi ? 'Home_5G' : 'Off'; | |
| status.className = system.wifi ? 'fas fa-wifi text-sm' : 'fas fa-slash text-sm text-gray-500'; | |
| } else if (type === 'bluetooth') { | |
| system.bluetooth = !system.bluetooth; | |
| const icon = document.getElementById('cc-bt-icon'); | |
| const text = document.getElementById('cc-bt-text'); | |
| icon.className = `w-8 h-8 rounded-full flex items-center justify-center transition ${system.bluetooth ? 'bg-blue-500 text-white' : 'bg-gray-600 text-gray-400'}`; | |
| text.innerText = system.bluetooth ? 'On' : 'Off'; | |
| } | |
| } | |
| // --- WINDOW MANAGER --- | |
| let zIndex = 100; | |
| const activeWindows = {}; | |
| function openApp(appId, extraData = null) { | |
| if (activeWindows[appId]) { | |
| bringToFront(appId); | |
| if (extraData && appId === 'notes') loadNote(extraData); | |
| return; | |
| } | |
| const appConfig = getAppConfig(appId); | |
| const win = document.createElement('div'); | |
| win.id = `win-${appId}`; | |
| win.className = 'window glass rounded-xl absolute shadow-2xl pointer-events-auto resize-y'; | |
| win.style.width = appConfig.width + 'px'; | |
| win.style.height = appConfig.height + 'px'; | |
| win.style.left = (50 + Math.random() * 50) + 'px'; | |
| win.style.top = (50 + Math.random() * 50) + 'px'; | |
| win.style.zIndex = ++zIndex; | |
| win.innerHTML = ` | |
| <!-- Header --> | |
| <div class="h-9 bg-gray-200/80 border-b border-gray-300 flex items-center px-3 justify-between cursor-move select-none" onmousedown="startDrag(event, '${appId}')" ondblclick="maximizeApp('${appId}')"> | |
| <div class="flex space-x-2"> | |
| <div onclick="closeApp('${appId}')" class="w-3 h-3 rounded-full bg-red-500 hover:brightness-90 cursor-pointer flex items-center justify-center group"><i class="fas fa-times text-[6px] opacity-0 group-hover:opacity-100 text-black"></i></div> | |
| <div onclick="minimizeApp('${appId}')" class="w-3 h-3 rounded-full bg-yellow-500 hover:brightness-90 cursor-pointer flex items-center justify-center group"><i class="fas fa-minus text-[6px] opacity-0 group-hover:opacity-100 text-black"></i></div> | |
| <div onclick="maximizeApp('${appId}')" class="w-3 h-3 rounded-full bg-green-500 hover:brightness-90 cursor-pointer flex items-center justify-center group"><i class="fas fa-expand text-[6px] opacity-0 group-hover:opacity-100 text-black"></i></div> | |
| </div> | |
| <div class="font-semibold text-sm text-gray-600">${appConfig.title}</div> | |
| <div class="w-12"></div> | |
| </div> | |
| <!-- Content --> | |
| <div class="flex-1 bg-white relative overflow-hidden flex flex-col window-content"> | |
| ${appConfig.content} | |
| </div> | |
| <!-- Resize Handle --> | |
| <div class="resize-handle" onmousedown="startResize(event, '${appId}')"></div> | |
| `; | |
| document.getElementById('window-container').appendChild(win); | |
| activeWindows[appId] = win; | |
| requestAnimationFrame(() => win.classList.add('active')); | |
| updateMenuBar(appConfig.title); | |
| // Post-render initialization | |
| if (appId === 'camera') initCamera(); | |
| if (appId === 'finder') navigateFinder('Desktop'); | |
| if (appId === 'notes' && extraData) loadNote(extraData); | |
| if (appId === 'ai') setTimeout(() => addAIMessage("Hello! I am Qwen (Simulated). How can I help?", false), 500); | |
| } | |
| function closeApp(id) { | |
| const win = activeWindows[id]; | |
| if(win) { | |
| win.classList.remove('active'); | |
| setTimeout(() => win.remove(), 200); | |
| delete activeWindows[id]; | |
| if (id === 'camera') stopCamera(); | |
| } | |
| } | |
| function minimizeApp(id) { | |
| const win = activeWindows[id]; | |
| win.classList.add('minimized'); | |
| } | |
| function bringToFront(id) { | |
| const win = activeWindows[id]; | |
| if(win) { | |
| win.style.zIndex = ++zIndex; | |
| win.classList.remove('minimized'); | |
| updateMenuBar(getAppConfig(id).title); | |
| } | |
| } | |
| function maximizeApp(id) { | |
| const win = activeWindows[id]; | |
| if (win.dataset.maximized) { | |
| win.style.width = win.dataset.origW; | |
| win.style.height = win.dataset.origH; | |
| win.style.top = win.dataset.origT; | |
| win.style.left = win.dataset.origL; | |
| delete win.dataset.maximized; | |
| win.style.borderRadius = '0.75rem'; | |
| } else { | |
| win.dataset.origW = win.style.width; | |
| win.dataset.origH = win.style.height; | |
| win.dataset.origT = win.style.top; | |
| win.dataset.origL = win.style.left; | |
| win.style.width = '100%'; | |
| win.style.height = 'calc(100% - 32px)'; // Minus top bar | |
| win.style.top = '32px'; | |
| win.style.left = '0'; | |
| win.style.borderRadius = '0'; | |
| win.dataset.maximized = true; | |
| } | |
| } | |
| function updateMenuBar(name) { | |
| document.getElementById('active-app-name').innerText = name; | |
| } | |
| // --- DRAG & RESIZE LOGIC --- | |
| let dragObj = null, resizeObj = null; | |
| let initialX, initialY, initialW, initialH; | |
| function startDrag(e, id) { | |
| bringToFront(id); | |
| if(e.target.closest('.resize-handle')) return; // Avoid drag if resizing | |
| dragObj = activeWindows[id]; | |
| initialX = e.clientX - dragObj.getBoundingClientRect().left; | |
| initialY = e.clientY - dragObj.getBoundingClientRect().top; | |
| // Prevent dragging maximized windows | |
| if(dragObj.dataset.maximized) dragObj = null; | |
| } | |
| function startResize(e, id) { | |
| e.stopPropagation(); | |
| bringToFront(id); | |
| resizeObj = activeWindows[id]; | |
| initialX = e.clientX; | |
| initialY = e.clientY; | |
| initialW = parseInt(document.defaultView.getComputedStyle(resizeObj).width, 10); | |
| initialH = parseInt(document.defaultView.getComputedStyle(resizeObj).height, 10); | |
| } | |
| window.addEventListener('mousemove', (e) => { | |
| if (dragObj) { | |
| e.preventDefault(); | |
| dragObj.style.left = (e.clientX - initialX) + 'px'; | |
| dragObj.style.top = (e.clientY - initialY) + 'px'; | |
| } | |
| if (resizeObj) { | |
| e.preventDefault(); | |
| resizeObj.style.width = (initialW + (e.clientX - initialX)) + 'px'; | |
| resizeObj.style.height = (initialH + (e.clientY - initialY)) + 'px'; | |
| } | |
| }); | |
| window.addEventListener('mouseup', () => { | |
| dragObj = null; | |
| resizeObj = null; | |
| }); | |
| // --- APP CONTENT GENERATORS --- | |
| function getAppConfig(id) { | |
| const configs = { | |
| 'finder': { title: 'Finder', width: 800, height: 500, content: ` | |
| <div class="flex h-full"> | |
| <div class="w-48 bg-gray-50 border-r border-gray-200 p-4 space-y-2"> | |
| <div class="text-xs font-bold text-gray-400 mb-2 px-2">Favorites</div> | |
| ${['Desktop', 'Documents', 'Pictures', 'Downloads'].map(f => | |
| `<div onclick="navigateFinder('${f}')" class="flex items-center space-x-2 px-2 py-1.5 rounded cursor-pointer hover:bg-gray-200 text-sm text-gray-700"> | |
| <i class="fas fa-${f === 'Pictures' ? 'image' : f === 'Downloads' ? 'download' : 'folder'} text-blue-500 w-4"></i> | |
| <span>${f}</span> | |
| </div>` | |
| ).join('')} | |
| </div> | |
| <div class="flex-1 flex flex-col"> | |
| <div class="h-10 border-b flex items-center px-4 space-x-4 bg-white"> | |
| <div class="flex space-x-2"> | |
| <i class="fas fa-chevron-left text-gray-400 cursor-pointer hover:text-gray-600"></i> | |
| <i class="fas fa-chevron-right text-gray-400 cursor-pointer hover:text-gray-600"></i> | |
| </div> | |
| <div class="font-semibold text-sm" id="finder-path">Desktop</div> | |
| </div> | |
| <div class="flex-1 p-4 overflow-auto grid grid-cols-4 gap-4 content-start" id="finder-content"> | |
| <!-- Files injected here --> | |
| </div> | |
| </div> | |
| </div> | |
| `}, | |
| 'settings': { title: 'System Settings', width: 700, height: 450, content: ` | |
| <div class="flex h-full bg-gray-100"> | |
| <div class="w-1/3 bg-white border-r p-3 space-y-1 overflow-y-auto"> | |
| <div class="flex items-center p-2 rounded-lg bg-gray-100 mb-4"> | |
| <div class="w-10 h-10 bg-gray-300 rounded-full flex items-center justify-center text-white"><i class="fas fa-user"></i></div> | |
| <div class="ml-3"> | |
| <div class="text-sm font-bold">User</div> | |
| <div class="text-xs text-gray-500">Apple ID</div> | |
| </div> | |
| </div> | |
| <div onclick="switchSettingsTab('wifi')" class="p-2 hover:bg-blue-50 rounded cursor-pointer flex items-center text-sm"><i class="fas fa-wifi w-6 text-blue-500"></i> Wi-Fi</div> | |
| <div onclick="switchSettingsTab('bluetooth')" class="p-2 hover:bg-blue-50 rounded cursor-pointer flex items-center text-sm"><i class="fab fa-bluetooth w-6 text-blue-500"></i> Bluetooth</div> | |
| <div onclick="switchSettingsTab('wallpaper')" class="p-2 hover:bg-blue-50 rounded cursor-pointer flex items-center text-sm"><i class="fas fa-image w-6 text-purple-500"></i> Wallpaper</div> | |
| <div onclick="switchSettingsTab('ai')" class="p-2 hover:bg-blue-50 rounded cursor-pointer flex items-center text-sm"><i class="fas fa-microchip w-6 text-green-500"></i> Artificial Intelligence</div> | |
| </div> | |
| <div class="flex-1 p-6 overflow-y-auto" id="settings-content"> | |
| <!-- Dynamic Settings Content --> | |
| </div> | |
| </div> | |
| `}, | |
| 'browser': { title: 'Safari', width: 900, height: 600, content: ` | |
| <div class="flex flex-col h-full bg-white"> | |
| <div class="h-12 bg-gray-100 border-b flex items-center px-3 space-x-3"> | |
| <i class="fas fa-chevron-left text-gray-400 cursor-pointer"></i> | |
| <i class="fas fa-chevron-right text-gray-400 cursor-pointer"></i> | |
| <i class="fas fa-newspaper text-gray-600"></i> | |
| <form onsubmit="handleBrowserSearch(event)" class="flex-1 relative"> | |
| <input id="browser-url" type="text" class="w-full h-8 bg-gray-200 rounded-md pl-8 pr-3 text-sm focus:bg-white focus:ring-2 ring-blue-500 outline-none transition" placeholder="Search or enter website name"> | |
| <i class="fas fa-search absolute left-2.5 top-2.5 text-gray-400 text-xs"></i> | |
| </form> | |
| <i class="fas fa-plus text-gray-500 cursor-pointer"></i> | |
| </div> | |
| <div class="h-1 bg-blue-500 w-0 transition-all duration-1000" id="browser-load-bar"></div> | |
| <div class="flex-1 overflow-auto p-8 flex flex-col items-center justify-center" id="browser-view"> | |
| <div class="text-center"> | |
| <i class="fab fa-safari text-6xl text-gray-300 mb-4"></i> | |
| <h1 class="text-2xl font-bold text-gray-700">Start Page</h1> | |
| <div class="grid grid-cols-4 gap-6 mt-8"> | |
| ${['Apple', 'Google', 'Bing', 'Wiki'].map(n => ` | |
| <div onclick="loadUrl('${n.toLowerCase()}.com')" class="flex flex-col items-center cursor-pointer group"> | |
| <div class="w-12 h-12 bg-gray-100 rounded-xl flex items-center justify-center text-xl group-hover:bg-gray-200 transition">${n[0]}</div> | |
| <span class="text-xs mt-2 text-gray-500">${n}</span> | |
| </div> | |
| `).join('')} | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| `}, | |
| 'camera': { title: 'Camera', width: 600, height: 500, content: ` | |
| <div class="h-full bg-black flex flex-col relative overflow-hidden"> | |
| <video id="camera-feed" autoplay playsinline class="flex-1 object-cover transform scale-x-[-1]"></video> | |
| <div id="flash-overlay" class="absolute inset-0 bg-white opacity-0 pointer-events-none"></div> | |
| <div class="h-20 bg-black/40 absolute bottom-0 w-full flex items-center justify-center space-x-12 backdrop-blur-sm"> | |
| <div class="text-yellow-400 text-xs font-bold">PHOTO</div> | |
| <div onclick="takePhoto()" class="w-16 h-16 rounded-full border-4 border-white flex items-center justify-center cursor-pointer active:scale-90 transition"> | |
| <div class="w-14 h-14 bg-white rounded-full"></div> | |
| </div> | |
| <div class="text-white text-xs font-bold opacity-50">VIDEO</div> | |
| </div> | |
| </div> | |
| `}, | |
| 'notes': { title: 'Notes', width: 500, height: 400, content: ` | |
| <div class="h-full flex flex-col bg-yellow-50"> | |
| <div class="h-10 border-b border-yellow-200 flex items-center justify-between px-4"> | |
| <span class="text-xs text-gray-500" id="note-timestamp">Today</span> | |
| <i class="far fa-edit text-yellow-600 cursor-pointer"></i> | |
| </div> | |
| <textarea id="note-area" class="flex-1 bg-transparent p-4 outline-none resize-none text-gray-800 font-serif text-lg" placeholder="Type your notes here..."></textarea> | |
| </div> | |
| `}, | |
| 'ai': { title: 'Qwen AI', width: 700, height: 550, content: ` | |
| <div class="flex flex-col h-full bg-white"> | |
| <div class="flex-1 overflow-y-auto p-4 space-y-4" id="ai-chat-container"></div> | |
| <div class="p-4 bg-gray-50 border-t flex"> | |
| <input id="ai-input" type="text" class="flex-1 bg-white border rounded-full px-4 py-2 text-sm outline-none focus:ring-2 ring-purple-500" placeholder="Message Qwen..." onkeypress="if(event.key==='Enter') sendAIMessage()"> | |
| <button onclick="sendAIMessage()" class="ml-2 w-9 h-9 bg-purple-600 text-white rounded-full flex items-center justify-center hover:bg-purple-700"><i class="fas fa-arrow-up"></i></button> | |
| </div> | |
| </div> | |
| `}, | |
| 'bin': { title: 'Bin', width: 600, height: 400, content: ` | |
| <div class="flex-1 bg-white flex items-center justify-center flex-col text-gray-400"> | |
| <i class="fas fa-trash-alt text-5xl mb-4"></i> | |
| <span>Bin is empty</span> | |
| </div> | |
| `} | |
| }; | |
| return configs[id] || configs['finder']; | |
| } | |
| // --- FEATURE: FINDER --- | |
| let currentPath = 'Desktop'; | |
| function navigateFinder(path) { | |
| currentPath = path; | |
| document.getElementById('finder-path').innerText = path; | |
| const container = document.getElementById('finder-content'); | |
| if(!container) return; | |
| const files = system.files[path] || []; | |
| container.innerHTML = files.map(file => ` | |
| <div ondblclick="openFile('${file.name}')" class="flex flex-col items-center justify-center p-2 hover:bg-blue-100 rounded cursor-pointer group w-24"> | |
| ${getFileIcon(file)} | |
| <span class="text-xs text-center mt-2 text-gray-700 truncate w-full px-1 group-hover:text-blue-800">${file.name}</span> | |
| </div> | |
| `).join(''); | |
| } | |
| function getFileIcon(file) { | |
| if(file.type === 'image') return `<img src="${file.src}" class="w-14 h-14 object-cover rounded shadow-sm">`; | |
| if(file.type === 'text') return `<i class="fas fa-file-alt text-4xl text-gray-400"></i>`; | |
| return `<i class="fas fa-file text-4xl text-gray-400"></i>`; | |
| } | |
| function openFile(filename) { | |
| const file = system.files[currentPath].find(f => f.name === filename); | |
| if (!file) return; | |
| if (file.type === 'text') { | |
| openApp('notes', file.content); | |
| } else if (file.type === 'image') { | |
| // Create a preview window for image | |
| const id = 'img-' + Math.random().toString(36).substr(2, 9); | |
| const win = document.createElement('div'); | |
| // Simplified: just alert for now or update wallpaper | |
| if(confirm("Set as wallpaper?")) { | |
| system.wallpaper = `url('${file.src}')`; | |
| document.getElementById('wallpaper').style.backgroundImage = `url('${file.src}')`; | |
| } | |
| } else { | |
| alertMsg("Cannot open file type"); | |
| } | |
| } | |
| function createNewFolder() { | |
| const name = prompt("Folder Name:", "New Folder"); | |
| if(name) { | |
| if(!system.files['Desktop']) system.files['Desktop'] = []; | |
| system.files['Desktop'].push({ name: name, type: 'folder' }); | |
| if(activeWindows['finder']) navigateFinder(currentPath); // Refresh if open | |
| alertMsg("Folder created on Desktop"); | |
| } | |
| } | |
| // --- FEATURE: NOTES --- | |
| function loadNote(content) { | |
| const area = document.getElementById('note-area'); | |
| if(area) area.value = content; | |
| } | |
| // --- FEATURE: SETTINGS --- | |
| function switchSettingsTab(tab) { | |
| const container = document.getElementById('settings-content'); | |
| if (!container) return; | |
| // Highlight active sidebar item (simplified) | |
| let html = ''; | |
| if (tab === 'wifi') { | |
| html = ` | |
| <h2 class="text-xl font-bold mb-4">Wi-Fi</h2> | |
| <div class="flex items-center justify-between bg-white p-4 rounded-lg border"> | |
| <span>Wi-Fi</span> | |
| <div onclick="toggleSystemState('wifi'); switchSettingsTab('wifi')" class="w-12 h-6 ${system.wifi ? 'bg-blue-500' : 'bg-gray-300'} rounded-full relative cursor-pointer transition"> | |
| <div class="w-5 h-5 bg-white rounded-full absolute top-0.5 ${system.wifi ? 'left-6.5' : 'left-0.5'} shadow transition-all" style="left: ${system.wifi ? '26px' : '2px'}"></div> | |
| </div> | |
| </div> | |
| <p class="text-sm text-gray-500 mt-2">Known Networks: Home_5G, Office_Guest</p> | |
| `; | |
| } else if (tab === 'wallpaper') { | |
| const walls = [ | |
| 'https://images.unsplash.com/photo-1618005182384-a83a8bd57fbe?q=80&w=2564&auto=format&fit=crop', | |
| 'https://images.unsplash.com/photo-1477346611705-65d1883cee1e?auto=format&fit=crop&w=1000&q=80', | |
| 'https://images.unsplash.com/photo-1451187580459-43490279c0fa?auto=format&fit=crop&w=1000&q=80', | |
| 'https://images.unsplash.com/photo-1506744038136-46273834b3fb?auto=format&fit=crop&w=1000&q=80' | |
| ]; | |
| html = ` | |
| <h2 class="text-xl font-bold mb-4">Wallpaper</h2> | |
| <div class="grid grid-cols-2 gap-4"> | |
| ${walls.map(url => ` | |
| <div onclick="changeWallpaper('${url}')" class="h-24 rounded-lg bg-cover bg-center cursor-pointer border-2 border-transparent hover:border-blue-500" style="background-image: url('${url}')"></div> | |
| `).join('')} | |
| </div> | |
| `; | |
| } else if (tab === 'bluetooth') { | |
| html = ` | |
| <h2 class="text-xl font-bold mb-4">Bluetooth</h2> | |
| <div class="flex items-center justify-between bg-white p-4 rounded-lg border"> | |
| <span>Bluetooth</span> | |
| <div onclick="toggleSystemState('bluetooth'); switchSettingsTab('bluetooth')" class="w-12 h-6 ${system.bluetooth ? 'bg-blue-500' : 'bg-gray-300'} rounded-full relative cursor-pointer transition"> | |
| <div class="w-5 h-5 bg-white rounded-full absolute top-0.5 shadow transition-all" style="left: ${system.bluetooth ? '26px' : '2px'}"></div> | |
| </div> | |
| </div> | |
| <div class="mt-4 text-sm text-gray-500"> | |
| <div class="p-2 border-b">AirPods Pro <span class="float-right text-gray-400">Not Connected</span></div> | |
| <div class="p-2 border-b">Magic Keyboard <span class="float-right text-gray-400">Connected</span></div> | |
| </div> | |
| `; | |
| } else { | |
| html = `<div class="flex items-center justify-center h-full text-gray-400">Select a setting</div>`; | |
| } | |
| container.innerHTML = html; | |
| } | |
| function changeWallpaper(url) { | |
| document.getElementById('wallpaper').style.backgroundImage = `url('${url}')`; | |
| system.wallpaper = url; | |
| } | |
| // --- FEATURE: CAMERA --- | |
| function initCamera() { | |
| navigator.mediaDevices.getUserMedia({ video: true }) | |
| .then(stream => { | |
| const vid = document.getElementById('camera-feed'); | |
| if(vid) vid.srcObject = stream; | |
| }) | |
| .catch(() => { | |
| const vid = document.getElementById('camera-feed'); | |
| if(vid) { | |
| vid.parentElement.innerHTML = '<div class="flex items-center justify-center h-full text-white bg-gray-900">Camera not available</div>'; | |
| } | |
| }); | |
| } | |
| function stopCamera() { | |
| // Browser handles cleanup mostly, but good practice | |
| } | |
| function takePhoto() { | |
| const video = document.getElementById('camera-feed'); | |
| if (!video) return; | |
| // Flash effect | |
| const flash = document.getElementById('flash-overlay'); | |
| flash.style.animation = 'none'; | |
| flash.offsetHeight; /* trigger reflow */ | |
| flash.style.animation = 'flash 0.3s ease-out'; | |
| // Capture logic | |
| const canvas = document.createElement('canvas'); | |
| canvas.width = video.videoWidth; | |
| canvas.height = video.videoHeight; | |
| canvas.getContext('2d').drawImage(video, 0, 0); | |
| const imgUrl = canvas.toDataURL('image/jpeg'); | |
| // Save to system files | |
| const filename = `Photo_${Date.now()}.jpg`; | |
| system.files['Pictures'].push({ name: filename, type: 'image', src: imgUrl }); | |
| system.files['Downloads'].push({ name: filename, type: 'image', src: imgUrl }); // Add to downloads too | |
| alertMsg('Photo saved to Pictures'); | |
| } | |
| // --- FEATURE: BROWSER --- | |
| function handleBrowserSearch(e) { | |
| e.preventDefault(); | |
| const val = document.getElementById('browser-url').value; | |
| if(val) loadUrl(val); | |
| } | |
| function loadUrl(url) { | |
| const view = document.getElementById('browser-view'); | |
| const bar = document.getElementById('browser-load-bar'); | |
| const input = document.getElementById('browser-url'); | |
| input.value = url; | |
| bar.style.width = '80%'; | |
| view.innerHTML = '<div class="flex h-full items-center justify-center"><i class="fas fa-spinner fa-spin text-4xl text-gray-300"></i></div>'; | |
| setTimeout(() => { | |
| bar.style.width = '100%'; | |
| setTimeout(() => bar.style.width = '0', 200); | |
| // Mock Content | |
| let content = ''; | |
| if(url.includes('google')) content = `<div class="flex flex-col items-center mt-20"><h1 class="text-5xl font-bold text-gray-700 mb-4">Google</h1><input class="border rounded-full px-4 py-2 w-96 shadow-sm" placeholder="Search Google"></div>`; | |
| else if(url.includes('bing')) content = `<div class="flex flex-col items-center mt-20"><h1 class="text-5xl font-bold text-blue-700 mb-4">Bing</h1><input class="border rounded-full px-4 py-2 w-96 shadow-sm" placeholder="Search Bing"></div>`; | |
| else content = `<div class="p-10"><h1 class="text-3xl font-bold">${url}</h1><p class="mt-4 text-gray-600">This is a simulated webpage for ${url}. The sandbox prevents loading real external iframes properly.</p><div class="mt-8 h-4 bg-gray-200 rounded w-3/4"></div><div class="mt-2 h-4 bg-gray-200 rounded w-1/2"></div></div>`; | |
| view.innerHTML = content; | |
| }, 1000); | |
| } | |
| // --- FEATURE: AI --- | |
| function sendAIMessage() { | |
| const input = document.getElementById('ai-input'); | |
| const text = input.value; | |
| if(!text) return; | |
| const container = document.getElementById('ai-chat-container'); | |
| container.innerHTML += `<div class="flex justify-end mb-2"><div class="bg-blue-500 text-white px-4 py-2 rounded-2xl rounded-tr-none max-w-[80%]">${text}</div></div>`; | |
| input.value = ''; | |
| container.scrollTop = container.scrollHeight; | |
| setTimeout(() => { | |
| const reply = getAIResponse(text); | |
| container.innerHTML += `<div class="flex justify-start mb-2"><div class="bg-gray-100 text-gray-800 px-4 py-2 rounded-2xl rounded-tl-none max-w-[80%] shadow-sm">${reply}</div></div>`; | |
| container.scrollTop = container.scrollHeight; | |
| }, 800); | |
| } | |
| function getAIResponse(msg) { | |
| msg = msg.toLowerCase(); | |
| if(msg.includes('hello')) return "Hi there! I'm running locally in Mios."; | |
| if(msg.includes('time')) return "It's currently " + new Date().toLocaleTimeString(); | |
| if(msg.includes('joke')) return "Why did the developer go broke? Because he used up all his cache."; | |
| return "I'm a simulated AI model. I can answer basic questions or help you navigate this OS!"; | |
| } | |
| </script> | |
| </body> | |
| </html> |