<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Starred Articles</title>
    <description>Starred Articles</description>
    <atom:link href="https://feedbin.com/starred/56d6b0fe68e5efd3d447fd5bf328a57e.xml" rel="self" type="application/rss+xml"/>
    <link>https://feedbin.com/</link>
    <item>
      <title><![CDATA[All You Fascists (Bound to Lose)]]></title>
      <description><![CDATA[<img src="https://storage.ghost.io/c/18/7c/187cc681-d3f3-49fc-87de-b01d06b76821/content/images/2026/04/Woody_Guthrie_2.jpg" alt="All You Fascists (Bound to Lose)"><p>I've had "All You Fascists (Bound to Lose)" in my song rotation for a little while – for, you know, <em>reasons</em>.</p><p>Bette Midler's getting some coverage for her cover, so I thought I'd round up some other versions I like very much.</p><p>To begin with, here's that Bette Midler cover:</p><figure class="kg-card kg-embed-card"><iframe width="200" height="113" src="https://www.youtube.com/embed/uBj57ivPsxQ?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" title="ALL YOU FASCISTS (Bound to Lose) Music Video Bette Midler"></iframe></figure><p>This <a href="https://www.resistancerevivalchorus.com/?ref=werd.io" rel="noreferrer">Resistance Revival Chorus</a> version with <a href="https://rhiannongiddens.com/?ref=werd.io" rel="noreferrer">Rhiannon Giddens</a> is probably my favorite: upbeat and alive.</p><figure class="kg-card kg-embed-card"><iframe width="200" height="113" src="https://www.youtube.com/embed/dWUa7aAIfLE?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" title="Resistance Revival Chorus with Rhiannon Giddens &quot;All You Fascists Bound To Lose&quot;"></iframe></figure><p>The <a href="https://www.billybragg.co.uk/?ref=werd.io" rel="noreferrer">Billy Bragg</a> version with <a href="https://wilcoworld.net/?ref=werd.io" rel="noreferrer">Wilco</a> is really strong too: </p><figure class="kg-card kg-embed-card"><iframe width="200" height="150" src="https://www.youtube.com/embed/SFPL97m2dsw?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" title="All You Fascists"></iframe></figure><p><a href="https://www.yahoo.com/entertainment/music/articles/nina-hagen-live-forever-133000223.html?ref=werd.io" rel="noreferrer">Nina Hagen's</a> father Hans was a Holocaust survivor held at Moabit, and her paternal grandparents were murdered at Sachsenhausen. This is therefore a very personal cover:</p><figure class="kg-card kg-embed-card"><iframe width="200" height="150" src="https://www.youtube.com/embed/FMDgqMdpBMs?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" title="All You Fascists Bound to Lose"></iframe></figure><p>Here's <a href="https://anidifranco.com/?ref=werd.io" rel="noreferrer">Ani DiFranco</a> with <a href="https://zeoboekbinder.com/?ref=werd.io" rel="noreferrer">Zoe Boekbinder</a>, <a href="https://www.gracieandrachel.com/?ref=werd.io" rel="noreferrer">Gracie and Rachel</a>, and <a href="https://www.dianepatterson.org/?ref=werd.io" rel="noreferrer">Diane Patterson</a> - all artists I love:</p><figure class="kg-card kg-embed-card"><iframe width="200" height="113" src="https://www.youtube.com/embed/BbjCtIaMwBg?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" title="Boundalose - Ani DiFranco w/ Zoe Boekbinder, Gracie and Rachel, and Diane Patterson live on the Mall"></iframe></figure><p>And finally, here's the great <a href="https://woodyguthrie.org/?ref=werd.io" rel="noreferrer">Woody Guthrie</a>, one of my heroes, singing the original:</p><figure class="kg-card kg-embed-card"><iframe width="200" height="150" src="https://www.youtube.com/embed/VwcKwGS7OSQ?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" title="Woody Guthrie~ All You Fascists Bound To Lose"></iframe></figure>]]></description>
      <pubDate>Mon, 27 Apr 2026 13:39:32 +0000</pubDate>
      <link>https://werd.io/all-you-fascists-bound-to-lose/</link>
      <dc:creator>Ben Werdmuller</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5206763175</guid>
    </item>
    <item>
      <title><![CDATA[Actually reading RSS feeds]]></title>
      <description><![CDATA[<!-- Thanks to Hans de Zwart I now read my RSS feeds. /-->
<h1 id="actually-reading-rss-feeds">Actually reading RSS feeds</h1>
<p>I’ve been trying to follow weblogs and news via <abbr>RSS</abbr> for ages. And I’ve tried various ways of coping with the endless stream of updates. Different apps, different ways of stucturing my feeds, but with all of them I quit using feeds after a while. There was just too much to read, and I basically gave up.</p>
<h2 id="the-hans-de-zwart-method">The Hans de Zwart method</h2>
<p>My colleague Hans de Zwart explained me <a href="https://blog.hansdezwart.nl/2024/01/05/the-books-i-read-in-2023/#my-consumption-of-other-media">his simple but effective <abbr>RSS</abbr>-feed method</a>: Some feeds are in the <em>morning</em> directory, others in the <em>daily</em>, and the rest is in the <em>weekly</em> folder. I’ve been using this method for a while now and it works, kinda.</p>
<h2 id="be-picky">Be picky</h2>
<p>Every morning I open the <em>morning</em> directory and click on a feed that has new items. Then <em>I choose what to read.</em> And that, for me, is the whole trick. I always tried to be complete, read every article in order of arrival. And now I just pick the stuff that sounds interesting, from one feed at a time, and read it.</p>
<p>In the evening I open the <em>daily</em> folder and read the items that seem interesting and ignore the rest. The only thing I haven’t figured out yet is <em>on what day should I read the weekly feeds?</em> My weeks aren’t structured enough to follow a strict weekly regime. Maybe I should call this directory (ir)regularly.</p>
]]></description>
      <pubDate>Tue, 28 Apr 2026 07:53:00 +0000</pubDate>
      <link>https://vasilis.nl/nerd/2026/actually-reading-rss-feeds/</link>
      <dc:creator>Nerd</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5207638823</guid>
    </item>
    <item>
      <title><![CDATA[The Drawing Hand]]></title>
      <description><![CDATA[
      
      
      <p><em>Design tooling, LLMs, and why I still use a pencil.</em></p>
      <p>A friend and I were talking about AI and the question of where I lie on the spectrum came up: “I’m now on the handmade, artisanal, craftsperson end of the spectrum.”</p>
<p>A client project recently moved into the LLM layer. Some of the team had already been working with an LLM to wireframe and prototype some aspects based on my initial Figma designs. I’m far more judicious and faster (it is my design to be fair), but it’s interesting to see how Figma Make and Claude extrapolate my basic design system file and screens into primitives and code. Things got... lost in translation. Extraneous primitives, too many primitives, borders got ignored. The results look like the blurry part before something comes into sharp focus.</p>
<p>When I was a little kid, all I wanted to do was draw and make things. I drew characters from movies and cartoons. I later kept at it through the art I’d experience in skate culture, and eventually music. That through line is knowing how to draw the line, knowing when to stop, dig in, turn the pencil to make it thicker, release pressure to make it lighter. All in the hand, through years of practice, and experienced intuition.</p>
<p>Decades later, I still love the <em>drawing</em>. When something goes off the rails, the hand can guide it back to a path, or work with the line to form something completely new.</p>

      <hr style="border: 0; border-bottom: 1px solid #dddddd; margin-top: 30px;">
      <p>→ <a href="https://nazhamid.com/journal/the-drawing-hand/">This looks better on the web</a><br>→ <a href="mailto:naz@nazhamid.com">Reply via email</a></p><p><small>Thanks for reading and using RSS!</small></p>
    ]]></description>
      <pubDate>Wed, 29 Apr 2026 03:42:34 +0000</pubDate>
      <link>https://nazhamid.com/journal/the-drawing-hand/</link>
      <dc:creator>Naz Hamid • Journal</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5208721758</guid>
    </item>
    <item>
      <title><![CDATA[Dos &amp; Don'ts of Anchor Positioning]]></title>
      <description><![CDATA[<img src="https://www.oddbird.net/assets/images/winging-it/winging-it-31-1600w.jpeg" alt=""><p>CSS anchor positioning isn’t baseline yet,
and there’s good reason for that.
You can use it,
but it comes with some caveats.
James, Stacy, and Miriam
cover new resources
to make anchor positioning easier,
and work through some demos
to help you understand
how anchor positioning works.
We also look at
what new capabilities are in the pipeline.</p>
<div data-callout="note"><div>
<p>Check out our <a href="https://www.youtube.com/playlist?list=PL4jAKUtAhpHk7CKHosxrEILMwrncZioZY">Anchor Positioning Playlist</a>
to learn how to use and troubleshoot anchor positioning today.</p>
</div></div>
<figure class="embed">
      <div class="gallery"></div></figure>
<p class="main-action"><a href="http://www.youtube.com/channel/UCUkHxN78y9On9YH1zd-aTGw?sub_confirmation=1">Subscribe to Channel »</a></p>
<div class="anchor-link-wrapper">
<h2 id="what-we-cover%3A" tabindex="-1">What We Cover:</h2>
</div>
<ul>
<li>Don’t ignore the containing block.</li>
<li>Do get excited about future things.</li>
<li>Do try it. Many paths work.</li>
<li>Don’t be surprised if something doesn’t work, but ask whether it’s baseline.</li>
</ul>
<div class="anchor-link-wrapper">
<h2 id="links%3A" tabindex="-1">Links:</h2>
</div>
<ul>
<li><a href="https://codepen.io/jamessw/pen/MYeqVyL">Absolute value CodePen</a></li>
<li><a href="https://codepen.io/jamessw/pen/raLeboW">Clamping CodePen</a></li>
<li><a href="https://codepen.io/jamessw/pen/QwEePzy">Select flip CodePen</a></li>
<li><a href="https://github.com/w3c/csswg-drafts/issues/6205">Element.matchContainer()</a></li>
<li><a href="https://github.com/w3c/csswg-drafts/issues/13716">Allow anchor() within shape()</a></li>
<li><a href="https://anchor-positioning-in-space.schalkneethling.com/">Anchor Positioning in Space</a></li>
<li><a href="https://shadow-dom-css.adobe.com/#anchor-positioning">Shadow DOM CSS - Anchor Positioning</a></li>
</ul>
]]></description>
      <pubDate>Thu, 16 Apr 2026 00:00:00 +0000</pubDate>
      <link>https://www.oddbird.net/2026/04/16/winging-it-31/</link>
      <dc:creator>OddBird</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5215825520</guid>
    </item>
    <item>
      <title><![CDATA[Canvas-ing the Web]]></title>
      <description><![CDATA[<p>Over the years, I’ve created an experiment or two that drew stuff to a <code>&lt;canvas&gt;</code> element: a wave function collapse experiment here, a crystallizing palette there.&nbsp; After a while, I found a way to wire up a button so that clicking it would save the canvas’s contents to my computer as a PNG file.&nbsp; <em>Pretty cool</em>, I thought.&nbsp; <em>Can I do the same thing with HTML+CSS structures?</em></p>

<figure>
<a href="https://meyerweb.com/eric/thoughts/wp-content/uploads/wfv_2026-04-21T16_35_49.png"><img decoding="async" src="https://meyerweb.com/eric/thoughts/wp-content/uploads/wfv_2026-04-21T16_35_49.png" alt="An abstract image somewhat resembling a flower, rendered in dusky purples, greens, and similar colors."></a>
<figcaption>
First I generated it on a canvas, then I clicked a button to save it.
</figcaption>
</figure>

<p>Turns out, no.&nbsp; I could use, and often have used, Firefox’s “Screenshot node” menu entry in the web inspector, or <a href="https://meyerweb.com/eric/thoughts/2018/08/24/firefoxs-screenshot-command-2018/">the <code>:screenshot</code> command</a> in Firefox’s console, but not do it with an in-page button.&nbsp; Because HTML nodes don’t go in <code>&lt;canvas&gt;</code>, you see, let alone styled and scripted ones.</p>

<p>Or they <em>didn’t</em>, until just recently, when Chrome shipped a flag-gated preview of the <a href="https://github.com/WICG/html-in-canvas">HTML-in-canvas API</a>.&nbsp; How it works is, you add a <code>layoutsubtree</code> attribute to a <code>&lt;canvas&gt;</code> element, and then you can put whatever HTML you want in there, with whatever CSS and JS you would normally apply to it, add a couple of magic JScantations, and what the browser would normally have painted to the page is painted to the canvas, at whatever speed the browser can manage (usually 60 frames per second or more, because web browsers are high-end <a href="https://meyerweb.com/eric/thoughts/2023/06/20/first-person-scrollers/">first-person scrollers</a>).</p>

<p>If you want to try all this out for yourself, I commend you to <a href="https://frontendmasters.com/blog/the-web-is-fun-again-first-experiments-with-html-in-canvas/">Amit Sheen’s “The Web Is Fun Again”</a> over at the Frontend Masters blog, where he details how to get yourself set up for the wackiness this makes possible, and then shows some experiments.&nbsp; Water ripples over your pages, lens distortions that follow the mouse pointer, chromatic aberrations!</p>

<p>Which, I admit, all sound really off-putting to the “I just want to use the web” folks among us.&nbsp; What possible utility is there in having an input form that, say, makes ripples spread out from every character you type?&nbsp; Or having dropdown menus fall to the bottom of the page, but still actually work?&nbsp; Probably not a lot, unless you’re an expensive design studio working on a brag page.</p>

<p>But remember, this is how any new graphic advancement goes: we, by which I mean the collective web industry, start by doing really outré and eye-catching stuff that we later have cause to regret.&nbsp; Remember parallax scrolling effects?&nbsp; The early days of CSS animation?&nbsp; <em>Drop shadows?</em>&nbsp; There will be an initial period of excess, and then it will all settle down.</p>

<p>I’ve already skipped straight to the settle down part, though.</p>

<p>See, when I asked myself if I could render HTML+CSS on a <code>&lt;canvas&gt;</code> and then save the image to my computer, it wasn’t just me doing that “push at the limits of web features” thing <a href="https://meyerweb.com/eric/thoughts/2025/08/07/infinite-pixels/">I do sometimes</a>.&nbsp; I had an actual, practical use case in mind: I wanted to save social media banners and thumbnails from a browser-based tool I built for my work at <a href="https://igalia.com">Igalia</a>, just by clicking or otherwise triggering a button.</p>

<p>If you’re subscribed to our <a href="https://www.youtube.com/igalia">YouTube channel</a>, you’ve seen these thumbnails; ditto if you’re following us on <a href="https://floss.social/@igalia">Mastodon</a> or <a href="https://bsky.app/profile/igalia.com">Bluesky</a>.&nbsp; To produce those, I have an in-browser thing I built out of custom elements.&nbsp; It’s where <a href="https://meyerweb.com/eric/thoughts/2023/11/01/blinded-by-the-light-dom/">the <code>super-slider</code> pattern</a> developed <aside-note>(though they have a different name in the tool)</aside-note>.&nbsp; I’m not going to link to the tool because it’s on our intranet and very few of you have a login, so here’s a screenshot of it in all its dweeb-designed semi-glory.</p>

<figure class="standalone">
<a href="https://meyerweb.com/eric/thoughts/wp-content/uploads/bannermaker-screenshot.png"><img decoding="async" src="https://meyerweb.com/eric/thoughts/wp-content/uploads/bannermaker-screenshot.png" alt="The banner-making tool being discussed, showing a number of panels with range slider inputs to set things like font size for various pieces of the banner.&nbsp; There are also color inputs to change the coloration of both foreground and background elements, and a couple of places to drag and drop background or highlight images."></a>
<figcaption>
The banner maker, with a recent thumbnail already loaded in.
</figcaption>
</figure>

<p>The text bits in the banner are all <code>contenteditable</code> HTML elements, and the various themes are managed with various blocks of CSS.&nbsp; (And yeah, those range inputs are all “super sliders”.)&nbsp; The point of all this being, I built it so that anyone at work could use it to make
banners whenever they needed, without having to wait on me to do so.</p>

<p>What I’ve always wanted, in order to make things easy for anyone who isn’t me, is a “click this button to save the banner as an image”&nbsp;feature.&nbsp; Anyone at Igalia could easily learn (if they didn’t already know) the web-inspector-or-console stuff I was using, of course, but it just felt so janky.&nbsp; A touch embarrassing, if I’m being honest.</p>

<p>Well, now I have what I wanted.&nbsp; In any browser that supports HTML-in-canvas, there is a button labeled “Download banner image”.&nbsp; Right now, that’s recent Chrome with the proper developer flag enabled.&nbsp; For all other browsers, there’s no button, and you just use the same web inspector screenshot tricks we’ve always relied on.</p>

<p>Making this happen wasn’t as easy as maybe that sounded, though.&nbsp; I hit a couple of snags along the way, one of which was quite frustrating.&nbsp; Those are what I actually brought you here to talk about.</p>

<p>The first snag was that I had to get the thumbnail preview into a <code>&lt;canvas&gt;</code> element without blowing the call stack.&nbsp; To explain that, let me show you a rough skeleton of the tool’s markup.</p>

<pre class="html"><code>&lt;section id="youtube_talks"&gt;
	&lt;thumb-panel class="text"&gt; … &lt;/thumb-panel&gt;
	&lt;thumb-panel class="colors"&gt; … &lt;/thumb-panel&gt;
	&lt;thumb-panel class="highlightImage"&gt; … &lt;/thumb-panel&gt;
	&lt;thumb-panel class="backgroundImage"&gt; … &lt;/thumb-panel&gt;
	&lt;thumb-panel class="icons"&gt; … &lt;/thumb-panel&gt;
	&lt;thumb-panel class="scaler"&gt; … &lt;/thumb-panel&gt;
	&lt;thumb-panel class="loader"&gt; … &lt;/thumb-panel&gt;
	&lt;thumb-preview&gt; … &lt;/thumb-preview&gt;
&lt;/section&gt;</code></pre>

<p>As you can read, it’s basically all custom elements, each with their own <code>connectedCallback()</code> function to do whatever scripting magic needs to be done when the browser first encounters them.&nbsp; To wrap that last element, the <code>&lt;thumb-preview&gt;</code>, inside a <code>&lt;canvas&gt;</code>, I needed to create a new canvas element, shift the preview element into the new canvas, and then insert the preview-bearing canvas, ending up with this structure.</p>

<pre class="html"><code>&lt;section id="youtube_talks"&gt;
	&lt;thumb-panel class="text"&gt; … &lt;/thumb-panel&gt;
	&lt;thumb-panel class="colors"&gt; … &lt;/thumb-panel&gt;
	&lt;thumb-panel class="highlightImage"&gt; … &lt;/thumb-panel&gt;
	&lt;thumb-panel class="backgroundImage"&gt; … &lt;/thumb-panel&gt;
	&lt;thumb-panel class="icons"&gt; … &lt;/thumb-panel&gt;
	&lt;thumb-panel class="scaler"&gt; … &lt;/thumb-panel&gt;
	&lt;thumb-panel class="loader"&gt; … &lt;/thumb-panel&gt;
<ins>	&lt;canvas layoutsubtree&gt;</ins>
		&lt;thumb-preview&gt; … &lt;/thumb-preview&gt;
<ins>	&lt;/canvas&gt;</ins>
&lt;/section&gt;</code></pre>

<p>Thus, when the <code>&lt;thumb-preview&gt;</code> was loaded in, I had its <code>connectedCallback()</code> run a check to see if HTML-in-canvas is supported.&nbsp; In situations where it is supported, I did what was needed to get to the above result.</p>

<p>At which point, since the <code>&lt;thumb-preview&gt;</code> is a custom element that was being placed into the DOM, it fired its <code>connectedCallback()</code>, thus starting the process again, creating a canvas and inserting the <code>&lt;thumb-preview&gt;</code> into the new canvas, which started the process again, <a href="https://meyerweb.com/bkkt/recursion-w-innie-the-pooh.jpg">recursing toward infinity</a>.&nbsp; Within milliseconds, the call stack was exceeded.</p>

<p>So… <em>that</em> wasn’t going to work.</p>

<p>I thought for a moment that I could avoid this by setting a flag variable to <code>true</code> and then checking for its existence in order to skip the whole canvas-creation-preview-insertion part, but I couldn’t figure out how to make that actually work.&nbsp; Then I thought maybe I could sidestep the whole imbroglio using <code>connectedMoveCallback()</code>, but this wasn’t a move, it was a (re-)creation.</p>

<p>That callback was the route to fixing this problem, though.&nbsp; You see, there <em>is</em> a way to move elements from one part of the DOM to another: <a href="https://developer.mozilla.org/en-US/docs/Web/API/Element/moveBefore"><code>Element.moveBefore()</code></a>.&nbsp; There’s no <code>moveAfter()</code> or <code>moveInto()</code>, sadly, just “move this node to the spot right before some other node”.</p>

<p>Here’s how I made use of that feature:</p>

<pre class="js"><code>let canvas = document.createElement('canvas');
canvas.setAttribute('layoutsubtree','');
canvas.setAttribute('width','1280');
canvas.setAttribute('height','720');

this.closest('section').appendChild(canvas);

let beacon = document.createElement('span');
canvas.appendChild(beacon);
canvas.moveBefore(this,beacon);
beacon.remove();</code></pre>

<p>Yep.&nbsp; I created a canvas, stuck the canvas into the closest ancestor section, created a span, stuck the span into the canvas, moved the preview element to right before the span, and then deleted the span.&nbsp; (There may well be a better way to do this, one that my DuckDucking failed to turn up.&nbsp; If so, please comment below!)</p>

<p>Oh, and here’s what gets executed when the preview is moved, instead of append-created:</p>

<pre class="js"><code>connectedMoveCallback() {
	return;
}</code></pre>

<p>Heckuva way to run a railroad.</p>

<p>At that point, I had the canvas where I wanted it and the preview where I wanted it, and the call stack remained un-blown.&nbsp; Huzzah!&nbsp; I then recited the magic JScantations to make the canvas actually render its subtree <aside-note>(see the “Web is Fun Again” article I linked earlier for details on this)</aside-note>, and hey presto, DOM was being rendered into a canvas!&nbsp; Then, when I clicked the button, the canvas was rendered as a PNG and my browser downloaded that PNG!&nbsp; I had what I wanted!</p>

<p>Almost.</p>

<p>Because the second snag, you see, is that canvases have an explicit size.&nbsp; Are in effect <em>required</em> to do so, because otherwise they default to zero pixels tall and wide.&nbsp; So if you want to see anything, you need to give them some dimensions.&nbsp; I did that, as the code before showed, making the canvas 1280×720 <aside-note>(YouTube’s recommended thumbnail size)</aside-note> through <code>setAttribute()</code> methods.</p>

<p>The problem is, the default scale factor on the thumbnail preview is <code>0.75</code>, which translates to 960×540.&nbsp; Thus, when I clicked the image capture button, my browser downloaded a 1280×720 image with the thumbnail in the top left, and transparency below and to its right.</p>

<figure class="standalone">
<a href="https://meyerweb.com/eric/thoughts/wp-content/uploads/banner-point75-acorn.png"><img decoding="async" src="https://meyerweb.com/eric/thoughts/wp-content/uploads/banner-point75-acorn.png" alt=""></a>
<figcaption>
The previously-seen banner, which was rendered at 0.75 scale in an un-resized canvas, as shown in the macOS image editor <a href="https://flyingmeat.com/acorn/">Acorn</a>.
</figcaption>
</figure>
<p>“Just resize the canvas, ya dork!” you might say.&nbsp; I certainly did (say that, I mean).&nbsp; But if I set it to 960 wide and 540 tall, then when the scale was increased to <code>1</code>, I got a 1280×720 DOM node cropped to its top left 960×540.&nbsp; I needed to dynamically resize the canvas element to have its size match the size of the thumb-preview.</p>

<p>And this is where I ran headfirst into several brick walls, because orcing a canvas element to resize in all the situations you want it to, including when it’s spawned, is not nearly as easy as you’d think.&nbsp; It wasn’t for me, anyway.&nbsp; I bulled my way through to a solution, eventually, painfully, but I got there.</p>

<p>(As I write this, I’m wondering if I should have also created a <code>&lt;div&gt;</code>, appended the canvas to <em>that</em>, and then used CSS to change the div’s size while the canvas was set to have <code>100%</code> height and width.&nbsp; Or maybe have the DOM subtree pinned to 1280×720 and use <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/scale">CSS <code>scale</code></a> to change the canvas size visually.&nbsp; Or perhaps some kind of <a href="https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver"><code>resizeObserver</code></a> shenanigans.&nbsp; Or probably just pass some parameters to the HTML-in-canvas <a href="https://github.com/WICG/html-in-canvas#2-drawelementimage-and-webglwebgpu-equivalents"><code>drawElementImage</code></a> method.&nbsp; Hmmm.)</p>

<p>Regardless of whether I overlooked a less frustrating way do what I wanted, this does still point to a fundamental tension in the HTML-in-canvas approach: sizing.</p>

<p>Canvases do not, as a rule, grow or shrink to fit their contents.&nbsp; DOM elements, as a rule, very much do, unless you force them not to.&nbsp; HTML-in-canvas is taking a very fluid, flexible, mostly unbounded layout paradigm and rasterizing it, or at least some of it, into a very bounded window of a given size.&nbsp; Sixty times (or more) every second, the browser is taking a screenshot the size of the canvas’s content box and pasting said screenshot into that content box.&nbsp; You can do fun stuff to it along the way, with filters or shaders or canvas draw calls or whatever you can code up, so that each one of those screenshots gets jazzed up in some fashion, but at base, it’s still fundamentally screenshot, paste, screenshot, paste, over and over.</p>

<p>For use cases like mine, this isn’t really a big problem.&nbsp; I am, in the end, trying to get a screenshot of a static part of the page.&nbsp; HTML-in-canvas is very good for that.&nbsp; It could <em>completely</em> revolutionize the browser-based slideshow genre.&nbsp; The Reveal.js plugin landscape alone could be a sight to behold.</p>

<p>But in the general cases — the kinds of things we mostly do most every day — I don’t think this is likely to catch on.&nbsp; We might develop some patterns to make it easier, some interesting hacks to overcome the mismatch, but I don’t think that will significantly move the needle.&nbsp; On the other hand, if canvases can be made as flexible and content-wrapping as a bog-standard <code>&lt;div&gt;</code>, then I would expect to see a lot more usage.</p>

<p>Although if <em>that</em> can be done, then we wouldn’t really need to stay chained to HTML-in-canvas.&nbsp; Instead, we could define a syntax to mark standard HTML elements as more visually manipulable, via an HTML attribute or CSS property or DOM method or all three.</p>

<p>We’ve gotten close to that before: <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Properties_and_values_API/Houdini">CSS Houdini</a> and <a href="https://learn.microsoft.com/en-us/previous-versions/ms530752(v=vs.85)">Microsoft’s original <code>filter</code></a> property, to pick two examples.&nbsp; We could try again.&nbsp; Maybe the HTML-in-canvas period is how we figure out what that simpler syntax should look like, by figuring out what it should make possible, and what it should make easy.</p>

<p>I’d be okay with that.&nbsp; How about you?</p>

<hr>
<p class="note">Many thanks to my colleagues <a href="https://igalia.com/team/bkardell">Brian Kardell</a> and <a href="https://igalia.com/team/schenney">Stephen Chenney</a> for their early review and feedback on this post.</p>
	<hr><p>Have something to say to all that?  You can <a href="https://meyerweb.com/eric/thoughts/2026/04/27/canvas-ing-the-web/#commentform">add a comment to the post</a>, or <a href="mailto:eric@meyerweb.com?subject=In%20reply%20to%20%22Canvas-ing%20the%20Web%22">email Eric directly</a>.</p>]]></description>
      <pubDate>Mon, 27 Apr 2026 14:20:33 +0000</pubDate>
      <link>https://meyerweb.com/eric/thoughts/2026/04/27/canvas-ing-the-web/</link>
      <dc:creator>Thoughts From Eric</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5206826184</guid>
    </item>
    <item>
      <title><![CDATA[Collective Speed Is Not the Summation of Individual Speed]]></title>
      <description><![CDATA[<p><a href="https://blog.jim-nielsen.com/2026/speed-not-conducive-to-wisdom/">I’ve been thinking about speed</a> which is why Chris Coyier caught my attention <a href="https://chriscoyier.net/2026/04/25/ai-alignment/">in his latest piece</a> discussing how AI might be 10✕ing the speed with which we code, but it’s not making our software 10✕ better:</p>
<blockquote>
<p>Faster individuals don’t make a fast company</p>
</blockquote>
<p>My mind immediately went to the 4✕100 relay at the Olympics.</p>
<p>(Not sure which race that is? Watch <a href="https://www.youtube.com/watch?v=bQOw8LF5SM4">the London 2012 one</a>.)</p>
<p>Imagine you were put in charge of winning the 4✕100 relay.</p>
<p>All you gotta do is find the four faster sprinters in your country — right?</p>
<p>I’m no track and field expert, but I doubt it’s that simple.</p>
<p>In a relay race, the baton is arguably the most critical element. Passing it cleanly is vital because if you fumble it you’re easily behind a few meters or maybe even disqualified.</p>
<p>So, one could argue, a sprinter’s ability to pass and receive the baton is more important than speed because all the speed in the world won’t help you overcome a dropped baton.</p>
<p>(There are other considerations too, like which leg each runner takes, which sequence works best given individual pairings and rapport, and whether a slower veteran might perform better in the heat of the moment.)</p>
<p>Faster runners won’t guarantee a faster team.</p>
<p>And faster coders won’t guarantee a faster company.</p>
<p>Like a relay race, it might be worth giving some thought to the relationships and interfaces between people.</p>

    <hr>
    

    <p>
      Reply via:
      

      <a href="mailto:jimniels%2Bblog@gmail.com?subject=Re:%20blog.jim-nielsen.com/2026/collective-speed-isnt-the-sum-of-individual-speed/">Email</a>
      · <a href="https://mastodon.social/@jimniels">Mastodon</a> ·

      <a href="https://bsky.app/profile/jim-nielsen.com">Bluesky</a>
    </p>

    
  ]]></description>
      <pubDate>Sun, 26 Apr 2026 19:00:00 +0000</pubDate>
      <link>https://blog.jim-nielsen.com/2026/collective-speed-isnt-the-sum-of-individual-speed/</link>
      <dc:creator>Jim Nielsen’s Blog</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5208164831</guid>
    </item>
    <item>
      <title><![CDATA[CSS Recently In All Browsers]]></title>
      <description><![CDATA[
          <img style="display: none" src="https://nerdy.dev/media/css-recently-in-all-browsers-2026.jpg" alt="CSS FTW" height="680" width="1280">
        <script type="module">
  import "https://cdn.jsdelivr.net/npm/baseline-status";
</script>

<p>Here's some rad CSS that just hit baseline between October 2025 - April 2026. </p>
<p>At this point with the following features, the "I can't use it" vibe can shed away.</p>

        <h2>
          Anchor Positioning
          <a name="anchor-positioning" href="https://nerdy.dev/CSS-recently-in-all-browsers?utm_source=rss#anchor-positioning">#</a>
        </h2>
       <p>Anchor positioning lets you natively tether components to target elements without adjusting DOM semantics or crowding the main thread.</p>
<p><baseline-status featureid="anchor-positioning"></baseline-status></p>
<p>This widget doesn't show green across the board as of writing this, there are sub features that aren't all supported, but the general featureset is ready.</p>
<p>Checkout <a href="https://xanthir.com/contact/">Tab's</a> talk about <code>anchor</code> called <a href="https://www.youtube.com/watch?v=p18LhnYNkDQ">
Anchor Positioning</a> at <a href="https://cssday.nl/2025/">CSS Day 2025</a>.</p>
<iframe width="800" height="450" style="aspect-ratio: 16/9" src="https://www.youtube.com/embed/p18LhnYNkDQ?si=7s1TLjTe2EfSwGfb" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen=""></iframe>

<p><small>Checkout my post on <a href="https://nerdy.dev/anchor-interpolated-morphing">AIM</a> if you missed it, neat anchor technique for FLIP-like transitions with anchor.</small></p>

        <h2>
          @scope
          <a name="@scope" href="https://nerdy.dev/CSS-recently-in-all-browsers?utm_source=rss#@scope">#</a>
        </h2>
       <p>CSS <strong>selector</strong> scoping, not <em>style</em> scoping; a common misconception. </p>
<p>However! With <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@scope"><code>@scope</code></a> we can simplify naming conventions or robobarf classnames just to avoid global cascade collisions. </p>
<p>The <a href="https://www.stubbornella.org/2011/10/08/scope-donuts/">"donut"</a> feature is very special, it can limit styles from cascading into nested components by setting an end to the selector (a donut hole 🍩).</p>
<p><baseline-status featureid="scope"></baseline-status></p>
<p>Chris gave a talk about <code>@scope</code> called <a href="https://www.youtube.com/watch?v=VO1uz2zVCBU">Scope In CSS</a> at <a href="https://cssday.nl/2025/">CSS Day 2025</a>.</p>
<iframe width="800" height="450" style="aspect-ratio: 16/9" src="https://www.youtube.com/embed/VO1uz2zVCBU?si=V6sRqJ22umyjvP2F" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen=""></iframe>


        <h2>
          Name Only Container Queries
          <a name="name-only-container-queries" href="https://nerdy.dev/CSS-recently-in-all-browsers?utm_source=rss#name-only-container-queries">#</a>
        </h2>
       <p><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Container_Queries"><code>@container</code></a> no longer requires a size condition. </p>
<p>You can now conditionally style elements just by name: </p>
<pre><code class="language-css"><pre class="shiki css-variables" style="background-color:var(--shiki-background);color:var(--shiki-foreground)" tabindex="0"><code><span class="line"><span style="color:var(--shiki-token-function)">.sidebar</span><span style="color:var(--shiki-foreground)"> {</span></span>
<span class="line"><span style="color:var(--shiki-token-constant)">  container-name</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> sidebar</span><span style="color:var(--shiki-foreground)">;</span></span>
<span class="line"><span style="color:var(--shiki-foreground)">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:var(--shiki-token-function)">.card</span><span style="color:var(--shiki-foreground)"> {</span></span>
<span class="line"><span style="color:var(--shiki-token-constant)">  display</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> grid</span><span style="color:var(--shiki-foreground)">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:var(--shiki-foreground)">  @</span><span style="color:var(--shiki-token-constant)">container</span><span style="color:var(--shiki-token-constant)"> sidebar</span><span style="color:var(--shiki-foreground)"> {</span></span>
<span class="line"><span style="color:var(--shiki-token-constant)">    grid-auto-flow</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> column</span><span style="color:var(--shiki-foreground)">;</span></span>
<span class="line"><span style="color:var(--shiki-foreground)">  }</span></span>
<span class="line"><span style="color:var(--shiki-foreground)">}</span></span></code></pre>
</code></pre>
<ul>
<li>Chrome 149</li>
<li>Safari <a href="https://developer.apple.com/documentation/safari-release-notes/safari-26_4-release-notes">26.4</a> </li>
<li>Firefox 148</li>
</ul>
<p><a href="https://chriscoyier.net/">Chris</a> wrote a good post about it over on <a href="https://frontendmasters.com/blog/name-only-containers-the-scoping-we-needed/">FrontEnd Masters</a>. </p>

        <h2>
          shape()
          <a name="shape()" href="https://nerdy.dev/CSS-recently-in-all-browsers?utm_source=rss#shape()">#</a>
        </h2>
       <p>Responsive, native CSS geometry. </p>
<p>Draw complex clipping paths using standard CSS syntax and dynamic units (like <code>rem</code> or <code>calc()</code>) instead of being locked into rigid SVG pixel coordinates.</p>
<p><baseline-status featureid="shape-function"></baseline-status></p>
<p><a href="https://css-articles.com/">Temani</a> wrote a rad post about it over on <a href="https://frontendmasters.com/blog/drawing-css-shapes-using-corner-shape/">FrontEnd Masters</a> and a <a href="https://css-tricks.com/complex-css-shapes-with-shape-function/">"complex" post CSS-Tricks</a>.</p>

        <h2>
          shape-outside with xywh() and rect()
          <a name="shape-outside-with-xywh()-and-rect()" href="https://nerdy.dev/CSS-recently-in-all-browsers?utm_source=rss#shape-outside-with-xywh()-and-rect()">#</a>
        </h2>
       <p>Wrap inline text around precise geometric boundaries with <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/basic-shape/xywh">xywh()</a> and <a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Reference/Element/rect">rect()</a>. </p>
<p>Typographical control over floating text flows without relying on images or clip-paths.</p>
<ul>
<li><a href="https://chromestatus.com/feature/6323071520735232">Chrome 150 (soon)</a></li>
<li>Safari 18</li>
<li>Firefox 149</li>
</ul>
<p>Still no <a href="https://chenglou.me/pretext/">pretext</a>, but also still coo.</p>

        <h2>
          view-transition-class and Types
          <a name="view-transition-class-and-types" href="https://nerdy.dev/CSS-recently-in-all-browsers?utm_source=rss#view-transition-class-and-types">#</a>
        </h2>
       <p>SPA-like routing animations.</p>
<p>With <a href="https://developer.chrome.com/docs/web-platform/view-transitions/same-document#view-transition-class"><code>view-transition-class</code></a> you can target massive collections of DOM nodes with a single animation rule, while <a href="https://developer.chrome.com/docs/web-platform/view-transitions/same-document#view-transition-types">JS View Transition Types</a> let you programmatically direct "forward" or "backward" contextual motion.</p>
<p><baseline-status featureid="view-transition-class"></baseline-status>
<baseline-status featureid="active-view-transition"></baseline-status></p>

        <h2>
          rcap, rch, rex, ric
          <a name="rcap,-rch,-rex,-ric" href="https://nerdy.dev/CSS-recently-in-all-browsers?utm_source=rss#rcap,-rch,-rex,-ric">#</a>
        </h2>
       <p>Even more typographic precision. </p>
<p><baseline-status featureid="rcap"></baseline-status>
<baseline-status featureid="rch"></baseline-status>
<baseline-status featureid="rex"></baseline-status>
<baseline-status featureid="ric"></baseline-status></p>
<p>I <a href="https://nerdy.dev/new-relative-units-ric-rex-rlh-and-rch">wrote about these here</a>.</p>
]]></description>
      <pubDate>Sun, 26 Apr 2026 01:32:25 +0000</pubDate>
      <link>https://nerdy.dev/CSS-recently-in-all-browsers?utm_source=rss</link>
      <dc:creator>Adam Argyle</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5205414336</guid>
    </item>
    <item>
      <title><![CDATA[Matt Mullenweg thinks WordPress is in decline. He may be right]]></title>
      <description><![CDATA[<p>Link: <a href="https://www.therepository.email/matt-mullenweg-says-the-wheels-have-fallen-off-in-wide-ranging-wordpress-critique?ref=werd.io"><em>Matt Mullenweg Says “The Wheels Have Fallen Off” in Wide-Ranging WordPress Critique, by Rae Morey in The Repository</em></a></p><p>I’m going to put my neck on the line on this story about Matt Mullenweg’s criticism of Wordpress’s open source release culture:</p><blockquote>“WordPress co-founder Matt Mullenweg has delivered a wide-ranging critique of the WordPress project, saying it has spent years doing damage to itself and calling out a release culture he says produces ‘boring or mediocre crap.’”</blockquote><p>It goes on to describe Mullenweg’s frustrations with an open source culture that prevents anything being released without a wide-ranging discussion that brings dozens of people into the thread.</p><blockquote>“We are not being killed by competition, I believe we have done this to ourselves. We did it by blindly following rules and ideals to a point when they became iatrogenic. […] By definition the things that will give us the biggest wins will be the most non-consensus, so we have to accept the occasional failure or mistake otherwise we will never have any wins.”</blockquote><p>So here’s my controversial statement in 2026: on these points, <em>Matt Mullenweg is completely right.</em></p><p>This bureaucratic, consensus-driven culture has also been a blight on other large open source projects, for example at Mozilla. Contributions should be made quickly, and product design should be opinionated rather than consensus-driven. The more a project seeks consensus, the less able it is to innovate.</p><p>That doesn’t mean it should be a fiefdom or a dictatorship. Governance structures have been well-established by co-operatives and similar organizations that allow people to be elected into key roles; if they underperform, the voting base can support someone else. But it’s far better to put your trust in an architect — and achieve consensus about that trust — than it is to try and reach broad consensus about every change. Otherwise it’s not just that nobody wants to try bold new ideas; they literally <em>can’t</em>.</p><p>This is distinct from web standards, for example, which <em>need</em> a consensus basis to prevent a single vendor from dominating how interoperability works. For example, <a href="https://github.com/mozilla/standards-positions/issues/1213?ref=werd.io">Mozilla’s objection to the web Prompt API that Google proposed</a> is good; that’s how those systems should work. But for an individual software project, moving quickly and genuinely innovating are vital.</p><p><a href="http://scripting.com/2026/04/30/125459.html?ref=werd.io">Dave Winer has another take</a>: that WordPress should be more of a platform and allow different people to build opinionated interfaces on top of it. I think that makes a ton of sense too; in that world, WordPress <em>can</em> be an ecosystem monolith, and the opinionated innovation is left to smaller entrepreneurs. That, to be honest, might work a lot better.</p>]]></description>
      <pubDate>Thu, 30 Apr 2026 14:33:03 +0000</pubDate>
      <link>https://werd.io/matt-mullenweg-thinks-wordpress-is-in-decline-he-may-be-right/</link>
      <dc:creator>Ben Werdmuller</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5210522648</guid>
    </item>
    <item>
      <title><![CDATA[Next.js Link as a Button]]></title>
      <description><![CDATA[<p>You’d think it would be simple. And in principle, it can be. The Next.js <code>Link</code> component accept both a <code>className</code> and <code>style</code> prop so you can style it however you want. But what if you <em>already</em> have a <code>Button</code> component that can render a <code>&lt;a&gt;</code> element, and want that component to support router navigation?</p>
<p>In my case, I use Ant Design in a side project, and want to be able to use router navigation with <a href="https://ant.design/components/button">Ant’s <code>Button</code> component</a>. Passing a resolved <code>href</code> prop technically works, but it will be a browser navigation (including a full page reload), and not a router navigation.</p>
<pre class="language-tsx"><code class="language-tsx"><span class="token keyword">import</span> <span class="token punctuation">{</span> Button <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'antd'</span>

<span class="token comment">// 😐 Kinda works, but triggers full-page navigation</span>
<span class="token keyword">function</span> <span class="token function">MyComponent</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
	<span class="token keyword">return</span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">Button</span></span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>/item/1234<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token plain-text">Jump back</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span><span class="token class-name">Button</span></span><span class="token punctuation">&gt;</span></span>
<span class="token punctuation">}</span></code></pre>
<h2>Approach 1: imperative routing</h2>
<p>In its documentation, Next.js mentions <a href="https://nextjs.org/docs/pages/building-your-application/routing/linking-and-navigating#imperative-routing">“imperative routing”</a>. It would look like this:</p>
<pre class="language-tsx"><code class="language-tsx"><span class="token keyword">import</span> <span class="token punctuation">{</span> Button <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'antd'</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> useRouter <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'next/router'</span>

<span class="token keyword">function</span> <span class="token function">MyComponent</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
	<span class="token keyword">const</span> router <span class="token operator">=</span> <span class="token function">useRouter</span><span class="token punctuation">(</span><span class="token punctuation">)</span>

	<span class="token keyword">return</span> <span class="token punctuation">(</span>
		<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">Button</span></span> <span class="token attr-name">onClick</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> router<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span><span class="token string">'/item/1234'</span><span class="token punctuation">)</span><span class="token punctuation">}</span></span><span class="token punctuation">&gt;</span></span><span class="token plain-text">
			Jump back
		</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span><span class="token class-name">Button</span></span><span class="token punctuation">&gt;</span></span>
	<span class="token punctuation">)</span>
<span class="token punctuation">}</span></code></pre>
<p>The problem with this approach is that it will render a <code>&lt;button&gt;</code> element, which is not semantically correct and an accessibility <em><span lang="fr">faux-pas</span></em>. This is not actually a button, it’s a link to another page, and as such should render an <code>&lt;a&gt;</code> element.</p>
<h2>Approach 2: headless link</h2>
<p>The second approach, which I opted for, is to use Next’s <code>&lt;Link&gt;</code> as a headless component — that is a component that doesn’t actually render DOM. This is how to do it (and still works in Next.js 16):</p>
<pre class="language-tsx"><code class="language-tsx"><span class="token keyword">import</span> <span class="token punctuation">{</span> Button <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'antd'</span>
<span class="token keyword">import</span> Link <span class="token keyword">from</span> <span class="token string">'next/link'</span>

<span class="token keyword">function</span> <span class="token function">MyComponent</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
	<span class="token keyword">return</span> <span class="token punctuation">(</span>
		<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">Link</span></span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">'</span>/item/1234<span class="token punctuation">'</span></span> <span class="token attr-name">passHref</span> <span class="token attr-name">legacyBehavior</span><span class="token punctuation">&gt;</span></span><span class="token plain-text">
			</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">Button</span></span><span class="token punctuation">&gt;</span></span><span class="token plain-text">
				Jump back
			</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span><span class="token class-name">Button</span></span><span class="token punctuation">&gt;</span></span><span class="token plain-text">
		</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">Link</span></span><span class="token punctuation">&gt;</span></span><span class="token plain-text">
	)
}
</span></code></pre>
<p>There is <a href="https://github.com/vercel/next.js/blob/4ba05cc300cd3f196bdcebe56de2f6811171bb68/packages/next/src/client/link.tsx#L690-L718">a lot going on in Next’s code</a>, so here is the summary:</p>
<ul>
<li>The <code>legacyBehavior</code> prop resorts to <em>cloning</em> the child element. On its own, it basically does nothing.</li>
<li>The <code>passHref</code> prop assigns the <code>href</code> prop onto the child.</li>
</ul>
<p>So if you use only <code>legacyBehavior</code>, nothing happens because all it does is clone the child element. And if you use only <code>passHref</code>, it renders a <code>&lt;button&gt;</code> element (from Ant) within a <code>&lt;a&gt;</code> element (from Next), which is not what we want. It’s by combining both that we get it to render an Ant <code>&lt;Button&gt;</code> component rendering a <code>&lt;a&gt;</code> element, using Next’s routing logic.</p>
<aside class="Callout Callout--warning" role="note"><p>As you can infer from the prop name, this approach is not future-proof because the <code>legacyBehavior</code> will likely be removed in a future Next.js version. I’m unclear what will be the approach in the future. I recall there used to be a <code>withRouterLink()</code> <abbr title="Higher-Order Component">HOC</abbr> in older Next.js version, but it’s no longer a thing.</p>
</aside>
<h2>Making a reusable component</h2>
<p>It’s something I’ve used often enough in the project that I resorted to create a small wrapping component for that:</p>
<pre class="language-tsx"><code class="language-tsx"><span class="token keyword">import</span> <span class="token punctuation">{</span> Button<span class="token punctuation">,</span> <span class="token keyword">type</span> <span class="token class-name">ButtonProps</span> <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'antd'</span>
<span class="token keyword">import</span> Link<span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token keyword">type</span> <span class="token class-name">LinkProps</span> <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'next/link'</span>

<span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">RouterButtonProps</span> <span class="token operator">=</span> Omit<span class="token operator">&lt;</span>ButtonProps<span class="token punctuation">,</span> <span class="token string">'href'</span> <span class="token operator">|</span> <span class="token string">'htmlType'</span><span class="token operator">&gt;</span> <span class="token operator">&amp;</span> <span class="token punctuation">{</span>
  href<span class="token operator">:</span> LinkProps<span class="token punctuation">[</span><span class="token string">'href'</span><span class="token punctuation">]</span>
  linkProps<span class="token operator">?</span><span class="token operator">:</span> Omit<span class="token operator">&lt;</span>LinkProps<span class="token punctuation">,</span> <span class="token string">'href'</span><span class="token operator">&gt;</span>
<span class="token punctuation">}</span>

<span class="token keyword">export</span> <span class="token keyword">function</span> <span class="token function">RouterButton</span><span class="token punctuation">(</span><span class="token punctuation">{</span> href<span class="token punctuation">,</span> linkProps<span class="token punctuation">,</span> <span class="token operator">...</span>props <span class="token punctuation">}</span><span class="token operator">:</span> RouterButtonProps<span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">return</span> <span class="token punctuation">(</span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">Link</span></span> <span class="token attr-name">href</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>href<span class="token punctuation">}</span></span> <span class="token spread"><span class="token punctuation">{</span><span class="token operator">...</span>linkProps<span class="token punctuation">}</span></span> <span class="token attr-name">passHref</span> <span class="token attr-name">legacyBehavior</span><span class="token punctuation">&gt;</span></span><span class="token plain-text">
      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">Button</span></span> <span class="token spread"><span class="token punctuation">{</span><span class="token operator">...</span>props<span class="token punctuation">}</span></span> <span class="token punctuation">/&gt;</span></span><span class="token plain-text">
    </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span><span class="token class-name">Link</span></span><span class="token punctuation">&gt;</span></span>
  <span class="token punctuation">)</span>
<span class="token punctuation">}</span></code></pre>
<p>And it can be used like this:</p>
<pre class="language-tsx"><code class="language-tsx"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">RouterButton</span></span>
	<span class="token comment">// The `href` prop is handled by the Next link</span>
	<span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>/item/1234<span class="token punctuation">"</span></span>
	<span class="token comment">// Occasional and optional link props can be passed</span>
	<span class="token comment">// See: https://nextjs.org/docs/app/api-reference/components/link#reference</span>
	<span class="token attr-name">linkProps</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">{</span> <span class="token function-variable function">onNavigate</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token function">track</span><span class="token punctuation">(</span><span class="token string">'jump_back_click'</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">}</span></span>
	<span class="token comment">// Everything else goes to the Ant button</span>
	<span class="token comment">// See: https://ant.design/components/button#api</span>
	<span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">'</span>primary<span class="token punctuation">'</span></span>
	<span class="token attr-name">ghost</span>
	<span class="token attr-name">block</span>
	<span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>MyButton<span class="token punctuation">"</span></span>
<span class="token punctuation">&gt;</span></span><span class="token plain-text">
	Jump back
</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span><span class="token class-name">RouterButton</span></span><span class="token punctuation">&gt;</span></span></code></pre>
<p>And of course, it renders what we expect: a single <code>&lt;a&gt;</code> element, benefiting from Next routing navigation, styled as a button. No invalid DOM, no full page reload. Neat!</p>
]]></description>
      <pubDate>Sat, 02 May 2026 00:00:00 +0000</pubDate>
      <link>https://kittygiraudel.com/2026/05/02/nextjs-link-as-a-button/index.md</link>
      <dc:creator>Kitty Giraudel</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5212669819</guid>
    </item>
    <item>
      <title><![CDATA[At Machine Speed]]></title>
      <description><![CDATA[
                    
																																	<p>Yesterday, I opened Discord to a message from my friend Bastian Allgeier that I had never quite seen in all the years I’ve been building sites with his <a href="https://getkirby.com/">Kirby CMS</a>. “Today we are releasing our biggest security release in the last 14+ years,” Bastian wrote. “The last few weeks have been intense, to say the least. We received 8 reports within just a couple of days.”</p>
<p>Six of those eight security reports turned out to be valid vulnerabilities. The resulting <a href="https://github.com/getkirby/kirby/releases/tag/5.4.0">Kirby 5.4.0 release</a> ships with a list of advisories that reads less like a changelog and more like a triage report. Nothing critical, nothing exploited in the wild, luckily – but for a small team that has been quietly maintaining a beautifully designed CMS for more than a decade, this is unlike anything they’ve had to deal with before.</p>
<p>And Kirby is far from alone.</p>
<p>A few weeks ago, Anthropic’s red team <a href="https://red.anthropic.com/2026/zero-days/">described</a> how their large language model <a href="https://en.wikipedia.org/wiki/Claude_Shannon">Claude</a> Opus, pointed at a handful of open source projects, autonomously found and validated more than 500 high-severity vulnerabilities – some of them in code that had been aggressively fuzzed and reviewed by humans for years. Earlier this month, the same kind of work went public in a much bigger way. <a href="https://www.anthropic.com/research/glasswing">Project Glasswing</a>, Anthropic’s partnership with AWS, Apple, Google, Microsoft, the Linux Foundation, and roughly forty other organisations, uses an unreleased model called Claude Mythos Preview to find vulnerabilities in the software that runs pretty much everything. Thousands of zero-days. Including a <a href="https://www.infosecurity-magazine.com/news/anthropic-launch-project-glasswing/">27-year-old bug in OpenBSD</a>, an operating system famous for its security hardening. A model, Anthropic says, capable enough that they have decided not to release it.</p>
<p>There is, obviously, a marketing layer to all of this. Glasswing is a beautifully staged announcement, complete with partner logos and a butterfly metaphor. Independent researchers have <a href="https://news.aibase.com/news/27173">pointed out</a> that the list of CVEs directly attributable to the project is, at the moment, much shorter than the headline numbers would suggest. And there are <a href="https://www.promarket.org/2026/04/22/the-antitrust-risks-of-anthropics-project-glasswing-and-the-ai-avengers/">legitimate antitrust concerns</a> about a private consortium of the world’s largest tech companies having exclusive access to a tool like this. And yet, if you look around, even the more sceptical voices in security seem to be treating this as a genuine inflection point. Rich Mogull at the Cloud Security Alliance has been calling it the <a href="https://cloudsecurityalliance.org/blog/2026/04/08/anthropic-s-mythos-is-here-defending-from-the-vulnpocalypse">“Vulnpocalypse”</a> – the moment when language models can find zero-days and write working exploits faster than we can patch them.</p>
<p>What this means on the ground, for the people actually maintaining the software, is a flood.</p>
<p>In January, Daniel Stenberg, who has maintained <a href="https://curl.se/">curl</a> for nearly three decades, <a href="https://daniel.haxx.se/blog/2026/01/26/the-end-of-the-curl-bug-bounty/">shut down the project’s bug bounty program</a> because he and his security team were drowning in AI-generated slop. A few months later, the slop receded. But it was replaced by something harder to dismiss: genuinely <em>good</em> security reports, almost all done with the help of AI, arriving at a frequency his team has never seen before. The quality went up. The workload did not go down.</p>
<p>And that’s the problem: generating a finding is cheap. Validating and patching it is not. And that cost then falls entirely on the maintainers – on people like Bastian and his <a href="https://getkirby.com/team">Kirby team</a>, on the Linux kernel reviewers, on the handful of volunteers who keep the dependencies of the dependencies of the dependencies alive. If the only realistic answer to machine-speed discovery is machine-speed triage and patching, then the maintainers who are drowning in reports don’t just need more time and more hands – it feels like at some point, they will need the same kind of tooling that is drowning them. The tool is both the flood and the pump.</p>
<p>But the pump has a price tag. Right now, that pricing is softened by subsidy – the big AI companies are losing money on every query while they compete for market share. When prices adjust – and they will, in fact, <a href="https://github.blog/news-insights/company-news/changes-to-github-copilot-individual-plans/">they already are</a> – the bill lands somewhere. Big companies will pay it without flinching. The corporate security industry is already busy selling them tools to handle attacks “at machine speed” – agentic AI, autonomous SOCs (security operations centres), hyperautomation platforms. But who pays the bill for a small team like Kirby’s, or the maintainer of a Craft CMS plugin, or the PHP library three layers down that half the web quietly depends on? All of this is also going to become a governance question for open source, and I don’t know whether we have an answer yet.</p>
<p>And ultimately, we, as an industry, are the downstream.</p>
<p>I run Kirby in several client projects and I trust the team and their work. Every time a security release lands, I update quickly and quietly, usually covered by a reasonable maintenance retainer. But the cadence is changing. The usual “let’s skip a few minor updates to save the budget for the bigger ones” will probably not hold up anymore – at least not if you care about security, which you should. And for each update, someone has to read the release notes, test the update, and push it to production, ideally the same day. That is going to become a much more explicit part of the conversations I have with clients.</p>
<p>And of course, it’s good that these bugs are being found. And they need to be fixed. The 27-year-old hole in OpenBSD was there all those years – we just couldn’t see it. Making bugs visible is a gift, even when the gift arrives wrapped in a pile of work. But visibility is only the first step. What matters is whether we can really close the gap between finding and fixing – and whether we, as clients, as agencies, as developers, as a community, are willing to fund, staff, and support the people doing the work.</p>
<p>But also: what can we do, together, to make the software we depend on more secure? In budgets, in update routines, in client conversations, in the way we contribute to and pay for the projects we use every day. I will see Bastian at <a href="https://beyondtellerrand.com/events/dusseldorf-2026">beyond tellerrand Düsseldorf</a> next week and we’ll certainly talk about all of this a bit. But I know I’ll also be having that conversation with every one of my clients in the coming weeks. And I’d love to hear your thoughts on this as well: how does it all play out for the projects and websites you maintain?</p>

																												            	]]></description>
      <pubDate>Thu, 23 Apr 2026 13:56:00 +0000</pubDate>
      <link>https://matthiasott.com/notes/at-machine-speed</link>
      <dc:creator>Matthias Ott</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5203437095</guid>
    </item>
    <item>
      <title><![CDATA[Summary punishment]]></title>
      <description><![CDATA[

<p>In <a href="https://newsletter.ownyourweb.site/archive/own-your-web-issue-18-curators/">the latest issue</a> of Matthias’s excellent <cite>Own Your Web</cite> series, he describes the recent betrayal by Google:</p>

<blockquote>
  <p>The search engine no longer says “here, go read what this person wrote.” It now says “here, I’ve already read it for you.” The contract is broken.</p>
</blockquote>

<p>He’s absolutely right.</p>

<p>But…</p>

<p>Have you ever clicked on a result from a search engine? Unless you’re lucky enough to land on a nice personal website, you’re more than likely to be confronted with pop-ups to allow tracking, or a desparate plea to subscribe to a newsletter, or just rubbish ads all accompanied by a slow page loading somewhere in the mix.</p>

<p>Don’t get me wrong. I’m not saying that what Google is doing is okay. But let’s not pretend that everything indexed by Google is just fine and dandy for people to visit.</p>

<p>And of course the main reason why websites are so terrible is because they’ve tied their business model to heaps of behavioral advertising driven by invasive tracking courtesy of …Google.</p>

<p>This reminds me of AMP. Remember <a href="https://adactio.com/tags/google,amp">Google AMP</a>? It was a terrible solution to a real problem. Web pages were (and still are) bloated and slow. The correct solution would be to encourage people to fix that, but instead Google mandated a proprietary format for your content that had to be hosted on their servers.</p>

<p>AMP was a disaster, both in practical terms and in the reputational damage it did to Google’s developer relations.</p>

<p>Now they’re doing it again, powerwashing away any goodwill they ever had with site owners. Now Google doesn’t even send search engine traffic to the websites that host the ads that Google encouraged people to put on every page.</p>

<p>It’s almost as if Google is a company so large and with so many competing interests that it now suffers from an incurable split personality disorder.</p>

<p>Personally I think they’re missing a trick. They should be using “AI” summaries as a stick.</p>

<p>If your site is slow, or filled with user-hostile annoyances then it <em>should</em> be cockblocked by a hallucinated summary. But a nice fast respectful website? Send the traffic their way! Everyone wins—users, site owners, Google, the World Wide Web.</p>

<p>Could you imagine how quickly this would revolutionise the world of search engine optimisation? They’ve always told us that we should make websites for humans in order to get good Google juice. This would be a way of making it come true, without any of the over-engineered woefulness of AMP.</p>

<p>It’ll never happen of course. But I can dream.</p>


            ]]></description>
      <pubDate>Thu, 23 Apr 2026 14:45:59 +0000</pubDate>
      <link>https://adactio.com/journal/22540</link>
      <dc:creator>Adactio: Journal</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5202611504</guid>
    </item>
    <item>
      <title><![CDATA[Subscription Cost Visualizer]]></title>
      <description><![CDATA[<p><a href="https://visualize.nguyenvu.dev"><img loading="lazy" decoding="async" src="https://www.swiss-miss.com/wp-content/uploads/2026/04/Screenshot-2026-04-22-at-7.12.39-PM-1024x940.png" alt="" width="1024" height="940" class="alignnone size-large wp-image-86257"></a></p>
<p><a href="https://visualize.nguyenvu.dev">Subscription Cost Visualizer</a> is a lightweight, interactive tool that turns your subscriptions into a visual grid. Instead of a list of numbers, you get a clear, proportional view of where your money is going—larger blocks represent higher costs, making your spending instantly legible.</p>
]]></description>
      <pubDate>Wed, 22 Apr 2026 23:15:33 +0000</pubDate>
      <link>https://www.swiss-miss.com/2026/04/subscription-cost-visualizer.html</link>
      <dc:creator>swissmiss</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5201835535</guid>
    </item>
    <item>
      <title><![CDATA[Collaborative Editing as Progressive Enhancement]]></title>
      <description><![CDATA[<p>We’re ramping up again to launch the <a href="https://www.kickstarter.com/projects/fontawesome/build-awesome-pro?ref=43ttgb">Build Awesome (11ty) Kickstarter Final_FINAL_v2 on <strong>April 28, 2026</strong></a> and in this post I make the case for a new web site builder can layer itself on top of your existing projects as a progressive enhancement. Infrastructure as progressive enhancement!</p>
<div class="primarylink">
	<is-land on:idle="" on:media="(prefers-reduced-motion: no-preference)"><a href="https://www.kickstarter.com/projects/fontawesome/build-awesome-pro?ref=43ttgb">Launching <time-difference live="" units="milliseconds" mode="countdown"><time datetime="2026-04-28T15:00:00Z">April 28</time></time-difference>.</a></is-land>
</div>]]></description>
      <pubDate>Wed, 22 Apr 2026 05:00:00 +0000</pubDate>
      <link>https://www.zachleat.com/web/collaborative-editing/</link>
      <dc:creator>Zach Leatherman</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5201566147</guid>
    </item>
    <item>
      <title><![CDATA[Introducing Beeswacks]]></title>
      <description><![CDATA[<img src="https://storage.ghost.io/c/e6/db/e6db78f5-f507-465e-97cb-f965b4d12d26/content/images/2026/04/bees-card.png" alt="Introducing Beeswacks"><p>Hello, Simple Friends. Today, we're announcing the official release of our latest typeface, <a href="https://simplebits.shop/products/beeswacks?ref=simplebits.com" rel="noreferrer"><strong>Beeswacks</strong></a><strong>!</strong></p><figure class="kg-card kg-image-card kg-width-wide"><a href="https://simplebits.shop/products/beeswacks?ref=simplebits.com"><img src="https://storage.ghost.io/c/e6/db/e6db78f5-f507-465e-97cb-f965b4d12d26/content/images/2026/04/bees-cover-cm-1.png" class="kg-image" alt="Introducing Beeswacks" loading="lazy" width="2000" height="1334" srcset="https://storage.ghost.io/c/e6/db/e6db78f5-f507-465e-97cb-f965b4d12d26/content/images/size/w600/2026/04/bees-cover-cm-1.png 600w, https://storage.ghost.io/c/e6/db/e6db78f5-f507-465e-97cb-f965b4d12d26/content/images/size/w1000/2026/04/bees-cover-cm-1.png 1000w, https://storage.ghost.io/c/e6/db/e6db78f5-f507-465e-97cb-f965b4d12d26/content/images/size/w1600/2026/04/bees-cover-cm-1.png 1600w, https://storage.ghost.io/c/e6/db/e6db78f5-f507-465e-97cb-f965b4d12d26/content/images/size/w2400/2026/04/bees-cover-cm-1.png 2400w" sizes="(min-width: 1200px) 1200px"></a></figure><p>Beeswacks is a vintage-esque, display sans-serif based on a handful of letters found on an old box of candles made by Columbia Wax Works in Ozone Park, New York. Like a lot of other old lettering, this one spoke to me, particularly the wide M, R, and S.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://storage.ghost.io/c/e6/db/e6db78f5-f507-465e-97cb-f965b4d12d26/content/images/2026/04/waxworks-1.png" class="kg-image" alt="Introducing Beeswacks" loading="lazy" width="1600" height="1000" srcset="https://storage.ghost.io/c/e6/db/e6db78f5-f507-465e-97cb-f965b4d12d26/content/images/size/w600/2026/04/waxworks-1.png 600w, https://storage.ghost.io/c/e6/db/e6db78f5-f507-465e-97cb-f965b4d12d26/content/images/size/w1000/2026/04/waxworks-1.png 1000w, https://storage.ghost.io/c/e6/db/e6db78f5-f507-465e-97cb-f965b4d12d26/content/images/2026/04/waxworks-1.png 1600w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Found via </span><a href="https://purveyorsofpackaging.com/?ref=simplebits.com" rel="noreferrer"><span style="white-space: pre-wrap;">Purveyors of Packaging</span></a></figcaption></figure><p>We took those capital letters as guidance in filling out a whole alphabet that retains the worn, hand-painted vibes from the candle box. Beeswacks will be fantastic for logos, posters, signage, branding, etc.</p><figure class="kg-card kg-image-card"><a href="https://simplebits.shop/products/beeswacks?ref=simplebits.com"><img src="https://storage.ghost.io/c/e6/db/e6db78f5-f507-465e-97cb-f965b4d12d26/content/images/2026/04/bees-2-cm.png" class="kg-image" alt="Introducing Beeswacks" loading="lazy" width="2000" height="1334" srcset="https://storage.ghost.io/c/e6/db/e6db78f5-f507-465e-97cb-f965b4d12d26/content/images/size/w600/2026/04/bees-2-cm.png 600w, https://storage.ghost.io/c/e6/db/e6db78f5-f507-465e-97cb-f965b4d12d26/content/images/size/w1000/2026/04/bees-2-cm.png 1000w, https://storage.ghost.io/c/e6/db/e6db78f5-f507-465e-97cb-f965b4d12d26/content/images/size/w1600/2026/04/bees-2-cm.png 1600w, https://storage.ghost.io/c/e6/db/e6db78f5-f507-465e-97cb-f965b4d12d26/content/images/size/w2400/2026/04/bees-2-cm.png 2400w" sizes="(min-width: 720px) 720px"></a></figure><figure class="kg-card kg-image-card"><a href="https://simplebits.shop/products/beeswacks?ref=simplebits.com"><img src="https://storage.ghost.io/c/e6/db/e6db78f5-f507-465e-97cb-f965b4d12d26/content/images/2026/04/bw-3.png" class="kg-image" alt="Introducing Beeswacks" loading="lazy" width="2000" height="2500" srcset="https://storage.ghost.io/c/e6/db/e6db78f5-f507-465e-97cb-f965b4d12d26/content/images/size/w600/2026/04/bw-3.png 600w, https://storage.ghost.io/c/e6/db/e6db78f5-f507-465e-97cb-f965b4d12d26/content/images/size/w1000/2026/04/bw-3.png 1000w, https://storage.ghost.io/c/e6/db/e6db78f5-f507-465e-97cb-f965b4d12d26/content/images/size/w1600/2026/04/bw-3.png 1600w, https://storage.ghost.io/c/e6/db/e6db78f5-f507-465e-97cb-f965b4d12d26/content/images/size/w2400/2026/04/bw-3.png 2400w" sizes="(min-width: 720px) 720px"></a></figure><p>Beeswacks comes in a single weight and includes upper &amp; lowercase letters, numerals, punctuation, diacritics with expanded&nbsp;<strong>language support</strong>, as well as some alternate character icons and script-y, discretionary ligatures for the words&nbsp;<em>the</em>,&nbsp;<em>of</em>,&nbsp;<em>in</em>,&nbsp;<em>at</em>,&nbsp;<em>and</em>,&nbsp;<em>for</em>.</p><figure class="kg-card kg-image-card"><a href="https://simplebits.shop/products/beeswacks?ref=simplebits.com"><img src="https://storage.ghost.io/c/e6/db/e6db78f5-f507-465e-97cb-f965b4d12d26/content/images/2026/04/bees-3-sq.png" class="kg-image" alt="Introducing Beeswacks" loading="lazy" width="2000" height="2000" srcset="https://storage.ghost.io/c/e6/db/e6db78f5-f507-465e-97cb-f965b4d12d26/content/images/size/w600/2026/04/bees-3-sq.png 600w, https://storage.ghost.io/c/e6/db/e6db78f5-f507-465e-97cb-f965b4d12d26/content/images/size/w1000/2026/04/bees-3-sq.png 1000w, https://storage.ghost.io/c/e6/db/e6db78f5-f507-465e-97cb-f965b4d12d26/content/images/size/w1600/2026/04/bees-3-sq.png 1600w, https://storage.ghost.io/c/e6/db/e6db78f5-f507-465e-97cb-f965b4d12d26/content/images/size/w2400/2026/04/bees-3-sq.png 2400w" sizes="(min-width: 720px) 720px"></a></figure><div class="kg-card kg-button-card kg-align-center"><a href="https://simplebits.shop/products/beeswacks?ref=simplebits.com" class="kg-btn kg-btn-accent">GET THE BEESWACKS FONT</a></div><p>Beeswacks is available now, <a href="https://simplebits.shop/products/beeswacks?ref=simplebits.com" rel="noreferrer"><strong>individually in our shop</strong></a> or <a href="https://simplebits.com/club" rel="noreferrer"><strong>join our Secret Club™</strong></a> and get ALL of our fonts + other perks for just $60/year.</p><p>Thanks for your support! Enjoy the new font, tag us if you use it, and see you back here on Friday for our next Studio Notes™.</p><p>Cheers!</p><figure class="kg-card kg-image-card"><img src="https://storage.ghost.io/c/e6/db/e6db78f5-f507-465e-97cb-f965b4d12d26/content/images/2026/04/sig-5.png" class="kg-image" alt="Introducing Beeswacks" loading="lazy" width="1000" height="77" srcset="https://storage.ghost.io/c/e6/db/e6db78f5-f507-465e-97cb-f965b4d12d26/content/images/size/w600/2026/04/sig-5.png 600w, https://storage.ghost.io/c/e6/db/e6db78f5-f507-465e-97cb-f965b4d12d26/content/images/2026/04/sig-5.png 1000w" sizes="(min-width: 720px) 720px"></figure>]]></description>
      <pubDate>Tue, 21 Apr 2026 16:05:05 +0000</pubDate>
      <link>https://simplebits.com/n/introducing-beeswacks/</link>
      <dc:creator>SimpleBits®</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5200085316</guid>
    </item>
    <item>
      <title><![CDATA[Teaser Trailer for Silo Season Three]]></title>
      <description><![CDATA[
         	<p><iframe src="https://www.youtube.com/embed/C9-_VVX9BvE" title="YouTube video" allowfullscreen=""></iframe></p>
	<p>As I watched <a href="https://www.youtube.com/watch?v=C9-_VVX9BvE">the teaser trailer</a> for season three of <a href="https://tv.apple.com/us/show/silo/umc.cmc.3yksgc857px0k0rqe5zd4jice">Silo</a>, I discovered that I am very much looking forward to this new season. July 3, 2026.
</p>
 

        
         <p><strong>Tags:</strong> <a href="https://kottke.org/tag/silo">silo</a> · <a href="https://kottke.org/tag/trailers">trailers</a> · <a href="https://kottke.org/tag/TV">TV</a> · <a href="https://kottke.org/tag/video">video</a></p>
        
    ]]></description>
      <pubDate>Tue, 21 Apr 2026 16:20:00 +0000</pubDate>
      <link>https://kottke.org/26/04/teaser-trailer-for-silo-season-three</link>
      <dc:creator>kottke.org</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5200102960</guid>
    </item>
    <item>
      <title><![CDATA[Web tools are cool]]></title>
      <description><![CDATA[<p>My website has a <a href="https://dbushell.com/uses/">new page!</a> The <code>/uses</code> URL pathname is an “official” <a href="https://slashpages.net/" rel="noopener noreferrer" target="_blank">slash page</a>. I’m only listing web tools I use for now. My <a href="https://dbushell.com/2026/01/08/app-defaults/">default apps</a> change too frequently. The list is an evolution of an <a href="https://dbushell.com/2024/11/25/links-initiative/">old post</a> I was secretly maintaining.</p><div class="Box"><p>👉 <a href="https://dbushell.com/uses/">Visit my /uses page!</a></p></div><h2 id="web-tools">Web tools</h2><p>Web tools are web-based tools, obviously. Used in a web browser without a download. Some may be installed as <glossary-term id="--term-pwa"><a href="https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps/Guides/What_is_a_progressive_web_app" rel="noopener noreferrer" target="_blank">progressive web apps</a></glossary-term> but I prefer a <em>progressive browser tabs</em>. Most tools do <strong>one thing</strong> and do it well. Some of my links are actually just text information, but <em>“web tools and documentation and specifications”</em> is a mouthful.</p><p>All the tools I’m collating are related to web development. I’d guess I use <a href="https://squoosh.app/" rel="noopener noreferrer" target="_blank">Squoosh</a> the most, followed by <a href="https://caniuse.com/" rel="noopener noreferrer" target="_blank">Can I use</a> or this <a href="https://specificity.keegan.st/" rel="noopener noreferrer" target="_blank">specificity calculator</a> (there are many, I like that one.) Stu Robson’s <a href="https://www.alwaystwisted.com/relicss/" rel="noopener noreferrer" target="_blank">ReliCSS</a> is a new addition that looks very useful.</p><p>I realise now I’m not hosting any web tools myself. Many moons ago I maintained a few NPM packages that did stuff (<a href="https://gruntjs.com/" rel="noopener noreferrer" target="_blank">grunted</a>, mostly). Web tools are so much better. Less malware, for one. I once hosted a <a href="https://dbushell.com/2020/03/05/bundle-a-pwa-as-an-android-app/">to-do app</a>, but who hasn’t? That reminds me, sub-domains are the secret to hosting side projects. I must stop buying short-lived project domains.</p><p>Anyway, I should build a <a href="https://dbushell.com/uses/#web-tools">web tool</a>. Any ideas?</p><p>Do you have any favourite web tools I should be aware of? I’m not looking to list everything, just stuff I might personally use.</p><p><small>P.S. This is definitely not another bookmarks project I’ll forget about.</small></p>
<hr>
<p>
Thanks for reading! Follow me on <a href="https://dbushell.com/mastodon/">Mastodon</a> and <a href="https://dbushell.com/bluesky/">Bluesky</a>.
Subscribe to my <a href="https://dbushell.com/rss.xml">Blog</a> and <a href="https://dbushell.com/notes/rss.xml">Notes</a> or <a href="https://dbushell.com/merge/rss.xml">Combined</a> feeds.
</p>
]]></description>
      <pubDate>Tue, 21 Apr 2026 07:31:59 +0000</pubDate>
      <link>https://dbushell.com/2026/04/21/web-tools/</link>
      <dc:creator>dbushell.com (blog)</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5199554487</guid>
    </item>
    <item>
      <title><![CDATA[*Pro-level* travel tips]]></title>
      <description><![CDATA[<img src="https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/2026/04/IMG_0464-1-1.JPG" alt="*Pro-level* travel tips"><p>I love traveling to new places and getting to enjoy new experiences. And as much as I love travel, it can be chaotic.</p><p>A friend once said that if you’ve ever tried to meditate and failed to quiet your jumbled mind, you should take a trip instead.&nbsp;</p><p>Because when you travel, your entire world distills down to finishing a single task to keep yourself safe and alive, letting everything else fall away (just like meditation!). </p><p>First, you have to find your plane. Then you have to find your luggage. Next, you have to find your hotel. Then—surprise!—you need to find dinner at 9pm in a city you’ve never set foot in. Good luck!</p><p>So here's a bunch of tips I use to keep the chaos at bay, to feel more at home when I'm traveling, and bring some semblance of control back into my time in new places.</p><h2 id="have-a-dedicated-electronics-travel-bag">Have a dedicated electronics travel bag</h2><figure class="kg-card kg-image-card"><img src="https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/2026/04/TB0967-VKPL-CoPilot-SaguaroBallistic-PersimmonCerylon.png" class="kg-image" alt="*Pro-level* travel tips" loading="lazy" width="1200" height="800" srcset="https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/size/w600/2026/04/TB0967-VKPL-CoPilot-SaguaroBallistic-PersimmonCerylon.png 600w, https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/size/w1000/2026/04/TB0967-VKPL-CoPilot-SaguaroBallistic-PersimmonCerylon.png 1000w, https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/2026/04/TB0967-VKPL-CoPilot-SaguaroBallistic-PersimmonCerylon.png 1200w" sizes="(min-width: 720px) 720px"></figure><p>I put this off for years and it wasn't until <a href="https://a.wholelottanothing.org/recent-camping-and-travel-reviews/" rel="noreferrer">last summer</a> when I finally bought a dedicated bag to hold all my electronics that is <strong>ONLY</strong> used for travel. I've stuck to my promise to never touch anything inside <a href="https://www.tombihn.com/products/co-pilot?variant=45240443207869&amp;ref=a.wholelottanothing.org" rel="noreferrer">my bag</a> when I'm at home, so it's always fully stocked and ready for a trip. This has been <em>such</em> a game changer. </p><p>Here's what's always in the bag:</p><ul><li>A wall charger with 4 high-speed usb-c charge ports</li><li>Two usb-c to usb-c cables</li><li>A couple usb-a to usb-c cables because you never know what a rental car or hotel room might have for extra charge ports</li><li>A usb-c to lightning cable for my headphones</li><li>A usb-c to Apple Watch charger in <a href="https://makerworld.com/en/models/940742-apple-watch-compact-travel-case?ref=a.wholelottanothing.org" rel="noreferrer">a 3D printed case</a> to keep things tidy</li></ul><p>For each trip I add these things from home:</p><ul><li><a href="https://amzn.to/3QiYcoN?ref=a.wholelottanothing.org" rel="noreferrer">A Bivo water bottle</a> that fits in the bottle slot in the bag</li><li>My iPad Pro with Magic keyboard (I only bring a laptop when I must for work, otherwise this is enough)</li><li>AirPod Max headphones for noise canceling on the plane, AirPods 4 headphones for walking around</li></ul><p>I also recently added two new things to the permanently-in-the-bag pile.</p><h2 id="take-an-old-appletv-and-hdmi-cord-with-you">Take an old AppleTV and HDMI cord with you</h2><figure class="kg-card kg-image-card"><img src="https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/2026/04/Screenshot-2026-04-21-at-10.32.21---AM.png" class="kg-image" alt="*Pro-level* travel tips" loading="lazy" width="1946" height="1012" srcset="https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/size/w600/2026/04/Screenshot-2026-04-21-at-10.32.21---AM.png 600w, https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/size/w1000/2026/04/Screenshot-2026-04-21-at-10.32.21---AM.png 1000w, https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/size/w1600/2026/04/Screenshot-2026-04-21-at-10.32.21---AM.png 1600w, https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/2026/04/Screenshot-2026-04-21-at-10.32.21---AM.png 1946w" sizes="(min-width: 720px) 720px"></figure><p>The AppleTV is my absolute favorite set-top box and I have two of them at home. I tend to upgrade them whenever a new version comes out, so I have a few older AppleTVs laying around and I recently started bringing one with a power cord and a short HDMI cable with me on trips.</p><p>Every hotel I stay in has a modern flat screen TV but it's rare for them to support any screencasting technology (maybe 1 in 10 hotels have this feature in my experience?). But every hotel TV has a HDMI port and I've yet to have any issues plugging in my own AppleTV while using my phone as the remote for controlling it.</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/2026/04/Screenshot-2026-04-21-at-10.37.11---AM.png" class="kg-image" alt="*Pro-level* travel tips" loading="lazy" width="2000" height="1075" srcset="https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/size/w600/2026/04/Screenshot-2026-04-21-at-10.37.11---AM.png 600w, https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/size/w1000/2026/04/Screenshot-2026-04-21-at-10.37.11---AM.png 1000w, https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/size/w1600/2026/04/Screenshot-2026-04-21-at-10.37.11---AM.png 1600w, https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/2026/04/Screenshot-2026-04-21-at-10.37.11---AM.png 2110w" sizes="(min-width: 1200px) 1200px"></figure><p>If you use profiles on your AppleTV, you can even make your travel AppleTV mirror exactly the same apps you have at home. This means after a long day, you'll get a little slice of home wherever you are, and you can keep up with shows before you drift off to sleep. </p><p>But how do you connect it to the hotel's wifi? </p><h2 id="use-a-travel-router">Use a travel router</h2><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/2026/04/Screenshot-2026-04-21-at-10.42.27---AM.png" class="kg-image" alt="*Pro-level* travel tips" loading="lazy" width="1644" height="968" srcset="https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/size/w600/2026/04/Screenshot-2026-04-21-at-10.42.27---AM.png 600w, https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/size/w1000/2026/04/Screenshot-2026-04-21-at-10.42.27---AM.png 1000w, https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/size/w1600/2026/04/Screenshot-2026-04-21-at-10.42.27---AM.png 1600w, https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/2026/04/Screenshot-2026-04-21-at-10.42.27---AM.png 1644w" sizes="(min-width: 1200px) 1200px"><figcaption><span style="white-space: pre-wrap;">My Beryl travel router plugged into a hotel clock for power</span></figcaption></figure><p>I've heard about the benefits of a travel router for years but it wasn't until last month that I finally picked up <a href="https://amzn.to/4sXPuKk?ref=a.wholelottanothing.org" rel="noreferrer">a Beryl 7 travel router</a> and after a couple trips, I wish I bought it sooner.</p><p>First, they're great for sharing a connection with all your devices, which means gadgets like an AppleTV or Apple Watch that can't connect to a hotel's WiFi now get can get online. It also means you can buy WiFi on a plane for $8 and share it with everyone in your party if you want.</p><p>A travel router has a couple ethernet ports but also a couple wifi points inside it. It's got flexibility so it can take any outside internet connection and funnel it into its own WiFi point that all your devices connect to normally with a simple password.</p><p>Here's how it works:</p><ul><li>Plug it into usb-c power and give it a few seconds to boot up</li><li>Connect to the router's WiFi and pull up its own internal website (the default is at http://192.168.8.1)</li><li>Use the website's wizard to log into whatever system you're trying to connect to, including hotels, planes, trains, or cruise ship wifi</li><li>Once you're logged in, anything connecting to your travel router shares a single outside connection</li></ul><p>You can do more things with a travel router like share files and tunnel all your connections through a VPN but at its core, getting to share one connection with all your devices is really handy.</p><h2 id="install-tailscale-at-home-and-setup-an-exit-node">Install Tailscale at home and setup an exit node</h2><p>I've said it many times before but <a href="https://tailscale.com/?ref=a.wholelottanothing.org" rel="noreferrer">Tailscale</a> is the glue that holds my tech stack together, and you can level up your travel experience by installing it at home onto an always-on device like a desktop Mac, Raspberry Pi, or an AppleTV (in my case it's on a Synology NAS server).</p><p>Next, set up <a href="https://tailscale.com/docs/features/exit-nodes?ref=a.wholelottanothing.org" rel="noreferrer">an Exit Node</a> so you can pipe all your traffic through your house connection, no matter where you are.</p><p>I bring this up because whenever I travel, I like to unwind before bed by watching an hour or so of TV and movies.&nbsp;</p><p>But I remember one trip to Vancouver where I popped open my laptop to finish a show I was watching at home, and instead I got an error that I couldn't view it because it’s wasn't available on Canadian Netflix. </p><p>The worst experience was when I was flying into SFO and I spent most of the flight watching a Golden State Warriors game using my $17/mo NBA League Pass, but we landed with ten minutes left in the game. I pulled it up on my phone once I got into the terminal but all I saw was a blackout screen blocking playback and I missed the end of the game I'd watched for the past 90 minutes.</p><figure class="kg-card kg-image-card"><img src="https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/2026/04/IMG_4642.JPG" class="kg-image" alt="*Pro-level* travel tips" loading="lazy" width="2000" height="1208" srcset="https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/size/w600/2026/04/IMG_4642.JPG 600w, https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/size/w1000/2026/04/IMG_4642.JPG 1000w, https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/size/w1600/2026/04/IMG_4642.JPG 1600w, https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/size/w2400/2026/04/IMG_4642.JPG 2400w" sizes="(min-width: 720px) 720px"></figure><p>I get that companies have complex broadcast rules governed by your geo-location but I pay streaming services hundreds of dollars a year to use them and last week when I got a cutesy error on the Peacock app while trying to watch a baseball game, I felt like I, as a customer, was being treated like a child. It's patronizing and annoying as hell.</p><p>The good news is that if you use an app like Tailscale, you can avoid this when you travel, because you’ll be able to do <strong><em>everything</em></strong> you can at home, as you'll actually be sharing your home's IP address when you connect your phone or AppleTV to your Exit Node. Everything you use will be <strong>exactly</strong> as if you were at home, no matter where you are on earth.</p><p><strong>Bonus tip</strong>: If you run a network-level adblocker at home like <a href="https://pi-hole.net/?ref=a.wholelottanothing.org" rel="noreferrer">Pi-hole</a> or <a href="https://adguard.com/en/welcome.html?ref=a.wholelottanothing.org" rel="noreferrer">Adguard</a>, you can set Tailscale to use that as your DNS server. So that means the setup I have to keep ads off for anyone using my home network works outside of my house too, since my phone is always connected to Tailscale. </p><p>Now, when I read the New York Times (that I also pay for!) on my phone while I'm out and about, all the movie ads and junk in the middle of articles is gone, just like when I'm on home WiFi.</p><h2 id="the-old-internet-classic-use-the-pants-hanger-hack-to-sleep-in">The old internet classic: use the pants hanger hack to sleep in</h2><p>I don't remember where I first heard about this but it was probably around 2008 or so. You've probably have seen this tip, but if it's new to you, you're in luck because it'll change your life while traveling.</p><p>Most hotel chains have blackout curtains but they always seem to not quite meet in the middle correctly. So you get a searing beam of light piercing your eyes at 7am when you really wanted to sleep in and feel well-rested after a long flight. </p><p>The next time you travel, look for a hanger in the hotel closet with spring-loaded clasps for hanging pants, and use those to clamp the blackout curtains tightly together the night you get into your room. Then put the do not disturb sign on your door. </p><figure class="kg-card kg-image-card"><img src="https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/2026/04/IMG_6197.JPG" class="kg-image" alt="*Pro-level* travel tips" loading="lazy" width="2000" height="2667" srcset="https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/size/w600/2026/04/IMG_6197.JPG 600w, https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/size/w1000/2026/04/IMG_6197.JPG 1000w, https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/size/w1600/2026/04/IMG_6197.JPG 1600w, https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/size/w2400/2026/04/IMG_6197.JPG 2400w" sizes="(min-width: 720px) 720px"></figure><p>The next morning, you'll sleep like a baby.</p><h2 id="two-classic-apps-i-never-travel-without">Two classic apps I never travel without</h2><p>Speaking of internet classics, there are two apps I've been using every time I travel, one for over 10 years and the other for 20.</p><p>The oldest one is <a href="https://www.tripit.com/web?ref=a.wholelottanothing.org" rel="noreferrer">TripIt</a>, which I joined when it launched in the 2000s and all it does is check your email for travel-related confirmation emails, then adds them to your calendar along with all the confirmation numbers and addresses and proper flight times. It's worked flawlessly for years doing that one function very well.</p><p>The other app is <a href="https://www.flightaware.com/?ref=a.wholelottanothing.org" rel="noreferrer">FlightAware</a>. I run the iOS app and any time you fly, you put in your airline and flight number and it'll show you the flight history over the past few days, how often the plane was late, etc, but there's a button marked "Where is my plane?" that you can hit on the day of your flight and see in realtime exactly where your plane is located, and quickly determine if your flight will leave on time, or if it's backed up from three late arrivals earlier today. Honestly, this little app gives me more information than the airline ever would, and I love it to death.</p><h2 id="last-one-every-hotel-has-room-service-thanks-to-food-delivery-apps">Last one: every hotel has room service thanks to food delivery apps</h2><figure class="kg-card kg-image-card kg-width-wide"><img src="https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/2026/04/IMG_2262.JPG" class="kg-image" alt="*Pro-level* travel tips" loading="lazy" width="2000" height="1664" srcset="https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/size/w600/2026/04/IMG_2262.JPG 600w, https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/size/w1000/2026/04/IMG_2262.JPG 1000w, https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/size/w1600/2026/04/IMG_2262.JPG 1600w, https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/size/w2400/2026/04/IMG_2262.JPG 2400w" sizes="(min-width: 1200px) 1200px"></figure><p>I've never been a big user of DoorDash, Uber Eats, Toast, or any of the local restaurant delivery apps because most of the time adding delivery doubles the order cost and I can drive myself to pickup my food just as quickly.</p><p>But when I'm in a strange town and I'm exhausted and it's late and I don't know anything good to eat at nearby, I pull up a food delivery app, search for something that would hit the spot, and treat myself by getting something delivered. It's been great on trips, as I often fly to cities and skip renting a car, using public transit or walking as much as possible.</p><p>After a long day on a plane, nothing hits the spot like getting to eat my favorite Indian dish that was brought to my lobby at the same time I was settling in and unpacking my clothes.</p>]]></description>
      <pubDate>Tue, 21 Apr 2026 19:02:02 +0000</pubDate>
      <link>https://a.wholelottanothing.org/pro-level-travel-tips/</link>
      <dc:creator>A Whole Lotta Nothing</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5200351304</guid>
    </item>
    <item>
      <title><![CDATA[Retour.]]></title>
      <description><![CDATA[
      <p>I’m giving a talk at <a href="https://bostonwp.org/2026/04/april-2026-meetup-ethan-marcotte-mary-hubbard/">next week’s Boston WordPress meetup</a>! If you’re around on Monday night<span class="w"> —</span> that’s 27 April 2026, for you Julian calendar sickos<span class="w"> —</span> I hope you’ll be able to join us. I’ll be giving a short talk on design systems: how they’ve changed the way digital teams work, and how we might need to rethink them. It’s a fun talk, and I’m excited to be there.</p>
<p>I’ve been rehearsing the talk this week, and was struck by…well, how weird it felt to rehearse a talk. That’s when I realized that next week’s meetup will be the first in-person talk I’ve given since 2019. Twenty-nineteen! Seven entire years! An eternity ago. Public speaking used to be, like, a whole entire thing I used to do. And now it’s just…not. I’ve given plenty of <em>remote</em> talks since then, mind you. (And they’re still <a href="https://ethanmarcotte.com/wrote/speaking-remotely/">plenty of work</a>!) But as much fun as a virtual event is, I’ve missed getting a chance to meet attendees, hear what they’re working on, and what they’re excited about.</p>
<p>A lot of this is due to a changing world. The amount of travel I did fell off <em>sharply</em> <a href="https://ethanmarcotte.com/wrote/let-a-website-be-a-worry-stone/">back in 2020</a>, and my house acquired <a href="https://ethanmarcotte.com/wrote/notes-from-a-week/">some new health concerns</a> back in 2021 that’ve made it harder to attend public events amid an ongoing pandemic. And the industry hasn’t escaped unscathed, either: many of the conferences I used to speak at simply don’t exist anymore.</p>
<p>This is all to say that amid all that change<span class="w"> —</span> and my nerves<span class="w"> —</span> I’m so glad to see that local, community-focused events are still going strong. And I’m <em>honored</em> to be in the room at the next Boston WordPress meetup. I’ll be masked up, sure, and probably a little more rusty than I used to be. But if you’re able to come, I hope you’ll say hi. It’d be great to meet you.</p>

      
      <hr>
      <p>This has been “<a href="https://ethanmarcotte.com/wrote/retour/">Retour.</a>” a post from <a href="https://ethanmarcotte.com/wrote/">Ethan’s journal.</a></p>
      <p><a href="mailto:listener+rss@ethanmarcotte.com?subject=Reply%20to:%20“Retour.”">Reply via email</a></p>
      
    ]]></description>
      <pubDate>Tue, 21 Apr 2026 04:00:00 +0000</pubDate>
      <link>https://ethanmarcotte.com/wrote/retour/</link>
      <dc:creator>My journal — Ethan Marcotte’s website</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5200251268</guid>
    </item>
    <item>
      <title><![CDATA[Why I don't chain everything in JavaScript anymore]]></title>
      <description><![CDATA[JavaScript chaining looks clean at first, but it can hurt readability and hide extra work. Here's when to break chains into simpler steps.]]></description>
      <pubDate>Mon, 20 Apr 2026 04:00:00 +0000</pubDate>
      <link>https://allthingssmitty.com/2026/04/20/why-i-dont-chain-everything-in-javascript-anymore/</link>
      <dc:creator>Matt Smith</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5198457005</guid>
    </item>
    <item>
      <title><![CDATA[Google Releases Gemini for Mac]]></title>
      <description><![CDATA[<div class="media-wrapper"><img decoding="async" src="https://cdn.macstories.net/cleanshot-2026-04-15-at-12-15-21-2x-1776270702507.png" alt=""><p class="image-caption"></p></div>
<p id="p2"><a href="https://gemini.google/mac" rel="noopener noreferrer">Google released a native Mac app</a> for its Gemini chatbot today.</p>
<p id="p3">The app, which can be launched from your Applications folder, Dock, the menu bar, or a global hotkey, will be familiar to anyone who has used Gemini in a browser. The chatbot supports Gemini 3 in Fast and Thinking modes, as well as Pro mode, which uses Gemini 3.1 Pro. Gemini can also interact with files, the contents of a window, Google Drive, Photos, and NotebookLM. It’s multimodal, too, with support for the generation of text, images, video, and music. Dig a little deeper into Gemini’s menus and you’ll find support for Canvas, Deep Research, Guided Learning, and <a href="https://gemini.google/overview/personal-intelligence/" rel="noopener noreferrer">Personalized Intelligence</a>.</p>
<div class="media-wrapper"><img decoding="async" src="https://cdn.macstories.net/cleanshot-2026-04-15-at-12-17-01-2x-1776270966224.png" alt="A Gemini mini window is available from the menu bar and a global hotkey."><p class="image-caption">A Gemini mini window is available from the menu bar and a global hotkey.</p></div>
<p id="p5">Even though I just downloaded the app a short time ago, my Gemini chat history was immediately available in the app. The history appears in the app’s sidebar along with a search field, My Stuff, which includes things like images and video generated in the past, and access to your account. The app is <a href="https://x.com/joshwoodward/status/2044452201947627709?s=46" rel="noopener noreferrer">written in Swift</a> which was a pleasant surprise.</p>
<div class="media-wrapper"><img decoding="async" src="https://cdn.macstories.net/cleanshot-2026-04-15-at-12-30-58-2x-1776271027181.png" alt="All my past prompts were immediately available in the new Gemini Mac app."><p class="image-caption">All my past prompts were immediately available in the new Gemini Mac app.</p></div>
<p id="p7">I’ve only just begun testing Gemini for Mac, but I can already tell that it’s a cut above my hand-crafted single-purpose Safari web app solution. All the same tools found on the web are here, but in a native wrapper, which I appreciate. If you use a Mac and Gemini, the new app is well worth giving a try.</p>
<p id="p8">Gemini for Mac <a href="https://gemini.google/mac" rel="noopener noreferrer">is available as a free download from Google</a>.</p>
]]></description>
      <pubDate>Wed, 15 Apr 2026 16:46:27 +0000</pubDate>
      <link>https://www.macstories.net/news/google-releases-gemini-for-mac/</link>
      <dc:creator>MacStories</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5193686999</guid>
    </item>
    <item>
      <title><![CDATA[Vintage Pan-Am Luggage Tags]]></title>
      <description><![CDATA[
         	<p><img src="https://kottke.org/cdn-cgi/image/format=auto,fit=scale-down,width=1200,metadata=none//plus/misc/images/editor-1776177088-f795ae53.jpg" srcset="https://kottke.org/cdn-cgi/image/format=auto,fit=scale-down,width=500,metadata=none//plus/misc/images/editor-1776177088-f795ae53.jpg 500w, https://kottke.org/cdn-cgi/image/format=auto,fit=scale-down,width=1200,metadata=none//plus/misc/images/editor-1776177088-f795ae53.jpg 1200w" sizes="(max-width: 500px) 500px, 1200px" loading="lazy"></p>
	<p><img src="https://kottke.org/cdn-cgi/image/format=auto,fit=scale-down,width=1200,metadata=none//plus/misc/images/editor-1776177097-1c7e69cb.jpg" srcset="https://kottke.org/cdn-cgi/image/format=auto,fit=scale-down,width=500,metadata=none//plus/misc/images/editor-1776177097-1c7e69cb.jpg 500w, https://kottke.org/cdn-cgi/image/format=auto,fit=scale-down,width=1200,metadata=none//plus/misc/images/editor-1776177097-1c7e69cb.jpg 1200w" sizes="(max-width: 500px) 500px, 1200px" loading="lazy"></p>
	<p><img src="https://kottke.org/cdn-cgi/image/format=auto,fit=scale-down,width=1200,metadata=none//plus/misc/images/editor-1776177102-e83de26b.jpg" srcset="https://kottke.org/cdn-cgi/image/format=auto,fit=scale-down,width=500,metadata=none//plus/misc/images/editor-1776177102-e83de26b.jpg 500w, https://kottke.org/cdn-cgi/image/format=auto,fit=scale-down,width=1200,metadata=none//plus/misc/images/editor-1776177102-e83de26b.jpg 1200w" sizes="(max-width: 500px) 500px, 1200px" loading="lazy"></p>
	<p>I love <a href="https://ellafreire.com/collections/pan-american-luggage-labels">these oversized prints of vintage Pan-Am luggage tags</a> from artist Ella Freire. The typography and colors are just perfect. (via <a href="https://daringfireball.net/">daringfireball</a>)
</p>
 

        
         <p><strong>Tags:</strong> <a href="https://kottke.org/tag/art">art</a> · <a href="https://kottke.org/tag/design">design</a> · <a href="https://kottke.org/tag/Ella%20Freire">Ella Freire</a> · <a href="https://kottke.org/tag/travel">travel</a></p>
        
    ]]></description>
      <pubDate>Wed, 15 Apr 2026 17:45:00 +0000</pubDate>
      <link>https://kottke.org/26/04/vintage-pan-am-luggage-tags</link>
      <dc:creator>kottke.org</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5193745879</guid>
    </item>
    <item>
      <title><![CDATA[E-bikes are a *thorny* issue for trails and parks]]></title>
      <description><![CDATA[<img src="https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/2026/04/IMG_2177-1.JPG" alt="E-bikes are a *thorny* issue for trails and parks"><p>I ran across this article on the LA Times site about the friction developing around shared trails in the Los Angeles area between e-bikes, bikes, horses, hikers, and dog walkers. It does a good job summing up everyone's issues but it barely scratches the surface of just how complicated trying to regulate e-bikes can be.</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://www.latimes.com/california/story/2026-04-15/e-bikes-are-all-over-mountain-trails-some-want-them-banned?ref=a.wholelottanothing.org"><div class="kg-bookmark-content"><div class="kg-bookmark-title">E-bikes are all over mountain trails. Some want them banned</div><div class="kg-bookmark-description">The intrusion of e-bikes is sparking a fierce backlash from traditional trail users and forcing land managers into a confusing new debate over safety and fairness.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/icon/apple-touch-icon.png" alt="E-bikes are a *thorny* issue for trails and parks"><span class="kg-bookmark-author">Los Angeles Times</span><span class="kg-bookmark-publisher">Jack Dolan</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/thumbnail/75" alt="E-bikes are a *thorny* issue for trails and parks" onerror="this.style.display = 'none'"></div></a></figure><p>I want to walk through all the competing viewpoints, hopefully giving everyone some background context on how hard it is to develop future laws around these devices.</p><h2 id="my-bona-fides">My bona fides</h2><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/2026/04/b23349dd125b31a707232509de86d06549c71ce28a0a53c242386f7f90f2fbab.jpg" class="kg-image" alt="E-bikes are a *thorny* issue for trails and parks" loading="lazy" width="1200" height="800" srcset="https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/size/w600/2026/04/b23349dd125b31a707232509de86d06549c71ce28a0a53c242386f7f90f2fbab.jpg 600w, https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/size/w1000/2026/04/b23349dd125b31a707232509de86d06549c71ce28a0a53c242386f7f90f2fbab.jpg 1000w, https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/2026/04/b23349dd125b31a707232509de86d06549c71ce28a0a53c242386f7f90f2fbab.jpg 1200w" sizes="(min-width: 1200px) 1200px"><figcaption><span style="white-space: pre-wrap;">Me, jumping the finish line in the middle of the pack in a short track MTB race in 2016</span></figcaption></figure><p>I first learned to ride a bike at age 7 and never stopped. It gave me the freedom to go anywhere and see the world, and that's what I use bikes for. I've regularly competed in bike events since I was twelve, all the way up to last weekend when I raced in a 40mi gravel event. If you ask me how many bikes I currently own I know I sound like an asshole when I say "I dunno, somewhere between 8 and 10ish maybe?" I've collected loads of bikes and parts over the decades and so that tends to happen.</p><p>I disliked the concept of e-bikes when I first heard about them like every other bike racer. They seem like "cheating" and the idea seemed silly because why would you let a motor do all the fun of actual bike riding? Eventually I learned the origin story and understood it better (more on that in a bit), but I still couldn't imagine myself owning or even wanting one.</p><p>Then one day back in 2019, I was at my local bike shop getting some parts and saw a big rack of city-focused e-bikes all charged and ready for test rides, so I took one out for a 20 minute spin around town. Long story short, I had a smile on my face bigger than any I could remember since I was seven years old doing it without training wheels for the first time. It was liberating and felt safer now that I could keep up with car traffic, it was endlessly fun to have a motor to back up your movements, and I saw how it could open the possibilities of what you could do on a bike. I was instantly converted and bought my first one for running errands around town.</p><h2 id="my-current-stance-on-e-bikes">My current stance on e-bikes</h2><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/2026/04/IMG_6166.JPG" class="kg-image" alt="E-bikes are a *thorny* issue for trails and parks" loading="lazy" width="2000" height="1500" srcset="https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/size/w600/2026/04/IMG_6166.JPG 600w, https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/size/w1000/2026/04/IMG_6166.JPG 1000w, https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/size/w1600/2026/04/IMG_6166.JPG 1600w, https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/size/w2400/2026/04/IMG_6166.JPG 2400w" sizes="(min-width: 1200px) 1200px"><figcaption><span style="white-space: pre-wrap;">My Trek eMTB (I also own an e-bike for riding in the city by Trek)</span></figcaption></figure><p>Over the years I've tried all sorts of e-bikes. The kind of e-bikes I own and enjoy are pedal-assisting Class 1 types (top motor speed: 20mph), which is the most mild of the e-bike varieties. They ride like normal bikes and the motor doesn't kick until you're actively pedaling, offering an added push to let you go further and faster. They tend to have about 10 to 20 pounds of additional weight compared to a normal bike.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/2026/04/Screenshot-2026-04-16-at-1.45.13---PM.png" class="kg-image" alt="E-bikes are a *thorny* issue for trails and parks" loading="lazy" width="1180" height="984" srcset="https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/size/w600/2026/04/Screenshot-2026-04-16-at-1.45.13---PM.png 600w, https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/size/w1000/2026/04/Screenshot-2026-04-16-at-1.45.13---PM.png 1000w, https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/2026/04/Screenshot-2026-04-16-at-1.45.13---PM.png 1180w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">A screenshot from a Minnesota police account trying to educate the public about bikes</span></figcaption></figure><p>Class 1 e-bikes make up about 50% of bike sales in the US and I'm all for more people getting to enjoy them in more places. I'm less of a fan of Class 2 e-bikes, which are those that use a throttle and don't require pedaling, although they do have a 20mph motor limit. People tend to use Class 2 bikes like mopeds around town. The ones I most dislike the higher powered "e-motos" that come with a throttle and either no pedals or a joke set of sprockets and chains to still be classified loosely as a bike by some regulatory body. They're backed by much larger motors and batteries and are essentially electric motorcycles but are often sold as e-bikes in the marketplace. These are significantly heavier and much faster, sometimes weighing as much as motorcycles, so the chance for injury or damage from impacts is much higher.</p><h2 id="the-physical-abilities-angle">The physical abilities angle</h2><p>The origin story of e-bikes is worth knowing. I remember when they were first developed in The Netherlands, primarily for a specific population. Amsterdam is famous as a place where you can run many short errands on a bike so much so that it's often easier than a car to get around. Middle-aged people there often ride 5-20 miles a day while they go to work, run errands, and ride home, but as people age into their 70s, 80s, and beyond, it's harder to cover that much distance in a day, and that's where the e-bike concept was born. It makes perfect sense for that specific audience as it lets you ride further while also saving effort.</p><p>I also have a friend that suffered a serious illness a few years ago, requiring an organ transplant, and they were formerly a competitive cyclist. Thanks to e-bikes, today that friend can hang onto group rides with their old bike racing buddies, because of their e-bike's additional propulsion.</p><p><strong>The bottom line:</strong> An e-bike is revolutionary for anyone that wants to continue riding a bike and covering distances when some other factor is keeping them from doing that. It's important to know that any kind of a blanket e-bike ban means a small subset of e-bike riders using them as assistive devices will lose their access to the outdoors and exercise.</p><h2 id="the-safety-angle">The safety angle</h2><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/2026/04/TR0C9311.JPG" class="kg-image" alt="E-bikes are a *thorny* issue for trails and parks" loading="lazy" width="2000" height="1333" srcset="https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/size/w600/2026/04/TR0C9311.JPG 600w, https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/size/w1000/2026/04/TR0C9311.JPG 1000w, https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/size/w1600/2026/04/TR0C9311.JPG 1600w, https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/size/w2400/2026/04/TR0C9311.JPG 2400w" sizes="(min-width: 1200px) 1200px"><figcaption><span style="white-space: pre-wrap;">Bike race crash I captured at a cyclocross race in 2014</span></figcaption></figure><p>Another obvious issue around e-bikes is that the faster you go on a bike, the more you can get hurt (and hurt others in the process). Bikes handle differently at different speeds and seasoned riders know you put your weight back and slow down any turning when you're flying downhill at high speed, but e-bikes often end up in the hands of newer riders and casual cyclists who ride less often. They might only be used to riding bikes at 10mph but when their bike can now hit 25mph+, bad things can happen. Helmets are way more important for e-bikes and collisions with walkers and hikers are going to happen when people are moving faster.</p><p>Lately there's been a rash of stories about tweens and teens getting seriously injured on powerful e-bikes with at least one lawsuit against an e-bike manufacturer brought on by the parents of a child who died in a crash riding one of those e-bikes. To date, most e-bike companies say you should be at least 18 to ride so kids getting hurt isn't their fault, legally speaking.</p><p><strong>The bottom line:</strong> The safety of riders and people around them is vital to crafting any legislation around e-bikes, but it's really difficult to set the specifics into law that make sense. I do think high powered "e-moto" types of e-bikes should be kept out of the hands of kids and remain off-road use only. But it's tough to justify a blanket e-bike ban on safety concerns when a Class 1 e-bike generally gets to the same speeds as a bike without a motor. Lawmakers often end up trying to craft nuanced rules around which e-bikes are ok versus which are not, but the public often views any bike with a motor an e-bike they'd like to see stopped.</p><h2 id="the-legal-angle">The legal angle</h2><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/2026/04/Screenshot-2026-04-16-at-10.14.10---AM.png" class="kg-image" alt="E-bikes are a *thorny* issue for trails and parks" loading="lazy" width="1774" height="1224" srcset="https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/size/w600/2026/04/Screenshot-2026-04-16-at-10.14.10---AM.png 600w, https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/size/w1000/2026/04/Screenshot-2026-04-16-at-10.14.10---AM.png 1000w, https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/size/w1600/2026/04/Screenshot-2026-04-16-at-10.14.10---AM.png 1600w, https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/2026/04/Screenshot-2026-04-16-at-10.14.10---AM.png 1774w" sizes="(min-width: 1200px) 1200px"><figcaption><span style="white-space: pre-wrap;">I honestly can't tell you what class a Super73 is by just looking at it</span></figcaption></figure><p>There is lots of confusion around all the varieties of e-bikes and not just in how the general public views e-bikes but even among owners of the bikes themselves. The Class 1, 2, 3, and beyond system I describe isn't totally clear when you walk up to any random e-bike and look at it, or even if you're riding one. Different manufacturers build different features into their e-bikes based on what sells and there isn't a strict process or vetting of which bikes are classified into each class. There isn't even accurate labeling around it! When I look at any random instagram ad I get for an e-bike, speaking as someone that spent years working at bike shops I can't tell you which class of e-bike something is unless I look at a detailed spec sheet of what the bike has on it.</p><p><strong>The bottom line:</strong> It's incredibly difficult to put up rules or signs banning certain types of bikes when even the riders might not even know what they're riding. It's also tough to train people to enforce e-bike laws because it's usually not clear what power levels, top speeds, or features a specific e-bike has, just based on looking at them.</p><h2 id="the-trail-access-angle">The trail access angle</h2><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/2026/04/D48F1A9E-4EEC-4E0B-B710-5D308E088C74.jpg" class="kg-image" alt="E-bikes are a *thorny* issue for trails and parks" loading="lazy" width="1440" height="1800" srcset="https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/size/w600/2026/04/D48F1A9E-4EEC-4E0B-B710-5D308E088C74.jpg 600w, https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/size/w1000/2026/04/D48F1A9E-4EEC-4E0B-B710-5D308E088C74.jpg 1000w, https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/2026/04/D48F1A9E-4EEC-4E0B-B710-5D308E088C74.jpg 1440w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Quick stop during a group gravel ride in 2022</span></figcaption></figure><p>Access to trails is vital, and finding good places to ride anywhere in the US isn't easy. My current favorite MTB trails are located two hours away from me and though they've been around for about a decade, any changes to state or federal funding could close them up instantly. A couple of years ago they allowed Class 1 e-bikes on those same trails, but any sorts of bans could take those away.</p><p>A typical MTB trail system involves driving to a trailhead parking lot, then riding your bike up a long slow climb to the top, where you'll find a variety of downhill trails to get back to where you started. Most places I end up climbing for 45 minutes or so, then enjoying a 10-15 minute quick jaunt back down to the bottom. Usually I do another lap or two, until I feel exhausted.</p><p>A Class 1 e-bike makes the long climb up much easier, as you can get to the top of that same trail system in about 15 minutes, then ride down as normal. Instead of being exhausted after a couple laps on a normal mountain bike, an eMTB might let you do 3-4 laps in the same time it'd take you to complete 1 or 2.</p><figure class="kg-card kg-image-card"><img src="https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/2026/04/IMG_1371.JPG" class="kg-image" alt="E-bikes are a *thorny* issue for trails and parks" loading="lazy" width="2000" height="1500" srcset="https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/size/w600/2026/04/IMG_1371.JPG 600w, https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/size/w1000/2026/04/IMG_1371.JPG 1000w, https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/size/w1600/2026/04/IMG_1371.JPG 1600w, https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/size/w2400/2026/04/IMG_1371.JPG 2400w" sizes="(min-width: 720px) 720px"></figure><p>A couple years ago, I rode trails in the Gemini Bridges area of Moab, Utah. At each trailhead there were signs that said all not only were all e-bikes were banned on MTB trails in Moab, but you could be fined thousands of dollars, have your bike confiscated, and risk up to 6 months in jail for your first infraction. I thought that was pretty extreme, so I only brought my regular mountain bike on the trip.</p><p>The trails were great fun, but at 6,000' above sea level, after an hour I was close to exhaustion. While I was resting on a ride, I saw an older couple likely in their 70s riding downhill towards me and they looked happy. I noticed their mountain bikes had large downtubes (where batteries are stored on e-bikes) and though they were toying with some pretty steep penalties, those trails were the perfect place and reason to use a mild e-bike on MTB trails.</p><p>A lot of organizations that maintain trails want to keep them safe for hikers, walkers, and horse riders so they don't get hit, and they want trails to last year after year. A Class 1 eMTB usually only weighs about 10-15lbs more than a regular MTB, and though people on them might do more laps, I would argue the additional impact on trails from eMTBs is minimal.</p><p>But without a blanket ban, you might see more of the e-moto type "bikes" show up and those can be 100-200lb bikes with up to 80 horsepower and larger 3-5" wide tires that can really tear a trail system up because they are basically motorcycles and will have a much larger impact from usage.</p><p><strong>The bottom line:</strong> Trail access is vital to riders but that access has always been tenuous and can go away at any time, so riders tend to defend their trails. Blanket bans on e-bikes can certainly curtail impacts and dangers of all the various kinds of e-bikes, but my hope is any trail access includes Class 1 e-bikes as they're closer to regular bikes than anything else in the mix of what is generally considered "an e-bike". It's a little nuanced, but I really do hope all MTB trails eventually allow Class 1 e-bikes for all the previously mentioned reasons, while trying to keep the faster/larger bikes/e-motos out.</p><h2 id="whats-the-ultimate-solution">What's the ultimate solution?</h2><p>Unfortunately there's no easy way out of this, as here are the competing problems:</p><ul><li>The bike industry is largely unregulated and there's not much oversight or standardization across e-bikes. Even if we wanted consistent labeling on e-bikes, it would be a difficult thing to implement. Any mix of features can end up on any kind of e-bike as there are almost no requirements around most e-bikes.</li><li>People in charge of trails are getting complaints about riders (likely on e-motos) and they can do much more damage to trails that require increased maintenance costs to the trail owners. They're also potentially reaching dangerous speeds and endangering others and that is worth talking about, but a blanket ban, while easier to enforce, goes too far.</li><li>There's a rash of teens and kids getting seriously hurt on high powered, high speed "e-bikes." I see people riding them on streets like real motorcycles but an e-bike has no special licensing process, whereas getting a motorcycle license in Oregon is an expensive, month-long process with hours of classroom training and parking lot testing. There's a lot of lawmaking that would be required to get e-moto "bikes" treated more like motorcycles,</li><li>Implementing a blanket e-bike ban for anything with two wheels and a motor makes enforcement easier, since you don't have to figure out which class an e-bike is part of. Laws that carve out access for Class 1 bikes will need to be nuanced and training will be required for those enforcing any rules.</li><li>Any ban on e-bikes in outdoor spaces is going to restrict people using them for assistive purposes, which likely isn't the goal of any e-bike laws and will be an unfortunate side effect.</li></ul><p>Honestly this is a complicated issue on several fronts, but I do believe Class 1 e-bikes to be the safest and most mild variety of motor assist, and should be able to go wherever most bikes can go. But I also see why bikes with a throttle stop being quite like a regular bike and more like something else while larger e-motos shouldn't be considered e-bikes and instead classified as motorcycles.</p><p>Hopefully this clears up why it's so hard to make rules around e-bikes, and why some cities are implementing new laws, trying things out to curtail the bad effects of e-bikes while still allowing for some e-bikes.</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/2026/04/IMG_9672.JPG" class="kg-image" alt="E-bikes are a *thorny* issue for trails and parks" loading="lazy" width="2000" height="1500" srcset="https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/size/w600/2026/04/IMG_9672.JPG 600w, https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/size/w1000/2026/04/IMG_9672.JPG 1000w, https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/size/w1600/2026/04/IMG_9672.JPG 1600w, https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/size/w2400/2026/04/IMG_9672.JPG 2400w" sizes="(min-width: 1200px) 1200px"><figcaption><span style="white-space: pre-wrap;">Riding my old fat bike in Sunriver, Oregon back in 2016</span></figcaption></figure>]]></description>
      <pubDate>Thu, 16 Apr 2026 22:57:29 +0000</pubDate>
      <link>https://a.wholelottanothing.org/e-bikes-are-a-thorny-issue-for-trails-and-parks/</link>
      <dc:creator>A Whole Lotta Nothing</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5195269712</guid>
    </item>
    <item>
      <title><![CDATA[7 View Transitions Recipes to Try]]></title>
      <description><![CDATA[
<p>View transitions are really, really neat. Not only that, but they’re starting to pop up <em>everywhere</em>. I’m sure you’re like me and have come across more than a few in the wild that both make you go <em>wow</em> and want to instantly use them on your own site or project.</p>



<p>At the same time, view transitions can be tricky to “get” at first. <a href="https://css-tricks.com/snippets/css/basic-view-transition/">They can be simple</a>, sure, but most anything beyond a cross-fade involves several moving parts.</p>



<p>I tend to find that the best way to learn something new is to see the code, use them myself, and then build upon them. So, I’ve collected seven view transition recipes for exactly that. We’ll go over the basic setup, demo the recipes, and turn you loose to experiment!</p>



<span id="more-392002"></span>



<p>It’s perfectly fine to go below and just copy the one you like the most, but if you want to understand what view transitions are all about, then I recommend going through a&nbsp;<a href="https://css-tricks.com/toe-dipping-into-view-transitions/">quick introduction first</a>&nbsp;before getting to the recipes.</p>



<p>Oh, and before we jump in, it’s worth noting that view transitions are indeed Baseline and supported by all major browsers as I’m writing this. But some types of animations may or may not be supported by a specific browser, so keep an eye on that and test, as always.</p>




<baseline-status class="wp-block-css-tricks-baseline-status" featureid="view-transitions"></baseline-status>


<h3 class="wp-block-heading" id="the-set-up">The setup</h3>


<p>For each view transition, we’ll need to do a little setup beforehand. First off, we need to <em>opt in</em> to them using the&nbsp;<a href="https://css-tricks.com/almanac/rules/v/view-transition/"><code>@view-transition</code></a>&nbsp;at-rule on&nbsp;<strong>both pages</strong> — the page we’re on and the page we’re transitioning to. If you’re using templates on your site, then this might go in the header template so it globally applies everywhere.</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">@media (prefers-reduced-motion: no-preference) {
  @view-transition {
    navigation: auto;
    types: &lt;transition-type&gt;;
  }
}</code></pre>



<p>That <code>&lt;transition-type&gt;</code> is the only part you can’t directly copy-paste. It’s a placeholder for the&nbsp;<code>types</code>&nbsp;descriptor, <a href="https://css-tricks.com/what-on-earth-is-the-types-descriptor-in-view-transitions/">something we’ve covered in detail before</a>. It’s more nuanced than this, but <code>types</code> are basically the animation name we give to a specific transition. That way, if we’re working with multiple transitions, we can be explicit about which ones are active to prevent them from conflicting with one another. But read that linked article to get deeper into it.</p>



<p>Notice how we have the&nbsp;<code>@view-transition</code>&nbsp;walled behind the&nbsp;<code><a href="https://css-tricks.com/almanac/rules/m/media/prefers-reduced-motion/">prefers-reduced-motion: no-preference</a></code> media query. Not everyone wants movement on their pages and that’s a preference that can be set at the OS level, so we’ll respect that where needed this way.</p>



<p>Lastly, we’ll apply our animation as follows:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">html:active-view-transition-type(&lt;transition-type&gt;)::view-transition-old(root) {
  animation: a-cool-outgoing-animation 1.4s ease forwards;
}

html:active-view-transition-type(&lt;transition-type&gt;)::view-transition-new(root) {
  animation: a-cool-incoming-animation 1.4s ease forwards;
}</code></pre>



<p>…where the&nbsp;<code>:active-view-transtion-type()</code>&nbsp;pseudo matches the transition <code>type</code> we define in&nbsp;the <code>@view-transition</code> rule. For example, if we’re calling an animation that we’ve named <code>bounce</code>, then we’d use that in the at-rule like this:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">@media (prefers-reduced-motion: no-preference) {
  @view-transition {
    navigation: auto;
    types: &lt;transition-type&gt;;
  }
}</code></pre>



<p>…as well as the pseudo like this:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">/* The "current" page */
html:active-view-transition-type(bounce)::view-transition-old(root) {
  animation: bounce-in 1.4s ease forwards;
}

/* The page we're transitioning to */
html:active-view-transition-type(bounce)::view-transition-new(root) {
  animation: bounce-in 1.4s ease forwards;
}</code></pre>



<p>OK, that’s enough context to get started with the recipes. Again, feel free to use any of these in your own experiments or projects.</p>


<h3 class="wp-block-heading" id="pixelate-dissolve">Pixelate dissolve</h3>


<p>This one’s sort of like a simple cross-fade, but blurs things out as the old page content fades out and the new page content fades in.</p>



<figure class="wp-block-video"><video height="604" style="aspect-ratio: 1280 / 604;" width="1280" controls="" src="https://css-tricks.com/wp-content/uploads/2026/02/View-transition-Pixelate-dissolve.mp4" playsinline=""></video></figure>



<details class="wp-block-details is-layout-flow wp-block-details-is-layout-flow" open=""><summary>Full snippet</summary>
<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">@media (prefers-reduced-motion: no-preference) {
  @view-transition {
    navigation: auto;
    types: pixelate-dissolve;
  }
}

html:active-view-transition-type(pixelate-dissolve)::view-transition-old(root) {
  animation: pixelate-out 1.4s ease forwards;
}

html:active-view-transition-type(pixelate-dissolve)::view-transition-new(root) {
  animation: pixelate-in 1.4s ease forwards;
}

@keyframes pixelate-out {
  0% {
    filter: blur(0px);
    opacity: 1;
  }
  100% {
    filter: blur(40px);
    opacity: 0;
  }
}

@keyframes pixelate-in {
  0% {
    filter: blur(40px);
    opacity: 0;
  }
  100% {
    filter: blur(0px);
    opacity: 1;
  }
}</code></pre>
</details>


<h3 class="wp-block-heading" id="wipe-up">Wipe up</h3>


<p>Here, we’re using the <a href="https://css-tricks.com/almanac/properties/c/clip-path/"><code>clip-path</code></a> property to achieve the “wipe-up” effect we’re the content for a new page slides up from the bottom, replacing the “old” content.</p>



<p> The process is straightforward: for the outgoing page, we move from its default&nbsp;<code>inset()</code>&nbsp;value of&nbsp;<code>0 0 0 0</code>&nbsp;(which creates a rectangle at the top, right, bottom, and left borders) of the page and change the&nbsp;<strong>bottom</strong>&nbsp;value to&nbsp;<code>100%</code>. Meaning, the page goes from&nbsp;<strong>top</strong>&nbsp;to&nbsp;<strong>bottom</strong>.</p>



<p>The incoming page starts clipping from the <code>top</code> at&nbsp;<code>100%</code>&nbsp;and goes down to&nbsp;<code>0</code>.</p>



<figure class="wp-block-video"><video height="570" style="aspect-ratio: 1280 / 570;" width="1280" controls="" src="https://css-tricks.com/wp-content/uploads/2026/02/View-transition-techniques_-Wipe-Up.mp4" playsinline=""></video></figure>



<details class="wp-block-details is-layout-flow wp-block-details-is-layout-flow" open=""><summary>Full snippet</summary>
<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">@media (prefers-reduced-motion: no-preference) {
  @view-transition {
    navigation: auto;
    types: wipe-up;
  }
}

html:active-view-transition-type(wipe-up)::view-transition-old(root) {
  animation: wipe-out 1.4s ease forwards;
}

html:active-view-transition-type(wipe-up)::view-transition-new(root) {
  animation: wipe-in 1.4s ease forwards;
}

@keyframes wipe-out {
  from {
    clip-path: inset(0 0 0 0);
  }
  to {
    clip-path: inset(0 0 100% 0);
  }
}

@keyframes wipe-in {
  from {
    clip-path: inset(100% 0 0 0);
  }
  to {
    clip-path: inset(0 0 0 0);
  }
}</code></pre>
</details>



<p id="wipe-right">We could just as easily make things wipe right, wipe bottom, and wipe left simply by changing the inset values. For example, here’s things wiping right:</p>



<figure class="wp-block-video"><video height="1080" style="aspect-ratio: 1920 / 1080;" width="1920" controls="" src="https://css-tricks.com/wp-content/uploads/2026/02/wipe-right-edit.mp4"></video></figure>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">@keyframes wipe-out {
  from {
    clip-path: inset(0 0 0 0);
  }
  to {
    clip-path: inset(0 0 0 100%);
  }
}

@keyframes wipe-in {
  from {
    clip-path: inset(0 100% 0 0);
  }
  to {
    clip-path: inset(0 0 0 0);
  }
}</code></pre>



<p>The wipe right works similarly to wipe up, except that the outgoing page goes from the center and cuts towards the right. That’s why the second value goes from&nbsp;<code>0</code>&nbsp;to&nbsp;<code>100%</code>. Similarly, the incoming page goes from&nbsp;<code>100%</code>&nbsp;from the left to&nbsp;<code>0</code>.</p>



<p>Same sort of deal with wiping downward:</p>



<figure class="wp-block-video"><video height="570" style="aspect-ratio: 1280 / 570;" width="1280" controls="" src="https://css-tricks.com/wp-content/uploads/2026/02/View-Transition-Techniques_-Wipe-down.mp4" playsinline=""></video></figure>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">@keyframes wipe-out {
  from {
    clip-path: inset(0 0 0 0);
  }
  to {
    clip-path: inset(100% 0 0 0);
  }
}

@keyframes wipe-in {
  from {
    clip-path: inset(0 0 100% 0);
  }
  to {
    clip-path: inset(0 0 0 0);
  }
}</code></pre>



<p>You get the idea!</p>


<h3 class="wp-block-heading" id="3-zoom-rotate">Rotate in-out</h3>


<p>This one’s a little, um, weird. Definitely not the most practical thing in the world, but it does demonstrate how far you can go with view transitions.</p>



<p>We use the the&nbsp;<a href="https://css-tricks.com/almanac/properties/t/transform/#aa-values"><code>scale()</code>&nbsp;and&nbsp;<code>rotate()</code></a>&nbsp;functions to zoom and rotate the page content, where the “old” page scales down to <code>0</code> and rotates clockwise by&nbsp;<code>180deg</code>. Following that, the “new” page content  scales up to <code>1</code> and rotates counter-clockwise by&nbsp;<code>-180deg</code>. A little <code>opacity</code> is thrown in to help give the illusion that stuff is going out and coming in.</p>



<figure class="wp-block-video"><video height="602" style="aspect-ratio: 1280 / 602;" width="1280" controls="" src="https://css-tricks.com/wp-content/uploads/2026/02/View-Transition-Zoom-rotate.mp4" playsinline=""></video></figure>



<details class="wp-block-details is-layout-flow wp-block-details-is-layout-flow" open=""><summary>Full snippet</summary>
<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">@media (prefers-reduced-motion: no-preference) {
  @view-transition {
    navigation: auto;
    types: zoom-rotate;
  }
}

html:active-view-transition-type(zoom-rotate)::view-transition-old(root) {
  animation: zoom-rotate-out 1.4s ease forwards;
  transform-origin: center;
}

html:active-view-transition-type(zoom-rotate)::view-transition-new(root) {
  animation: zoom-rotate-in 1.4s ease forwards;
  transform-origin: center;
}

@keyframes zoom-rotate-out {
  to {
    transform: scale(0) rotate(180deg);
    opacity: 0;
  }
}

@keyframes zoom-rotate-in {
  from {
    transform: scale(0) rotate(-180deg);
    opacity: 0;
  }
}</code></pre>
</details>


<h3 class="wp-block-heading" id="4-circular-wipe">Circle wipe-out</h3>


<p>This one’s a lot more subtle than the last one. It could be a lot more noticeable if the content we’re transitioning to is more distinct. But as you’ll see in the following video, the “background between “old” and “new” pages share the same background, making for a more seamless transition.</p>



<figure class="wp-block-video"><video height="1080" style="aspect-ratio: 1920 / 1080;" width="1920" controls="" src="https://css-tricks.com/wp-content/uploads/2026/02/View-Transitions-Circular-Wipe-1.mp4" playsinline=""></video></figure>



<p>The circle comes courtesy of the&nbsp;<a href="https://css-tricks.com/almanac/properties/c/clip-path/"><code>clip-pat</code>h</a> property, draws the shape from the center using the&nbsp;<code>circle()</code>&nbsp;function, going from from&nbsp;0% (no size)&nbsp;to&nbsp;150% (sized beyond the content), making it encapsulate the entire page.</p>



<details class="wp-block-details is-layout-flow wp-block-details-is-layout-flow" open=""><summary>Full snippet</summary>
<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">@media (prefers-reduced-motion: no-preference) {
  @view-transition {
    navigation: auto;
    types: circular-wipe;
  }
}

html:active-view-transition-type(circular-wipe)::view-transition-old(root) {
  animation: circle-wipe-out 1.4s ease forwards;
}

html:active-view-transition-type(circular-wipe)::view-transition-new(root) {
  animation: circle-wipe-in 1.4s ease forwards;
}

@keyframes circle-wipe-out {
  to {
    clip-path: circle(0% at 50% 50%);
  }
}

@keyframes circle-wipe-in {
  from {
    clip-path: circle(0% at 50% 50%);
  }
  to {
    clip-path: circle(150% at 50% 50%);
  }
}</code></pre>
</details>


<h3 class="wp-block-heading" id="5-diagonal-slide">Diagonal push</h3>


<p>This one pushes out the “old” page with the “new” page from the bottom-right corner of the screen to the top-right corner — or, really, any corner we want by adjusting values.</p>



<p>For the bottom-right, I set the animation to translate to&nbsp;<code>-100%</code>&nbsp;on the X and Y axes, which pushes it away from the screen. Then it comes in from the opposite corner to its default position at&nbsp;<code>0%</code>. A little <code>opacity</code> helps smooth things out.</p>



<figure class="wp-block-video"><video height="602" style="aspect-ratio: 1280 / 602;" width="1280" controls="" src="https://css-tricks.com/wp-content/uploads/2026/02/View-Transitions-Diagonal-Slide.mp4" playsinline=""></video></figure>



<details class="wp-block-details is-layout-flow wp-block-details-is-layout-flow" open=""><summary>Full snippet</summary>
<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">@media (prefers-reduced-motion: no-preference) {
  @view-transition {
    navigation: auto;
    types: diagonal-push;
  }
}

html:active-view-transition-type(diagonal-push)::view-transition-old(root) {
  animation: diagonal-out 1.4s ease forwards;
}

html:active-view-transition-type(diagonal-push)::view-transition-new(root) {
  animation: diagonal-in 1.4s ease forwards;
}

@keyframes diagonal-out {
  to {
    transform: translate(-100%, -100%);
    opacity: 0;
  }
}

@keyframes diagonal-in {
  from {
    transform: translate(100%, 100%);
    opacity: 0;
  }
}</code></pre>
</details>


<h3 class="wp-block-heading" id="6-curtain-reveal">Curtain reveal</h3>


<p>This one’s like the a curtain is closing on the “old” page and opens up with the “new” page. It’s another one where the&nbsp;<code>inset()</code>&nbsp;function comes into play. We define rectangles placed 50% at the right and left. This increases to 50% when the page is going out and reduces to 0 when the page is coming in, making the image appear from the middle going to left and right like a curtain!</p>



<figure class="wp-block-video"><video height="1080" style="aspect-ratio: 1920 / 1080;" width="1920" controls="" src="https://css-tricks.com/wp-content/uploads/2026/02/View-Transitions-Curtain-Reveal-1.mp4" playsinline=""></video></figure>



<details class="wp-block-details is-layout-flow wp-block-details-is-layout-flow" open=""><summary>Full snippet</summary>
<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">@media (prefers-reduced-motion: no-preference) {
  @view-transition {
    navigation: auto;
    types: curtain;
  }
}

html:active-view-transition-type(curtain)::view-transition-old(root) {
  animation: curtain-out 1.4s ease forwards;
}

html:active-view-transition-type(curtain)::view-transition-new(root) {
  animation: curtain-in 1.4s ease forwards;
}

@keyframes curtain-out {
  from {
    clip-path: inset(0 0 0 0);
  }
}

@keyframes curtain-in {
  from {
    clip-path: inset(0 50% 0 50%);
  }
  to {
    clip-path: inset(0 0 0 0);
  }
}</code></pre>
</details>


<h3 class="wp-block-heading" id="7-flip-3d">3D flip</h3>


<p>We’re sort of faking one page “flipping” out like a two-sided card while the next page flips in, both along the Z axis.</p>



<figure class="wp-block-video"><video height="1080" style="aspect-ratio: 1920 / 1080;" width="1920" controls="" src="https://css-tricks.com/wp-content/uploads/2026/02/View-Transitions-Flip-3D-1.mp4" playsinline=""></video></figure>



<details class="wp-block-details is-layout-flow wp-block-details-is-layout-flow" open=""><summary>Full snippet</summary>
<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">@media (prefers-reduced-motion: no-preference) {
  @view-transition {
    navigation: auto;
    types: flip-3d;
  }
}

html:active-view-transition-type(flip-3d)::view-transition-old(root) {
  animation: flip-out 1.4s ease forwards;
}

html:active-view-transition-type(flip-3d)::view-transition-new(root) {
  animation: flip-in 1.4s ease forwards;
}

@keyframes flip-out {
  0% {
    transform: rotateY(0deg) translateX(0vw);
  }
  100% {
    transform: rotateY(-90deg) translateX(-100vw);
    opacity: 1;
  }
}

@keyframes flip-in {
  0% {
    transform: rotateY(90deg) translateX(100vw);
  }
  100% {
    transform: rotateY(0deg) translateX(0vw);
  }
}</code></pre>
</details>


<h3 class="wp-block-heading" id="what-about-your-technique-">Any cool recipes you want to share?</h3>


<p>I would <em>love</em> to see more examples and ideas if you have them! Bramus (or Brandi, as I call him) took the time to <a href="https://page-transitions.style/spa/" rel="noopener">create a bunch of view transition examples</a> in an interactive demo that are definitely worth looking at.</p>
<hr>
<p><small><a rel="nofollow" href="https://css-tricks.com/7-view-transitions-recipes-to-try/">7 View Transitions Recipes to Try</a> originally handwritten and published with love on <a rel="nofollow" href="https://css-tricks.com">CSS-Tricks</a>. You should really <a href="https://css-tricks.com/newsletters/">get the newsletter</a> as well.</small></p><small>
</small>]]></description>
      <pubDate>Mon, 13 Apr 2026 14:14:43 +0000</pubDate>
      <link>https://css-tricks.com/7-view-transitions-recipes-to-try/</link>
      <dc:creator>CSS-Tricks</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5190961866</guid>
    </item>
    <item>
      <title><![CDATA[The taxonomy is loose]]></title>
      <description><![CDATA[<p>There’s something good going on with music these days. Romance by <a href="https://fontainesdc.com/">Fontaines D.C.</a> is a perfect album. I have listened to it almost every single day for weeks and have not gotten sick of it once,  which is – and I mean this clinically – a medical miracle. Jack White is on SNL. The Strokes are touring. There is a band called <a href="https://www.youtube.com/watch?v=0Ssi-9wS1so">Angine de poitrine</a> who are the most aggressively anti-AI band I can imagine existing, out here doing absolutely Weird Shit™, and I don’t even normally listen to math rock like this but I am so glad someone is making it. My current theory (slash hopeful prayer) is that the best thing we are going to get out of all this AI slop era is the good bands dusting themselves off and getting back in the van. We have the robots writing our emails and apparently that is enough. That is exactly the kind of low-grade existential dread that makes people pick up a guitar again, and honestly good, great, more of this please.</p>

<hr>

<p>For a laugh I ran some of my pre-2020 blog posts (safely written in the pre-ChatGPT era) through an AI detector and it came back telling me they were between 59% and 93% AI-generated. I am now in a full spiral that I just naturally write like a language model that has been asked to sound like a person. I’ve been a robot this whole time. The robots didn’t take my job. I was always the robot.</p>

<hr>
<p>One of my absolute favourite pieces of trivia, which I keep locked and loaded in my back pocket, is that we only have good blueberries because of USAID (rip). The full thing is a <a href="https://www.npr.org/2025/04/04/1242780124/blueberries-asparagus-cocaine-foreign-aid-usaid-war-on-drugs-peru-free-trade">Planet Money episode</a> which you should listen to but the tl; dr is: blueberries used to be bad and seasonal and therefore pointless, Peru was growing coca leaves, America was doing its whole thing about that, and USAID showed up (with what I can only describe as the most optimistic foreign policy proposal in recorded history) and said what if instead of cocaine you grew asparagus. The farmers loved asparagus so much they immediately destroyed the asparagus market by growing too much asparagus, so this one Peruvian dude had a look at what else could grow in sandy Peruvian soil and the answer was blueberries and that’s it, that’s the whole thing. Bingo bango, you can now eat blueberries in January like a queen. The war on drugs gave us bloobs.</p>

<hr>

<p>Saved from last week’s update in what I am choosing to call “planning”: <a href="https://high.org/collection/face-jug-7/">face jugs</a> at the Atlanta High museum. Face jugs are jugs (or jars, the taxonomy is loose) from the 1800s American South, and they have faces on them, and the reason they have faces on them is to scare children away from the moonshine inside. Or spices. Or genuinely anything else you care about; children get their grubby little hands in everything. I found this especially delightful because I took a portrait sculpture class last year and since then have been <a href="https://www.instagram.com/p/DVwTfX0D8QF/">putting faces</a> on pottery, and it turns out once again that nothing is new. Nothing has ever been new.</p>
]]></description>
      <pubDate>Mon, 13 Apr 2026 00:00:00 +0000</pubDate>
      <link>https://meowni.ca/posts/taxonomy-is-loose/</link>
      <dc:creator>Monica Dinculescu</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5191522834</guid>
    </item>
    <item>
      <title><![CDATA[No-stack web development]]></title>
      <description><![CDATA[<p>This year I’ve been asked more than ever before what web development “stack” I use. I always respond: none. We shouldn’t have a go-to stack! Let me explain why.</p><h2 id="what-stack">What stack?</h2><p>My understanding is that a “stack” is a choice of software used to build a website. That includes language and tooling, <a href="https://dbushell.com/2025/02/06/frameworks-vs-libraries/">libraries and frameworks</a>, and heaven forbid: subscription services. Text editors aren’t always considered part of the stack but integration is a major factor.</p><p>Web dev stacks often manifest as <code>package.json</code> used to install hundreds of megs of JavaScript, <em>Blazing Fast</em>™ Rust binaries, and never ending <a href="https://www.stepsecurity.io/blog/axios-compromised-on-npm-malicious-versions-drop-remote-access-trojan" rel="noopener noreferrer" target="_blank">supply chain attacks</a>.</p><figure class="Image"><video autoplay="" controls="" loop="" muted="" playsinline="" preload="metadata" width="380" height="570" aria-label="hundreds of biome processes in the macOS activity monitor" poster="https://dbushell.com/images/blog/2025/biome-process.avif">    <source src="https://dbushell.com/images/blog/2025/biome-process.mp4" type="video/mp4">  </video><figcaption>My <a href="https://dbushell.com/notes/2025-07-17T12:36Z/">Biome stack</a> was a tall one.</figcaption></figure><p>A stack is also technical debt, non-transferable knowledge, accelerated obsolescence, and vendor lock-in. That means fragility and overall unnecessary complication. Popular stacks inevitably turn into <a href="https://dbushell.com/2025/10/23/react-regulation/">cargo cults</a> that build in spite of the web, not for it.</p><p>Let’s break that down.</p><h3 id="technical-debt">Technical debt</h3><p>If you have a go-to stack, you’ve prescribed a solution before you’ve diagnosed a problem. You’ve automatically opted in to technical baggage that you must carry the entire project. Project doesn’t fit the stack? Tough; shoehorn it to fit.</p><h3 id="non-transferable-knowledge">Non-transferable knowledge</h3><p>Stacks are opinionated by design. To facilitate their opinions, they abstract away from web fundamentals. It takes all of five minutes for a tech-savvy person to learn <glossary-term id="--term-json"><a href="https://www.json.org/" rel="noopener noreferrer" target="_blank">JSON</a></glossary-term>. It takes far, far longer to learn <a href="https://webpack.js.org/configuration/" rel="noopener noreferrer" target="_blank">Webpack JSON</a>. The latter becomes useless knowledge once you’ve moved on to better things. Brain space is expensive. Other standards like CSS are never truly mastered but learning an abstraction like Tailwind will severely limit your understanding.</p><h3 id="accelerated-obsolescence">Accelerated obsolescence</h3><p>Stacks are a collection of <em>move-fast-and-break</em> churnware; fleeting software that updates with incompatible changes, or deprecates entirely in favour of yet another Rust refactor. A basic HTML document written 20 years ago remains compatible today. A codebase built upon a stack 20 months ago might refuse to play.</p><h3 id="vendor-lock-in">Vendor lock-in</h3><p>The cost of re-stacking is usually unbearable. <em>Stack-as-a-service</em> is the endgame where websites become hopelessly trapped. Now you’re paying for a service that <a href="https://dbushell.com/2025/06/13/your-framework-is-showing-nextjs-error/">can’t fix errors</a>. You’ve sacrificed long-term stability and freedom for “developer experience”.</p><h2 id="when-stack">When stack?</h2><p>I’m not saying you should code artisanal organic free-range websites. I’m saying be aware of the <strong>true costs</strong> associated with a stack. Don’t prescribe a solution before you’ve diagnosed a problem. Choose the right tool for each job only once the impact is known. Satisfy specific goals of the website, not temporary development goals.</p><p>Don’t ask a developer what their stack is without asking what problem they’re solving. Be wary of those who promote or mandate a default stack. Be doubtful of those <em>selling</em> a stack. When you develop for a stack, you risk trading the stability of the open web platform, that is to say: decades of broad backwards compatibility, for GitHub’s flavour of the month.</p><p>The web platform does not require build toolchains. Always default to, and regress to, the fundamentals of CSS, HTML, and JavaScript. Those core standards are the web stack. Yes, you’ll probably benefits from more tools. Choose them wisely. Good tools are intuitive by being based on standards, they can be introduced and replaced with minimal pain.</p><p>My only absolute advice: do not continue legacy frameworks like <glossary-term id="--term-react"><a href="https://jsx.lol/" rel="noopener noreferrer" target="_blank">React</a></glossary-term>. If that triggers an emotional reaction: you need a stack intervention! It may be difficult to accept but Facebook never was your stack; it’s time to move on.</p><p>Use the tool, don’t become the tool.</p><hr><p><strong>Edit:</strong> forgot to say: for personal projects, the gloves are off. Go nuts! <a href="https://dbushell.com/2025/05/11/the-static-site-churns/">Be the churn.</a> Learn new tools and even code your own stack. If you’re the sole maintainer the freedom to make your own mistakes can be a learning exercise in itself.</p>
<hr>
<p>
Thanks for reading! Follow me on <a href="https://dbushell.com/mastodon/">Mastodon</a> and <a href="https://dbushell.com/bluesky/">Bluesky</a>.
Subscribe to my <a href="https://dbushell.com/rss.xml">Blog</a> and <a href="https://dbushell.com/notes/rss.xml">Notes</a> or <a href="https://dbushell.com/merge/rss.xml">Combined</a> feeds.
</p>
]]></description>
      <pubDate>Fri, 10 Apr 2026 05:03:48 +0000</pubDate>
      <link>https://dbushell.com/2026/04/10/no-stack-web-development/</link>
      <dc:creator>dbushell.com (blog)</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5187420257</guid>
    </item>
    <item>
      <title><![CDATA[One Developer, Two Dozen Agents, Zero Alignment]]></title>
      <description><![CDATA[Why we need collaborative AI engineering and a tour of Ace: the multiplayer coding workspace]]></description>
      <pubDate>Mon, 13 Apr 2026 00:00:00 +0000</pubDate>
      <link>https://maggieappleton.com/zero-alignment/</link>
      <dc:creator>Maggie Appleton</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5192130364</guid>
    </item>
    <item>
      <title><![CDATA[box-shadow is no alternative to outline]]></title>
      <description><![CDATA[
                                <p>People like to use the <code>box-shadow</code> property for styling focus outlines because it gives them more flexibility.</p><pre><code class="language-css">button:focus-visible {
  outline: none;
  box-shadow: 0 0 0px 4px blue, 0 0 0px 8px red, 0 0 0 12px blue;
}</code></pre>
<style>
button.demobad {
  outline: none;
  box-shadow: 0 0 0px 4px blue, 0 0 0px 8px red, 0 0 0 12px blue;
  border: none;
  font-size: 2rem;
}
button.demogood {
  box-shadow: 0 0 0px 4px blue, 0 0 0px 8px red, 0 0 0 12px blue;
  border: none;
  font-size: 2rem;
  outline: 2px solid transparent;
  outline-offset: 2px;
}
</style>
<div data-sample="demo">
<p>
  <button class="demobad">
    A focused button
  </button>
</p>
</div>
<p>That's okay in browsers' default color mode, but in <a href="https://www.w3.org/TR/css-color-adjust-1/#forced">forced colors mode</a>, it's problematic because the <a href="https://www.w3.org/TR/css-color-adjust-1/#forced-colors-properties"><code>box-shadow</code> property computes to <code>none</code></a>, which means that there are no shadows and consequently no focus styles in this mode. You can confirm that by opening <a href="https://es-d-9279738920260417-019d90fa-faad-7655-b9a5-8d29a97d44dc.codepen.dev/">this page</a> in a Chromium-based browser, switching to the <i>Rendering</i> tab in DevTools, and setting forced-colors to active.</p>
<img alt="The button is visible in forced colors mode but no box-shadow" loading="lazy" src="https://www.matuzo.at/media/pages/blog/2026/box-shadow-no-alternative-to-outline/16c0fb8c73-1776278476/chromium.avif">
<p>There are two solutions to that problem. Either don't try to be fancy and simply use an outline, or, if you think the user experience will be better with a box shadow, don't set <code>outline: none;</code> instead, set <code>outline: 2px solid transparent</code>. </p>
<pre><code class="language-css">button:focus-visible {
  outline: 2px solid transparent;
  outline-offset: 2px;
  box-shadow: 0 0 0px 1px blue, 0 0 0px 2px white, 0 0 0 4px blue;
}</code></pre>
<div data-sample="demo">
<p>
  <button class="demogood">
    A focused button
  </button>
</p>
</div>
<p>The outline will be transparent in the regular mode but visible in forced colors mode.  That works because in forced colors mode, colors on the page are constrained to a restricted, user-chosen palette. For <code>transparent</code>, which is a CSS color, this means it becomes visible.</p><p>My blog doesn't support comments yet, but you can reply via <a href="mailto:blog@matuzo.at?subject=Comment+on+%E2%80%9Cbox-shadow+is+no+alternative+to+outline%E2%80%9D">blog@matuzo.at</a>.</p>        ]]></description>
      <pubDate>Wed, 15 Apr 2026 12:30:12 +0000</pubDate>
      <link>https://www.matuzo.at/blog/2026/box-shadow-no-alternative-to-outline</link>
      <dc:creator>Manuel Matuzović - Blog</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5193389106</guid>
    </item>
    <item>
      <title><![CDATA[Chrome, Safari, and battery myths]]></title>
      <description><![CDATA[<p>On <a href="https://youtu.be/RYok1AdumBU?ref=birchtree.me" rel="noreferrer">this week's Waveform podcast</a>, Marques Brownlee and crew were talking about browsers and a common topic came up:</p><blockquote>Safari is only good for one thing, which is battery life.</blockquote><p>And:</p><blockquote>Commonly accepted facts.<br><br>Accepted by who? Lunatics?<br><br>I would say ask any browser enthusiast.</blockquote><p>And:</p><blockquote>I'm on an Apple Silicon laptop. Battery life is going to be legit no matter what or where I'm going.<br><br>No, no, it's not. That's not true.<br><br>Bro, I'm a super turbo power user. I can at least get like four hours of battery life minimum.<br><br>The difference between Chrome and Safari in regular browsing for several hours is shockingly high. It's huge.<br><br>Really?<br><br>It's dramatic. This laptop is terrifyingly long-lasting battery on Safari, is average at best with Chrome.</blockquote><p><a href="https://birchtree.me/blog/everyone-says-chrome-devastates-mac-battery-life-but-does-it-i-tested-for-36-hours-to-find-out/">I happened to test exactly this in 2024</a>, and I took great care to document my process and findings. Here's what I found:</p><blockquote>In my 3-hour tests, Safari consumed 18.67% of my battery each time on average, and Chrome averaged 17.33% battery drain.&nbsp;<strong>That works out to about 9% less battery drain from Chrome than Safari.</strong>&nbsp;Yes, you read that right, I found Chrome was easier on my battery than Safari.</blockquote><p>Yeah, I found that in controlled tests where I did the exact same things in both browsers for hours at a time (across multiple test runs), and found that they were very close to each other, with Chrome using slightly less battery. As Marques put it, it sounds like Chrome halves his Mac's battery life. As someone who uses Chrome for work and spends all day in it doing work in Atlassian apps and video calls, I can confidently say it easily makes it through an 8-9 hour work day on battery. This is true of my M5 Pro and it was also true of my M1.</p><p>I asked for any sort of data from the past decade people had to back up the idea that Chrome was horrible on battery and Safari was a saint, and I got nothing sent my way in 2024. If you have it now, I'd love to see it! <a href="https://mastodon.social/@matt_birchler?ref=birchtree.me">Hit me up on Mastodon</a> if you've got it!</p><p>Until I get any sort of compelling data, I'm going to continue thinking that people who think this are just like people who think that religiously force closing your iPhone apps improves your phone's speed and battery. It's a shared hallucination that <em>feels</em> like it's true, even though it's not. Considering many Safari users can't seem to believe that Chrome is a faster browser than Safari these days either, despite effectively every benchmark showing this, I suspect the feelings will continue.</p>]]></description>
      <pubDate>Tue, 14 Apr 2026 22:15:54 +0000</pubDate>
      <link>https://birchtree.me/blog/chrome-safari-and-battery-myths/</link>
      <dc:creator>Birchtree</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5192766433</guid>
    </item>
    <item>
      <title><![CDATA[This looks interesting:  Quiche is a highly customizable...]]></title>
      <description><![CDATA[
         	<p>This looks interesting: <a href="https://quiche.industries/browser/">Quiche is a highly customizable but simple browser for iOS</a>.
</p>
 

        
        
        
    ]]></description>
      <pubDate>Tue, 14 Apr 2026 19:47:59 +0000</pubDate>
      <link>https://kottke.org/26/04/0048684-this-looks-interesting-qu</link>
      <dc:creator>kottke.org</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5192624270</guid>
    </item>
    <item>
      <title><![CDATA[Design and Engineering, As One · Matthias Ott]]></title>
      <description><![CDATA[

<p>A thoughtful piece by Matthias that’s a must-read for both designers and developers.</p>

<p><a href="https://adactio.com/links/22527">adactio.com/links/22527</a></p>

            ]]></description>
      <pubDate>Tue, 14 Apr 2026 17:36:16 +0000</pubDate>
      <link>https://matthiasott.com/articles/design-and-engineering-as-one</link>
      <dc:creator>Adactio: Links</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5192485002</guid>
    </item>
    <item>
      <title><![CDATA[So, you want a React modal that uses the <dialog> element and transitions in AND out?</dialog>]]></title>
      <description><![CDATA[I had to make a modal recently, it happened to be in React, so some of what follows is React specific, but the principles are all good ol' CSS/HTML and JavaScript, so don't necassarily go running just because I'm using React here…. wait, where are you going? Come back… Oh well, just you left I […]]]></description>
      <pubDate>Tue, 14 Apr 2026 14:47:00 +0000</pubDate>
      <link>https://benfrain.com/a-react-modal-that-uses-the-dialog-element-and-transitions-in-and-out/</link>
      <dc:creator>Ben Frain</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5192303724</guid>
    </item>
    <item>
      <title><![CDATA[MacOS Tip: Enable the Zoom ‘Peek’ Gesture]]></title>
      <description><![CDATA[
<p>Marcin Wichary, at Unsung:</p>

<blockquote>
  <p>Go to Settings &gt; Accessibility &gt; Zoom, and then turn on “Use
scroll gesture with modifier keys to zoom.”</p>

<p>Then, at any moment, you can hold Control and swipe with two
fingers (or use a scroll wheel) up or down to zoom the
entire screen.</p>

<p>I’d also recommend turning off “Smooth images” under “Advanced…”
so you see individual pixels better.</p>
</blockquote>

<p>This is one of the very best MacOS tips. No third-party software. Built into MacOS for several (many?) years now. Incredibly useful.</p>

<p>But I had no idea it existed until last June at WWDC. It was Monday, after the morning keynote and just before the afternoon State of the Union. <a href="https://glass.photo/gruber/series/2skCCKMH4LYm8HAuxeoMHo-wwdc-2025-apple-park">Beautiful sunny day</a> at Apple Park. I ran into my <a href="https://daringfireball.net/search/cabel+sasser">old friend</a> <a href="https://cabel.com/">Cabel Sasser</a>, and, maniac that he is, he’d already installed the first Tahoe developer beta on his MacBook Pro. So I sat next to him and we started examining the UI changes in detail. And Cabel was zooming in and out instantaneously. I was like, “Whoa, how are you doing that?” And Cabel was like, “You don’t know about the Accessibility Zoom gesture? Here, let me show you!” And my mind was blown. Cabel emphasized the importance of going into the “Advanced…” dialog to turn off “Smooth images”, and I agree. This is a fantastic feature, but Apple has the default setting wrong for smoothing (a.k.a. blurring) the zoomed image. I honestly can’t imagine why anyone would want the zoomed image blurred.</p>

<p>Anyway, then we both laughed ourselves silly and made ourselves a little queasy examining, in detail, just how bad the Tahoe UI was. And I thought to myself, <em>I need to post this as a tip on Daring Fireball.</em></p>

<p>Well, it took 10 months, but Wichary posting it on Unsung reminded me that I never posted about it here. [<strong>Update:</strong> <a href="https://daringfireball.net/linked/2026/04/13/memory-they-say">Actually...</a>] Turn this on, start using it, and you’ll find yourself using it every day if you care about design details.</p>

<p><strong>Bonus tip:</strong> Subscribe to Wichary’s <a href="https://unsung.aresluna.org/">Unsung</a> in your RSS reader. He’s only been posting there since early December, and it quickly became one of my favorite blogs in the world. One of those blogs where I’m excited every time I see there’s a new post. I would read a post from Wichary describing what it’s like to watch paint dry, because I know he’d only write about it if he noticed something interesting and nuanced. Because he’s only been writing Unsung for a few months, you can catch up on the whole thing.</p>

<div>
<a title="Permanent link to ‘MacOS Tip: Enable the Zoom ‘Peek’ Gesture’" href="https://daringfireball.net/linked/2026/04/13/macos-zoom-gesture">&nbsp;★&nbsp;</a>
</div>

	]]></description>
      <pubDate>Mon, 13 Apr 2026 17:43:16 +0000</pubDate>
      <link>https://unsung.aresluna.org/testing-tip-enable-the-zoom-peek-gesture/</link>
      <dc:creator>Daring Fireball</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5191199975</guid>
    </item>
    <item>
      <title><![CDATA[Why AI Sucks At Front End]]></title>
      <description><![CDATA[
          <img style="display: none" src="https://nerdy.dev/media/why-ai-sucks-at-front-end.jpg" alt="The left panel is titled AI - MASTER OF ENGINE &amp; CREATION and features a sophisticated, human-like android robot with detailed internal circuits and a glowing brain. Floating text around the robot says - REWRITING GAME ENGINES with a small video screen, GENERATING REALISTIC VIDEO/IMAGES with code on a screen, and CONFIDENCE - 100%. The right panel is titled AI - FRONT-END CSS FAIL and shows a cluttered desk with a distressed small robot next to a frustrated human man pointing aggressively at a computer screen. The computer displays a chaotic webpage full of broken layout elements and a giant black void called a gaping hole. The small robot has a speech bubble that says - I'M DONE! THE FEATURE IS COMPLETE. Floating text phrases on the right side are - BESPOKE INTERACTION NIGHTMARE, CSS IS HARD, YOU'RE ABSOLUTELY RIGHT, CAN'T RENDER, LLM CAN'T MATH. The human has a mug with </> and nerdy.dev written on it." height="674" width="1920">
        <p>AI is a sycophantic dev wannabe that skimmed a shitload of tutorials. You get the results of a probabilistic guess based on patterns it saw during training. What did it train on? Ancient solutions, unoriginal UI patterns, and watered down junk.</p>
<p>I'm about to rant about how this is both useful and lame. </p>

        <h2>
          The Good
          <a name="the-good" href="https://nerdy.dev/why-ai-sucks-at-front-end?utm_source=rss#the-good">#</a>
        </h2>
       <p><strong>AI loves the boring stuff. It thrives on mediocrity.</strong></p>
<p>If you want some gloriously unoriginal UI, it has your back 😜</p>
<ul>
<li><strong>Scaffolding:</strong> Generic regurgitation of patterns it's seen, done.</li>
<li><strong>Tokens:</strong> Migrating tokens or mapping them out? It eats this tedious garbage for breakfast. </li>
<li><strong>Outlining features:</strong> Generic lists ✅</li>
<li><strong>Lying to your face:</strong> Confident hot garbage on a silver platter. It'll hand you a snippet, dust off its digital hands, and tell you it finished the work. <em>It did not finish the work.</em></li>
</ul>
<p>Aka: If it's a well-worn pattern, AI is there to help you copy-paste faster. Which, for a lot of programming, is totally the case. I'm genuinely finding a lot of helpful stuff in this department.</p>

        <h2>
          The Bad
          <a name="the-bad" href="https://nerdy.dev/why-ai-sucks-at-front-end?utm_source=rss#the-bad">#</a>
        </h2>
       <p><strong>Pixel perfection &amp; bespoke solutions… what are those?</strong></p>
<p>The exact second you step off the paved road of unoriginality, it faceplants. </p>
<ul>
<li><strong>Bespoke solutions &amp; custom interactions:</strong> Try asking it for some scroll-driven animations or custom micro-interactions. It will invent a CSS syntax that hasn't existed since IE6. </li>
<li><strong>Layout &amp; Spacing:</strong> Predicting intrinsic/extrinsic page properties? It's already bad at math, how could it get this rediculously dynamic calculation correct. Spacing? Ha, seems reasonably to expect symmetry, but it's terrible at the math.</li>
<li><strong>Combined states:</strong> Pinpointing where to edit a complex component state makes it cry. </li>
<li><strong>Accessibility:</strong> It throws <code>aria-hidden="true"</code> at a wall and hopes it sticks. </li>
<li><strong>Performance:</strong> It will give you the heaviest, jankiest solution unless you explicitly ask it to be for a specific (apparently "indie") performance solution. </li>
<li><strong>Tests:</strong> Writing good tests? Good, no. A lot, yes.</li>
</ul>
<p>And the absolute best part? The more complex the component gets, the slower and dumber the front-end help becomes. Incredible how it can one shot a totally decent front-end design or component, than choke on a follow up request. Speaks to what it's good at.</p>

        <h2>
          Why?
          <a name="why?" href="https://nerdy.dev/why-ai-sucks-at-front-end?utm_source=rss#why?">#</a>
        </h2>
       
        <h3>
          1. It trained on ancient garbage
          <a name="1.-it-trained-on-ancient-garbage" href="https://nerdy.dev/why-ai-sucks-at-front-end?utm_source=rss#1.-it-trained-on-ancient-garbage">#</a>
        </h3>
       <p><strong>It lacks modern training data.</strong></p>
<p>It has an excessive reliance on standard templates because that's what the internet is full of. Modern CSS? It's barely aware of it. </p>

        <h3>
          2. It literally cannot see
          <a name="2.-it-literally-cannot-see" href="https://nerdy.dev/why-ai-sucks-at-front-end?utm_source=rss#2.-it-literally-cannot-see">#</a>
        </h3>
       <p><strong>It's an LLM, not a rendering engine!</strong></p>
<p>It's notoriously bad at math, and throwing screenshots at it means very little. It's stabbing in the dark. </p>
<p>This leads to the classic UI interaction:  </p>
<p><strong>AI:</strong> <em>"I'm done! Here is your perfectly crafted UI."</em><br><strong>Me:</strong> <em>"There's a gaping hole where the icon should be, fix the missing icon."</em><br><strong>AI:</strong> <em>"You're absolutely right. Let me fix that for you."</em></p>

        <h3>
          3. It does not know WHY we do things
          <a name="3.-it-does-not-know-why-we-do-things" href="https://nerdy.dev/why-ai-sucks-at-front-end?utm_source=rss#3.-it-does-not-know-why-we-do-things">#</a>
        </h3>
       <p><strong>It doesn't understand the "why" behind our architectural decisions.</strong></p>
<p><a href="https://github.com/github/spec-kit/blob/main/spec-driven.md">SDD</a>, <a href="https://cucumber.io/docs/bdd/">BDD</a>, or <a href="https://stately.ai/docs/machines">state machines</a> might help guide it, but the models weren't exactly trained on those paired with <em>stellar</em> solutions. </p>
<p>We're asking a giant text-predictor to make new connections on the fly. We can get it there, but there's so much to consider we have to spell it out before it starts making the connections we want.</p>

        <h3>
          4. Zero environmental control
          <a name="4.-zero-environmental-control" href="https://nerdy.dev/why-ai-sucks-at-front-end?utm_source=rss#4.-zero-environmental-control">#</a>
        </h3>
       <p><strong>It doesn't control where the code lives.</strong></p>
<p>It can write annoyingly amazing Rust, TypeScript or Python, but those have the distinct advantage of a predictable (pinnable!!! like v14.4) environment the code executes in. </p>
<p>That's not how HTML or CSS work, there is no pinning the browser type, browser window size, browser version, the users input type (keyboard, mouse, touch, voice), their user preferences, etc. That's complex end environment shit.</p>
<p>The list goes on too, for scenarios, contexts and variables the rendering engine juggles before resolving the final output. The LLM doesn't control these, so it ignores them until you make them relevant.</p>
<p>Even <a href="https://nerdy.dev/prompt-in-logical-properties">prompting in logical properties</a>, you have to ask for this kind of CSS. These should be CSS tablestakes output from LLMs, but it's not. And even when you ask for it, or provide documentation that spells it out, it's not guaranteed to work. </p>
<p>The place where HTML and CSS have to render is chaotic. It's a browser, with a million different versions, a million different ways to render, a million different ways to interact with it, and a million different ways to break it. </p>
<p><strong>It's a moving target, and LLMs are terrible at moving targets.</strong></p>

        <h2>
          Damnit humans
          <a name="damnit-humans" href="https://nerdy.dev/why-ai-sucks-at-front-end?utm_source=rss#damnit-humans">#</a>
        </h2>
       <p><strong>We're a LLM combinatorial explosion.</strong></p>
<p>We're wildly unpredictable targets. We change our minds, we switch viewports, we change theme preferences, we changes devices, we change browsers, we change browser versions, we switch inputs, we change our everything. </p>
<p>We're not a static target. We're not a pattern that can be learned. </p>
<p>There is a "human mainstream" of behaviors, preferences, and expectations where LLMs can be genuinely helpful; but our "full potential" matrix will be exploding LLM output patterns for a long time to come. IMO at least.</p>
<p><small>unless we <a href="https://en.wikipedia.org/wiki/Borg">Borg</a>.</small></p>
]]></description>
      <pubDate>Sun, 12 Apr 2026 05:48:57 +0000</pubDate>
      <link>https://nerdy.dev/why-ai-sucks-at-front-end?utm_source=rss</link>
      <dc:creator>Adam Argyle</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5189570052</guid>
    </item>
    <item>
      <title><![CDATA[Oh wow,  this is a trippy game inspired by MC Escher . My...]]></title>
      <description><![CDATA[
         	<p>Oh wow, <a href="https://managore.itch.io/print-gallery-of-an-artist">this is a trippy game inspired by MC Escher</a>. My brain may be permanently broken by this.
</p>
 

        
        
        
    ]]></description>
      <pubDate>Fri, 10 Apr 2026 18:15:09 +0000</pubDate>
      <link>https://kottke.org/26/04/0048715-oh-wow-this-is-a</link>
      <dc:creator>kottke.org</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5188266199</guid>
    </item>
    <item>
      <title><![CDATA[Multi-stroke text effect in CSS]]></title>
      <description><![CDATA[<article>
  <p>
    I used to see that retro multi-stroke text effect quite often and
    tried to replicate it using the CSS <code>text-stroke</code> property,
    but the results never quite matched.
    Because <code>text-stroke</code> accepts a single value, stacking elements was the only workaround I could think of, though it didn't seem to work.
  </p>
  <!--more-->
  <p>
    One evening late last year,
    I was eager to give it another shot after seeing the text effect again in the book <a href="https://archive.org/details/graphicjapanfrom0000avel">Graphic Japan : from woodblock and zen to manga and kawaii</a>.
  </p>
  <figure>
    <img alt="pattern from book Graphic Japan : from woodblock and zen to manga and kawaii" src="https://yuanchuan.dev/assets/images/post/multiple-text-stroke/from-book.webp">
    <figcaption><span>Text stroke effect from the book</span></figcaption>
  </figure>

  <p>
    I kept stacking several elements and accidentally varied the <code>text-stroke-width</code> for each layer.
    To my suprise, the result was getting closer this time.
  </p>

  <div class="example">
    <css-doodle><style>
      --c: #cc0d55;
      --n: @i(-1);
      @grid: 36x1 / 240px;
      @content: '✱';

      position: absolute;
      inset: 0;
      font: 100px/0 sans-serif;
      color: var(--c);
      will-change: transform;
      z-index: @I(-@i);

      -webkit-text-stroke-color: @pn(--c, #f4e1e8);
      -webkit-text-stroke-width: $em(.08n+.02(1-(-1)^n));
    </style></css-doodle>

<figure class="highlight"><pre><code class="language-css" data-lang="css"><span class="nt">--c</span><span class="o">:</span> <span class="nf">#cc0d55</span><span class="o">;</span>
<span class="nt">--n</span><span class="o">:</span> <span class="k">@i</span><span class="p">(</span><span class="m">-1</span><span class="p">);</span>

<span class="k">@grid</span><span class="p">:</span> <span class="m">36</span><span class="n">x1</span> <span class="p">/</span> <span class="m">240px</span><span class="p">;</span>
<span class="k">@content</span><span class="p">:</span> <span class="s2">'✱'</span><span class="p">;</span>

<span class="nt">position</span><span class="o">:</span> <span class="nt">absolute</span><span class="o">;</span>
<span class="nt">inset</span><span class="o">:</span> <span class="err">0</span><span class="o">;</span>
<span class="nt">font</span><span class="o">:</span> <span class="err">100</span><span class="nt">px</span><span class="o">/</span><span class="err">0</span> <span class="nt">sans-serif</span><span class="o">;</span>
<span class="nt">color</span><span class="o">:</span> <span class="nt">var</span><span class="o">(</span><span class="nt">--c</span><span class="o">);</span>
<span class="nt">z-index</span><span class="o">:</span> <span class="k">@I</span><span class="p">(</span><span class="n">-</span><span class="err">@</span><span class="n">i</span><span class="p">);</span>

<span class="nt">-webkit-text-stroke-color</span><span class="o">:</span> <span class="k">@pn</span><span class="p">(</span><span class="n">--c</span><span class="p">,</span> <span class="m">#f4e1e8</span><span class="p">);</span>
<span class="nt">-webkit-text-stroke-width</span><span class="o">:</span> <span class="err">$</span><span class="nt">em</span><span class="o">(.</span><span class="err">08</span><span class="nt">n</span><span class="o">+.</span><span class="err">02</span><span class="o">(</span><span class="err">1</span><span class="nt">-</span><span class="o">(</span><span class="nt">-1</span><span class="o">)^</span><span class="nt">n</span><span class="o">));</span></code></pre></figure>
  </div>

  <h2>How it works</h2>

  <p>
    For different values of the <code>text-stroke-width</code>,
    browsers will automatically draw outlines of the charater,
    The larger you set the stroke width, the thicker the outline will get,
    while still maintain its original shape.
  </p>

  <p>
    <css-doodle>
      @grid: 7x1/ 360px auto noclip;
      @content: '✱';
      font: 50px/0 sans-serif;
      color: transparent;
      -webkit-text-stroke-color: #cc0d55;
      -webkit-text-stroke-width: @i(*1.8px);
    </css-doodle>
  </p>

  <p>
    The next step is to use different colors and put them in order.
  </p>

  <p>
    <css-doodle>
      @grid: 12x1/ 100px ß #f4e1e8;
      @content: '✱';
      position: absolute;
      inset: 0;
      z-index: @I(-@i);
      font: 50px/0 sans-serif;
      color: transparent;
      -webkit-text-stroke-color: @pn(#f4e1e8, #cc0d55);
      -webkit-text-stroke-width: @i(*3px);
    </css-doodle>
  </p>

  <p>The interesting part is how the browsers outlining the character shapes differently.
    FireFox offers more smoother rendering than in Chrome and Safari.
  </p>

  <div class="compare">
    <figure>
      <img alt="" src="https://yuanchuan.dev/assets/images/post/multiple-text-stroke/chrome.webp">
      <figcaption><span>Chrome/Safari</span></figcaption>
    </figure>

    <figure>
      <img alt="" src="https://yuanchuan.dev/assets/images/post/multiple-text-stroke/firefox.webp">
      <figcaption><span>Firefox</span></figcaption>
    </figure>
  </div>

  <p>
    Another interesting part is when there are more text put inline, the character shapes will be merged.
  </p>

  <div>

    <css-doodle class="merge"><style>
      --c: #cc0d55;
      --n: @i(-1);

      @grid: 12x1 / 600px 300px / @p(--c);
      @content: '秋收冬藏';

      position: absolute;
      inset: 0;
      font: 70px/0 sans-serif;
      white-space: nowrap;
      color: var(--c);
      z-index: @I(-@i);

      -webkit-text-stroke-color: @pn(--c, #f4e1e8);
      -webkit-text-stroke-width: @i(*4px);
    </style></css-doodle>

<figure class="highlight"><pre><code class="language-css" data-lang="css"><span class="c">/* ... */</span>
<span class="k">@content</span><span class="p">:</span> <span class="s2">'秋收冬藏'</span><span class="p">;</span></code></pre></figure>

  </div>

  <h2>Trying different fonts</h2>

  <p>
    The final result really depends on the font you choose.
    To help experimenting with different fonts more quickly,
    I added the <code>@google-font</code> function for faster font loading.
  </p>

  <div>
    <css-doodle>
      --c: #cc0d55,#fff;
      @grid: 34x1 / 320px;
      @content: 'b';
      font: 150px/0 @google-font(Matemasie);
      @place: center;
      z-index: @I(-@i);
      color: @pn(--c);
      -webkit-text-stroke-color: @pn(--c);
      -webkit-text-stroke-width: @i(-1, ease, *12px);
    </css-doodle>
<figure class="highlight"><pre><code class="language-css" data-lang="css"><span class="nt">font-family</span><span class="o">:</span> <span class="k">@google-font</span><span class="p">(</span><span class="n">Matemasie</span><span class="p">);</span>
<span class="k">@content</span><span class="p">:</span> <span class="s2">'b'</span><span class="p">;</span></code></pre></figure>

  </div>


  <div>
    <css-doodle seed="1774291354234">
      --c: #cc0d55,#fff;
      @grid: 30x1 / 320px;
      :after {
         content: 'Love';
         position: absolute;
      }
      font: 80px/0 @google-font("Pacifico");
      @place: center;
      z-index: @I(-@i);
      color: @pn(--c);

      -webkit-text-stroke-color: @pn(--c);
      -webkit-text-stroke-width: @i(ease, -1, *15px);
    </css-doodle>
<figure class="highlight"><pre><code class="language-css" data-lang="css"><span class="nt">font-family</span><span class="o">:</span> <span class="k">@google-font</span><span class="p">(</span><span class="n">Tangerine</span><span class="p">);</span>
<span class="k">@content</span><span class="p">:</span> <span class="s2">'Love'</span><span class="p">;</span></code></pre></figure>

  </div>

  <div>
    <css-doodle seed="1774291354234">
      --c: #fff9e0,#f1c550,#ff6600,#ce2525;
      --c: #cc0d55,#fff;
      @grid: 30x1 / 320px +2 ~0 -10%;
      :after {
         content: '+';
         position: absolute;
      }
      @place: 50% 50%;
      font: 120px/0 @google-font("Cherry Bomb One");
      z-index: @I(-@i);
      color: @pn(--c);

      -webkit-text-stroke-color: @pn(--c);
      -webkit-text-stroke-width: @i(ease, -1, *12px);
    </css-doodle>
<figure class="highlight"><pre><code class="language-css" data-lang="css"><span class="nt">font-family</span><span class="o">:</span> <span class="k">@google-font</span><span class="p">(</span><span class="s2">'Cherry Bomb One'</span><span class="p">);</span>
<span class="k">@content</span><span class="p">:</span> <span class="s2">'+'</span><span class="p">;</span></code></pre></figure>

  </div>

  <p>
    Unfortunately, the performance is as bad as CSS filters, especially when the font-size is getting bigger,
    you may have noticed some flicking above.
    It's fine for experiments like this, or for generating images with css-doodle,
    but it's not well-suited for production usage.
  </p>

  <h2>More examples</h2>

  <p>
    Here are two more examples to play around with different colors and characters, generated with css-doodle, just for fun.
  </p>

  <div class="example image-row">
    <img alt="" src="https://yuanchuan.dev/assets/images/post/multiple-text-stroke/poster-1.webp">
    <img alt="" src="https://yuanchuan.dev/assets/images/post/multiple-text-stroke/poster-2.webp">
  </div>

  <p>
    CodePen link for the first one: <a href="https://codepen.io/yuanchuan/pen/ogzarGo">https://codepen.io/yuanchuan/pen/ogzarGo</a>
  </p>

</article>

<style>
  .article {
    max-width: 46em;
  }
  css-doodle {
    display: block;
    max-width: 100%;
  }

  css-doodle.merge {
    width: 480px;
  }

  css-doodle:not(:defined) {
    width: 240px;
    height: 240px;
    visibility: hidden;
  }

  .compare {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 5px;
  }

  .example {
    display: flex;
    margin-bottom: 2em;
  }

  .example figure {
    margin: 0;
    order: 1;
    overflow: auto;
  }
  .example figure pre {
    margin: 0;
  }
  .example .doodle,
  .example css-doodle {
    order: 2;
    margin-left: auto;
    flex-shrink: 0;
  }

  .image-row {
    display: grid;
    gap: 5px;
    grid-template-columns: 1fr 1fr;
  }
  .image-row img {
    width: 100%;
  }

  @media screen and (max-width: 720px) {
    .example {
      display: block;
    }
    .example css-doodle {
      margin: 0 0 1em 0;
    }
    .example .doodle css-doodle {
      margin: 0;
    }
  }
</style>

<script type="module">import 'https://esm.sh/css-doodle@0.50.2'</script>
]]></description>
      <pubDate>Fri, 10 Apr 2026 16:00:00 +0000</pubDate>
      <link>https://yuanchuan.dev/multi-stroke-text-effect-in-css</link>
      <dc:creator>yuanchuan.dev</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5189492696</guid>
    </item>
    <item>
      <title><![CDATA[A compelling title that is cryptic enough to get you to take action on it]]></title>
      <description><![CDATA[<p>A bold first sentence that draws you in. A steering second sentence to set you further down the path. A third sentence that tantalizes and alludes to content to follow.</p>
<p><img role="img" alt="An intentionally generic-looking placeholder image with the text, 'An attention-grabbing hero image'. The text is centered in the image and set in a nondescript typeface." loading="lazy" src="https://ericwbailey.website/img/posts/a-compelling-title-that-is-cryptic-enough-to-get-you-to-take-action-on-it/hero-image.svg"></p>
<p>Following is an initial explanatory paragraph. It serves to help back up the previous paragraphs, and start to ground it in more applicable information. Expectations are set, and potential skepticism is addressed. A <a id="link-to-prior-art" href="https://ericwbailey.website/published/a-compelling-title-that-is-cryptic-enough-to-get-you-to-take-action-on-it/#link-to-prior-art">link to prior art</a> is supplied, to provide additional context.</p>
<p>There is then a paragraph that serves as a segue. It connects the high-level concepts and begins to draw them down the realm of the practical.</p>
<h2 id="a-subheading-to-help-segment-the-content">A subheading to help segment the content</h2>
<p>This paragraph begins to answer the questions in the reader’s mind that the segue paragraph introduced. It begins with level-setting. Certain <strong>key concepts are bolded</strong> to accommodate people who skim. Following that is supplying more context, albeit still at a relatively high level.</p>
<p>A short sentence isolated as its own paragraph to <strong>drive attention and impact</strong>.</p>
<p>The following paragraph begins to dive into particulars. It <strong>introduces a new concept</strong> related to the subsection’s topic at-hand, careful to stay focused to help the reader understand the larger goal you’re driving towards.</p>
<ul>
<li>Bulleted lists help the reader digest these particulars,</li>
<li>Break up the flow of content, and</li>
<li>Step through a process.</li>
</ul>
<p>A follow-up paragraph at the end of a subsection may allude to the author‘s opinions or larger thoughts about the topic. It also sets up the next subsection.</p>
<h2 id="another-subheading">Another subheading</h2>
<p>The next concept is addressed, <strong>getting more technical as the reader becomes more acclimated</strong>. <a id="a-link-to-a-peer-resource-is-threaded-in" href="https://ericwbailey.website/published/a-compelling-title-that-is-cryptic-enough-to-get-you-to-take-action-on-it/#a-link-to-a-peer-resource-is-threaded-in">A link to a peer resource is threaded in</a> as an appeal to authority, to help reinforce confidence in the author.</p>
<p>An ordered list is used to:</p>
<ol>
<li>Communicate a series of instructions the reader should take,</li>
<li>In which order they should be followed, and</li>
<li>Do so in a way that both makes it easy to follow and also breaks up reading flow to be more noticeable.</li>
</ol>
<p>There might then be another follow-up paragraph. This one might contain a sentence with an em dash—indicative of a trailing thought that is still topically related.</p>
<h3 id="a-deeper-subheading">A deeper subheading</h3>
<p>This section works deeper into the technical topic. It dispenses with reasoning and analogies and discusses practical specifics.</p>
<pre><code>A piece of code that translates the practical specifics into language a computer can be instructed with. Comments are supplied to help facilitate understanding.
</code></pre>
<ul>
<li>A bulleted list is used,</li>
<li>It breaks down and explains code concepts that may not be self-evident, and</li>
<li>These points may be more holistic, meaning they aren’t a good fit for inline code comments.</li>
</ul>
<h2 id="a-new-concept-is-introduced">A new concept is introduced</h2>
<p>We break out of the depth of the deeper subheading’s content and return to a level higher. This is a new concept, yet still living under the umbrella of the overall topic at-hand.</p>
<p>The format of this new concept <strong>mirrors the structure of the previous section</strong>. This predictability helps with reading flow and answering the reader’s unspoken expectations.</p>
<p><img role="img" alt="An intentionally generic-looking placeholder image with the text, 'An illustration that serves as a metaphor'. The text is centered in the image and set in a nondescript typeface." loading="lazy" src="https://ericwbailey.website/img/posts/a-compelling-title-that-is-cryptic-enough-to-get-you-to-take-action-on-it/metaphor-image.svg"></p>
<h2 id="a-subheading-that-begins-to-tie-all-the-previous-sections%E2%80%99-content-together">A subheading that begins to tie all the previous sections’ content together</h2>
<p>This subsection takes each of the previous points raised and reinforces their need to be sequentially discussed. It then <strong>explicitly confirms the case</strong> the rest of the content has been implicitly building, that this topic was worth breaking down to better appreciate as a holistic whole.</p>
<p>Some established trust is cashed in. The author is allowed some space to wax philosophical about larger implications, or discuss their feelings on the matter.</p>
<h2 id="a-conclusion">A conclusion</h2>
<p>The bold first sentence is revisited now that the reader has completed learning about the concept being discussed. A subsequent sentence explicitly ties the nuance the rest of the content discusses to the overall point.</p>
<p>The reader is thanked, and the content ends.</p>
]]></description>
      <pubDate>Thu, 09 Apr 2026 00:00:00 +0000</pubDate>
      <link>https://ericwbailey.website/published/a-compelling-title-that-is-cryptic-enough-to-get-you-to-take-action-on-it/</link>
      <dc:creator>Eric Bailey</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5186039978</guid>
    </item>
    <item>
      <title><![CDATA[Quick &amp; Easy UI Wins (for real)]]></title>
      <description><![CDATA[<img src="https://www.oddbird.net/assets/images/winging-it/winging-it-30-1600w.jpeg" alt=""><p>Join Stacy, James, and Miriam
as we explore some hidden gems
of UI development –
from <code>@starting-style</code>
for smoother entry transitions
to performance boosts with AVIF images
and using the browser’s built-in lazy-loading.
We cover a variety of quick wins
that you can use
to make your life easier
and improve the experience
for your website visitors.</p>
<div data-callout="note"><div>
<p>Based on Stacy’s <a href="https://web.dev/articles/baseline-in-action-image-gallery">web.dev article</a></p>
</div></div>
<figure class="embed">
      <div class="gallery"></div></figure>
<p class="main-action"><a href="http://www.youtube.com/channel/UCUkHxN78y9On9YH1zd-aTGw?sub_confirmation=1">Subscribe to Channel »</a></p>
<div class="anchor-link-wrapper">
<h2 id="what-we-cover%3A" tabindex="-1">What We Cover:</h2>
</div>
<ul>
<li><strong>AVIF</strong>
<ul>
<li>What makes AVIF a good choice</li>
<li>How to convert images</li>
<li>Where you can use them</li>
<li>Example image sizes after converting</li>
</ul>
</li>
<li><strong>Lazy Loading</strong></li>
<li><strong><code>@starting-style</code></strong></li>
<li><strong>Backdrop-filter</strong></li>
<li><strong>Relative colors</strong></li>
<li><strong>Text-box</strong></li>
<li><strong><code>nth-child</code> of .class</strong> <a href="https://codepen.io/editor/stacy/pen/019d06f5-09b0-739b-b4ec-3104bbe8af6f">CodePen</a></li>
</ul>
<div class="anchor-link-wrapper">
<h2 id="links%3A" tabindex="-1">Links:</h2>
</div>
<ul>
<li><a href="https://caniuse.com/avif">caniuse AVIF</a></li>
<li><a href="https://jakearchibald.com/2020/avif-has-landed/">AVIF has landed</a></li>
<li><a href="https://squoosh.app/">Squoosh</a></li>
<li><a href="https://caniuse.com/loading-lazy-attr">caniuse Lazy Loading</a></li>
<li><a href="https://caniuse.com/wf-starting-style">caniuse <code>@starting-style</code></a></li>
<li><a href="https://caniuse.com/css-backdrop-filter">caniuse CSS <code>backdrop-filter</code></a></li>
<li><a href="https://caniuse.com/css-relative-colors">caniuse CSS relative colors</a></li>
<li><a href="https://caniuse.com/css-text-box-trim">caniuse CSS <code>text-box-trim</code></a></li>
<li><a href="https://developer.chrome.com/blog/css-text-box-trim">CSS text-box-trim by Adam Argyle</a></li>
<li><a href="http://codepen.io/editor/stacy/pen/019d038f-2feb-761c-9fbd-aa3e319166ac">CodePen Quick Wins</a></li>
<li><a href="https://caniuse.com/mdn-css_properties_transition-behavior_allow-discrete">caniuse <code>transition-behavior</code></a></li>
</ul>
]]></description>
      <pubDate>Thu, 19 Mar 2026 00:00:00 +0000</pubDate>
      <link>https://www.oddbird.net/2026/03/19/winging-it-30/</link>
      <dc:creator>OddBird</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5193643702</guid>
    </item>
    <item>
      <title><![CDATA[Book review: "This Is for Everyone" by Tim Berners-Lee]]></title>
      <description><![CDATA[
<p>The World Wide Web is one of the greatest inventions of humanity. This medium gave me the opportunity to do what I do, made me passionate about it, taught me so many things and gave me so many laughs. In many contexts this saying sounds cliché, but without the internet this world really wouldn’t be what it is now.</p>
<p>I’m a huge fan of Sir Tim Berners-Lee, his values and his modest and humble aura. I ordered a physical copy of <a href="https://thisisforeveryone.timbl.com/index.html">“This is for Everyone”</a> the moment it came out, but I mostly listened to it on <a href="https://www.audible.co.uk/pd/This-Is-for-Everyone-Audiobook/B0DK42QWHT">Audible</a>, read by a great voice of Stephen Fry. The audiobook contains a great prologue read by the author, and also a (not so great) conversation between the web inventor and the narrator.</p>
<picture><source srcset="https://pawelgrzybek.com/book-review-this-is-for-everyone-by-tim-berners-lee/1.avif" type="image/avif"><source srcset="https://pawelgrzybek.com/book-review-this-is-for-everyone-by-tim-berners-lee/1.webp" type="image/webp"><img src="https://pawelgrzybek.com/book-review-this-is-for-everyone-by-tim-berners-lee/1.jpg" alt="“This Is for Everyone” by Tim Berners-Lee" loading="lazy" decoding="async" width="1499" height="1000"></picture>
<picture><source srcset="https://pawelgrzybek.com/book-review-this-is-for-everyone-by-tim-berners-lee/2.avif" type="image/avif"><source srcset="https://pawelgrzybek.com/book-review-this-is-for-everyone-by-tim-berners-lee/2.webp" type="image/webp"><img src="https://pawelgrzybek.com/book-review-this-is-for-everyone-by-tim-berners-lee/2.jpg" alt="“This Is for Everyone” by Tim Berners-Lee" loading="lazy" decoding="async" width="1499" height="1000"></picture>
<p>Book starts in a very biographical way by telling a little story of Tim’s and his family, education and early career that eventually drove him to CERN. Inspired by the lack of interoperability between documents systems in the particle laboratory, he started constructing a mesh of interconnections. In fact “Mesh” was what Tim called his invention, but inspired by catchy names of products Steve Jobs liked to give to his products, he decided to call it World Wide Web.</p>
<p>The early days were rather tough and the book goes in depth about other competing protocols, first browser wars and attempts to monopolise the Internet. Throughout the book the author expresses his never-ending desire to keep the web open, valuable and accessible for everyone. On multiple occasions the author mentions the fear of fragmentation present on the web, domination of the social media silos and also the importance of the governance bodies behind them. The last chapters of the book go in depth (too much if you ask me) into the <a href="https://solidproject.org/">SOLID protocol</a> that Tim has worked on for the recent years.</p>
<p>I would recommend it wholeheartedly to everyone, not only tech-savvy folk. Geeks will also find this read incredibly interesting as it reveals a lot of great stories about other tech makers of our generation, some drama behind the web clients, an interesting relationship between the author and the UNIX philosophy and tonnes more. I loved the read!</p>]]></description>
      <pubDate>Fri, 10 Apr 2026 00:00:00 +0000</pubDate>
      <link>https://pawelgrzybek.com/book-review-this-is-for-everyone-by-tim-berners-lee/</link>
      <dc:creator>pawelgrzybek.com</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5187929084</guid>
    </item>
    <item>
      <title><![CDATA[Inverted themes with light-dark()]]></title>
      <description><![CDATA[<p>We rolled out <a href="https://nerdy.dev/page-and-component-light-dark-strategies">adaptive <code>light-dark()</code> support</a> on our design system themes and it’s been a delightful upgrade. Creating light and dark variable sets isn’t difficult, but delivery has trade-offs. Most apps that do this probably ship both sets of token values in a single stylesheet. That’s fine until you have multiple kilobytes of duplicate definitions. To get around the performance problems we built two separate stylesheets –which is also not great– but my coworker <a href="https://marchbox.com/">Zacky</a> found a good trick with <code>&lt;link disabled&gt;</code> to make it tolerable. Ultimately, we wanted to offer a single stylesheet for our human (and agent) friends to control theming.</p>
<p>Having <code>light-dark()</code> makes it trivial to support dual color modes in a single stylesheet and doesn’t add too much weight (0.5kb gzip for ~500 variables). It also gives you the ability to switch themes mid-page.</p>
<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code data-lang="css"><span class="nd">:root</span> <span class="p">{</span>
	<span class="py">color-scheme</span><span class="p">:</span> <span class="n">light</span> <span class="n">dark</span><span class="p">;</span>
	<span class="py">--bg-color</span><span class="p">:</span> <span class="n">light-dark</span><span class="p">(</span><span class="no">white</span><span class="p">,</span> <span class="no">black</span><span class="p">);</span>
	<span class="py">--text-color</span><span class="p">:</span> <span class="n">light-dark</span><span class="p">(</span><span class="no">black</span><span class="p">:</span> <span class="no">white</span><span class="p">);</span>
	<span class="c">/* ...etc */</span>
<span class="p">}</span>

<span class="c">/* Add hard-coded theme overrides */</span>
<span class="o">[</span><span class="nt">data-theme</span><span class="o">=</span><span class="s1">"light"</span><span class="o">]</span> <span class="p">{</span> <span class="py">color-scheme</span><span class="p">:</span> <span class="n">light</span><span class="p">;</span> <span class="p">}</span>
<span class="o">[</span><span class="nt">data-theme</span><span class="o">=</span><span class="s1">"dark"</span><span class="o">]</span> <span class="p">{</span> <span class="py">color-scheme</span><span class="p">:</span> <span class="n">dark</span><span class="p">;</span> <span class="p">}</span>
</code></pre></div></div>
<p>Adding <code>[data-theme="dark"]</code> to the body, or any element with color tokens defined, will force that section to be dark mode. The difference here between hanging variable definitions off a class is that you can respect the user preference without using JS to toggle, <code>light-dark()</code> does all the work.</p>
<p>Adding dark tier on a light theme is dramatic. Adding light tier on a dark theme sure would stand out. But when yielding light and dark modes over to the browser… something changes. In that situation hardcoding a theme mode into HTML loses a bit of meaning because I’m not controlling the theme anymore, the user is. What I actually want is the theme to be “opposite” the current theme. After a handful of attempts I think I came across a solution that’s easy to understand, maintain, and I even wrote a polyfill for you.</p>
<h2>Automatic inversion with [data-theme=“inverted]</h2>
<p class="codepen" data-height="400" data-pen-title="[data-theme=&amp;quot;inverted&amp;quot;]" data-version="2" data-default-tab="result" data-slug-hash="zxKpvLq" data-user="davatron5000" style="height: 400px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/editor/davatron5000/pen/019d27b8-d501-760e-8832-877024393885">
  [data-theme="inverted"]</a> by Dave Rupert (<a href="https://codepen.io/davatron5000">@davatron5000</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async="" src="https://public.codepenassets.com/embed/index.js"></script>
<p>The goal was to make it so that when the browser switched from light/dark modes, themed elements would switch to their inverse theme. The trick I uncovered was setting the current theme as a CSS variable:</p>
<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code data-lang="css"><span class="nd">:root</span> <span class="p">{</span>
	<span class="py">color-scheme</span><span class="p">:</span> <span class="n">light</span> <span class="n">dark</span><span class="p">;</span>
	
	<span class="c">/* Initialize the theme variables */</span>
	<span class="py">--theme</span><span class="p">:</span> <span class="n">light</span><span class="p">;</span> 
	<span class="err">@media</span> <span class="err">(</span><span class="py">prefers-color-scheme</span><span class="p">:</span> <span class="n">dark</span><span class="p">)</span> <span class="err">{</span>
		<span class="n">--theme</span><span class="p">:</span> <span class="n">dark</span><span class="p">;</span>
	<span class="p">}</span>
<span class="err">}</span>

<span class="c">/* Have data-theme use the variable */</span>
<span class="o">[</span><span class="nt">data-theme</span><span class="o">]</span> <span class="p">{</span> <span class="py">color-scheme</span><span class="p">:</span> <span class="n">var</span><span class="p">(</span><span class="n">--theme</span><span class="p">);</span> <span class="p">}</span>

<span class="c">/* Update hard-coded themes to use the variable */</span>
<span class="o">[</span><span class="nt">data-theme</span><span class="o">=</span><span class="s1">"light"</span><span class="o">]</span> <span class="p">{</span> <span class="py">--theme</span><span class="p">:</span> <span class="n">light</span><span class="p">;</span> <span class="p">}</span>
<span class="o">[</span><span class="nt">data-theme</span><span class="o">=</span><span class="s1">"dark"</span><span class="o">]</span> <span class="p">{</span> <span class="py">--theme</span><span class="p">:</span> <span class="n">dark</span><span class="p">;</span> <span class="p">}</span>
</code></pre></div></div>
<p>The reason we use manage  <code>--theme</code> variable instead of purely <code>color-scheme</code> is because we can pass that into a <code>style()</code> query. An that’s the magic that enables inverted themes:</p>
<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code data-lang="css"><span class="c">/* Add style query magic ✨ */</span>
<span class="k">@container</span> <span class="n">style</span><span class="p">(</span><span class="n">--theme</span><span class="p">:</span> <span class="n">light</span><span class="p">)</span> <span class="p">{</span>
	<span class="o">[</span><span class="nt">data-theme</span><span class="o">=</span><span class="s1">"inverted"</span><span class="o">]</span> <span class="p">{</span>
		<span class="py">--theme</span><span class="p">:</span> <span class="n">dark</span><span class="p">;</span>
	<span class="p">}</span>
<span class="p">}</span>

<span class="k">@container</span> <span class="n">style</span><span class="p">(</span><span class="n">--theme</span><span class="p">:</span> <span class="n">dark</span><span class="p">)</span> <span class="p">{</span>
	<span class="o">[</span><span class="nt">data-theme</span><span class="o">=</span><span class="s1">"inverted"</span><span class="o">]</span> <span class="p">{</span>
		<span class="py">--theme</span><span class="p">:</span> <span class="n">light</span><span class="p">;</span>
	<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Again, the nice thing here is that we’re not creating two different trees of variables or re-redefining variables inside <code>[data-theme="inverted"]</code>, we’re relying on <code>light-dark()</code> and <code>color-scheme</code> to do the heavy lifting. We’re contextually updating the <code>color-scheme</code> and letting the browser negotiate the cascade.</p>
<p>I added a <code>.funky</code> card theme to the demo to give an idea of how far you might be able to push this tech. <a href="https://blog.kizu.dev/querying-the-color-scheme/">Roman Kamorov noted</a> in his post on <em>Querying the Color Scheme</em> something Vadim Makeev said on a Russian podcast that while this inversion trick is neat, people who prefer dark-mode (e.g. for medical reasons) probably want dark mode and not to be flash-banged mid-page. That’s something to think about and I think I have some ideas about that but I’d love to see/hear yours.</p>
<h2>Polyfilling browser support</h2>
<p>At the time of writing this trick only works in Safari 18+ and Edge/Chrome 111+. Firefox is the outlier but the good news is container style queries is on the roadmap for <a href="https://hacks.mozilla.org/2026/02/launching-interop-2026/">Interop 2026</a> and behind a flag in nightly. That gives you two options:</p>
<ul>
<li>Roll out a bespoke polyfill for <code>[data-theme="inverted]</code></li>
<li>Do nothing. Wait it out.</li>
</ul>
<p>Given that I know the experience will auto-upgrade for Firefox users at some point this year, I’m prone to wait it out per the rules of progressive enhancement. If it falls back to the current theme, I don’t think the world falls over.</p>
<p>However, you probably support browsers outside the latest version and what’s acceptable for a fallback depends on your company’s understanding of the eventual consistency of browsers. iOS devices version-locked at Safari 17.4 are of particular concern for me, so I wrote an inverted theme polyfill.</p>
<p>The CSS style-query-support detection is pretty simple, but quirky.</p>
<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code data-lang="css"><span class="c">/* Style query support check */</span>
<span class="nt">body</span> <span class="p">{</span>
  <span class="py">--syle-query-support</span><span class="p">:</span> <span class="m">0</span><span class="p">;</span>
<span class="p">}</span>

<span class="k">@container</span> <span class="n">style</span><span class="p">(</span><span class="n">--theme</span><span class="p">)</span> <span class="p">{</span>
  <span class="nt">body</span> <span class="p">{</span> <span class="py">--syle-query-support</span><span class="p">:</span> <span class="m">1</span> <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>There’s a new <code>@supports at-rule()</code> function that would be more idiomatic, but <a href="https://www.bram.us/2026/03/15/at-rule/">you can’t detect at-rule function support</a> with <code>at-rule()</code> so we have to do this variable hack. Ideally we could do this purely in JS with <code>CSS.supports()</code>, but alas. Booleans in CSS, what could go wrong?</p>
<p>That brings us to the JavaScript part which is pretty simple as well but comes with <strong>one big potential tradeoff</strong>…</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code data-lang="js"><span class="cm">/**
 * Polyfill for inverted themes using a `--theme` variable in a style query
 * ⚠️ The use of `getComputedStyle` can trigger layout and style recalcs
 */</span>
<span class="p">(()</span><span class="o">=&gt;</span><span class="p">{</span>
  <span class="kd">const</span> <span class="nx">isContainerStyleQueriesSupported</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">bodyElStyle</span> <span class="o">=</span> <span class="nf">getComputedStyle</span><span class="p">(</span><span class="nb">document</span><span class="p">.</span><span class="nx">body</span><span class="p">);</span>
    <span class="kd">const</span> <span class="nx">hasStyleQueries</span> <span class="o">=</span> <span class="nx">bodyElStyle</span><span class="p">.</span><span class="nf">getPropertyValue</span><span class="p">(</span><span class="dl">"</span><span class="s2">--syle-query-support</span><span class="dl">"</span><span class="p">);</span>
    
    <span class="k">if</span><span class="p">(</span><span class="nx">hasStyleQueries</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">1</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span>
      <span class="k">return</span> <span class="kc">true</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="k">return</span> <span class="kc">false</span><span class="p">;</span>
  <span class="p">}</span>
  
  <span class="k">if</span><span class="p">(</span><span class="o">!</span><span class="nf">isContainerStyleQueriesSupported</span><span class="p">())</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">invertedThemes</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nf">querySelectorAll</span><span class="p">(</span><span class="dl">'</span><span class="s1">[data-theme="inverted"]</span><span class="dl">'</span><span class="p">);</span>
  
    <span class="nx">invertedThemes</span><span class="p">.</span><span class="nf">forEach</span><span class="p">((</span><span class="nx">el</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
      <span class="kd">const</span> <span class="nx">elTheme</span> <span class="o">=</span> <span class="nf">getComputedStyle</span><span class="p">(</span><span class="nx">el</span><span class="p">).</span><span class="nf">getPropertyValue</span><span class="p">(</span><span class="dl">"</span><span class="s2">--theme</span><span class="dl">"</span><span class="p">);</span>
      <span class="kd">const</span> <span class="nx">invertedTheme</span> <span class="o">=</span> <span class="nx">elTheme</span> <span class="o">==</span> <span class="dl">"</span><span class="s2">light</span><span class="dl">"</span> <span class="p">?</span> <span class="dl">"</span><span class="s2">dark</span><span class="dl">"</span> <span class="p">:</span> <span class="dl">"</span><span class="s2">light</span><span class="dl">"</span><span class="p">;</span>
      <span class="nx">el</span><span class="p">.</span><span class="nx">dataset</span><span class="p">.</span><span class="nx">theme</span> <span class="o">=</span> <span class="nx">invertedTheme</span><span class="p">;</span>
    <span class="p">})</span>
  <span class="p">}</span>
<span class="p">})();</span>
</code></pre></div></div>
<p>Because we need to get the computed value of  <code>--style-query-support</code>, <a href="https://gist.github.com/paulirish/5d52fb081b3570c81e3a"><code>getComputedStyle</code> is known to trigger style recalcs and layout reflows</a>, effectively penalizing all users not just browsers that don’t support container style queries. I tested this out by putting the polyfill inside a <code>setTimeout</code>, turning on paint flashing, and checking the <a href="https://daverupert.com/2024/09/dev-tools-performance-monitor-panel/">Performance Monitor panel</a> and I didn’t see any recalcs or layout reflows, but your mileage may vary depending on your setup.</p>
<p>Anyways, happy inverting! Let me know if you do something cool with it or if you already figured this out 10 months ago and I didn’t see your blog post.</p>
]]></description>
      <pubDate>Tue, 07 Apr 2026 15:31:00 +0000</pubDate>
      <link>https://daverupert.com/2026/04/inverted-light-dark/</link>
      <dc:creator>daverupert.com</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5184100143</guid>
    </item>
    <item>
      <title><![CDATA[[Sponsor] Zed, a Font Superfamily]]></title>
      <description><![CDATA[
<p>Zed is a type system that was developed with one question in mind: what do readers actually need? Not what looks good in a type specimen, but what works for the widest possible range of readers. We tested Zed with visually impaired patients at a French ophthalmology hospital and found that Zed Text outperformed Helvetica in terms of reading speed across all patient groups. Designed from scratch to perform different functions, it comes in two optical versions — Text and Display — with four variable axes and support for 547 languages, including endangered ones. It is available directly from the designers.</p>

<p><img src="https://daringfireball.net/misc/2026/04/zed-star-1100.png" width="550" alt="Sample of a variety of lowercase a’s from Zed, arranged in a star pattern."></p>

<div>
<a title="Permanent link to ‘Zed, a Font Superfamily’" href="https://daringfireball.net/feeds/sponsors/2026/04/zed_a_font_superfamily">&nbsp;★&nbsp;</a>
</div>

	]]></description>
      <pubDate>Mon, 06 Apr 2026 19:15:05 +0000</pubDate>
      <link>https://www.typotheque.com/blog/zed-a-sans-for-the-needs-of-21century/?utm_source=df</link>
      <dc:creator>Daring Fireball</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5183076859</guid>
    </item>
    <item>
      <title><![CDATA[I Tried Vibing an RSS Reader and My Dreams Did Not Come True]]></title>
      <description><![CDATA[<p>Simon Willison wrote about how <a href="https://simonwillison.net/2026/Feb/25/present/">he vibe coded his dream presentation app for macOS</a>.</p>
<p>I also took a stab at vibe coding my dream app: an RSS reader.</p>
<p>To clarify: <a href="https://reederapp.com/classic/">Reeder</a> is my dream RSS app and it already exists, so I guess you could say my dreams have already come true?</p>
<p>But I’ve kind of always wanted to try an app where my RSS feed is just a list of unread articles and clicking any one opens it in the format in which it was published (e.g. the original website).</p>
<p>So I took a stab at it.</p>
<p>(Note: the backend portion of this was already solved, as I simply connected to my Feedbin account via <a href="https://github.com/feedbin/feedbin-api">the API</a>.)</p>
<p>First I tried a macOS app because I <em>never</em> would’ve tried a macOS app before. Xcode, Swift, a Developer Account? All completely outside my wheelhouse. But AI helped be get past that hurdle of going from <em>nothing</em> to <em>something</em>.</p>
<p class="image-container"><img src="https://cdn.jim-nielsen.com/blog/2026/vibe-rss-mac-app.png" width="1382" height="996" alt="Screenshot of a macOS RSS reader app with a list of unread articles in the sidebar and a preview of one of the selected articles on the left available as a web page on the right.">

</p><p>It was fun to browse articles and see them <em>in situ</em>. A lot of folks have really great personal websites so it’s fun to see their published articles in that format.</p>
<p class="image-container"><img src="https://cdn.jim-nielsen.com/blog/2026/vibe-rss-mac-app-2.png" width="1382" height="996" alt="Screenshot of a macOS RSS reader app with a list of unread articles in the sidebar and a preview of one of the selected articles on the left available as a web page on the right." data-og-image="">

</p><p>This was pretty much pure vibes. I didn’t really look at the code at all because I knew I wouldn’t understand any of it.</p>
<p>I got it working the first night I sat down and tried it. It was pretty crappy but it worked. </p>
<p>From there I iterated. I’d use it for a day, fix things that were off, keep using it, etc.</p>
<p><video controls="" src="https://cdn.jim-nielsen.com/blog/2026/vibe-rss-mac-app.mp4" width="770" height="540"></video></p>
<p>Eventually I got to the point where I thought:</p>
<ul>
<li>Ok, I could use this on my personal computer.</li>
<li>I don’t know that I’ll be able to iterate on this much more because its getting more complicated and failing more and more with each ask (<a href="https://mastodon.social/@jimniels/116267651599562927">I was just trying to move some stupid buttons around in the UI</a> and the AI was like, “Nah bro, I can’t.”)</li>
<li>I have no idea how I’d share this with someone.</li>
<li>I don’t think I’d be comfortable sharing this with someone (even though <em>I think</em> I did things like security right by putting credentials in the built-in keychain, etc.)</li>
<li>I guess this is where the road stops.</li>
</ul>
<p>I’m picky about software, so the bar for my dreams is high. But I’m also lazy, so my patience is quite low.</p>
<p>The intersection of: the LLM failing over and over + my inability to troubleshoot any of it + not wanting to learn = a bad combination for persevering through debugging.</p>
<p>Which made me say: “Screw it, I’ll build it as a website!”</p>
<p>But websites don’t really work for this kind of app because of CORS. I can’t just stick an article’s URL in an <code>&lt;iframe&gt;</code> and preview it because certain sites have cross site headers that don’t allow it to display under another domain.</p>
<p>But that didn’t stop me. I tried building the idea anyway as just a list view. I could install this as a web app on my Mac and I'd get a simple list view:</p>
<p class="image-container"><img src="https://cdn.jim-nielsen.com/blog/2026/vibe-rss-website-app.png" width="711" height="919" alt="Screenshot of an application window on macOS showing a list of article titles from an RSS feed.">

</p><p>Anytime I clicked on a link, it would open in my default browser. Actually not a bad experience.</p>
<p>It worked pretty decent on my phone too. Once I visited my preview deploy, I could "isntall" it to my home screen and then when I opened it, I'd have my latest unread articles. Clicking on any of them would open a webview that I could easily dismiss and get back to my list.</p>
<p class="image-container"><img src="https://cdn.jim-nielsen.com/blog/2026/vibe-rss-website-app-phone.png" width="645" height="656" alt="Screenshot of two screens from iOS side-by-side. One is a list of articles,  the other is an article preview. The list has a specific article highlighted with an arrow pointing to the preview, showing that a click results in a page load.">

</p><p>Not too bad.</p>
<p>But not what I wanted, especially on desktop.</p>
<p>It seemed like the only option to 1) get exactly what I wanted, and 2) distribute it — all in a way that I could understand in case something went wrong or I had to overcome an obstacle — was to make a native app.</p>
<p>At this point, I was thinking: “I’m too tired to learn Apple development right now, and I’ve worked for a long time on the web, so I may as well leverage the skills that I got.”</p>
<p>So I vibed an Electron app because Electron will let me get around the cross site request issues of a website. </p>
<p>This was my very first Electron app and, again, the LLM helped me go from <em>nothing</em> to <em>something</em> quite quickly (but this time I could understand my <em>something</em> way better).</p>
<p>The idea was the same: unread articles on the left, a preview of any selected articles on the right. Here’s a screenshot:</p>
<p class="image-container"><img src="https://cdn.jim-nielsen.com/blog/2026/vibe-rss-electron-app.png" width="1312" height="912" alt="Screenshot of an Electron app showing a list of feed articles in the sidebar and a preview of the selected article on the right.">

</p><p>It’s fine. Not really what I want. But it’s a starting point.</p>
<p>Is it better than Reeder? Hell no.</p>
<p>Is it my wildest dreams realized? Also no.</p>
<p>But it’s a prototype of an idea I’ve wanted to explore.</p>
<p>I”m not sure I’ll go any further on it. It’s hacky enough that I can grasp a vision for what it could be. The question is: do I actually want this? Is this experience something I want in the long run?</p>
<p>I think it could be. But I have to figure out exactly how I want to build it as a complementary experience to my preferred way of going through my RSS feed.</p>
<p>Which won't be your preference.</p>
<p>Which is why I'm not sharing it.</p>
<p>So what’s my takeaway from all this? I don’t know. That’s why I’m typing this all out in a blog post.</p>
<p>Vibe coding is kinda cool. It lets you go from “blank slate” to “something” way faster and easier than before.</p>
<p>But you have to <a href="https://blog.jim-nielsen.com/2025/be-mindful-of-what-you-make-easy/">be mindful of what you make easy</a>. You know what else is easy? Fast food. But I don’t want that all the time.</p>
<p>In fact, vibe coding kinda left me with that feeling I get after indulging in social media, like “What just happened? Two hours have passed and what did I even spend my time doing? Just mindlessly chasing novelty?” It’s fun and easy to mindlessly chasing your whims. But part of me thinks the next best step for this is to sit and think about what I actually want, rather than just yeeting the next prompt out. </p>
<p><a href="https://mastodon.social/@jimniels/116267907171151857">I’ve quipped before</a> that our new timelines are something like:</p>
<ul>
<li>Nothing -&gt; Something? 1hr.</li>
<li>Something -&gt; Something <em>Good</em>? 1 year.</li>
</ul>
<p>The making from nothing isn't as hard anymore. But everything after that still is. Understanding it. Making it good. Distributing it. Supporting it. Maintaining it. All that stuff. When you know absolutely nothing about those — like I did with macOS development — things are still hard.</p>
<p>After all this time vibing, instead of feeling closer to my dream, I actually kinda feel further from it. Like the LLM helped close the gap in understanding what it would actually take for me to realize my dreams. Which made me really appreciate the folks who have poured a lot of time and thought and effort into building RSS readers I use on a day-to-day basis. </p>
<p>Thank you makers of <a href="https://feedbin.com">Feedbin</a> &amp; <a href="https://reederapp.com">Reeder</a> &amp; <a href="https://netnewswire.com">others</a> through the years. I’ll gladly pay you $$$ for your thought and care.</p>
<p>In the meantime, I may or may not be over here slowly iterating on my own supplemental RSS experience. In fact, I might’ve just found the name: RxSSuplement.</p>

    <hr>
    

    <p>
      Reply via:
      

      <a href="mailto:jimniels%2Bblog@gmail.com?subject=Re:%20blog.jim-nielsen.com/2026/vibe-dreams-didnt-come-true/">Email</a>
      · <a href="https://mastodon.social/@jimniels">Mastodon</a> ·

      <a href="https://bsky.app/profile/jim-nielsen.com">Bluesky</a>
    </p>

    
      <p>
        Related posts linking here:
        <a href="https://blog.jim-nielsen.com/2026/prototyping-with-llm/">(2026) Prototyping with LLMs</a>
      </p>
    
  ]]></description>
      <pubDate>Sun, 05 Apr 2026 19:00:00 +0000</pubDate>
      <link>https://blog.jim-nielsen.com/2026/vibe-dreams-didnt-come-true/</link>
      <dc:creator>Jim Nielsen’s Blog</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5182322943</guid>
    </item>
    <item>
      <title><![CDATA[Chrome gets side tabs]]></title>
      <description><![CDATA[<p>David Pierce: <a href="https://www.theverge.com/tech/907998/google-chrome-vertical-tabs?ref=birchtree.me" rel="noopener noreferrer">Vertical browser tabs are better and you should use them</a></p><blockquote>The good news here is, you don’t have to take my word for it. Switching browsers is hard, and maybe you don’t want to do it just for vertical tabs. But update Chrome, right-click the tab bar, and just see what happens when you select Show Tabs Vertically. I bet you’ll never go back.</blockquote><p>And there we go, the last of the major browsers has added side tabs, validating my opinion that Arc is right up there with ChatGPT for the most influential new apps released in the past 5 years.</p><p><em>Editor's note: Safari technically has side tabs, although they are a weird add-on, not a replacement for the normal tabs. I'm counting it, but it's not really what we're talking about when we talk about Arc-style side tabs.</em></p>]]></description>
      <pubDate>Tue, 07 Apr 2026 19:15:10 +0000</pubDate>
      <link>https://birchtree.me/blog/chrome-gets-side-tabs/</link>
      <dc:creator>Birchtree</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5184343035</guid>
    </item>
    <item>
      <title><![CDATA[Meet Tuna: a brand new, modern, modal launcher for macOS]]></title>
      <description><![CDATA[<div class="block-embed block-embed-service-youtube"><iframe type="text/html" src="https://www.youtube.com/embed/vkm-ZFlivyI" frameborder="0" width="640" height="390" webkitallowfullscreen="" mozallowfullscreen="" allowfullscreen=""></iframe></div>
<p>The excitement—and, frankly, sheer <em>giddiness</em>—with which this developer introduces his new app is the most wholesome thing I’ve seen this week.</p>
<p>From the minute when I started watching it, I was thinking that the last thing I needed was an app launcher, having had a Powerpack license for <a href="https://www.alfredapp.com/">Alfred</a> for… <em>checks notes</em>… nearly 14 years!</p>
<p>But the more I watched, the more I was intrigued. Before I could even get done with the video, I had the app and the Pro license.</p>
<p>Yes, I am aware I have a problem.</p>
<p>(Yes, I know <a href="https://www.raycast.com/">Raycast</a> can do everything. If you like it, cool.)</p>
]]></description>
      <pubDate>Fri, 03 Apr 2026 18:17:24 +0000</pubDate>
      <link>https://blakewatson.com/links/2026-04-03-meet-tuna-a-brand-new-modern-modal-launcher-for-macos/</link>
      <dc:creator>Blake&#39;s link feed</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5180466589</guid>
    </item>
    <item>
      <title><![CDATA[Mistrust]]></title>
      <description><![CDATA[

<p>Four years ago I wrote about something that has long puzzled me in the world of front-end development. <a href="https://adactio.com/journal/19021">Trust</a>:</p>

<blockquote>
  <p>The mindset I’ve noticed is that many developers are <em>suspicious</em> of browser features but <em>trusting</em> of third-party libraries.</p>
  
  <p>Developers are more likely to trust, say, Bootstrap than they are to trust CSS grid or custom properties. Developers are more likely to trust React than they are to trust web components.</p>
</blockquote>

<p>That post got <a href="https://adactio.com/journal/19029">some thoughtful responses</a> but I never really understood the imbalance of trust and suspicion:</p>

<blockquote>
  <p>I’m kind of confused by this prevalent mindset of trusting third-party code more than built-in browser features.</p>
</blockquote>

<p>But something happened recently that helped me understand that mindset better.</p>

<p>I wrote a while back about how <a href="https://adactio.com/journal/22360">the datalist element on iOS</a> has been completely fucked up. It’s worse than if Safari simply didn’t support it.</p>

<p>Breaking the web like that should be a five-alarm fire, but <a href="https://bugs.webkit.org/show_bug.cgi?id=305719">nobody is in any rush to fix it</a>. I recall a similar lackadaisical attitude when Safari completely broke their implentation of IndexedDB.</p>

<p>I had it in my head that browser features followed a forward path generally. They’d be iterated on and improved on to iron out any glitches, but it was reasonable to expect things to get <em>better</em> with each new version of a browser.</p>

<p>Now I see that’s not necessarily the case.</p>

<p>Had I used an over-engineered JavaScript library instead of the <code>datalist</code> element, I wouldn’t be facing the current situation of having to use browser-sniffing to avoid sending a standard HTML element to any browser on iOS.</p>

<p>Sure, that third-party JavaScript would mean that users are downloading more code, and it probably wouldn’t work well with assistive technology, but as long as I didn’t touch it, it would continue to work. That <em>should</em> be true of web standards—I should be able to use them secure in the knowledge that they won’t suddenly shit the bed.</p>

<p>Perhaps I should be grateful to Apple for dispelling my naïveté. I now have much more empathy and understanding for web developers who are suspicious of web standards and prefer to use third-party libraries instead.</p>

<p>Good job, Apple. Happy anniversary.</p>


            ]]></description>
      <pubDate>Thu, 02 Apr 2026 14:57:40 +0000</pubDate>
      <link>https://adactio.com/journal/22507</link>
      <dc:creator>Adactio: Journal</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5178746427</guid>
    </item>
    <item>
      <title><![CDATA[You've gained a new achievement]]></title>
      <description><![CDATA[
				
                        <p>For the past month or so I've been obsessed with a book series that's apparently been popular and I just didn't realize - Dungeon Crawler Carl. Without giving too much away, it's basically about a person, and his glorious cat, who get caught up in a real world RPG. I'm currently on book 3 (of 8) and am enjoying every page of it. It's <em>incredibly</em> funny and cool at the same time. If you haven't checked it out yet, I highly recommend picking up the first book and giving it a shot. I don't think you'll regret it.</p>
<p>
<img src="https://static.raymondcamden.com/images/2026/04/iamyelling.jpg" loading="lazy" alt="A cat by a keyboard saying I AM YELLING, CARL" class="imgborder imgcenter">
</p>
<p>As I mentioned, the book series involves a man (and his cat, the cat is crucial) experiencing a real-world RPG and like a RPG, it's got achievements as the character progress through the dungeon. For those of you aren't gamers, most games now will give you an achievement for performing some task. These achievements give you nothing but bragging rights and a slight feeling of accomplishment, but some folks get really excited about them.</p>
<p>In the books, the main characters will also get achievements and typically the achievement is <em>incredibly</em> snarky and sarcastic. As an example:</p>
<div class="callout">
<strong>New achievement! Why aren’t you wearing pants?</strong><br>
You entered the dungeon wearing no pants. Dude. Seriously?
<p><strong>Reward</strong>: You've received a Gold Apparel Box!</p>
</div>
<p>And another:</p>
<div class="callout">
<strong>New achievement! You’ve entered a guildhall!</strong><br>
Congratulations. You know how to open doors.
<p><strong>Reward</strong>: That sense of fulfillment you feel? That’s reward enough.</p>
</div>
<p>These almost always make me LOL and I thought - what if I could make a simple web tool to generate these on the fly? I did so, using Chrome's <a href="https://developer.chrome.com/docs/ai/built-in">built-in AI</a> feature, which as of today is still behind a flag, so if you want to play with this, you'll need to follow the <a href="https://developer.chrome.com/docs/ai/prompt-api">instructions</a> on which flags to enable in your browser. If you've done that, and don't care about <em>how</em> this tool was built, you can head over to the demo now: <a href="https://dcc.raymondcamden.com/">https://dcc.raymondcamden.com/</a></p>
<h2 id="the-code" tabindex="-1"><a class="header-anchor" href="https://www.raymondcamden.com/2026/04/01/youve-gained-a-new-achievement#the-code">The Code</a></h2>
<p>The code behind this was a rather simple usage of the Chrome <a href="https://developer.chrome.com/docs/ai/prompt-api">Prompt API</a>. Here's the code that initializes the session:</p>
<pre><code class="language-js">session = await LanguageModel.create({
    initialPrompts:[
                { 
                    role: 'system', 
                    content: 
`Generate an achievement announcement in the style of 
the Dungeon Crawler Carl series. You will be given a 
prompt to base the achievement on, which will be an 
ordinary, mundane task. The achievement you generate
should have the snarky, over the top prose associated 
with the book.

do not comment about carl at all, or have any other output 
except the achievement title, text, and rewards` 
                }
            ],		
        monitor(m) {
                m.addEventListener("downloadprogress", e =&gt; {
                    console.log(`Downloaded ${e.loaded * 100}%`);
                    if(e.loaded === 0 || e.loaded === 1) {
                        $status.innerHTML = '';
                        return;
                    }
                    $status.innerHTML = `Downloading AI model, currently at ${Math.floor(e.loaded * 100)}%`;
                });
            }		
    });
</code></pre>
<p>Make note of the system instruction which is what guides the model when generating content. Your input is basically a "mundane" thing you've done, like wash dishes or take out the trash. When you've entered that and hit submit, I then simply get the achievement:</p>
<pre><code class="language-js">let achievement = JSON.parse(await session.prompt(input, {  
    responseConstraint: schema,
  }));
</code></pre>
<p>Notice the <code>responseConstraint</code> value? This is how I get precise results back, in my case an achievement title, the text, and a list of rewards. This is defined earlier in my code:</p>
<pre><code class="language-js">let schema = {
	"type": "object",
	"properties": {
		"title": {
			"type":"string"
		},
		"achievementText": {
			"type":"string"
		},
		"rewards": {
			"type":"array",
			"items":{
				"type":"string"
			}
		},
  },
  "additionalProperties": false
};
</code></pre>
<p>And that's pretty much it. The rest of the code is straight up DOM manipulation.</p>
<h2 id="the-design" tabindex="-1"><a class="header-anchor" href="https://www.raymondcamden.com/2026/04/01/youve-gained-a-new-achievement#the-design">The Design</a></h2>
<p>As for the look and feel of the app, I made use of Cursor and one simple prompt:</p>
<blockquote>
im going to be building a tool that uses Chrome AI to generate random achievements in the style of Dungeon Crawler Carl. i need your help with a design. i dont need you to do any of the javascript, just create my scaffold for me. its one web page and the title should be, "DCC Achievement Generator". There is a prompt, "What mundane thing did you get done?", a form field for the task, and then it will render out the achievment. 
<p>Achievements have a title, a body of text, and a list of rewards (bulleted list). Create a nice design for the achievement as well (just make something up for now)</p>
</blockquote>
<p>I'm just now noticing the typo and thankfully Cursor didn't complain. I let it output my HTML and CSS and then simply copied that over to CodePen so I could start working on the actual implementation. I think the only work I needed to do was add some IDs to the HTML in order for my JavaScript to connect to what it needed to update. The design Cursor created was just fine:</p>
<p>
<img src="https://static.raymondcamden.com/images/2026/04/dcc2.jpg" loading="lazy" alt="A screenshot of the app" class="imgborder imgcenter">
</p>
<p>In case you can't read that, my input was: <code>i wrote a blog post</code> and the achievement I received was:</p>
<div class="callout">
<strong>The Scroll of Slightly Above Average Prose</strong><br>
By the gods...you *authored* a blog post? A digital missive of words, painstakingly crafted (or, let's be honest, hastily assembled) and disseminated across the ethereal web? A feat of remarkable… mediocrity? Fear not, adventurer! While not slaying a dragon or retrieving a legendary artifact, you have demonstrated a modicum of linguistic dexterity. A tiny flicker of creativity in the vast darkness of internet noise. Consider yourself mildly commended. It's not much, but it's… something.
<br><br>
<strong>Rewards</strong>:<br>
<ul>
<li>A digital badge of… acknowledgment.</li>
<li>The fleeting satisfaction of knowing you’ve added to the collective human output.</li>
<li>A slightly less judgmental stare from your cat.</li>
<li>Potentially, an increase in internet karma. (Results may vary.)</li>
</ul></div>
<h2 id="results-may-vary" tabindex="-1"><a class="header-anchor" href="https://www.raymondcamden.com/2026/04/01/youve-gained-a-new-achievement#results-may-vary">Results May Vary</a></h2>
<p>If you've got the flags enabled and try it out, please share your results, and let me know what you think. Feel free to fork the pen and modify to your heart's content. Also... Mongo is appalled!</p>
<p class="codepen" data-theme-id="dark" data-height="500" data-pen-title="DCC Generator" data-preview="true" data-version="2" data-default-tab="js" data-slug-hash="qEaxdmg" data-user="cfjedimaster" style="height: 500px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/editor/cfjedimaster/pen/019d3084-ca56-7b39-bb6b-5a67093a283e">
  DCC Generator</a> by Raymond Camden (<a href="https://codepen.io/cfjedimaster">@cfjedimaster</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async="" src="https://public.codepenassets.com/embed/index.js"></script>
<p>Photo by <a href="https://unsplash.com/@giorgiotrovato?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Giorgio Trovato</a> on <a href="https://unsplash.com/photos/yellow-and-white-trophy-_XTY6lD8jgM?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></p>

                        
                
				]]></description>
      <pubDate>Wed, 01 Apr 2026 14:24:32 +0000</pubDate>
      <link>https://www.raymondcamden.com/2026/04/01/youve-gained-a-new-achievement</link>
      <dc:creator>Raymond Camden</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5177364242</guid>
    </item>
    <item>
      <title><![CDATA[Creation]]></title>
      <description><![CDATA[<img src="https://imgs.xkcd.com/comics/creation.png" title="This xkcd.com update introduces a variety of new reading modes which can be activated through the menu below the comic." alt="This xkcd.com update introduces a variety of new reading modes which can be activated through the menu below the comic.">]]></description>
      <pubDate>Wed, 01 Apr 2026 04:00:00 +0000</pubDate>
      <link>https://xkcd.com/3227/</link>
      <dc:creator>xkcd.com</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5177758125</guid>
    </item>
    <item>
      <title><![CDATA[Note 4/4/2026]]></title>
      <description><![CDATA[<p>Blues Traveler was underrated. The Hook still brings me back.</p>]]></description>
      <pubDate>Sat, 04 Apr 2026 04:39:06 +0000</pubDate>
      <link>https://tonysull.co/notes/blues-traveler-was-underrated-the-hook/</link>
      <dc:creator>Tony Sullivan</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5180637645</guid>
    </item>
    <item>
      <title><![CDATA[Note 3/31/2026]]></title>
      <description><![CDATA[<p>The USA is nearing 250 years old.</p>
<p>i hope everyone remembers what this was all for.</p>
<p>Read the writings of Hamilton or Washington or Jefferson or MLK or the inscription on the Statue of Liberty. They wished to rid us of monarchies and professional armies and tyrants and hate.</p>
<p>Don't give that up. Fly your flag upside down, fly a Gadsden flag or Serapis flag. Just don't forget. #now</p>]]></description>
      <pubDate>Tue, 31 Mar 2026 05:31:10 +0000</pubDate>
      <link>https://tonysull.co/notes/the-usa-is-nearing-250-years/</link>
      <dc:creator>Tony Sullivan</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5175360980</guid>
    </item>
    <item>
      <title><![CDATA[CSS subgrid is super good]]></title>
      <description><![CDATA[<p>I’m all aboard the <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Grid_layout/Subgrid" rel="noopener noreferrer" target="_blank">CSS subgrid</a> train. Now I’m seeing subgrid everywhere. Seriously, what was I doing before subgrid? I feel like I was bashing rocks together.</p><p>Consider the follower HTML:</p><pre data-lang="html" tabindex="0" id="pre-3939492e"><code><span class="line"><span class="syntax-26f6a8db">&lt;</span><span class="syntax-1656d967">main</span><span class="syntax-26f6a8db">&gt;</span></span>
<span class="line"><span class="space"> </span><span class="space"> </span><span class="syntax-26f6a8db">&lt;</span><span class="syntax-1656d967">p</span><span class="syntax-26f6a8db">&gt;I</span><span class="space"> </span><span class="syntax-26f6a8db">am</span><span class="space"> </span><span class="syntax-26f6a8db">main</span><span class="space"> </span><span class="syntax-26f6a8db">content.&lt;/</span><span class="syntax-1656d967">p</span><span class="syntax-26f6a8db">&gt;</span></span>
<span class="line"><span class="syntax-26f6a8db">&lt;/</span><span class="syntax-1656d967">main</span><span class="syntax-26f6a8db">&gt;</span></span></code></pre><p>The <code>&lt;main&gt;</code> content could be simple headings and paragraphs.</p><p>It could also be complex HTML patterns from a Content Management System (CMS) like the WordPress block editor, or <a href="https://www.advancedcustomfields.com/resources/flexible-content/" rel="noopener noreferrer" target="_blank">ACF flexible content</a> (a personal favourite).</p><p>Typically when working with CMS output, the main content will be restricted to a maximum width for readable line lengths. We could use a CSS grid to achieve such a layout. Below is a visual example using the Chromium dev tools to highlight grid lines.</p><figure class="Image"><img src="https://dbushell.com/images/blog/2026/subgrid-1.avif" alt="Grid lines marked one through six with a centred paragraph." width="1001" height="165" decoding="async" fetchpriority="low" loading="lazy" id="--img-1c2975ff"></figure><p>This example uses five columns with no gap resulting in six grid lines.</p><pre data-lang="css" tabindex="0" id="pre-f8fdda04"><code><span class="line"><span class="syntax-1656d967">main</span><span class="space"> </span><span class="syntax-26f6a8db">{</span></span>
<span class="line"><span class="space"> </span><span class="space"> </span><span class="syntax-3eeadcf9">display</span><span class="syntax-1656d967">:</span><span class="space"> </span><span class="syntax-765b360f">grid</span><span class="syntax-26f6a8db">;</span></span>
<span class="line"><span class="space"> </span><span class="space"> </span><span class="syntax-3eeadcf9">grid-template-columns</span><span class="syntax-1656d967">:</span></span>
<span class="line"><span class="space"> </span><span class="space"> </span><span class="space"> </span><span class="space"> </span><span class="syntax-765b360f">auto</span></span>
<span class="line"><span class="space"> </span><span class="space"> </span><span class="space"> </span><span class="space"> </span><span class="syntax-765b360f">30</span><span class="syntax-1656d967">px</span></span>
<span class="line"><span class="space"> </span><span class="space"> </span><span class="space"> </span><span class="space"> </span><span class="syntax-3eeadcf9">min</span><span class="syntax-26f6a8db">(</span><span class="syntax-765b360f">45</span><span class="syntax-1656d967">rem</span><span class="syntax-26f6a8db">,</span><span class="space"> </span><span class="syntax-3eeadcf9">calc</span><span class="syntax-26f6a8db">(</span><span class="syntax-765b360f">100</span><span class="syntax-1656d967">%</span><span class="space"> </span><span class="syntax-1656d967">-</span><span class="space"> </span><span class="space"> </span><span class="syntax-765b360f">60</span><span class="syntax-1656d967">px</span><span class="syntax-26f6a8db">))</span></span>
<span class="line"><span class="space"> </span><span class="space"> </span><span class="space"> </span><span class="space"> </span><span class="syntax-765b360f">30</span><span class="syntax-1656d967">px</span></span>
<span class="line"><span class="space"> </span><span class="space"> </span><span class="space"> </span><span class="space"> </span><span class="syntax-765b360f">auto</span><span class="syntax-26f6a8db">;</span></span>
<span class="line"></span>
<span class="line"><span class="space"> </span><span class="space"> </span><span class="syntax-1656d967">:</span><span class="syntax-26f6a8db">where(&amp;)</span><span class="space"> </span><span class="syntax-26f6a8db">&gt;</span><span class="space"> </span><span class="syntax-26f6a8db">*</span><span class="space"> </span><span class="syntax-26f6a8db">{</span></span>
<span class="line"><span class="space"> </span><span class="space"> </span><span class="space"> </span><span class="space"> </span><span class="syntax-3eeadcf9">grid-column</span><span class="syntax-1656d967">:</span><span class="space"> </span><span class="syntax-765b360f">3</span><span class="space"> </span><span class="syntax-26f6a8db">/</span><span class="space"> </span><span class="syntax-765b360f">4</span><span class="syntax-26f6a8db">;</span></span>
<span class="line"><span class="space"> </span><span class="space"> </span><span class="syntax-26f6a8db">}</span></span>
<span class="line"><span class="syntax-26f6a8db">}</span></span></code></pre><p>The two outer most columns are <code>auto</code> meaning they can expand to fill space or collapse to zero-width. The two inner columns are <code>30px</code> which act as a margin. The centre column is the smallest or two values; either <code>45rem</code>, or the full viewport width (minus the margins).</p><p>Counting grid line correctly requires embarrassing finger math and pointing at the screen. Thankfully we can name the lines.</p><pre data-lang="css" tabindex="0" id="pre-18e2ae2e"><code><span class="line"><span class="syntax-1656d967">main</span><span class="space"> </span><span class="syntax-26f6a8db">{</span></span>
<span class="line"><span class="space"> </span><span class="space"> </span><span class="syntax-3eeadcf9">display</span><span class="syntax-1656d967">:</span><span class="space"> </span><span class="syntax-765b360f">grid</span><span class="syntax-26f6a8db">;</span></span>
<span class="line"><span class="space"> </span><span class="space"> </span><span class="syntax-3eeadcf9">grid-template-columns</span><span class="syntax-1656d967">:</span></span>
<span class="line"><span class="space"> </span><span class="space"> </span><span class="space"> </span><span class="space"> </span><span class="syntax-26f6a8db">[</span><span class="syntax-765b360f">inline-start</span><span class="syntax-26f6a8db">]</span></span>
<span class="line"><span class="space"> </span><span class="space"> </span><span class="space"> </span><span class="space"> </span><span class="syntax-765b360f">auto</span></span>
<span class="line"><span class="space"> </span><span class="space"> </span><span class="space"> </span><span class="space"> </span><span class="syntax-26f6a8db">[margin-start]</span></span>
<span class="line"><span class="space"> </span><span class="space"> </span><span class="space"> </span><span class="space"> </span><span class="syntax-765b360f">30</span><span class="syntax-1656d967">px</span></span>
<span class="line"><span class="space"> </span><span class="space"> </span><span class="space"> </span><span class="space"> </span><span class="syntax-26f6a8db">[main-start]</span></span>
<span class="line"><span class="space"> </span><span class="space"> </span><span class="space"> </span><span class="space"> </span><span class="syntax-3eeadcf9">min</span><span class="syntax-26f6a8db">(</span><span class="syntax-765b360f">45</span><span class="syntax-1656d967">rem</span><span class="syntax-26f6a8db">,</span><span class="space"> </span><span class="syntax-3eeadcf9">calc</span><span class="syntax-26f6a8db">(</span><span class="syntax-765b360f">100</span><span class="syntax-1656d967">%</span><span class="space"> </span><span class="syntax-1656d967">-</span><span class="space"> </span><span class="space"> </span><span class="syntax-765b360f">60</span><span class="syntax-1656d967">px</span><span class="syntax-26f6a8db">))</span></span>
<span class="line"><span class="space"> </span><span class="space"> </span><span class="space"> </span><span class="space"> </span><span class="syntax-26f6a8db">[main-end]</span></span>
<span class="line"><span class="space"> </span><span class="space"> </span><span class="space"> </span><span class="space"> </span><span class="syntax-765b360f">30</span><span class="syntax-1656d967">px</span></span>
<span class="line"><span class="space"> </span><span class="space"> </span><span class="space"> </span><span class="space"> </span><span class="syntax-26f6a8db">[margin-end]</span></span>
<span class="line"><span class="space"> </span><span class="space"> </span><span class="space"> </span><span class="space"> </span><span class="syntax-765b360f">auto</span></span>
<span class="line"><span class="space"> </span><span class="space"> </span><span class="space"> </span><span class="space"> </span><span class="syntax-26f6a8db">[</span><span class="syntax-765b360f">inline-end</span><span class="syntax-26f6a8db">];</span></span>
<span class="line"></span>
<span class="line"><span class="space"> </span><span class="space"> </span><span class="syntax-1656d967">:</span><span class="syntax-26f6a8db">where(&amp;)</span><span class="space"> </span><span class="syntax-26f6a8db">&gt;</span><span class="space"> </span><span class="syntax-26f6a8db">*</span><span class="space"> </span><span class="syntax-26f6a8db">{</span></span>
<span class="line"><span class="space"> </span><span class="space"> </span><span class="space"> </span><span class="space"> </span><span class="syntax-3eeadcf9">grid-column</span><span class="syntax-1656d967">:</span><span class="space"> </span><span class="syntax-26f6a8db">main;</span></span>
<span class="line"><span class="space"> </span><span class="space"> </span><span class="syntax-26f6a8db">}</span></span>
<span class="line"><span class="syntax-26f6a8db">}</span></span></code></pre><p>I set a default column of <code>main</code> for all child elements.</p><p>Of course, we could have done this the old fashioned way. Something like:</p><pre data-lang="css" tabindex="0" id="pre-ac044569"><code><span class="line"><span class="syntax-1656d967">main</span><span class="space"> </span><span class="syntax-26f6a8db">{</span></span>
<span class="line"><span class="space"> </span><span class="space"> </span><span class="syntax-3eeadcf9">margin-inline</span><span class="syntax-1656d967">:</span><span class="space"> </span><span class="syntax-765b360f">auto</span><span class="syntax-26f6a8db">;</span></span>
<span class="line"><span class="space"> </span><span class="space"> </span><span class="syntax-3eeadcf9">max-inline-size</span><span class="syntax-1656d967">:</span><span class="space"> </span><span class="syntax-765b360f">45</span><span class="syntax-1656d967">rem</span><span class="syntax-26f6a8db">;</span></span>
<span class="line"><span class="space"> </span><span class="space"> </span><span class="syntax-3eeadcf9">padding-inline</span><span class="syntax-1656d967">:</span><span class="space"> </span><span class="syntax-765b360f">30</span><span class="syntax-1656d967">px</span><span class="syntax-26f6a8db">;</span></span>
<span class="line"><span class="syntax-26f6a8db">}</span></span></code></pre><p>But grid has so much more potential to unlock!</p><p>What if a fancy CMS wraps a paragraph in a block with the class <code>full-width</code>.</p><pre data-lang="html" tabindex="0" id="pre-ea570572"><code><span class="line"><span class="syntax-26f6a8db">&lt;</span><span class="syntax-1656d967">main</span><span class="syntax-26f6a8db">&gt;</span></span>
<span class="line"><span class="space"> </span><span class="space"> </span><span class="syntax-26f6a8db">&lt;</span><span class="syntax-1656d967">p</span><span class="syntax-26f6a8db">&gt;I</span><span class="space"> </span><span class="syntax-26f6a8db">am</span><span class="space"> </span><span class="syntax-26f6a8db">main</span><span class="space"> </span><span class="syntax-26f6a8db">content.&lt;/</span><span class="syntax-1656d967">p</span><span class="syntax-26f6a8db">&gt;</span></span>
<span class="line"><span class="space"> </span><span class="space"> </span><span class="syntax-26f6a8db">&lt;</span><span class="syntax-1656d967">div</span><span class="space"> </span><span class="syntax-4ddd3d58">class</span><span class="syntax-1656d967">=</span><span class="syntax-6168685d">"</span><span class="syntax-e661ac3e">full-width</span><span class="syntax-6168685d">"</span><span class="syntax-26f6a8db">&gt;</span></span>
<span class="line"><span class="space"> </span><span class="space"> </span><span class="space"> </span><span class="space"> </span><span class="syntax-26f6a8db">&lt;</span><span class="syntax-1656d967">p</span><span class="syntax-26f6a8db">&gt;I</span><span class="space"> </span><span class="syntax-26f6a8db">am</span><span class="space"> </span><span class="syntax-26f6a8db">subgrid.&lt;/</span><span class="syntax-1656d967">p</span><span class="syntax-26f6a8db">&gt;</span></span>
<span class="line"><span class="space"> </span><span class="space"> </span><span class="syntax-26f6a8db">&lt;/</span><span class="syntax-1656d967">div</span><span class="syntax-26f6a8db">&gt;</span></span>
<span class="line"><span class="syntax-26f6a8db">&lt;/</span><span class="syntax-1656d967">main</span><span class="syntax-26f6a8db">&gt;</span></span></code></pre><p>This block is expected to magically extend a background to the full-width of the viewport like the example below.</p><figure class="Image"><img src="https://dbushell.com/images/blog/2026/subgrid-2.avif" alt="Two paragraphs. The second has white text with a red background extending to the edge of the viewport." width="1001" height="230" decoding="async" fetchpriority="low" loading="lazy" id="--img-4f5b7148"></figure><p>This used to be a nightmare to code but with CSS subgrid it’s a piece of cake.</p><pre data-lang="css" tabindex="0" id="pre-aa212128"><code><span class="line"><span class="syntax-4ddd3d58">.full-width</span><span class="space"> </span><span class="syntax-26f6a8db">{</span></span>
<span class="line"><span class="space"> </span><span class="space"> </span><span class="syntax-3eeadcf9">display</span><span class="syntax-1656d967">:</span><span class="space"> </span><span class="syntax-765b360f">grid</span><span class="syntax-26f6a8db">;</span></span>
<span class="line"><span class="space"> </span><span class="space"> </span><span class="syntax-3eeadcf9">grid-column</span><span class="syntax-1656d967">:</span><span class="space"> </span><span class="syntax-765b360f">inline</span><span class="syntax-26f6a8db">;</span></span>
<span class="line"><span class="space"> </span><span class="space"> </span><span class="syntax-3eeadcf9">grid-template-columns</span><span class="syntax-1656d967">:</span><span class="space"> </span><span class="syntax-765b360f">subgrid</span><span class="syntax-26f6a8db">;</span></span>
<span class="line"></span>
<span class="line"><span class="space"> </span><span class="space"> </span><span class="syntax-1656d967">:</span><span class="syntax-26f6a8db">where(&amp;)</span><span class="space"> </span><span class="syntax-26f6a8db">&gt;</span><span class="space"> </span><span class="syntax-26f6a8db">*</span><span class="space"> </span><span class="syntax-26f6a8db">{</span></span>
<span class="line"><span class="space"> </span><span class="space"> </span><span class="space"> </span><span class="space"> </span><span class="syntax-3eeadcf9">grid-column</span><span class="syntax-1656d967">:</span><span class="space"> </span><span class="syntax-26f6a8db">main;</span></span>
<span class="line"><span class="space"> </span><span class="space"> </span><span class="syntax-26f6a8db">}</span></span>
<span class="line"><span class="syntax-26f6a8db">}</span></span></code></pre><p>We break out of the <code>main</code> column by changing the <code>grid-column</code> to <code>inline</code> — that’s the name I chose for the outer most grid lines. We then inherit the parent grid using the <code>subgrid</code> template. Finally, the nested children are moved back to the <code>main</code> column.</p><p>The <code>:where</code> selector keeps specificity low. This allows a single class to override the default column. CSS subgrid isn’t restricted to one level. We could keep nesting <code>full-width</code> blocks inside each other and they would all break containment.</p><p>If we wanted to create a “boxed” style we can simply change the <code>grid-column</code> to <code>margin</code> instead of <code>inline</code>. This is why I put the margins inside.</p><figure class="Image"><img src="https://dbushell.com/images/blog/2026/subgrid-3.avif" alt="Two paragraphs. The second now has a background that does not extend." width="1001" height="230" decoding="async" fetchpriority="low" loading="lazy" id="--img-f1081fbd"></figure><p>In hindsight my grid line names are probably confusing, but I don’t have time to edit the examples so go paint your own bikeshed :)</p><p>On smaller viewports below <code>45rem</code> the outer most columns collapse to zero-width and the “boxed” style looks exactly like the <code>full-width</code> style.</p><h2 id="more-columns">More columns</h2><p>This approach is not restricted to one centred column. <a href="https://codepen.io/editor/dbushell/pen/019d4513-f140-72f6-ae6e-ee32aebba690" rel="noopener noreferrer" target="_blank">See my CodePen example</a> and the screenshot below. I split the main content in half to achieve a two-column block where the text edge still aligns, but the image covers the available space.</p><figure class="Image"><img src="https://dbushell.com/images/blog/2026/subgrid-4.avif" alt="Content layout with a full-width blockquote at the top. Below two columns extend to either side of the viewport. The left side with a blue background and the right side with a night sky photo." width="999" height="669" decoding="async" fetchpriority="low" loading="lazy" id="--img-9c073fbe"><figcaption>This may look funny on an ultra-wide monitor. See my post on <a href="https://dbushell.com/2026/03/23/top-ten-figma-betrayls/">Figma betrayals</a> where I cover that topic.</figcaption></figure><p>CSS subgrid is perfect for WordPress and other CMS content that is spat out as a giant blob of HTML. We basically have to centre the content wrapper for top-level prose to look presentable. With the technique I’ve shown we can break out more complex block patterns and then use subgrid to align their contents back inside. It only takes a single class to start!</p><p>Here’s the <a href="https://codepen.io/editor/dbushell/pen/019d4513-f140-72f6-ae6e-ee32aebba690" rel="noopener noreferrer" target="_blank">CodePen link</a> again if you missed it.</p><p>Look how clean that HTML is! Subgrid helps us avoid repetitive nested wrappers. Not to mention any negative margin shenanigans.</p><p>Powerful stuff, right?</p><p>Browser support? Yes. Good enough that I’ve not had any complaints. Your mileage may vary, I am not a lawyer. Don’t subgrid and drive.</p>
<hr>
<p>
Thanks for reading! Follow me on <a href="https://dbushell.com/mastodon/">Mastodon</a> and <a href="https://dbushell.com/bluesky/">Bluesky</a>.
Subscribe to my <a href="https://dbushell.com/rss.xml">Blog</a> and <a href="https://dbushell.com/notes/rss.xml">Notes</a> or <a href="https://dbushell.com/merge/rss.xml">Combined</a> feeds.
</p>
]]></description>
      <pubDate>Thu, 02 Apr 2026 07:14:24 +0000</pubDate>
      <link>https://dbushell.com/2026/04/02/css-subgrid-is-super-good/</link>
      <dc:creator>dbushell.com (blog)</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5178277885</guid>
    </item>
    <item>
      <title><![CDATA[Front-End Fools: Top 10 April Fools’ UI Pranks of All Time]]></title>
      <description><![CDATA[
<p>April Fools’ Day pranks on the web imply that we’re not trying to fool each other every day in web design anyway. Indeed, one of my <a href="https://css-tricks.com/scrollytelling-on-steroids-with-scroll-state-queries/#comment-1883701">favorite comments</a> I received on an article was, “I can’t believe my eyes!” You shouldn’t, since <a href="https://www.interaction-design.org/literature/topics/illusions-in-design?srsltid=AfmBOorUffn89UTjtk5jgvNVMfKFhVTuSlTciyAOjyGon8dDF0rbgUU_#1._understand_the_principles_of_perception-11" rel="noopener">web design relies on fooling the user’s brain</a> by manipulating the way we process visual information via <a href="https://www.interaction-design.org/literature/topics/gestalt-principles" rel="noopener">Gestalt laws</a>, which make a website feel real.</p>



<p>April Fools’ Day on the web exemplifies what philosopher <a href="https://en.wikipedia.org/wiki/Jean_Baudrillard" rel="noopener">Jean Baudrillard</a> called a <a href="https://www.ipl.org/essay/Jean-Boaudillards-Theory-Of-Jean-Baaudrillard-Theory-FKL6SC5K6CED6" rel="noopener">deterrence machine</a> — a single day on the calendar to celebrate funny fake news is like a theme park designed to make the fake constructs beyond its gates seem real by comparison. And oftentimes, the online pranks on April 1st are indistinguishable from the <a href="https://www.bbc.com/news/uk-56597184" rel="noopener">bizarreness</a> that ensues all year round in the “real” virtual world.</p>



<span id="more-392794"></span>


<h3 class="wp-block-heading" id="real-things-that-looked-like-april-fools-pranks">Real things that looked like April Fools’ pranks</h3>


<p>Tech has a history of April Fools’ Day announcements that remind me of what Philip K. Dick called “fake fakes,” emerging every year like <a href="https://philipdick.com/mirror/essays/How_to_Build_a_Universe.pdf" rel="noopener">real animals surreptitiously replacing the fake ones at Disneyland</a>.</p>



<p>For instance, in 2004, people famously thought Gmail <a href="https://en.wikipedia.org/wiki/List_of_Google_April_Fools%27_Day_jokes#2004" rel="noopener">was an April Fools’ joke</a> since it was announced on April 1st.</p>



<p>And on April Fools’ Day in 2013, long before the current generation of AI, <a href="https://tom7.org/" rel="noopener">Tom Murphy</a> announced <a href="https://tom7.org/mario/" rel="noopener">an AI that learns to play NES games</a>. It was the real deal, even though he published the research paper and source code on “<a href="http://sigbovik.org/2013" rel="noopener">SIGBOVIK 2013</a>, an April 1st conference that usually publishes fake research. Mine is real!” In Tom’s <a href="https://www.youtube.com/watch?v=xOCurBYI_gY" rel="noopener">demo</a>, the AI even devised the strategy of indefinitely pausing Tetris, because in that game on NES, “The only way to win is <a href="https://en.wikipedia.org/wiki/Tetris_(NES_video_game)#Gameplay" rel="noopener">not to play</a>.”</p>



<p>To give a more personal example of real tech that could be mistaken for an April Fools’ joke, my article on <a href="https://css-tricks.com/worlds-collide-keyframe-collision-detection-using-style-queries/">pure CSS collision detection</a> was published on April 1st, 2025, <a href="https://www.google.com/search?q=march+31st+US+time+in+AEST&amp;newwindow=1&amp;sca_esv=3d646b66db581d96&amp;biw=1163&amp;bih=605&amp;sxsrf=ANbL-n5k_h4tZKn5GJXplIYnR5lxWqmjKg%3A1772227081965&amp;ei=CQqiabnZOraTseMP86WBwAk&amp;ved=0ahUKEwj5gbqtzPqSAxW2SWwGHfNSAJgQ4dUDCBE&amp;uact=5&amp;oq=march+31st+US+time+in+AEST&amp;gs_lp=Egxnd3Mtd2l6LXNlcnAiGm1hcmNoIDMxc3QgVVMgdGltZSBpbiBBRVNUMgUQIRigATIFECEYoAEyBRAhGKABMgQQIRgVSMtdUJEZWO9ZcAl4AZABAJgB-wGgAe4pqgEGMC4yNi41uAEDyAEA-AEBmAIkoAL2KsICCxAuGIAEGJECGIoFwgILEAAYgAQYkQIYigXCAgsQABiABBixAxiDAcICCBAAGIAEGLEDwgIOEC4YgAQYsQMYgwEYigXCAg4QLhiABBixAxjRAxjHAcICDhAAGIAEGLEDGIMBGIoFwgIKEC4YgAQYQxiKBcICChAAGIAEGEMYigXCAhcQLhiABBixAxiDARjHARiKBRiOBRivAcICFBAuGIAEGLEDGIMBGMcBGI4FGK8BwgIFEC4YgATCAggQLhiABBixA8ICDRAuGIAEGLEDGEMYigXCAhMQLhiABBixAxjRAxhDGMcBGIoFwgILEC4YgAQYsQMYgwHCAhEQABiABBiRAhixAxiDARiKBcICEBAAGIAEGLEDGEMYgwEYigXCAgQQABgDwgIFEAAYgATCAgYQABgWGB7CAgcQIRigARgKmAMAiAYBkgcGNS4yNS42oAfPjQKyBwYwLjI1Lja4B-AqwgcGMS4yNi45yAdXgAgA&amp;sclient=gws-wiz-serp" rel="noopener">my local time</a>. I was amused when someone <a href="https://css-tricks.com/worlds-collide-keyframe-collision-detection-using-style-queries/#comment-1882402">commented</a> that using <code>min</code> to detect if a paddle was in range of a ball seemed like a clever hack that “brings up the question: <em>Should game logic be done in CSS?”</em> Of course it shouldn’t! I wasn’t seriously proposing this as the future of web game development.</p>



<p>I replied that if the commenter can take the idea seriously for a minute, it’s a testament to how far CSS has come as a language. It seems even funnier in hindsight, now that <a href="https://css-tricks.com/the-range-syntax-has-come-to-container-style-queries-and-if/">the range syntax has come to style queries</a>, meaning we no longer need the <code>min</code> hack. So, maybe everyone should make games in CSS now, if the <code>min</code> hack was the only deal breaker (I kid because I love).</p>



<p><a href="https://codepen.io/leemeyer/pen/ZYEJQNO" rel="noopener">My CSS collision detection demo</a> had a resurgence in popularity recently, when Chris Coyier <a href="https://x.com/CodePen/status/2006806143213973723">chose it as a picked Pen</a>. And in that CodePen, a comment again made me laugh: “Can it be multiplayer/online?” Yet, once I stopped laughing, I found myself trying to get a multiplayer mode working. Whether I can or not, I guess the joke’s on me for taking CSS hacking too seriously.</p>



<p>The thing is, much of what <a href="https://rentahuman.ai/" rel="noopener">we have</a> on the web this year seemed unthinkable last year.</p>



<p>Even the story of the origin of April Fool’s Day sounds like a geeky April Fools’ joke — the leading theory is that the 15th-century equivalent of the <a href="https://en.wikipedia.org/wiki/Year_2000_problem" rel="noopener">Y2K bug</a> had some foolish people incorrectly celebrating the new year on April 1st when the Pope changed the calendars in France from the Julian Calendar to the Gregorian Calendar. And — April Fools’ again! — <a href="https://www.snopes.com/fact-check/april-fools39-day-origins/" rel="noopener">that’s a legend nobody has been able to prove happened</a>.</p>



<p>But whichever way you feel about the constant disruptions at the heart of the evolution of tech, the disruptions work like pranks by flipping common narratives on their heads in the same way April Fools’ Day does. With that in mind, let’s go through history with an eye for exploring <a href="https://hcspire.com/2025/03/28/why-theres-a-little-truth-in-every-joke/" rel="noopener">the core of truth inside the jokes</a> of April Fools’ Days passed.</p>



<p class="is-style-explanation"><strong>Note:</strong> These are the historical pranks I consider the top 10 most noteworthy, rather than the “best.” You’ll see that some of them crossed the line and/or backfired.</p>


<h3 class="wp-block-heading" id="google-april-fools-games">Google April Fools’ games</h3>


<p>Google is <a href="https://en.wikipedia.org/wiki/List_of_Google_April_Fools%27_Day_jokes" rel="noopener">famous for its April Fools’ pranks</a>, but they’ve also historically blurred the line between pranks and features. For example, on April 1st 2019, Google introduced a <a href="https://au.lifehacker.com/news/79649/how-to-turn-google-calendar-into-space-invaders" rel="noopener">temporary easter egg</a> that transformed Google Calendar into a Space Invaders game. It was such a cool “joke” that nowadays, there’s a <a href="https://eieio.games/blog/breaktime/" rel="noopener">Chrome extension</a> that offers a similar experience, turning your Google Calendar into a Breakout game. This extension also offers the option to actually delete items that your ball hit from your calendar at the end of a game.</p>



<p>On April Fools’ Day the same year as the original calendar game, Google also released a feature that allowed Google Maps users to <a href="https://youtu.be/LRfGo7LTjro?si=BXLRGG7pSuMAgP_4&amp;t=68" rel="noopener">play Snake on maps</a>.</p>



<p class="is-style-explanation"><strong>Personal Sidenote:</strong> The Google gag inspired an unreleased game I once made with an overworld that’s a gamified calendar, in which your character is trying to avoid an abusive partner by creating excuses not to be at home at the same time as their partner, but that’s a little dark for April Fools’.</p>


<h3 class="wp-block-heading" id="prank-npm-packages">Prank npm packages</h3>


<p>In March 2016, a legit — if arguably trivial — eleven-line package was deleted from the npm registry after its creator decided to boycott npm. Turns out that deletion <a href="https://en.wikipedia.org/wiki/Npm_left-pad_incident" rel="noopener">disrupted big companies whose code relied on the <code>left-pad</code> package</a> and this prompted npm to <a href="https://en.wikipedia.org/wiki/Npm_left-pad_incident#Reactions" rel="noopener">change its policies on which packages can be deleted</a>. I mention this because the humour of the npm packages released as jokes often revolves around poking fun at JavaScript developers’ overuse of dependencies that might not be needed.</p>



<p><a href="https://www.npmjs.com/package/vanilla-javascript" rel="noopener">Here is a 0kb npm package called <code>vanilla-javascript</code></a> and a <a href="http://vanilla-js.com/" rel="noopener">page for the Vanilla JS “framework”</a> that is always 0kb, no matter which features you add to the “bundle.” It lists all the JavaScript frameworks as “plugins.” Some of the dependent packages for <code>vanilla-javascript</code> are quite funny. I like <a href="https://www.npmjs.com/package/@falsejs/falsejs" rel="noopener">false-js</a>, which ensures <code>true</code> and <code>false</code> are defined properly. The library can be initialized with the settings <code>disableAprilFoolsSideEffects</code>, <code>definitelyDisableAprilFoolsSideEffects</code>, and <code>strictDisableAprilFoolsSideEffectsCheck</code>. If you read the source code, there is a <a href="https://github.com/10xly/FalseJS/blob/universe/aprilFoolsCalculateFalse.js" rel="noopener">comment</a> saying, “Haha, this code is obfuscated, you’ll never figure out what happens on April Fools.”</p>



<p>There is also this useless <a href="https://www.npmjs.com/package/get-current-day" rel="noopener">library</a> to get the current day. It seems plausible till you look carefully at the <a href="https://marmelab.com/get-current-day/" rel="noopener">website</a> and the description: “<em>This package is ephemeral for April Fools’ Day and will be removed at some point.</em>“ The testimonials from fictional time-traveling characters are also a bit of a giveaway, and you have to love that he updated it every day for months, “because… why not? 🤷‍♂️”</p>



<p>More “terrible npm packages” for April Fools’ are <a href="https://davidlozzi.com/2022/04/01/completely-terrible-npm-packages/" rel="noopener">here</a>.</p>


<h3 class="wp-block-heading" id="-aprilfools-css-https-gist-github-com-steveosoule-5295646-">aprilFools.css</h3>


<p>There’s another category of dependencies that are functional but used for <em>playing</em> April Fools pranks. For instance, <a href="https://gist.github.com/steveosoule/5295646" rel="noopener">aprilFools.css</a> by Wes Bos, which has a comment at the top saying:</p>



<pre rel="CSS" class="wp-block-csstricks-code-block language-css" data-line=""><code markup="tt">/*
  I assume no responsibility for angry co-workers or lost productivity
  Put these CSS definitons into your co-workers Custom.css file.
  They will be applied to every website they visit as well as their developer tools.
*/</code></pre>



<p>It does things like use <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/transform" rel="noopener">CSS transforms</a> to turn the page upside down.</p>



<p>It strikes me that following the advice in the comments could be a slippery slope to a dark place of workplace bullying, if you were to try it on the wrong coworker, just because they left their computer unlocked. As Chris Coyier pointed out in <a href="https://css-tricks.com/practical-jokes-in-the-browser/">his post on practical jokes in the browser</a>:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>“Fair warning on this stuff… you gotta be tasteful. Putting someone’s stapler in the jello is pretty hilarious unless it’s somehow a family heirloom, or it’s someone who’s been the target of a little too much office prankery to the point it isn’t funny anymore.”</p>
</blockquote>


<h3 class="wp-block-heading" id="april-fool-s-pranks-using-vs-code-extensions">April Fool’s pranks using VS Code Extensions</h3>


<p>While we’re on the topic of behavior that blurs the line between pranks and workplace bullying, let’s talk about <a href="https://justcodeit.medium.com/april-fools-pranks-with-vs-code-extensions-701a0d35647a" rel="noopener">this list of VS Code Extensions</a> that could be used to prank a coworker by causing their code editor UI to behave unexpectedly. Most of the examples sound funny and harmless, like having the IDE intermittently pop up “Dad Jokes” or make funny sounds when typing. Changing the code editor to resemble Slack using a theme is also funny.</p>



<p>Then there’s the last example that made me do a double-take: “Imagine hitting <kbd>CTRL</kbd> + <kbd>S</kbd> to save your work and then it gets erased!” Yeah, if I were interviewing someone and they mentioned they consider this a funny joke, I would end the interview there. And if anyone ever does this to me, I’m going to HR.</p>


<h3 class="wp-block-heading" id="pranks-by-the-w3c">Pranks by the W3C</h3>


<p>I don’t think of the W3C as having a sense of humor, although I guess getting me excited about <a href="https://www.w3.org/TR/html-imports/" rel="noopener">HTML imports</a> back in the day, only to discontinue them, was funny in hindsight, if you have a dark sense of humor. Nevertheless, they have posted pranks on their official website, such as restyling to make their <a href="https://www.youtube.com/watch?v=gbiv-b5uVso" rel="noopener">page look like a nineties GeoCities website in 2012</a>, or claiming they were <a href="https://www.w3.org/press-releases/2021/blink/" rel="noopener">reviving the <code>&lt;blink&gt;</code> tag in 2021</a>. There’s a theme of playing on the nostalgia of people my age who want these things to be real.</p>



<p class="is-style-explanation"><strong>Sidenote:</strong> If you want more Nineties internet experiences, the game <a href="https://www.youtube.com/watch?v=i5EWQSW4Nd8" rel="noopener">Hypnospace Outlaw</a>, set on a retro internet in an alternative 1999, might be up your alley.</p>



<p>Other sites over the years have played a similar joke, which can never fail to charm an old-timer like me who remembers using a web like this at the public library, back when the internet was too expensive for my family to afford at home.</p>


<h3 class="wp-block-heading" id="stackoverflow-retro-restyle">StackOverflow retro restyle</h3>


<p>I can’t get enough of these nostalgia trips, so here’s what StackOverflow looked like on <a href="https://www.reddit.com/r/web_design/s/eKBc3NkvpT" rel="noopener">April Fools’ Day in 2019</a>. They turned the site “full GeoCities” for fun. Yet everything comes full circle. Now StackOverflow itself <a href="https://www.ericholscher.com/blog/2025/jan/21/stack-overflows-decline/" rel="noopener">seems destined to become as fossilized as GeoCities</a>. Even so, the site is currently attempting a new, real redesign to <a href="https://stackoverflow.blog/2026/02/25/your-sneak-peek-at-the-redesigned-stack-overflow/" rel="noopener">survive rather than for fun</a>. It’s sobering to consider that maybe the only StackOverflow experience for the next generation of coders will be if ChatGPT gets a StackOverflow restyle on a future April Fools’.</p>


<h3 class="wp-block-heading" id="stack-egg">Stack Egg</h3>


<p>While we’re on the topic of StackOverflow, their <a href="https://balpha.de/2015/04/the-making-of-stackegg/" rel="noopener">Stack Egg prank</a> from 2015 was very cool, but it might win my award for the most over-engineered April Fools’ prank that caused the most serious problems for a website. The premise was another Nineties throwback, this time to the nineties <a href="https://en.wikipedia.org/wiki/Tamagotchi" rel="noopener">Tamagotchi</a> craze.</p>



<p>The idea, as the creator describes it, was that every site on the Stack Exchange network would have its own “Stack Egg,” representing that site. The goal was to collaboratively keep your metaphorical “site” alive using hypothetical actions named after real actions on the site, such as upvotes to feed the Tamagotchi, and review actions to clean up the poop so the Tamagotchi doesn’t get sick.</p>



<p>It was a nifty concept, although like Google’s April Fools’ games, it’s more neat than laugh-out-loud funny. The part that does make me laugh — I don’t feel too guilty saying it since it was more than a decade ago — was that this is a game about keeping the websites alive, and it inadvertently <a href="https://en.wikipedia.org/wiki/Denial-of-service_attack" rel="noopener">DDoS-ed</a> its <a href="https://meta.stackoverflow.com/questions/289044/is-stackegg-causing-me-problems-or-something-else/289045#289045" rel="noopener">own websites and took down the whole StackExchange network</a>.</p>



<p>And yet, the creators thought the fact that they had the foresight to implement a feature flag that allowed switching it off meant this was a case study in “<a href="https://cacm.acm.org/practice/operational-excellence-in-april-fools-pranks/" rel="noopener">Operational Excellence in AFPs (April Fools’ Pranks)</a>.” Yep, that is an actual article published in a peer-reviewed journal. According to the article, the engineers involved pushed a fix about two hours later to salvage the prank. <a href="https://meta.stackexchange.com/a/252403" rel="noopener">Code Golf was the winner of the game</a>, in case you’re wondering. According to the same post that announced the winner, “it’s by no means designed to withstand exploits,” and in the two days the feature was live, users discovered a vulnerability that was “close to <a href="https://blog.stackoverflow.com/2008/12/vote-fraud-and-you/" rel="noopener">voting fraud</a>.”</p>



<p>I mentioned the over-engineering, so here’s the part that makes the unintentional punchline even funnier: rather than investing more time guarding against the basics, such as not bringing down the website and considering security, the creator spent time making his own <a href="https://en.wikipedia.org/wiki/Turing_completeness" rel="noopener">Turing-complete</a> language to handle the LCD-style <a href="https://stackexchange.github.io/stackegg/" rel="noopener">animations</a>, <a href="https://stackexchange.github.io/stackegg/" rel="noopener"></a>“because I wanted to! Creating a programming language is fun.”</p>



<p>That’s such a classically geeky way to prioritize!</p>


<h3 class="wp-block-heading" id="google-mic-drop">Google Mic Drop</h3>


<p>If Stack Egg created the most issues I’ve ever heard of for a website that created the prank, the most mean-spirited high-profile UI prank — which caused the most problems for users — has to be <a href="https://blog.google/products-and-platforms/products/gmail/introducing-gmail-mic-drop/" rel="noopener">Google Mic Drop</a>. It dropped (pun intended) on April Fools’ Day 2016, shortly after Google <a href="https://www.engadget.com/2015-10-02-alphabet-do-the-right-thing.html" rel="noopener">changed its motto</a> from “don’t be evil” to “do the right thing.” Then, they promptly redefined the “right thing” as sabotaging people’s professional reputations with a minion GIF.</p>



<p>Google added a button, nice and close to the regular “Send” button in Gmail, that would send a farewell message to the recipient with an animated Minion dropping a mic then <strong>block all emails from that recipient permanently, without prompting the sender to confirm first</strong>. Better still, there was a bug that meant the recipient could receive that “GIF of death” and the block, even if the sender managed to press the correct “Send” button in the confusing new UI.</p>



<p>The <a href="https://www.9news.com.au/technology/google-april-fools-day-prank-backfires-man-loses-job/4a9699dc-06ba-426a-a84e-aad1f0eae313" rel="noopener">“hilarity” that ensued</a> included:</p>



<ul class="wp-block-list">
<li>A funeral home accidentally sent a mic drop and block to a grieving family.</li>



<li><a href="https://www.9news.com.au/technology/google-april-fools-day-prank-backfires-man-loses-job/4a9699dc-06ba-426a-a84e-aad1f0eae313" rel="noopener"></a>A man posted on the Gmail help forum, “Thanks to Mic Drop, I just lost my job.”</li>
</ul>



<p>Google <a href="https://blog.google/products-and-platforms/products/gmail/introducing-gmail-mic-drop/" rel="noopener">disabled the feature</a> before the end of April Fools’ Day and issued an apology saying, “It looks like we pranked ourselves this year.” I am not sure how the joke was on Google, so much as the people whose livelihoods and relationships were destroyed.</p>



<p>Remember when I said in the intro that April Fools’ is a distraction from how the joke is on us for believing that the web is what it seems? This Google prank was a reminder that if you believe an <a href="https://about.google/company-info/how-our-business-works/" rel="noopener">advertising company masquerading as a search company</a> has the judgment and ethics to prioritize your interests, when they <a href="https://www.digitaltrends.com/computing/googles-new-policy-tracks-all-your-devices-with-no-opt-out/" rel="noopener">hoard your personal data</a> and <a href="https://www.wheresyoured.at/the-men-who-killed-google/" rel="noopener">don’t actually care if you can find anything</a>, the real mic drop moment is when you realize that your career and relationships are a data point in Google’s next <a href="https://www.reddit.com/r/YoutubeMusic/comments/15b4adh/come_on_google_stop_being_so_ridiculous_with_the/" rel="noopener">A/B test</a>.</p>


<h3 class="wp-block-heading" id="prank-ui-ux-research-articles">Prank UI/UX research articles</h3>


<p>The funniest part of <a href="https://www.nngroup.com/topic/ux-humor/" rel="noopener">these April Fools’ UI/UX advice articles</a> is that they’re published by a serious, high-profile consultancy and research group, so the authors work hard to make it obvious these are April Fools’ hoaxes. In each article, “APRIL FOOLS” is in the title in ALL CAPS. And in the first paragraph of the newer hoax articles: “<em>This article was published as an April Fool’s hoax and does not contain real recommendations.”</em> I like to imagine the marketing department thought this was a great idea, and then the authors of the articles tried their best not to make fools of themselves. I noticed the group stopped posting hoax content after 2022.</p>



<p class="is-style-explanation"><strong>Sidenote:</strong> Educational resources people rely on as a source may not be the best place for prank posts. It reminds me of this peer-reviewed <a href="https://radiopaedia.org/" rel="noopener">radiology website</a> that on April Fools ‘ Day 2015 posted a hoax X-ray image under the title <em>“Ectopia cordis interna – <a href="https://en.wikipedia.org/wiki/Tin_Man_(Oz)" rel="noopener">Tin(Man</a>) syndrome.”</em> Over the years, medical professionals circulated the image unaware it was a hoax, and then, in 2025, <a href="https://retractionwatch.com/2025/09/02/tin-man-syndrome-five-other-case-studies-retracted-following-retraction-watch-coverage/" rel="noopener">six medical journal case studies involving the made-up condition had to be retracted</a>.</p>



<p>Actually, the hoax UI/UX articles are educational, in a <a href="https://ui-patterns.com/blog/User-Interface-AntiPatterns" rel="noopener">UI antipatterns</a> kind of way, such as “<a href="https://www.nngroup.com/articles/users-love-change/" rel="noopener">Users Love Change: Combatting a UX Myth</a>,” which advocates redesigning the UI as often as possible for the heck of it — except I can’t help but feel <a href="https://community.atlassian.com/forums/Jira-questions/Why-are-there-so-many-UI-changes-happening-continuously/qaq-p/939786" rel="noopener">JIRA took that advice literally</a>. The “<a href="https://www.nngroup.com/articles/dog-ux/" rel="noopener">Canine UX</a>” article teaches ideas of user personas and design in a fun way. And “<a href="https://www.nngroup.com/articles/ux-public-bathrooms/" rel="noopener">The User Experience of Public Bathrooms</a>” reads as if <a href="https://www.reddit.com/r/seinfeld/comments/v7xfiw/georges_obsession_with_bathrooms/" rel="noopener">George Costanza</a> from <em>Seinfeld</em> turned his toilet obsession into a lesson in usability.</p>


<h3 class="wp-block-heading" id="digitalocean-buys-codepen-io">DigitalOcean buys codepen.io</h3>


<p>Regular readers of CSS-Tricks know that the founder, Chris Coyier, <a href="https://css-tricks.com/css-tricks-is-joining-digitalocean/">really did decide in 2022 to sell the website to our current stewards, DigitalOcean</a>, so that he could focus on his other projects, such as <a href="https://codepen.io/" rel="noopener">CodePen</a>. Therefore, the <a href="https://youtu.be/dQw4w9WgXcQ?si=Jx-MFGEMeBp6nyan" rel="noopener">announcement</a> on CodePen that DigitalOcean was also buying that website seemed maddeningly plausible. The level of detail in the hoax announcement increased verisimilitude. For instance, the claim that users could use custom domain names on CodePen for free, as long as the domain was DigitalOcean-hosted. In fact, the only sign it was a prank is that nobody anywhere announced anything like this, unless you count me posting it today on a DigitalOcean-owned website.</p>



<p><em>Happy April Fools’ Day, everyone!</em></p>
<hr>
<p><small><a rel="nofollow" href="https://css-tricks.com/front-end-april-fools-top-10/">Front-End Fools: Top 10 April Fools’ UI Pranks of All Time</a> originally handwritten and published with love on <a rel="nofollow" href="https://css-tricks.com">CSS-Tricks</a>. You should really <a href="https://css-tricks.com/newsletters/">get the newsletter</a> as well.</small></p><small>
</small>]]></description>
      <pubDate>Wed, 01 Apr 2026 13:54:26 +0000</pubDate>
      <link>https://css-tricks.com/front-end-april-fools-top-10/</link>
      <dc:creator>CSS-Tricks</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5177355680</guid>
    </item>
  </channel>
</rss>
