What’s that, girl? Two children are lost somewhere in this bookshop?
Yeah, I should probably find them and take them home for lunch.
This checkin to GCB61CG The witney way reflects a geocaching.com log entry. See more of Dan's cache logs.
Came out to the park around the corner, and while the kids played in the play area the dog and I wandered down the lane to find this QEF. TFTC!
This is a repost promoting content originally published elsewhere. See more things Dan's reposted.
This video, which I saw on Nebula but which is also available on YouTube, explores a hypothetical alternate history in which the Schuman Plan/European Coal & Steel Community never happened, and the knock-on effects lead to no EU, a more fragmented Europe, and an ultimately more-fractured and more-complicated Europe of the late 20th/early 21st century.
Obviously it’s highly-speculative and you could easily come up with your own alternative alternative history! But the Twilight Struggle player in me as well as the alternate history lover (and, of course, European Union fan) especially loves the way this story is told.
It’s worth remembering that for the last half-millenium or more, the default state of Europe has been to be fighting one another: if not outright war then at least agressive economic and political rivals. Post-WWII gave Europe perhaps its longest ever period of relative peace, and that’s great enough that all of the other benefits of a harmonised and cooperative union are just icing on the cake.
EU Made Simple is a fantastic channel in general, and I’d recommend you give it a look. It ties news and history in with its creators outlook, but it’s always clear which bits are opinion and it’s delightfully bitesized. For Europeans-in-exile in this post-Brexit age, it’s hopeful and happy, and I like it.
Happy Europe Day, one and all.
While working on something else entirely1, I had a random thought:
Could the :checked
and and :has
pseudo-classes and the subsequent-sibling (~
) selector be combined to perform interactive filtering
without JavaScript?
Turns out, yes. Have a play with the filters on the side of this. You can either use:
(if it doesn’t work right where you are, e.g. in a feed reader, you can view it “standalone”)
The source code is available to download under the Unlicense, but the animal images are CC-BY licensed (with thanks to Aslan Almukhambetov).
There’s nothing particularly complicated here, although a few of the selectors are a little verbose.
First, we set the initial state of each animal. In “OR” mode, they’re hidden, because each selected checkbox is additive. In “AND” mode, they’re shown, because checking a checkbox can only ever remove an animal from the result set:
#filters:has(#filter-or:checked) ~ #animals .animal { display: none; } #filters:has(#filter-and:checked) ~ #animals .animal { display: flex; }
The magic of the :has
pseudo-class is that it doesn’t change the scope, which means that after checking whether “AND” or “OR” is checked within the #filters,
the #animals
container is still an adjacent element.
Then all we need to do is to use daisy-chain :has to show animals with a particular class if that class is checked in “OR” mode, or to hide animals that don’t have a particular class in “AND” mode. Here’s what that looks like:
#filters:has(#filter-or:checked):has(#aquatic:checked) ~ #animals .aquatic, #filters:has(#filter-or:checked):has(#bird:checked) ~ #animals .bird, ... #filters:has(#filter-or:checked):has(#reptile:checked) ~ #animals .reptile { display: flex; } #filters:has(#filter-and:checked):has(#aquatic:checked) ~ #animals .animal:not(.aquatic), #filters:has(#filter-and:checked):has(#bird:checked) ~ #animals .animal:not(.bird), ... #filters:has(#filter-and:checked):has(#reptile:checked) ~ #animals .animal:not(.reptile) { display: none; }
It could probably enjoy an animation effect to make it clearer when items are added and removed2, but that’s a consideration for another day.
Many developers would be tempted to use JavaScript to implement the client-side version of a filter like this. And in some cases, that might be the right option.
But it’s always worth remembering that:
1 The thing I was actually working on when I got distracted was an OAuth provider implementation for Three Rings, connected with work that took place at this weekend’s hackathon to (eventually) bring single-sign-on “across” Three Rings CIC’s products. Eventually being the operative word.
2 Such an animation should, of course, be wrapped in a @media
(prefers-reduced-motion: no-preference)
media query!
Never underestimate the power of people who are motivated by the good they can do in the world.
Today I was in awe of this team of unpaid volunteers who, having already given up their bank holiday weekend, worked through dinner and into the night to ensure the continued uptime of a piece software that enables the listening service of emotional support and suicide helplines.
This checkin to GCA2025 Where's 25? - Locationless Cache reflects a geocaching.com log entry. See more of Dan's cache logs.
Happy 25th birthday, geocaching.
I’m spending the weekend volunteering for a nonprofit I founded (it’s almost as old as geocaching, at 23). We’re staying in a hotel at N 52° 36.184′ W 001° 53.869′. I’ve also gotten out to find a couple of local geocaches.
But guess which room number the hotel have given me…
This checkin to GC7H5M9 Hedge your bets reflects a geocaching.com log entry. See more of Dan's cache logs.
Found following a short hunt and a brief rummage after walking up the nearby footpath, with the help of a previous log which talked about crossing a fence (which I didn’t need to do, having come up the correct way in the first place).
Some fellow volunteers and I are meeting at a hotel to the West of here for a weekend of making software to help charities. When we meet up, I have a tradition of getting up early and finding a geocache or two before breakfast. Having exhausted the very-local supply of caches on previous visits, and not wishing to miss out on the tradition on this, geocaching’s 25th birthday, I decided it was time to come further afield (and to finally solve this puzzle!… I’m still stumped by its sibling, though!).
Nice container. Log slightly damp, but still usable. TFTC, and FP awarded for the enjoyable (once I spotted a pattern!) puzzle.
This checkin to GC9QD6R Centurion reflects a geocaching.com log entry. See more of Dan's cache logs.
Woke early, as usual, for the first day of a weekend of volunteering, while staying at nearby Fairlawns. I’ve already tapped out the most-local caches to that hotel on previous stays, so for this morning’s walk I came further afield to find this (and one of the two puzzle caches not too far away; that’s next, hopefully!)
This nice (topical!) container was an easy find once I poked my head into the right place. TFTC!
Happy 25th birthday, geocaching!
I’m travelling by train in just over a week, and I signed up an account with Avanti West Coast, who proudly show off their Shaw Trust accessibility certificate.
Clearly that certificate only applies to their website, though, and not to e.g. their emails. When you sign up an account with them, you need to verify your email address. They send you a (HTML-only) email with a link to click. Here’s what that link looks like to a sighted person:
So far, so good.
But here’s the HTML code they’re using to create that button. Maybe you’ll spot the problem:
<a href="https://www.avantiwestcoast.co.uk/train-tickets/verify-email?prospectId=12345678&customerKey=12345:1234567" target="_blank" style="font-family:Averta, Arial, Helvetica, sans-serif;"> <div class="mobile_image" style="font-family:Averta, Arial, Helvetica, sans-serif; display:none; border:none; text-align:center;"> <img width="40%" src="https://image.e.avantiwestcoast.co.uk/lib/fe371170756404747d1670/m/1/9069c7a2-b9ed-4188-9809-bf58592ec002.png" alt="" style="font-family:Averta, Arial, Helvetica, sans-serif;width:143px; height:40px;" /> </div> </a>
Despite specifying the font to use three times, they don’t actually have any alt text. So for somebody who can’t see that image, the link is completely unusable1.
This made me angry enough that I gave up on my transaction and bought my train tickets from LNER instead.
Accessibility matters. And that includes emails. Do better, Avanti.
1 Incidentally, this also makes the email unusable for privacy-conscious people who, like me, don’t routinely load remote images in emails. But that’s a secondary concern, really.
The video below is presented in portrait orientation, because your screen is taller than it is wide.
The video below is presented in landscape orientation, because your screen is wider than it is tall.
The video below is presented in square orientation (the Secret Bonus Square Video!), because your screen has approximately the same width as as its height. Cool!
This is possible (with a single <video>
element, and without any Javascript!) thanks to some cool HTML features you might not be aware of, which I’ll briefly explain
in the video. Or scroll down for the full details.
I saw a 2023 blog post by Scott Jehl about how he helped Firefox 120 (re)gain support
for the <source media="...">
attribute. Chrome added support later that year,
and Safari already had it. This means that it’s pretty safe to do something like this:
<video controls> <source src="squareish.mp4" media="(min-aspect-ratio: 0.95) and (max-aspect-ratio: 1.05)" /> <source src="portrait.mp4" media="(orientation: portrait)" /> <source src="landscape.mp4" /> </video>
squareish.mp4
which is shown to people on “squareish” viewports, failing that portrait.mp4
which is shown to
people whose viewports are taller than wide, and failing that landscape.mp4
which is shown to anybody else.
That’s broadly-speaking how the video above is rendered. No JavaScript needed.
Browsers only handle media queries on videos when they initially load, so you can’t just tip your phone over or resize the window: you’ll need to reload the page, too. But it works! Give it a go: take a look at the video in both portrait and landscape modes and let me know what you think1.
Here’s another cool technology that you might not have realised you could “just use”: adaptive bitrate streaming with HLS!
You’ve used adaptive bitrate streaming before, though you might not have noticed it. It’s what YouTube, Netflix, etc. are doing when your network connection degrades and you quickly get dropped-down, mid-video, to a lower-resolution version2.
Turns out you can do it on your own static hosting, no problem at all. I used this guide (which has a great description of the parameters used) to help me:
ffmpeg -i landscape.mp4 \ -filter_complex "[0:v]split=3[v1][v2][v3]; [v1]copy[v1out]; [v2]scale=w=1280:h=720[v2out]; [v3]scale=w=640:h=360[v3out]" \ -map "[v1out]" -c:v:0 libx264 -x264-params "nal-hrd=cbr:force-cfr=1" -b:v:0 5M -maxrate:v:0 5M -minrate:v:0 5M -bufsize:v:0 10M -preset slow -g 48 -sc_threshold 0 -keyint_min 48 \ -map "[v2out]" -c:v:1 libx264 -x264-params "nal-hrd=cbr:force-cfr=1" -b:v:1 3M -maxrate:v:1 3M -minrate:v:1 3M -bufsize:v:1 3M -preset slow -g 48 -sc_threshold 0 -keyint_min 48 \ -map "[v3out]" -c:v:2 libx264 -x264-params "nal-hrd=cbr:force-cfr=1" -b:v:2 1M -maxrate:v:2 1M -minrate:v:2 1M -bufsize:v:2 1M -preset slow -g 48 -sc_threshold 0 -keyint_min 48 \ -map a:0 -c:a:0 aac -b:a:0 96k -ac 2 \ -map a:0 -c:a:1 aac -b:a:1 96k -ac 2 \ -map a:0 -c:a:2 aac -b:a:2 48k -ac 2 \ -f hls -hls_time 2 -hls_playlist_type vod -hls_flags independent_segments -hls_segment_type mpegts \ -hls_segment_filename landscape_%v/data%02d.ts \ -master_pl_name landscape.m3u8 \ -var_stream_map "v:0,a:0 v:1,a:1 v:2,a:2" landscape_%v.m3u8
landscape.mp4
into three different resolutions: the original “v1” (1920×1080, in my case, with 96kbit audio), “v2” (1280×720, with
96kbit audio), and “v3” (640×360, with 48kbit audio), each with a resolution-appropriate maximum bitrate, and forced keyframes every 48th frame. Then it breaks each of those into HLS
segments (.ts
files) and references them from a .m3u8
playlist.
The output from this includes:
landscape.m3u8
, which references the other playlists with reference to their resolution and bandwidth, so that browsers can make smart choices,
landscape_0.m3u8
(“v1”), landscape_1.m3u8
(“v2”), etc., each of which references the “parts” of that video,
landscape_0/
, landscape_1/
etc., each of which contain
data00.ts
, data01.ts
, etc.: the actual “chunks” that contain the video segments, which can be downloaded independently by the browser as-needed
We can bring all of that together, then, to produce a variable-aspect, adaptive bitrate, HLS-streamed video player… in pure HTML and suitable for static hosting:
<video controls> <source src="squareish.m3u8" type="application/x-mpegURL" media="(min-aspect-ratio: 0.95) and (max-aspect-ratio: 1.05)" /> <source src="portrait.m3u8" type="application/x-mpegURL" media="(orientation: portrait)" /> <source src="landscape.m3u8" type="application/x-mpegURL" /> </video>
That’ll “just work” in Safari and a handful of mobile browsers… but won’t display anything for most desktop browsers. Boo!
One solution is to also provide the standard .mp4
files as an alternate <source>
, and that’s fine I guess, but you lose the benefit of HLS (and
you have to store yet more files). But there’s a workaround:
If you’re willing to use a JavaScript polyfill, you can make the code above work on virtually any device. I gave this a go, here, by:
// Find all <video>s which have HLS sources: for( hlsVideo of document.querySelectorAll('video:has(source[type="application/x-mpegurl"]), video:has(source[type="vnd.apple.mpegurl"])') ) { // If the browser has native support, do nothing: if( hlsVideo.canPlayType('application/x-mpegurl') || hlsVideo.canPlayType('application/vnd.apple.mpegurl') ) continue; // If hls.js can't help fix that, do nothing: if ( ! Hls.isSupported() ) continue; // Find the best source based on which is the first one to match any applicable CSS media queries const bestSource = Array.from(hlsVideo.querySelectorAll('source')).find(source=>window.matchMedia(source.media).matches) // Use hls.js to attach the best source: const hls = new Hls(); hls.loadSource(bestSource.src); hls.attachMedia(hlsVideo); }
<video>
depend on JavaScript, but if that’s the route you want to go down while we wait for HLS support to become
more widespread (rather than adding different-typed sources) then that’s fine, I guess.
This was a fun dive into some technologies I’ve not had the chance to try before. A fringe benefit of being a generalist full-stack developer is that when you’re “between jobs” you get to play with all the cool things when you’re brushing up your skills before your next big challenge!
(Incidentally: if you think you might be looking to employ somebody like me, my CV is over there!)
“Trans former judge plans to challenge gender ruling at European court”: https://www.bbc.co.uk/news/articles/c9qw2149yelo
Representation matters. That we have a trans former-judge, somebody both well-equipped and motivated to escalate this important challenge to the ECHR, is hugely fortunate.
We need more representation (of trans people specifically, but many other groups too, and perhaps particularly in the intersections) in positions of power, expertise, and authority. To defend the human rights of all of us.
Wishing you luck, Victoria McCloud.
This checkin to GC5FGMC R65: Hazelborough 1: Abby's woofday cache reflects a geocaching.com log entry. See more of Dan's cache logs.
Found after a brief hunt with the help of a boy and his dog. Definitely needed the hint! TFTC!
This checkin to GC9KWV4 Hazelborough trail reflects a geocaching.com log entry. See more of Dan's cache logs.
Found after an extended hunt with the younger child.
A tough search: we were glad of the CO’s recent comment to reassure us it’s still there! SL, TFTC!