8d955aa802
02 Capabilities becomes a 4-col bento (Edge AI 2x2 flagship w/ magenta corner radial, Platform wide, magenta-flavoured security tile, Observability row-completer) driven by span/flavor fields in skills.ts. 03 Selected Work becomes dossier cards: house-style SVG thumbnails (per-project motif via DossierThumb), DOSSIER/00N corner tags, 'the argument' line from the outcome field, chip tags, mono meta footer. 04 Writing cards gain the security-series badge (data/series.ts) and sit three across. Section numbers glow cyan; a circuit divider (gradient hairlines + two diamonds) closes the hero. Footer opens with the GitOps receipt - git sha + build date baked at build time via lib/build-info.ts, zero runtime calls.
129 lines
3.2 KiB
Plaintext
129 lines
3.2 KiB
Plaintext
---
|
|
import type { CollectionEntry } from "astro:content";
|
|
import { formatDate } from "../lib/blog";
|
|
import { readingTime } from "../lib/reading";
|
|
import { series } from "../data/series";
|
|
|
|
interface Props {
|
|
posts: CollectionEntry<"blog">[];
|
|
}
|
|
const { posts } = Astro.props;
|
|
---
|
|
|
|
<ol class="posts">
|
|
{
|
|
posts.map((post) => (
|
|
<li class="post" data-reveal>
|
|
<a class="post__link" href={`/blog/${post.id}/`}>
|
|
{post.data.hero && (
|
|
<div class="post__thumb">
|
|
<img src={`${post.data.hero}?v=2`} alt="" width="1600" height="1073" loading="lazy" decoding="async" />
|
|
</div>
|
|
)}
|
|
<div class="post__card-body">
|
|
<div class="post__meta mono">
|
|
{series[post.id] && (
|
|
<span class="post__series">
|
|
{series[post.id].name} · {String(series[post.id].number).padStart(2, "0")}
|
|
</span>
|
|
)}
|
|
<time datetime={post.data.date.toISOString()}>{formatDate(post.data.date)}</time>
|
|
<span class="post__sep">·</span>
|
|
<span>{readingTime(post.body)}</span>
|
|
</div>
|
|
<h3 class="post__title">{post.data.title}</h3>
|
|
<p class="post__summary">{post.data.summary}</p>
|
|
<ul class="post__tags">
|
|
{post.data.tags.map((t) => (
|
|
<li class="tag">{t}</li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
</a>
|
|
</li>
|
|
))
|
|
}
|
|
</ol>
|
|
|
|
<style>
|
|
.posts {
|
|
list-style: none;
|
|
margin: 0;
|
|
padding: 0;
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(min(100%, 20rem), 1fr));
|
|
gap: var(--space-4);
|
|
}
|
|
.post__link {
|
|
display: block;
|
|
overflow: hidden;
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius);
|
|
background: var(--surface);
|
|
color: inherit;
|
|
transition: border-color 0.2s var(--ease), transform 0.2s var(--ease);
|
|
}
|
|
.post__thumb {
|
|
aspect-ratio: 1600 / 1073;
|
|
overflow: hidden;
|
|
border-bottom: 1px solid var(--border);
|
|
background: var(--surface-2);
|
|
}
|
|
.post__thumb img {
|
|
display: block;
|
|
width: 100%;
|
|
height: 100%;
|
|
object-fit: cover;
|
|
transition: transform 0.3s var(--ease);
|
|
}
|
|
.post__link:hover .post__thumb img {
|
|
transform: scale(1.03);
|
|
}
|
|
.post__card-body {
|
|
padding: var(--space-5);
|
|
}
|
|
.post__link:hover {
|
|
text-decoration: none;
|
|
border-color: var(--accent-line);
|
|
transform: translateY(-2px);
|
|
}
|
|
.post__meta {
|
|
font-size: 0.72rem;
|
|
color: var(--text-faint);
|
|
display: flex;
|
|
align-items: center;
|
|
flex-wrap: wrap;
|
|
gap: 0.5rem;
|
|
}
|
|
.post__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;
|
|
}
|
|
.post__sep {
|
|
opacity: 0.6;
|
|
}
|
|
.post__title {
|
|
margin-top: var(--space-2);
|
|
font-size: var(--step-1);
|
|
color: var(--text);
|
|
}
|
|
.post__summary {
|
|
margin-top: var(--space-2);
|
|
color: var(--text-dim);
|
|
font-size: var(--step--1);
|
|
line-height: 1.6;
|
|
}
|
|
.post__tags {
|
|
list-style: none;
|
|
padding: 0;
|
|
margin: var(--space-3) 0 0;
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 0.4rem;
|
|
}
|
|
</style>
|