MoreRSS

site iconAlex WlchanModify

I‘m a software developer, writer, and hand crafter from the UK. I’m queer and trans.
Please copy the RSS to your reader, or quickly subscribe to:

Inoreader Feedly Follow Feedbin Local Reader

Rss preview of Blog of Alex Wlchan

Parody posters for made-up movies

2026-01-16 16:29:53

In my previous post, I needed a collection of movies to show off my CSS grid layout. The easy thing to do would be to use real movie posters, but I decided to have some fun and get a custom collection. I went to Blockbuster, HBO Max-Width, and Netflex, and this is what I got:

A grid of portrait-sized posters for made-up movies.

In this post, I’ll explain how I created this collection, and why I spent so much time on it.

Glossary of the Galaxy: what do the titles mean?

Each title is a reference to a concept in CSS or web development:

Apollo 13px
Pixels (px) are a unit of length in CSS. They’re a common way to define fixed sizes for text, borders, and spacing.
Breakpoint at Tiffany’s
A breakpoint is the screen width at which a website’s layout changes – for example, when it switches from a single column on a phone to a multi-column grid on a desktop.
The Color #9D00FF
This is a hexadecimal colour code for a shade of purple. Hex codes are a common way to define colours in CSS.
Chungking Flexpress
Flexbox is a layout model that allows elements to “flex” – growing to fill extra space, or shrinking to fit into small spaces.
The Devil Wears Padding
The padding is the space inside an element, between its content and its border. In this list, the padding is the gap between the grey border and the text.
The Empire Strikes Block
A block-level element is one that starts on a new line and reserves the full width available, like a heading or a paragraph.
Git Out
Git is a version control tool used to track changes in source code. It’s the industry standard for managing web development projects.
Gridiator
CSS Grid is a layout system for arranging elements in rows and columns. Unlike Flexbox, which is one-dimensional, Grid is designed for two-dimensional layouts.
Hidden <Figure>
The <figure> element is used to show an image with a caption, while the hidden attribute tells browsers not to render a specific element on a page.
Interstyler
The <style> element is used to embed CSS rules directly in an HTML page. These rules are colloquially referred to as “styles”.
The Margin
The margin is the space outside an element, the gap between it and its neighbours. In this list, the margin is the gap between the grey border and the text above it.
vh for Vendetta
The viewport is the visible area of a web page in your browser. The vh unit stands for viewport height, where 1vh is equal to 1% of the screen's height.

I’m pretty happy with this list, and with the amount of variety and wordplay I managed to fit into a dozen titles.

Top Pun: choosing the movie titles

The trick to writing good puns is to write lots of puns, then throw away the bad ones. I only needed a dozen movies, but I had over thirty other titles that I didn’t use.

If the puns aren’t coming immediately, I write two lists: the phrases or words I want to parody, and the words I’m trying to shoehorn in. In this case, the first list had phrases like X-Men or Mission Impossible, and the second had words like pixel, margin, and flex.

This is where I reach for search engines – I won’t find anybody else making the exact puns I want, but I can find pre-existing lists of these building blocks. In this case, I looked at lists of famous and iconic films, and I read web development tutorials and glossaries. I leant toward popular films so more people would get the reference; a pun on an obscure film would likely be missed.

As I build the two lists, I start to spot connections, like the fact that X-Men could become Flex-Men. I write down all my ideas, even the bad ones – often a bad idea is the jumping off point for a good one. For example, an early idea was Block to the Future, which isn’t very good, but later I realised I could use Back/Block for The Empire Strikes Block instead, which is much better.

If this was a purely text-based exercise, the titles would be enough – but I also needed posters.

Blurhemian Rhapsody: making the posters with Primitive

I needed some posters to go with the titles, but what to use?

I wanted to use the movie posters because many films have iconic posters, and that would help people recognise the pun – but I didn’t want to use the real movie posters, because they often show the title. That would contradict my text, not help it.

But I do have an image editor, and while I lack the Photoshop skills to replace the title in a convincing way, I can make text that looks okay if you squint – and that gave me an idea.

Several years ago, I used Michael Fogleman’s Primitive tool to create some wallpapers. Primitive redraws images with a simple geometric shapes, adding one shape at a time, trying to get closer and closer to the original image.

Here’s an example, in which my face has been redrawn as several hundred triangles:

A selfie. I'm wearing glasses, have dark brown hair falling down one side of my face, I'm smiling at the camera, I'm wearing a green dress, and sitting in front of some plants and greenery.The same picture, but now redrawn in coloured triangles. There's a resemblance to the original image, but the detailed elements like the plant leaves or strands of hair have been blurred out.

This gives a recognisable version of the image, but it’s a distinct style and you won’t mistake it for the real thing.

For each movie I was considering, I downloaded a poster from The Movie Databaase, and I used Primitive to blur it. Sometimes the original title would appear through the blur, in which case I used an image editor to replace the title and re-blurred it. The blurring meant I could get away with a rough edit – for example, I didn’t need the exact font – because any imperfections would be blurred away by Primitive.

This added a new dimension to my search for puns – I wanted movie posters that would still be recognisable after this blurring. This ruled out posters that are very busy, because it’s difficult to distinguish individual elements after the blurring. I looked at lists of iconic movie posters, which often have clear, distinct shapes that hold up well when converted into triangles.

One of the best examples of an iconic poster is The Devil Wears Prada. I know nothing about the film, but I remember the poster with the big red heel. When you blur the poster with Primitive, it becomes recognisable almost immediately. This is what it looks like with 5, 25, and 50 triangles:

The original poster, which shows a glossy red high-heeled shoe. The point of the heel has been replaced with a trident that looks like a three-pronged devil's tail.The blurred poster with 5 triangles. A maroon red triangular shape is discernible on the left-hand side, which is recognisable if you compare it to the original, but doesn't stand out on its own.The blurred poster with 10 triangles. The shape of the shoe is now clearly visible, and the heel is starting to resolve.The blurred poster with 10 triangles. The shape of the shoe is now clear, the shades match the gloss colouring, and the three points of the trident are also appearing.

I had a year where all my desktop wallpapers were photos that I’d blurred using Primitive, and I’ve been waiting for a chance to use it in a bigger project. I’m really pleased with the result – it lets me lean into the titles I’ve created, and it gives the whole collection a coherent appearance.

Widening the Lens: choosing a more diverse selection

I picked a dozen movies and started writing the article. But as I was taking screenshots of the movie grid, I noticed that my initial selection wasn’t very representative. Ten of the twelve films had all-or-mostly men in the main roles, and all of the lead characters were white.

I was tempted to ignore this problem, because this is just a fake collection for a blog post and does it really matter? But that was disingenuous – I cared enough to put in all this effort, so it must be a meaningful selection to me. I wanted a more diverse and interesting selection.

I looked for lists of famous movies which centre women and non-white characters, and added several to my made-up collection. Ideally I’d also have some movies that centre queer or disabled characters, but I couldn’t find any with an iconic poster or a pun-worthy title.

Blooper Reel: the movies I didn’t use

I made a lot of puns and posters, including a couple of personal favourites that I cut from the post:

A blurred poster for ‘Fifty Shades of #999’. It’s a mostly-grey image with a man pushing a woman against a wall, and the title of the film. The letters ‘#999’ are especially prominent.A blurred poster for ‘X-Men’. It’s a bright and colourful image with lots of superheroes, not enough detail to discern any of them clearly. The title ‘X-Men’ is clearly visible in bold colours at the top of the poster.A blurred poster for ‘Home Align’. A small child holds their hands up to their face, while two criminals look through a window behind them. The title of the movie is just about readable at the top of the poster.

Fifty Shades of Grey became Fifty Shades of #999 and was the first movie where I considered replacing a colour with a hex code. I swapped this out for The Colour Purple when I was trying to create a more diverse list, and replacing a mostly-grey poster with a pop of colour helped too.

X-Men became Flex-men, and I’m really sad I couldn’t use that pun. This was let down by the poster – the original X-Men branding is very prominent and would be hard to change, and all of the colourful X-Men posters are very busy with lots of characters.

Home Alone became Home Align, which is a weaker pun but another easily-recognisable poster.

I had good reasons to cut all of them, and the selection is better off without them – but maybe they’ll reappear in a future post.

Why would you do this?

This is a lot of effort for placeholder data in a single blog post. I did it because it was fun, and it helped me enjoy writing the rest of the post. Every time I thought of another title or saw a poster in a screenshot, it made me smile. That’s enough of a reason.

This sort of fun detail is why I like having a personal blog which isn’t a business or an income stream. I write because I enjoy it, and I can make decisions that don’t make commercial sense because it’s not a commercial website. This side quest had terrible return on investment if you only care about time and money – but it was fantastic for joy.

[If the formatting of this post looks odd in your feed reader, visit the original article]

The Good, the Bad, and the Gutters

2026-01-14 16:38:52

I’ve been organising my local movie collection recently, and converting it into a static site. I want the homepage to be a scrolling grid of movie posters, where I can click on any poster and start watching the movie. Here’s a screenshot of the design:

A grid of portrait-sized posters for made-up movies. There are two rows of six posters, and each poster is the same height. The posters line up horiozntally, and below each poster is the title of the movie.

This scrolling grid of posters is something I’d like to reuse for other media collections – books, comics, and TV shows.

I wrote an initial implementation with CSS grid layout, but over time I found rough edges and bugs. I kept adding rules and properties to “fix” the layout, but these piecemeal changes introduced new bugs and conflicts, and eventually I no longer understood the page as a whole. This gradual degradation often happens when I write CSS, and when I no longer understand how the page works, it’s time to reset and start again.

To help me understand how this layout works, I’m going to step through it and explain how I built the new version of the page.

Table of contents

Step 1: Write the unstyled HTML

This is a list of movies, so I use an unordered list <ul>. Each list item is pretty basic, with just an image and a title. I wrap them both in a <figure> element – I don’t think that’s strictly necessary, but it feels semantically correct to group the image and title together.

<ul id="movies">
  <li>
    <a href="#">
      <figure>
        <img src="apollo-13px.png">
        <figcaption>Apollo 13px</figcaption>
      </figure>
    </a>
  </li>
  <li>
    <a href="#">
      <figure>
        <img src="breakpoint-at-tiffanys.png">
        <figcaption>Breakpoint at Tiffany’s</figcaption>
      </figure>
    </a>
  </li>
  ...
</ul>

I did wonder if this should be an ordered list, because the list is ordered alphabetically, but I decided against it because the numbering isn’t important.

Having a particular item be #1 is meaningful in a ranked list (the 100 best movies) or a sequence of steps (a cooking recipe), but there’s less significance to #1 in an alphabetical list. If I get a new movie that goes at the top of the list, it doesn’t matter that the previous #1 has moved to #2.

This is an unstyled HTML page, so it looks pretty rough:

A web page which is mostly dominated by a poster for ‘Apollo 13px’, with a bullet point vaguely visible on the left. The title of the movie is visible in small blue, underlined text below the image. The spacing looks weird.

Step 2: Add a CSS grid layout

Next, let’s get the items arranged in a grid. This is a textbook use case for CSS grid layout.

I start by resetting some default styles: removing the bullet point and whitespace from the list, and the whitespace around the figure.

#movies {
  list-style-type: none;
  padding: 0;
  margin:  0;
  
  figure {
    margin: 0;
  }
}

Then I create a grid that creates columns which are 200px wide, as many columns as will fit on the screen. The column width was an arbitrary choice and caused some layout issues – I’ll explain how to choose this properly in the next step.

#movies {
  display: grid;
  grid-template-columns: repeat(auto-fill, 200px);
  column-gap: 1em;
  row-gap:    2em;
}

By default, browsers show images at their original size, which means they overlap each other. For now, clamp the width of the images to the columns, so they don’t overlap:

#movies {
  img {
    width: 100%;
  }
}

With these styles, the grid fills up from the left and stops as soon as it runs out of room for a full 200px column. It looks a bit like an unfinished game of Tetris – there’s an awkward gap on the right-hand side of the window that makes the page feel off-balance.

A grid of movie posters on a white background, two rows of six posters. All the posters are pushed to the left of the screen, with a big white gap on the right-hand side.

We can space the columns more evenly by adding a justify-content property which tells the browser to create equal spacing between each of them, including on the left and right-hand side:

#movies {
  justify-content: space-evenly;
}

With just ten CSS properties, the page looks a lot closer to the desired result:

A grid of movie posters on a white background, two rows of six posters. Below each poster is a blue link with the title of the movie. Every poster is the same width, but some are different heights.

After this step, what stands out here is the inconsistent heights, especially the text beneath the posters. The mismatched height of The Empire Strikes Block is obvious, but the posters for The Devil Wears Padding and vh for Vendetta are also slightly shorter than their neighbours. Let’s fix that next.

Step 3: Choosing the correct column size

Although movie posters are always portrait orientation, the aspect ratio can vary. Because my first grid fixes the width, some posters will be a different height to others.

I prefer to have the posters be fixed height and allow varied widths, so all the text is on the same level. Let’s replace the width rule on images:

#movies {
  img {
    height: 300px;
  }
}

This causes an issue with my columns, because now some of the posters are wider than 200px, and overflow into their neighbour. I need to pick a column size which is wide enough to allow all of my posters at this fixed height. I can calculate the displayed width of a single poster:

display width=300px×poster widthposter height

Then I pick the largest display width in my collection, so even the widest poster has enough room to breathe without overlapping its neighbour.

In my case, the largest poster is 225px wide when it’s shown at 300px tall, so I change my column rule to match:

#movies {
  grid-template-columns: repeat(auto-fill, 225px);
}

If I ever change the height of the posters or get a wider poster, I’ll need to adjust this widths. If I was adding movies too fast for that to be sustainable, I’d look at using something like object-fit: cover to clip anything that was extra wide. I’ve skipped that here because I don’t need it, and I like seeing the whole poster.

If you have big columns or small devices, you need some extra CSS to make columns and images shrink when they’re wider than the device, but I can ignore that here. A 225px column is narrower than my iPhone, which is the smallest device I’ll use this for. (I did try writing that CSS, and I quickly got stuck. I’ll come back to it if it’s ever an issue, but I don’t need it today.)

Now the posters which are narrower than the column are flush left with the edge of the column, whereas I’d really like them to be centred inside the column. I cam fix this with one more rule:

#movies {
  li {
    text-align: center;
  }
}

This is a more subtle transformation from the previous step – nothing’s radically different, but all the posters line up neatly in a way they didn’t before.

A grid of movie posters on a white background, but now each poster is the same height and the text under each poster is centre-aligned.

Swapping fixed width for fixed height means there’s now an inconsistent amount of horizontal space between posters – but I find that less noticeable. You can’t get a fixed space in both directions unless all your posters have the same aspect ratio, which would mean clipping or stretching. I’d rather have the slightly inconsistent gaps.

The white background and blue underlined text are still giving “unstyled HTML page” vibes, so let’s tidy up the colours.

Step 4: Invert the colours with a dark background

The next set of rules change the page to white text on a dark background. I use a dark grey, so I can distinguish the posters which often use black:

body {
  background: #222;
  font-family: -apple-system, sans-serif;
}

#movies {
  a {
    color: white;
    text-decoration: none;
  }
}

Let’s also make the text bigger, and add a bit of spacing between it and the image. And when the title and image are more spaced apart, let’s increase the row spacing even more, so it’s always clear which title goes with which poster:

#movies {
  grid-row-gap: 3em;
  
  figcaption {
    font-size:  1.5em;
    margin-top: 0.4em;
  }
}

The movie title is a good opportunity to use text-wrap: balance. This tells the browser to balance the length of each line, which can make the text look a bit nicer. You’ll get several lines of roughly the same length, rather than one or more long lines and a short line. For example, it changes “The Empire Strikes // Block” to the more balanced “The Empire // Strikes Block”.

#movies {  
  figcaption {
    text-wrap: balance;
  }
}

Here’s what the page looks like now, which is pretty close to the final result:

A grid of movie posters on a dark grey background, and now the text under each poster is larger and white.

What’s left is a couple of dynamic elements – hover states for individual posters, and placeholders while images are loading.

Step 5: Add a border/underline on hover

As I’m mousing around the grid, I like to add a hover style that shows me which movie is currently selected – a coloured border around the poster, and a text underline on the title.

First, I use my dominant_colours tool to get a suitable tint colour for use with this background:

$ dominant_colours gridiator.png --best-against-bg '#222'
▇ #ecd3ab

Then I add this to my markup as a CSS variable:

<ul id="movies">
  ...
  <li style="--tint-colour: #ecd3ab">
    <a href="#">
      <figure>
        <img src="gridiator.png">
        <figcaption>Gridiator</figcaption>
      </figure>
    </a>
  </li>
  ...
</ul>

Finally, I can add some hover styles that use this new variable:

#movies {
  a:hover {
    figcaption {
      text-decoration-line: underline;
      text-decoration-thickness: 3px;
    }
  
    img {
      outline: 3px solid var(--tint-colour);
    }
  }
}

I’ve added the text-decoration styles directly on the figcaption rather than the a, because browsers are inconsistent about whether those properties are inherited from parent elements.

I used outline instead of border so the 3px width doesn’t move the image when the style is applied.

Here’s what the page looks like when I hover over Breakpoint at Tiffany’s:

A grid of movie posters on a dark grey background, and one of the posters has a pink outline and the title is underlined.

We’re almost there!

Step 6: Add placeholder colours

As my movie collection grows, I want to lazy load my images so I don’t try to load them all immediately, especially posters that aren’t scrolled into view. But then if I scroll and I’m on a slow connection, it can take a few seconds for the image to load, and until then the page has a hole. I like having solid colour placeholders which get replaced by the image when it loads.

First I have to insert a wrapper <div> which I’m going to colour, and a CSS variable with the aspect ratio of the poster so I can size it correctly:

<ul id="movies">
  ...
  <li style="--tint-colour: #ecd3ab; --aspect-ratio: 510 / 768">
    <a href="#">
      <figure>
        <div class="wrapper">
          <img src="gridiator.png" loading="lazy">
        </div>
        <figcaption>Gridiator</figcaption>
      </figure>
    </a>
  </li>
  ...
</ul>

We can add a coloured background to this wrapper and make it the right size:

#movies {
  img, .wrapper {
    height: 300px;
    aspect-ratio: var(--aspect-ratio);
  }
  
  .wrapper {
    background: var(--tint-colour);
  }
}

But a <div> is a block element by default, so it isn’t centred properly – it sticks to the left-hand side of the column, and doesn’t line up with the text. We could add margin: 0 auto; to move it to the middle, but that duplicates the text-align: center; property we wrote earlier. Instead, I prefer to make the wrapper an inline-block, so it follows the existing text alignment rule:

#movies {
  .wrapper {
    display: inline-block;
  }
}

Here’s what the page looks like when some of the images have yet to load:

A grid of movie posters on a dark grey background, where three of the posters are solid colour rectangles where the images haven’t yet loaded.

And we’re done!

The final page

There’s a demo page where you can try this design and see how it works in practice.

Here’s what the HTML markup looks like:

<ul id="movies">
  <li style="--tint-colour: #dbdfde; --aspect-ratio: 510 / 768">
    <a href="#">
      <figure>
        <div class="wrapper">
          <img src="apollo-13px.png" loading="lazy">
        </div>
        <figcaption>Apollo 13px</figcaption>
      </figure>
    </a>
  </li>
  ...
</ul>

and here’s the complete CSS:

body {
  background: #222;
  font-family: -apple-system, sans-serif;
}

#movies {
  list-style-type: none;
  padding: 0;
  margin:  0;
  
  display: grid;
  grid-template-columns: repeat(auto-fill, 225px);
  column-gap: 1em;
  row-gap:    3em;

  justify-content: space-evenly;

  figure {
    margin: 0;
  }
  
  li {
    text-align: center;
  }
  
  a {
    color: white;
    text-decoration: none;
  }
  
  figcaption {
    font-size:  1.5em;
    margin-top: 0.4em;
    text-wrap: balance;
  }
  
  a:hover, a#tiffanys {
    figcaption {
      text-decoration-line: underline;
      text-decoration-thickness: 3px;
    }
    
    img {
      outline: 3px solid var(--tint-colour);
    }
  }
  
  img, .wrapper {
    height: 300px;
    aspect-ratio: var(--aspect-ratio);
  }

  .wrapper {
    background: var(--tint-colour);
    display: inline-block;
  }
}

I’m really happy with the result – not just the final page, but how well I understand it. CSS can be tricky to reason about, and writing this step-by-step guide has solidified my mental model.

I learnt a few new details while checking references, like the outline property for hover states, the way text-decoration isn’t meant to inherit, and the fact that column-gap and row-gap have replaced the older grid- prefixed versions.

This layout is working well enough for now, but more importantly, I’m confident I could tweak it if I want to make changes later.

[If the formatting of this post looks odd in your feed reader, visit the original article]

Using perceptual distance to create better headers

2026-01-13 02:01:38

For nearly a decade, the header of this website has been decorated with a mosaic-like pattern of coloured squares. I can choose a colour for individual posts or pages, and that tints the title, the links, and the header. It adds some texture and visual interest, without being too distracting.

The implementation is pretty straightforward: I have one function that generates the coordinates of each square, and another that generates varying shades of the tint colour. Put those together, and it draws the header image.

I recently improved the way I choose the shades of the tint colour, which makes the headers look more coherent, especially in dark mode. The change is subtle, but a definite improvement.

The old approach: varying the HSL lightness

Before, this is how I generated the shades:

  1. Map to HSL. Convert the tint colour to the hue-saturation-lightness (HSL) colour space.
  2. Define the bounds. I chose 7/8 and 8/7 of the original lightness, because it looked good in the first few colours I tried.
  3. Jitter lightness. Pick a random lightness value in this range.
  4. Recombine and convert. Pair this new lightness with the original hue and saturation, and convert back to sRGB.

I was trying to create colours which looked similar and varied only in lightness, so you’d see lighter or darker shades of the tint colour. My headers are PNG images, which are usually saved as sRGB, which is I why I convert back in the final step.

Here’s what the old code looked like:

require 'color'

# Given a hex colour as a string (e.g. '#123456') generate
# an infinite sequence of colours which vary only in brightness.
def get_colours_like(hex)
  seeded_random = Random.new(hex[1..].to_i(16))

  hsl = Color::RGB.by_hex(hex).to_hsl
  
  min_luminosity = hsl.luminosity * 7 / 8
  max_luminosity = hsl.luminosity * 8 / 7
  luminosity_diff = max_luminosity - min_luminosity
  
  Enumerator.new do |enum|
    loop do
      new_hsl = Color::HSL.from_values(
        hsl.hue,
        hsl.saturation,
        min_luminosity + (seeded_random.rand * luminosity_diff)
      )
      enum.yield new_hsl.to_rgb
    end
  end
end

I seeded the random generator so it always returned the same colours – this meant my local dev environment and web server would always generate identical header images. Note that it’s seeded based on the colour, so different tint colours will have light/dark squares in different places.

All the colour calculations are done by Austin Ziegler’s excellent color gem, which saved me from implementing colour conversions myself.

This approach is simple, but it has problems. Varying the lightness by proportion means the range varied from colour to colour – headers for dark colours didn’t have enough contrast, while light colours had too much contrast.

Here are three examples – notice how the dark header is almost solid colour, while the light header has enough contrast to become distracting:

Dark red coloured squares, which all blend into a dark red mush
#470906
Brighter red coloured squares, with some visible variation but not much
#d01c11
Very bright red coloured squares, some of which are almost white or light pink
#f69b96

This heuristic worked for the first colour I tried (#d01c11, the site’s original tint colour) but it breaks down as I’ve added more colours, especially in dark mode.

I could replace the percentages with fixed offsets – for example, plus or minus 25% lightness – but this wouldn’t fix the problem. Humans aren’t machines; we don’t perceive colours as linear numerical values. The human eye is more sensitive to some colours than others, so the same numerical jump in HSL doesn’t feel like the same visual difference.

Let’s look at another example, where I’ll fix the hue and saturation, and step the lightness by 25%. These differences don’t feel the same:

A deep blue square which is highly saturated
hsl(240, 100%, 50%)
A lavender-coloured square
hsl(240, 100%, 75%)
A square of pure white
hsl(240, 100%, 100%)

There are alternative colour spaces like OKLCH and CIELAB which try to capture the nuances of human biology and how we interpret colours, and that’s where I looked at for a replacement.

The CIELAB colour space

The CIELAB colour space is based on opponent process theory, which suggests that we perceive colour as a battle of three opposing pairs: black vs. white, red vs. green, and blue vs. yellow. Think about how you never see a reddish-green or a blueish-yellow – these colours are opposites.

These three pairs give us the three coordinates in CIELAB space:

  • L* is the perceptual lightness (black vs. white)
  • a* is the red-green axis
  • b* is the blue-yellow axis

(The other three letters stand for Commission internationale de l’éclairage, the standards body who developed CIELAB in 1976.)

Within this colour space, we can calculate the perceptual difference between two colours. Ideally, that numerical distance should match our human perception of the change. The goal is perceptually uniformity: if you move a fixed numerical distance anywhere in the space, the “amount” of change should feel the same to a human observer.

That’s much easier said than done: the measurement formulas (like Delta E) have been refined over decades, and deficiences have been found in CIELAB, especially for shades of blue. Newer spaces like OKLAB try to capture the nuances of human biology even more accurately. But for the purpose of my header images, CIELAB is good enough, and a big improvement over HSL.

One place I already use CIELAB is in my tool for extracting dominant colours. I’m using k‑means clustering to group colours that are “close” together, and it makes sense to measure closeness using perceptual distance.

The Ruby gem I’m using supports CIELAB but not OKLAB, which also informed my decision. Colour maths is complicated, and I’d rather use an existing implementation than write it all myself.

My new approach: varying the CIELAB perceptual lightness

Here’s my new heuristic:

  1. Map to CIELAB. Convert the tint colour to CIELAB space.
  2. Define the bounds. Choose a fixed distance, and find how much you need to increase/decrease the perceptual brightness L* to reach that distance.
  3. Jitter lightness. Pick a random L* value in this range.
  4. Recombine and convert. Pair this new lightness with the original a* and b* components, and convert back to sRGB.

To find the bounds, I do a binary search on the possible lightness values to find the perceptual lightness which gets me closest to the target distance. If I’m looking for the lighter shade, I search the range (</mo>L*,100)</ml>. If I’m looking for the darker shade, I search the range (0,L*)</ml>.

Here’s the code:

require 'color'

# Find the perceptual lightness of a CIELAB colour that's a specific
# perceptual difference (target_distance) from the original colour, while
# maintaining the original hue and colourfulness.
def lightness_at_distance(original_lab, direction, target_distance)
  # 1. Define the search range for L*
  if direction == 'lighter'
    low_l = original_lab.l
    high_l = 100
  else
    low_l = 0
    high_l = original_lab.l
  end

  # 2. Run a binary search on L*
  best_lab = original_lab
  best_delta = 0

  15.times do
    mid_l = (low_l + high_l) / 2.0

    candidate_lab = Color::CIELAB.from_values(mid_l, original_lab.a, original_lab.b)
    candidate_delta = original_lab.delta_e2000(candidate_lab)

    # Are we closer than the current best colour? If so, replace it.
    if (candidate_delta - target_distance).abs < (best_delta - target_distance).abs
      best_lab = candidate_lab
      best_delta = candidate_delta
    end

    if candidate_delta < target_distance
      # We need more distance, move away from the original L*
      direction == 'lighter' ? (low_l = mid_l) : (high_l = mid_l)
    else
      # We've gone too far, move back toward the original L*
      direction == 'lighter' ? (high_l = mid_l) : (low_l = mid_l)
    end
  end

  best_lab.l
end

Then I can write a very similar function to what I wrote for HSL:

# Given a hex colour as a string (e.g. '#123456') generate
# an infinite sequence of colours which vary only in lightness.
def get_colours_like(hex)
  seeded_random = Random.new(hex[1..].to_i(16))
  
  lab = Color::RGB.by_hex(hex).to_lab

  min_lightness = lightness_at_distance(lab, 'darker',  6)
  max_lightness = lightness_at_distance(lab, 'lighter', 6)
  lightness_diff = max_lightness - min_lightness

  Enumerator.new do |enum|
    loop do
      new_lab = Color::CIELAB.from_values(
        min_lightness + (seeded_random.rand * lightness_diff),
        lab.a,
        lab.b
      )
      
      # Discard colours which don't map cleanly from CIELAB to sRGB
      if new_lab.delta_e2000(new_lab.to_rgb.to_lab) > 1
        next
      end
      
      enum.yield new_lab.to_rgb
    end
  end
end

One gotcha is that CIELAB is a wider range than sRGB, so CIELAB colours don’t always map cleanly into sRGB. For example, certain bright colours like neon green may lose their vibrancy when converted from CIELAB to sRGB.

When it does the conversion, the color gem automatically clamps colours to fit into the sRGB space, but this creates some unusually dark or bright squares. I check if this clipping has occurred by converting back to CIELAB and looking at the distance – if there’s too much drift, I discard the colour and pick another. This is another subtle difference, but I think it improves the overall vibe.

Let’s look at the results, which compare the HSL heuristic (top), the original tint colour (middle), and the CIELAB heuristic (bottom):

Dark red coloured squares with a horizontal dark red stripe. The squares on the bottom have slightly more variety than the top.
#470906
Brighter red coloured squares, with the top and bottom looking about the same
#d01c11
Very bright red coloured squares on the top, more muted squares which match the salmon pink tint colour
#f69b96

The dark squares have a bit more variety, while the light squares have much less and avoid the bright and noticeable shades. It’s a particular improvement in dark mode, where I always use light tint colours. There’s almost no difference for the middle colour, which makes sense because it was how I designed the original heuristic. It already looked pretty good.

The new colours are closer to what I want: a bit of subtle texture, not loud enough to draw attention. I switched to them a fortnight ago, and nobody noticed. It’s small refinement, not a radical change.

[If the formatting of this post looks odd in your feed reader, visit the original article]

The passwords I actually memorise

2026-01-11 04:31:10

The promise of a password manager is that it remembers and autofills all of your passwords, so you only have to remember one – the password that unlocks your password manager.

In practice, I have a handful passwords that I think worth memorising. It’s still a short list, but I’m not convinced that a single password is either sensible or feasible. I generally trust my password manager, but I don’t want it to be a single point of failure for my entire digital life.

What passwords do I try to remember?

  1. The login password for my computer. I configure my Mac to sleep after a few minutes of inactivity, then ask for my login password when I try to resume. Although I often use Touch ID to log in, I remember this password because I still have to enter it multiple times a day.

  2. The master password for my password manager. This unlocks all of my other passwords.

  3. The login passcode for my phone. I use an alphanumeric password, and I remember it because I have to enter it multiple times a day.

  4. My email password. My email account is the gateway to all my other digital accounts. If I lost access to my password manager but could still receive email, I could reset my passwords and regain access to everything.

  5. My remote backup password. I have offsite backups of all of my computers with Backblaze. If all of my devices were destroyed at once (for example, in a house fire), this would allow me to retrieve files from my backups, even without access to my password manager.

  6. The encryption password for my multi-factor authentication (MFA) recovery codes. I have an MFA app on my phone, protected by Face ID or my passcode – but in an emergency, I have single-use recovery codes I can use instead. These are stored in an encrypted disk image.

  7. My Apple Account password. I’m heavily enmeshed in the Apple ecosystem, and this account has powerful access to my devices, including remote wiping and backups.

  8. The “memorable word” for my online banking. When I log in to my bank account, my password manager autofills a password, and then I have to fill in three characters of a longer “memorable word”. For example, I might be asked to enter the 1st, 5th, and 8th characters.

    I memorise this both for security and convenience. If somebody compromises my password manager, my bank account is safe – and even if this was in my password manager, it can’t fill in single characters this way.

All of these passwords are long, alphanumeric, and unique.

I have a regular calendar reminder to review them, and make sure I still remember them correctly. This would be a useful feature in a password manager – periodic tests on whether you still remember important passwords.

Where are these passwords stored?

Although I’ve memorised all eight passwords, there are some copies elsewhere.

Five of them are stored in my password manager, because it’s convenient: my computer’s login password, my phone’s login passcode, my email password, my remote backup password, and my Apple account password. (My email and Apple account are protected by multi-factor authentication, and the codes aren’t in my password manager.)

Two of them aren’t written down anywhere, but they might be soon: the master password for my password manager, and the encryption password for my MFA recovery codes. At some point I’d like to change this, probably with a paper copy in a fire safe or similar. This would allow my family to retrieve those passwords in an emergency.

The “memorable word” for my online banking isn’t written down anywhere, and I doubt it ever will be. If I lose access to my bank account and I’m really stuck, I can visit a physical branch.

How would I regain access to my accounts?

Here’s how I’d get back into my key accounts:

  • Remote backups. My Backblaze account is only protected with a password, not MFA. I have this memorised, so I could download files from my remote backups on any device.

  • MFA recovery codes. These are in an encrypted disk image in my remote backups. I’ve memorised the disk image password, so I could retrieve my MFA codes once I get to my remote backups.

  • Email inbox. This is protected by a password and MFA. I’ve memorised the password, and I could use an MFA recovery code to regain access to the account.

  • Password manager. I use 1Password. Logging in on a new device needs two secrets: my Master Password and Secret Key.

    I remember the former, but the latter is a random UUID I don’t type in or see regularly. Instead, I have a 1Password Emergency Kit which includes my Secret Key (but not my Master Password). I have a printed copy of this kit in my folder of important papers, and a digital copy in my disk image of MFA recovery codes.

    If I can get a copy of the Emergency Kit, I can regain access to my password manager.

What passwords don’t I remember?

There are a couple of important passwords you might expect me to memorise, but I don’t:

  1. The email password for my work email. This password is stored in the password manager I use at work, and if I unexpectedly lost access, I’d contact the IT team for help. I don’t need self-service recovery for this account.

  2. The master password for my password manager at work. For similar reasons to work email, I’d rely on the IT team to regain access in an emergency.

  3. My banking app username or password password. Logging into my bank requires three values: my username, the full-length password, and the “memorable information”. I’ve memorised the memorable information, but not the username or password. If I need emergency access to my bank account, I can visit a high street branch.

What scenario am I trying to prevent?

Imagine you lost all of your devices. Could you regain access to your digital life? That’s my worst-case scenario that I’m trying to avoid, and these memorised passwords should be enough to bootstrap everything.

I can retrieve my MFA recovery codes from my remote backups, and then I can either log into my password manager and retrieve the current passwords, or log into my email inbox and reset all my passwords. Either way, I’m back into my accounts.

This doesn’t cover the scenario where I lose access to both my email inbox and my password manager, but that would be a catastrophic digital disaster.

It also doesn’t cover the scenario where I’m incapacitated and a family member needs emergency access to my digital accounts. That’s something I’m planning to fix this year. My plan is to purchase a fire safe that somebody else can open, in which I’d place printed instructions for access my password manager. Inside my password manager, I’ll have a note that explains what the key accounts are, which I can update regularly without reopening the safe.

I hope I can continue to rely on my password manager, and I never encounter one of these emergency scenarios – but I feel better knowing I’ve tried to prepare.

[If the formatting of this post looks odd in your feed reader, visit the original article]

Where I store my multi-factor recovery codes

2026-01-11 00:30:24

When I read advice about passwords and online accounts, it usually goes something like this:

  1. Create unique passwords for each account, and store them in a password manager.
  2. Enable multi-factor authentication (MFA), and use an authenticator app or hardware token as your second factor.

But enabling MFA isn’t everything – what if you lose access to that second factor? For example, I store my MFA codes in an app on my phone. What happens if my phone is broken or stolen?

Most services that support MFA give me a set of recovery codes I can use in an emergency to regain access to my account, but don’t explain what to do with them. I’m advised to “store them securely”, but what does that mean in practice?

I don’t want to store my recovery codes in my password manager, because that compresses multiple authentication factors back into one. Somebody who compromised my password manager would have access to everything. (That’s the same reason I don’t store my MFA codes in there.)

Instead, I have an encrypted disk image on my Mac, which I created using Disk Utility. The password is a long, unique password that I only use for this purpose, and I only keep the disk image mounted when I’m editing or using a recovery code.

This disk image contains two files:

  • My 1Password Emergency Kit, a PDF document that contains the details for my 1Password account – including a secret key that I don’t see or type on a day-to-day basis
  • An HTML file I write by hand, which has all my MFA recovery codes and notes on when I created them

Here’s what the HTML file looks like (with fake data, obviously):

A page with a couple of sections (headed 'Apple Account', 'Etsy', 'GitHub'). In each section is a bit of explanatory text, about when I saved the recovery codes and what account they're for, then the recovery codes in a monospaced font.

You can download the HTML file I use as a template.

When I need to save some new recovery codes, I mount the disk image, edit the HTML file, then eject the disk image. When I need to use a recovery code, I mount the disk image, copy a code out of the HTML file, make a note that I’ve used it, then eject the disk image. This is a plain text file that’s not dependent on proprietary software or cloud services.

I have a second disk image on my work laptop, with a similar file, where I store recovery codes related to my work accounts.

A malicious program could theoretically read the HTML file while the disk image is mounted, so I try to keep it mounted as little as possible. But if a malicious program had long-running access to arbitrary files on my Mac, it can do more damage than just reading my recovery codes.

This setup assumes I’ll always have access to this disk image, which is why I have offsite backups that include it (in encrypted form, of course). I’ve memorised the password for my offsite backups, which gives me a clear recovery path if I lose my phone and all my home devices:

  1. Log into my offsite backup
  2. Download the encrypted disk image
  3. Mount the disk image
  4. Retrieve my 1Password Secret Key and MFA recovery codes

From there, I can get access to everything in my digital life.

Later this year, I plan to get a fire safe where I can store important documents, and a paper copy of these codes will definitely be in there. That’s why I include dates in the notes, and the current date at the top of the file – that way, I can see if the printed version is up-to-date.

I’m fortunate that I’ve never had to use this system in a real emergency – and helpful as it could be, let’s hope I never have to.

[If the formatting of this post looks odd in your feed reader, visit the original article]

Quick-and-dirty print debugging in Go

2026-01-08 16:54:38

I’ve been writing a lot of Go in my new job, and trying to understand a new codebase.

When I’m reading unfamiliar code, I like to use print debugging to follow what’s happening. I print what branches I’m in, the value of different variables, which functions are being called, and so on. Some people like debuggers or similar tools, but when you’re learning a new language they’re another thing to learn – whereas printing “hello world” is the first step in every language tutorial.

The built-in way to do print debugging in Go is fmt.Printf or log.Printf. That’s fine, but my debug messages get interspersed with the existing logs so they’re harder to find, and it’s easy for those debug statements to slip through code review.

Instead, I’ve taken inspiration from Ping Yee’s Python module “q”. If you’re unfamiliar with it, I recommend his lightning talk, where he explains the frustration of trying to find a single variable in a sea of logs. His module provides a function q.q(), which logs any expressions to a standalone file. It’s quick and easy to type, and the output is separate from all your other logging.

I created something similar for Go: a module which exports a single function Q(), and logs anything it receives to /tmp/q.txt. Here’s an example:

package main

import (
	"github.com/alexwlchan/q"
	"os"
)

func printShapeInfo(name string, sides int) {
	q.Q("a %s has %d sides", name, sides)
}

func main() {
	q.Q("hello world")

	q.Q(2 + 2)

	_, err := os.Stat("does_not_exist.txt")
	q.Q(err)

	printShapeInfo("triangle", 3)
}

The logged output in /tmp/q.txt includes the name of the function and the expression that was passed to Q():

main: "hello world"

main: 2 + 2 = 4

main: err = stat does_not_exist.txt: no such file or directory

printShapeInfo: a triangle has 3 sides

I usually open a terminal window running tail -f /tmp/q.txt to watch what gets logged by q.

The module is only 120 lines of Go, and available on GitHub. You can copy it into your project, or it’s simple enough that you could write your own version. It has two interesting ideas that might have broader use.

Getting context with the runtime package

When you call Q(), it receives the final value – for example, if you call Q(2 + 2), it receives 4 – but I wanted to log the original expression and function name. This is a feature from Ping’s Python package, and it’s what makes q so pleasant to use. This gives context for the log messages, and saves you typing that context yourself.

I get this information from Go’s runtime package, in particular the runtime.Caller function, which gives you information about the currently-running function.

I call runtime.Caller(1) to step up the callstack by 1, to the actual line in my code where I typed Q(). It tells me the “program counter”, the filename, and the line number. I can resolve the program counter to a function name with runtime.FuncForPC, and I can just open the file and look up that line to read the expression. (This assumes the source code hasn’t changed since compilation, which is always true when I’m doing local debugging.)

Not affecting my coworkers with a local gitignore

To use this file, I copy q.go into my work repos and add it to my .git/info/exclude. The latter is a local-only ignore file, unlike the .gitignore file which is checked into the repo. This means I won’t accidentally check in q.go or push it to GitHub.

It also means I can’t forget to remove my debugging code, because if I do, the tests in CI will fail when they can’t find q.go.

This avoids other approaches that would be more disruptive or annoying, like making it a project dependency or adding it to the shared .gitignore file.

[If the formatting of this post looks odd in your feed reader, visit the original article]