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:
+164
-3
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user