blog: three curated series across the whole catalogue + badge on post pages
Extends the lone security thread into three arcs that read as skill pillars - SECURITY (6), NETWORKING (5), PLATFORM (7) - so every post carries a numbered badge and the catalogue advertises three areas of depth rather than a loose list. Adds the badge to the post detail header too, styled to match the cards.
This commit is contained in:
+31
-4
@@ -1,7 +1,34 @@
|
|||||||
// Post series — numbered badges on blog cards. Keyed by post slug.
|
// Post series - numbered badges on blog cards, keyed by post slug.
|
||||||
// The security series threads the zero-trust arc across posts as they publish.
|
//
|
||||||
|
// Three arcs, each a skill pillar the site advertises. A post carries exactly
|
||||||
|
// ONE badge; the number is its reading order within the arc (curated, not by
|
||||||
|
// date). New posts pick their series at draft time - see the blog-writing skill.
|
||||||
|
//
|
||||||
|
// SECURITY - identity, secrets, least-privilege, blast-radius (zero-trust)
|
||||||
|
// NETWORKING - Cilium/eBPF, edge exposure, TLS, load-balancing
|
||||||
|
// PLATFORM - GitOps delivery, immutable infra, operations, DR
|
||||||
export const series: Record<string, { name: string; number: number }> = {
|
export const series: Record<string, { name: string; number: number }> = {
|
||||||
|
// SECURITY SERIES - the zero-trust spine
|
||||||
"secret-zero": { name: "SECURITY SERIES", number: 1 },
|
"secret-zero": { name: "SECURITY SERIES", number: 1 },
|
||||||
"broker-pattern": { name: "SECURITY SERIES", number: 2 },
|
"workload-least-privilege": { name: "SECURITY SERIES", number: 2 },
|
||||||
"workload-least-privilege": { name: "SECURITY SERIES", number: 3 },
|
"broker-pattern": { name: "SECURITY SERIES", number: 3 },
|
||||||
|
"authentik-sso-2fa": { name: "SECURITY SERIES", number: 4 },
|
||||||
|
"one-doorman-many-doors": { name: "SECURITY SERIES", number: 5 },
|
||||||
|
"clone-not-keys": { name: "SECURITY SERIES", number: 6 },
|
||||||
|
|
||||||
|
// NETWORKING SERIES - the eBPF datapath and the edge
|
||||||
|
"landing-on-cilium": { name: "NETWORKING SERIES", number: 1 },
|
||||||
|
"observe-first-deny-second": { name: "NETWORKING SERIES", number: 2 },
|
||||||
|
"snat-ate-my-source": { name: "NETWORKING SERIES", number: 3 },
|
||||||
|
"outbound-only-exposure": { name: "NETWORKING SERIES", number: 4 },
|
||||||
|
"cert-couldnt-see-its-proof": { name: "NETWORKING SERIES", number: 5 },
|
||||||
|
|
||||||
|
// PLATFORM SERIES - how the fleet is delivered and kept alive
|
||||||
|
"shipping-this-site": { name: "PLATFORM SERIES", number: 1 },
|
||||||
|
"no-ssh": { name: "PLATFORM SERIES", number: 2 },
|
||||||
|
"bastion-is-a-pattern": { name: "PLATFORM SERIES", number: 3 },
|
||||||
|
"init-gating-gpu-readiness": { name: "PLATFORM SERIES", number: 4 },
|
||||||
|
"sleep-half-the-fleet": { name: "PLATFORM SERIES", number: 5 },
|
||||||
|
"restore-or-rumour": { name: "PLATFORM SERIES", number: 6 },
|
||||||
|
"stage-the-locks": { name: "PLATFORM SERIES", number: 7 },
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import Layout from "../../layouts/Layout.astro";
|
import Layout from "../../layouts/Layout.astro";
|
||||||
import { getPosts, formatDate } from "../../lib/blog";
|
import { getPosts, formatDate } from "../../lib/blog";
|
||||||
import { readingTime } from "../../lib/reading";
|
import { readingTime } from "../../lib/reading";
|
||||||
|
import { series } from "../../data/series";
|
||||||
import { render } from "astro:content";
|
import { render } from "astro:content";
|
||||||
import type { GetStaticPaths } from "astro";
|
import type { GetStaticPaths } from "astro";
|
||||||
|
|
||||||
@@ -21,6 +22,12 @@ const { Content } = await render(entry);
|
|||||||
|
|
||||||
<header class="post__head" data-reveal>
|
<header class="post__head" data-reveal>
|
||||||
<div class="post__meta mono">
|
<div class="post__meta mono">
|
||||||
|
{series[entry.id] && (
|
||||||
|
<>
|
||||||
|
<span class="post__series">{series[entry.id].name} · {String(series[entry.id].number).padStart(2, "0")}</span>
|
||||||
|
<span class="post__sep">·</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
<time datetime={entry.data.date.toISOString()}>{formatDate(entry.data.date)}</time>
|
<time datetime={entry.data.date.toISOString()}>{formatDate(entry.data.date)}</time>
|
||||||
<span class="post__sep">·</span>
|
<span class="post__sep">·</span>
|
||||||
<span>{readingTime(entry.body)}</span>
|
<span>{readingTime(entry.body)}</span>
|
||||||
@@ -87,6 +94,16 @@ const { Content } = await render(entry);
|
|||||||
color: var(--text-faint);
|
color: var(--text-faint);
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
.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 {
|
.post__sep {
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
|
|||||||
Reference in New Issue
Block a user