| | |
| | |
| | |
| | |
| |
|
| | |
| | const CatOS = { |
| | |
| | windows: new Map(), |
| | apps: new Map(), |
| | nextWindowId: 1, |
| | focusedWindow: null, |
| | startMenuOpen: false, |
| | |
| | |
| | projects: { |
| | 'sneaky-cat-proxy': { |
| | title: 'Sneaky Cat Proxy', |
| | icon: '🔗', |
| | description: 'A purr-fessional proxy that helps cats navigate the internet stealthily! 🕵️♀️', |
| | technologies: ['Node.js', 'Express', 'Stealth'], |
| | features: [ |
| | 'Stealth browsing capabilities', |
| | 'Cat-safe internet filtering', |
| | 'Automatic mouse toy detection', |
| | 'Built-in treat dispenser API' |
| | ], |
| | github: 'https://github.com/catcoder/sneaky-proxy', |
| | demo: 'https://sneaky-cat-proxy.vercel.app' |
| | }, |
| | 'cat-photo-gallery': { |
| | title: 'Cat Photo Gallery', |
| | icon: '📸', |
| | description: 'A claw-some gallery to showcase all your favorite cat pics with purr-fect filtering! 😻', |
| | technologies: ['React', 'TypeScript', 'Tailwind'], |
| | features: [ |
| | 'Advanced cat photo filtering', |
| | 'Automatic whisker detection', |
| | 'Paw print watermarking', |
| | 'Social sharing for cat influencers' |
| | ], |
| | github: 'https://github.com/catcoder/cat-gallery', |
| | demo: 'https://cat-gallery.vercel.app' |
| | }, |
| | 'robo-cat-manager': { |
| | title: 'Robo-Cat Manager', |
| | icon: '🤖', |
| | description: 'Managing Discord bots like herding cats! Keep your digital kitties in line with style! 🐱💻', |
| | technologies: ['Discord.js', 'MongoDB', 'Docker'], |
| | features: [ |
| | 'Multi-bot management dashboard', |
| | 'Automatic yarn ball deployment', |
| | 'Cat behavior analytics', |
| | 'Emergency tuna button' |
| | ], |
| | github: 'https://github.com/catcoder/robo-cat-manager', |
| | demo: 'https://discord-bot-manager.vercel.app' |
| | } |
| | }, |
| |
|
| | |
| | init() { |
| | this.showLoadingScreen(); |
| | this.setupEventListeners(); |
| | this.updateClock(); |
| | this.registerApps(); |
| | this.setRandomWallpaper(); |
| | |
| | |
| | setTimeout(() => { |
| | this.hideLoadingScreen(); |
| | this.playStartupSound(); |
| | this.showWelcomeMessage(); |
| | }, 3000); |
| | }, |
| |
|
| | |
| | setupEventListeners() { |
| | |
| | document.querySelectorAll('.desktop-icon').forEach(icon => { |
| | icon.addEventListener('dblclick', this.handleIconDoubleClick.bind(this)); |
| | icon.addEventListener('click', this.handleIconClick.bind(this)); |
| | }); |
| |
|
| | |
| | document.getElementById('start-button').addEventListener('click', this.toggleStartMenu.bind(this)); |
| |
|
| | |
| | document.querySelectorAll('.menu-app').forEach(app => { |
| | app.addEventListener('click', this.handleMenuAppClick.bind(this)); |
| | }); |
| |
|
| | |
| | document.getElementById('desktop').addEventListener('contextmenu', this.showContextMenu.bind(this)); |
| | |
| | |
| | document.addEventListener('click', this.hideContextMenu.bind(this)); |
| | |
| | |
| | document.addEventListener('click', (e) => { |
| | if (!e.target.closest('.start-button') && !e.target.closest('.start-menu')) { |
| | this.hideStartMenu(); |
| | } |
| | }); |
| |
|
| | |
| | document.addEventListener('keydown', this.handleKeyboard.bind(this)); |
| |
|
| | |
| | document.getElementById('windows-container').addEventListener('click', (e) => { |
| | if (e.target.id === 'windows-container') { |
| | this.deselectAllIcons(); |
| | } |
| | }); |
| | }, |
| |
|
| | |
| | handleIconClick(e) { |
| | this.deselectAllIcons(); |
| | e.currentTarget.classList.add('selected'); |
| | }, |
| |
|
| | handleIconDoubleClick(e) { |
| | const icon = e.currentTarget; |
| | const appType = icon.dataset.app; |
| | const projectId = icon.dataset.project; |
| | |
| | if (appType === 'project-viewer' && projectId) { |
| | this.launchApp('project-viewer', { projectId }); |
| | } else { |
| | this.launchApp(appType); |
| | } |
| | }, |
| |
|
| | deselectAllIcons() { |
| | document.querySelectorAll('.desktop-icon.selected').forEach(icon => { |
| | icon.classList.remove('selected'); |
| | }); |
| | }, |
| |
|
| | |
| | toggleStartMenu() { |
| | const menu = document.getElementById('start-menu'); |
| | const button = document.getElementById('start-button'); |
| | |
| | if (this.startMenuOpen) { |
| | this.hideStartMenu(); |
| | } else { |
| | menu.classList.add('show'); |
| | button.classList.add('active'); |
| | this.startMenuOpen = true; |
| | } |
| | }, |
| |
|
| | hideStartMenu() { |
| | const menu = document.getElementById('start-menu'); |
| | const button = document.getElementById('start-button'); |
| | |
| | menu.classList.remove('show'); |
| | button.classList.remove('active'); |
| | this.startMenuOpen = false; |
| | }, |
| |
|
| | handleMenuAppClick(e) { |
| | const appType = e.currentTarget.dataset.app; |
| | this.launchApp(appType); |
| | this.hideStartMenu(); |
| | }, |
| |
|
| | |
| | showContextMenu(e) { |
| | e.preventDefault(); |
| | const menu = document.getElementById('context-menu'); |
| | |
| | menu.style.left = e.clientX + 'px'; |
| | menu.style.top = e.clientY + 'px'; |
| | menu.classList.add('show'); |
| | }, |
| |
|
| | hideContextMenu() { |
| | document.getElementById('context-menu').classList.remove('show'); |
| | }, |
| |
|
| | |
| | registerApps() { |
| | this.apps.set('terminal', { |
| | title: 'Terminal', |
| | icon: '💻', |
| | create: () => this.createTerminalApp() |
| | }); |
| |
|
| | this.apps.set('project-viewer', { |
| | title: 'Project Viewer', |
| | icon: '📁', |
| | create: (options) => this.createProjectViewerApp(options) |
| | }); |
| |
|
| | this.apps.set('about', { |
| | title: 'About CatOS', |
| | icon: '📋', |
| | create: () => this.createAboutApp() |
| | }); |
| |
|
| | this.apps.set('contact', { |
| | title: 'Contact', |
| | icon: '📧', |
| | create: () => this.createContactApp() |
| | }); |
| |
|
| | this.apps.set('file-explorer', { |
| | title: 'File Explorer', |
| | icon: '📁', |
| | create: () => this.createFileExplorerApp() |
| | }); |
| | }, |
| |
|
| | launchApp(appType, options = {}) { |
| | const app = this.apps.get(appType); |
| | if (!app) { |
| | this.showNotification(`App '${appType}' not found! 🙀`, 'error'); |
| | return; |
| | } |
| |
|
| | |
| | if (appType !== 'project-viewer') { |
| | for (const [windowId, window] of this.windows) { |
| | if (window.appType === appType) { |
| | this.focusWindow(windowId); |
| | return; |
| | } |
| | } |
| | } |
| |
|
| | const content = app.create(options); |
| | const windowId = this.createWindow({ |
| | title: app.title, |
| | icon: app.icon, |
| | content: content, |
| | appType: appType |
| | }); |
| |
|
| | this.addToTaskbar(windowId, app); |
| | }, |
| |
|
| | |
| | createWindow(options) { |
| | const windowId = `window-${this.nextWindowId++}`; |
| | const window = document.createElement('div'); |
| | window.className = 'window focused'; |
| | window.id = windowId; |
| | window.innerHTML = ` |
| | <div class="window-titlebar"> |
| | <div class="window-title"> |
| | <span class="window-icon">${options.icon}</span> |
| | ${options.title} |
| | </div> |
| | <div class="window-controls"> |
| | <button class="window-control minimize" data-action="minimize">−</button> |
| | <button class="window-control maximize" data-action="maximize">□</button> |
| | <button class="window-control close" data-action="close">×</button> |
| | </div> |
| | </div> |
| | <div class="window-content"> |
| | ${options.content} |
| | </div> |
| | <div class="window-resize-handle resize-handle-n" data-direction="n"></div> |
| | <div class="window-resize-handle resize-handle-s" data-direction="s"></div> |
| | <div class="window-resize-handle resize-handle-e" data-direction="e"></div> |
| | <div class="window-resize-handle resize-handle-w" data-direction="w"></div> |
| | <div class="window-resize-handle resize-handle-ne" data-direction="ne"></div> |
| | <div class="window-resize-handle resize-handle-nw" data-direction="nw"></div> |
| | <div class="window-resize-handle resize-handle-se" data-direction="se"></div> |
| | <div class="window-resize-handle resize-handle-sw" data-direction="sw"></div> |
| | `; |
| |
|
| | |
| | const offset = (this.nextWindowId - 2) * 30; |
| | window.style.left = (100 + offset) + 'px'; |
| | window.style.top = (50 + offset) + 'px'; |
| | window.style.width = options.width || '600px'; |
| | window.style.height = options.height || '400px'; |
| |
|
| | |
| | document.getElementById('windows-container').appendChild(window); |
| |
|
| | |
| | this.windows.set(windowId, { |
| | element: window, |
| | title: options.title, |
| | icon: options.icon, |
| | appType: options.appType, |
| | minimized: false, |
| | maximized: false |
| | }); |
| |
|
| | |
| | this.setupWindowEvents(windowId); |
| | this.focusWindow(windowId); |
| |
|
| | return windowId; |
| | }, |
| |
|
| | setupWindowEvents(windowId) { |
| | const window = this.windows.get(windowId); |
| | const element = window.element; |
| | |
| | |
| | element.querySelectorAll('.window-control').forEach(btn => { |
| | btn.addEventListener('click', (e) => { |
| | e.stopPropagation(); |
| | const action = btn.dataset.action; |
| | |
| | switch(action) { |
| | case 'minimize': |
| | this.minimizeWindow(windowId); |
| | break; |
| | case 'maximize': |
| | this.toggleMaximizeWindow(windowId); |
| | break; |
| | case 'close': |
| | this.closeWindow(windowId); |
| | break; |
| | } |
| | }); |
| | }); |
| |
|
| | |
| | element.addEventListener('mousedown', () => { |
| | this.focusWindow(windowId); |
| | }); |
| |
|
| | |
| | const titlebar = element.querySelector('.window-titlebar'); |
| | this.makeDraggable(element, titlebar); |
| |
|
| | |
| | this.setupWindowResizing(windowId); |
| | }, |
| |
|
| | focusWindow(windowId) { |
| | |
| | document.querySelectorAll('.window').forEach(w => { |
| | w.classList.remove('focused'); |
| | }); |
| |
|
| | |
| | document.querySelectorAll('.taskbar-app').forEach(app => { |
| | app.classList.remove('focused'); |
| | }); |
| |
|
| | |
| | const window = this.windows.get(windowId); |
| | if (window) { |
| | window.element.classList.add('focused'); |
| | this.focusedWindow = windowId; |
| | |
| | |
| | const taskbarApp = document.querySelector(`[data-window-id="${windowId}"]`); |
| | if (taskbarApp) { |
| | taskbarApp.classList.add('focused'); |
| | } |
| | } |
| | }, |
| |
|
| | minimizeWindow(windowId) { |
| | const window = this.windows.get(windowId); |
| | if (window) { |
| | window.element.classList.add('minimized'); |
| | window.minimized = true; |
| | |
| | |
| | const taskbarApp = document.querySelector(`[data-window-id="${windowId}"]`); |
| | if (taskbarApp) { |
| | taskbarApp.classList.remove('focused'); |
| | } |
| | } |
| | }, |
| |
|
| | toggleMaximizeWindow(windowId) { |
| | const window = this.windows.get(windowId); |
| | if (window) { |
| | if (window.maximized) { |
| | window.element.classList.remove('maximized'); |
| | window.maximized = false; |
| | } else { |
| | window.element.classList.add('maximized'); |
| | window.maximized = true; |
| | } |
| | } |
| | }, |
| |
|
| | closeWindow(windowId) { |
| | const window = this.windows.get(windowId); |
| | if (window) { |
| | |
| | window.element.remove(); |
| | |
| | |
| | const taskbarApp = document.querySelector(`[data-window-id="${windowId}"]`); |
| | if (taskbarApp) { |
| | taskbarApp.remove(); |
| | } |
| | |
| | |
| | this.windows.delete(windowId); |
| | |
| | |
| | if (this.focusedWindow === windowId) { |
| | const remainingWindows = Array.from(this.windows.keys()); |
| | if (remainingWindows.length > 0) { |
| | this.focusWindow(remainingWindows[remainingWindows.length - 1]); |
| | } else { |
| | this.focusedWindow = null; |
| | } |
| | } |
| | } |
| | }, |
| |
|
| | |
| | addToTaskbar(windowId, app) { |
| | const taskbar = document.getElementById('taskbar-apps'); |
| | const taskbarApp = document.createElement('div'); |
| | taskbarApp.className = 'taskbar-app focused'; |
| | taskbarApp.dataset.windowId = windowId; |
| | taskbarApp.innerHTML = ` |
| | <span class="taskbar-app-icon">${app.icon}</span> |
| | <span class="taskbar-app-name">${app.title}</span> |
| | `; |
| | |
| | taskbarApp.addEventListener('click', () => { |
| | const window = this.windows.get(windowId); |
| | if (window) { |
| | if (window.minimized) { |
| | window.element.classList.remove('minimized'); |
| | window.minimized = false; |
| | } |
| | this.focusWindow(windowId); |
| | } |
| | }); |
| | |
| | taskbar.appendChild(taskbarApp); |
| | }, |
| |
|
| | |
| | makeDraggable(element, handle) { |
| | let isDragging = false; |
| | let currentX, currentY, initialX, initialY, xOffset = 0, yOffset = 0; |
| |
|
| | const dragStart = (e) => { |
| | const window = this.windows.get(element.id); |
| | if (window && window.maximized) return; |
| | |
| | if (e.type === "touchstart") { |
| | initialX = e.touches[0].clientX - xOffset; |
| | initialY = e.touches[0].clientY - yOffset; |
| | } else { |
| | initialX = e.clientX - xOffset; |
| | initialY = e.clientY - yOffset; |
| | } |
| |
|
| | if (e.target === handle || handle.contains(e.target)) { |
| | isDragging = true; |
| | document.body.classList.add('dragging'); |
| | } |
| | }; |
| |
|
| | const drag = (e) => { |
| | if (isDragging) { |
| | e.preventDefault(); |
| | |
| | if (e.type === "touchmove") { |
| | currentX = e.touches[0].clientX - initialX; |
| | currentY = e.touches[0].clientY - initialY; |
| | } else { |
| | currentX = e.clientX - initialX; |
| | currentY = e.clientY - initialY; |
| | } |
| |
|
| | xOffset = currentX; |
| | yOffset = currentY; |
| |
|
| | element.style.left = currentX + "px"; |
| | element.style.top = currentY + "px"; |
| | } |
| | }; |
| |
|
| | const dragEnd = () => { |
| | initialX = currentX; |
| | initialY = currentY; |
| | isDragging = false; |
| | document.body.classList.remove('dragging'); |
| | }; |
| |
|
| | handle.addEventListener("mousedown", dragStart); |
| | handle.addEventListener("touchstart", dragStart); |
| | document.addEventListener("mousemove", drag); |
| | document.addEventListener("touchmove", drag); |
| | document.addEventListener("mouseup", dragEnd); |
| | document.addEventListener("touchend", dragEnd); |
| | }, |
| |
|
| | |
| | setupWindowResizing(windowId) { |
| | const window = this.windows.get(windowId); |
| | const element = window.element; |
| | const resizeHandles = element.querySelectorAll('.window-resize-handle'); |
| | |
| | let isResizing = false; |
| | let resizeDirection = ''; |
| | let startX, startY, startWidth, startHeight, startLeft, startTop; |
| |
|
| | const resizeStart = (e) => { |
| | const windowData = this.windows.get(element.id); |
| | if (windowData && windowData.maximized) return; |
| | |
| | e.preventDefault(); |
| | e.stopPropagation(); |
| | |
| | isResizing = true; |
| | resizeDirection = e.target.dataset.direction; |
| | |
| | startX = e.clientX; |
| | startY = e.clientY; |
| | startWidth = parseInt(getComputedStyle(element).width, 10); |
| | startHeight = parseInt(getComputedStyle(element).height, 10); |
| | startLeft = parseInt(getComputedStyle(element).left, 10); |
| | startTop = parseInt(getComputedStyle(element).top, 10); |
| | |
| | element.classList.add('resizing'); |
| | document.body.style.cursor = e.target.style.cursor; |
| | document.body.style.userSelect = 'none'; |
| | }; |
| |
|
| | const resize = (e) => { |
| | if (!isResizing) return; |
| | |
| | e.preventDefault(); |
| | |
| | const deltaX = e.clientX - startX; |
| | const deltaY = e.clientY - startY; |
| | |
| | let newWidth = startWidth; |
| | let newHeight = startHeight; |
| | let newLeft = startLeft; |
| | let newTop = startTop; |
| | |
| | |
| | if (resizeDirection.includes('e')) { |
| | newWidth = Math.max(300, startWidth + deltaX); |
| | } |
| | if (resizeDirection.includes('w')) { |
| | newWidth = Math.max(300, startWidth - deltaX); |
| | newLeft = startLeft + (startWidth - newWidth); |
| | } |
| | if (resizeDirection.includes('s')) { |
| | newHeight = Math.max(200, startHeight + deltaY); |
| | } |
| | if (resizeDirection.includes('n')) { |
| | newHeight = Math.max(200, startHeight - deltaY); |
| | newTop = startTop + (startHeight - newHeight); |
| | } |
| | |
| | |
| | element.style.width = newWidth + 'px'; |
| | element.style.height = newHeight + 'px'; |
| | element.style.left = newLeft + 'px'; |
| | element.style.top = newTop + 'px'; |
| | }; |
| |
|
| | const resizeEnd = () => { |
| | if (!isResizing) return; |
| | |
| | isResizing = false; |
| | resizeDirection = ''; |
| | |
| | element.classList.remove('resizing'); |
| | document.body.style.cursor = ''; |
| | document.body.style.userSelect = ''; |
| | }; |
| |
|
| | |
| | resizeHandles.forEach(handle => { |
| | handle.addEventListener('mousedown', resizeStart); |
| | }); |
| | |
| | document.addEventListener('mousemove', resize); |
| | document.addEventListener('mouseup', resizeEnd); |
| | }, |
| |
|
| | |
| | createTerminalApp() { |
| | const terminalId = `terminal-${Date.now()}`; |
| | setTimeout(() => { |
| | this.initializeTerminal(terminalId); |
| | }, 100); |
| | |
| | return ` |
| | <div class="terminal-app" data-terminal-id="${terminalId}"> |
| | <div class="terminal-header"> |
| | <span class="terminal-title">CatOS Terminal v9.0</span> |
| | <div class="terminal-controls"> |
| | <span class="terminal-dot red"></span> |
| | <span class="terminal-dot yellow"></span> |
| | <span class="terminal-dot green"></span> |
| | </div> |
| | </div> |
| | <div class="terminal-content" id="terminal-content-${terminalId}"> |
| | <div class="terminal-line"> |
| | <span class="terminal-prompt">cat@catos:~$</span> |
| | <span class="terminal-text">Welcome to CatOS Terminal! 🐱</span> |
| | </div> |
| | <div class="terminal-line"> |
| | <span class="terminal-prompt">cat@catos:~$</span> |
| | <span class="terminal-text">Type 'help' for available commands or 'meow' for cat wisdom</span> |
| | </div> |
| | </div> |
| | <div class="terminal-input-line"> |
| | <span class="terminal-prompt" id="prompt-${terminalId}">cat@catos:~$</span> |
| | <input type="text" class="terminal-input" id="terminal-input-${terminalId}" autofocus> |
| | </div> |
| | </div> |
| | <style> |
| | .terminal-app { |
| | background: #1a1a1a; |
| | color: #00ff00; |
| | font-family: var(--font-mono); |
| | height: 100%; |
| | display: flex; |
| | flex-direction: column; |
| | padding: 0; |
| | } |
| | .terminal-header { |
| | background: #333; |
| | padding: 8px 16px; |
| | display: flex; |
| | justify-content: space-between; |
| | align-items: center; |
| | border-bottom: 1px solid #555; |
| | } |
| | .terminal-title { |
| | color: #fff; |
| | font-size: 0.875rem; |
| | } |
| | .terminal-controls { |
| | display: flex; |
| | gap: 4px; |
| | } |
| | .terminal-dot { |
| | width: 12px; |
| | height: 12px; |
| | border-radius: 50%; |
| | } |
| | .terminal-dot.red { background: #ff5f56; } |
| | .terminal-dot.yellow { background: #ffbd2e; } |
| | .terminal-dot.green { background: #27ca3f; } |
| | .terminal-content { |
| | flex: 1; |
| | padding: 16px; |
| | overflow-y: auto; |
| | font-size: 0.875rem; |
| | line-height: 1.5; |
| | } |
| | .terminal-line { |
| | margin-bottom: 4px; |
| | } |
| | .terminal-prompt { |
| | color: #00ffff; |
| | margin-right: 8px; |
| | } |
| | .terminal-text { |
| | color: #00ff00; |
| | } |
| | .terminal-input-line { |
| | padding: 16px; |
| | border-top: 1px solid #333; |
| | display: flex; |
| | align-items: center; |
| | } |
| | .terminal-input { |
| | background: transparent; |
| | border: none; |
| | color: #00ff00; |
| | font-family: var(--font-mono); |
| | font-size: 0.875rem; |
| | outline: none; |
| | flex: 1; |
| | margin-left: 8px; |
| | } |
| | .terminal-output { |
| | color: #00ff00; |
| | white-space: pre-line; |
| | } |
| | .terminal-category { |
| | color: #ffff00; |
| | font-weight: bold; |
| | } |
| | .terminal-success { |
| | color: #00ffff; |
| | } |
| | .terminal-directory { |
| | color: #0088ff; |
| | font-weight: bold; |
| | } |
| | .terminal-file { |
| | color: #ff8800; |
| | } |
| | </style> |
| | `; |
| | }, |
| |
|
| | createProjectViewerApp(options) { |
| | const project = this.projects[options.projectId]; |
| | if (!project) { |
| | return '<p>Project not found! 🙀</p>'; |
| | } |
| |
|
| | return ` |
| | <div class="project-viewer"> |
| | <div class="project-header"> |
| | <div class="project-icon">${project.icon}</div> |
| | <div class="project-info"> |
| | <h2>${project.title}</h2> |
| | <p>${project.description}</p> |
| | </div> |
| | </div> |
| | |
| | <div class="project-details"> |
| | <div class="project-section"> |
| | <h3>🛠️ Technologies</h3> |
| | <div class="tech-tags"> |
| | ${project.technologies.map(tech => `<span class="tech-tag">${tech}</span>`).join('')} |
| | </div> |
| | </div> |
| | |
| | <div class="project-section"> |
| | <h3>✨ Features</h3> |
| | <ul class="feature-list"> |
| | ${project.features.map(feature => `<li>${feature}</li>`).join('')} |
| | </ul> |
| | </div> |
| | |
| | <div class="project-actions"> |
| | <a href="${project.github}" target="_blank" class="project-btn github-btn"> |
| | <i class="fab fa-github"></i> View Code |
| | </a> |
| | <a href="${project.demo}" target="_blank" class="project-btn demo-btn"> |
| | <i class="fas fa-external-link-alt"></i> Live Demo |
| | </a> |
| | </div> |
| | </div> |
| | </div> |
| | <style> |
| | .project-viewer { |
| | padding: 0; |
| | } |
| | .project-header { |
| | display: flex; |
| | gap: 16px; |
| | padding: 24px; |
| | border-bottom: 1px solid #e5e7eb; |
| | background: var(--desktop-bg); |
| | color: white; |
| | margin: -24px -24px 24px -24px; |
| | } |
| | .project-icon { |
| | font-size: 3rem; |
| | } |
| | .project-info h2 { |
| | font-size: 1.5rem; |
| | margin-bottom: 8px; |
| | } |
| | .project-details { |
| | padding: 0 24px 24px 24px; |
| | } |
| | .project-section { |
| | margin-bottom: 24px; |
| | } |
| | .project-section h3 { |
| | color: var(--text-primary); |
| | margin-bottom: 12px; |
| | font-size: 1.125rem; |
| | } |
| | .tech-tags { |
| | display: flex; |
| | flex-wrap: wrap; |
| | gap: 8px; |
| | } |
| | .tech-tag { |
| | background: var(--cat-primary); |
| | color: white; |
| | padding: 4px 12px; |
| | border-radius: 16px; |
| | font-size: 0.875rem; |
| | font-weight: 500; |
| | } |
| | .feature-list { |
| | list-style: none; |
| | padding: 0; |
| | } |
| | .feature-list li { |
| | padding: 8px 0; |
| | border-bottom: 1px solid #f3f4f6; |
| | color: var(--text-secondary); |
| | } |
| | .feature-list li:before { |
| | content: '🐾'; |
| | margin-right: 8px; |
| | } |
| | .project-actions { |
| | display: flex; |
| | gap: 12px; |
| | margin-top: 24px; |
| | } |
| | .project-btn { |
| | display: flex; |
| | align-items: center; |
| | gap: 8px; |
| | padding: 12px 20px; |
| | border-radius: 8px; |
| | text-decoration: none; |
| | font-weight: 500; |
| | transition: all 0.3s ease; |
| | } |
| | .github-btn { |
| | background: #24292e; |
| | color: white; |
| | } |
| | .demo-btn { |
| | background: var(--cat-primary); |
| | color: white; |
| | } |
| | .project-btn:hover { |
| | transform: translateY(-2px); |
| | box-shadow: 0 4px 12px rgba(0,0,0,0.15); |
| | } |
| | </style> |
| | `; |
| | }, |
| |
|
| | createAboutApp() { |
| | return ` |
| | <div class="about-app"> |
| | <div class="about-header"> |
| | <div class="about-logo">🐱💻</div> |
| | <div class="about-info"> |
| | <h2>CatOS v9.0 (Whiskers Edition)</h2> |
| | <p>A purr-fessional desktop portfolio experience</p> |
| | </div> |
| | </div> |
| | |
| | <div class="system-info"> |
| | <div class="info-section"> |
| | <h3>System Information</h3> |
| | <div class="info-grid"> |
| | <div class="info-item"> |
| | <span class="info-label">Developer:</span> |
| | <span class="info-value">Rafael (Certified Cat Whisperer)</span> |
| | </div> |
| | <div class="info-item"> |
| | <span class="info-label">Uptime:</span> |
| | <span class="info-value">5+ years coding experience</span> |
| | </div> |
| | <div class="info-item"> |
| | <span class="info-label">Memory:</span> |
| | <span class="info-value">∞ GB coffee-powered RAM</span> |
| | </div> |
| | <div class="info-item"> |
| | <span class="info-label">Storage:</span> |
| | <span class="info-value">∞ TB of cat photos and code</span> |
| | </div> |
| | </div> |
| | </div> |
| | |
| | <div class="info-section"> |
| | <h3>Installed Skills</h3> |
| | <div class="skills-list"> |
| | <div class="skill-item"> |
| | <span class="skill-name">JavaScript</span> |
| | <div class="skill-bar"> |
| | <div class="skill-progress" style="width: 95%"></div> |
| | </div> |
| | <span class="skill-level">95%</span> |
| | </div> |
| | <div class="skill-item"> |
| | <span class="skill-name">React</span> |
| | <div class="skill-bar"> |
| | <div class="skill-progress" style="width: 90%"></div> |
| | </div> |
| | <span class="skill-level">90%</span> |
| | </div> |
| | <div class="skill-item"> |
| | <span class="skill-name">Node.js</span> |
| | <div class="skill-bar"> |
| | <div class="skill-progress" style="width: 85%"></div> |
| | </div> |
| | <span class="skill-level">85%</span> |
| | </div> |
| | <div class="skill-item"> |
| | <span class="skill-name">Cat Psychology</span> |
| | <div class="skill-bar"> |
| | <div class="skill-progress" style="width: 100%"></div> |
| | </div> |
| | <span class="skill-level">Expert</span> |
| | </div> |
| | </div> |
| | </div> |
| | </div> |
| | </div> |
| | <style> |
| | .about-app { |
| | padding: 0; |
| | } |
| | .about-header { |
| | display: flex; |
| | gap: 16px; |
| | padding: 24px; |
| | background: var(--desktop-bg); |
| | color: white; |
| | margin: -24px -24px 24px -24px; |
| | } |
| | .about-logo { |
| | font-size: 3rem; |
| | } |
| | .about-info h2 { |
| | font-size: 1.5rem; |
| | margin-bottom: 8px; |
| | } |
| | .system-info { |
| | padding: 0 24px 24px 24px; |
| | } |
| | .info-section { |
| | margin-bottom: 24px; |
| | } |
| | .info-section h3 { |
| | color: var(--text-primary); |
| | margin-bottom: 16px; |
| | font-size: 1.125rem; |
| | } |
| | .info-grid { |
| | display: grid; |
| | gap: 12px; |
| | } |
| | .info-item { |
| | display: flex; |
| | justify-content: space-between; |
| | padding: 12px 0; |
| | border-bottom: 1px solid #f3f4f6; |
| | } |
| | .info-label { |
| | font-weight: 600; |
| | color: var(--text-primary); |
| | } |
| | .info-value { |
| | color: var(--text-secondary); |
| | font-family: var(--font-mono); |
| | } |
| | .skills-list { |
| | display: grid; |
| | gap: 16px; |
| | } |
| | .skill-item { |
| | display: grid; |
| | grid-template-columns: 120px 1fr 60px; |
| | gap: 12px; |
| | align-items: center; |
| | } |
| | .skill-name { |
| | font-weight: 500; |
| | color: var(--text-primary); |
| | } |
| | .skill-bar { |
| | height: 8px; |
| | background: #e5e7eb; |
| | border-radius: 4px; |
| | overflow: hidden; |
| | } |
| | .skill-progress { |
| | height: 100%; |
| | background: var(--cat-primary); |
| | border-radius: 4px; |
| | transition: width 1s ease; |
| | } |
| | .skill-level { |
| | font-size: 0.875rem; |
| | color: var(--text-secondary); |
| | text-align: right; |
| | font-family: var(--font-mono); |
| | } |
| | </style> |
| | `; |
| | }, |
| |
|
| | createContactApp() { |
| | return ` |
| | <div class="contact-app"> |
| | <div class="contact-header"> |
| | <div class="contact-icon">📧</div> |
| | <div class="contact-info"> |
| | <h2>Let's Be Cat Friends!</h2> |
| | <p>Got a purr-fect project idea? Want to code together? 🐾</p> |
| | </div> |
| | </div> |
| | |
| | <div class="contact-form"> |
| | <form id="contact-form"> |
| | <div class="form-group"> |
| | <label for="name">Name</label> |
| | <input type="text" id="name" name="name" required> |
| | </div> |
| | <div class="form-group"> |
| | <label for="email">Email</label> |
| | <input type="email" id="email" name="email" required> |
| | </div> |
| | <div class="form-group"> |
| | <label for="message">Message</label> |
| | <textarea id="message" name="message" rows="5" placeholder="Tell me about your project... or just say meow! 😸" required></textarea> |
| | </div> |
| | <button type="submit" class="submit-btn"> |
| | <i class="fas fa-paper-plane"></i> |
| | Send Message |
| | </button> |
| | </form> |
| | </div> |
| | |
| | <div class="contact-links"> |
| | <h3>Other ways to reach me:</h3> |
| | <div class="links-grid"> |
| | <a href="mailto:your@email.com" class="contact-link email"> |
| | <i class="fas fa-envelope"></i> |
| | <span>Email</span> |
| | </a> |
| | <a href="https://github.com/yourusername" class="contact-link github" target="_blank"> |
| | <i class="fab fa-github"></i> |
| | <span>GitHub</span> |
| | </a> |
| | <a href="https://discord.com/users/yourid" class="contact-link discord" target="_blank"> |
| | <i class="fab fa-discord"></i> |
| | <span>Discord</span> |
| | </a> |
| | </div> |
| | </div> |
| | </div> |
| | <style> |
| | .contact-app { |
| | padding: 0; |
| | } |
| | .contact-header { |
| | display: flex; |
| | gap: 16px; |
| | padding: 24px; |
| | background: var(--desktop-bg); |
| | color: white; |
| | margin: -24px -24px 24px -24px; |
| | } |
| | .contact-icon { |
| | font-size: 3rem; |
| | } |
| | .contact-info h2 { |
| | font-size: 1.5rem; |
| | margin-bottom: 8px; |
| | } |
| | .contact-form { |
| | padding: 0 24px 24px 24px; |
| | } |
| | .form-group { |
| | margin-bottom: 20px; |
| | } |
| | .form-group label { |
| | display: block; |
| | margin-bottom: 8px; |
| | font-weight: 500; |
| | color: var(--text-primary); |
| | } |
| | .form-group input, |
| | .form-group textarea { |
| | width: 100%; |
| | padding: 12px; |
| | border: 1px solid #d1d5db; |
| | border-radius: 8px; |
| | font-family: var(--font-system); |
| | font-size: 0.875rem; |
| | transition: border-color 0.3s ease; |
| | } |
| | .form-group input:focus, |
| | .form-group textarea:focus { |
| | outline: none; |
| | border-color: var(--cat-primary); |
| | } |
| | .submit-btn { |
| | background: var(--cat-primary); |
| | color: white; |
| | border: none; |
| | padding: 12px 24px; |
| | border-radius: 8px; |
| | font-weight: 500; |
| | cursor: pointer; |
| | display: flex; |
| | align-items: center; |
| | gap: 8px; |
| | transition: all 0.3s ease; |
| | } |
| | .submit-btn:hover { |
| | background: #5856eb; |
| | transform: translateY(-1px); |
| | } |
| | .contact-links { |
| | padding: 24px; |
| | border-top: 1px solid #e5e7eb; |
| | margin: 0 -24px -24px -24px; |
| | } |
| | .contact-links h3 { |
| | margin-bottom: 16px; |
| | color: var(--text-primary); |
| | } |
| | .links-grid { |
| | display: grid; |
| | grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); |
| | gap: 12px; |
| | } |
| | .contact-link { |
| | display: flex; |
| | align-items: center; |
| | gap: 8px; |
| | padding: 12px; |
| | border-radius: 8px; |
| | text-decoration: none; |
| | color: white; |
| | font-weight: 500; |
| | transition: transform 0.3s ease; |
| | } |
| | .contact-link:hover { |
| | transform: translateY(-2px); |
| | } |
| | .contact-link.email { background: #ea4335; } |
| | .contact-link.github { background: #24292e; } |
| | .contact-link.discord { background: #5865f2; } |
| | </style> |
| | `; |
| | }, |
| |
|
| | createFileExplorerApp() { |
| | return ` |
| | <div class="file-explorer"> |
| | <div class="explorer-toolbar"> |
| | <div class="breadcrumb"> |
| | <span class="breadcrumb-item active">🏠 Home</span> |
| | <span class="breadcrumb-separator">/</span> |
| | <span class="breadcrumb-item">catcoder</span> |
| | </div> |
| | <div class="explorer-controls"> |
| | <button class="explorer-btn" title="Back">⬅️</button> |
| | <button class="explorer-btn" title="Forward">➡️</button> |
| | <button class="explorer-btn" title="Up">⬆️</button> |
| | </div> |
| | </div> |
| | |
| | <div class="explorer-content"> |
| | <div class="explorer-sidebar"> |
| | <div class="sidebar-section"> |
| | <div class="sidebar-item active"> |
| | <i class="fas fa-home"></i> |
| | <span>Home</span> |
| | </div> |
| | <div class="sidebar-item"> |
| | <i class="fas fa-desktop"></i> |
| | <span>Desktop</span> |
| | </div> |
| | <div class="sidebar-item"> |
| | <i class="fas fa-folder"></i> |
| | <span>Projects</span> |
| | </div> |
| | <div class="sidebar-item"> |
| | <i class="fas fa-file-alt"></i> |
| | <span>Documents</span> |
| | </div> |
| | <div class="sidebar-item"> |
| | <i class="fas fa-image"></i> |
| | <span>Pictures</span> |
| | </div> |
| | </div> |
| | </div> |
| | |
| | <div class="explorer-main"> |
| | <div class="file-grid"> |
| | <div class="file-item folder"> |
| | <div class="file-icon">📁</div> |
| | <div class="file-name">Desktop</div> |
| | </div> |
| | <div class="file-item folder"> |
| | <div class="file-icon">📁</div> |
| | <div class="file-name">Projects</div> |
| | </div> |
| | <div class="file-item folder"> |
| | <div class="file-icon">📁</div> |
| | <div class="file-name">Documents</div> |
| | </div> |
| | <div class="file-item folder"> |
| | <div class="file-icon">📁</div> |
| | <div class="file-name">Pictures</div> |
| | </div> |
| | <div class="file-item file"> |
| | <div class="file-icon">📄</div> |
| | <div class="file-name">Resume.pdf</div> |
| | </div> |
| | <div class="file-item file"> |
| | <div class="file-icon">🐱</div> |
| | <div class="file-name">CatWisdom.txt</div> |
| | </div> |
| | </div> |
| | </div> |
| | </div> |
| | </div> |
| | <style> |
| | .file-explorer { |
| | display: flex; |
| | flex-direction: column; |
| | height: 100%; |
| | padding: 0; |
| | } |
| | .explorer-toolbar { |
| | display: flex; |
| | justify-content: space-between; |
| | align-items: center; |
| | padding: 12px 16px; |
| | border-bottom: 1px solid #e5e7eb; |
| | background: #f9fafb; |
| | margin: -24px -24px 0 -24px; |
| | } |
| | .breadcrumb { |
| | display: flex; |
| | align-items: center; |
| | font-size: 0.875rem; |
| | color: var(--text-secondary); |
| | } |
| | .breadcrumb-item.active { |
| | color: var(--text-primary); |
| | font-weight: 500; |
| | } |
| | .breadcrumb-separator { |
| | margin: 0 8px; |
| | } |
| | .explorer-controls { |
| | display: flex; |
| | gap: 4px; |
| | } |
| | .explorer-btn { |
| | width: 32px; |
| | height: 32px; |
| | border: 1px solid #d1d5db; |
| | background: white; |
| | border-radius: 4px; |
| | cursor: pointer; |
| | font-size: 0.875rem; |
| | } |
| | .explorer-btn:hover { |
| | background: #f3f4f6; |
| | } |
| | .explorer-content { |
| | display: flex; |
| | flex: 1; |
| | overflow: hidden; |
| | } |
| | .explorer-sidebar { |
| | width: 200px; |
| | background: #f9fafb; |
| | border-right: 1px solid #e5e7eb; |
| | padding: 16px 0; |
| | } |
| | .sidebar-item { |
| | display: flex; |
| | align-items: center; |
| | gap: 12px; |
| | padding: 8px 16px; |
| | cursor: pointer; |
| | font-size: 0.875rem; |
| | color: var(--text-secondary); |
| | transition: all 0.3s ease; |
| | } |
| | .sidebar-item:hover { |
| | background: rgba(99, 102, 241, 0.1); |
| | color: var(--text-primary); |
| | } |
| | .sidebar-item.active { |
| | background: rgba(99, 102, 241, 0.1); |
| | color: var(--cat-primary); |
| | font-weight: 500; |
| | } |
| | .explorer-main { |
| | flex: 1; |
| | padding: 16px; |
| | overflow-y: auto; |
| | } |
| | .file-grid { |
| | display: grid; |
| | grid-template-columns: repeat(auto-fill, minmax(100px, 1fr)); |
| | gap: 16px; |
| | } |
| | .file-item { |
| | display: flex; |
| | flex-direction: column; |
| | align-items: center; |
| | padding: 12px; |
| | border-radius: 8px; |
| | cursor: pointer; |
| | transition: all 0.3s ease; |
| | } |
| | .file-item:hover { |
| | background: rgba(99, 102, 241, 0.1); |
| | } |
| | .file-icon { |
| | font-size: 2rem; |
| | margin-bottom: 8px; |
| | } |
| | .file-name { |
| | font-size: 0.75rem; |
| | text-align: center; |
| | color: var(--text-primary); |
| | line-height: 1.3; |
| | } |
| | </style> |
| | `; |
| | }, |
| |
|
| | |
| | updateClock() { |
| | const clockElement = document.querySelector('.time'); |
| | const updateTime = () => { |
| | const now = new Date(); |
| | const time = now.toLocaleTimeString([], { |
| | hour: '2-digit', |
| | minute: '2-digit', |
| | hour12: true |
| | }); |
| | clockElement.textContent = time; |
| | }; |
| | |
| | updateTime(); |
| | setInterval(updateTime, 1000); |
| | }, |
| |
|
| | showLoadingScreen() { |
| | const messages = [ |
| | 'Initializing cat-ware...', |
| | 'Loading whiskers.dll...', |
| | 'Calibrating purr engine...', |
| | 'Installing 9 lives protection...', |
| | 'Warming up the litter box...', |
| | 'Ready to pounce! 🐾' |
| | ]; |
| |
|
| | let messageIndex = 0; |
| | const messageElement = document.querySelector('.loading-message'); |
| | |
| | const updateMessage = () => { |
| | if (messageIndex < messages.length) { |
| | messageElement.textContent = messages[messageIndex]; |
| | messageIndex++; |
| | setTimeout(updateMessage, 500); |
| | } |
| | }; |
| | |
| | updateMessage(); |
| | }, |
| |
|
| | hideLoadingScreen() { |
| | const loadingScreen = document.getElementById('loading-screen'); |
| | loadingScreen.classList.add('hidden'); |
| | }, |
| |
|
| | playStartupSound() { |
| | |
| | console.log('🐱 *gentle startup meow*'); |
| | }, |
| |
|
| | showWelcomeMessage() { |
| | this.showNotification('Welcome to CatOS! Double-click icons to launch apps 🐾', 'success'); |
| | }, |
| |
|
| | showNotification(message, type = 'info') { |
| | const notification = document.createElement('div'); |
| | notification.className = `notification ${type}`; |
| | notification.innerHTML = ` |
| | <div class="notification-content"> |
| | <span class="notification-icon"> |
| | ${type === 'success' ? '✅' : type === 'error' ? '❌' : 'ℹ️'} |
| | </span> |
| | <span class="notification-message">${message}</span> |
| | </div> |
| | `; |
| | |
| | |
| | Object.assign(notification.style, { |
| | position: 'fixed', |
| | top: '20px', |
| | right: '20px', |
| | background: type === 'error' ? '#fee2e2' : type === 'success' ? '#d1fae5' : '#dbeafe', |
| | color: type === 'error' ? '#dc2626' : type === 'success' ? '#059669' : '#1d4ed8', |
| | padding: '12px 16px', |
| | borderRadius: '8px', |
| | border: `1px solid ${type === 'error' ? '#fecaca' : type === 'success' ? '#a7f3d0' : '#bfdbfe'}`, |
| | boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)', |
| | zIndex: '10000', |
| | fontSize: '0.875rem', |
| | maxWidth: '300px', |
| | animation: 'slideIn 0.3s ease' |
| | }); |
| | |
| | document.body.appendChild(notification); |
| | |
| | |
| | setTimeout(() => { |
| | notification.style.animation = 'slideOut 0.3s ease'; |
| | setTimeout(() => { |
| | document.body.removeChild(notification); |
| | }, 300); |
| | }, 3000); |
| | }, |
| |
|
| | |
| | handleKeyboard(e) { |
| | |
| | if (e.altKey && e.key === 'Tab') { |
| | e.preventDefault(); |
| | this.switchToNextWindow(); |
| | } |
| | |
| | |
| | if (e.ctrlKey && e.altKey && e.key.toLowerCase() === 't') { |
| | e.preventDefault(); |
| | this.launchApp('terminal'); |
| | } |
| | |
| | |
| | if (e.key === 'Escape') { |
| | this.hideContextMenu(); |
| | this.hideStartMenu(); |
| | } |
| | }, |
| |
|
| | switchToNextWindow() { |
| | const windowIds = Array.from(this.windows.keys()).filter(id => { |
| | const window = this.windows.get(id); |
| | return !window.minimized; |
| | }); |
| | |
| | if (windowIds.length === 0) return; |
| | |
| | const currentIndex = windowIds.indexOf(this.focusedWindow); |
| | const nextIndex = (currentIndex + 1) % windowIds.length; |
| | this.focusWindow(windowIds[nextIndex]); |
| | }, |
| |
|
| | |
| | setRandomWallpaper() { |
| | const wallpaperCount = 6; |
| | const randomWallpaper = Math.floor(Math.random() * wallpaperCount) + 1; |
| | const wallpaperPath = `./static/wallpapers/wallpaper${randomWallpaper}.png`; |
| | |
| | |
| | const desktop = document.getElementById('desktop'); |
| | const currentStyle = getComputedStyle(desktop); |
| | const patternImage = `url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><text y=".9em" font-size="20" opacity="0.1">🐾</text></svg>')`; |
| | |
| | desktop.style.backgroundImage = `url('${wallpaperPath}'), ${patternImage}`; |
| | |
| | |
| | console.log(`🖼️ Selected wallpaper: wallpaper${randomWallpaper}.png`); |
| | |
| | |
| | desktop.style.opacity = '0'; |
| | setTimeout(() => { |
| | desktop.style.transition = 'opacity 0.5s ease'; |
| | desktop.style.opacity = '1'; |
| | }, 100); |
| | }, |
| |
|
| | |
| | initializeTerminal(terminalId) { |
| | const input = document.getElementById(`terminal-input-${terminalId}`); |
| | const content = document.getElementById(`terminal-content-${terminalId}`); |
| | const prompt = document.getElementById(`prompt-${terminalId}`); |
| | |
| | if (!input || !content || !prompt) return; |
| |
|
| | |
| | const terminal = { |
| | currentDirectory: '/home/catcoder', |
| | commandHistory: [], |
| | historyIndex: -1, |
| | visitorInfo: this.getVisitorInfo() |
| | }; |
| |
|
| | |
| | input.addEventListener('keydown', (e) => { |
| | if (e.key === 'Enter') { |
| | const command = input.value.trim(); |
| | if (command) { |
| | this.executeCommand(command, terminal, content, prompt); |
| | terminal.commandHistory.push(command); |
| | terminal.historyIndex = terminal.commandHistory.length; |
| | } |
| | input.value = ''; |
| | } else if (e.key === 'ArrowUp') { |
| | e.preventDefault(); |
| | if (terminal.historyIndex > 0) { |
| | terminal.historyIndex--; |
| | input.value = terminal.commandHistory[terminal.historyIndex]; |
| | } |
| | } else if (e.key === 'ArrowDown') { |
| | e.preventDefault(); |
| | if (terminal.historyIndex < terminal.commandHistory.length - 1) { |
| | terminal.historyIndex++; |
| | input.value = terminal.commandHistory[terminal.historyIndex]; |
| | } else { |
| | terminal.historyIndex = terminal.commandHistory.length; |
| | input.value = ''; |
| | } |
| | } |
| | }); |
| |
|
| | |
| | content.addEventListener('click', () => { |
| | input.focus(); |
| | }); |
| | }, |
| |
|
| | getVisitorInfo() { |
| | return { |
| | browser: navigator.userAgent.split(' ').pop().split('/')[0] || 'Unknown', |
| | platform: navigator.platform || 'Unknown', |
| | language: navigator.language || 'en-US', |
| | screenWidth: screen.width, |
| | screenHeight: screen.height, |
| | timezone: Intl.DateTimeFormat().resolvedOptions().timeZone || 'Unknown', |
| | connection: navigator.connection?.effectiveType || 'Unknown', |
| | visitTime: new Date().toISOString() |
| | }; |
| | }, |
| |
|
| | executeCommand(command, terminal, content, prompt) { |
| | |
| | this.addTerminalLine(content, `${prompt.textContent} ${command}`); |
| | |
| | const [cmd, ...args] = command.toLowerCase().split(' '); |
| | const fullArgs = args.join(' '); |
| |
|
| | |
| | switch(cmd) { |
| | case 'help': |
| | this.terminalHelp(content); |
| | break; |
| | case 'clear': |
| | this.terminalClear(content); |
| | break; |
| | case 'whoami': |
| | this.terminalWhoami(content, terminal.visitorInfo); |
| | break; |
| | case 'ls': |
| | this.terminalLs(content, terminal.currentDirectory, fullArgs); |
| | break; |
| | case 'cat': |
| | this.terminalCat(content, fullArgs, terminal.currentDirectory); |
| | break; |
| | case 'cd': |
| | this.terminalCd(content, terminal, fullArgs); |
| | break; |
| | case 'pwd': |
| | this.terminalPwd(content, terminal.currentDirectory); |
| | break; |
| | case 'ps': |
| | this.terminalPs(content, fullArgs); |
| | break; |
| | case 'date': |
| | this.terminalDate(content); |
| | break; |
| | case 'uptime': |
| | this.terminalUptime(content); |
| | break; |
| | case 'meow': |
| | this.terminalMeow(content, fullArgs); |
| | break; |
| | case 'purr': |
| | this.terminalPurr(content); |
| | break; |
| | case 'whiskers': |
| | this.terminalWhiskers(content); |
| | break; |
| | case 'nap': |
| | this.terminalNap(content); |
| | break; |
| | case 'fortune': |
| | this.terminalFortune(content); |
| | break; |
| | case 'curl': |
| | this.terminalCurl(content, fullArgs); |
| | break; |
| | case 'git': |
| | this.terminalGit(content, fullArgs); |
| | break; |
| | case 'npm': |
| | this.terminalNpm(content, fullArgs); |
| | break; |
| | case 'history': |
| | this.terminalHistory(content, terminal.commandHistory); |
| | break; |
| | case 'echo': |
| | this.terminalEcho(content, fullArgs); |
| | break; |
| | default: |
| | this.terminalUnknown(content, cmd); |
| | } |
| |
|
| | |
| | this.updatePrompt(prompt, terminal.currentDirectory); |
| | |
| | |
| | content.scrollTop = content.scrollHeight; |
| | }, |
| |
|
| | addTerminalLine(content, text, className = 'terminal-text') { |
| | const line = document.createElement('div'); |
| | line.className = 'terminal-line'; |
| | line.innerHTML = `<span class="${className}">${text}</span>`; |
| | content.appendChild(line); |
| | }, |
| |
|
| | addTerminalOutput(content, text) { |
| | const line = document.createElement('div'); |
| | line.className = 'terminal-line'; |
| | line.innerHTML = `<span class="terminal-output">${text}</span>`; |
| | content.appendChild(line); |
| | }, |
| |
|
| | updatePrompt(prompt, currentDirectory) { |
| | const dirName = currentDirectory.split('/').pop() || currentDirectory; |
| | const shortDir = dirName === 'catcoder' ? '~' : dirName; |
| | prompt.textContent = `cat@catos:${shortDir}$`; |
| | }, |
| |
|
| | |
| | terminalHelp(content) { |
| | const helpText = ` |
| | Available Commands: |
| | <span class="terminal-category">📁 Navigation:</span> |
| | ls [path] - List directory contents |
| | cd [directory] - Change directory |
| | pwd - Print working directory |
| | cat [file] - Display file contents |
| | |
| | <span class="terminal-category">🔍 System:</span> |
| | whoami - Display visitor information |
| | ps aux - Show running processes |
| | date - Show current date/time |
| | uptime - Show system uptime |
| | history - Show command history |
| | clear - Clear terminal |
| | |
| | <span class="terminal-category">🌐 Network:</span> |
| | curl [url] - Fetch web content |
| | git [command] - Git operations |
| | npm [command] - NPM operations |
| | |
| | <span class="terminal-category">🐱 Cat Commands:</span> |
| | meow [message] - Cat responses |
| | purr - Show happiness level |
| | whiskers - ASCII cat art |
| | nap - Activate screensaver |
| | fortune - Cat wisdom |
| | |
| | <span class="terminal-category">💡 Tips:</span> |
| | Use ↑/↓ arrows for command history |
| | Try: cat projects/sneaky-cat-proxy.md |
| | `; |
| | this.addTerminalOutput(content, helpText); |
| | }, |
| |
|
| | terminalClear(content) { |
| | content.innerHTML = ''; |
| | }, |
| |
|
| | terminalWhoami(content, visitorInfo) { |
| | const info = ` |
| | <span class="terminal-category">🕵️ Visitor Detective Results:</span> |
| | ┌─────────────────────────────────────┐ |
| | │ Browser: ${visitorInfo.browser} |
| | │ Platform: ${visitorInfo.platform} |
| | │ Screen: ${visitorInfo.screenWidth}x${visitorInfo.screenHeight} |
| | │ Language: ${visitorInfo.language} |
| | │ Timezone: ${visitorInfo.timezone} |
| | │ Connection: ${visitorInfo.connection} |
| | │ Visit Time: ${new Date(visitorInfo.visitTime).toLocaleString()} |
| | └─────────────────────────────────────┘ |
| | <span class="terminal-success">*purrs* Nice to meet you, fellow human! 🐱</span> |
| | `; |
| | this.addTerminalOutput(content, info); |
| | }, |
| |
|
| | terminalLs(content, currentDirectory, args) { |
| | const directories = { |
| | '/home/catcoder': [ |
| | { name: 'Desktop', type: 'dir', icon: '📁' }, |
| | { name: 'projects', type: 'dir', icon: '📁' }, |
| | { name: 'documents', type: 'dir', icon: '📁' }, |
| | { name: 'pictures', type: 'dir', icon: '📁' }, |
| | { name: 'resume.pdf', type: 'file', icon: '📄' }, |
| | { name: 'cat-wisdom.txt', type: 'file', icon: '🐱' } |
| | ], |
| | '/home/catcoder/projects': [ |
| | { name: 'sneaky-cat-proxy', type: 'dir', icon: '🔗' }, |
| | { name: 'cat-photo-gallery', type: 'dir', icon: '📸' }, |
| | { name: 'robo-cat-manager', type: 'dir', icon: '🤖' } |
| | ], |
| | '/home/catcoder/documents': [ |
| | { name: 'ideas.md', type: 'file', icon: '💡' }, |
| | { name: 'todo.txt', type: 'file', icon: '📝' }, |
| | { name: 'cat-facts.json', type: 'file', icon: '🐾' } |
| | ], |
| | '/home/catcoder/pictures': [ |
| | { name: 'profile-cat.jpg', type: 'file', icon: '😸' }, |
| | { name: 'project-screenshots', type: 'dir', icon: '📁' }, |
| | { name: 'memes', type: 'dir', icon: '😹' } |
| | ] |
| | }; |
| |
|
| | const contents = directories[currentDirectory] || []; |
| | |
| | if (contents.length === 0) { |
| | this.addTerminalOutput(content, 'Directory is empty... like a cat\'s food bowl at 3am 🍽️'); |
| | return; |
| | } |
| |
|
| | let output = `<span class="terminal-category">📂 Contents of ${currentDirectory}:</span>\n`; |
| | contents.forEach(item => { |
| | const type = item.type === 'dir' ? '<span class="terminal-directory">DIR</span>' : '<span class="terminal-file">FILE</span>'; |
| | output += `${item.icon} ${type} ${item.name}\n`; |
| | }); |
| | |
| | this.addTerminalOutput(content, output); |
| | }, |
| |
|
| | terminalCat(content, filename, currentDirectory) { |
| | const files = { |
| | 'resume.pdf': '📄 PDF files need special cat vision! Try opening the About app instead 😸', |
| | 'cat-wisdom.txt': `🐱 Cat Wisdom Collection: |
| | • The early cat gets the tuna |
| | • In cat we trust, all others bring treats |
| | • Curiosity didn't kill the cat, it made it a developer |
| | • A cat's work is never done... mostly because we nap too much |
| | • Debugging is like herding cats, but the cats are invisible`, |
| | 'ideas.md': `💡 Project Ideas: |
| | - Cat-themed password manager |
| | - Automatic laser pointer for remote work breaks |
| | - AI that detects when treats are needed |
| | - Social network for cats (MeowSpace) |
| | - Smart litter box with analytics dashboard`, |
| | 'sneaky-cat-proxy.md': this.getProjectFile('sneaky-cat-proxy'), |
| | 'cat-photo-gallery.md': this.getProjectFile('cat-photo-gallery'), |
| | 'robo-cat-manager.md': this.getProjectFile('robo-cat-manager') |
| | }; |
| |
|
| | |
| | if (filename.includes('/')) { |
| | const parts = filename.split('/'); |
| | const projectName = parts[parts.length - 2]; |
| | if (this.projects[projectName]) { |
| | this.addTerminalOutput(content, this.getProjectFile(projectName)); |
| | return; |
| | } |
| | } |
| |
|
| | const fileContent = files[filename]; |
| | if (fileContent) { |
| | this.addTerminalOutput(content, fileContent); |
| | } else { |
| | this.addTerminalOutput(content, `cat: ${filename}: No such file or directory 🙀\nTry 'ls' to see available files!`); |
| | } |
| | }, |
| |
|
| | getProjectFile(projectId) { |
| | const project = this.projects[projectId]; |
| | if (!project) return 'Project not found! 🙀'; |
| |
|
| | return ` |
| | <span class="terminal-category">${project.icon} ${project.title}</span> |
| | ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ |
| | |
| | <span class="terminal-success">Description:</span> |
| | ${project.description} |
| | |
| | <span class="terminal-success">Technologies:</span> |
| | ${project.technologies.map(tech => `• ${tech}`).join('\n')} |
| | |
| | <span class="terminal-success">Features:</span> |
| | ${project.features.map(feature => `🐾 ${feature}`).join('\n')} |
| | |
| | <span class="terminal-success">Links:</span> |
| | • GitHub: ${project.github} |
| | • Demo: ${project.demo} |
| | |
| | <span class="terminal-category">*purrs approvingly* 😸</span> |
| | `; |
| | }, |
| |
|
| | terminalCd(content, terminal, directory) { |
| | if (!directory) { |
| | terminal.currentDirectory = '/home/catcoder'; |
| | this.addTerminalOutput(content, 'Changed to home directory 🏠'); |
| | return; |
| | } |
| |
|
| | const validDirs = { |
| | '~': '/home/catcoder', |
| | 'home': '/home/catcoder', |
| | 'projects': '/home/catcoder/projects', |
| | 'documents': '/home/catcoder/documents', |
| | 'pictures': '/home/catcoder/pictures', |
| | 'desktop': '/home/catcoder/Desktop', |
| | '..': '/home/catcoder' |
| | }; |
| |
|
| | if (validDirs[directory]) { |
| | terminal.currentDirectory = validDirs[directory]; |
| | this.addTerminalOutput(content, `Changed to ${terminal.currentDirectory} 📁`); |
| | } else { |
| | this.addTerminalOutput(content, `cd: ${directory}: No such directory 🙀`); |
| | } |
| | }, |
| |
|
| | terminalPwd(content, currentDirectory) { |
| | this.addTerminalOutput(content, currentDirectory); |
| | }, |
| |
|
| | terminalPs(content, args) { |
| | const processes = ` |
| | <span class="terminal-category">🔄 CatOS Process Status:</span> |
| | USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND |
| | cat 1337 15.2 8.5 245760 32156 pts/0 Sl+ 09:30 0:42 /usr/bin/coffee-maker |
| | cat 2048 25.1 12.3 512000 45678 pts/1 R+ 09:31 1:23 /bin/cat-nap-scheduler |
| | cat 4096 5.8 3.2 128000 12345 pts/2 S 09:32 0:15 /usr/bin/treat-dispenser |
| | cat 8192 45.7 25.1 1024000 98765 pts/3 R+ 09:33 2:34 /opt/purr-engine --turbo |
| | cat 1024 2.1 1.8 64000 5432 pts/4 S 09:34 0:08 /bin/whiskers-daemon |
| | rafael 9999 98.5 75.2 2048000 234567 pts/5 R+ 09:35 5:67 /usr/bin/coding-furiously |
| | |
| | <span class="terminal-success">Current mood: Caffeinated and ready to code! ☕</span> |
| | `; |
| | this.addTerminalOutput(content, processes); |
| | }, |
| |
|
| | terminalDate(content) { |
| | const now = new Date(); |
| | const catTime = `${now.toLocaleDateString()} ${now.toLocaleTimeString()}`; |
| | this.addTerminalOutput(content, `${catTime} (Cat Standard Time) 🐱⏰`); |
| | }, |
| |
|
| | terminalUptime(content) { |
| | const uptime = ` |
| | <span class="terminal-category">⏱️ System Uptime:</span> |
| | Developer: 5+ years of coding experience |
| | Coffee Machine: 3 hours since last refill ☕ |
| | Cat Nap Counter: 42 naps today 😴 |
| | Bug Squashing: 99.9% success rate 🐛 |
| | Treat Dispenser: Fully operational 🐟 |
| | Purr Engine: Running at optimal frequency 😸 |
| | |
| | <span class="terminal-success">Load average: 1.33, 7.77, 42.00 (that's normal for a cat) 📊</span> |
| | `; |
| | this.addTerminalOutput(content, uptime); |
| | }, |
| |
|
| | terminalMeow(content, message) { |
| | const responses = [ |
| | '🐱 *meows back softly*', |
| | '😸 Purrfect! I understand completely!', |
| | '🐾 *headbutts your screen affectionately*', |
| | '😻 That\'s exactly what I was thinking!', |
| | '🙀 You speak fluent cat!', |
| | '😺 *slow blinks* - that means "I love you" in cat', |
| | '🐱💻 Meow back! Want to see my latest code?' |
| | ]; |
| |
|
| | if (message) { |
| | this.addTerminalOutput(content, `🐱 You said: "${message}"`); |
| | this.addTerminalOutput(content, `🐾 Translation: "Please give me treats"`); |
| | } |
| |
|
| | const response = responses[Math.floor(Math.random() * responses.length)]; |
| | this.addTerminalOutput(content, response); |
| | }, |
| |
|
| | terminalPurr(content) { |
| | this.addTerminalOutput(content, ` |
| | 🐱 *PURRRRRRRRRRRRRRRR* |
| | 😸 Happiness Level: 95% (needs more treats) |
| | 🐾 Satisfaction with website: MAXIMUM |
| | 😻 Current mood: Content developer cat`); |
| | }, |
| |
|
| | terminalWhiskers(content) { |
| | const catArt = ` |
| | /\\_/\\ |
| | ( o.o ) |
| | > ^ < |
| | |
| | /\\_/\\ ( |
| | ( ^.^ ) _) <- This is me coding |
| | o_(")(") |
| | |
| | |\\---/| |
| | | o_o | <- Me when code works |
| | \\_^_/ |
| | |
| | /\\_____/\\ |
| | / o o \\ <- Me reviewing code |
| | ( == ^ == ) |
| | ) ( |
| | ( ) |
| | ( ( ) ( ) ) |
| | (__(__)___(__)__) |
| | `; |
| | this.addTerminalOutput(content, catArt); |
| | }, |
| |
|
| | terminalNap(content) { |
| | this.addTerminalOutput(content, '😴 Activating cat nap mode...'); |
| | setTimeout(() => { |
| | this.addTerminalOutput(content, '🐱 *stretches and yawns*'); |
| | setTimeout(() => { |
| | this.addTerminalOutput(content, '😸 Refreshed and ready to code! *tail swish*'); |
| | }, 2000); |
| | }, 2000); |
| | }, |
| |
|
| | terminalFortune(content) { |
| | const fortunes = [ |
| | '🐱 A cat\'s code is worth a thousand barks.', |
| | '😸 Today is a good day to push to production.', |
| | '🐾 Your code will run purrfectly... eventually.', |
| | '😻 The best debugging happens at 2 AM with a cat on your keyboard.', |
| | '🙀 Error 404: Treats not found. Please refill immediately.', |
| | '😺 In the future, all websites will be operated by cats.', |
| | '🐱💻 Curiosity didn\'t kill the cat; it made it a senior developer.', |
| | '😹 Your next commit will be legendary... like a cat video.', |
| | '🐾 Remember: There are no mistakes, only happy little bugs.', |
| | '🐱 The cloud is just other people\'s litter boxes.' |
| | ]; |
| |
|
| | const fortune = fortunes[Math.floor(Math.random() * fortunes.length)]; |
| | this.addTerminalOutput(content, `<span class="terminal-category">🔮 Cat Fortune:</span>\n${fortune}`); |
| | }, |
| |
|
| | terminalCurl(content, url) { |
| | if (!url) { |
| | this.addTerminalOutput(content, 'curl: no URL specified 🙀\nUsage: curl <url>'); |
| | return; |
| | } |
| |
|
| | this.addTerminalOutput(content, `🌐 Fetching ${url}...`); |
| | |
| | setTimeout(() => { |
| | if (url.includes('cat') || url.includes('meow')) { |
| | this.addTerminalOutput(content, `😸 Connection successful! Cat-approved website detected.`); |
| | } else if (url.includes('dog')) { |
| | this.addTerminalOutput(content, `🙀 Warning: Canine content detected. Proceed with caution.`); |
| | } else { |
| | this.addTerminalOutput(content, `📡 HTTP/1.1 200 OK - Site looks paw-some!`); |
| | } |
| | }, 1500); |
| | }, |
| |
|
| | terminalGit(content, args) { |
| | if (args.startsWith('status')) { |
| | this.addTerminalOutput(content, ` |
| | On branch main |
| | Your branch is up to date with 'origin/main' |
| | |
| | Changes not staged for commit: |
| | modified: life.js (added more cat puns) |
| | modified: happiness.css (increased by 200%) |
| | |
| | Untracked files: |
| | cat-treats.json |
| | purr-sounds.wav |
| | |
| | 😸 Working tree status: Purrfect!`); |
| | } else if (args.startsWith('log')) { |
| | this.addTerminalOutput(content, ` |
| | commit a1b2c3d (HEAD -> main) |
| | Author: CatDeveloper <meow@catos.dev> |
| | Date: ${new Date().toDateString()} |
| | |
| | Fix: Resolved all bugs with strategic cat napping |
| | |
| | commit d4e5f6g |
| | Author: CatDeveloper <meow@catos.dev> |
| | Date: Yesterday |
| | |
| | Add: More cat puns to error messages`); |
| | } else { |
| | this.addTerminalOutput(content, '🐱 Git command executed! *purrs approvingly*'); |
| | } |
| | }, |
| |
|
| | terminalNpm(content, args) { |
| | if (args.startsWith('install')) { |
| | this.addTerminalOutput(content, ` |
| | 📦 Installing cat-packages... |
| | 🐾 + cat-utils@9.0.0 |
| | 😸 + purr-framework@1.2.3 |
| | 🐱 + meow-validator@0.5.7 |
| | 😻 + treat-dispenser@2.1.0 |
| | |
| | added 42 packages in 3.14s (purr time) |
| | 😺 All packages installed successfully!`); |
| | } else if (args.startsWith('run')) { |
| | this.addTerminalOutput(content, `🏃♀️ Running npm script... *cat runs in circles*`); |
| | } else { |
| | this.addTerminalOutput(content, '📦 NPM operation completed! Dependencies are purr-fect! 😸'); |
| | } |
| | }, |
| |
|
| | terminalHistory(content, commandHistory) { |
| | if (commandHistory.length === 0) { |
| | this.addTerminalOutput(content, 'History is empty... like a cat\'s promise to stay off the keyboard 😸'); |
| | return; |
| | } |
| |
|
| | let output = '<span class="terminal-category">📚 Command History:</span>\n'; |
| | commandHistory.forEach((cmd, index) => { |
| | output += `${index + 1}. ${cmd}\n`; |
| | }); |
| | this.addTerminalOutput(content, output); |
| | }, |
| |
|
| | terminalEcho(content, text) { |
| | if (!text) { |
| | this.addTerminalOutput(content, ''); |
| | return; |
| | } |
| | |
| | if (text.toLowerCase().includes('cat') || text.toLowerCase().includes('meow')) { |
| | this.addTerminalOutput(content, `${text} 😸`); |
| | } else { |
| | this.addTerminalOutput(content, text); |
| | } |
| | }, |
| |
|
| | terminalUnknown(content, command) { |
| | const suggestions = [ |
| | `🙀 Command '${command}' not found! Did you mean to meow instead?`, |
| | `😿 '${command}' is not a valid command. Try 'help' for available commands!`, |
| | `🐱 Unknown command '${command}'. Even cats make typos! Try 'help'.`, |
| | `😸 '${command}'? That's not cat language! Type 'help' to see what I understand.`, |
| | `🐾 Command '${command}' not recognized. Are you sure you're not a dog? 🐕` |
| | ]; |
| | |
| | const suggestion = suggestions[Math.floor(Math.random() * suggestions.length)]; |
| | this.addTerminalOutput(content, suggestion); |
| | } |
| | }; |
| |
|
| | |
| | document.addEventListener('DOMContentLoaded', () => { |
| | CatOS.init(); |
| | }); |
| |
|
| | |
| | const style = document.createElement('style'); |
| | style.textContent = ` |
| | @keyframes slideIn { |
| | from { |
| | transform: translateX(100%); |
| | opacity: 0; |
| | } |
| | to { |
| | transform: translateX(0); |
| | opacity: 1; |
| | } |
| | } |
| | |
| | @keyframes slideOut { |
| | from { |
| | transform: translateX(0); |
| | opacity: 1; |
| | } |
| | to { |
| | transform: translateX(100%); |
| | opacity: 0; |
| | } |
| | } |
| | |
| | .notification-content { |
| | display: flex; |
| | align-items: center; |
| | gap: 8px; |
| | } |
| | `; |
| | document.head.appendChild(style); |
| |
|
| | console.log('🐱💻 CatOS v9.0 (Whiskers Edition) loaded successfully!'); |
| | console.log('💡 Try these keyboard shortcuts:'); |
| | console.log(' • Alt + Tab: Switch windows'); |
| | console.log(' • Ctrl + Alt + T: Open terminal'); |
| | console.log(' • Escape: Close menus'); |