layout redesign
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
---
|
||||
import Layout from '../../layouts/Layout.astro';
|
||||
import DeletePostButton from '../../components/react/DeletePostButton';
|
||||
import { renderMarkdown } from '../../lib/markdown';
|
||||
|
||||
const { slug } = Astro.params;
|
||||
@@ -44,72 +45,120 @@ function formatSlug(s: string) {
|
||||
}
|
||||
|
||||
const isAdmin = Astro.cookies.get('admin_session')?.value === '1';
|
||||
const displayTitle = post ? (post.title || formatSlug(post.slug)) : 'Post';
|
||||
---
|
||||
|
||||
<Layout
|
||||
title={post ? (post.title || formatSlug(post.slug)) : 'Post'}
|
||||
title={displayTitle}
|
||||
description={post?.summary}
|
||||
type="article"
|
||||
>
|
||||
<article class="glass p-6 md:p-12 mb-8 md:mb-12 animate-in fade-in slide-in-from-bottom-4 duration-700">
|
||||
{error && (
|
||||
<div class="text-red text-center py-12">
|
||||
<h2 class="text-2xl font-bold mb-4">{error}</h2>
|
||||
<a href="/" class="text-blue underline">Return home</a>
|
||||
</div>
|
||||
)}
|
||||
{/* Reading progress bar */}
|
||||
<div
|
||||
id="reading-progress"
|
||||
class="fixed top-0 left-0 right-0 h-[3px] bg-gradient-to-r from-mauve via-blue to-teal z-[150] origin-left"
|
||||
style="transform: scaleX(0); transition: transform 80ms linear;"
|
||||
aria-hidden="true"
|
||||
></div>
|
||||
|
||||
{post && (
|
||||
<>
|
||||
<header class="mb-8 md:mb-12 border-b border-white/5 pb-8 md:pb-12">
|
||||
<a
|
||||
href="/"
|
||||
class="text-blue hover:text-sky transition-colors mb-6 md:mb-8 inline-flex items-center gap-2 group text-sm md:text-base"
|
||||
style="color: var(--blue);"
|
||||
>
|
||||
<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="md:w-5 md:h-5 transition-transform group-hover:-translate-x-1"><path d="m15 18-6-6 6-6"/></svg>
|
||||
Back to list
|
||||
</a>
|
||||
<div class="flex flex-col md:flex-row md:justify-between md:items-start mt-2 md:mt-4 gap-4">
|
||||
<div class="flex-1 min-w-0">
|
||||
<h1 class="text-3xl md:text-5xl font-extrabold text-mauve mb-3">
|
||||
{post.title || formatSlug(post.slug)}
|
||||
</h1>
|
||||
<div class="flex flex-wrap items-center gap-x-3 gap-y-1 text-sm text-subtext0">
|
||||
<time datetime={post.date}>{formatDate(post.date)}</time>
|
||||
<span class="opacity-50">·</span>
|
||||
<span>{post.reading_time} min read</span>
|
||||
{post.draft && (
|
||||
<>
|
||||
<span class="opacity-50">·</span>
|
||||
<span class="text-peach uppercase tracking-wide font-semibold">Draft</span>
|
||||
</>
|
||||
)}
|
||||
{post.tags?.length > 0 && (
|
||||
<>
|
||||
<span class="opacity-50">·</span>
|
||||
<div class="flex flex-wrap gap-1.5">
|
||||
{post.tags.map(tag => (
|
||||
<span class="text-[10px] uppercase tracking-wider px-2 py-0.5 rounded-full bg-surface0 text-subtext0 border border-surface1">{tag}</span>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{error && (
|
||||
<div class="max-w-2xl mx-auto py-16 md:py-24 text-center">
|
||||
<h2 class="text-2xl md:text-3xl font-bold text-red mb-4">{error}</h2>
|
||||
<a href="/" class="inline-flex items-center gap-2 text-blue hover:text-sky 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" aria-hidden="true"><path d="m15 18-6-6 6-6"/></svg>
|
||||
Back home
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{post && (
|
||||
<article class="animate-in fade-in slide-in-from-bottom-2 duration-500">
|
||||
{/* Toolbar: Back to list + admin actions */}
|
||||
<div class="flex items-center justify-between gap-3 mb-8 md:mb-12">
|
||||
<a
|
||||
href="/"
|
||||
class="inline-flex items-center gap-2 text-subtext0 hover:text-text transition-colors text-sm group"
|
||||
>
|
||||
<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="transition-transform group-hover:-translate-x-1" aria-hidden="true"><path d="m15 18-6-6 6-6"/></svg>
|
||||
Back to list
|
||||
</a>
|
||||
|
||||
{isAdmin && (
|
||||
<div class="flex items-center gap-2">
|
||||
<a
|
||||
id="edit-link"
|
||||
href={`/admin/editor?edit=${encodeURIComponent(post.slug)}`}
|
||||
class={`bg-surface0 hover:bg-surface1 text-blue px-3 py-1.5 md:px-4 md:py-2 rounded border border-surface1 transition-colors inline-flex items-center gap-2 text-sm md:text-base self-start ${isAdmin ? '' : 'hidden'}`}
|
||||
class="inline-flex items-center gap-2 bg-surface0 hover:bg-blue/15 text-subtext1 hover:text-blue px-3 py-1.5 md:px-4 md:py-2 rounded-md border border-surface1 hover:border-blue/30 transition-colors text-sm"
|
||||
>
|
||||
<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="md:w-4 md:h-4"><path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/><path d="m15 5 4 4"/></svg>
|
||||
<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" aria-hidden="true"><path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/><path d="m15 5 4 4"/></svg>
|
||||
Edit
|
||||
</a>
|
||||
<DeletePostButton slug={post.slug} title={displayTitle} client:load />
|
||||
</div>
|
||||
</header>
|
||||
<div id="post-content" class="prose" set:html={html} />
|
||||
</>
|
||||
)}
|
||||
</article>
|
||||
</Layout>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Hero header — centered title + meta */}
|
||||
<header class="max-w-3xl mx-auto text-center mb-10 md:mb-16">
|
||||
<div class="flex flex-wrap items-center justify-center gap-x-3 gap-y-1 mb-4 text-xs md:text-sm text-subtext0">
|
||||
<time datetime={post.date}>{formatDate(post.date)}</time>
|
||||
<span class="opacity-50">·</span>
|
||||
<span>{post.reading_time} min read</span>
|
||||
{post.draft && (
|
||||
<>
|
||||
<span class="opacity-50">·</span>
|
||||
<span class="text-peach uppercase tracking-wide font-semibold">Draft</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<h1 class="text-3xl md:text-5xl lg:text-6xl font-extrabold text-mauve leading-[1.1] mb-6 md:mb-8 tracking-tight">
|
||||
{displayTitle}
|
||||
</h1>
|
||||
{post.summary && (
|
||||
<p class="text-base md:text-xl text-subtext1 leading-relaxed max-w-2xl mx-auto">
|
||||
{post.summary}
|
||||
</p>
|
||||
)}
|
||||
{post.tags?.length > 0 && (
|
||||
<div class="flex flex-wrap justify-center gap-2 mt-6">
|
||||
{post.tags.map(tag => (
|
||||
<span class="text-[10px] uppercase tracking-wider px-2.5 py-1 rounded-full bg-surface0 text-subtext0 border border-surface1">{tag}</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</header>
|
||||
|
||||
<div class="w-full max-w-3xl mx-auto h-px bg-gradient-to-r from-transparent via-surface2 to-transparent mb-10 md:mb-16"></div>
|
||||
|
||||
{/* Body */}
|
||||
<div id="post-content" class="prose px-1" set:html={html} />
|
||||
|
||||
{/* Footer separator + back link */}
|
||||
<div class="max-w-3xl mx-auto mt-16 md:mt-24 pt-8 md:pt-12 border-t border-surface1/60 text-center">
|
||||
<a
|
||||
href="/"
|
||||
class="inline-flex items-center gap-2 text-subtext0 hover:text-mauve transition-colors text-sm group"
|
||||
>
|
||||
<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="transition-transform group-hover:-translate-x-1" aria-hidden="true"><path d="m15 18-6-6 6-6"/></svg>
|
||||
Back to all posts
|
||||
</a>
|
||||
</div>
|
||||
</article>
|
||||
)}
|
||||
|
||||
<script is:inline>
|
||||
(function () {
|
||||
const bar = document.getElementById('reading-progress');
|
||||
const article = document.getElementById('post-content');
|
||||
if (!bar || !article) return;
|
||||
function update() {
|
||||
const startY = article.offsetTop;
|
||||
const distance = Math.max(1, article.offsetHeight - window.innerHeight);
|
||||
const pct = Math.max(0, Math.min(1, (window.scrollY - startY) / distance));
|
||||
bar.style.transform = 'scaleX(' + pct + ')';
|
||||
}
|
||||
update();
|
||||
window.addEventListener('scroll', update, { passive: true });
|
||||
window.addEventListener('resize', update);
|
||||
})();
|
||||
</script>
|
||||
</Layout>
|
||||
|
||||
Reference in New Issue
Block a user