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:
parent
54506d0aad
commit
bc639ec1b4
@ -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();
|
||||
});
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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
68
src/pages/404.astro
Normal 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>
|
||||
@ -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 {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user