When I first heard about Rahul writing a book titled “Kokoro — 31 Timeless Japanese Lessons for the Everyday Life” I wasn’t too interested in the topic. However, I’ve been enjoying to read hurly.com for quite a while now and really like Rahul’s writing. So I figure why not just give it a try, let myself be surprised and pre-ordered the book. Worst thing that can happen: I support an author I like reading.

XKCD 303, forked for Figma

Too much waiting. Waiting for components to get published. Waiting for updated components to appear in the consuming document. Waiting for assets to be downloaded on export. When has working in #Figma become such a torture. Sent while waiting.

I’m very close to stop using iCloud drive altogether. It’s such a hell hole for the most basic file management. Bad enough I had to log out and back into iCloud because the App Store didn’t work anymore until doing so. But the fact that all data stored in iCloud drive needs to be downloaded all over again is ridiculous. That downloading a mere 100 GB still hasn’t finished yet days later is inacceptable. Let alone MacOS seems to be unable to prioritize the quick download of a tiny PDF I just want to open, so here I am, going to the web interface to download a PDF that was supposed to be on my disk anyways.

Mama, wenn der Papa von deinem Mann einen Fuchs hat dann ist es dein Schwiegerfuchs

Was im Kopf von K1 so vor sich geht

Oh, Superwhisper is now available on Setapp, really a nice addition (though I’ve bought the lifetime license last year).

My relationship with mymind is rather complicated, but the new “Same Vibe” feature blew my mind – no pun intended. It’s a joyful way to browse images already saved 💫

www.youtube.com/watch

Automattic selling user data to Midjourney and OpenAI to train their LLMs was the last straw I needed to cancel my Day One subscription. Having 12+ years of memories in there, this wasn’t an easy choice, but Diarly has come a long way and now feels like a worthy alternative.

Tailwind and Web Craftsmanship

Rob articulates my feelings in this whole stupid “Tailwind vs Vanilla CSS” argument. Thanks Rob!

Gatekeeping and framing in web development

There’s a lot of articles and website out there dedicated to bash on Tailwind and their users. I have over a decade of experience in writing vanilla CSS and I love CSS. I always was very proficient in it, but I started using atomic CSS with great results. This very often goes unsaid by the semantic CSS proponents. I still write CSS but with an abstraction layer on top that really helps me. The websites I built have a low carbon footprint, they are performant, accessible, look good and do well in search engines. I take pride in my craftsmanship.

Buttons with CSS

Going back from all-utilities to plain CSS for styling buttons has made my life easier.

Please note: I have zero interest in taking part in that dreaded love/hate Tailwind argument that cooks up every other month on social media. These are just some reflections about how I’ve been doings thing for years, and what I consider changing.

Starting with buttons

As a part of reevaluating the use of utility CSS in various use cases, the ubiquitous button component is one of the first instances where I’ve decided to go back to vanilla CSS. It has become tedious to change things, add additional variants or add template logic.

So I’ve removed those utilities from the button component’s markup:

Git diff showing how utility classes were replaced with (content-derived) semantic class name

I’ve just moved those rules in a file called button.css , as a first step by just dumping all rules into the CSS using Tailwind’s @apply . (I’m aware that using @apply is not the best or even encouraged way, but it’s a good bridge to move quickly).

I came for better maintainability, I stayed for contextual overrides

The big bonus: By writing good old CSS it becomes very simple to override a button variant based its context. This saves a lot of template logic or editor work. The cascade is, of course, as old as CSS itself, but I confess I kind of forgot about its possible usefulness.

Example: Change button appearance if used inside a container with dark background

Say this is our default styling for a primary and secondary button:

.button {
    @apply font-semibold inline-flex items-center border-2 gap-2 py-4 px-6 rounded-full leading-none no-underline select-none motion-safe:transition hover:shadow-lg active:translate-y-0.5;
}

.button--primary {
    @apply bg-primary text-white border-primary hover:shadow-primary-600/30 focus-green;
}

.button--secondary {
    @apply text-blue-950 border-cream-200 hover:bg-white/30 hover:shadow-cream-600/20;
}

This creates a secondary button with a transparent background, dark text and a subtle border. It work’s great on a light background:

Screenshot showing a button on a with dark text on transparent background

But when put on a dark background, it fails:

Screenshot showing a button on a with dark text on transparent background, but now placed on a dark blue background. The button becomes hard to read.

Now, we could create a new button variant. Then we’d write some template logic to use that new variant at the right time, such as when the button is placed inside a certain component. Or we put the burden on the editor and make them choose the correct variant in the CMS. Then they need to choose a button variant based on hierarchy and visual design. Both makes things more complicated. Let alone that we’re still talking about a secondary-level button from a UX hierarchy.

This is a perfect use case to embrace the cascade and just override the secondary button’s default styling – depending on the component it’s used within:

/* Buttons on dark background need some overrides */
[data-component="night_slide"],
[data-component="cta"] {
    .button--secondary {
        @apply bg-white border-white focus-white;
    }
}

And we’re done.

The once hard to read button now has a white background and is readable against a dark background

This seems almost too obvious and I’m really curious where this journey will take me 😊

Expand Basic Markdown Formatting in Apple Notes

If you use Apple Notes on a Mac, you might enjoy ProNotes.

It’s an little app that can watch what you type in Apple Notes and on certain triggers, such as ### or [] it converts your text to Apple Note’s formatting:

Animated Gif that shows how ProNotes expands two hash symbols to a second level heading, and two brackets to a task checbox

I miss a couple more options that could be auto-expanded, such as > for block quotes, ``` for code blocks and single backticks for inline code. Maybe future updates will bring those.

Beyond keystroke expansion, ProNotes also provides a medium-style floating formatting toolbar, that appears any time you select text:

I like that every feature can be turned off. I really don't need any more menu bar items and I can imagine the floating toolbar become annoying soon. Here you can see the apps current settings:

ProNotes settings panel

It's free (as-in-beer) and can be downloaded here:

ProNotes
Supercharged Apple Notes

Cannot Divide by Zero in Peak Picture Partial

When assets don't contain valid images that can be processed by Glide (which can happen by migration or by not setting up proper sanitation rules for asset field uploads), a white screen of death due to a division will occur.

This happened to me in various scenarios

  • A migration of legacy data created assets without actual images
  • Images were uploaded in TIFF format
  • Images were uploaded without extension
  • A file's extension doesn't match it's mime_type

This happens because the picture partial tries to divide height by width to get the original image's ratio. To prevent this, we could just wrap the original height calculation within a simple if statement:

{{ if height && width }}
    {{ original_ratio = height / width }}
{{ /if }}

Applying a Growth Mindset (Doodle)

In an attempt to retain more of the information I'm consuming, I started to doodle on my iPad while listening to the Huberman Lab episode on How to Enhance Performance & Learning by applying a Growth Mindset.

Read More →

When Gmail Messages deleted in Apple Mail keep coming back

For the last coupe of weeks I had this weird behavior that whenever I deleted a message from my Google Workspace inbox through Apple Mail on Mac, these messages never disappeared from the web interface. And after fetching new messages, they also crept back into Apple Mail.

Looking into the account settings, I saw that the Trash Mailbox had somehow been set to None. After changing it back to [Gmail]/Bin, deleting messages  worked again as expected. Also, I had to change it back twice until the setting sticked (hopefully 🤞).

Using the AVIF format for CSS background images

Today I learned that browser support for the AVIF image format has become excellent, with every major browser except Edge supporting it.

As I'm implementing a design with a big background image, this is great. At half the size of the JPEG, it offers way better quality. (WebP wasn't noticeably better than JPEG in this case, btw)

So I also learned about the CSS function image-set, which allows us to give a browser multiple image formats (and/ or resolutions) and let it choose the most appropriate format. It also has great support, and a fallback is easily added in the form of a regular background image.

PS: If you're looking for a tool to convert images to AVIF, Squoosh is a nice option.

Workaround for Email Validation Issue in Statamic

Through a failed Horizon job I found out about the following: A user had entered an email address with an accidental space before the @ symbol. This caused the email to not be able to be sent and the Horizon job to fail.

Apparently, according to some email spec, spaces are allowed in the front part of an email address (before the @ symbal), and Laravel's validation follows that (and as such, Statamic's forms as well.

I got it resoved in the following way:

  1. convert all email fields to input type="email" (should have been so before)
  2. Disabling native form validation (otherwise browser validation would nag about thek space before we can strip it
  3. In the script that handles async sending, iterate over all fields of type="email" and strip any space that might be present in its value
sendForm: async function() {
// ...
// Get the value of each input type email
document.querySelectorAll('form input[type="email"]').forEach(
    function(item) {
        // Strip all spaces from the item's value
        item.value = item.value.replace(/\s/g, '');
    }
);

// …

A First Quick Look at Cosmos.so – and Why I'll Stick with MyMind

Cosmos.so is a inspiration capture system marketing itself as an alternative to Pinterest. There is quite some overlap with the features offeret by MyMind, so this will come up often as a comparison.

  • Yesterday, I received an invitation to Cosmos' early access and tried it on iOS and the web.
  • Cosmos is a really great looking app, and their pricing sounds reasonable to me (6$ a month, when paid annually), at least when compared MyMind, which is about twice that amount.

However, great UI and a good price tag isn't enough. There are a couple of things that immediately put me off and make it unlikely that Cosmos will keep a place in my digital world:

  • I need to create at least one cluster, which essentially is something like a board or folder.
  • I have to decide, into which cluster something should be captured. That's one crucial step I don't have to worry about in MyMind, which lets me do one click without having to decide anything. Though, if I want to, I can add custom tags and create smart spaces that act like such boards, but are much more versatile.
  • There is currently no way to test the full feature set without paying. I wouldn't mind paying, but I want to test it first.
  • I can only create public clusters on the free tier.
  • I cannot test AI tagging and decide if it's of value to me. Automatic categorization is a huge selling point, which should be up for some tyre-kicking before deciding to pay.
  • There is a very tight integration of a public, social-network-kinda-thing with my captures. I don't like the very idea of social andd public features. I get that this is the point for a product that places itself in a market with Pinterest, but that is definitely not what I'm looking for. MyMind makes me feel much more at ease with it's clear statements against such social features.
  • Cosmos is backed by venture capital. Not being bad per se, I very much prefer MyMind's bootstrapped approach, where I can be sure the company is only accountable to their customers and do what is in their very best interest.

I'm not affiliated with either MyMind or Cosmos in any way.

Install a Starter Kit from local repo to get the latest Peak working in Statamic 4 beta

I needed to do this to install the latest Statamic Peak into the Statamic v4 beta.

  • Clone the Starter Kit (read: Statamic Peak) Repo locally
  • I also had to update Peak's dependencies in starter-kit.yaml to beta
dependencies:
  studio1902/statamic-peak-browser-appearance: '2.0-beta.2'
  studio1902/statamic-peak-commands: '2.0-beta.4'
  studio1902/statamic-peak-seo: '2.0-beta.2'
  studio1902/statamic-peak-tools: '3.0-beta.3'
  • Add the local path to the Peak repo to the global composer config.json:
{
    "config": {},
	"repositories": [
		{
			"type": "path",
			"url": "/Users/daniel/www/statamic-peak"
		}
	]
}
  • Notice this has to go into the global config.json, not the composer.json
  • For me this was ~/.composer.config.json , but one can find it's place via composer config --global home
  • Install with the --local flag. To get the Statamic beta, also add the --dev flag:
  • statamic new mysite studio1902/statamic-peak --dev --local
  • Official documentation about installing from local repos : https://statamic.dev/starter-kits/creating-a-starter-kit#installing-from-a-local-repo

Set sensible validations for Statamic image uploads

By default, Statamic asset fields don’t validate size or file type. Recently I’ve had a bit of trouble with large image files clogging up the Git repo and image color being off due to the client uploading hug Tiff files.

To prevent this from now on I decided to limit image uploads to the jpeg and png MIME types and add a maximum file size of ~20M.

validate:
  - 'mimetypes:image/jpeg,image/png'
  - 'max_filesize:20000'

It’s probably a good idea to define some validation rules that fit your project and provide them by default to prevent problems in the future.

PS: I generate webp  versions for each image served, that’s why I don’t allow more modern formats to be uploaded. This way I can make sure to always have a format that is supported everywhere as a fallback.

Render first heading inside Statamic Page Builder as `h1`

Say you have a headline field on various Page Builder elements, and one as a set of in a Bard field. Both should visually look the same. Semantically, the first occurrence of any of these  headlines on a given page should render as h1, every following as h2, without an editor having to set anything manually.

We start by passing  the count of the current Page Puilder as page_builder_count  down to each Page Builder component. This solves the problem that  count will take another value further down the bubble, e.g. inside a Bard field.

{{ page_builder scope="block" }}
    {{ partial src="page_builder/{type}" :page_builder_count="count" }}
{{ /page_builder }}

Now any Page Builder block can check if it’s the first item, and if so, render as h1, else as h2. (We’re using Peak’s h2 partial here, that allows to pass the semantic heading level via the as attribute):

{{ partial:typography/h2 as="{ page_builder_count == 1  ? 'h1' : 'h2'}" :content="heading" }}

But I have another heading field inside a Bard set, which is also part of the Page Builder.  (sometimes it makes more sense to have dedicated fields vs Bard formatted fields). In this case, checking the headings position in page_builder_count is not enough, because there might be multiple headings inside the Bard field, all of which would be in the first Page Builder block. So we check whether we’re in the first Page Builder block and if count equals 1. In this case, count refers to the count of the sets inside the Bard field.

{{ partial:typography/h2 as="{ (page_builder_count == 1 && count == 1)  ? 'h1' : 'h2'}" :content="heading" }}

Note that  I assume that this bard field always starts with a heading, otherwise we’d have to make sure to be in the first field of this type.