Revamp navigation, progress, and layout components for smoother UX

- Add animated hover and gradient effects to PostNavigation
- Introduce reading progress bar with shadow and status indicator
- Update RelatedPosts and TableOfContents with new styling
- Adjust BlogPost layout and index page for consistent design

- Hubert The Eunuch
This commit is contained in:
Nicholai 2025-12-18 15:57:42 -07:00
parent 53e5edd02c
commit fc58689a86
6 changed files with 99 additions and 99 deletions

View File

@ -20,7 +20,7 @@ const { prevPost, nextPost } = Astro.props;
<nav class="post-navigation mt-20 pt-12 border-t border-white/10" aria-label="Post navigation">
<div class="flex items-center gap-4 mb-8">
<span class="text-[10px] font-mono text-slate-500 uppercase tracking-widest font-bold">
/// CONTINUE READING
/// NEXT_IN_SEQUENCE
</span>
<span class="h-px flex-grow bg-white/10"></span>
</div>
@ -30,33 +30,15 @@ const { prevPost, nextPost } = Astro.props;
{prevPost ? (
<a
href={prevPost.href}
class="group relative flex items-center gap-6 p-6 border border-white/10 bg-white/[0.02] hover:border-brand-accent/40 hover:bg-white/[0.04] transition-all duration-500"
class="group relative flex items-center gap-6 p-6 border border-white/10 bg-white/[0.02] hover:border-brand-accent/40 hover:bg-white/[0.04] transition-all duration-500 overflow-hidden"
>
<div class="absolute top-0 left-0 w-1 h-full bg-slate-700 opacity-50 group-hover:bg-brand-accent group-hover:opacity-100 transition-all duration-500"></div>
<!-- Arrow -->
<div class="flex-shrink-0 w-10 h-10 flex items-center justify-center border border-white/10 group-hover:border-brand-accent/50 transition-colors">
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="text-slate-500 group-hover:text-brand-accent transition-colors group-hover:-translate-x-1 transition-transform duration-300"
>
<path d="M19 12H5" />
<path d="m12 19-7-7 7-7" />
</svg>
</div>
<div class="absolute top-0 left-0 w-[2px] h-full bg-slate-700 opacity-50 group-hover:bg-brand-accent group-hover:opacity-100 transition-all duration-500"></div>
<div class="absolute inset-0 bg-brand-accent/5 translate-x-[-100%] group-hover:translate-x-0 transition-transform duration-500 pointer-events-none"></div>
<!-- Content -->
<div class="flex-grow min-w-0">
<span class="text-[10px] font-mono text-slate-500 uppercase tracking-widest mb-2 block">
Previous
<div class="flex-grow min-w-0 z-10">
<span class="text-[10px] font-mono text-slate-500 uppercase tracking-widest mb-2 block group-hover:text-brand-accent transition-colors">
&lt; PREV_FILE
</span>
<h4 class="text-sm font-bold text-white uppercase tracking-tight truncate group-hover:text-brand-accent transition-colors">
{prevPost.title}
@ -65,7 +47,7 @@ const { prevPost, nextPost } = Astro.props;
<!-- Thumbnail -->
{prevPost.heroImage && (
<div class="hidden sm:block flex-shrink-0 w-16 h-16 overflow-hidden border border-white/10">
<div class="hidden sm:block flex-shrink-0 w-12 h-12 overflow-hidden border border-white/10 z-10 grayscale group-hover:grayscale-0 transition-all duration-500">
<Image
src={prevPost.heroImage}
alt=""
@ -77,20 +59,23 @@ const { prevPost, nextPost } = Astro.props;
)}
</a>
) : (
<div></div>
<div class="border border-white/5 bg-white/[0.01] p-6 flex items-center justify-center">
<span class="text-[10px] font-mono text-slate-600 uppercase tracking-widest">/// START_OF_ARCHIVE</span>
</div>
)}
<!-- Next Post -->
{nextPost ? (
<a
href={nextPost.href}
class="group relative flex items-center gap-6 p-6 border border-white/10 bg-white/[0.02] hover:border-brand-accent/40 hover:bg-white/[0.04] transition-all duration-500"
class="group relative flex items-center gap-6 p-6 border border-white/10 bg-white/[0.02] hover:border-brand-accent/40 hover:bg-white/[0.04] transition-all duration-500 overflow-hidden"
>
<div class="absolute top-0 right-0 w-1 h-full bg-slate-700 opacity-50 group-hover:bg-brand-accent group-hover:opacity-100 transition-all duration-500"></div>
<div class="absolute top-0 right-0 w-[2px] h-full bg-slate-700 opacity-50 group-hover:bg-brand-accent group-hover:opacity-100 transition-all duration-500"></div>
<div class="absolute inset-0 bg-brand-accent/5 translate-x-[100%] group-hover:translate-x-0 transition-transform duration-500 pointer-events-none"></div>
<!-- Thumbnail -->
{nextPost.heroImage && (
<div class="hidden sm:block flex-shrink-0 w-16 h-16 overflow-hidden border border-white/10">
<div class="hidden sm:block flex-shrink-0 w-12 h-12 overflow-hidden border border-white/10 z-10 grayscale group-hover:grayscale-0 transition-all duration-500">
<Image
src={nextPost.heroImage}
alt=""
@ -102,36 +87,19 @@ const { prevPost, nextPost } = Astro.props;
)}
<!-- Content -->
<div class="flex-grow min-w-0 text-right">
<span class="text-[10px] font-mono text-slate-500 uppercase tracking-widest mb-2 block">
Next
<div class="flex-grow min-w-0 text-right z-10">
<span class="text-[10px] font-mono text-slate-500 uppercase tracking-widest mb-2 block group-hover:text-brand-accent transition-colors">
NEXT_FILE &gt;
</span>
<h4 class="text-sm font-bold text-white uppercase tracking-tight truncate group-hover:text-brand-accent transition-colors">
{nextPost.title}
</h4>
</div>
<!-- Arrow -->
<div class="flex-shrink-0 w-10 h-10 flex items-center justify-center border border-white/10 group-hover:border-brand-accent/50 transition-colors">
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="text-slate-500 group-hover:text-brand-accent transition-colors group-hover:translate-x-1 transition-transform duration-300"
>
<path d="M5 12h14" />
<path d="m12 5 7 7-7 7" />
</svg>
</div>
</a>
) : (
<div></div>
<div class="border border-white/5 bg-white/[0.01] p-6 flex items-center justify-center">
<span class="text-[10px] font-mono text-slate-600 uppercase tracking-widest">/// END_OF_ARCHIVE</span>
</div>
)}
</div>
</nav>

View File

@ -3,12 +3,20 @@
---
<div id="reading-progress-container" class="fixed top-0 left-0 w-full h-[3px] z-[100] bg-brand-dark/50">
<div id="reading-progress-bar" class="h-full bg-brand-accent w-0 transition-[width] duration-100 ease-out"></div>
<div id="reading-progress-bar" class="h-full bg-brand-accent w-0 transition-[width] duration-100 ease-out shadow-[0_0_10px_rgba(221,65,50,0.5)]"></div>
</div>
<div id="reading-status" class="fixed top-4 right-4 z-[90] hidden lg:flex items-center gap-3 px-3 py-1 bg-brand-dark/80 backdrop-blur-md border border-white/10 opacity-0 transition-opacity duration-300 pointer-events-none">
<div class="w-1.5 h-1.5 bg-brand-accent rounded-full animate-pulse"></div>
<span class="text-[9px] font-mono text-slate-400 uppercase tracking-widest">READING_BUFFER: <span id="progress-text" class="text-white">0%</span></span>
</div>
<script>
function initReadingProgress() {
const progressBar = document.getElementById('reading-progress-bar');
const statusContainer = document.getElementById('reading-status');
const statusText = document.getElementById('progress-text');
if (!progressBar) return;
function updateProgress() {
@ -32,8 +40,22 @@
} else if (current > end) {
progress = 100;
}
progressBar.style.width = `${Math.min(100, Math.max(0, progress))}%`;
const percentage = Math.round(Math.min(100, Math.max(0, progress)));
progressBar.style.width = `${percentage}%`;
if (statusText) {
statusText.textContent = `${percentage}%`;
}
// Show status only when reading (between 1% and 99%)
if (statusContainer) {
if (percentage > 2 && percentage < 98) {
statusContainer.classList.remove('opacity-0');
} else {
statusContainer.classList.add('opacity-0');
}
}
}
window.addEventListener('scroll', updateProgress, { passive: true });

View File

@ -24,7 +24,7 @@ const { posts, class: className = '' } = Astro.props;
<section class:list={['related-posts mt-20 pt-12 border-t border-white/10', className]}>
<div class="flex items-center gap-4 mb-8">
<span class="text-[10px] font-mono text-slate-500 uppercase tracking-widest font-bold">
/// RELATED ARTICLES
/// RELATED_ARCHIVES
</span>
<span class="h-px flex-grow bg-white/10"></span>
</div>

View File

@ -37,7 +37,7 @@ const tocHeadings = headings.filter((h) => h.depth === 2 || h.depth === 3);
>
<span class="flex items-center gap-2">
{heading.depth === 2 && (
<span class="w-1.5 h-1.5 bg-slate-600 rounded-full toc-indicator transition-colors duration-300"></span>
<span class="w-1.5 h-1.5 bg-slate-600 toc-indicator transition-colors duration-300"></span>
)}
{heading.text}
</span>

View File

@ -102,43 +102,43 @@ const articleSchema = {
<!-- Main Column -->
<div class="lg:col-span-8 lg:col-start-3">
<!-- Back Navigation -->
<div class="mb-8">
<a href="/blog" class="inline-flex items-center gap-3 text-xs font-semibold uppercase tracking-widest text-slate-500 hover:text-white transition-colors duration-300 group">
<span class="w-8 h-[1px] bg-slate-600 group-hover:bg-brand-accent group-hover:w-12 transition-all duration-300"></span>
Back to Blog
<div class="mb-12">
<a href="/blog" class="inline-flex items-center gap-3 px-5 py-3 border border-slate-700 bg-brand-dark/50 text-xs font-mono font-bold uppercase tracking-widest text-slate-400 hover:border-brand-accent hover:text-white hover:bg-brand-accent/5 transition-all duration-300 group backdrop-blur-sm">
<span class="text-brand-accent group-hover:-translate-x-1 transition-transform duration-300">&lt;</span>
<span>RETURN_TO_ARCHIVE</span>
</a>
</div>
<!-- Hero Section: Side-by-Side Layout -->
<header class="mb-16 lg:mb-20">
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8 items-start">
<header class="mb-20 lg:mb-24 relative">
<div class="grid grid-cols-1 lg:grid-cols-2 gap-12 items-start">
<!-- Text Content -->
<div class="order-2 lg:order-1">
<div class="order-2 lg:order-1 relative z-10">
<!-- Metadata -->
<div class="flex flex-wrap items-center gap-3 text-[10px] font-mono text-slate-500 uppercase tracking-widest mb-5">
<div class="flex flex-wrap items-center gap-4 text-[10px] font-mono text-slate-500 uppercase tracking-widest mb-8 border-b border-white/10 pb-4">
<div class="flex items-center gap-2">
<div class="w-2 h-2 bg-brand-accent rounded-full animate-pulse"></div>
<span class="text-brand-accent font-bold">SYS.ARTICLE</span>
<div class="w-1.5 h-1.5 bg-brand-accent shadow-[0_0_10px_rgba(221,65,50,0.5)] rounded-full animate-pulse"></div>
<span class="text-brand-accent font-bold">SYS.LOG</span>
</div>
<span class="h-px w-4 bg-white/20"></span>
<span class="text-slate-700">/</span>
<FormattedDate date={pubDate} />
<span class="h-px w-4 bg-white/20"></span>
<span class="text-slate-700">/</span>
<span>{readTime}</span>
</div>
{category && (
<div class="mb-4">
<span class="px-3 py-1.5 text-[10px] font-mono font-bold uppercase tracking-widest bg-brand-accent/10 border border-brand-accent/30 text-brand-accent">
{category}
<div class="mb-6">
<span class="inline-block px-3 py-1.5 text-[10px] font-mono font-bold uppercase tracking-[0.2em] bg-white/5 border border-white/10 text-brand-accent hover:bg-brand-accent/10 transition-colors cursor-default">
/// {category}
</span>
</div>
)}
<h1 class="text-2xl md:text-3xl lg:text-4xl font-bold text-white uppercase leading-[0.95] tracking-tighter mb-4">
<h1 class="text-4xl md:text-5xl lg:text-6xl font-bold text-white uppercase leading-[0.9] tracking-tighter mb-8 break-words text-balance">
{title}
</h1>
<p class="text-sm lg:text-base text-slate-400 leading-relaxed font-light mb-5 border-l border-white/10 pl-4">
<p class="text-base md:text-lg text-slate-400 leading-relaxed font-light mb-8 border-l-2 border-brand-accent pl-6">
{description}
</p>
@ -146,8 +146,8 @@ const articleSchema = {
{tags && tags.length > 0 && (
<div class="flex flex-wrap gap-2">
{tags.map((tag) => (
<span class="px-2 py-1 text-[9px] font-mono uppercase border border-white/10 text-slate-500 hover:border-white/20 transition-colors">
{tag}
<span class="px-2 py-1 text-[9px] font-mono uppercase bg-brand-dark border border-slate-800 text-slate-500 hover:border-brand-accent/50 hover:text-white transition-colors cursor-default">
#{tag}
</span>
))}
</div>
@ -157,18 +157,25 @@ const articleSchema = {
<!-- Hero Image -->
{heroImage && (
<div class="order-1 lg:order-2">
<div class="relative aspect-[4/3] overflow-hidden border border-white/10 bg-white/[0.02]">
<div class="relative aspect-[4/3] lg:aspect-square overflow-hidden border border-white/10 bg-white/[0.02] group">
<!-- Tech corners -->
<div class="absolute top-0 left-0 w-2 h-2 border-t border-l border-brand-accent z-20"></div>
<div class="absolute top-0 right-0 w-2 h-2 border-t border-r border-brand-accent z-20"></div>
<div class="absolute bottom-0 left-0 w-2 h-2 border-b border-l border-brand-accent z-20"></div>
<div class="absolute bottom-0 right-0 w-2 h-2 border-b border-r border-brand-accent z-20"></div>
<Image
src={heroImage}
alt=""
width={600}
height={450}
width={800}
height={800}
loading="eager"
class="w-full h-full object-cover"
class="w-full h-full object-cover opacity-80 group-hover:opacity-100 group-hover:scale-105 transition-all duration-700 ease-out grayscale hover:grayscale-0"
/>
<!-- Subtle overlay -->
<div class="absolute inset-0 bg-brand-dark/10"></div>
<div class="absolute inset-0 grid-overlay opacity-20 pointer-events-none"></div>
<!-- Scanline overlay (subtle) -->
<div class="absolute inset-0 bg-[linear-gradient(rgba(18,16,16,0)_50%,rgba(0,0,0,0.25)_50%),linear-gradient(90deg,rgba(255,0,0,0.06),rgba(0,255,0,0.02),rgba(0,0,255,0.06))] z-10 bg-[length:100%_2px,3px_100%] pointer-events-none opacity-20"></div>
<div class="absolute inset-0 grid-overlay opacity-30 pointer-events-none"></div>
</div>
</div>
)}
@ -184,20 +191,21 @@ const articleSchema = {
<footer class="mt-24 pt-10 border-t border-white/10">
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-6">
<div>
<p class="text-[10px] font-mono text-slate-500 uppercase tracking-widest mb-2">
<p class="text-[10px] font-mono text-brand-accent uppercase tracking-widest mb-2 flex items-center gap-2">
<span class="w-1.5 h-1.5 bg-brand-accent rounded-full animate-pulse"></span>
/// END TRANSMISSION
</p>
<p class="text-slate-400 text-sm">
Published <FormattedDate date={pubDate} />
<p class="text-slate-400 text-sm font-mono">
LOG_DATE: <FormattedDate date={pubDate} />
{updatedDate && (
<span class="text-slate-500"> · Last updated <FormattedDate date={updatedDate} /></span>
<span class="text-slate-500"> // UPDATED: <FormattedDate date={updatedDate} /></span>
)}
</p>
</div>
<!-- Share Links -->
<div class="flex items-center gap-4">
<span class="text-[10px] font-mono text-slate-500 uppercase tracking-widest">Share</span>
<div class="flex items-center gap-6">
<span class="text-[10px] font-mono text-slate-500 uppercase tracking-widest">DATA_UPLINK:</span>
<div class="flex items-center gap-2">
<a
href={`https://twitter.com/intent/tweet?text=${encodeURIComponent(title)}&url=${encodeURIComponent(Astro.url.href)}`}
@ -246,10 +254,10 @@ const articleSchema = {
<RelatedPosts posts={relatedPosts} />
<!-- Back to Blog -->
<div class="mt-20 pt-10 border-t border-white/10">
<a href="/blog" class="inline-flex items-center gap-3 text-xs font-semibold uppercase tracking-widest text-slate-500 hover:text-white transition-colors duration-300 group">
<span class="w-8 h-[1px] bg-slate-600 group-hover:bg-brand-accent group-hover:w-12 transition-all duration-300"></span>
Back to All Posts
<div class="mt-20 pt-10 border-t border-white/10 flex justify-center lg:justify-start">
<a href="/blog" class="inline-flex items-center gap-4 px-8 py-4 border border-slate-600 text-xs font-bold uppercase tracking-widest text-white hover:border-brand-accent hover:bg-brand-accent hover:text-brand-dark transition-all duration-300 group">
<span class="font-mono transition-transform duration-300 group-hover:-translate-x-1">&lt;</span>
ACCESS_FULL_ARCHIVE
</a>
</div>
</div>

View File

@ -30,10 +30,12 @@ const categories = [...new Set(allPosts.map((post) => post.data.category).filter
<BaseLayout title={`Blog | ${SITE_TITLE}`} description={SITE_DESCRIPTION}>
<section class="container mx-auto px-6 lg:px-12">
<!-- Back Navigation -->
<a href="/" class="inline-flex items-center gap-3 text-xs font-semibold uppercase tracking-widest text-slate-500 hover:text-white transition-colors duration-300 mb-12 group">
<span class="w-8 h-[1px] bg-slate-600 group-hover:bg-brand-accent group-hover:w-12 transition-all duration-300"></span>
Back to Home
</a>
<div class="mb-12">
<a href="/" class="inline-flex items-center gap-3 px-5 py-3 border border-slate-700 bg-brand-dark/50 text-xs font-mono font-bold uppercase tracking-widest text-slate-400 hover:border-brand-accent hover:text-white hover:bg-brand-accent/5 transition-all duration-300 group backdrop-blur-sm">
<span class="text-brand-accent group-hover:-translate-x-1 transition-transform duration-300">&lt;</span>
<span>RETURN_TO_HOME</span>
</a>
</div>
<!-- Page Header -->
<div class="grid grid-cols-1 lg:grid-cols-12 gap-8 lg:gap-12 mb-16 lg:mb-24">