mirror of
https://github.com/ghndrx/webos.git
synced 2026-02-10 06:45:00 +00:00
Initial WebOS portfolio site
This commit is contained in:
299
js/app.js
Normal file
299
js/app.js
Normal file
@@ -0,0 +1,299 @@
|
||||
// GregOS - WebOS Resume Site
|
||||
|
||||
class GregOS {
|
||||
constructor() {
|
||||
this.windows = new Map();
|
||||
this.zIndex = 100;
|
||||
this.windowOffset = { x: 50, y: 50 };
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
this.bindIconClicks();
|
||||
this.startClock();
|
||||
this.setupTerminal();
|
||||
}
|
||||
|
||||
bindIconClicks() {
|
||||
document.querySelectorAll('.icon').forEach(icon => {
|
||||
icon.addEventListener('dblclick', () => {
|
||||
const windowId = icon.dataset.window;
|
||||
this.openWindow(windowId);
|
||||
});
|
||||
// Mobile: single tap
|
||||
icon.addEventListener('click', (e) => {
|
||||
if (window.innerWidth <= 768) {
|
||||
const windowId = icon.dataset.window;
|
||||
this.openWindow(windowId);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
openWindow(id) {
|
||||
if (this.windows.has(id)) {
|
||||
const win = this.windows.get(id);
|
||||
win.element.classList.remove('minimized');
|
||||
this.focusWindow(win.element);
|
||||
return;
|
||||
}
|
||||
|
||||
const template = document.getElementById('window-template');
|
||||
const content = document.getElementById(`content-${id}`);
|
||||
|
||||
if (!template || !content) return;
|
||||
|
||||
const windowEl = template.content.cloneNode(true).querySelector('.window');
|
||||
const title = document.querySelector(`[data-window="${id}"] span`).textContent;
|
||||
|
||||
windowEl.querySelector('.window-title').textContent = title;
|
||||
windowEl.querySelector('.window-content').innerHTML = content.innerHTML;
|
||||
|
||||
// If terminal, add terminal class
|
||||
if (id === 'terminal') {
|
||||
windowEl.querySelector('.window-content').classList.add('terminal-content');
|
||||
}
|
||||
|
||||
// Position window
|
||||
windowEl.style.left = this.windowOffset.x + 'px';
|
||||
windowEl.style.top = this.windowOffset.y + 'px';
|
||||
windowEl.style.width = '500px';
|
||||
windowEl.style.height = '400px';
|
||||
|
||||
this.windowOffset.x += 30;
|
||||
this.windowOffset.y += 30;
|
||||
if (this.windowOffset.x > 300) this.windowOffset = { x: 50, y: 50 };
|
||||
|
||||
document.querySelector('.windows-container').appendChild(windowEl);
|
||||
|
||||
this.windows.set(id, { element: windowEl, title });
|
||||
this.focusWindow(windowEl);
|
||||
this.addTaskbarItem(id, title);
|
||||
this.setupWindowControls(windowEl, id);
|
||||
this.setupDrag(windowEl);
|
||||
this.setupResize(windowEl);
|
||||
|
||||
// Focus terminal input
|
||||
if (id === 'terminal') {
|
||||
setTimeout(() => {
|
||||
const input = windowEl.querySelector('#terminal-input');
|
||||
if (input) input.focus();
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
|
||||
setupWindowControls(windowEl, id) {
|
||||
windowEl.querySelector('.close').addEventListener('click', () => {
|
||||
windowEl.remove();
|
||||
this.windows.delete(id);
|
||||
this.removeTaskbarItem(id);
|
||||
});
|
||||
|
||||
windowEl.querySelector('.minimize').addEventListener('click', () => {
|
||||
windowEl.classList.add('minimized');
|
||||
});
|
||||
|
||||
windowEl.querySelector('.maximize').addEventListener('click', () => {
|
||||
if (windowEl.dataset.maximized === 'true') {
|
||||
windowEl.style.left = windowEl.dataset.prevLeft;
|
||||
windowEl.style.top = windowEl.dataset.prevTop;
|
||||
windowEl.style.width = windowEl.dataset.prevWidth;
|
||||
windowEl.style.height = windowEl.dataset.prevHeight;
|
||||
windowEl.dataset.maximized = 'false';
|
||||
} else {
|
||||
windowEl.dataset.prevLeft = windowEl.style.left;
|
||||
windowEl.dataset.prevTop = windowEl.style.top;
|
||||
windowEl.dataset.prevWidth = windowEl.style.width;
|
||||
windowEl.dataset.prevHeight = windowEl.style.height;
|
||||
windowEl.style.left = '0';
|
||||
windowEl.style.top = '0';
|
||||
windowEl.style.width = '100%';
|
||||
windowEl.style.height = 'calc(100vh - 50px)';
|
||||
windowEl.dataset.maximized = 'true';
|
||||
}
|
||||
});
|
||||
|
||||
windowEl.addEventListener('mousedown', () => this.focusWindow(windowEl));
|
||||
}
|
||||
|
||||
focusWindow(windowEl) {
|
||||
this.zIndex++;
|
||||
windowEl.style.zIndex = this.zIndex;
|
||||
}
|
||||
|
||||
setupDrag(windowEl) {
|
||||
const header = windowEl.querySelector('.window-header');
|
||||
let isDragging = false;
|
||||
let startX, startY, startLeft, startTop;
|
||||
|
||||
header.addEventListener('mousedown', (e) => {
|
||||
if (e.target.closest('.window-controls')) return;
|
||||
isDragging = true;
|
||||
startX = e.clientX;
|
||||
startY = e.clientY;
|
||||
startLeft = parseInt(windowEl.style.left) || 0;
|
||||
startTop = parseInt(windowEl.style.top) || 0;
|
||||
windowEl.style.transition = 'none';
|
||||
});
|
||||
|
||||
document.addEventListener('mousemove', (e) => {
|
||||
if (!isDragging) return;
|
||||
const dx = e.clientX - startX;
|
||||
const dy = e.clientY - startY;
|
||||
windowEl.style.left = startLeft + dx + 'px';
|
||||
windowEl.style.top = startTop + dy + 'px';
|
||||
});
|
||||
|
||||
document.addEventListener('mouseup', () => {
|
||||
isDragging = false;
|
||||
windowEl.style.transition = '';
|
||||
});
|
||||
}
|
||||
|
||||
setupResize(windowEl) {
|
||||
let isResizing = false;
|
||||
let startX, startY, startWidth, startHeight;
|
||||
|
||||
windowEl.addEventListener('mousedown', (e) => {
|
||||
const rect = windowEl.getBoundingClientRect();
|
||||
if (e.clientX > rect.right - 15 && e.clientY > rect.bottom - 15) {
|
||||
isResizing = true;
|
||||
startX = e.clientX;
|
||||
startY = e.clientY;
|
||||
startWidth = rect.width;
|
||||
startHeight = rect.height;
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('mousemove', (e) => {
|
||||
if (!isResizing) return;
|
||||
const dx = e.clientX - startX;
|
||||
const dy = e.clientY - startY;
|
||||
windowEl.style.width = Math.max(400, startWidth + dx) + 'px';
|
||||
windowEl.style.height = Math.max(300, startHeight + dy) + 'px';
|
||||
});
|
||||
|
||||
document.addEventListener('mouseup', () => {
|
||||
isResizing = false;
|
||||
});
|
||||
}
|
||||
|
||||
addTaskbarItem(id, title) {
|
||||
const item = document.createElement('div');
|
||||
item.className = 'taskbar-item';
|
||||
item.dataset.window = id;
|
||||
item.textContent = title;
|
||||
item.addEventListener('click', () => {
|
||||
const win = this.windows.get(id);
|
||||
if (win) {
|
||||
win.element.classList.toggle('minimized');
|
||||
if (!win.element.classList.contains('minimized')) {
|
||||
this.focusWindow(win.element);
|
||||
}
|
||||
}
|
||||
});
|
||||
document.querySelector('.taskbar-items').appendChild(item);
|
||||
}
|
||||
|
||||
removeTaskbarItem(id) {
|
||||
const item = document.querySelector(`.taskbar-item[data-window="${id}"]`);
|
||||
if (item) item.remove();
|
||||
}
|
||||
|
||||
startClock() {
|
||||
const updateClock = () => {
|
||||
const now = new Date();
|
||||
const time = now.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
||||
document.querySelector('.clock').textContent = time;
|
||||
};
|
||||
updateClock();
|
||||
setInterval(updateClock, 1000);
|
||||
}
|
||||
|
||||
setupTerminal() {
|
||||
document.addEventListener('keydown', (e) => {
|
||||
const input = document.getElementById('terminal-input');
|
||||
if (!input || document.activeElement !== input) return;
|
||||
|
||||
if (e.key === 'Enter') {
|
||||
this.executeCommand(input.value);
|
||||
input.value = '';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
executeCommand(cmd) {
|
||||
const output = document.getElementById('terminal-output');
|
||||
if (!output) return;
|
||||
|
||||
const addLine = (text, color = '#0f0') => {
|
||||
const p = document.createElement('p');
|
||||
p.textContent = text;
|
||||
p.style.color = color;
|
||||
output.appendChild(p);
|
||||
output.scrollTop = output.scrollHeight;
|
||||
};
|
||||
|
||||
addLine(`guest@gregos:~$ ${cmd}`);
|
||||
|
||||
const commands = {
|
||||
help: () => {
|
||||
addLine('Available commands:');
|
||||
addLine(' help - Show this help');
|
||||
addLine(' about - About me');
|
||||
addLine(' projects - List projects');
|
||||
addLine(' skills - Show skills');
|
||||
addLine(' contact - Contact info');
|
||||
addLine(' clear - Clear terminal');
|
||||
addLine(' neofetch - System info');
|
||||
},
|
||||
about: () => {
|
||||
addLine('Greg Hendrickson');
|
||||
addLine('Developer, tinkerer, builder of things.');
|
||||
},
|
||||
projects: () => {
|
||||
addLine('Projects:');
|
||||
addLine(' - ShellMate (shellmate.sh)');
|
||||
addLine(' - Clawdbot');
|
||||
},
|
||||
skills: () => {
|
||||
addLine('Skills: Python, TypeScript, Go, Linux, Docker, AI/ML');
|
||||
},
|
||||
contact: () => {
|
||||
addLine('GitHub: github.com/ghndrx');
|
||||
},
|
||||
clear: () => {
|
||||
output.innerHTML = '';
|
||||
},
|
||||
neofetch: () => {
|
||||
addLine(' ____ ___ ____ ', '#e94560');
|
||||
addLine(' / ___|_ __ ___ __ _ / _ \\/ ___| ', '#e94560');
|
||||
addLine(' | | _| \'__/ _ \\/ _` | | | \\___ \\ ', '#e94560');
|
||||
addLine(' | |_| | | | __/ (_| | |_| |___) |', '#e94560');
|
||||
addLine(' \\____|_| \\___|\\__, |\\___/|____/ ', '#e94560');
|
||||
addLine(' |___/ ', '#e94560');
|
||||
addLine('');
|
||||
addLine('OS: GregOS v1.0');
|
||||
addLine('Host: The Internet');
|
||||
addLine('Uptime: Since the early days');
|
||||
addLine('Shell: bash');
|
||||
addLine('Terminal: WebTerminal');
|
||||
}
|
||||
};
|
||||
|
||||
const trimmedCmd = cmd.trim().toLowerCase();
|
||||
if (trimmedCmd === '') return;
|
||||
|
||||
if (commands[trimmedCmd]) {
|
||||
commands[trimmedCmd]();
|
||||
} else {
|
||||
addLine(`Command not found: ${cmd}. Type 'help' for available commands.`, '#ff6b6b');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
window.gregos = new GregOS();
|
||||
});
|
||||
Reference in New Issue
Block a user