Refactor navigation & filter UI, add 404 page, tweak global CSS

- Update BlogFilters: sector selection, search placeholder, and styles
- Refactor Navigation: dynamic active link styling, branding link
- Add 404 page component
- Adjust global CSS rules
- Modify utils/git-commit.js

Hubert The Eunuch
This commit is contained in:
Nicholai 2025-12-18 15:45:45 -07:00
parent 54506d0aad
commit bc639ec1b4
5 changed files with 148 additions and 57 deletions

View File

@ -11,14 +11,14 @@ const { categories, class: className = '' } = Astro.props;
<!-- Filters row -->
<div class="flex flex-col lg:flex-row lg:items-center lg:justify-between gap-6 mb-10">
<!-- Category chips -->
<div class="flex flex-wrap items-center gap-3">
<span class="text-[10px] font-mono text-slate-500 uppercase tracking-widest mr-2">
/// FILTER BY
<div class="flex flex-wrap items-center gap-1">
<span class="text-[10px] font-mono text-slate-500 uppercase tracking-widest mr-4">
/// SECTOR SELECT
</span>
<button
type="button"
data-category="all"
class="filter-chip active px-4 py-2 text-[10px] font-mono font-bold uppercase tracking-widest border border-white/20 text-white bg-white/5 hover:border-brand-accent hover:bg-brand-accent/5 transition-all duration-300"
class="filter-chip active px-4 py-2 text-[10px] font-mono font-bold uppercase tracking-widest border-b-2 border-brand-accent text-white bg-white/5 transition-all duration-300 hover:bg-white/10"
>
All
</button>
@ -26,7 +26,7 @@ const { categories, class: className = '' } = Astro.props;
<button
type="button"
data-category={category}
class="filter-chip px-4 py-2 text-[10px] font-mono font-bold uppercase tracking-widest border border-white/10 text-slate-400 hover:border-brand-accent hover:text-white hover:bg-brand-accent/5 transition-all duration-300"
class="filter-chip px-4 py-2 text-[10px] font-mono font-bold uppercase tracking-widest border-b-2 border-transparent text-slate-500 hover:text-white hover:border-brand-accent/50 hover:bg-white/5 transition-all duration-300"
>
{category}
</button>
@ -34,49 +34,22 @@ const { categories, class: className = '' } = Astro.props;
</div>
<!-- Search input -->
<div class="relative lg:w-80">
<div class="absolute left-4 top-1/2 -translate-y-1/2 pointer-events-none">
<svg
xmlns="http://www.w3.org/2000/svg"
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="text-slate-500"
>
<circle cx="11" cy="11" r="8" />
<path d="m21 21-4.3-4.3" />
</svg>
<div class="relative lg:w-80 group">
<div class="absolute left-0 top-1/2 -translate-y-1/2 pointer-events-none text-brand-accent">
<span class="font-mono text-xs">></span>
</div>
<input
type="text"
id="blog-search"
placeholder="Search articles..."
class="w-full pl-11 pr-4 py-3 text-sm font-mono bg-transparent border border-white/10 text-white placeholder:text-slate-500 focus:border-brand-accent focus:outline-none transition-colors duration-300"
placeholder="SEARCH_DATABASE..."
class="w-full pl-6 pr-4 py-2 text-sm font-mono bg-transparent border-b border-slate-700 text-white placeholder:text-slate-600 focus:border-brand-accent focus:outline-none transition-colors duration-300 uppercase"
/>
<button
type="button"
id="clear-search"
class="absolute right-3 top-1/2 -translate-y-1/2 text-slate-500 hover:text-white transition-colors hidden"
class="absolute right-0 top-1/2 -translate-y-1/2 text-slate-500 hover:text-brand-accent transition-colors hidden"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M18 6 6 18" />
<path d="m6 6 12 12" />
</svg>
<span class="font-mono text-xs">[CLR]</span>
</button>
</div>
</div>
@ -147,10 +120,15 @@ const { categories, class: className = '' } = Astro.props;
activeCategory = chipEl.dataset.category || 'all';
// Update active state
filterChips.forEach((c) => c.classList.remove('active', 'border-brand-accent', 'text-white', 'bg-white/5'));
filterChips.forEach((c) => c.classList.add('border-white/10', 'text-slate-400'));
// Reset all to inactive state
filterChips.forEach((c) => {
c.classList.remove('active', 'border-brand-accent', 'text-white', 'bg-white/5');
c.classList.add('border-transparent', 'text-slate-500');
});
// Set clicked to active state
chipEl.classList.add('active', 'border-brand-accent', 'text-white', 'bg-white/5');
chipEl.classList.remove('border-white/10', 'text-slate-400');
chipEl.classList.remove('border-transparent', 'text-slate-500');
filterPosts();
});

View File

@ -1,28 +1,48 @@
---
---
---
---
<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-brand-dark/80 border-b border-white/5">
<!-- Left side - can be empty or have subtle branding -->
<div class="hidden md:block">
<span class="text-[10px] font-mono text-slate-600 tracking-widest uppercase">NV / 2026</span>
<a href="/" class="text-[10px] font-mono text-slate-600 tracking-widest uppercase hover:text-brand-accent transition-colors duration-300">NV / 2026</a>
</div>
<!-- Right side navigation -->
<div class="flex items-center gap-8 lg:gap-12 ml-auto">
<div class="hidden md:flex items-center gap-10 lg:gap-12">
<a href="/"
class="relative text-xs font-semibold uppercase tracking-[0.15em] text-slate-500 hover:text-white transition-all duration-300 py-2 group">
class:list={[
"relative text-xs font-semibold uppercase tracking-[0.15em] transition-all duration-300 py-2 group",
Astro.url.pathname === '/' ? "text-white" : "text-slate-500 hover:text-white"
]}>
<span class="relative z-10">Home</span>
<span class="absolute bottom-0 left-0 w-0 h-[1px] bg-brand-accent transition-all duration-300 ease-out group-hover:w-full"></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="relative text-xs font-semibold uppercase tracking-[0.15em] text-slate-500 hover:text-white transition-all duration-300 py-2 group">
class:list={[
"relative text-xs font-semibold uppercase tracking-[0.15em] transition-all duration-300 py-2 group",
Astro.url.pathname.startsWith('/blog') ? "text-white" : "text-slate-500 hover:text-white"
]}>
<span class="relative z-10">Blog</span>
<span class="absolute bottom-0 left-0 w-0 h-[1px] bg-brand-accent transition-all duration-300 ease-out group-hover:w-full"></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="hidden md:block border border-slate-600 px-5 lg:px-6 py-2.5 lg:py-3 text-xs font-bold uppercase tracking-[0.15em] text-white hover:border-brand-accent hover:bg-brand-accent hover:text-brand-dark transition-all duration-300">
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-slate-600 text-white hover:border-brand-accent hover:bg-brand-accent hover:text-brand-dark"
]}>
Let's Talk
</a>
</div>

View File

@ -63,23 +63,35 @@ const imageMap: Record<string, string> = {
: "border-white/20 text-slate-300";
return (
<div class={`skill-row group relative grid grid-cols-12 gap-4 py-10 border-b border-white/10 items-center transition-colors duration-300 hover:border-brand-accent/30 ${index < skills.length - 1 ? '' : ''}`} data-image={imageMap[skill.id] || "default"}>
<div class="col-span-2 md:col-span-1 text-brand-accent font-mono text-sm relative overflow-hidden">
<div class={`skill-row group relative grid grid-cols-12 gap-4 py-10 border-b border-white/10 items-center transition-colors duration-300 hover:border-brand-accent/30 overflow-hidden ${index < skills.length - 1 ? '' : ''}`} data-image={imageMap[skill.id] || "default"}>
<!-- Hover Background Effect -->
<div class="absolute inset-0 bg-brand-accent/5 opacity-0 group-hover:opacity-100 transition-opacity duration-300 pointer-events-none"></div>
<!-- ID Column -->
<div class="col-span-2 md:col-span-1 text-brand-accent font-mono text-sm relative overflow-hidden z-10">
<span class="block group-hover:-translate-y-full transition-transform duration-500">{skill.id}</span>
<span class="absolute top-0 left-0 translate-y-full group-hover:translate-y-0 transition-transform duration-500">{skill.id}</span>
</div>
<div class="col-span-10 md:col-span-4 relative overflow-hidden">
<!-- Main Content (Domain) -->
<div class="col-span-10 md:col-span-4 relative z-10">
<h3 class="text-3xl md:text-5xl font-bold text-white uppercase tracking-tighter group-hover:text-brand-accent transition-colors duration-300">{skill.domain}</h3>
{index === 0 && (
<div class="absolute inset-0 bg-brand-accent transform scale-x-0 group-hover:scale-x-100 transition-transform duration-500 origin-left mix-blend-difference pointer-events-none opacity-0 group-hover:opacity-100"></div>
)}
<!-- Scan line effect for ALL items on hover -->
<div class="absolute bottom-0 left-0 h-[1px] w-full bg-brand-accent transform scale-x-0 group-hover:scale-x-100 transition-transform duration-700 ease-out origin-left opacity-0 group-hover:opacity-100"></div>
</div>
<div class="col-span-12 md:col-span-5 text-slate-400 font-mono text-xs md:text-sm tracking-wide group-hover:text-white transition-colors duration-300">
<!-- Tools Stack -->
<div class="col-span-12 md:col-span-5 text-slate-400 font-mono text-xs md:text-sm tracking-wide group-hover:text-white transition-colors duration-300 z-10">
{skill.tools}
</div>
<div class="col-span-6 md:col-span-2 text-right hidden md:block">
<span class={`inline-block px-3 py-1 border text-[10px] font-bold uppercase tracking-widest ${proficiencyClass}`}>{skill.proficiency}</span>
<!-- Proficiency Badge -->
<div class="col-span-6 md:col-span-2 text-right hidden md:block z-10">
<span class={`inline-block px-3 py-1 border text-[10px] font-bold uppercase tracking-widest ${proficiencyClass} group-hover:bg-brand-accent group-hover:text-brand-dark transition-all duration-300`}>{skill.proficiency}</span>
</div>
<!-- Decorative "Scan" Sweep Overlay -->
<div class="absolute inset-0 bg-gradient-to-r from-transparent via-brand-accent/10 to-transparent -translate-x-full group-hover:animate-scan-sweep pointer-events-none"></div>
</div>
);
})}

68
src/pages/404.astro Normal file
View File

@ -0,0 +1,68 @@
---
import BaseLayout from '../layouts/BaseLayout.astro';
import { SITE_TITLE, SITE_DESCRIPTION } from '../consts';
---
<BaseLayout title={`404 | ${SITE_TITLE}`} description="System signal lost. Page not found.">
<div class="container mx-auto px-6 lg:px-12 flex flex-col justify-center min-h-[70vh] relative z-20">
<!-- Error Header -->
<div class="mb-8">
<h1 class="text-[12rem] md:text-[18rem] lg:text-[22rem] font-bold leading-[0.8] tracking-tighter text-white select-none">
404
</h1>
<div class="h-2 w-full bg-brand-accent/50 mb-8 max-w-xl"></div>
</div>
<!-- Status Message -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-12 max-w-5xl">
<div>
<h2 class="text-4xl md:text-5xl font-bold uppercase tracking-tight text-white mb-6">
Signal Lost
</h2>
<p class="text-slate-400 font-mono text-sm md:text-base leading-relaxed mb-8 border-l-2 border-brand-accent pl-6">
/// SYSTEM ERROR: PATHWAY_NOT_FOUND<br>
The requested coordinates do not resolve to a valid sector. The page you are looking for may have been decommissioned or relocated.
</p>
<a href="/" class="group inline-flex items-center gap-4 px-8 py-4 border border-brand-accent text-brand-accent font-mono font-bold uppercase tracking-widest hover:bg-brand-accent hover:text-brand-dark transition-all duration-300">
<span class="group-hover:animate-pulse">///</span>
Reboot System
</a>
</div>
<!-- Technical Diagnostics (Visual Fluff) -->
<div class="hidden md:block font-mono text-xs text-slate-600 space-y-2 select-none">
<div class="flex justify-between border-b border-white/5 pb-2">
<span>ERR_CODE</span>
<span>0x000404</span>
</div>
<div class="flex justify-between border-b border-white/5 pb-2">
<span>STATUS</span>
<span class="text-brand-red">CRITICAL</span>
</div>
<div class="flex justify-between border-b border-white/5 pb-2">
<span>MODULE</span>
<span>NAV_SYSTEM</span>
</div>
<div class="flex justify-between border-b border-white/5 pb-2">
<span>TIMESTAMP</span>
<span id="error-time">--:--:--</span>
</div>
<div class="mt-8 p-4 border border-white/5 bg-white/[0.02]">
<span class="block mb-2">> DIAGNOSTIC_TOOL --RUN</span>
<span class="block text-brand-accent">> TRACE COMPLETE</span>
<span class="block">> END OF LINE.</span>
</div>
</div>
</div>
</div>
<script>
const timeElement = document.getElementById('error-time');
if (timeElement) {
const now = new Date();
timeElement.textContent = now.toISOString().split('T')[1].split('.')[0];
}
</script>
</BaseLayout>

View File

@ -88,6 +88,19 @@
transform: scale(1);
}
}
@keyframes scan-sweep {
0% {
transform: translateX(-100%) skewX(-15deg);
}
100% {
transform: translateX(200%) skewX(-15deg);
}
}
}
@utility animate-scan-sweep {
animation: scan-sweep 1.5s cubic-bezier(0.16, 1, 0.3, 1) infinite;
}
@utility text-massive {