feat: redesign homepage from flat card list to book-style pitch page

This commit is contained in:
Ken
2026-05-27 01:25:52 +00:00
parent adc487e63b
commit 29b7dcc51d
3 changed files with 374 additions and 104 deletions
+260 -14
View File
@@ -2,38 +2,284 @@
import { getCollection } from 'astro:content';
import BaseLayout from '../layouts/BaseLayout.astro';
import NavBar from '../components/NavBar.astro';
import ArticleCard from '../components/ArticleCard.astro';
import { getReadingTime } from '../utils/reading-time';
const posts = await getCollection('posts');
const sortedPosts = posts.sort((a, b) => b.data.date.getTime() - a.data.date.getTime());
const base = import.meta.env.BASE_URL.replace(/\/?$/, '/');
const rageSeries = [
{ title: 'Your Build Cache Is Lying to You', status: 'coming-soon' },
{ title: 'Two-Phase Fingerprinting: BuildXL\u2019s Deep Magic', status: 'coming-soon' },
{ title: 'Hooking Syscalls Without a Kernel Driver', status: 'coming-soon' },
{ title: 'TypeScript 7 Makes the Build Harness More Important', status: 'coming-soon' },
{ title: 'node_modules in a Content-Addressed Store', status: 'coming-soon' },
{ title: 'What I Learned Maintaining Lage and Why I Rewrote It', status: 'coming-soon' },
];
---
<BaseLayout title='Crash Test Dev'>
<NavBar slot="nav" />
<section class="post-list" aria-label="All posts">
<section class="hero" aria-label="Introduction">
<h1 class="site-title">Crash Test Dev</h1>
<p class="tagline">Engineering judgment, tested against reality.</p>
<p class="byline">By Ken Hiatt</p>
</section>
<hr class="divider" />
<section class="featured" aria-label="Featured excerpt">
<p class="featured-label">From the latest</p>
<blockquote class="excerpt">
AI raises the floor dramatically. It does not flatten the ceiling. The profession that survives is the one hardest to demo without, not just hardest to do without. The professional sees second-order failures coming. The amateur with AI doesn't know there is a second order.
</blockquote>
<a class="featured-link" href={`${base}posts/ai-commoditizes-features-but-judgment-compounds/`}>
Read "AI Commoditizes Features, But Judgment Compounds"
</a>
</section>
<hr class="divider" />
<section class="series" aria-label="Rage series">
<h2 class="section-heading">Rage: Rebuilding the Build System</h2>
<p class="series-description">A six-part investigation into rage, a Rust monorepo build system that fuses BuildXL's provable correctness with lage's ergonomics. From syscall sandboxing to content-addressed storage.</p>
<ol class="series-list">
{rageSeries.map((item) => (
<li class="series-item">
<span class="series-item-title">{item.title}</span>
<span class="series-item-status">Coming soon</span>
</li>
))}
</ol>
</section>
<hr class="divider" />
<section class="all-posts" aria-label="All posts">
<h2 class="section-heading">All Posts</h2>
{sortedPosts.map((post) => (
<ArticleCard
title={post.data.title}
date={post.data.date}
type={post.data.type}
summary={post.data.summary}
slug={post.id}
readingTime={post.body ? getReadingTime(post.body) : undefined}
/>
<article class="post-entry">
<a href={`${base}posts/${post.id}/`} class="post-entry-link">
<span class="post-entry-badge">{post.data.type}</span>
<span class="post-entry-title">{post.data.title}</span>
{post.body && (
<span class="post-entry-meta">{getReadingTime(post.body)} min</span>
)}
</a>
</article>
))}
</section>
</BaseLayout>
<style>
.post-list {
/* Layout column */
.hero,
.featured,
.series,
.all-posts {
max-width: var(--width-prose);
margin-top: var(--space-6);
margin-bottom: var(--space-12);
margin-left: auto;
margin-right: auto;
}
/* Dividers */
.divider {
max-width: var(--width-prose);
margin: var(--space-6) auto;
border: none;
border-top: 1px solid var(--border-subtle);
}
/* Hero */
.hero {
margin-top: var(--space-10);
margin-bottom: var(--space-6);
text-align: center;
}
.hero .site-title {
font-family: var(--font-structure);
font-size: var(--type-3xl);
font-weight: 700;
color: var(--text-primary);
line-height: var(--leading-tight);
letter-spacing: -0.02em;
margin-bottom: var(--space-2);
}
.hero .tagline {
font-family: var(--font-prose);
font-size: var(--type-lg);
color: var(--text-secondary);
line-height: var(--leading-prose);
margin-bottom: var(--space-1);
}
.hero .byline {
font-family: var(--font-structure);
font-size: var(--type-sm);
color: var(--text-tertiary);
letter-spacing: 0.03em;
text-transform: uppercase;
}
/* Featured excerpt */
.featured {
margin-bottom: var(--space-6);
}
.featured-label {
font-family: var(--font-structure);
font-size: var(--type-xs);
font-weight: 600;
color: var(--accent-primary);
text-transform: uppercase;
letter-spacing: 0.05em;
margin-bottom: var(--space-2);
}
.excerpt {
font-family: var(--font-prose);
font-size: var(--type-lg);
color: var(--text-primary);
line-height: var(--leading-prose);
border-left: 3px solid var(--accent-primary);
padding-left: var(--space-3);
margin-bottom: var(--space-3);
font-style: italic;
}
.featured-link {
font-family: var(--font-structure);
font-size: var(--type-sm);
font-weight: 500;
}
/* Series */
.series {
margin-bottom: var(--space-6);
}
.section-heading {
font-family: var(--font-structure);
font-size: var(--type-xl);
font-weight: 600;
color: var(--text-primary);
line-height: var(--leading-snug);
margin-bottom: var(--space-1-5);
}
.series-description {
font-family: var(--font-prose);
font-size: var(--type-base);
color: var(--text-secondary);
line-height: var(--leading-prose);
margin-bottom: var(--space-4);
}
.series-list {
list-style: none;
counter-reset: series;
display: flex;
flex-direction: column;
gap: var(--space-2);
gap: var(--space-1-5);
}
.series-item {
counter-increment: series;
display: flex;
justify-content: space-between;
align-items: baseline;
padding: var(--space-1) var(--space-1-5);
border-radius: var(--rounded-md);
background-color: var(--surface-card);
border: 1px solid var(--border-subtle);
}
.series-item::before {
content: counter(series) ".";
font-family: var(--font-evidence);
font-size: var(--type-sm);
color: var(--text-tertiary);
margin-right: var(--space-1-5);
flex-shrink: 0;
}
.series-item-title {
font-family: var(--font-structure);
font-size: var(--type-sm);
font-weight: 500;
color: var(--text-primary);
flex: 1;
}
.series-item-status {
font-family: var(--font-evidence);
font-size: var(--type-xs);
color: var(--text-tertiary);
flex-shrink: 0;
margin-left: var(--space-2);
}
/* All posts */
.all-posts {
margin-bottom: var(--space-12);
}
.all-posts .section-heading {
margin-bottom: var(--space-3);
}
.post-entry {
border-bottom: 1px solid var(--border-subtle);
}
.post-entry:last-child {
border-bottom: none;
}
.post-entry-link {
display: flex;
align-items: baseline;
gap: var(--space-1-5);
padding: var(--space-1-5) 0;
text-decoration: none;
color: inherit;
transition: color var(--duration-fast) var(--ease-default);
}
.post-entry-link:hover {
text-decoration: none;
}
.post-entry-link:hover .post-entry-title {
color: var(--accent-primary);
}
.post-entry-badge {
font-family: var(--font-structure);
font-size: var(--type-xs);
font-weight: 600;
color: var(--accent-primary);
text-transform: uppercase;
letter-spacing: 0.05em;
flex-shrink: 0;
}
.post-entry-title {
font-family: var(--font-structure);
font-size: var(--type-base);
font-weight: 500;
color: var(--text-primary);
flex: 1;
transition: color var(--duration-fast) var(--ease-default);
}
.post-entry-meta {
font-family: var(--font-evidence);
font-size: var(--type-xs);
color: var(--text-tertiary);
flex-shrink: 0;
}
</style>