967 lines
43 KiB
Plaintext
967 lines
43 KiB
Plaintext
align-items: center;
|
|
justify-content: center;
|
|
text-align: center;
|
|
position: relative;
|
|
overflow: hidden;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.diagram-node:hover {
|
|
transform: translateY(-5px);
|
|
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.2);
|
|
z-index: 10;
|
|
border-color: var(--primary, #3b82f6);
|
|
}
|
|
|
|
.diagram-node::before {
|
|
content: '';
|
|
position: absolute;
|
|
width: 150%;
|
|
height: 150%;
|
|
background: radial-gradient(circle, rgba(99, 102, 241, 0.3) 0%, transparent 70%);
|
|
opacity: 0;
|
|
transition: opacity 0.3s ease;
|
|
}
|
|
|
|
.diagram-node:hover::before {
|
|
opacity: 1;
|
|
}
|
|
|
|
.diagram-node.gateway {
|
|
grid-column: 1 / 2;
|
|
grid-row: 1 / 2;
|
|
border-color: var(--warning, #f59e0b);
|
|
}
|
|
|
|
.diagram-node.firewall {
|
|
grid-column: 2 / 3;
|
|
grid-row: 1 / 2;
|
|
border-color: var(--error, #ef4444);
|
|
}
|
|
|
|
.diagram-node.proxmox {
|
|
grid-column: 3 / 5;
|
|
grid-row: 1 / 2;
|
|
border-color: var(--primary, #3b82f6);
|
|
}
|
|
|
|
.diagram-node.kubernetes {
|
|
grid-column: 2 / 4;
|
|
grid-row: 2 / 3;
|
|
border-color: var(--accent, #6366f1);
|
|
}
|
|
|
|
.diagram-node.storage {
|
|
grid-column: 1 / 2;
|
|
grid-row: 2 / 3;
|
|
border-color: var(--success, #10b981);
|
|
}
|
|
|
|
.diagram-node.monitoring {
|
|
grid-column: 4 / 5;
|
|
grid-row: 2 / 3;
|
|
border-color: var(--primary, #3b82f6);
|
|
}
|
|
|
|
.diagram-node.services {
|
|
grid-column: 1 / 5;
|
|
grid-row: 3 / 4;
|
|
display: flex;
|
|
flex-direction: row;
|
|
justify-content: space-around;
|
|
flex-wrap: wrap;
|
|
background-color: rgba(15, 23, 42, 0.3);
|
|
}
|
|
|
|
.service-icon {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
flex-direction: column;
|
|
width: 80px;
|
|
height: 80px;
|
|
margin: 0.5rem;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.service-icon:hover {
|
|
transform: scale(1.1);
|
|
}
|
|
|
|
.service-icon i {
|
|
font-size: 1.75rem;
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
.service-icon span {
|
|
font-size: 0.75rem;
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
/* Connection lines for architecture diagram */
|
|
.connection-line {
|
|
position: absolute;
|
|
background-color: var(--border, rgba(148, 163, 184, 0.1));
|
|
transition: all 0.3s ease;
|
|
z-index: 5;
|
|
}
|
|
|
|
.connection-line.horizontal {
|
|
height: 2px;
|
|
}
|
|
|
|
.connection-line.vertical {
|
|
width: 2px;
|
|
}
|
|
|
|
.connection-line.active {
|
|
background-color: var(--accent, #6366f1);
|
|
box-shadow: 0 0 10px var(--accent, #6366f1);
|
|
}
|
|
|
|
/* Enhanced Service Items */
|
|
.service-item {
|
|
position: relative;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.service-item::before {
|
|
content: '';
|
|
position: absolute;
|
|
left: 0;
|
|
top: 0;
|
|
height: 100%;
|
|
width: 0;
|
|
background: linear-gradient(90deg, var(--accent, #6366f1) 0%, transparent 100%);
|
|
opacity: 0.1;
|
|
transition: width 0.3s ease;
|
|
}
|
|
|
|
.service-item.available:hover::before {
|
|
width: 100%;
|
|
}
|
|
|
|
/* Enhanced Card Hover Effects */
|
|
.tech-card {
|
|
position: relative;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.tech-card::after {
|
|
content: '';
|
|
position: absolute;
|
|
bottom: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 3px;
|
|
background: linear-gradient(90deg, var(--primary, #3b82f6), var(--accent, #6366f1));
|
|
transform: scaleX(0);
|
|
transform-origin: right;
|
|
transition: transform 0.5s ease;
|
|
}
|
|
|
|
.tech-card:hover::after {
|
|
transform: scaleX(1);
|
|
transform-origin: left;
|
|
}
|
|
|
|
/* Enhanced Project Cards */
|
|
.project-card {
|
|
position: relative;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.project-card::before {
|
|
content: '';
|
|
position: absolute;
|
|
top: -50px;
|
|
right: -50px;
|
|
width: 100px;
|
|
height: 100px;
|
|
background: linear-gradient(45deg, var(--primary, #3b82f6), var(--accent, #6366f1));
|
|
transform: rotate(45deg);
|
|
opacity: 0;
|
|
transition: all 0.5s ease;
|
|
}
|
|
|
|
.project-card:hover::before {
|
|
opacity: 0.15;
|
|
}
|
|
|
|
/* Enhanced Dashboard Cards */
|
|
.dashboard-preview.infrastructure {
|
|
background-image: linear-gradient(45deg, rgba(30, 41, 59, 0.7), rgba(15, 23, 42, 0.7)), url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNDAgMTIwIj48cmVjdCB4PSIxMCIgeT0iMTAiIHdpZHRoPSI1MCIgaGVpZ2h0PSIxMDAiIGZpbGw9IiMyNTYzZWIiIGZpbGwtb3BhY2l0eT0iMC4yIiByeD0iNSIvPjxyZWN0IHg9IjgwIiB5PSIxMCIgd2lkdGg9IjUwIiBoZWlnaHQ9IjEwMCIgZmlsbD0iIzI1NjNlYiIgZmlsbC1vcGFjaXR5PSIwLjMiIHJ4PSI1Ii8+PHJlY3QgeD0iMTUwIiB5PSIxMCIgd2lkdGg9IjUwIiBoZWlnaHQ9IjEwMCIgZmlsbD0iIzI1NjNlYiIgZmlsbC1vcGFjaXR5PSIwLjQiIHJ4PSI1Ii8+PC9zdmc+');
|
|
}
|
|
|
|
.dashboard-preview.kubernetes {
|
|
background-image: linear-gradient(45deg, rgba(30, 41, 59, 0.7), rgba(15, 23, 42, 0.7)), url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNDAgMTIwIj48Y2lyY2xlIGN4PSIxMjAiIGN5PSI2MCIgcj0iNDAiIGZpbGw9IiM2MzY2ZjEiIGZpbGwtb3BhY2l0eT0iMC4yIi8+PGNpcmNsZSBjeD0iNTAiIGN5PSI0MCIgcj0iMjAiIGZpbGw9IiM2MzY2ZjEiIGZpbGwtb3BhY2l0eT0iMC4zIi8+PGNpcmNsZSBjeD0iNTAiIGN5PSI5MCIgcj0iMjAiIGZpbGw9IiM2MzY2ZjEiIGZpbGwtb3BhY2l0eT0iMC4zIi8+PGNpcmNsZSBjeD0iMTkwIiBjeT0iNDAiIHI9IjIwIiBmaWxsPSIjNjM2NmYxIiBmaWxsLW9wYWNpdHk9IjAuMyIvPjxjaXJjbGUgY3g9IjE5MCIgY3k9IjkwIiByPSIyMCIgZmlsbD0iIzYzNjZmMSIgZmlsbC1vcGFjaXR5PSIwLjMiLz48bGluZSB4MT0iNTAiIHkxPSI0MCIgeDI9IjEyMCIgeTI9IjYwIiBzdHJva2U9IiM2MzY2ZjEiIHN0cm9rZS1vcGFjaXR5PSIwLjQiIHN0cm9rZS13aWR0aD0iMiIvPjxsaW5lIHgxPSI1MCIgeTE9IjkwIiB4Mj0iMTIwIiB5Mj0iNjAiIHN0cm9rZT0iIzYzNjZmMSIgc3Ryb2tlLW9wYWNpdHk9IjAuNCIgc3Ryb2tlLXdpZHRoPSIyIi8+PGxpbmUgeDE9IjE5MCIgeTE9IjQwIiB4Mj0iMTIwIiB5Mj0iNjAiIHN0cm9rZT0iIzYzNjZmMSIgc3Ryb2tlLW9wYWNpdHk9IjAuNCIgc3Ryb2tlLXdpZHRoPSIyIi8+PGxpbmUgeDE9IjE5MCIgeTE9IjkwIiB4Mj0iMTIwIiB5Mj0iNjAiIHN0cm9rZT0iIzYzNjZmMSIgc3Ryb2tlLW9wYWNpdHk9IjAuNCIgc3Ryb2tlLXdpZHRoPSIyIi8+PC9zdmc+');
|
|
}
|
|
|
|
.dashboard-preview.network {
|
|
background-image: linear-gradient(45deg, rgba(30, 41, 59, 0.7), rgba(15, 23, 42, 0.7)), url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNDAgMTIwIj48cGF0aCBkPSJNMTAgMTAgQzIzMCAxMCAyMzAgNTAgMTAgNTAgQzIzMCA1MCAyMzAgOTAgMTAgOTAgQzIzMCA5MCAyMzAgMTMwIDEwIDEzMCIgc3Ryb2tlPSIjMTBiOTgxIiBzdHJva2Utb3BhY2l0eT0iMC40IiBzdHJva2Utd2lkdGg9IjIiIGZpbGw9Im5vbmUiLz48L3N2Zz4=');
|
|
}
|
|
|
|
.dashboard-preview.services {
|
|
background-image: linear-gradient(45deg, rgba(30, 41, 59, 0.7), rgba(15, 23, 42, 0.7)), url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNDAgMTIwIj48cmVjdCB4PSIxMCIgeT0iMTAiIHdpZHRoPSI0MCIgaGVpZ2h0PSIyMCIgZmlsbD0iI2VmNDQ0NCIgZmlsbC1vcGFjaXR5PSIwLjIiIHJ4PSI1Ii8+PHJlY3QgeD0iMTAiIHk9IjQwIiB3aWR0aD0iNzAiIGhlaWdodD0iMjAiIGZpbGw9IiNlZjQ0NDQiIGZpbGwtb3BhY2l0eT0iMC4zIiByeD0iNSIvPjxyZWN0IHg9IjEwIiB5PSI3MCIgd2lkdGg9IjMwIiBoZWlnaHQ9IjIwIiBmaWxsPSIjZWY0NDQ0IiBmaWxsLW9wYWNpdHk9IjAuNCIgcng9IjUiLz48cmVjdCB4PSIxMDAiIHk9IjEwIiB3aWR0aD0iODAiIGhlaWdodD0iMjAiIGZpbGw9IiM2MzY2ZjEiIGZpbGwtb3BhY2l0eT0iMC4yIiByeD0iNSIvPjxyZWN0IHg9IjEwMCIgeT0iNDAiIHdpZHRoPSI1MCIgaGVpZ2h0PSIyMCIgZmlsbD0iIzYzNjZmMSIgZmlsbC1vcGFjaXR5PSIwLjMiIHJ4PSI1Ii8+PHJlY3QgeD0iMTAwIiB5PSI3MCIgd2lkdGg9IjEyMCIgaGVpZ2h0PSIyMCIgZmlsbD0iIzYzNjZmMSIgZmlsbC1vcGFjaXR5PSIwLjQiIHJ4PSI1Ii8+PC9zdmc+');
|
|
}
|
|
|
|
/* Enhanced Dashboard Overlay */
|
|
.dashboard-overlay .overlay-icon {
|
|
transition: transform 0.3s ease;
|
|
}
|
|
|
|
.dashboard-card:hover .overlay-icon {
|
|
transform: scale(1.2);
|
|
}
|
|
|
|
/* Keep your existing responsive styles */
|
|
@media (max-width: 1024px) {
|
|
.architecture-diagram {
|
|
grid-template-columns: repeat(2, 1fr);
|
|
grid-template-rows: repeat(6, auto);
|
|
gap: 1rem;
|
|
}
|
|
|
|
.diagram-node.gateway { grid-column: 1 / 2; grid-row: 1 / 2; }
|
|
.diagram-node.firewall { grid-column: 2 / 3; grid-row: 1 / 2; }
|
|
.diagram-node.proxmox { grid-column: 1 / 3; grid-row: 2 / 3; }
|
|
.diagram-node.kubernetes { grid-column: 1 / 3; grid-row: 3 / 4; }
|
|
.diagram-node.storage { grid-column: 1 / 2; grid-row: 4 / 5; }
|
|
.diagram-node.monitoring { grid-column: 2 / 3; grid-row: 4 / 5; }
|
|
.diagram-node.services { grid-column: 1 / 3; grid-row: 5 / 6; }
|
|
}
|
|
</style>
|
|
|
|
<script>
|
|
// --- Enhanced Terminal Animation ---
|
|
function animateTerminal() {
|
|
const terminalBody = document.getElementById('animated-terminal');
|
|
if (!terminalBody) return;
|
|
|
|
const lines = terminalBody.querySelectorAll('.terminal-line');
|
|
let delay = 100;
|
|
|
|
lines.forEach((line, index) => {
|
|
setTimeout(() => {
|
|
line.classList.add('visible');
|
|
}, delay * index);
|
|
});
|
|
}
|
|
|
|
// --- Terminal Cursor Animation ---
|
|
function initTerminalTyping() {
|
|
const cursor = document.querySelector('.cursor');
|
|
if (!cursor) return;
|
|
|
|
setInterval(() => {
|
|
cursor.style.opacity = cursor.style.opacity === '0' ? '1' : '0';
|
|
}, 600);
|
|
}
|
|
|
|
// --- Particle Animation ---
|
|
function createBackgroundParticles(count = 30) {
|
|
const particlesContainer = document.getElementById('particles-container');
|
|
if (!particlesContainer) return;
|
|
particlesContainer.innerHTML = '';
|
|
|
|
for (let i = 0; i < count; i++) {
|
|
const particle = document.createElement('div');
|
|
particle.classList.add('particle');
|
|
const size = Math.random() * 3 + 1;
|
|
particle.style.width = `${size}px`;
|
|
particle.style.height = `${size}px`;
|
|
particle.style.left = `${Math.random() * 100}%`;
|
|
particle.style.top = `${Math.random() * 100}%`;
|
|
particle.style.opacity = (Math.random() * 0.2 + 0.05).toString();
|
|
particle.style.animation = `float-particle ${Math.random() * 25 + 15}s linear infinite`;
|
|
particle.style.animationDelay = `${Math.random() * 15}s`;
|
|
particlesContainer.appendChild(particle);
|
|
}
|
|
}
|
|
|
|
// --- Architecture Diagram Connections ---
|
|
function createArchitectureDiagramConnections() {
|
|
const diagram = document.querySelector('.architecture-diagram');
|
|
if (!diagram) return;
|
|
|
|
// Define connections between nodes
|
|
const connections = [
|
|
{ from: 'gateway', to: 'firewall', type: 'horizontal' },
|
|
{ from: 'firewall', to: 'proxmox', type: 'horizontal' },
|
|
{ from: 'firewall', to: 'kubernetes', type: 'vertical' },
|
|
{ from: 'proxmox', to: 'kubernetes', type: 'vertical' },
|
|
{ from: 'storage', to: 'kubernetes', type: 'horizontal' },
|
|
{ from: 'kubernetes', to: 'monitoring', type: 'horizontal' },
|
|
{ from: 'kubernetes', to: 'services', type: 'vertical' }
|
|
];
|
|
|
|
// Create connection lines
|
|
connections.forEach((connection, index) => {
|
|
const fromNode = diagram.querySelector(`[data-node="${connection.from}"]`);
|
|
const toNode = diagram.querySelector(`[data-node="${connection.to}"]`);
|
|
|
|
if (!fromNode || !toNode) return;
|
|
|
|
const fromRect = fromNode.getBoundingClientRect();
|
|
const toRect = toNode.getBoundingClientRect();
|
|
const diagramRect = diagram.getBoundingClientRect();
|
|
|
|
const line = document.createElement('div');
|
|
line.classList.add('connection-line', connection.type);
|
|
line.id = `connection-${index}`;
|
|
|
|
if (connection.type === 'horizontal') {
|
|
const fromCenter = fromRect.left + fromRect.width / 2 - diagramRect.left;
|
|
const toCenter = toRect.left + toRect.width / 2 - diagramRect.left;
|
|
const top = (fromRect.top + fromRect.height / 2 - diagramRect.top);
|
|
|
|
line.style.top = `${top}px`;
|
|
line.style.left = `${Math.min(fromCenter, toCenter)}px`;
|
|
line.style.width = `${Math.abs(toCenter - fromCenter)}px`;
|
|
} else { // vertical
|
|
const fromCenter = fromRect.top + fromRect.height / 2 - diagramRect.top;
|
|
const toCenter = toRect.top + toRect.height / 2 - diagramRect.top;
|
|
const left = (fromRect.left + fromRect.width / 2 - diagramRect.left);
|
|
|
|
line.style.left = `${left}px`;
|
|
line.style.top = `${Math.min(fromCenter, toCenter)}px`;
|
|
line.style.height = `${Math.abs(toCenter - fromCenter)}px`;
|
|
}
|
|
|
|
diagram.appendChild(line);
|
|
});
|
|
}
|
|
|
|
// --- Animate Active Connections ---
|
|
function simulateActiveConnections() {
|
|
const lines = document.querySelectorAll('.connection-line');
|
|
if (lines.length === 0) return;
|
|
|
|
// Randomly activate connections
|
|
setInterval(() => {
|
|
const randomIndex = Math.floor(Math.random() * lines.length);
|
|
const line = lines[randomIndex];
|
|
|
|
line.classList.add('active');
|
|
setTimeout(() => {
|
|
line.classList.remove('active');
|
|
}, 1000);
|
|
}, 2000);
|
|
}
|
|
|
|
// --- Check Component Status ---
|
|
function checkAnsibleSandboxStatus() {
|
|
const btn = document.getElementById('ansible-sandbox-btn');
|
|
const badge = btn?.querySelector('.offline-badge');
|
|
if (!btn || !badge) return;
|
|
|
|
// This is a placeholder - replace with actual API call
|
|
const isOnline = false; // Simulate offline for now
|
|
|
|
if (isOnline) {
|
|
btn.classList.remove('btn-danger');
|
|
btn.classList.add('btn-primary');
|
|
badge.textContent = 'Online';
|
|
badge.classList.remove('offline-badge');
|
|
badge.classList.add('online-badge');
|
|
badge.style.display = 'inline-block';
|
|
}
|
|
}
|
|
|
|
// --- Intersection Observer for Scroll Animations ---
|
|
function setupScrollAnimations() {
|
|
const sections = document.querySelectorAll('.section-padding');
|
|
|
|
const observer = new IntersectionObserver((entries) => {
|
|
entries.forEach(entry => {
|
|
if (entry.isIntersecting) {
|
|
entry.target.classList.add('section-visible');
|
|
observer.unobserve(entry.target);
|
|
}
|
|
});
|
|
}, { threshold: 0.1 });
|
|
|
|
sections.forEach(section => {
|
|
observer.observe(section);
|
|
});
|
|
}
|
|
|
|
// --- Initialize everything when DOM is loaded ---
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// Create particle effect
|
|
createBackgroundParticles();
|
|
|
|
// Animate terminal on load
|
|
setTimeout(animateTerminal, 500);
|
|
initTerminalTyping();
|
|
|
|
// Set up architecture diagram
|
|
setTimeout(() => {
|
|
try {
|
|
createArchitectureDiagramConnections();
|
|
simulateActiveConnections();
|
|
} catch (error) {
|
|
console.log('Architecture diagram not available:', error);
|
|
document.querySelector('.architecture-fallback')?.style.display = 'block';
|
|
document.querySelector('.architecture-diagram-container')?.style.display = 'none';
|
|
}
|
|
}, 500);
|
|
|
|
// Check component status
|
|
checkAnsibleSandboxStatus();
|
|
|
|
// Set up scroll animations
|
|
setupScrollAnimations();
|
|
|
|
// Add smooth scrolling for anchor links
|
|
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
|
|
anchor.addEventListener('click', function(e) {
|
|
e.preventDefault();
|
|
const targetId = this.getAttribute('href');
|
|
const targetElement = document.querySelector(targetId);
|
|
|
|
if (targetElement) {
|
|
window.scrollTo({
|
|
top: targetElement.offsetTop - 80,
|
|
behavior: 'smooth'
|
|
});
|
|
}
|
|
});
|
|
});
|
|
});
|
|
|
|
// --- Handle window resize ---
|
|
window.addEventListener('resize', function() {
|
|
// Clear existing connections and recreate them
|
|
document.querySelectorAll('.connection-line').forEach(line => line.remove());
|
|
|
|
setTimeout(() => {
|
|
createArchitectureDiagramConnections();
|
|
}, 200);
|
|
});
|
|
</script>
|
|
---
|
|
// src/pages/homelab.astro - Enhanced version
|
|
import BaseLayout from '../layouts/BaseLayout.astro';
|
|
import Header from '../components/Header.astro';
|
|
import Footer from '../components/Footer.astro';
|
|
|
|
const title = "Home Lab | ArgoBox - ArgoBox Tech Hub";
|
|
const description = "ArgoBox - A production-grade Kubernetes homelab for DevOps experimentation, infrastructure automation, and containerized application deployment.";
|
|
|
|
// Using your existing data structure
|
|
const servicesData = {
|
|
development: [
|
|
{ name: "Gitea", description: "Self-hosted Git service", url: "https://git.argobox.com", status: "live", icon: "fas fa-code-branch", available: true },
|
|
{ name: "VS Code Server", description: "Remote development environment", url: "https://code.argobox.com", status: "live", icon: "fas fa-terminal", available: true },
|
|
{ name: "Drone CI", description: "Continuous Integration server", url: "https://drone.argobox.com", status: "live", icon: "fas fa-cogs", available: true },
|
|
],
|
|
media: [
|
|
{ name: "Plex", description: "Media streaming server", url: "https://plex.argobox.com", status: "live", icon: "fas fa-play-circle", available: true },
|
|
{ name: "Jellyfin", description: "Open source media system", url: "https://jellyfin.argobox.com", status: "live", icon: "fas fa-film", available: true },
|
|
{ name: "Sonarr", description: "TV show management", url: "#", status: "restricted", icon: "fas fa-tv", available: false },
|
|
{ name: "Radarr", description: "Movie management", url: "#", status: "restricted", icon: "fas fa-video", available: false },
|
|
{ name: "Prowlarr", description: "Indexer management", url: "#", status: "restricted", icon: "fas fa-search", available: false },
|
|
],
|
|
utilities: [
|
|
{ name: "File Browser", description: "Web file manager", url: "https://files.argobox.com", status: "live", icon: "fas fa-folder-open", available: true },
|
|
{ name: "Vaultwarden", description: "Password manager", url: "#", status: "restricted", icon: "fas fa-key", available: false },
|
|
{ name: "Homepage", description: "Service dashboard", url: "https://dash.argobox.com", status: "live", icon: "fas fa-tachometer-alt", available: true },
|
|
{ name: "Uptime Kuma", description: "Status monitoring", url: "https://status.argobox.com", status: "live", icon: "fas fa-heartbeat", available: true },
|
|
],
|
|
infrastructure: [
|
|
{ name: "Proxmox VE", description: "Virtualization platform", url: "#", status: "restricted", icon: "fas fa-server", available: false },
|
|
{ name: "Kubernetes (K3s)", description: "Container orchestration", url: "#", status: "restricted", icon: "fas fa-dharmachakra", available: false },
|
|
{ name: "Traefik", description: "Ingress controller", url: "#", status: "restricted", icon: "fas fa-route", available: false },
|
|
{ name: "OPNsense", description: "Firewall/Router", url: "#", status: "restricted", icon: "fas fa-shield-alt", available: false },
|
|
]
|
|
};
|
|
|
|
// Use your existing project data
|
|
const projectsData = [
|
|
{ title: "Ansible Playbooks", description: "Collection of playbooks for automating system configuration and application deployment.", icon: "fab fa-ansible", tech: ["Ansible", "YAML", "Jinja2"], url: "/resources/iac" },
|
|
{ title: "Kubernetes Manifests", description: "YAML definitions for deploying various applications and services on Kubernetes.", icon: "fas fa-dharmachakra", tech: ["Kubernetes", "YAML", "Helm"], url: "/resources/kubernetes" },
|
|
{ title: "Monitoring Dashboards", description: "Grafana dashboards for visualizing infrastructure and application metrics.", icon: "fas fa-chart-line", tech: ["Grafana", "PromQL", "JSON"], url: "/resources/config-files" },
|
|
{ title: "Cloudflare Tunnel Setup", description: "Securely exposing home lab services to the internet using Cloudflare Tunnels.", icon: "fas fa-cloud", tech: ["Cloudflare", "Networking", "Security"], url: "/posts/cloudflare-tunnel-setup" }
|
|
];
|
|
|
|
// Use your existing dashboards data
|
|
const dashboardsData = [
|
|
{ title: "Infrastructure Overview", description: "Key metrics for Proxmox hosts, network devices, and storage.", previewClass: "infrastructure", url: "https://dash.argobox.com/goto/...", icon: "fas fa-server" },
|
|
{ title: "Kubernetes Cluster", description: "Detailed view of K3s cluster resources, node status, and pod health.", previewClass: "kubernetes", url: "https://dash.argobox.com/goto/...", icon: "fas fa-dharmachakra" },
|
|
{ title: "Network Traffic", description: "Real-time and historical network usage, firewall logs, and connection tracking.", previewClass: "network", url: "https://dash.argobox.com/goto/...", icon: "fas fa-network-wired" },
|
|
{ title: "Service Performance", description: "Application-specific metrics, request latency, and error rates.", previewClass: "services", url: "https://dash.argobox.com/goto/...", icon: "fas fa-cogs" }
|
|
];
|
|
---
|
|
|
|
<BaseLayout {title} {description}>
|
|
<Header slot="header" />
|
|
|
|
<main class="homelab-page">
|
|
<!-- Hero Section (Enhanced) -->
|
|
<section id="home" class="hero">
|
|
<div class="particles-container" id="particles-container"></div>
|
|
<div class="container">
|
|
<div class="hero-content">
|
|
<div class="hero-text">
|
|
<h1 class="hero-title">
|
|
Enterprise-Grade <span class="highlight">Home Lab</span> Environment
|
|
</h1>
|
|
<p class="hero-description">
|
|
A production-ready infrastructure platform for DevOps experimentation, distributed systems, and automating everything with code.
|
|
</p>
|
|
|
|
<!-- Dynamic terminal-style stats display -->
|
|
<div class="terminal-style-stats">
|
|
<div class="stat-line">argobox:~$<span class="cmd"> ls -la /services | wc -l</span></div>
|
|
<div class="stat-line output">{Object.values(servicesData).flat().length}+</div>
|
|
<div class="stat-line">argobox:~$<span class="cmd"> nproc --all</span></div>
|
|
<div class="stat-line output">32+</div>
|
|
<div class="stat-line">argobox:~$<span class="cmd"> free -h | grep Mem | awk '{print $2}'</span></div>
|
|
<div class="stat-line output">64GB</div>
|
|
<div class="stat-line">argobox:~$<span class="cmd"> df -h /data | grep /data | awk '{print $2}'</span></div>
|
|
<div class="stat-line output">12TB</div>
|
|
</div>
|
|
|
|
<div class="cta-buttons">
|
|
<a href="/ansible-sandbox" class="btn btn-danger" id="ansible-sandbox-btn">
|
|
<i class="fab fa-ansible btn-icon"></i>
|
|
<span class="btn-text">Try Ansible Sandbox</span>
|
|
<span class="offline-badge">Offline</span> <!-- Status updated by JS -->
|
|
</a>
|
|
<a href="#architecture" class="btn btn-outline">
|
|
<i class="fas fa-network-wired btn-icon"></i>
|
|
<span class="btn-text">Explore Architecture</span>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Enhanced terminal with animation -->
|
|
<div class="hero-terminal">
|
|
<div class="terminal-header">
|
|
<div class="terminal-buttons">
|
|
<span class="terminal-btn close"></span>
|
|
<span class="terminal-btn minimize"></span>
|
|
<span class="terminal-btn maximize"></span>
|
|
</div>
|
|
<div class="terminal-title">argobox ~ k8s-status</div>
|
|
</div>
|
|
<div class="terminal-body" id="animated-terminal">
|
|
<div class="terminal-line" data-index="1">$ kubectl get nodes</div>
|
|
<div class="terminal-line output" data-index="2">NAME STATUS ROLES AGE VERSION</div>
|
|
<div class="terminal-line output" data-index="3">argobox Ready control-plane,master 154d v1.25.16+k3s1</div>
|
|
<div class="terminal-line output" data-index="4">argobox-lite Ready worker 154d v1.25.16+k3s1</div>
|
|
<div class="terminal-line blank" data-index="5"> </div>
|
|
<div class="terminal-line" data-index="6">$ kubectl get pods -A | grep Running | wc -l</div>
|
|
<div class="terminal-line output" data-index="7">32</div>
|
|
<div class="terminal-line blank" data-index="8"> </div>
|
|
<div class="terminal-line" data-index="9">$ uptime</div>
|
|
<div class="terminal-line output" data-index="10">14:30:25 up 154 days, 23:12, 1 user, load average: 0.22, 0.18, 0.15</div>
|
|
<div class="terminal-line blank" data-index="11"> </div>
|
|
<div class="terminal-line" data-index="12">$ ansible-playbook status.yml</div>
|
|
<div class="terminal-line output" data-index="13">PLAY [Check system status] *******************************************</div>
|
|
<div class="terminal-line output" data-index="14">TASK [Gathering Facts] **********************************************</div>
|
|
<div class="terminal-line output success" data-index="15">ok: [argobox]</div>
|
|
<div class="terminal-line output success" data-index="16">ok: [argobox-lite]</div>
|
|
<div class="terminal-line output" data-index="17">TASK [Check service status] *****************************************</div>
|
|
<div class="terminal-line output success" data-index="18">ok: [argobox]</div>
|
|
<div class="terminal-line output success" data-index="19">ok: [argobox-lite]</div>
|
|
<div class="terminal-line output" data-index="20">PLAY RECAP **********************************************************</div>
|
|
<div class="terminal-line output success" data-index="21">argobox : ok=2 changed=0 unreachable=0 failed=0 skipped=0</div>
|
|
<div class="terminal-line output success" data-index="22">argobox-lite: ok=2 changed=0 unreachable=0 failed=0 skipped=0</div>
|
|
<div class="terminal-line typing" data-index="23">$ <span class="cursor">|</span></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Architecture Section (Enhanced with Interactive Diagram) -->
|
|
<section id="architecture" class="architecture section-padding">
|
|
<div class="container">
|
|
<div class="section-header">
|
|
<h2 class="section-title">Infrastructure Architecture</h2>
|
|
<p class="section-description">
|
|
Enterprise-grade network topology with redundancy, virtualization, and secure segmentation.
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Interactive Architecture Diagram -->
|
|
<div class="architecture-diagram-container" id="arch-diagram">
|
|
<div class="architecture-diagram">
|
|
<div class="diagram-node gateway" data-node="gateway">
|
|
<div class="diagram-node-icon"><i class="fas fa-router"></i></div>
|
|
<div class="diagram-node-title">Internet Gateway</div>
|
|
<div class="diagram-node-detail">Multiple WAN connections with failover</div>
|
|
</div>
|
|
<div class="diagram-node firewall" data-node="firewall">
|
|
<div class="diagram-node-icon"><i class="fas fa-shield-alt"></i></div>
|
|
<div class="diagram-node-title">OPNsense Firewall</div>
|
|
<div class="diagram-node-detail">Advanced security & traffic management</div>
|
|
</div>
|
|
<div class="diagram-node proxmox" data-node="proxmox">
|
|
<div class="diagram-node-icon"><i class="fas fa-server"></i></div>
|
|
<div class="diagram-node-title">Proxmox Cluster</div>
|
|
<div class="diagram-node-detail">Virtualization platform with HA capabilities</div>
|
|
</div>
|
|
<div class="diagram-node storage" data-node="storage">
|
|
<div class="diagram-node-icon"><i class="fas fa-database"></i></div>
|
|
<div class="diagram-node-title">ZFS Storage</div>
|
|
<div class="diagram-node-detail">RAID10 configuration with snapshots</div>
|
|
</div>
|
|
<div class="diagram-node kubernetes" data-node="kubernetes">
|
|
<div class="diagram-node-icon"><i class="fas fa-dharmachakra"></i></div>
|
|
<div class="diagram-node-title">Kubernetes (K3s)</div>
|
|
<div class="diagram-node-detail">Container orchestration across multiple nodes</div>
|
|
</div>
|
|
<div class="diagram-node monitoring" data-node="monitoring">
|
|
<div class="diagram-node-icon"><i class="fas fa-chart-line"></i></div>
|
|
<div class="diagram-node-title">Monitoring Stack</div>
|
|
<div class="diagram-node-detail">Prometheus, Grafana, & AlertManager</div>
|
|
</div>
|
|
<div class="diagram-node services" data-node="services">
|
|
<div class="service-icon"><i class="fas fa-code-branch" style="color: #3b82f6;"></i><span>Git</span></div>
|
|
<div class="service-icon"><i class="fas fa-terminal" style="color: #6366f1;"></i><span>Code</span></div>
|
|
<div class="service-icon"><i class="fas fa-play-circle" style="color: #ec4899;"></i><span>Media</span></div>
|
|
<div class="service-icon"><i class="fas fa-key" style="color: #10b981;"></i><span>Vault</span></div>
|
|
<div class="service-icon"><i class="fas fa-globe" style="color: #f59e0b;"></i><span>Web</span></div>
|
|
<div class="service-icon"><i class="fas fa-project-diagram" style="color: #ef4444;"></i><span>CI/CD</span></div>
|
|
</div>
|
|
<!-- Connection lines will be added by JS -->
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Fallback for clients with JS disabled -->
|
|
<div class="architecture-fallback">
|
|
<img src="/images/homelab/argobox-architecture.svg" alt="ArgoBox Architecture Diagram" class="architecture-diagram-image" />
|
|
</div>
|
|
|
|
<div class="architecture-details">
|
|
<div class="detail-card">
|
|
<div class="detail-icon"><i class="fas fa-shield-alt"></i></div>
|
|
<h3 class="detail-title">Network Security</h3>
|
|
<p class="detail-description">
|
|
Enterprise firewall with network segmentation using VLANs and strict access controls. Redundant routing with automatic failover between OPNsense and OpenWrt.
|
|
</p>
|
|
</div>
|
|
<div class="detail-card">
|
|
<div class="detail-icon"><i class="fas fa-cloud"></i></div>
|
|
<h3 class="detail-title">Virtualization</h3>
|
|
<p class="detail-description">
|
|
Proxmox virtualization platform with ZFS storage pools in RAID10 configuration. Optimized storage pools for VMs and containers with proper resource allocation.
|
|
</p>
|
|
</div>
|
|
<div class="detail-card">
|
|
<div class="detail-icon"><i class="fas fa-route"></i></div>
|
|
<h3 class="detail-title">High Availability</h3>
|
|
<p class="detail-description">
|
|
Full redundancy with failover routing, replicated storage, and resilient services. Automatic service recovery and load balancing across nodes.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Technologies Section -->
|
|
<section id="technologies" class="technologies section-padding alt-bg">
|
|
<div class="container">
|
|
<div class="section-header">
|
|
<h2 class="section-title">Core Technologies</h2>
|
|
<p class="section-description">
|
|
The ArgoBox lab leverages cutting-edge open source technologies to create a powerful, flexible infrastructure.
|
|
</p>
|
|
</div>
|
|
<div class="tech-grid">
|
|
<div class="tech-card">
|
|
<div class="tech-icon"><i class="fas fa-dharmachakra"></i></div>
|
|
<h3 class="tech-title">Kubernetes (K3s)</h3>
|
|
<p class="tech-description">Lightweight Kubernetes distribution running across multiple nodes for container orchestration. Powers all microservices and applications.</p>
|
|
<div class="tech-features"><span class="tech-feature">Multi-node cluster</span><span class="tech-feature">Persistent volumes</span><span class="tech-feature">Traefik ingress</span><span class="tech-feature">Auto-healing</span></div>
|
|
</div>
|
|
<div class="tech-card featured">
|
|
<div class="tech-icon"><i class="fab fa-ansible"></i></div>
|
|
<h3 class="tech-title">Ansible Automation</h3>
|
|
<p class="tech-description">Infrastructure as code platform for automated provisioning, configuration management, and application deployment across the entire environment.</p>
|
|
<div class="tech-features"><span class="tech-feature">Playbook library</span><span class="tech-feature">Role-based configs</span><span class="tech-feature">Interactive sandbox</span><span class="tech-feature">Idempotent workflows</span></div>
|
|
</div>
|
|
<div class="tech-card">
|
|
<div class="tech-icon"><i class="fas fa-server"></i></div>
|
|
<h3 class="tech-title">Proxmox</h3>
|
|
<p class="tech-description">Enterprise-class virtualization platform running virtual machines and containers with ZFS storage backend for data integrity.</p>
|
|
<div class="tech-features"><span class="tech-feature">ZFS storage</span><span class="tech-feature">Resource balancing</span><span class="tech-feature">Live migration</span><span class="tech-feature">Hardware passthrough</span></div>
|
|
</div>
|
|
<div class="tech-card">
|
|
<div class="tech-icon"><i class="fas fa-shield-alt"></i></div>
|
|
<h3 class="tech-title">Zero Trust Security</h3>
|
|
<p class="tech-description">Comprehensive security architecture with Cloudflare tunnels, network segmentation, and authentication at all service boundaries.</p>
|
|
<div class="tech-features"><span class="tech-feature">Cloudflare tunnels</span><span class="tech-feature">OPNsense firewall</span><span class="tech-feature">VLAN segmentation</span><span class="tech-feature">WireGuard VPN</span></div>
|
|
</div>
|
|
<div class="tech-card">
|
|
<div class="tech-icon"><i class="fas fa-database"></i></div>
|
|
<h3 class="tech-title">PostgreSQL</h3>
|
|
<p class="tech-description">Enterprise database cluster for application data storage with automated backups, replication, and performance optimization.</p>
|
|
<div class="tech-features"><span class="tech-feature">Automated backups</span><span class="tech-feature">Connection pooling</span><span class="tech-feature">Optimized for K8s</span><span class="tech-feature">Multi-app support</span></div>
|
|
</div>
|
|
<div class="tech-card">
|
|
<div class="tech-icon"><i class="fas fa-chart-line"></i></div>
|
|
<h3 class="tech-title">Monitoring Stack</h3>
|
|
<p class="tech-description">Comprehensive monitoring with Prometheus, Grafana, and AlertManager for real-time visibility into all infrastructure components.</p>
|
|
<div class="tech-features"><span class="tech-feature">Prometheus metrics</span><span class="tech-feature">Grafana dashboards</span><span class="tech-feature">Automated alerts</span><span class="tech-feature">Historical data</span></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Services Section (Enhanced with Interactive Elements) -->
|
|
<section id="services" class="services section-padding">
|
|
<div class="container">
|
|
<div class="section-header">
|
|
<h2 class="section-title">Available Services</h2>
|
|
<p class="section-description">
|
|
Explore the various services and applications hosted in the ArgoBox environment.
|
|
</p>
|
|
</div>
|
|
<div class="services-info-banner">
|
|
<i class="fas fa-info-circle info-icon"></i>
|
|
<p>Some services require authentication and are restricted. Available public services are highlighted and clickable.</p>
|
|
</div>
|
|
<div class="services-grid">
|
|
{Object.entries(servicesData).map(([categoryKey, categoryServices]) => (
|
|
<div class="services-category" data-category={categoryKey}>
|
|
<h3 class="category-title">
|
|
<i class={`fas ${
|
|
categoryKey === 'development' ? 'fa-code' :
|
|
categoryKey === 'media' ? 'fa-photo-video' :
|
|
categoryKey === 'utilities' ? 'fa-tools' :
|
|
'fa-cogs' // Default for infrastructure
|
|
} category-icon`}></i>
|
|
{categoryKey.charAt(0).toUpperCase() + categoryKey.slice(1)} Tools
|
|
</h3>
|
|
<div class="service-items">
|
|
{categoryServices.map(service => (
|
|
<a
|
|
href={service.available ? service.url : '#'}
|
|
class:list={["service-item", { available: service.available }, `service-${service.name.toLowerCase().replace(/\s+/g, '-')}`]}
|
|
target={service.available ? "_blank" : undefined}
|
|
rel={service.available ? "noopener noreferrer" : undefined}
|
|
aria-disabled={!service.available}
|
|
data-service={service.name.toLowerCase().replace(/\s+/g, '-')}
|
|
>
|
|
<div class="service-icon"><i class={service.icon}></i></div>
|
|
<div class="service-info">
|
|
<div class="service-name">{service.name}</div>
|
|
<p class="service-description">{service.description}</p>
|
|
</div>
|
|
<span class={`service-status ${service.status}`}>
|
|
<span class="status-dot"></span>
|
|
{service.status.charAt(0).toUpperCase() + service.status.slice(1)}
|
|
</span>
|
|
</a>
|
|
))}
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Projects Section -->
|
|
<section id="projects" class="projects section-padding alt-bg">
|
|
<div class="container">
|
|
<div class="section-header">
|
|
<h2 class="section-title">Related Projects & Code</h2>
|
|
<p class="section-description">
|
|
Explore associated projects, configurations, and code repositories related to the ArgoBox lab.
|
|
</p>
|
|
</div>
|
|
<div class="projects-grid">
|
|
{projectsData.map(project => (
|
|
<a href={project.url} class="project-card" target={project.url.startsWith('http') ? '_blank' : undefined} rel={project.url.startsWith('http') ? 'noopener noreferrer' : undefined} data-project={project.title.toLowerCase().replace(/\s+/g, '-')}>
|
|
<div class="project-header">
|
|
<div class="project-icon"><i class={project.icon}></i></div>
|
|
<h3 class="project-title">{project.title}</h3>
|
|
</div>
|
|
<p class="project-description">{project.description}</p>
|
|
<div class="project-tech">
|
|
{project.tech.map(tech => <span class="tech-badge">{tech}</span>)}
|
|
</div>
|
|
<div class="project-cta">
|
|
<span class="btn btn-sm btn-outline">
|
|
{project.url.startsWith('http') ? 'View Project' : 'View Details'} <i class="fas fa-arrow-right"></i>
|
|
</span>
|
|
</div>
|
|
</a>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Dashboards Section (Enhanced) -->
|
|
<section id="dashboards" class="dashboards section-padding">
|
|
<div class="container">
|
|
<div class="section-header">
|
|
<h2 class="section-title">Live Dashboards</h2>
|
|
<p class="section-description">
|
|
Real-time monitoring dashboards providing insights into the lab's performance and status. (Authentication Required)
|
|
</p>
|
|
</div>
|
|
<div class="services-info-banner">
|
|
<i class="fas fa-lock info-icon"></i>
|
|
<p>Access to live dashboards requires authentication via Cloudflare Access.</p>
|
|
</div>
|
|
<div class="dashboard-grid">
|
|
{dashboardsData.map(dash => (
|
|
<a href={dash.url} class="dashboard-card" target="_blank" rel="noopener noreferrer" data-dashboard={dash.title.toLowerCase().replace(/\s+/g, '-')}>
|
|
<div class={`dashboard-preview ${dash.previewClass}`}>
|
|
<div class="dashboard-overlay">
|
|
<div class="overlay-content">
|
|
<div class="overlay-icon"><i class={dash.icon}></i></div>
|
|
<div class="overlay-text">View Dashboard</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="dashboard-info">
|
|
<h3 class="dashboard-title">{dash.title}</h3>
|
|
<p class="dashboard-description">{dash.description}</p>
|
|
<div class="dashboard-cta">
|
|
<span class="btn btn-sm btn-primary">Access Dashboard</span>
|
|
</div>
|
|
</div>
|
|
</a>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</main>
|
|
|
|
<Footer slot="footer" />
|
|
</BaseLayout>
|
|
|
|
<style is:global>
|
|
/* Keep all your original styles from homelab.astro */
|
|
|
|
/* Hero Section Enhancements */
|
|
.hero {
|
|
min-height: 100vh;
|
|
display: flex;
|
|
align-items: center;
|
|
position: relative;
|
|
overflow: hidden;
|
|
padding-top: 6rem;
|
|
padding-bottom: 4rem;
|
|
background: linear-gradient(180deg, var(--bg-secondary), var(--bg-primary));
|
|
}
|
|
|
|
/* Enhanced Particles Animation */
|
|
.particles-container {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
overflow: hidden;
|
|
pointer-events: none;
|
|
z-index: 0;
|
|
}
|
|
|
|
.particle {
|
|
position: absolute;
|
|
background-color: var(--accent);
|
|
border-radius: 50%;
|
|
pointer-events: none;
|
|
}
|
|
|
|
@keyframes float-particle {
|
|
0% { transform: translateY(0) translateX(0); opacity: 0.3; }
|
|
50% { transform: translateY(-100px) translateX(50px); opacity: 0.1; }
|
|
100% { transform: translateY(0) translateX(0); opacity: 0.3; }
|
|
}
|
|
|
|
/* Terminal Style Stats */
|
|
.terminal-style-stats {
|
|
background-color: rgba(15, 23, 42, 0.7);
|
|
border: 1px solid var(--border);
|
|
border-radius: 0.5rem;
|
|
padding: 1rem;
|
|
font-family: var(--font-mono, monospace);
|
|
margin: 2rem 0;
|
|
max-width: 90%;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.stat-line {
|
|
margin-bottom: 0.5rem;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.stat-line .cmd {
|
|
color: var(--primary, #3b82f6);
|
|
}
|
|
|
|
.stat-line.output {
|
|
color: var(--success, #10b981);
|
|
font-weight: bold;
|
|
font-size: 1.1rem;
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
/* Enhanced terminal animation */
|
|
.terminal-body {
|
|
padding: 1rem;
|
|
font-family: var(--font-mono, monospace);
|
|
font-size: 0.875rem;
|
|
color: var(--text-primary);
|
|
line-height: 1.4;
|
|
max-height: 400px;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
#animated-terminal .terminal-line {
|
|
opacity: 0;
|
|
transform: translateY(5px);
|
|
transition: opacity 0.2s ease, transform 0.2s ease;
|
|
}
|
|
|
|
#animated-terminal .terminal-line.visible {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
|
|
/* Enhanced architecture diagram */
|
|
.architecture-diagram-container {
|
|
position: relative;
|
|
max-width: 1000px;
|
|
margin: 3rem auto;
|
|
aspect-ratio: 16 / 9;
|
|
border-radius: 1rem;
|
|
overflow: hidden;
|
|
box-shadow: var(--card-shadow);
|
|
border: 1px solid var(--border);
|
|
background-color: rgba(15, 23, 42, 0.5);
|
|
}
|
|
|
|
.architecture-fallback {
|
|
display: none; /* Hide by default, show if JS disabled */
|
|
}
|
|
|
|
.architecture-diagram {
|
|
position: relative;
|
|
width: 100%;
|
|
height: 100%;
|
|
display: grid;
|
|
grid-template-rows: repeat(3, 1fr);
|
|
grid-template-columns: repeat(4, 1fr);
|
|
gap: 1rem;
|
|
padding: 2rem;
|
|
}
|
|
|
|
.diagram-node {
|
|
background-color: rgba(30, 41, 59, 0.7);
|
|
border: 1px solid var(--border);
|
|
border-radius: 0.5rem;
|
|
padding: 1rem;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: |