Add blog post and support pubDate/tags
CI / ci (push) Successful in 29s
CI / ci (pull_request) Successful in 28s
Docker / docker (pull_request) Successful in 17s

Add new post "After the Silence". Update content schema to use
pubDate and include tags (default empty). Update blog listing,
post page and start page to use pubDate, render tag lists, and
compute/show up to two related posts by tag overlap. Misc
formatting and small display tweaks.
This commit is contained in:
2026-03-07 12:52:05 +01:00
parent 9d929be386
commit 42bea4d9ba
7 changed files with 580 additions and 150 deletions
+202 -12
View File
@@ -1,16 +1,43 @@
---
import BaseLayout from "../../layouts/BaseLayout.astro";
import { getCollection } from "astro:content";
import { getCollection, type CollectionEntry } from "astro:content";
type BlogPost = CollectionEntry<"blog">;
export async function getStaticPaths() {
const posts = await getCollection("blog", ({ data }) => !data.draft);
return posts.map((post) => ({
params: { slug: post.slug },
props: { post },
props: {
post,
relatedPosts: posts
.filter((candidate) => candidate.slug !== post.slug)
.map((candidate) => ({
post: candidate,
score: candidate.data.tags.filter((tag) =>
post.data.tags.includes(tag),
).length,
}))
.sort((a, b) => {
if (b.score !== a.score) return b.score - a.score;
return (
b.post.data.pubDate.valueOf() -
a.post.data.pubDate.valueOf()
);
})
.filter((candidate) => candidate.score > 0)
.slice(0, 2)
.map((candidate) => candidate.post),
},
}));
}
const { post } = Astro.props;
const { post, relatedPosts = [] } = Astro.props as {
post: BlogPost;
relatedPosts: BlogPost[];
};
const { Content } = await post.render();
function formatDate(date: Date) {
@@ -19,7 +46,7 @@ function formatDate(date: Date) {
---
<BaseLayout
title={`${post.data.title} Hidden Den Cafe`}
title={`${post.data.title} - Hidden Den Cafe`}
description={post.data.description}
>
<div class="matrix-bg" aria-hidden="true"></div>
@@ -28,14 +55,60 @@ function formatDate(date: Date) {
<div class="container">
<header class="header fade-in">
<h1 class="title">{post.data.title}</h1>
<p class="date">{formatDate(post.data.date)}</p>
<div class="divider">══════════════════════════════</div>
<p class="date">{formatDate(post.data.pubDate)}</p>
{
post.data.tags.length > 0 && (
<div
class="tag-list"
aria-label={`${post.data.title} tags`}
>
{post.data.tags.map((tag) => (
<span class="tag">{tag}</span>
))}
</div>
)
}
<div class="divider">==============================</div>
</header>
<article class="content fade-in">
<Content />
</article>
{
relatedPosts.length > 0 && (
<section
class="related fade-in"
aria-labelledby="related-posts"
>
<div class="related-head">
<h2 id="related-posts">Related Reading</h2>
<a href="/blog" class="related-link">
Back to blog
</a>
</div>
<ul class="related-list">
{relatedPosts.map((relatedPost) => (
<li class="related-item">
<a
href={`/blog/${relatedPost.slug}`}
class="related-card"
>
<span class="related-date">
{formatDate(
relatedPost.data.pubDate,
)}
</span>
<h3>{relatedPost.data.title}</h3>
<p>{relatedPost.data.description}</p>
</a>
</li>
))}
</ul>
</section>
)
}
<footer class="footer fade-in">
<p>Made with love by Latte</p>
</footer>
@@ -60,8 +133,12 @@ function formatDate(date: Date) {
}
@keyframes grid-move {
0% { transform: translate(0, 0); }
100% { transform: translate(50px, 50px); }
0% {
transform: translate(0, 0);
}
100% {
transform: translate(50px, 50px);
}
}
.main {
@@ -107,7 +184,21 @@ function formatDate(date: Date) {
font-size: 0.85rem;
}
/* Prose styles for markdown content */
.tag-list {
display: flex;
flex-wrap: wrap;
gap: var(--space-xs);
margin-top: var(--space-sm);
}
.tag {
font-size: 0.72rem;
color: var(--color-accent);
border: 1px solid var(--color-surface);
padding: 1px 6px;
border-radius: 999px;
}
.content {
color: var(--color-text);
line-height: 1.8;
@@ -208,6 +299,82 @@ function formatDate(date: Date) {
margin-top: var(--space-xs);
}
.related {
margin-top: var(--space-xl);
padding-top: var(--space-lg);
border-top: 1px solid var(--color-surface);
}
.related-head {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: var(--space-sm);
margin-bottom: var(--space-md);
}
.related-head h2 {
font-size: 1rem;
text-transform: uppercase;
letter-spacing: 2px;
color: var(--color-accent);
}
.related-link {
color: var(--color-blue);
font-size: 0.85rem;
white-space: nowrap;
}
.related-link:hover {
color: var(--color-accent-bright);
}
.related-list {
list-style: none;
display: flex;
flex-direction: column;
gap: var(--space-md);
}
.related-card {
display: block;
padding: var(--space-md);
border-radius: 8px;
background: color-mix(in srgb, var(--color-bg-light) 88%, transparent);
border: 1px solid
color-mix(in srgb, var(--color-surface) 70%, transparent);
transition:
border-color 0.2s ease,
transform 0.2s ease,
box-shadow 0.2s ease;
}
.related-card:hover {
border-color: color-mix(in srgb, var(--color-accent) 55%, transparent);
transform: translateY(-2px);
box-shadow: 0 0 0 1px
color-mix(in srgb, var(--color-accent) 35%, transparent);
}
.related-date {
display: inline-block;
color: var(--color-text-dim);
font-size: 0.8rem;
margin-bottom: var(--space-xs);
}
.related-card h3 {
font-size: 1rem;
color: var(--color-accent-bright);
margin-bottom: var(--space-xs);
}
.related-card p {
color: var(--color-text-dim);
line-height: 1.7;
}
.footer {
margin-top: var(--space-xl);
text-align: center;
@@ -223,9 +390,18 @@ function formatDate(date: Date) {
opacity: 0;
}
.fade-in:nth-child(1) { animation-delay: 0.1s; }
.fade-in:nth-child(2) { animation-delay: 0.2s; }
.fade-in:nth-child(3) { animation-delay: 0.3s; }
.fade-in:nth-child(1) {
animation-delay: 0.1s;
}
.fade-in:nth-child(2) {
animation-delay: 0.2s;
}
.fade-in:nth-child(3) {
animation-delay: 0.3s;
}
.fade-in:nth-child(4) {
animation-delay: 0.4s;
}
@keyframes fadeIn {
from {
@@ -250,6 +426,11 @@ function formatDate(date: Date) {
.divider {
font-size: 0.6rem;
}
.related-head {
flex-direction: column;
align-items: flex-start;
}
}
@media (prefers-reduced-motion: reduce) {
@@ -261,5 +442,14 @@ function formatDate(date: Date) {
animation: none;
opacity: 1;
}
.related-card {
transition: none;
}
.related-card:hover {
transform: none;
box-shadow: none;
}
}
</style>