diff --git a/index.html b/index.html index 1fda39f..f48cbce 100644 --- a/index.html +++ b/index.html @@ -521,67 +521,103 @@
🛠️ Toolbox

Skills & Stack

-

Technologies and tools I work with

+

Technologies and tools powering the homelab

-
-
-

Virtualization

-
- Proxmox VE - KVM/QEMU - LXC Containers - VMware +
+ +
+
+
+ +
+

Virtualization

+
+
+
Proxmox VE
+
KVM/QEMU
+
LXC
+
VMware
-
-

Containerization

-
- Docker - Docker Compose - Portainer - Kubernetes + +
+
+
+ +
+

Containers

+
+
+
Docker
+
Compose
+
Portainer
+
Kubernetes
-
-

Networking

-
- Nginx Proxy Manager - Tailscale - VLANs - WireGuard + +
+
+
+ +
+

Networking

+
+
+
Nginx PM
+
Tailscale
+
VLANs
+
WireGuard
-
-

Storage

-
- TrueNAS SCALE - ZFS - NFS - Samba + +
+
+
+ +
+

Storage

+
+
+
TrueNAS
+
ZFS
+
NFS
+
Samba
-
-

Monitoring

-
- Netdata - Grafana - Prometheus - Uptime Kuma + +
+
+
+ +
+

Monitoring

+
+
+
Netdata
+
Grafana
+
Prometheus
+
Uptime Kuma
-
-

Development

-
- Git/Gitea - CI/CD - Bash - Python + +
+
+
+ +
+

Development

+
+
+
Git/Gitea
+
CI/CD
+
Bash
+
Python
diff --git a/script.js b/script.js index cad4cb1..0cc5d8a 100644 --- a/script.js +++ b/script.js @@ -334,108 +334,48 @@ function initTerminalTyping() { setInterval(typeCommand, 8000); } -// Initialize terminal typing when contact section is visible - limited to 3 cycles -const terminalObserver = new IntersectionObserver((entries) => { - entries.forEach(entry => { - if (entry.isIntersecting) { - initLimitedTerminalTyping(); - terminalObserver.unobserve(entry.target); - } - }); -}, { threshold: 0.5 }); - -const contactSection = document.querySelector('.contact-section'); -if (contactSection) { - terminalObserver.observe(contactSection); -} - -/** - * Limited Terminal Typing Effect (3 cycles max) - */ -function initLimitedTerminalTyping() { +// Simple Terminal Animation - Pre-defined content, no expanding +document.addEventListener('DOMContentLoaded', () => { const terminal = document.querySelector('.terminal-body'); if (!terminal) return; - - const commands = [ - { text: 'docker ps', output: 'CONTAINER ID IMAGE STATUS\nabc123 nginx Up 3 days\ndef456 nextcloud Up 5 days' }, - { text: 'uptime', output: 'Load: 1.06, 1.06, 1.14 | RAM: 18% of 96GB' }, - { text: 'tailscale status', output: '100.x.x.x compute-01 linux -' } - ]; - - let currentCommand = 0; - let isTyping = false; - - const typeCommand = () => { - if (isTyping) return; - isTyping = true; - - const cmd = commands[currentCommand]; - const lines = terminal.querySelectorAll('.terminal-line'); - - // Remove cursor from previous line - const cursor = terminal.querySelector('.cursor'); - if (cursor) cursor.remove(); - - // Create new command line - const newLine = document.createElement('div'); - newLine.className = 'terminal-line'; - newLine.innerHTML = ` + + // Pre-defined terminal content (no dynamic expansion) + const terminalContent = ` +
+ + ~ + whoami +
+
lemon_admin
+
+ + ~ + uptime +
+
Load: 1.06 | RAM: 18% of 96GB | 50+ containers
+
~ - | - `; - terminal.appendChild(newLine); - - const commandSpan = newLine.querySelector('.command'); - let charIndex = 0; - - const typeChar = () => { - if (charIndex < cmd.text.length) { - commandSpan.textContent += cmd.text[charIndex]; - charIndex++; - setTimeout(typeChar, 50 + Math.random() * 50); - } else { - setTimeout(() => { - const outputDiv = document.createElement('div'); - outputDiv.className = 'terminal-output'; - outputDiv.innerHTML = cmd.output.replace(/\n/g, '
'); - terminal.appendChild(outputDiv); - - const nextLine = document.createElement('div'); - nextLine.className = 'terminal-line'; - nextLine.innerHTML = ` - - ~ - | - `; - terminal.appendChild(nextLine); - - terminal.scrollTop = terminal.scrollHeight; - - isTyping = false; - currentCommand = (currentCommand + 1) % commands.length; - }, 500); - } - }; - - typeChar(); - }; - - // Start typing effect - only run 3 commands total, then stop - let commandsTyped = 0; - const maxCommands = 3; +
+ `; - const intervalId = setInterval(() => { - if (commandsTyped >= maxCommands) { - clearInterval(intervalId); - // Just keep the cursor blinking on last line - return; - } - typeCommand(); - commandsTyped++; - }, 4000); -} + // Animate each line appearing + const lines = terminalContent.trim().split('
').filter(l => l.trim()); + terminal.innerHTML = ''; + + let delay = 0; + lines.forEach((line, index) => { + const cleanLine = line + '
'; + setTimeout(() => { + const div = document.createElement('div'); + div.innerHTML = cleanLine; + terminal.appendChild(div.firstElementChild); + terminal.scrollTop = terminal.scrollHeight; + }, delay); + delay += 600; + }); +}); /** * Network connection lines animation diff --git a/styles.css b/styles.css index 531577b..58ad308 100644 --- a/styles.css +++ b/styles.css @@ -1855,3 +1855,105 @@ body { grid-template-columns: 1fr; } } + + +/* ======================================== + Tech Stack Cards (New Design) + ======================================== */ +.tech-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 1.5rem; +} + +.tech-card { + background: linear-gradient(145deg, rgba(20, 20, 30, 0.8), rgba(15, 15, 25, 0.9)); + border: 1px solid rgba(255, 255, 255, 0.06); + border-radius: 20px; + padding: 1.5rem; + transition: all 0.3s ease; +} + +.tech-card:hover { + transform: translateY(-5px); + border-color: rgba(255, 255, 255, 0.12); + box-shadow: 0 20px 40px rgba(0, 0, 0, 0.4); +} + +.tech-header { + display: flex; + align-items: center; + gap: 1rem; + margin-bottom: 1.25rem; +} + +.tech-icon { + width: 48px; + height: 48px; + background: var(--icon-bg); + border-radius: 14px; + display: flex; + align-items: center; + justify-content: center; + color: var(--icon-color); + transition: transform 0.3s ease; +} + +.tech-card:hover .tech-icon { + transform: scale(1.1); +} + +.tech-icon svg { + width: 24px; + height: 24px; +} + +.tech-title { + font-family: 'Space Grotesk', sans-serif; + font-size: 1.1rem; + font-weight: 600; + color: var(--color-text); +} + +.tech-items { + display: flex; + flex-wrap: wrap; + gap: 0.6rem; +} + +.tech-item { + display: flex; + align-items: center; + gap: 0.4rem; + padding: 0.4rem 0.9rem; + background: rgba(255, 255, 255, 0.03); + border: 1px solid rgba(255, 255, 255, 0.06); + border-radius: 100px; + font-size: 0.85rem; + color: var(--color-text-muted); + transition: all 0.2s ease; +} + +.tech-item:hover { + background: rgba(255, 255, 255, 0.06); + color: var(--color-text); +} + +.tech-item .dot { + width: 6px; + height: 6px; + background: var(--dot-color); + border-radius: 50%; +} + +@media (max-width: 1024px) { + .tech-grid { + grid-template-columns: repeat(2, 1fr); + } +} + +@media (max-width: 640px) { + .tech-grid { + grid-template-columns: 1fr; + } +}