facelift: blog index - featured security-series opener + image-led grid

Breadcrumb eyebrow (~/jwright/blog), 'Notes from the build.' H1, featured card for the series
opener (text left / hero art scrimmed right, series badge, eager-loaded), remaining posts in the
image-led responsive grid with series badges + reading time. Post CONTENT untouched (index only).
This commit is contained in:
2026-07-03 19:46:07 +10:00
parent 8d955aa802
commit de12f028eb
+164 -3
View File
@@ -2,14 +2,22 @@
import Layout from "../../layouts/Layout.astro";
import Section from "../../components/Section.astro";
import PostList from "../../components/PostList.astro";
import { getPosts, allTags } from "../../lib/blog";
import { getPosts, allTags, formatDate } from "../../lib/blog";
import { readingTime } from "../../lib/reading";
import { series } from "../../data/series";
import { site } from "../../data/site";
const posts = await getPosts();
const tags = allTags(posts);
// Featured = the security-series opener when present, else the newest post.
const featured = posts.find((p) => p.id === "secret-zero") ?? posts[0];
const rest = posts.filter((p) => p.id !== featured.id);
const featuredSeries = series[featured.id];
---
<Layout title="Blog" path="/blog" description="Notes on platform engineering, edge Kubernetes, GPUs and homelab infrastructure.">
<Section id="blog" eyebrow="Writing" index="*" title="Notes from the platform.">
<Section id="blog" eyebrow={`~/${site.handle}/blog`} index="*" title="Notes from the build.">
<p class="lead blog__intro" data-reveal>
Lessons from edge Kubernetes, GPUs, and running infrastructure like it matters.
</p>
@@ -23,7 +31,44 @@ const tags = allTags(posts);
</nav>
)}
<PostList posts={posts} />
{featured && (
<a class="featured" href={`/blog/${featured.id}/`} data-reveal>
<div class="featured__body">
<div class="featured__meta mono">
{featuredSeries && (
<span class="featured__series">
{featuredSeries.name} · {String(featuredSeries.number).padStart(2, "0")}
</span>
)}
<time datetime={featured.data.date.toISOString()}>{formatDate(featured.data.date)}</time>
<span class="featured__sep">·</span>
<span>{readingTime(featured.body)}</span>
</div>
<h3 class="featured__title">{featured.data.title}</h3>
<p class="featured__summary">{featured.data.summary}</p>
<ul class="featured__tags" role="list">
{featured.data.tags.map((t) => (
<li class="tag">{t}</li>
))}
</ul>
<span class="featured__read mono">Read the post →</span>
</div>
{featured.data.hero && (
<div class="featured__art" aria-hidden="true">
<img
src={`${featured.data.hero}?v=2`}
alt=""
width="1600"
height="1073"
loading="eager"
decoding="async"
/>
</div>
)}
</a>
)}
<PostList posts={rest} />
</Section>
</Layout>
@@ -49,4 +94,120 @@ const tags = allTags(posts);
color: var(--text);
text-decoration: none;
}
/* featured card — text left, hero art right, scrimmed into the panel */
.featured {
position: relative;
display: grid;
grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr);
background: var(--panel);
border: 1px solid var(--border-accent);
border-radius: 12px;
overflow: hidden;
color: inherit;
text-decoration: none;
backdrop-filter: var(--panel-blur);
-webkit-backdrop-filter: var(--panel-blur);
margin-bottom: var(--space-6);
transition:
border-color 180ms var(--ease),
box-shadow 180ms var(--ease);
}
.featured:hover {
text-decoration: none;
border-color: var(--border-accent-hover);
box-shadow: var(--glow-card);
}
.featured__body {
padding: var(--space-6);
display: flex;
flex-direction: column;
gap: var(--space-3);
position: relative;
z-index: 1;
}
.featured__meta {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 0.5rem;
font-size: 0.72rem;
color: var(--text-faint);
}
.featured__series {
color: var(--accent-2);
border: 1px solid var(--border-magenta);
border-radius: 3px;
padding: 2px 7px;
letter-spacing: 0.1em;
font-size: 0.66rem;
}
.featured__sep {
opacity: 0.6;
}
.featured__title {
margin: 0;
font-family: var(--font-display);
font-size: var(--step-2);
font-weight: 600;
color: var(--text-strong);
letter-spacing: -0.01em;
}
.featured__summary {
margin: 0;
color: var(--text-dim);
font-size: var(--step-0);
line-height: 1.65;
text-wrap: pretty;
}
.featured__tags {
list-style: none;
padding: 0;
margin: var(--space-2) 0 0;
display: flex;
flex-wrap: wrap;
gap: 0.4rem;
}
.featured__read {
margin-top: auto;
padding-top: var(--space-3);
font-size: 0.8rem;
color: var(--accent);
}
.featured__art {
position: relative;
min-height: 280px;
}
.featured__art img {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
/* scrim the art into the panel from the text side */
.featured__art::before {
content: "";
position: absolute;
inset: 0;
z-index: 1;
background: linear-gradient(90deg, var(--panel-solid) 0%, transparent 45%);
}
[data-theme="light"] .featured__art::before {
background: linear-gradient(90deg, var(--panel-solid) 0%, transparent 45%);
opacity: 0.85;
}
@media (max-width: 860px) {
.featured {
grid-template-columns: 1fr;
}
.featured__art {
order: -1;
min-height: 200px;
}
.featured__art::before {
background: linear-gradient(180deg, transparent 55%, var(--panel-solid) 100%);
}
}
</style>