- Refactor component styles to use CSS custom properties for colors and backgrounds. - Replace hard‑coded Tailwind classes with theme variables across BlogCard, BlogFilters, Footer, GridOverlay, Navigation, PostNavigation, ReadingProgress, RelatedPosts, TableOfContents, ThemeToggle, sections, layouts, pages, and global.css. - Add ThemeToggle component for user‑controlled theme switching. - Update global styles to define new theme variables. - Ensure all components respect theme changes and maintain accessibility. Hubert The Eunuch
169 lines
7.2 KiB
Plaintext
169 lines
7.2 KiB
Plaintext
---
|
|
import ThemeToggle from './ThemeToggle.astro';
|
|
---
|
|
|
|
<nav class="fixed top-0 left-0 w-full z-50 px-6 lg:px-12 py-6 lg:py-8 flex justify-between items-center backdrop-blur-md bg-[var(--theme-overlay)] border-b border-[var(--theme-border-secondary)]">
|
|
<!-- Left side - branding and theme toggle -->
|
|
<div class="flex items-center gap-6">
|
|
<a href="/" class="text-[10px] font-mono text-[var(--theme-text-muted)] tracking-widest uppercase hover:text-brand-accent transition-colors duration-300">NV / 2026</a>
|
|
<div class="hidden md:block">
|
|
<ThemeToggle />
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Right side navigation -->
|
|
<div class="flex items-center gap-6 lg:gap-10 ml-auto">
|
|
<div class="hidden md:flex items-center gap-10 lg:gap-12">
|
|
<a href="/"
|
|
class:list={[
|
|
"relative text-xs font-semibold uppercase tracking-[0.15em] transition-all duration-300 py-2 group",
|
|
Astro.url.pathname === '/' ? "text-[var(--theme-text-primary)]" : "text-[var(--theme-text-muted)] hover:text-[var(--theme-text-primary)]"
|
|
]}>
|
|
<span class="relative z-10">Home</span>
|
|
<span class:list={[
|
|
"absolute bottom-0 left-0 h-[1px] bg-brand-accent transition-all duration-300 ease-out",
|
|
Astro.url.pathname === '/' ? "w-full" : "w-0 group-hover:w-full"
|
|
]}></span>
|
|
</a>
|
|
<a href="/blog"
|
|
class:list={[
|
|
"relative text-xs font-semibold uppercase tracking-[0.15em] transition-all duration-300 py-2 group",
|
|
Astro.url.pathname.startsWith('/blog') ? "text-[var(--theme-text-primary)]" : "text-[var(--theme-text-muted)] hover:text-[var(--theme-text-primary)]"
|
|
]}>
|
|
<span class="relative z-10">Blog</span>
|
|
<span class:list={[
|
|
"absolute bottom-0 left-0 h-[1px] bg-brand-accent transition-all duration-300 ease-out",
|
|
Astro.url.pathname.startsWith('/blog') ? "w-full" : "w-0 group-hover:w-full"
|
|
]}></span>
|
|
</a>
|
|
</div>
|
|
|
|
<a href="/contact"
|
|
class:list={[
|
|
"hidden md:block border px-5 lg:px-6 py-2.5 lg:py-3 text-xs font-bold uppercase tracking-[0.15em] transition-all duration-300",
|
|
Astro.url.pathname.startsWith('/contact')
|
|
? "border-brand-accent bg-brand-accent text-brand-dark"
|
|
: "border-[var(--theme-border-strong)] text-[var(--theme-text-primary)] hover:border-brand-accent hover:bg-brand-accent hover:text-brand-dark"
|
|
]}>
|
|
Let's Talk
|
|
</a>
|
|
</div>
|
|
|
|
<!-- Mobile menu button -->
|
|
<div class="md:hidden flex items-center">
|
|
<button
|
|
id="mobile-menu-toggle"
|
|
class="p-2 text-[var(--theme-text-muted)] hover:text-[var(--theme-text-primary)] transition-colors z-[60]"
|
|
aria-label="Toggle menu"
|
|
aria-expanded="false"
|
|
>
|
|
<!-- Hamburger icon -->
|
|
<svg id="menu-icon-open" class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M4 6h16M4 12h16M4 18h16"></path>
|
|
</svg>
|
|
<!-- Close icon (hidden by default) -->
|
|
<svg id="menu-icon-close" class="w-6 h-6 hidden" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M6 18L18 6M6 6l12 12"></path>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</nav>
|
|
|
|
<!-- Mobile Menu Overlay -->
|
|
<div
|
|
id="mobile-menu"
|
|
class="fixed inset-0 z-40 bg-[var(--theme-overlay-heavy)] backdrop-blur-xl transform translate-x-full transition-transform duration-300 ease-out md:hidden"
|
|
>
|
|
<!-- Menu Content -->
|
|
<div class="flex flex-col justify-center items-center h-full px-8">
|
|
<!-- Navigation Links -->
|
|
<nav class="flex flex-col items-center gap-8 mb-12">
|
|
<a
|
|
href="/"
|
|
class="mobile-nav-link text-3xl font-bold uppercase tracking-wider text-[var(--theme-text-primary)] hover:text-brand-accent transition-colors duration-300"
|
|
>
|
|
Home
|
|
</a>
|
|
<a
|
|
href="/blog"
|
|
class="mobile-nav-link text-3xl font-bold uppercase tracking-wider text-[var(--theme-text-primary)] hover:text-brand-accent transition-colors duration-300"
|
|
>
|
|
Blog
|
|
</a>
|
|
<a
|
|
href="/contact"
|
|
class="mobile-nav-link text-3xl font-bold uppercase tracking-wider text-[var(--theme-text-primary)] hover:text-brand-accent transition-colors duration-300"
|
|
>
|
|
Contact
|
|
</a>
|
|
</nav>
|
|
|
|
<!-- CTA Button -->
|
|
<a
|
|
href="/contact"
|
|
class="border border-brand-accent px-8 py-4 text-sm font-bold uppercase tracking-[0.2em] text-brand-accent hover:bg-brand-accent hover:text-brand-dark transition-all duration-300 mb-8"
|
|
>
|
|
Let's Talk
|
|
</a>
|
|
|
|
<!-- Decorative Elements -->
|
|
<div class="absolute bottom-12 left-8 right-8 flex justify-between items-center">
|
|
<div class="flex flex-col gap-2">
|
|
<div class="text-[10px] font-mono text-[var(--theme-text-muted)] uppercase tracking-widest">
|
|
NV / 2026
|
|
</div>
|
|
<ThemeToggle />
|
|
</div>
|
|
<div class="text-[10px] font-mono text-[var(--theme-text-muted)] uppercase tracking-widest self-end">
|
|
Menu
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
const toggle = document.getElementById('mobile-menu-toggle');
|
|
const menu = document.getElementById('mobile-menu');
|
|
const iconOpen = document.getElementById('menu-icon-open');
|
|
const iconClose = document.getElementById('menu-icon-close');
|
|
const mobileNavLinks = document.querySelectorAll('.mobile-nav-link');
|
|
|
|
let isOpen = false;
|
|
|
|
function toggleMenu() {
|
|
isOpen = !isOpen;
|
|
|
|
if (isOpen) {
|
|
menu?.classList.remove('translate-x-full');
|
|
menu?.classList.add('translate-x-0');
|
|
iconOpen?.classList.add('hidden');
|
|
iconClose?.classList.remove('hidden');
|
|
document.body.style.overflow = 'hidden';
|
|
toggle?.setAttribute('aria-expanded', 'true');
|
|
} else {
|
|
menu?.classList.add('translate-x-full');
|
|
menu?.classList.remove('translate-x-0');
|
|
iconOpen?.classList.remove('hidden');
|
|
iconClose?.classList.add('hidden');
|
|
document.body.style.overflow = '';
|
|
toggle?.setAttribute('aria-expanded', 'false');
|
|
}
|
|
}
|
|
|
|
toggle?.addEventListener('click', toggleMenu);
|
|
|
|
// Close menu when clicking a link
|
|
mobileNavLinks.forEach(link => {
|
|
link.addEventListener('click', () => {
|
|
if (isOpen) toggleMenu();
|
|
});
|
|
});
|
|
|
|
// Close menu on escape key
|
|
document.addEventListener('keydown', (e) => {
|
|
if (e.key === 'Escape' && isOpen) {
|
|
toggleMenu();
|
|
}
|
|
});
|
|
</script>
|