This site is handmade. Here’s how.

It’s also open source. Find it on GitHub or read on for details.

Carbon footprint

I regularly estimate my website’s carbon footprint using the Website Carbon Calculator. The most recent calculation put its footprint at 0.1 grams of CO2e.

My homepage footer shows an an equivalent amount of unique visits per phone charge. I use the EPA’s Greenhouse Gas Equivalencies Calculator to get that figure. They explain the smartphone emissions math here.

Here are some ways I’ve reduced the carbon footprint of my site:

See Sustainable Web Design for a full list of best practices which I am trying my best to stick to.


I write a lot of technical notes-to-self on this site. Some of them I revisit and improve over time. I also write the occasional musing or experiment.

Assigning a stage to each note frees me up to do both, all in the same space. It’s a solution I’ve copied from Maggie Appleton who calls them growth stages and uses them in a similar way.

Like Maggie, I use three stages:

The first version of a note, regardless of its length. They all start here.
A note that has had some level of refinement.
A note that has had several rounds of updates.

Assumed audience

Sometimes I will prepend a note with a short explanation of what type of person it is written for. Here’s one example. Here’s another.

I copied this idea from Robin Sloan who got it from Chris Krycho. Here’s Robin explaining why assumed audience sections are a good idea, much more eloquently than I could:

I appreciate the way they push back against the “context collapse” of the internet, in which every public post is, by default, addressed to everyone.

Writing an assumed audience selection frees me up to write unapologetically to a specific audience without preamble or watering the whole thing down.



This is still in flux. Here are the values I use in Utopia at the moment:

  • Min: 320px
  • Max: 1280px
  • Article Max: 720px


The entire site is set in ITC Franklin Gothic, licensed through Paratype. I use Utopia to calculate and handle a fluid type scale. It goes from Minor Third (1.2) to Perfect Fourth (1.333).

See the Fonts section for implementation details.


Read on if you’re a nerd. It’s really just a collection of notes-to-self on how I maintain the website. A style guide, if you will.


I use Eleventy to build and publish my site. I’m such a fan of Eleventy that I’ve written a bunch of notes about it.

Assume everything build-related in this colophon is heavily Eleventy-flavoured.



I exclude drafts by using the following in collection frontmatter:

eleventyExcludeFromCollections: true

Draft blog post contents here.

It’s a simple brute-force way to exclude the file anywhere it might be looped through, such as on the Notes page. This lets me continue working on a draft post whilst seeing it in the browser (at its URL) without needing to set if statements everywhere to exclude files with some bespoke frontmatter value.


The Trove is a pinboard-esque section on my homepage that shows off what I’ve ‘pinned’ lately from around the web. I use Raindrop to collect these ‘pins’ and their API to pull them into my site. I use the eleventy-fetch plugin to cache these pins.

In terms of keeping the site ‘fresh’ with Trove data: I no longer use Netlify’s Scheduled Functions and instead just use this tension as healthy pressure to regularly update the site (thus refreshing Trove data along the way)[2].

I made a macOS and iOS Shortcut for manual builds using Netlify’s Build Hooks for the scenario where I want to force a rebuild. That doesn’t visibly change the site if the data has already been refreshed in the last day, given that I have the netlify-plugin-cache package installed.

See the Images section of this page for how I process pinned imagery.


I use Markdown for anything longform on this site: pretty much just notes and this page here. I refer to this content type as article in code.


You can add blockquotes in Markdown like so:

> Classics cut to fit fifteen-minute radio shows, then cut again to fill a two-minute book column.

But things get messy if you want to add a cite tag. How is Markdown to know that both that and the quote above it should be treated as a single unit?

I use a raw HTML figure element to wrap both blockquotes and the occasional accompanying cite element for semantic reasons described in this CSS-Tricks article.

Here’s what that looks like in my Markdown files:

Regular Markdown content above.

<figure class="quote">
      Classics cut to fit fifteen-minute radio shows, then cut again to fill a
      two-minute book column...

Regular Markdown content below.

I could figure out how to have the HTML write itself from Markdown automatically, probably in the same way I sweep up and turn straight quotes into smart quotes. For another day.


I use Graham F. Scott’s Embed Everything plugins for any YouTube and Vimeo video embeds. Graham’s plugins detect media from plain text (such as in your Markdown files) and automatically creates embeds. They look like this:

Here's some Markdown content followed by a YouTube video:

That will be turned into a nicely formatted `<iframe>` during build.

I’ve turned on Paul Irish’s Lite YouTube Embed method for the YouTube embed.


Aside from minor images like favicons, I host most of them on Cloudinary. I use shortcodes.js to process responsive image sizes and prepend image URLs with my Cloudinary URL.

The Trove section of the homepage is an exception to my Cloudinary setup. Instead, I use the eleventy-img plugin to download, reformat, and resize Trove image via the troveImg shortcode. I like this method because eleventy-img will generate HTML according to what image sizes are available. For example: if the original image for a Trove item is lower resolution than one or more of my desired widths, it will only generate HTML for the ones it can supply.

I add loading="lazy" to all images except for those above-the-fold.


I use WOFF2 across the board. Fonts have been subset using pyftsubset. I can’t find a record of what I subset the fonts to exactly, but I’m pretty sure it was Latin characters and a subset of common Unicode ones.

I’ll update this section next time I run the subsetting, or when I make the subsetting happen automatically upon build based on what characters are actually used on the site.

See the Typography section for design details.


See feed.njk. It loops through my note collection and excludes drafts.


Once a member of the Sass cult, I now process all my CSS with LightningCSS. I do this via Stephanie Eckles’ LightningCSS Eleventy plugin for reasons she’s already explained on her blog. In summary:

  • Regular CSS now does a lot of the things we used to use Sass for (such as variables and nesting)
  • LightningCSS fills in the gaps more elegantly

One quirk on Eleventy is that it might process the CSS as a collection. That’s why I have a css.json file with the following:

"eleventyExcludeFromCollections": true;

  1. My site is hosted by Netlify, who use a global CDN. Most of their locations use clean energy (via AWS and Google Cloud) but whether or not the your visit to the site is running on clean energy depends on where you are visiting from and at what time. ↩︎

  2. There is a mystery bug where my site will auto-deploy every few weeks on its own. I’m guessing it’s got something to do with the netlify-plugin-cache package but that’s for another day. ↩︎