I rebuilt this site from scratch. Not because the old one was broken. Because every design decision it carried was made for a different era.
React hydration on every page. A CSS framework that fought the design. Build times measured in geological epochs. The code worked. The architecture did not.
This is the new stack, the reasoning behind it, and the principles that will guide everything built on top of it.
The Stack
The new Maxplaining runs on Astro 5, Tailwind CSS v4, and zero JavaScript by default. Every page ships as static HTML unless it explicitly needs interactivity. That is not a limitation — it is the design.
- Astro 5 ships zero client-side JavaScript by default
- Tailwind CSS v4 uses the new CSS-native engine via
@tailwindcss/vite - Content Collections with Zod schemas enforce structure at build time
- View Transitions handle page navigation without a client-side router
- GSAP + ScrollTrigger for scroll-driven animations
- Lenis for smooth scrolling
- Pagefind for build-time search indexing
The best code is the code you never ship to the browser.
The JavaScript budget is under 40KB total. GSAP, ScrollTrigger, Lenis, and inline scripts. No framework runtime. No hydration overhead. Just fast pages.
Content as Infrastructure
The content layer uses Astro’s built-in Content Collections. Every blog post has a typed schema validated by Zod at build time. If the frontmatter is wrong, the build fails. No surprises in production.
const blog = defineCollection({
loader: glob({ pattern: "**/*.{md,mdx}", base: "./src/content/blog" }),
schema: ({ image }) =>
z.object({
title: z.string().max(70),
description: z.string().max(160),
publishedAt: z.coerce.date(),
category: z.enum(["ai-agentic", "building-in-public", "technical"]),
tags: z.array(z.string()).default([]),
draft: z.boolean().default(true),
}),
});
All data access goes through a thin abstraction layer in src/lib/content.ts. This decouples page components from the data source. Phase 2 can swap in an API without touching templates.
export async function getBlogPosts() {
const posts = await getCollection("blog", ({ data }) =>
import.meta.env.PROD ? !data.draft : true,
);
return posts.sort(
(a, b) => b.data.publishedAt.getTime() - a.data.publishedAt.getTime(),
);
}
Design Principles
The visual language follows hard rules:
- Dark mode is the default, not an afterthought
- Typography carries the design — Inter Variable for body, JetBrains Mono for code
- No AI-generated imagery as brand identity — craft over convenience
- Motion is intentional — scroll reveals, parallax, glow effects. Never decorative.
- The code is the design element — syntax-highlighted blocks with copy buttons, dual-theme Shiki rendering
What Comes Next
The roadmap is deliberately small:
- Content — the articles you are reading. AI infrastructure, agentic systems, building in public.
- Projects — a showcase of things I have built and am building.
- Content pipeline — automated workflows from draft to published.
Everything else is distraction. The site grows from these foundations.
This is the starting point. The infrastructure is built. Now the writing begins.