This blog was running stock AstroPaper. If you’ve seen one AstroPaper blog, you’ve seen them all: same layout, same colors, same Fira Code headings. I wanted something that looked like it belonged to me.
I spent a Saturday morning looking at personal sites that had actual personality. gwern.net with its heavy typography and footnotes. chriscoyier.net with its playful layout shifts. cassidoo.co with that confident color use. The common thread was that none of them looked like a template.
I’m a data engineer. I live in terminals. I read logs for a living. So the concept I landed on was “Engineer’s Notebook / Terminal Blueprint.” The terminal and data pipeline metaphor felt honest to what I actually do every day.
Phase 1: Typography and color
The fastest way to break the stock look is fonts.
I paired JetBrains Mono for headings with Atkinson Hyperlegible for body text. Mono headings against a proportional body font immediately signals “this is a developer’s blog” without screaming about it. The contrast between the two faces does most of the heavy lifting.
Color was next. The default AstroPaper palette is fine, but it’s generic. I added semantic color tokens to the CSS:
:root { --surface: #f4f4f5; --accent-2: #0d9488;}
[data-theme="dark"] { --surface: #111827; --accent-2: #2dd4bf; --background: #0b1020;}--surface is for card backgrounds, --accent-2 is a teal secondary color. That dark mode background at #0b1020 is way deeper than the default. It actually looks like a terminal now.
Last thing: I set the prose measure to max-width: 72ch. Long lines are hard to read and 72 characters is the sweet spot I kept coming back to.
Phase 1: Terminal hero
The stock AstroPaper hero is just a heading and a tagline. I replaced it with a terminal prompt:
rezha@jakarta:~$ abusing computers for fun & profit▍The blinking cursor is pure CSS:
.cursor { display: inline-block; width: 0.5em; height: 1.1em; background: var(--accent); vertical-align: bottom; animation: blink 1s step-end infinite;}
@keyframes blink { 0%, 100% { opacity: 1; } 50% { opacity: 0; }}Later I added a typewriter effect that loops the tagline. The animation types out the text character by character, pauses, deletes, and starts over. Getting the cursor to sit on the same baseline as the prompt text was annoying. The fix turned out to be vertical-align: bottom on the cursor element. Without it, the cursor would jump a pixel or two above the text depending on the font metrics.
.typewriter { display: inline-block; overflow: hidden; white-space: nowrap; border-right: none; animation: typing 3s steps(40) 1s forwards, pause 8s step-end infinite; max-width: 0;}
.typewriter.active { max-width: 100%;}Phase 1: Data pipeline cards
I wanted the post list to look less like a list and more like a data pipeline. In Card.astro, I added a vertical line running down the left side with a colored dot at each post. Think of it like a pipeline spine.
Featured posts get an orange dot using var(--accent). Recent posts get teal with var(--accent-2). Everything else gets the muted default.
I also reformatted the metadata line. Instead of the usual “January 15, 2026” format, it looks more like a system log: mono font, tiny text, and it includes the primary tag.
<div class="pipeline-node"> <span class="dot" style={`background: ${featured ? 'var(--accent)' : 'var(--accent-2)'}`} /> <span class="meta font-mono text-xs opacity-70"> {formattedDate} · {primaryTag} </span></div>The vertical line connecting the dots is just a border-left: 2px solid var(--accent-2) on the container with some padding. Simple, but it completely changes how the page reads.
Phase 2: Two-rail homepage
The single-column layout felt wasteful on wide screens. I split the homepage into a two-rail grid:
.home-grid { @apply lg:grid lg:grid-cols-[1fr_280px] lg:gap-10;}The main column has posts. The right sidebar (desktop only, collapses on mobile) has a few widgets: a “Now” status blurb, tech stack badges, and an RSS subscribe link.
One layout detail that took way too long: the sidebar needed pt-8 to align visually with the hero section. I tried self-start first because that seemed correct, but it broke sticky scrolling. The padding hack isn’t pretty, but it works.
Phase 2: Animated underlines
Default browser underlines are ugly and they cut through descenders. I replaced them with a background-size animation:
a { text-decoration: none; background-image: linear-gradient(currentColor, currentColor); background-position: 0% 100%; background-repeat: no-repeat; background-size: 100% 1px; transition: background-size 0.2s ease;}
a:hover { background-size: 100% 2px;}Links start with a 1px underline and thicken to 2px on hover. It’s a small detail but it makes the whole site feel more considered.
Phase 2: ETL pipeline SVG
I drew a small inline SVG diagram on the homepage showing an extract, transform, load flow. It was a nod to the data engineering theme. I actually removed it at one point because it felt like too much, then I got asked to bring it back. It sits centered with mx-auto and ties the whole terminal/pipeline concept together.
Post-redesign bugs
Redesigns break things. Here’s what I found.
Search broke entirely. Pagefind generates its index from the dist folder at build time. The dev server doesn’t have a dist folder. I kept refreshing the search page wondering why it was empty. Fix: run bun run build first, then use the dev server. Obvious in hindsight.
Theme detection was wrong. I had code checking classList.contains("dark") but this site uses the data-theme="dark" attribute on the HTML element, not a class. The ARIA labels on the theme toggle were also backwards because of this.
Invalid scroll behavior. I had window.scrollTo({ behavior: "instant" }) in a few places. "instant" is not a valid value. The spec says "auto" or "smooth". Changed them all to "auto".
Scroll-smooth without motion preference. I applied scroll-smooth globally on the html element. That’s bad for people who get motion sick. Moved it inside a media query:
@media (prefers-reduced-motion: no-preference) { html { scroll-behavior: smooth; }}Heading anchors lacked accessible names. The auto-generated heading anchor links had no aria-label or visible text for screen readers. Added aria-label="Link to this section" on each one.
What’s next
This was the biggest chunk of work in the whole redesign. The blog finally looks like my blog and not a demo site. There are still rough edges, but the identity is there.
This is part 3 of a series about redesigning this blog.