On Apple products and the subjectivity of “expensive”

Until the Apple Watch came along, I had gone 20 years without wearing a watch. And before that, the most I had ever spent on one was $50. A watch was not an object I was willing to spend more money on than that. But then Apple released a very compelling product, and I went for it. (Even if, in retrospect, the original iteration of the Apple Watch was… underwhelming.)

I’ve owned three Apple Watches: the original (a.k.a. “Series 0”), a Series 3, and my current one, a Series 5. But after visiting a local Apple Store yesterday and looking at the latest models, I decided it was time to upgrade my Apple Watch experience once again, so today I ordered a Series 8… and for the first time, I sprung for the extra $100 to get one with cellular. Now I can finally go out running without bringing my phone. This will transform my running experience… once the snow melts and I’m back outside, that is. (But actually it will transform my experience on the indoor track too. I hate having my phone in my pocket when I run!)

Of course, running without my iPhone means that also means I need wireless earbuds. I may be willing to drop a lot of cash on a lot of Apple products, but AirPods are one area where I feel their options are way overpriced. Well… maybe not overpriced for what they offer, but more than I am willing to spend, for features I don’t really care about.

What’s worse, I can’t stand hard plastic earbuds, and Apple’s only option with silicone tips is the $250 “Pro” models. There’s no way I’m paying that much of a premium for something so easily lost. I’d rather stick with my tried-and-true $9 wired Panasonic earbuds. (But of course you can’t use wired earbuds with the watch!)

Anyway, I found these no-name Bluetooth earbuds on Amazon for $20. I’m going to give them a try before I go for anything higher-end. Hopefully they won’t explode while I’m wearing them!

Bootstrap 5 Carousel: position captions outside (i.e. below) the images

(If you want to get right to the point, a full code example is provided at the bottom of this post.)

I’m not a Bootstrap expert. During most of its evolution, I’ve mostly ignored it in favor of rolling my own… everything. I finally really embraced Bootstrap when I had a rush project in October 2022 that was way too precisely designed to work with WordPress, especially Gutenberg. (And the client didn’t need editing capabilities.) So I decided to hand-code it, but to use Bootstrap to… uh… bootstrap my HTML/CSS layouts.

I think Bootstrap 5 is excellent. I wish Gutenberg was built on top of it instead of the idiosyncratic house of cards it’s actually built on, but whatever.

As it happens, I’m actually now using Bootstrap 5 with Gutenberg for some custom blocks, specifically a Carousel block. One of the options I want to provide in my block is the ability to show the carousel’s captions and controls outside of the image, but apparently, at least with Bootstrap 5, that’s not an option.

I decided to Google for a quick solution before creating my own and I came across… this. I’m sure it gets the job done, but it seems severely over-engineered, so here I’m presenting my own comparatively simple, CSS-only solution.

Basically there are two things you need to do: 1) move the absolute-positioned caption text below the image, and 2) add padding to the bottom of the container, so the caption has somewhere to go instead of just overlapping the content below it.

Let’s start with the second item first:

.carousel { padding-bottom: 4rem; }

There may be some trial and error here, as you need to make sure you’re accommodating captions of varying length. I will admit this is not fully thought out here, and unlike the rest of what is about to follow, it may be a deal breaker under certain circumstances. But let’s assume your captions are a fairly consistent length, and you can determine how much padding you need.

Getting the caption pushed below the images is easy…

.carousel-caption { top: 100%; }

…except, oops, vertical overflow is hidden. Let’s fix that:

.carousel-inner { overflow: visible; }

Of course, if you have your transition effect set to slide (which is the default), that now spews stuff all over the page in an ugly way. But we can fix that by hiding overflow on the outer carousel element instead:

.carousel { overflow: hidden; }

You might, at this point, wonder why I didn’t just set overflow-y: visible on .carousel-inner which seems perfectly reasonable, and which, of course, I tried. But for reasons I couldn’t be bothered with investigating, that ended up causing .carousel-inner to just show a vertical scrollbar and not display the caption unless you scrolled it. Ugh. No matter, the above takes care of it.

That’s pretty much it, as far as the captions go. But if you’re using the controls (previous/next arrows) or indicators (dots/lines for the number of slides and current selection), you’ll notice there’s some weirdness to their placement, so let’s fix that too. The indicators just get shoved to the bottom of the container, so your bottom padding can accommodate that. But if you want to move them back up onto the image, you just need to offset that extra padding, like this:

.carousel-indicators { bottom: 4rem; }

Make that value the same as the bottom padding you added to .carousel itself.

As for the controls, since you’ve made the overall container taller, they’re now a bit too low rather than being vertically centered on the image. Guess what… setting their bottom value to match the extra bottom padding fixes their placement too!

.carousel-control-next, .carousel-control-prev { bottom: 4rem; }

So, putting it all together as concisely as possible, here’s what we have:

.carousel { overflow: hidden; padding-bottom: 4rem; }
.carousel-caption { top: 100%; }
.carousel-inner { overflow: visible; }
.carousel-control-next, .carousel-control-prev, .carousel-indicators { bottom: 4rem; }

WooCommerce code snippet: convert the Order Notes field into an EU VAT ID field

The scenario: My WooCommerce store has no need for the Order Notes field. In fact, up until now I had it hidden on the checkout page. But what my site does need is an EU VAT ID field. The portion of my business that takes place in Europe is, so far, well below the VAT reporting threshold, but I am increasingly being asked by customers to provide an invoice containing their VAT ID.

Well, my site does already produce PDF invoices. But there was no way for customers to include their VAT ID on the invoice. Until now.

A simple code snippet converts the existing WooCommerce Order Notes field into an EU VAT ID field, including changing it from a <textarea> to an <input type="text"> field. Put this in your theme’s functions.php file, or wherever else is appropriate in your setup:

add_filter('woocommerce_checkout_fields', function($fields) {
    $fields['order']['order_comments'] = array_merge(
            'class' => array('eu-only'),
            'label' => 'EU VAT ID',
            'type' => 'text',
    return $fields;
}, 10, 1);

That’s it. You can stop right here. But you may notice a line in there that seems unnecessary: 'class' => array('eu-only')

What’s that all about? Well, I’m using that with a bit of jQuery to enhance the functionality: only showing my new EU VAT ID field when the user’s selected Billing Country is an EU country.

Here’s a JavaScript function you can use to dynamically show/hide elements with an .eu-only CSS class, depending on a given passed-in value:

function showHideEUOnly(val) {
    var eu = ['AT', 'BE', 'BG', 'HR', 'CY', 'CZ', 'DK', 'EE', 'FI', 'FR', 'DE', 'GR', 'HU', 'IE', 'IT', 'LV', 'LT', 'LU', 'MT', 'NL', 'PL', 'PT', 'RO', 'SK', 'ES', 'SE', 'GB'];
    if (eu.indexOf(val) != -1) {
    else {

I obtained the list of EU VAT-applicable countries here, and I decided to include 'GB' (the United Kingdom) in the list, despite… y’know, uh… Brexit, because I have the vague impression that UK customers may still be impacted by VAT policies. (Being a dumb American, I don’t know much about it. I think maybe the UK has its own VAT now? Anyway, suffice to say, you may want to modify your list of 2-digit country codes in the eu array, as applicable to your situation.)

This function isn’t going to do anything unless it’s called though, so let’s do that. Here’s a bit of jQuery that will call it both on the initial page load and any time the Billing Country field changes:

jQuery(function() {
    if (jQuery('body').hasClass('woocommerce-checkout')) {
        jQuery('select[name="billing_country"]').on('change', function() {

Both of these JavaScript snippets can go in a script.js file in your theme, or wherever else is appropriate in your setup.

That’s the end of the story, but there’s more…

Incidentally, there’s more to my custom setup. I’ve significantly modified the layout of my checkout page. I’ve got WooCommerce configured for billing addresses only, with this setting in Shipping Options:

I then used CSS to hide everything else in the second column (including, up until now, the Order Notes field) and moved the product summary and payment information up into that space. Explaining all of that is outside the scope of this post, but one thing you may find useful is my CSS for hiding the “Additional Information” <h3> heading. This selector is a bit of overkill, but it works:

body.woocommerce-checkout .woocommerce > form.checkout .col2-set > .col-2 .woocommerce-additional-fields > h3:first-child { display: none; }

There’s context in my CSS file to justify all of that, but you should be able to accomplish the same with just this:

.woocommerce-additional-fields > h3 { display: none; }

On the Futility of Naming Colors

I posted this over on the ICS Calendar blog, but it’s probably of some interest to the audience of this blog, dealing with the intersection of code and design.

HTML has “named colors” which are… weird. And I have finally given up on using them in the plugin, which was a bit of a silly thing to do anyway, when there are 16 million RGB colors at one’s disposal. The post ends with swatches of both the old and the newly revised color palettes.


How I learned to stop worrying and embrace being a middle-aged curmudgeon

Reading John Gruber’s thoughts on the potential future existence of an iPhone SE 4, which necessarily included a note on the lack of an ongoing slot in the iPhone lineup for my beloved 13 mini, got me once again thinking more generally about something that’s been on my mind a lot lately, as my 49th birthday approaches in 2023.

That thing I have been thinking is, this is not the future I signed up for. I’m beginning to understand why older people get set in their ways and cranky about change, in ways that probably could not have occurred to me until I had lived enough to experience a lot of things firsthand.

I remember lots of things, both good and bad. Most things, in general, improve and get better over time. But some things get worse — at least, worse by my subjective standards, because there are certain things I don’t want, like a phone that’s too big for me to use with one hand, or fit comfortably in my jeans pocket.

Possibly even more frustrating than things that get worse are things that just stop existing. Things I really liked that are now gone, especially if they are gone for reasons that I do not think are sound. That happens a lot for me with styles of music or genres of video games. I want new synthwave music* that I can get as excited about as when I first heard Tycho and Com Truise in 2012, for instance, or new Castlevania games that are as good as Aria of Sorrow.

True, sometimes those glories do return, and 2021 gave me a double whammy, in the form of Mitch Murder’s Then Again album and the all-time classic Metroid Dread. But more often than not, I just have to move on and give up on dreaming of a decent modern SimCity game, or a computer Scrabble that will ever be even a fraction as “smart” as the GameHouse version I loved back in 2006.

The world is change. I get that. It’s just hard to let go of things that I know are better than what has replaced them, and that makes me cling desperately to the old things I have that I’m still able to enjoy, whether that’s 40-year-old vinyl records on my turntable or 20-year-old GameBoy Advance ROMs in an emulator.

The true nature of the curmudgeon, I think, is not borne of pessimism. The curmudgeon is not purely a crank. It’s optimism, idealism. Belief in a world that could and should be better than the one we’re living in, because you remember something that’s gone. And beyond that, you remember the trajectory those past things suggested we were on. But somehow we never got where we were going.

Now, let me be clear: I’m not one of those MAGA types who longs for a return to their fictional, idealized version of what the 1950s were (at least, for white people). I’m inspired by the progress we are making towards a more equitable society for everyone — even as I see how far we still have to go. I’m longing for a future world that never was, but that I believed we could — and would — have by now.

Ironically, it was the sci-fi dystopias that were so popular in my childhood in the 1980s, that seemed to get many of the worst things about the 21st century right. The techno-fascism of corporations more powerful than governments, spying on our every action as a way to make more money. Aggregations of the intimate details of millions of people’s personal lives have become the most valuable commodity around. I didn’t believe this was the future we would have, but here we are.

I just want a small phone, some good music to listen to on it (ideally through wired headphones and a 1/8″ jack), and maybe a couple of games that are actually mentally stimulating instead of just “idle” ways to give up money or personal information in an endless stream of “microtransactions.”

Is that too much to ask?

* Of course, synthwave itself is an appeal to the future-should-be-better-than-it-is nostalgia of people (like me) who grew up in the 1980s.