Redesign skills section with nice tech cards, simplify terminal animation

This commit is contained in:
Roberth Rajala 2026-02-01 18:12:37 +01:00
parent 22b65311cc
commit 890665e4d7
3 changed files with 218 additions and 140 deletions

View File

@ -521,67 +521,103 @@
<div class="section-header"> <div class="section-header">
<span class="section-badge">🛠️ Toolbox</span> <span class="section-badge">🛠️ Toolbox</span>
<h2 class="section-title">Skills & Stack</h2> <h2 class="section-title">Skills & Stack</h2>
<p class="section-desc">Technologies and tools I work with</p> <p class="section-desc">Technologies and tools powering the homelab</p>
</div> </div>
<div class="skills-grid"> <div class="tech-grid">
<div class="skill-category"> <!-- Virtualization -->
<h3 class="skill-cat-title">Virtualization</h3> <div class="tech-card">
<div class="skill-tags"> <div class="tech-header">
<span class="skill-tag">Proxmox VE</span> <div class="tech-icon" style="--icon-bg: rgba(229, 126, 32, 0.15); --icon-color: #e57e20;">
<span class="skill-tag">KVM/QEMU</span> <svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"/></svg>
<span class="skill-tag">LXC Containers</span> </div>
<span class="skill-tag">VMware</span> <h3 class="tech-title">Virtualization</h3>
</div>
<div class="tech-items">
<div class="tech-item"><span class="dot" style="--dot-color: #e57e20;"></span>Proxmox VE</div>
<div class="tech-item"><span class="dot" style="--dot-color: #e57e20;"></span>KVM/QEMU</div>
<div class="tech-item"><span class="dot" style="--dot-color: #e57e20;"></span>LXC</div>
<div class="tech-item"><span class="dot" style="--dot-color: #e57e20;"></span>VMware</div>
</div> </div>
</div> </div>
<div class="skill-category"> <!-- Containerization -->
<h3 class="skill-cat-title">Containerization</h3> <div class="tech-card">
<div class="skill-tags"> <div class="tech-header">
<span class="skill-tag">Docker</span> <div class="tech-icon" style="--icon-bg: rgba(36, 150, 237, 0.15); --icon-color: #2496ed;">
<span class="skill-tag">Docker Compose</span> <svg viewBox="0 0 24 24" fill="currentColor"><path d="M13.983 11.078h2.119a.186.186 0 00.186-.185V9.006a.186.186 0 00-.186-.186h-2.119a.185.185 0 00-.185.185v1.888c0 .102.083.185.185.185zm-2.954-5.43h2.118a.186.186 0 00.186-.186V3.574a.186.186 0 00-.186-.185h-2.118a.185.185 0 00-.185.185v1.888c0 .102.082.185.185.186zm0 2.716h2.118a.187.187 0 00.186-.186V6.29a.186.186 0 00-.186-.185h-2.118a.185.185 0 00-.185.185v1.887c0 .102.082.186.185.186zm-2.93 0h2.12a.186.186 0 00.184-.186V6.29a.185.185 0 00-.185-.185H8.1a.185.185 0 00-.185.185v1.887c0 .102.083.186.185.186zm-2.964 0h2.119a.186.186 0 00.185-.186V6.29a.185.185 0 00-.185-.185H5.136a.186.186 0 00-.186.185v1.887c0 .102.084.186.186.186zm5.893 2.715h2.118a.186.186 0 00.186-.185V9.006a.186.186 0 00-.186-.186h-2.118a.185.185 0 00-.185.185v1.888c0 .102.082.185.185.185zm-2.93 0h2.12a.185.185 0 00.184-.185V9.006a.185.185 0 00-.184-.186h-2.12a.185.185 0 00-.184.185v1.888c0 .102.083.185.185.185zm-2.964 0h2.119a.185.185 0 00.185-.185V9.006a.185.185 0 00-.184-.186h-2.12a.186.186 0 00-.186.186v1.887c0 .102.084.185.186.185zm-2.92 0h2.12a.185.185 0 00.184-.185V9.006a.185.185 0 00-.184-.186h-2.12a.185.185 0 00-.184.185v1.888c0 .102.082.185.185.185zm20.744 1.3c-.66-1.305-2.348-1.816-3.376-1.128-.18.117-.348.244-.507.374-.468.383-.978.696-1.51.912-.532.217-1.09.333-1.65.342-.56.01-1.12-.087-1.657-.287-.538-.2-1.04-.502-1.476-.89-.435-.388-.793-.856-1.053-1.38-.26-.525-.417-1.096-.46-1.678h9.217c.44 0 .84-.26 1.02-.665l1.03-2.35c.145-.33.08-.715-.167-.977-.246-.26-.626-.35-.965-.228l-1.92.74c-.36-1.09-1.09-2.01-2.06-2.58-1.88-1.08-4.24-.67-5.66.95l-4.04 4.7c-1.02 1.18-1.3 2.82-.73 4.26.5 1.27 1.59 2.2 2.9 2.46.34.07.69.09 1.03.06h7.92c.13 0 .26-.01.38-.03.89-.14 1.62-.72 1.95-1.5l.01-.02c.27-.62.32-1.33.14-1.99z"/></svg>
<span class="skill-tag">Portainer</span> </div>
<span class="skill-tag">Kubernetes</span> <h3 class="tech-title">Containers</h3>
</div>
<div class="tech-items">
<div class="tech-item"><span class="dot" style="--dot-color: #2496ed;"></span>Docker</div>
<div class="tech-item"><span class="dot" style="--dot-color: #2496ed;"></span>Compose</div>
<div class="tech-item"><span class="dot" style="--dot-color: #2496ed;"></span>Portainer</div>
<div class="tech-item"><span class="dot" style="--dot-color: #2496ed;"></span>Kubernetes</div>
</div> </div>
</div> </div>
<div class="skill-category"> <!-- Networking -->
<h3 class="skill-cat-title">Networking</h3> <div class="tech-card">
<div class="skill-tags"> <div class="tech-header">
<span class="skill-tag">Nginx Proxy Manager</span> <div class="tech-icon" style="--icon-bg: rgba(34, 211, 238, 0.15); --icon-color: #22d3ee;">
<span class="skill-tag">Tailscale</span> <svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 17.93c-3.95-.49-7-3.85-7-7.93 0-.62.08-1.21.21-1.79L9 15v1c0 1.1.9 2 2 2v1.93zm6.9-2.54c-.26-.81-1-1.39-1.9-1.39h-1v-3c0-.55-.45-1-1-1H8v-2h2c.55 0 1-.45 1-1V7h2c1.1 0 2-.9 2-2v-.41c2.93 1.19 5 4.06 5 7.41 0 2.08-.8 3.97-2.1 5.39z"/></svg>
<span class="skill-tag">VLANs</span> </div>
<span class="skill-tag">WireGuard</span> <h3 class="tech-title">Networking</h3>
</div>
<div class="tech-items">
<div class="tech-item"><span class="dot" style="--dot-color: #22d3ee;"></span>Nginx PM</div>
<div class="tech-item"><span class="dot" style="--dot-color: #22d3ee;"></span>Tailscale</div>
<div class="tech-item"><span class="dot" style="--dot-color: #22d3ee;"></span>VLANs</div>
<div class="tech-item"><span class="dot" style="--dot-color: #22d3ee;"></span>WireGuard</div>
</div> </div>
</div> </div>
<div class="skill-category"> <!-- Storage -->
<h3 class="skill-cat-title">Storage</h3> <div class="tech-card">
<div class="skill-tags"> <div class="tech-header">
<span class="skill-tag">TrueNAS SCALE</span> <div class="tech-icon" style="--icon-bg: rgba(139, 92, 246, 0.15); --icon-color: #8b5cf6;">
<span class="skill-tag">ZFS</span> <svg viewBox="0 0 24 24" fill="currentColor"><path d="M2 20h20v-4H2v4zm2-3h2v2H4v-2zM2 4v4h20V4H2zm4 3H4V5h2v2zm-4 7h20v-4H2v4zm2-3h2v2H4v-2z"/></svg>
<span class="skill-tag">NFS</span> </div>
<span class="skill-tag">Samba</span> <h3 class="tech-title">Storage</h3>
</div>
<div class="tech-items">
<div class="tech-item"><span class="dot" style="--dot-color: #8b5cf6;"></span>TrueNAS</div>
<div class="tech-item"><span class="dot" style="--dot-color: #8b5cf6;"></span>ZFS</div>
<div class="tech-item"><span class="dot" style="--dot-color: #8b5cf6;"></span>NFS</div>
<div class="tech-item"><span class="dot" style="--dot-color: #8b5cf6;"></span>Samba</div>
</div> </div>
</div> </div>
<div class="skill-category"> <!-- Monitoring -->
<h3 class="skill-cat-title">Monitoring</h3> <div class="tech-card">
<div class="skill-tags"> <div class="tech-header">
<span class="skill-tag">Netdata</span> <div class="tech-icon" style="--icon-bg: rgba(34, 197, 94, 0.15); --icon-color: #22c55e;">
<span class="skill-tag">Grafana</span> <svg viewBox="0 0 24 24" fill="currentColor"><path d="M3 3v18h18v-2H5V3H3zm11 12.59l-4-4-5 5L4 16l5-5 4 4 6.5-6.5 1.5 1.5-8 8z"/></svg>
<span class="skill-tag">Prometheus</span> </div>
<span class="skill-tag">Uptime Kuma</span> <h3 class="tech-title">Monitoring</h3>
</div>
<div class="tech-items">
<div class="tech-item"><span class="dot" style="--dot-color: #22c55e;"></span>Netdata</div>
<div class="tech-item"><span class="dot" style="--dot-color: #22c55e;"></span>Grafana</div>
<div class="tech-item"><span class="dot" style="--dot-color: #22c55e;"></span>Prometheus</div>
<div class="tech-item"><span class="dot" style="--dot-color: #22c55e;"></span>Uptime Kuma</div>
</div> </div>
</div> </div>
<div class="skill-category"> <!-- Development -->
<h3 class="skill-cat-title">Development</h3> <div class="tech-card">
<div class="skill-tags"> <div class="tech-header">
<span class="skill-tag">Git/Gitea</span> <div class="tech-icon" style="--icon-bg: rgba(245, 158, 11, 0.15); --icon-color: #f59e0b;">
<span class="skill-tag">CI/CD</span> <svg viewBox="0 0 24 24" fill="currentColor"><path d="M9.4 16.6L4.8 12l4.6-4.6L8 6l-6 6 6 6 1.4-1.4zm5.2 0l4.6-4.6-4.6-4.6L16 6l6 6-6 6-1.4-1.4z"/></svg>
<span class="skill-tag">Bash</span> </div>
<span class="skill-tag">Python</span> <h3 class="tech-title">Development</h3>
</div>
<div class="tech-items">
<div class="tech-item"><span class="dot" style="--dot-color: #f59e0b;"></span>Git/Gitea</div>
<div class="tech-item"><span class="dot" style="--dot-color: #f59e0b;"></span>CI/CD</div>
<div class="tech-item"><span class="dot" style="--dot-color: #f59e0b;"></span>Bash</div>
<div class="tech-item"><span class="dot" style="--dot-color: #f59e0b;"></span>Python</div>
</div> </div>
</div> </div>
</div> </div>

128
script.js
View File

@ -334,108 +334,48 @@ function initTerminalTyping() {
setInterval(typeCommand, 8000); setInterval(typeCommand, 8000);
} }
// Initialize terminal typing when contact section is visible - limited to 3 cycles // Simple Terminal Animation - Pre-defined content, no expanding
const terminalObserver = new IntersectionObserver((entries) => { document.addEventListener('DOMContentLoaded', () => {
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() {
const terminal = document.querySelector('.terminal-body'); const terminal = document.querySelector('.terminal-body');
if (!terminal) return; if (!terminal) return;
const commands = [ // Pre-defined terminal content (no dynamic expansion)
{ text: 'docker ps', output: 'CONTAINER ID IMAGE STATUS\nabc123 nginx Up 3 days\ndef456 nextcloud Up 5 days' }, const terminalContent = `
{ text: 'uptime', output: 'Load: 1.06, 1.06, 1.14 | RAM: 18% of 96GB' }, <div class="terminal-line">
{ text: 'tailscale status', output: '100.x.x.x compute-01 linux -' } <span class="prompt"></span>
]; <span class="path">~</span>
<span class="command">whoami</span>
let currentCommand = 0; </div>
let isTyping = false; <div class="terminal-output">lemon_admin</div>
<div class="terminal-line">
const typeCommand = () => { <span class="prompt"></span>
if (isTyping) return; <span class="path">~</span>
isTyping = true; <span class="command">uptime</span>
</div>
const cmd = commands[currentCommand]; <div class="terminal-output">Load: 1.06 | RAM: 18% of 96GB | 50+ containers</div>
const lines = terminal.querySelectorAll('.terminal-line'); <div class="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 = `
<span class="prompt"></span> <span class="prompt"></span>
<span class="path">~</span> <span class="path">~</span>
<span class="command"></span>
<span class="cursor">|</span> <span class="cursor">|</span>
`; </div>
terminal.appendChild(newLine); `;
const commandSpan = newLine.querySelector('.command'); // Animate each line appearing
let charIndex = 0; const lines = terminalContent.trim().split('</div>').filter(l => l.trim());
terminal.innerHTML = '';
const typeChar = () => { let delay = 0;
if (charIndex < cmd.text.length) { lines.forEach((line, index) => {
commandSpan.textContent += cmd.text[charIndex]; const cleanLine = line + '</div>';
charIndex++; setTimeout(() => {
setTimeout(typeChar, 50 + Math.random() * 50); const div = document.createElement('div');
} else { div.innerHTML = cleanLine;
setTimeout(() => { terminal.appendChild(div.firstElementChild);
const outputDiv = document.createElement('div'); terminal.scrollTop = terminal.scrollHeight;
outputDiv.className = 'terminal-output'; }, delay);
outputDiv.innerHTML = cmd.output.replace(/\n/g, '<br>'); delay += 600;
terminal.appendChild(outputDiv); });
});
const nextLine = document.createElement('div');
nextLine.className = 'terminal-line';
nextLine.innerHTML = `
<span class="prompt"></span>
<span class="path">~</span>
<span class="cursor">|</span>
`;
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);
}
/** /**
* Network connection lines animation * Network connection lines animation

View File

@ -1855,3 +1855,105 @@ body {
grid-template-columns: 1fr; 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;
}
}