feat: redesign homepage as multi-column visual landing page
- Full-width hero with CSS grid: site title/tagline (left) + hand-coded SVG workbench illustration (right), stacks on mobile - Featured excerpt section with terracotta left border and inline diagram from 'Your Build Cache Is Lying to You' - Rage series as 2-column card grid at container width (1100px): each card shows number, title, description, reading time or 'Coming soon' badge - All posts as 2-column grid at reading width (720px) - Three-tier width system: hero (100%), series (--width-container), all-posts (--width-prose) - Responsive: collapses to single column below 768px - Updated tests to verify multi-column layout, SVG illustration, card grid structure, and responsive breakpoints (41 tests, all pass)
This commit is contained in:
+349
-142
@@ -16,6 +16,15 @@ const ragePosts: Record<string, string> = {
|
||||
'TypeScript 7 Makes the Build Harness More Important': 'typescript-7-makes-the-build-harness-more-important',
|
||||
};
|
||||
|
||||
const rageDescriptions = [
|
||||
'The silent stale cache problem. Every JS monorepo build cache shares this flaw.',
|
||||
'Weak fingerprints find candidates. Strong fingerprints verify them. The pathset is the bridge.',
|
||||
'Three operating systems. Three interception mechanisms. macOS interpose, Linux eBPF, Windows Detours.',
|
||||
'Counter-intuitive: tsgo\u2019s 10x speedup makes distributed coordination more important, not less.',
|
||||
'Per-package content-addressed storage. The unsexy problem that kills CI build times.',
|
||||
'Personal narrative. From lage maintainer to Rust rewrite. The architecture\u2019s original sin.',
|
||||
];
|
||||
|
||||
const rageSeries = [
|
||||
'Your Build Cache Is Lying to You',
|
||||
'Two-Phase Fingerprinting: BuildXL\u2019s Deep Magic',
|
||||
@@ -23,7 +32,7 @@ const rageSeries = [
|
||||
'TypeScript 7 Makes the Build Harness More Important',
|
||||
'node_modules in a Content-Addressed Store',
|
||||
'What I Learned Maintaining Lage and Why I Rewrote It',
|
||||
].map(title => {
|
||||
].map((title, i) => {
|
||||
const slug = ragePosts[title];
|
||||
const post = slug ? posts.find(p => p.id === slug) : null;
|
||||
return {
|
||||
@@ -31,6 +40,8 @@ const rageSeries = [
|
||||
slug: slug || null,
|
||||
published: !!post,
|
||||
readingTime: post?.body ? getReadingTime(post.body) : null,
|
||||
number: i + 1,
|
||||
description: rageDescriptions[i],
|
||||
};
|
||||
});
|
||||
---
|
||||
@@ -38,93 +49,185 @@ const rageSeries = [
|
||||
<BaseLayout title='Crash Test Dev'>
|
||||
<NavBar slot="nav" />
|
||||
|
||||
<!-- Hero: full-width, two-column -->
|
||||
<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 Chau</p>
|
||||
<div class="hero-text">
|
||||
<h1 class="site-title">Crash Test Dev</h1>
|
||||
<p class="tagline">Engineering judgment, tested against reality.</p>
|
||||
<p class="byline">By Ken Chau</p>
|
||||
</div>
|
||||
<div class="hero-illustration" aria-hidden="true">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 400" role="img" aria-label="Engineering workbench illustration">
|
||||
<!-- Background -->
|
||||
<rect width="400" height="400" fill="#F5F0E8" rx="6"/>
|
||||
|
||||
<!-- Pegboard grid (subtle dots) -->
|
||||
<g fill="#DDD7CD">
|
||||
<circle cx="60" cy="60" r="2"/><circle cx="100" cy="60" r="2"/><circle cx="140" cy="60" r="2"/><circle cx="180" cy="60" r="2"/><circle cx="220" cy="60" r="2"/><circle cx="260" cy="60" r="2"/><circle cx="300" cy="60" r="2"/><circle cx="340" cy="60" r="2"/>
|
||||
<circle cx="60" cy="100" r="2"/><circle cx="100" cy="100" r="2"/><circle cx="140" cy="100" r="2"/><circle cx="180" cy="100" r="2"/><circle cx="220" cy="100" r="2"/><circle cx="260" cy="100" r="2"/><circle cx="300" cy="100" r="2"/><circle cx="340" cy="100" r="2"/>
|
||||
<circle cx="60" cy="140" r="2"/><circle cx="100" cy="140" r="2"/><circle cx="140" cy="140" r="2"/><circle cx="180" cy="140" r="2"/><circle cx="220" cy="140" r="2"/><circle cx="260" cy="140" r="2"/><circle cx="300" cy="140" r="2"/><circle cx="340" cy="140" r="2"/>
|
||||
</g>
|
||||
|
||||
<!-- Wrench (hanging tool) -->
|
||||
<line x1="100" y1="60" x2="100" y2="90" stroke="#2B2421" stroke-width="2"/>
|
||||
<rect x="88" y="90" width="24" height="8" rx="2" fill="none" stroke="#2B2421" stroke-width="1.5"/>
|
||||
<rect x="92" y="98" width="4" height="32" rx="1" fill="#2B2421"/>
|
||||
<circle cx="94" cy="134" r="8" fill="none" stroke="#2B2421" stroke-width="1.5"/>
|
||||
|
||||
<!-- Caliper (hanging tool) -->
|
||||
<line x1="220" y1="60" x2="220" y2="85" stroke="#2B2421" stroke-width="2"/>
|
||||
<rect x="205" y="85" width="30" height="6" rx="1" fill="#7D6C2F"/>
|
||||
<line x1="210" y1="91" x2="210" y2="140" stroke="#2B2421" stroke-width="1.5"/>
|
||||
<line x1="230" y1="91" x2="230" y2="130" stroke="#2B2421" stroke-width="1.5"/>
|
||||
<line x1="210" y1="140" x2="218" y2="148" stroke="#2B2421" stroke-width="1.5"/>
|
||||
<line x1="230" y1="130" x2="222" y2="138" stroke="#2B2421" stroke-width="1.5"/>
|
||||
|
||||
<!-- Gear 1 (large) -->
|
||||
<circle cx="160" cy="260" r="44" fill="none" stroke="#A0522D" stroke-width="2"/>
|
||||
<circle cx="160" cy="260" r="32" fill="none" stroke="#A0522D" stroke-width="1.5"/>
|
||||
<circle cx="160" cy="260" r="8" fill="#A0522D"/>
|
||||
<!-- Gear teeth -->
|
||||
<rect x="155" y="212" width="10" height="10" rx="1" fill="#A0522D"/>
|
||||
<rect x="155" y="298" width="10" height="10" rx="1" fill="#A0522D"/>
|
||||
<rect x="200" y="255" width="10" height="10" rx="1" fill="#A0522D"/>
|
||||
<rect x="112" y="255" width="10" height="10" rx="1" fill="#A0522D"/>
|
||||
<rect x="188" y="225" width="10" height="10" rx="1" fill="#A0522D" transform="rotate(45 193 230)"/>
|
||||
<rect x="122" y="285" width="10" height="10" rx="1" fill="#A0522D" transform="rotate(45 127 290)"/>
|
||||
<rect x="122" y="225" width="10" height="10" rx="1" fill="#A0522D" transform="rotate(-45 127 230)"/>
|
||||
<rect x="188" y="285" width="10" height="10" rx="1" fill="#A0522D" transform="rotate(-45 193 290)"/>
|
||||
|
||||
<!-- Gear 2 (smaller, interlocking) -->
|
||||
<circle cx="280" cy="240" r="28" fill="none" stroke="#7D6C2F" stroke-width="2"/>
|
||||
<circle cx="280" cy="240" r="18" fill="none" stroke="#7D6C2F" stroke-width="1.5"/>
|
||||
<circle cx="280" cy="240" r="5" fill="#7D6C2F"/>
|
||||
<!-- Gear teeth -->
|
||||
<rect x="275" y="208" width="10" height="8" rx="1" fill="#7D6C2F"/>
|
||||
<rect x="275" y="264" width="10" height="8" rx="1" fill="#7D6C2F"/>
|
||||
<rect x="304" y="235" width="8" height="10" rx="1" fill="#7D6C2F"/>
|
||||
<rect x="248" y="235" width="8" height="10" rx="1" fill="#7D6C2F"/>
|
||||
|
||||
<!-- Workbench surface -->
|
||||
<rect x="30" y="340" width="340" height="12" rx="2" fill="#2B2421"/>
|
||||
<rect x="24" y="352" width="16" height="28" rx="1" fill="#2B2421"/>
|
||||
<rect x="360" y="352" width="16" height="28" rx="1" fill="#2B2421"/>
|
||||
|
||||
<!-- Blueprint/ruler on bench -->
|
||||
<rect x="50" y="324" width="80" height="14" rx="1" fill="none" stroke="#7D6C2F" stroke-width="1"/>
|
||||
<line x1="60" y1="324" x2="60" y2="338" stroke="#7D6C2F" stroke-width="0.5"/>
|
||||
<line x1="70" y1="328" x2="70" y2="338" stroke="#7D6C2F" stroke-width="0.5"/>
|
||||
<line x1="80" y1="324" x2="80" y2="338" stroke="#7D6C2F" stroke-width="0.5"/>
|
||||
<line x1="90" y1="328" x2="90" y2="338" stroke="#7D6C2F" stroke-width="0.5"/>
|
||||
<line x1="100" y1="324" x2="100" y2="338" stroke="#7D6C2F" stroke-width="0.5"/>
|
||||
<line x1="110" y1="328" x2="110" y2="338" stroke="#7D6C2F" stroke-width="0.5"/>
|
||||
<line x1="120" y1="324" x2="120" y2="338" stroke="#7D6C2F" stroke-width="0.5"/>
|
||||
</svg>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<hr class="divider" />
|
||||
|
||||
<!-- Featured excerpt: full-width with visual -->
|
||||
<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>
|
||||
<div class="featured-inner">
|
||||
<div class="featured-content">
|
||||
<p class="featured-label">From the series</p>
|
||||
<blockquote class="excerpt">
|
||||
Your CI passed. Your tests passed. The build cache said nothing changed. It was wrong. You shipped a broken build to production because a cache returned stale output from three weeks ago. The bug took two days to find. The fix was one line. The root cause was not your code. It was your build system's trust model.
|
||||
</blockquote>
|
||||
<a class="featured-link" href={`${base}posts/your-build-cache-is-lying-to-you/`}>
|
||||
Read "Your Build Cache Is Lying to You" →
|
||||
</a>
|
||||
</div>
|
||||
<div class="featured-diagram" aria-hidden="true">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 420 200" role="img" aria-label="The Silent Lie: how an undeclared input causes a stale cache hit">
|
||||
<rect width="420" height="200" fill="#F5F0E8" rx="4"/>
|
||||
<text x="210" y="24" text-anchor="middle" font-family="Georgia, serif" font-size="12" font-weight="bold" fill="#2B2421">The Silent Lie</text>
|
||||
<rect x="14" y="40" width="100" height="44" rx="3" fill="none" stroke="#2B2421" stroke-width="1.2"/>
|
||||
<text x="64" y="58" text-anchor="middle" font-family="monospace" font-size="8" fill="#2B2421">Developer changes</text>
|
||||
<text x="64" y="72" text-anchor="middle" font-family="monospace" font-size="8" font-weight="bold" fill="#A0522D">feature-flags.json</text>
|
||||
<line x1="114" y1="62" x2="136" y2="62" stroke="#2B2421" stroke-width="1.2"/>
|
||||
<polygon points="134,58 142,62 134,66" fill="#2B2421"/>
|
||||
<rect x="144" y="40" width="100" height="44" rx="3" fill="none" stroke="#2B2421" stroke-width="1.2"/>
|
||||
<text x="194" y="56" text-anchor="middle" font-family="monospace" font-size="7.5" fill="#2B2421">Cache check:</text>
|
||||
<text x="194" y="68" text-anchor="middle" font-family="monospace" font-size="7.5" fill="#2B2421">hash(src/**/*.ts)</text>
|
||||
<text x="194" y="78" text-anchor="middle" font-family="monospace" font-size="7.5" fill="#2B2421">Nothing changed</text>
|
||||
<line x1="244" y1="62" x2="266" y2="62" stroke="#2B2421" stroke-width="1.2"/>
|
||||
<polygon points="264,58 272,62 264,66" fill="#2B2421"/>
|
||||
<rect x="274" y="40" width="60" height="44" rx="3" fill="#A0522D"/>
|
||||
<text x="304" y="60" text-anchor="middle" font-family="Georgia, serif" font-size="11" font-weight="bold" fill="#F5F0E8">MATCH</text>
|
||||
<text x="304" y="74" text-anchor="middle" font-family="monospace" font-size="7" fill="#F5F0E8">(stale!)</text>
|
||||
<line x1="334" y1="62" x2="356" y2="62" stroke="#2B2421" stroke-width="1.2"/>
|
||||
<polygon points="354,58 362,62 354,66" fill="#2B2421"/>
|
||||
<rect x="364" y="44" width="44" height="36" rx="3" fill="#A0522D"/>
|
||||
<text x="386" y="62" text-anchor="middle" font-family="Georgia, serif" font-size="8" font-weight="bold" fill="#F5F0E8">Bug ships</text>
|
||||
<text x="386" y="72" text-anchor="middle" font-family="Georgia, serif" font-size="8" font-weight="bold" fill="#F5F0E8">to prod</text>
|
||||
<rect x="120" y="110" width="200" height="36" rx="3" fill="none" stroke="#A0522D" stroke-width="1" stroke-dasharray="4,2"/>
|
||||
<text x="220" y="128" text-anchor="middle" font-family="Georgia, serif" font-size="9" fill="#A0522D">feature-flags.json was never declared.</text>
|
||||
<text x="220" y="140" text-anchor="middle" font-family="Georgia, serif" font-size="9" fill="#A0522D">The cache didn't know it existed.</text>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<hr class="divider" />
|
||||
|
||||
<!-- Rage Series: 2-column grid at container width -->
|
||||
<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">
|
||||
<div class="series-grid">
|
||||
{rageSeries.map((item) => (
|
||||
<li class={`series-item ${item.published ? 'published' : ''}`}>
|
||||
{item.published ? (
|
||||
<a href={`${base}posts/${item.slug}/`} class="series-item-link">
|
||||
<span class="series-item-title">{item.title}</span>
|
||||
<span class="series-item-meta">{item.readingTime} min</span>
|
||||
</a>
|
||||
) : (
|
||||
<>
|
||||
<span class="series-item-title">{item.title}</span>
|
||||
<span class="series-item-status">Coming soon</span>
|
||||
</>
|
||||
)}
|
||||
</li>
|
||||
<div class={`series-card ${item.published ? 'published' : ''}`}>
|
||||
<span class="card-number">{String(item.number).padStart(2, '0')}</span>
|
||||
<div class="card-body">
|
||||
<span class="card-title">{item.title}</span>
|
||||
<span class="card-description">{item.description}</span>
|
||||
{item.published ? (
|
||||
<a href={`${base}posts/${item.slug}/`} class="card-link">
|
||||
Read · {item.readingTime} min
|
||||
</a>
|
||||
) : (
|
||||
<span class="card-status">Coming soon</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</ol>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<hr class="divider" />
|
||||
|
||||
<!-- All Posts: compact 2-column grid at reading width -->
|
||||
<section class="all-posts" aria-label="All posts">
|
||||
<h2 class="section-heading">All Posts</h2>
|
||||
{sortedPosts.map((post) => (
|
||||
<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>
|
||||
))}
|
||||
<div class="posts-grid">
|
||||
{sortedPosts.map((post) => (
|
||||
<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>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
</BaseLayout>
|
||||
|
||||
<style>
|
||||
/* Layout column */
|
||||
.hero,
|
||||
.featured,
|
||||
.series,
|
||||
.all-posts {
|
||||
max-width: var(--width-prose);
|
||||
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: full-width two-column ────────────────────────────── */
|
||||
.hero {
|
||||
margin-top: var(--space-10);
|
||||
margin-bottom: var(--space-6);
|
||||
text-align: center;
|
||||
max-width: 100%;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
align-items: center;
|
||||
gap: var(--space-6);
|
||||
padding: var(--space-10) var(--space-8);
|
||||
border-bottom: 1px solid var(--border-subtle);
|
||||
}
|
||||
|
||||
.hero .site-title {
|
||||
.hero-text {
|
||||
max-width: 540px;
|
||||
justify-self: end;
|
||||
}
|
||||
|
||||
.site-title {
|
||||
font-family: var(--font-structure);
|
||||
font-size: var(--type-3xl);
|
||||
font-weight: 700;
|
||||
@@ -134,15 +237,16 @@ const rageSeries = [
|
||||
margin-bottom: var(--space-2);
|
||||
}
|
||||
|
||||
.hero .tagline {
|
||||
.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);
|
||||
font-style: italic;
|
||||
margin-bottom: var(--space-1-5);
|
||||
}
|
||||
|
||||
.hero .byline {
|
||||
.byline {
|
||||
font-family: var(--font-structure);
|
||||
font-size: var(--type-sm);
|
||||
color: var(--text-tertiary);
|
||||
@@ -150,11 +254,37 @@ const rageSeries = [
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
/* Featured excerpt */
|
||||
.hero-illustration {
|
||||
max-width: 400px;
|
||||
justify-self: start;
|
||||
}
|
||||
|
||||
.hero-illustration svg {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
border-radius: var(--rounded-lg);
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
/* ── Featured excerpt: full-width with terracotta border ────── */
|
||||
.featured {
|
||||
max-width: var(--width-container);
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
padding: var(--space-8) 0;
|
||||
border-left: 4px solid var(--accent-primary);
|
||||
margin-top: var(--space-6);
|
||||
margin-bottom: var(--space-6);
|
||||
}
|
||||
|
||||
.featured-inner {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: var(--space-6);
|
||||
align-items: center;
|
||||
padding-left: var(--space-4);
|
||||
}
|
||||
|
||||
.featured-label {
|
||||
font-family: var(--font-structure);
|
||||
font-size: var(--type-xs);
|
||||
@@ -170,8 +300,6 @@ const rageSeries = [
|
||||
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;
|
||||
}
|
||||
@@ -182,9 +310,20 @@ const rageSeries = [
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Series */
|
||||
.featured-diagram svg {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
border-radius: var(--rounded-md);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
/* ── Series: container-width 2-column grid ─────────────────── */
|
||||
.series {
|
||||
margin-bottom: var(--space-6);
|
||||
max-width: var(--width-container);
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
margin-bottom: var(--space-8);
|
||||
padding: var(--space-6) 0;
|
||||
}
|
||||
|
||||
.section-heading {
|
||||
@@ -202,100 +341,106 @@ const rageSeries = [
|
||||
color: var(--text-secondary);
|
||||
line-height: var(--leading-prose);
|
||||
margin-bottom: var(--space-4);
|
||||
max-width: var(--width-prose);
|
||||
}
|
||||
|
||||
.series-list {
|
||||
list-style: none;
|
||||
counter-reset: series;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-1-5);
|
||||
.series-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
.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);
|
||||
.series-card {
|
||||
background: 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);
|
||||
}
|
||||
|
||||
.series-item-link {
|
||||
border-radius: var(--rounded-lg);
|
||||
padding: var(--space-3);
|
||||
box-shadow: var(--shadow-sm);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
width: 100%;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
gap: var(--space-2);
|
||||
align-items: flex-start;
|
||||
transition: box-shadow var(--duration-normal) var(--ease-default);
|
||||
}
|
||||
|
||||
.series-item-link:hover {
|
||||
text-decoration: none;
|
||||
.series-card:hover {
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
.series-item-link:hover .series-item-title {
|
||||
color: var(--accent-primary);
|
||||
}
|
||||
|
||||
.series-item-meta {
|
||||
font-family: var(--font-evidence);
|
||||
font-size: var(--type-xs);
|
||||
color: var(--text-tertiary);
|
||||
flex-shrink: 0;
|
||||
margin-left: var(--space-2);
|
||||
}
|
||||
|
||||
.series-item.published {
|
||||
.series-card.published {
|
||||
border-color: var(--border-default);
|
||||
}
|
||||
|
||||
.series-item.published .series-item-title {
|
||||
.card-number {
|
||||
font-family: var(--font-evidence);
|
||||
font-size: var(--type-xl);
|
||||
font-weight: 700;
|
||||
color: var(--accent-primary);
|
||||
line-height: 1;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.card-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-0-5);
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-family: var(--font-structure);
|
||||
font-size: var(--type-base);
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
line-height: var(--leading-snug);
|
||||
}
|
||||
|
||||
.card-description {
|
||||
font-family: var(--font-prose);
|
||||
font-size: var(--type-sm);
|
||||
color: var(--text-secondary);
|
||||
line-height: var(--leading-normal);
|
||||
}
|
||||
|
||||
.card-link {
|
||||
font-family: var(--font-structure);
|
||||
font-size: var(--type-sm);
|
||||
font-weight: 500;
|
||||
margin-top: var(--space-0-5);
|
||||
transition: color var(--duration-fast) var(--ease-default);
|
||||
}
|
||||
|
||||
/* All posts */
|
||||
.card-status {
|
||||
font-family: var(--font-evidence);
|
||||
font-size: var(--type-xs);
|
||||
color: var(--text-tertiary);
|
||||
margin-top: var(--space-0-5);
|
||||
background: var(--surface-inset);
|
||||
padding: var(--space-0-5) var(--space-1);
|
||||
border-radius: var(--rounded-full);
|
||||
display: inline-block;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
/* ── All posts: reading-width 2-column grid ────────────────── */
|
||||
.all-posts {
|
||||
max-width: var(--width-prose);
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
margin-bottom: var(--space-12);
|
||||
padding: var(--space-6) 0;
|
||||
}
|
||||
|
||||
.all-posts .section-heading {
|
||||
margin-bottom: var(--space-3);
|
||||
}
|
||||
|
||||
.post-entry {
|
||||
border-bottom: 1px solid var(--border-subtle);
|
||||
.posts-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 0 var(--space-4);
|
||||
}
|
||||
|
||||
.post-entry:last-child {
|
||||
border-bottom: none;
|
||||
.post-entry {
|
||||
border-bottom: 1px solid var(--border-subtle);
|
||||
}
|
||||
|
||||
.post-entry-link {
|
||||
@@ -341,4 +486,66 @@ const rageSeries = [
|
||||
color: var(--text-tertiary);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* ── Responsive: tablet ────────────────────────────────────── */
|
||||
@media (max-width: 1023px) {
|
||||
.hero {
|
||||
padding: var(--space-8) var(--space-4);
|
||||
gap: var(--space-4);
|
||||
}
|
||||
|
||||
.hero-text {
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
.hero-illustration {
|
||||
max-width: 320px;
|
||||
}
|
||||
|
||||
.featured-inner {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
|
||||
.posts-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Responsive: mobile ────────────────────────────────────── */
|
||||
@media (max-width: 768px) {
|
||||
.hero {
|
||||
grid-template-columns: 1fr;
|
||||
text-align: center;
|
||||
padding: var(--space-6) var(--space-3);
|
||||
gap: var(--space-4);
|
||||
}
|
||||
|
||||
.hero-text {
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.hero-illustration {
|
||||
justify-self: center;
|
||||
max-width: 280px;
|
||||
}
|
||||
|
||||
.featured {
|
||||
border-left: none;
|
||||
border-top: 4px solid var(--accent-primary);
|
||||
padding: var(--space-4) 0;
|
||||
}
|
||||
|
||||
.featured-inner {
|
||||
grid-template-columns: 1fr;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.series-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.posts-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user