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:
+112
-16
@@ -63,6 +63,24 @@ describe('Index page', () => {
|
||||
const content = readPage();
|
||||
assert.match(content, /class="byline"/, 'should have a byline element');
|
||||
});
|
||||
|
||||
it('should contain an inline SVG hero illustration', () => {
|
||||
const content = readPage();
|
||||
// The hero section should contain an SVG element
|
||||
assert.match(content, /class="hero-illustration"/, 'should have a hero illustration');
|
||||
assert.match(content, /<svg[^>]*viewBox/, 'should have an inline SVG with viewBox');
|
||||
});
|
||||
|
||||
it('should use CSS grid for hero layout', () => {
|
||||
const content = readPage();
|
||||
assert.match(content, /\.hero\s*\{[^}]*display:\s*grid/s, 'hero should use CSS grid');
|
||||
assert.match(content, /\.hero\s*\{[^}]*grid-template-columns/s, 'hero should define grid-template-columns');
|
||||
});
|
||||
|
||||
it('should be full viewport width', () => {
|
||||
const content = readPage();
|
||||
assert.match(content, /\.hero\s*\{[^}]*max-width:\s*100%/s, 'hero should be full viewport width');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Featured excerpt section', () => {
|
||||
@@ -78,12 +96,22 @@ describe('Index page', () => {
|
||||
|
||||
it('should have a link to the full post', () => {
|
||||
const content = readPage();
|
||||
// The featured section should contain a link to read the full post
|
||||
assert.match(content, /class="featured-link"/, 'should have a featured-link to the full post');
|
||||
});
|
||||
|
||||
it('should have a terracotta left border on the featured section', () => {
|
||||
const content = readPage();
|
||||
assert.match(content, /\.featured[\s\S]*?border-left.*accent-primary/s, 'should have terracotta left border');
|
||||
});
|
||||
|
||||
it('should include an inline SVG diagram from a post', () => {
|
||||
const content = readPage();
|
||||
// The featured section should contain a small SVG from one of the posts
|
||||
assert.match(content, /class="featured-diagram"/, 'should have a featured-diagram element');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Series section', () => {
|
||||
describe('Series section - multi-column grid', () => {
|
||||
it('should have a series section', () => {
|
||||
const content = readPage();
|
||||
assert.match(content, /class="series"/, 'should have a section with class series');
|
||||
@@ -94,33 +122,77 @@ describe('Index page', () => {
|
||||
assert.match(content, /Rage/, 'should mention Rage in the series section');
|
||||
});
|
||||
|
||||
it('should list planned posts', () => {
|
||||
it('should use a CSS grid for series cards', () => {
|
||||
const content = readPage();
|
||||
assert.match(content, /class="series-item"/, 'should have series items');
|
||||
assert.match(content, /\.series-grid\s*\{[^}]*display:\s*grid/s, 'should use CSS grid for series cards');
|
||||
assert.match(content, /\.series-grid\s*\{[^}]*grid-template-columns:\s*1fr\s+1fr/s, 'should have 2-column grid');
|
||||
});
|
||||
|
||||
it('should have series card elements', () => {
|
||||
const content = readPage();
|
||||
assert.match(content, /series-card/, 'should have series-card elements');
|
||||
});
|
||||
|
||||
it('should show card numbers in mono font with accent color', () => {
|
||||
const content = readPage();
|
||||
assert.match(content, /class="card-number"/, 'should have card-number elements');
|
||||
assert.match(content, /\.card-number\s*\{[^}]*font-family:\s*var\(--font-evidence\)/s, 'card number should use mono font');
|
||||
assert.match(content, /\.card-number\s*\{[^}]*color:\s*var\(--accent-primary\)/s, 'card number should use accent color');
|
||||
});
|
||||
|
||||
it('should show card descriptions', () => {
|
||||
const content = readPage();
|
||||
assert.match(content, /class="card-description"/, 'should have card-description elements');
|
||||
});
|
||||
|
||||
it('should indicate coming soon items', () => {
|
||||
const content = readPage();
|
||||
assert.match(content, /coming soon/i, 'should indicate coming soon posts');
|
||||
});
|
||||
|
||||
it('should use --width-container for series section width', () => {
|
||||
const content = readPage();
|
||||
assert.match(content, /\.series\s*\{[^}]*max-width:\s*var\(--width-container\)/s, 'series should use container width');
|
||||
});
|
||||
|
||||
it('should collapse to single column on mobile', () => {
|
||||
const content = readPage();
|
||||
// Check for media query that changes grid to 1 column
|
||||
assert.match(content, /@media.*768px[\s\S]*?grid-template-columns:\s*1fr\s*[;}]/s, 'should collapse to 1 column on mobile');
|
||||
});
|
||||
|
||||
it('should have card surface background', () => {
|
||||
const content = readPage();
|
||||
assert.match(content, /\.series-card\s*\{[^}]*background[^:]*:\s*var\(--surface-card\)/s, 'cards should have card surface bg');
|
||||
});
|
||||
});
|
||||
|
||||
describe('All posts section', () => {
|
||||
it('should have a posts listing section below the pitch', () => {
|
||||
describe('All posts section - 2-column grid', () => {
|
||||
it('should have a posts listing section', () => {
|
||||
const content = readPage();
|
||||
assert.match(content, /class="all-posts"/, 'should have a section with class all-posts');
|
||||
});
|
||||
|
||||
it('should map posts to compact entries', () => {
|
||||
it('should map posts to entries', () => {
|
||||
const content = readPage();
|
||||
assert.match(content, /sortedPosts\.map/, 'should map sortedPosts');
|
||||
});
|
||||
|
||||
it('should show post titles as links', () => {
|
||||
const content = readPage();
|
||||
// Each post entry should link to its page
|
||||
assert.match(content, /posts\/\$\{post\.id\}/, 'should link to post pages');
|
||||
});
|
||||
|
||||
it('should use --width-prose for all-posts width', () => {
|
||||
const content = readPage();
|
||||
assert.match(content, /\.all-posts\s*\{[^}]*max-width:\s*var\(--width-prose\)/s, 'all-posts should use prose width');
|
||||
});
|
||||
|
||||
it('should use CSS grid for posts on desktop', () => {
|
||||
const content = readPage();
|
||||
assert.match(content, /\.posts-grid\s*\{[^}]*display:\s*grid/s, 'should use CSS grid for posts');
|
||||
assert.match(content, /\.posts-grid\s*\{[^}]*grid-template-columns:\s*1fr\s+1fr/s, 'should have 2-column posts grid');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Layout', () => {
|
||||
@@ -136,25 +208,49 @@ describe('Index page', () => {
|
||||
});
|
||||
|
||||
describe('Style', () => {
|
||||
it('should use --width-prose for content width', () => {
|
||||
const content = readPage();
|
||||
assert.match(content, /max-width\s*:\s*var\(--width-prose\)/, 'should constrain content to prose width');
|
||||
});
|
||||
|
||||
it('should use --font-structure for hero heading', () => {
|
||||
const content = readPage();
|
||||
assert.match(content, /\.hero[\s\S]*?font-family\s*:\s*var\(--font-structure\)/, 'should use structure font in hero');
|
||||
assert.match(content, /\.hero[\s\S]*?font-family:\s*var\(--font-structure\)/, 'should use structure font in hero');
|
||||
});
|
||||
|
||||
it('should use --font-prose for excerpt', () => {
|
||||
const content = readPage();
|
||||
assert.match(content, /\.excerpt[\s\S]*?font-family\s*:\s*var\(--font-prose\)/, 'should use prose font for excerpt');
|
||||
assert.match(content, /\.excerpt[\s\S]*?font-family:\s*var\(--font-prose\)/, 'should use prose font for excerpt');
|
||||
});
|
||||
|
||||
it('should not have the old .post-list as the main layout', () => {
|
||||
const content = readPage();
|
||||
// The old flat list structure should be gone
|
||||
assert.doesNotMatch(content, /<ArticleCard/, 'should not use ArticleCard component');
|
||||
});
|
||||
|
||||
it('should use different widths for different sections', () => {
|
||||
const content = readPage();
|
||||
// Hero: full width (100%)
|
||||
assert.match(content, /\.hero[\s\S]*?max-width:\s*100%/s, 'hero should be full width');
|
||||
// Series: container width
|
||||
assert.match(content, /\.series[\s\S]*?max-width:\s*var\(--width-container\)/s, 'series should use container width');
|
||||
// All posts: prose width
|
||||
assert.match(content, /\.all-posts[\s\S]*?max-width:\s*var\(--width-prose\)/s, 'all-posts should use prose width');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Rage series data', () => {
|
||||
it('should have descriptions for each series item', () => {
|
||||
const content = readPage();
|
||||
// Check for at least a couple of the descriptions
|
||||
assert.match(content, /silent stale cache/i, 'should have description for post 1');
|
||||
assert.match(content, /Weak fingerprints find candidates/i, 'should have description for post 2');
|
||||
assert.match(content, /content-addressed storage/i, 'should have description for post 5');
|
||||
});
|
||||
|
||||
it('should include all 6 rage series items', () => {
|
||||
const content = readPage();
|
||||
assert.match(content, /Your Build Cache Is Lying to You/, 'should have post 1');
|
||||
assert.match(content, /Two-Phase Fingerprinting/, 'should have post 2');
|
||||
assert.match(content, /Hooking Syscalls/, 'should have post 3');
|
||||
assert.match(content, /TypeScript 7/, 'should have post 4');
|
||||
assert.match(content, /node_modules/, 'should have post 5');
|
||||
assert.match(content, /Maintaining Lage/, 'should have post 6');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user