|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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'); |