fresh-main #1

Merged
KeyArgo merged 16 commits from fresh-main into main 2025-05-01 22:37:52 +00:00
9 changed files with 1170 additions and 1007 deletions
Showing only changes of commit 1fa31d0617 - Show all commits

View File

@ -0,0 +1,230 @@
---
// src/components/homelab/HeroSection.astro - Ultra minimal version
const { servicesCount } = Astro.props;
---
<section id="home" class="hero">
<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>
<!-- Simple stats display -->
<div class="stats-box">
<div class="stat-item">
<div class="stat-label">Services:</div>
<div class="stat-value">{servicesCount}+</div>
</div>
<div class="stat-item">
<div class="stat-label">CPU Cores:</div>
<div class="stat-value">32+</div>
</div>
<div class="stat-item">
<div class="stat-label">Memory:</div>
<div class="stat-value">64GB</div>
</div>
<div class="stat-item">
<div class="stat-label">Storage:</div>
<div class="stat-value">12TB</div>
</div>
</div>
<div class="cta-buttons">
<a href="/ansible-sandbox" class="btn btn-danger" id="ansible-sandbox-btn">
<span class="btn-text">Try Ansible Sandbox</span>
<span class="offline-badge">Offline</span>
</a>
<a href="#architecture" class="btn btn-outline">
<span class="btn-text">Explore Architecture</span>
</a>
</div>
</div>
<!-- Simple status box instead of terminal -->
<div class="status-box">
<div class="status-header">
<div class="status-title">System Status</div>
</div>
<div class="status-body">
<div class="status-line">
<span class="status-label">Nodes:</span>
<span class="status-value">2 (Ready)</span>
</div>
<div class="status-line">
<span class="status-label">Running Pods:</span>
<span class="status-value">32</span>
</div>
<div class="status-line">
<span class="status-label">Uptime:</span>
<span class="status-value">154 days</span>
</div>
<div class="status-line">
<span class="status-label">Load:</span>
<span class="status-value">0.22, 0.18, 0.15</span>
</div>
</div>
</div>
</div>
</div>
</section>
<style>
.hero {
min-height: 100vh;
display: flex;
align-items: center;
position: relative;
overflow: hidden;
padding: 6rem 0 4rem;
background: linear-gradient(180deg, var(--bg-secondary), var(--bg-primary));
}
.hero-content {
display: flex;
flex-wrap: wrap;
gap: 2rem;
align-items: center;
justify-content: space-between;
}
.hero-text {
flex: 1;
min-width: 300px;
}
.hero-title {
font-size: 2.5rem;
margin-bottom: 1rem;
}
.highlight {
color: var(--primary, #3b82f6);
}
.hero-description {
font-size: 1.125rem;
margin-bottom: 2rem;
color: var(--text-secondary);
}
.stats-box {
background-color: rgba(15, 23, 42, 0.7);
border: 1px solid var(--border);
border-radius: 0.5rem;
padding: 1.5rem;
margin: 2rem 0;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 1rem;
}
.stat-item {
display: flex;
flex-direction: column;
}
.stat-label {
font-size: 0.875rem;
color: var(--text-secondary);
}
.stat-value {
font-size: 1.5rem;
font-weight: bold;
color: var(--success, #10b981);
}
.cta-buttons {
display: flex;
gap: 1rem;
margin-top: 2rem;
flex-wrap: wrap;
}
.btn {
display: inline-flex;
align-items: center;
padding: 0.75rem 1.5rem;
border-radius: 0.375rem;
font-weight: 500;
text-decoration: none;
transition: all 0.2s ease;
}
.btn-danger {
background-color: var(--error, #ef4444);
color: white;
}
.btn-outline {
background-color: transparent;
border: 1px solid var(--border);
color: var(--text-primary);
}
.offline-badge {
background-color: rgba(0, 0, 0, 0.2);
font-size: 0.75rem;
padding: 0.25rem 0.5rem;
border-radius: 1rem;
margin-left: 0.5rem;
}
.status-box {
background-color: rgba(15, 23, 42, 0.9);
border-radius: 0.5rem;
overflow: hidden;
width: 100%;
max-width: 350px;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
}
.status-header {
padding: 0.75rem 1rem;
background-color: rgba(30, 41, 59, 0.8);
}
.status-title {
font-size: 0.875rem;
color: var(--text-secondary);
}
.status-body {
padding: 1rem;
}
.status-line {
display: flex;
justify-content: space-between;
margin-bottom: 0.75rem;
}
.status-label {
color: var(--text-secondary);
}
.status-value {
color: var(--success, #10b981);
}
@media (max-width: 768px) {
.hero-content {
flex-direction: column;
align-items: flex-start;
}
.stats-box {
grid-template-columns: repeat(2, 1fr);
}
.status-box {
max-width: none;
width: 100%;
}
}
</style>

View File

@ -0,0 +1,119 @@
---
// src/components/homelab/ProjectsSection.astro
// Making sure we properly handle the projectsData prop
const { projectsData } = Astro.props;
// If projectsData is undefined or not an array, provide a fallback
const projects = Array.isArray(projectsData) ? projectsData : [];
// Debug logging
console.log("ProjectsSection received projectsData:", !!projectsData);
console.log("ProjectsSection projects count:", projects.length);
---
<section id="projects" class="projects section-padding alt-bg">
<div class="container">
<div class="section-header">
<h2 class="section-title">Related Projects</h2>
<p class="section-description">
Explore associated projects and repositories related to the ArgoBox lab.
</p>
</div>
<div class="projects-grid">
{projects.length > 0 ? (
projects.map(project => (
<a
href={project.url}
class="project-card"
target="_blank"
rel="noopener noreferrer"
>
<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 && project.tech.map(tech => (
<span class="tech-badge">{tech}</span>
))}
</div>
</a>
))
) : (
<div class="no-projects">
<p>No projects available at this time.</p>
</div>
)}
</div>
</div>
</section>
<style>
.projects-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 2rem;
margin-top: 2rem;
}
.project-card {
padding: 1.5rem;
border-radius: 0.5rem;
background-color: var(--bg-card);
border: 1px solid var(--border);
transition: all 0.3s ease;
text-decoration: none;
color: var(--text-primary);
}
.project-card:hover {
transform: translateY(-5px);
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
}
.project-header {
display: flex;
align-items: center;
gap: 1rem;
margin-bottom: 1rem;
}
.project-icon {
font-size: 1.5rem;
color: var(--primary);
}
.project-title {
font-size: 1.25rem;
margin: 0;
}
.project-description {
margin-bottom: 1.5rem;
color: var(--text-secondary);
}
.project-tech {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
.tech-badge {
padding: 0.25rem 0.5rem;
border-radius: 0.25rem;
background-color: var(--bg-secondary);
font-size: 0.75rem;
}
.no-projects {
grid-column: 1 / -1;
text-align: center;
padding: 3rem;
background-color: var(--bg-card);
border-radius: 0.5rem;
border: 1px solid var(--border);
}
</style>

View File

@ -0,0 +1,80 @@
// src/components/homelab/ServiceCategory.astro
---
const { category } = Astro.props;
function getCategoryIcon(categoryKey) {
if (categoryKey === 'development') return 'fa-code';
if (categoryKey === 'media') return 'fa-photo-video';
if (categoryKey === 'utilities') return 'fa-tools';
return 'fa-cogs'; // Default for infrastructure
}
const categoryIcon = getCategoryIcon(category.key);
const categoryTitle = category.key.charAt(0).toUpperCase() + category.key.slice(1);
---
<div class="services-category" data-category={category.key}>
<h3 class="category-title">
<i class={`fas ${categoryIcon} category-icon`}></i>
{categoryTitle} Tools
</h3>
<div class="service-items">
{category.services.map(service => (
<a
href={service.available ? service.url : '#'}
class={`service-item ${service.available ? 'available' : ''} service-${service.name.toLowerCase().replace(/\s+/g, '-')}`}
target={service.available ? "_blank" : null}
rel={service.available ? "noopener noreferrer" : null}
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>
<style>
.services-category {
margin-bottom: 2rem;
}
.category-title {
display: flex;
align-items: center;
margin-bottom: 1rem;
font-size: 1.5rem;
}
.category-icon {
margin-right: 0.75rem;
opacity: 0.8;
}
.service-items {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 1rem;
}
.service-item {
display: flex;
padding: 1.25rem;
border-radius: 0.5rem;
background-color: var(--bg-card);
border: 1px solid var(--border);
transition: all 0.3s ease;
text-decoration: none;
color: var(--text-primary);
position: relative;
}
</style>

View File

@ -0,0 +1,201 @@
---
// src/components/homelab/ServicesSection.astro
// Making sure servicesData is properly handled
const { servicesData } = Astro.props;
// Helper functions
function getCategoryIcon(category) {
if (category === 'development') return 'fa-code';
if (category === 'media') return 'fa-photo-video';
if (category === 'utilities') return 'fa-tools';
return 'fa-cogs'; // Default for infrastructure
}
function formatServiceName(name) {
return name.toLowerCase().replace(/\s+/g, '-');
}
// Create a safe version of the data in case it's undefined
const safeServicesData = servicesData || {};
// Prepare data for rendering to minimize template expressions
const categoryEntries = Object.entries(safeServicesData).map(([key, services]) => {
return {
key,
title: key.charAt(0).toUpperCase() + key.slice(1),
services: Array.isArray(services) ? services : []
};
});
// Debug logging
console.log("ServicesSection received servicesData:", !!servicesData);
console.log("ServicesSection categories count:", categoryEntries.length);
---
<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">
{categoryEntries.length > 0 ? (
categoryEntries.map(category => (
<div class="services-category" data-category={category.key}>
<h3 class="category-title">
<i class={`fas ${getCategoryIcon(category.key)} category-icon`}></i>
{category.title} Tools
</h3>
<div class="service-items">
{category.services.map(service => (
<a
href={service.available ? service.url : '#'}
class={`service-item ${service.available ? 'available' : ''} service-${formatServiceName(service.name)}`}
target={service.available ? "_blank" : null}
rel={service.available ? "noopener noreferrer" : null}
aria-disabled={!service.available}
>
<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 class="no-services">
<p>No services available at this time.</p>
</div>
)}
</div>
</div>
</section>
<style>
.services-grid {
display: grid;
grid-template-columns: 1fr;
gap: 2rem;
margin-top: 2rem;
}
.services-info-banner {
display: flex;
align-items: center;
padding: 1rem;
background-color: rgba(59, 130, 246, 0.1);
border-radius: 0.5rem;
margin-top: 1rem;
}
.info-icon {
margin-right: 1rem;
color: var(--primary, #3b82f6);
}
.services-category {
margin-bottom: 2rem;
}
.category-title {
display: flex;
align-items: center;
margin-bottom: 1rem;
font-size: 1.5rem;
}
.category-icon {
margin-right: 0.75rem;
opacity: 0.8;
}
.service-items {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 1rem;
}
.service-item {
display: flex;
padding: 1.25rem;
border-radius: 0.5rem;
background-color: var(--bg-card);
border: 1px solid var(--border);
transition: all 0.3s ease;
text-decoration: none;
color: var(--text-primary);
position: relative;
}
.service-item.available:hover {
transform: translateY(-3px);
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
}
.service-icon {
margin-right: 1rem;
font-size: 1.25rem;
color: var(--primary);
}
.service-info {
flex: 1;
}
.service-name {
font-weight: 500;
margin-bottom: 0.5rem;
}
.service-description {
font-size: 0.875rem;
color: var(--text-secondary);
}
.service-status {
display: flex;
align-items: center;
font-size: 0.75rem;
padding: 0.25rem 0.5rem;
border-radius: 1rem;
background-color: rgba(0, 0, 0, 0.1);
margin-left: 1rem;
}
.status-dot {
width: 8px;
height: 8px;
border-radius: 50%;
margin-right: 0.25rem;
background-color: var(--success);
}
.service-status.offline .status-dot {
background-color: var(--error);
}
.no-services {
grid-column: 1 / -1;
text-align: center;
padding: 3rem;
background-color: var(--bg-card);
border-radius: 0.5rem;
border: 1px solid var(--border);
}
</style>

40
src/data/homelabData.ts Normal file
View File

@ -0,0 +1,40 @@
export 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 },
]
};
export 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" }
];
export 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" }
];

View File

@ -79,9 +79,9 @@ const {
<!-- Favicon -->
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
&lt;link rel="icon" type="image/x-icon" href="/favicon.ico" /&gt;
&lt;link rel="apple-touch-icon" href="/apple-touch-icon.png" /&gt;
&lt;link rel="manifest" href="/site.webmanifest" /&gt;
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
<link rel="manifest" href="/site.webmanifest" />
<!-- Theme CSS -->
<link rel="stylesheet" href="/src/styles/theme.css" />

View File

@ -1,967 +1,56 @@
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
// src/pages/homelab.astro - Fixed main file with proper component imports
import BaseLayout from '../layouts/BaseLayout.astro';
import Header from '../components/Header.astro';
import Footer from '../components/Footer.astro';
import HeroSection from '../components/homelab/HeroSection.astro';
import ServicesSection from '../components/homelab/ServicesSection.astro';
import ProjectsSection from '../components/homelab/ProjectsSection.astro';
import { servicesData, projectsData, dashboardsData } from '../data/homelabData';
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" }
];
// Calculate total services count for the hero section
const servicesCount = Object.values(servicesData).flat().length;
---
<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">&nbsp;</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">&nbsp;</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">&nbsp;</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>
<HeroSection servicesCount={servicesCount} />
<ServicesSection servicesData={servicesData} />
<ProjectsSection projectsData={projectsData} />
</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));
/* Global styles - can be moved to a separate CSS file if needed */
.section-padding {
padding: 5rem 0;
}
/* Enhanced Particles Animation */
.particles-container {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
overflow: hidden;
pointer-events: none;
z-index: 0;
.alt-bg {
background-color: var(--bg-secondary);
}
.particle {
position: absolute;
background-color: var(--accent);
border-radius: 50%;
pointer-events: none;
.section-header {
text-align: center;
margin-bottom: 3rem;
}
@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;
.section-title {
font-size: 2.5rem;
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;
.section-description {
font-size: 1.125rem;
color: var(--text-secondary);
max-width: 700px;
margin: 0 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:
</style>

View File

@ -6,7 +6,6 @@ import Header from '../components/Header.astro';
import Footer from '../components/Footer.astro';
import Terminal from '../components/Terminal.astro';
import PostCard from '../components/PostCard.astro';
import MiniKnowledgeGraph from '../components/MiniKnowledgeGraph.astro';
import '../styles/card-animations.css';
const title = "ArgoBox | Enterprise-Grade Home Lab & DevOps Hub";
@ -48,32 +47,6 @@ const projectHighlights = [
}
];
// Enhanced Graph Data for Homepage Feature
const miniGraphData = {
nodes: [
{ id: 'kubernetes', label: 'Kubernetes', type: 'tag' },
{ id: 'homelab', label: 'Home Lab', type: 'tag' },
{ id: 'devops', label: 'DevOps', type: 'tag' },
{ id: 'automation', label: 'Automation', type: 'tag' },
{ id: 'infrastructure', label: 'Infrastructure', type: 'tag' },
{ id: 'post1', label: 'K3s Cluster Setup', type: 'post' },
{ id: 'post2', label: 'GitOps Workflow', type: 'post' },
{ id: 'post3', label: 'Home Lab Monitoring', type: 'post' },
{ id: 'post4', label: 'IaC Best Practices', type: 'post' },
],
edges: [
{ source: 'post1', target: 'kubernetes', type: 'post-tag' },
{ source: 'post1', target: 'homelab', type: 'post-tag' },
{ source: 'post2', target: 'devops', type: 'post-tag' },
{ source: 'post2', target: 'kubernetes', type: 'post-tag' },
{ source: 'post2', target: 'automation', type: 'post-tag' },
{ source: 'post3', target: 'homelab', type: 'post-tag' },
{ source: 'post3', target: 'infrastructure', type: 'post-tag' },
{ source: 'post4', target: 'infrastructure', type: 'post-tag' },
{ source: 'post4', target: 'automation', type: 'post-tag' },
]
};
// Define Commands for Hero Terminal
const heroCommands = [
{ prompt: "[user@argobox]$ ", command: "uname -a", output: ["Linux argobox 6.1.0-18-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.76-1 (2024-02-01) x86_64 GNU/Linux"] },
@ -99,7 +72,7 @@ const heroCommands = [
</p>
<div class="hero-cta">
<a href="/blog" class="cta-button primary">Explore Guides</a>
<a href="#knowledge-graph" class="cta-button secondary">Discover Connections</a>
<a href="/blog#knowledge-graph" class="cta-button secondary">Discover Connections</a>
</div>
</div>
<div class="terminal-container">
@ -109,24 +82,6 @@ const heroCommands = [
</div>
</section>
<!-- Knowledge Graph Feature - Moved up for impact -->
<section id="knowledge-graph" class="graph-section section-padding">
<div class="container">
<div class="section-header">
<h2 class="section-title">Knowledge Graph</h2>
<p class="section-description">
Explore connections between infrastructure components, technologies, and implementation guides.
</p>
</div>
<div class="graph-container">
<MiniKnowledgeGraph graphData={miniGraphData} height="450px" />
</div>
<div class="graph-link-container">
<a href="/blog#knowledge-graph" class="cta-button primary">Explore Full Graph</a>
</div>
</div>
</section>
<!-- Intro Section - Reframed as community resource -->
<section class="intro-section section-padding">
<div class="container">
@ -310,27 +265,6 @@ const heroCommands = [
.hero-cta { display: flex; gap: 1rem; }
.terminal-container { flex: 1; max-width: 550px; }
/* Graph Section - New Prominent Section */
.graph-section {
background: var(--bg-primary);
position: relative;
overflow: hidden;
}
.graph-container {
border: 1px solid var(--border-primary);
border-radius: 12px;
overflow: hidden;
background: var(--bg-secondary);
margin-bottom: 2rem;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15);
position: relative;
}
.graph-link-container {
text-align: center;
}
/* Intro Section - Adjusted for community focus */
.intro-section .section-title { text-align: center; margin-bottom: 1.5rem; }
.intro-text {

View File

@ -0,0 +1,470 @@
---
// src/pages/projects/infrastructure-templates.astro
import BaseLayout from '../../layouts/BaseLayout.astro';
import Header from '../../components/Header.astro';
import Footer from '../../components/Footer.astro';
const title = "Infrastructure as Code Templates | ArgoBox";
const description = "Curated collection of reusable Terraform, Ansible, and Kubernetes manifests for building modern infrastructure environments.";
---
<BaseLayout title={title} description={description}>
<Header slot="header" />
<div class="container">
<div class="page-header">
<h1>Infrastructure as Code Templates</h1>
<div class="header-accent"></div>
</div>
<div class="coming-soon-container">
<div class="coming-soon-card">
<div class="icon-container">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="60" height="60" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<polyline points="16 18 22 12 16 6"></polyline>
<polyline points="8 6 2 12 8 18"></polyline>
<line x1="12" y1="2" x2="12" y2="22"></line>
</svg>
</div>
<h2>IaC Templates Coming Soon</h2>
<p class="description">
This section will provide a comprehensive collection of reusable Infrastructure as Code templates for building modern tech environments. These templates are designed to help you accelerate your infrastructure deployments with best practices built in.
</p>
<div class="template-categories">
<div class="template-category">
<div class="category-icon">
<svg xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<rect x="2" y="2" width="20" height="8" rx="2" ry="2"></rect>
<rect x="2" y="14" width="20" height="8" rx="2" ry="2"></rect>
<line x1="6" y1="6" x2="6.01" y2="6"></line>
<line x1="6" y1="18" x2="6.01" y2="18"></line>
</svg>
</div>
<div class="category-content">
<h3>Terraform Modules</h3>
<p>Modular infrastructure components for cloud providers and on-premises environments.</p>
<ul class="template-list">
<li>Multi-cloud networking templates</li>
<li>Kubernetes cluster provisioning</li>
<li>Database deployment patterns</li>
<li>Security & compliance configurations</li>
</ul>
</div>
</div>
<div class="template-category">
<div class="category-icon">
<svg xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M18 20V10"></path>
<path d="M12 20V4"></path>
<path d="M6 20v-6"></path>
<path d="M18 14a2 2 0 1 0 0-4 2 2 0 0 0 0 4z"></path>
<path d="M12 8a2 2 0 1 0 0-4 2 2 0 0 0 0 4z"></path>
<path d="M6 18a2 2 0 1 0 0-4 2 2 0 0 0 0 4z"></path>
</svg>
</div>
<div class="category-content">
<h3>Ansible Playbooks</h3>
<p>Automation workflows for system configuration and application deployment.</p>
<ul class="template-list">
<li>Server hardening & compliance</li>
<li>Application deployment patterns</li>
<li>Monitoring setup automation</li>
<li>Disaster recovery procedures</li>
</ul>
</div>
</div>
<div class="template-category">
<div class="category-icon">
<svg xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M22 12H2"></path>
<path d="M5 12l3-3"></path>
<path d="M5 12l3 3"></path>
<path d="M19 12l-3-3"></path>
<path d="M19 12l-3 3"></path>
</svg>
</div>
<div class="category-content">
<h3>Kubernetes Manifests</h3>
<p>Production-ready configurations for containerized applications.</p>
<ul class="template-list">
<li>Application deployment blueprints</li>
<li>GitOps-ready repository structure</li>
<li>Security policies & network configurations</li>
<li>Stateful workload templates</li>
</ul>
</div>
</div>
</div>
<div class="cta-container">
<a href="/blog?tag=infrastructure-as-code" class="cta-button secondary">
<span>Read Related Articles</span>
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="5" y1="12" x2="19" y2="12"></line>
<polyline points="12 5 19 12 12 19"></polyline>
</svg>
</a>
<button class="cta-button primary">
<span>Notify Me When Available</span>
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"></path>
<path d="M13.73 21a2 2 0 0 1-3.46 0"></path>
</svg>
</button>
</div>
</div>
</div>
<!-- Related Projects Section -->
<div class="related-projects">
<h2 class="section-title">Related Projects</h2>
<div class="projects-grid">
<a href="/projects/github" class="related-project">
<div class="project-icon">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path>
</svg>
</div>
<h3>GitHub Repositories</h3>
<p>Open-source infrastructure code and automation scripts.</p>
<span class="project-link">View Project <span class="arrow">→</span></span>
</a>
<a href="/projects/tech-stack" class="related-project">
<div class="project-icon">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="16.5" y1="9.4" x2="7.5" y2="4.21"></line>
<path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"></path>
<polyline points="3.27 6.96 12 12.01 20.73 6.96"></polyline>
<line x1="12" y1="22.08" x2="12" y2="12"></line>
</svg>
</div>
<h3>Tech Stack</h3>
<p>Explore the technologies used in the ArgoBox infrastructure.</p>
<span class="project-link">View Project <span class="arrow">→</span></span>
</a>
<a href="/homelab" class="related-project">
<div class="project-icon">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="2" y="2" width="20" height="8" rx="2" ry="2"></rect>
<rect x="2" y="14" width="20" height="8" rx="2" ry="2"></rect>
<line x1="6" y1="6" x2="6.01" y2="6"></line>
<line x1="6" y1="18" x2="6.01" y2="18"></line>
</svg>
</div>
<h3>Home Lab Infrastructure</h3>
<p>Enterprise-grade home lab environment with production services.</p>
<span class="project-link">View Project <span class="arrow">→</span></span>
</a>
</div>
</div>
</div>
<Footer slot="footer" />
</BaseLayout>
<style>
.container {
max-width: 1280px;
margin: 0 auto;
padding: 0 var(--container-padding, 1.5rem);
}
.page-header {
margin: 3rem 0 4rem;
position: relative;
}
h1 {
font-size: var(--font-size-4xl, 2.5rem);
background: linear-gradient(90deg, var(--accent-secondary, #3b82f6), var(--accent-primary, #06b6d4));
-webkit-background-clip: text;
background-clip: text;
color: transparent;
display: inline-block;
margin-bottom: 0.5rem;
}
.header-accent {
width: 80px;
height: 4px;
background: linear-gradient(90deg, var(--accent-secondary, #3b82f6), var(--accent-primary, #06b6d4));
border-radius: 2px;
}
.coming-soon-container {
display: flex;
justify-content: center;
padding: 2rem 0 4rem;
}
.coming-soon-card {
background: var(--card-bg, #1e293b);
border: 1px solid var(--card-border, #334155);
border-radius: 16px;
padding: 2.5rem;
max-width: 900px;
width: 100%;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
position: relative;
overflow: hidden;
}
.coming-soon-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: radial-gradient(circle at 30% 50%, rgba(59, 130, 246, 0.1), transparent 70%);
pointer-events: none;
}
.icon-container {
display: flex;
justify-content: center;
margin-bottom: 1.5rem;
color: var(--accent-secondary, #3b82f6);
}
h2 {
text-align: center;
margin-bottom: 1.5rem;
font-size: var(--font-size-3xl, 1.875rem);
color: var(--text-primary, #f1f5f9);
}
.description {
text-align: center;
margin-bottom: 3rem;
color: var(--text-secondary, #cbd5e1);
font-size: var(--font-size-lg, 1.125rem);
line-height: 1.7;
max-width: 800px;
margin-left: auto;
margin-right: auto;
}
.template-categories {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 2rem;
margin-bottom: 3rem;
}
.template-category {
background: rgba(30, 41, 59, 0.5);
border: 1px solid var(--border-primary, #334155);
border-radius: 12px;
padding: 1.5rem;
transition: all 0.3s ease;
display: flex;
flex-direction: column;
}
.template-category:hover {
transform: translateY(-5px);
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.15);
border-color: var(--accent-secondary, #3b82f6);
}
.category-icon {
color: var(--accent-secondary, #3b82f6);
margin-bottom: 1.5rem;
display: flex;
justify-content: center;
}
.category-content {
flex: 1;
}
.category-content h3 {
font-size: var(--font-size-xl, 1.25rem);
margin-bottom: 1rem;
color: var(--text-primary, #f1f5f9);
text-align: center;
}
.category-content p {
color: var(--text-secondary, #cbd5e1);
margin-bottom: 1.5rem;
text-align: center;
font-size: var(--font-size-md, 1rem);
}
.template-list {
list-style-type: none;
padding: 0;
margin: 0;
}
.template-list li {
padding: 0.75rem 0;
border-bottom: 1px solid rgba(203, 213, 225, 0.1);
color: var(--text-tertiary, #94a3b8);
font-size: var(--font-size-sm, 0.875rem);
position: relative;
padding-left: 1.5rem;
}
.template-list li:last-child {
border-bottom: none;
}
.template-list li::before {
content: '→';
position: absolute;
left: 0;
color: var(--accent-primary, #06b6d4);
}
.cta-container {
display: flex;
justify-content: center;
gap: 1.5rem;
margin-top: 2rem;
}
.cta-button {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.75rem 1.5rem;
border-radius: 8px;
font-weight: 500;
font-size: var(--font-size-md, 1rem);
transition: all 0.3s ease;
cursor: pointer;
text-decoration: none;
}
.cta-button.primary {
background: linear-gradient(90deg, var(--accent-secondary, #3b82f6), var(--accent-primary, #06b6d4));
color: var(--bg-primary, #0f172a);
border: none;
box-shadow: 0 5px 15px rgba(59, 130, 246, 0.2);
}
.cta-button.secondary {
background: transparent;
border: 1px solid var(--accent-secondary, #3b82f6);
color: var(--accent-secondary, #3b82f6);
}
.cta-button.primary:hover {
transform: translateY(-3px);
box-shadow: 0 8px 20px rgba(59, 130, 246, 0.3);
}
.cta-button.secondary:hover {
transform: translateY(-3px);
background: rgba(59, 130, 246, 0.1);
}
/* Related Projects Section */
.related-projects {
margin: 4rem 0;
}
.section-title {
font-size: var(--font-size-2xl, 1.5rem);
color: var(--text-primary, #f1f5f9);
margin-bottom: 2rem;
position: relative;
padding-bottom: 0.75rem;
}
.section-title::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
width: 60px;
height: 3px;
background: linear-gradient(90deg, var(--accent-secondary, #3b82f6), var(--accent-primary, #06b6d4));
border-radius: 2px;
}
.projects-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 1.5rem;
}
.related-project {
background: var(--card-bg, #1e293b);
border: 1px solid var(--border-primary, #334155);
border-radius: 12px;
padding: 1.5rem;
text-decoration: none;
transition: all 0.3s ease;
}
.related-project:hover {
transform: translateY(-5px);
border-color: var(--accent-primary, #06b6d4);
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.15);
}
.project-icon {
color: var(--accent-primary, #06b6d4);
margin-bottom: 1rem;
}
.related-project h3 {
font-size: var(--font-size-lg, 1.125rem);
color: var(--text-primary, #f1f5f9);
margin-bottom: 0.75rem;
}
.related-project p {
color: var(--text-tertiary, #94a3b8);
font-size: var(--font-size-sm, 0.875rem);
margin-bottom: 1.5rem;
}
.project-link {
color: var(--accent-secondary, #3b82f6);
font-size: var(--font-size-sm, 0.875rem);
font-weight: 500;
display: flex;
align-items: center;
}
.arrow {
margin-left: 0.5rem;
transition: transform 0.3s ease;
}
.related-project:hover .arrow {
transform: translateX(5px);
}
@media (max-width: 768px) {
.template-categories {
grid-template-columns: 1fr;
}
.cta-container {
flex-direction: column;
}
.coming-soon-card {
padding: 2rem 1.5rem;
}
h1 {
font-size: var(--font-size-3xl, 1.875rem);
}
h2 {
font-size: var(--font-size-2xl, 1.5rem);
}
}
</style>