The Rustling of the Leaves

<p>day 2 - when good boys go bad</p>

day 2 - when good boys go bad

last week
(last updated: last week)
📷 Affinity
<p>it do be like that sometimes</p>

it do be like that sometimes

last week
(last updated: last week)
📷 Affinity

Sensational Typography Showcase Sites

There's an art to showing off a typeface. A proper font showcase page sells, inspires and delights the reader. Here are some best-in-class web pages that do just that. This is a living list, updated as I discover worthy new entries.

Fraunces by Undercase Type

The font I use as the header for this very site. Both stately and playful at the same time, it occupies a tidy mid-point between Cooper Black and Bookman Swash.

🔗 fraunces.undercase.xyz

A screenshot from the Fraunces showcase page

Playwrite by Type Together

An effort by Type Together to document and preserve traditional handwriting styles taught in primary schools around the world. The project has resulted in a truly magnificent collection of typefaces. Moreover, its website showcases the many documents used in the creation of the final artifacts. Curisve handwriting samples from Dutch textbooks

3 weeks ago
(last updated: 3 weeks ago)

Relative date filter in Eleventy

For this new blog, I wanted to display the time since the post was published, e.g. “two days ago”. My CMS appends a published and updated date to each post automatically, indicating when it was originally published and when it was last updated. These are formatted like this: 2026-02-18T21:58:50.158Z.

I wanted to use the templating language support built into Eleventy to turn a hard-to-read date string into a relative date, such as “5 days ago”. In Eleventy, you can set up custom filters that allow you to easily transform data, which is exactly what I'm trying to do here. For example, let's say you are editing a page with a title property of “About Me". If you wanted to render this in uppercase, you would write {{ title | upcase }} (in Liquid). Let's introduce a relative_date filter.

The first step is working out the logic that translates the time difference between Date.now() and the timestamp attached to each post. I went with the following implementation:

const relativeDate = (input) => {
  const diff = new Date(input) - Date.now();
  const rel = new Intl.RelativeTimeFormat("en", {
    style: "long",
    numeric: "auto", // Gives us "yesterday" instead of "1 day ago", which I prefer
  });

  const units = [
    { unit: "year", ms: 1000 * 60 * 60 * 24 * 365 },
    { unit: "month", ms: 1000 * 60 * 60 * 24 * 30 }, // Choosing to ignore that months have different durations in days
    { unit: "week", ms: 1000 * 60 * 60 * 24 * 7 },
    { unit: "day", ms: 1000 * 60 * 60 * 24 },
    { unit: "hour", ms: 1000 * 60 * 60 },
    { unit: "minute", ms: 1000 * 60 },
  ];

  // Find the largest time unit that exceeds the corresponding ms thresholds
  const { unit, ms } =
    units.find(({ ms }) => Math.abs(diff) >= ms) ?? units.at(-1);
  return rel.format(Math.round(diff / ms), unit);
};

This relies on the native JavaScript Intl.RelativeTimeFormat API, which provides all this logic out of the box. It's amazing to have such a powerful API natively supported in the language, rather than having to use a library. It's good separation of concerns to have this code as a module that we can invoke in a few places (as will become clear later).

Second, we want to ensure that this code is accessible within the templates of my blog.

import { relativeDate } from "./scripts/relativeDate.js";

eleventyConfig.addFilter("relative_date", (input) => {
    return `<span class="relative-date" data-date="${input}">${relativeDate(
      input,
    )}</span>`;
  });

This code creates a filter relative_date which wraps the input (the data) in a span element with a relevant class. I'm also baking the publication date into a data-* attribute, so it's easier to use in the next step.

Combining these two steps, we now have what we need. Let's say we have a Markdown page with the following frontmatter.

---
title: Relative date filter in Eleventy
tags:
  - eleventy
  - javascript
published: '2026-02-23T16:16:35.098Z'
updated: '2026-02-23T16:28:24.036Z'
---

We can now easily use our filter {{ published | relative_date }} to get:

Finally, Eleventy generates static sites and the above code is only generated at build time. If we built the site a week ago, then the relative time difference will be a week out of date! Therefore, we grab all elements with the class relative-date and update their contents at run time. Because we baked the published date in the span element itself, we can recalculate the relative time difference.

const updateRelativeDate = () => {
  const elements = document.querySelectorAll(".relative-date");
  elements.forEach((el) => {
    el.textContent = relativeDate(el.dataset.date);
  });
};

Finally, in our base template, we add the following code:

<script type="module">
  import { updateRelativeDate } from './scripts/relativeDate.js';

  updateRelativeDate();
  setInterval(updateRelativeDate, 60_000);
</script>

This ensures that our relative time difference is always accurate to within one minute!

4 weeks ago
(last updated: 4 weeks ago)
<p>Swiss Alps in the Summer of 2025.</p>

Swiss Alps in the Summer of 2025.

4 weeks ago
(last updated: 4 weeks ago)
📷 Fujifilm X100V
Poster for Star Trek: Strange New Worlds

Star Trek: Strange New Worlds

I'm not a massive Star Trek guy, so I wasn't prepared to like the odd mixture of bonkers sci-fi in Star Trek: Strange New Worlds quite so much. Something I always appreciate about Star Trek is its dedication to diversity and to the notion that conflict is resolved through curiosity and empathy. Battles are won by the side that best understands what drives their adversary, rather than by strength. Strange New Worlds uses this backdrop to put the crew through wacky space adventures (body swap! pirates! medieval fantasy!) with surprising emotional beats that hit hard (for me, at least). 🖖

posted 4 weeks ago
(last updated: 4 weeks ago)

Starting up this new site. First note!

last month
(last updated: 4 weeks ago)
new