fresh-main #7
|
@ -1,112 +0,0 @@
|
||||||
---
|
|
||||||
import '../styles/global.css';
|
|
||||||
import Header from '../components/Header.astro';
|
|
||||||
import Footer from '../components/Footer.astro';
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
title: string;
|
|
||||||
description?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { title, description = "LaForce IT - Home Lab & DevOps Insights" } = Astro.props;
|
|
||||||
---
|
|
||||||
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>{title}</title>
|
|
||||||
<meta name="description" content={description}>
|
|
||||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
||||||
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&family=Space+Grotesk:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
|
||||||
|
|
||||||
<!-- Open Graph / Social Media Meta Tags -->
|
|
||||||
<meta property="og:title" content={title} />
|
|
||||||
<meta property="og:description" content={description} />
|
|
||||||
<meta property="og:type" content="website" />
|
|
||||||
<meta property="og:url" content={Astro.url} />
|
|
||||||
<meta property="og:image" content="/blog/images/placeholders/default.jpg" />
|
|
||||||
|
|
||||||
<!-- Twitter Meta Tags -->
|
|
||||||
<meta name="twitter:card" content="summary_large_image" />
|
|
||||||
<meta property="twitter:domain" content="laforceit.blog" />
|
|
||||||
<meta property="twitter:url" content={Astro.url} />
|
|
||||||
<meta name="twitter:title" content={title} />
|
|
||||||
<meta name="twitter:description" content={description} />
|
|
||||||
<meta name="twitter:image" content="/blog/images/placeholders/default.jpg" />
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<!-- Neural network nodes - Added via JavaScript -->
|
|
||||||
<div id="neural-network"></div>
|
|
||||||
|
|
||||||
<!-- Floating shapes for background effect -->
|
|
||||||
<div class="floating-shapes">
|
|
||||||
<div class="floating-shape shape-1"></div>
|
|
||||||
<div class="floating-shape shape-2"></div>
|
|
||||||
<div class="floating-shape shape-3"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Header />
|
|
||||||
|
|
||||||
<slot />
|
|
||||||
|
|
||||||
<Footer />
|
|
||||||
|
|
||||||
<script>
|
|
||||||
// Create neural network nodes
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
|
||||||
const neuralNetwork = document.getElementById('neural-network');
|
|
||||||
if (!neuralNetwork) return;
|
|
||||||
|
|
||||||
const nodeCount = Math.min(window.innerWidth / 20, 70); // Responsive node count
|
|
||||||
|
|
||||||
for (let i = 0; i < nodeCount; i++) {
|
|
||||||
const node = document.createElement('div');
|
|
||||||
node.classList.add('neural-node');
|
|
||||||
|
|
||||||
// Random position
|
|
||||||
node.style.left = `${Math.random() * 100}%`;
|
|
||||||
node.style.top = `${Math.random() * 100}%`;
|
|
||||||
|
|
||||||
// Random animation delay
|
|
||||||
node.style.animationDelay = `${Math.random() * 4}s`;
|
|
||||||
|
|
||||||
neuralNetwork.appendChild(node);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Terminal typing effect
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
|
||||||
const terminalTyping = document.querySelector('.terminal-typing');
|
|
||||||
if (!terminalTyping) return;
|
|
||||||
|
|
||||||
const typingCommands = [
|
|
||||||
'cloudflared tunnel status',
|
|
||||||
'kubectl get pods -A',
|
|
||||||
'helm list -n monitoring',
|
|
||||||
'flux reconcile kustomization --all'
|
|
||||||
];
|
|
||||||
|
|
||||||
let currentCommandIndex = 0;
|
|
||||||
|
|
||||||
function typeCommand(command: string, element: Element, index = 0) {
|
|
||||||
if (index < command.length) {
|
|
||||||
element.textContent = command.substring(0, index + 1);
|
|
||||||
setTimeout(() => typeCommand(command, element, index + 1), 100);
|
|
||||||
} else {
|
|
||||||
// Move to next command after delay
|
|
||||||
setTimeout(() => {
|
|
||||||
currentCommandIndex = (currentCommandIndex + 1) % typingCommands.length;
|
|
||||||
typeCommand(typingCommands[currentCommandIndex], element, 0);
|
|
||||||
}, 3000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
typeCommand(typingCommands[currentCommandIndex], terminalTyping);
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,108 +0,0 @@
|
||||||
---
|
|
||||||
import { getCollection } from 'astro:content';
|
|
||||||
import BaseLayout from '../../layouts/BaseLayout.astro';
|
|
||||||
|
|
||||||
// Get all blog entries
|
|
||||||
const allPosts = await getCollection('blog');
|
|
||||||
|
|
||||||
// Sort by publication date
|
|
||||||
const sortedPosts = allPosts.sort((a, b) => {
|
|
||||||
const dateA = a.data.pubDate ? new Date(a.data.pubDate) : new Date(0);
|
|
||||||
const dateB = b.data.pubDate ? new Date(b.data.pubDate) : new Date(0);
|
|
||||||
return dateB.getTime() - dateA.getTime();
|
|
||||||
});
|
|
||||||
---
|
|
||||||
|
|
||||||
<BaseLayout title="Blog | LaForce IT - Home Lab & DevOps Insights" description="Explore articles about Kubernetes, Infrastructure, DevOps, and Home Lab setups">
|
|
||||||
<main class="container">
|
|
||||||
<section class="blog-header">
|
|
||||||
<h1 class="blog-title">Blog</h1>
|
|
||||||
<p class="blog-description">
|
|
||||||
Technical insights, infrastructure guides, and DevOps best practices from my home lab to production environments.
|
|
||||||
</p>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<div class="blog-grid">
|
|
||||||
{sortedPosts.map((post) => (
|
|
||||||
<article class="post-card">
|
|
||||||
{post.data.heroImage ? (
|
|
||||||
<img
|
|
||||||
width={720}
|
|
||||||
height={360}
|
|
||||||
src={post.data.heroImage}
|
|
||||||
alt=""
|
|
||||||
class="post-image"
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<img
|
|
||||||
width={720}
|
|
||||||
height={360}
|
|
||||||
src="/blog/images/placeholders/default.jpg"
|
|
||||||
alt=""
|
|
||||||
class="post-image"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<div class="post-content">
|
|
||||||
<div class="post-meta">
|
|
||||||
<time datetime={post.data.pubDate ? new Date(post.data.pubDate).toISOString() : ''}>
|
|
||||||
{post.data.pubDate ? new Date(post.data.pubDate).toLocaleDateString('en-us', {
|
|
||||||
year: 'numeric',
|
|
||||||
month: 'short',
|
|
||||||
day: 'numeric',
|
|
||||||
}) : 'No date'}
|
|
||||||
</time>
|
|
||||||
{post.data.category && (
|
|
||||||
<span class="post-category">
|
|
||||||
{post.data.category}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<h3 class="post-title">
|
|
||||||
<a href={`/blog/${post.slug}/`}>{post.data.title}</a>
|
|
||||||
{post.data.draft && <span class="ml-2 px-2 py-1 bg-gray-200 text-gray-700 text-xs rounded">Draft</span>}
|
|
||||||
</h3>
|
|
||||||
<p class="post-excerpt">{post.data.description}</p>
|
|
||||||
<div class="post-footer">
|
|
||||||
<span class="post-read-time">{post.data.readTime || '5 min read'}</span>
|
|
||||||
<a href={`/blog/${post.slug}/`} class="read-more">Read More</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
</BaseLayout>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.blog-header {
|
|
||||||
margin: 3rem 0;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.blog-title {
|
|
||||||
font-size: clamp(2rem, 5vw, 3.5rem);
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
background: linear-gradient(90deg, var(--accent-primary), var(--accent-tertiary));
|
|
||||||
-webkit-background-clip: text;
|
|
||||||
background-clip: text;
|
|
||||||
color: transparent;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.blog-description {
|
|
||||||
color: var(--text-secondary);
|
|
||||||
font-size: clamp(1rem, 2vw, 1.2rem);
|
|
||||||
max-width: 700px;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.blog-grid {
|
|
||||||
margin: 2rem 0 4rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.blog-header {
|
|
||||||
margin: 2rem 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,832 +0,0 @@
|
||||||
@tailwind base;
|
|
||||||
@tailwind components;
|
|
||||||
@tailwind utilities;
|
|
||||||
|
|
||||||
:root {
|
|
||||||
--bg-primary: #050a18;
|
|
||||||
--bg-secondary: #0d1529;
|
|
||||||
--text-primary: #e2e8f0;
|
|
||||||
--text-secondary: #94a3b8;
|
|
||||||
--accent-primary: #06b6d4;
|
|
||||||
--accent-secondary: #3b82f6;
|
|
||||||
--accent-tertiary: #8b5cf6;
|
|
||||||
--glow-primary: rgba(6, 182, 212, 0.3);
|
|
||||||
--glow-secondary: rgba(59, 130, 246, 0.3);
|
|
||||||
--card-bg: rgba(15, 23, 42, 0.8);
|
|
||||||
--card-border: rgba(56, 189, 248, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
* {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
font-family: 'Space Grotesk', sans-serif;
|
|
||||||
background: var(--bg-primary);
|
|
||||||
color: var(--text-primary);
|
|
||||||
line-height: 1.6;
|
|
||||||
overflow-x: hidden;
|
|
||||||
background-image:
|
|
||||||
radial-gradient(circle at 20% 35%, rgba(6, 182, 212, 0.05) 0%, transparent 50%),
|
|
||||||
radial-gradient(circle at 75% 15%, rgba(59, 130, 246, 0.05) 0%, transparent 45%),
|
|
||||||
radial-gradient(circle at 85% 70%, rgba(139, 92, 246, 0.05) 0%, transparent 40%);
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Grid overlay effect */
|
|
||||||
body::before {
|
|
||||||
content: "";
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background-image:
|
|
||||||
linear-gradient(rgba(226, 232, 240, 0.03) 1px, transparent 1px),
|
|
||||||
linear-gradient(90deg, rgba(226, 232, 240, 0.03) 1px, transparent 1px);
|
|
||||||
background-size: 30px 30px;
|
|
||||||
pointer-events: none;
|
|
||||||
z-index: -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Neural network nodes */
|
|
||||||
.neural-node {
|
|
||||||
position: fixed;
|
|
||||||
width: 2px;
|
|
||||||
height: 2px;
|
|
||||||
background: rgba(226, 232, 240, 0.2);
|
|
||||||
border-radius: 50%;
|
|
||||||
animation: pulse 4s infinite alternate ease-in-out;
|
|
||||||
z-index: -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes pulse {
|
|
||||||
0% {
|
|
||||||
transform: scale(1);
|
|
||||||
opacity: 0.3;
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
transform: scale(1.5);
|
|
||||||
opacity: 0.6;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Terminal cursor animation */
|
|
||||||
@keyframes blink {
|
|
||||||
0%, 100% { opacity: 1; }
|
|
||||||
50% { opacity: 0; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Header styles */
|
|
||||||
header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
padding: 1.5rem clamp(1rem, 5%, 3rem);
|
|
||||||
position: relative;
|
|
||||||
background: linear-gradient(180deg, var(--bg-secondary), transparent);
|
|
||||||
border-bottom: 1px solid rgba(56, 189, 248, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.logo {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 1rem;
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 1.5rem;
|
|
||||||
text-decoration: none;
|
|
||||||
color: var(--text-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.logo span {
|
|
||||||
background: linear-gradient(90deg, var(--accent-primary), var(--accent-secondary));
|
|
||||||
-webkit-background-clip: text;
|
|
||||||
background-clip: text;
|
|
||||||
color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logo-symbol {
|
|
||||||
width: 2.5rem;
|
|
||||||
height: 2.5rem;
|
|
||||||
border-radius: 10px;
|
|
||||||
background: linear-gradient(135deg, var(--accent-primary), var(--accent-secondary));
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
font-family: 'JetBrains Mono', monospace;
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 1.25rem;
|
|
||||||
color: var(--bg-primary);
|
|
||||||
box-shadow: 0 0 15px var(--glow-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
nav {
|
|
||||||
display: flex;
|
|
||||||
gap: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
nav a {
|
|
||||||
color: var(--text-secondary);
|
|
||||||
text-decoration: none;
|
|
||||||
font-weight: 500;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
nav a:hover {
|
|
||||||
color: var(--text-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
nav a::after {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
width: 0;
|
|
||||||
height: 2px;
|
|
||||||
bottom: -5px;
|
|
||||||
left: 0;
|
|
||||||
background: linear-gradient(90deg, var(--accent-primary), var(--accent-secondary));
|
|
||||||
transition: width 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
nav a:hover::after {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mobile-menu-btn {
|
|
||||||
display: none;
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
color: var(--text-primary);
|
|
||||||
font-size: 1.5rem;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Floating shapes */
|
|
||||||
.floating-shapes {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
pointer-events: none;
|
|
||||||
z-index: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.floating-shape {
|
|
||||||
position: absolute;
|
|
||||||
border-radius: 50%;
|
|
||||||
opacity: 0.05;
|
|
||||||
filter: blur(30px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.shape-1 {
|
|
||||||
width: 300px;
|
|
||||||
height: 300px;
|
|
||||||
background: var(--accent-primary);
|
|
||||||
top: 20%;
|
|
||||||
right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.shape-2 {
|
|
||||||
width: 200px;
|
|
||||||
height: 200px;
|
|
||||||
background: var(--accent-secondary);
|
|
||||||
bottom: 10%;
|
|
||||||
left: 10%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.shape-3 {
|
|
||||||
width: 150px;
|
|
||||||
height: 150px;
|
|
||||||
background: var(--accent-tertiary);
|
|
||||||
top: 70%;
|
|
||||||
right: 20%;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Blog post cards */
|
|
||||||
.post-card {
|
|
||||||
background: var(--card-bg);
|
|
||||||
border-radius: 10px;
|
|
||||||
border: 1px solid var(--card-border);
|
|
||||||
overflow: hidden;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
position: relative;
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.post-card:hover {
|
|
||||||
transform: translateY(-5px);
|
|
||||||
box-shadow: 0 10px 30px rgba(6, 182, 212, 0.1);
|
|
||||||
border-color: rgba(56, 189, 248, 0.4);
|
|
||||||
}
|
|
||||||
|
|
||||||
.post-card::before {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
inset: 0;
|
|
||||||
background: linear-gradient(135deg, rgba(6, 182, 212, 0.05), rgba(139, 92, 246, 0.05));
|
|
||||||
z-index: -1;
|
|
||||||
opacity: 0;
|
|
||||||
transition: opacity 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.post-card:hover::before {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.post-image {
|
|
||||||
width: 100%;
|
|
||||||
height: 200px;
|
|
||||||
object-fit: cover;
|
|
||||||
border-bottom: 1px solid var(--card-border);
|
|
||||||
}
|
|
||||||
|
|
||||||
.post-content {
|
|
||||||
padding: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.post-meta {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
font-size: 0.85rem;
|
|
||||||
color: var(--text-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.post-category {
|
|
||||||
background: rgba(6, 182, 212, 0.1);
|
|
||||||
color: var(--accent-primary);
|
|
||||||
padding: 0.25rem 0.5rem;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-family: 'JetBrains Mono', monospace;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.post-title {
|
|
||||||
font-size: 1.25rem;
|
|
||||||
margin-bottom: 0.75rem;
|
|
||||||
line-height: 1.3;
|
|
||||||
}
|
|
||||||
|
|
||||||
.post-title a {
|
|
||||||
color: var(--text-primary);
|
|
||||||
text-decoration: none;
|
|
||||||
transition: color 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.post-title a:hover {
|
|
||||||
color: var(--accent-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.post-excerpt {
|
|
||||||
color: var(--text-secondary);
|
|
||||||
font-size: 0.9rem;
|
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
display: -webkit-box;
|
|
||||||
-webkit-line-clamp: 3;
|
|
||||||
-webkit-box-orient: vertical;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.post-footer {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
color: var(--text-secondary);
|
|
||||||
font-size: 0.85rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.read-more {
|
|
||||||
color: var(--accent-primary);
|
|
||||||
text-decoration: none;
|
|
||||||
font-weight: 500;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.25rem;
|
|
||||||
transition: color 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.read-more:hover {
|
|
||||||
color: var(--accent-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.read-more::after {
|
|
||||||
content: '→';
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Section styles */
|
|
||||||
.section-title {
|
|
||||||
font-size: clamp(1.5rem, 3vw, 2.5rem);
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
position: relative;
|
|
||||||
display: inline-block;
|
|
||||||
color: var(--text-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-title::after {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
height: 4px;
|
|
||||||
width: 60px;
|
|
||||||
background: linear-gradient(90deg, var(--accent-primary), var(--accent-secondary));
|
|
||||||
bottom: -10px;
|
|
||||||
left: 0;
|
|
||||||
border-radius: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Footer styles */
|
|
||||||
footer {
|
|
||||||
background: var(--bg-secondary);
|
|
||||||
padding: 3rem clamp(1rem, 5%, 3rem);
|
|
||||||
position: relative;
|
|
||||||
border-top: 1px solid rgba(56, 189, 248, 0.1);
|
|
||||||
margin-top: 5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
||||||
gap: 2rem;
|
|
||||||
margin-bottom: 3rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer-col h4 {
|
|
||||||
font-size: 1.1rem;
|
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
color: var(--text-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer-links {
|
|
||||||
list-style: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer-links li {
|
|
||||||
margin-bottom: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer-links a {
|
|
||||||
color: var(--text-secondary);
|
|
||||||
text-decoration: none;
|
|
||||||
transition: color 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer-links a:hover {
|
|
||||||
color: var(--accent-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.social-links {
|
|
||||||
display: flex;
|
|
||||||
gap: 1rem;
|
|
||||||
margin-top: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.social-link {
|
|
||||||
width: 36px;
|
|
||||||
height: 36px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background: rgba(226, 232, 240, 0.05);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
color: var(--text-secondary);
|
|
||||||
text-decoration: none;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.social-link:hover {
|
|
||||||
background: var(--accent-primary);
|
|
||||||
color: var(--bg-primary);
|
|
||||||
transform: translateY(-3px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer-bottom {
|
|
||||||
text-align: center;
|
|
||||||
padding-top: 2rem;
|
|
||||||
border-top: 1px solid rgba(226, 232, 240, 0.05);
|
|
||||||
color: var(--text-secondary);
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer-bottom a {
|
|
||||||
color: var(--accent-primary);
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Hero section for homepage */
|
|
||||||
.hero {
|
|
||||||
min-height: 80vh;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
padding: 3rem clamp(1rem, 5%, 3rem);
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-content {
|
|
||||||
max-width: 650px;
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-subtitle {
|
|
||||||
font-family: 'JetBrains Mono', monospace;
|
|
||||||
color: var(--accent-primary);
|
|
||||||
font-size: 0.9rem;
|
|
||||||
letter-spacing: 2px;
|
|
||||||
text-transform: uppercase;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-subtitle::before {
|
|
||||||
content: '>';
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-title {
|
|
||||||
font-size: clamp(2.5rem, 5vw, 4rem);
|
|
||||||
line-height: 1.1;
|
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-title span {
|
|
||||||
background: linear-gradient(90deg, var(--accent-primary), var(--accent-tertiary));
|
|
||||||
-webkit-background-clip: text;
|
|
||||||
background-clip: text;
|
|
||||||
color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-description {
|
|
||||||
color: var(--text-secondary);
|
|
||||||
font-size: 1.1rem;
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
max-width: 85%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cta-button {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
background: linear-gradient(135deg, var(--accent-primary), var(--accent-secondary));
|
|
||||||
color: var(--bg-primary);
|
|
||||||
font-weight: 600;
|
|
||||||
padding: 0.75rem 1.5rem;
|
|
||||||
border-radius: 8px;
|
|
||||||
text-decoration: none;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
box-shadow: 0 0 20px var(--glow-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.cta-button:hover {
|
|
||||||
transform: translateY(-2px);
|
|
||||||
box-shadow: 0 0 30px var(--glow-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Terminal box */
|
|
||||||
.terminal-box {
|
|
||||||
width: 100%;
|
|
||||||
background: var(--bg-secondary);
|
|
||||||
border-radius: 10px;
|
|
||||||
border: 1px solid var(--card-border);
|
|
||||||
box-shadow: 0 0 30px rgba(6, 182, 212, 0.1);
|
|
||||||
padding: 1.5rem;
|
|
||||||
font-family: 'JetBrains Mono', monospace;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
z-index: 1;
|
|
||||||
overflow: hidden;
|
|
||||||
margin: 2rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.terminal-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
padding-bottom: 0.5rem;
|
|
||||||
border-bottom: 1px solid rgba(226, 232, 240, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.terminal-dots {
|
|
||||||
display: flex;
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.terminal-dot {
|
|
||||||
width: 12px;
|
|
||||||
height: 12px;
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.terminal-dot-red {
|
|
||||||
background: #ef4444;
|
|
||||||
}
|
|
||||||
|
|
||||||
.terminal-dot-yellow {
|
|
||||||
background: #eab308;
|
|
||||||
}
|
|
||||||
|
|
||||||
.terminal-dot-green {
|
|
||||||
background: #22c55e;
|
|
||||||
}
|
|
||||||
|
|
||||||
.terminal-title {
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
color: var(--text-secondary);
|
|
||||||
font-size: 0.8rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.terminal-content {
|
|
||||||
flex: 1;
|
|
||||||
color: var(--text-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.terminal-line {
|
|
||||||
margin-bottom: 0.75rem;
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.terminal-prompt {
|
|
||||||
color: var(--accent-primary);
|
|
||||||
margin-right: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.terminal-command {
|
|
||||||
color: var(--text-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.terminal-output {
|
|
||||||
color: var(--text-secondary);
|
|
||||||
padding-left: 1.5rem;
|
|
||||||
margin-bottom: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.terminal-typing {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.terminal-typing::after {
|
|
||||||
content: '|';
|
|
||||||
position: absolute;
|
|
||||||
right: -10px;
|
|
||||||
animation: blink 1s infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Container and content layout */
|
|
||||||
.container {
|
|
||||||
width: 100%;
|
|
||||||
max-width: 1200px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 0 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
main {
|
|
||||||
padding: 2rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Blog content styling */
|
|
||||||
.blog-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
|
||||||
gap: 2rem;
|
|
||||||
margin-top: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Digital Garden */
|
|
||||||
.digital-garden-intro {
|
|
||||||
color: var(--text-secondary);
|
|
||||||
font-size: 1.1rem;
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
max-width: 800px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Responsive adjustments */
|
|
||||||
@media (max-width: 1024px) {
|
|
||||||
.hero {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-content {
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
header {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
gap: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
nav {
|
|
||||||
width: 100%;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-description {
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.blog-grid {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
gap: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero {
|
|
||||||
padding: 2rem 1rem;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-content {
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-title {
|
|
||||||
font-size: clamp(1.5rem, 6vw, 2.5rem);
|
|
||||||
}
|
|
||||||
|
|
||||||
.terminal-box {
|
|
||||||
width: 100%;
|
|
||||||
min-height: 300px;
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer-grid {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
gap: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.post-card {
|
|
||||||
min-height: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.featured-grid {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
|
|
||||||
.post-metadata {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
gap: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.post-info {
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.post-tags {
|
|
||||||
margin-top: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Add mobile menu functionality */
|
|
||||||
.mobile-menu-btn {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
nav.desktop-nav {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
nav.mobile-nav-active {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100vh;
|
|
||||||
background: var(--bg-primary);
|
|
||||||
z-index: 1000;
|
|
||||||
padding: 2rem;
|
|
||||||
align-items: flex-start;
|
|
||||||
}
|
|
||||||
|
|
||||||
nav.mobile-nav-active a {
|
|
||||||
font-size: 1.5rem;
|
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mobile-menu-close {
|
|
||||||
align-self: flex-end;
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
color: var(--text-primary);
|
|
||||||
font-size: 1.5rem;
|
|
||||||
cursor: pointer;
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 640px) {
|
|
||||||
.mobile-menu-btn {
|
|
||||||
display: block;
|
|
||||||
position: absolute;
|
|
||||||
right: 1.5rem;
|
|
||||||
top: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
nav {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.blog-grid {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Additional mobile optimizations for very small screens */
|
|
||||||
@media (max-width: 480px) {
|
|
||||||
:root {
|
|
||||||
--container-padding: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.post-title {
|
|
||||||
font-size: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.post-card {
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.post-image {
|
|
||||||
height: 160px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-title {
|
|
||||||
font-size: 1.5rem;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-subtitle {
|
|
||||||
font-size: 0.8rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cta-button {
|
|
||||||
width: 100%;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.post-content {
|
|
||||||
font-size: 1rem;
|
|
||||||
line-height: 1.6;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Adjust footer layout */
|
|
||||||
.footer-col {
|
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer-bottom {
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 1rem;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Touch device optimizations */
|
|
||||||
@media (hover: none) {
|
|
||||||
.post-card:hover {
|
|
||||||
transform: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cta-button:hover {
|
|
||||||
transform: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
nav a:hover::after {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.social-link:hover {
|
|
||||||
transform: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.post-tag:hover {
|
|
||||||
transform: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Increase tap target sizes */
|
|
||||||
nav a {
|
|
||||||
padding: 0.5rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer-links li {
|
|
||||||
margin-bottom: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer-links a {
|
|
||||||
padding: 0.5rem 0;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.post-footer {
|
|
||||||
padding: 1rem;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,566 +0,0 @@
|
||||||
---
|
|
||||||
import { getCollection } from 'astro:content';
|
|
||||||
import type { CollectionEntry } from 'astro:content';
|
|
||||||
import BaseLayout from '../layouts/BaseLayout.astro';
|
|
||||||
import DigitalGardenGraph from '../components/DigitalGardenGraph.astro';
|
|
||||||
|
|
||||||
type Post = CollectionEntry<'posts'>;
|
|
||||||
type Config = CollectionEntry<'configurations'>;
|
|
||||||
type Project = CollectionEntry<'projects'>;
|
|
||||||
|
|
||||||
// Get all blog posts (excluding configurations and specific guides)
|
|
||||||
const posts = (await getCollection('blog'))
|
|
||||||
.filter(item =>
|
|
||||||
!item.slug.startsWith('configurations/') &&
|
|
||||||
!item.slug.startsWith('projects/') &&
|
|
||||||
!item.data.category?.toLowerCase().includes('configuration') &&
|
|
||||||
!item.slug.includes('setup-guide') &&
|
|
||||||
!item.slug.includes('config')
|
|
||||||
)
|
|
||||||
.sort((a, b) => new Date(b.data.pubDate || 0).valueOf() - new Date(a.data.pubDate || 0).valueOf());
|
|
||||||
|
|
||||||
// Get configuration posts
|
|
||||||
const configurations = (await getCollection('blog'))
|
|
||||||
.filter(item =>
|
|
||||||
item.slug.startsWith('configurations/') ||
|
|
||||||
item.data.category?.toLowerCase().includes('configuration') ||
|
|
||||||
item.slug.includes('setup-guide') ||
|
|
||||||
item.slug.includes('config') ||
|
|
||||||
item.slug.includes('monitoring') ||
|
|
||||||
item.slug.includes('server') ||
|
|
||||||
item.slug.includes('tunnel')
|
|
||||||
)
|
|
||||||
.sort((a, b) => new Date(b.data.pubDate || 0).valueOf() - new Date(a.data.pubDate || 0).valueOf());
|
|
||||||
|
|
||||||
// Get project posts
|
|
||||||
const projects = (await getCollection('blog'))
|
|
||||||
.filter(item =>
|
|
||||||
item.slug.startsWith('projects/') ||
|
|
||||||
item.data.category?.toLowerCase().includes('project')
|
|
||||||
)
|
|
||||||
.sort((a, b) => new Date(b.data.pubDate || 0).valueOf() - new Date(a.data.pubDate || 0).valueOf());
|
|
||||||
---
|
|
||||||
|
|
||||||
<BaseLayout title="LaForce IT - Home Lab & DevOps Insights">
|
|
||||||
<!-- Hero section -->
|
|
||||||
<section class="hero">
|
|
||||||
<div class="hero-content">
|
|
||||||
<div class="hero-subtitle">Home Lab & DevOps</div>
|
|
||||||
<h1 class="hero-title">Exploring <span>advanced infrastructure</span> and automation</h1>
|
|
||||||
<p class="hero-description">
|
|
||||||
Join me on a journey through enterprise-grade home lab setups, Kubernetes deployments, and DevOps best practices for the modern tech enthusiast.
|
|
||||||
</p>
|
|
||||||
<div class="social-links-hero">
|
|
||||||
<a href="https://github.com/keyargo" target="_blank" rel="noopener noreferrer" class="social-link-hero github">
|
|
||||||
<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>
|
|
||||||
</a>
|
|
||||||
<a href="https://linkedin.com/in/danlaforce" target="_blank" rel="noopener noreferrer" class="social-link-hero linkedin">
|
|
||||||
<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="M16 8a6 6 0 0 1 6 6v7h-4v-7a2 2 0 0 0-2-2 2 2 0 0 0-2 2v7h-4v-7a6 6 0 0 1 6-6z"></path><rect x="2" y="9" width="4" height="12"></rect><circle cx="4" cy="4" r="2"></circle></svg>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<a href="#posts" class="cta-button">
|
|
||||||
Explore Latest Posts
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="terminal-box">
|
|
||||||
<div class="terminal-header">
|
|
||||||
<div class="terminal-dots">
|
|
||||||
<div class="terminal-dot terminal-dot-red"></div>
|
|
||||||
<div class="terminal-dot terminal-dot-yellow"></div>
|
|
||||||
<div class="terminal-dot terminal-dot-green"></div>
|
|
||||||
</div>
|
|
||||||
<div class="terminal-title">argobox:~/homelab</div>
|
|
||||||
</div>
|
|
||||||
<div class="terminal-content">
|
|
||||||
<div class="terminal-line">
|
|
||||||
<span class="terminal-prompt">$</span>
|
|
||||||
<span class="terminal-command">kubectl get nodes</span>
|
|
||||||
</div>
|
|
||||||
<div class="terminal-output">
|
|
||||||
NAME STATUS ROLES AGE VERSION<br>
|
|
||||||
argobox Ready <none> 47d v1.28.3+k3s1<br>
|
|
||||||
argobox-lite Ready control-plane,master 47d v1.28.3+k3s1
|
|
||||||
</div>
|
|
||||||
<div class="terminal-line">
|
|
||||||
<span class="terminal-prompt">$</span>
|
|
||||||
<span class="terminal-command">helm list -A</span>
|
|
||||||
</div>
|
|
||||||
<div class="terminal-output">
|
|
||||||
NAME NAMESPACE REVISION STATUS CHART<br>
|
|
||||||
cloudnative-pg postgres 1 deployed cloudnative-pg-0.18.0<br>
|
|
||||||
prometheus monitoring 2 deployed kube-prometheus-stack-51.2.0
|
|
||||||
</div>
|
|
||||||
<div class="terminal-line">
|
|
||||||
<span class="terminal-prompt">$</span>
|
|
||||||
<span class="terminal-command terminal-typing">cloudflared tunnel status</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<!-- Digital Garden Visualization -->
|
|
||||||
<section class="container">
|
|
||||||
<h2 class="section-title">My Digital Garden</h2>
|
|
||||||
<p class="digital-garden-intro">
|
|
||||||
This blog functions as my personal digital garden - a collection of interconnected ideas, guides, and projects.
|
|
||||||
Browse through the visualization below to see how different concepts relate to each other.
|
|
||||||
</p>
|
|
||||||
<DigitalGardenGraph />
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<!-- Main content sections -->
|
|
||||||
<main class="container">
|
|
||||||
<section id="posts" class="mb-16">
|
|
||||||
<h2 class="section-title">Latest Posts</h2>
|
|
||||||
<div class="blog-grid">
|
|
||||||
{posts.map((post) => (
|
|
||||||
<article class="post-card">
|
|
||||||
{post.data.heroImage ? (
|
|
||||||
<img
|
|
||||||
width={720}
|
|
||||||
height={360}
|
|
||||||
src={post.data.heroImage}
|
|
||||||
alt=""
|
|
||||||
class="post-image"
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<img
|
|
||||||
width={720}
|
|
||||||
height={360}
|
|
||||||
src="/blog/images/placeholders/default.jpg"
|
|
||||||
alt=""
|
|
||||||
class="post-image"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<div class="post-content">
|
|
||||||
<div class="post-meta">
|
|
||||||
<time datetime={post.data.pubDate ? new Date(post.data.pubDate).toISOString() : ''}>
|
|
||||||
{post.data.pubDate ? new Date(post.data.pubDate).toLocaleDateString('en-us', {
|
|
||||||
year: 'numeric',
|
|
||||||
month: 'short',
|
|
||||||
day: 'numeric',
|
|
||||||
}) : 'No date'}
|
|
||||||
</time>
|
|
||||||
{post.data.category && (
|
|
||||||
<span class="post-category">
|
|
||||||
{post.data.category}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<h3 class="post-title">
|
|
||||||
<a href={`/blog/${post.slug}/`}>{post.data.title}</a>
|
|
||||||
{post.data.draft && <span class="ml-2 px-2 py-1 bg-gray-200 text-gray-700 text-xs rounded">Draft</span>}
|
|
||||||
</h3>
|
|
||||||
<p class="post-excerpt">{post.data.description}</p>
|
|
||||||
<div class="post-footer">
|
|
||||||
<span class="post-read-time">{post.data.readTime || '5 min read'}</span>
|
|
||||||
<a href={`/blog/${post.slug}/`} class="read-more">Read More</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section id="configurations" class="mb-16">
|
|
||||||
<h2 class="section-title">Configurations</h2>
|
|
||||||
<div class="blog-grid">
|
|
||||||
{configurations.map((config) => (
|
|
||||||
<article class="post-card">
|
|
||||||
{config.data.heroImage ? (
|
|
||||||
<img
|
|
||||||
width={720}
|
|
||||||
height={360}
|
|
||||||
src={config.data.heroImage}
|
|
||||||
alt=""
|
|
||||||
class="post-image"
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<img
|
|
||||||
width={720}
|
|
||||||
height={360}
|
|
||||||
src="/blog/images/placeholders/default.jpg"
|
|
||||||
alt=""
|
|
||||||
class="post-image"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<div class="post-content">
|
|
||||||
<div class="post-meta">
|
|
||||||
<time datetime={config.data.pubDate ? new Date(config.data.pubDate).toISOString() : ''}>
|
|
||||||
{config.data.pubDate ? new Date(config.data.pubDate).toLocaleDateString('en-us', {
|
|
||||||
year: 'numeric',
|
|
||||||
month: 'short',
|
|
||||||
day: 'numeric',
|
|
||||||
}) : 'No date'}
|
|
||||||
</time>
|
|
||||||
{config.data.category && (
|
|
||||||
<span class="post-category">
|
|
||||||
{config.data.category}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<h3 class="post-title">
|
|
||||||
<a href={`/blog/${config.slug}/`}>{config.data.title}</a>
|
|
||||||
{config.data.draft && <span class="ml-2 px-2 py-1 bg-gray-200 text-gray-700 text-xs rounded">Draft</span>}
|
|
||||||
</h3>
|
|
||||||
<p class="post-excerpt">{config.data.description}</p>
|
|
||||||
<div class="post-footer">
|
|
||||||
<span class="post-read-time">{config.data.readTime || '5 min read'}</span>
|
|
||||||
<a href={`/blog/${config.slug}/`} class="read-more">Read More</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section id="projects" class="mb-16">
|
|
||||||
<h2 class="section-title">Projects</h2>
|
|
||||||
<div class="blog-grid">
|
|
||||||
{projects.map((project) => (
|
|
||||||
<article class="post-card">
|
|
||||||
{project.data.heroImage ? (
|
|
||||||
<img
|
|
||||||
width={720}
|
|
||||||
height={360}
|
|
||||||
src={project.data.heroImage}
|
|
||||||
alt=""
|
|
||||||
class="post-image"
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<img
|
|
||||||
width={720}
|
|
||||||
height={360}
|
|
||||||
src="/blog/images/placeholders/default.jpg"
|
|
||||||
alt=""
|
|
||||||
class="post-image"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<div class="post-content">
|
|
||||||
<div class="post-meta">
|
|
||||||
<time datetime={project.data.pubDate ? new Date(project.data.pubDate).toISOString() : ''}>
|
|
||||||
{project.data.pubDate ? new Date(project.data.pubDate).toLocaleDateString('en-us', {
|
|
||||||
year: 'numeric',
|
|
||||||
month: 'short',
|
|
||||||
day: 'numeric',
|
|
||||||
}) : 'No date'}
|
|
||||||
</time>
|
|
||||||
{project.data.category && (
|
|
||||||
<span class="post-category">
|
|
||||||
{project.data.category}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<h3 class="post-title">
|
|
||||||
<a href={`/blog/${project.slug}/`}>{project.data.title}</a>
|
|
||||||
{project.data.draft && <span class="ml-2 px-2 py-1 bg-gray-200 text-gray-700 text-xs rounded">Draft</span>}
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
{project.data.technologies && (
|
|
||||||
<div class="mb-2 flex flex-wrap gap-2">
|
|
||||||
{project.data.technologies.map((tech) => (
|
|
||||||
<span class="post-category">
|
|
||||||
{tech}
|
|
||||||
</span>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<p class="post-excerpt">{project.data.description}</p>
|
|
||||||
<div class="post-footer">
|
|
||||||
<div class="flex gap-4">
|
|
||||||
{project.data.github && (
|
|
||||||
<a href={project.data.github} target="_blank" rel="noopener noreferrer" class="read-more">
|
|
||||||
GitHub
|
|
||||||
</a>
|
|
||||||
)}
|
|
||||||
{project.data.live && (
|
|
||||||
<a href={project.data.live} target="_blank" rel="noopener noreferrer" class="read-more">
|
|
||||||
Live Demo
|
|
||||||
</a>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<a href={`/blog/${project.slug}/`} class="read-more">View Project</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<!-- Featured section -->
|
|
||||||
<section class="featured-section">
|
|
||||||
<div class="featured-grid">
|
|
||||||
<div class="featured-content">
|
|
||||||
<div class="featured-subtitle">Featured Project</div>
|
|
||||||
<h2 class="featured-title">ArgoBox <span>Home Lab Architecture</span></h2>
|
|
||||||
<p class="featured-description">
|
|
||||||
A complete enterprise-grade home infrastructure built on Kubernetes, featuring high availability, zero-trust networking, and fully automated deployments.
|
|
||||||
</p>
|
|
||||||
<ul class="featured-list">
|
|
||||||
<li class="featured-list-item">
|
|
||||||
<div class="featured-list-icon">✓</div>
|
|
||||||
<div>Multi-node K3s cluster with automatic failover</div>
|
|
||||||
</li>
|
|
||||||
<li class="featured-list-item">
|
|
||||||
<div class="featured-list-icon">✓</div>
|
|
||||||
<div>Gitea + Flux CD for GitOps-based continuous deployment</div>
|
|
||||||
</li>
|
|
||||||
<li class="featured-list-item">
|
|
||||||
<div class="featured-list-icon">✓</div>
|
|
||||||
<div>Cloudflare Tunnels for secure, zero-trust remote access</div>
|
|
||||||
</li>
|
|
||||||
<li class="featured-list-item">
|
|
||||||
<div class="featured-list-icon">✓</div>
|
|
||||||
<div>Synology NAS integration with Kubernetes volumes</div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<a href="#" class="cta-button">
|
|
||||||
View Project Details
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<!-- About Me Section -->
|
|
||||||
<section class="about-section mb-16">
|
|
||||||
<h2 class="section-title">About Me</h2>
|
|
||||||
<div class="about-content">
|
|
||||||
<div class="about-text">
|
|
||||||
<p>
|
|
||||||
Hi, I'm Daniel LaForce, a passionate DevOps and infrastructure engineer with a focus on Kubernetes,
|
|
||||||
automation, and cloud technologies. When I'm not working on enterprise systems, I'm building and
|
|
||||||
refining my home lab environment to test and learn new technologies.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
This site serves as both my technical blog and digital garden - a place to share what I've learned
|
|
||||||
and document my ongoing projects. Feel free to connect with me on GitHub or LinkedIn!
|
|
||||||
</p>
|
|
||||||
<div class="social-links">
|
|
||||||
<a href="https://github.com/keyargo" target="_blank" rel="noopener noreferrer" class="social-link">
|
|
||||||
<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>
|
|
||||||
<span>GitHub</span>
|
|
||||||
</a>
|
|
||||||
<a href="https://linkedin.com/in/danlaforce" target="_blank" rel="noopener noreferrer" class="social-link">
|
|
||||||
<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="M16 8a6 6 0 0 1 6 6v7h-4v-7a2 2 0 0 0-2-2 2 2 0 0 0-2 2v7h-4v-7a6 6 0 0 1 6-6z"></path><rect x="2" y="9" width="4" height="12"></rect><circle cx="4" cy="4" r="2"></circle></svg>
|
|
||||||
<span>LinkedIn</span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</main>
|
|
||||||
</BaseLayout>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.featured-section {
|
|
||||||
margin-top: 4rem;
|
|
||||||
background: var(--card-bg);
|
|
||||||
border-radius: 1rem;
|
|
||||||
border: 1px solid var(--card-border);
|
|
||||||
padding: 2rem;
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.featured-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
gap: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.featured-subtitle {
|
|
||||||
font-family: 'JetBrains Mono', monospace;
|
|
||||||
color: var(--accent-primary);
|
|
||||||
font-size: 0.9rem;
|
|
||||||
letter-spacing: 2px;
|
|
||||||
text-transform: uppercase;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.featured-title {
|
|
||||||
font-size: clamp(1.8rem, 4vw, 2.5rem);
|
|
||||||
line-height: 1.2;
|
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.featured-title span {
|
|
||||||
background: linear-gradient(90deg, var(--accent-primary), var(--accent-tertiary));
|
|
||||||
-webkit-background-clip: text;
|
|
||||||
background-clip: text;
|
|
||||||
color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.featured-description {
|
|
||||||
color: var(--text-secondary);
|
|
||||||
font-size: 1.1rem;
|
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
max-width: 600px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.featured-list {
|
|
||||||
list-style: none;
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.featured-list-item {
|
|
||||||
display: flex;
|
|
||||||
margin-bottom: 0.75rem;
|
|
||||||
align-items: flex-start;
|
|
||||||
}
|
|
||||||
|
|
||||||
.featured-list-icon {
|
|
||||||
color: var(--accent-primary);
|
|
||||||
margin-right: 1rem;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mb-16 {
|
|
||||||
margin-bottom: 4rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.flex {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.flex-wrap {
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gap-2 {
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gap-4 {
|
|
||||||
gap: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ml-2 {
|
|
||||||
margin-left: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.px-2 {
|
|
||||||
padding-left: 0.5rem;
|
|
||||||
padding-right: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.py-1 {
|
|
||||||
padding-top: 0.25rem;
|
|
||||||
padding-bottom: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bg-gray-200 {
|
|
||||||
background-color: rgba(226, 232, 240, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-gray-700 {
|
|
||||||
color: #94a3b8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-xs {
|
|
||||||
font-size: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rounded {
|
|
||||||
border-radius: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.social-links-hero {
|
|
||||||
display: flex;
|
|
||||||
gap: 1rem;
|
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.social-link-hero {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background-color: var(--card-bg);
|
|
||||||
color: var(--text-primary);
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
border: 1px solid var(--card-border);
|
|
||||||
}
|
|
||||||
|
|
||||||
.social-link-hero:hover {
|
|
||||||
transform: translateY(-3px);
|
|
||||||
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.social-link-hero.github:hover {
|
|
||||||
background-color: #24292e;
|
|
||||||
border-color: #24292e;
|
|
||||||
}
|
|
||||||
|
|
||||||
.social-link-hero.linkedin:hover {
|
|
||||||
background-color: #0077b5;
|
|
||||||
border-color: #0077b5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.about-section {
|
|
||||||
background: var(--card-bg);
|
|
||||||
border-radius: 1rem;
|
|
||||||
border: 1px solid var(--card-border);
|
|
||||||
padding: 2rem;
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.about-content {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
gap: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.about-text {
|
|
||||||
color: var(--text-secondary);
|
|
||||||
font-size: 1.1rem;
|
|
||||||
line-height: 1.6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.about-text p {
|
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.social-links {
|
|
||||||
display: flex;
|
|
||||||
gap: 1.5rem;
|
|
||||||
margin-top: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.social-link {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
color: var(--text-primary);
|
|
||||||
text-decoration: none;
|
|
||||||
padding: 0.5rem 1rem;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
background-color: rgba(226, 232, 240, 0.05);
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.social-link:hover {
|
|
||||||
background-color: rgba(226, 232, 240, 0.1);
|
|
||||||
transform: translateY(-2px);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
|
||||||
.featured-grid {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
|
|
||||||
.about-content {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 1024px) {
|
|
||||||
.about-content {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
// Footer.astro
|
// src/components/Footer.astro
|
||||||
// High-quality footer with navigation, social links and additional elements
|
// High-quality footer with navigation, social links and additional elements
|
||||||
|
|
||||||
const currentYear = new Date().getFullYear();
|
const currentYear = new Date().getFullYear();
|
||||||
|
@ -9,11 +9,11 @@ const categories = [
|
||||||
{
|
{
|
||||||
title: 'Technology',
|
title: 'Technology',
|
||||||
links: [
|
links: [
|
||||||
{ name: 'Kubernetes', path: '/blog/category/kubernetes' },
|
{ name: 'Kubernetes', path: '/categories/kubernetes' },
|
||||||
{ name: 'Docker', path: '/blog/category/docker' },
|
{ name: 'Docker', path: '/categories/docker' },
|
||||||
{ name: 'DevOps', path: '/blog/category/devops' },
|
{ name: 'DevOps', path: '/categories/devops' },
|
||||||
{ name: 'Networking', path: '/blog/category/networking' },
|
{ name: 'Networking', path: '/categories/networking' },
|
||||||
{ name: 'Storage', path: '/blog/category/storage' }
|
{ name: 'Storage', path: '/categories/storage' }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -29,8 +29,8 @@ const categories = [
|
||||||
{
|
{
|
||||||
title: 'Projects',
|
title: 'Projects',
|
||||||
links: [
|
links: [
|
||||||
{ name: 'HomeLab Setup', path: '/projects/homelab' },
|
{ name: 'HomeLab Setup', url: 'https://argobox.com' },
|
||||||
{ name: 'Tech Stack', path: '/projects/tech-stack' },
|
{ name: 'Tech Stack', url: 'https://argobox.com/#services' },
|
||||||
{ name: 'Github Repos', path: '/projects/github' },
|
{ name: 'Github Repos', path: '/projects/github' },
|
||||||
{ name: 'Live Services', path: '/projects/services' },
|
{ name: 'Live Services', path: '/projects/services' },
|
||||||
{ name: 'Obsidian Templates', path: '/projects/obsidian' }
|
{ name: 'Obsidian Templates', path: '/projects/obsidian' }
|
||||||
|
@ -42,7 +42,7 @@ const categories = [
|
||||||
const socialLinks = [
|
const socialLinks = [
|
||||||
{
|
{
|
||||||
name: 'GitHub',
|
name: 'GitHub',
|
||||||
url: 'https://github.com/yourusername',
|
url: 'https://github.com/KeyArgo/',
|
||||||
icon: '<path fill-rule="evenodd" clip-rule="evenodd" d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385c.6.105.825-.255.825-.57c0-.285-.015-1.23-.015-2.235c-3.015.555-3.795-.735-4.035-1.41c-.135-.345-.72-1.41-1.23-1.695c-.42-.225-1.02-.78-.015-.795c.945-.015 1.62.87 1.845 1.23c1.08 1.815 2.805 1.305 3.495.99c.105-.78.42-1.305.765-1.605c-2.67-.3-5.46-1.335-5.46-5.925c0-1.305.465-2.385 1.23-3.225c-.12-.3-.54-1.53.12-3.18c0 0 1.005-.315 3.3 1.23c.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23c.66 1.65.24 2.88.12 3.18c.765.84 1.23 1.905 1.23 3.225c0 4.605-2.805 5.625-5.475 5.925c.435.375.81 1.095.81 2.22c0 1.605-.015 2.895-.015 3.3c0 .315.225.69.825.57A12.02 12.02 0 0 0 24 12c0-6.63-5.37-12-12-12z" />'
|
icon: '<path fill-rule="evenodd" clip-rule="evenodd" d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385c.6.105.825-.255.825-.57c0-.285-.015-1.23-.015-2.235c-3.015.555-3.795-.735-4.035-1.41c-.135-.345-.72-1.41-1.23-1.695c-.42-.225-1.02-.78-.015-.795c.945-.015 1.62.87 1.845 1.23c1.08 1.815 2.805 1.305 3.495.99c.105-.78.42-1.305.765-1.605c-2.67-.3-5.46-1.335-5.46-5.925c0-1.305.465-2.385 1.23-3.225c-.12-.3-.54-1.53.12-3.18c0 0 1.005-.315 3.3 1.23c.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23c.66 1.65.24 2.88.12 3.18c.765.84 1.23 1.905 1.23 3.225c0 4.605-2.805 5.625-5.475 5.925c.435.375.81 1.095.81 2.22c0 1.605-.015 2.895-.015 3.3c0 .315.225.69.825.57A12.02 12.02 0 0 0 24 12c0-6.63-5.37-12-12-12z" />'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -130,7 +130,14 @@ const services = [
|
||||||
<ul class="footer-links">
|
<ul class="footer-links">
|
||||||
{category.links.map(link => (
|
{category.links.map(link => (
|
||||||
<li>
|
<li>
|
||||||
<a href={link.path} class="footer-link">{link.name}</a>
|
<a
|
||||||
|
href={link.url || link.path}
|
||||||
|
class="footer-link"
|
||||||
|
target={link.url ? "_blank" : undefined}
|
||||||
|
rel={link.url ? "noopener noreferrer" : undefined}
|
||||||
|
>
|
||||||
|
{link.name}
|
||||||
|
</a>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
@ -309,7 +309,13 @@ const nodeTypeCounts = {
|
||||||
{ selector: '.filtered', style: { 'background-color': 'data(color)', 'border-color': '#FFFFFF', 'border-width': '2px', 'color': '#FFFFFF', 'text-background-opacity': 0.8, 'opacity': 0.8, 'z-index': 15 } },
|
{ selector: '.filtered', style: { 'background-color': 'data(color)', 'border-color': '#FFFFFF', 'border-width': '2px', 'color': '#FFFFFF', 'text-background-opacity': 0.8, 'opacity': 0.8, 'z-index': 15 } },
|
||||||
{ selector: '.faded', style: { 'opacity': 0.15, 'text-opacity': 0.3, 'background-opacity': 0.3, 'z-index': 1 } },
|
{ selector: '.faded', style: { 'opacity': 0.15, 'text-opacity': 0.3, 'background-opacity': 0.3, 'z-index': 1 } },
|
||||||
{ selector: 'node:selected', style: { 'border-width': '4px', 'border-color': '#FFFFFF', 'border-opacity': 1, 'background-color': 'data(color)', 'text-opacity': 1, 'color': '#FFFFFF', 'z-index': 30 } },
|
{ selector: 'node:selected', style: { 'border-width': '4px', 'border-color': '#FFFFFF', 'border-opacity': 1, 'background-color': 'data(color)', 'text-opacity': 1, 'color': '#FFFFFF', 'z-index': 30 } },
|
||||||
{ selector: 'edge:selected', style: { 'width': 'mapData(weight, 1, 10, 2, 6)', 'line-color': '#FFFFFF', 'opacity': 1, 'z-index': 30 } }
|
{ selector: 'edge:selected', style: { 'width': 'mapData(weight, 1, 10, 2, 6)', 'line-color': '#FFFFFF', 'opacity': 1, 'z-index': 30 } },
|
||||||
|
// Styles for dragging effects
|
||||||
|
{ selector: 'node.grabbed', style: {
|
||||||
|
'border-width': '3px',
|
||||||
|
'border-color': '#FFFFFF',
|
||||||
|
'border-opacity': 1
|
||||||
|
}}
|
||||||
],
|
],
|
||||||
// Update layout for better visualization of post-tag connections
|
// Update layout for better visualization of post-tag connections
|
||||||
layout: {
|
layout: {
|
||||||
|
@ -537,11 +543,20 @@ const nodeTypeCounts = {
|
||||||
// Zoom controls
|
// Zoom controls
|
||||||
document.getElementById('zoom-in')?.addEventListener('click', () => cy.zoom(cy.zoom() * 1.2));
|
document.getElementById('zoom-in')?.addEventListener('click', () => cy.zoom(cy.zoom() * 1.2));
|
||||||
document.getElementById('zoom-out')?.addEventListener('click', () => cy.zoom(cy.zoom() / 1.2));
|
document.getElementById('zoom-out')?.addEventListener('click', () => cy.zoom(cy.zoom() / 1.2));
|
||||||
|
|
||||||
|
// Reset button functionality
|
||||||
document.getElementById('reset-graph')?.addEventListener('click', () => {
|
document.getElementById('reset-graph')?.addEventListener('click', () => {
|
||||||
cy.fit(null, 30);
|
// Reset zoom and position
|
||||||
|
cy.fit(null, 30);
|
||||||
|
// Reset all filters and highlighting
|
||||||
cy.elements().removeClass('faded highlighted filtered');
|
cy.elements().removeClass('faded highlighted filtered');
|
||||||
|
// Reset active filter buttons
|
||||||
const allFilterButton = document.querySelector('.graph-filter[data-filter="all"]');
|
const allFilterButton = document.querySelector('.graph-filter[data-filter="all"]');
|
||||||
if (allFilterButton) allFilterButton.click();
|
if (allFilterButton) allFilterButton.click();
|
||||||
|
// Close node details panel if open
|
||||||
|
if (nodeDetailsEl) nodeDetailsEl.classList.remove('active');
|
||||||
|
// Reset any selected nodes
|
||||||
|
cy.$(':selected').unselect();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add mouse wheel zoom controls
|
// Add mouse wheel zoom controls
|
||||||
|
@ -561,6 +576,41 @@ const nodeTypeCounts = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add Obsidian-like dragging behavior
|
||||||
|
// When dragging a node, connected nodes follow with a damping effect
|
||||||
|
cy.on('drag', 'node', function(e) {
|
||||||
|
const node = e.target;
|
||||||
|
const neighbors = node.neighborhood('node');
|
||||||
|
|
||||||
|
// Add grabbed class for styling
|
||||||
|
node.addClass('grabbed');
|
||||||
|
|
||||||
|
if (neighbors.length > 0) {
|
||||||
|
neighbors.forEach(neighbor => {
|
||||||
|
// Don't move nodes that are being manually dragged by the user
|
||||||
|
if (!neighbor.grabbed()) {
|
||||||
|
// Calculate the position to move the neighbor node
|
||||||
|
// This creates a "pull" effect where neighbors follow but with resistance
|
||||||
|
// The 0.2 factor controls how much the neighbor follows (smaller = less movement)
|
||||||
|
const damping = 0.2;
|
||||||
|
const dx = node.position('x') - neighbor.position('x');
|
||||||
|
const dy = node.position('y') - neighbor.position('y');
|
||||||
|
|
||||||
|
// Apply the position change with damping
|
||||||
|
neighbor.position({
|
||||||
|
x: neighbor.position('x') + dx * damping,
|
||||||
|
y: neighbor.position('y') + dy * damping
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
cy.on('dragfree', 'node', function(e) {
|
||||||
|
// Remove grabbed class when drag ends
|
||||||
|
e.target.removeClass('grabbed');
|
||||||
|
});
|
||||||
|
|
||||||
// Connect search input if it exists
|
// Connect search input if it exists
|
||||||
const searchInput = document.getElementById('search-input');
|
const searchInput = document.getElementById('search-input');
|
||||||
if (searchInput) {
|
if (searchInput) {
|
||||||
|
@ -1107,4 +1157,5 @@ const nodeTypeCounts = {
|
||||||
padding: 0.3rem 0.6rem;
|
padding: 0.3rem 0.6rem;
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
</style>
|
|
@ -1,17 +1,21 @@
|
||||||
// src/pages/search-index.json.js
|
// src/pages/search-index.json.js
|
||||||
// Generates a JSON file with all posts for client-side search
|
// Generates a JSON file with content from all collections for site-wide search
|
||||||
|
|
||||||
import { getCollection } from 'astro:content';
|
import { getCollection } from 'astro:content';
|
||||||
|
|
||||||
export async function get() {
|
export async function get() {
|
||||||
// Get all posts
|
// Get content from all collections
|
||||||
const allPosts = await getCollection('posts', ({ data }) => {
|
const posts = await getCollection('posts', ({ data }) => {
|
||||||
// Exclude draft posts in production
|
// Exclude draft posts in production
|
||||||
return import.meta.env.PROD ? !data.draft : true;
|
return import.meta.env.PROD ? !data.draft : true;
|
||||||
});
|
}).catch(() => []);
|
||||||
|
|
||||||
|
const projects = await getCollection('projects').catch(() => []);
|
||||||
|
const configurations = await getCollection('configurations').catch(() => []);
|
||||||
|
const externalPosts = await getCollection('external-posts').catch(() => []);
|
||||||
|
|
||||||
// Transform posts into search-friendly format
|
// Transform posts into search-friendly format
|
||||||
const searchablePosts = allPosts.map(post => ({
|
const searchablePosts = posts.map(post => ({
|
||||||
slug: post.slug,
|
slug: post.slug,
|
||||||
title: post.data.title,
|
title: post.data.title,
|
||||||
description: post.data.description || '',
|
description: post.data.description || '',
|
||||||
|
@ -19,14 +23,60 @@ export async function get() {
|
||||||
category: post.data.category || 'Uncategorized',
|
category: post.data.category || 'Uncategorized',
|
||||||
tags: post.data.tags || [],
|
tags: post.data.tags || [],
|
||||||
readTime: post.data.readTime || '5 min read',
|
readTime: post.data.readTime || '5 min read',
|
||||||
|
type: 'post',
|
||||||
|
url: `/posts/${post.slug}/`
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
// Transform projects
|
||||||
|
const searchableProjects = projects.map(project => ({
|
||||||
|
slug: project.slug,
|
||||||
|
title: project.data.title,
|
||||||
|
description: project.data.description || '',
|
||||||
|
pubDate: project.data.pubDate ? new Date(project.data.pubDate).toISOString() : '',
|
||||||
|
category: project.data.category || 'Projects',
|
||||||
|
tags: project.data.tags || [],
|
||||||
|
type: 'project',
|
||||||
|
url: `/projects/${project.slug}/`
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Transform configurations
|
||||||
|
const searchableConfigurations = configurations.map(config => ({
|
||||||
|
slug: config.slug,
|
||||||
|
title: config.data.title,
|
||||||
|
description: config.data.description || '',
|
||||||
|
pubDate: config.data.pubDate ? new Date(config.data.pubDate).toISOString() : '',
|
||||||
|
category: config.data.category || 'Configurations',
|
||||||
|
tags: config.data.tags || [],
|
||||||
|
type: 'configuration',
|
||||||
|
url: `/configurations/${config.slug}/`
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Transform external posts
|
||||||
|
const searchableExternalPosts = externalPosts.map(post => ({
|
||||||
|
slug: post.slug,
|
||||||
|
title: post.data.title,
|
||||||
|
description: post.data.description || '',
|
||||||
|
pubDate: post.data.pubDate ? new Date(post.data.pubDate).toISOString() : '',
|
||||||
|
category: post.data.category || 'External',
|
||||||
|
tags: post.data.tags || [],
|
||||||
|
type: 'external',
|
||||||
|
url: post.data.url // Use the external URL directly
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Combine all searchable content
|
||||||
|
const allSearchableContent = [
|
||||||
|
...searchablePosts,
|
||||||
|
...searchableProjects,
|
||||||
|
...searchableConfigurations,
|
||||||
|
...searchableExternalPosts
|
||||||
|
];
|
||||||
|
|
||||||
// Return JSON
|
// Return JSON
|
||||||
return {
|
return {
|
||||||
body: JSON.stringify(searchablePosts),
|
body: JSON.stringify(allSearchableContent),
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'Cache-Control': 'max-age=3600'
|
'Cache-Control': 'max-age=3600'
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
}
|
Loading…
Reference in New Issue