add shelled-ui

This commit is contained in:
turtle89431 2026-04-06 10:45:18 -07:00
parent c161af5f04
commit 701c6be0d3
23 changed files with 1614 additions and 0 deletions

5
.env Normal file
View File

@ -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

6
shelled/package-lock.json generated Normal file
View File

@ -0,0 +1,6 @@
{
"name": "shelled",
"lockfileVersion": 3,
"requires": true,
"packages": {}
}

300
shelled/plan.md Normal file
View File

@ -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
```

2
shelled/shelled-os-ui/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
node_modules/
dist/

244
shelled/shelled-os-ui/package-lock.json generated Normal file
View File

@ -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"
}
}
}
}

View File

@ -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"
}
}

View File

@ -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"

View File

@ -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");
}

View File

@ -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
}
}
}

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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; }

View File

@ -0,0 +1,101 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Shelled OS</title>
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&family=Noto+Sans+SC:wght@300;400;500&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<link rel="stylesheet" href="./css/variables.css">
<link rel="stylesheet" href="./css/main.css">
<link rel="stylesheet" href="./css/taskbar.css">
<link rel="stylesheet" href="./css/start-menu.css">
<link rel="stylesheet" href="./css/file-explorer.css">
<link rel="stylesheet" href="./css/browser.css">
<link rel="stylesheet" href="./css/settings.css">
</head>
<body>
<div id="desktop">
<!-- Popups Container -->
<div id="popups-container">
<!-- Start Menu Popup -->
<div id="start-menu" class="popup">
<div class="popup-header">
<h2>All Applications</h2>
</div>
<div class="search-container">
<span class="material-icons search-icon">search</span>
<input type="text" id="app-search" placeholder="Type to search...">
</div>
<div id="app-grid" class="app-grid">
<!-- Apps dynamically added here -->
</div>
</div>
<!-- File Explorer Popup -->
<div id="file-explorer" class="popup">
<div class="popup-header">
<h2>File Explorer</h2>
<button class="close-btn"><span class="material-icons">close</span></button>
</div>
<div class="file-list" id="file-list">
<!-- Files dynamically added here -->
</div>
</div>
<!-- Browser Popup -->
<div id="browser" class="popup">
<div class="popup-header">
<h2>Browser</h2>
<button class="close-btn"><span class="material-icons">close</span></button>
</div>
<div class="address-bar">
<span class="material-icons lock-icon">lock</span>
<input type="text" value="https://example.com" readonly>
</div>
<div class="browser-content">
<h1>Welcome to the Web</h1>
<p>This is a placeholder for browser content.</p>
</div>
</div>
<!-- Settings Popup -->
<div id="settings" class="popup">
<div class="popup-header">
<h2>Settings</h2>
<button class="close-btn"><span class="material-icons">close</span></button>
</div>
<div class="settings-list" id="settings-list">
<!-- Settings dynamically added here -->
</div>
</div>
</div>
<!-- Taskbar -->
<div id="taskbar">
<div class="taskbar-icons">
<button class="taskbar-btn" id="btn-start" title="Start">
<img src="./assets/images/conch-shell.png" alt="Shell" onerror="this.src=''; this.className='material-icons'; this.innerHTML='widgets'; this.alt='Start'">
</button>
<button class="taskbar-btn" id="btn-explorer" title="File Explorer">
<span class="material-icons">folder</span>
</button>
<button class="taskbar-btn" id="btn-browser" title="Browser">
<span class="material-icons">language</span>
</button>
<button class="taskbar-btn" id="btn-settings" title="Settings">
<span class="material-icons">settings</span>
</button>
</div>
<div class="system-tray">
<span class="material-icons tray-icon">wifi</span>
<span class="material-icons tray-icon">volume_up</span>
<span class="material-icons tray-icon">battery_full</span>
<span class="clock" id="clock">12:00 PM</span>
</div>
</div>
</div>
<script type="module" src="./js/main.js"></script>
</body>
</html>

View File

@ -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);
}
}

View File

@ -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 = `
<div class="file-icon file-${file.type}">
<span class="material-icons">${file.icon}</span>
</div>
<div class="file-info">
<span class="file-name">${file.name}</span>
<span class="file-meta">${file.meta}</span>
</div>
`;
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);
}
}

View File

@ -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 = `
<div class="setting-info">
<span class="material-icons setting-icon">${setting.icon}</span>
<span class="setting-label">${setting.label}</span>
</div>
<div class="toggle-switch ${activeClass}" data-index="${index}">
<div class="toggle-thumb"></div>
</div>
`;
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);
}
}

View File

@ -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 = '<div style="grid-column: 1/-1; text-align:center; padding: 20px; opacity:0.6;">No apps found</div>';
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 = `
<div class="app-icon color-${app.color}">
<span class="material-icons">${app.icon}</span>
</div>
<div class="app-info">
<span class="app-name">${app.name}</span>
<span class="app-desc">${app.desc}</span>
</div>
`;
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);
}
}

View File

@ -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);
}
}

View File

@ -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();
});