Sign in

Piccalilli

@piccalilli.front-end.social.ap.brid.gy
182 followers 0 following 936 posts

High quality, approachable and transferable content from people with decades of elite industry experience. 🌉 bridged from ⁂ front-end.social/@piccalilli, follow @ap.brid.gy to interact

PostsRepliesMedia
Piccalilli @piccalilli.front-end.social.ap.brid.gy · 10h
An extremely satisfying web-based game. www.keithcirkel.co.uk/wire-loop
keithcirkel.co.uk
Wire Loop
Drag the loop along the wire without touching it.
000
Piccalilli @piccalilli.front-end.social.ap.brid.gy · 14/10/2026
A really cool service for cataloging physical media. The bookshelves are really nicely done too. reelswap.net
reelswap.net
ReelSwap — VHS & Physical Media Collection Tracker
Catalog your VHS, Betamax, Laserdisc, DVD and Blu-ray collection, see every edition up close in 3D, and help build a community archive of physical media.
000
Piccalilli @piccalilli.front-end.social.ap.brid.gy · 14/10/2026
This is a great piece. As we see it, the web — at large — always wins and those green shoots will come. michellebarker.co.uk/writing/little…
michellebarker.co.uk
Little Websites Everywhere
000
Piccalilli @piccalilli.front-end.social.ap.brid.gy · 13/10/2026
There are so many better options than Spotify for music and Elliot helpfully breaks plenty of options for you. elliotjaystocks.com/blog/spotify
elliotjaystocks.com
Elliot Jay Stocks | The search for a Spotify alternative
Elliot Jay Stocks is a designer and author, usually doing something with typography
000
Piccalilli @piccalilli.front-end.social.ap.brid.gy · 12/10/2026
A handy service that helps you explore music and find the best prices for vinyl. midnightvinylclub.com
midnightvinylclub.com
Midnight Vinyl Club
Curated vinyl record deals
000
Piccalilli @piccalilli.front-end.social.ap.brid.gy · 12/10/2026
An extremely lightweight syntax highlighter using the new ::highlight() functionality? Yes please! daverupert.com/2026/08/microlighter
daverupert.com
000
Piccalilli @piccalilli.front-end.social.ap.brid.gy · 09/10/2026
So much good stuff has arrived in HTML that gives us rich functionality for free and Chris helpfully breaks that down for us. chrisburnell.com/html-can-do-that
chrisburnell.com
HTML Can Do That
HTML has been gobbling up swathes of what used to be JavaScript’s remit. This page lists a bunch of dynamic functionality that we can now achieve with just HTML.
021
Piccalilli @piccalilli.front-end.social.ap.brid.gy · 09/10/2026
The Index: Issue #201 It’s Friday, so let’s close out the week with some Good Links™ in The Index #201! piccalil.li/the-index/201
piccalil.li
The Index: Issue #201
It’s Friday, so let’s close out the week with some Good Links™ in The Index #201!
000
Piccalilli @piccalilli.front-end.social.ap.brid.gy · 08/10/2026
A good write-up on how annoying and harmful notifications are. dbushell.com/2026/08/20/ruminations…
dbushell.com
Ruminations on notifications
The one where I study UI’s worst feature.
001
Piccalilli @piccalilli.front-end.social.ap.brid.gy · 07/10/2026
As Bramus says in the article, we can sort of do this already, but those substring selectors don't perform well. This new method is a very good improvement! www.bram.us/2026/08/20/the-future-o…
bram.us
The Future of CSS: Target Multiple Classes with the Class Prefix Selector
To target multiple classes that share the same prefix, you’d typically have to resort to adding an extra base classes to your markup or to using badly performing attribute selectors. To make things easier, CSS is getting a new selector: The Class Prefix Selector (`.prefix-*`). ~ **⚠️ This post is about an upcoming CSS feature. You can’t use it … yet.** This feature is hot off the press — it was resolved on only two weeks ago — and currently only exists in spec text. The spec will most likely see some changes before this is ready for a browser to implement. If you have any feedback on the shape of this in-development feature, leave feedback below or at the CSS Working Group in w3c/csswg-drafts#10001 ~ ### The Problem: Targeting Multiple Prefixed Classes When coming up with classnames for use in the `class` attribute, a common practice is to use a prefix to retain some grouping or hierarchy. You might be familiar with classes like `.btn-primary`, `.btn-secondary`, `.btn-danger`, and so on. To apply a base style to all of these buttons today, you typically have to list them all out, or introduce a separate `.btn` base class: /* Adding a base class */ .btn { padding: 0.5rem 1rem; border-radius: 4px; } /* Or listing everything... yuck! */ .btn-primary, .btn-secondary, .btn-danger { padding: 0.5rem 1rem; border-radius: 4px; } Some of you even resort to substring-matching attribute selectors, but those selectors perform badly: /* Works, but performs badly */ [class^="btn-"], [class*=" btn-"] { padding: 0.5rem 1rem; } Looking at a benchmark I ran (CLI results here) using `css-selector-benchmark`, a regular class selector ran at more than 6000 runs/s, whereas `[class*=" btn-"]` dipped as low as 328 runs/s … that’s almost 20 times slower and also 3ms out of your frame budget just to match an element! ~ ### The Solution: The Class Prefix Selector Just two weeks ago, at the CSS Working Group F2F meeting in Berlin (August 2026), we resolved to add a dedicated **Class Prefix Selector** to the CSS Selectors Level 5 specification. The idea was originally pitched by Lea Verou back in 2024 _(w3c/csswg-drafts/#10001)_, and also championed by Lea (and Tab Atkins—Bittner) at the F2F. The syntax is incredibly straightforward: .btn-* { padding: 0.5rem 1rem; border-radius: 4px; } That’s it! The `-*` part at the end makes the selector a **Class Prefix Selector** and will try to match any class that begins with that hyphen-separated prefix. It’s a huge win for utility classes and design systems, allowing you to easily target groups of related elements without having to bloat your HTML payload or write fragile attribute selectors. ~ ### What about the empty string? An interesting question that popped up during the discussions is whether `.foo-*` should match the empty string _(w3c/csswg-drafts/#14291)_, meaning: should `.foo-*` also match an element that _merely_ has the `.foo-` class? While the exact default behavior is still being ironed out, currently the selector is specified to only match classes that start with the prefix and that have at least one character beyond the prefix _(and the first such character beyond the prefix is not also a hyphen)_ So no, `class="foo-"` would NOT be matched by `.foo-*`, which I think is fine. That same selector also would not match `class="foo--"`, which is also probably fine. ~ ### What about non-dashes? The Class Prefix Selector is currently limited to hyphen-separated prefixes, at least at first. Other separators, like `_`, might be added as possibilities in the future as we receive request from authors like yourself about what would be needed. One thing that is already quite clear right now, is that there must at least be _some_ separator. Arbitrary prefixes _(like`.foo*`)_ are not going to be allowed for at least two reasons: 1. You could accidentally overselect: `.foo*` would also match `.footer` 2. Selector Performance: Browsers typically create buckets for class selectors for quick selector matching. Adding arbitrary wildcards defeat that optimization entirely. With the `-` as a separator, the browser can already create extra buckets when parsing the HTML, long before CSS ever gets parsed and starts matching. Similarly, wildcards in the middle of a selector (such as `.card-*-primary`) are also not going to be allowed. ~ ### Why not reuse the `|=` selector? This section was added on 2026.08.21, after Brian asked about this In the issue thread and during the CSSWG call, the idea of reusing the existing “dash match” attribute selector (`|=`) was brought up. Instead of introducing the new `.foo-*` syntax, what if we just wrote `[class|="foo"]`? I also initially thought this would be a good idea, but the working group ultimately decided against it for valid reasons. First of all, there are a few issues with the `|=` selector that prevent it from being a drop-in solution for wildcard class matching: * The `|=` operator was originally created for language attributes (like `[lang|="en"]`). By design, it matches the hyphenated prefix (like `en-us`) _but also the exact value_ (`en`). As argued on the call, this is a massive footgun for utility classes: if you want to target all `.bi-*` icons, you probably don’t want those styles accidentally leaking to a standalone `.bi` base class. * To make the `|=` selector work for class matching, its behavior would need to change. The way the selector currently works is by only checking values from the _very beginning_ of an attribute’s string. If you have an element with multiple classes like `<div class="card btn-primary">`, the selector `[class|="btn"]` would completely fail to match it because `btn-primary` isn’t at the start of the `class` attribute. * Selector Performance _(see previous mentions about this)_. The most important reason, though, is The Bigger Picture™ for wildcards in CSS: there is an ongoing, larger effort within the CSSWG (also championed by Lea) to standardize wildcards across all of CSS By choosing `-*` as the syntax for prefixes, the syntax can later be reused for future extensions such as wildcard attribute _names_ (e.g. `[data-*]`) and wildcard (custom) element names (e.g. `custom-framework-*`). Because the `|=` selector only works for attribute _values_ , it is effectively a dead end for those other use cases. For completeness: Right after we discussed the issue at the CSS WG, w3c/csswg-drafts#14289 was filed to explore if we can relax `|=` so that it fits the bill. _If_ that can be done, it would not tick the The Bigger Picture™ box, though. * * * ### # Browser Support 💡 Although this post was originally published in August 2026, the list below is constantly being updated. _Last update: August 20, 2026_. Since this was literally just resolved at the CSSWG F2F in Berlin two weeks ago, browser support is currently non-existent. To follow along with the progress – if any – you can follow these browser issues: Chromium _(Blink)_ ❌ No Support Subscribe to CrBug #550093337 to follow along. Firefox _(Gecko)_ ❌ No Support There is no bug tracking this yet. Safari _(WebKit)_ ❌ No Support There is no bug tracking this yet. This feature is still in its early days and needs to be fleshed out further, so could be that it takes a few more years before you can use it in production … * * * ### # Feature Detection You can feature detect support with a regular `@supports` rule: @supports selector(.foo-*) { /* Browser has support */ } The following CodePen uses this and will light green when you browser supports it: See the Pen CSS Class Prefix Selector Support test by Bramus (@bramus) on CodePen. * * * ### Spread the word Feel free to reshare one of the following posts on social media to help spread the word: * 🦋 Bluesky * 🦣 Mastodon * 💼 LinkedIn ~ **🔥 Like what you see? Want to stay in the loop? Here's how:** * 🦋 Follow @bram.us on Bluesky * 🔸 Follow bram.us using RSS I can also be found on 𝕏 Twitter and 🐘 Mastodon but only post there sporadically.
000
Piccalilli @piccalilli.front-end.social.ap.brid.gy · 07/10/2026
This is the stuff that's exciting about the AT protocol. Not the "new twitter" bullshit, but the endless possibilities that this technology opens up. It's using the fancy new private data stuff too. bulleted.app
bulleted.app
Bulleted — an outliner for lists, notes, and plans
One outline instead of folders and files. Every line is saved to your own account, so it leaves when you do.
000
Piccalilli @piccalilli.front-end.social.ap.brid.gy · 02/10/2026
The Index: Issue #200 It's issue #200 of The Index! A real milestone that we should celebrate with a dedicated CSS links issue! Sponsored by our friends at Mozilla Festival. piccalil.li/the-index/200
piccalil.li
The Index: Issue #200
It's issue #200 of The Index! A real milestone that we should celebrate with a dedicated CSS links issue! Sponsored by our friends at Mozilla Festival.
000
Piccalilli @piccalilli.front-end.social.ap.brid.gy · 25/09/2026
The Index: Issue #199 It’s Friday, so let’s close out the week with some Good Links™ in The Index #199, sponsored by Mozilla Festival! piccalil.li/the-index/199
001
Piccalilli @piccalilli.front-end.social.ap.brid.gy · 23/09/2026
It's so frustrating that this still hasn't been addressed with the new "masonry" capability. Not like we covered it over two years ago or anything. Thanks to Manuel for giving us the details here. www.matuzo.at/blog/2026/grid-lanes-…
matuzo.at
Your Grid Lanes will likely fail WCAG 2.4.3 - Manuel Matuzović
I’m a frontend developer, consultant, accessibility auditor, blogger, teacher, and book author from Austria.
002
Piccalilli @piccalilli.front-end.social.ap.brid.gy · 23/09/2026
Stop buttons triggering zoom when they’re double tapped It’s annoying when your browser zooms if a button is rapidly tapped. There’s actually a really simple fix and no, you don’t have to prevent all zooming with a meta tag! […]
front-end.social
Original post on front-end.social
001
Piccalilli @piccalilli.front-end.social.ap.brid.gy · 22/09/2026
In this free lesson from JavaScript for Everyone, @Wilto introduces us to concepts like execution threads, the event loop, and the callback and microtask queues, so we can start putting them to work. piccalilli.link/js4e-free-lesson-qu…
000
Piccalilli @piccalilli.front-end.social.ap.brid.gy · 21/09/2026
There's been so much dry content about Temporal so far but Sophie Koonin has written about a nice creative usage here. localghost.dev/blog/time-based-back…
localghost.dev
Time-based background colour transitions with Temporal and CSS color-mix
I've given my website a bit of a refresh! There's a slightly updated layout if you're on desktop, plus I ditched the `etc` page and I've revamped my links page to be powered by raindrop.io. The minimalist theme is still minimalist, but a bit more fancy. The vaporwave theme has a newly jazzed-up nav bar with some adorable little icons. But the biggest change is to the city theme, which was previously a starry-sky dark mode theme. If you're reading this between the hours of 9pm - 5am, you might be wondering what all the fuss is about - it looks pretty much the same as it did before. That's because the theme changes depending on the time of day! You can select the time of day using the picker in the top right, after the theme switcher. I'm persisting the choice in session storage so you don't get attacked by sudden light mode when changing pages, but if you visit again in the future it'll reset back to "now". I was going to just turn the layout into a pastel lo-fi-aesthetic thing, but then I realised that a) I needed _some_ kind of dark mode and b) I'd miss the stars! So I thought... why not both? And why stop at just night and day? (Hat tip to Alistair Shepherd who did something similar with his beautiful Firewatch-inspired website.) Then I remembered that the Temporal API was available experimentally in Chrome and Firefox, and I'd been looking for an excuse to try it out. ## Introducing Temporal For the uninitiated, Temporal is a solution to the objectively terrible Date API in JavaScript. Date was based on Java's Date library, which was also objectively terrible and has long been deprecated. It's always really confusing that `Date` instances show either local or UTC time depending on which function you use to display them, and date operations are so fiddly that most of us turn to third party libraries like `date-fns` or `luxon`. Temporal massively simplifies the API, introducing some new concepts: * `PlainDateTime`: a date and time with no timezone (TZ) * `PlainDate`: a date with no time information and no TZ * `PlainTime`: a time with no date information and no TZ * `ZonedDateTime`: a date and time in a specified TZ `PlainTime` came in useful for this project, as we don't really care what the day is - only what time it is, so we know what colours to show. ### Getting the user's local time The first thing to do was figure out the time according to the user's browser. The `Temporal.Now` namespace has various methods for interacting with the current time, including `plainTimeISO()` which by default gives us a `PlainTime` in local time. (You can also pass in a time zone to get a zoned time.) const timeNow = Temporal.Now.plainTimeISO(); Now we need to know when to show the different colours. ## Defining the stages The day is split into four stages: sunrise, daytime, sunset and night. Daytime and night are long - 11.5 hours each - whereas sunrise and sunset each last 90 minutes. The background of the page has a two-colour gradient: --background: fixed linear-gradient(var(--bg-gradient-top), var(--bg-gradient-mid) 80%); The footer has an additional colour that's created with a linear gradient from transparent `oklch(0 0 0 / 0)` to the chosen third colour. background: linear-gradient(oklch(0 0 0 / 0) 40%, var(--bg-gradient-bottom)); This means the last colour sticks to the bottom of the page rather than stretching across the viewport height (it's hard to control even when you specify a percentage in the gradient). It also gives more of a glow that really looks like the sun rising/setting or the glow of the city, which I love. I defined an object for the stages and colours: const stages = { sunrise: { start: Temporal.PlainTime.from("06:30:00"), next: "day", color1: "oklch(0.618 0.3157 265.76)", color2: "oklch(0.8867 0.1222 328.24)", color3: "oklch(0.9529 0.1222 106.94)", }, day: { start: Temporal.PlainTime.from("08:00:00"), next: "sunset", color1: "oklch(58% 0.15433 300)", color2: "oklch(85% 0.22133 302)", color3: "oklch(98 0.22133 302)", }, sunset: { start: Temporal.PlainTime.from("19:30:00"), next: "night", color1: "oklch(0.6933 0.1899 297.53)", color2: "oklch(75.504% 0.24612 357.26)", color3: "oklch(88.591% 0.1422 62.595)", }, night: { start: Temporal.PlainTime.from("21:00:00"), next: "sunrise", color1: "oklch(25.27% 0.0919 276.73)", color2: "oklch(47.35% 0.284 283.78)", color3: "oklch(62.831% 0.23521 310.291)", }, }; CSS custom properties are easy to set via JS - you can use `root.style.setProperty`: root.style.setProperty( "--bg-gradient-top", "oklch(25.27% 0.0919 276.73)", ); Unlike `Date`, we don't have to do any gymnastics to compare Temporal instances: there's literally a `compare` function on each type of instance. Just like with other JS comparison functions, it returns `1` if the first instance is greater than the second, `0` if the two instances are the same, and `-1` if the first instance is less than the second. const compare = Temporal.PlainTime.compare // extracted for brevity switch (true) { case compare(timeNow, stages.sunrise.start) < 0 || compare(timeNow, stages.night.start) >= 0: { currentStageName = "night"; break; } case compare(timeNow, stages.sunrise.start) >= 0 && compare(timeNow, stages.day.start) < 0: { currentStageName = "sunrise"; break; } case compare(timeNow, stages.day.start) >= 0 && compare(timeNow, stages.sunset.start) < 0: { currentStageName = "day"; break; } case compare(timeNow, stages.sunset.start) >= 0 && compare(timeNow, stages.night.start) < 0: { currentStageName = "sunset"; break; } default: break; } Once we've got the stage name, we can look up the colours and set the custom property values. root.style.setProperty( "--bg-gradient-top", stages[currentStageName].color1, ); root.style.setProperty( "--bg-gradient-mid", stages[currentStageName].color2, ); root.style.setProperty( "--bg-gradient-bottom", stages[currentStageName].color3, ); I'm also setting a data attribute on the root so we can do some additional stage-based customisations, such as showing the stars when it's night. root.setAttribute("data-time", currentStageName); And that will give us our different gradient colours at different times of day! And _then_ I remembered that `color-mix` exists. Why restrict ourselves to just 4 times of day and 4 sets of colours, when we could make them... transition into each other????? ## Blending transitions with color-mix `color-mix` is an extremely cool CSS function that lets you, well, mix two colours together. You tell it what colour space you're working with, and the colours, and the browser magically outputs the mix between the two. background: color-mix(in oklch, color1, color2) Much like with gradients, you can also specify a percentage value for the colours, which indicates the proportions of the colours: background: color-mix(in oklch, color1 20%, color2) So I could gradually feed in a bit of the next stage's colour until the next stage took over completely. To get a percentage value for the next stage colour to feed in, I had to figure out how far through the current stage we are. First, I'm calculating the time until the next stage - super simple with the `until` function on `Temporal` instances: time1.until(time2) This gives us a `Temporal.Duration` which represents a period between two time points. So, for example, if it's 7:45pm now and we're calculating `timeUntilNextStage`: const timeUntilNextStage = timeNow.until(stages.night.start) console.log(timeUntilNextStage.toString()) // PT1H15M `Duration`s are stringified (and specified) using the ISO 8601 duration format, so "PT1H15M" means "period, time separator, 1 hour, 15 minutes".Time information appears after the `T`; if the duration had any date information in it, it'd appear before the `T`. We set `timeUntilNextStage` in the switch statement where we're deciding what stage we're in, for example: case compare(timeNow, stages.sunrise.start) >= 0 && compare(timeNow, stages.day.start) < 0: { currentStageName = "sunrise"; timeUntilNextStage = timeNow.until(stages.night.start); break; } Once we've got the duration representing time until the next stage, we need to know the duration between the start of the current stage and the start of the next stage - let's call it the "transition duration". For sunset-to-night and sunrise-to-day, the transition duration is always 90 minutes; for night-sunrise and day-sunset, it'd be 11.5 hours. I didn't want the colour mixing to happen all throughout the day, only around sunrise/sunset like in real life, so I just decided to hardcode the transition duration for day and night to be 90 minutes so it matches the other two. So for that, I can instantiate a `Duration` using the same ISO 8601 syntax: const entireTransitionDuration = Temporal.Duration.from("PT1H30M") Now I need to calculate the difference between the total duration and the time until next stage - basically, how far into the transition period we are, and therefore how much of a percentage we should mix in of the next colour. Handily, Temporal gives us a `subtract` function as well: const diff = entireTransitionDuration.subtract(timeUntilNextStage) Then to figure out the transition progress as a percentage, we can divide `diff` by `entireTransitionDuration`. We'll do that with the time values in seconds so we can divide them, using the instance's `total` function: const entireTransitionDurationInSeconds = entireTransitionDuration.total({ unit: "seconds" }) const diffInSeconds = diff.total({ unit: "seconds" }) const transitionProgressPercent = Math.round((diffInSeconds / entireTransitionDurationInSeconds)*100).toFixed() // gives us a string representation with 0 d.p. ### The midnight problem It's a little more complicated for the "night" stage, because that crosses midnight into the next day. Remember that our `PlainTime` only has time information, not date information - so if it's 10pm and you're asking it how long until sunrise at 6:30am, it'll give you a negative number! const now = Temporal.PlainTime.from("22:00") const sunrise = Temporal.PlainTime.from("06:30") const d = now.until(sunrise) // Temporal.Duration -PT15H30M This causes problems at the point where I calculate the diff, as it'll come out as a large number and completely throw off the calculations. I got around this by getting the absolute value of the duration with `.abs()`, so `timeUntilNextStage` will always be positive, even if it's before midnight: e.g. what was`-PT15H30M` will now be `PT15H30M`. Calculating the diff by subtracting that from a `transitionDuration` of 90 mins will always yield a negative number. case compare(timeNow, stages.sunrise.start) < 0 || compare(timeNow, stages.night.start) >= 0: { currentStageName = "night"; timeUntilNextStage = timeNow.until(stages.sunrise.start).abs(); break; } Then, we only calculate a transition percentage if `diff` is greater than 0: let transitionProgressPercent = 0; if (diffInSeconds > 0) { transitionProgressPercent = Math.round((diffInSeconds / entireTransitionDurationInSeconds) * 100); } This works for the daytime stage too: if it's more than 90 mins before sunset, it'll come out with a negative diff - so that will just display the daytime colours and no transition. ### Let's mix! Now we can use that percentage value (which will always be a whole number) in the `color-mix` function to dictate how much of the next colour we should interpolate. color-mix(in oklch, ${color1} ${transitionProgressPercent}%, ${color2}) I updated my `stages` object to include the next stage name as well: night: { start: Temporal.PlainTime.from("21:00:00"), next: "sunrise", color1: "oklch(25.27% 0.0919 276.73)", color2: "oklch(47.35% 0.284 283.78)", color3: "oklch(62.831% 0.23521 310.291)", }, // etc So we can get both colours dynamically when we set the variables with `color-mix`: root.style.setProperty( "--bg-gradient-top", `color-mix(in oklch, ${stages[nextStageName].color1} ${transitionProgressPercent}%, ${stages[currentStageName].color1})`, ); And that's how we transition the colours! ## Transitioning the transitions As a bonus touch, I wanted the colour change to transition smoothly when you switch between stages manually using the picker on the top right. By declaring my `bg-gradient-xx` variables using `@property`, I can tell the browsers that yes, they are definitely colours - and therefore they can be animated. Without this explicit custom property declaration, I could set the value of `--bg-gradient-top` to a number, or a position, or anything I wanted. By saying it's definitely a colour, the browser knows how to transition it into other values of the same type. I initially did this with `@property` declarations in the CSS: @property --bg-gradient-top { syntax: "<color>"; inherits: true; initial-value: oklch(...); } @property --bg-gradient-mid { syntax: "<color>"; inherits: true; initial-value: oklch(...); } @property --bg-gradient-bottom { syntax: "<color>"; inherits: true; initial-value: oklch(...); } Unfortunately, setting these in the CSS meant that you got a flash of whichever initial values I'd set before the JS kicked in and set the appropriate colours for time of day. If this page were server-driven, or always started from the same colour for everyone, it would've been fine. But the starting colour depends on your time zone and is only calculated when the initial JS runs. I got around this by setting the properties via JS instead: window.CSS.registerProperty({ name: "--bg-gradient-top", syntax: "<color>", inherits: true, initialValue: stages[currentStageName].color1, }); window.CSS.registerProperty({ name: "--bg-gradient-mid", syntax: "<color>", inherits: true, initialValue: stages[currentStageName].color2, }); window.CSS.registerProperty({ name: "--bg-gradient-bottom", syntax: "<color>", inherits: true, initialValue: stages[currentStageName].color3, }); I had to wrap these in a `try/catch` as it will throw if the property's already been defined. It wasn't super trivial to figure out if this property had already been set, as the CSS does define some values for these with the regular `--bg-gradient-xx: ...` syntax. On the `body` and `footer` I set `transition-property` and `transition-duration` to tell it which properties I want to animate: body { --background: fixed linear-gradient(var(--bg-gradient-top), var(--bg-gradient-mid) 80%); transition-property: --bg-gradient-top, --bg-gradient-mid; transition-duration: 0.5s; } footer { background: linear-gradient(oklch(0 0 0 / 0) 40%, var(--bg-gradient-bottom)); transition: --bg-gradient-bottom 0.5s; } And like motherflipping magic, the colours transition seamlessly into each other when the values change! I love CSS. The animation is such an unnecessary touch, but this is my website so unnecessary is the name of the game. ## Polyfilling Temporal for Safari Alas, Safari is behind the times. We love progressive enhancement, and of course I could have just removed any of the transition logic for people whose browsers don't support Temporal, but that's no fun. They deserve sunsets too! Writing a shim for Temporal was also no fun, but I did it because I love you. There are various Temporal polyfills around and about, but I didn't want to end up importing a whole lot of extra JS when I only needed one or two functions. I'm not using any kind of bundler on this site - I use Eleventy to generate the pages, but scripts are just imported vanilla - so I couldn't import something with NPM and expect it to tree-shake any bits I wasn't using. It was a lot more lightweight to just write my own. To check for Temporal support, it's a matter of just checking if `window.Temporal?.PlainTime` is undefined: const supportsTemporal = typeof window.Temporal?.PlainTime !== "undefined"; I'm checking for `PlainTime` specifically as some browsers may have very high level Temporal implementations, but we can't do much without `PlainTime`. To get the user's time in a non-Temporal world, we can just call the good old-fashioned `new Date()`: function getUserTime() { if (!supportsTemporal) { return new Date(); } return Temporal.Now.plainTimeISO(); } To compare dates, we do it by comparing epoch timestamps. These represent the number of milliseconds since the Unix epoch, 01 Jan 1970. export function jsDateCompare(date1, date2) { const date1Ms = date1.getTime(); const date2Ms = date2.getTime(); if (date1Ms === date2Ms) return 0; return date1Ms < date2Ms ? -1 : 1; } Then, we can just assign whichever version of the function we need: const compare = supportsTemporal ? Temporal.PlainTime.compare : jsDateCompare; To polyfill `until`, I've got a `durationBetween` function which will call `until` if Temporal's supported, otherwise it'll subtract two epoch timestamps, and divide the result by 1000 to get the duration as seconds: export function durationBetween(time1, time2) { if (!supportsTemporal) { return (time2.getTime() - time1.getTime()) / 1000; } return time1.until(time2); } Then I call it like this: case compare(timeNow, stages.sunset.start) >= 0 && compare(timeNow, stages.night.start) < 0: { currentStageName = "sunset"; timeUntilNextStage = durationBetween(timeNow, stages.night.start); break; } I wrote a whole suite of unit tests (for a PERSONAL project! I know!) to make sure behaviour was exactly the same, and it seems to be working nicely. I'm hoping I can remove the polyfills in time, but given that the web is beautifully backwards-compatible, it's not the end of the world if it stays around longer than it needs to. ## Fixing a weird background glitch in Safari SAFARI WHY. I started experiencing a very odd glitch in Safari for MacOS where the background gradient would only show up in the initial viewport - when you scrolled, it went white or black depending on whether it was light or dark mode. I narrowed it down to the `background-attachment: fixed` property of the body. After a lot of disabling random CSS and diffing against the `main` branch, which doesn't have that problem, I found out that it really doesn't play nicely with `container-type: inline-size`. In the course of redesigning the site, I'd added a new container context to the `<body>` element and in the process broken the gradient rendering for Safari, as something goes wrong when it tries to render the gradient background with a `fixed` attachment. Chrome, Firefox and iOS Safari were totally fine. When I half-jokingly said I'd have to find out what the modern equivalent of `<!--[IF IE]>` was, David Bushell pointed me to Eric Meyer's post about accessible table headers, which in turn led me to Browser Strangeness. That did indeed have a `@supports` query that targeted Safari for MacOS: @supports (not (-webkit-text-size-adjust:none)) and (font: -apple-system-body) { .selector { property:value; } } } I stuck a `position-attachment: initial` in there, and lo and behold, the problem went away. It means the background isn't quite how I wanted it to look in Safari, but I'll survive. ## This was surprisingly complex The individual moving parts of this project - getting the time and choosing colours, mixing the colours by percentages, animating the transitions - were not that complicated in isolation. Sure, they required me to learn things and look things up, but it was a fun thing to build (until the point where Safari came into the picture). The challenge came wiring it all together in a way that didn't cause flashes of unstyled content (the dreaded FOUC) or flashes of the wrong stage before we calculate the current time. This is a static site, so it's all client-side JS. Ideally I'd compute user's the current time on the server and serve the content with the correct colour values in the HTML, but my web host only supports static sites. To get around that I had to add another separate `init.js` script which runs instantly - it's got a bit of a copy and paste job going on with some of the functions, but it does a very rudimentary check of the user's current time and sets the stage accordingly with no transitions, just so there's _some_ styling on initial load. My JS is all modules, so is deferred by default. I experimented with making _all_ the JS render-blocking with `blocking="render"`, but that felt a bit gross and also didn't fix the FOUC in Firefox. But that's fine, y'know? It still loads in well under a second, and still looks good if you have JS disabled. It's my personal site and it doesn't need to be perfect.
013
Piccalilli @piccalilli.front-end.social.ap.brid.gy · 21/09/2026
🚨 Our huge 35% discount on all courses ends tomorrow! 🚨 Use the coupon code PRICEFALL at checkout. Don't miss out! piccalil.li/courses
Autumn sale, September 8th until September 22nd. 

35% off all courses. 

Save over £80 with code “PRICEFALL”
001
Piccalilli @piccalilli.front-end.social.ap.brid.gy · 18/09/2026
Dan Cederholm has brought out yet another affordable, and very nice font. simplebits.shop/products/kyroh
simplebits.shop
Kyroh
Kyroh is an all-caps, sans-serif display font inspired by a vintage luggage label from the National Hotel in Cairo, Egypt (found in the amazing book from Letterform Archive). I riffed off the hand-lettered word “EGYPT” from the label and filled out the rest of the alphabet informed by those five letterforms. I love how
000
Piccalilli @piccalilli.front-end.social.ap.brid.gy · 18/09/2026
Our huge 35% discount on all courses ends on Tuesday. Use the coupon code PRICEFALL at checkout. Don't miss out! piccalil.li/courses
Autumn sale, September 8th until September 22nd. 

35% off all courses. 

Save over £80 with code “PRICEFALL”
000
Piccalilli @piccalilli.front-end.social.ap.brid.gy · 18/09/2026
The Index: Issue #198 It’s Friday, so let’s close out the week with some Good Links™ in The Index #198! piccalil.li/the-index/198
piccalil.li
The Index: Issue #198
It’s Friday, so let’s close out the week with some Good Links™ in The Index #198!
001
Piccalilli @piccalilli.front-end.social.ap.brid.gy · 17/09/2026
A decent custom checkbox pattern for until ::checkmark is ready We're a while off from being able to leverage ::checkmark with checkbox styling, but here's a decent way to get the styling control you want, while preserving the useful parts of the native input element […]
front-end.social
Original post on front-end.social
000
Piccalilli @piccalilli.front-end.social.ap.brid.gy · 16/09/2026
Mat has not given up and nor should you! wil.to/js4e/newsletter/mat-update-22
000
Piccalilli @piccalilli.front-end.social.ap.brid.gy · 15/09/2026
There's only one week left of our huge 35% discount on all courses if you use the coupon code PRICEFALL at checkout. Don't miss out! piccalil.li/courses
Autumn sale, September 8th until September 22nd. 

35% off all courses. 

Save over £80 with code “PRICEFALL”
010
Piccalilli @piccalilli.front-end.social.ap.brid.gy · 14/09/2026
We very rarely share video content, but this episode was an absolute delight to watch. They're running a hackathon too which is well worth checking out. codetv.dev/series/web-dev-challenge…
000
Piccalilli @piccalilli.front-end.social.ap.brid.gy · 11/09/2026
A very handy looking tool that certainly beats a laborious Photoshop process. ditherimage.online
ditherimage.online
Dither Image Online
Get professional dithering effects instantly with this dithertone free tool. Features real-time, local processing with no sign-up required for maximum speed and security.
000
Piccalilli @piccalilli.front-end.social.ap.brid.gy · 11/09/2026
Just a quick reminder, we're offering a huge 35% discount on all courses if you use the coupon code PRICEFALL at checkout. piccalil.li/courses
Autumn sale, September 8th until September 22nd. 

35% off all courses. 

Save over £80 with code “PRICEFALL”
001
Piccalilli @piccalilli.front-end.social.ap.brid.gy · 11/09/2026
The Index: Issue #197 It’s Friday, so let’s close out the week with some Good Links™ in The Index #197! piccalil.li/the-index/197
piccalil.li
The Index: Issue #197
It’s Friday, so let’s close out the week with some Good Links™ in The Index #197!
001
Piccalilli @piccalilli.front-end.social.ap.brid.gy · 10/09/2026
JavaScript for Everyone's Mat Marquis takes us on a history lesson of how document.all ended up being the only object that is falsy. Not to spoil the show, but it's IE's fault… youtube.com/shorts/Ntii4QH6iaY Grab JavaScript for Everyone here: piccalil.li/javascript-for-everyone
piccalil.li
JavaScript for Everyone
A high quality, expansive written course that will elevate your JavaScript skills to a level you never thought was achievable.
000
Piccalilli @piccalilli.front-end.social.ap.brid.gy · 09/09/2026
This is great fun! bayeuxgo.live
bayeuxgo.live
Bayeux Go!
Bayeux Go! Thou art Harold. Go forth and win thine battle. Jump your way to victory, avoiding arrows…
012
Piccalilli @piccalilli.front-end.social.ap.brid.gy · 09/09/2026
Save 35% on all courses for two weeks only The summer is over and the autumn (or fall, for our American friends) is here, so we're offering a huge 35% discount on all courses if you use the coupon code PRICEFALL at checkout. piccalil.li/links/pricefall-2026
piccalil.li
Save 35% on all courses for two weeks only
The summer is over and the autumn (or fall, for our American friends) is here, so we're offering a huge 35% discount on all courses if you use the coupon code PRICEFALL at checkout.
000
Piccalilli @piccalilli.front-end.social.ap.brid.gy · 08/09/2026
In this free lesson from JavaScript for Everyone, @Wilto guides us through the intricacies of asynchronous functions, restitching the fabric of time in the process. piccalilli.link/js4e-free-lesson-qu…
000
Piccalilli @piccalilli.front-end.social.ap.brid.gy · 08/09/2026
The summer is over and the autumn (or fall, for our American friends) is here, so we're offering a huge 35% discount on all courses if you use the coupon code PRICEFALL at checkout. piccalil.li/courses
Autumn sale, September 8th until September 22nd. 

35% off all courses. 

Save over £80 with code “PRICEFALL”
000
Piccalilli @piccalilli.front-end.social.ap.brid.gy · 07/09/2026
Even the new custom <select> capabilities bring headaches, but at least Jake's got height sorted for you. jakearchibald.com/2026/goldilocks-s…
jakearchibald.com
The Goldilocks customizable select height
The 'ideal' sizing is more complicated than you think…
011
Piccalilli @piccalilli.front-end.social.ap.brid.gy · 04/09/2026
'It’s hard to find anything other than anecdata from individuals telling us how AI has made them individually more productive. If AI really was creating measurable improvements in productivity across entire organisations, wouldn’t we be seeing that data?' […]
front-end.social
Original post on front-end.social
000
Piccalilli @piccalilli.front-end.social.ap.brid.gy · 02/09/2026
A rather deep dive into how some of the newer CSS can make this age old pattern even better. dbushell.com/2026/07/03/fixing-full…
000
Piccalilli @piccalilli.front-end.social.ap.brid.gy · 31/08/2026
One for the freelancers/moonlighters amongst us who are sick of over-complicated invoice solutions. usebilly.app/en
usebilly.app
Billy · Invoice Manager
A tool crafted with love for all the freelancers and small businesses that want their bureaucracy to be a little more organized and beautiful.
010
Piccalilli @piccalilli.front-end.social.ap.brid.gy · 28/08/2026
It's about time we got this! www.matuzo.at/blog/2026/content-awa…
010
Piccalilli @piccalilli.front-end.social.ap.brid.gy · 28/08/2026
The Index: Issue #196 It’s Friday, so let’s close out the week with some Good Links™ in The Index #196! piccalil.li/the-index/196
001
Piccalilli @piccalilli.front-end.social.ap.brid.gy · 27/08/2026
Grab JavaScript for Everyone here: piccalil.li/javascript-for-everyone
piccalil.li
JavaScript for Everyone
A high quality, expansive written course that will elevate your JavaScript skills to a level you never thought was achievable.
000
Piccalilli @piccalilli.front-end.social.ap.brid.gy · 27/08/2026
We asked @Wilto what it is he likes about JavaScript. Knowing Mat, it's no surprise to hear him talk with affection for all its rough edges. "A little dust on the lens is all part of the charm" as he puts it. We've got a lot of time for this perspective. piccalilli.link/js4e-q-and-a-1
000
Piccalilli @piccalilli.front-end.social.ap.brid.gy · 27/08/2026
A CLI for adding new music to the collection Adding markdown files for every new music item is a faff, so I built a multi-step CLI to do it for me. piccalil.li/projects/personal-site/…
piccalil.li
A CLI for adding new music to the collection
Adding markdown files for every new music item is a faff, so I built a multi-step CLI to do it for me.
001
Piccalilli @piccalilli.front-end.social.ap.brid.gy · 26/08/2026
Actually mind-blowing read. Never use client-side only auth, pals. bobdahacker.com/blog/fifa-hack
bobdahacker.com
I Could've Rickrolled the Entire FIFA World Cup. All I Needed Was My ID.
How I found that anyone could register on FIFA's public Agent Platform, gain access to the Football Data Platform's Streaming Management panel, and get RTMP ingest URLs and stream keys for every live FIFA World Cup 2026 camera feed. I then spent hours calling FIFA, MediaKind, HBS, CISA, and the FBI trying to get someone to pick up the phone.
001
Piccalilli @piccalilli.front-end.social.ap.brid.gy · 24/08/2026
A very good explainer of how the AT protocol stuff actually works. overreacted.io/a-social-filesystem
003
Piccalilli @piccalilli.front-end.social.ap.brid.gy · 21/08/2026
'We’re approaching—or arguably are in—an era where ‘made by humans’ is a differentiator. A notion that something beyond money and prompts was put into whatever the heck it is we’re using or consuming or enjoying. We should never lose sight of that.' mindfuldesign.xyz/by-humans
mindfuldesign.xyz
By Humans, For Humans – Mindful Design
Now, more than ever, we need to consider the real, human impact of the work we do. This is a fundamental principle of Mindful Design.
010
Piccalilli @piccalilli.front-end.social.ap.brid.gy · 21/08/2026
The Index: Issue #195 It’s Friday, so let’s close out the week with some Good Links™ in The Index #195! piccalil.li/the-index/195
piccalil.li
The Index: Issue #195
It’s Friday, so let’s close out the week with some Good Links™ in The Index #195!
001
Piccalilli @piccalilli.front-end.social.ap.brid.gy · 20/08/2026
A look at the geolocation HTML element and how it works There’s a new HTML in town that provides a much better interface for geolocation than the older API. Daniel Schwarz is here to explain how it all works together […]
front-end.social
Original post on front-end.social
021
Piccalilli @piccalilli.front-end.social.ap.brid.gy · 19/08/2026
We challenged Alistair to write this and boy, did they deliver! www.mohkohn.co.uk/writing/html-first
mohkohn.co.uk
How building an HTML-first site doubled our users overnight
My client was a utility company, and they had a big problem...
000
Piccalilli @piccalilli.front-end.social.ap.brid.gy · 18/08/2026
In this free lesson from JavaScript for Everyone, @Wilto shows us to put the (eventual) results represented by one or more promises to use. piccalilli.link/js4e-free-lesson-qu…
000
Piccalilli @piccalilli.front-end.social.ap.brid.gy · 17/08/2026
This is a really nice RSS reader-like standard.site reader. standard-reader.app
standard-reader.app
Today · Standard Reader
Fresh writing from the publications you subscribe to, every day.
011