laforceit-blog/src/components/Header.astro

439 lines
12 KiB
Plaintext

---
// Header.astro
// Primary navigation component with premium design elements
// Get current path to highlight active nav item
const pathname = new URL(Astro.request.url).pathname;
const currentPath = pathname.split('/')[1]; // Get the first path segment
// Define navigation items
const navItems = [
{ name: 'Home', path: '/' },
{ name: 'Blog', path: '/blog/' },
{ name: 'Projects', path: '/projects/' },
{ name: 'Home Lab', path: '/homelab/' },
{ name: 'Resources', path: '/resources/' },
{ name: 'About', path: '/about/' },
{ name: 'Contact', path: '/contact/' }
];
---
<header class="site-header">
<div class="nebula-bg"></div>
<div class="container">
<div class="header-container">
<a href="/" class="logo">
<div class="logo-symbol">
<span class="logo-text">LF</span>
<div class="logo-glow"></div>
</div>
<div class="logo-text-container">
<span class="logo-name">LaForceIT</span>
<span class="logo-tagline">Infrastructure & Automation</span>
</div>
</a>
<nav>
<div class="main-nav">
{navItems.map((item) => (
<a
href={item.path}
class={`nav-link ${currentPath === item.path.replace(/\//g, '') ? 'active' : ''}`}
>
{item.name}
<div class="nav-highlight"></div>
</a>
))}
</div>
<div class="header-actions">
<button id="theme-toggle" class="theme-toggle" aria-label="Toggle dark mode">
<svg class="icon-sun" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="5"></circle>
<line x1="12" y1="1" x2="12" y2="3"></line>
<line x1="12" y1="21" x2="12" y2="23"></line>
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
<line x1="1" y1="12" x2="3" y2="12"></line>
<line x1="21" y1="12" x2="23" y2="12"></line>
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
</svg>
<svg class="icon-moon" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
</svg>
</button>
<button id="search-button" class="search-button" aria-label="Search">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="11" cy="11" r="8"></circle>
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
</svg>
</button>
<button id="mobile-menu-btn" class="mobile-menu-btn" aria-label="Toggle menu">
<svg class="icon-menu" 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="3" y1="12" x2="21" y2="12"></line>
<line x1="3" y1="6" x2="21" y2="6"></line>
<line x1="3" y1="18" x2="21" y2="18"></line>
</svg>
<svg class="icon-close" 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="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
</div>
</nav>
</div>
</div>
<!-- Animated network lines effect -->
<div class="network-lines"></div>
</header>
<style>
.site-header {
background: linear-gradient(180deg, rgba(15, 18, 25, 0.9), rgba(13, 16, 23, 0.8));
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
padding: 1rem 0;
position: sticky;
top: 0;
z-index: 100;
transition: all 0.3s ease;
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
}
.site-header.scrolled {
padding: 0.6rem 0;
background: rgba(10, 12, 20, 0.95);
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.2);
}
.header-container {
display: flex;
justify-content: space-between;
align-items: center;
}
.logo {
display: flex;
align-items: center;
gap: 0.75rem;
color: var(--text-primary);
text-decoration: none;
position: relative;
}
.logo-symbol {
width: 2.75rem;
height: 2.75rem;
border-radius: 10px;
background: linear-gradient(135deg, var(--accent-primary), var(--accent-secondary));
display: flex;
align-items: center;
justify-content: center;
position: relative;
overflow: hidden;
transition: transform 0.3s ease;
}
.logo:hover .logo-symbol {
transform: translateY(-3px);
}
.logo-text {
font-family: var(--font-mono);
font-weight: bold;
font-size: 1.25rem;
color: var(--bg-primary);
z-index: 2;
}
.logo-glow {
position: absolute;
width: 150%;
height: 150%;
background: radial-gradient(circle, var(--accent-primary) 0%, transparent 70%);
opacity: 0.5;
filter: blur(15px);
z-index: 1;
animation: pulse 4s infinite alternate ease-in-out;
}
.logo-text-container {
display: flex;
flex-direction: column;
}
.logo-name {
font-weight: 600;
font-size: 1.4rem;
background: linear-gradient(90deg, var(--accent-primary), var(--accent-secondary));
-webkit-background-clip: text;
background-clip: text;
color: transparent;
letter-spacing: 0.02em;
}
.logo-tagline {
font-size: 0.75rem;
color: var(--text-secondary);
font-family: var(--font-mono);
letter-spacing: 0.05em;
}
.main-nav {
display: flex;
gap: 1.5rem;
align-items: center;
}
.nav-link {
color: var(--text-secondary);
position: relative;
text-decoration: none;
font-weight: 500;
transition: color 0.3s ease;
padding: 0.5rem 0;
}
.nav-link:hover, .nav-link.active {
color: var(--text-primary);
}
.nav-highlight {
position: absolute;
bottom: -2px;
left: 0;
width: 0;
height: 2px;
background: linear-gradient(90deg, var(--accent-primary), var(--accent-secondary));
transition: width 0.3s ease;
border-radius: 2px;
}
.nav-link:hover .nav-highlight,
.nav-link.active .nav-highlight {
width: 100%;
}
.header-actions {
display: flex;
align-items: center;
gap: 1rem;
margin-left: 2rem;
}
.theme-toggle,
.search-button {
background: none;
border: none;
color: var(--text-secondary);
padding: 0.5rem;
cursor: pointer;
border-radius: 50%;
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
}
.theme-toggle:hover,
.search-button:hover {
color: var(--text-primary);
background: rgba(255, 255, 255, 0.1);
}
.icon-sun {
display: none;
}
:global(.light-mode) .icon-moon {
display: none;
}
:global(.light-mode) .icon-sun {
display: block;
}
.mobile-menu-btn {
display: none;
background: none;
border: none;
color: var(--text-primary);
cursor: pointer;
}
.icon-close {
display: none;
}
.mobile-menu-active .icon-menu {
display: none;
}
.mobile-menu-active .icon-close {
display: block;
}
/* Animated network lines effect */
.network-lines {
position: absolute;
bottom: -1px;
left: 0;
width: 100%;
height: 1px;
overflow: hidden;
opacity: 0.5;
}
.network-lines::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent 0%, var(--accent-primary) 50%, transparent 100%);
animation: network-scan 8s infinite linear;
}
/* Nebula background */
.nebula-bg {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image:
radial-gradient(circle at 20% 50%, rgba(6, 182, 212, 0.1) 0%, transparent 50%),
radial-gradient(circle at 80% 50%, rgba(139, 92, 246, 0.1) 0%, transparent 50%);
opacity: 0.3;
z-index: -1;
}
@keyframes pulse {
0% {
opacity: 0.4;
transform: scale(0.8);
}
100% {
opacity: 0.8;
transform: scale(1.2);
}
}
@keyframes network-scan {
0% {
transform: translateX(-100%);
}
100% {
transform: translateX(100%);
}
}
/* Responsive Adjustments */
@media (max-width: 1024px) {
.main-nav {
gap: 1rem;
}
.header-actions {
margin-left: 1rem;
}
}
@media (max-width: 768px) {
.mobile-menu-btn {
display: block;
}
.main-nav {
display: none;
position: absolute;
top: 100%;
left: 0;
width: 100%;
background: var(--bg-secondary);
padding: 1rem;
flex-direction: column;
align-items: flex-start;
gap: 1rem;
border-bottom: 1px solid var(--border-primary);
z-index: 10;
}
.main-nav.active {
display: flex;
}
.logo-tagline {
display: none;
}
.theme-toggle, .search-button {
padding: 0.4rem;
}
}
</style>
<script>
// Only define one DOMContentLoaded event handler
document.addEventListener('DOMContentLoaded', () => {
const menuBtn = document.getElementById('mobile-menu-btn');
const mainNav = document.querySelector('.main-nav');
const header = document.querySelector('.site-header');
// Mobile menu toggle
if (menuBtn && mainNav) {
menuBtn.addEventListener('click', () => {
mainNav.classList.toggle('active');
menuBtn.classList.toggle('mobile-menu-active');
});
}
// Header scroll effect
window.addEventListener('scroll', () => {
if (window.scrollY > 50) {
header?.classList.add('scrolled');
} else {
header?.classList.remove('scrolled');
}
});
// Theme toggle functionality - only define once
const themeToggle = document.getElementById('theme-toggle');
if (themeToggle) {
themeToggle.addEventListener('click', () => {
document.documentElement.classList.toggle('light-mode');
// Store preference in localStorage
const isLightMode = document.documentElement.classList.contains('light-mode');
localStorage.setItem('theme', isLightMode ? 'light' : 'dark');
});
// Apply saved theme preference
const savedTheme = localStorage.getItem('theme');
if (savedTheme === 'light') {
document.documentElement.classList.add('light-mode');
}
}
// Add interactive network nodes animation
const header_el = document.querySelector('.site-header');
if (header_el) {
// Create animated nodes
for (let i = 0; i < 5; i++) {
const node = document.createElement('div');
node.className = 'nav-node';
node.style.left = `${Math.random() * 100}%`;
node.style.animationDelay = `${Math.random() * 5}s`;
node.style.animationDuration = `${5 + Math.random() * 5}s`;
header_el.appendChild(node);
}
}
});
</script>