project-bifrost-platform/src/pages/updates/[slug].astro
2026-04-18 22:47:13 +02:00

243 lines
6.5 KiB
Text
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
import { getCollection, getEntry } from 'astro:content';
import AppLayout from '../../layouts/AppLayout.astro';
import { fmtDate } from '../../lib/markdown';
const user = Astro.locals.user;
const { slug } = Astro.params;
const update = await getEntry('updates', slug as string);
if (!update) return Astro.redirect('/updates');
const { Content } = await update.render();
const allUpdates = await getCollection('updates');
const sorted = allUpdates.sort(
(a, b) => new Date(b.data.date).getTime() - new Date(a.data.date).getTime()
);
const currentIndex = sorted.findIndex((u) => u.slug === update.slug);
const prev = sorted[currentIndex + 1] ?? null;
const next = sorted[currentIndex - 1] ?? null;
---
<AppLayout title={update.data.title} user={user}>
<div class="page">
<nav class="breadcrumb" aria-label="Breadcrumb">
<a href="/updates" class="crumb label-sm">Updates</a>
<span class="crumb-sep" aria-hidden="true"></span>
<span class="crumb label-sm crumb-current">{update.data.title}</span>
</nav>
<article class="article">
<header class="article-header">
<time class="label-sm post-date" datetime={String(update.data.date)}>
{fmtDate(String(update.data.date))}
</time>
<h1 class="display-md article-title">{update.data.title}</h1>
<p class="lead article-summary">{update.data.summary}</p>
<p class="label-sm article-author">
<span class="author-name">{update.data.author}</span>
</p>
</header>
<div class="prose">
<Content />
</div>
</article>
<nav class="post-nav" aria-label="Post navigation">
{prev && (
<a href={`/updates/${prev.slug}`} class="post-nav-link post-nav-prev">
<span class="label-sm nav-dir">Earlier</span>
<span class="nav-title body-md">{prev.data.title}</span>
</a>
)}
{next && (
<a href={`/updates/${next.slug}`} class="post-nav-link post-nav-next">
<span class="label-sm nav-dir">Later</span>
<span class="nav-title body-md">{next.data.title}</span>
</a>
)}
</nav>
</div>
</AppLayout>
<style>
.page {
padding: var(--space-10) var(--space-20) var(--space-16);
max-width: var(--content-max);
margin: 0 auto;
}
/* ── Breadcrumb ──────────────────────────────────────────────────── */
.breadcrumb {
display: flex;
align-items: center;
gap: var(--space-2);
margin-bottom: var(--space-8);
}
.crumb {
color: var(--on-surface-muted);
letter-spacing: var(--tracking-wide);
text-transform: uppercase;
text-decoration: none;
border-bottom: none;
transition: color var(--duration-fast) var(--ease-standard);
}
a.crumb:hover {
color: var(--on-surface-variant);
border-bottom: none;
}
.crumb-sep {
color: var(--on-surface-muted);
}
.crumb-current {
color: var(--on-surface-variant);
}
/* ── Article ─────────────────────────────────────────────────────── */
.article {
max-width: var(--reading-max);
}
.article-header {
margin-bottom: var(--space-10);
display: flex;
flex-direction: column;
gap: var(--space-4);
}
.post-date {
letter-spacing: var(--tracking-wide);
text-transform: uppercase;
color: var(--on-surface-muted);
}
.article-title {
margin: 0;
}
.article-summary {
color: var(--on-surface-variant);
margin: 0;
}
.article-author {
color: var(--on-surface-muted);
letter-spacing: var(--tracking-wide);
text-transform: uppercase;
margin: 0;
}
.author-name {
color: var(--secondary);
}
/* ── Prose content ───────────────────────────────────────────────── */
.prose :global(p) {
margin: 0 0 var(--space-5);
color: var(--on-surface-variant);
line-height: var(--leading-relaxed);
font-size: var(--text-body-lg);
}
.prose :global(h2) {
font-family: var(--font-serif);
font-size: var(--text-headline-md);
font-weight: 400;
letter-spacing: var(--tracking-snug);
color: var(--on-surface);
margin: var(--space-10) 0 var(--space-4);
line-height: var(--leading-snug);
}
.prose :global(h3) {
font-family: var(--font-serif);
font-size: var(--text-headline-sm);
font-weight: 400;
letter-spacing: var(--tracking-snug);
color: var(--on-surface);
margin: var(--space-8) 0 var(--space-3);
line-height: var(--leading-snug);
}
.prose :global(ul),
.prose :global(ol) {
padding-left: var(--space-6);
margin: 0 0 var(--space-5);
color: var(--on-surface-variant);
line-height: var(--leading-relaxed);
font-size: var(--text-body-lg);
}
.prose :global(li) {
margin-bottom: var(--space-2);
}
.prose :global(strong) {
font-weight: 600;
color: var(--on-surface);
}
.prose :global(em) {
font-style: italic;
}
.prose :global(code) {
font-family: var(--font-mono);
font-size: 0.875em;
background: var(--surface-container);
padding: 0.15em 0.4em;
border-radius: var(--radius-sm);
color: var(--on-surface);
}
.prose :global(hr) {
border: none;
border-top: var(--ghost-border);
margin: var(--space-8) 0;
}
/* ── Post nav ────────────────────────────────────────────────────── */
.post-nav {
display: flex;
justify-content: space-between;
gap: var(--space-6);
margin-top: var(--space-12);
padding-top: var(--space-8);
border-top: var(--ghost-border);
max-width: var(--reading-max);
}
.post-nav-link {
display: flex;
flex-direction: column;
gap: var(--space-2);
text-decoration: none;
border-bottom: none;
max-width: 48%;
}
.post-nav-link:hover {
border-bottom: none;
}
.post-nav-next {
margin-left: auto;
text-align: right;
}
.nav-dir {
letter-spacing: var(--tracking-wide);
text-transform: uppercase;
color: var(--on-surface-muted);
}
.nav-title {
color: var(--on-surface-variant);
transition: color var(--duration-fast) var(--ease-standard);
}
.post-nav-link:hover .nav-title {
color: var(--on-surface);
}
</style>