diff --git a/.env b/.env new file mode 100644 index 0000000..19730ed --- /dev/null +++ b/.env @@ -0,0 +1,5 @@ +git_pw_zia=f8e1300d871e905e27ce54c3455a3343104d9b04 +git_pw_master=817e2660d9f51dc5db0ac1bd06674f37154d8e14 +OLLAMA_API_KEY=a36032b17f894a2aa2a26575117a5347.Iok_WbBUSryMP1irX5ACv-3L +OPEN_ROUTER_KEY=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 +OPEN_ROUTER_MODEL=nvidia/llama-nemotron-embed-vl-1b-v2:free \ No newline at end of file diff --git a/shelled/package-lock.json b/shelled/package-lock.json new file mode 100644 index 0000000..8b89bf5 --- /dev/null +++ b/shelled/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "shelled", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} diff --git a/shelled/plan.md b/shelled/plan.md new file mode 100644 index 0000000..4122269 --- /dev/null +++ b/shelled/plan.md @@ -0,0 +1,300 @@ +```markdown +# Shelled OS UI Development Specification for LLM Implementation + +## Project Overview +Generate a complete Shelled OS UI implementation using HTML/CSS/JavaScript with Tauri 2.0 integration. The UI is a desktop environment that runs applications from Android, Windows, macOS, and Linux platforms. + +## Technology Stack Requirements +- Frontend: HTML5, CSS3, JavaScript ES6+ modules +- Backend: Tauri 2.0 with Rust +- Rendering: WebView +- Icons: Google Material Icons +- Fonts: Outfit (primary), Noto Sans SC (secondary) +- Build System: Cargo (Rust) + npm + +## Project Structure to Generate +``` +shelled-os-ui/ +├── src/ +│ ├── index.html +│ ├── css/ +│ │ ├── variables.css +│ │ ├── main.css +│ │ ├── taskbar.css +│ │ ├── start-menu.css +│ │ ├── file-explorer.css +│ │ ├── browser.css +│ │ └── settings.css +│ ├── js/ +│ │ ├── main.js +│ │ └── components/ +│ │ ├── Taskbar.js +│ │ ├── StartMenu.js +│ │ ├── FileExplorer.js +│ │ ├── Browser.js +│ │ └── Settings.js +│ └── assets/ +│ └── images/ +│ └── conch-shell.png +├── src-tauri/ +│ ├── src/ +│ │ └── main.rs +│ ├── Cargo.toml +│ └── tauri.conf.json +└── package.json +``` + +## HTML Structure Requirements + +### Main Layout (index.html) +- Full-screen desktop environment container +- Gradient background (purple to blue gradient) +- Bottom taskbar component +- Popup windows for: Start Menu, File Explorer, Browser, Settings +- Include Material Icons and Google Fonts CDN links + +### Taskbar Component +- Fixed at bottom of screen +- Height: 60px +- Background: Glassmorphism effect (semi-transparent white with blur) +- Contains 4 clickable icons: Shell (start), Folder, Browser, Settings +- System tray on right with WiFi, Volume, Battery icons +- All icons must have hover effects and click handlers + +### Start Menu Component +- Position: Bottom-left, above taskbar +- Size: 650px × 540px +- Header with "All Applications" title +- Search input box with real-time filtering +- Two-column grid layout for apps +- Alphabetically organized with letter dividers (A, B, C, D, G, M, W, Y) +- Each app item contains: icon, name, description +- Selection state with blue left border indicator + +### File Explorer Component +- Position: Bottom, offset from folder icon +- Size: 580px × 480px +- Header with "File Explorer" title and close button +- List of mock files and folders +- Click to select functionality + +### Browser Component +- Position: Bottom, offset from browser icon +- Size: 520px × 420px +- Header with "Browser" title and close button +- Address bar with lock icon +- Content area with placeholder + +### Settings Component +- Position: Bottom, offset from settings icon +- Size: 460px × 380px +- Header with "Settings" title and close button +- List of toggle switches for: Dark Mode, Notifications, Sound Effects, Bluetooth, Location Services +- Toggle switches must be clickable and change state + +## CSS Requirements + +### Design System Variables +```css +:root { + --primary-blue: #0078D7; + --secondary-blue: #005A9E; + --accent-cyan: #00BCF2; + --bg-white: #FFFFFF; + --text-dark: #1F1F1F; + --text-grey: #605E5C; + --glass-bg: rgba(255, 255, 255, 0.15); + --glass-border: rgba(255, 255, 255, 0.2); + --shadow-lg: 0 -8px 32px rgba(0, 0, 0, 0.3); +} +``` + +### Animation Requirements +- Popup windows: Slide up with scale animation (0.95 to 1.0) +- Hover effects: scale(1.05) for buttons, translateX(4px) for list items +- Transition timing: 0.2-0.3s ease +- Use cubic-bezier(0.175, 0.885, 0.32, 1.275) for popup animations + +### Glassmorphism Effects +- Background: rgba(255, 255, 255, 0.15) +- Backdrop-filter: blur(20px) +- Border: 1px solid rgba(255, 255, 255, 0.2) + +## JavaScript Requirements + +### Main Controller (main.js) +- Initialize all component controllers +- Manage popup state (only one popup open at a time) +- Click outside to close popups +- Export application data (list of apps with icons, colors, descriptions) + +### Component Controllers +Each component should have: +- `element` property referencing the DOM element +- `buttonElement` property for the triggering button +- `open()` method to show popup +- `close()` method to hide popup +- `render()` method to populate content +- `initialize()` method to setup event listeners + +### App Data Structure +```javascript +[ + { name: "Angry Birds", icon: "mood_bad", color: "red", desc: "Mobile Game", letter: "A" }, + { name: "Adobe Photoshop", icon: "edit", color: "green", desc: "Image Editor", letter: "A" }, + { name: "Brave Browser", icon: "language", color: "blue", desc: "Web Browser", letter: "B" }, + { name: "Books", icon: "book", color: "purple", desc: "Reading App", letter: "B" }, + { name: "Chrome", icon: "code", color: "orange", desc: "Web Browser", letter: "C" }, + { name: "Discord", icon: "chat", color: "blue", desc: "Communication", letter: "D" }, + { name: "GarageBand", icon: "music_note", color: "yellow", desc: "macOS Music App", letter: "G" }, + { name: "Movies & TV", icon: "movie", color: "purple", desc: "Media Player", letter: "M" }, + { name: "Music", icon: "music_note", color: "green", desc: "Audio Player", letter: "M" }, + { name: "Word", icon: "description", color: "default", desc: "Document Editor", letter: "W" }, + { name: "YouTube", icon: "theaters", color: "orange", desc: "Video Platform", letter: "Y" } +] +``` + +### Search Functionality +- Real-time filtering as user types +- Filter by app name (case-insensitive) +- Show/hide letter dividers based on visible apps +- Clear search when popup closes + +### File Explorer Data +```javascript +[ + { name: "Documents", type: "folder", icon: "folder", meta: "5 items • Modified today" }, + { name: "Downloads", type: "folder", icon: "folder", meta: "12 items • Modified yesterday" }, + { name: "Pictures", type: "folder", icon: "folder", meta: "28 items • Modified 2 days ago" }, + { name: "report.pdf", type: "file", icon: "insert_drive_file", meta: "PDF • 2.4 MB • Today" }, + { name: "photo_001.jpg", type: "image", icon: "image", meta: "JPEG • 3.8 MB • Today" }, + { name: "notes.txt", type: "document", icon: "description", meta: "Text • 1.2 KB • Yesterday" }, + { name: "Music", type: "folder", icon: "folder", meta: "45 items • Modified last week" } +] +``` + +### Settings Data +```javascript +[ + { label: "Dark Mode", icon: "dark_mode", active: false }, + { label: "Notifications", icon: "notifications", active: true }, + { label: "Sound Effects", icon: "volume_up", active: true }, + { label: "Bluetooth", icon: "bluetooth", active: false }, + { label: "Location Services", icon: "location_on", active: false } +] +``` + +## Tauri Configuration Requirements + +### tauri.conf.json +- Product name: "shelled-os" +- Version: "0.1.0" +- Window configuration: fullscreen, resizable +- Identifier: "com.shelledos.app" +- Enable all Tauri features for development + +### Cargo.toml +- Package name: "shelled-os-ui" +- Tauri version: "2.0" +- Dependencies: tauri, serde, serde_json +- Edition: "2021" + +### main.rs +- Simple Tauri builder initialization +- Windows subsystem configuration for release builds + +## Interactive Behaviors + +### Click Interactions +1. Click Shell button → Toggle Start Menu +2. Click Folder button → Toggle File Explorer +3. Click Browser button → Toggle Browser popup +4. Click Settings button → Toggle Settings popup +5. Click app item → Select it (add blue left border) +6. Click file item → Select it +7. Click toggle switch → Toggle active state +8. Click close button (X) → Close respective popup +9. Click outside popup → Close all popups + +### Search Interactions +1. Type in search box → Filter apps in real-time +2. Empty search → Show all apps +3. Partial match → Show matching apps +4. No match → Show no apps (hide letter dividers) + +### Visual Feedback +1. Hover over button → Scale up (1.05) +2. Hover over list item → Slide right (4px) and change color +3. Active popup → Button shows indicator +4. Selected item → Blue left border appears +5. Toggle switch → Smooth transition of thumb position + +## Color Coding for App Icons +- red: linear-gradient(135deg, #e52e00 0%, #ff6b35 100%) +- green: linear-gradient(135deg, #11998e 0%, #38ef7d 100%) +- blue: linear-gradient(135deg, #0078D7 0%, #00BCF2 100%) +- orange: linear-gradient(135deg, #f093fb 0%, #f5576c 100%) +- purple: linear-gradient(135deg, #a18cd1 0%, #fbc2eb 100%) +- yellow: linear-gradient(135deg, #f7971e 0%, #ffd200 100%) +- default: linear-gradient(135deg, #667eea 0%, #764ba2 100%) + +## File Icon Colors +- folder: #ff9800 +- file: #90caf9 +- image: #66bb6a +- document: #42a5f5 + +## Performance Requirements +- Startup time: < 3 seconds +- Menu open animation: < 200ms +- Search filtering: < 50ms response time +- All animations: 60fps +- Memory usage: < 150MB + +## Browser Compatibility +- Chrome 90+ +- Firefox 88+ +- Safari 14+ +- Edge 90+ + +## Testing Checklist +After implementation, verify: +- [ ] Application opens fullscreen +- [ ] Taskbar displays at bottom +- [ ] All 4 icons are clickable +- [ ] Clicking Shell opens Start Menu +- [ ] Start Menu shows 11 apps alphabetically +- [ ] Search filters apps correctly +- [ ] Clicking apps selects them +- [ ] File Explorer opens with mock files +- [ ] Browser popup opens +- [ ] Settings popup opens with working toggles +- [ ] Close buttons work +- [ ] Click outside closes popups +- [ ] Animations are smooth +- [ ] No console errors + +## Implementation Notes +- Use ES6 modules for JavaScript +- All event listeners must use stopPropagation where appropriate +- Use document fragments for DOM manipulation +- Implement proper cleanup when closing popups +- Use CSS transitions, not JavaScript animations where possible +- Ensure z-index management prevents layering issues +- All text must use the Outfit font family + +## Critical Constraints +1. DO NOT use any external JavaScript libraries +2. DO NOT modify the specified color values +3. DO NOT change the layout dimensions +4. DO NOT alter the interaction behaviors +5. DO NOT add additional features beyond specification +6. MUST maintain exact animations as specified +7. MUST use glassmorphism effects as described +8. MUST implement all search functionality +9. MUST ensure all toggles are interactive +10. MUST follow the exact component architecture + +--- +End of Specification Document +``` \ No newline at end of file diff --git a/shelled/shelled-os-ui/.gitignore b/shelled/shelled-os-ui/.gitignore new file mode 100644 index 0000000..04c01ba --- /dev/null +++ b/shelled/shelled-os-ui/.gitignore @@ -0,0 +1,2 @@ +node_modules/ +dist/ \ No newline at end of file diff --git a/shelled/shelled-os-ui/package-lock.json b/shelled/shelled-os-ui/package-lock.json new file mode 100644 index 0000000..ed61712 --- /dev/null +++ b/shelled/shelled-os-ui/package-lock.json @@ -0,0 +1,244 @@ +{ + "name": "shelled-os-ui", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "shelled-os-ui", + "version": "1.0.0", + "devDependencies": { + "@tauri-apps/api": "^2.0.0-rc", + "@tauri-apps/cli": "^2.0.0-rc" + } + }, + "node_modules/@tauri-apps/api": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.10.1.tgz", + "integrity": "sha512-hKL/jWf293UDSUN09rR69hrToyIXBb8CjGaWC7gfinvnQrBVvnLr08FeFi38gxtugAVyVcTa5/FD/Xnkb1siBw==", + "dev": true, + "license": "Apache-2.0 OR MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/tauri" + } + }, + "node_modules/@tauri-apps/cli": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.10.1.tgz", + "integrity": "sha512-jQNGF/5quwORdZSSLtTluyKQ+o6SMa/AUICfhf4egCGFdMHqWssApVgYSbg+jmrZoc8e1DscNvjTnXtlHLS11g==", + "dev": true, + "license": "Apache-2.0 OR MIT", + "bin": { + "tauri": "tauri.js" + }, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/tauri" + }, + "optionalDependencies": { + "@tauri-apps/cli-darwin-arm64": "2.10.1", + "@tauri-apps/cli-darwin-x64": "2.10.1", + "@tauri-apps/cli-linux-arm-gnueabihf": "2.10.1", + "@tauri-apps/cli-linux-arm64-gnu": "2.10.1", + "@tauri-apps/cli-linux-arm64-musl": "2.10.1", + "@tauri-apps/cli-linux-riscv64-gnu": "2.10.1", + "@tauri-apps/cli-linux-x64-gnu": "2.10.1", + "@tauri-apps/cli-linux-x64-musl": "2.10.1", + "@tauri-apps/cli-win32-arm64-msvc": "2.10.1", + "@tauri-apps/cli-win32-ia32-msvc": "2.10.1", + "@tauri-apps/cli-win32-x64-msvc": "2.10.1" + } + }, + "node_modules/@tauri-apps/cli-darwin-arm64": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.10.1.tgz", + "integrity": "sha512-Z2OjCXiZ+fbYZy7PmP3WRnOpM9+Fy+oonKDEmUE6MwN4IGaYqgceTjwHucc/kEEYZos5GICve35f7ZiizgqEnQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-darwin-x64": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.10.1.tgz", + "integrity": "sha512-V/irQVvjPMGOTQqNj55PnQPVuH4VJP8vZCN7ajnj+ZS8Kom1tEM2hR3qbbIRoS3dBKs5mbG8yg1WC+97dq17Pw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-arm-gnueabihf": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.10.1.tgz", + "integrity": "sha512-Hyzwsb4VnCWKGfTw+wSt15Z2pLw2f0JdFBfq2vHBOBhvg7oi6uhKiF87hmbXOBXUZaGkyRDkCHsdzJcIfoJC2w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-arm64-gnu": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.10.1.tgz", + "integrity": "sha512-OyOYs2t5GkBIvyWjA1+h4CZxTcdz1OZPCWAPz5DYEfB0cnWHERTnQ/SLayQzncrT0kwRoSfSz9KxenkyJoTelA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-arm64-musl": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.10.1.tgz", + "integrity": "sha512-MIj78PDDGjkg3NqGptDOGgfXks7SYJwhiMh8SBoZS+vfdz7yP5jN18bNaLnDhsVIPARcAhE1TlsZe/8Yxo2zqg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-riscv64-gnu": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-riscv64-gnu/-/cli-linux-riscv64-gnu-2.10.1.tgz", + "integrity": "sha512-X0lvOVUg8PCVaoEtEAnpxmnkwlE1gcMDTqfhbefICKDnOTJ5Est3qL0SrWxizDackIOKBcvtpejrSiVpuJI1kw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-x64-gnu": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.10.1.tgz", + "integrity": "sha512-2/12bEzsJS9fAKybxgicCDFxYD1WEI9kO+tlDwX5znWG2GwMBaiWcmhGlZ8fi+DMe9CXlcVarMTYc0L3REIRxw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-x64-musl": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.10.1.tgz", + "integrity": "sha512-Y8J0ZzswPz50UcGOFuXGEMrxbjwKSPgXftx5qnkuMs2rmwQB5ssvLb6tn54wDSYxe7S6vlLob9vt0VKuNOaCIQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-win32-arm64-msvc": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.10.1.tgz", + "integrity": "sha512-iSt5B86jHYAPJa/IlYw++SXtFPGnWtFJriHn7X0NFBVunF6zu9+/zOn8OgqIWSl8RgzhLGXQEEtGBdR4wzpVgg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-win32-ia32-msvc": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.10.1.tgz", + "integrity": "sha512-gXyxgEzsFegmnWywYU5pEBURkcFN/Oo45EAwvZrHMh+zUSEAvO5E8TXsgPADYm31d1u7OQU3O3HsYfVBf2moHw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-win32-x64-msvc": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.10.1.tgz", + "integrity": "sha512-6Cn7YpPFwzChy0ERz6djKEmUehWrYlM+xTaNzGPgZocw3BD7OfwfWHKVWxXzdjEW2KfKkHddfdxK1XXTYqBRLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + } + } +} diff --git a/shelled/shelled-os-ui/package.json b/shelled/shelled-os-ui/package.json new file mode 100644 index 0000000..a05d09d --- /dev/null +++ b/shelled/shelled-os-ui/package.json @@ -0,0 +1,15 @@ +{ + "name": "shelled-os-ui", + "version": "1.0.0", + "description": "Shelled OS UI for Tauri", + "main": "src/js/main.js", + "scripts": { + "dev": "tauri dev", + "build": "tauri build" + }, + "dependencies": {}, + "devDependencies": { + "@tauri-apps/cli": "^2.0.0-rc", + "@tauri-apps/api": "^2.0.0-rc" + } +} diff --git a/shelled/shelled-os-ui/src-tauri/Cargo.toml b/shelled/shelled-os-ui/src-tauri/Cargo.toml new file mode 100644 index 0000000..5d9ca5a --- /dev/null +++ b/shelled/shelled-os-ui/src-tauri/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "shelled-os-ui" +version = "0.1.0" +description = "A Tauri App" +authors = ["you"] +edition = "2021" + +[build-dependencies] +tauri-build = { version = "2.0.0-rc", features = [] } + +[dependencies] +tauri = { version = "2.0.0-rc", features = [] } +serde = { version = "1", features = ["derive"] } +serde_json = "1" diff --git a/shelled/shelled-os-ui/src-tauri/src/main.rs b/shelled/shelled-os-ui/src-tauri/src/main.rs new file mode 100644 index 0000000..860b10d --- /dev/null +++ b/shelled/shelled-os-ui/src-tauri/src/main.rs @@ -0,0 +1,10 @@ +#![cfg_attr( + all(not(debug_assertions), target_os = "windows"), + windows_subsystem = "windows" +)] + +fn main() { + tauri::Builder::default() + .run(tauri::generate_context!()) + .expect("error while running tauri application"); +} diff --git a/shelled/shelled-os-ui/src-tauri/tauri.conf.json b/shelled/shelled-os-ui/src-tauri/tauri.conf.json new file mode 100644 index 0000000..0064f4f --- /dev/null +++ b/shelled/shelled-os-ui/src-tauri/tauri.conf.json @@ -0,0 +1,25 @@ +{ + "productName": "shelled-os", + "version": "0.1.0", + "identifier": "com.shelledos.app", + "build": { + "beforeDevCommand": "", + "beforeBuildCommand": "", + "devPath": "../src", + "distDir": "../src" + }, + "app": { + "windows": [ + { + "title": "Shelled OS", + "width": 1280, + "height": 720, + "fullscreen": true, + "resizable": true + } + ], + "security": { + "csp": null + } + } +} diff --git a/shelled/shelled-os-ui/src/css/browser.css b/shelled/shelled-os-ui/src/css/browser.css new file mode 100644 index 0000000..3559ec4 --- /dev/null +++ b/shelled/shelled-os-ui/src/css/browser.css @@ -0,0 +1,52 @@ +#browser { + left: 140px; /* Offset from browser icon */ + width: 520px; + height: 420px; + z-index: 80; +} + +.address-bar { + display: flex; + align-items: center; + padding: 8px 16px; + background: rgba(0, 0, 0, 0.2); + border-bottom: 1px solid var(--glass-border); +} + +.lock-icon { + font-size: 16px; + color: rgba(255, 255, 255, 0.7); + margin-right: 8px; +} + +.address-bar input { + flex-grow: 1; + background: transparent; + border: none; + color: var(--bg-white); + font-family: inherit; + font-size: 0.9rem; + outline: none; +} + +.browser-content { + flex-grow: 1; + background: var(--bg-white); + color: var(--text-dark); + padding: 32px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + text-align: center; +} + +.browser-content h1 { + font-weight: 600; + margin-bottom: 8px; + color: var(--primary-blue); +} + +.browser-content p { + color: var(--text-grey); +} diff --git a/shelled/shelled-os-ui/src/css/file-explorer.css b/shelled/shelled-os-ui/src/css/file-explorer.css new file mode 100644 index 0000000..05fc4ee --- /dev/null +++ b/shelled/shelled-os-ui/src/css/file-explorer.css @@ -0,0 +1,62 @@ +#file-explorer { + left: 80px; /* Offset from folder icon */ + width: 580px; + height: 480px; + z-index: 85; +} + +.file-list { + flex-grow: 1; + overflow-y: auto; + padding: 12px; +} + +.file-item { + display: flex; + align-items: center; + padding: 12px 16px; + border-radius: 6px; + cursor: pointer; + transition: transform 0.2s ease, background 0.2s ease; + border-left: 3px solid transparent; +} + +.file-item:hover { + background: rgba(255, 255, 255, 0.1); + transform: translateX(4px); +} + +.file-item.selected { + background: rgba(255, 255, 255, 0.15); + border-left: 3px solid var(--accent-cyan); +} + +.file-icon { + margin-right: 16px; + font-size: 28px; +} + +.file-info { + display: flex; + flex-direction: column; +} + +.file-name { + font-weight: 500; + font-size: 1rem; + margin-bottom: 2px; +} + +.file-meta { + font-size: 0.8rem; + color: rgba(255, 255, 255, 0.6); +} + +.file-list::-webkit-scrollbar { + width: 6px; +} + +.file-list::-webkit-scrollbar-thumb { + background: rgba(255, 255, 255, 0.2); + border-radius: 4px; +} diff --git a/shelled/shelled-os-ui/src/css/main.css b/shelled/shelled-os-ui/src/css/main.css new file mode 100644 index 0000000..7b1d483 --- /dev/null +++ b/shelled/shelled-os-ui/src/css/main.css @@ -0,0 +1,94 @@ +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + margin: 0; + padding: 0; + font-family: 'Outfit', sans-serif; + overflow: hidden; + color: var(--text-dark); +} + +#desktop { + width: 100vw; + height: 100vh; + background: linear-gradient(135deg, #4b1a8a 0%, #1e3c72 100%); + position: relative; + display: flex; + flex-direction: column; +} + +#popups-container { + flex-grow: 1; + position: relative; + pointer-events: none; /* Let clicks pass through desktop */ +} + +/* Glassmorphism utility */ +.glass { + background: var(--glass-bg); + backdrop-filter: blur(20px); + -webkit-backdrop-filter: blur(20px); + border: 1px solid var(--glass-border); + box-shadow: var(--shadow-lg); +} + +/* Base Popup Styles */ +.popup { + position: absolute; + bottom: 70px; + background: var(--glass-bg); + backdrop-filter: blur(20px); + -webkit-backdrop-filter: blur(20px); + border: 1px solid var(--glass-border); + border-radius: 12px; + box-shadow: var(--shadow-lg); + display: flex; + flex-direction: column; + opacity: 0; + transform: scale(0.95); + transition: opacity 0.25s cubic-bezier(0.175, 0.885, 0.32, 1.275), transform 0.25s cubic-bezier(0.175, 0.885, 0.32, 1.275); + visibility: hidden; + pointer-events: auto; /* Re-enable pointer events for popups */ + color: var(--bg-white); + overflow: hidden; +} + +.popup.active { + opacity: 1; + transform: scale(1.0); + visibility: visible; +} + +.popup-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 16px 20px; + border-bottom: 1px solid var(--glass-border); +} + +.popup-header h2 { + font-size: 1.25rem; + font-weight: 500; +} + +.close-btn { + background: none; + border: none; + color: var(--bg-white); + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + padding: 4px; + border-radius: 50%; + transition: background 0.2s ease; +} + +.close-btn:hover { + background: rgba(255, 255, 255, 0.1); +} diff --git a/shelled/shelled-os-ui/src/css/settings.css b/shelled/shelled-os-ui/src/css/settings.css new file mode 100644 index 0000000..cccf1ae --- /dev/null +++ b/shelled/shelled-os-ui/src/css/settings.css @@ -0,0 +1,71 @@ +#settings { + left: 200px; /* Offset from settings icon */ + width: 460px; + height: 380px; + z-index: 75; +} + +.settings-list { + flex-grow: 1; + padding: 20px; + display: flex; + flex-direction: column; + gap: 16px; + overflow-y: auto; +} + +.setting-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 12px 16px; + background: rgba(255, 255, 255, 0.05); + border-radius: 8px; + border: 1px solid rgba(255, 255, 255, 0.1); +} + +.setting-info { + display: flex; + align-items: center; +} + +.setting-icon { + margin-right: 12px; + font-size: 24px; + color: rgba(255, 255, 255, 0.8); +} + +.setting-label { + font-size: 1rem; + font-weight: 500; +} + +/* Toggle Switch Styles */ +.toggle-switch { + position: relative; + width: 44px; + height: 24px; + background: rgba(0, 0, 0, 0.3); + border-radius: 12px; + cursor: pointer; + transition: background 0.3s ease; +} + +.toggle-switch.active { + background: var(--primary-blue); +} + +.toggle-thumb { + position: absolute; + top: 2px; + left: 2px; + width: 20px; + height: 20px; + background: var(--bg-white); + border-radius: 50%; + transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275); +} + +.toggle-switch.active .toggle-thumb { + transform: translateX(20px); +} diff --git a/shelled/shelled-os-ui/src/css/start-menu.css b/shelled/shelled-os-ui/src/css/start-menu.css new file mode 100644 index 0000000..0801b30 --- /dev/null +++ b/shelled/shelled-os-ui/src/css/start-menu.css @@ -0,0 +1,115 @@ +#start-menu { + left: 20px; + width: 650px; + height: 540px; + z-index: 90; +} + +.search-container { + padding: 16px 20px; + display: flex; + align-items: center; + background: rgba(0, 0, 0, 0.1); + border-bottom: 1px solid var(--glass-border); +} + +.search-icon { + color: rgba(255, 255, 255, 0.6); + margin-right: 12px; +} + +#app-search { + flex-grow: 1; + background: transparent; + border: none; + color: var(--bg-white); + font-size: 1rem; + font-family: 'Outfit', sans-serif; + outline: none; +} + +#app-search::placeholder { + color: rgba(255, 255, 255, 0.5); +} + +.app-grid { + flex-grow: 1; + overflow-y: auto; + padding: 20px; + display: grid; + grid-template-columns: 1fr 1fr; + gap: 12px; + align-content: start; +} + +/* Custom Scrollbar for App Grid */ +.app-grid::-webkit-scrollbar { + width: 6px; +} + +.app-grid::-webkit-scrollbar-thumb { + background: rgba(255, 255, 255, 0.2); + border-radius: 4px; +} + +.letter-divider { + grid-column: 1 / -1; + padding-top: 12px; + padding-bottom: 4px; + margin-bottom: 4px; + font-weight: 600; + font-size: 1.1rem; + color: rgba(255, 255, 255, 0.8); + border-bottom: 1px solid rgba(255, 255, 255, 0.1); +} + +.app-item { + display: flex; + align-items: center; + padding: 12px; + border-radius: 8px; + cursor: pointer; + transition: transform 0.2s ease, background 0.2s ease, border-left 0.2s ease; + border-left: 3px solid transparent; +} + +.app-item:hover { + background: rgba(255, 255, 255, 0.1); + transform: translateX(4px); +} + +.app-item.selected { + background: rgba(255, 255, 255, 0.15); + border-left: 3px solid var(--accent-cyan); +} + +.app-icon { + width: 40px; + height: 40px; + border-radius: 8px; + display: flex; + align-items: center; + justify-content: center; + margin-right: 16px; + color: white; +} + +.app-icon .material-icons { + font-size: 24px; +} + +.app-info { + display: flex; + flex-direction: column; +} + +.app-name { + font-size: 1rem; + font-weight: 500; + margin-bottom: 2px; +} + +.app-desc { + font-size: 0.8rem; + color: rgba(255, 255, 255, 0.6); +} diff --git a/shelled/shelled-os-ui/src/css/taskbar.css b/shelled/shelled-os-ui/src/css/taskbar.css new file mode 100644 index 0000000..d3ca28c --- /dev/null +++ b/shelled/shelled-os-ui/src/css/taskbar.css @@ -0,0 +1,76 @@ +#taskbar { + height: 60px; + width: 100%; + background: var(--glass-bg); + backdrop-filter: blur(20px); + -webkit-backdrop-filter: blur(20px); + border-top: 1px solid var(--glass-border); + display: flex; + justify-content: space-between; + align-items: center; + padding: 0 20px; + position: relative; + z-index: 100; +} + +.taskbar-icons { + display: flex; + gap: 8px; + align-items: center; +} + +.taskbar-btn { + width: 44px; + height: 44px; + border-radius: 8px; + background: transparent; + border: none; + color: var(--bg-white); + display: flex; + justify-content: center; + align-items: center; + cursor: pointer; + transition: transform 0.2s ease, background 0.2s ease; +} + +.taskbar-btn img { + width: 24px; + height: 24px; +} + +.taskbar-btn:hover { + background: rgba(255, 255, 255, 0.1); + transform: scale(1.05); +} + +.taskbar-btn.active { + background: rgba(255, 255, 255, 0.2); + position: relative; +} + +.taskbar-btn.active::after { + content: ''; + position: absolute; + bottom: -8px; + width: 16px; + height: 3px; + background: var(--bg-white); + border-radius: 4px; +} + +.system-tray { + display: flex; + align-items: center; + gap: 16px; + color: var(--bg-white); +} + +.tray-icon { + font-size: 18px; + opacity: 0.9; +} + +.clock { + font-size: 0.9rem; + font-weight: 400; +} diff --git a/shelled/shelled-os-ui/src/css/variables.css b/shelled/shelled-os-ui/src/css/variables.css new file mode 100644 index 0000000..17e6767 --- /dev/null +++ b/shelled/shelled-os-ui/src/css/variables.css @@ -0,0 +1,26 @@ +:root { + --primary-blue: #0078D7; + --secondary-blue: #005A9E; + --accent-cyan: #00BCF2; + --bg-white: #FFFFFF; + --text-dark: #1F1F1F; + --text-grey: #605E5C; + --glass-bg: rgba(255, 255, 255, 0.15); + --glass-border: rgba(255, 255, 255, 0.2); + --shadow-lg: 0 -8px 32px rgba(0, 0, 0, 0.3); +} + +/* Color properties for app icons */ +.color-red { background: linear-gradient(135deg, #e52e00 0%, #ff6b35 100%); } +.color-green { background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%); } +.color-blue { background: linear-gradient(135deg, #0078D7 0%, #00BCF2 100%); } +.color-orange { background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); } +.color-purple { background: linear-gradient(135deg, #a18cd1 0%, #fbc2eb 100%); } +.color-yellow { background: linear-gradient(135deg, #f7971e 0%, #ffd200 100%); } +.color-default { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); } + +/* File colors */ +.file-folder { color: #ff9800; } +.file-file { color: #90caf9; } +.file-image { color: #66bb6a; } +.file-document { color: #42a5f5; } diff --git a/shelled/shelled-os-ui/src/index.html b/shelled/shelled-os-ui/src/index.html new file mode 100644 index 0000000..60db01c --- /dev/null +++ b/shelled/shelled-os-ui/src/index.html @@ -0,0 +1,101 @@ + + + + + + Shelled OS + + + + + + + + + + + +
+ +
+ + + + + + + + + + + +
+ + +
+
+ + + + +
+
+ wifi + volume_up + battery_full + 12:00 PM +
+
+
+ + + + diff --git a/shelled/shelled-os-ui/src/js/components/Browser.js b/shelled/shelled-os-ui/src/js/components/Browser.js new file mode 100644 index 0000000..1cbd903 --- /dev/null +++ b/shelled/shelled-os-ui/src/js/components/Browser.js @@ -0,0 +1,26 @@ +export default class Browser { + constructor(os) { + this.os = os; + this.element = document.getElementById('browser'); + this.closeBtn = this.element.querySelector('.close-btn'); + + this.initialize(); + } + + initialize() { + this.closeBtn.addEventListener('click', (e) => { + e.stopPropagation(); + this.os.closeAllPopups(); + }); + } + + open() { + this.element.classList.add('active'); + this.os.components.taskbar.updateActiveButton('browser'); + } + + close() { + this.element.classList.remove('active'); + this.os.components.taskbar.updateActiveButton(null); + } +} diff --git a/shelled/shelled-os-ui/src/js/components/FileExplorer.js b/shelled/shelled-os-ui/src/js/components/FileExplorer.js new file mode 100644 index 0000000..7a1175f --- /dev/null +++ b/shelled/shelled-os-ui/src/js/components/FileExplorer.js @@ -0,0 +1,68 @@ +export default class FileExplorer { + constructor(os) { + this.os = os; + this.element = document.getElementById('file-explorer'); + this.listElement = document.getElementById('file-list'); + this.closeBtn = this.element.querySelector('.close-btn'); + + this.fileData = [ + { name: "Documents", type: "folder", icon: "folder", meta: "5 items • Modified today" }, + { name: "Downloads", type: "folder", icon: "folder", meta: "12 items • Modified yesterday" }, + { name: "Pictures", type: "folder", icon: "folder", meta: "28 items • Modified 2 days ago" }, + { name: "report.pdf", type: "file", icon: "insert_drive_file", meta: "PDF • 2.4 MB • Today" }, + { name: "photo_001.jpg", type: "image", icon: "image", meta: "JPEG • 3.8 MB • Today" }, + { name: "notes.txt", type: "document", icon: "description", meta: "Text • 1.2 KB • Yesterday" }, + { name: "Music", type: "folder", icon: "folder", meta: "45 items • Modified last week" } + ]; + + this.initialize(); + this.render(); + } + + initialize() { + this.closeBtn.addEventListener('click', (e) => { + e.stopPropagation(); + this.os.closeAllPopups(); + }); + } + + render() { + this.listElement.innerHTML = ''; + const fragment = document.createDocumentFragment(); + + this.fileData.forEach(file => { + const fileEl = document.createElement('div'); + fileEl.className = 'file-item'; + fileEl.innerHTML = ` +
+ ${file.icon} +
+
+ ${file.name} + ${file.meta} +
+ `; + + fileEl.addEventListener('click', (e) => { + this.listElement.querySelectorAll('.file-item').forEach(el => el.classList.remove('selected')); + fileEl.classList.add('selected'); + e.stopPropagation(); + }); + + fragment.appendChild(fileEl); + }); + + this.listElement.appendChild(fragment); + } + + open() { + this.element.classList.add('active'); + this.os.components.taskbar.updateActiveButton('fileExplorer'); + } + + close() { + this.element.classList.remove('active'); + this.listElement.querySelectorAll('.file-item').forEach(el => el.classList.remove('selected')); + this.os.components.taskbar.updateActiveButton(null); + } +} diff --git a/shelled/shelled-os-ui/src/js/components/Settings.js b/shelled/shelled-os-ui/src/js/components/Settings.js new file mode 100644 index 0000000..6700115 --- /dev/null +++ b/shelled/shelled-os-ui/src/js/components/Settings.js @@ -0,0 +1,77 @@ +export default class Settings { + constructor(os) { + this.os = os; + this.element = document.getElementById('settings'); + this.listElement = document.getElementById('settings-list'); + this.closeBtn = this.element.querySelector('.close-btn'); + + this.settingsData = [ + { label: "Dark Mode", icon: "dark_mode", active: false }, + { label: "Notifications", icon: "notifications", active: true }, + { label: "Sound Effects", icon: "volume_up", active: true }, + { label: "Bluetooth", icon: "bluetooth", active: false }, + { label: "Location Services", icon: "location_on", active: false } + ]; + + this.initialize(); + this.render(); + } + + initialize() { + this.closeBtn.addEventListener('click', (e) => { + e.stopPropagation(); + this.os.closeAllPopups(); + }); + } + + render() { + this.listElement.innerHTML = ''; + const fragment = document.createDocumentFragment(); + + this.settingsData.forEach((setting, index) => { + const settingEl = document.createElement('div'); + settingEl.className = 'setting-item'; + + const activeClass = setting.active ? 'active' : ''; + + settingEl.innerHTML = ` +
+ ${setting.icon} + ${setting.label} +
+
+
+
+ `; + + const toggleSwitch = settingEl.querySelector('.toggle-switch'); + toggleSwitch.addEventListener('click', (e) => { + e.stopPropagation(); + // Toggle active state locally + this.settingsData[index].active = !this.settingsData[index].active; + toggleSwitch.classList.toggle('active'); + }); + + // Make whole row clickable + settingEl.addEventListener('click', (e) => { + e.stopPropagation(); + // Find and click the toggle switch inside + toggleSwitch.click(); + }); + + fragment.appendChild(settingEl); + }); + + this.listElement.appendChild(fragment); + } + + open() { + this.element.classList.add('active'); + this.os.components.taskbar.updateActiveButton('settings'); + } + + close() { + this.element.classList.remove('active'); + this.os.components.taskbar.updateActiveButton(null); + } +} diff --git a/shelled/shelled-os-ui/src/js/components/StartMenu.js b/shelled/shelled-os-ui/src/js/components/StartMenu.js new file mode 100644 index 0000000..59a38cb --- /dev/null +++ b/shelled/shelled-os-ui/src/js/components/StartMenu.js @@ -0,0 +1,92 @@ +export default class StartMenu { + constructor(os) { + this.os = os; + this.element = document.getElementById('start-menu'); + this.gridElement = document.getElementById('app-grid'); + this.searchInput = document.getElementById('app-search'); + + this.appData = [ + { name: "Angry Birds", icon: "mood_bad", color: "red", desc: "Mobile Game", letter: "A" }, + { name: "Adobe Photoshop", icon: "edit", color: "green", desc: "Image Editor", letter: "A" }, + { name: "Brave Browser", icon: "language", color: "blue", desc: "Web Browser", letter: "B" }, + { name: "Books", icon: "book", color: "purple", desc: "Reading App", letter: "B" }, + { name: "Chrome", icon: "code", color: "orange", desc: "Web Browser", letter: "C" }, + { name: "Discord", icon: "chat", color: "blue", desc: "Communication", letter: "D" }, + { name: "GarageBand", icon: "music_note", color: "yellow", desc: "macOS Music App", letter: "G" }, + { name: "Movies & TV", icon: "movie", color: "purple", desc: "Media Player", letter: "M" }, + { name: "Music", icon: "music_note", color: "green", desc: "Audio Player", letter: "M" }, + { name: "Word", icon: "description", color: "default", desc: "Document Editor", letter: "W" }, + { name: "YouTube", icon: "theaters", color: "orange", desc: "Video Platform", letter: "Y" } + ]; + + this.initialize(); + this.render(this.appData); + } + + initialize() { + this.searchInput.addEventListener('input', (e) => { + const query = e.target.value.toLowerCase(); + const filteredApps = this.appData.filter(app => + app.name.toLowerCase().includes(query) + ); + this.render(filteredApps, query !== ''); + }); + } + + render(apps, isSearching = false) { + this.gridElement.innerHTML = ''; + + if (apps.length === 0) { + this.gridElement.innerHTML = '
No apps found
'; + return; + } + + let currentLetter = ''; + const fragment = document.createDocumentFragment(); + + apps.forEach(app => { + if (!isSearching && app.letter !== currentLetter) { + currentLetter = app.letter; + const divider = document.createElement('div'); + divider.className = 'letter-divider'; + divider.textContent = currentLetter; + fragment.appendChild(divider); + } + + const appEl = document.createElement('div'); + appEl.className = 'app-item'; + appEl.innerHTML = ` +
+ ${app.icon} +
+
+ ${app.name} + ${app.desc} +
+ `; + + appEl.addEventListener('click', (e) => { + // Remove selected from all + this.gridElement.querySelectorAll('.app-item').forEach(el => el.classList.remove('selected')); + appEl.classList.add('selected'); + e.stopPropagation(); + }); + + fragment.appendChild(appEl); + }); + + this.gridElement.appendChild(fragment); + } + + open() { + this.element.classList.add('active'); + this.os.components.taskbar.updateActiveButton('startMenu'); + } + + close() { + this.element.classList.remove('active'); + this.searchInput.value = ''; + this.render(this.appData); // Reset search + this.os.components.taskbar.updateActiveButton(null); + } +} diff --git a/shelled/shelled-os-ui/src/js/components/Taskbar.js b/shelled/shelled-os-ui/src/js/components/Taskbar.js new file mode 100644 index 0000000..3c8c18b --- /dev/null +++ b/shelled/shelled-os-ui/src/js/components/Taskbar.js @@ -0,0 +1,73 @@ +export default class Taskbar { + constructor(os) { + this.os = os; + this.buttons = { + start: document.getElementById('btn-start'), + explorer: document.getElementById('btn-explorer'), + browser: document.getElementById('btn-browser'), + settings: document.getElementById('btn-settings') + }; + this.clockElement = document.getElementById('clock'); + + this.initialize(); + } + + initialize() { + // Start Menu Button + this.buttons.start.addEventListener('click', (e) => { + this.os.togglePopup('startMenu'); + }); + + // File Explorer Button + this.buttons.explorer.addEventListener('click', (e) => { + this.os.togglePopup('fileExplorer'); + }); + + // Browser Button + this.buttons.browser.addEventListener('click', (e) => { + this.os.togglePopup('browser'); + }); + + // Settings Button + this.buttons.settings.addEventListener('click', (e) => { + this.os.togglePopup('settings'); + }); + + this.startClock(); + } + + updateActiveButton(componentName) { + // Remove active class from all + Object.values(this.buttons).forEach(btn => btn.classList.remove('active')); + + // Add to mapped + const map = { + 'startMenu': this.buttons.start, + 'fileExplorer': this.buttons.explorer, + 'browser': this.buttons.browser, + 'settings': this.buttons.settings + }; + + if (componentName && map[componentName]) { + map[componentName].classList.add('active'); + } + } + + startClock() { + const updateClock = () => { + const now = new Date(); + let hours = now.getHours(); + let minutes = now.getMinutes(); + const ampm = hours >= 12 ? 'PM' : 'AM'; + + hours = hours % 12; + hours = hours ? hours : 12; // 0 should be 12 + minutes = minutes < 10 ? '0' + minutes : minutes; + + this.clockElement.textContent = `${hours}:${minutes} ${ampm}`; + }; + + updateClock(); + setInterval(updateClock, 1000); + } +} diff --git a/shelled/shelled-os-ui/src/js/main.js b/shelled/shelled-os-ui/src/js/main.js new file mode 100644 index 0000000..6fc3344 --- /dev/null +++ b/shelled/shelled-os-ui/src/js/main.js @@ -0,0 +1,60 @@ +import Taskbar from './components/Taskbar.js'; +import StartMenu from './components/StartMenu.js'; +import FileExplorer from './components/FileExplorer.js'; +import Browser from './components/Browser.js'; +import Settings from './components/Settings.js'; + +class OSController { + constructor() { + this.components = {}; + this.activePopup = null; + this.initialize(); + } + + initialize() { + // Initialize all components + this.components.startMenu = new StartMenu(this); + this.components.fileExplorer = new FileExplorer(this); + this.components.browser = new Browser(this); + this.components.settings = new Settings(this); + + // Taskbar comes last as it needs references to others + this.components.taskbar = new Taskbar(this); + + // Global click to close popups + document.addEventListener('click', (e) => { + // Check if click was inside a popup or on a taskbar button + const isPopupClick = e.target.closest('.popup'); + const isTaskbarBtnClick = e.target.closest('.taskbar-btn'); + + if (!isPopupClick && !isTaskbarBtnClick && this.activePopup) { + this.closeAllPopups(); + } + }); + } + + togglePopup(componentName) { + const component = this.components[componentName]; + if (!component) return; + + if (this.activePopup === component) { + this.closeAllPopups(); + } else { + this.closeAllPopups(); + component.open(); + this.activePopup = component; + } + } + + closeAllPopups() { + if (this.activePopup) { + this.activePopup.close(); + this.activePopup = null; + } + } +} + +// Initialize on load +window.addEventListener('DOMContentLoaded', () => { + window.os = new OSController(); +});