Redesign skills section with nice tech cards, simplify terminal animation
This commit is contained in:
parent
22b65311cc
commit
890665e4d7
124
index.html
124
index.html
|
|
@ -521,67 +521,103 @@
|
|||
<div class="section-header">
|
||||
<span class="section-badge">🛠️ Toolbox</span>
|
||||
<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 class="skills-grid">
|
||||
<div class="skill-category">
|
||||
<h3 class="skill-cat-title">Virtualization</h3>
|
||||
<div class="skill-tags">
|
||||
<span class="skill-tag">Proxmox VE</span>
|
||||
<span class="skill-tag">KVM/QEMU</span>
|
||||
<span class="skill-tag">LXC Containers</span>
|
||||
<span class="skill-tag">VMware</span>
|
||||
<div class="tech-grid">
|
||||
<!-- Virtualization -->
|
||||
<div class="tech-card">
|
||||
<div class="tech-header">
|
||||
<div class="tech-icon" style="--icon-bg: rgba(229, 126, 32, 0.15); --icon-color: #e57e20;">
|
||||
<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>
|
||||
</div>
|
||||
<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 class="skill-category">
|
||||
<h3 class="skill-cat-title">Containerization</h3>
|
||||
<div class="skill-tags">
|
||||
<span class="skill-tag">Docker</span>
|
||||
<span class="skill-tag">Docker Compose</span>
|
||||
<span class="skill-tag">Portainer</span>
|
||||
<span class="skill-tag">Kubernetes</span>
|
||||
<!-- Containerization -->
|
||||
<div class="tech-card">
|
||||
<div class="tech-header">
|
||||
<div class="tech-icon" style="--icon-bg: rgba(36, 150, 237, 0.15); --icon-color: #2496ed;">
|
||||
<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>
|
||||
</div>
|
||||
<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 class="skill-category">
|
||||
<h3 class="skill-cat-title">Networking</h3>
|
||||
<div class="skill-tags">
|
||||
<span class="skill-tag">Nginx Proxy Manager</span>
|
||||
<span class="skill-tag">Tailscale</span>
|
||||
<span class="skill-tag">VLANs</span>
|
||||
<span class="skill-tag">WireGuard</span>
|
||||
<!-- Networking -->
|
||||
<div class="tech-card">
|
||||
<div class="tech-header">
|
||||
<div class="tech-icon" style="--icon-bg: rgba(34, 211, 238, 0.15); --icon-color: #22d3ee;">
|
||||
<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>
|
||||
</div>
|
||||
<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 class="skill-category">
|
||||
<h3 class="skill-cat-title">Storage</h3>
|
||||
<div class="skill-tags">
|
||||
<span class="skill-tag">TrueNAS SCALE</span>
|
||||
<span class="skill-tag">ZFS</span>
|
||||
<span class="skill-tag">NFS</span>
|
||||
<span class="skill-tag">Samba</span>
|
||||
<!-- Storage -->
|
||||
<div class="tech-card">
|
||||
<div class="tech-header">
|
||||
<div class="tech-icon" style="--icon-bg: rgba(139, 92, 246, 0.15); --icon-color: #8b5cf6;">
|
||||
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M2 20h20v-4H2v4zm2-3h2v2H4v-2zM2 4v4h20V4H2zm4 3H4V5h2v2zm-4 7h20v-4H2v4zm2-3h2v2H4v-2z"/></svg>
|
||||
</div>
|
||||
<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 class="skill-category">
|
||||
<h3 class="skill-cat-title">Monitoring</h3>
|
||||
<div class="skill-tags">
|
||||
<span class="skill-tag">Netdata</span>
|
||||
<span class="skill-tag">Grafana</span>
|
||||
<span class="skill-tag">Prometheus</span>
|
||||
<span class="skill-tag">Uptime Kuma</span>
|
||||
<!-- Monitoring -->
|
||||
<div class="tech-card">
|
||||
<div class="tech-header">
|
||||
<div class="tech-icon" style="--icon-bg: rgba(34, 197, 94, 0.15); --icon-color: #22c55e;">
|
||||
<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>
|
||||
</div>
|
||||
<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 class="skill-category">
|
||||
<h3 class="skill-cat-title">Development</h3>
|
||||
<div class="skill-tags">
|
||||
<span class="skill-tag">Git/Gitea</span>
|
||||
<span class="skill-tag">CI/CD</span>
|
||||
<span class="skill-tag">Bash</span>
|
||||
<span class="skill-tag">Python</span>
|
||||
<!-- Development -->
|
||||
<div class="tech-card">
|
||||
<div class="tech-header">
|
||||
<div class="tech-icon" style="--icon-bg: rgba(245, 158, 11, 0.15); --icon-color: #f59e0b;">
|
||||
<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>
|
||||
</div>
|
||||
<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>
|
||||
|
|
|
|||
132
script.js
132
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 = `
|
||||
<div class="terminal-line">
|
||||
<span class="prompt">➜</span>
|
||||
<span class="path">~</span>
|
||||
<span class="command">whoami</span>
|
||||
</div>
|
||||
<div class="terminal-output">lemon_admin</div>
|
||||
<div class="terminal-line">
|
||||
<span class="prompt">➜</span>
|
||||
<span class="path">~</span>
|
||||
<span class="command">uptime</span>
|
||||
</div>
|
||||
<div class="terminal-output">Load: 1.06 | RAM: 18% of 96GB | 50+ containers</div>
|
||||
<div class="terminal-line">
|
||||
<span class="prompt">➜</span>
|
||||
<span class="path">~</span>
|
||||
<span class="command"></span>
|
||||
<span class="cursor">|</span>
|
||||
`;
|
||||
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, '<br>');
|
||||
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;
|
||||
</div>
|
||||
`;
|
||||
|
||||
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('</div>').filter(l => l.trim());
|
||||
terminal.innerHTML = '';
|
||||
|
||||
let delay = 0;
|
||||
lines.forEach((line, index) => {
|
||||
const cleanLine = line + '</div>';
|
||||
setTimeout(() => {
|
||||
const div = document.createElement('div');
|
||||
div.innerHTML = cleanLine;
|
||||
terminal.appendChild(div.firstElementChild);
|
||||
terminal.scrollTop = terminal.scrollHeight;
|
||||
}, delay);
|
||||
delay += 600;
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Network connection lines animation
|
||||
|
|
|
|||
102
styles.css
102
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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue