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:
parent
53e5edd02c
commit
fc58689a86
@ -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">
|
||||
< 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 >
|
||||
</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>
|
||||
|
||||
@ -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 });
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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"><</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"><</span>
|
||||
ACCESS_FULL_ARCHIVE
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -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"><</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">
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user