<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet href="/feeds.xsl" type="text/xsl"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:base="https://chameth.com/">
    <title>Chameth.com - posts like building-a-new-computer, g15-ram-upgrade, modern-css-is-fun, understanding-docker-volume-mounts, why-you-should-be-using-https</title>
    <subtitle>Personal homepage of Chris Smith</subtitle>
    <link href="https://chameth.com/feeds/posts/like/building-a-new-computer,g15-ram-upgrade,modern-css-is-fun,understanding-docker-volume-mounts,why-you-should-be-using-https/" rel="self"/>
    <link href="https://chameth.com/"/>
    <icon>https://chameth.com/favicon.png</icon>
    <updated>2026-04-09T00:00:00Z</updated>
    <id>https://chameth.com/</id>
    <author>
        <name>Chris Smith</name>
    </author>
    <entry>
        <title>The Case of the Unchanging Config</title>
        <link href="https://chameth.com/the-case-of-the-unchanging-config/"/>
        <updated>2026-04-09T00:00:00Z</updated>
        <id>https://chameth.com/the-case-of-the-unchanging-config/</id>
        <content xml:lang="en" type="html">&lt;p&gt;Last week I was attempting to make it so I could share pictures on IRC directly from my client. This sounds simple, but it involves a bouncer&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a class=&#34;footnote-ref&#34; href=&#34;#fn:1&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; that proxies the request to a standalone image hosting service that I had to modify to be compatible. At one point my testing loop was:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Make a change to the hosting service&lt;/li&gt;
&lt;li&gt;Commit it&lt;/li&gt;
&lt;li&gt;Tag a new release&lt;/li&gt;
&lt;li&gt;Wait for it to build&lt;/li&gt;
&lt;li&gt;Update the version I’m running on my server&lt;/li&gt;
&lt;li&gt;Reconfigure the bouncer to pass the new parameter or change the URL or whatever&lt;/li&gt;
&lt;li&gt;Try uploading a photo from my phone&lt;/li&gt;
&lt;li&gt;Realise I’ve overlooked something and go back to step one&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I &lt;em&gt;could&lt;/em&gt; have set up a local copy of everything and tested it sensibly. I &lt;em&gt;should&lt;/em&gt; have set up a local copy of everything and tested it sensibly. But it seemed like such a trivial change, and setting up the whole environment seemed like such a pain. After the third or so iteration of failure I was pretty annoyed with myself, computers, and basically everything.&lt;/p&gt;
&lt;p&gt;My biggest annoyance was that my bouncer would not pick up the new URL from the config when I changed it. It’s meant to reload the config when it receives a &lt;code&gt;SIGHUP&lt;/code&gt;, and it claimed to in the logs, but I could clearly see it was still hitting the old URL. Restarting the bouncer to update the config is a pain, as it disconnects me from all the IRC networks, and has to reconnect to them all, reauthenticate, etc. It also mildly spams everyone who shares a channel with me&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a class=&#34;footnote-ref&#34; href=&#34;#fn:2&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;When I finally got everything working I had a look at the bouncer source, and thought I’d spotted the issue. I raised a bug report, ending in this remark:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;It looks like the config is reloaded properly, but the handler for uploads is created once at startup and has its own copy of the uploader, so effectively snapshots the config to whatever it is at startup:&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&#34;chroma-chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-nx&#34;&gt;fileUploadHandler&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;http&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;chroma-nf&#34;&gt;HandlerFunc&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;chroma-kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;w&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;http&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;ResponseWriter&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;r&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;http&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;Request&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;cfg&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;srv&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;chroma-nf&#34;&gt;Config&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;h&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;fileupload&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;Handler&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;Uploader&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;cfg&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;FileUploader&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;DB&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt;          &lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;db&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;Auth&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;cfg&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;Auth&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;HTTPOrigins&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;cfg&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;HTTPOrigins&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;h&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;chroma-nf&#34;&gt;ServeHTTP&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;w&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;r&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-p&#34;&gt;})&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I’m not quite sure what I thought I saw there, but that’s not right. I want to call it a hallucination, but that feels like a very overloaded word these days. I guess I was just seeing what I wanted to see, and that was a good excuse to stop investigating. Still, even if I misidentified the cause, the bug was still present, right? … Right?&lt;/p&gt;
&lt;h3 id=&#34;the-science-of-deduction&#34;&gt;The Science of Deduction&lt;/h3&gt;
&lt;p&gt;When the developer got back and said he couldn’t reproduce my issue, I went back to my install and immediately reproduced it. With a calmer head, I figured it was probably something with my particular set up. First thing to check: am I actually running the version I think I am?&lt;/p&gt;
&lt;p&gt;Unfortunately the bouncer doesn’t actually seem to expose the version anywhere that I can see. It’s not in the logs, it doesn’t have a &lt;code&gt;-version&lt;/code&gt; flag, and none of the IRC-based status commands seem to include it. But I know it’s a Go app, and I know Go embeds the version information. &lt;code&gt;go version -m &amp;lt;binary&amp;gt;&lt;/code&gt; will dump it all out, but the binary is inside a Docker image, and the Docker image is one of my &lt;a href=&#34;https://chameth.com/artisanal-docker-images/&#34;&gt;nice, minimal, artisanal ones&lt;/a&gt; so doesn’t ship a &lt;code&gt;go&lt;/code&gt; binary. No problem, &lt;code&gt;docker compose cp bouncer:/bnc ./bnc&lt;/code&gt; yoinks the binary out of the container, and then dumping the version shows that, yes, I am running the version I thought I was. Hmm.&lt;/p&gt;
&lt;p&gt;The next tool I reached for in &lt;a href=&#34;https://chameth.com/debugging-beyond-the-debugger/&#34;&gt;my toolbox&lt;/a&gt; was &lt;code&gt;strace&lt;/code&gt;. Maybe it’s not actually reading the file for some reason? I immediately executed &lt;code&gt;strace -p &amp;lt;pid&amp;gt; -e trace=openat,open,read,pread64 -f&lt;/code&gt; without having to look any part of that up. Yep. Definitely. Then I edited the config, &lt;code&gt;HUP&lt;/code&gt;’d the bouncer, and saw that it was… reading the config file. As it’s meant to. By default &lt;code&gt;strace&lt;/code&gt; truncates strings to 32 bytes, so I couldn’t actually see the line I’d changed. Some more definitely-not-RTFMing later, and rerunning it with an extra &lt;code&gt;-s 65536&lt;/code&gt; let me see the full config. Surprise! The config hadn’t changed!&lt;/p&gt;
&lt;p&gt;To confirm my findings, I used &lt;code&gt;docker compose cp&lt;/code&gt; again, this time yoinking the config file from inside the container. The inside config file was definitely different to the outside config file. What? My hypothesis at this point was “something something Docker nonsense”. I mount the config as read-only, and was wondering if that meant that Docker was doing something &lt;em&gt;weird&lt;/em&gt; instead of just bind mounting it. A quick trip to &lt;code&gt;/proc/&amp;lt;pid&amp;gt;/mounts&lt;/code&gt; showed that it was, in fact, not doing anything weird, and was just bind mounting it.&lt;/p&gt;
&lt;p&gt;If the file is bind mounted, then surely it’s the same file? I ran &lt;code&gt;stat&lt;/code&gt; on the file on the host, noted the inode number, then pondered how to actually run &lt;code&gt;stat&lt;/code&gt; on the file inside the container, given the aforementioned awkwardly minimal image. The solution was easy: access it via &lt;code&gt;/proc/&amp;lt;pid&amp;gt;/root/&lt;/code&gt;. I could’ve saved myself a bunch of &lt;code&gt;docker compose cp&lt;/code&gt; if I’d thought about that earlier. Oh well. The inode of that file was different. What?&lt;/p&gt;
&lt;p&gt;The answer was DNS. It’s always DNS. Oh, sorry, force of habit. I meant the answer was &lt;em&gt;vim&lt;/em&gt;. I was editing the config in vim, and when it saves files, by default, it writes the new content to a temporary file and does an atomic rename. That’s normally a good thing: it prevents corruption if the write fails midway through&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a class=&#34;footnote-ref&#34; href=&#34;#fn:3&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;. In this case, though, that means the new file has a new inode. Bind mounting a file binds to the inode, so the container just keeps clutching onto the original config from when it was started, blissfully unaware that the party has relocated down the street.&lt;/p&gt;
&lt;p&gt;This problem is likely to happen whenever you bind mount a file into a container. When I mentioned this to a friend, he immediately responded “oh yeah, never do that”, and went on to describe the horrible hacks he’s had to add to Ansible to sidestep the issue. The nicer solution is to just bind mount an entire directory if you can, as then it doesn’t matter what happens to the files within it. I really like having the config files sat alongside the Docker compose files, though; having to create a directory just to work around some bind mount weirdness upsets me.&lt;/p&gt;
&lt;p&gt;Now I knew what the problem was, I found there was an issue &lt;a href=&#34;https://github.com/moby/moby/issues/6011&#34;&gt;raised against Docker&lt;/a&gt; twelve years ago&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a class=&#34;footnote-ref&#34; href=&#34;#fn:4&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt;. The first response was “that’s expected”, and I fully agree with the author: “respectfully, that might be expected by you, but it was not expected by me”!&lt;sup id=&#34;fnref:5&#34;&gt;&lt;a class=&#34;footnote-ref&#34; href=&#34;#fn:5&#34; role=&#34;doc-noteref&#34;&gt;5&lt;/a&gt;&lt;/sup&gt; As a result of the issue they &lt;a href=&#34;https://github.com/moby/moby/pull/6854/changes&#34;&gt;added a nice note to the docs&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt;
Many tools used to edit files including &lt;code&gt;vi&lt;/code&gt; and &lt;code&gt;sed --in-place&lt;/code&gt; may result
in an inode change. Since Docker v1.1.0, this will produce an error such as
“&lt;em&gt;sed: cannot rename ./sedKdJ9Dy: Device or resource busy&lt;/em&gt;”. In the case where
you want to edit the mounted file, it is often easiest to instead mount the
parent directory.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;But in the intervening twelve years, both the note and the functionality described have gone missing. Ho-hum.&lt;/p&gt;
&lt;p&gt;I found it interesting how I’ve only just hit this problem, given how long I’ve used Docker. But I realised that almost everything I run I’m happy to just restart. Cattle, not pets, and so on. My IRC bouncer is one of the few exceptions to that. The only other thing I regularly hot reloaded was &lt;a href=&#34;https://github.com/csmith/centauri&#34;&gt;Centauri&lt;/a&gt;, my reverse proxy, but that had a whole config directory mounted because it was shared between containers, so nicely sidestepped the foot-gun.&lt;/p&gt;
&lt;p&gt;So lesson learnt: check for weird bind mount issues before raising issues about config hot reloading. It’ll join the esteemed company of “maybe the drive is full and causing completely unrelated problems?”, “perhaps everything is dog slow because the kernel ran out of entropy?”, and “did systemd sneakily take over that functionality while you weren’t looking?” in the troubleshooting checklist.&lt;/p&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr/&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;An IRC bouncer is basically an always-on proxy. It connects to the IRC networks for you, then your clients connect to your bouncer. The bouncer can then send incoming messages to all your different clients, cache them when you’re offline and replay them later, and lots of other nice things people take for granted in their chat apps these days. &lt;a class=&#34;footnote-backref&#34; href=&#34;#fnref:1&#34; role=&#34;doc-backlink&#34;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34;&gt;
&lt;p&gt;Join/part/quit spam is part of IRC, and clients have ways of handling it, but I still &lt;em&gt;feel&lt;/em&gt; bad about doing it excessively. &lt;a class=&#34;footnote-backref&#34; href=&#34;#fnref:2&#34; role=&#34;doc-backlink&#34;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34;&gt;
&lt;p&gt;You can disable this in vim by setting &lt;code&gt;backupcopy=yes&lt;/code&gt;. Good luck redoing all the debugging if you ever accidentally remove that from your &lt;code&gt;vimrc&lt;/code&gt;, though! &lt;a class=&#34;footnote-backref&#34; href=&#34;#fnref:3&#34; role=&#34;doc-backlink&#34;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:4&#34;&gt;
&lt;p&gt;Which is weird, because Docker can’t possibly be that old. That would make me much older than I’m prepared to accept. &lt;a class=&#34;footnote-backref&#34; href=&#34;#fnref:4&#34; role=&#34;doc-backlink&#34;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:5&#34;&gt;
&lt;p&gt;If you read the thread they didn’t actually mean to sound so dismissive, but it’s still pretty funny. “Yes, it’s expected that the foot-gun causes your foot to hurt. Duh. What did you expect?” &lt;a class=&#34;footnote-backref&#34; href=&#34;#fnref:5&#34; role=&#34;doc-backlink&#34;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
</content>
    </entry>
    <entry>
        <title>Monthly Meanderings: March 2026</title>
        <link href="https://chameth.com/monthly-meanderings-2026-03/"/>
        <updated>2026-04-01T00:00:00Z</updated>
        <id>https://chameth.com/monthly-meanderings-2026-03/</id>
        <content xml:lang="en" type="html">&lt;p&gt;Since &lt;a href=&#34;https://chameth.com/monthly-meanderings-2026-02/&#34;&gt;last month’s update&lt;/a&gt; I’ve been unpleasantly reminded that I’m middle aged, through the medium of a dodgy knee. There’s nothing quite like not being able to stand up without groaning to underscore that you’re not young any more. The ongoing game of “will this seemingly mundane activity make my knee go funny again?” is a &lt;em&gt;delight&lt;/em&gt; to play, too. I’m great at it.&lt;/p&gt;
&lt;h3 id=&#34;website-updates&#34;&gt;Website updates&lt;/h3&gt;
&lt;p&gt;Two new blog posts this month: &lt;a href=&#34;https://chameth.com/the-longest-way-to-represent-a-date/&#34;&gt;the longest way to represent a date&lt;/a&gt; is a short thought experiment on absurd date formats, and &lt;a href=&#34;https://chameth.com/modern-css-is-fun/&#34;&gt;modern CSS is fun&lt;/a&gt; goes through some neat new CSS features I’ve used recently for this site.&lt;/p&gt;
&lt;p&gt;I did some design tweaks this month: headers now have a blue background behind them&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a class=&#34;footnote-ref&#34; href=&#34;#fn:1&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;, and a lot of components have been redesigned to have a consistent style. You can see that component in action in the new “now playing” widget I’ve added:&lt;/p&gt;
&lt;div class=&#34;now-playing raised-box&#34; data-title=&#34;Now playing&#34;&gt;
    &lt;img src=&#34;https://chameth.com/music/albums/207/cover.jpg&#34; alt=&#34;Fixed at Zero&#34; loading=&#34;lazy&#34;/&gt;
    &lt;div class=&#34;now-playing-info&#34;&gt;
        &lt;span class=&#34;artist-track&#34;&gt;VersaEmerge — Redesign Me&lt;/span&gt;
        &lt;span class=&#34;album-name&#34;&gt;Fixed at Zero&lt;/span&gt;
        &lt;span class=&#34;play-status&#34;&gt;Scrobbled 1h 7m ago&lt;/span&gt;
    &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Instead of each component defining its own styles (which were &lt;em&gt;mostly&lt;/em&gt; copied and pasted), there’s now a single class that handles the drop shadow, border, background colour, and the optional title. It also applies a consistent format to elements that are clickable: they use the blue accent colour for their borders, and have a glow effect shown on hover.&lt;/p&gt;
&lt;p&gt;The now playing widget is part of a larger bit of work I did on importing some music stats. I’m pulling in the data from my &lt;a href=&#34;https://www.navidrome.org/&#34;&gt;Navidrome&lt;/a&gt; instance. A new &lt;a href=&#34;https://chameth.com/music/&#34;&gt;music page&lt;/a&gt; shows my most listened albums and artists. Navidrome doesn’t store a complete play history, just the last play and the count, so I can’t do “what I listened to last month” stats until next month.&lt;/p&gt;
&lt;h3 id=&#34;other-projects&#34;&gt;Other projects&lt;/h3&gt;
&lt;p&gt;Other than a minor bug-fix update to &lt;a href=&#34;https://github.com/csmith/contempt&#34;&gt;contempt&lt;/a&gt;, my Dockerfile templating/updating tool, I’ve not done much on my other open source projects. I’ve got a bit more work to do finish migrating all my repositories from GitHub to my private &lt;a href=&#34;https://forgejo.org/&#34;&gt;Forgejo&lt;/a&gt; instance. I’ve also been thinking about mirroring my public repositories to &lt;a href=&#34;https://codeberg.org/&#34;&gt;Codeberg&lt;/a&gt; so there’s a non-GitHub way to access them.&lt;/p&gt;
&lt;h3 id=&#34;entertainment&#34;&gt;Entertainment&lt;/h3&gt;
&lt;p&gt;I apparently only watched a single film in March:&lt;/p&gt;
&lt;div class=&#34;film-review-parent&#34;&gt;
  &lt;section class=&#34;film-review raised-box&#34;&gt;
    &lt;img src=&#34;https://chameth.com/films/253/poster.jpg&#34; alt=&#34;Poster for The Substance&#34; loading=&#34;lazy&#34;/&gt;
    &lt;header&gt;
      &lt;h3 class=&#34;plain-header&#34;&gt;&lt;a href=&#34;https://chameth.com/films/the-substance-2024/&#34;&gt;The Substance&lt;/a&gt;&lt;/h3&gt;
      &lt;div&gt;&lt;/div&gt;
      &lt;div title=&#34;5/10&#34;&gt;
&lt;span class=&#34;star-rating&#34;&gt;&lt;img src=&#34;https://chameth.com/star.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Full star&#34; class=&#34;rot-2&#34;/&gt;&lt;img src=&#34;https://chameth.com/star.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Full star&#34; class=&#34;rot-0&#34;/&gt;&lt;img src=&#34;https://chameth.com/star-half.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Half star&#34;/&gt;&lt;img src=&#34;https://chameth.com/star-empty.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Empty star&#34;/&gt;&lt;img src=&#34;https://chameth.com/star-empty.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Empty star&#34;/&gt;&lt;/span&gt;
&lt;/div&gt;
      &lt;time&gt;2026-03-18&lt;/time&gt;
    &lt;/header&gt;
    &lt;div&gt;&lt;p&gt;I really didn’t get on with this. It’s too long for what it is, and over-the-top in a way that just didn’t work for me.&lt;/p&gt;
&lt;p&gt;Demi Moore and Margaret Qualley were great, but nothing else really was. The cinematography and sound design were too affected, the plot was pretty shallow and obvious, most of the body horror fell into the uncanny CGI valley for me.&lt;/p&gt;
&lt;p&gt;It’s not completely without merit: there were around 40 minutes in the middle where I was really into it, but that’s not really enough given it’s closing in on a 2.5 hour runtime.&lt;/p&gt;
&lt;p&gt;There’s something to be said for the message it’s presenting about women in Hollywood, too, but it was very heavy handed. Dennis Quaid’s character is even called Harvey; it’s not so much a subtle nod as a sledgehammer to the face.&lt;/p&gt;
&lt;/div&gt;
  &lt;/section&gt;
&lt;/div&gt;
&lt;p&gt;Instead of films, I’ve been watching a bunch of TV. After devouring &lt;a href=&#34;https://www.themoviedb.org/tv/250307-the-pitt&#34;&gt;The Pitt&lt;/a&gt; last month, I struggled to find something to fill the hole it left. I settled on catching up on &lt;a href=&#34;https://www.themoviedb.org/tv/44006-chicago-fire&#34;&gt;Chicago Fire&lt;/a&gt; and &lt;a href=&#34;https://www.themoviedb.org/tv/58841-chicago-p-d&#34;&gt;Chicago P.D.&lt;/a&gt;. They’re both mostly about getting things done without personal drama becoming the primary focus. They’re not quite as grounded as The Pitt, but at least they’ve not completely gone off the deep end like &lt;a href=&#34;https://www.themoviedb.org/tv/75219-9-1-1&#34;&gt;9-1-1&lt;/a&gt; where the first responders end up in space somehow?&lt;/p&gt;
&lt;p&gt;I’ve also watched the first two episodes of &lt;a href=&#34;https://www.themoviedb.org/tv/288670-saturday-night-live-uk&#34;&gt;SNL UK&lt;/a&gt;. I didn’t have very high expectations, and it gave me a pleasant surprise. Both episodes so far have some hilarious sketches, and are obviously written by British comics with the typical darker, more sardonic humour that separates us from the USA.&lt;/p&gt;
&lt;p&gt;Last but not least, board games! I spent a weekend at a tabletop/LAN event with friends, so got to physically play some games for a change. I also kept up with some turn-based games on &lt;a href=&#34;https://boardgamearena.com/&#34;&gt;Board Game Arena&lt;/a&gt;, as usual.&lt;/p&gt;
&lt;ul class=&#34;played-boardgames&#34;&gt;
  &lt;li class=&#34;raised-box&#34;&gt;
    &lt;div class=&#34;image-container&#34; title=&#34;Just One (2018)&#34;&gt;
      &lt;img class=&#34;background&#34; src=&#34;https://chameth.com/boardgames/254640/image.jpg&#34; aria-hidden=&#34;true&#34; loading=&#34;lazy&#34;/&gt;
      &lt;img class=&#34;foreground&#34; src=&#34;https://chameth.com/boardgames/254640/image.jpg&#34; alt=&#34;Box art of Just One&#34; loading=&#34;lazy&#34;/&gt;
    &lt;/div&gt;
    &lt;p&gt;6 plays&lt;/p&gt;
  &lt;/li&gt;
  &lt;li class=&#34;raised-box&#34;&gt;
    &lt;div class=&#34;image-container&#34; title=&#34;7 Wonders Dice (2025)&#34;&gt;
      &lt;img class=&#34;background&#34; src=&#34;https://chameth.com/boardgames/446231/image.jpg&#34; aria-hidden=&#34;true&#34; loading=&#34;lazy&#34;/&gt;
      &lt;img class=&#34;foreground&#34; src=&#34;https://chameth.com/boardgames/446231/image.jpg&#34; alt=&#34;Box art of 7 Wonders Dice&#34; loading=&#34;lazy&#34;/&gt;
    &lt;/div&gt;
    &lt;p&gt;4 plays&lt;/p&gt;
  &lt;/li&gt;
  &lt;li class=&#34;raised-box&#34;&gt;
    &lt;div class=&#34;image-container&#34; title=&#34;Can&amp;#39;t Stop (1980)&#34;&gt;
      &lt;img class=&#34;background&#34; src=&#34;https://chameth.com/boardgames/41/image.png&#34; aria-hidden=&#34;true&#34; loading=&#34;lazy&#34;/&gt;
      &lt;img class=&#34;foreground&#34; src=&#34;https://chameth.com/boardgames/41/image.png&#34; alt=&#34;Box art of Can&amp;#39;t Stop&#34; loading=&#34;lazy&#34;/&gt;
    &lt;/div&gt;
    &lt;p&gt;4 plays&lt;/p&gt;
  &lt;/li&gt;
  &lt;li class=&#34;raised-box&#34;&gt;
    &lt;div class=&#34;image-container&#34; title=&#34;Martian Dice (2011)&#34;&gt;
      &lt;img class=&#34;background&#34; src=&#34;https://chameth.com/boardgames/99875/image.png&#34; aria-hidden=&#34;true&#34; loading=&#34;lazy&#34;/&gt;
      &lt;img class=&#34;foreground&#34; src=&#34;https://chameth.com/boardgames/99875/image.png&#34; alt=&#34;Box art of Martian Dice&#34; loading=&#34;lazy&#34;/&gt;
    &lt;/div&gt;
    &lt;p&gt;4 plays&lt;/p&gt;
  &lt;/li&gt;
  &lt;li class=&#34;raised-box&#34;&gt;
    &lt;div class=&#34;image-container&#34; title=&#34;Cartographers (2019)&#34;&gt;
      &lt;img class=&#34;background&#34; src=&#34;https://chameth.com/boardgames/263918/image.png&#34; aria-hidden=&#34;true&#34; loading=&#34;lazy&#34;/&gt;
      &lt;img class=&#34;foreground&#34; src=&#34;https://chameth.com/boardgames/263918/image.png&#34; alt=&#34;Box art of Cartographers&#34; loading=&#34;lazy&#34;/&gt;
    &lt;/div&gt;
    &lt;p&gt;3 plays&lt;/p&gt;
  &lt;/li&gt;
  &lt;li class=&#34;raised-box&#34;&gt;
    &lt;div class=&#34;image-container&#34; title=&#34;Coffee Rush (2023)&#34;&gt;
      &lt;img class=&#34;background&#34; src=&#34;https://chameth.com/boardgames/377061/image.jpg&#34; aria-hidden=&#34;true&#34; loading=&#34;lazy&#34;/&gt;
      &lt;img class=&#34;foreground&#34; src=&#34;https://chameth.com/boardgames/377061/image.jpg&#34; alt=&#34;Box art of Coffee Rush&#34; loading=&#34;lazy&#34;/&gt;
    &lt;/div&gt;
    &lt;p&gt;3 plays&lt;/p&gt;
  &lt;/li&gt;
  &lt;li class=&#34;raised-box&#34;&gt;
    &lt;div class=&#34;image-container&#34; title=&#34;Super Mega Lucky Box (2021)&#34;&gt;
      &lt;img class=&#34;background&#34; src=&#34;https://chameth.com/boardgames/341530/image.png&#34; aria-hidden=&#34;true&#34; loading=&#34;lazy&#34;/&gt;
      &lt;img class=&#34;foreground&#34; src=&#34;https://chameth.com/boardgames/341530/image.png&#34; alt=&#34;Box art of Super Mega Lucky Box&#34; loading=&#34;lazy&#34;/&gt;
    &lt;/div&gt;
    &lt;p&gt;3 plays&lt;/p&gt;
  &lt;/li&gt;
  &lt;li class=&#34;raised-box&#34;&gt;
    &lt;div class=&#34;image-container&#34; title=&#34;Cthulhu Fluxx (2012)&#34;&gt;
      &lt;img class=&#34;background&#34; src=&#34;https://chameth.com/boardgames/122159/image.jpg&#34; aria-hidden=&#34;true&#34; loading=&#34;lazy&#34;/&gt;
      &lt;img class=&#34;foreground&#34; src=&#34;https://chameth.com/boardgames/122159/image.jpg&#34; alt=&#34;Box art of Cthulhu Fluxx&#34; loading=&#34;lazy&#34;/&gt;
    &lt;/div&gt;
    &lt;p&gt;2 plays&lt;/p&gt;
  &lt;/li&gt;
  &lt;li class=&#34;raised-box&#34;&gt;
    &lt;div class=&#34;image-container&#34; title=&#34;Obsession (2018)&#34;&gt;
      &lt;img class=&#34;background&#34; src=&#34;https://chameth.com/boardgames/231733/image.png&#34; aria-hidden=&#34;true&#34; loading=&#34;lazy&#34;/&gt;
      &lt;img class=&#34;foreground&#34; src=&#34;https://chameth.com/boardgames/231733/image.png&#34; alt=&#34;Box art of Obsession&#34; loading=&#34;lazy&#34;/&gt;
    &lt;/div&gt;
    &lt;p&gt;2 plays&lt;/p&gt;
  &lt;/li&gt;
  &lt;li class=&#34;raised-box&#34;&gt;
    &lt;div class=&#34;image-container&#34; title=&#34;Plant-Based Riot (2022)&#34;&gt;
      &lt;img class=&#34;background&#34; src=&#34;https://chameth.com/boardgames/342940/image.jpg&#34; aria-hidden=&#34;true&#34; loading=&#34;lazy&#34;/&gt;
      &lt;img class=&#34;foreground&#34; src=&#34;https://chameth.com/boardgames/342940/image.jpg&#34; alt=&#34;Box art of Plant-Based Riot&#34; loading=&#34;lazy&#34;/&gt;
    &lt;/div&gt;
    &lt;p&gt;2 plays&lt;/p&gt;
  &lt;/li&gt;
  &lt;li class=&#34;raised-box&#34;&gt;
    &lt;div class=&#34;image-container&#34; title=&#34;Space Base (2018)&#34;&gt;
      &lt;img class=&#34;background&#34; src=&#34;https://chameth.com/boardgames/242302/image.jpg&#34; aria-hidden=&#34;true&#34; loading=&#34;lazy&#34;/&gt;
      &lt;img class=&#34;foreground&#34; src=&#34;https://chameth.com/boardgames/242302/image.jpg&#34; alt=&#34;Box art of Space Base&#34; loading=&#34;lazy&#34;/&gt;
    &lt;/div&gt;
    &lt;p&gt;2 plays&lt;/p&gt;
  &lt;/li&gt;
  &lt;li class=&#34;raised-box&#34;&gt;
    &lt;div class=&#34;image-container&#34; title=&#34;Terraforming Mars (2016)&#34;&gt;
      &lt;img class=&#34;background&#34; src=&#34;https://chameth.com/boardgames/167791/image.jpg&#34; aria-hidden=&#34;true&#34; loading=&#34;lazy&#34;/&gt;
      &lt;img class=&#34;foreground&#34; src=&#34;https://chameth.com/boardgames/167791/image.jpg&#34; alt=&#34;Box art of Terraforming Mars&#34; loading=&#34;lazy&#34;/&gt;
    &lt;/div&gt;
    &lt;p&gt;2 plays&lt;/p&gt;
  &lt;/li&gt;
  &lt;li class=&#34;raised-box&#34;&gt;
    &lt;div class=&#34;image-container&#34; title=&#34;Next Station: Tokyo (2023)&#34;&gt;
      &lt;img class=&#34;background&#34; src=&#34;https://chameth.com/boardgames/380165/image.jpg&#34; aria-hidden=&#34;true&#34; loading=&#34;lazy&#34;/&gt;
      &lt;img class=&#34;foreground&#34; src=&#34;https://chameth.com/boardgames/380165/image.jpg&#34; alt=&#34;Box art of Next Station: Tokyo&#34; loading=&#34;lazy&#34;/&gt;
    &lt;/div&gt;
    &lt;p&gt;1 play&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;around-the-web&#34;&gt;Around the web&lt;/h3&gt;
&lt;h4 id=&#34;warranty-void-if-regeneratedhttpsnearzerosoftwarepwarranty-void-if-regenerated&#34;&gt;&lt;a href=&#34;https://nearzero.software/p/warranty-void-if-regenerated&#34;&gt;Warranty Void if Regenerated&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Some really cool speculative fiction about a future where all software is written by LLMs, and the social effects that may have. Unfortunately the story itself is written with an LLM, and that becomes painfully obvious about a third of the way in. By that point I was already hooked, though.&lt;/p&gt;
&lt;h4 id=&#34;my-2-step-process-for-ai-free-blogginghttpsmanuelmorealecomthoughtsmy-2-step-process-for-ai-free-blogging&#34;&gt;&lt;a href=&#34;https://manuelmoreale.com/thoughts/my-2-step-process-for-ai-free-blogging&#34;&gt;My 2-step process for AI-free blogging&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Speaking of LLM-generated text, this short piece by Manuel Moreale made me laugh. I’m by no means anti-LLM, but I really hate people publishing the output like it’s their own work. It breaks the social assumption that the writer put more effort in than the reader, and makes me wonder how much is hallucinated.&lt;/p&gt;
&lt;h4 id=&#34;25-years-of-eggshttpswwwjohn-rushcompostseggs-25-years-20260219html&#34;&gt;&lt;a href=&#34;https://www.john-rush.com/posts/eggs-25-years-20260219.html&#34;&gt;25 Years of Eggs&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;An interesting applied use of coding agents. I’m a sucker for personal statistics. I have no explicit desire to keep my receipts, or generate graphs of my egg consumption, but seeing the graphs makes me question that. The journey to get there is a great read, too.&lt;/p&gt;
&lt;h4 id=&#34;i-made-a-one-page-notebookhttpsjoelchronoxyzblogi-made-a-one-page-notebook&#34;&gt;&lt;a href=&#34;https://joelchrono.xyz/blog/i-made-a-one-page-notebook/&#34;&gt;I made a one-page notebook&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;The clever paper folding is interesting, but I really like some of the small touches on the page. There’s a nice little “in reply to” callout providing context at the top of the page, you can click to rotate the image further down, and there’s a details element containing a textual version of it. It has that great ‘small web’ vibe of someone who really cares about what they’re doing.&lt;/p&gt;
&lt;h4 id=&#34;workers-who-love-synergizing-paradigms-might-be-bad-at-their-jobshttpsnewscornelledustories202603workers-who-love-synergizing-paradigms-might-be-bad-their-jobs&#34;&gt;&lt;a href=&#34;https://news.cornell.edu/stories/2026/03/workers-who-love-synergizing-paradigms-might-be-bad-their-jobs&#34;&gt;Workers who love ‘synergizing paradigms’ might be bad at their jobs&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;I haven’t read the full paper, but the summary given by the article is equal parts interesting and amusing. They developed a “Corporate Bullshit Receptivity Scale”, what more is there to say?&lt;/p&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr/&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;It’s meant to resemble painter’s tape, but I’m not sure quite how well it works. &lt;a class=&#34;footnote-backref&#34; href=&#34;#fnref:1&#34; role=&#34;doc-backlink&#34;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
</content>
    </entry>
    <entry>
        <title>Modern CSS is fun</title>
        <link href="https://chameth.com/modern-css-is-fun/"/>
        <updated>2026-03-17T00:00:00Z</updated>
        <id>https://chameth.com/modern-css-is-fun/</id>
        <content xml:lang="en" type="html">&lt;p&gt;I’ve been doing a bunch of CSS tweaking recently, and keep being surprised by how nice modern CSS is to work with. As someone grey-haired enough to remember writing HTML &lt;em&gt;without&lt;/em&gt; CSS, it’s amazing to think how far along web technology has come&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a class=&#34;footnote-ref&#34; href=&#34;#fn:1&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;. I wanted to demonstrate some of the handy bits and pieces I’ve used recently.&lt;/p&gt;
&lt;h3 id=&#34;has&#34;&gt;:has&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;:has&lt;/code&gt; selector allows you to effectively query for child elements. While &lt;code&gt;a span&lt;/code&gt; will match a &lt;code&gt;&amp;lt;span&amp;gt;&lt;/code&gt; within an &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt;, &lt;code&gt;a:has(span)&lt;/code&gt; will match an &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; that contains a &lt;code&gt;&amp;lt;span&amp;gt;&lt;/code&gt;. This really shines when combined with more complex selectors, for example:&lt;/p&gt;
&lt;pre class=&#34;chroma-chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-nt&#34;&gt;input&lt;/span&gt; &lt;span class=&#34;chroma-p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;    &lt;span class=&#34;chroma-na&#34;&gt;border-radius&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;chroma-nf&#34;&gt;var&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;--&lt;/span&gt;&lt;span class=&#34;chroma-n&#34;&gt;border-radius&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;    &lt;span class=&#34;chroma-k&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;chroma-nd&#34;&gt;:has&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;(+&lt;/span&gt; &lt;span class=&#34;chroma-nc&#34;&gt;.results&lt;/span&gt;&lt;span class=&#34;chroma-nd&#34;&gt;:not&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;chroma-nd&#34;&gt;:empty&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;))&lt;/span&gt; &lt;span class=&#34;chroma-p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;        &lt;span class=&#34;chroma-na&#34;&gt;border-bottom-left-radius&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;chroma-mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;        &lt;span class=&#34;chroma-na&#34;&gt;border-bottom-right-radius&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;chroma-mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;    &lt;span class=&#34;chroma-p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This is part of the styling for my film search box. When the search box is presented initially it has fully rounded corners. But when it is followed by a non-empty results element, it removes the rounding on the bottom corners so that the border continues in a straight line down into the results. You can see it in action below; just enter a few characters (like “the”) to get some results:&lt;/p&gt;
&lt;div data-form-search=&#34;&#34;&gt;
    &lt;noscript&gt;JavaScript required for film search&lt;/noscript&gt;
&lt;/div&gt;
&lt;p&gt;You could achieve the same effect by having JavaScript add a class to the &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt; element, but I’ll take a CSS solution over a JavaScript solution any day.&lt;/p&gt;
&lt;h3 id=&#34;nested-rules&#34;&gt;Nested rules&lt;/h3&gt;
&lt;p&gt;You probably spotted this in the example above. It’s what finally made me switch from &lt;a href=&#34;https://sass-lang.com/&#34;&gt;SCSS&lt;/a&gt; to plain CSS. If you have a rule for &lt;code&gt;.foo&lt;/code&gt; and a rule for &lt;code&gt;.foo .bar&lt;/code&gt; you can just nest them. Not only does it save repeating yourself an awful lot, it keeps everything organised nicely. For example, the CSS for my film list embeds looks like this:&lt;/p&gt;
&lt;pre class=&#34;chroma-chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-nt&#34;&gt;a&lt;/span&gt;&lt;span class=&#34;chroma-nc&#34;&gt;.film-list&lt;/span&gt; &lt;span class=&#34;chroma-p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;    &lt;span class=&#34;chroma-na&#34;&gt;display&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;chroma-ni&#34;&gt;grid&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;    &lt;span class=&#34;chroma-na&#34;&gt;grid-template-areas&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;chroma-s2&#34;&gt;&amp;#34;header images&amp;#34;&lt;/span&gt; &lt;span class=&#34;chroma-s2&#34;&gt;&amp;#34;description images&amp;#34;&lt;/span&gt; &lt;span class=&#34;chroma-s2&#34;&gt;&amp;#34;meta images&amp;#34;&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;    &lt;span class=&#34;chroma-na&#34;&gt;grid-template-columns&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;chroma-mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;chroma-kt&#34;&gt;fr&lt;/span&gt; &lt;span class=&#34;chroma-ni&#34;&gt;auto&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;    &lt;span class=&#34;chroma-na&#34;&gt;grid-template-rows&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;chroma-ni&#34;&gt;auto&lt;/span&gt; &lt;span class=&#34;chroma-mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;chroma-kt&#34;&gt;fr&lt;/span&gt; &lt;span class=&#34;chroma-ni&#34;&gt;auto&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;    &lt;span class=&#34;chroma-na&#34;&gt;row-gap&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;chroma-nf&#34;&gt;var&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;--&lt;/span&gt;&lt;span class=&#34;chroma-ni&#34;&gt;small&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;-&lt;/span&gt;&lt;span class=&#34;chroma-ni&#34;&gt;space&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;    &lt;span class=&#34;chroma-k&#34;&gt;@media&lt;/span&gt; &lt;span class=&#34;chroma-p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;chroma-ni&#34;&gt;width&lt;/span&gt; &lt;span class=&#34;chroma-o&#34;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&#34;chroma-mi&#34;&gt;800&lt;/span&gt;&lt;span class=&#34;chroma-kt&#34;&gt;px&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;chroma-p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;        &lt;span class=&#34;chroma-na&#34;&gt;grid-template-areas&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;chroma-s2&#34;&gt;&amp;#34;header&amp;#34;&lt;/span&gt; &lt;span class=&#34;chroma-s2&#34;&gt;&amp;#34;description&amp;#34;&lt;/span&gt; &lt;span class=&#34;chroma-s2&#34;&gt;&amp;#34;images&amp;#34;&lt;/span&gt; &lt;span class=&#34;chroma-s2&#34;&gt;&amp;#34;meta&amp;#34;&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;        &lt;span class=&#34;chroma-na&#34;&gt;grid-template-columns&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;chroma-mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;chroma-kt&#34;&gt;fr&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;        &lt;span class=&#34;chroma-na&#34;&gt;grid-template-rows&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;chroma-ni&#34;&gt;auto&lt;/span&gt; &lt;span class=&#34;chroma-ni&#34;&gt;auto&lt;/span&gt; &lt;span class=&#34;chroma-ni&#34;&gt;auto&lt;/span&gt; &lt;span class=&#34;chroma-ni&#34;&gt;auto&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;    &lt;span class=&#34;chroma-p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;    &lt;span class=&#34;chroma-k&#34;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&#34;chroma-o&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;chroma-k&#34;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&#34;chroma-p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;        &lt;span class=&#34;chroma-na&#34;&gt;margin-top&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;chroma-nf&#34;&gt;var&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;--&lt;/span&gt;&lt;span class=&#34;chroma-ni&#34;&gt;medium&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;-&lt;/span&gt;&lt;span class=&#34;chroma-ni&#34;&gt;space&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;    &lt;span class=&#34;chroma-p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;    &lt;span class=&#34;chroma-nt&#34;&gt;h3&lt;/span&gt; &lt;span class=&#34;chroma-p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;        &lt;span class=&#34;chroma-na&#34;&gt;font-size&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;chroma-nf&#34;&gt;var&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;--&lt;/span&gt;&lt;span class=&#34;chroma-n&#34;&gt;font-size-xxlarge&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;    &lt;span class=&#34;chroma-p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;    &lt;span class=&#34;chroma-nc&#34;&gt;.description&lt;/span&gt; &lt;span class=&#34;chroma-p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;        &lt;span class=&#34;chroma-cm&#34;&gt;/* ... */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;    &lt;span class=&#34;chroma-p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Everything is wrapped up in a single bundle, including the &lt;code&gt;@media&lt;/code&gt; rules for changing the layout on smaller screens, the lovely little &lt;code&gt;&amp;amp; + &amp;amp;&lt;/code&gt; rule that adds some extra margin if there are two lists in a row, etc. The &lt;code&gt;&amp;amp;&lt;/code&gt; syntax refers to the parent selector, so &lt;code&gt;&amp;amp; + &amp;amp;&lt;/code&gt; in this case is the same as &lt;code&gt;a.film-list + a.film-list&lt;/code&gt;: it’s very handy! The best part about this is that it’s the exact same syntax as SCSS, so converting is easy&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a class=&#34;footnote-ref&#34; href=&#34;#fn:2&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;. The styled element looks like this:&lt;/p&gt;
&lt;a class=&#34;film-list raised-box plain-link&#34; data-title=&#34;Film list&#34; href=&#34;https://chameth.com/films/lists/ranking/&#34;&gt;
  &lt;h3 class=&#34;plain-header&#34;&gt;Watched films ranked&lt;/h3&gt;
  &lt;div class=&#34;poster-stack&#34;&gt;
    &lt;img src=&#34;https://chameth.com/films/265/poster.jpg&#34; alt=&#34;Poster of Midsommar&#34;/&gt;&lt;img src=&#34;https://chameth.com/films/4/poster.jpg&#34; alt=&#34;Poster of Easy A&#34;/&gt;&lt;img src=&#34;https://chameth.com/films/97/poster.jpg&#34; alt=&#34;Poster of Hackers&#34;/&gt;&lt;img src=&#34;https://chameth.com/films/25/poster.jpg&#34; alt=&#34;Poster of The Matrix&#34;/&gt;&lt;img src=&#34;https://chameth.com/films/202/poster.jpg&#34; alt=&#34;Poster of Sinners&#34;/&gt;
  &lt;/div&gt;
  &lt;div class=&#34;description&#34;&gt;&lt;p&gt;Every film I’ve watched since I started logging, ranked.&lt;/p&gt;
&lt;p&gt;Obviously super subjective, and subject to change often and arbitrarily.&lt;/p&gt;
&lt;/div&gt;
  &lt;p class=&#34;count&#34;&gt;264 films&lt;/p&gt;
&lt;/a&gt;
&lt;h3 id=&#34;media-range-syntax&#34;&gt;Media range syntax&lt;/h3&gt;
&lt;p&gt;Once again, you might have spotted this in the previous snippet. I’m not deliberately teasing things, I promise! Back in the day, you did media queries like so:&lt;/p&gt;
&lt;pre class=&#34;chroma-chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-k&#34;&gt;@media&lt;/span&gt; &lt;span class=&#34;chroma-p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;chroma-na&#34;&gt;min-width&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;chroma-mi&#34;&gt;1000&lt;/span&gt;&lt;span class=&#34;chroma-kt&#34;&gt;px&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;chroma-ow&#34;&gt;and&lt;/span&gt; &lt;span class=&#34;chroma-p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;chroma-na&#34;&gt;max-width&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;chroma-mi&#34;&gt;2000&lt;/span&gt;&lt;span class=&#34;chroma-kt&#34;&gt;px&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;chroma-p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;    &lt;span class=&#34;chroma-cm&#34;&gt;/* some rules that work for screens at least 1000px wide and at most 2000px wide */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I always hated this syntax. I always got muddled up as to whether I wanted “min” or “max”, and whether they were inclusive or not. It’s easy to reason through,
but it never came naturally. Fortunately you can now just use ranges:&lt;/p&gt;
&lt;pre class=&#34;chroma-chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-k&#34;&gt;@media&lt;/span&gt; &lt;span class=&#34;chroma-p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;chroma-mi&#34;&gt;1000&lt;/span&gt;&lt;span class=&#34;chroma-kt&#34;&gt;px&lt;/span&gt; &lt;span class=&#34;chroma-o&#34;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&#34;chroma-ni&#34;&gt;width&lt;/span&gt; &lt;span class=&#34;chroma-o&#34;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&#34;chroma-mi&#34;&gt;2000&lt;/span&gt;&lt;span class=&#34;chroma-kt&#34;&gt;px&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;chroma-p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;    &lt;span class=&#34;chroma-cm&#34;&gt;/* some rules that work for screens at least 1000px wide and at most 2000px wide */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I find this style so much easier to write and understand.&lt;/p&gt;
&lt;h3 id=&#34;anchor-positioning&#34;&gt;Anchor positioning&lt;/h3&gt;
&lt;p&gt;Trying to dynamically position one element next to another used to exclusively fall within the purview of JavaScript. Fortunately anchor positioning
fixes all this. I use this in the film search field I showed above:&lt;/p&gt;
&lt;pre class=&#34;chroma-chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-nc&#34;&gt;.film-search&lt;/span&gt; &lt;span class=&#34;chroma-p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;    &lt;span class=&#34;chroma-nt&#34;&gt;input&lt;/span&gt; &lt;span class=&#34;chroma-p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;        &lt;span class=&#34;chroma-na&#34;&gt;anchor-name&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;chroma-o&#34;&gt;--&lt;/span&gt;&lt;span class=&#34;chroma-n&#34;&gt;film-search-box&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;    &lt;span class=&#34;chroma-p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;    &lt;span class=&#34;chroma-nc&#34;&gt;.results&lt;/span&gt; &lt;span class=&#34;chroma-p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;        &lt;span class=&#34;chroma-na&#34;&gt;position&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;chroma-ni&#34;&gt;absolute&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;        &lt;span class=&#34;chroma-na&#34;&gt;position-anchor&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;chroma-o&#34;&gt;--&lt;/span&gt;&lt;span class=&#34;chroma-n&#34;&gt;film-search-box&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;        &lt;span class=&#34;chroma-na&#34;&gt;position-area&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;chroma-ni&#34;&gt;bottom&lt;/span&gt; &lt;span class=&#34;chroma-ni&#34;&gt;center&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;        &lt;span class=&#34;chroma-na&#34;&gt;position-visibility&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;chroma-ni&#34;&gt;always&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;        &lt;span class=&#34;chroma-na&#34;&gt;width&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;chroma-nf&#34;&gt;anchor-size&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;    &lt;span class=&#34;chroma-p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This does lots of fun things. The &lt;code&gt;.results&lt;/code&gt; element is anchored to the &lt;code&gt;input&lt;/code&gt; field (via the &lt;code&gt;--film-search-box&lt;/code&gt; name), and it’s positioned on the bottom,
directly under the anchor. The &lt;code&gt;position-area&lt;/code&gt; rule describes a 3x3 grid, with the anchor at the center, so &lt;code&gt;top left&lt;/code&gt; would put it diagonally adjacent to
the anchor. Next, &lt;code&gt;position-visibility&lt;/code&gt; keeps the &lt;code&gt;.results&lt;/code&gt; element visible when the anchor &lt;em&gt;isn’t&lt;/em&gt;; otherwise when you scroll the anchor off the screen
the results would immediately vanish. Finally, the special &lt;code&gt;anchor-size()&lt;/code&gt; function makes the &lt;code&gt;.results&lt;/code&gt; element take up the same exact width as the anchor.&lt;/p&gt;
&lt;p&gt;All-in-all, this puts the results exactly where I want them, without having to deal with any JavaScript at all. I can see how it’d also be amazingly useful
trying to do tooltips or other forms of ‘floating’ content too. You can even leave the positioning up to the browser, giving it hints about which order to
try, or what property to optimise for (e.g. you can say “anchor to the left or right, whichever has more horizontal space”).&lt;/p&gt;
&lt;h3 id=&#34;attr-function&#34;&gt;attr() function&lt;/h3&gt;
&lt;p&gt;This one blew my mind a little. In some of the elements on the site, I’ve added a little label to the top. It looks something like this:&lt;/p&gt;
&lt;figure class=&#34;rating-distribution raised-box&#34; data-title=&#34;Film rating distribution&#34;&gt;
 &lt;div class=&#34;chart-container&#34;&gt;
  &lt;span class=&#34;left-label&#34;&gt;
&lt;span class=&#34;star-rating&#34;&gt;&lt;img src=&#34;https://chameth.com/star-half.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Half star&#34;/&gt;&lt;img src=&#34;https://chameth.com/star-empty.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Empty star&#34;/&gt;&lt;img src=&#34;https://chameth.com/star-empty.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Empty star&#34;/&gt;&lt;img src=&#34;https://chameth.com/star-empty.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Empty star&#34;/&gt;&lt;img src=&#34;https://chameth.com/star-empty.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Empty star&#34;/&gt;&lt;/span&gt;
&lt;/span&gt;
  &lt;svg width=&#34;200&#34; height=&#34;50&#34; viewBox=&#34;0 0 200 50&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34; role=&#34;img&#34; aria-label=&#34;Film rating distribution chart&#34;&gt;
   &lt;rect x=&#34;1&#34; y=&#34;37&#34; width=&#34;18&#34; height=&#34;13&#34; fill=&#34;var(--accent-colour)&#34; class=&#34;rating-bar&#34;&gt;
    &lt;title&gt;0.5 stars: 11 films&lt;/title&gt;
   &lt;/rect&gt;&lt;rect x=&#34;21&#34; y=&#34;29&#34; width=&#34;18&#34; height=&#34;21&#34; fill=&#34;var(--accent-colour)&#34; class=&#34;rating-bar&#34;&gt;
    &lt;title&gt;1.0 stars: 18 films&lt;/title&gt;
   &lt;/rect&gt;&lt;rect x=&#34;41&#34; y=&#34;33&#34; width=&#34;18&#34; height=&#34;17&#34; fill=&#34;var(--accent-colour)&#34; class=&#34;rating-bar&#34;&gt;
    &lt;title&gt;1.5 stars: 14 films&lt;/title&gt;
   &lt;/rect&gt;&lt;rect x=&#34;61&#34; y=&#34;31&#34; width=&#34;18&#34; height=&#34;19&#34; fill=&#34;var(--accent-colour)&#34; class=&#34;rating-bar&#34;&gt;
    &lt;title&gt;2.0 stars: 16 films&lt;/title&gt;
   &lt;/rect&gt;&lt;rect x=&#34;81&#34; y=&#34;12&#34; width=&#34;18&#34; height=&#34;38&#34; fill=&#34;var(--accent-colour)&#34; class=&#34;rating-bar&#34;&gt;
    &lt;title&gt;2.5 stars: 32 films&lt;/title&gt;
   &lt;/rect&gt;&lt;rect x=&#34;101&#34; y=&#34;13&#34; width=&#34;18&#34; height=&#34;37&#34; fill=&#34;var(--accent-colour)&#34; class=&#34;rating-bar&#34;&gt;
    &lt;title&gt;3.0 stars: 31 films&lt;/title&gt;
   &lt;/rect&gt;&lt;rect x=&#34;121&#34; y=&#34;7&#34; width=&#34;18&#34; height=&#34;43&#34; fill=&#34;var(--accent-colour)&#34; class=&#34;rating-bar&#34;&gt;
    &lt;title&gt;3.5 stars: 36 films&lt;/title&gt;
   &lt;/rect&gt;&lt;rect x=&#34;141&#34; y=&#34;0&#34; width=&#34;18&#34; height=&#34;50&#34; fill=&#34;var(--accent-colour)&#34; class=&#34;rating-bar&#34;&gt;
    &lt;title&gt;4.0 stars: 42 films&lt;/title&gt;
   &lt;/rect&gt;&lt;rect x=&#34;161&#34; y=&#34;19&#34; width=&#34;18&#34; height=&#34;31&#34; fill=&#34;var(--accent-colour)&#34; class=&#34;rating-bar&#34;&gt;
    &lt;title&gt;4.5 stars: 26 films&lt;/title&gt;
   &lt;/rect&gt;&lt;rect x=&#34;181&#34; y=&#34;4&#34; width=&#34;18&#34; height=&#34;46&#34; fill=&#34;var(--accent-colour)&#34; class=&#34;rating-bar&#34;&gt;
    &lt;title&gt;5.0 stars: 39 films&lt;/title&gt;
   &lt;/rect&gt;
  &lt;/svg&gt;
  &lt;span class=&#34;right-label&#34;&gt;
&lt;span class=&#34;star-rating&#34;&gt;&lt;img src=&#34;https://chameth.com/star-flat.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Full star&#34; class=&#34;rot-1&#34;/&gt;&lt;img src=&#34;https://chameth.com/star-flat.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Full star&#34; class=&#34;rot-1&#34;/&gt;&lt;img src=&#34;https://chameth.com/star-flat.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Full star&#34; class=&#34;rot-2&#34;/&gt;&lt;img src=&#34;https://chameth.com/star-flat.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Full star&#34; class=&#34;rot-2&#34;/&gt;&lt;img src=&#34;https://chameth.com/star-flat.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Full star&#34; class=&#34;rot-0&#34;/&gt;&lt;/span&gt;
&lt;/span&gt;
 &lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;It’s meant to just be a visual flourish, not a semantic title or anything, so I initially just added a &lt;code&gt;::before&lt;/code&gt; selector to each element, customising the
&lt;code&gt;content&lt;/code&gt; to have the right value. I didn’t like the duplication, though. Now, instead, I do this:&lt;/p&gt;
&lt;pre class=&#34;chroma-chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-k&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;chroma-nd&#34;&gt;::before&lt;/span&gt; &lt;span class=&#34;chroma-p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;    &lt;span class=&#34;chroma-na&#34;&gt;content&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;chroma-nf&#34;&gt;attr&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;chroma-n&#34;&gt;data-title&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Each component defines its title in a data attribute: &lt;code&gt;&amp;lt;div class=&amp;#34;raised-box&amp;#34; data-title=&amp;#34;an example&amp;#34;&amp;gt;&lt;/code&gt;, and the &lt;code&gt;attr&lt;/code&gt; function plucks it out and
puts it in the content rule. It’s worth noting that this is only widely supported for the &lt;code&gt;content&lt;/code&gt; rule, you can’t yet use it for colours or dimensions
or other things. It still feels a bit magical, though. Like you’re making a reusable, customisable component with just HTML and CSS.&lt;/p&gt;
&lt;h3 id=&#34;layers&#34;&gt;layers&lt;/h3&gt;
&lt;p&gt;I’d come across layers a few times, but I never found a need for them. It seemed like something you’d only really need with complicated design systems,
or something. Then I had a problem and layers were the perfect solution!&lt;/p&gt;
&lt;p&gt;The headings on this site have a whole bunch of CSS attached to them. Currently they look a bit like blue painter’s tape with handwriting on them.
This style is applied to all headers, and then the various places that &lt;em&gt;don’t&lt;/em&gt; want it had to manually reset everything. I got fed up with that, so
decided to add a &lt;code&gt;plain-header&lt;/code&gt; class which would “disable” the extra styling:&lt;/p&gt;
&lt;pre class=&#34;chroma-chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-nt&#34;&gt;h2&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-nt&#34;&gt;h3&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-nt&#34;&gt;h4&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-nt&#34;&gt;h5&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-nt&#34;&gt;h6&lt;/span&gt; &lt;span class=&#34;chroma-p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;    &lt;span class=&#34;chroma-cm&#34;&gt;/* common styles for all headers, regardless */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;    &lt;span class=&#34;chroma-na&#34;&gt;font-family&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;chroma-s2&#34;&gt;&amp;#34;Chris Hand&amp;#34;&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;chroma-ni&#34;&gt;sans-serif&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;    &lt;span class=&#34;chroma-na&#34;&gt;font-size&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;chroma-nf&#34;&gt;var&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;--&lt;/span&gt;&lt;span class=&#34;chroma-n&#34;&gt;font-size-xxlarge&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;    &lt;span class=&#34;chroma-na&#34;&gt;color&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;chroma-nf&#34;&gt;var&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;--&lt;/span&gt;&lt;span class=&#34;chroma-ni&#34;&gt;text&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;-&lt;/span&gt;&lt;span class=&#34;chroma-n&#34;&gt;highlight-colour&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;    &lt;span class=&#34;chroma-na&#34;&gt;margin&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;chroma-nf&#34;&gt;var&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;--&lt;/span&gt;&lt;span class=&#34;chroma-ni&#34;&gt;medium&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;-&lt;/span&gt;&lt;span class=&#34;chroma-ni&#34;&gt;space&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;chroma-mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;    &lt;span class=&#34;chroma-na&#34;&gt;line-height&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;chroma-mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;    &lt;span class=&#34;chroma-k&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;chroma-nd&#34;&gt;:not&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;chroma-nc&#34;&gt;.plain-header&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;chroma-p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;        &lt;span class=&#34;chroma-cm&#34;&gt;/* do fancy stuff if it _doesn&amp;#39;t_ have the .plain-header class */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;        &lt;span class=&#34;chroma-na&#34;&gt;transform&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;chroma-nf&#34;&gt;rotate&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;-&lt;/span&gt;&lt;span class=&#34;chroma-mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;chroma-mf&#34;&gt;.15&lt;/span&gt;&lt;span class=&#34;chroma-kt&#34;&gt;deg&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;        &lt;span class=&#34;chroma-na&#34;&gt;padding&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;chroma-nf&#34;&gt;var&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;--&lt;/span&gt;&lt;span class=&#34;chroma-n&#34;&gt;smedium-space&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;        &lt;span class=&#34;chroma-na&#34;&gt;font-weight&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;chroma-mi&#34;&gt;800&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;        &lt;span class=&#34;chroma-na&#34;&gt;filter&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;chroma-nf&#34;&gt;drop-shadow&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;chroma-mi&#34;&gt;3&lt;/span&gt;&lt;span class=&#34;chroma-kt&#34;&gt;px&lt;/span&gt; &lt;span class=&#34;chroma-mi&#34;&gt;3&lt;/span&gt;&lt;span class=&#34;chroma-kt&#34;&gt;px&lt;/span&gt; &lt;span class=&#34;chroma-mi&#34;&gt;5&lt;/span&gt;&lt;span class=&#34;chroma-kt&#34;&gt;px&lt;/span&gt; &lt;span class=&#34;chroma-nf&#34;&gt;rgb&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;chroma-mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;chroma-mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;chroma-mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;));&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;        &lt;span class=&#34;chroma-nt&#34;&gt;a&lt;/span&gt; &lt;span class=&#34;chroma-p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;            &lt;span class=&#34;chroma-na&#34;&gt;background&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;chroma-ni&#34;&gt;none&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;            &lt;span class=&#34;chroma-na&#34;&gt;text-decoration-skip-ink&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;chroma-ni&#34;&gt;auto&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;        &lt;span class=&#34;chroma-p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;    &lt;span class=&#34;chroma-p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This might seem a bit backwards but I want the default to be styled, as those are far more common. I added this change,
sprinkled around some “plain-header” classes, tidied up a lot of duplicate CSS, and things mostly worked. Mostly.
Some things stopped working. In a few places the headers had slight tweaks, and they stopped working entirely.&lt;/p&gt;
&lt;p&gt;It turns out in moving the rules from a plain &lt;code&gt;h2&lt;/code&gt; selector to a &lt;code&gt;h2:not(.plain-header)&lt;/code&gt;, I’d made them more specific.
Previously a selector like &lt;code&gt;h2.special&lt;/code&gt; would have been more specific, so its properties would override those from the
less specific &lt;code&gt;h2&lt;/code&gt; selector. My first reaction was to try and hack around it. Changing all the overrides to
&lt;code&gt;body h2.special&lt;/code&gt; would make them more specific again, but I didn’t want to have to remember to do that forever more.&lt;/p&gt;
&lt;p&gt;Instead, I defined some custom layers, and put the header definitions in one:&lt;/p&gt;
&lt;pre class=&#34;chroma-chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-k&#34;&gt;@layer&lt;/span&gt; &lt;span class=&#34;chroma-nt&#34;&gt;reset&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;chroma-nt&#34;&gt;links&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;chroma-nt&#34;&gt;headings&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-o&#34;&gt;@&lt;/span&gt;&lt;span class=&#34;chroma-nt&#34;&gt;layer&lt;/span&gt; &lt;span class=&#34;chroma-nt&#34;&gt;headings&lt;/span&gt; &lt;span class=&#34;chroma-p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;    &lt;span class=&#34;chroma-nt&#34;&gt;h2&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;    &lt;span class=&#34;chroma-nt&#34;&gt;h3&lt;/span&gt; &lt;span class=&#34;chroma-p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;        &lt;span class=&#34;chroma-cm&#34;&gt;/* etc */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;    &lt;span class=&#34;chroma-p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The first &lt;code&gt;@layer&lt;/code&gt; rule defines some layers that will be handled in a specific order: first the “reset” layer, then
the “links” layer, then the “headings” layer, and then anything not in a layer after that. Rules in one layer don’t
have to worry about beating the specificity of rules in another layer, because they’re handled separately.&lt;/p&gt;
&lt;p&gt;Note that I couldn’t just put the headings in a layer and call it a day: their rules have to come after the CSS reset,
and after the normal link styles, as headers have a bit of extra styling for nested links. If the CSS reset weren’t in
a layer, then the &lt;code&gt;* { margin: 0; padding: 0; }&lt;/code&gt; type reset would apply over the rules from the headings layer.&lt;/p&gt;
&lt;p&gt;Using layers is definitely a bit fiddly, and is not necessary for a lot of sites, but it’s a much cleaner alternative
to ugly specificity hacking.&lt;/p&gt;
&lt;h3 id=&#34;things-im-looking-forward-to&#34;&gt;Things I’m looking forward to&lt;/h3&gt;
&lt;p&gt;There are a few things that aren’t (widely) available yet, that particularly interest me:&lt;/p&gt;
&lt;h4 id=&#34;sibling-index-and-sibling-count&#34;&gt;sibling-index() and sibling-count()&lt;/h4&gt;
&lt;p&gt;These new functions are available in WebKit and Blink based browsers, but not Firefox. They give you the index of
the element within its siblings, or the count of siblings. For the film list component above, I currently have
this abomination for the overlapping posters:&lt;/p&gt;
&lt;pre class=&#34;chroma-chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-k&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;chroma-nd&#34;&gt;:nth-child&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;chroma-nt&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;chroma-p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;    &lt;span class=&#34;chroma-na&#34;&gt;z-index&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;chroma-mi&#34;&gt;5&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;    &lt;span class=&#34;chroma-na&#34;&gt;left&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;chroma-mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-k&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;chroma-nd&#34;&gt;:nth-child&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;chroma-nt&#34;&gt;2&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;chroma-p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;    &lt;span class=&#34;chroma-na&#34;&gt;z-index&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;chroma-mi&#34;&gt;4&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;    &lt;span class=&#34;chroma-na&#34;&gt;left&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;chroma-nf&#34;&gt;calc&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;chroma-mi&#34;&gt;133&lt;/span&gt;&lt;span class=&#34;chroma-kt&#34;&gt;px&lt;/span&gt; &lt;span class=&#34;chroma-o&#34;&gt;*&lt;/span&gt; &lt;span class=&#34;chroma-nf&#34;&gt;var&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;--&lt;/span&gt;&lt;span class=&#34;chroma-n&#34;&gt;overlap&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;chroma-o&#34;&gt;*&lt;/span&gt; &lt;span class=&#34;chroma-mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-k&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;chroma-nd&#34;&gt;:nth-child&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;chroma-nt&#34;&gt;3&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;chroma-p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;    &lt;span class=&#34;chroma-na&#34;&gt;z-index&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;chroma-mi&#34;&gt;3&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;    &lt;span class=&#34;chroma-na&#34;&gt;left&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;chroma-nf&#34;&gt;calc&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;chroma-mi&#34;&gt;133&lt;/span&gt;&lt;span class=&#34;chroma-kt&#34;&gt;px&lt;/span&gt; &lt;span class=&#34;chroma-o&#34;&gt;*&lt;/span&gt; &lt;span class=&#34;chroma-nf&#34;&gt;var&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;--&lt;/span&gt;&lt;span class=&#34;chroma-n&#34;&gt;overlap&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;chroma-o&#34;&gt;*&lt;/span&gt; &lt;span class=&#34;chroma-mi&#34;&gt;2&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-k&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;chroma-nd&#34;&gt;:nth-child&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;chroma-nt&#34;&gt;4&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;chroma-p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;    &lt;span class=&#34;chroma-na&#34;&gt;z-index&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;chroma-mi&#34;&gt;2&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;    &lt;span class=&#34;chroma-na&#34;&gt;left&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;chroma-nf&#34;&gt;calc&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;chroma-mi&#34;&gt;133&lt;/span&gt;&lt;span class=&#34;chroma-kt&#34;&gt;px&lt;/span&gt; &lt;span class=&#34;chroma-o&#34;&gt;*&lt;/span&gt; &lt;span class=&#34;chroma-nf&#34;&gt;var&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;--&lt;/span&gt;&lt;span class=&#34;chroma-n&#34;&gt;overlap&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;chroma-o&#34;&gt;*&lt;/span&gt; &lt;span class=&#34;chroma-mi&#34;&gt;3&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-k&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;chroma-nd&#34;&gt;:nth-child&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;chroma-nt&#34;&gt;5&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;chroma-p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;    &lt;span class=&#34;chroma-na&#34;&gt;z-index&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;chroma-mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;    &lt;span class=&#34;chroma-na&#34;&gt;left&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;chroma-nf&#34;&gt;calc&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;chroma-mi&#34;&gt;133&lt;/span&gt;&lt;span class=&#34;chroma-kt&#34;&gt;px&lt;/span&gt; &lt;span class=&#34;chroma-o&#34;&gt;*&lt;/span&gt; &lt;span class=&#34;chroma-nf&#34;&gt;var&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;--&lt;/span&gt;&lt;span class=&#34;chroma-n&#34;&gt;overlap&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;chroma-o&#34;&gt;*&lt;/span&gt; &lt;span class=&#34;chroma-mi&#34;&gt;4&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;These could be replaced with something like:&lt;/p&gt;
&lt;pre class=&#34;chroma-chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-na&#34;&gt;z-index&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;chroma-nf&#34;&gt;calc&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;chroma-mi&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;chroma-o&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;chroma-nf&#34;&gt;sibling-count&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;chroma-o&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;chroma-nf&#34;&gt;sibling-index&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;());&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-na&#34;&gt;left&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;chroma-nf&#34;&gt;calc&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;chroma-mi&#34;&gt;133&lt;/span&gt;&lt;span class=&#34;chroma-kt&#34;&gt;px&lt;/span&gt; &lt;span class=&#34;chroma-o&#34;&gt;*&lt;/span&gt; &lt;span class=&#34;chroma-nf&#34;&gt;var&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;--&lt;/span&gt;&lt;span class=&#34;chroma-n&#34;&gt;overlap&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;chroma-o&#34;&gt;*&lt;/span&gt; &lt;span class=&#34;chroma-p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;chroma-nf&#34;&gt;sibling-index&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;chroma-o&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;chroma-mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;));&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;As an added bonus, it would scale to any number of posters. &lt;code&gt;index&lt;/code&gt; being 1-based not 0-based is a
bit unfortunate as it makes using it in calculations awkward (see &lt;code&gt;1&lt;/code&gt; offset in both of those rules!),
but it’s leagues better than writing 5 separate rules.&lt;/p&gt;
&lt;h4 id=&#34;random&#34;&gt;random()&lt;/h4&gt;
&lt;p&gt;Currently only available in Safari. There are a few places where I’d like to have slight random variations
of the style. Things like elements that are rotated slightly for aesthetics; they look a bit silly if
they’re all identically positioned. I also randomise the icons and positions of my rating stars to break
up the visual monotony. Here are a few examples:&lt;/p&gt;
&lt;p&gt;&lt;span class=&#34;star-rating&#34;&gt;&lt;img src=&#34;https://chameth.com/star-flat.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Full star&#34; class=&#34;rot-2&#34;/&gt;&lt;img src=&#34;https://chameth.com/star-flat.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Full star&#34; class=&#34;rot-2&#34;/&gt;&lt;img src=&#34;https://chameth.com/star-flat.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Full star&#34; class=&#34;rot-2&#34;/&gt;&lt;img src=&#34;https://chameth.com/star-flat.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Full star&#34; class=&#34;rot-2&#34;/&gt;&lt;img src=&#34;https://chameth.com/star-flat.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Full star&#34; class=&#34;rot-0&#34;/&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span class=&#34;star-rating&#34;&gt;&lt;img src=&#34;https://chameth.com/star.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Full star&#34; class=&#34;rot-0&#34;/&gt;&lt;img src=&#34;https://chameth.com/star.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Full star&#34; class=&#34;rot-1&#34;/&gt;&lt;img src=&#34;https://chameth.com/star-flat.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Full star&#34; class=&#34;rot-0&#34;/&gt;&lt;img src=&#34;https://chameth.com/star.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Full star&#34; class=&#34;rot-0&#34;/&gt;&lt;img src=&#34;https://chameth.com/star-flat.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Full star&#34; class=&#34;rot-2&#34;/&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span class=&#34;star-rating&#34;&gt;&lt;img src=&#34;https://chameth.com/star.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Full star&#34; class=&#34;rot-0&#34;/&gt;&lt;img src=&#34;https://chameth.com/star-flat.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Full star&#34; class=&#34;rot-2&#34;/&gt;&lt;img src=&#34;https://chameth.com/star-flat.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Full star&#34; class=&#34;rot-1&#34;/&gt;&lt;img src=&#34;https://chameth.com/star-flat.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Full star&#34; class=&#34;rot-1&#34;/&gt;&lt;img src=&#34;https://chameth.com/star-flat.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Full star&#34; class=&#34;rot-1&#34;/&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Currently I do the stars by hardcoding a bunch of classes and having the backend randomly apply one when
it generates the markup for stars. Being able to do it in CSS would be great, though.&lt;/p&gt;
&lt;h4 id=&#34;mixins&#34;&gt;Mixins&lt;/h4&gt;
&lt;p&gt;This one’s so far off it’s not even listed on &lt;a href=&#34;https://caniuse.com/&#34;&gt;caniuse.com&lt;/a&gt; yet. Mixins are another handy feature of
SCSS, that allow you to define reusable blocks of rules, then import them when needed:&lt;/p&gt;
&lt;pre class=&#34;chroma-chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-k&#34;&gt;@mixin&lt;/span&gt;&lt;span class=&#34;chroma-nf&#34;&gt; fancy-background&lt;/span&gt; &lt;span class=&#34;chroma-p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;    &lt;span class=&#34;chroma-na&#34;&gt;background&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;chroma-cm&#34;&gt;/*...&lt;/span&gt;&lt;span class=&#34;chroma-c&#34;&gt;*/&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-nc&#34;&gt;.some-element&lt;/span&gt; &lt;span class=&#34;chroma-p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;    &lt;span class=&#34;chroma-k&#34;&gt;@include&lt;/span&gt;&lt;span class=&#34;chroma-nd&#34;&gt; fancy-background&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-nn&#34;&gt;#other-element&lt;/span&gt; &lt;span class=&#34;chroma-p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;    &lt;span class=&#34;chroma-k&#34;&gt;@include&lt;/span&gt;&lt;span class=&#34;chroma-nd&#34;&gt; fancy-background&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This would be particularly useful if you can’t easily control the markup to add classes to everything. For example, my
footnotes are rendered by a markdown plugin, and don’t easily have a way to add extra classes to them. To style it the
same as another element, I currently duplicate a bunch of rules between them both. Mixins would allow me to define those
rules once, and then import them in both places.&lt;/p&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr/&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;Aside from JavaScript, which seems to just endlessly reinvent new frameworks and ways to make the most sprawling dependency tree possible. &lt;a class=&#34;footnote-backref&#34; href=&#34;#fnref:1&#34; role=&#34;doc-backlink&#34;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34;&gt;
&lt;p&gt;It also means that if your website’s syntax highlighting library doesn’t seem to understand nested rules, you can just pretend your perfectly valid CSS is actually SCSS and it’ll magically work. Grumble, grumble. &lt;a class=&#34;footnote-backref&#34; href=&#34;#fnref:2&#34; role=&#34;doc-backlink&#34;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
</content>
    </entry>
    <entry>
        <title>Fixing a loud PSU fan without dying</title>
        <link href="https://chameth.com/fixing-a-loud-psu-fan-without-dying/"/>
        <updated>2025-07-30T00:00:00Z</updated>
        <id>https://chameth.com/fixing-a-loud-psu-fan-without-dying/</id>
        <content xml:lang="en" type="html">&lt;p&gt;Three months after I &lt;a href=&#34;https://chameth.com/building-a-new-computer/&#34;&gt;built my new computer&lt;/a&gt;, it
started annoying me. There would occasionally be a noise that sounded like
a fan was catching on a cable, but there weren’t any loose cables to be a
problem. Over the course of a few weeks, the sound got progressively worse
to the extent that I didn’t want to use the computer without headphones on.
I measured the sound at 63 dB, which is about the sound of someone talking.
That may not sound terrible, but it’s a constant, nasty noise coming from
something that sits about 40cm from my head.&lt;/p&gt;
&lt;p&gt;After some investigating&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a class=&#34;footnote-ref&#34; href=&#34;#fn:1&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;, I identified the PSU fan as the culprit. I have
a Cooler Master V750 SFX, which is not super high-end, but wasn’t cheap, either.
It shouldn’t be developing issues after three months. Thankfully, it comes with
a ten-year warranty, so it should be easy to get sorted, right?&lt;/p&gt;
&lt;h3 id=&#34;warranty-woes&#34;&gt;Warranty woes&lt;/h3&gt;
&lt;p&gt;I looked at Cooler Master’s warranty, and for issues within the first two years
you have to deal with the retailer. That would be Amazon in my case. So I looked
at Amazon’s information on warranty issues. Their policy is that if it’s more
than 30 days since purchase, you have to send it off to a third-party repair
center and wait for them to diagnose and try to repair it. Here’s the kicker:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Usually repairs take up to 20 business days (including delivery time), but
could take slightly longer&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I use the computer for work, have upcoming LAN parties to go to, and generally
can’t do without it for an entire month. That’s assuming they reproduce
the issue: the PSU fan only turns on when it reaches a certain temperature,
so if you just plug it in briefly it won’t exhibit any symptoms.&lt;/p&gt;
&lt;!--more--&gt;
&lt;p&gt;If I was going to do a warranty return, I’d need to buy a new PSU first to
use while this one was away. If I’m spending £100+ on a PSU, then I’d be getting
a better one that doesn’t have known issues with the fans. So when the Cooler
Master eventually got returned I’d have no use for it. What a waste.&lt;/p&gt;
&lt;p&gt;So the warranty is about as much use as a chocolate teapot. Good to know, I
guess. If I wanted decent service, I should have probably used a real retailer,
not Amazon. Lesson learnt.&lt;/p&gt;
&lt;h3 id=&#34;doing-some-research&#34;&gt;Doing some research&lt;/h3&gt;
&lt;p&gt;If the warranty is useless to me, maybe I should just void it and try to fix it
myself. I did some research online. Most of the results went like this:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;if you’re not a trained professional you should not be opening up your PSU
at all. There’s a lot of current that goes through it which will seriously
hurt/main/kill you&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;or&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;it’s time consuming and potentially dangerous&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;or some other variety of doom and gloom. There were some actually useful results
sprinkled in, though. &lt;a href=&#34;https://smallformfactor.net/forum/threads/cooler-master-v850-sfx-psu-fan-swap.17294/&#34;&gt;A post on the smallformfactor.net forums&lt;/a&gt;
details someone swapping out the fan on the 850W version of the same power
supply because it was too noisy. It looks like the fan connector in the PSU
is a two-pin connector, and they just used an off-the-shelf adapter to convert
the standard four-pin case fan connector. It sounded a bit annoying to make
it all fit, though.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://old.reddit.com/r/sffpc/comments/1h97sz6/cooler_master_v850_sfx_noctua_fan_swap/&#34;&gt;A similar post on reddit&lt;/a&gt;
showed basically the same upgrade, but identified the connector as a 2.54mm
JST-XH. They crimped their own connector on, which feels a lot better than
using a bulky connector. I do have a crimping set for JST connectors, but I
absolutely hate doing them because they’re so fiddly and annoying. I’m not sure
if I trust one of my crimps inside a power supply, either.&lt;/p&gt;
&lt;p&gt;Looking at the pictures in those posts, I identified the fan as an Ong Hua
&lt;code&gt;HA9215VH12FD-F00&lt;/code&gt;. I searched to see if I could see any specifications for it.
I couldn’t. But what I did get back as my first result was another Reddit post,
this one titled &lt;a href=&#34;https://old.reddit.com/r/sffpc/comments/1b4j6vr/getting_rid_of_a_ha9215sh12fdf00_fan_from_your_psu/&#34;&gt;“Getting rid of a HA9215SH12FD-F00 fan from your PSU”&lt;/a&gt;.
It says something about the quality of a component when the first result
is a post about getting rid of it because it’s annoying&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a class=&#34;footnote-ref&#34; href=&#34;#fn:2&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;All of these mods seemed easy enough, and I’m reasonably confident in my ability
to both discharge and avoid touching capacitors, so I probably wouldn’t die.
I ordered a Noctua NF-A9x14 fan and some pre-made JST-XH connectors and waited
for them to arrive.&lt;/p&gt;
&lt;h3 id=&#34;upgrade-time&#34;&gt;Upgrade time&lt;/h3&gt;
&lt;aside class=&#34;warning&#34;&gt;
  &lt;div&gt;
    &lt;h5&gt;Warning&lt;/h5&gt;
    &lt;p&gt;Like the Reddit keyboard warriors said, the stuff inside PSUs is dangerous.
Try not to kill yourself or release the magic smoke if you do this.
I’m not taking responsibility for your actions!&lt;/p&gt;
  &lt;/div&gt;
&lt;/aside&gt;
&lt;p&gt;The replacement parts arrived one evening, so when I turned the computer off
for the night I unplugged it from the mains, held the power button down for
a while, and left it overnight for good measure. The next morning I popped
open the case, disconnected and removed the PSU, gleefully tore the
“warranty void if removed” sticker off, and opened it up.&lt;/p&gt;
&lt;p&gt;Removing the fan was simple: unplug the JST connector and undo the screws
holding it to the outer panel. I confirmed that my JST connectors were the
right size, then started surgery on the Noctua fan. I carefully removed the
heatshrink, cut off the normal fan connector, and separated out the four wires.
A quick check of their &lt;a href=&#34;https://faqs.noctua.at/en/support/solutions/articles/101000081757-what-pin-configuration-do-noctua-fans-use-&#34;&gt;FAQ&lt;/a&gt;
confirmed the wiring colours: black was ground, yellow +12V, and blue and
green used for speed control (which we’re not using in this instance). I clipped
the blue and green wires off near the hub so they wouldn’t be in the way, then
cut the yellow and black wires down to length, and attached the JST connector
with a solder seal:&lt;/p&gt;
&lt;figure class=&#34;image full&#34;&gt;
  &lt;picture&gt;
      &lt;source srcset=&#34;https://chameth.com/fixing-a-loud-psu-fan-without-dying/fans.avif&#34; type=&#34;image/avif&#34;/&gt;
      &lt;source srcset=&#34;https://chameth.com/fixing-a-loud-psu-fan-without-dying/fans.webp&#34; type=&#34;image/webp&#34;/&gt;
      &lt;img src=&#34;https://chameth.com/fixing-a-loud-psu-fan-without-dying/fans.jpg&#34; alt=&#34;Two fans side by side on a mouse mat: a black fan with a short power connector, and a beige-and-brown fan with transparent solder seals over the joins in its power connector. The mouse mat has a shiny patch near the solder seals.&#34; loading=&#34;lazy&#34; width=&#34;1600&#34; height=&#34;887&#34;/&gt;
  &lt;/picture&gt;
  &lt;figcaption&gt;&lt;p&gt;Old fan, new fan, burnt mouse mat&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;In the process I discovered my mouse mat doesn’t like being hit with a heat gun.
Whoops. The solder seals are great: the solder in the center melts to form an
electrical connection, the parts at the end grip the cable to provide mechanical
strength, and the whole thing acts like heatshrink and keeps the connection
insulated. A lot less fiddly than crimping JST connectors, and a lot smaller
and cheaper than an adapter.&lt;/p&gt;
&lt;p&gt;I installed the fan and reassembled the PSU. Then undid it all and tried again,
as apparently the face the fan mounts to isn’t quite symmetrical and I’d
installed the fan upside down. Connected everything back up, plugged the
computer in, booted it up, and… nothing. The PSU doesn’t actually power the fan
until it hits a certain temperature. I figured putting it under load would
probably heat it up, so ran a graphics benchmark a few times. Eventually the
fan spun up. Huzzah.&lt;/p&gt;
&lt;figure class=&#34;image full&#34;&gt;
  &lt;picture&gt;
      &lt;source srcset=&#34;https://chameth.com/fixing-a-loud-psu-fan-without-dying/installed.avif&#34; type=&#34;image/avif&#34;/&gt;
      &lt;source srcset=&#34;https://chameth.com/fixing-a-loud-psu-fan-without-dying/installed.webp&#34; type=&#34;image/webp&#34;/&gt;
      &lt;img src=&#34;https://chameth.com/fixing-a-loud-psu-fan-without-dying/installed.jpg&#34; alt=&#34;Computer with the side panel removed, with a Noctua fan visibly spinning inside the PSU, next to another Noctua fan spinning above the CPU.&#34; loading=&#34;lazy&#34; width=&#34;1280&#34; height=&#34;720&#34;/&gt;
  &lt;/picture&gt;
  &lt;figcaption&gt;&lt;p&gt;Soon the entire computer will be beige and brown&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&#34;peace-at-last&#34;&gt;Peace at last&lt;/h3&gt;
&lt;p&gt;I measured the sound of the new fan: 43 dB. That’s a decrease of 20 dB. Due
to the weird logarithmic properties of the dB scale that no one understands,
that means it sounds about 4 times quieter to human ears. Big improvement!&lt;/p&gt;
&lt;p&gt;It’s worth pointing out this isn’t a straight one-to-one upgrade. I don’t know
the specs of the old fan in terms of airflow, and I’m just assuming that the
Noctua will be good enough by virtue of being a decent fan. They also behave
slightly differently as the power ramps up: the stock fan starts spinning
at a lower voltage than the Noctua, which needs around 7V to get started
properly&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a class=&#34;footnote-ref&#34; href=&#34;#fn:3&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;. I don’t think this will be an issue, as it’s effectively just
putting a small step at the lower end of the fan curve. It might shorten the
overall lifespan of the PSU, but from my point of view it failed after four
months, so any extra time is a bonus.&lt;/p&gt;
&lt;p&gt;I recorded some before/after audio, so you can hear the difference. I think
there’s some amount of volume normalisation going on, so it’s not quite as
stark a difference as in person, but it’s still noticeable.&lt;/p&gt;
&lt;figure class=&#34;full&#34;&gt;
  &lt;audio src=&#34;https://chameth.com/fixing-a-loud-psu-fan-without-dying/before.mp3&#34; alt=&#34;Audio sample from the old fan&#34; controls=&#34;&#34;&gt;&lt;/audio&gt;
  &lt;figcaption&gt;Audio sample from the old fan&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&#34;full&#34;&gt;
  &lt;audio src=&#34;https://chameth.com/fixing-a-loud-psu-fan-without-dying/after.mp3&#34; alt=&#34;Audio sample from the new fan&#34; controls=&#34;&#34;&gt;&lt;/audio&gt;
  &lt;figcaption&gt;Audio sample from the new fan&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;And if you couldn’t hear it, here’s a visual representation:&lt;/p&gt;
&lt;figure class=&#34;image full&#34;&gt;
  &lt;picture&gt;
      &lt;source srcset=&#34;https://chameth.com/fixing-a-loud-psu-fan-without-dying/comparison.avif&#34; type=&#34;image/avif&#34;/&gt;
      &lt;source srcset=&#34;https://chameth.com/fixing-a-loud-psu-fan-without-dying/comparison.webp&#34; type=&#34;image/webp&#34;/&gt;
      &lt;img src=&#34;https://chameth.com/fixing-a-loud-psu-fan-without-dying/comparison.png&#34; alt=&#34;Side-by-side comparison of the audio spectrum of the old fan and the new fan. The old fan makes a reasonable amount of noise across a lot of frequencies, the new one is concentrated at very low frequencies.&#34; loading=&#34;lazy&#34; width=&#34;1467&#34; height=&#34;728&#34;/&gt;
  &lt;/picture&gt;
  &lt;figcaption&gt;&lt;p&gt;Side-by-side comparison of the audio clips&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The whole upgrade took about an hour (less time than writing this post about
it!), and I even managed to avoid killing myself.&lt;/p&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr/&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;Opening the case and sticking my ear near to various components. High-tech
stuff. &lt;a class=&#34;footnote-backref&#34; href=&#34;#fnref:1&#34; role=&#34;doc-backlink&#34;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34;&gt;
&lt;p&gt;On closer inspection it’s actually a slightly different model — there’s
an &lt;code&gt;S&lt;/code&gt; not a &lt;code&gt;V&lt;/code&gt; in the middle there — but I’m going to assume that doesn’t
change much on the quality/noise front. &lt;a class=&#34;footnote-backref&#34; href=&#34;#fnref:2&#34; role=&#34;doc-backlink&#34;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34;&gt;
&lt;p&gt;It then keeps spinning down to about 5V or so, it just needs enough power
to initially get started. &lt;a class=&#34;footnote-backref&#34; href=&#34;#fnref:3&#34; role=&#34;doc-backlink&#34;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
</content>
    </entry>
    <entry>
        <title>Building a new Computer</title>
        <link href="https://chameth.com/building-a-new-computer/"/>
        <updated>2025-06-03T00:00:00Z</updated>
        <id>https://chameth.com/building-a-new-computer/</id>
        <content xml:lang="en" type="html">&lt;figure class=&#34;image right&#34;&gt;
  &lt;picture&gt;
      &lt;source srcset=&#34;https://chameth.com/building-a-new-computer/finished.avif&#34; type=&#34;image/avif&#34;/&gt;
      &lt;source srcset=&#34;https://chameth.com/building-a-new-computer/finished.webp&#34; type=&#34;image/webp&#34;/&gt;
      &lt;img src=&#34;https://chameth.com/building-a-new-computer/finished.jpg&#34; alt=&#34;A small form factor PC sat on a desk, with a pen propped up in front of it for scale. It&amp;#39;s about 1.5 pens tall. The case is silver, with a wooden panel at the bottom.&#34; loading=&#34;lazy&#34; width=&#34;375&#34; height=&#34;500&#34;/&gt;
  &lt;/picture&gt;
  &lt;figcaption&gt;&lt;p&gt;The finished PC. Cat pen for scale.&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;I recently&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a class=&#34;footnote-ref&#34; href=&#34;#fn:1&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; built a new computer, after exclusively using a laptop for three
years. It’s also the first time I’ve departed from the usual combo of an Intel
CPU and Nvidia GPU.&lt;/p&gt;
&lt;p&gt;While the form factor of a laptop did make it amazingly handy for travelling
and attending LAN events, it was starting to show its age and there was
basically no sane upgrade path. The main problem was its 3060 mobile graphics
card, which was &lt;em&gt;okay&lt;/em&gt; for the first few years and then slowly descended into
&lt;em&gt;painful&lt;/em&gt;. At the time, the (fairly disappointing) 50-series had just been
released, but hadn’t yet made it into laptops. Nvidia had stopped production
on the 40-series beforehand, so there also weren’t any compelling options there,
either.&lt;/p&gt;
&lt;p&gt;That’s not to say there were no laptops at all that I could have upgraded to.
There were. Just not really any that ticked all of my perfectly normal boxes
like “run Linux”, “have at least 64GB of ram”, and “run modern games well”.
Having to upgrade the entire system just because the graphics card was
showing its age was a bit of a drag, too. So back to the land of desktop
computers I went!&lt;/p&gt;
&lt;p&gt;To try to preserve some of that convenience, I opted for a small form factor
(SFF) case. The computer and all the accessories can fit inside a
hand-luggage-sized flight case. More on that later, though.&lt;/p&gt;
&lt;h3 id=&#34;components-choices-and-cramming&#34;&gt;Components, Choices, and Cramming&lt;/h3&gt;
&lt;p&gt;So what, exactly, is in this computer? The case is a Fractal Design Terra,
which is a lovely little 10.4L case&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a class=&#34;footnote-ref&#34; href=&#34;#fn:2&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;. My last desktop PC was a normal-sized
Fractal Design case, which I liked a lot, and it seems like they’ve upped their
game since then. The Terra is both pleasant to look at (just look at that
wooden panel!), and a breeze to work in. The sides and top are fully and easily
removable, giving you great access to everything inside&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a class=&#34;footnote-ref&#34; href=&#34;#fn:3&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;. I expected an SFF
build to be fiddly, but the case made it feel about the same as a normal
full-sized build&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a class=&#34;footnote-ref&#34; href=&#34;#fn:4&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;!--more--&gt;
&lt;figure class=&#34;image left&#34;&gt;
  &lt;picture&gt;
      &lt;source srcset=&#34;https://chameth.com/building-a-new-computer/build.avif&#34; type=&#34;image/avif&#34;/&gt;
      &lt;source srcset=&#34;https://chameth.com/building-a-new-computer/build.webp&#34; type=&#34;image/webp&#34;/&gt;
      &lt;img src=&#34;https://chameth.com/building-a-new-computer/build.jpg&#34; alt=&#34;The computer fully built, but with the sides and top of the case off, and wires protruding everywhere.&#34; loading=&#34;lazy&#34; width=&#34;500&#34; height=&#34;375&#34;/&gt;
  &lt;/picture&gt;
  &lt;figcaption&gt;&lt;p&gt;The case with sides and top off, and wires everywhere in desperate need of management.&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Another nice feature of the case is the movable spine. I think this is
relatively common in SFF cases, but it was new to me. You mount the graphics
card on one side of a metal divider, and everything else on the other. The
divider (or spine) can be moved side-to-side depending on what exactly you’re
putting in the case.&lt;/p&gt;
&lt;p&gt;As I mentioned earlier, this was my first departure from the Intel + Nvidia
bandwagon. Nvidia’s 50 series was underwhelming, and Intel managed to put out
a generation of chips that literally fried themselves. It seemed like a good
time to try out AMD’s offering! I went for the newly released Radeon RX 9070XT
graphics card, and a Ryzen 7 9800X3D processor&lt;sup id=&#34;fnref:5&#34;&gt;&lt;a class=&#34;footnote-ref&#34; href=&#34;#fn:5&#34; role=&#34;doc-noteref&#34;&gt;5&lt;/a&gt;&lt;/sup&gt;. The processor has a
Thermalright AXP90 X47 Full heatsink on top of it, with a Noctua NF-A9 fan
on top of that.&lt;/p&gt;
&lt;p&gt;The motherboard is a Gigabyte B850I Aorus Pro, and it hosts 2 32GB Corsair
Vengeance RAM modules, and a 2TB Crucial T705 NVMe drive. Powering it all is
a Cooler Master V750 SFX. The PSU is modular to reduce the number of wires that
need to be fitted in, but they’re still pretty big and bulky. I’m tempted by
custom cables but they’re rather pricey, and you can’t actually see inside
with the sides closed…&lt;/p&gt;
&lt;p&gt;With the diminutive size of the case and all the components crammed in,
there’s actually no room left over for case fans. The PSU, GPU and CPU all have
fans attached, but that’s it. There are stories online of people having heat
issues, but thankfully everything seems pretty happy in my setup. The components
all draw in air from the sides, and the hot air passively vents out the top. If
I keep the CPU under maximum load for a while then it starts to thermal throttle
slightly, but the only time I’ve actually managed that is when compiling a
kernel. Why was I compiling a kernel, you ask? Well, we’ll come to that.&lt;/p&gt;
&lt;h3 id=&#34;but-does-it-linux&#34;&gt;But does it Linux?&lt;/h3&gt;
&lt;p&gt;I didn’t go into this build &lt;em&gt;completely&lt;/em&gt; blind. I did a bit of reading and
chatting, and it sounded like AMD drivers were pretty good on Linux these
days. I didn’t check specifically if any of the components were supported.&lt;/p&gt;
&lt;p&gt;The 9070XT was released on the 6th of March. I built my PC on the 26th
of March. The first version of Mesa (the 3D graphics lib) that was generally
stable with 9070XT cards landed in Arch on the 20th of March. If I did the build
just one week earlier, I would’ve had a much less fun experience.&lt;/p&gt;
&lt;p&gt;It still wasn’t quite perfectly smooth, though. When playing games I’d
occasionally have issues. In some cases the screen just went blank and came
back after switching windows, but in others the graphics drivers couldn’t
recover and the PC needed rebooting. Fortunately the drivers are open source,
and they have a public &lt;a href=&#34;https://gitlab.freedesktop.org/drm/amd&#34;&gt;issue tracker&lt;/a&gt;
hosted by the Freedesktop project. Searching through the issues, I found they’d
already been reported (by another Arch user, of course), and there were various
suggestions for work-arounds, as well as lots of work trying to reproduce the
issue.&lt;/p&gt;
&lt;p&gt;Within a week, an AMD developer had posted a set of patches that might address
the issues, and asked people to test them. That’s fortunately pretty easy on
Arch: you just clone the repository containing the &lt;code&gt;PKGBUILD&lt;/code&gt; file, drop in
the patches, and update the checksums for them. Then you start compiling,
realise you didn’t tell it to use more than one CPU core, abort it, and start
again but this time running on all cylinders. Soon enough I was running a
patched 6.14 kernel and the problems stopped completely. Others reported
similar outcomes, and the patches worked their way into the kernel proper.&lt;/p&gt;
&lt;p&gt;I spent a few weeks having to compile a custom kernel every time I accidentally
updated it (I should’ve just stopped &lt;code&gt;pacman&lt;/code&gt; from touching the kernel for a
bit…), then one day the patches didn’t apply because they’d been merged
upstream. Since then everything has been perfect.&lt;/p&gt;
&lt;p&gt;A friend commented that “this wouldn’t happen with Nvidia”. They meant it
negatively, but I wholeheartedly agree! Maybe Nvidia have fewer launch bugs
than AMD, but you &lt;em&gt;absolutely&lt;/em&gt; wouldn’t be able to talk to their driver
engineers on a public bug tracker, and get access to test patches that fix
your issue. I’ll happily take a few bugs in exchange for a company that operates
openly in a way consistent with the rest of the Linux community.&lt;/p&gt;
&lt;h3 id=&#34;but-does-it-travel&#34;&gt;But does it travel?&lt;/h3&gt;
&lt;figure class=&#34;image right&#34;&gt;
  &lt;picture&gt;
      &lt;source srcset=&#34;https://chameth.com/building-a-new-computer/case.avif&#34; type=&#34;image/avif&#34;/&gt;
      &lt;source srcset=&#34;https://chameth.com/building-a-new-computer/case.webp&#34; type=&#34;image/webp&#34;/&gt;
      &lt;img src=&#34;https://chameth.com/building-a-new-computer/case.jpg&#34; alt=&#34;An open flight case, with the PC slotted in surrounded by foam. A keyboard sits below it, and various accessories on the side with 3D-printed supports.&#34; loading=&#34;lazy&#34; width=&#34;500&#34; height=&#34;375&#34;/&gt;
  &lt;/picture&gt;
  &lt;figcaption&gt;&lt;p&gt;Travel mode, engage!&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;So the computer works fine at home, but how do I get it to other places? I just
shoved a laptop in a backpack&lt;sup id=&#34;fnref:6&#34;&gt;&lt;a class=&#34;footnote-ref&#34; href=&#34;#fn:6&#34; role=&#34;doc-noteref&#34;&gt;6&lt;/a&gt;&lt;/sup&gt;, but you can’t quite do that with a PC even
one this size. I’d seen a few people at LANs who packed everything in flight
cases, but they always seemed a bit unwieldy. Doing some research, though, I
found that I’d just be able to squeeze everything into a flight case sized to
be taken onto a plane as cabin baggage.&lt;/p&gt;
&lt;p&gt;Peli (or Pelican in the US) are the most well known maker of flight cases, but
I chose to go with Nanuk instead. Spec-wise they’re fairly similar. Nanuk are
a bit cheaper. The killer difference for me, though, is that Nanuk publish
STEP files of their cases. As someone with a 3D printer, giving me CAD models
of things so that I can easily mod them is definitely a way to win my favour.&lt;/p&gt;
&lt;p&gt;After lots of measuring and thinking and modelling I came up with an arrangement
where everything could fit snugly into the case. The case has cutouts for the
wheels and handles, which constrain what can go where. I ended up wanting the
computer at the handle-end of the case to make best use of space, but that means
it needs something to take its weight when the case is vertical. The solution
was, of course, 3D printing.&lt;/p&gt;
&lt;p&gt;Using the STEP files from Nanuk I designed an insert that spans across the
case, with screw holes that line up with the ones in the flight case. I wasn’t
sure it would be able to take the weight like that, so I also added in some
supports in the other direction (running down to the bottom of the case, if it’s
stood up). I ended up printing it in three parts, and it came out perfectly first
time. I can’t emphasise how nice it is having CAD models instead of having to
measure and guess at things.&lt;/p&gt;
&lt;p&gt;With the support in place, I focused on cushioning the computer. I opted for
10mm thick EVA foam as it’s easy to obtain and work with, and doesn’t take up too much
room. I used contact adhesive to glue the foam directly to the case and the
3D printed divider. There’s a piece that goes between the computer and the
keyboard, so I printed some little 3D supports to hold that more firmly in
place, and stuck those in with contact adhesive as well.&lt;/p&gt;
&lt;p&gt;So how does it all hold up? It’s been to one LAN event so far, and it’s great.
I think the flight case might actually be easier to carry than hefting a heavy
backpack: the handles are sturdy and comfortable, and it has chunky wheels for
pulling it around. It’s also actually easier to get everything set up when you
can see it all laid out in the flight case and pull out what you need, and then
the case can be tucked away out the way for the duration of the event.&lt;/p&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr/&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;Recently-ish. It may or may not have taken me a few months to get around
to writing this post. &lt;a class=&#34;footnote-backref&#34; href=&#34;#fnref:1&#34; role=&#34;doc-backlink&#34;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34;&gt;
&lt;p&gt;Using litres to measure computer cases always feels wrong. Those are
the units we use for water. Don’t put them near the computers! &lt;a class=&#34;footnote-backref&#34; href=&#34;#fnref:2&#34; role=&#34;doc-backlink&#34;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34;&gt;
&lt;p&gt;The case also has holes to put security screws in to stop all of these
nice easily removable parts from being removable. Very handy when you’re
leaving it unattended at a LAN. &lt;a class=&#34;footnote-backref&#34; href=&#34;#fnref:3&#34; role=&#34;doc-backlink&#34;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:4&#34;&gt;
&lt;p&gt;Which for me means it was good fun for about an hour, and then I got fed
up and just wanted it to be done already. &lt;a class=&#34;footnote-backref&#34; href=&#34;#fnref:4&#34; role=&#34;doc-backlink&#34;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:5&#34;&gt;
&lt;p&gt;Three months in and I still can’t remember these random strings of
numbers. Someone even explained the versioning scheme to me. &lt;a class=&#34;footnote-backref&#34; href=&#34;#fnref:5&#34; role=&#34;doc-backlink&#34;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:6&#34;&gt;
&lt;p&gt;Along with a portable monitor, mouse, keyboard, headset, mouse mat,
kensington lock, power supply, cables, and controller. It was a heavy bag. &lt;a class=&#34;footnote-backref&#34; href=&#34;#fnref:6&#34; role=&#34;doc-backlink&#34;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
</content>
    </entry>
</feed>
