<?xml version="1.0" encoding="utf-8" ?>
<?xml-stylesheet href="/feed.xsl" type="text/xsl" ?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en" xml:base="https://chrisburnell.com">
	<title>Chris Burnell</title>
	<subtitle>Latest 10 posts on the blog.</subtitle>
	<id>https://chrisburnell.com/</id>
	<link href="https://chrisburnell.com/feed.xml" rel="self" />
	<link href="https://chrisburnell.com/" rel="alternate" />
	<author>
		<name>Chris Burnell</name>
		<uri>https://chrisburnell.com/</uri>
		<email>me@chrisburnell.com</email>
	</author>
	<logo>https://chrisburnell.com/images/raven.svg</logo>
	<image>https://chrisburnell.com/images/avatar@4x.jpeg</image>
	<updated>2026-05-09T12:49:02+00:00</updated>
	<generator>Eleventy v3.1.0</generator>
	<entry>
		<id>https://chrisburnell.com/article/fresh-pixels/</id>
		<link href="https://chrisburnell.com/article/fresh-pixels/" />
		<title>Fresh pixels</title>
		<published>2026-05-09T09:49:02-03:00</published>
		<publishedFriendly>9 May 2026</publishedFriendly>
		<updated>2026-05-09T09:49:02-03:00</updated>
		<category term="article" scheme="https://chrisburnell.com/article/" label="Article" />
		<summary>Status updates and enforcing the crispiest-possible 88x31 badges across the web</summary>
		<content xml:lang="en" type="html">
			&lt;p&gt;Status updates and enforcing the crispiest-possible 88x31 badges across the web&lt;/p&gt;
				&lt;hr&gt;
			
			
			&lt;p&gt;Yesterday afternoon, I spent some time creating a new &lt;a href=&quot;/88x31/&quot;&gt;88x31 badge&lt;/a&gt; for my website footer.&lt;/p&gt;
&lt;p&gt;In case you missed &lt;a href=&quot;/article/new-year-new-server-2/&quot;&gt;my last post&lt;/a&gt;, I’m in the midst of migrating servers, and one of the things I migrated was &lt;a href=&quot;https://status.chrisburnell.com&quot;&gt;my status page&lt;/a&gt;, which pings each of my websites once per minute and reports whether the request was successful, and if so, how long the request took. Each website is represented on the front end by a graph of response times across the last hour as well as 60 red or green-coloured &lt;em&gt;bloops&lt;/em&gt;, which represent the successes of the last hour’s pings.&lt;/p&gt;
&lt;p&gt;So the idea was to create a dynamic 88x31 badge that relayed information from my status page by displaying some number of the most recent &lt;em&gt;bloops&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;I set out achieving this by first creating the 88x31 image with some blank areas that I could fill in later with green or red, depending on the status of each &lt;em&gt;bloop&lt;/em&gt;, and the next step was to create a short PHP script to consume this image, fill in the colours of the &lt;em&gt;bloops&lt;/em&gt;, and spit out the result.&lt;/p&gt;
&lt;p&gt;Fortunately, generating images with PHP is one of the first things I ever learned how to do outside of HTML and CSS early on in my web development journey, nearly 20 years ago, so this was a familiar task!&lt;/p&gt;
&lt;p&gt;And the result (blown up for clarity):&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&quot;https://chrisburnell.com/images/uptime.gif&quot; alt=&quot;Server uptime badge&quot; width=&quot;352&quot; height=&quot;124&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;/figure&gt;
&lt;details&gt;
    &lt;summary&gt;Backend code, if you’re interested&lt;/summary&gt;
&lt;p&gt;Here’s what the JSON file of minute-by-minute data looks like:&lt;/p&gt;
&lt;pre data-language=&quot;json&quot; data-language-friendly=&quot;JSON&quot; data-filename=&quot;uptime.json&quot;&gt;&lt;code&gt;&lt;span&gt;[&lt;/span&gt;
	&lt;span&gt;{&lt;/span&gt;
		&lt;span&gt;&quot;timestamp&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;1778260261&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
		&lt;span&gt;&quot;time&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;0.406&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
		&lt;span&gt;&quot;response&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;200&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
		&lt;span&gt;&quot;http_version&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;&quot;2&quot;&lt;/span&gt;
	&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
	&lt;span&gt;{&lt;/span&gt;
		&lt;span&gt;&quot;timestamp&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;1778260321&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
		&lt;span&gt;&quot;time&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;0.446&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
		&lt;span&gt;&quot;response&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;200&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
		&lt;span&gt;&quot;http_version&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;&quot;2&quot;&lt;/span&gt;
	&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
	&lt;span&gt;{&lt;/span&gt;
		&lt;span&gt;&quot;timestamp&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;1778260381&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
		&lt;span&gt;&quot;time&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;0.807&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
		&lt;span&gt;&quot;response&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;200&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
		&lt;span&gt;&quot;http_version&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;&quot;2&quot;&lt;/span&gt;
	&lt;span&gt;}&lt;/span&gt;
&lt;span&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And here’s the gist of the PHP that generates the dynamic image:&lt;/p&gt;
&lt;pre data-language=&quot;php&quot; data-language-friendly=&quot;PHP&quot; data-filename=&quot;uptime.php&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;lt;?php&lt;/span&gt;
&lt;p&gt;&lt;span&gt;$image&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;imagecreatefromgif&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;uptime.gif&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;br&gt;
&lt;span&gt;$green&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;imagecolorallocate&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;$image&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;102&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;204&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;51&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;br&gt;
&lt;span&gt;$red&lt;/span&gt;   &lt;span&gt;=&lt;/span&gt; &lt;span&gt;imagecolorallocate&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;$image&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;153&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;51&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;br&gt;
&lt;span&gt;$cacheFile&lt;/span&gt;  &lt;span&gt;=&lt;/span&gt; &lt;span&gt;&quot;cache.json&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;br&gt;
&lt;span&gt;$uptimeFile&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;&quot;uptime.json&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;br&gt;
&lt;span&gt;$numBloops&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;9&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;if&lt;/span&gt; &lt;span&gt;(&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;file_exists&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;$cacheFile&lt;/span&gt;&lt;span&gt;)&lt;/span&gt; &lt;span&gt;||&lt;/span&gt; &lt;span&gt;filemtime&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;$cacheFile&lt;/span&gt;&lt;span&gt;)&lt;/span&gt; &lt;span&gt;&amp;lt;&lt;/span&gt; &lt;span&gt;time&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt; &lt;span&gt;-&lt;/span&gt; &lt;span&gt;60&lt;/span&gt;&lt;span&gt;)&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;&lt;br&gt;
&lt;span&gt;$uptimeData&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;json_decode&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;file_get_contents&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;$uptimeFile&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;true&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;br&gt;
&lt;span&gt;usort&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;$uptimeData&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;fn&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;$a&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;$b&lt;/span&gt;&lt;span&gt;)&lt;/span&gt; &lt;span&gt;=&amp;gt;&lt;/span&gt; &lt;span&gt;$b&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;&quot;timestamp&quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt; &lt;span&gt;-&lt;/span&gt; &lt;span&gt;$a&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;&quot;timestamp&quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;br&gt;
&lt;span&gt;$recent&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;array_slice&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;$uptimeData&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;$numBloops&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;br&gt;
&lt;span&gt;$cache&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;[&lt;/span&gt;&lt;br&gt;
&lt;span&gt;&quot;timestamp&quot;&lt;/span&gt; &lt;span&gt;=&amp;gt;&lt;/span&gt; &lt;span&gt;$recent&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;&quot;timestamp&quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;br&gt;
&lt;span&gt;&quot;bloops&quot;&lt;/span&gt; &lt;span&gt;=&amp;gt;&lt;/span&gt; &lt;span&gt;array_map&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;br&gt;
&lt;span&gt;fn&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;$item&lt;/span&gt;&lt;span&gt;)&lt;/span&gt; &lt;span&gt;=&amp;gt;&lt;/span&gt; &lt;span&gt;$item&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;&quot;time&quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt; &lt;span&gt;&amp;gt;&lt;/span&gt; &lt;span&gt;0&lt;/span&gt; &lt;span&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span&gt;$item&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;&quot;response&quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt; &lt;span&gt;&amp;gt;=&lt;/span&gt; &lt;span&gt;200&lt;/span&gt; &lt;span&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span&gt;$item&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;&quot;response&quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt; &lt;span&gt;&amp;lt;=&lt;/span&gt; &lt;span&gt;299&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;br&gt;
&lt;span&gt;$recent&lt;/span&gt;&lt;br&gt;
&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;br&gt;
&lt;span&gt;]&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;br&gt;
&lt;span&gt;file_put_contents&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;$cacheFile&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;json_encode&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;$cache&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;br&gt;
&lt;span&gt;}&lt;/span&gt; &lt;span&gt;else&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;&lt;br&gt;
&lt;span&gt;$cache&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;json_decode&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;file_get_contents&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;$cacheFile&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;true&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;br&gt;
&lt;span&gt;}&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;$bloops&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;array_reverse&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;$cache&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;&quot;bloops&quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;foreach&lt;/span&gt; &lt;span&gt;(&lt;/span&gt;&lt;span&gt;$bloops&lt;/span&gt; &lt;span&gt;as&lt;/span&gt; &lt;span&gt;$i&lt;/span&gt; &lt;span&gt;=&amp;gt;&lt;/span&gt; &lt;span&gt;$ok&lt;/span&gt;&lt;span&gt;)&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;&lt;br&gt;
&lt;span&gt;$color&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;$ok&lt;/span&gt; &lt;span&gt;?&lt;/span&gt; &lt;span&gt;$green&lt;/span&gt; &lt;span&gt;:&lt;/span&gt; &lt;span&gt;$red&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;br&gt;
&lt;span&gt;imagefilledrectangle&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;$image&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;2&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;3&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;4&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;$color&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;br&gt;
&lt;span&gt;}&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;header&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Content-type: image/gif&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;br&gt;
&lt;span&gt;header&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Cache-Control: public, max-age=60&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/p&gt;
&lt;/span&gt;&lt;/code&gt;&lt;p&gt;&lt;code&gt;&lt;span&gt;imagegif&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;$image&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;And while we’re at it, here’s how I’m serving my PHP file as if it’s a GIF in &lt;em&gt;nginx&lt;/em&gt;:&lt;/p&gt;
&lt;pre data-language=&quot;nginx&quot; data-language-friendly=&quot;nginx&quot; data-filename=&quot;/etc/nginx/sites-available/example.com&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;location&lt;/span&gt; = /images/uptime.gif&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;
    &lt;span&gt;&lt;span&gt;include&lt;/span&gt; fastcgi_params&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;
    &lt;span&gt;&lt;span&gt;fastcgi_param&lt;/span&gt; SCRIPT_FILENAME /absolute/path/to/uptime.php&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;
    &lt;span&gt;&lt;span&gt;fastcgi_pass&lt;/span&gt; unix:/run/php/php8.4-fpm.sock&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;
&lt;span&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;
&lt;hr&gt;
&lt;p&gt;In building this new 88x31, I was struck with another idea.&lt;/p&gt;
&lt;p&gt;Because I so fiercely believe that (most) 88x31 badges look best on the web when they have &lt;code&gt;image-rendering: pixelated;&lt;/code&gt; applied to them, why not enforce my belief across (almost) the entire web?&lt;/p&gt;
&lt;p id=&quot;using-css&quot;&gt;Using CSS&lt;/p&gt;
&lt;p&gt;I use the &lt;a href=&quot;https://add0n.com/stylus.html&quot;&gt;Stylus&lt;/a&gt; extension in my browser (&lt;a href=&quot;https://github.com/openstyles/stylus#releases&quot;&gt;get it here&lt;/a&gt;) to apply my own CSS to websites and pages. My custom styles are mostly to fix bugs, improve shaky layouts, or to add little quality-of-life stuff, and this seems like an ideal place for me to throw a small chunk of CSS and have my browser apply it across every page that I visit.&lt;/p&gt;
&lt;p&gt;The CSS I came up with is pretty minimal, and although it relies on the &lt;code&gt;width&lt;/code&gt; and &lt;code&gt;height&lt;/code&gt; attributes being present, many websites that I visit that &lt;em&gt;do&lt;/em&gt; have 88x31 badges at least have the attributes present, if not the desired CSS property applied as well:&lt;/p&gt;
&lt;pre data-language=&quot;css&quot; data-language-friendly=&quot;CSS&quot;&gt;&lt;code&gt;&lt;span&gt;:where(img[width=&quot;88&quot;][height=&quot;31&quot;])&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;
	&lt;span&gt;image-rendering&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; pixelated&lt;span&gt;;&lt;/span&gt;
&lt;span&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here’s how it looks in &lt;em&gt;Stylus&lt;/em&gt;. Note that I’m applying this CSS to &lt;q&gt;Everything&lt;/q&gt;, which means &lt;em&gt;every&lt;/em&gt; webpage that you visit:&lt;/p&gt;
&lt;figure&gt;
    &lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;/images/built/stylus-pixelate-88x31s-500.avif 500w, /images/built/stylus-pixelate-88x31s-800.avif 800w&quot; sizes=&quot;100vw&quot;&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/images/built/stylus-pixelate-88x31s-500.webp 500w, /images/built/stylus-pixelate-88x31s-800.webp 800w&quot; sizes=&quot;100vw&quot;&gt;&lt;img alt=&quot;Stylus UI when adding styles for all websites to pixelate 88x31 badges&quot; title=&quot;Stylus UI when adding styles for all websites to pixelate 88x31 badges&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;/images/built/stylus-pixelate-88x31s-500.png&quot; width=&quot;800&quot; height=&quot;128&quot; srcset=&quot;/images/built/stylus-pixelate-88x31s-500.png 500w, /images/built/stylus-pixelate-88x31s-800.png 800w&quot; sizes=&quot;100vw&quot;&gt;&lt;/picture&gt;
&lt;/figure&gt;
&lt;p id=&quot;using-js&quot;&gt;Using JavaScript&lt;/p&gt;
&lt;p&gt;Alternatively, if you want to achieve this for &lt;em&gt;all&lt;/em&gt; 88x31 images, whether they have the correct attributes or not, there’s the &lt;a href=&quot;https://www.tampermonkey.net/&quot;&gt;Tampermonkey&lt;/a&gt; extension (&lt;a href=&quot;https://www.tampermonkey.net/#download&quot;&gt;get it here&lt;/a&gt;) that works much like Stylus but with JavaScript instead of CSS. Here’s my script that checks the width and height of images &lt;em&gt;whether their attributes are set or not&lt;/em&gt; and appropriately applies the pixelated effect to 88x31 images:&lt;/p&gt;
&lt;pre data-language=&quot;javascript&quot; data-language-friendly=&quot;JavaScript&quot;&gt;&lt;code&gt;&lt;span&gt;// ==UserScript==&lt;/span&gt;
&lt;span&gt;// @name         Pixelate 88x31s&lt;/span&gt;
&lt;span&gt;// @match        *://*/*&lt;/span&gt;
&lt;span&gt;// @run-at       document-idle&lt;/span&gt;
&lt;span&gt;// ==/UserScript==&lt;/span&gt;
&lt;/code&gt;&lt;p&gt;&lt;code&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;...&lt;/span&gt;document&lt;span&gt;.&lt;/span&gt;&lt;span&gt;querySelectorAll&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;img&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;forEach&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;img&lt;/span&gt;&lt;span&gt;)&lt;/span&gt; &lt;span&gt;=&amp;gt;&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;&lt;br&gt;
&lt;span&gt;const&lt;/span&gt; &lt;span&gt;pixelate&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; &lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt; &lt;span&gt;=&amp;gt;&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;&lt;br&gt;
&lt;span&gt;if&lt;/span&gt; &lt;span&gt;(&lt;/span&gt;img&lt;span&gt;.&lt;/span&gt;naturalWidth &lt;span&gt;===&lt;/span&gt; &lt;span&gt;88&lt;/span&gt; &lt;span&gt;&amp;amp;&amp;amp;&lt;/span&gt; img&lt;span&gt;.&lt;/span&gt;naturalHeight &lt;span&gt;===&lt;/span&gt; &lt;span&gt;31&lt;/span&gt;&lt;span&gt;)&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;&lt;br&gt;
img&lt;span&gt;.&lt;/span&gt;style&lt;span&gt;.&lt;/span&gt;imageRendering &lt;span&gt;=&lt;/span&gt; &lt;span&gt;&quot;pixelated&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;br&gt;
&lt;span&gt;}&lt;/span&gt;&lt;br&gt;
&lt;span&gt;}&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;br&gt;
img&lt;span&gt;.&lt;/span&gt;complete &lt;span&gt;?&lt;/span&gt; &lt;span&gt;pixelate&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt; &lt;span&gt;:&lt;/span&gt; img&lt;span&gt;.&lt;/span&gt;&lt;span&gt;addEventListener&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;load&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; pixelate&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;br&gt;
&lt;span&gt;}&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Now properly-attributed 88x31 images will always appear nice and sharp, everywhere on the web, including but not limited to the ones in the footer of my website (though they already have the &lt;em&gt;correct&lt;/em&gt; &lt;code&gt;image-rendering&lt;/code&gt;)!&lt;/p&gt;
			
			
			
			
				&lt;hr&gt;
				&lt;p&gt;Thanks for subscribing and reading this post via RSS!&lt;/p&gt;
				&lt;p&gt;You can read &lt;a href=&quot;https://chrisburnell.com/article/fresh-pixels/&quot;&gt;this post&lt;/a&gt; and others &lt;a href=&quot;https://chrisburnell.com/posts/&quot;&gt;on my website&lt;/a&gt;.&lt;/p&gt;
		</content>
		
		
	</entry>
	<entry>
		<id>https://chrisburnell.com/article/new-year-new-server-2/</id>
		<link href="https://chrisburnell.com/article/new-year-new-server-2/" />
		<title>New Year, New Server 2: Electric Boogaloo</title>
		<published>2026-05-06T18:52:16-03:00</published>
		<publishedFriendly>6 May 2026</publishedFriendly>
		<updated>2026-05-06T18:52:16-03:00</updated>
		<category term="article" scheme="https://chrisburnell.com/article/" label="Article" />
		<summary>I got to really put my recently-practised and newly-learned system administrator skills to the test again, mere months after my my last update about migrating the server that hosts my websites. Why’s that, you ask? Because the server host that I switched to has been a major disappointment.</summary>
		<content xml:lang="en" type="html">
			&lt;p&gt;I got to really put my recently-practised and newly-learned system administrator skills to the test again, mere months after my my last update about migrating the server that hosts my websites. Why’s that, you ask? Because the server host that I switched to has been a major disappointment.&lt;/p&gt;
				&lt;hr&gt;
			
			
			&lt;p&gt;To cut a long story short—&lt;em&gt;although, I never seem to be able to tell short stories&lt;/em&gt;—the server host that I switched to at the end of last year employs deceptive pricing tactics.&lt;/p&gt;
&lt;p&gt;&lt;q&gt;Advertise price &lt;code&gt;A&lt;/code&gt;, but deliver price &lt;code&gt;A × 1.5&lt;/code&gt; after 3 months&lt;/q&gt; type of stuff.&lt;/p&gt;
&lt;p&gt;And if that wasn’t bad enough, I’ve also been having &lt;em&gt;horrendous&lt;/em&gt; issues with establishing and maintaining connections to the server. I have lost track of how many times this website’s scheduled and new commit CI workflows failed to sync newly-built files to my server, seemingly at random times and intervals. Checking the logs would reveal that CI (from multiple sources) wouldn’t even be &lt;em&gt;reaching&lt;/em&gt; my server.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Just accept the connection, damn it!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Enough was enough.&lt;/p&gt;
&lt;p&gt;I got a new server. It’s actually got even better specs for the same price as I was paying, and it’s hosted in Finland instead of the US too! I went scouring through the terminal history on my scorned server and checked through all the places that I was &lt;s&gt;working&lt;/s&gt; playing in and have migrated about 95% of everything! Only a few services and bits and bobs remain to be sifted through.&lt;/p&gt;
&lt;p&gt;It was also a fun learning experience, as I decided to use &lt;a href=&quot;https://www.docker.com/&quot;&gt;Docker&lt;/a&gt; this time around to segment the different pieces of software I use to power my &lt;a href=&quot;/projects/#self-hosted&quot;&gt;self-hosted stuff&lt;/a&gt;. I had everything running on &lt;q&gt;bare metal&lt;/q&gt; before and felt like I had a decent grasp of how (at least) my setup worked, but jumped at the opportunity to clean things up and learn something new.&lt;/p&gt;
&lt;p&gt;Because I spent so much time learning what I was &lt;em&gt;actually&lt;/em&gt; doing last migration, this one went really smoothly. Things were in places that I expected them to be, I had put effort into organising things, and I was able to reach &lt;em&gt;real&lt;/em&gt; deep and pull out memories of the migration from four months ago. I’m pretty confident I’ll be able to pull everything that would be missed from my scorned server before it’s decommissioned at the end of this month.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Cue a post from me in June lamenting tremendous data loss…&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;It was great to watch the sparklines on my uptime tracker shoot downward as I migrated my different websites. Across the board I saw response times to uptime pings decrease by ~10×! So far so good!&lt;/p&gt;
			
			
			
			
				&lt;hr&gt;
				&lt;p&gt;Thanks for subscribing and reading this post via RSS!&lt;/p&gt;
				&lt;p&gt;You can read &lt;a href=&quot;https://chrisburnell.com/article/new-year-new-server-2/&quot;&gt;this post&lt;/a&gt; and others &lt;a href=&quot;https://chrisburnell.com/posts/&quot;&gt;on my website&lt;/a&gt;.&lt;/p&gt;
		</content>
		
		
	</entry>
	<entry>
		<id>https://chrisburnell.com/article/unusual-rotations-2026-03/</id>
		<link href="https://chrisburnell.com/article/unusual-rotations-2026-03/" />
		<title>Unusual Rotations (March 2026): Baroque</title>
		<published>2026-03-01T22:23:11+00:00</published>
		<publishedFriendly>1 March 2026</publishedFriendly>
		<updated>2026-03-01T22:23:11+00:00</updated>
		<category term="article" scheme="https://chrisburnell.com/article/" label="Article" />
		<summary>Reflecting on February and looking ahead to March’s genre(s), Baroque.</summary>
		<content xml:lang="en" type="html">
			&lt;p&gt;Reflecting on February and looking ahead to March’s genre(s), Baroque.&lt;/p&gt;
				&lt;hr&gt;
			
			
			&lt;h2&gt;February: Vaporwave (in review)&lt;/h2&gt;
&lt;p&gt;Unfortunately, February was a difficult month, as my family and I went through the loss of a family member. As a result, I wasn’t in a particularly adventurous mindset, and I fell back on familiar and comforting music rather than explore and learn more about music.&lt;/p&gt;
&lt;p&gt;In a way, it’s probably a good thing that I picked &lt;em&gt;Vaporwave&lt;/em&gt; for this month because I already listen to the genre regularly, so I don’t have an overwhelming sense of having &lt;q&gt;failed&lt;/q&gt; this little challenge already at the second month of twelve.&lt;/p&gt;
&lt;p&gt;I will probably end up taking a few hours during March to go back and find new stuff to listen to throughout the year and educate myself further on the (frankly) complex history of this scene and its lineage.&lt;/p&gt;
&lt;p&gt;All that being said, &lt;a href=&quot;https://isitbandcampfriday.com/&quot;&gt;Bandcamp Friday&lt;/a&gt; took place on the 6&lt;sup&gt;th&lt;/sup&gt; of this month, so I took advantage of the event to purchase a number of fun Vaporwave albums. I haven’t had the opportunity to properly go through them all (yet), but the album &lt;a href=&quot;https://slowerpace.bandcamp.com/album/encardia-99&quot;&gt;ENCARDIA &#39;99&lt;/a&gt; by &lt;a href=&quot;https://slowerpace.bandcamp.com&quot;&gt;slowerpace &lt;span lang=&quot;ja&quot;&gt;音楽&lt;/span&gt;&lt;/a&gt; was a stand-out for me not just because it so strongly epitomises the Vaporwave sound, but also because it sounds as if someone translated the &lt;a href=&quot;https://archive.org/details/MSEncarta99&quot;&gt;Encarta 99 Encyclopedia&lt;/a&gt; directly into Vaporwave music, with amazing track titles like &lt;em&gt;LEARNING TIME&lt;/em&gt; and &lt;em&gt;FORCES OF NATURE&lt;/em&gt;.&lt;/p&gt;
&lt;h3&gt;Vaporwave, a short history&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;… at least, as told by me. I might get some stuff wrong, but I have no interest in having debates about it—feel free to send me corrections, if you want. I might read &#39;em.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;As far as I understand, like most genres of music, the &lt;q&gt;origin&lt;/q&gt; is both difficult and contentious to pinpoint. By and large, music changes through evolution, and to gaze at the timeframe of a given genre &lt;q&gt;birth&lt;/q&gt; would show a blurry, watercolour gradient of hues and tones. Sometimes you can maybe discern a shape in the wash of colours (these are the artists/albums/tracks that &lt;em&gt;really&lt;/em&gt; pushed the needle), but more often than not, there is no pinpoint that definitively marks the &lt;em&gt;start&lt;/em&gt; of a new genre of music.&lt;/p&gt;
&lt;p&gt;When it comes to Vaporwave, there is a &lt;strong&gt;strong&lt;/strong&gt; case to be made that its origins are too murky to call a start time on, but there’s an &lt;strong&gt;equally strong&lt;/strong&gt; case to be made for pinpointing &lt;q&gt;the start of Vaporwave as we know it&lt;/q&gt;:&lt;/p&gt;
&lt;p&gt;In the late 2000s, as the Internet matured (the Internet itself, not its denizens &lt;span&gt;lol&lt;/span&gt;) towards a new decade, there were a few genres like chillwave and &lt;abbr title=&quot;hypnogogic pop&quot;&gt;h-pop&lt;/abbr&gt; that were already using retro sounds, lo-fi aesthetics, and playing on nostalgia and a wistfulness for a half-remembered past.&lt;/p&gt;
&lt;p&gt;It was out of this primordial soup that Vaporwave grew. One of the key moments in this early time was in October of 2011 when &lt;a href=&quot;https://jamesferraroarchive.bandcamp.com&quot;&gt;James Ferraro&lt;/a&gt; released his album, &lt;a href=&quot;https://jamesferraroarchive.bandcamp.com/album/far-side-virtual&quot;&gt;Far Side Virtual&lt;/a&gt;. In it, James Ferraro masterfully blends ringtones, elevator music, and the &lt;q&gt;sound&lt;/q&gt; of corporate technology and techno-optimism from the 80s, 90s, and early 2000s to evoke an uncanny dreamscape of consumerism and overwhelming nostalgia (as a kid of the 90s, at least).&lt;/p&gt;
&lt;p&gt;Unbeknownst to the world, as the genre bubbled slowly to life in underground, indie circles, the artists that were pushing the envelope in this scene were creating a &lt;em&gt;supersaturated solution&lt;/em&gt;. All the elements were there, in suspension, just waiting for the right conditions.&lt;/p&gt;
&lt;p&gt;On the 9&lt;sup&gt;th&lt;/sup&gt; of December, 2011, &lt;a href=&quot;https://vektroid.bandcamp.com&quot;&gt;Vektroid (as Macintosh Plus)&lt;/a&gt; released &lt;a href=&quot;https://vektroid.bandcamp.com/album/floral-shoppe&quot;&gt;Floral Shoppe&lt;/a&gt;, crystallising &lt;em&gt;Vaporwave&lt;/em&gt; in these underground circles, and, in a way, helped to define the &lt;span lang=&quot;fr&quot;&gt;je ne sais quoi&lt;/span&gt; that makes up what Vaporwave &lt;em&gt;is&lt;/em&gt;: 80s/90s Japanese city pop, corporate &lt;abbr title=&quot;branded instrumental music, elevator music&quot;&gt;muzak&lt;/abbr&gt;, lo-fi and chopped and screwed samples, and subtly-critical or ironic &lt;q&gt;sounds&lt;/q&gt; of late stage capitalism.&lt;/p&gt;
&lt;p&gt;This is what gives Vaporwave its interesting but slightly-eerie quality: a world that was &lt;em&gt;sold&lt;/em&gt; to us, but refracted through the uncynical, wide-eyed wonder of childhood, before we knew enough to be critical, when everything just seemed so shiny and glossy and exciting and &lt;em&gt;new&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Of course, nothing happens in a vacuum, and the Internet at large quickly caught onto this new but nostalgic genre of music. Maybe the conditions were &lt;em&gt;just right&lt;/em&gt;, because it exploded in popularity throughout 2012, and I think this probably has a lot to do with how &lt;abbr title=&quot;how easily something can be made into a meme&quot;&gt;memeable&lt;/abbr&gt; the Vaporwave aesthetic was and that the nostalgia factor of Vaporwave is targeted at the demographic of people who are more likely to make memes about things in the first place.&lt;/p&gt;
&lt;p&gt;From there, genres and sub-genres and sub-sub-genres (that some would argue are genres unto themselves, thank you very much) would grow and blossom through their own scenes and wider circles that were developing.&lt;/p&gt;
&lt;p&gt;There are far too many genres that were born from this time and from very similar origins for me to even name, and it’s been interesting to see and hear how Vaporwave has evolved and splintered in the ~14 years since &lt;em&gt;Far Side Virtual&lt;/em&gt; and &lt;em&gt;Floral Shoppe&lt;/em&gt; came out.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;March: Baroque&lt;/h2&gt;
&lt;p&gt;I spent a lot of my youth learning piano, mostly baroque and classical pieces, and have been trying to pick it back up as an adult. My parents are both big fans of these genres, and I’ve been very familiar throughout my life (as a result), so this month, again, will probably be more about the depth of my exploration rather than breadth.&lt;/p&gt;
&lt;p&gt;I’d really love to get a stronger understanding of what defines these genres (usually lumped together under the umbrella of &lt;q&gt;classical music&lt;/q&gt;), what kind of influence history had on this music, and deep dive on some of my favourite aritsts/composers of these periods.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;I’m definitely going to be listening to and playing a lot of Beethoven this month!&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;As always, if you have any ideas, thoughts, recommendations for this month or future ones, hit me up!&lt;/p&gt;
&lt;blockquote&gt;
    &lt;p&gt;It&#39;s all right here in my noodle. The rest is just scribbling. Scribbling and bibbling, bibbling and scribbling.&lt;/p&gt;
    &lt;cite&gt;Mozart (from the movie, &lt;a href=&quot;https://app.trakt.tv/movies/amadeus-1984&quot; rel=&quot;external noopener&quot;&gt;Amadeus&lt;/a&gt;)&lt;/cite&gt;
&lt;/blockquote&gt;
			
			
			&lt;hr&gt;
&lt;div id=&quot;unusual-rotations&quot;&gt;
	&lt;p&gt;This post is part of &lt;a href=&quot;/tag/unusualrotations2026/&quot;&gt;Unusual Rotations 2026&lt;/a&gt;, a year-long project to immerse myself in 12 different musical genres that aren’t in my &lt;q&gt;usual&lt;/q&gt; rotation over the course of a year, one genre of focus per month, in an effort to expand the horizons of my musical tastes, learn more about how genres have evolved and grown over time, and expose myself to a rich history of music that I might have otherwise overlooked!&lt;/p&gt;
	&lt;ol&gt;
		&lt;li&gt;&lt;a href=&quot;/article/unusual-rotations-2026-01/&quot;&gt;January: Grunge&lt;/a&gt;&lt;/li&gt;
		&lt;li&gt;&lt;a href=&quot;/article/unusual-rotations-2026-02/&quot;&gt;February: Vaporwave&lt;/a&gt;&lt;/li&gt;
		&lt;li&gt;&lt;a href=&quot;/article/unusual-rotations-2026-03/&quot;&gt;March: Baroque&lt;/a&gt;&lt;/li&gt;
	&lt;/ol&gt;
	&lt;p&gt;More to come…&lt;/p&gt;
&lt;/div&gt;
			
				&lt;hr&gt;
				&lt;p&gt;Thanks for subscribing and reading this post via RSS!&lt;/p&gt;
				&lt;p&gt;You can read &lt;a href=&quot;https://chrisburnell.com/article/unusual-rotations-2026-03/&quot;&gt;this post&lt;/a&gt; and others &lt;a href=&quot;https://chrisburnell.com/posts/&quot;&gt;on my website&lt;/a&gt;.&lt;/p&gt;
		</content>
		
		
	</entry>
	<entry>
		<id>https://chrisburnell.com/article/unusual-rotations-2026-02/</id>
		<link href="https://chrisburnell.com/article/unusual-rotations-2026-02/" />
		<title>Unusual Rotations (February 2026): Vaporwave</title>
		<published>2026-02-01T21:50:47-04:00</published>
		<publishedFriendly>1 February 2026</publishedFriendly>
		<updated>2026-02-01T21:50:47-04:00</updated>
		<category term="article" scheme="https://chrisburnell.com/article/" label="Article" />
		<summary>Reflecting on listening to Grunge throughout January and planning February’s genre, Vaporwave.</summary>
		<content xml:lang="en" type="html">
			&lt;p&gt;Reflecting on listening to Grunge throughout January and planning February’s genre, Vaporwave.&lt;/p&gt;
				&lt;hr&gt;
			
			
			&lt;h2&gt;January: Grunge&lt;/h2&gt;
&lt;p&gt;On the whole, I really enjoyed exploring grunge more this month! There were some classic, quintessential tracks in there that I was already pretty intimately aware of, but it was nostalgic and it always rules to listen to great music you haven’t heard before.&lt;/p&gt;
&lt;p&gt;Although I &lt;em&gt;did&lt;/em&gt; listen to a smattering of &lt;span&gt;&lt;q&gt;big music’s&lt;/q&gt;&lt;/span&gt; &lt;em&gt;Grunge&lt;/em&gt; playlists (e.g. &lt;a href=&quot;https://music.youtube.com/playlist?list=RDCLAK5uy_kQDX7ciH8Pm8h48FWNLv5suF7U6abRhU4&quot;&gt;The Grunge Era on YouTube Music&lt;/a&gt;) and sampled through some &lt;q&gt;top 50 albums of all time&lt;/q&gt; lists (e.g. &lt;a href=&quot;https://www.rollingstone.com/music/music-lists/50-greatest-grunge-albums-798851/&quot;&gt;50 Greatest Grunge Albums by Rolling Stone&lt;/a&gt;), I think I let my own personal taste really take the reins this month. Because I ended up really enjoying so much of it, it was hard to stop listening to artists and albums once I put them on! Even though I don’t think it was necessarily a mistake, I’m going to try to cast a wider net going forward and absorb &lt;em&gt;more&lt;/em&gt; music, rather than latch onto the first things that prick up my ears.&lt;/p&gt;
&lt;p&gt;I want to guess that I listened to &lt;em&gt;at least&lt;/em&gt; 100 hours of Grunge across January, and I captured &lt;a href=&quot;/music/&quot;&gt;10 albums on my Music Reviews page&lt;/a&gt; that really stood out to me. I don’t know whether it’s a testament to just how good these bands are at grunge, or just the pervasive nature of &lt;em&gt;popularity&lt;/em&gt;, but most of them are the well-known heavy-hitters of the grunge world.&lt;/p&gt;
&lt;p&gt;In no particular order, here are a few of my favourites from this month:&lt;/p&gt;
&lt;div&gt;
&lt;article&gt;									&lt;a href=&quot;/music/jar-of-flies/&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;						&lt;picture&gt;								&lt;source type=&quot;image/webp&quot; srcset=&quot;/images/built/jar-of-flies.webp&quot;&gt;				&lt;img src=&quot;/images/built/jar-of-flies.jpeg&quot; alt=&quot;Art for Jar of Flies&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;			&lt;/picture&gt;							&lt;/a&gt;							&lt;h1&gt;							&lt;a href=&quot;/music/jar-of-flies/&quot;&gt;							Jar of Flies							&lt;/a&gt;					&lt;/h1&gt;									&lt;div&gt;									&lt;a href=&quot;https://www.last.fm/music/Alice+in+Chains&quot; rel=&quot;external noopener&quot;&gt;Alice in Chains&lt;/a&gt;													&lt;small&gt;(&lt;time datetime=&quot;1994-01-25T12:00:00+00:00&quot;&gt;1994&lt;/time&gt;)&lt;/small&gt;							&lt;/div&gt;							&lt;div&gt;									&lt;data value=&quot;4.25&quot; title=&quot;4.25 out of 5&quot;&gt; &lt;/data&gt;					&lt;strong&gt;4.25/5&lt;/strong&gt;					&lt;data value=&quot;0&quot; hidden=&quot;&quot;&gt;0&lt;/data&gt;					&lt;data value=&quot;5&quot; hidden=&quot;&quot;&gt;5&lt;/data&gt;							&lt;/div&gt;							&lt;time datetime=&quot;2026-01-16T09:22:57-04:00&quot; hidden=&quot;&quot;&gt;Friday, 16 January 2026&lt;/time&gt;										&lt;data value=&quot;https://album.link/s/4FCoFSNIFhK36holxHWCnc&quot; hidden=&quot;&quot;&gt;&lt;/data&gt;							&lt;data value=&quot;/music/&quot; hidden=&quot;&quot;&gt;music&lt;/data&gt;							&lt;data value=&quot;/music/jar-of-flies/&quot; hidden=&quot;&quot;&gt;&lt;/data&gt;				&lt;data value=&quot;/&quot; hidden=&quot;&quot;&gt;&lt;/data&gt;	&lt;/article&gt;&lt;article&gt;									&lt;a href=&quot;/music/in-utero/&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;						&lt;picture&gt;								&lt;source type=&quot;image/webp&quot; srcset=&quot;/images/built/in-utero.webp&quot;&gt;				&lt;img src=&quot;/images/built/in-utero.jpeg&quot; alt=&quot;Art for In Utero&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;			&lt;/picture&gt;							&lt;/a&gt;							&lt;h1&gt;							&lt;a href=&quot;/music/in-utero/&quot;&gt;							In Utero							&lt;/a&gt;					&lt;/h1&gt;									&lt;div&gt;									&lt;a href=&quot;https://www.last.fm/music/Nirvana&quot; rel=&quot;external noopener&quot;&gt;Nirvana&lt;/a&gt;													&lt;small&gt;(&lt;time datetime=&quot;1993-09-21T12:00:00+00:00&quot;&gt;1993&lt;/time&gt;)&lt;/small&gt;							&lt;/div&gt;							&lt;div&gt;									&lt;data value=&quot;4.25&quot; title=&quot;4.25 out of 5&quot;&gt; &lt;/data&gt;					&lt;strong&gt;4.25/5&lt;/strong&gt;					&lt;data value=&quot;0&quot; hidden=&quot;&quot;&gt;0&lt;/data&gt;					&lt;data value=&quot;5&quot; hidden=&quot;&quot;&gt;5&lt;/data&gt;							&lt;/div&gt;							&lt;time datetime=&quot;2026-01-16T20:40:57-04:00&quot; hidden=&quot;&quot;&gt;Friday, 16 January 2026&lt;/time&gt;										&lt;data value=&quot;https://album.link/s/7wOOA7l306K8HfBKfPoafr&quot; hidden=&quot;&quot;&gt;&lt;/data&gt;							&lt;data value=&quot;/music/&quot; hidden=&quot;&quot;&gt;music&lt;/data&gt;							&lt;data value=&quot;/music/in-utero/&quot; hidden=&quot;&quot;&gt;&lt;/data&gt;				&lt;data value=&quot;/&quot; hidden=&quot;&quot;&gt;&lt;/data&gt;	&lt;/article&gt;&lt;article&gt;									&lt;a href=&quot;/music/superunknown/&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;						&lt;picture&gt;								&lt;source type=&quot;image/webp&quot; srcset=&quot;/images/built/superunknown.webp&quot;&gt;				&lt;img src=&quot;/images/built/superunknown.jpeg&quot; alt=&quot;Art for Superunknown&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;			&lt;/picture&gt;							&lt;/a&gt;							&lt;h1&gt;							&lt;a href=&quot;/music/superunknown/&quot;&gt;							Superunknown							&lt;/a&gt;					&lt;/h1&gt;									&lt;div&gt;									&lt;a href=&quot;https://www.last.fm/music/Soundgarden&quot; rel=&quot;external noopener&quot;&gt;Soundgarden&lt;/a&gt;													&lt;small&gt;(&lt;time datetime=&quot;1994-03-08T12:00:00+00:00&quot;&gt;1994&lt;/time&gt;)&lt;/small&gt;							&lt;/div&gt;							&lt;div&gt;									&lt;data value=&quot;4&quot; title=&quot;4 out of 5&quot;&gt; &lt;/data&gt;					&lt;strong&gt;4/5&lt;/strong&gt;					&lt;data value=&quot;0&quot; hidden=&quot;&quot;&gt;0&lt;/data&gt;					&lt;data value=&quot;5&quot; hidden=&quot;&quot;&gt;5&lt;/data&gt;							&lt;/div&gt;							&lt;time datetime=&quot;2026-01-19T01:25:25-04:00&quot; hidden=&quot;&quot;&gt;Monday, 19 January 2026&lt;/time&gt;										&lt;data value=&quot;https://album.link/s/4SPc25NzdqOQt2IX4JDLUv&quot; hidden=&quot;&quot;&gt;&lt;/data&gt;							&lt;data value=&quot;/music/&quot; hidden=&quot;&quot;&gt;music&lt;/data&gt;							&lt;data value=&quot;/music/superunknown/&quot; hidden=&quot;&quot;&gt;&lt;/data&gt;				&lt;data value=&quot;/&quot; hidden=&quot;&quot;&gt;&lt;/data&gt;	&lt;/article&gt;
&lt;/div&gt;
&lt;p&gt;I also received some much-appreciated recommendations from a handful of lovely folks: &lt;a href=&quot;https://mastodon.social/@ellikerjames&quot;&gt;James Elliker&lt;/a&gt; recommended a whole &lt;a href=&quot;https://mastodon.social/@ellikerjames/115859716847721196&quot;&gt;personalised list of top 50 albums&lt;/a&gt; which was incredibly fruitful; &lt;a href=&quot;https://knowler.dev/&quot;&gt;Nathan Knowler&lt;/a&gt; recommended &lt;a href=&quot;https://bsky.app/profile/knowler.dev/post/3mbtyovsbc22v&quot;&gt;Superheaven’s album from last year&lt;/a&gt; which helped me settle into the driver’s seat of a nomad in the grunge world; and &lt;a href=&quot;https://adactio.com&quot;&gt;Jeremy Keith&lt;/a&gt; recommended I check out &lt;a href=&quot;https://adactio.com/notes/22346&quot;&gt;Babes in Toyland&lt;/a&gt; (two very sick music videos). Thanks all!&lt;/p&gt;
&lt;p&gt;I suspect I’m going to be juggling grunge through February as well, so don’t be surprised if a few albums slip onto my Music Reviews page over the next… well, probably over the course of this year!&lt;/p&gt;
&lt;p&gt;I’m going to take a quick moment to pay myself on the back for taking on this idea or project or whatever you want to call it. It has been really fun and I encourage you to join! Reject the Gregorians and start your own exploration into new and semi-familiar genres whenever you want!&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;On with February!&lt;/p&gt;
&lt;h2&gt;Vaporwave&lt;/h2&gt;
&lt;p&gt;To say that I’m new to this genre would be… a complete and utter lie! I got really into it probably around a decade ago, and I’ve been listening to it pretty steadily ever since.&lt;/p&gt;
&lt;p&gt;With that being said, my exploration of the genre has been &lt;em&gt;pretty&lt;/em&gt; limited. As with my month focusing on grunge, I’ve tended to be drawn to depth rather than breadth. I’m hoping by &lt;span&gt;cheating&lt;/span&gt; with a rather familiar genre this early on will help me get into exploring more. Pausing along the way is fine—pun ~not~ intended—but covering as much ground as I can over the course of this short month is my goal!&lt;/p&gt;
&lt;p&gt;I imagine I’ll be fleshing out my Music Review section with some old favourites that I never added for whatever reason, but I’m hoping to find myself immersed in that wonderful, euphoric feeling of discovering something new that electrifies you once again.&lt;/p&gt;
&lt;p&gt;’Til March!&lt;/p&gt;
			
			
			&lt;hr&gt;
&lt;div id=&quot;unusual-rotations&quot;&gt;
	&lt;p&gt;This post is part of &lt;a href=&quot;/tag/unusualrotations2026/&quot;&gt;Unusual Rotations 2026&lt;/a&gt;, a year-long project to immerse myself in 12 different musical genres that aren’t in my &lt;q&gt;usual&lt;/q&gt; rotation over the course of a year, one genre of focus per month, in an effort to expand the horizons of my musical tastes, learn more about how genres have evolved and grown over time, and expose myself to a rich history of music that I might have otherwise overlooked!&lt;/p&gt;
	&lt;ol&gt;
		&lt;li&gt;&lt;a href=&quot;/article/unusual-rotations-2026-01/&quot;&gt;January: Grunge&lt;/a&gt;&lt;/li&gt;
		&lt;li&gt;&lt;a href=&quot;/article/unusual-rotations-2026-02/&quot;&gt;February: Vaporwave&lt;/a&gt;&lt;/li&gt;
		&lt;li&gt;&lt;a href=&quot;/article/unusual-rotations-2026-03/&quot;&gt;March: Baroque&lt;/a&gt;&lt;/li&gt;
	&lt;/ol&gt;
	&lt;p&gt;More to come…&lt;/p&gt;
&lt;/div&gt;
			
				&lt;hr&gt;
				&lt;p&gt;Thanks for subscribing and reading this post via RSS!&lt;/p&gt;
				&lt;p&gt;You can read &lt;a href=&quot;https://chrisburnell.com/article/unusual-rotations-2026-02/&quot;&gt;this post&lt;/a&gt; and others &lt;a href=&quot;https://chrisburnell.com/posts/&quot;&gt;on my website&lt;/a&gt;.&lt;/p&gt;
		</content>
		
		
	</entry>
	<entry>
		<id>https://chrisburnell.com/note/sotb2026/</id>
		<link href="https://chrisburnell.com/note/sotb2026/" />
		<title>RSVP to State of the Browser 2026</title>
		<published>2026-01-28T16:17:20-04:00</published>
		<publishedFriendly>28 January 2026</publishedFriendly>
		<updated>2026-01-28T16:17:20-04:00</updated>
		<category term="note" scheme="https://chrisburnell.com/note/" label="Note" />
		<summary>Only one month until State of the Browser, and I will once again be organising, but this year I’ll be there in-person! 🥳 Sadly, I’ve had to do what I can rather remotely since 2023, so I’m really looking forward...</summary>
		<content xml:lang="en" type="html">
			
			
			
			&lt;p&gt;Only one month until State of the Browser, and I will once again be organising, but this year I’ll be there &lt;em&gt;in-person&lt;/em&gt;! 🥳&lt;/p&gt;
&lt;p&gt;Sadly, I’ve had to do what I can &lt;em&gt;rather remotely&lt;/em&gt; since 2023, so I’m really looking forward to some amazing talks, meeting old pals, and making new ones too.&lt;/p&gt;
&lt;p&gt;Less than 15 in-person tickets left, so I hope to see you there! (I’ll just take credit for any tickets claimed in the next hour or so 😘)&lt;/p&gt;
&lt;div&gt;
    &lt;a href=&quot;https://2026.stateofthebrowser.com/tickets/&quot;&gt;Get yourself a ticket!&lt;br&gt;2026.stateofthebrowser.com&lt;/a&gt;
&lt;/div&gt;
			
			
			
			
				&lt;hr&gt;
				&lt;p&gt;Thanks for subscribing and reading this post via RSS!&lt;/p&gt;
				&lt;p&gt;You can read &lt;a href=&quot;https://chrisburnell.com/note/sotb2026/&quot;&gt;this post&lt;/a&gt; and others &lt;a href=&quot;https://chrisburnell.com/posts/&quot;&gt;on my website&lt;/a&gt;.&lt;/p&gt;
		</content>
		
		<link rel="related" type="reply" href="https://2026.stateofthebrowser.com" />
	</entry>
	<entry>
		<id>https://chrisburnell.com/note/echofeed-buttons/</id>
		<link href="https://chrisburnell.com/note/echofeed-buttons/" />
		<title>EchoFeed Buttons</title>
		<published>2026-01-17T17:28:48-04:00</published>
		<publishedFriendly>17 January 2026</publishedFriendly>
		<updated>2026-01-17T17:28:48-04:00</updated>
		<category term="note" scheme="https://chrisburnell.com/note/" label="Note" />
		<summary>EchoFeed is an incredible web service by my pal, Robb Knight, that periodically checks your RSS feeds and can echo them out to various social media websites, and can even send Webmentions on your behalf! I proudly use EchoFeed on...</summary>
		<content xml:lang="en" type="html">
			
			
			
			&lt;p&gt;&lt;a href=&quot;https://echofeed.app&quot;&gt;EchoFeed&lt;/a&gt; is an incredible web service by my pal, &lt;a href=&quot;https://rknight.me&quot;&gt;Robb Knight&lt;/a&gt;, that periodically checks your RSS feeds and can echo them out to various social media websites, and can even send Webmentions on your behalf!&lt;/p&gt;
&lt;p&gt;I proudly use &lt;em&gt;EchoFeed&lt;/em&gt; on my website. I think the Pro subscription ($25 a year) is &lt;strong&gt;super&lt;/strong&gt; worth the cost, and in a time where it feels like we’re constantly drowning in low-effort, &lt;q&gt;let us monetise you&lt;/q&gt; &lt;strong&gt;slop&lt;/strong&gt;, it is abundantly evident that &lt;em&gt;EchoFeed&lt;/em&gt; has been built with attention and care by someone who cares deeply for the web and its denizens, and that’s worth supporting in my book.&lt;/p&gt;
&lt;p&gt;If the insane quality of Robb’s work doesn’t do enough to prove to you just how much faith the web community can put in his capable hands… heck, just try talkin’ to the guy. Leave some kindness for the rest of us, dude.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;All this is to say that I did a little riffing on the illustrious &lt;a href=&quot;https://www.andycarolan.com&quot;&gt;Andy Carolan’s&lt;/a&gt; work in creating &lt;em&gt;EchoFeed’s&lt;/em&gt; &lt;q&gt;Powered by EchoFeed&lt;/q&gt; SVG buttons and remade them in the style of my other pixel-art 88x31 buttons (shown at 4× size so you can see more detail):&lt;/p&gt;
&lt;div&gt;
	&lt;img src=&quot;/images/powered-by-echofeed-orange.gif&quot; alt=&quot;Powered by EchoFeed badge (orange)&quot; width=&quot;352&quot; height=&quot;124&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
	&lt;img src=&quot;/images/powered-by-echofeed-white.gif&quot; alt=&quot;Powered by EchoFeed badge (white)&quot; width=&quot;352&quot; height=&quot;124&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;/div&gt;
&lt;p&gt;I’ve since relinquished any and all ownership I have over the buttons I created, and you can grab ’em on &lt;a href=&quot;https://help.echofeed.app/buttons/&quot;&gt;the Buttons page&lt;/a&gt; from the &lt;a href=&quot;https://help.echofeed.app&quot;&gt;EchoFeed Help Pages&lt;/a&gt;.&lt;/p&gt;
			
			
			
			
				&lt;hr&gt;
				&lt;p&gt;Thanks for subscribing and reading this post via RSS!&lt;/p&gt;
				&lt;p&gt;You can read &lt;a href=&quot;https://chrisburnell.com/note/echofeed-buttons/&quot;&gt;this post&lt;/a&gt; and others &lt;a href=&quot;https://chrisburnell.com/posts/&quot;&gt;on my website&lt;/a&gt;.&lt;/p&gt;
		</content>
		
		
	</entry>
	<entry>
		<id>https://chrisburnell.com/article/unusual-rotations-2026-01/</id>
		<link href="https://chrisburnell.com/article/unusual-rotations-2026-01/" />
		<title>Unusual Rotations (January 2026): Grunge</title>
		<published>2026-01-06T19:57:37-04:00</published>
		<publishedFriendly>6 January 2026</publishedFriendly>
		<updated>2026-01-06T19:57:37-04:00</updated>
		<category term="article" scheme="https://chrisburnell.com/article/" label="Article" />
		<summary>I&#39;ve come up with a fun, new project to work on over the course of this year, and maybe you’ll join me!</summary>
		<content xml:lang="en" type="html">
			&lt;p&gt;I&#39;ve come up with a fun, new project to work on over the course of this year, and maybe you’ll join me!&lt;/p&gt;
				&lt;hr&gt;
			
			
			&lt;p&gt;Towards the end of 2025, I came up with this idea to expand the horizons of my musical tastes.&lt;/p&gt;
&lt;p&gt;I’ve always had quite a broad taste in music, I think. When I was growing up, I would tell people, &lt;q&gt;I listen to everything except jazz and country.&lt;/q&gt; I didn’t &lt;em&gt;really&lt;/em&gt; know what I was talking about, and definitely listen to all sorts of jazz and country music these days, so is it fair to say that I listen to everything?&lt;/p&gt;
&lt;p&gt;Maybe! But I’m going to try and find out.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Over the course of 2026, I’m going to be dedicate each month to a genre of music that’s either way outside the realm of what I normally listen to or is a genre that I’ve been tiptoeing around and deserves more attention.&lt;/p&gt;
&lt;p&gt;I’ll be documenting the journey here, on my website, as something that I can reflect back on and might inspire someone else to try something new too!&lt;/p&gt;
&lt;p&gt;At the start of each new month, I’ll reflect on the previous month, maybe highlight some stand-out artists/albums/songs, touch on some historical stuff that intrigued me, and whatever else I take away from my exploration into that genre.&lt;/p&gt;
&lt;p&gt;This whole project is really a venture into the unknown. To be frank, I haven’t even thought much about &lt;em&gt;how&lt;/em&gt; I want to structure my research and listening, let alone if this project is a reasonable thing to consider doing in the first place. How much can I really learn about something as deep and as rich as an entire genre of music in just a month?&lt;/p&gt;
&lt;p&gt;The reality is: I can’t. But I don’t expect to, either. Reaching some ethereal finish line is not the point. On the contrary, the journey is the whole point! Imagine the sadness that would come with knowing you had already heard everything. To know that the feelings of sating one’s curiosity is behind me and that no surprises await would be a terrible condemnation to endure.&lt;/p&gt;
&lt;p&gt;I’m more concerned with having enough time each month to juggle spending time towards the genre of the month and continuing to listen to the previous genres I’ve explored that I’ll inevitably become obsessed with!&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;I know &lt;em&gt;way&lt;/em&gt; too many cool people with vast, sprawling, wildly different tastes in music, so I’m really looking forward to &lt;s&gt;hassling&lt;/s&gt; hearing from folks about their favourite genres. Educate me! What are &lt;em&gt;your&lt;/em&gt; favourite artists/albums/songs? I’ll check them out! Shoot me a message with a totally un-asked for braindump of the &lt;q&gt;lore&lt;/q&gt; of your favourite genre! Cause &lt;strong&gt;I’m askin’&lt;/strong&gt;, and I’ll read &lt;strong&gt;every single word&lt;/strong&gt; of it. What are the genre’s big hits? Hidden gems? Do &lt;em&gt;you&lt;/em&gt; make this kind of music? &lt;strong&gt;I WILL LISTEN TO IT.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;And please, by all means, join me! Make your own list of 12 genres that you want to dive into or maybe the ones I’ll pick interest you already. But make sure you let me know! I’d love to go on this journey with you.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;So, without further ado, &lt;em&gt;my&lt;/em&gt; genre for January 2026 is…&lt;/p&gt;
&lt;h2&gt;Grunge&lt;/h2&gt;
&lt;p&gt;Growing up in the 90s and 2000s, I’m a bit too young to have really caught the peak of the grunge wave (I think?). Regardless, my friend group in school was sort of tangentially into grunge, so this genre isn’t a particularly new sound for me.&lt;/p&gt;
&lt;p&gt;However, when I moved to Singapore in 2023, I had the opportunity to spend a lot more (in-person) time with my good friend, Raga, who’s a metalhead and huge fan of grunge. He (re-)opened my eyes to a lot of grunge that I hadn’t deliberately listened to in at least 15 years! Thinking about it now, I don’t really know why I let it fade out of rotation. I’ve been giving hip hop and drum and bass too much space! They’re getting set on the backburner for, at least, January, so I can concentrate on immersing myself in &lt;strong&gt;grunge&lt;/strong&gt;!&lt;/p&gt;
&lt;p&gt;Again, please do reach out if you join me on this journey &lt;strong&gt;and&lt;/strong&gt;/&lt;strong&gt;or&lt;/strong&gt; if you’re a fan of grunge yourself. Send me your recs!&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Apologies to the neighbors.&lt;/em&gt;&lt;/p&gt;
			
			
			&lt;hr&gt;
&lt;div id=&quot;unusual-rotations&quot;&gt;
	&lt;p&gt;This post is part of &lt;a href=&quot;/tag/unusualrotations2026/&quot;&gt;Unusual Rotations 2026&lt;/a&gt;, a year-long project to immerse myself in 12 different musical genres that aren’t in my &lt;q&gt;usual&lt;/q&gt; rotation over the course of a year, one genre of focus per month, in an effort to expand the horizons of my musical tastes, learn more about how genres have evolved and grown over time, and expose myself to a rich history of music that I might have otherwise overlooked!&lt;/p&gt;
	&lt;ol&gt;
		&lt;li&gt;&lt;a href=&quot;/article/unusual-rotations-2026-01/&quot;&gt;January: Grunge&lt;/a&gt;&lt;/li&gt;
		&lt;li&gt;&lt;a href=&quot;/article/unusual-rotations-2026-02/&quot;&gt;February: Vaporwave&lt;/a&gt;&lt;/li&gt;
		&lt;li&gt;&lt;a href=&quot;/article/unusual-rotations-2026-03/&quot;&gt;March: Baroque&lt;/a&gt;&lt;/li&gt;
	&lt;/ol&gt;
	&lt;p&gt;More to come…&lt;/p&gt;
&lt;/div&gt;
			
				&lt;hr&gt;
				&lt;p&gt;Thanks for subscribing and reading this post via RSS!&lt;/p&gt;
				&lt;p&gt;You can read &lt;a href=&quot;https://chrisburnell.com/article/unusual-rotations-2026-01/&quot;&gt;this post&lt;/a&gt; and others &lt;a href=&quot;https://chrisburnell.com/posts/&quot;&gt;on my website&lt;/a&gt;.&lt;/p&gt;
		</content>
		
		
	</entry>
	<entry>
		<id>https://chrisburnell.com/article/new-year-new-server/</id>
		<link href="https://chrisburnell.com/article/new-year-new-server/" />
		<title>New Year, New Server</title>
		<published>2026-01-06T15:37:40-04:00</published>
		<publishedFriendly>6 January 2026</publishedFriendly>
		<updated>2026-01-06T15:37:40-04:00</updated>
		<category term="article" scheme="https://chrisburnell.com/article/" label="Article" />
		<summary>Over the last two weeks, I’ve been toiling away on the ugly underside of this website: the server. It powers and hosts all the data for this and my many other websites, and I thought as we say goodbye to 2025 and welcome in 2026, I’d reminisce on the history of my website.</summary>
		<content xml:lang="en" type="html">
			&lt;p&gt;Over the last two weeks, I’ve been toiling away on the ugly underside of this website: the server. It powers and hosts all the data for this and my many other websites, and I thought as we say goodbye to 2025 and welcome in 2026, I’d reminisce on the history of my website.&lt;/p&gt;
				&lt;hr&gt;
			
			
			&lt;h2&gt;In the beginning…&lt;/h2&gt;
&lt;p&gt;When I first started publishing on my own domain on the web (under my own name, at least, I’ve been &lt;em&gt;webmastering&lt;/em&gt; since 2005), I really wanted a WordPress blog, which I thought was the &lt;strong&gt;coolest&lt;/strong&gt; thing you could possibly own in 2008. (Things have changed, to say the least!) Somehow, I managed to get one running on a shared hosting provider, and suddenly it felt like a part of &lt;em&gt;me&lt;/em&gt; was on the Internet now, or at least some kind of extension of myself.&lt;/p&gt;
&lt;p&gt;That shared hosting dashboard (&lt;em&gt;cPanel&lt;/em&gt;) kept my handful of websites going from 2008 until 2014, a &lt;strong&gt;solid&lt;/strong&gt; 6 years of service, but it didn’t come without friction.&lt;/p&gt;
&lt;p&gt;Unfortunately, the style of the dashboard that is/was common with these types of sharing hosting services feels like it was constructed to be some impossibly-capable tool-belt that extends ad infinitum, but is thrust around you in the vain hope that the little &lt;q&gt;One size fits all&lt;/q&gt; label will answer your many questions and provide some level of comfort amidst your utter confusion and horror.&lt;/p&gt;
&lt;p&gt;I’m sure this is &lt;em&gt;exactly&lt;/em&gt; what other folks expect and want of their web hosting dashboard, but this isn’t something that I can effectively learn how to use. I just end up feeling like my grasp is slipping on the surface of a very large, very well-maintained planet. No shade if this stuff is your jam!&lt;/p&gt;
&lt;p&gt;Somehow (this isn’t the last time I’ll use that word), I managed to work through this inadequate/over-adequate tooling for years and years, but my skills as a developer eventually caught up with my needs of control over the backend of my websites. I remember starting to &lt;em&gt;really&lt;/em&gt; detest having to wade through all the pre-installed &lt;strong&gt;junk&lt;/strong&gt; in my shared hosting dashboard, but not being nearly interested enough in the dashboard itself to do more than some basic modifications to the UI.&lt;/p&gt;
&lt;h2&gt;Taking the reins&lt;/h2&gt;
&lt;p&gt;By 2014, my role at work and my personal projects had already begun demanding some actual know-how with the command line, and as my familiarity with Node.js and bash grew, it became obvious that I wasn’t destined to chain myself to my shared hosting dashboard forever.&lt;/p&gt;
&lt;p&gt;So I decided do something about it.&lt;/p&gt;
&lt;p&gt;As someone who &lt;s&gt;spent&lt;/s&gt; still spends &lt;em&gt;tonnes&lt;/em&gt; of my personal time in this &lt;q&gt;space&lt;/q&gt;, I have come to realise that the moments where I really enjoy developing and learning are when the virtual space I’m in feels &lt;strong&gt;comfy and familiar&lt;/strong&gt;. &lt;q&gt;Flow state&lt;/q&gt; and all that jazz.&lt;/p&gt;
&lt;p&gt;To me, this is like the difference between following maps and following landmarks and paths your feet have tread a hundred times before. Both methods get you to the same place, but I want &lt;em&gt;my&lt;/em&gt; steps to be &lt;em&gt;my own&lt;/em&gt;, not the steps that have been determined for me by someone or something else.&lt;/p&gt;
&lt;p&gt;This probably comes from a ~decade of obsessing over the minute details of my website to the point of exhaustion, moving on to modifying and customising my Android phone to a ridiculous degree until it feels &lt;em&gt;just right&lt;/em&gt;, and looking for something else to dive into and learn about and absorb myself in and transform it into something that feels homey and familiar only to find my itch unscratched.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;So it’s been nearly &lt;em&gt;twelve&lt;/em&gt; years (4,348 days, to be exact) since I first installed the &lt;abbr title=&quot;virtual private server, an isolated, virtual environment on a physical server&quot;&gt;VPS&lt;/abbr&gt; that succeeded the shared hosting that was the home for &lt;code&gt;chrisburnell.com&lt;/code&gt; et al. for so many years.&lt;/p&gt;
&lt;p&gt;This came with a &lt;em&gt;steep&lt;/em&gt; learning curve, but each step forward brought with it deep lungfuls of fresh air. I got to learn first-hand where the gaps in my knowledge and skills were from years of depending on janky web UIs to manage databases and oversee processes.&lt;/p&gt;
&lt;p&gt;I’m super fortunate and grateful to be friends with great people like &lt;a href=&quot;https://www.linkedin.com/in/hank-woods/&quot;&gt;Hank Woods&lt;/a&gt; and &lt;a href=&quot;https://uk.linkedin.com/in/callum-loh-717b66224&quot;&gt;Callum Loh&lt;/a&gt; who guided and taught me &lt;em&gt;boatloads&lt;/em&gt; throughout many of those early, hands-on hours with my new server.&lt;/p&gt;
&lt;p&gt;That’s not to say that I didn’t go off and make a shambles of everything when I wasn’t supervised: configuration files in the &lt;code&gt;tmp&lt;/code&gt; directory, SSHing directly in as root with a far-too-trivial password, binaries where configuration is supposed to go… Name a mistake and I probably made it.&lt;/p&gt;
&lt;p&gt;But, somehow (again?), I &lt;em&gt;MacGyvered&lt;/em&gt; together a mountain out of a molehill, and was finally at a level of control over my website (front and back) that felt like I could work with. There were times it outpaced me or felt like I couldn’t get it to do what I wanted, but without that veil between me and the systems I was trying to interact with, I was able to explore and learn so much, achieve new things that weren’t possible for me before (let alone conceive of), and again, make the environment that I was spending &lt;em&gt;so much time in&lt;/em&gt; feel like a &lt;em&gt;home&lt;/em&gt;.&lt;/p&gt;
&lt;h2&gt;Clueless&lt;/h2&gt;
&lt;p&gt;Over some quiet evenings this last December, a old, familiar feeling started to creep back into my mind:&lt;/p&gt;
&lt;p&gt;Sure, I’m not making the &lt;em&gt;total&lt;/em&gt; newbie mistakes anymore, or at least not that often. I can edit and find the stuff that makes my server run. I can handle nginx configuration, I have &lt;em&gt;enough&lt;/em&gt; of a handle on firewall rules and reverse proxies exposing services to the web, but if anything goes wrong…&lt;/p&gt;
&lt;p&gt;Yeah, I ended up creating a grotesque sibling for the shared hosting dashboard that I hated so much, but this behemoth was of my own making… &lt;em&gt;*facepalm*&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The difference with &lt;em&gt;this&lt;/em&gt; behemoth was that when things went wrong, there wasn’t a customer support chat for me to jump into and get things resolved for me. The responsibility was mine and mine alone, and I certainly threw down the gauntlet at my own feet.&lt;/p&gt;
&lt;p&gt;I must have been feeling especially self-sabotaging when I decided I wanted to add hosting my own Mastodon instance to my skillset in 2022. It’s a real testament to their documentation that &lt;em&gt;I&lt;/em&gt; was able to get things running, but I figured it out and have kept it ticking along ever since, but this is where my old VPS’s age really began to show.&lt;/p&gt;
&lt;p&gt;After over a decade of mucking around and leaving grease stains on all the server’s good furniture, the toll on the server was showing. Running 3+ database management systems, pumping the traffic of 5+ domains and dozens of subdomain projects through shoddily-configured web servers, and an &lt;q&gt;interesting&lt;/q&gt; mishmash of who-knows how many iterations of different projects in different languages written at very different stages of my career was enough, but supporting a Mastodon instance on top of that meant that the server’s memory was running like a lump of molten lava pretty much nonstop.&lt;/p&gt;
&lt;p&gt;Not good.&lt;/p&gt;
&lt;h2&gt;It’s alive!&lt;/h2&gt;
&lt;p&gt;So I began researching. Maybe I could just upgrade the server’s capabilities and things would be fine?&lt;/p&gt;
&lt;p&gt;But I couldn’t shake that feeling of how great it would feel to start fresh and take the time to actually learn what I’m doing to get things right from the start. At the very least, even if I got things wrong, my attitude this time would be to understand and learn, so I figured I would be capable of understanding &lt;em&gt;why&lt;/em&gt; it was wrong.&lt;/p&gt;
&lt;p&gt;Rather than chasing search queries about my issue for hours, looking for the silver bullet to copy and paste that will &lt;q&gt;just&lt;/q&gt; fix things, my plan was to actually read logs and man pages. After all, associating patterns of strings in bash scripts and postgres commands with certain actions is only really one step removed from associating the &lt;em&gt;phpMyAdmin&lt;/em&gt; logo with the way that I used to interact with databases.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;(Again, to each their own! I’d rather have you on the web with me than not, and I don’t hold any judgement towards how you get comfy and comfortable in your web space. This article’s about me, though!)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;And that solidified it for me. This was happening.&lt;/p&gt;
&lt;p&gt;I began shopping around and discovered I could get a server for a comparable, competitive price that had twice the CPUs, memory, storage, and transfer capabilities of my old server. &lt;strong&gt;Lock it in.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;So that’s what I’ve been up to for the last ~2 weeks: slowly trickling stuff across from my old VPS to the new. I started with the straightforward static sites and then moved onto the more complex ones, taking things really slowly to make sure I understood every line of config I authored and looking for ways to DRY things out just enough to build a sweet library of my own reusable stuff while not making it impossible to revisit this stuff in a year’s time and have to re-learn how hundreds of moving pieces fit together. A careful and measured approach.&lt;/p&gt;
&lt;p&gt;And I’m &lt;em&gt;very&lt;/em&gt; pleased to say that my old server is just about ready to be decommissioned! Everything important (read: that I can remember exists) has been ported over and the new server looks to be in great health after a week of stability.&lt;/p&gt;
&lt;p&gt;I still need to go through things on the old server with a fine-toothed comb &lt;em&gt;once&lt;/em&gt; more. There’s so many old and unimportant one-off experiments that I’d like to keep just for the heck of it, but there’s also a lot of files that I can only assume were accidentally saved in the wrong location or moved haphazardly or for some other indecipherable reason. It’s funny, though, to think how much I probably confused myself in 2014!&lt;/p&gt;
&lt;p&gt;I was wandering around so naïvely back then, but it’s a fun and nostalgic experience to retrace the first steps I took on fresh earth twelve years ago; to see where my feet took me, what piqued my interest, what parts of the landscape I strayed from out of fear, what parts I let nature take its course out of deference, where I built lookouts and small homes to rest and read in.&lt;/p&gt;
&lt;p&gt;One day soon will be the last day I set foot in that space that I know so well, but I haven’t left the experience empty-handed.&lt;/p&gt;
&lt;p&gt;Now, I have a map. A map that has all my secret little paths, notes, warnings about &lt;q&gt;here be dragons&lt;/q&gt;, all the stuff, all the character that isn’t on the pages of the maps you can get off a shelf. It’s not a perfect map by any means—the corners are dog-earred; words, written in pen, have been scratched out; and, later on, eraser marks betray hidden mistakes in charcoal—this map is far from finished.&lt;/p&gt;
&lt;p&gt;But this map that I made is my own.&lt;/p&gt;
			
			
			
			
				&lt;hr&gt;
				&lt;p&gt;Thanks for subscribing and reading this post via RSS!&lt;/p&gt;
				&lt;p&gt;You can read &lt;a href=&quot;https://chrisburnell.com/article/new-year-new-server/&quot;&gt;this post&lt;/a&gt; and others &lt;a href=&quot;https://chrisburnell.com/posts/&quot;&gt;on my website&lt;/a&gt;.&lt;/p&gt;
		</content>
		
		
	</entry>
	<entry>
		<id>https://chrisburnell.com/article/color-scheming/</id>
		<link href="https://chrisburnell.com/article/color-scheming/" />
		<title>Color Scheming</title>
		<published>2025-10-24T12:01:00-03:00</published>
		<publishedFriendly>24 October 2025</publishedFriendly>
		<updated>2025-10-24T12:01:00-03:00</updated>
		<category term="article" scheme="https://chrisburnell.com/article/" label="Article" />
		<summary>It’s been about a year and a half since I changed how colour schemes (i.e., light and dark themes) work on my website, and after reviewing and refactoring things again recently, I thought I would share how everything ties together and allows for different levels of control over colour schemes.</summary>
		<content xml:lang="en" type="html">
			&lt;p&gt;It’s been about a year and a half since I changed how colour schemes (i.e., light and dark themes) work on my website, and after reviewing and refactoring things again recently, I thought I would share how everything ties together and allows for different levels of control over colour schemes.&lt;/p&gt;
				&lt;hr&gt;
			
			
			&lt;p&gt;In this article, I’ll cover &lt;em&gt;how&lt;/em&gt; to implement a colour-scheming system, gain some understanding of how to control the colour scheme in different ways, and write some CSS that leverages the strengths of the language to build a hierarchy of colour scheme controls.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Note: We’re going to build this hierarchy of controls out of order, but we’ll wrap everything back around to honour its order by the end.&lt;/em&gt;&lt;/p&gt;
&lt;ol&gt;
    &lt;li&gt;&lt;a href=&quot;#part-2&quot;&gt;Element-level Override&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;#part-2&quot;&gt;Page-level Override&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;#part-3&quot;&gt;Site-level Preference&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;#part-1&quot;&gt;&lt;abbr title=&quot;Operating System&quot;&gt;OS&lt;/abbr&gt;/Browser-level Preference&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;hr&gt;
&lt;h2 id=&quot;build&quot;&gt;Let’s build&lt;/h2&gt;
&lt;h3 id=&quot;part-1&quot;&gt;4. &lt;abbr title=&quot;Operating System&quot;&gt;OS&lt;/abbr&gt;/Browser-level Preference&lt;/h3&gt;
&lt;ol aria-hidden=&quot;true&quot;&gt;
    &lt;li&gt;&lt;a href=&quot;#part-2&quot;&gt;Element-level Override&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;#part-2&quot;&gt;Page-level Override&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;#part-3&quot;&gt;Site-level Preference&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;OS/Browser-level Preference&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;To begin with, we should make sure to include this &lt;code&gt;meta&lt;/code&gt; tag in the &lt;code&gt;head&lt;/code&gt; of our page(s). This &lt;q&gt;indicates a suggested [colour] scheme that user agents should use for a page&lt;/q&gt; (&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/meta/name/color-scheme&quot;&gt;MDN&lt;/a&gt;).&lt;/p&gt;
&lt;pre data-language=&quot;html&quot; data-language-friendly=&quot;HTML&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;meta&lt;/span&gt; &lt;span&gt;name&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;color-scheme&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span&gt;content&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;light dark&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, we need to indicate in our CSS what colour schemes we want to have available, and we can apply this to the document root (&lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt; in &lt;em&gt;most&lt;/em&gt; cases) and the cascade will apply the appropriate colour scheme to the entire document:&lt;/p&gt;
&lt;pre data-language=&quot;css&quot; data-language-friendly=&quot;CSS&quot;&gt;&lt;code&gt;&lt;span&gt;:root&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;
	&lt;span&gt;color-scheme&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; light dark&lt;span&gt;;&lt;/span&gt;
&lt;span&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This, alone, allows us to write CSS that responds dynamically to the &lt;em&gt;OS/Browser-level Preference&lt;/em&gt; for a light or dark colour scheme. Where colours are concerned in our CSS, we can use the &lt;code&gt;light-dark()&lt;/code&gt; function to hook into which of the two colour schemes is active and provide an appropriate colour for both conditions.&lt;/p&gt;
&lt;p&gt;Also, because we’ve written &lt;code&gt;light dark&lt;/code&gt;, we have indicated that where an &lt;em&gt;OS/Browser-level Preference&lt;/em&gt; does not exist, we should default to the first value. This means that in the absense of all preferences and overrides, the default colour scheme will be the &lt;code&gt;light&lt;/code&gt; one. If we had instead written &lt;code&gt;dark light&lt;/code&gt;, the &lt;code&gt;dark&lt;/code&gt; colour scheme would be the default.&lt;/p&gt;
&lt;h3 id=&quot;part-2&quot;&gt;1 &amp;amp; 2. Element-level &amp;amp; Page-level Overrides&lt;/h3&gt;
&lt;ol aria-hidden=&quot;true&quot;&gt;
    &lt;li&gt;Element-level Override&lt;/li&gt;
    &lt;li&gt;Page-level Override&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;#part-3&quot;&gt;Site-level Preference&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;#part-1&quot;&gt;OS/Browser-level Preference&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Next, let’s take care of Page-level and Element-level colour scheme overrides. The way I’ve chosen to do this is by creating classes for each colour scheme that can be appled to any element and use a specific colour scheme for itself and its children:&lt;/p&gt;
&lt;pre data-language=&quot;css&quot; data-language-friendly=&quot;CSS&quot;&gt;&lt;code&gt;&lt;span&gt;.light&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;
	&lt;span&gt;color-scheme&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; light&lt;span&gt;;&lt;/span&gt;
&lt;span&gt;}&lt;/span&gt;
&lt;span&gt;.dark&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;
	&lt;span&gt;color-scheme&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; dark&lt;span&gt;;&lt;/span&gt;
&lt;span&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can apply these classes to the &lt;code&gt;html&lt;/code&gt; element to force an entire page to use a specific colour scheme:&lt;/p&gt;
&lt;pre data-language=&quot;html&quot; data-language-friendly=&quot;HTML&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;html&lt;/span&gt; &lt;span&gt;class&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;dark&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Similarly, we might want a particular section of a page to always use a specific colour scheme, so we can apply the class to a wrapper element and allow the &lt;code&gt;color-scheme&lt;/code&gt; value to cascade to its children:&lt;/p&gt;
&lt;pre data-language=&quot;html&quot; data-language-friendly=&quot;HTML&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;main&lt;/span&gt; &lt;span&gt;class&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;light&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
	...
&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;main&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;part-3&quot;&gt;3. Site-level Preference&lt;/h3&gt;
&lt;ol aria-hidden=&quot;true&quot;&gt;
    &lt;li&gt;&lt;a href=&quot;#part-2&quot;&gt;Element-level Override&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;#part-2&quot;&gt;Page-level Override&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;Site-level Preference&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;#part-1&quot;&gt;OS/Browser-level Preference&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The last part is unfortunately the most complex, so let’s dive in and flesh out how we can allow website visitors to override their &lt;em&gt;OS/Browser-level Preference&lt;/em&gt; &lt;em&gt;without&lt;/em&gt; stepping on top of our &lt;em&gt;Page-level/Element-level Overrides&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;To start with, I’m using &lt;a href=&quot;https://darn.es/&quot;&gt;David Darnes’&lt;/a&gt; web component, &lt;a href=&quot;https://github.com/daviddarnes/storage-form/&quot;&gt;&lt;code&gt;&amp;lt;storage-form&amp;gt;&lt;/code&gt;&lt;/a&gt;, to capture the &lt;em&gt;Site-level Preference&lt;/em&gt;. This works by wrapping a &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; that contains a &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt;. The &lt;code&gt;&amp;lt;storage-form&amp;gt;&lt;/code&gt; web component listens for selection changes on the &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt; value and saves those changes to local storage.&lt;/p&gt;
&lt;pre data-language=&quot;html&quot; data-language-friendly=&quot;HTML&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;storage-form&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
	&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;form&lt;/span&gt; &lt;span&gt;autocomplete&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;off&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
		&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;select&lt;/span&gt; &lt;span&gt;name&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;color-scheme&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
			&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;optgroup&lt;/span&gt; &lt;span&gt;label&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;Select a colour scheme&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
				&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;option&lt;/span&gt; &lt;span&gt;value&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span&gt;selected&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;OS Default&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;option&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
				&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;option&lt;/span&gt; &lt;span&gt;value&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;light&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;Light&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;option&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
				&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;option&lt;/span&gt; &lt;span&gt;value&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;dark&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;Dark&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;option&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
			&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;optgroup&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
		&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;select&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
	&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;form&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;storage-form&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;From then on, whenever the web component’s JavaScript finds the same &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt; (matched by &lt;code&gt;name&lt;/code&gt; attribute) on subsequent page loads or on other pages, it will modify the selected &lt;code&gt;&amp;lt;option&amp;gt;&lt;/code&gt; in the DOM to match the local storage value.&lt;/p&gt;
&lt;p&gt;This takes care of persistence of &lt;em&gt;Site-level Preferences&lt;/em&gt;. I cannot overstate how amazingly powerful this little web component is! I use it every time I need to manage some sort of &lt;q&gt;state&lt;/q&gt; on my website.&lt;/p&gt;
&lt;p&gt;What’s great about this approach is that we can leave &lt;code&gt;&amp;lt;storage-form&amp;gt;&lt;/code&gt; alone from here and continue using CSS to control and manage how we respond to different colour schemes. We can build selectors that match when a particular &lt;code&gt;&amp;lt;option&amp;gt;&lt;/code&gt; is selected (i.e., &lt;code&gt;checked&lt;/code&gt;), and using the &lt;code&gt;:has()&lt;/code&gt; selector, we can then apply a specific colour scheme based on that selection:&lt;/p&gt;
&lt;pre data-language=&quot;css&quot; data-language-friendly=&quot;CSS&quot;&gt;&lt;code&gt;&lt;span&gt;:root:has([name=&quot;color-scheme&quot;] [value=&quot;light&quot;]:checked)&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;
	&lt;span&gt;color-scheme&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; light&lt;span&gt;;&lt;/span&gt;
&lt;span&gt;}&lt;/span&gt;
&lt;span&gt;:root:has([name=&quot;color-scheme&quot;] [value=&quot;dark&quot;]:checked)&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;
	&lt;span&gt;color-scheme&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; dark&lt;span&gt;;&lt;/span&gt;
&lt;span&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So when the &lt;code&gt;light&lt;/code&gt; option has been selected, we are applying the light colour scheme to the document root, and vice versa for the &lt;code&gt;dark&lt;/code&gt; option.&lt;/p&gt;
&lt;h3 id=&quot;part-4&quot;&gt;A nod to performance&lt;/h3&gt;
&lt;p&gt;Before we can put everything together, there are some problems we need to address.&lt;/p&gt;
&lt;p&gt;Firstly, we need to think about &lt;em&gt;how&lt;/em&gt; we initiate the JavaScript that defines the &lt;code&gt;&amp;lt;storage-form&amp;gt;&lt;/code&gt; web component because until it is loaded, the &lt;em&gt;Site-level Preference&lt;/em&gt; that exists in local storage won’t have been mirrored onto the &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt; on the page. At present, we’re &lt;em&gt;relying&lt;/em&gt; on the value of the &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt; to apply a &lt;em&gt;Site-level Preference&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;This gives us some options and a choice to make.&lt;/p&gt;
&lt;p&gt;We &lt;em&gt;could&lt;/em&gt; instantiate the web component in the &lt;code&gt;head&lt;/code&gt; of our page(s) and be sure that by the time the HTML parser reaches our &lt;code&gt;&amp;lt;storage-form&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt;, the browser will know what to do with it, and the correct colour scheme will be applied; however, this will block the parsing of HTML while the entirety of the web component’s JavaScript is being processed.&lt;/p&gt;
&lt;p&gt;We might think to defer the script or run it asynchronously, but no matter how we slice it—loading the JavaScript before the HTML or vice versa—we’re going to run into an unsightly problem. The effect will be more pronounced on slower devices and/or slower connections, but there &lt;em&gt;will&lt;/em&gt; be a period of time where some portion of HTML has been parsed and rendered &lt;em&gt;before&lt;/em&gt; the locally-stored colour scheme value has been applied to the HTML. Depending on how different in brightness the light and dark colour schemes are, this can result in an intense shift from light to dark or dark to light before and after the locally-stored colour scheme value is applied.&lt;/p&gt;
&lt;p&gt;What &lt;em&gt;I’ve&lt;/em&gt; done to solve this is to defer the loading of the JavaScript that initiates the web component and instead include a small snippet of (minified) JavaScript in the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; that &lt;em&gt;is technically&lt;/em&gt; parser-blocking, but will reliably run (or fail) very quickly.&lt;/p&gt;
&lt;pre data-language=&quot;javascript&quot; data-language-friendly=&quot;JavaScript&quot;&gt;&lt;code&gt;&lt;span&gt;const&lt;/span&gt; &lt;span&gt;COLOR_SCHEME&lt;/span&gt; &lt;span&gt;=&lt;/span&gt; localStorage&lt;span&gt;.&lt;/span&gt;&lt;span&gt;getItem&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;color-scheme&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;
&lt;span&gt;if&lt;/span&gt; &lt;span&gt;(&lt;/span&gt;&lt;span&gt;COLOR_SCHEME&lt;/span&gt;&lt;span&gt;)&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;
	document&lt;span&gt;.&lt;/span&gt;documentElement&lt;span&gt;.&lt;/span&gt;&lt;span&gt;setAttribute&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;data-color-scheme&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;COLOR_SCHEME&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;
&lt;span&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;p&gt;&lt;code&gt;window&lt;span&gt;.&lt;/span&gt;&lt;span&gt;addEventListener&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;load&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt; &lt;span&gt;=&amp;gt;&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;&lt;br&gt;
document&lt;span&gt;.&lt;/span&gt;documentElement&lt;span&gt;.&lt;/span&gt;&lt;span&gt;removeAttribute&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;data-color-scheme&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;br&gt;
&lt;span&gt;}&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;What this does is independently check the browser’s local storage for the same value that gets set by the &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt; in the &lt;code&gt;&amp;lt;storage-form&amp;gt;&lt;/code&gt; web component. &lt;em&gt;If&lt;/em&gt; a value is found, then it sets a data attribute on the document root. When the window has finished loading, this means that the &lt;code&gt;&amp;lt;storage-form&amp;gt;&lt;/code&gt; JavaScript has completed, so the data attribute on the document root gets removed, allowing the CSS selector based on the &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt; value (which is now in parity with local storage) to once again take control.&lt;/p&gt;
&lt;p&gt;We can apply some further CSS that will apply the correct colour scheme during this &lt;q&gt;in-between&lt;/q&gt; state when the data attribute is present on the document root:&lt;/p&gt;
&lt;pre data-language=&quot;css&quot; data-language-friendly=&quot;CSS&quot;&gt;&lt;code&gt;&lt;span&gt;[data-color-scheme=&quot;light&quot;]&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;
	&lt;span&gt;color-scheme&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; light&lt;span&gt;;&lt;/span&gt;
&lt;span&gt;}&lt;/span&gt;
&lt;span&gt;[data-color-scheme=&quot;dark&quot;]&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;
	&lt;span&gt;color-scheme&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; dark&lt;span&gt;;&lt;/span&gt;
&lt;span&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;part-5&quot;&gt;Review and Refactor&lt;/h3&gt;
&lt;p&gt;Let’s put all of our CSS together. We’ll combine our initial &lt;code&gt;:root&lt;/code&gt; declaration alongside two other declarations, one for each of two groups, selectors that should apply the light colour scheme and selectors that should apply the dark colour scheme.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;span&gt;Warning!&lt;/span&gt; The CSS below is not the full solution! Keep reading below to learn about what we need to do to fix its problems.&lt;/em&gt;&lt;/p&gt;
&lt;pre data-language=&quot;css&quot; data-language-friendly=&quot;CSS&quot;&gt;&lt;code&gt;&lt;span&gt;:root&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;
	&lt;span&gt;color-scheme&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; light dark&lt;span&gt;;&lt;/span&gt;
&lt;span&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;p&gt;&lt;code&gt;&lt;span&gt;:root:has([name=&quot;color-scheme&quot;] [value=&quot;light&quot;]:checked),&lt;br&gt;
[data-color-scheme=&quot;light&quot;],&lt;br&gt;
.light&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;&lt;br&gt;
&lt;span&gt;color-scheme&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; light&lt;span&gt;;&lt;/span&gt;&lt;br&gt;
&lt;span&gt;}&lt;/span&gt;&lt;br&gt;
&lt;span&gt;:root:has([name=&quot;color-scheme&quot;] [value=&quot;dark&quot;]:checked),&lt;br&gt;
[data-color-scheme=&quot;dark&quot;],&lt;br&gt;
.dark&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;&lt;br&gt;
&lt;span&gt;color-scheme&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; dark&lt;span&gt;;&lt;/span&gt;&lt;br&gt;
&lt;span&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Unfortunately, while this CSS does target the different parts of the DOM that we need, there are a few last considerations to make before this will preserve the order of preferences and overrides that we want. We’re going to use two important features of CSS to help us tailor things to do that.&lt;/p&gt;
&lt;p&gt;First of all, we can amend our first declaration with the &lt;code&gt;:where()&lt;/code&gt; selector. This selector &lt;em&gt;nullifies&lt;/em&gt; the specifity of all selectors inside it, so when our &lt;code&gt;:root&lt;/code&gt; selector becomes &lt;code&gt;:where(:root)&lt;/code&gt;, it has a specificity of &lt;code&gt;(0,0,0)&lt;/code&gt;. This makes it very easy to &lt;em&gt;out-specify&lt;/em&gt; later on.&lt;/p&gt;
&lt;pre data-language=&quot;css&quot; data-language-friendly=&quot;CSS&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;:root&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;:where(:root)&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;
&lt;/span&gt;	color-scheme: light dark;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, we’ll consider the three methods of control (&lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt; value, data attribute, class) and which should be more specific.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt; value selector and data attribute selector can be treated as one—that is to say that their specificity in this hierarchy should be identical, as they both &lt;em&gt;actually&lt;/em&gt; represent the same thing, the value of the &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Based on the hierarchy that we want, the specificity of the class-based controls should be higher than the &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt; value and data attribute controls.&lt;/p&gt;
&lt;p&gt;Let’s consider the specificity of the three selectors in each group:&lt;/p&gt;
&lt;table&gt;
    &lt;tbody&gt;
        &lt;tr&gt;
            &lt;th&gt;&lt;code&gt;:root:has([name=color-scheme] [value=light]:checked)&lt;/code&gt;&lt;/th&gt;
            &lt;td&gt;(0,4,0)&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;th&gt;&lt;code&gt;[data-color-scheme=light]&lt;/code&gt;&lt;/th&gt;
            &lt;td&gt;(0,1,0)&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;th&gt;&lt;code&gt;.light&lt;/code&gt;&lt;/th&gt;
            &lt;td&gt;(0,1,0)&lt;/td&gt;
        &lt;/tr&gt;
    &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Given that their specificities don’t align with what we want, we can leverage a second important feature of CSS, source order.&lt;/p&gt;
&lt;p&gt;Let’s use the &lt;code&gt;:where()&lt;/code&gt; selector again to nullify the specificity of the &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt; value selector and data attribute selector:&lt;/p&gt;
&lt;pre data-language=&quot;css&quot; data-language-friendly=&quot;CSS&quot;&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;root&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;has&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;[name=&lt;span&gt;&quot;color-scheme&quot;&lt;/span&gt;] [value=&lt;span&gt;&quot;light&quot;&lt;/span&gt;]&lt;span&gt;:&lt;/span&gt;checked&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
&lt;span&gt;-&lt;/span&gt;[data-color-scheme=&lt;span&gt;&quot;light&quot;&lt;/span&gt;]&lt;span&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;where&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;root&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;has&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;[name=&lt;span&gt;&quot;color-scheme&quot;&lt;/span&gt;] [value=&lt;span&gt;&quot;light&quot;&lt;/span&gt;]&lt;span&gt;:&lt;/span&gt;checked&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
&lt;span&gt;+&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;where&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;[data-color-scheme=&lt;span&gt;&quot;light&quot;&lt;/span&gt;]&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
&lt;/span&gt;.light {
	color-scheme: light;
}
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;root&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;has&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;[name=&lt;span&gt;&quot;color-scheme&quot;&lt;/span&gt;] [value=&lt;span&gt;&quot;dark&quot;&lt;/span&gt;]&lt;span&gt;:&lt;/span&gt;checked&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
&lt;span&gt;-&lt;/span&gt;[data-color-scheme=&lt;span&gt;&quot;dark&quot;&lt;/span&gt;]&lt;span&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;where&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;root&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;has&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;[name=&lt;span&gt;&quot;color-scheme&quot;&lt;/span&gt;] [value=&lt;span&gt;&quot;dark&quot;&lt;/span&gt;]&lt;span&gt;:&lt;/span&gt;checked&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
&lt;span&gt;+&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;where&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;[data-color-scheme=&lt;span&gt;&quot;dark&quot;&lt;/span&gt;]&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
&lt;/span&gt;.dark {
	color-scheme: dark;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But why do this? Won’t they have the same specificity as the &lt;code&gt;:where(:root)&lt;/code&gt; declaration?&lt;/p&gt;
&lt;p&gt;They &lt;strong&gt;do&lt;/strong&gt; have the same specificity &lt;code&gt;(0,0,0)&lt;/code&gt;, but, &lt;strong&gt;critically&lt;/strong&gt;, they come &lt;em&gt;after&lt;/em&gt; the &lt;code&gt;:where(:root)&lt;/code&gt; declaration in source order. This means that our new &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt; value and data attribute selectors take precedence over &lt;code&gt;:where(:root)&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I’ve inlined the specificity of each selector to demonstrate this below:&lt;/p&gt;
&lt;pre data-language=&quot;css&quot; data-language-friendly=&quot;CSS&quot;&gt;&lt;code&gt;&lt;span&gt;/**
 * (0,0,0) — 1st in source order
 */&lt;/span&gt;
&lt;span&gt;:where(:root)&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;
	&lt;span&gt;color-scheme&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; light dark&lt;span&gt;;&lt;/span&gt;
&lt;span&gt;}&lt;/span&gt;
&lt;p&gt;&lt;span&gt;/**&lt;/span&gt;&lt;/p&gt;
&lt;/code&gt;&lt;ul&gt;&lt;code&gt;
&lt;li&gt;Order of selector specificities below:&lt;/li&gt;
&lt;li&gt;(0,0,0) – 2nd in source order&lt;/li&gt;
&lt;li&gt;(0,0,0) – 2nd in source order&lt;/li&gt;
&lt;/code&gt;&lt;li&gt;&lt;code&gt;(0,1,0)&lt;br&gt;
*/&lt;br&gt;
&lt;span&gt;:where(:root:has([name=&quot;color-scheme&quot;] [value=&quot;light&quot;]:checked)),&lt;br&gt;
:where([data-color-scheme=&quot;light&quot;]),&lt;br&gt;
.light&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;&lt;br&gt;
&lt;span&gt;color-scheme&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; light&lt;span&gt;;&lt;/span&gt;&lt;br&gt;
&lt;span&gt;}&lt;/span&gt;&lt;br&gt;
&lt;span&gt;:where(:root:has([name=&quot;color-scheme&quot;] [value=&quot;dark&quot;]:checked)),&lt;br&gt;
:where([data-color-scheme=&quot;dark&quot;]),&lt;br&gt;
.dark&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;&lt;br&gt;
&lt;span&gt;color-scheme&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; dark&lt;span&gt;;&lt;/span&gt;&lt;br&gt;
&lt;span&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/pre&gt;

&lt;hr&gt;
&lt;h2 id=&quot;part-6&quot;&gt;Fin.&lt;/h2&gt;
&lt;p&gt;At this point, the build is complete! For the most part, the code involved in this solution &lt;em&gt;rarely&lt;/em&gt; needs amending or changing, so I tend to put it wherever you might find things like resets in my CSS.&lt;/p&gt;
&lt;p&gt;We can make powerful use of the &lt;code&gt;light-dark()&lt;/code&gt; function to toggle between colour values depending on the active colour scheme, e.g.:&lt;/p&gt;
&lt;pre data-language=&quot;css&quot; data-language-friendly=&quot;CSS&quot;&gt;&lt;code&gt;&lt;span&gt;main&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;
	&lt;span&gt;background-color&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;light-dark&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;#e1e1e1&lt;span&gt;,&lt;/span&gt; #1e1e1e&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;
	&lt;span&gt;color&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;light-dark&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;#1e1e1e&lt;span&gt;,&lt;/span&gt; #e1e1e1&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;
&lt;span&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Furthermore, because we’ve set up a hierarchy for the controls that we’ve made available to us, we’ve instructed our styles to hook into visitors’ &lt;em&gt;OS/Browser-level Preference&lt;/em&gt;. If they’d rather not let their OS/Browser dictate their preference, they can use the &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt; to choose a &lt;em&gt;Site-level Preference&lt;/em&gt; that will persist across tabs, windows, and even sessions, thanks to the power of local storage.&lt;/p&gt;
&lt;p&gt;We’ve also thought about user experience and poor-performance situations by introducing a light sprinkling of JavaScript. Despite incurring a small performance impact itself, this gives us the tremendous benefit of applying a chosen colour scheme to the page as &lt;em&gt;early&lt;/em&gt; as possible to prevent the &lt;em&gt;wrong&lt;/em&gt; colour scheme from being shown &lt;em&gt;before&lt;/em&gt; the heftier parts of this solution have had a chance to make their impact.&lt;/p&gt;
&lt;p&gt;On top of that, we, as website authors, can override the user’s &lt;em&gt;Site-level Preference&lt;/em&gt; to be able to say that certain pages or parts of pages should &lt;em&gt;always&lt;/em&gt; be rendered with a specific colour scheme.&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;Full CSS Solution&lt;/summary&gt;
&lt;pre data-language=&quot;css&quot; data-language-friendly=&quot;CSS&quot;&gt;&lt;code&gt;&lt;span&gt;:where(:root)&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;
	&lt;span&gt;color-scheme&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; light dark&lt;span&gt;;&lt;/span&gt;
&lt;span&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;p&gt;&lt;code&gt;&lt;span&gt;:where(:root:has([name=&quot;color-scheme&quot;] [value=&quot;light&quot;]:checked)),&lt;br&gt;
:where([data-color-scheme=&quot;light&quot;]),&lt;br&gt;
.light&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;&lt;br&gt;
&lt;span&gt;color-scheme&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; light&lt;span&gt;;&lt;/span&gt;&lt;br&gt;
&lt;span&gt;}&lt;/span&gt;&lt;br&gt;
&lt;span&gt;:where(:root:has([name=&quot;color-scheme&quot;] [value=&quot;dark&quot;]:checked)),&lt;br&gt;
:where([data-color-scheme=&quot;dark&quot;]),&lt;br&gt;
.dark&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;&lt;br&gt;
&lt;span&gt;color-scheme&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; dark&lt;span&gt;;&lt;/span&gt;&lt;br&gt;
&lt;span&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;br&gt;
&lt;/details&gt;&lt;p&gt;&lt;/p&gt;
&lt;h4&gt;Bonus!&lt;/h4&gt;
&lt;p&gt;Although it isn’t stable across browsers yet, the &lt;a href=&quot;/feature-watch/#css-if&quot;&gt;&lt;code&gt;if()&lt;/code&gt; selector&lt;/a&gt; will allow us to toggle non-colour property values based on the resolved colour scheme. This is great for things like changing a &lt;code&gt;background-image&lt;/code&gt; based on the colour scheme. That would look something like this:&lt;/p&gt;
&lt;pre data-language=&quot;css&quot; data-language-friendly=&quot;CSS&quot;&gt;&lt;code&gt;&lt;span&gt;background-image&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;if&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;color-scheme&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;light&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;&lt;span&gt;url&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;/light.png&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt; &lt;span&gt;else&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;&lt;span&gt;url&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;/dark.png&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2 id=&quot;part-7&quot;&gt;Fin. Part 2:&amp;nbsp;Electric Boogaloo&lt;/h2&gt;
&lt;p&gt;We can actually take this one step further without &lt;em&gt;much&lt;/em&gt; extra CSS, and this gives us the ability to set a Page-level or Element-level colour scheme that is more of a &lt;em&gt;suggestion&lt;/em&gt; than a &lt;em&gt;rule&lt;/em&gt;, and can be overridden by a &lt;em&gt;Site-level Preference&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;This gives us two new ways to apply colour schemes to our pages and an amended hierarchy of how colour schemes can be applied:&lt;/p&gt;
&lt;ol&gt;
    &lt;li&gt;&lt;a href=&quot;#part-2&quot;&gt;Element-level Override&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;#part-2&quot;&gt;Page-level Override&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;#part-3&quot;&gt;Site-level Preference&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;Element-level Suggestion&lt;/li&gt;
    &lt;li&gt;Page-level Suggestion&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;#part-1&quot;&gt;OS/Browser-level Preference&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre data-language=&quot;css&quot; data-language-friendly=&quot;CSS&quot;&gt;&lt;code&gt;:where(:root) {
	color-scheme: light dark;
}
&lt;/code&gt;&lt;p&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;:where(:root:has([name=&quot;color-scheme&quot;] [value=&quot;&quot;]:checked):not([data-color-scheme], .light, .dark))&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;&lt;br&gt;
&lt;span&gt;+&lt;/span&gt;	&lt;span&gt;&amp;amp;:where(.suggested-light),&lt;br&gt;
&lt;span&gt;+&lt;/span&gt;	&amp;amp; :where(.suggested-light)&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;&lt;br&gt;
&lt;span&gt;+&lt;/span&gt;		&lt;span&gt;color-scheme&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; light&lt;span&gt;;&lt;/span&gt;&lt;br&gt;
&lt;span&gt;+&lt;/span&gt;	&lt;span&gt;}&lt;/span&gt;&lt;br&gt;
&lt;span&gt;+&lt;/span&gt;	&lt;span&gt;&amp;amp;:where(.suggested-dark),&lt;br&gt;
&lt;span&gt;+&lt;/span&gt;	&amp;amp; :where(.suggested-dark)&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;&lt;br&gt;
&lt;span&gt;+&lt;/span&gt;		&lt;span&gt;color-scheme&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; dark&lt;span&gt;;&lt;/span&gt;&lt;br&gt;
&lt;span&gt;+&lt;/span&gt;	&lt;span&gt;}&lt;/span&gt;&lt;br&gt;
&lt;span&gt;+&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;br&gt;
&lt;/span&gt;&lt;br&gt;
:where(:root:has([name=&quot;color-scheme&quot;] [value=&quot;light&quot;]:checked)),&lt;br&gt;
:where([data-color-scheme=&quot;light&quot;]),&lt;br&gt;
.light {&lt;br&gt;
color-scheme: light;&lt;br&gt;
}&lt;br&gt;
:where(:root:has([name=&quot;color-scheme&quot;] [value=&quot;dark&quot;]:checked)),&lt;br&gt;
:where([data-color-scheme=&quot;dark&quot;]),&lt;br&gt;
.dark {&lt;br&gt;
color-scheme: dark;&lt;br&gt;
}&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;p&gt;&lt;/p&gt;
&lt;div&gt;
    &lt;p&gt;This introduces a rather unwieldy selector, &lt;code&gt;:where(:root:has([name=&quot;color-scheme&quot;] [value=&quot;&quot;]:checked):not([data-color-scheme], .light, .dark))&lt;/code&gt;, so let’s break down what it does:&lt;/p&gt;
    &lt;ul&gt;
        &lt;li&gt;&lt;code&gt;:root:has([name=&quot;color-scheme&quot;] [value=&quot;&quot;]:checked)&lt;/code&gt; checks if the &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt; on the page has its default value but targets the document root when applying styles. When this is the case, it means that the user has selected neither &lt;code&gt;light&lt;/code&gt; nor &lt;code&gt;dark&lt;/code&gt; as their &lt;em&gt;Site-level Preference&lt;/em&gt;.&lt;/li&gt;
        &lt;li&gt;&lt;code&gt;:not([data-color-scheme], .light, .dark)&lt;/code&gt; ensures that the document root has neither a &lt;code&gt;data-color-scheme&lt;/code&gt; attribute nor the classes &lt;code&gt;light&lt;/code&gt; or &lt;code&gt;dark&lt;/code&gt; applied to it.&lt;/li&gt;
        &lt;li&gt;&lt;code&gt;:where(...)&lt;/code&gt; makes sure that this complex selector has a specificity of &lt;code&gt;(0,0,0)&lt;/code&gt;. This ensures that it doesn’t compete in specificity with other important parts of this solution, but rather takes a specific place in the source order to help define its place in the hierarchy (see below).&lt;/li&gt;
    &lt;/ul&gt;
&lt;/div&gt;
&lt;p&gt;The two extra classes that this introduces, &lt;code&gt;.suggested-light&lt;/code&gt; and &lt;code&gt;.suggested-dark&lt;/code&gt;, can be applied to our HTML in the same way as our previous classes (&lt;code&gt;.light&lt;/code&gt; and &lt;code&gt;.dark&lt;/code&gt;), but the important thing to note here again is the source order.&lt;/p&gt;
&lt;p&gt;By placing these declarations &lt;em&gt;between&lt;/em&gt; the two parts of our previous solution (and matching the specificity of &lt;code&gt;(0,0,0)&lt;/code&gt;), they receive &lt;em&gt;higher&lt;/em&gt; precendence than the &lt;em&gt;OS/Browser-level Preference&lt;/em&gt; but &lt;em&gt;lower&lt;/em&gt; precedence than the &lt;em&gt;Site-level Preference&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;This can be useful in situations where, for artistic reasons, you&#39;d like a particular page or section to be a specific colour scheme, but if the user has set a &lt;em&gt;Site-level Preference&lt;/em&gt;—maybe for reasons relating to eyesight or otherwise—then that choice should be respected and the particular page/section should be rendered as they’ve chosen, &lt;em&gt;not&lt;/em&gt; from your &lt;q&gt;suggested&lt;/q&gt; colour scheme.&lt;/p&gt;
			
			
			
			
				&lt;hr&gt;
				&lt;p&gt;Thanks for subscribing and reading this post via RSS. This post is part of &lt;a href=&quot;https://chrisburnell.com/rss-club/&quot;&gt;RSS Club&lt;/a&gt;, which means it appears &lt;em&gt;only&lt;/em&gt; in my RSS feeds for 3 days (until &lt;time datetime=&quot;2025-10-27T12:01:00-03:00&quot;&gt;Monday, 27 October 2025&lt;/time&gt;), rewarding folks who subscribe to my RSS feeds with knowing about this post early!&lt;/p&gt;
				&lt;p&gt;You can read &lt;a href=&quot;https://chrisburnell.com/article/color-scheming/&quot;&gt;this post&lt;/a&gt; and others &lt;a href=&quot;https://chrisburnell.com/posts/&quot;&gt;on my website&lt;/a&gt;.&lt;/p&gt;
		</content>
		
		
	</entry>
	<entry>
		<id>https://chrisburnell.com/note/eleventy-animated-88x31/</id>
		<link href="https://chrisburnell.com/note/eleventy-animated-88x31/" />
		<title>“Built with Eleventy” Animated 88x31</title>
		<published>2025-06-10T03:47:32+08:00</published>
		<publishedFriendly>10 June 2025</publishedFriendly>
		<updated>2025-06-10T21:41:50+08:00</updated>
		<category term="note" scheme="https://chrisburnell.com/note/" label="Note" />
		<summary>I was tagged several days ago on Mastodon, as someone was looking for a &quot;90s-style 88x31 footer GIF&quot; for pages built with Eleventy, and [... I] gave animating the badge a shot!</summary>
		<content xml:lang="en" type="html">
			
			
			
			&lt;p&gt;I was &lt;a href=&quot;https://linh.social/@qlp/114586765878511923&quot;&gt;tagged&lt;/a&gt; several days ago on Mastodon, as someone was looking for a &lt;a href=&quot;https://linuxmom.net/@vkc/114586734164356317&quot;&gt;&lt;q&gt;90s-style 88x31 footer GIF&lt;/q&gt;&lt;/a&gt; for pages built with Eleventy, and it has been just over a year now since I first created mine:&lt;/p&gt;
&lt;figure&gt;
	&lt;img src=&quot;/images/built-with-eleventy.gif&quot; alt=&quot;Built with Eleventy badge&quot; width=&quot;352&quot; height=&quot;124&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
	&lt;figcaption&gt;&lt;em&gt;Shown at 4× original size so that the detail is a bit easier to see.&lt;/em&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;I had a lot of fun creating a handful of &lt;a href=&quot;https://en.wikipedia.org/wiki/Web_badge&quot;&gt;88x31 badges&lt;/a&gt;, and the post on Mastodon reinvigorated my desire to play with pixels. So this evening, I once again fired up &lt;a href=&quot;https://www.aseprite.org/&quot;&gt;Aseprite&lt;/a&gt; and gave animating the badge a shot!&lt;/p&gt;
&lt;figure&gt;
	&lt;img src=&quot;/images/animated/built-with-eleventy.gif&quot; alt=&quot;Built with Eleventy animated badge&quot; width=&quot;352&quot; height=&quot;124&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
&lt;/figure&gt;
&lt;p&gt;It may not be the most flashy 88x31 badge you’ve ever seen, but I think it’s pretty fun in its simplicity, and it’s only 3.3 &lt;abbr title=&quot;kilobytes&quot;&gt;kB&lt;/abbr&gt;.&lt;/p&gt;
&lt;p&gt;You’ve got my &lt;strong&gt;full permission&lt;/strong&gt; to take these badges and use them on your own “Built with Eleventy” websites or however else you want!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Important to note&lt;/strong&gt;, if you are displaying these on the web, I &lt;strong&gt;highly&lt;/strong&gt; recommend that you apply the following CSS to the image to ensure it displays nice and crispy:&lt;/p&gt;
&lt;pre data-language=&quot;css&quot; data-language-friendly=&quot;CSS&quot;&gt;&lt;code&gt;&lt;span&gt;image-rendering&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; pixelated&lt;span&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I also built some longer versions that include call-outs to the &lt;a href=&quot;https://11ty.dev&quot;&gt;11ty.dev&lt;/a&gt; website.&lt;/p&gt;
&lt;figure&gt;
	&lt;img src=&quot;/images/animated/built-with-eleventy-long.gif&quot; alt=&quot;Built with Eleventy longer animated badge&quot; width=&quot;352&quot; height=&quot;124&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
	&lt;figcaption&gt;&lt;em&gt;This one’s a bit chunkier, clocking in at 9 kB.&lt;/em&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;
	&lt;img src=&quot;/images/animated/built-with-eleventy-long-alternate.gif&quot; alt=&quot;Built with Eleventy alternate longer animated badge&quot; width=&quot;352&quot; height=&quot;124&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;&gt;
	&lt;figcaption&gt;&lt;em&gt;And this one’s a bit bigger than the previous at 9.5 kB.&lt;/em&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
			
			
			
			
				&lt;hr&gt;
				&lt;p&gt;Thanks for subscribing and reading this post via RSS!&lt;/p&gt;
				&lt;p&gt;You can read &lt;a href=&quot;https://chrisburnell.com/note/eleventy-animated-88x31/&quot;&gt;this post&lt;/a&gt; and others &lt;a href=&quot;https://chrisburnell.com/posts/&quot;&gt;on my website&lt;/a&gt;.&lt;/p&gt;
		</content>
		
		
	</entry>
	
</feed>
