fresh-main #1
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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" }
|
||||
];
|
|
@ -79,9 +79,9 @@ const {
|
|||
|
||||
<!-- Favicon -->
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<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" />
|
||||
<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" />
|
||||
|
|
|
@ -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"> </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>
|
||||
<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>
|
|
@ -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 {
|
||||
|
|
|
@ -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>
|
Loading…
Reference in New Issue