Skip to content
A generic cover for a blog post about building bdbch.com — a dark-themed personal site built with Astro and AI tools
Development

Building bdbch.com: How I built my new site in a day

How I rebuilt my personal website using Astro, Motion, some AI agents, and an embarrassingly small token bill.

#astro #ai #opencode #web-development #design

I had a website before this one. It was fine. A static HTML page with some text, a few links, the absolute minimum. It worked, but it didn’t feel like me. It was flat, lifeless, and honestly kind of boring. I wanted a place that actually represents what I do. A place to show my projects, the tools I use and love, and somewhere to write down my thoughts for whoever wants to read them.

So I built this.

Tech Stack

I’ve been a fan of Astro for static sites for a while now. I think it’s one of the cleverest SSG solutions out there. The islands architecture, the zero-JS-by-default approach, the way it lets you bring your own framework. It just clicks for me.

For this site, I paired it with Tailwind CSS using a custom theme built on content-semantic CSS variables, Motion (the new standalone library from the Framer Motion team) for animations, and MDX for content.

Content collections + Zod = ❤️

One thing I really enjoyed was Astro’s content collection system. Defining a schema with Zod gives you typed, validated content out of the box. No manual type assertions, no runtime surprises.

const blog = defineCollection({
  loader: glob({ pattern: '**/*.mdx', base: './src/content/blog' }),
  schema: z.object({
    title: z.string(),
    excerpt: z.string(),
    category: z.string(),
    tags: z.array(z.string()),
    published: z.string(),
    updated: z.string().optional(),
    cover: z.string().optional(),
  }),
});

Building a paginated blog page with category filters was almost trivial. Astro’s getStaticPaths makes pagination straightforward, and having typed collections means I get full autocompletion when accessing post data. The helper functions for sorting and formatting are tiny and composable:

export function sortPostsByDate(posts: BlogPost[]): BlogPost[] {
  return posts.sort(
    (a, b) => new Date(b.data.published).getTime() - new Date(a.data.published).getTime(),
  );
}

export function getCategories(posts: BlogPost[]): string[] {
  const categories = new Set(posts.map((post) => post.data.category));
  return Array.from(categories).sort();
}

Design

I wanted something minimalistic that still felt like it had personality. I’m a big fan of dark sites with good typography. I’m not a designer by trade, but I think I nailed the vibe I was going for.

I originally planned a purple/indigo accent palette, but the colors felt weak in practice. They didn’t pop enough against the dark background. So I switched to a gold/yellow accent, and that made all the difference. It gives the site warmth without being loud.

I’m using content-semantic CSS variables for the theme. Instead of naming colors --blue-500, everything is --accent, --muted-foreground, --surface. It makes theming consistent and swapping values painless:

--accent: oklch(0.80 0.18 90);
--accent-hover: oklch(0.70 0.20 90);
--accent-foreground: oklch(0.05 0.005 285.823);

A minimal use of gradients helps the site feel less flat, and the Motion library handles the subtle entrance animations. Nothing fancy, just enough to make it feel alive:

import { animate } from "motion";

animate(
  content,
  { opacity: [0, 1], y: [24, 0] },
  { duration: 0.7, ease: "easeOut" }
);

It’s not perfect. There’s plenty I’d still improve. If you have feedback, hit me up on Bluesky. I’m always open to hearing what works and what doesn’t.

AI: This is the fun part

This is where things get interesting.

I wanted to test out Opencode Go, the $10/month subscription (I got it for $5 for the first month). It gives you access to a bunch of pretty cheap models. I’ll write a more detailed post about Opencode later, but for this project, the setup was dead simple.

I started by defining the whole project in my AGENTS.md: the stack, the conventions, the goals. Then I used a custom agent I call “Peer Programming”, which I use for incremental, back-and-forth coding. I definitely prefer this over just vibecoding everything with no control. It let me steer the project at every step and review changes as they came in.

Every now and then I’d pull in a few subagents like a design reviewer, a code reviewer, and an SEO optimizer, and I gave them browser access via the Chrome MCP tools. Each of them would constantly check for inconsistencies, potential refactors, or SEO gaps. The results were honestly crazy good.

The actual coding was done using Kimi K2.6 for planning and DeepSeek V4 Flash on max settings to write the code. The whole project, from idea to where it is now, took about a day. And the craziest part? It cost me around $2–3 in tokens total. Seriously. A few bucks for an entire site.

Right now I’m hitting 100% on Lighthouse scores, both mobile and desktop.

This isn’t sponsored or anything. I’m just genuinely impressed with the workflow.

My AI Coding Stack

If you’re wondering what tools I actually used:

  • Opencode Go ($5/mo intro) — the orchestrator. This is where I define my custom agents and route tasks.
  • Kimi K2.6 — my go-to for upfront planning, architecture decisions, and content outlines. Cheap, fast, and surprisingly good at reasoning.
  • DeepSeek V4 Flash (max) — the workhorse. I used it for almost all code generation. At max settings it produces reliable, well-structured code.
  • Claude — occasional use for polishing copy and content. I prefer it for creative writing tasks.
  • GitHub Copilot — inline completions during manual edits. Not part of the agentic workflow but still in my daily setup.

Outro

This was a really fun project. The only caveat I’d add: I don’t use Opencode Go models on private or client projects because I’m not entirely sure how they handle code sharing with third-party providers. For personal stuff? Absolutely worth it.

If you’re curious about my exact Opencode setup, you can find my config and agents over on github.com/bdbch/ai-dotfiles.

I had fun building this, and I’m happy I finally have a website that actually looks and feels like me.