Journal tags: light



Dark mode revisited

I added a dark mode to my website a while back. It was a fun thing to do during Indie Web Camp Amsterdam last year.

I tied the colour scheme to the operating system level. If you choose a dark mode in your OS, my website will adjust automatically thanks to the prefers-color-scheme: dark media query.

But I’ve seen notes from a few friends, not about my site specifically, but about how they like having an explicit toggle for dark mode (as well as the media query). Whenever I read those remarks, I’d think “I’m really not sure I’ve got time to deal with adding that kind of toggle to my site.”

But then I realised, “Jeremy, you absolute muffin! You’ve had a theme switcher on your website for almost two decades now!”

Doh! I had forgotten about that theme switcher. It dates back to the early days of CSS. I wanted my site to be a demonstration of how you could apply different styles to the same underlying markup (this was before the CSS Zen Garden came along). Those themes are very dated now, but if you like you can view my site with a Zeldman theme or a sci-fi theme.

To offer a dark-mode theme for my site, all I had to do was take the default stylesheet, pull out the custom properties from the prefers-color-scheme: dark media query, and done. It took less than five minutes.

So if you want to view my site in dark mode, it’s one of the options in the “Customise” dropdown on every page of the website.

Programming CSS to perform Sass colour functions

I wrote recently about moving away from Sass to using native CSS features. I had this to say on the topic of mixins in Sass:

These can be very useful, but now there’s a lot that you can do just in CSS with calc(). The built-in darken() and lighten() mixins are handy though when it comes to colours.

I know we will be getting these in the future but we’re not there yet with CSS.

Anyway, I had all this in the back of my mind when I was reading Lea’s excellent feature in this month’s Increment: A user’s guide to CSS variables. She’s written about a really clever technique of combining custom properites with hsl() colour values for creating colour palettes. (See also: Una’s post on dynamic colour theming with pure CSS.)

As so often happens when I’m reading something written by Lea—or seeing her give a talk—light bulbs started popping over my head (my usual response to Lea’s knowledge bombs is either “I didn’t know you could do that!” or “I never thought of doing that!”).

I immediately set about implementing this technique over on The Session. The trick here is to use separate custom properties for the hue, saturation, and lightness parts of hsl() colour values. Then, when you want to lighten or darken the colour—say, on hover—you can update the lightness part.

I’ve made a Codepen to show what I’m doing.

Let’s say I’m styling a button element. I make custom propertes for hsl() values:

button {
  --button-colour-hue: 19;
  --button-colour-saturation: 82%;
  --button-colour-lightness: 38%;
  background-color: hsl(

For my buttons, I want the borders to be slightly darker than the background colour. When I was using Sass, I used the darken() function to this. Now I use calc(). Here’s how I make the borders 10% darker:

border-color: hsl(
  calc(var(--button-colour-lightness) - 10%)

That calc() function is substracting a percentage from a percentage: 38% minus 10% in this case. The borders will have a lightness of 28%.

I make the bottom border even darker and the top border lighter to give a feeling of depth.

On The Session there’s a “cancel” button style that’s deep red.

Here’s how I set its colour:

.cancel {
  --button-colour-hue: 0;
  --button-colour-saturation: 100%;
  --button-colour-lightness: 40%;

That’s it. The existing button declarations take care of assigning the right shades for the border colours.

Here’s another example. Site admins see buttons for some actions only available to them. I want those buttons to have their own colour:

.admin {
  --button-colour-hue: 45;
  --button-colour-saturation: 100%;
  --button-colour-lightness: 40%;

You get the idea. It doesn’t matter how many differently-coloured buttons I create, the effect of darkening or lightening their borders is all taken care of.

So it turns out that the lighten() and darken() functions from Sass are available to us in CSS by using a combination of custom properties, hsl(), and calc().

I’m also using this combination to lighten or darken background and border colours on :hover. You can poke around the Codepen if you want to see that in action.

I love seeing the combinatorial power of these different bits of CSS coming together. It really is a remarkably powerful programming language.


It’s been fascinating to see how television programmes have adapted to The Situation. It’s like there’s been a weird inversion with the YouTube asthetic. Instead of YouTubers doing their utmost to emulate the look of professional television, now everyone on professional television looks like a YouTuber.

No more lighting or audio technicians. No more studio audiences. Heck, no more studios.

There are some kinds of TV programmes that are showing the strain. A lot of comedy formats just fall flat without the usual production values. But a lot of programmes work just fine. In fact, some of them might be better. Watching Mary Beard present Front Row Late from her house is an absolute delight. It feels more direct and honest without the artiface of a television studio. It kind of makes you wonder whether expensive production costs are really necessary when what you really care about is the content.

All of this is one big belaboured metaphor for websites.

In times of crisis, informational websites sometimes offer a “lite” version. Max has even made an emergency website kit:

The site contains only the bare minimum - no webfonts, no tracking, no unnecessary images. The entire thing should fit in a single HTTP request. It’s basically just a small, ultra-lean blog focused on maximum resilience and accessibility. The Service Worker takes it a step further from there so if you’ve visited the site once, the information is still accessible even if you lose network coverage.

Eric emphasises the importance of performance in his post Get Static:

I’m thinking here of sites for places like health departments (and pretty much all government services), hospitals and clinics, utility services, food delivery and ordering, and I’m sure there are more that haven’t occurred to me.  As much as you possibly can, get it down to static HTML and CSS and maybe a tiny bit of enhancing JS, and pare away every byte you can.

Tom Loosemore offers this advice to teams building new coronavirus services:

  1. Get a 4 year-old Android phone, and use it as your test/demo device.
  2. is your friend.
  3. Full React isn’t your friend if it makes your service slow & inaccessible

Remember: This is for everyone.

Indeed, are usually a paragon of best practices in just about any situation. But they dropped the ball recently, as Matthew attests: is a static site, fetching and displaying remote data. It is also a 100% client-side JavaScript React site. is 238K vs 770K (basics) on load. I’ve removed about 550K of JavaScript. It seems to work the same.

As Tom says:

One sign that your website isn’t meeting the needs of all your users is when Matthew Somerville gets sufficiently grumpy about it to do a proper version himself.

It’s true enough that Matthew excels at creating lightweight, accessible versions of services that are too bloated or buggy to use. His accessible Odeon project from back in the day is legendary. And I use his slimline version of the National Rail website all the time:—it’s a terrificly performant progressive web app.

It’s thankless work though. It flies in the face of everything considered “modern” web development. (If you want to know the cost of “modern” framework-driven JavaScript-first web development, Tim has the numbers.) But Matthew is kind of a hero to me. I wish more developers would follow his example.

Maybe now, with this rush to make lightweight versions of valuable services, we might stop and reflect on whether we ever really needed all those added extras in the first place.

Hope springs eternal.

Update: Matthew has written about his process in Looking at

Lighthouse bookmarklet

I use Firefox. You should too. It’s fast, secure, and more privacy-focused than the leading browser from the big G.

When it comes to web development, the CSS developer tooling in Firefox is second-to-none. But when it comes to JavaScript and network-related debugging (like service workers), Chrome’s tools are currently better than Firefox’s (for now). For example, Chrome has a tab in its developer tools that lets you run Lighthouse on the currently open tab.

Yesterday, I got the Calibre newsletter, which always has handy performance-related links from Karolina. She pointed to a Lighthouse extension for Firefox. “Excellent!”, I thought, and I immediately installed it. But I had some qualms about installing a plug-in from Google into a browser from Mozilla, particularly as the plug-in page says:

This is not a Recommended Extension. Make sure you trust it before installing

Well, I gave it a go. It turns out that all it actually does is redirect to the online version of Lighthouse. “Hang on”, I thought. “This could just be a bookmarklet!”

So I immediately uninstalled the browser extension and made this bookmarklet:


Drag that up to your desktop browser’s bookmarks toolbar. Press it whenever you’re on a site that you want to test.

Dark mode

I had a very productive time at Indie Web Camp Amsterdam. The format really lends itself to getting the most of a weekend—one day of discussions followed by one day of hands-on making and doing. You should definitely come along to Indie Web Camp Brighton on October 19th and 20th to experience it for yourself.

By the end of the “doing” day, I had something fun to demo—a dark mode for my website.

Y’know, when I first heard about Apple adding dark mode to their OS—and also to CSS—I thought, “Oh, great, Apple are making shit up again!” But then I realised that, like user style sheets, this is one more reminder to designers and developers that they don’t get the last word—users do.

Applying the dark mode styles is pretty straightforward in theory. You put the styles inside this media query:

@media (prefers-color-scheme: dark) {

Rather than over-riding every instance of a colour in my style sheet, I decided I’d do a little bit of refactoring first and switch to using CSS custom properties (or variables, if you will).

:root {
  --background-color: #fff;
  --text-color: #333;
  --link-color: #b52;
body {
  background-color: var(--background-color);
  color: var(--text-color);
a {
  color: var(--link-color);

Then I can over-ride the custom properties without having to touch the already-declared styles:

@media (prefers-color-scheme: dark) {
  :root {
    --background-color: #111416
    --text-color: #ccc;
    --link-color: #f96;

All in all, I have about a dozen custom properties for colours—variations for text, backgrounds, and interface elements like links and buttons.

By using custom properties and the prefers-color-scheme media query, I was 90% of the way there. But the devil is in the details.

I have SVGs of sparklines on my homepage. The SVG has a hard-coded colour value in the stroke attribute of the path element that draws the sparkline. Fortunately, this can be over-ridden in the style sheet:

svg.activity-sparkline path {
  stroke: var(--text-color);

The real challenge came with the images I use in the headers of my pages. They’re JPEGs with white corners on one side and white gradients on the other.

header images

I could make them PNGs to get transparency, but the file size would shoot up—they’re photographic images (with a little bit of scan-line treatment) so JPEGs (or WEBPs) are the better format. Then I realised I could use CSS to recreate the two effects:

  1. For the cut-out triangle in the top corner, there’s clip-path.
  2. For the gradient, there’s …gradients!
background-image: linear-gradient(
  to right,
  transparent 50%,
  var(—background-color) 100%

Oh, and I noticed that when I applied the clip-path for the corners, it had no effect in Safari. It turns out that after half a decade of support, it still only exists with -webkit prefix. That’s just ridiculous. At this point we should be burning vendor prefixes with fire. I can’t believe that Apple still ships standardised CSS properties that only work with a prefix.

In order to apply the CSS clip-path and gradient, I needed to save out the images again, this time without the effects baked in. I found the original Photoshop file I used to export the images. But I don’t have a copy of Photoshop any more. I haven’t had a copy of Photoshop since Adobe switched to their Mafia model of pricing. A quick bit of searching turned up Photopea, which is pretty much an entire recreation of Photoshop in the browser. I was able to open my old PSD file and re-export my images.

LEGO clone trooper Brighton bandstand Scaffolding Tokyo Florence

Let’s just take a moment here to pause and reflect on the fact that we can now use CSS to create all sorts of effects that previously required a graphic design tool like Photoshop. I could probably do those raster scan lines with CSS if I were smart enough.

dark mode

This is what I demo’d at the end of Indie Web Camp Amsterdam, and I was pleased with the results. But fate had an extra bit of good timing in store for me.

The very next day at the View Source conference, Melanie Richards gave a fantastic talk called The Tailored Web: Effectively Honoring Visual Preferences (seriously, conference organisers, you want this talk on your line-up). It was packed with great insights and advice on impementing dark mode, like this little gem for adjusting images:

@media (prefers-color-scheme: dark) {
  img {
    filter: brightness(.8) contrast(1.2);

Melanie also pointed out that you can indicate the presence of dark mode styles to browsers, although the mechanism is yet to shake out. You can do it in CSS:

:root {
  color-scheme: light dark;

But you can also do it in HTML:

That allows browsers to swap out replaced content; interface elements like form fields and dropdowns.

Oh, and one other addition I added after the fact was swapping out map imagery by using the picture element to point to darker map tiles:

<source media="(prefers-color-scheme: dark)" srcset="">
<img src="" alt="map">

light map dark map

So now I’ve got a dark mode for my website. Admittedly, it’s for just one of the eight style sheets. I’ve decided that, while I’ll update my default styles at every opportunity, I’m going to preservethe other skins as they are, like the historical museum pieces they are.

If you’re on the latest version of iOS, go ahead and toggle the light and dark options in your system preferences to flip between this site’s colour schemes.

The voice of MOL

The latest issue of Spaceflight—the magazine of the British Interplanetary Society—dropped through my door, adding to my weekend reading list. This issue contains a “whatever happened to” article about the military personnel who were supposed to crew the never-realised MOL project.

Before Salyut, Skylab, Mir, or the ISS, the Manned Orbital Laboratory was the first proposed space station. It would use a Gemini capsule and a Titan propellant tank.

Manned Orbital Laboratory

But this wasn’t to be a scientific endeavour. The plan was to use the MOL as a crewed spy satellite—human eyes in the sky watching the enemy below.

The MOL was cancelled (because uncrewed satellites were getting better at that sort of thing), so that particular orbital panopticon never came to pass.

I remember when I first heard of the MOL and I was looking it up on Wikipedia, that this little nugget of information stood out to me:

The MOL was planned to use a helium-oxygen atmosphere.

That’s right: instead of air (21% oxygen, 79% nitrogen), the spies in the sky would be breathing heliox (21% oxygen, 79% helium). Considering the effect that helium has on the human voice, I can only imagine that the grave nature of the mission would have been somewhat compromised.

100 words 075

Today was a Salter Cane practice day. It was a good one. We tried throwing some old songs at our new drummer, Emily. They stuck surprisingly well. Anomie, Long Gone, John Hope …they all sounded pretty damn good. To be honest, Emily was probably playing them better than the rest of us.

It was an energetic band practice so by the time I got home, I was really tired. I kicked back and relaxed with the latest copy of Spaceflight magazine from the British Interplanetary Society.

Then I went outside and watched the International Space Station fly over my house.

100 words 003

I measure transatlantic flights in movies watched. Yesterday’s journey from London to Seattle was four movies long.

  1. The Imitation Game: a necessarily fictionalised account of Turing’s life (one of the gotchas about top-secret work is that it’s, well, secret). But couldn’t Tommy Flowers have been given at least a walk-on part?
  2. Fury: Brad Pitt plays Lee Marvin in a war story told through the eyes of the naive rookie as seen in The Big Red One and Saving Private Ryan.
  3. Hunger Games: Mockingjay: Part One: The Hungering.
  4. Paddington: just right for the end of a flight.

Brighton Arton

If you’re already in Brighton for dConstruct this week, there are two art shows you might want to check out:

Timo Arnall and friends are presenting their Immaterials exhibit at Lighthouse, 28 Kensington Street. Timo has written a few words about the exhibit. The exhibition is open from tomorrow, September 5th until October 13th, and the opening is tonight, Wednesday, at 6pm.

Just around the corner at the Ink-d gallery on North Road, the opening of Jon Burgerman’s exhibit is happening at exactly the same time: 6pm this evening. His Failure of Judgement exhibition runs from tomorrow, September 5th to October 6th.

Jon’s opening will also include the debut of the world’s first “Jon Burgerman Veggie Burger” courtesy of Burger Brothers next door.

And if you haven’t gorged yourself too much on art after this evening, don’t forget that there’s a whole programme of different art shows running this month at 68 Middle Street.

Jets dream

I’m back home after a bit of a whirlwind visit to the US for An Event Apart in Atlanta (which was, as always, superb). I’m currently battling east-west jet lag. My usual technique is exactly what Charles Stross describes:

Simply put: go to bed immediately but set an alarm to wake you after no more than 3 hours. Then get up, and stay up, until 11pm. That’s around 3-5 hours. During this time, do nothing more intellectually challenging than running a hot bath. You haven’t caught up with your sleep deficit, you’ve just pushed it back a bit: you are as cognitively impaired as if you are medium-drunk. Now is a good time — if you have the energy — to load your dirty clothes into the washing machine, have a bath, watch something mindless on TV, and catch up on web comics. Don’t worry: you won’t remember anything tomorrow. Just refrain from answering urgent business email, driving, assembling delicate instruments, or discussing important matters — if you do any of these things, odds are high that you’ll get them horribly wrong due to the impairment caused by cumulative sleep deprivation.

He goes on to wish for the invention of teleportation (and to describe a jet-lag inspired RPG).

There’s another situation where we have to deal with sitting in one place through a long uncomfortable experience: dental surgery. In that situation, we rely on medication to get us through. A little bit of nitrous oxide and the whole thing is literally over before you know it.

Why don’t we do the same thing for transatlantic air travel? The equipment is already in place—those oxygen masks above every chair could easily be repurposed to pump out laughing gas.

If this is a stupid idea, you’ll have to forgive me: I blame the jet lag.


The travelling time is underway. I’m in Denmark right now, leading an HTML5 workshop at NoMA, the Nordic Multimedia Academy, and thanks to some excellent questions from the students, it’s all going smoothly.

Last week I was in Belgium for the Phare conference, which also went smoothly. I enjoyed giving my presentation and I really enjoyed the excellent hospitality of the Ghentians.

While I was in Belgium, the occasion of my fortieth birthday arrived with a sense of long-foreseen inevitability. I spent it in Bruges.

Four zero. The big four oh. Two squared times ten. The answer to life, the universe and everything minus two.

The photons that were reflected from Earth at the time of my birth are arriving at GJ 1214 b. Or, to put in another way, the light that left GJ 1214 at the moment of my birth is entering our solar system, perhaps even reaching the retinas of human beings somewhere on this planet who happen to be looking into just the right part of the sky at just the right time.

Demo hell

The Future of Web Design just took a nose-dive. I’m having flashbacks to MIX08 as we are subjected to an interminable Silverlight demo.

Any conference that allows sponsors to buy speaking slots isn’t putting together a user-centred schedule. At best, these shill spots are tolerated. For most people, they engender downright hostility.

On the plus side, these pathetic little sales pitches are shorter than the real speaking slots. I can see Paul edging up on the side of the stage, ready to drag this guy off—he has definitely outstayed his welcome.