<?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/ecb854304371ecaf7966948fb8244c36.xml" rel="self" type="application/rss+xml"/>
    <link>https://feedbin.com/</link>
    <item>
      <title><![CDATA[Golden Tickets]]></title>
      <description><![CDATA[
<p><a href="https://daringfireball.net/linked/2026/04/11/ella-freire-pan-am-tags">More</a> vintage graphic-design weekend fun — this time, a collection of Milwaukee bus tickets from the late 1940s to early 1950s, collected on the Present &amp; Correct blog. So much variety in the colors and typography, but yet they all feel branded together. Think about the care and thought here. Whoever was making these was designing one for each week, every week — and it’s so clear they <em>loved</em> making them. Even something as mundane as weekly bus passes can be exuberant expressions of fun.</p>

<p>(<a href="https://mstdn.social/@ianrogers/116386374945524177">Via Ian K. Rogers</a>, who particularly notes the tickets’ integration of hand-lettering with typefaces.)</p>

<div>
<a title="Permanent link to ‘Golden Tickets’" href="https://daringfireball.net/linked/2026/04/12/golden-tickets">&nbsp;★&nbsp;</a>
</div>

	]]></description>
      <pubDate>Sun, 12 Apr 2026 17:39:26 +0000</pubDate>
      <link>https://www.presentandcorrect.com/blogs/blog/golden-tickets</link>
      <dc:creator>Daring Fireball</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5190044398</guid>
    </item>
    <item>
      <title><![CDATA[Pan American Luggage Labels]]></title>
      <description><![CDATA[
<p>Some graphic design fun for the weekend: achingly gorgeous art pieces recreating vintage Pan Am luggage tags, by Ella Freire. I love them all. The colors, the type, the shapes — sublime.</p>

<p>(<a href="https://simplebits.com/n/studio-notes-79/">Via Dan Cederholm’s Studio Notes</a>.)</p>

<div>
<a title="Permanent link to ‘Pan American Luggage Labels’" href="https://daringfireball.net/linked/2026/04/11/ella-freire-pan-am-tags">&nbsp;★&nbsp;</a>
</div>

	]]></description>
      <pubDate>Sat, 11 Apr 2026 16:55:24 +0000</pubDate>
      <link>https://ellafreire.com/collections/pan-american-luggage-labels</link>
      <dc:creator>Daring Fireball</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5189159359</guid>
    </item>
    <item>
      <title><![CDATA[Artemis II Splashes Down]]></title>
      <description><![CDATA[<div id="" class="hds-media hds-module wp-block-image"><div class="margin-left-auto margin-right-auto nasa-block-align-inline"><div class="hds-media-wrapper margin-left-auto margin-right-auto"><figure class="hds-media-inner hds-cover-wrapper hds-media-ratio-none "><a href="https://www.nasa.gov/wp-content/uploads/2026/04/nhq202604100018.jpg"><img fetchpriority="high" decoding="async" width="2048" height="1500" src="https://www.nasa.gov/wp-content/uploads/2026/04/nhq202604100018.jpg?w=2048" class="attachment-2048x2048 size-2048x2048" alt="" style="transform: scale(1); transform-origin: 50% 50%; object-position: 50% 50%; object-fit: cover;" block_context="nasa-block" loading="eager" srcset="https://www.nasa.gov/wp-content/uploads/2026/04/nhq202604100018.jpg 4000w, https://www.nasa.gov/wp-content/uploads/2026/04/nhq202604100018.jpg?resize=300,220 300w, https://www.nasa.gov/wp-content/uploads/2026/04/nhq202604100018.jpg?resize=768,563 768w, https://www.nasa.gov/wp-content/uploads/2026/04/nhq202604100018.jpg?resize=1024,750 1024w, https://www.nasa.gov/wp-content/uploads/2026/04/nhq202604100018.jpg?resize=1536,1125 1536w, https://www.nasa.gov/wp-content/uploads/2026/04/nhq202604100018.jpg?resize=2048,1500 2048w, https://www.nasa.gov/wp-content/uploads/2026/04/nhq202604100018.jpg?resize=400,293 400w, https://www.nasa.gov/wp-content/uploads/2026/04/nhq202604100018.jpg?resize=600,440 600w, https://www.nasa.gov/wp-content/uploads/2026/04/nhq202604100018.jpg?resize=900,659 900w, https://www.nasa.gov/wp-content/uploads/2026/04/nhq202604100018.jpg?resize=1200,879 1200w, https://www.nasa.gov/wp-content/uploads/2026/04/nhq202604100018.jpg?resize=2000,1465 2000w" sizes="auto, (max-width: 2048px) 100vw, 2048px"></a></figure><figcaption class="hds-caption padding-y-2"><div class="hds-credits">NASA/Bill Ingalls</div></figcaption></div></div></div>


<p>This image from April 10, 2026, captures NASA’s Orion spacecraft, with its parachutes deployed, seconds before splashdown in the Pacific Ocean. The Artemis II crew accomplished many milestones on their nearly 10-day mission, surpassing the Apollo 13 record for farthest crewed spaceflight and capturing views of the far side of the Moon.</p>



<p>Under Artemis, NASA will send astronauts on increasingly difficult missions to explore more of the Moon for scientific discovery, economic benefits, and to build on our foundation for the first crewed missions to Mars.</p>



<p>See more photos from the mission. (Link to <a href="https://gcc02.safelinks.protection.outlook.com/?url=https%3A%2F%2Fwww.nasa.gov%2Fartemis-ii-multimedia%2F&amp;data=05%7C02%7Cgary.daines%40nasa.gov%7C59ac1fff159a4fd192df08de97b33af2%7C7005d45845be48ae8140d43da96dd17b%7C0%7C0%7C639114993205524722%7CUnknown%7CTWFpbGZsb3d8eyJFbXB0eU1hcGkiOnRydWUsIlYiOiIwLjAuMDAwMCIsIlAiOiJXaW4zMiIsIkFOIjoiTWFpbCIsIldUIjoyfQ%3D%3D%7C0%7C%7C%7C&amp;sdata=AkDGv66joKqwqTJAEpvL1Ix32u806N994KiKkCKDV%2FM%3D&amp;reserved=0">https://www.nasa.gov/artemis-ii-multimedia/</a>)</p>
]]></description>
      <pubDate>Sat, 11 Apr 2026 11:05:23 +0000</pubDate>
      <link>https://www.nasa.gov/image-article/artemis-ii-splashes-down/</link>
      <dc:creator>Artemis – NASA</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5188955133</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[Claude Code won April Fools Day this year]]></title>
      <description><![CDATA[
        <p>April Fools Day is somewhat of a legendary day among nerds. Historically it's been when the nerds at GMail introduced <a href="https://archive.google/customtime/">GMail Custom Time</a>, where you could interrupt causality by making GMail look like you sent a message before it was actually sent. It actually worked.</p>
        <p>Sometimes this gets taken too far and the joke falls flat, causing a lot more problems than would exist if the joke never happened in the first place. Incidents like this have resulted in many companies just putting in policies against doing that to avoid customer growth impact.</p>
        <p>It's refreshing to see the Claude Code team introduce the <code>/buddy</code> system this year. When you run <code>/buddy</code>, it hatches a coding companion that hangs out in your Claude Code interface like a tamagochi. Here's my buddy Xentwine:</p>
        <pre><code class="code-highlight"><span class="code-line">╭──────────────────────────────────────╮
        </span><span class="code-line">│                                      │
        </span><span class="code-line">│  ★★★ RARE                     ROBOT  │
        </span><span class="code-line">│                                      │
        </span><span class="code-line">│     (   )                            │
        </span><span class="code-line">│     .[||].                           │
        </span><span class="code-line">│    [ @  @ ]                          │
        </span><span class="code-line">│    [ ==== ]                          │
        </span><span class="code-line">│    `------´                          │
        </span><span class="code-line">│                                      │
        </span><span class="code-line">│  Xentwine                            │
        </span><span class="code-line">│                                      │
        </span><span class="code-line">│  "A methodical circuit-whisperer     │
        </span><span class="code-line">│  obsessed with untangling logical    │
        </span><span class="code-line">│  snarls; speaks in patient,          │
        </span><span class="code-line">│  patronizing riddles and will        │
        </span><span class="code-line">│  absolutely let you sit in your own  │
        </span><span class="code-line">│  bug for three minutes before        │
        </span><span class="code-line">│  offering the blindingly obvious     │
        </span><span class="code-line">│  fix."                               │
        </span><span class="code-line">│                                      │
        </span><span class="code-line">│  DEBUGGING  █████░░░░░  47           │
        </span><span class="code-line">│  PATIENCE   █████░░░░░  47           │
        </span><span class="code-line">│  CHAOS      ██░░░░░░░░  21           │
        </span><span class="code-line">│  WISDOM     █████████░  92           │
        </span><span class="code-line">│  SNARK      █████░░░░░  49           │
        </span><span class="code-line">│                                      │
        </span><span class="code-line">╰──────────────────────────────────────╯
        </span></code></pre>
        <p>Here's what it looks like in the Claude Code app:</p>
        <figure class="max-w-3xl mx-auto not-prose w-full undefined"><a href="https://files.xeiaso.net/blog/2026/claude-code-wins-april-fools/claude-buddy.jpg"><picture><source type="image/avif" srcset="https://files.xeiaso.net/blog/2026/claude-code-wins-april-fools/claude-buddy.avif"><source type="image/webp" srcset="https://files.xeiaso.net/blog/2026/claude-code-wins-april-fools/claude-buddy.webp"><img loading="lazy" src="https://files.xeiaso.net/blog/2026/claude-code-wins-april-fools/claude-buddy.jpg"></picture></a></figure>
        <p>I think this is the best April Fools Day feature in recent memory because it seems intentionally designed to avoid impacting users in a way that would cause problems:</p>
        <ul>
        <li>You have to take manual action to create your coding buddy, it's off by default.</li>
        <li>It mostly stays out of the way when you do create it, meaning that it doesn't impact your normal working process.</li>
        <li>Your buddy sometimes randomly interjects like a tamagochi.</li>
        <li>You can pet the dog, dragon, or robot with <code>/buddy pet</code>.</li>
        </ul>
        <p>This is the kind of harmless prank that all nerds should aspire for. 10/10.</p>
      ]]></description>
      <pubDate>Wed, 01 Apr 2026 00:00:00 +0000</pubDate>
      <link>https://xeiaso.net/notes/2026/claude-code-wins-april-fools/</link>
      <dc:creator>Xe Iaso&#39;s blog</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5177369050</guid>
    </item>
    <item>
      <title><![CDATA[Slash AI]]></title>
      <description><![CDATA[
        <p>I’ve seen <code>/ai</code> <a href="https://www.bydamo.la/p/ai-manifesto">pages</a> popping up here and there on <a href="https://cassidoo.co/ai/">other people’s blogs</a>. The idea for these pages is, and I quote, «promote trust and transparency». Trust, in the context of 2026 internet—and society in general—is quite the complex topic. Dishing out trust willy-nilly is no longer a reasonable thing to do, and I also think we’re getting to the point where the “benefit of the doubt” is no longer worth considering.</p>
<p>If I were to write on this /ai page that I don’t let these tools touch anything I post on this blog, would you trust me? Would that change the perception you have of me? And if you did trust me, why are you doing it? After all, you have no way to actually know for sure. But that is precisely what trust is, isn’t it? Trust is not based on knowledge, but on instinct, on intuitions, on feelings, and on prior experience.</p>
<p>Personally, I couldn’t care less what you write on your /ai page. The same way I couldn’t care less if you use em-dashed. Words are cheap, easy to write, and they mean less and less. But your history, all the baggage you carry with you, all you have written and said, that is harder to fake, building it is time-consuming, but destroying it takes a second. If you start posting AI slop, my trust in you is gone in an instant, and no matter how you’ll try to justify it, that trust will not come back.</p>        <hr>
        <p>Thank you for keeping RSS alive. You're awesome.</p>
        <p><a href="mailto:hello@manuelmoreale.com">Email me</a> ::
        <a href="https://manuelmoreale.com/guestbook">Sign my guestbook</a> :: 
        <a href="https://ko-fi.com/manuelmoreale">Support for 1$/month</a> :: 
        <a href="https://manuelmoreale.com/supporters">See my generous supporters</a> :: 
        <a href="https://buttondown.email/peopleandblogs">Subscribe to People and Blogs</a></p>
    ]]></description>
      <pubDate>Wed, 01 Apr 2026 09:00:00 +0000</pubDate>
      <link>https://manuelmoreale.com/thoughts/slash-ai</link>
      <dc:creator>Manuel Moreale — Everything Feed</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5176973780</guid>
    </item>
    <item>
      <title><![CDATA[Lou-Lou Dollar]]></title>
      <description><![CDATA[
		<p>My friends have a little reward system for their kids: <strong>Jōmon points</strong>. The kids receive little slips of paper with a stamps on them, and they can exchange those for Japanese Yen to buy whatever they want.</p>
				<p>It was loosely decided at the beginning of 2026 that I would have my own point system for the kids when they speak English or do an English-language activity. Nikki declared it would be a “Lou-Lou Dollar” and, naturally, I overdid it.</p>
				<img src="https://lmnt.me/files/images/sketchbook/lou-lou-dollar-1.webp">
				<img src="https://lmnt.me/files/images/sketchbook/lou-lou-dollar-one.webp">
				<p>I made a 1 Dollar bill, with these little details that only matter to me, bits of nonsense that exist only to satisfy the aesthetic. But I couldn’t stop at <strong>One</strong>. So I made a <strong>Two</strong>. Please note while the <strong>One</strong> has a ginkgo leaf and is from the Tokyo series, the <strong>Two</strong> has a sakura and is from the Osaka series. Because.</p>
				<img src="https://lmnt.me/files/images/sketchbook/lou-lou-dollar-2.webp">
				<img src="https://lmnt.me/files/images/sketchbook/lou-lou-dollar-two.webp">
				<p>And then a <strong>Five</strong>, of course, which has a maple leaf from the Nagoya series.</p>
				<img src="https://lmnt.me/files/images/sketchbook/lou-lou-dollar-5.webp">
				<img src="https://lmnt.me/files/images/sketchbook/lou-lou-dollar-five.webp">
				<p>I recognize this is completely out of hand. However, the <strong>Ten</strong> has a chrysanthemum from the Kyoto series.</p>
				<img src="https://lmnt.me/files/images/sketchbook/lou-lou-dollar-10.webp">
				<img src="https://lmnt.me/files/images/sketchbook/lou-lou-dollar-ten.webp">
				<p>I used <a href="https://letterror.com/fonts/federal.html">LTR Federal</a>, <a href="https://ohnotype.co/fonts/fatface">Ohno Fatface</a>, <a href="https://ohnotype.co/fonts/swear">Swear</a>, and <a href="https://hex.xyz/HEX_Franklin/">HEX Franklin</a>. Because if I’m gonna do it, I’m gonna do it right, and those are what made it all feel right.</p>
				<p>Oh, and the red kanji stamp in the corners is a counterfeit-prevention mechanism, should the kids discover this page on my website and get the dumb idea of thinking they can just print their own money.</p>
		]]></description>
      <pubDate>Mon, 19 Jan 2026 08:30:00 +0000</pubDate>
      <link>https://lmnt.me/blog/sketchbook/lou-lou-dollar.html</link>
      <dc:creator>LMNT</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5096303366</guid>
    </item>
    <item>
      <title><![CDATA[pixel raster toy]]></title>
      <description><![CDATA[
<p>Experimenting with pixel rasterization in canvas, and put this little generator together. Fun to tweak the knobs and look for pleasing composition!</p>
<p><strong>2026-01-09</strong>: <em>updated to include background colors and dithered shading</em></p>
<div id="bin-here" style="height:600px;display:flex;align-items:center;justify-content:center;background:#8882"><i>loading demo...</i></div>
<script defer="">
  Promise.all([
    import('https://unpkg.com/@potch/html-bin@2.2.1/build/index.min.js'),
    fetch("/2026/pixelraster.js?5").then(r => r.text()),
    fetch("/2026/pixelraster.css?5").then(r => r.text())
  ])
  .then(([{ createBin }, jsSource, cssSource]) => {
    const container = document.querySelector("#bin-here");
    container.innerHTML = '';
    const bin = createBin({
      initialTab: "preview",
      splitMode: false,
      height: "600px",
      container: container,
      sources: {
        js: jsSource,
        css: cssSource
      }
    })
    bin.start();
  });
</script>
]]></description>
      <pubDate>Wed, 07 Jan 2026 21:08:50 +0000</pubDate>
      <link>https://potch.me/2026/pixel-raster-toy.html</link>
      <dc:creator>potch has a website</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5075348587</guid>
    </item>
    <item>
      <title><![CDATA[Miscalibrated]]></title>
      <description><![CDATA[
<p>I’ve been gaining weight again. More than twenty pounds in the last ~4 months. </p>



<p>I’ve been hitting the gym hard and getting measurably stronger, so: Food! See, your boy can eat. The amount I can eat before I feel full would astound most of you out there. Whatever you think of as a complete hearty meal, sure as you’re born, ain’t gonna get me there. </p>



<p>Being fat comes with one (1) society-regimented bucket of shame. People look away. It’s a thing.</p>



<p>I had gone off my last round of GLP-1 drugs because I was doing OK, and it had lost its effectiveness. I’m not sure if it’s everyone’s experience, but it’s mine, and it’s happened a couple of times now. Honestly, I think my <strong>I CAN EAT THROUGH OZEMPIC</strong> line of XXXL T-Shirts has a chance. These drugs work very well for a bit. I like them because it gives me a glimpse of what it’s like to be a regular person who eats a regular amount of food and feels a regular amount of full. You settle into that for a while with these drugs. </p>



<p>But, in time, effectiveness wanes. And the pharmacies have an answer: higher doses! All these GLP-1 drugs, and I’m pretty sure it is <em>all</em> of them, have dosage tiers. The three I’ve tried have three tiers. </p>



<p>Ozempic rolls like this:</p>



<figure class="wp-block-image size-large"><img data-recalc-dims="1" loading="lazy" decoding="async" width="1024" height="295" data-attachment-id="13226" data-permalink="https://chriscoyier.net/2026/02/20/miscalibrated/screenshot-2026-02-20-at-6-41-42-pm/" data-orig-file="https://i0.wp.com/chriscoyier.net/wp-content/uploads/2026/02/Screenshot-2026-02-20-at-6.41.42-PM.png?fit=2000%2C576&amp;ssl=1" data-orig-size="2000,576" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="Screenshot 2026-02-20 at 6.41.42 PM" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/chriscoyier.net/wp-content/uploads/2026/02/Screenshot-2026-02-20-at-6.41.42-PM.png?fit=300%2C86&amp;ssl=1" data-large-file="https://i0.wp.com/chriscoyier.net/wp-content/uploads/2026/02/Screenshot-2026-02-20-at-6.41.42-PM.png?fit=1024%2C295&amp;ssl=1" src="https://i0.wp.com/chriscoyier.net/wp-content/uploads/2026/02/Screenshot-2026-02-20-at-6.41.42-PM.png?resize=1024%2C295&amp;ssl=1" alt="Infographic explaining dosing guidelines for Ozempic injections, including initial doses of 0.25 mg and 0.5 mg for the first 4 weeks, followed by 1 mg and 2 mg for additional blood sugar control, with pen delivery instructions." class="wp-image-13226" srcset="https://i0.wp.com/chriscoyier.net/wp-content/uploads/2026/02/Screenshot-2026-02-20-at-6.41.42-PM.png?resize=1024%2C295&amp;ssl=1 1024w, https://i0.wp.com/chriscoyier.net/wp-content/uploads/2026/02/Screenshot-2026-02-20-at-6.41.42-PM.png?resize=300%2C86&amp;ssl=1 300w, https://i0.wp.com/chriscoyier.net/wp-content/uploads/2026/02/Screenshot-2026-02-20-at-6.41.42-PM.png?resize=768%2C221&amp;ssl=1 768w, https://i0.wp.com/chriscoyier.net/wp-content/uploads/2026/02/Screenshot-2026-02-20-at-6.41.42-PM.png?resize=1536%2C442&amp;ssl=1 1536w, https://i0.wp.com/chriscoyier.net/wp-content/uploads/2026/02/Screenshot-2026-02-20-at-6.41.42-PM.png?w=2000&amp;ssl=1 2000w" sizes="auto, (max-width: 1000px) 100vw, 1000px"></figure>



<p>Wegovy is getting in on the action:</p>



<figure class="wp-block-image size-large"><img data-recalc-dims="1" loading="lazy" decoding="async" width="1024" height="231" data-attachment-id="13227" data-permalink="https://chriscoyier.net/2026/02/20/miscalibrated/screenshot-2026-02-20-at-6-42-07-pm/" data-orig-file="https://i0.wp.com/chriscoyier.net/wp-content/uploads/2026/02/Screenshot-2026-02-20-at-6.42.07-PM.png?fit=2212%2C500&amp;ssl=1" data-orig-size="2212,500" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="Screenshot 2026-02-20 at 6.42.07 PM" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/chriscoyier.net/wp-content/uploads/2026/02/Screenshot-2026-02-20-at-6.42.07-PM.png?fit=300%2C68&amp;ssl=1" data-large-file="https://i0.wp.com/chriscoyier.net/wp-content/uploads/2026/02/Screenshot-2026-02-20-at-6.42.07-PM.png?fit=1024%2C231&amp;ssl=1" src="https://i0.wp.com/chriscoyier.net/wp-content/uploads/2026/02/Screenshot-2026-02-20-at-6.42.07-PM.png?resize=1024%2C231&amp;ssl=1" alt="Image showing a dosage chart for a medication, with starting dosages of 0.25 mg, 0.5 mg, 1 mg, 1.7 mg, and 2.4 mg. Each dosage is accompanied by its duration and type, including escalation and maintenance doses." class="wp-image-13227" srcset="https://i0.wp.com/chriscoyier.net/wp-content/uploads/2026/02/Screenshot-2026-02-20-at-6.42.07-PM.png?resize=1024%2C231&amp;ssl=1 1024w, https://i0.wp.com/chriscoyier.net/wp-content/uploads/2026/02/Screenshot-2026-02-20-at-6.42.07-PM.png?resize=300%2C68&amp;ssl=1 300w, https://i0.wp.com/chriscoyier.net/wp-content/uploads/2026/02/Screenshot-2026-02-20-at-6.42.07-PM.png?resize=768%2C174&amp;ssl=1 768w, https://i0.wp.com/chriscoyier.net/wp-content/uploads/2026/02/Screenshot-2026-02-20-at-6.42.07-PM.png?resize=1536%2C347&amp;ssl=1 1536w, https://i0.wp.com/chriscoyier.net/wp-content/uploads/2026/02/Screenshot-2026-02-20-at-6.42.07-PM.png?resize=2048%2C463&amp;ssl=1 2048w" sizes="auto, (max-width: 1000px) 100vw, 1000px"></figure>



<p>Mounjaro has even more layers:</p>



<figure class="wp-block-image size-large"><img data-recalc-dims="1" loading="lazy" decoding="async" width="1024" height="711" data-attachment-id="13228" data-permalink="https://chriscoyier.net/2026/02/20/miscalibrated/screenshot-2026-02-20-at-6-43-38-pm/" data-orig-file="https://i0.wp.com/chriscoyier.net/wp-content/uploads/2026/02/Screenshot-2026-02-20-at-6.43.38-PM.png?fit=1872%2C1300&amp;ssl=1" data-orig-size="1872,1300" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="Screenshot 2026-02-20 at 6.43.38 PM" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/chriscoyier.net/wp-content/uploads/2026/02/Screenshot-2026-02-20-at-6.43.38-PM.png?fit=300%2C208&amp;ssl=1" data-large-file="https://i0.wp.com/chriscoyier.net/wp-content/uploads/2026/02/Screenshot-2026-02-20-at-6.43.38-PM.png?fit=1024%2C711&amp;ssl=1" src="https://i0.wp.com/chriscoyier.net/wp-content/uploads/2026/02/Screenshot-2026-02-20-at-6.43.38-PM.png?resize=1024%2C711&amp;ssl=1" alt="Infographic detailing a dosage schedule for a medication, featuring various dosage levels (2.5 mg, 5 mg, 7.5 mg, 10 mg, 12.5 mg, 15 mg) taken once weekly over several months." class="wp-image-13228" srcset="https://i0.wp.com/chriscoyier.net/wp-content/uploads/2026/02/Screenshot-2026-02-20-at-6.43.38-PM.png?resize=1024%2C711&amp;ssl=1 1024w, https://i0.wp.com/chriscoyier.net/wp-content/uploads/2026/02/Screenshot-2026-02-20-at-6.43.38-PM.png?resize=300%2C208&amp;ssl=1 300w, https://i0.wp.com/chriscoyier.net/wp-content/uploads/2026/02/Screenshot-2026-02-20-at-6.43.38-PM.png?resize=768%2C533&amp;ssl=1 768w, https://i0.wp.com/chriscoyier.net/wp-content/uploads/2026/02/Screenshot-2026-02-20-at-6.43.38-PM.png?resize=1536%2C1067&amp;ssl=1 1536w, https://i0.wp.com/chriscoyier.net/wp-content/uploads/2026/02/Screenshot-2026-02-20-at-6.43.38-PM.png?w=1872&amp;ssl=1 1872w" sizes="auto, (max-width: 1000px) 100vw, 1000px"></figure>



<p>Again, they do this <em>because it loses effectiveness.</em> I don’t think people quite realize this??? Even though it’s not hidden in any way.</p>



<p>I think these drugs are pretty amazing, and I’m proud of science for starting to figure all this out, but I’m also a little sick of hearing about how <a href="https://www.cbsnews.com/news/weight-loss-drugs-glp1s-airlines-fuel-costs/">airlines are going to spend less money on fuel</a> now. I’ve been reading this story for many years. It’s laughable when we literally <em>know</em> they don’t work permanently.</p>



<p>Look at those graphics above. This isn’t a forever solution yet. They are literally showing and telling us that. There is no answer once they lose effectiveness. Perhaps controversial, but I think overeating, in the form I experience it, is an addiction, and addictions come back. Is it <em>possible</em> to beat it? Absolutely. Is it <em>likely?</em> No. </p>



<p>I hope you don’t know firsthand, but I bet you already know that cocaine doesn’t maintain effectiveness, either. You need a second line for the same thrill before long. It doesn’t end well.</p>



<p>Anyway, I’m back on GLP-1s.</p>



<p>At least they work for a while, and that while feels pretty good.</p>



<p>It was a rough start, though. My doctor agreed it’s good for me and we should kick up the dosage based on the waned effectiveness. Wegovy this time. It was this past Tuesday that I picked up the meds. It’s down to $350 now! It used to be like $1,200 without insurance. I jabbed myself Tuesday night at about 8pm. </p>



<p>I was hugging the toilet hard by midnight. That was a first. </p>



<p>See, there was a lot of food in my body. I remember lunch that day, where I made a sandwich were my rational brain saw it and thought <em>that’s 2-3 sandwiches.</em> But of course I ate all of it. And one of those salad bags that make a Caesar salad for a family of four. And a pint of cottage cheese. And a bag of Doritos. I was full after that, but the trick is just to switch to sugar after that, and I can keep going.  It wasn’t quite noon, and I had a decent breakfast in me already. I ate dinner that night as well.</p>



<p>So when the Wegovy started to hit, which tells your body you’re full when you eat a celery stick, it told my body that it was about to <em>pop</em>. I puked in four sessions over 24 hours. </p>



<p>Now it’s Friday, and I’ve barely eaten since. I’ve eaten a <em>little</em>. Like, I’m <em>fine.</em> It’s just weird. </p>



<p>I’m miscalibrated. </p>



<p>On my own, nature, nurture, whatever you think, my current body is miscalibrated. It doesn’t do food correctly.</p>



<p>On GLP-1 drugs, I’m also miscalibrated. My body doesn’t do food correctly. It highly <em>over</em>corrects. That can feel good for a while. I don’t wanna be skinny, I just wanna be normal. I want to eat, and stop eating, like a calibrated person.</p>
]]></description>
      <pubDate>Sat, 21 Feb 2026 03:25:57 +0000</pubDate>
      <link>https://chriscoyier.net/2026/02/20/miscalibrated/</link>
      <dc:creator>Chris Coyier</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5125856513</guid>
    </item>
    <item>
      <title><![CDATA[Owning your data]]></title>
      <description><![CDATA[
                    <p><mark>Owning your own data is hard</mark>. I've been trying to own as much as I can, and my site has become a reflection of that process, both in what I display and discuss. As difficult as it is, there's a freedom in owning as much as you can.</p>

                ]]></description>
      <pubDate>Wed, 18 Feb 2026 22:39:00 +0000</pubDate>
      <link>https://www.coryd.dev/posts/2026/owning-your-data</link>
      <dc:creator>Posts feed • Cory Dransfeldt</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5123139551</guid>
    </item>
    <item>
      <title><![CDATA[Building a Navidrome scrobbling plugin]]></title>
      <description><![CDATA[
                    <p><a href="https://github.com/navidrome/navidrome/releases/tag/v0.60.0">Navidrome released plugin support a few weeks ago</a> and I've been working on implementing a plugin that scrobbles my listens to my own API endpoint. This has replaced my previous approach of regularly polling a private endpoint that sat unused in <a href="https://www.navidrome.org">Navidrome</a>'s UI. It works better and uses a properly supported mechanism to meet my needs.</p>

                ]]></description>
      <pubDate>Wed, 18 Feb 2026 23:48:00 +0000</pubDate>
      <link>https://www.coryd.dev/posts/2026/building-a-navidrome-scrobbling-plugin</link>
      <dc:creator>Posts feed • Cory Dransfeldt</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5123194688</guid>
    </item>
    <item>
      <title><![CDATA[Two different tricks for fast LLM inference]]></title>
      <description><![CDATA[<p><a href="https://platform.claude.com/docs/en/build-with-claude/fast-mode">Anthropic</a> and <a href="https://openai.com/index/introducing-gpt-5-3-codex-spark/">OpenAI</a> both recently announced “fast mode”: a way to interact with their best coding model at significantly higher speeds.</p>
<p>These two versions of fast mode are very different. Anthropic’s <a href="https://platform.claude.com/docs/en/build-with-claude/fast-mode#how-fast-mode-works">offers</a> up to 2.5x tokens per second (so around 170, up from Opus 4.6’s 65). OpenAI’s offers more than 1000 tokens per second (up from GPT-5.3-Codex’s 65 tokens per second, so 15x). So OpenAI’s fast mode is six times faster than Anthropic’s<sup id="fnref-1"><a href="https://seangoedecke.com/fast-llm-inference/#fn-1" class="footnote-ref">1</a></sup>.</p>
<p>However, Anthropic’s big advantage is that they’re serving their actual model. When you use their fast mode, you get real Opus 4.6, while when you use OpenAI’s fast mode you get GPT-5.3-Codex-Spark, not the real GPT-5.3-Codex. Spark is indeed much faster, but is a notably less capable model: good enough for many tasks, but it gets confused and messes up tool calls in ways that vanilla GPT-5.3-Codex would never do.</p>
<p>Why the differences? The AI labs aren’t advertising the details of how their fast modes work, but I’m pretty confident it’s something like this: <strong>Anthropic’s fast mode is backed by <em>low-batch-size</em> inference, while OpenAI’s fast mode is backed by special monster Cerebras chips</strong>. Let me unpack that a bit.</p>
<h3>How Anthropic’s fast mode works</h3>
<p>The tradeoff at the heart of AI inference economics is <em>batching</em>, because the main bottleneck is <em>memory</em>. GPUs are very fast, but moving data onto a GPU is not. Every inference operation requires copying all the tokens of the user’s prompt<sup id="fnref-2"><a href="https://seangoedecke.com/fast-llm-inference/#fn-2" class="footnote-ref">2</a></sup> onto the GPU before inference can start. Batching multiple users up thus increases overall throughput at the cost of making users wait for the batch to be full.</p>
<p>A good analogy is a bus system. If you had zero batching for passengers - if, whenever someone got on a bus, the bus departed immediately - commutes would be much faster <em>for the people who managed to get on a bus</em>. But obviously overall throughput would be much lower, because people would be waiting at the bus stop for hours until they managed to actually get on one.</p>
<p>Anthropic’s fast mode offering is basically a bus pass that guarantees that the bus immediately leaves as soon as you get on. It’s six times the cost, because you’re effectively paying for all the other people who could have got on the bus with you, but it’s way faster<sup id="fnref-3"><a href="https://seangoedecke.com/fast-llm-inference/#fn-3" class="footnote-ref">3</a></sup> because you spend <em>zero</em> time waiting for the bus to leave.</p>
<p>edit: I want to thank a reader for emailing me to point out that the “waiting for the bus” cost is really only paid for the first token, so that won’t affect <em>streaming</em> latency (just latency per turn or tool call). It’s thus better to think of the performance impact of batch size being mainly that smaller batches require fewer flops and thus execute more quickly. In my analogy, maybe it’s “lighter buses drive faster”, or something.</p>
<p>Obviously I can’t be fully certain this is right. Maybe they have access to some new ultra-fast compute that they’re running this on, or they’re doing some algorithmic trick nobody else has thought of. But I’m pretty sure this is it. Brand new compute or algorithmic tricks would likely require changes to the model (see below for OpenAI’s system), and “six times more expensive for 2.5x faster” is right in the ballpark for the kind of improvement you’d expect when switching to a low-batch-size regime.</p>
<h3>How OpenAI’s fast mode works</h3>
<p>OpenAI’s fast mode does not work anything like this. You can tell that simply because they’re introducing a new, worse model for it. There would be absolutely no reason to do that if they were simply tweaking batch sizes. Also, they told us in the announcement <a href="https://openai.com/index/introducing-gpt-5-3-codex-spark/">blog post</a> exactly what’s backing their fast mode: Cerebras.</p>
<p>OpenAI <a href="https://openai.com/index/cerebras-partnership/">announced</a> their Cerebras partnership a month ago in January. What’s Cerebras? They build “ultra low-latency compute”. What this means in practice is that they build <em>giant chips</em>. A H100 chip (fairly close to the frontier of inference chips) is just over a square inch in size. A Cerebras chip is <em>70</em> square inches.</p>
<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; ">
      <a class="gatsby-resp-image-link" href="https://seangoedecke.com/static/a32e19a54795813e122dcbc1a5e013ef/d165a/cerebras.jpg" style="display: block" target="_blank" rel="noopener">
    <span class="gatsby-resp-image-background-image" style="padding-bottom: 100%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAUABQDASIAAhEBAxEB/8QAGAABAQADAAAAAAAAAAAAAAAAAAMCBAX/xAAWAQEBAQAAAAAAAAAAAAAAAAABAgP/2gAMAwEAAhADEAAAAexKbG9tNtEgOYT/xAAcEAABAwUAAAAAAAAAAAAAAAACAAEREBIhMTL/2gAIAQEAAQUCcoQlmVyMptEI30//xAAWEQEBAQAAAAAAAAAAAAAAAAABICH/2gAIAQMBAT8BDI//xAAXEQEAAwAAAAAAAAAAAAAAAAACARIg/9oACAECAQE/AUptj//EABoQAAEFAQAAAAAAAAAAAAAAABAAAQIRMSH/2gAIAQEABj8CNasEXrp//8QAGxAAAgIDAQAAAAAAAAAAAAAAAAERIRAxQWH/2gAIAQEAAT8hSlWdNTgqk06RbER7iKSEjUn/2gAMAwEAAgADAAAAEK/Xgf/EABYRAQEBAAAAAAAAAAAAAAAAAAEQIf/aAAgBAwEBPxANQcn/xAAXEQEBAQEAAAAAAAAAAAAAAAABEQAQ/9oACAECAQE/EBwY0Lef/8QAHBABAQEAAwADAAAAAAAAAAAAAREAITFBUXHB/9oACAEBAAE/EHqqvWC3iV46Hd2mNsD0atxARPX4yQSz7wgEiL044xYBOLhUDofzf//Z'); background-size: cover; display: block;"></span>
  <img class="gatsby-resp-image-image" alt="cerebras" title="cerebras" src="https://seangoedecke.com/static/a32e19a54795813e122dcbc1a5e013ef/1c72d/cerebras.jpg" srcset="https://seangoedecke.com/static/a32e19a54795813e122dcbc1a5e013ef/a80bd/cerebras.jpg 148w, https://seangoedecke.com/static/a32e19a54795813e122dcbc1a5e013ef/1c91a/cerebras.jpg 295w, https://seangoedecke.com/static/a32e19a54795813e122dcbc1a5e013ef/1c72d/cerebras.jpg 590w, https://seangoedecke.com/static/a32e19a54795813e122dcbc1a5e013ef/a8a14/cerebras.jpg 885w, https://seangoedecke.com/static/a32e19a54795813e122dcbc1a5e013ef/fbd2c/cerebras.jpg 1180w, https://seangoedecke.com/static/a32e19a54795813e122dcbc1a5e013ef/d165a/cerebras.jpg 1400w" sizes="(max-width: 590px) 100vw, 590px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy">
  </a>
    </span></p>
<p>You can see from pictures that the Cerebras chip has a grid-and-holes pattern all over it. That’s because silicon wafers this big are supposed to be broken into dozens of chips. Instead, Cerebras etches a giant chip over the entire thing.</p>
<p>The larger the chip, the more internal memory it can have. The idea is to have a chip with SRAM large enough <em>to fit the entire model</em>, so inference can happen entirely in-memory. Typically GPU SRAM is measured in the tens of <em>megabytes</em>. That means that a lot of inference time is spent streaming portions of the model weights from outside of SRAM into the GPU compute<sup id="fnref-4"><a href="https://seangoedecke.com/fast-llm-inference/#fn-4" class="footnote-ref">4</a></sup>. If you could stream all of that from the (much faster) SRAM, inference would a big speedup: fifteen times faster, as it turns out!</p>
<p>So how much internal memory does the latest Cerebras chip have? <a href="https://arxiv.org/html/2503.11698v1#:~:text=Most%20recently%2C%20the%20Wafer%20Scale,of%2021%20petabytes%20per%20second.">44GB</a>. This puts OpenAI in kind of an awkward position. 44GB is enough to fit a small model (~20B params at fp16, ~40B params at int8 quantization), but clearly not enough to fit GPT-5.3-Codex. That’s why they’re offering a brand new model, and why the Spark model has a bit of “small model smell” to it: it’s a smaller <a href="https://en.wikipedia.org/wiki/Knowledge_distillation">distil</a> of the much larger GPT-5.3-Codex model<sup id="fnref-5"><a href="https://seangoedecke.com/fast-llm-inference/#fn-5" class="footnote-ref">5</a></sup>.</p>
<p>edit: I was wrong about this - the Codex model is almost certainly larger than this, and doesn’t need to fit entirely in one chip’s SRAM (if it did, we’d be seeing faster speeds). Thanks to the Hacker News commenters for correcting me. But I think there’s still a good chance that Spark is SRAM-resident (split across a few Cerebras chips) which is what’s driving the speedup.</p>
<h3>OpenAI’s version is much more technically impressive</h3>
<p>It’s interesting that the two major labs have two very different approaches to building fast AI inference. If I had to guess at a conspiracy theory, it would go something like this:</p>
<ul>
<li>OpenAI partner with Cerebras in mid-January, obviously to work on putting an OpenAI model on a fast Cerebras chip</li>
<li>Anthropic have no similar play available, but they know OpenAI will announce some kind of blazing-fast inference in February, and they want to have something in the news cycle to compete with that</li>
<li>Anthropic thus hustles to put together the kind of fast inference they <em>can</em> provide: simply lowering the batch size on their existing inference stack</li>
<li>Anthropic (probably) waits until a few days before OpenAI are done with their much more complex Cerebras implementation to announce it, so it looks like OpenAI copied them</li>
</ul>
<p>Obviously OpenAI’s achievement here is more technically impressive. Getting a model running on Cerebras chips is not trivial, because they’re so weird. Training a 20B or 40B param distil of GPT-5.3-Codex that is still kind-of-good-enough is not trivial. But I commend Anthropic for finding a sneaky way to get ahead of the announcement that will be largely opaque to non-technical people. It reminds me of OpenAI’s mid-2025 sneaky introduction of the Responses API to help them <a href="https://seangoedecke.com/responses-api">conceal their reasoning tokens</a>.</p>
<h3>Is fast AI inference the next big thing?</h3>
<p>Seeing the two major labs put out this feature might make you think that fast AI inference is the new major goal they’re chasing. I don’t think it is. If my theory above is right, Anthropic don’t care <em>that</em> much about fast inference, they just didn’t want to appear behind OpenAI. And OpenAI are mainly just exploring the capabilities of their new Cerebras partnership. It’s still largely an open question what kind of models can fit on these giant chips, how useful those models will be, and if the economics will make any sense.</p>
<p>I personally don’t find “fast, less-capable inference” particularly useful. I’ve been playing around with it in Codex and I don’t like it. The usefulness of AI agents is dominated by <em>how few mistakes they make</em>, not by their raw speed. Buying 6x the speed at the cost of 20% more mistakes is a bad bargain, because most of the user’s time is spent handling mistakes instead of waiting for the model<sup id="fnref-6"><a href="https://seangoedecke.com/fast-llm-inference/#fn-6" class="footnote-ref">6</a></sup>.</p>
<p>However, it’s certainly possible that fast, less-capable inference becomes a core lower-level primitive in AI systems. Claude Code already uses <a href="https://github.com/anthropics/claude-code/issues/1098#issuecomment-2884244872">Haiku</a> for some operations. Maybe OpenAI will end up using Spark in a similar way.</p>
<p>edit: there are some good comments about this post on <a href="https://news.ycombinator.com/item?id=47022329">Hacker News</a>. First, a good <a href="https://news.ycombinator.com/item?id=47022810">correction</a>: Cerebras offers a ~355B model, GLM-4.7, at 1000 tokens per second already, so I’m wrong about Spark living in a single chip’s SRAM. Presumably they’re sharding Spark across multiple chips, like they’re doing with GLM-4.7.</p>
<p>Many commenters disagreed with me (and each other) about the performance characteristics of batching. Some <a href="https://news.ycombinator.com/item?id=47025656">said</a> that continuous batching means nobody ever waits for a bus, or that the <a href="https://news.ycombinator.com/item?id=47025997">volume</a> of requests for Anthropic models means batch wait time is negligible. Other users <a href="https://news.ycombinator.com/item?id=47023038">disagreed</a> about whether chip-to-chip communication is a bottleneck at inference time, or whether chaining chips together affects throughput.</p>
<p>I only have a layman’s understanding of continuous batching, but it seems to me that you still have to wait for a slot to become available (even if you’re not waiting for the entire previous batch to finish), so the batch size throughput/latency tradeoff still applies.</p>
<p>edit: A reader wrote in with a compelling alternate explanation for Anthropic’s fast AI mode - that they’re using more aggressive <a href="https://arxiv.org/abs/2402.12374">speculative decoding</a>, which spends more tokens but could plausibly deliver a 2.5x speedup at significantly higher costs (because many big-model rollouts are done in parallel and thrown away). I don’t know if I’m 100% convinced - I’m confident big labs are already doing speculative decoding, and the longer sequences you try the less reliable it is - but I think it’s certainly possible.</p>
<div class="footnotes">
<hr>
<ol>
<li id="fn-1">
<p>This isn’t even factoring in latency. Anthropic explicitly warns that time to first token might still be slow (or even slower), while OpenAI thinks the Spark latency is fast enough to warrant switching to a persistent websocket (i.e. they think the 50-200ms round trip time for the handshake is a significant chunk of time to first token).</p>
<a href="https://seangoedecke.com/fast-llm-inference/#fnref-1" class="footnote-backref">↩</a>
</li>
<li id="fn-2">
<p>Either in the form of the KV-cache for previous tokens, or as some big tensor of intermediate activations if inference is being pipelined through multiple GPUs. I write a lot more about this in <a href="https://seangoedecke.com/inference-batching-and-deepseek"><em>Why DeepSeek is cheap at scale but expensive to run locally</em></a>, since it explains why DeepSeek can be offered at such cheap prices (massive batches allow an economy of scale on giant expensive GPUs, but individual consumers can’t access that at all).</p>
<a href="https://seangoedecke.com/fast-llm-inference/#fnref-2" class="footnote-backref">↩</a>
</li>
<li id="fn-3">
<p>Is it a contradiction that low-batch-size means low throughput, but this fast pass system gives users much greater throughput? No. The overall throughput of the <em>GPU</em> is much lower when some users are using “fast mode”, but those user’s throughput is much higher.</p>
<a href="https://seangoedecke.com/fast-llm-inference/#fnref-3" class="footnote-backref">↩</a>
</li>
<li id="fn-4">
<p>Remember, GPUs are fast, but copying data onto them is not. Each “copy these weights to GPU” step is a meaningful part of the overall inference time.</p>
<a href="https://seangoedecke.com/fast-llm-inference/#fnref-4" class="footnote-backref">↩</a>
</li>
<li id="fn-5">
<p>Or a smaller distil of whatever more powerful base model GPT-5.3-Codex was itself distilled from. I don’t know how AI labs do it exactly, and they keep it very secret. More on that <a href="https://seangoedecke.com/ai-lab-structure">here</a>.</p>
<a href="https://seangoedecke.com/fast-llm-inference/#fnref-5" class="footnote-backref">↩</a>
</li>
<li id="fn-6">
<p>On this note, it’s interesting to point out that Cursor’s hype dropped away basically at the same time they <a href="https://cursor.com/blog/composer">released</a> their own “much faster, a little less-capable” agent model. Of course, much of this is due to Claude Code sucking up all the oxygen in the room, but having a very fast model certainly didn’t <em>help</em>.</p>
<a href="https://seangoedecke.com/fast-llm-inference/#fnref-6" class="footnote-backref">↩</a>
</li>
</ol>
</div>]]></description>
      <pubDate>Sat, 14 Feb 2026 23:29:02 +0000</pubDate>
      <link>https://seangoedecke.com/fast-llm-inference/</link>
      <dc:creator>seangoedecke.com RSS feed</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5118676558</guid>
    </item>
    <item>
      <title><![CDATA[We mourn our&nbsp;craft]]></title>
      <description><![CDATA[<div class="entry"> <p>I didn’t ask for this and neither did you.</p>
<p>I didn’t ask for a robot to consume every blog post and piece of code I ever wrote and parrot it back so that some hack could make money off of it.</p>
<p>I didn’t ask for the role of a programmer to be reduced to that of a glorified TSA agent, reviewing code to make sure the AI didn’t smuggle something dangerous into production.</p>
<p>And yet here we are. The worst fact about these tools is that they work. They can write code better than you or I can, and if you don’t believe me, wait six months.</p>
<p>You could abstain out of moral principle. And that’s fine, especially if you’re at the tail end of your career. And if you’re at the beginning of your career, you don’t need me to explain any of this to you, because you already use Warp and Cursor and Claude, with ChatGPT as your therapist and pair programmer and maybe even your lover. This post is for the 40-somethings in my audience who don’t realize this fact yet.</p>
<p>So as a senior, you could abstain. But then your junior colleagues will eventually code circles around you, because they’re wearing bazooka-powered jetpacks and you’re still riding around on a fixie bike. Eventually your boss will start asking why you’re getting paid twice your zoomer colleagues’ salary to produce a tenth of the code.</p>
<p>Ultimately if you have a mortgage and a car payment and a family you love, you’re going to make your decision. It’s maybe not the decision that your younger, more idealistic self would want you to make, but it does keep your car and your house and your family safe inside it.</p>
<p>Someday years from now we will look back on the era when we were the last generation to code by hand. We’ll laugh and explain to our grandkids how silly it was that we typed out JavaScript syntax with our fingers. But secretly we’ll miss it.</p>
<p>We’ll miss the feeling of holding code in our hands and molding it like clay in the caress of a master sculptor. We’ll miss the sleepless wrangling of some odd bug that eventually relents to the debugger at 2 AM. We’ll miss creating something we feel proud of, something true and right and good. We’ll miss the satisfaction of the artist’s signature at the bottom of the oil painting, the GitHub repo saying “I made this.”</p>
<p>I don’t celebrate the new world, but I also don’t resist it. The sun rises, the sun sets, I orbit helplessly around it, and my protests can’t stop it. It doesn’t care; it continues its arc across the sky regardless, moving but unmoved.</p>
<p>If you would like to grieve, I invite you to grieve with me. We are the last of our kind, and those who follow us won’t understand our sorrow. Our craft, as we have practiced it, will end up like some blacksmith’s tool in an archeological dig, a curio for future generations. It cannot be helped, it is the nature of all things to pass to dust, and yet still we can mourn. Now is the time to mourn the passing of our craft.</p> </div>]]></description>
      <pubDate>Sat, 07 Feb 2026 16:52:07 +0000</pubDate>
      <link>https://nolanlawson.com/2026/02/07/we-mourn-our-craft/</link>
      <dc:creator>Pages</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5110813803</guid>
    </item>
    <item>
      <title><![CDATA[It's hard to give a shit]]></title>
      <description><![CDATA[ 

<p>I’m finding it pretty much impossible to give a shit about anything web dev or tech related right now.</p>

<p>Today, I want to rant for a minute, and then offer some pragmatic advice.</p>

<h2 id="everything-sucks">Everything sucks</h2>

<p>The world has been pretty bad for a while.</p>

<p>The juggernaut of environmental collapse. The flood of refugees into Europe (and their horrific treatment by the countries that colonized and destabilized their home countries). The assault on Ukraine. The genocide of Palestine. The civil war in Sudan.</p>

<p>And most recently, the fascist takeover of my country, the United States, and the rampant abuses committed by the ICE gestapo.</p>

<p>Why would I give a single fuck about the best way to render content with web components or the latest tech drama when Nazi bootlickers are literally kicking down doors without warrants and kidnapping my neighbors?</p>

<p>But under capitalism, it’s all “dance, monkey, dance.”</p>

<p>You’re expected to wake up every day, but on a fucking smile, and grind out more code widgets to keep capital flowing up so that crumbs can trickle down.</p>

<p>Ok, pitty party over. Let’s talk tactics.</p>

<h2 id="give-a-shit-about-people">Give a shit about people</h2>

<p>One of the most anti-fascist things you can do is give a shit about people.</p>

<p>Like, truly, genuinely care.</p>

<p>Learn your neighbors names. Buy tents and sleeping bags for the unhoused. Start a little free pantry or community fridge. Buy groceries or deliver meals to people in your town who can’t drive. Shovel driveways.</p>

<p>Throw parties. Make boring infrastructure pretty with stickers or chalk or paint.</p>

<p>Fascism hates joy.</p>

<p>You can also <a href="https://dansinker.com/posts/2025-11-21-whistle-up/">buy and distribute whistles</a> and generally <a href="https://gomakethings.com/guides/anarchy-socialism/dirt-in-a-cog/">be dirt in the cog</a> whenever possible.</p>

<h2 id="help-people-attack-systems">Help people. Attack systems.</h2>

<p>This is a lesson straight from <a href="https://gomakethings.com/dungeon-crawler-carl/">the <em>Dungeon Crawler Carl</em> book series</a>.</p>

<p>First, recognize that you can’t save everyone, but you <em>can</em> fight like hell to help the people you can.</p>

<p>Second, understand that systems are the real enemy, and often shitty people are doing bad shit <em>because</em> shitty systems force or encourage them too.</p>

<p>That doesn’t mean you should go all kumbaya and hug it out with Nazis. But recognize that until you destroy the system, cutting off one head of the hydra just causes two more to grow.</p>

<p>And if you’re feeling strong, and even more so if you’re not, repeat Carl’s mantra…</p>

<blockquote>
<p>You will not break me, fuck you all, I will break you.</p>
</blockquote>

<p>Stay strong, friends! ✊</p>
<p><em><strong>Like this?</strong> A <a href="https://members.gomakethings.com">Lean Web Club</a> membership is the best way to support my work and help me create more free content.</em></p>]]></description>
      <pubDate>Thu, 22 Jan 2026 14:30:00 +0000</pubDate>
      <link>https://gomakethings.com/its-hard-to-give-a-shit/</link>
      <dc:creator>Go Make Things</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5091538069</guid>
    </item>
    <item>
      <title><![CDATA[]]></title>
      <description><![CDATA[
        
      <h1>How to know if that job will crush your soul</h1>
<p>Last week, we talked about one huge question, “<a href="https://www.anildash.com/2026/01/05/a-tech-career-in-2026/">How the hell are you supposed to have a career in tech in 2026?</a>” That’s pretty specific to this current moment, but there are some timeless, more perennial questions I've been sharing with friends for years that I wanted to give to all of you. They're a short list of questions that help you judge whether a job that you’re considering is going to crush your soul or not.</p>
<p>Obviously, not everyone is going to get to work in an environment that has perfect answers to all of these questions; a lot of the time, we’re lucky just to get a place to work at all. But these questions are framed in this way to encourage us all to aspire towards roles that enable us to do our best work, to have the biggest impact, and to live according to our values.</p>
<h2>Seven Questions to Check If A Job Will Crush Your Soul</h2>
<ul>
<li>If what you do succeeds, will the world be better?</li>
</ul>
<p>This question originally started for me when I would talk to people about new startups, where people were judging the basic idea of the product or the company itself, but it actually applies to <em>any</em> institution, at <em>any</em> size. If the organization that you’re considering working for, or the team you’re considering joining, is able to achieve their stated goals, is it ultimately going to have a positive effect? Will you be proud of what it means? Will the people you love and care about respect you for making that choice, and will those with the least to gain feel like you’re the kind of person who cares about their impact on the world?</p>
<ul>
<li>Whose money do they have to take to stay in business?</li>
</ul>
<p>Where does the money in the organization <em>really</em> come from? You need to know this for a lot of reasons. First of all, you need to be sure that <em>they</em> know the answer. (You’d be surprised how often that’s not the case!) Even if they do know the answer, it may make you realize that those customers are not the people whose needs or wants you’d like to spend most of your waking hours catering to. This goes beyond the simple basics of the business model — it can be about whether they're profitable or not, and what the corporate ownership structure is like.</p>
<p>It’s also increasingly common for companies to mistake those who are <em>investing</em> in a company with those who are their <em>customers</em>. But there’s a world of difference between those who are paying you, and those who you have to pay back tenfold. Or thousandfold.</p>
<p>The same goes for nonprofits — do you know who has to stay happy and smiling in order for the institution to stay stable and successful? If you know those answers, you'll be far more confident about the motivations and incentives that will drive key decisions within the organization.</p>
<ul>
<li>What do you have to believe to think that they’re going to succeed? In what way does the world have to change or not change?</li>
</ul>
<p>Now we’re getting a little bit deeper into thinking about the systems that surround the organization that you’re evaluating. Every company, every institution, even every small team, is built around a set of invisible assumptions. Many times, they’re completely reasonable assumptions that are unlikely to change in the future. But <em>sometimes</em>, the world you’re working in is about to shift in a big way, or things are built on a foundation that’s speculative or even unrealistic.</p>
<p>Maybe they're assuming there aren't going to be any big new competitors. Perhaps they think they'll always remain the most popular product in their category. Or their assumptions could be about the stability of the rule of law, or a lack of corruption — more fundamental assumptions that they've never seen challenged in their lifetime or in their culture, but that turn out to be far more fragile than they'd imagined.</p>
<p>Thinking through the context that everyone is sharing, and reflecting on whether they’re really planning for any potential disruptions, is an essential part of judging the psychological health of an organization. It’s the equivalent of a person having self-awareness, and it’s just as much of a red flag if it’s missing.</p>
<ul>
<li>What’s the lived experience of the workers there whom you trust? Do you have evidence of leaders in the organization making hard choices to do the right thing?</li>
</ul>
<p>Here is how we can tell the culture and character of an organization. If you’ve got connections into the company, or a backchannel to workers there, finding out as much information as you can about the real story of its working conditions is often one of the best ways of understanding whether it’s a fit for your needs. Now, people can always have a bad day, but overall, workers are usually very good at providing helpful perspectives about their context.</p>
<p>And more broadly, if people can provide examples of those in power within an organization <em>using</em> that power to take care of their workers or customers, or to fight for the company to be more responsible, then you’ve got an extremely positive sign about the health of the place even before you’ve joined. It’s vital that these be stories you are able to find and discover on your own, not the ones amplified by the institution itself for PR purposes.</p>
<ul>
<li>What were you wrong about?</li>
</ul>
<p>And here we have perhaps one of the easiest and most obvious ways to judge the culture of an organization. This is even a question you can ask people while you’re in an interview process, and you can judge their responses to help form your opinion. A company, and <em>leadership culture</em>, that can change its mind when faced with new information and new circumstances is much more likely to adapt to challenges in a healthy way. (If you want to be nice, phrase it as "What is a way in which the company has evolved or changed?")</p>
<ul>
<li>Does your actual compensation take care of what you need for all of your current goals and needs —&nbsp;from day one?</li>
</ul>
<p>This is where we go from the abstract and psychological goals to the practical and everyday concerns: can you pay your bills? The phrasing and framing here is very intentional: <em>are they really going to pay you enough</em>? I ask this question very specifically because you’d be surprised how often companies actually dance around this question, or how often we trick ourselves into hearing what we <em>want</em> to hear as the answer to this question when we’re in the exciting (or stressful) process of considering a new job, instead of looking at the facts of what’s actually written in black-and-white on an offer letter.</p>
<p>It's also important not to get distracted with potential, even if you're optimistic about the future. Don’t listen to promises about what might happen, or descriptions of what’s possible if you advance in your role. Think about what your real life will be like, after taxes, if you take the job that they’ve described.</p>
<ul>
<li>Is the role you’re being hired into one where you can credibly advance, and where there’s sufficient resources for success?</li>
</ul>
<p>This is where you can apply your optimism in a practical way: can the organization accurately describe how your career will proceed within the company? Does it have a specific and defined trajectory, or does it involve ambiguous processes or changes in teams or departments? Would you have to lobby for the support of leaders from other parts of the organization? Would making progress require acquiring new skills or knowledge? Have they committed to providing you with the investment and resources required to learn those skills?</p>
<p>These questions are essential to understand, because lacking these answers can lead to an ugly later realization that even an initially-exciting position may turn out to be a dead-end job over time.</p>
<h3>Towards better working worlds</h3>
<p>Sometimes it can really feel like the deck is stacked against you when you're trying to find a new job. It can feel even worse to be faced with an opportunity and have a nagging sense that something is <em>not quite right</em>. Much of the time, that feeling comes from the vague worry that we're taking a job that is going to make us miserable.</p>
<p>Even in a tough job market, there are some places that are trying to do their best to treat people decently. In larger organizations, there are often pockets of relative sanity, led by good leaders, who are trying to do the right thing. It can be a massive improvement in quality of life if you can find these places and use them as foundations for the next stage of your career.</p>
<p>The best way to navigate towards these better opportunities is to be systematic when evaluating all of your options, and to hold out for as high standards as possible when you're out there looking. These seven questions give you the tools to do exactly that.</p>

    
      ]]></description>
      <pubDate>Mon, 12 Jan 2026 00:00:00 +0000</pubDate>
      <link>https://anildash.com/posts/2026-01-12-seven-questions-to-not-crush-your-soul/</link>
      <dc:creator>Anil Dash</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5080829904</guid>
    </item>
    <item>
      <title><![CDATA[What a year of solar and batteries saved us in 2025 | Hacker News]]></title>
      <description><![CDATA[<div class="commtext c00">I've been following a story where Elon Musk's xAI is building an 88 acre solar farm next to its Colossus data center near Memphis TN after public outrage due to running 35 methane gas turbines without a permit, which increased NOx emissions enough to allegedly impact health:<p><a href="https://techcrunch.com/2026/01/12/trumps-epa-plans-to-ignore-health-effects-of-air-pollution/">https://techcrunch.com/2026/01/12/trumps-epa-plans-to-ignore...</a></p><p></p><pre><code>  88 acres = 356,124 m2
  4.56 kWh/m2 per day solar insolation (4.5 is typical for much of the US)
  4.56 kWh/m2 per day \* 356,124 m2 = 1,623,924 kWh/day = 67,664 kW = 67.66 MW average
  1000 W/m2 \* 356,124 m2 = 356 MW peak
</code></pre>
They're estimating that they'll get 30 MW on average from that, but I'd estimate more like 15 MW at a solar panel efficiency just over 20%. Still, the total cost for that power should be less than for turbines, since solar is now the cheapest electricity other than hypothetical nuclear (assuming an ideal breeder or waste-consuming reactor and excluding mining/waste externalities/insurance).<p></p><p>30 MW is still only 10% of the the 300 MW used by the data center. But there's lots of land out there, so roughly 1000 acres per data center doesn't seem that extreme to me. That's a 4 km2 or 1.5 mile2 lot, or about 2 km or 1.25 miles on a side.</p><p>Basically every GPU server uses 1 kW (about 1 space heater), which puts into perspective just how much computing power is available at these data centers. Running a GPU continuously at home would need 24 kWh/day, so with &gt; 20% efficiency panels that's 4.5*.2 = 0.9 kWh/m2 per day, so 26.67 m2, so at 2 m2 per commercial solar panel and assuming that my math is right: that's about 14 panels considering nights and seasons.</p><p>It's interesting to think just how many panels it takes to run a GPU or space heater continuously, even when they put out 500 W or 250 W/m2 peak. And how cheap that electricity really is when it's sold for on the order of $0.15 per kWh, or $3.60 per day.</p><p>I've found that the very best way to save on your electric bill is to have a few south-facing slider doors and windows, which is like running a space heater every square meter of window. There's just no way that any other form of power generation can compete with that. Also, I feel that we're doing it wrong with solar. This analysis shows just how much better alternatives like trough solar and concentrated solar (mirrors towards solar panels) might be cost-wise. On an ironic note, solar panels now cost less than windows by area, and probably mirrors.</p></div>]]></description>
      <pubDate>Tue, 13 Jan 2026 19:47:15 +0000</pubDate>
      <link>https://news.ycombinator.com/item?id=46602532</link>
      <dc:creator>Pages</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5081708856</guid>
    </item>
    <item>
      <title><![CDATA[Level Devil. This game is hilarious and diabolical....]]></title>
      <description><![CDATA[
        <a href="https://poki.com/en/g/level-devil">Level Devil</a>. This game is hilarious and diabolical.

        

        

         <p>💬 <a href="https://kottke.org/25/12/0047934-level-devil-this-game-is">Join the discussion on kottke.org</a> →</p>

    ]]></description>
      <pubDate>Tue, 02 Dec 2025 20:51:16 +0000</pubDate>
      <link>https://kottke.org/25/12/0047934-level-devil-this-game-is</link>
      <dc:creator>Starred Articles</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5068231093</guid>
    </item>
    <item>
      <title><![CDATA[Year 10]]></title>
      <description><![CDATA[
        <p>I distinctly remember waking up early, on January 1st, 2017, going downstairs with my laptop, making myself some coffee, and coding what ended up being the first iteration of this blog. I wanted to write weekly updates to hold myself accountable. I failed spectacularly. Reading that post from 9 years ago made  me smile: 27-year-old me wanted to cut down on distractions and get the habit of waking up early back. Guess what? 36-year-old me also wants to cut down distractions and get the habit of waking up early back. Some things apparently never change.</p>
<p>On the first day of 2017, I published my first blog post; I’m posting the 620th. I also sent out the 1st edition of <a href="https://buttondown.com/dealgorithmed">Dealgorithmed</a> because I guess I’m a sucker for starting projects on the first day of the year. It does make it easy to remember when there’s an anniversary to celebrate, though.</p>
<p>I genuinely think this is going to be my last digital project. I said it many times before, but this time it does feel different. I don’t know about you, but I’m seriously starting to feel digital fatigue. I’m cruising towards my 15th year as a freelancer—I’ll officially hit that milestone on July 1st, 2027, even though I started working solo at the end of 2011—and I find myself reflecting a lot on the possibility of completely changing career and doing something completely different that has nothing to do with the digital world. Time will tell if this stays an idea or it becomes a concrete plan.</p>
<p>I do know that no matter what I end up doing, I’ll still continue posting on this blog. Because blogging is fun, it’s therapeutic, and more people should do it. Plus, I want to become one of those oldheads with a blog that is 30 years old!</p>        <hr>
        <p>Thank you for keeping RSS alive. You're awesome.</p>
        <p><a href="mailto:hello@manuelmoreale.com">Email me</a> ::
        <a href="https://manuelmoreale.com/guestbook">Sign my guestbook</a> :: 
        <a href="https://ko-fi.com/manuelmoreale">Support for 1$/month</a> :: 
        <a href="https://manuelmoreale.com/supporters">See my generous supporters</a> :: 
        <a href="https://buttondown.email/peopleandblogs">Subscribe to People and Blogs</a></p>
    ]]></description>
      <pubDate>Thu, 01 Jan 2026 10:35:00 +0000</pubDate>
      <link>https://manuelmoreale.com/thoughts/year-10</link>
      <dc:creator>Manuel Moreale — Everything Feed</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5068127517</guid>
    </item>
    <item>
      <title><![CDATA[::target-text: An easy way to style text fragments]]></title>
      <description><![CDATA[<p>You’re reading a great blog post. You want to share it with your friend but instead of getting them to read the whole thing, you really just want to highlight a few key sentences and have them go directly to that section of the page. That’s what text fragments are for.</p>
<p>As a user, you can highlight any section of text on a page and right click to make it a text fragment. In Safari, that means right clicking and selecting “Copy Link with Highlight” from the menu and getting a url that will highlight the text fragment when the page loads.</p>
<p>The default highlighting gives you a pale yellow highlight under the fragment text, like this:</p>
<figure><img loading="lazy" decoding="async" src="https://webkit.org/wp-content/uploads/Screenshot-2025-12-02-at-12.34.03-PM.png" alt="Lorem ipsum blog post with pale yellow highlight for text fragment." width="2522" height="1504" class="preserve-color wp-image-17289"></figure>
<p>You can click on <a href="https://webkit.org/blog/17628/target-text-an-easy-way-to-style-text-fragments/#:~:text=As%20a%20user%2C%20you%20can%20highlight%20any%20section%20of%20text%20on%20a%20page%20and%20right%20click%20to%20make%20it%20a%20text%20fragment.%20In%20Safari%2C%20that%20means%20right%20clicking%20and%20selecting%20“Copy%20Link%20with%20Highlight”%20from%20the%20menu%20and%20getting%20a%20url%20that%20will%20highlight%20the%20text%20fragment%20when%20the%20page%20loads.">this link</a> to see for yourself how it works.</p>
<p>That’s the user experience. But what about the developer experience? Is there something we developers can do to customize that experience for our users a bit more? Actually, there is! We’ll use the <code>::target-text</code> pseudo-element to help us style our text fragment.</p>
<p>In your CSS file, use the <code>::target-text</code> pseudo-element and style the text with whatever properties you wish, like this:</p>
<pre><code>::target-text {
  background-color: blue;
  color: white;
}
</code></pre>
<p>That’ll get you this result:</p>
<figure><img loading="lazy" decoding="async" src="https://webkit.org/wp-content/uploads/Screenshot-2025-12-02-at-12.38.54-PM.png" alt="Lorem ipsum blog post with white text on blue highlight for text fragment." width="2522" height="1504" class="preserve-color wp-image-17289"></figure>
<p>So if you want to decide how a text fragment looks to your users, take advantage of <code>::target-text</code> and own the user’s text fragment experience. It’s fully supported in all browsers.</p>
<p>If you enjoyed this kind of bite-size content, let me know. You can reach me, Saron Yitbarek, on <a href="https://bsky.app/profile/saron.bsky.social">BlueSky</a>, or reach out to our other evangelists — Jon Davis, on <a href="https://bsky.app/profile/jondavis.bsky.social">Bluesky</a> / <a href="https://mastodon.social/@jondavis">Mastodon</a>, and Jen Simmons, on <a href="https://bsky.app/profile/jensimmons.bsky.social">Bluesky</a> / <a href="https://front-end.social/@jensimmons">Mastodon</a>. You can also follow WebKit <a href="https://www.linkedin.com/in/apple-webkit/">on LinkedIn</a>. If you find a bug or problem, please file a <a href="https://bugs.webkit.org/">WebKit bug report</a>.</p>
]]></description>
      <pubDate>Thu, 04 Dec 2025 19:21:31 +0000</pubDate>
      <link>https://webkit.org/blog/17628/target-text-an-easy-way-to-style-text-fragments/</link>
      <dc:creator>Starred Articles</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5064502117</guid>
    </item>
    <item>
      <title><![CDATA[2025 Podcast Update]]></title>
      <description><![CDATA[<p>While I was moving content to the new version of my site and got to <a href="https://stegrainer.com/journal/2019/my-favorite-podcasts">My Favorite Podcasts</a> post from 2019, it occurred to me that some of them had changed. I’ve added quite a few and stopped listening to a couple others. Without further ado, here are the new shows I have added to my rotation:</p>
<ul class="item-list">
		<li>
		<figure>
		<a href="https://critrole.com/campaign-1-podcast/">		<img src="https://stegrainer.com/-/img/2025/podcasts/critical-role.jpg" alt="">
		</a>	</figure>
		<div class="item-detail">
				<h5>
			<a href="https://critrole.com/campaign-1-podcast/">				Critical Role
			</a>		</h5>
						<p>This is by far the biggest addition to my listening since 2019. I started shortly before the pandemic and practically tore through the first couple of campaigns until I caught up early in their third campaign. This has consumed the majority of my podcast time over the past few years as I started at the beginning and listened to over 100+ 3-5 hour long episodes each across three campaigns. I adore listening to these seasoned voice actors live play Dungeons &amp; Dragons.</p><p>It’s hard to say where to start because each of the original three campaigns is very different, but all of them are fun. The first 10-20 episodes of campaign 1 can be hard to listen to or watch because they were still learning how to produce them. They did just start a fourth campaign with a new dungeon master (Brennan Lee Mulligan), new world, and an expanded cast so if you’re new to Critical Role, maybe start there. You can also <a href="https://www.youtube.com/c/criticalrole">watch on YouTube</a> or their own streaming service <a href="https://beacon.tv">Beacon</a>.</p>
			</div>
</li>
		<li>
		<figure>
		<a href="https://www.wbur.org/podcasts/endlessthread">		<img src="https://stegrainer.com/-/img/2025/podcasts/endless-thread.jpg" alt="">
		</a>	</figure>
		<div class="item-detail">
				<h5>
			<a href="https://www.wbur.org/podcasts/endlessthread">				Endless Thread
			</a>		</h5>
						<p>I discovered this podcast from Ben Brock Johnson and Amory Sivertson through a recent mini-crossover with 99% Invisible called Hidden Levels where they talked about video games. So far I’ve dipped a toe in here and there (selecting specific episodes rather than blanket subscribing), but I really enjoy it.</p>
			</div>
</li>
		<li>
		<figure>
		<a href="https://www.lorepodcast.com">		<img src="https://stegrainer.com/-/img/2025/podcasts/lore.jpg" alt="">
		</a>	</figure>
		<div class="item-detail">
				<h5>
			<a href="https://www.lorepodcast.com">				Lore
			</a>		</h5>
						<p><i>Lore</i> with Aaron Mahnke had been on my list to check out for awhile. It’s a fun way to explore the more mysterious, weird, and scary stories in history. This is another I’ve only just started listening to, but so far I’m really enjoying it. I love history podcasts because they can be good creative kindling for D&amp;D ideas, and these stories in particular spark so many ideas.</p>
			</div>
</li>
		<li>
		<figure>
		<a href="https://podcasts.apple.com/us/podcast/the-rest-is-history/id1537788786">		<img src="https://stegrainer.com/-/img/2025/podcasts/the-rest-is-history.jpg" alt="">
		</a>	</figure>
		<div class="item-detail">
				<h5>
			<a href="https://podcasts.apple.com/us/podcast/the-rest-is-history/id1537788786">				The Rest is History
			</a>		</h5>
						<p>Speaking of history, <i>The Rest is History</i> is a great introduction to lots of random stories in history. The hosts – Tom Holland (not the Spider-Man star) and Dominic Sandbrook – explore specific aspects of important historical moments. Some they revisit regularly (like the French Revolution, the Roman Empire, and the World Wars) while others get short bursts in between. They enjoy poking fun at each other and generally make it fun to learn about history. My one complaint is that it's very heavily focused on western history. I would love recommendations for history podcasts for other parts of the world in particular.</p>
			</div>
</li>
		<li>
		<figure>
		<a href="https://shoptalkshow.com">		<img src="https://stegrainer.com/-/img/2025/podcasts/shop-talk-show.jpg" alt="">
		</a>	</figure>
		<div class="item-detail">
				<h5>
			<a href="https://shoptalkshow.com">				ShopTalk
			</a>		</h5>
						<p>I enjoy the camaraderie of Dave Rupert and Chris Coyier talking about making websites. I feel like I learn something new about the web every time I listen, and they both have a fun friendly banter that’s easy to enjoy. They’ll occasionally talk about something they see on the screen together, which can be hard for an audio podcast, but they’re generally pretty self-aware of when they do that. (A lot of other dev and design podcasts struggle with this, too.)</p>
			</div>
</li>
		<li>
		<figure>
		<a href="https://podcast.wakeupexcited.show">		<img src="https://stegrainer.com/-/img/2025/podcasts/wake-up-excited.jpg" alt="">
		</a>	</figure>
		<div class="item-detail">
				<h5>
			<a href="https://podcast.wakeupexcited.show">				Wake Up Excited
			</a>		</h5>
						<p><br>This new podcast from Brad Frost is really inspiring. He chats with creative people in all kinds of fields about what gets them excited and keeps them going.</p>
			</div>
</li>
	</ul>
<h4>Give me recommendations!</h4><p>What podcasts do you enjoy? Are you a regular weekly show kind of person? A limited run series on a specific topic? Do you like deep dives on esoteric subjects? Stories? Comedy? Weekly news? I am always interested in new things to listen to so <a href="https://stegrainer.com/contact">send your favorites</a> my way. Or write about them on your own site and <a href="https://mastodon.social/@stegrainer">share the link</a>!</p>]]></description>
      <pubDate>Sun, 28 Dec 2025 14:04:00 +0000</pubDate>
      <link>https://stegrainer.com/journal/2025/2025-podcast-update</link>
      <dc:creator>Starred Articles</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5064213401</guid>
    </item>
    <item>
      <title><![CDATA[]]></title>
      <description><![CDATA[
<p>Three good thoughts <a href="https://fs.blog/brain-food/december-28-2025/">from Shane Parrish</a>:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>Short-term results come from intensity. Long-term results come from consistency.</p>



<p>**</p>



<p>Ninety percent of success can be boiled down to consistently doing the obvious thing for an uncommonly long period of time without convincing yourself that you’re smarter than you are.</p>



<p>***</p>



<p>Working smart isn’t the opposite of working hard. It’s the result of working hard.</p>



<p>You have to put in the hours before you can see the shortcuts. You have to learn the details before you can know which ones matter.</p>



<p>You have to do the work wrong many times before you discover how to do it right.</p>
</blockquote>
]]></description>
      <pubDate>Wed, 31 Dec 2025 23:06:36 +0000</pubDate>
      <link>https://chriscoyier.net/2025/12/31/13094/</link>
      <dc:creator>Chris Coyier</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5067523618</guid>
    </item>
    <item>
      <title><![CDATA[New Years Bingo]]></title>
      <description><![CDATA[
<p><img src="https://potch.me/2025/bingonewyears-w_640-f_jpg.jpg" alt="A Screenshot of bingonewyears.com, showing a grid of goals"></p>
<p>Last year, my friend Stacy introduced our friend group to the idea of a "New Years Bingo Card": the idea being to create smaller, more achievable things you want to do in the coming year, with a focus on positive/fun ideas. It was a fun NYE activity and so my buddy Andrew and I (who built <a href="https://listed.fun">Listed</a> together) put together a "New Years Bingo" webapp! You can play it at <a href="https://bingonewyears.com/">bingonewyears.com</a>. You can set "yes/no" goals, or create counters/monthly goals.</p>
<p>We used <a href="https://svelte.dev/">svelte</a> for its speed of development, small output size, and nice static site generation. All the goals are saved to local storage and never sent elsewhere- the plan is to add both a print stylesheet as well as a way to move the data around!</p>
]]></description>
      <pubDate>Wed, 31 Dec 2025 02:07:51 +0000</pubDate>
      <link>https://potch.me/2025/new-years-bingo.html</link>
      <dc:creator>potch has a website</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5067279225</guid>
    </item>
    <item>
      <title><![CDATA[Software engineers should be a little bit cynical]]></title>
      <description><![CDATA[<div><section><p>A lot of my readers <a href="https://lobste.rs/c/ch8tn0">call</a> <a href="https://news.ycombinator.com/item?id=46085088">me</a> <a href="https://news.ycombinator.com/item?id=46082989">a cynic</a> when I say things like “you should do things that <a href="https://www.seangoedecke.com/how-to-ship">make your manager happy</a>” or “big tech companies <a href="https://www.seangoedecke.com/bad-code-at-big-companies">get to decide</a> what projects you work on”. Alex Wennerberg put the “Sean Goedecke is a cynic” case well in his post <a href="https://alexwennerberg.com/blog/2025-11-28-engineering.html"><em>Software Engineers Are Not Politicians</em></a>. Here are some excerpts:</p>
<blockquote>
<p>I have no doubt that [Sean’s] advice is quite effective for navigating the upper levels of an organization dedicated to producing a large, mature software product. But what is lost is any sort of conception of value. Is it too naive to say that engineers are more than “tools in a political game”, they are specialized professionals whose role is to apply their expertise towards solving meaningful problems?</p>
</blockquote>
<blockquote>
<p>The irony is that this kind of thinking destroys a company’s ability to actually make money … the idea that engineers should begin with a self-conception of doing what their manager tells them to is, to me, very bleak. It may be a good way to operate smoothly within a bureaucratic organization, and of course, one must often make compromises and take direction, but it is a bad way to do good work.</p>
</blockquote>
<p>I can see why people would think this way. But I <em>love</em> working in big tech companies! I do see myself as a professional solving meaningful problems. And I think navigating the organization to put real features or improvements in the hands of users is an excellent way - maybe the best way - to do good work.</p>
<p>Why do I write such cynical posts, then? Well, I think that a small amount of cynicism is necessary in order to think clearly about how organizations work, and to avoid falling into the trap of being overly cynical. In general, I think <strong>good engineers ought to be a little bit cynical</strong>.</p>
<h3>The idealist view is more cynical than idealists think</h3>
<p>One doctrinaire “idealist” view of software engineering goes something like this. I’m obviously expressing it in its most lurid form, but I do think many people believe this more or less literally:<sup id="fnref-1"><a href="https://www.seangoedecke.com/a-little-bit-cynical/#fn-1" class="footnote-ref">1</a></sup></p>
<blockquote>
<p>We live in a late-stage-capitalist hellscape, where large companies are run by aspiring robber barons who have no serious convictions beyond desiring power. All those companies want is for obedient engineering drones to churn out bad code fast, so they can goose the (largely fictional) stock price. Meanwhile, end-users are left holding the bag: paying more for worse software, being hassled by advertisements, and dealing with bugs that are unprofitable to fix. The only thing an ethical software engineer can do is to try and find some temporary niche where they can defy their bosses and do real, good engineering work, or to retire to a hobby farm and write elegant open-source software in their free time.</p>
</blockquote>
<p>When you write it all out, I think it’s clear to see that this is <em>incredibly</em> cynical. At the very least, it’s a cynical way to view your coworkers and bosses, who are largely people like you: doing a job, balancing a desire to do good work with the need to please their own bosses. It’s a cynical way to view the C-staff of a company. I think it’s also inaccurate: from my limited experience, the people who run large tech companies really do want to deliver good software to users.</p>
<p>It’s idealistic only in the sense that it does not accept the need for individual software engineers to compromise. According to this view, <em>you</em> never need to write bad software. No matter how hard the company tells you to compromise and just get something out, you’re morally required to plant your feet and tell them to go to hell. In fact, by doing so, you’re taking a stand against the general degeneration of the modern software world. You’re protecting - unsung, like Batman - the needs of the end-user who will never know you exist.</p>
<p>I can certainly see the appeal of this view! But I don’t think it’s an <em>idealistic</em> appeal. It comes from seeing the world as fundamentally corrupted and selfish, and believing that real positive change is impossible. In other words, <strong>I think it’s a <em>cynical</em> appeal.</strong></p>
<h3>The cynical view is more idealistic than idealists think</h3>
<p>I don’t see a hard distinction between engineers being “tools in a political game” and professionals who solve meaningful problems. In fact, I think that in practice <strong>almost all meaningful problems are solved by playing political games</strong>.</p>
<p>There are very few problems that you can solve entirely on your own. Software engineers encounter more of these problems than average, because the nature of software means that a single engineer can have huge leverage by sitting down and making a single code change. But in order to make changes to large products - for instance, to make it possible for GitHub’s 150M users to <a href="https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/writing-mathematical-expressions">use LaTeX in markdown</a> - you need to coordinate with many other people at the company, which means you need to be involved in politics.</p>
<p>It is just a plain fact that software engineers are not the movers and shakers in large tech organizations. They do not set the direction of the company. To the extent that they have political influence, it’s in how they translate the direction of the company into specific technical changes. But <strong>that is actually quite a lot of influence!</strong> </p>
<p>Large tech companies serve hundreds of millions (or billions) of users. Small changes to these products can have a massive positive or negative effect in the aggregate. As I see it, choosing to engage in the messy, political process of making these changes - instead of washing your hands of it as somehow impure - is an act of idealism. </p>
<p>I think the position of a software engineer in a large tech company is similar to people who go into public service: idealistically hoping that they can do some good, despite knowing that they themselves will never set the broad strokes of government policy.</p>
<p>Of course, big-tech software engineers are paid far better, so many people who go into this kind of work in fact are purely financially-motivated cynics. But I’m not one of them! I think it’s possible, by doing good work, to help steer the giant edifice of a large tech company for the better.</p>
<h3>Cynicism as inoculation</h3>
<p>Cynical writing is like most medicines: the dose makes the poison. A healthy amount of cynicism can serve as an inoculation from being overly cynical.</p>
<p>If you don’t have an slightly cynical explanation for why engineers write bad code in large tech companies - such as the one I write about <a href="https://www.seangoedecke.com/bad-code-at-big-companies">here</a> - you risk adopting an overly cynical one. For instance, you might think that big tech engineers are being <a href="https://news.ycombinator.com/item?id=46082989">deliberately demoralized</a> as part of an anti-labor strategy to prevent them from unionizing, which is nuts. Tech companies are simply not set up to engage in these kind of conspiracies.</p>
<p>If you don’t have a slightly cynical explanation for why large tech companies sometimes make inefficient decisions - such as <a href="https://www.seangoedecke.com/seeing-like-a-software-company">this one</a> - you risk adopting an overly cynical one. For instance, you might think that tech companies are full of incompetent <a href="https://news.ycombinator.com/item?id=46133179">losers</a>, which is simply not true. Tech companies have a normal mix of strong and <a href="https://www.seangoedecke.com/weak-engineers">weak engineers</a>.</p>
<h3>Final thoughts</h3>
<p><strong>Idealist writing is massively over-represented in writing about software engineering</strong>. There is no shortage of books or blog posts (correctly) explaining that we ought to value good code, that we ought to be kind to our colleagues, that we ought to work on projects with positive real-world impact, and so on. There <em>is</em> a shortage of writing that accurately describes how big tech companies operate.</p>
<p>Of course, cynical writing can harm people: by making them sad, or turning them into bitter cynics. But <strong>idealist writing can harm people too</strong>. There’s a whole generation of software engineers who came out of the 2010s with a <em>factually incorrect</em> model of how big tech companies work, and who are effectively being fed into the woodchipper in the 2020s. They would be better off if they internalized a correct model of how these companies work: not just less likely to get into trouble, but better at achieving their own idealist goals<sup id="fnref-2"><a href="https://www.seangoedecke.com/a-little-bit-cynical/#fn-2" class="footnote-ref">2</a></sup>.</p>
</section><p>If you liked this post, consider <a href="https://buttondown.com/seangoedecke">subscribing</a> to email updates about my new posts, or <a href="https://news.ycombinator.com/submitlink?u=https://www.seangoedecke.com/a-little-bit-cynical/&amp;t=Software engineers should be a little bit cynical">sharing it on Hacker News</a>. Here's a preview of a related post that shares tags with this one.</p><blockquote><p>You can't design software you don't work on</p><div><p>Only the engineers who work on a large software system can meaningfully participate in the design process. That’s because you cannot do good software design without an intimate understanding of the concrete details of the system. In other words, <strong>generic software design advice is typically useless</strong> for most practical software design problems.</p><p>What is generic software design? It’s “designing to the problem”: the kind of advice you give when you have a reasonable understanding of the <em>domain</em>, but very little knowledge of the existing <em>codebase</em>. Unfortunately, this is the only kind of advice you’ll read in software books and blog posts. Engineers love giving generic software design advice for the same reason that all technical professionals love “talking shop”. However, you should be very careful about applying generic advice to your concrete day-to-day work problems.<br><a href="https://www.seangoedecke.com/you-cant-design-software-you-dont-work-on/">Continue reading...</a></p></div></blockquote></div>]]></description>
      <pubDate>Sun, 28 Dec 2025 22:21:01 +0000</pubDate>
      <link>https://www.seangoedecke.com/a-little-bit-cynical/</link>
      <dc:creator>Pages</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5062845926</guid>
    </item>
    <item>
      <title><![CDATA[How I ship projects at big tech companies]]></title>
      <description><![CDATA[<div><section><p>I have shipped a lot of different projects over the last ~10 years in tech. I often get tapped to lead new ones when it’s important to get it right, because I’m good at it. Shipping in a big tech company is a very different skill to writing code, and lots of people who are great at writing code are terrible at shipping.</p>
<p>Here’s what I think about when I’m leading a project and what I’ve seen people get wrong.</p>
<h2>Shipping is hard</h2>
<p>The most common error I see is to assume that shipping is easy. The default state of a project is to <em>not ship</em>: to be delayed indefinitely, cancelled, or to go out half-baked and burst into flames. Projects do not ship automatically once all the code has been written or all the Jira tickets closed. They ship because someone takes up the difficult and delicate job of shipping them.</p>
<p>That means that in almost all cases, <strong>shipping has to come first</strong>. You cannot have anything else as your top priority. If you spend all your time worrying about polishing the customer experience (for example), you will not ship! Obsessing over UX is praiseworthy behaviour when you are an engineer on the team, but a blunder if you are leading the project. You should cherish the other engineers on your team who are doing that work, and give them as much support as you can. But your primary concern has to be shipping the project. It is too hard a job to do in your spare time.</p>
<p>In my experience, projects almost always ship because one single person makes them ship. To be clear, that person doesn’t write all the code or do all the work, and I’m not saying the project could ship without an entire team behind it. But it’s <em>really important</em> that one person on the project has an end-to-end understanding of the whole thing: how it hangs together technically, and what product or business purpose it serves. Good teams and companies understand this, and make sure every project has a single responsible engineer (typically this position is called a “technical lead” or “DRI” role). Bad teams and companies don’t do this, and projects live and die based on whether an engineer steps up into this role of their own accord.</p>
<h2>What is shipping?</h2>
<p>Why do so many engineers think shipping is easy? I know it sounds extreme, but I think many engineers do not understand what shipping even is inside a large tech company. What does it mean to ship? It does <em>not</em> mean deploying code or even making a feature available to users. Shipping is a social construct within a company. Concretely, that means that <strong>a project is shipped when the important people at your company believe it is shipped.</strong> If you deploy your system, but your manager or VP or CEO is very unhappy with it, <em>you did not ship</em>. (Maybe you shipped something, but you didn’t ship the actual project.) You only know you’ve shipped when your company’s leadership acknowledge you’ve shipped. A congratulations message in Slack from your VP is a good sign, as is an internal blog post that claims victory. For small ships, an atta-boy from your manager will do.</p>
<p>This probably sounds circular, but I think it’s a really important point. Of course if you deploy something that users love and makes a ton of money, you’ve shipped. But that’s only true because satisfying users and making money is something that makes your leadership team happy. If you ship something users hate and makes no money, but your leadership team is happy, <em>you still shipped</em>. You can feel any way you like about that, but it’s true. If you don’t like it, you should probably go work for companies that really care how happy their users are.</p>
<p>Engineers who think shipping means delivering a spec or deploying code will repeatedly engineer their way into failed ships.</p>
<h2>Communication</h2>
<p>So if your primary job when shipping something is to make your company’s leadership happy with the project, what does that mean in practice? First, <strong>you have to get clear on what the company is looking to get out of the project</strong>. Sometimes it’s extracting more money from a small set of users (e.g. enterprise features). Sometimes it’s spending money to grow the total set of users (e.g. splashy free-tier features). Sometimes it’s mollifying a particular very large customer by building a feature specifically for them. Sometimes it’s just an influential VP or CEO’s pet project, and you need to align with their vision. There are lots of potential reasons, and if you want to ship the project you need to know which ones apply in this case. Align your work and communication accordingly! For instance, enterprise features often don’t need splashy UI but are completely inflexible on requirements, end-user features need to be polished, pet projects mean you need to be in active communication with the specific mover and shaker whose pet it is, and so on.</p>
<p>Second, no matter the project goal, your leadership team (the people in your reporting chain who care about the project) will always have basically zero technical context about the project compared to you. That means they will be trusting you for estimates, to answer technical questions, and to anticipate technical problems. <strong>Maintaining that trust should be your top priority</strong>. If they don’t have faith in your ability to do the job and to keep them informed, you will not ship. They’ll de-risk by cancelling the project, or letting it roll out with zero attention or celebration (remember that an un-celebrated launch is not a ship!) Alternatively, they’ll sideline you and go to another engineer, who will then formally or informally be the one who actually ships the project. Either way, you’ll feel it at review time and they’ll go to someone else for the next one.</p>
<p>How do you maintain trust with your leadership team? This could be a whole article (or book) by itself, but here’s my summary:</p>
<ul>
<li>The best thing is a track record of having shipped in the past, if you can get it</li>
<li>Project confidence (if you’re visibly worried, they will be too)</li>
<li>Project competence. You want to aim for something like a NASA mission control vibe</li>
<li>Communicate professionally and concisely, and don’t make them chase you for updates: post a daily or weekly thread somewhere</li>
</ul>
<p>It is much, much more important to do these things than for the project to ship with zero bugs on the exact deadline. If a project has to be delayed for technical reasons, in my experience you will not suffer consequences so long as you communicate it clearly, confidently (and ideally with some warning). In fact, it’s paradoxically often <em>better</em> for you if there is some kind of problem that forces a delay, for the same reason that the heroic on-call engineer who hotfixes an incident gets more credit than the careful engineer who prevents one.</p>
<h2>Getting into production</h2>
<p>Even so, you typically still have to get the project into production. The most common problem here is missing a key detail. Sometimes it’s a technical detail: maybe we rely on storing the user documents in Memcached, but many documents are multiple megabytes and will be larger than the Memcached block size. Sometimes it’s a detail of coordination: maybe the platform team that owns Memcached was expecting one-tenth of the traffic our project will send them, so they call a meeting with the VPs and delay the project. Sometimes it’s a legal detail: maybe the user data is unexpectedly sensitive, and our system doesn’t have the controls we’d need to handle it safely. These issues can come from anywhere, and are very hard to anticipate. Dealing with them requires a deep technical understanding of the system and the ability to pivot quickly.</p>
<p>For instance, you may have read that first example and are now thinking “well, you could split the documents across multiple Memcached keys, or increase the block size, or move to Redis, etc…“. All of those are potential solutions! But knowing which of those solutions will work - and more importantly, which of those solutions will not blow out the project timeline - is impossible unless you’ve got a deep understanding.</p>
<p>This is doubly important because the problem in question doesn’t even need to be real. In the lead up to a project launch, it’s very common for other teams or engineers to raise <em>potential</em> problems (e.g. “hey, are we sure the user data will fit in Memcached?“) If nobody steps forward and explains why this isn’t a problem (or if it is, how it’s being addressed before launch), the project will be delayed, and it will be your responsibility. Why? Because your manager (or their manager) will not know whether this is a serious problem. That’s what they pay you for! If you’re not stepping up to address it, they will naturally err on the side of caution and <em>not ship</em>.</p>
<p>You need to stay light on your feet so that when these issues come up you’re not neck-deep in other work. That usually means not being fully heads-down on implementation (i.e. delegating tasks to other engineers on the project). Ideally you should have at least 20% of your time free from implementation in the early stages of the project, scaling up to 90-100% in the final days. If you do that, when issues do come up you’ll be able to grant them your full attention.</p>
<h2>Can we ship right now?</h2>
<p>Feature flags are the best way I’ve seen to do this, but staging environments also work, and so on. The key thing is to get whatever you’re building in front of as many eyes as possible: yours, but also other engineers, and ideally leadership, product, design and so on. Five minutes playing with the actual feature, even in a very rough state, will bring up issues that nobody anticipated. Being able to directly see it themselves also does wonders for reassuring leadership that you’ve got things under control.</p>
<p>The best way to anticipate problems is to deploy early. In general, a helpful question to ask is <strong>can I ship this right now?</strong> Not this week, not today: right this second. If not, what would have to change for me to be able to ship <em>something</em>? If the ship needs a deploy, can that happen now behind a feature flag? If we’re waiting on some other team to make a change on their end, can I make it so the system doesn’t strictly require their change after all? For instance, if the platform team is setting up a cache layer, I could make it so my feature still works (albeit a little slower) if it can’t find the cache.</p>
<p>Remember, your main priority is maintaining trust with your leadership team. Nothing builds trust like having fallback plans, because in case of emergency fallback plans indicate control over the situation. If the worst does happen and you can’t release on the day, your manager will be much happier going to their manager about it if they can say something like “our options are to delay four days, or ship tomorrow by sacrificing X” - even if sacrificing X is a non-starter. That means they’ll be more likely to interpret the delay as an unavoidable problem that you handled effectively, instead of as a mistake you made that means they can’t rely on you.</p>
<p>I think a lot of engineers hold off on deploys essentially out of fear. If you want to ship, you need to do the exact opposite: you need to deploy as much as you can as early as possible, and you need to do the scariest changes as early as you can possibly do them. Remember that you have the most end-to-end context on the project, which means <strong>you should be the least scared of scary changes</strong>. Everyone else is dealing with more unknowns and is going to be even less keen to pull the big lever. (If there’s some other engineer who is across all of this who you’re waiting for, bad news: they’re probably the one actually shipping your project).</p>
<h2>Summary</h2>
<ul>
<li>Shipping is really hard and you have to make it your main priority</li>
<li>Shipping doesn’t mean deploying code, it means making your leadership team happy</li>
<li>You need your leadership team to trust you in order to ship</li>
<li>Most of the essential technical work is in anticipating problems and creating fallback plans</li>
<li>Scale back your implementation work as you approach launch so you’re free to jump on last-minute problems</li>
<li>You should constantly ask yourself “can I ship right this second?”</li>
<li>Have courage!</li>
</ul>
<p>edit: this post was discussed on <a href="https://news.ycombinator.com/item?id=42111031">Hacker News</a> with lots of comments.</p></section><p>If you liked this post, consider <a href="https://buttondown.com/seangoedecke">subscribing</a> to email updates about my new posts, or <a href="https://news.ycombinator.com/submitlink?u=https://www.seangoedecke.com/how-to-ship/&amp;t=How I ship projects at big tech companies">sharing it on Hacker News</a>. Here's a preview of a related post that shares tags with this one.</p><blockquote><p>Keep incidents boring</p><div><p>The internet is full of exciting incident war stories. Tough engineering problems, solved under pressure by sleep-deprived developers. In an industry where the best code is usually the most boring implementation, it’s nice to think that there’s at least one place where hacking up a cool idea is heroic rather than irresponsible. For some engineers, me included, being on-call has a really seductive appeal. Working under pressure is fun, and there’s a lot of immediate validation in being the one who figures an incident out. Other people have written a lot about the problems with this hero-mentality: both for the engineer, who rapidly becomes a single point of failure and burns out, and for the organisation, which incentivises incident <em>response</em> rather than incident <em>prevention</em>. I agree with all of that. But I think the prevalence of war stories also misrepresents what it’s like to be an effective incident responder. <em>Almost no incidents are interesting.</em> High-pressure, yes, but typically boring from an engineering perspective.<br><a href="https://www.seangoedecke.com/boring-incidents/">Continue reading...</a></p></div></blockquote></div>]]></description>
      <pubDate>Sun, 28 Dec 2025 22:21:09 +0000</pubDate>
      <link>https://www.seangoedecke.com/how-to-ship</link>
      <dc:creator>Pages</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5062846077</guid>
    </item>
    <item>
      <title><![CDATA[How good engineers write bad code at big companies]]></title>
      <description><![CDATA[<div><section><p>Every couple of years <a href="https://ziglang.org/news/migrating-from-github-to-codeberg/">somebody</a> <a href="https://github.com/microsoft/terminal/issues/10362">notices</a> that large tech companies sometimes produce surprisingly sloppy code. If you haven’t worked at a big company, it might be hard to understand how this happens. Big tech companies pay well enough to attract many competent engineers. They move slowly enough that it looks like they’re able to take their time and do solid work. How does bad code happen?</p>
<h3>Most code changes are made by relative beginners</h3>
<p>I think the main reason is that <strong>big companies are full of engineers working outside their area of expertise</strong>. The average big tech employee stays for only <a href="https://stackoverflow.blog/2022/04/19/whats-the-average-tenure-of-an-engineer-at-a-big-tech-company-ep-434/">a year or two</a><sup id="fnref-1"><a href="https://www.seangoedecke.com/bad-code-at-big-companies#fn-1" class="footnote-ref">1</a></sup>. In fact, big tech compensation packages are typically designed to put a four-year cap on engineer tenure: after four years, the initial share grant is fully vested, causing engineers to take what can be a 50% pay cut. Companies do extend temporary yearly refreshes, but it obviously incentivizes engineers to go find another job where they don’t have to wonder if they’re going to get the other half of their compensation each year.</p>
<p>If you count internal mobility, it’s even worse. The longest I have ever stayed on a single team or codebase was three years, near the start of my career. I expect to be <a href="https://www.youtube.com/watch?v=yDcaRklX7q4">re-orged</a> at least every year, and often much more frequently.</p>
<p>However, the average tenure of a codebase in a big tech company is a lot longer than that. Many of the services I work on are a decade old or more, and have had many, many different owners over the years. That means many big tech engineers are constantly “figuring it out”. <strong>A pretty high percentage of code changes are made by “beginners”:</strong> people who have onboarded to the company, the codebase, or even the programming language in the past six months.</p>
<h3>Old hands</h3>
<p>To some extent, this problem is mitigated by “old hands”: engineers who happen to have been in the orbit of a particular system for long enough to develop real expertise. These engineers can give deep code reviews and reliably catch obvious problems. But relying on “old hands” has two problems. </p>
<p>First, <strong>this process is entirely informal</strong>. Big tech companies make surprisingly little effort to develop long-term expertise in individual systems, and once they’ve got it they seem to barely care at all about retaining it. Often the engineers in question are moved to different services, and have to either keep up their “old hand” duties on an effectively volunteer basis, or abandon them and become a relative beginner on a brand new system.</p>
<p>Second, <strong>experienced engineers are always overloaded</strong>. It is a <em>busy</em> job being one of the few engineers who has deep expertise on a particular service. You don’t have enough time to personally review every software change, or to be actively involved in every decision-making process. Remember that <em>you also have your own work to do</em>: if you spend all your time reviewing changes and being involved in discussions, you’ll likely be punished by the company for not having enough individual output.</p>
<h3>The median productive engineer</h3>
<p>Putting all this together, what does the median productive<sup id="fnref-2"><a href="https://www.seangoedecke.com/bad-code-at-big-companies#fn-2" class="footnote-ref">2</a></sup> engineer at a big tech company look like? They are usually:</p>
<ul>
<li>competent enough to pass the hiring bar and be able to do the work, but either</li>
<li>working on a codebase or language that is largely new to them, or</li>
<li>trying to stay on top of a flood of code changes while also juggling their own work.</li>
</ul>
<p>They are almost certainly working to a deadline, or to a series of overlapping deadlines for different projects. In other words, <strong>they are trying to do their best in an environment that is not set up to produce quality code.</strong></p>
<p>That’s how “obviously” bad code happens. For instance, a junior engineer picks up a ticket for an annoying bug in a codebase they’re barely familiar with. They spend a few days figuring it out and come up with a hacky solution. One of the more senior “old hands” (if they’re lucky) glances over it in a spare half-hour, vetoes it, and suggests something slightly better that would at least work. The junior engineer implements that as best they can, tests that it works, it gets briefly reviewed and shipped, and everyone involved immediately moves on to higher-priority work. Five years later somebody notices this<sup id="fnref-3"><a href="https://www.seangoedecke.com/bad-code-at-big-companies#fn-3" class="footnote-ref">3</a></sup> and thinks “wow, that’s hacky - how did such bad code get written at such a big software company”?</p>
<h3>Big tech companies are fine with this</h3>
<p>I have written a lot about the internal tech company dynamics that contribute to this. Most directly, in <a href="https://www.seangoedecke.com/seeing-like-a-software-company"><em>Seeing like a software company</em></a> I argue that big tech companies consistently prioritize internal <em>legibility</em> - the ability to see at a glance who’s working on what and to change it at will - over productivity. Big companies know that treating engineers as fungible and moving them around destroys their ability to develop long-term expertise in a single codebase. <strong>That’s a deliberate tradeoff.</strong> They’re giving up some amount of expertise and software quality in order to gain the ability to rapidly deploy skilled engineers onto whatever the problem-of-the-month is.</p>
<p>I don’t know if this is a good idea or a bad idea. It certainly seems to be working for the big tech companies, particularly now that “how fast can you pivot to something AI-related” is so important. But if you’re doing this, then <em>of course</em> you’re going to produce some genuinely bad code. That’s what happens when you ask engineers to rush out work on systems they’re unfamiliar with.</p>
<p><strong>Individual engineers are entirely powerless to alter this dynamic</strong>. This is particularly true in 2025, when <a href="https://www.seangoedecke.com/good-times-are-over">the balance of power has tilted</a> away from engineers and towards tech company leadership. The most you can do as an individual engineer is to try and become an “old hand”: to develop expertise in at least one area, and to use it to block the worst changes and steer people towards at least minimally-sensible technical decisions. But even that is often swimming against the current of the organization, and if inexpertly done can cause you to get <a href="https://www.reddit.com/r/csMajors/comments/1et7miz/what_you_need_to_know_about_performance/">PIP-ed</a> or worse.</p>
<h3>Pure and impure engineering</h3>
<p>I think a lot of this comes down to the distinction between <a href="https://www.seangoedecke.com/pure-and-impure-engineering">pure and impure software engineering</a>. To pure engineers - engineers working on self-contained technical projects, like <a href="https://ziglang.org/">a programming language</a> - the only explanation for bad code is incompetence. But impure engineers operate more like plumbers or electricians. They’re working to deadlines on projects that are relatively new to them, and even if their technical fundamentals are impeccable, there’s always <em>something</em> about the particular setup of this situation that’s awkward or surprising. To impure engineers, bad code is inevitable. As long as the overall system works well enough, the project is a success.</p>
<p>At big tech companies, engineers don’t get to decide if they’re working on pure or impure engineering work. It’s <a href="https://www.seangoedecke.com/not-your-codebase">not their codebase</a>! If the company wants to move you from working on database infrastructure to building the new payments system, they’re fully entitled to do that. The fact that you might make some mistakes in an unfamiliar system - or that your old colleagues on the database infra team might suffer without your expertise - is a deliberate tradeoff being made by <strong>the company, not the engineer</strong>.</p>
<p>It’s fine to point out examples of bad code at big companies. If nothing else, it can be an effective way to get those specific examples fixed, since execs usually jump at the chance to turn bad PR into good PR. But I think it’s a mistake<sup id="fnref-4"><a href="https://www.seangoedecke.com/bad-code-at-big-companies#fn-4" class="footnote-ref">4</a></sup> to attribute primary responsibility to the engineers at those companies. If you could wave a magic wand and make every engineer twice as strong, <em>you would still have bad code</em>, because almost nobody can come into a brand new codebase and quickly make changes with zero mistakes. The root cause is that <strong>most big company engineers are forced to do most of their work in unfamiliar codebases</strong>.</p>
<p>edit: this post got lots of comments on both <a href="https://news.ycombinator.com/item?id=46082223">Hacker News</a> and <a href="https://lobste.rs/s/jxppk7/how_good_engineers_write_bad_code_at_big">lobste.rs</a>.</p>
<p>It was surprising to me that <a href="https://lobste.rs/c/glawav">many</a> <a href="https://news.ycombinator.com/item?id=46083941">commenters</a> find this point of view unplesasantly nihilistic. I consider myself fairly optimistic about my work. In fact, I meant this post as a rousing defence of big tech software engineers from their <a href="https://ziglang.org/news/migrating-from-github-to-codeberg">critics</a>! Still, I found this <a href="https://alexwennerberg.com/blog/2025-11-28-engineering.html">response blog post</a> to be an excellent articulation of the “this is too cynical” position, and will likely write a followup post about it soon. If you can’t wait, I wrote a bit on this topic at the start of 2025 in <a href="https://www.seangoedecke.com/cynicism"><em>Is it cynical to do what your manager wants?</em></a>.</p>
<p>Some Hacker News commenters had alternate theories for why bad code happens: <a href="https://news.ycombinator.com/item?id=46083625">lack of motivation</a>, deliberately <a href="https://news.ycombinator.com/item?id=46082989">demoralizing engineers</a> so they won’t unionize, or just purely optimizing for speed. I don’t find these compelling, based on my own experience. Many of my colleagues are highly motivated, and I just don’t believe any tech company is deliberately trying to make its engineers demoralized and unhappy.</p>
<p>A few readers <a href="https://lobste.rs/c/gq4ao9">disagreed with me</a> about RSUs providing an incentive to leave, because their companies give stock refreshers. I don’t know about this. I get refreshers too, but if they’re not in the contract, then I don’t think it matters - the company can decide not to give you 50% of your comp at-will by just pausing the refreshers, which is an incentive to move jobs so it’s locked in for four more years.</p>
</section><p>If you liked this post, consider <a href="https://buttondown.com/seangoedecke">subscribing</a> to email updates about my new posts, or <a href="https://news.ycombinator.com/submitlink?u=https://www.seangoedecke.com/bad-code-at-big-companies/&amp;t=How good engineers write bad code at big companies">sharing it on Hacker News</a>. Here's a preview of a related post that shares tags with this one.</p><blockquote><p>How I influence tech company politics as a staff software engineer</p><div><p>Many software engineers are fatalistic about company politics. They believe that it’s pointless to get involved, because:</p><p>The general idea here is that <strong>software engineers are simply not equipped to play the game at the same level as real political operators</strong>. This is true! It would be a terrible mistake for a software engineer to think that you ought to start scheming and plotting like you’re in <em>Game of Thrones</em>. Your schemes will be immediately uncovered and repurposed to your disadvantage and other people’s gain. Scheming takes practice and power, and neither of those things are available to software engineers.<br><a href="https://www.seangoedecke.com/how-to-influence-politics/">Continue reading...</a></p></div></blockquote></div>]]></description>
      <pubDate>Sun, 28 Dec 2025 22:21:14 +0000</pubDate>
      <link>https://www.seangoedecke.com/bad-code-at-big-companies</link>
      <dc:creator>Pages</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5062846143</guid>
    </item>
    <item>
      <title><![CDATA[What's in my HomeLab: 2026]]></title>
      <description><![CDATA[<p>This is a complete – and <strong>long</strong> – list of all services and tools that are running on my HomeLab going into 2026.</p><h3>Tips</h3><p>Before you start deploying this list in your cluster, <strong>some tips upfront</strong> on how to manage your services. More about that in detail in another blog post soon (subscribe to the <a target="_blank" href="https://romanzipp.com/rss">RSS feed</a>)</p><ul><li><p><strong>Use a real domain</strong> for your services (like <code>service.yourname.net</code>, point DNS to your server/cluster IP) which will allow you to run a cert-manager and issue valid Let's Encrypt SSL certificates. This will save you a lot of headaches.</p></li><li><p>If you want to <strong>access services remotely</strong> or share links to users without a VPN connection, setup a remote server that is only acting as a reverse proxy (like <a target="_blank" href="https://caddyserver.com/docs/quick-starts/reverse-proxy">Caddy</a>, with automated SSL certs) and only allow this specific IP to connect to your routers public IP via firewall rules.</p></li><li><p>Make your server/cluster deployment reproducable. Don't rely on fancy web UIs but rather configuration files. <strong>Document</strong> all changes and keep a <strong>single-source-of-truth</strong> configuration repository which can be instantly deployed.</p></li><li><p>Always deploy the docker images with a <strong>fixed version</strong> (eg. <code>henrygd/beszel-agent:0.17.0</code>) and manually update. You can monitor new image versions in <a target="_blank" href="https://github.com/glanceapp/glance/blob/main/docs/configuration.md#releases">Glance</a> or subscribe to Github releases with your RSS reader.</p></li><li><p>If you're using Kubernetes, apply <a target="_blank" href="https://kubernetes.io/docs/concepts/services-networking/network-policies/">Network Policies</a> to all deployments so than can only communicate to allow-listed IP spaces and services.</p></li></ul><h3>Outlook</h3><p>There's <strong>a lot</strong> I want to add in the coming year. Some of that includes:</p><ul><li><p><a target="_blank" href="https://smartgateways.nl/en/make-your-gas-meter-smart/">Smart meters</a> for my gas heating</p></li><li><p>Monitoring birds in my backyard via <a target="_blank" href="https://github.com/tphakala/birdnet-go">BirdNET</a></p></li><li><p>Self-hosting images via <a target="_blank" href="https://github.com/immich-app/immich">Immich</a> (thank's AI for those SSD prices)</p></li><li><p>Tracking flights with own receiver</p></li><li><p>Backup more things locally</p></li><li><p>Looking into making <a target="_blank" href="https://github.com/anyproto/anytype-ts">AnyType</a> and <a target="_blank" href="https://github.com/Someone0nEarth/excalidraw-self-hosted">Excalidraw</a> self-hostable</p></li><li><p>Add SSO via <a target="_blank" href="https://goauthentik.io/">authentik</a> or <a target="_blank" href="https://www.authelia.com/">Authelia</a></p></li></ul><p>The following list is separated into <strong>1. Tools</strong>, <strong>2. Monitoring</strong>, <strong>3. Media</strong> and <strong>4. Internal</strong>.</p><h2>1. Tools</h2><h3>Baikal: CalDAV / CardDAV</h3><p>I'm self-hosting my calendar and contact data and found that <a target="_blank" href="https://github.com/sabre-io/Baikal">Baikal</a> works best for me</p><h3>BentoPDF</h3><p>If you have tummy aches when uploading somewhat sensitive document into a "free online pdf tool converter", you should run your own instance of <a target="_blank" href="https://github.com/alam00000/bentopdf">BentoPDF</a> which provides many many features for editing PDFs.</p><p>Tip: Use the <code>bentopdf-simple</code> image so that the marking section, similar to the landing page, is hidden.</p><h3>ConvertX</h3><p>Pretty ugly but insanely powerfull. Convert anything to anything with <a target="_blank" href="https://github.com/C4illin/ConvertX">ConvertX</a>.</p><h3>FreshRSS</h3><p>You should start reading more news and blog posts. Great time to setup your own instance of <a target="_blank" href="https://github.com/FreshRSS/FreshRSS">FreshRSS</a> - an RSS aggregator with nice integration to NetNewsWire and many other clients.</p><h3>Gitea</h3><p>Maybe you don't need to upload anything to GitHub just so Microsoft can train their shit AI on it, so start hosting your own <a target="_blank" href="https://github.com/go-gitea/gitea">Gitea</a> instance, Helm chart included.</p><h3>Glance</h3><p>In my opinion the best HomeLab dashboard, customizable, pretty and feature-rich. Get <a target="_blank" href="https://github.com/glanceapp/glance">Glance</a>.</p><h3>Home Assistant</h3><p>This text only exists so I can link to <a target="_blank" href="https://github.com/home-assistant/core">Home Assistant</a> on GitHub - no introduction needed.</p><h3>iSponsorBlockTV</h3><p><a target="_blank" href="https://github.com/dmunozv04/iSponsorBlockTV">iSponsorBlockTV</a> integrates SponsorBlock with your smart TV - like Apple TV.</p><h3>Mermaid</h3><p>You can host your own <a target="_blank" href="https://github.com/mermaid-js/mermaid-live-editor">Mermaid Live editor</a>, no platform with saving, sharing and such but helpful if you're using Mermaid charts.</p><h3>MeTube</h3><p><a target="_blank" href="https://github.com/alexta69/metube">MeTube</a> is just a frontend for <code>yt-dlp</code> so I don't have to fight with annoying ads when sicherheitsspeichern YouTube videos.</p><h3>NocoDB</h3><p>A neat GUI database / spreadsheet-like web application, also good for collaboration: <a target="_blank" href="https://github.com/nocodb/nocodb">NocoDB</a>.</p><p><strong>Tip</strong>: Set env vars <code>NC_DISABLE_SUPPORT_CHAT</code> and <code>NC_DISABLE_TELE</code> to "<code>false</code>".</p><h3>Owntracks</h3><p>Stop pumping your geolocation into your Google account and start using self-hosted <a target="_blank" href="https://github.com/owntracks/owntracks">Owntracks</a> with the <a target="_blank" href="https://github.com/owntracks/frontend">Owntracks frontend</a>.</p><p><strong>Tip</strong>: Take a look at the Owntracks frontend <a target="_blank" href="https://github.com/owntracks/frontend/blob/main/docs/config.md">configuration options</a> which can really enhance the look.</p><h3>Roundcube</h3><p>If you also have a lot of mailboxes, it's a great idea to host <a target="_blank" href="https://github.com/roundcube/roundcubemail">Roundcube</a> instead of syncing every single one of them with your mail client.</p><p><strong>Tip</strong>: Enable the included managesieve plugin with <code>$config['plugins'] = ['managesieve'];</code> to allow editing mail rules directly in Roundcube.</p><h3>Slink</h3><p>A self-hosted image sharing platform for quickly sharing images: <a target="_blank" href="https://github.com/andrii-kryvoviaz/slink">Slink</a>.</p><h3>Traccar</h3><p>Similar to Owntracks but <a target="_blank" href="https://github.com/traccar/traccar">Traccar</a> more optimized for tracking vehicles and collaborating with multiple users and clients.</p><h2>2. Monitoring</h2><h3>Beszel</h3><p><a target="_blank" href="https://github.com/henrygd/beszel">Beszel</a> is great for getting an overview about CPU and memory with multiples machines.</p><h3>Grafana</h3><p>You should already know <a target="_blank" href="https://github.com/grafana/grafana">Grafana</a>, a data visualization tool.</p><h3>Kuvasz</h3><p>You can monitoring many many websites for uptime &amp; SSL validity with <a target="_blank" href="https://github.com/kuvasz-uptime/kuvasz">Kuvasz</a>.</p><h3>Prometheus</h3><p>Can't use Grafana without <a target="_blank" href="https://github.com/prometheus/prometheus">Prometheus</a> - can you?</p><h3>Speedtest Tracker</h3><p>Pretty okay-ish Speedtest monitoring with <a target="_blank" href="https://github.com/alexjustesen/speedtest-tracker">Speedtest Tracker</a>. It's running Ookla so be aware that all results are being logged by them.</p><h2>3. Media</h2><h3>Jellyfin</h3><p><a href="https://github.com/jellyfin/jellyfin">Jellyfin</a> provides media management and video players for your movies and TV shows.</p><h3>Prowlarr</h3><p>If you know you know, "RSS aggregator" for Linux ISOs with <a target="_blank" href="https://github.com/Prowlarr/Prowlarr">Prowlarr</a>.</p><h3>qBittorrent</h3><p>Can't boot Linux without an ISO, so share and care with <a target="_blank" href="https://github.com/qbittorrent/qBittorrent">qBittorrent</a>.</p><h3>Radarr</h3><p><a target="_blank" href="https://github.com/Radarr/Radarr">Radarr</a> is a movie organizer/manager for usenet and torrent users.</p><h3>Sonarr</h3><p><a target="_blank" href="https://github.com/Sonarr/Sonarr">Sonarr</a> is a smart PVR for newsgroup and bittorrent users.</p><h3>Seerr</h3><p>If you have multiple normie users watching movies on your Jellyfin, they can also request new media via <a target="_blank" href="https://github.com/seerr-team/seerr">Seer</a> (formerly Jellyseer)</p><h2>4. Internal</h2><h3>DDNS Updater</h3><p>A lightweight universal <a target="_blank" href="https://github.com/qdm12/ddns-updater">DDNS Updater</a> written in Go with a small but helpful web UI.</p><h3>Gitea Mirror</h3><p>In case GitHub get's flöten or Microsoft or their rogue AI deletes your GitHub account, setup a mirroring for your repositories with <a target="_blank" href="https://github.com/RayLabsHQ/gitea-mirror">Gitea mirror</a>.</p><h2>5. Backup</h2><h3>Biochon</h3><p><a target="_blank" href="https://github.com/rustmailer/bichon">Biochon</a> can pull your mails via IMAP - but has no restore/export functionality yet. Looks really good and is in active development.</p>]]></description>
      <pubDate>Mon, 29 Dec 2025 00:39:00 +0000</pubDate>
      <link>https://romanzipp.com/blog/whats-in-my-homelab-2026</link>
      <dc:creator>Roman Zipp</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5065223410</guid>
    </item>
    <item>
      <title><![CDATA[How I Write Custom Elements with lit-html]]></title>
      <description><![CDATA[
<p>When I started learning more about web development, or more specifically about front-end frameworks, I thought writing components was so much better and more maintainable than calling <code>.innerHTML()</code> whenever you need to perform DOM operations. <a href="https://en.wikipedia.org/wiki/JavaScript_XML">JSX</a> felt like a great way to mix HTML, CSS, and JS in a single file, but I wanted a more vanilla JavaScript solution instead of having to install a JSX framework like React or Solid.</p>



<p>So I’ve decided to go with <a href="https://lit.dev/docs/libraries/standalone-templates/">lit-html</a> for writing my own components.</p>



<h2 class="wp-block-heading">Why not use the entire lit package instead of just lit-html?</h2>



<p>Honestly, I believe something like lit-html should be a part of vanilla JavaScript (<a href="https://justinfagnani.com/2025/06/30/what-should-a-dom-templating-api-look-like/">maybe someday?</a>). So by using lit-html, I basically pretend like it is already. It’s my go-to solution when I want to write HTML in JavaScript. For more solid reasons, you can refer to the following list:</p>



<ul class="wp-block-list">
<li><strong>Size difference.</strong> This often does not really matter for most projects anyway.)
<ul class="wp-block-list">
<li><a href="https://bundlephobia.com/package/lit-html@3.3.1">lit-html</a> – 7.3 kb min, 3.1 kb min + gzip</li>



<li><a href="https://bundlephobia.com/package/lit@3.3.1">lit</a> – 15.8 kb min, 5.9 kb min + gzip</li>
</ul>
</li>



<li><strong>LitElement creates a <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_shadow_DOM">shadow DOM</a> by default.</strong> I don’t want to use the shadow DOM when creating my own components. I prefer to allow styling solutions like Tailwind to work instead of having to rely on solutions like <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_shadow_parts">CSS shadow parts</a> to style my components. <a href="https://frontendmasters.com/blog/light-dom-only/">The light DOM can be nice</a>.</li>



<li><strong><code>import { html, render } from "lit-html"</code> is all you need</strong> to get started to write lit-html templates whereas Lit requires you to learn about <a href="https://lit.dev/docs/components/decorators/">decorators</a> to use most of its features. Sometimes you may want to use Lit directives if you need performant renders but it’s not necessary to make lit-html work on your project.</li>
</ul>



<p>I will be showing two examples with what I consider to be two distinct methods to create a lit-html <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements">custom element</a>. The first example will use what I call a <strong>“stateless render”</strong> because there won’t be any state parameters passed into the lit-html template. Usually this kind of component will only call the render method once during its lifecycle since there is no state to update. The second example will use a <strong>“stateful render”</strong> which calls the render function every time a state parameter changes.</p>



<h2 class="wp-block-heading">Stateless Render</h2>



<p>For my first example, the custom-element is a <code>&lt;textarea&gt;</code> wrapper that also has a status bar similar to <a href="https://notepad-plus-plus.org/">Notepad++</a> that shows the length and lines of the content inside the <code>&lt;textarea&gt;</code>. The <strong>status bar</strong> will also display the position of the cursor and span of the selection if any characters are selected. Here is a picture of what it looks like for those readers that haven’t used Notepad++ before.</p>


<div class="wp-block-image">
<figure class="aligncenter size-large is-resized"><img data-recalc-dims="1" loading="lazy" decoding="async" width="1024" height="571" src="https://i0.wp.com/frontendmasters.com/blog/wp-content/uploads/2025/12/image1-1.png?resize=1024%2C571&amp;ssl=1" alt="A screenshot of a text editor displaying an excerpt about Lorem Ipsum, highlighting the text in yellow and showing line and character counts." class="wp-image-8108" style="aspect-ratio:1.793357655997292;width:607px;height:auto" srcset="https://i0.wp.com/frontendmasters.com/blog/wp-content/uploads/2025/12/image1-1.png?resize=1024%2C571&amp;ssl=1 1024w, https://i0.wp.com/frontendmasters.com/blog/wp-content/uploads/2025/12/image1-1.png?resize=300%2C167&amp;ssl=1 300w, https://i0.wp.com/frontendmasters.com/blog/wp-content/uploads/2025/12/image1-1.png?resize=768%2C428&amp;ssl=1 768w, https://i0.wp.com/frontendmasters.com/blog/wp-content/uploads/2025/12/image1-1.png?w=1075&amp;ssl=1 1075w" sizes="auto, (max-width: 1000px) 100vw, 1000px"></figure>
</div>


<div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_raegrMQ" src="//codepen.io/anon/embed/raegrMQ?height=750&amp;theme-id=1&amp;slug-hash=raegrMQ&amp;default-tab=result" height="750" scrolling="no" frameborder="0" allowfullscreen="" allowpaymentrequest="" name="CodePen Embed raegrMQ" title="CodePen Embed raegrMQ" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>



<p>I used a library called <a href="https://github.com/MatheusAvellar/textarea-line-numbers">TLN</a> (“Textarea with Line Numbers”) to make the aesthetic of the textarea feel more like Notepad++, similar to the library’s official <a href="https://lab.avl.la/textarea-line-numbers/demo.html">demo</a>. Since the base template has no state parameters, I’m using plain old JavaScript events to manually modify the DOM in response to changes within the textarea. I also used the render function again to display the updated status bar contents instead of user <code>.innerHTML()</code> to keep it consistent with the surrounding code.</p>



<p>Using lit-html to render stateless components like these is useful, but perhaps not taking full advantage of the power of lit-html. According to the <a href="https://lit.dev/docs/libraries/standalone-templates/">official documentation</a>:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>When you call render, <strong>lit-html only updates the parts of the template that have changed since the last render.</strong> This makes lit-html updates very fast.</p>
</blockquote>



<p>You may ask: <em>“Why should you use lit-html in examples like this where it won’t make that much of a difference performance wise? Since the root render function is really only called once (or once every <code>connectedCallback()</code>) in the custom elements lifecycle.”</em></p>



<p>My answer is that, yes, it’s not <em>necessary</em> if you just want rendering to the DOM to be fast. The main reason I use lit-html is that the syntax is so much nicer to me compared to setting HTML to raw strings. With vanilla JavaScript, you have to perform <code>.createElement()</code>, <code>.append()</code>, and <code>.addEventListener()</code> to create deeply nested HTML structures. Calling <code>.innerHTML() = `&lt;large html structure&gt;&lt;/&gt;`</code> is much better, but you still need to perform <code>.querySelector()</code> to lookup the newly created HTML and add event listeners to it.</p>



<p><a href="https://lit.dev/docs/components/events/">The <code>@event</code> syntax</a> makes it much more clear where the event listener is located compared to the rest of the template. For example…</p>


<pre class="wp-block-code"><span><code class="hljs language-javascript"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyElement</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">LitElement</span> </span>{
  ...
  render() {
    <span class="hljs-keyword">return</span> html`<span class="xml">
      <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">button</span> @<span class="hljs-attr">click</span>=<span class="hljs-string">"</span></span></span><span class="hljs-subst">${<span class="hljs-keyword">this</span>._doSomething}</span><span class="xml"><span class="hljs-tag"><span class="hljs-string">"</span>&gt;</span>Click Me!<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
    `</span>;
  }
  _doSomething(e) {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"something"</span>);
  }
}</code></span></pre>


<p>It also makes it much more apparent to me on first glance that <code>event.currentTarget</code> can only be the HTMLElement where you attached the listener and <code>event.target</code> can be the same but also may come from any child of the said HTMLElement. The template also calls <code>.removeEventListener()</code> on its own when the template is removed from the DOM so that’s also one less thing to worry about.</p>



<h3 class="wp-block-heading">The Status Bar Area</h3>



<p>Before I continue explaining the change events that make the status bar work, I would like to highlight one of the drawbacks of the “stateless render”: there isn’t really a neat way to render the initial state of HTML elements. I could add placeholder content for when the input is empty and no selection was made yet, but the <code>render()</code> function only appends the template to the given root. It doesn’t delete siblings within the root so the status bar text would end up being doubled. This could be fixed if I call an initial render somewhere in the custom element, similar to the render calls within the event listeners, but I’ve opted to omit that to keep the example simple.</p>



<p>The input change event is one of the more common change events. It’s straightforward to see that this will be the change event used to calculate and display the updated input length and the number of newlines that the input has.</p>



<p>I thought I would have a much harder time displaying the live status of selected text, but the <code>selectionchange</code> event provides everything I need to calculate the selection status within the textarea. This change event is relatively new too, having only been a part of baseline last <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/selectionchange_event">September 2024</a>. </p>



<p>Since I’ve already highlighted the two main events driving the status bar, I’ll proceed to the next example.</p>



<h2 class="wp-block-heading">Stateful Render</h2>



<p>My second example is a <code>&lt;pokemon-card&gt;</code> custom-element. The pokemon card component will generate a random Pokémon from a specific pokemon TCG set. The specifications of the web component are as follows:</p>



<ul class="wp-block-list">
<li>The placeholder will be this <a href="https://upload.wikimedia.org/wikipedia/en/3/3b/Pokemon_Trading_Card_Game_cardback.jpg">generic pokemon card back</a>.</li>



<li>A <strong>Generate</strong> button that adds a new Pokémon card from the TCG set.</li>



<li>Left and right arrow buttons for navigation.</li>



<li>Text that shows the name and page of the currently displayed Pokémon.</li>
</ul>



<div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_raegryo" src="//codepen.io/anon/embed/raegryo?height=630&amp;theme-id=1&amp;slug-hash=raegryo&amp;default-tab=result" height="630" scrolling="no" frameborder="0" allowfullscreen="" allowpaymentrequest="" name="CodePen Embed raegryo" title="CodePen Embed raegryo" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>



<p>In this example, only two other external libraries were used for the web component that weren’t related to lit and lit-html. I used <a href="https://es-toolkit.dev/reference/array/shuffle.html">shuffle</a> from <a href="https://es-toolkit.dev">es-toolkit</a> to make sure the array of cards is in a random order each time the component is instantiated. Though the shuffle function itself is likely small enough that you could just write your own implementation in the same file if you want to minimize dependencies.</p>



<p>I also wanted to mention <a href="https://es-toolkit.dev/">es-toolkit</a> in this article for readers that haven’t heard about it yet. I think it has a lot of useful utility functions so I included it in my example. According to their <a href="https://es-toolkit.dev/intro.html">introduction</a>, “es-toolkit is a modern JavaScript utility library that offers a collection of powerful functions for everyday use.” It’s a modern alternative to lodash, which used to be a staple utility library in every JavaScript project especially during the times before <a href="https://262.ecma-international.org/6.0">ES6</a> was released.</p>



<p>There are many ways to implement a random number generator or how to randomly choose an item from a list. I decided to just create a list of all possible choices, shuffle it, then use the pop method so that it’s guaranteed no card will get generated twice. The es-toolkit shuffle type documentation states that it “randomizes the order of elements in an array using the <a href="https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle">Fisher-Yates</a> algorithm”.</p>



<h2 class="wp-block-heading">Handling State using Signals</h2>



<p>Vanilla JavaScript doesn’t come with a state management solution. While LitElement’s <a href="https://lit.dev/docs/api/decorators#property">property</a> and <a href="https://lit.dev/docs/api/decorators#state">state</a> decorators do count as solutions, I want to utilize a solution that I consider should be a part of Vanilla JavaScript just as with lit-html. The state management solution for the component will be <a href="https://signaldb.js.org/signals">JavaScript Signals</a>. Unlike lit-html, signals are already a <a href="https://github.com/tc39/proposal-signals">Stage 1 Proposal</a> so there is a slightly better chance it will become a standard part of the JavaScript specification within the next few years.</p>



<p>As you can see from the Stage 1 Proposal, explaining JavaScript Signals from scratch can be very long that it might as well be its own multi-part article series so I will just give a rundown on how I used it in the <code>&lt;pokemon-card&gt;</code> custom-element. If you’re interested in a quick explanation of what signals are, the creator of SolidJS, which is a popular framework that uses signals, explains their thoughts <a href="https://www.youtube.com/watch?v=l-0fKa0w4ps">here</a>.</p>



<p>Signals need an effect implementation to work which is not a part of the proposed signal API, since according to the proposal, it ties into “framework-specific state or strategies which JS does not have access to”. I will be copy and pasting the <a href="https://github.com/tc39/proposal-signals?tab=readme-ov-file#implementing-effects">watcher code</a> in the example despite the comments recommending otherwise. My components are also too basic for any performance related issues to happen anyways. I also used the <a href="https://github.com/lit/lit/tree/main/packages/labs/signals">@lit-labs/signals</a> to keep the component “lit themed” but you can also just use the recommended <a href="https://github.com/proposal-signals/signal-polyfill">signal-polyfill</a> directly too.</p>



<h3 class="wp-block-heading">Signal Syntax</h3>



<p>The syntax I used to create a signal state in my custom HTMLElement are as follows:</p>


<pre class="wp-block-code"><span><code class="hljs language-javascript">#visibleIndex = <span class="hljs-keyword">new</span> Signal.State(<span class="hljs-number">0</span>)

<span class="hljs-keyword">get</span> visibleIndex() {
&nbsp;&nbsp;<span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>.#visibleIndex.get()
}

<span class="hljs-keyword">set</span> visibleIndex(value: number) {
&nbsp;&nbsp;<span class="hljs-keyword">this</span>.#visibleIndex.set(value)
}</code></span></pre>


<p>There is a much more concise way to define the above example which involves <a href="https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-9.html#auto-accessors-in-classes">auto accessors</a> and <a href="https://www.typescriptlang.org/docs/handbook/decorators.html#accessor-decorators">decorators</a>. Unfortunately, CodePen only <a href="https://codepen.io/versions/">supports</a> TypeScript 4.1.3 as of writing, so I’ve opted to just use long-hand syntax in the example. An <a href="https://github.com/proposal-signals/signal-polyfill?tab=readme-ov-file#combining-signals-and-decorators">example</a> of the accessor syntax involving signals is also shown in the signal-polyfill proposal.</p>



<h2 class="wp-block-heading">Card Component Extras</h2>



<p>The <a href="https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API">Intersection Observer API</a> was used to allow the user to navigate the card component via horizontal scroll bar while also properly updating the state of the current page being displayed.</p>



<p>There is also a <a href="https://developer.mozilla.org/en-US/docs/Web/API/Element/keydown_event"><code>keydown</code> event</a> handler present to also let the user navigate between the cards via keyboard presses. Depending on the key being pressed, it calls either the <code>handlePrev()</code> or <code>handleNext()</code> method to perform the navigation.</p>



<p>Finally, while entirely optional, I also added a feature to the component that will <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/Image">preload the next card in JavaScript</a> to improve loading times between generating new cards.</p>
]]></description>
      <pubDate>Mon, 29 Dec 2025 14:11:35 +0000</pubDate>
      <link>https://frontendmasters.com/blog/custom-elements-with-lit-html/</link>
      <dc:creator>Frontend Masters Boost RSS Feed</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5063713741</guid>
    </item>
    <item>
      <title><![CDATA[A moment with a sunset]]></title>
      <description><![CDATA[
        <p>No matter how busy life is, there's always time to admire a beautiful sunset.</p>
<figure class="media-container" data-template="with"><div class="media-content"><img class="media-img" loading="lazy" src="https://manuelmoreale.com/media/pages/thoughts/a-moment-with-another-sunset/20fb6c1cb3-1766943786/sunset.jpg" style="aspect-ratio:1000 / 1333"></div></figure>        <hr>
        <p>Thank you for keeping RSS alive. You're awesome.</p>
        <p><a href="mailto:hello@manuelmoreale.com">Email me</a> ::
        <a href="https://manuelmoreale.com/guestbook">Sign my guestbook</a> :: 
        <a href="https://ko-fi.com/manuelmoreale">Support for 1$/month</a> :: 
        <a href="https://manuelmoreale.com/supporters">See my generous supporters</a> :: 
        <a href="https://buttondown.email/peopleandblogs">Subscribe to People and Blogs</a></p>
    ]]></description>
      <pubDate>Sun, 28 Dec 2025 17:45:00 +0000</pubDate>
      <link>https://manuelmoreale.com/thoughts/a-moment-with-another-sunset</link>
      <dc:creator>Manuel Moreale — Everything Feed</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5062635795</guid>
    </item>
    <item>
      <title><![CDATA[Be a firework]]></title>
      <description><![CDATA[ <p>This morning, fellow ADHDer Abbey Perini post <a href="https://www.youtube.com/watch?v=mt2v2DX5Tno">this amazing 10-minute talk she gave at DotJS Conf</a> on coding and ADHD.</p>

<iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/mt2v2DX5Tno" 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>Abbey has a similar perspective that I do on ADHD: knowing you have it means that you can start to <a href="https://gomakethings.com/guides/adhd/work-with-your-adhd/">embrace your strengths</a> instead of constantly worrying about the things you suck at.</p>

<p>It’s <em>well worth</em> 10 minutes of your day.</p>

<p>Related to this, <a href="https://www.cambridge.org/core/journals/psychological-medicine/article/role-of-psychological-strengths-in-positive-life-outcomes-in-adults-with-adhd/69B7CE4D4D9F370214929ABF53701567">a new study was just published</a> found that people with ADHD who learned about and focused on their strengths had substantially better mental health than those who did not.</p>

<blockquote>
<p>…increased strengths knowledge and, to some extent, greater strengths use were associated with better wellbeing, improved quality of life, and fewer mental health symptoms.</p>
</blockquote>

<p>Or as Abbey says in her talk…</p>

<blockquote>
<p>Celebrate being a firework in a world that wants you to be a cube.</p>
</blockquote>

<p>Fuck yea! 🎉</p>
<p><em><strong>Like this?</strong> A <a href="https://members.gomakethings.com">Lean Web Club</a> membership is the best way to support my work and help me create more free content.</em></p>]]></description>
      <pubDate>Wed, 17 Dec 2025 14:30:00 +0000</pubDate>
      <link>https://gomakethings.com/be-a-firework/</link>
      <dc:creator>Go Make Things</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5051927017</guid>
    </item>
    <item>
      <title><![CDATA[Big Day]]></title>
      <description><![CDATA[
		<p>My friends’ boy, Daikichi, has been wanting to go with me to Disneyland for what seems like years now. To be clear, we go once or twice a year with the whole family, but he wants to go with just me, which is very sweet.</p>
		<p>But Daikichi has been so busy, studying for his upcoming entrance exam for junior high. Evenings and weekends are jam-packed with cram school. The only days he’s free are public holidays, which are among the busiest at Disneyland.</p>
		<p>I schemed with his parents, and we decided for Christmas, I could pull him out of school any day after his exam, and we could go to Disneyland that day. I thought that was a really cute idea, but it’s an intangible gift. Compared to the big LEGO set I got for his sister Nikki, just saying that we can go to Disneyland would not be enough.</p>
		<p>So I set out to make him a ticket book. Besides Disneyland in California and Magic Kingdom in Florida, Tokyo Disneyland also had ticket books! Some of them were marketed as “Big Ten” with standard ABCDE-tickets inside, with a promo ticket for a new attraction like <em>Meet the World</em>.</p>
		<p>The goal here was not to make a replica of any real ticket book, but rather make something reminiscent of a real one, something he never got to experience. Honestly, I never did either. To me, it was important to recreate not individual elements, but the <strong>vibe</strong> of having a ticket book. To that end, I started by measuring out ticket sizes and making a pattern. I took every opportunity to make fun details.</p>
		<img src="https://lmnt.me/files/images/big-day/pattern.webp">
		<p>Apart from the design, I also needed to determine what the tickets were good for, in a ranked value like the originals. Since nowadays park admission includes all attractions, the modern equivalent to an E-ticket is not really admission to an E-ticket attraction, but maybe the paid Fastpass option, Disney Premier Access. The D-ticket could be a normal free Fastpass, Priority Pass. Other things we’d purchase like dinner, lunch, and a snack could be C, B, and A tickets.</p>
		<p>I used Hoefler’s <a href="https://www.typography.com/fonts/tungsten/overview">Tungsten</a> for the big ticket letter and <a href="https://hex.xyz/HEX_Franklin/">HEX Franklin</a> for most body copy. I got to use Mark Simonson’s incredible <a href="https://www.marksimonson.com/fonts/view/bookmania/">Bookmania</a> for the slightly frilly “Big Day” and “Main Entrance Admission” titles. Hoefler’s <a href="https://www.typography.com/fonts/numbers/overview">Claimcheck Numbers</a> for the date, and my own <a href="https://crowntype.com/urayasu/">Urayasu</a> font <small>(unreleased 2.0)</small> for the Tokyo Disneyland logo. I drew everything else I needed.</p>
		<p><em>Meet the World</em> is long gone, so I needed a different promo ticket. And then it hit me. The Disney Resort Line <small>(monorail)</small> has these really cute souvenir day passes. So Daikichi can exchange one of my tickets for one of those tickets. To fill up the space, I drew the iconic hand straps hanging on the headline rule.</p>
		<img src="https://lmnt.me/files/images/big-day/ticket-design.webp">
		<p>Yes, this is all fine and great, but to make this into a reality, I didn’t just have to print them. I needed to cut them to size so they overlapped each other perfectly. Staple them with bookbinding tape to finish the edge. I needed a little thicker cardstock for the back to hold it all together. Oh, and what good would all this be if you couldn’t easily tear them out? So I needed to figure out how to perforate the pages too.</p>
		<p>I don’t make physical things very much. I’m not very crafty. Almost everything I make, I make digitally.</p>
		<p>I went to Yodobashi Camera’s stationery section and picked up everything I needed, even the exact color of bookbinding tape I could’ve ever hoped for, a baby blue, with the right kind of linen texture the original ticket books had. I got a rotary blade with an interchangeable perforator blade. And y’all, it’s my new favorite thing. It really makes you want to perforate everything. It’s so easy. Just pizza-cut your way to ticket-tearing heaven.</p>
		<img src="https://lmnt.me/files/images/big-day/tickets.webp">
		<p>Everything was printed at 7-Eleven, double-sided of course. I delicately cut them all to size, perforated them, stapled it all together, applied the tape. And I was done!</p>
		<img src="https://lmnt.me/files/images/big-day/ticket-book.webp">
		<p>Right? It’s cute! It’s done!</p>
		<img src="https://lmnt.me/files/images/big-day/ticket-books.webp">
		<p>It was <strong>not</strong> done. I can’t just hand over the ticket book like this to Daikichi. What kind of presentation is that?! It should be <strong>in</strong> something, but not a gift bag, a box, or wrapping paper. An envelope! Of course, an envelope. A Tokyo Disneyland envelope. That I also have to make.</p>
		<p>This is just who I am. I looked at some actual envelopes that once existed and there was this one castle illustration on a Tokyo Disneyland gift certificate envelope that appeared to be the same as one I’ve seen in the Tokyo Disneyland brand book, that I haven’t seen in use all that much.</p>
		<img src="https://lmnt.me/files/images/big-day/envelope-design.webp">
		<p>I carefully recreated it. Not exactly, but in the way I would draw it if I was drawing it, like everything else I was doing. I figured I probably would like to have this shape anyway, so it was worth drawing rather than auto-tracing in Illustrator, which probably would have sufficed.</p>
		<img src="https://lmnt.me/files/images/big-day/envelope.webp">
		<img src="https://lmnt.me/files/images/big-day/kingdom-of-magical-dreams.webp">
		<p>I designed the envelope such that when the ticket book was placed inside, only the words “Big Day” would be visible, with “Kingdom of Magical Dreams” underneath it, which is not the real tagline, but might as well be.</p>
		<img src="https://lmnt.me/files/images/big-day/envelope-inside.webp">
		<p>Upon gifting it to Daikichi last night, I got every reaction I could’ve hoped for. He was so excited, though we also kinda had to explain what ticket books once were. As he inspected it closely, he said something to Nob, which Nob relayed to me. “He says you’re smart.” Daikichi pointed out the circles in the pattern that aligned across tickets. The whole process, I thought I might have been making it more for me than him, but I’m glad he appreciated every detail as I did.</p>
		<p>You might not be surprised to discover that it wasn’t the end for me. I had already spent days making this exactly right, but now I felt like I had all these pieces to play with. The castle, the logo. Why not make my own little brand book?</p>
		<img src="https://lmnt.me/files/images/big-day/brand-book.webp">
		<img src="https://lmnt.me/files/images/big-day/logo.webp">
		<img src="https://lmnt.me/files/images/big-day/emblem.webp">
		<p>Anyway, Merry Christmas, fellow nerds.</p>
		<hr>
		<div>
			<a href="https://lmnt.me/"><img style="image-rendering: pixelated;" src="https://lmnt.me/files/images/badges/lmnt-animated.gif" alt="LMNT.me"></a>
			<a href="https://donate.stripe.com/eVag0g952cl90aQ9AA"><img style="image-rendering: pixelated;" src="https://lmnt.me/files/images/badges/please-donate.gif" alt="Please Donate"></a>
			<a href="https://lmnt.me/shop/"><img style="image-rendering: pixelated;" src="https://lmnt.me/files/images/badges/shop.gif" alt="Shop"></a>
			<a href="mailto:lmnt@mantia.me"><img style="image-rendering: pixelated;" src="https://lmnt.me/files/images/badges/reply-via-email.gif" alt="Reply via E-mail"></a>
			<a href="https://pdx.social/@louie"><img style="image-rendering: pixelated;" src="https://lmnt.me/files/images/badges/mastodon.gif" alt="Follow me on Mastodon"></a>
			<a href="https://patreon.com/LouieMantia"><img style="image-rendering: pixelated;" src="https://lmnt.me/files/images/badges/patreon.gif" alt="Subscribe on Patreon"></a>
		</div>
		]]></description>
      <pubDate>Thu, 25 Dec 2025 04:50:00 +0000</pubDate>
      <link>https://lmnt.me/blog/big-day.html</link>
      <dc:creator>LMNT</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5059710353</guid>
    </item>
    <item>
      <title><![CDATA[The Return of the Weirdo via Ted Gioia]]></title>
      <description><![CDATA[
                    <p>“People are less weird than they used to be,” claims psychologist Adam Mastroianni. He describes this as “an epidemic of the mundane.”</p>

                ]]></description>
      <pubDate>Tue, 23 Dec 2025 02:16:00 +0000</pubDate>
      <link>https://www.honest-broker.com/p/the-return-of-the-weirdo</link>
      <dc:creator>Links feed • Cory Dransfeldt</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5057577439</guid>
    </item>
    <item>
      <title><![CDATA[They have to be able to talk about us without us]]></title>
      <description><![CDATA[
        
      <p>It’s absolutely vital to be able to communicate effectively and efficiently to large groups of people. I’ve been lucky enough to get to refine and test my skills in communicating at scale for a few decades now, and the power of talking to communities is the one area where I’d most like to pass on what I’ve learned, because it’s this set of skills that can have the biggest effect on deciding whether good ideas and good work can have their greatest impact.</p>
<p>My own work crosses many disparate areas. Over the years, I’ve gotten to cycle between domains as distinct as building technology platforms and products for developers and creators, enabling activism and policy advocacy in service of humanist ideals, and more visible external-facing work such as public speaking or writing in various venues like magazines or on this site. (And then sometimes I dabble in my other hobbies and fun stuff like scholarship or research into areas like pop culture and media.)</p>
<p>What’s amazing is, in <em>every single one</em> of these wildly different areas, the exact same demands apply when trying to communicate to broad groups of people. This is true despite the broadly divergent cultural norms across all of these different disciplines. It can be a profoundly challenging, even intimidating, job to make sure a message is being communicated accurately, and in high fidelity, to everyone that you need to reach.</p>
<p>That vital task of communicating to a large group gets even <em>more</em> daunting when you inevitably realize that, even if you <em>were</em> to find the perfect wording or phrasing for your message, you’d still never be able to deliver your story to every single person in your target audience by yourself anyway. There will always be another person whom you’re trying to reach that you just haven’t found yet. So, is it hopeless? Is it simply impossible to effectively tell a story at scale if you don’t have massive resources?</p>
<p>It doesn’t have to be. We can start with one key insight about what it takes to get your most important stories out into the world. It’s a perspective that seems incredibly simple at first, but can lead to a pretty profound set of insights.</p>
<h2>They have to be able to talk about us <em>without us</em>.</h2>
<p>They have to be able to talk about us without us. What this phrase means, in its simplest form,  is that you have to tell a story so clear, so concise, so <em>memorable and evocative</em> that people can repeat it for you even after you’ve left the room. And the people who hear it need to be able to do this the <em>first time</em> they hear the story. Whether it’s the idea behind a new product, the core promise of a political campaign, or the basic takeaway from a persuasive essay (guess what the point of this one is!) —&nbsp;not only do you have to explain your idea and make your case, you have to be teaching your listener how to do the same thing for themselves.</p>
<p>This is a tall order, to be sure. In pop music, the equivalent is writing a hit where people feel like they can sing along to the chorus by the time they get to the end of the song for the first time. Not everybody has it in them to write a hook that good, but if you do, that thing is going to become a classic. And when someone <em>else</em> has done it, you know it because it gets stuck in your head. Sometimes you end up humming it to yourself even if you didn’t want to. Your best ideas —&nbsp;your most <em>vital</em> ideas —&nbsp;need to rest on a messaging platform that solid.</p>
<p>Delivering this kind of story actually requires substance. If you’re trying to fake it, or to force a narrative out of fluff or fakery, that will very immediately become obvious. When you set out to craft a story that travels in your absence, it has to have a body if it’s going to have legs. Bullshit is slippery and smells terrible, and the first thing people want to do when you leave the room is run away from it, not carry it with them.</p>
<h2>The mission is the message</h2>
<p>There’s another challenge to making a story that can travel in your absence: your ego has to let that happen. If you make a story that is effective and compelling enough that others can tell it, then, well…. those other people are going to tell it.  Not you. They’ll do it in their own words, and in their own voices, and make it <em>theirs</em>. They may use a similar story, but in their own phrasing, so it will resonate better with their people. This is a <em>gift</em>! They are doing you a kindness, and extending you great generosity. Respond with gratitude, and be wary of anyone who balks at not getting to be the voice or the face of a message themselves. Everyone gets a turn telling the story.</p>
<p>Maybe the simple fact that others will be hearing a good story for the first time will draw them to it, regardless of <em>who</em> the messenger is. Sometimes people get attached to the idea that <em>they</em> have to be the one to deliver the one true message. But a core precept of “talk about us without us” is that there’s a larger mission and goal that everyone is bought into, and this demands that everyone stay aligned to their values rather than to their own personal ambitions around who tells the story.</p>
<p>The truth of whomever will be most <em>effective</em> is the factor used to decide who will be the person to tell the story in any context. And this is a forgiving environment, because even if someone doesn’t get to be the voice one day, they’ll get another shot, since repetition and consistency are also key parts of this strategy, thanks to the disciplined approach that it brings to communication.</p>
<h2>The joy of communications discipline</h2>
<p>At nearly every organization where I’ve been in charge of onboarding team members in the last decade or so, one of the first messages we’ve presented to our new colleagues is, “We are disciplined communicators!” It’s a message that they hopefully get to hear as a joyous declaration, and as an assertion of our shared values. I always try to explicitly instill this value into teams I work with because, first, it’s good to communicate values explicitly, but also because this is a concept that is very seldom directly stated.</p>
<p>It is ironic that this statement usually goes unsaid, because nearly everyone who pays attention to culture understands the vital importance of disciplined communications. Brands that are strictly consistent in their use of things like logos, type, colors, and imagery get such wildly-outsized cultural impact in exchange for relatively modest investment that it’s mind-boggling to me that more organizations don’t insist on following suit. Similarly, institutions that develop and strictly enforce a standard tone of voice and way of communicating (even if the tone itself is playful or casual) capture an incredibly valuable opportunity at minimal additional cost relative to how much everyone’s already spending on internal and external communications.</p>
<p>In an era where every channel is being flooded with AI-generated slop, and when most of the slop tools are woefully incapable of being consistent about anything, simply showing up with an obviously-human, obviously-consistent story is a phenomenal way of standing out. That discipline demonstrates all the best of humanity: a shared ethos, discerning taste, joyful expression, a sense of belonging, an appealing consistency. And best of all, it represents the chance to participate for yourself — because it’s a message that you now know how to repeat for yourself.</p>
<p>Providing messages that individuals can pick up and run with on their own is a profoundly human-centric and empowering thing to do in a moment of rising authoritarianism. When the fascists in power are shutting down prominent voices for leveling critiques that they would like to censor, and demanding control over an increasingly broad number of channels, there’s reassurance in people being empowered to tell their own stories together. Seeing stories bubble up from the grassroots in collaboration, rather than being forced down upon people from authoritarians at the top, has an emotional resonance that only strengthens the substance of whatever story you’re telling.</p>
<h2>How to do it</h2>
<p>Okay, so it sounds great: Let’s tell stories that other people want to share! Now, uh… how do we do it? There are simple principles we can follow that help shape a message or story into one that is likely to be carried forward by a community on its own.</p>
<ul>
<li><strong>Ground it in your values.</strong> When we began telling the story of my last company Glitch, the conventional wisdom was that we were building a developer tool, so people would describe it as an “IDE” — an “integrated development environment”, which is the normal developer jargon for the tool coders use to write their code in. We <em>never</em> described Glitch that way. From <a href="https://web.archive.org/web/20170504080445/https://glitch.com/">day one</a>, we always said “Glitch is the friendly community where you'll build the app of your dreams” (later, “the friendly community where everybody builds the internet”). By talking about the site as a <em>friendly community</em> instead of an <code>integrated development environment</code>, it was crystal clear what expectations and norms we were setting, and what our values were. Within a few months, even our <em>competitors</em> were describing Glitch as a “friendly community” while they were trying to talk about how they were better than us about some feature or the other. That still feels like a huge victory — even the competition was talking about us without us! Make sure your message evokes the values you want people to share with each other, either directly or indirectly.</li>
<li><strong>Start with the principle.</strong> This is a topic I’ve covered before, but <a href="https://www.anildash.com/2022/01/31/you-have-to-start-with-the-principle/">you can't win unless you know what you're fighting for</a>. Identify concrete, specific, perhaps even <em>measurable</em> goals that are tied directly to the values that motivate your efforts. As <a href="https://www.anildash.com/2025/11/05/turn-the-volume-up/">noted recently</a>, Zohran Mamdani did this masterfully when running for mayor of New York City. While the <em>values</em> were affordability and the dignity of ordinary New Yorkers, the clear, understandable, measurable principle could be something as simple as “free buses”. This is a goal that everyone can get in 5 seconds, and can explain to their neighbor <em>the first time they hear it</em>. It’s a story that travels effortlessly on its own — and that people will be able to verify very easily when it’s been delivered. That’s a perfect encapsulation of “talk about us without us”.</li>
<li>**Know what makes you unique.**Another way of putting this is to simply make sure that you have a sense of self-awareness. But the story you tell about your work or your movement has to be <em>specific</em>. There can’t be platitudes or generalities or vague assertions as a core part of the message, or it will never take off. One of the most common failure states for this mistake is when people lean on <em>slogans</em>. Slogans can have their use in a campaign, for reminding people about the existence of a brand, or supporting broader messaging. But very often, people think a slogan <em>is</em> a story. The problem is that, while slogans are definitely repeatable, slogans are almost definitionally too vague and broad to offer a specific and unique narrative that will resonate. There’s no point in having people share something if it doesn’t say something. I usually articulate the challenge here like this:<strong>Only say what only <em>you</em> can say.</strong></li>
<li><strong>Be evocative, not comprehensive.</strong> Many times, when people are passionate about a topic or a movement, the temptation they have in telling the story is to work in <em>every little detail</em> about the subject. They often think, “if I include every detail, it will persuade more people, because they’ll know that I’m an expert, or it will convince them that I’ve thought of everything!” In reality, when people are not subject matter experts on a topic, or if they’re not already intrinsically interested in that topic, hearing a bunch of extensive minutia about it will almost always leave them feeling bored, confused, intimidated, condescended-to, or some combination of all of these. Instead, pick a small subset of the most <em>emotionally gripping</em> parts of your story, the aspects that have the deepest human connection or greatest relevance and specificity to the broadest set of your audience, and focus on telling those parts of the story as passionately as possible. If you succeed in communicating that initial small subset of your story effectively, then you may <em>earn</em> the chance to tell the other more complex and nuanced details of your story.</li>
<li><strong>Your enemies are your friends.</strong> Very often, when people are creating messages about advocacy, they’re focused on competition or rivals. In the political realm, this can be literal opposing candidates, or the abstraction of another political party. In the corporate world, this can be (real or imagined) competitive products or companies. In many cases, these other organizations or products or competitors occupy so much more mental space in your mind, or your team’s mind, than they do in the mind of your potential audience. Some of your audience has never heard of them at all. And a <em>huge</em> part of your audience thinks of you and your biggest rival as… basically the same thing. In a business or commercial context, customers can barely keep straight the difference between you and your competition — you’re both just part of the same amorphous blob that exists as “the things that occupy that space”. Your competitor may be the only other organization in the world that’s fighting just as hard as you are to create a market for the product that you’re selling. The same is true in the political space; sometimes the biggest friction arises over the narcissism of small differences. What we can take away from these perspectives is that our stories have to focus on what distinguishes us, yes, but also on what we might have in common with those whom we might otherwise have perceived to have been aligned with the “enemy”. Those folks might not have sworn allegiance to an opposing force; they may simply have chosen another option out of convenience, and not even seen that choice as being in opposition to your story at all.</li>
<li><strong>Find joy in repetition.</strong> Done correctly, a disciplined, collaborative, evocative message can become a mantra for a community. There’s a pride and enthusiasm that can come from people becoming proficient in sharing their own version of the collective story. And that means enjoying when that refrain comes back around, or when a slight improvement in the core message is discovered, and everyone finds a way to refine the way they’re communicating about the narrative. A lot of times, people worry that their team will get bored if they’re “just telling the same story over and over all the time”. In reality, as a brilliant man once said, <a href="https://youtu.be/FgP5VRp_myE">there’s joy in repetition</a>.</li>
<li><strong>Don’t obsess over exact wording.</strong> This one is tricky; you might say, “but you said we have to be disciplined communicators!” And it’s true: it’s important to be disciplined. But that doesn’t mean you can’t leave room for people to put their own spin on things. Let them translate to their own languages or communities. Let them augment a general principle with a specific, personal connection. If they have their own authentic experience which will amplify a story or drive a point home, let them weave that context into the consistent narrative that’s been shared over time. As long as you’re not enabling a “telephone game” where the story starts to morph into an unrecognizable form, it’s perfectly okay to add a human touch by going slightly off script.</li>
</ul>
<h2>Share the story</h2>
<p>Few things are more rewarding than when you find a meaningful narrative that resonates with the world. Stories have the power to change things, to make people feel empowered, to galvanize entire communities into taking action and recognizing their own power. There’s also a quiet reward in the craft and creativity of working on a story that travels, in finding notes that resonate with others, and in challenging yourself to get far enough out of your own head to get into someone else’s heart.</p>
<p>I still have so much to learn about being able to tell stories effectively. I still screw it up so much of the time, and I can look back on many times when I wish I had better words at hand for moments that sorely needed them. But many of the most meaningful and rewarding moments of my life have been when I’ve gotten to be in community with others, as we were not just sharing stories together, but <em>telling</em> a united story together. It unlocks a special kind of creativity that’s a lot bigger than what any one of us can do alone.</p>

    
      ]]></description>
      <pubDate>Fri, 05 Dec 2025 00:00:00 +0000</pubDate>
      <link>https://anildash.com/2025/12/05/talk-about-us-without-us/</link>
      <dc:creator>Anil Dash</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5039014446</guid>
    </item>
    <item>
      <title><![CDATA[Review your own git pull requests]]></title>
      <description><![CDATA[ <p>Last week, I wrote about <a href="https://gomakethings.com/cloud4eva/">my experience working with the team at Cloud Four</a>, and in particular some of <a href="https://gomakethings.com/pull-request-templates/">their great practices around PR management</a>.</p>

<p>Today, I wanted to talk specifically about a habit <a href="https://cloudfour.com/is/gerardo/">my friend Gerardo Rodriguez</a> got me into: reviewing your own pull request.</p>

<p>It’s pretty simple.</p>

<p>When you create a pull request in GitHub, click on the <code>Files changed</code> tab, and scroll through the diff. Anywhere you’ve done something new that’s not already explained by in-code comments, add a comment in the GUI about what you did and why.</p>

<ul>
<li>Often, it’s stuff that’s not important enough for in-code commentary, but is useful for the reviewer to know.</li>
<li>Sometimes, it’s stuff that should actually be documented in the code, and this is a good time to go back and add it.</li>
<li>Every now and then, you’ll notice a bug in your own code because you’re reading it with fresh eyes, in a different format than your text editor.</li>
</ul>

<p>It’s a simple behavior change that adds maybe 5 or 10 minutes to the time it takes to setup a PR, but it’s saved me so many headaches, and makes life for whoever reviews your PR a lot easier, too!</p>
<p><em><strong>Like this?</strong> A <a href="https://members.gomakethings.com">Lean Web Club</a> membership is the best way to support my work and help me create more free content.</em></p>]]></description>
      <pubDate>Tue, 11 Nov 2025 14:30:00 +0000</pubDate>
      <link>https://gomakethings.com/review-your-own-git-pull-requests/</link>
      <dc:creator>Go Make Things</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5011954347</guid>
    </item>
    <item>
      <title><![CDATA[Zepboundin': my first month on a GLP-1]]></title>
      <description><![CDATA[<img src="https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/2025/11/IMG_3201.JPG" alt="Zepboundin': my first month on a GLP-1"><p>Today I finish my fourth 2.5mg injection of Zepbound, a strong GLP-1 drug, and I thought I'd write about the experience of my first month on it, how I got here, and what I expect in the future. It's been wild, and after talking to half a dozen friends that are also trying these things out, it feels like we're all experimenting on ourselves, so it's helpful to share what that's like if you're curious about these drugs. </p><p>Anyone considering a GLP-1 should go in knowing what to expect and also what you might not expect.</p><figure class="kg-card kg-image-card"><img src="https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/2025/11/IMG_2092.JPG" class="kg-image" alt="Zepboundin': my first month on a GLP-1" loading="lazy" width="2000" height="1500" srcset="https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/size/w600/2025/11/IMG_2092.JPG 600w, https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/size/w1000/2025/11/IMG_2092.JPG 1000w, https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/size/w1600/2025/11/IMG_2092.JPG 1600w, https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/size/w2400/2025/11/IMG_2092.JPG 2400w" sizes="(min-width: 720px) 720px"></figure><h2 id="weight-and-metabolism">Weight and metabolism</h2><p>For most of my life, I was tall and lanky and I ate like crazy but also exercised several hours a day. Both my parents were pretty heavy, with my dad spending most of his life as a 6'1" guy weighing over 300lbs. He had a couple heart attacks in his 40s and a life-altering stroke in his 50s. My mom died a few weeks after her 65th birthday.</p><p>After I finished college, I was still pretty fit but I transitioned from distance running to casual cycling, and every few years I gained a few pounds and it definitely felt like my body was shifting into slower gears as I got older. </p><p>I dieted and exercised for the last 20 years to keep things just on the high side of normal-to-slightly obese and that was mostly fine until covid hit. The combo of being more sedentary than normal while also stressed and depressed at what was going on contributed to round-the-clock snacking and added another 30-40lbs and ever since, it's been nearly impossible to shed more than a couple pounds through diet and exercise.</p><p>I started this being 6'3" and weighing around 258lbs with my blood sugar in a Type 2 pre-diabetic state. I know BMI isn't a reliable metric overall, but I'm in the low 30s and I don't dip below 25 until I get down around 200lbs, which is when I've felt best in the past.  </p><p>I've spent a couple years walking and riding more and trying to eat less, and still it's quite difficult because now in my 50s, my metabolism feels glacial. I know I should be eating like a bird as I get older but I felt like I was constantly starving myself. I knew a few friends with good experiences taking GLP-1s and I've read plenty about them so I decided to finally give them a try.</p><h2 id="the-first-week-was-a-shock">The first week was a shock</h2><p>Hank Green dropped a video about GLP-1 drugs that is mostly positive about them, but I want to underscore how right he is when he says these are very powerful drugs with lots of unknown or unstudied side effects that can surprise you.</p><figure class="kg-card kg-embed-card"><iframe width="200" height="113" src="https://www.youtube.com/embed/sJzEMVkCCGg?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="Ozempic Is Both Better And Worse Than You Think"></iframe></figure><p>It took about 24hrs for the first injection to hit me, but the feeling of it was pretty substantial.</p><p><strong>The most shocking thing after my first dose was a <em>complete</em> absence of hunger.</strong></p><p>Honestly, it felt bizarre. It was as if all the nerves that linked my brain to my stomach were severed in that first week. I had no idea when I was hungry or when I was full. I could eat two bites or finish a plate of food. My stomach felt completely detached from everything and I had to rethink a lot of things. I ate lightly every few hours just to make sure I had calories going in but I almost had to remind myself to do it. </p><p>I was instantly hit by a wave of heartburn/acid reflux, which I rarely experienced in the past. I knew it was coming from my digestive system slowing down, but it was stronger than I expected at first.</p><p>The good news is that heartburn did eventually go away after 2-3 weeks as I got used to it and my generally smaller meals. Now in my fourth week, I'm finally starting to feel hunger pains again around lunchtime each day.</p><p>If you asked me a few months ago how much time I spent idly thinking about food, I'd probably say not very much, but after I started taking a GLP-1 and my hunger went away it honestly felt like half of my brain was freed up to do other things. I was no longer thinking about my next meal, or the one after that, and it sort of dawned on me that my life was previously way more food-motivated than I thought.</p><p>As this first month draws to a close, I'm down a bit over ten pounds and I suspect at the two month mark I might move up to a 5mg dose as the effects start to lessen, which is what most people tend to do on it.</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/2025/11/IMG_3046.JPG" class="kg-image" alt="Zepboundin': my first month on a GLP-1" loading="lazy" width="2000" height="1500" srcset="https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/size/w600/2025/11/IMG_3046.JPG 600w, https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/size/w1000/2025/11/IMG_3046.JPG 1000w, https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/size/w1600/2025/11/IMG_3046.JPG 1600w, https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/size/w2400/2025/11/IMG_3046.JPG 2400w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Portland Japanese Garden, October 2025</span></figcaption></figure><h2 id="weird-side-effects-i-didnt-expect">Weird side effects I didn't expect</h2><p>I read all I could about side effects going into this, but a few things still threw me unexpectedly once I started taking the drug.</p><p>A week into this, I ate some dodgy seafood at a restaurant while out with a few friends. One friend got food poisoning from the meal we shared but it passed for them in a day like I expected the same bad food would for me. However, being on a GLP-1, my symptoms lingered for nearly a week as the stuff worked itself out of me quite slowly. There's not much you can do to prevent food poisoning, but know that any time it does hit, it's <strong><em>really</em></strong> going to affect you for 2-3x longer than expected due to your slowed digestive system.</p><p>I had a few friends talk about a reduction in inflammation as side effects of a GLP-1 and I know someone taking it to counteract joint pain that they say is quite effective. For me, ever since I started taking it, getting up in the mornings I feel like I have way less aches and pains overall.</p><p>I have a long history of back pain and hamstring tightness so much so that I've seen the same massage therapist for the past ten years about once-a-month for some deep-tissue work. Last week I had my first session post GLP-1 and normally it's a slightly painful experience that feels better a day later, but this session felt like my therapist was only pushing my muscles at about 25% pressure. Typically my back feels wrecked immediately after a massage, but in this case I had zero back pain post-session. </p><p>It's weird but I'm guessing whatever effects the drug is having on my appetite is also working on pain centers in my body? At this point, I'm considering reducing or eliminating the regular massage entirely. I know people who go to acupuncture regularly that said they've stopped after starting a GLP-1 drug, because they no longer need the pain relief.</p><figure class="kg-card kg-image-card"><img src="https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/2025/11/IMG_2907.JPG" class="kg-image" alt="Zepboundin': my first month on a GLP-1" loading="lazy" width="2000" height="1500" srcset="https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/size/w600/2025/11/IMG_2907.JPG 600w, https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/size/w1000/2025/11/IMG_2907.JPG 1000w, https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/size/w1600/2025/11/IMG_2907.JPG 1600w, https://storage.ghost.io/c/91/15/9115e440-4672-4090-9706-8335a7b154ee/content/images/size/w2400/2025/11/IMG_2907.JPG 2400w" sizes="(min-width: 720px) 720px"></figure><h2 id="advice-for-anyone-considering-these-drugs">Advice for anyone considering these drugs</h2><p>Here are my lessons learned from being on this and experimenting for the past month.</p><h3 id="use-shotsy">Use Shotsy</h3><p><a href="https://shotsyapp.com/?ref=a.wholelottanothing.org" rel="noreferrer">There's a great app</a> for tracking your predicted levels of the drug in your system that can also talk to Apple Health and automatically graph your ongoing weight if you've got <a href="https://amzn.to/3LsThz2?ref=a.wholelottanothing.org" rel="noreferrer">a Withings scale</a> that can transmit your daily weigh-ins to the cloud.</p><h3 id="go-through-your-own-doctor-and-health-plan">Go through your own doctor and health plan</h3><p>I am doing my first couple months of this through Ro.co and it's pretty goofy. You consult with a "doctor" and "nurses" that feel like AI prompts when they talk to you. Of course, they approve everyone and will ship you injections within a day and it generally runs about $500 a month.</p><p>I wish I listened to Hank Green in the video embedded above and went through my local doctor and my own health insurance. Not only would I be getting more personal, comprehensive care, my costs would drop significantly. Friends who converted from a random online "health" store to their health plan typically only have to spend $25-50 per month on these drugs.</p><h3 id="weekly-shots-are-no-fun">Weekly shots are no fun</h3><p>I kinda hate the delivery of Zepbound, which is a vial of liquid that you have to administer via a shot that I can't make myself do on my own. Once you get health insurance coverage, they can send you pre-filled injectors with smaller needles to make it easier, but honestly, I'm guessing in a few years these drugs might be over the counter as small tablets or gummies instead of injections and I can't wait for that to be the norm.</p><h3 id="expect-this-for-the-long-haul">Expect this for the long haul</h3><p>Everyone I talk to who is considering a GLP-1 seems to worry about what they'll do after 6 to 12 months of using it once they've lost weight. From all accounts it seems like people who stop regain their appetite and put much of the weight back on. I've been on a couple small doses of maintenance drugs for other ailments that I suspect I'll remain on for life and I think it's not a huge deal to have one more thing to take long term. I suspect I'll go down to a minimal dose once I've shed 50 or so pounds.</p><h3 id="find-a-social-support-network-wherever-you-can">Find a social support network wherever you can</h3><p>The best advice I found about taking GLP-1's is from a private group chat with half a dozen friends who currently take one. We also share the space with a handful of friends who are considering taking it. We post questions about our side effects and get helpful responses from those who've been on the drug longer. New friends are asking lots of questions that we're all answering together and honestly it has been really useful. I don't know of any apps or sites that can fill this void for everyone, but having a small supportive friends network of people going through the same issues has been incredibly helpful.</p><h2 id="final-thoughts">Final thoughts</h2><p>Overall, I've had a good experience taking Zepbound that has made me rethink my relationship with food. I still enjoy the taste of things, but my next meal doesn't dominate my thinking like it did before. The side effects are wider ranging than I expected and I think we'll see a lot of interesting research around these drugs in the next few years. I don't know how ubiquitous these prescriptions will become, but I suspect health insurance companies in America will steer people towards it as a form of preventative care.</p><p>Now that I've taken this, I have to admit it has more of a "magic pill" effect than I suspected it would. It truly changed overnight how much food I take in, what my relationship is like to food, and helps me get on a better path towards a healthier weight that doesn't stress out my body. I've never been one to trust that miracle drugs could fix all our problems, but this one comes close. </p><p>Lastly, I have to acknowledge that as a middle-aged guy I don't go through 1/1000th the societal pressure that other people face around weight, but I don't feel bad about taking this drug and I am definitely seeing my numbers move in a better direction for the first time in years. The benefits seem wide ranging and while the side effects are significant, they're livable and I suspect general exercise and my performance on a bike will improve as my weight goes down over the next few months.</p>]]></description>
      <pubDate>Thu, 06 Nov 2025 19:25:51 +0000</pubDate>
      <link>https://a.wholelottanothing.org/zepboundin-my-first-month-on-a-glp-1/</link>
      <dc:creator>A Whole Lotta Nothing</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5006725950</guid>
    </item>
    <item>
      <title><![CDATA[My HTML Web Component boilerplate for 2026]]></title>
      <description><![CDATA[ 

<p><a href="https://gomakethings.com/guides">My digital garden</a> has an entire section with <a href="https://gomakethings.com/snippets">copy/paste code snippets and boilerplates</a>.</p>

<p>I use these nearly every day when writing code. It helps me start working faster, and saves me from having to rewrite the same code over-and-over again.</p>

<p>I created <a href="https://gomakethings.com/snippets/boilerplates/web-component/">web component boilerplate</a> years ago. Since then, my approach to writing web component has changed quite a bit.</p>

<p>I just updated the boilerplate, adding everything I’ve learned from working on <a href="https://kelpui.com">Kelp UI</a>. Today, I wanted to explain how it works.</p>

<p>Let’s dig in!</p>

<h2 id="instantiate-in-the-connectedcallback-method">Instantiate in the <code>connectedCallback()</code> method</h2>

<p>I used to instantiate my web components (run all of the code to set them up) inside the <code>constructor()</code>.</p>

<p>But when working with Kelp, <a href="https://gomakethings.com/web-components-dont-need-a-constructor/">I learned that this can throw errors</a>, especially if you try to move or replace components with JS after they’ve instantiated.</p>

<p>I moved all of that code to the <code>connectedCallback()</code> method, which runs when the web component is connected to the DOM, and it fixed the issue.</p>
<div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="cm">/**
</span><span class="cm"> * Initialize on connect
</span><span class="cm"> */</span>
<span class="nx">connectedCallback</span><span class="p">()</span> <span class="p">{</span>
	<span class="c1">// Startup code...
</span><span class="c1"></span><span class="p">}</span>
</code></pre></div>
<p>I also learned that you don’t need a <code>constructor()</code> at all if you do this, so I removed that method entirely from my boilerplate. There’s no need for it.</p>

<h2 id="waiting-for-dom-ready">Waiting for DOM Ready</h2>

<p>Web component JS gets loaded all sorts of different ways, and the DOM might yet be ready when the JS runs.</p>

<p>I’ve started checking the <code>document.readyState</code> first. If it’s not <code>loading</code>, I’ll instantiate immediately. If its still loading, I’ll set a one-time event listener for the <code>DOMContentLoaded</code> event and run my instantiations then.</p>

<p>To make that easier, I include an <code>init()</code> method that runs the actual instantiation code.</p>
<div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="cm">/**
</span><span class="cm"> * Initialize on connect
</span><span class="cm"> * Checks for DOM status first, ensuring code doesn't run before required
</span><span class="cm"> * elements exist in the DOM.
</span><span class="cm"> */</span>
<span class="nx">connectedCallback</span><span class="p">()</span> <span class="p">{</span>
	<span class="k">if</span> <span class="p">(</span><span class="nb">document</span><span class="p">.</span><span class="nx">readyState</span> <span class="o">!==</span> <span class="s1">'loading'</span><span class="p">)</span> <span class="p">{</span>
		<span class="k">this</span><span class="p">.</span><span class="nx">init</span><span class="p">();</span>
		<span class="k">return</span><span class="p">;</span>
	<span class="p">}</span>
	<span class="nb">document</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s1">'DOMContentLoaded'</span><span class="p">,</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="k">this</span><span class="p">.</span><span class="nx">init</span><span class="p">(),</span> <span class="p">{</span>
		<span class="nx">once</span><span class="o">:</span> <span class="kc">true</span><span class="p">,</span>
	<span class="p">});</span>
<span class="p">}</span>
</code></pre></div>
<h2 id="private-instance-properties">Private instance properties</h2>

<p>With Kelp, I’ve started using <a href="https://gomakethings.com/private-class-features-in-vanilla-js-classes/">private instance methods and properties</a> a lot more.</p>

<p>For methods, you can just slap a hashtag (<code>#</code>) in front of the name. For instance properties, you need to declare them at the start of the class.</p>

<p>I’ve <a href="https://gomakethings.com/typescript-without-typescript/">been using JSDoc to define types</a> for these as well.</p>
<div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="nx">customElements</span><span class="p">.</span><span class="nx">define</span><span class="p">(</span>
	<span class="s1">'my-library'</span><span class="p">,</span>
	<span class="k">class</span> <span class="k">extends</span> <span class="nx">HTMLElement</span> <span class="p">{</span>
		<span class="c1">// Declare private instance properties
</span><span class="c1"></span>		<span class="cm">/** @type HTMLButtonElement | null */</span> <span class="err">#</span><span class="nx">btn</span><span class="p">;</span>
		<span class="cm">/** @type string */</span> <span class="err">#</span><span class="nx">greeting</span><span class="p">;</span>
		<span class="cm">/** @type HTMLElement */</span> <span class="err">#</span><span class="nx">message</span><span class="p">;</span>

	<span class="c1">// ...
</span><span class="c1"></span><span class="p">});</span>
</code></pre></div>
<h2 id="the-handleevent-method">The <code>handleEvent()</code> method</h2>

<p>I make heavy use of <a href="https://gomakethings.com/the-handleevent-method-is-the-absolute-best-way-to-handle-events-in-web-components/">the <code>handleEvent()</code> method</a> for handling event listeners with my web components.</p>

<p>It makes it really easy to access instance properties and methods (<code>this</code>) inside the handlers. It also means that if you component gets removed and attached back to the DOM a few times, the callback method still only runs once without you needing to do any removal/cleanup.</p>
<div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="nx">connectedCallback</span><span class="p">()</span> <span class="p">{</span>
	<span class="c1">// ...
</span><span class="c1"></span>
	<span class="c1">// Attach event listeners
</span><span class="c1"></span>	<span class="k">this</span><span class="p">.</span><span class="err">#</span><span class="nx">btn</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s1">'click'</span><span class="p">,</span> <span class="k">this</span><span class="p">);</span>
<span class="p">}</span>

<span class="cm">/**
</span><span class="cm"> * Handle events for the web component
</span><span class="cm"> * @param  {Event} event
</span><span class="cm"> */</span>
<span class="nx">handleEvent</span><span class="p">(</span><span class="nx">event</span><span class="p">)</span> <span class="p">{</span>
	<span class="k">if</span> <span class="p">(</span><span class="nx">event</span><span class="p">.</span><span class="nx">type</span> <span class="o">===</span> <span class="s1">'click'</span><span class="p">)</span> <span class="p">{</span>
		<span class="k">this</span><span class="p">.</span><span class="err">#</span><span class="nx">onClick</span><span class="p">(</span><span class="nx">event</span><span class="p">);</span>
	<span class="p">}</span>
<span class="p">}</span>
</code></pre></div>
<h2 id="no-event-cleanup">No event cleanup</h2>

<p>I use to remove every event I attached in the <code>connectedCallback()</code> method in the <code>disconnectedCallback()</code>.</p>

<p>But the browser will automatically garbage collect events attached to the custom element and any of its child elements when its removed from the DOM when you use the <code>handleEvent()</code> method.</p>

<p>Now, the only ones I remove are events attached to elements <em>outside</em> the custom element, because they’re not automatically cleaned up.</p>
<div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="nx">connectedCallback</span><span class="p">()</span> <span class="p">{</span>
	<span class="c1">// ...
</span><span class="c1"></span>
	<span class="c1">// Attach event listeners
</span><span class="c1"></span>	<span class="nb">document</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s1">'input'</span><span class="p">,</span> <span class="k">this</span><span class="p">);</span>
<span class="p">}</span>

<span class="c1">// Cleanup global event listeners on disconnect
</span><span class="c1"></span><span class="nx">disconnectedCallback</span><span class="p">()</span> <span class="p">{</span>
	<span class="nb">document</span><span class="p">.</span><span class="nx">removeEventListener</span><span class="p">(</span><span class="s1">'input'</span><span class="p">,</span> <span class="k">this</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div>
<p>As a bonus for using <code>handleEvent()</code>, this is really easy to do because the handler is always <code>this</code>. No more caching callback methods for cleanup purposes.</p>

<h2 id="no-shadow-dom">No shadow DOM</h2>

<p><a href="https://gomakethings.com/the-shadow-dom-is-an-antipattern/">It’s an anti-pattern</a> that makes everything about using web components worse. Death to the shadow DOM!</p>
<p><em><strong>Like this?</strong> A <a href="https://members.gomakethings.com">Lean Web Club</a> membership is the best way to support my work and help me create more free content.</em></p>]]></description>
      <pubDate>Thu, 18 Dec 2025 14:30:00 +0000</pubDate>
      <link>https://gomakethings.com/my-html-web-component-boilerplate-for-2026/</link>
      <dc:creator>Go Make Things</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5053276894</guid>
    </item>
    <item>
      <title><![CDATA[Forced Colors Mode]]></title>
      <description><![CDATA[ 

<p><em>Forced Colors Mode</em> is an accessibility feature that, as <a href="https://benmyers.dev/encyclopedia/forced-colors-mode/">my friend Ben Myers explains</a>…</p>

<blockquote>
<p>replaces websites’ colors (backgrounds, borders, text, focus outlines, and more) with a significantly reduced color palette, sometimes comprised of user-selected colors. Forced colors mode also makes changes which simplify an interface’s appearance…</p>
</blockquote>

<p>Today, I wanted to talk briefly about what is, how it affects designs, and how to build for it. Let’s dig in!</p>

<h2 id="bye-bye-backgrounds">Bye bye backgrounds</h2>

<p>I first learned about <em>forced colors mode</em> when someone <a href="https://github.com/cferdinandi/kelp/issues/210">Michael Matthews opened a very helpful ticket up about on Kelp UI</a>.</p>

<p><a href="https://kelpui.com/docs/components/tabs/">Kelp’s tabs component</a> uses background color to show which tab is currently selected. <em>Forced colors mode</em> strips those colors out, making it impossible to tell which tab is currently active.</p>

<p>This also affected <a href="https://kelpui.com/docs/components/radio/">radio buttons</a>, <a href="https://kelpui.com/docs/components/switch/">toggle switches</a>, how well <a href="https://kelpui.com/docs/components/dialog/">dialog modals</a> standout from the background, and more.</p>

<p>Fortunately, this is relatively easy to address, once you know about it.</p>

<h2 id="windows-only">Windows-only</h2>

<p>One of the reasons this had completely flown under my radar is because it is still <em>mostly</em> a Windows-only feature.</p>

<p>macOS has an <em>Increase Contrast</em> accessibility setting that does not seem to pass information along to the browser. <a href="https://polypane.app/blog/forced-colors-explained-a-practical-guide/#forced-colors-in-firefox">Firefox has a specific setting for it</a>.</p>

<p>In Chromium, you can emulate it by opening up developer tools, opening up the command palette (Command+Shift+P in macOS), and typing <em>forced colors</em>, which surfaces some options.</p>

<p>Even though <em>enabling</em> forced colors mode is OS-specific, targeting it with CSS is widely supported cross-browser.</p>

<h2 id="css-and-forced-colors-mode">CSS and forced colors mode</h2>

<p>In Kelp’s CSS, I added media queries for <code>forced-colors: active</code> to detect forced colors mode in various places and update styles…</p>
<div class="highlight"><pre class="chroma"><code class="language-css" data-lang="css"><span class="p">@</span><span class="k">media</span> <span class="o">(</span><span class="nt">forced-colors</span><span class="o">:</span> <span class="nt">active</span><span class="o">)</span> <span class="p">{</span>
	<span class="c">/* high-contrast styles... */</span>
<span class="p">}</span></code></pre></div>
<p>The easiest fix in many situations is to use really beefy borders for things that should stand out.</p>

<p>For example, I give my dialog modals a really thick border so that they standout better from the background, since the <code>::backdrop</code> doesn’t show up in forced colors mode.</p>
<div class="highlight"><pre class="chroma"><code class="language-css" data-lang="css"><span class="p">@</span><span class="k">media</span> <span class="o">(</span><span class="nt">forced-colors</span><span class="o">:</span> <span class="nt">active</span><span class="o">)</span> <span class="p">{</span>
	<span class="nt">dialog</span> <span class="p">{</span>
		<span class="k">border-width</span><span class="p">:</span> <span class="mf">0.5</span><span class="kt">em</span><span class="p">;</span>
	<span class="p">}</span>
<span class="p">}</span></code></pre></div>
<p>I do something similar for the currently active tab.</p>
<div class="highlight"><pre class="chroma"><code class="language-css" data-lang="css"><span class="p">@</span><span class="k">media</span> <span class="o">(</span><span class="nt">forced-colors</span><span class="o">:</span> <span class="nt">active</span><span class="o">)</span> <span class="p">{</span>
	<span class="nt">kelp-tabs</span> <span class="o">[</span><span class="nt">role</span><span class="o">=</span><span class="s2">"tab"</span><span class="o">][</span><span class="nt">aria-selected</span><span class="o">=</span><span class="s2">"true"</span><span class="o">]</span> <span class="p">{</span>
		<span class="k">border-width</span><span class="p">:</span> <span class="mf">0.5</span><span class="kt">em</span><span class="p">;</span>
	<span class="p">}</span>
<span class="p">}</span></code></pre></div>
<p>Remember <a href="https://gomakethings.com/creating-a-toggle-switch-with-just-css/">that toggle switch</a> we built last week? It requires a slightly different approach.</p>

<p>For that one, the background color is what makes it look like a switch. There’s no way around that!</p>

<p>But <a href="https://moderncss.dev/pure-css-custom-checkbox-style/">my friend Stephanie Eckles found a neat trick</a>: in forced colors mode, background color <em>will</em> show up if you give it a value of <code>CanvasText</code>, which maps to the current text color.</p>

<p>For our toggle switch, we can use that provide a border and background and ensure our switch is still clearly visible and it’s state is apparent.</p>
<div class="highlight"><pre class="chroma"><code class="language-css" data-lang="css"><span class="p">@</span><span class="k">media</span> <span class="o">(</span><span class="nt">forced-colors</span><span class="o">:</span> <span class="nt">active</span><span class="o">)</span> <span class="p">{</span>
	<span class="o">[</span><span class="nt">role</span><span class="o">=</span><span class="s2">"switch"</span><span class="o">]</span> <span class="p">{</span>
		<span class="k">border</span><span class="p">:</span> <span class="mi">1</span><span class="kt">px</span> <span class="kc">solid</span> <span class="n">CanvasText</span><span class="p">;</span>
	<span class="p">}</span>

	<span class="o">[</span><span class="nt">role</span><span class="o">=</span><span class="s2">"switch"</span><span class="o">]</span><span class="p">:</span><span class="nd">checked</span> <span class="p">{</span>
		<span class="k">background-color</span><span class="p">:</span> <span class="n">CanvasText</span><span class="p">;</span>
	<span class="p">}</span>
<span class="p">}</span></code></pre></div>
<h2 id="ugly-and-obvious">Ugly and obvious</h2>

<p>The most important thing to keep in mind when designing for forced colors mode is that <a href="https://www.smashingmagazine.com/2022/06/guide-windows-high-contrast-mode/">making things obvious is the most important consideration</a>.</p>

<p>This browsing mode is often used by people who are not blind but do have vision issues, people who suffer migraines from lots of color, and so on.</p>

<p>In forced colors mode, your design preferences no longer matter.</p>

<p>Your site almost certainly will look ugly, or at least different from how you’d like it, and that’s ok. That’s the point. It’s not about you!</p>

<p>This is an instance where letting go and ceding control to the user is particularly important.</p>
<p><em><strong>Like this?</strong> A <a href="https://members.gomakethings.com">Lean Web Club</a> membership is the best way to support my work and help me create more free content.</em></p>]]></description>
      <pubDate>Mon, 15 Dec 2025 14:30:00 +0000</pubDate>
      <link>https://gomakethings.com/forced-colors-mode/</link>
      <dc:creator>Go Make Things</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5049439780</guid>
    </item>
    <item>
      <title><![CDATA[IndieWeb Carnival: where do I wish to see the IndieWeb in 2030]]></title>
      <description><![CDATA[
        <p>This is my entry for <a href="https://vhbelvadi.com/indieweb-carnival-future">December’s IWC</a> hosted by <a href="https://vhbelvadi.com">V.H. Belvadi</a>. If you have thoughts on the subject, make sure to write a blog post before the end of the month, and join the carnival.</p>
<hr>
<p>I’m not good at making predictions, so I don’t really know what the IndieWeb is gonna look like in 5 years. If I had to guess, I’d say it will probably look very much like it looks now, only with more AI-generated nonsense sprinkled throughout. But rather than making predictions, let me write about hopes and wishes. My feelings when it comes to the web can be described as a pendulum or a standing wave. I alternate between naïve optimism and endless pessimism. I’m writing this in my downward swinging phase, which means this post is gonna be kinda bleak.</p>
<hr>
<p>There’s <a href="https://social.spejset.org/@cellularmoose/115661783301109160">a post</a> I keep thinking about. More specifically, this question:</p>
<blockquote>
<p>In trying to escape the torment nexus, have we just built a nicer version of the torment nexus?</p>
</blockquote>
<p>Here’s my hope for the IW in 2030: I hope that in 5 years, we have stopped pretending. Pretending that replacing corporate platforms with bad copies of the same platforms is a good and desirable thing to do. Pretending that what we really need to solve the issues that are plaguing the web is more tools and more protocols. Pretending that all the people out there who use the web on a daily basis care about the same things we do. Pretending that the fault for all this digital mess lies entirely on the shoulders of a few mega corporations, while the billions of people out there are just bystanders, caught in the crossfire.</p>
<p>But also stop pretending that everything is doomed, that the web is about to die, that AI will sloppify everything, that writing on a blog is pointless, that tending to a digital garden is wasted time.</p>
<p>Yes, a vast chunk of the 2025 web fucking sucks. It’s an unusable mess, and going in without adblockers, VPNs, and network-level filters is an atrocious experience. And that won’t change in 2030. If there’s one prediction I can confidently make, it's this one: in 5 years, the web is still gonna be a mess.</p>
<p>At the same time, though, the web is a marvelous place if you know how to navigate it. There’s still delight to be found out there, and it’s still full of genuinely kind and wonderful people. And that’s my hope, my wish, and my dream for the IndieWeb in 2030: that we focus less on what’s on the screen and more on who’s in front of it.</p>
<p>Because people matter. Because you matter. And in this idiotic AI age we’re going through, all this matters more than ever.</p>        <hr>
        <p>Thank you for keeping RSS alive. You're awesome.</p>
        <p><a href="mailto:hello@manuelmoreale.com">Email me</a> ::
        <a href="https://manuelmoreale.com/guestbook">Sign my guestbook</a> :: 
        <a href="https://ko-fi.com/manuelmoreale">Support for 1$/month</a> :: 
        <a href="https://manuelmoreale.com/supporters">See my generous supporters</a> :: 
        <a href="https://buttondown.email/peopleandblogs">Subscribe to People and Blogs</a></p>
    ]]></description>
      <pubDate>Sun, 14 Dec 2025 09:10:00 +0000</pubDate>
      <link>https://manuelmoreale.com/thoughts/indieweb-carnival-where-do-i-wish-to-see-the-indieweb-in-2030</link>
      <dc:creator>Manuel Moreale — Everything Feed</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5048074208</guid>
    </item>
    <item>
      <title><![CDATA[App selection criteria]]></title>
      <description><![CDATA[
                    <p>The last app I bought through Apple's app store was a client for my <a href="https://www.audiobookshelf.org">Audiobookshelf</a> instance and this reminded me why <a href="https://www.coryd.dev/posts/2025/i-made-a-music-app">I'd built a Navidrome client</a>. I wanted to use the web application as a PWA, but Apple doesn't support continuous audio playback in PWAs or Safari on iOS. Audio plays, but it never advances to the next track.</p>
<p>I've moved my reading, audiobook and podcast activity to <a href="https://www.audiobookshelf.org">Audiobookshelf</a>, but it's ebook support is rudimentary.<sup id="fnref:1"><span>1</span></sup> I briefly opened up Xcode and started prototyping an ebook reader that would leverage <a href="https://www.audiobookshelf.org">Audiobookshelf</a>'s robust API and quickly realized that there was no reason to build a native app for this. It didn't require any native APIs, so why build something I intended largely for my own use only to deal with the headache of Apple's review process?</p>
<p>Instead, I closed Xcode, opened <a href="https://cursor.com">Cursor</a> and scaffolded out a PWA that uses <a href="https://github.com/gerhardsletten/react-reader">react-reader</a> (which leverages <a href="https://github.com/futurepress/epub.js">epub.js</a>) instead. I could iterate far quicker, have a performant and robust experience for my use case without anything between myself and any updates I want to issue. I have a reliable app that relies on authenticating against my <a href="https://www.audiobookshelf.org">Audiobookshelf</a> instance, persists preferences and state using an SQLite database and none of the platform lock-in or review headaches.</p>
<p>PWAs, being built on web technology, offer more transparency as well. I can inspect the application, dig through network calls and see not only how it works but evaluate whether it's whether there are possible privacy concerns. This has lead me to establish new criteria for selecting applications I intend to use:</p>
<ul>
<li>If there’s a PWA, use it.</li>
<li>If a platform-specific API is required, use a native app that respects your privacy.</li>
<li>If neither are options, don’t use it.</li>
</ul>
<p>I'm interacting with <a href="https://linkding.link">linkding</a>, my epub reader and <a href="https://joinmastodon.org">Mastodon</a> as web apps saved to my home screen on iOS and have <a href="https://joinmastodon.org">Mastodon</a> in my dock on macOS. Audio playback requires native APIs to work reliably, so <a href="https://www.coryd.dev/posts/2025/i-made-a-music-app">I built a Navidrome client</a> and selected an <a href="https://github.com/rasmuslos/ShelfPlayer">Audiobookshelf client</a> that respects my privacy.</p>
<p>In an ideal world, Apple would act in good faith and provide better support for PWAs. This world, however, is far from perfect and Apple has shown they'll only make choices like that, that benefit users, under duress.</p>
<p>The web platform provides an excellent experience, an open experience and one that should be preferred — when possible — over an onerous process that Apple controls.</p>
<p>I'll prefer openness and I will always value privacy.</p>
<div class="footnotes" role="doc-endnotes"><hr><ol><li class="footnote" id="fn:1" role="doc-endnote"><p>I'm not surprised by this, it's not the focus of the app.&nbsp;<span>↩</span></p></li></ol></div>

                ]]></description>
      <pubDate>Thu, 11 Dec 2025 18:30:00 +0000</pubDate>
      <link>https://www.coryd.dev/posts/2025/app-selection-criteria</link>
      <dc:creator>Posts feed • Cory Dransfeldt</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5045547435</guid>
    </item>
    <item>
      <title><![CDATA[The Deep Card Conundrum]]></title>
      <description><![CDATA[
<p>In the world of web design, we often talk about “cards”. Those neat little rectangles that group information together are the bread and butter of modern UI. But usually, these cards are as flat as the screens they live on. Maybe they have a subtle drop shadow to hint at elevation, but that’s where the illusion ends.</p>



<p>But what if a card wasn’t just a surface? What if it was a <em>window</em>?</p>



<p>Enter the <strong>Deep Card</strong>.</p>



<div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_ZYWrKoP/ebbe7336c4eac3acf7e9fde40387c8bc" src="//codepen.io/anon/embed/ZYWrKoP/ebbe7336c4eac3acf7e9fde40387c8bc?height=450&amp;theme-id=1&amp;slug-hash=ZYWrKoP/ebbe7336c4eac3acf7e9fde40387c8bc&amp;default-tab=result" height="450" scrolling="no" frameborder="0" allowfullscreen="" allowpaymentrequest="" name="CodePen Embed ZYWrKoP/ebbe7336c4eac3acf7e9fde40387c8bc" title="CodePen Embed ZYWrKoP/ebbe7336c4eac3acf7e9fde40387c8bc" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>



<p>Imagine a card that isn’t just a 2D plane, but a container with actual volume. A card that holds a miniature 3D world inside it. When you rotate this card, you don’t just see it skew, you see the elements inside it shift in perspective, revealing their depth. It’s like holding a glass box filled with floating objects.</p>



<p>The effect is mesmerizing. It transforms a static element into something tactile and alive. It invites interaction. Whether it’s for a digital trading card game, a premium product showcase, or just a portfolio piece that screams “look at me,” the Deep Card adds a layer of polish and “wow” factor that flat design simply can’t match.</p>



<p>But as I quickly discovered, building this illusion, especially one that feels right and performs smoothly, is a bit more of a puzzle than it first appears.</p>



<h2 class="wp-block-heading" id="the-css-trap">The CSS Trap</h2>



<p>There are plenty of JavaScript libraries out there that can handle this, but I’m a bit of a CSS purist (read: stubborn). I’ve spent years pushing stylesheets to their absolute limits, and I was convinced that a clean, performant, pure CSS solution was hiding in plain sight.</p>



<p>On paper, the logic seems flawless. If you’ve dabbled in 3D CSS before, you know the drill:</p>



<ol class="wp-block-list">
<li><strong>Set the Stage</strong>: Take a container element and give it some&nbsp;<code>perspective</code>.</li>



<li><strong>Build the World</strong>: Position the child elements in 3D space (<code>translateZ</code>,&nbsp;<code>rotateX</code>, etc.).</li>



<li><strong>Preserve the Illusion</strong>: Apply&nbsp;<code>transform-style: preserve-3d</code>&nbsp;so all those children share the same 3D space.</li>
</ol>



<p>Simple, right?</p>



<p>But here’s the catch. For a true “card” effect, you need the content to stay <em>inside</em> the card boundaries. If a 3D star floats “up” towards the viewer, you don’t want it to break the frame, you want it to be clipped by the card’s edges, reinforcing the idea that it’s inside a container.</p>



<p>So, naturally, you add <code>overflow: clip</code> (or <code>hidden</code>) to the card. And that is the exact moment everything falls apart.</p>



<figure class="wp-block-image size-large"><img data-recalc-dims="1" loading="lazy" decoding="async" width="1024" height="567" src="https://i0.wp.com/frontendmasters.com/blog/wp-content/uploads/2025/12/t0LtCaNQ.png?resize=1024%2C567&amp;ssl=1" alt="Comparison of overflow properties in CSS: left shows 'overflow: visible;' with layered rectangles, right shows 'overflow: clip;' with clipped edges." class="wp-image-7960" srcset="https://i0.wp.com/frontendmasters.com/blog/wp-content/uploads/2025/12/t0LtCaNQ.png?resize=1024%2C567&amp;ssl=1 1024w, https://i0.wp.com/frontendmasters.com/blog/wp-content/uploads/2025/12/t0LtCaNQ.png?resize=300%2C166&amp;ssl=1 300w, https://i0.wp.com/frontendmasters.com/blog/wp-content/uploads/2025/12/t0LtCaNQ.png?resize=768%2C426&amp;ssl=1 768w, https://i0.wp.com/frontendmasters.com/blog/wp-content/uploads/2025/12/t0LtCaNQ.png?w=1200&amp;ssl=1 1200w" sizes="auto, (max-width: 1000px) 100vw, 1000px"></figure>



<div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_RNaQZew/af221d197a7af667776c8a1936e186e6" src="//codepen.io/anon/embed/RNaQZew/af221d197a7af667776c8a1936e186e6?height=450&amp;theme-id=1&amp;slug-hash=RNaQZew/af221d197a7af667776c8a1936e186e6&amp;default-tab=result" height="450" scrolling="no" frameborder="0" allowfullscreen="" allowpaymentrequest="" name="CodePen Embed RNaQZew/af221d197a7af667776c8a1936e186e6" title="CodePen Embed RNaQZew/af221d197a7af667776c8a1936e186e6" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>



<h2 class="wp-block-heading" id="the-spec-says-no">The Spec Says No</h2>



<p>Suddenly, your beautiful 3D scene flattens out. The depth vanishes. The magic is gone.</p>



<p>Why? Because according to the&nbsp;<a href="https://www.w3.org/TR/css-transforms-2/#grouping-property-values">CSS Transforms Module Level 2 specification</a>, applying any “grouping property” like&nbsp;<code>overflow</code>&nbsp;(with any value other than&nbsp;<code>visible</code>),&nbsp;<code>opacity</code>&nbsp;less than 1, or&nbsp;<code>filter</code>, forces the element to flatten.</p>



<p class="learn-more"><strong>The sad reality:</strong> A value of <code>preserve-3d</code> for <code>transform-style</code> is ignored if the element has any grouping property values.</p>



<p>In other words: you can have a 3D container, or you can clip its content. <strong>You cannot do both on the same element.</strong></p>



<p>For a long time, this felt like a dead end. How do you keep the 3D depth while keeping the elements contained?!</p>



<h2 class="wp-block-heading" id="faking-it"><strong>Faking It</strong></h2>



<p>If the spec says we can’t have both perspective and clipping, maybe we can cheat. If we can’t use real 3D depth, perhaps we can fake it.</p>



<p>Faking perspective is a time-honored tradition in 2D graphics. You can simulate depth by manipulating the size and position of elements based on their “distance” from the viewer. In CSS terms, this means using <code>scale()</code> to make things smaller as they get “further away” and <code>translate()</code> to move them relative to the card’s angle.</p>


<pre class="wp-block-code"><span><code class="hljs language-css"><span class="hljs-selector-class">.card</span> {
  <span class="hljs-comment">/* --mouse-x and --mouse-y values ranage from -1 to 1 */</span>
  <span class="hljs-attribute">--tilt-x</span>: <span class="hljs-built_in">calc</span>(var(--mouse-y, <span class="hljs-number">0.1</span>) * -<span class="hljs-number">120deg</span>); 
  <span class="hljs-attribute">--tilt-y</span>: <span class="hljs-built_in">calc</span>(var(--mouse-x, <span class="hljs-number">0.1</span>) * <span class="hljs-number">120deg</span>); 
  <span class="hljs-attribute">transform</span>: <span class="hljs-built_in">rotateX</span>(var(--tilt-x)) <span class="hljs-built_in">rotateY</span>(var(--tilt-y));
}

<span class="hljs-selector-class">.card-layer</span> {
  <span class="hljs-comment">/* Fake perspective with scale and translate */</span>
  <span class="hljs-attribute">scale</span>: <span class="hljs-built_in">calc</span>(<span class="hljs-number">1</span> - var(--i) * <span class="hljs-number">0.02</span>);
  <span class="hljs-attribute">translate</span>:
    <span class="hljs-built_in">calc</span>(var(--mouse-x) * (<span class="hljs-built_in">var</span>(--i)) * -<span class="hljs-number">20%</span>)
    <span class="hljs-built_in">calc</span>(var(--mouse-y) * (<span class="hljs-built_in">var</span>(--i)) * -<span class="hljs-number">20%</span>);
}</code></span></pre>


<p>This technique can work wonders. There are some brilliant examples out there, like <a href="https://x.com/jh3yy/status/1987670585133187417">this one by Jhey</a>, that pull off the effect beautifully without using a single line of <code>perspective</code> or <code>preserve-3d</code>.</p>



<p>It’s a solid approach. It’s performant, it works across browsers, and for subtle effects, it’s often indistinguishable from the real thing.</p>



<p><strong>But it has a ceiling.</strong></p>



<p>The illusion holds up well within a limited range of motion. But the moment you push it too far, say, by rotating the card to a sharp angle or trying to flip it 180 degrees, the math starts to show its cracks. The perspective flattens out, and the movement stops feeling natural.</p>



<div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_raeJPLq/35bcca4b4b9aa9f3e8c8a2d52b989181" src="//codepen.io/anon/embed/raeJPLq/35bcca4b4b9aa9f3e8c8a2d52b989181?height=450&amp;theme-id=1&amp;slug-hash=raeJPLq/35bcca4b4b9aa9f3e8c8a2d52b989181&amp;default-tab=result" height="450" scrolling="no" frameborder="0" allowfullscreen="" allowpaymentrequest="" name="CodePen Embed raeJPLq/35bcca4b4b9aa9f3e8c8a2d52b989181" title="CodePen Embed raeJPLq/35bcca4b4b9aa9f3e8c8a2d52b989181" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>



<p>As you can see, when the card turns, the inner elements lose their spatial relationship. The magic evaporates. So while this is a great tool for the toolbox, it wasn’t the complete solution I was looking for. I wanted the real deal. Full 3D, full rotation, full clipping.</p>



<h2 class="wp-block-heading" id="road-to-a-nowhere"><strong>Road to a Nowhere</strong></h2>



<p>I spent years (on and off, I’m not <em>that</em> obsessed) trying to crack this. I was convinced there had to be a way to have my cake and eat it too.</p>



<p>Theoretically, there <em>is</em> a way. If you can’t clip the container, you have to clip the children.</p>



<p>Imagine using <code>clip-path</code> on every single layer inside the card. You would need to calculate, in real-time, exactly where the edges of the card are relative to the viewer, and then apply a dynamic clipping mask to each child element so that it cuts off exactly at those boundaries.</p>



<p>This involves a lot of math, even for me. We’re talking about projecting 3D coordinates onto a 2D plane, calculating intersections, and handling the trigonometry of the user’s perspective.</p>



<figure class="wp-block-image size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="1024" height="512" src="https://i0.wp.com/frontendmasters.com/blog/wp-content/uploads/2025/12/Rw8ilzFF.png?resize=1024%2C512&amp;ssl=1" alt="A textured blackboard covered with various mathematical equations, diagrams, and symbols, creating a complex academic backdrop." class="wp-image-7961" srcset="https://i0.wp.com/frontendmasters.com/blog/wp-content/uploads/2025/12/Rw8ilzFF.png?w=1024&amp;ssl=1 1024w, https://i0.wp.com/frontendmasters.com/blog/wp-content/uploads/2025/12/Rw8ilzFF.png?resize=300%2C150&amp;ssl=1 300w, https://i0.wp.com/frontendmasters.com/blog/wp-content/uploads/2025/12/Rw8ilzFF.png?resize=768%2C384&amp;ssl=1 768w" sizes="auto, (max-width: 1000px) 100vw, 1000px"></figure>



<p>I was almost ready to give up and accept that maybe, just maybe, this was one of those things CSS just wasn’t meant to do. And then, I got a message from <a href="https://x.com/CubiqNation">Cubiq</a>.</p>



<h2 class="wp-block-heading" id="the-breakthrough"><strong>The Breakthrough</strong></h2>



<p>This wasn’t the first time someone had asked me about this topic. As someone who’s known for pushing CSS 3D to its limits, I get this question a lot. People assume I have the answer. But, well… I didn’t.</p>



<p>So when Cubiq messaged me, showing me a GIF of a fully rotating card with deep 3D elements and asking how to achieve it, I went into auto-pilot. I gave him the standard explanation on why the spec forbids it, why <code>overflow</code> flattens the context, and how he could try to fake it with <code>scale</code> and <code>translate</code>.</p>



<p>I thought that was the end of it, but then, he surprised me.</p>



<figure class="wp-block-image size-full is-resized"><img data-recalc-dims="1" loading="lazy" decoding="async" width="602" height="230" src="https://i0.wp.com/frontendmasters.com/blog/wp-content/uploads/2025/12/image.png?resize=602%2C230&amp;ssl=1" alt="A screenshot of a messaging app conversation featuring a user's message expressing excitement about a discovery." class="wp-image-7969" style="width:585px;height:auto" srcset="https://i0.wp.com/frontendmasters.com/blog/wp-content/uploads/2025/12/image.png?w=602&amp;ssl=1 602w, https://i0.wp.com/frontendmasters.com/blog/wp-content/uploads/2025/12/image.png?resize=300%2C115&amp;ssl=1 300w" sizes="auto, (max-width: 602px) 100vw, 602px"></figure>



<h2 class="wp-block-heading" id="my-personal-blind-spot"><strong>My Personal Blind Spot</strong></h2>



<p>I’ve tried many tricks over the years, but one property I religiously avoided was <code>perspective-origin</code>.</p>



<p>If you really dig into how CSS calculates perspective, you realize that <code>perspective-origin</code> doesn’t just shift your point of view. It fundamentally skews the entire viewport. It creates this weird, unnatural distortion that usually looks terrible.</p>



<p class="learn-more">I cover this at length in my talk <a href="https://www.youtube.com/watch?v=LzDf8BizhmQ">3D in CSS, and the True Meaning of Perspective</a>, if you’re into that sort of thing.</p>



<p>Cubiq, however, didn’t have my baggage. He looked at the problem with fresh eyes and realized something brilliant: just as <code>perspective-origin</code> can be used to <em>create</em> distortion, it can also be used to <em>correct</em> it.</p>



<p class="learn-more">Alternate blog post title idea: <strong>Finally, we found one good use for <code>perspective-origin</code>!</strong> 🤣</p>



<h2 class="wp-block-heading" id="the-solution"><strong>The Solution</strong></h2>



<p>Here is the magic formula that Cubiq came up with:</p>


<pre class="wp-block-code"><span><code class="hljs language-css"><span class="hljs-selector-class">.card-container</span> {
  <span class="hljs-attribute">transform</span>: <span class="hljs-built_in">rotateX</span>(var(--tilt-x)) <span class="hljs-built_in">rotateY</span>(var(--tilt-y));
}

<span class="hljs-selector-class">.card</span> {
  <span class="hljs-attribute">perspective</span>: <span class="hljs-built_in">calc</span>(
    cos(var(--tilt-x)) * <span class="hljs-built_in">cos</span>(var(--tilt-y)) * <span class="hljs-built_in">var</span>(--perspective)
  );
  <span class="hljs-attribute">perspective-origin</span>: 
    <span class="hljs-built_in">calc</span>(cos(var(--tilt-x)) * <span class="hljs-built_in">sin</span>(var(--tilt-y)) * <span class="hljs-built_in">var</span>(--perspective) * -<span class="hljs-number">1</span> + <span class="hljs-number">50%</span>)
    <span class="hljs-built_in">calc</span>(sin(var(--tilt-x)) * <span class="hljs-built_in">var</span>(--perspective) + <span class="hljs-number">50%</span>);
  <span class="hljs-attribute">overflow</span>: clip;
}</code></span></pre>


<p>It looks a bit scary at first glance, but the logic is actually quite elegant.</p>



<p>Since we are using <code>overflow: clip</code>, the 3D context is flattened. This means the browser treats the card as a flat surface and renders its children onto that surface. Normally, this flattening would kill the 3D effect of the children. They would look like a flat painting on a rotating wall.</p>



<p>But here is the trick: <strong>We use</strong> <code>perspective</code> <strong>and</strong> <code>perspective-origin</code> <strong>to counter-act the rotation.</strong></p>



<p>By dynamically calculating the <code>perspective-origin</code> based on the card’s tilt, we are essentially telling the browser: “Hey, I know you flattened this element, but I want you to render the perspective of its children <em>as if</em> the viewer is looking at them from this specific angle.”</p>



<p>We are effectively projection-mapping the 3D scene onto the 2D surface of the card. The math ensures that the projection aligns perfectly with the card’s physical rotation, creating the illusion of a deep, 3D space inside a container that the browser considers “flat.”</p>



<div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_JoXWpYB/a3a2a25cca5bad7f5e83d17519abf503" src="//codepen.io/anon/embed/JoXWpYB/a3a2a25cca5bad7f5e83d17519abf503?height=450&amp;theme-id=1&amp;slug-hash=JoXWpYB/a3a2a25cca5bad7f5e83d17519abf503&amp;default-tab=result" height="450" scrolling="no" frameborder="0" allowfullscreen="" allowpaymentrequest="" name="CodePen Embed JoXWpYB/a3a2a25cca5bad7f5e83d17519abf503" title="CodePen Embed JoXWpYB/a3a2a25cca5bad7f5e83d17519abf503" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>



<p>It’s not about moving the world inside of the card, it’s about tricking the flat projection to look 3D by aligning the viewer’s perspective with the card’s rotation.</p>



<h2 class="wp-block-heading" id="the-lesson"><strong>The Lesson</strong></h2>



<p>I love this solution not just because it works (and it works beautifully), but because it taught me a humbling lesson.</p>



<p>I had written off <code>perspective-origin</code> as a “bad” property. I had a mental block against it because I only saw it as a source of distortion. I was so focused on the “right” way to do 3D, that I blinded myself to the tools that could actually solve the problem.</p>



<p>Cubiq didn’t have that bias. He saw a math problem: “I need the projection to look like X when the rotation is Y.” And he found the property that controls projection.</p>



<h2 class="wp-block-heading" id="breaking-it-down">Breaking It Down</h2>



<p>Now that we know it’s possible, let’s break down exactly what’s happening here, step by step, and look at some examples of what you can do with it. Let’s start with the basics.</p>



<h3 class="wp-block-heading">The HTML</h3>



<p>At its core, the structure is simple. We have a <code>.card-container</code> that holds the <code>.card</code>, which in turn contains the <code>.card-content</code>, that is the ‘front’ of the card and where all the inner layers live. and the <code>card-back</code> for the back face.</p>


<pre class="wp-block-code"><span><code class="hljs language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"outer-container"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"card"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"card-content"</span>&gt;</span>
      <span class="hljs-comment">&lt;!-- Inner layers go here --&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"card-back"</span>&gt;</span>
      <span class="hljs-comment">&lt;!-- Back face content --&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></code></span></pre>


<p>Inside the <code>.card-content</code>, we can now add <code>.card-layers</code> with multiple layers in it. Here I’m setting a <code>--i</code> custom property on each layer to later control its depth.</p>


<pre class="wp-block-code"><span><code class="hljs language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"card-layers"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"card-layer"</span> <span class="hljs-attr">style</span>=<span class="hljs-string">"--i: 0"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"card-layer"</span> <span class="hljs-attr">style</span>=<span class="hljs-string">"--i: 1"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"card-layer"</span> <span class="hljs-attr">style</span>=<span class="hljs-string">"--i: 2"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"card-layer"</span> <span class="hljs-attr">style</span>=<span class="hljs-string">"--i: 3"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
  <span class="hljs-comment">&lt;!-- more layers as needed --&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></code></span></pre>


<p>Now we can fill each layer with content, images, text, or whatever we want.</p>



<h3 class="wp-block-heading">The Movement</h3>



<p>To create the rotation effect, we need to track the mouse position and convert it into tilt angles for the card. So the first thing we need to do is to map the mouse position into two CSS variables, <code>--mouse-x</code> and <code>--mouse-y</code>.</p>



<p>This is done with few simple lines of JavaScript:</p>


<pre class="wp-block-code"><span><code class="hljs language-javascript"><span class="hljs-keyword">const</span> cardContainer = <span class="hljs-built_in">document</span>.querySelector(<span class="hljs-string">'.card-container'</span>);

<span class="hljs-built_in">window</span>.addEventListener(<span class="hljs-string">'mousemove'</span>, (e) =&gt; {
  <span class="hljs-keyword">const</span> rect = cardContainer.getBoundingClientRect();
  <span class="hljs-keyword">const</span> x = (e.clientX - rect.left) / rect.width * <span class="hljs-number">2</span> - <span class="hljs-number">1</span>;
  <span class="hljs-keyword">const</span> y = (e.clientY - rect.top) / rect.height * <span class="hljs-number">2</span> - <span class="hljs-number">1</span>;
  cardContainer.style.setProperty(<span class="hljs-string">'--mouse-x'</span>, x);
  cardContainer.style.setProperty(<span class="hljs-string">'--mouse-y'</span>, y);
});</code></span></pre>


<p>This gives us normalized values between -1 and 1 on each axis, so we can use them regardless of the card size or aspect ratio.</p>



<p>We convert these values to <code>--tilt-x</code> and <code>--tilt-y</code> in CSS, by multiplying them by the number of degrees we want the card to rotate:</p>


<pre class="wp-block-code"><span><code class="hljs language-css"><span class="hljs-selector-tag">--tilt-x</span>: <span class="hljs-selector-tag">calc</span>(<span class="hljs-selector-tag">var</span>(<span class="hljs-selector-tag">--mouse-y</span>, 0<span class="hljs-selector-class">.1</span>) * <span class="hljs-selector-tag">-120deg</span>);
<span class="hljs-selector-tag">--tilt-y</span>: <span class="hljs-selector-tag">calc</span>(<span class="hljs-selector-tag">var</span>(<span class="hljs-selector-tag">--mouse-x</span>, 0<span class="hljs-selector-class">.1</span>) * 120<span class="hljs-selector-tag">deg</span>);</code></span></pre>


<p>The higher the degree value, the more dramatic the rotation. 20–30 degrees will give us a subtle effect, while 180 degrees will spin the card all the way around.</p>



<p class="learn-more">Notice that <code>--mouse-x</code> affects <code>--tilt-y</code>, because movement of the mouse along the X axis should actually rotate the card around the Y axis, and vice versa. Also, we multiply <code>--mouse-y</code> by a negative number, because the Y axis on the screen is inverted compared to the mathematical Y axis.</p>



<p>Now that we have <code>--tilt-x</code> and <code>--tilt-y</code>, we can start using them. And first, we apply them to the card container to rotate it in 3D space:</p>


<pre class="wp-block-code"><span><code class="hljs language-css"><span class="hljs-selector-class">.card</span> {
  <span class="hljs-attribute">transform</span>: <span class="hljs-built_in">rotateX</span>(var(--tilt-x)) <span class="hljs-built_in">rotateY</span>(var(--tilt-y));
}</code></span></pre>


<p>This gives us the basic rotation effect. The card will now tilt and spin based on the mouse position.</p>



<h3 class="wp-block-heading">The Perspective</h3>



<p>We need to remember that we need to set <strong>two</strong> different perspectives: one for the card’s container (to create the 3D effect), and one for the card’s content (to maintain the depth of the inner elements).</p>



<p>on the <code>.card-container</code> we set a standard perspective:</p>


<pre class="wp-block-code"><span><code class="hljs language-css"><span class="hljs-selector-class">.card-container</span> {
  <span class="hljs-attribute">perspective</span>: <span class="hljs-built_in">var</span>(--perspective);
}</code></span></pre>


<p class="learn-more">You can set <code>--perspective</code> to any value you like, but a good starting point is around <code>800px</code>. Lower values will create a more dramatic perspective, while higher values will make it more subtle.</p>



<p>To preserve the 3D space and making sure all the inner elements share the same 3D context, we set <code>transform-style: preserve-3d</code>. I’m using the universal selector here to apply it to all children elements:</p>


<pre class="wp-block-code"><span><code class="hljs language-css">* {
  <span class="hljs-attribute">transform-style</span>: preserve-<span class="hljs-number">3</span>d;
}</code></span></pre>


<p>To deal with the inner perspective, we set up the <code>perspective</code> and <code>perspective-origin</code> on the <code>.card-content</code> element, which holds all the inner layers:</p>


<pre class="wp-block-code"><span><code class="hljs language-css"><span class="hljs-selector-class">.card-content</span> {
  <span class="hljs-attribute">perspective</span>: <span class="hljs-built_in">calc</span>(
    cos(var(--tilt-x)) * <span class="hljs-built_in">cos</span>(var(--tilt-y)) * <span class="hljs-built_in">var</span>(--perspective)
  );
  <span class="hljs-attribute">perspective-origin</span>: 
    <span class="hljs-built_in">calc</span>(cos(var(--tilt-x)) * <span class="hljs-built_in">sin</span>(var(--tilt-y)) * <span class="hljs-built_in">var</span>(--perspective) * -<span class="hljs-number">1</span> + <span class="hljs-number">50%</span>)
    <span class="hljs-built_in">calc</span>(sin(var(--tilt-x)) * <span class="hljs-built_in">var</span>(--perspective) + <span class="hljs-number">50%</span>);
  <span class="hljs-attribute">overflow</span>: clip;
}</code></span></pre>


<p>Note that we added <code>overflow: clip</code> to the <code>.card-content</code> to ensure that the inner elements are clipped by the card boundaries. This combination of <code>perspective</code>, <code>perspective-origin</code>, and <code>overflow: clip</code> is what allows us to maintain the 3D depth of the inner elements while keeping them contained within the card.</p>



<h3 class="wp-block-heading">The Depth</h3>



<p>Now that we have the rotation and perspective set up, we can start adding depth to the inner layers. Each layer will be positioned in 3D space using <code>translateZ</code>, based on its <code>--i</code> value.</p>


<pre class="wp-block-code"><span><code class="hljs language-css"><span class="hljs-selector-class">.card-layer</span> {
  <span class="hljs-attribute">position</span>: absolute;
  <span class="hljs-attribute">transform</span>: <span class="hljs-built_in">translateZ</span>(calc(var(--i) * <span class="hljs-number">1rem</span>));
}</code></span></pre>


<p>This will space out the layers along the Z axis, creating the illusion of depth. You can adjust the multiplier (here <code>1rem</code>) to control how far apart the layers are.</p>



<h2 class="wp-block-heading" id="putting-it-all-together">Putting It All Together</h2>



<p>Using the techniques outlined above, we can create a fully functional Deep Card that responds to mouse movement, maintains 3D depth, and clips its content appropriately.</p>



<p>Here is a complete ‘boilerplate’ example:</p>



<div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_QwNmjqo/3209fa5150d0d591176cbffac7129892" src="//codepen.io/anon/embed/QwNmjqo/3209fa5150d0d591176cbffac7129892?height=450&amp;theme-id=1&amp;slug-hash=QwNmjqo/3209fa5150d0d591176cbffac7129892&amp;default-tab=result" height="450" scrolling="no" frameborder="0" allowfullscreen="" allowpaymentrequest="" name="CodePen Embed QwNmjqo/3209fa5150d0d591176cbffac7129892" title="CodePen Embed QwNmjqo/3209fa5150d0d591176cbffac7129892" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>



<p>You can customize it to your needs, set the number of layers, their depth, and add content within each layer to create a wide variety of Deep Card effects.</p>



<h2 class="wp-block-heading" id="getting-deeper"><strong>Getting Deeper</strong></h2>



<p>To improve the Deep Card effect and further enhance the perception of depth, we can add shadows and darkening effects to the layers.</p>



<p>One way to achieve darker colors is just using darker colors. We can calculate the brightness of each layer based on its depth, making deeper layers darker to simulate light falloff.</p>


<pre class="wp-block-code"><span><code class="hljs language-css"><span class="hljs-selector-class">.card-layer</span> {
  <span class="hljs-attribute">color</span>: <span class="hljs-built_in">hsl</span>(<span class="hljs-number">0</span> <span class="hljs-number">0%</span> calc(<span class="hljs-number">100%</span> - var(--i) * <span class="hljs-number">9%</span>));
}</code></span></pre>


<p>Another technique is to add semi-transparent background to each layer. This way each layer is like screen that slightly darkens the layers behind it, enhancing the depth effect.</p>


<pre class="wp-block-code"><span><code class="hljs language-css"><span class="hljs-selector-class">.card-layer</span> {
  <span class="hljs-attribute">background-color</span>: <span class="hljs-number">#2224</span>;
}</code></span></pre>


<p>Here is an example of a two cards with different effects: The first card uses darker colors for deeper layers, while the second card uses semi-transparent overlays to create a more pronounced depth effect.</p>



<div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_bNpMoKq/71f99a6de6c2d62e80d7acfc1157da24" src="//codepen.io/anon/embed/bNpMoKq/71f99a6de6c2d62e80d7acfc1157da24?height=450&amp;theme-id=1&amp;slug-hash=bNpMoKq/71f99a6de6c2d62e80d7acfc1157da24&amp;default-tab=result" height="450" scrolling="no" frameborder="0" allowfullscreen="" allowpaymentrequest="" name="CodePen Embed bNpMoKq/71f99a6de6c2d62e80d7acfc1157da24" title="CodePen Embed bNpMoKq/71f99a6de6c2d62e80d7acfc1157da24" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>



<p>Choose the one that fits your design best, or combine both techniques for an even richer depth experience.</p>



<h2 class="wp-block-heading" id="the-z-index-effect"><strong>The <code>z-index</code> Effect</strong></h2>



<p>You might notice that I’m placing all the layers inside a container (<code>.card-layers</code>) rather than making them direct children of <code>.card-content</code>. The reason is that since we’re moving the layers along the Z axis, we don’t want them to be direct children of an element with <code>overflow: clip;</code> (like <code>.card-content</code>).</p>



<p>As mentioned earlier, once you set <code>overflow: clip;</code> on <code>.card-content</code>, its <code>transform-style</code> becomes <code>flat</code>, which means all of its direct children are rendered on a single plane. Their stacking order is determined by <code>z-index</code>, not by their position along the Z axis. By wrapping the layers in a container, we preserve their 3D positioning and allow the depth effect to work as intended.</p>



<h3 class="wp-block-heading">The Twist</h3>



<p>Now that we understand this limitation, let’s turn it to our advantage and see what kinds of effects we can create with it.</p>



<p>Here are the exact same two cards as in the previous example, but this time without a <code>.card-layers</code> container. The layers are direct children of <code>.card-content</code>:</p>



<div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_VYaxybX/060e427f5276054c778606a9b50be906" src="//codepen.io/anon/embed/VYaxybX/060e427f5276054c778606a9b50be906?height=450&amp;theme-id=1&amp;slug-hash=VYaxybX/060e427f5276054c778606a9b50be906&amp;default-tab=result" height="450" scrolling="no" frameborder="0" allowfullscreen="" allowpaymentrequest="" name="CodePen Embed VYaxybX/060e427f5276054c778606a9b50be906" title="CodePen Embed VYaxybX/060e427f5276054c778606a9b50be906" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>



<h2 class="wp-block-heading" id="adding-interaction"><strong>Adding Interaction</strong></h2>



<p>We often use cards that need to display extra information. One of my favorite things to do in these cases is to rotate the card 180 degrees and reveal the additional content on the back side. Now, we can do exactly that, and build an entire 3D world inside the card.</p>



<div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_pvyLELR/1d666cd030f54aad4d970aaaf9873046" src="//codepen.io/anon/embed/pvyLELR/1d666cd030f54aad4d970aaaf9873046?height=450&amp;theme-id=1&amp;slug-hash=pvyLELR/1d666cd030f54aad4d970aaaf9873046&amp;default-tab=result" height="450" scrolling="no" frameborder="0" allowfullscreen="" allowpaymentrequest="" name="CodePen Embed pvyLELR/1d666cd030f54aad4d970aaaf9873046" title="CodePen Embed pvyLELR/1d666cd030f54aad4d970aaaf9873046" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>



<p>In this example, we have a front face (<code>.card-content</code>) and a back face (<code>.card-back</code>). When the user clicks the card, we toggle a checkbox that rotates the card 180 degrees, revealing the back face.</p>


<pre class="wp-block-code"><span><code class="hljs language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"card-container"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"checkbox"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"card"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"card-content"</span>&gt;</span>
      <span class="hljs-comment">&lt;!-- front face content --&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"card-back"</span>&gt;</span>
      <span class="hljs-comment">&lt;!-- back face content --&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span></code></span></pre>

<pre class="wp-block-code"><span><code class="hljs language-css"><span class="hljs-selector-class">.card-container</span> {
  <span class="hljs-attribute">cursor</span>: pointer;
    
  &amp;:has(<span class="hljs-attribute">input</span>:checked) .card {
    rotate: y <span class="hljs-number">180deg</span>;
  }
  
  <span class="hljs-selector-tag">input</span><span class="hljs-selector-attr">[type=<span class="hljs-string">"checkbox"</span>]</span> {
    <span class="hljs-attribute">display</span>: none;
  }
}</code></span></pre>


<p>You can also use a <code>button</code> or any other interactive element to toggle the rotation, depending on your use case, and use any animation technique you like to make the rotation smooth.</p>



<h2 class="wp-block-heading" id="inner-movement"><strong>Inner Movement</strong></h2>



<p>Of course, we can also use any animation on the inner layers to create dynamic effects. It can be wild and complex, or subtle and elegant. The key is that since the layers are in 3D space, any movement along the Z axis will enhance the depth effect.</p>



<p>Here a simple example with parallax layers. each layer animates it’s background position on the X axis, and to enhance the depth effect, I’m animating the layers at different speeds based on their depth:</p>


<pre class="wp-block-code"><span><code class="hljs language-css"><span class="hljs-selector-class">.card-layer</span> {
  <span class="hljs-attribute">animation</span>: layer <span class="hljs-built_in">calc</span>(var(--i) * <span class="hljs-number">8s</span>) infinite linear;
}</code></span></pre>


<p>And the result:</p>



<div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_bNpMQxj/5bd4b50fedc6080cfa517d9cf625d81e" src="//codepen.io/anon/embed/bNpMQxj/5bd4b50fedc6080cfa517d9cf625d81e?height=450&amp;theme-id=1&amp;slug-hash=bNpMQxj/5bd4b50fedc6080cfa517d9cf625d81e&amp;default-tab=result" height="450" scrolling="no" frameborder="0" allowfullscreen="" allowpaymentrequest="" name="CodePen Embed bNpMQxj/5bd4b50fedc6080cfa517d9cf625d81e" title="CodePen Embed bNpMQxj/5bd4b50fedc6080cfa517d9cf625d81e" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>



<h2 class="wp-block-heading" id="deep-text-animation">Deep Text Animation</h2>



<p>This technique works beautifully with the concept of layered text, opening up a world of creative possibilities. There’s so much you can do with it, from subtle depth effects to wild, animated 3D lettering.</p>



<p>I actually <a href="https://css-tricks.com/3d-layered-text-motion-and-variations/">wrote an entire article about this</a>, featuring 20+ examples, and every single one of them looks fantastic inside a Deep Card. Here’s one of the examples from that article, now living inside a card:</p>



<div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_JoXLJQy/657423f3dbb8bf5781b534b8b79de0c5" src="//codepen.io/anon/embed/JoXLJQy/657423f3dbb8bf5781b534b8b79de0c5?height=450&amp;theme-id=1&amp;slug-hash=JoXLJQy/657423f3dbb8bf5781b534b8b79de0c5&amp;default-tab=result" height="450" scrolling="no" frameborder="0" allowfullscreen="" allowpaymentrequest="" name="CodePen Embed JoXLJQy/657423f3dbb8bf5781b534b8b79de0c5" title="CodePen Embed JoXLJQy/657423f3dbb8bf5781b534b8b79de0c5" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>



<h2 class="wp-block-heading" id="going-full-360">Going Full 360</h2>



<p>up until now, we’ve mostly focused on layering our inner content and using the Z axis to create depth. But we can definitely take it a step further, break out of the layering concept, and build a fully 3D object that you can spin around in all directions.</p>



<div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_QwNrEoP/9438cf97b6330d2e5f3c30726291c472" src="//codepen.io/anon/embed/QwNrEoP/9438cf97b6330d2e5f3c30726291c472?height=450&amp;theme-id=1&amp;slug-hash=QwNrEoP/9438cf97b6330d2e5f3c30726291c472&amp;default-tab=result" height="450" scrolling="no" frameborder="0" allowfullscreen="" allowpaymentrequest="" name="CodePen Embed QwNrEoP/9438cf97b6330d2e5f3c30726291c472" title="CodePen Embed QwNrEoP/9438cf97b6330d2e5f3c30726291c472" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>



<p>From here, the possibilities are truly endless. You can keep experimenting—add more interactions, more layers, or even create effects on both sides of the card to build two complete worlds, one on each face. Or, go all in and design an effect that dives deep into the card itself. The only real limit is your imagination.</p>



<h2 class="wp-block-heading" id="conclusion">Conclusion</h2>



<p>The <strong>Deep Card</strong> is now a solved problem. We can have our cake (3D depth), eat it (clipping), and even spin it around 360 degrees without breaking the illusion.</p>



<p>So, the next time you hit a wall with CSS, and you’re sure you’ve tried everything, maybe take a second look at those properties you swore you’d never use. You might just find your answer hiding in the documentation you skipped.</p>



<p>Now, go build something deep.</p>
]]></description>
      <pubDate>Thu, 04 Dec 2025 19:30:21 +0000</pubDate>
      <link>https://frontendmasters.com/blog/the-deep-card-conundrum/</link>
      <dc:creator>Frontend Masters Boost RSS Feed</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5037721347</guid>
    </item>
    <item>
      <title><![CDATA[Prefiguration]]></title>
      <description><![CDATA[ 

<p>A few weeks ago, I learned a fancy new word from <a href="https://www.andrewsage.org">Andrew Sage</a>: prefiguration.</p>

<p>A few months ago, when SNAP benefits abruptly disappeared, I immediately started working on bringing a <a href="https://en.wikipedia.org/wiki/Community_fridge">Community Fridge</a> to my town. It’s something I’d wanted to do for a while, and this was the spark that lit the fire in my belly.</p>

<p>If you’re unfamiliar with the concept, a Community Fridge is literally an open access fridge (and usually attached pantry) where people can share food they have and take food they need. It’s not a haves/have-nots thing. It’s a community resource for everyone.</p>

<h2 id="i-didn-t-think-it-would-be-easy">I didn’t think it would be easy.</h2>

<p>But I didn’t expect the level of push-back I got from people, either.</p>

<blockquote>
<ul>
<li>Why not just donate to the official Food Pantry in town?</li>
<li>What if someone poisons all the food?</li>
<li>What about food-born illness?</li>
<li>Who’s going to pay for electricity?</li>
<li>What if someone takes more than they deserve?</li>
<li>What if it’s empty?</li>
</ul>
</blockquote>

<p>Some of these are good questions about legitimate logistics concerns that need to be worked out. Some are obnoxious, parental, charity-model thinking. They’re all attempts to throw up road blocks and prevent the project from happening.</p>

<p><strong>None of them pause for a second and say, “How can we make this work?”</strong></p>

<p>I have answers to all of these questions. I’ve given them over-and-over again. Most of the people who ask just… disappear afterwards, which tells me the questions weren’t earnest or in good-faith.</p>

<h2 id="a-community-fridge-is-different-from-a-food-pantry-in-a-few-really-important-ways">A Community Fridge is different from a Food Pantry in a few really important ways.</h2>

<p>The most important is that it’s <em>not</em> charity. It’s not the rich helping the poor.</p>

<p>It’s an opportunity for a community to <em>help itself</em>: to share resources, divert food waste, and work together for the common good.</p>

<p>There is no means testing. No restrictions on who can use it or how much they can take. No one deciding who <em>deserves</em> it and who doesn’t.</p>

<p>Take what you need. Give what you can.</p>

<h2 id="but-that-s-really-hard-for-a-lot-of-people-to-conceptualize">But that’s <em>really</em> hard for a lot of people to conceptualize.</h2>

<p>Under capitalism in general, and in America (where I live) in particular, you’re taught to look out for yourself, be ruggedly individual, work hard, and accumulate as much wealth as you can.</p>

<p>A resource rooted in <em>sharing</em> rather than <em>throwing crumbs to the poor</em> is completely foreign to how so much of modern culture operates.</p>

<p>So you get questions like, “what if someone takes more than they deserve?”</p>

<h2 id="and-that-brings-us-to-prefiguration">And that brings us to prefiguration.</h2>

<p>Prefiguration is about building the future you want within the shell of existing structures.</p>

<p>It’s generally not going to replace an existing system—at least, not in any sort of immediate or rapid time scale.</p>

<p>But it <em>does</em> provide models people can look at to imagine something different.</p>

<p>Prefiguration serves as a lighthouse for a possible future. A direction you can head in. It provides little experiments that you can try and learn from.</p>

<h2 id="you-don-t-need-permission-to-build-the-future-you-want">You don’t need permission to build the future you want.</h2>

<p>Finding a host for a Community Fridge has been filled with many closed doors and false starts. I still don’t have one, though I have met some wonderful new friends in town.</p>

<p>My house is not an ideal spot for one. I’m nowhere near our small downtown, near farms, with little foot or car traffic.</p>

<p>But I hit a point the other day where I’m sick of waiting, sick of asking permission, and sick of letting perfect be the enemy of “a start.”</p>

<p>So yesterday, I sketched out some rough plans, bought a bunch of lumber, and will be building a little free farmstand on my property.</p>

<p>Not quite a fridge, yet. But still, a place for people to share food and excess fruits and vegetables (I live near farms), and clothes and toys and home goods.</p>

<p>Then, when people ask, “what about…?” I can point them to something tangible, and say, “kind of like this.”</p>
<p><em><strong>Like this?</strong> A <a href="https://members.gomakethings.com">Lean Web Club</a> membership is the best way to support my work and help me create more free content.</em></p>]]></description>
      <pubDate>Mon, 08 Dec 2025 14:30:00 +0000</pubDate>
      <link>https://gomakethings.com/prefiguration/</link>
      <dc:creator>Go Make Things</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5041624095</guid>
    </item>
    <item>
      <title><![CDATA[How to Fix Any Bug]]></title>
      <description><![CDATA[
<p>Dan Abramov has an interesting article <a href="https://overreacted.io/how-to-fix-any-bug/">How to Fix Any Bug</a> where he’s having Claude write the code, but a bug shows up he needs to fix. Claude just isn’t getting it and it keeps saying it’s fixed when it isn’t (classic).</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>Claude was repeatedly wrong because&nbsp;<strong>it didn’t have a repro</strong>.</p>
</blockquote>



<p>Meaning Claude couldn’t see the bug for itself. My first thought was: <em>Dan! You gotta get <a href="https://github.com/ChromeDevTools/chrome-devtools-mcp">Chrome DevTools MCP</a>!</em> This essentially makes the browser context for Claude, which is in my experience pretty damn helpful. But Dan actually had the <a href="https://github.com/microsoft/playwright-mcp">Playwright MCP</a> going, so it was a more complicated issue than just that. The path to a solution:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>This exact workflow—removing things one by one while ensuring the bug is still present—saved my ass many times.</p>
</blockquote>



<p>Reminds me of the lesser-known git feature <a href="https://git-scm.com/docs/git-bisect">git-bisect</a>.</p>
]]></description>
      <pubDate>Tue, 25 Nov 2025 21:48:06 +0000</pubDate>
      <link>https://frontendmasters.com/blog/how-to-fix-any-bug/</link>
      <dc:creator>Frontend Masters Boost RSS Feed</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5028091807</guid>
    </item>
    <item>
      <title><![CDATA[Self-hosting my photos with Immich]]></title>
      <description><![CDATA[<div class="Artikel" id="content"> <details class="ms_toc_details"> <summary>Table of contents</summary> <nav class="TableOfContents"> </nav> </details> <p>For every cloud service I use, I want to have a local copy of my data for backup
purposes and independence. Unfortunately, the <code>gphotos-sync</code> tool <a href="https://github.com/gilesknap/gphotos-sync-discussion/discussions/1">stopped
working in March
2025</a> when
Google restricted the OAuth scopes, so I needed an alternative for my existing
Google Photos setup. In this post, I describe how I have set up
<a href="https://immich.app/">Immich</a>, a self-hostable photo manager.</p>
<p>Here is the end result: a few (live) photos from <a href="https://michael.stapelberg.ch/posts/2025-09-21-nixcon-2025-trip-report/">NixCon
2025</a>:</p> <a href="https://michael.stapelberg.ch/posts/2025-11-29-self-hosting-photos-with-immich/2025-11-19-immich-screenshot-featured.jpg"><img srcset="https://michael.stapelberg.ch/posts/2025-11-29-self-hosting-photos-with-immich/2025-11-19-immich-screenshot-featured_hu_6928fc2a893484f1.jpg 2x, https://michael.stapelberg.ch/posts/2025-11-29-self-hosting-photos-with-immich/2025-11-19-immich-screenshot-featured_hu_7c70567581178dd5.jpg 3x" src="https://michael.stapelberg.ch/posts/2025-11-29-self-hosting-photos-with-immich/2025-11-19-immich-screenshot-featured_hu_744c6f048556917c.jpg" alt="screenshot of Immich in a web browser" width="600"></a> <h2 id="step-1-hardware">Step 1. Hardware</h2>
<p>I am running Immich on my <a href="https://michael.stapelberg.ch/posts/2024-07-02-ryzen-7-mini-pc-low-power-proxmox-hypervisor/">Ryzen 7 Mini PC (ASRock DeskMini
X600)</a>, which
consumes less than 10 W of power in idle and has plenty of resources for VMs (64
GB RAM, 1 TB disk). You can read more about it in my blog post from July 2024:</p> <p>I installed <a href="https://proxmox.com/en/">Proxmox</a>, an Open Source virtualization
platform, to divide this mini server into VMs, but you could of course also
install Immich directly on any server.</p>
<h2 id="step-2-install-immich">Step 2. Install Immich</h2>
<p>I created a VM (named “photos”) with 500 GB of disk space, 4 CPU cores and 4 GB of RAM.</p>
<p>For the initial import, you could assign more CPU and RAM, but for normal usage, that’s enough.</p>
<p>I <a href="https://michael.stapelberg.ch/posts/2025-06-01-nixos-installation-declarative/">(declaratively) installed
NixOS</a> on that VM as described in this blog post:</p> <p>Afterwards, I enabled Immich, with this exact configuration:</p>
<div class="highlight"><pre><code class="language-nix"><span><span>services<span>.</span>immich <span>=</span> {
</span></span><span><span> enable <span>=</span> <span>true</span>;
</span></span><span><span>};
</span></span></code></pre></div><p>At this point, Immich is available on <code>localhost</code>, but not over the network,
because NixOS enables a firewall by default. I could enable the
<code>services.immich.openFirewall</code> option, but I actually want Immich to only be
available via my Tailscale VPN, for which I don’t need to open firewall access —
instead, I use <code>tailscale serve</code> to forward traffic to <code>localhost:2283</code>:</p>
<pre><code>photos# tailscale serve --bg http://localhost:2283
</code></pre><p>Because I have <a href="https://tailscale.com/kb/1081/magicdns">Tailscale’s MagicDNS</a>
and <a href="https://tailscale.com/kb/1153/enabling-https">TLS certificate provisioning</a>
enabled, that means I can now open <a href="https://photos.example.ts.net">https://photos.example.ts.net</a> in my browser
on my PC, laptop or phone.</p>
<h2 id="step-2-initial-photos-import">Step 2. Initial photos import</h2>
<p>At first, I tried importing my photos using the official Immich CLI:</p>
<pre><code>% nix run nixpkgs#immich-cli -- login https://photos.example.ts.net secret
% nix run nixpkgs#immich-cli -- upload --recursive /home/michael/lib/photo/gphotos-takeout
</code></pre><p>Unfortunately, the upload was not running reliably and had to be restarted
manually a few times after running into a timeout. Later I realized that this
was because the Immich server runs background jobs like thumbnail creation,
metadata extraction or face detection, and these background jobs slow down the
upload to the extent that the upload can fail with a timeout.</p>
<p>The other issue was that even after the upload was done, I realized that Google
Takeout archives for Google Photos contain metadata in separate JSON files next
to the original image files:</p> <a href="https://michael.stapelberg.ch/posts/2025-11-29-self-hosting-photos-with-immich/2025-11-19-google-photos-takeout.jpg"><img srcset="https://michael.stapelberg.ch/posts/2025-11-29-self-hosting-photos-with-immich/2025-11-19-google-photos-takeout_hu_c2ca8fd1cf90122a.jpg 2x, https://michael.stapelberg.ch/posts/2025-11-29-self-hosting-photos-with-immich/2025-11-19-google-photos-takeout_hu_7169d5fe6b33d18d.jpg 3x" src="https://michael.stapelberg.ch/posts/2025-11-29-self-hosting-photos-with-immich/2025-11-19-google-photos-takeout_hu_8ac7b224e95d405d.jpg" alt="Takeout: Google Photos formats" width="600"></a> <p>Unfortunately, these files are not considered by <code>immich-cli</code>.</p>
<p>Luckily, there is a great third-party tool called
<a href="https://github.com/simulot/immich-go">immich-go</a>, which solves both of these
issues! It pauses background tasks before uploading and restarts them
afterwards, which works much better, and it does its best to understand Google
Takeout archives.</p>
<p>I ran <code>immich-go</code> as follows and it worked beautifully:</p>
<pre><code>% immich-go \
  upload \
  from-google-photos \
  --server=https://photos.example.ts.net \
  --api-key=secret \
  ~/Downloads/takeout-*.zip
</code></pre><h2 id="step-3-install-the-immich-iphone-app">Step 3. Install the Immich iPhone App</h2>
<p>My main source of new photos is my phone, so I installed the Immich app on my
iPhone, logged into my Immich server via its Tailscale URL and enabled automatic
backup of new photos via the icon at the top right.</p>
<p>I am not 100% sure whether these settings are correct, but it seems like camera
photos generally go into Live Photos, and Recent should cover other files…?!</p>
<p>If anyone knows, please send an explanation (or a link!) and I will update the article.</p> <a href="https://michael.stapelberg.ch/posts/2025-11-29-self-hosting-photos-with-immich/IMG_5893.PNG"><img srcset="https://michael.stapelberg.ch/posts/2025-11-29-self-hosting-photos-with-immich/IMG_5893_hu_a062fb5b5187dc76.PNG 2x, https://michael.stapelberg.ch/posts/2025-11-29-self-hosting-photos-with-immich/IMG_5893_hu_9575452ad065a609.PNG 3x" src="https://michael.stapelberg.ch/posts/2025-11-29-self-hosting-photos-with-immich/IMG_5893_hu_8ba00d1bf6088e0a.PNG" width="600"></a> <p>I also strongly recommend to disable notifications for Immich, because otherwise
you get notifications whenever it uploads images in the background. These
notifications are not required for background upload to work, as <a href="https://www.reddit.com/r/immich/comments/1nnk8i9/comment/nfoffbb/">an Immich
developer confirmed on
Reddit</a>. Open
<em>Settings</em> → <em>Apps</em> → <em>Immich</em> → <em>Notifications</em> and un-tick the permission checkbox:</p> <a href="https://michael.stapelberg.ch/posts/2025-11-29-self-hosting-photos-with-immich/IMG_5894.PNG"><img srcset="https://michael.stapelberg.ch/posts/2025-11-29-self-hosting-photos-with-immich/IMG_5894_hu_eae3457253ed621d.PNG 2x, https://michael.stapelberg.ch/posts/2025-11-29-self-hosting-photos-with-immich/IMG_5894_hu_b720b6d63d234471.PNG 3x" src="https://michael.stapelberg.ch/posts/2025-11-29-self-hosting-photos-with-immich/IMG_5894_hu_3ee31e31d3c5d235.PNG" width="600"></a> <h2 id="step-4-backup">Step 4. Backup</h2>
<p><a href="https://docs.immich.app/administration/backup-and-restore">Immich’s documentation on
backups</a> contains
some good recommendations. The Immich developers recommend backing up the entire
contents of <code>UPLOAD_LOCATION</code>, which is <code>/var/lib/immich</code> on NixOS. The
<code>backups</code> subdirectory contains SQL dumps, whereas the 3 directories <code>upload</code>,
<code>library</code> and <code>profile</code> contain all user-uploaded data.</p>
<p>Hence, I have set up a systemd timer that runs <code>rsync</code> to copy <code>/var/lib/immich</code>
onto my PC, which is enrolled in a <a href="https://www.backblaze.com/blog/the-3-2-1-backup-strategy/">3-2-1 backup
scheme</a>.</p>
<h2 id="whats-missing">What’s missing?</h2>
<p>Immich (currently?) does not contain photo editing features, so to rotate or
crop an image, I download the image and use <a href="https://www.gimp.org/">GIMP</a>.</p>
<p>To share images, I still upload them to Google Photos (depending on who I share
them with).</p>
<h2 id="why-immich-instead-of">Why Immich instead of…?</h2>
<p>The two most promising options in the space of self-hosted image management
tools seem to be <a href="https://immich.app/">Immich</a> and <a href="http://ente.io/">Ente</a>.</p>
<p>I got the impression that Immich is more popular in my bubble, and Ente made the
impression on me that its scope is far larger than what I am looking for:</p>
<blockquote>
<p>Ente is a service that provides a fully open source, end-to-end encrypted
platform for you to store your data in the cloud without needing to trust the
service provider. On top of this platform, we have built two apps so far: Ente
Photos (an alternative to Apple and Google Photos) and Ente Auth (a 2FA
alternative to the deprecated Authy).</p>
</blockquote>
<p>I don’t need an end-to-end encrypted platform. I already have encryption on the
transit layer (Tailscale) and disk layer (LUKS), no need for more complexity.</p>
<h2 id="conclusion">Conclusion</h2>
<p>Immich is a delightful app! It’s very fast and generally seems to work well.</p>
<p>The initial import is smooth, but only if you use the right tool. Ideally, the
official <code>immich-cli</code> could be improved. Or maybe <code>immich-go</code> could be made the
official one.</p>
<p>I think the auto backup is too hard to configure on an iPhone, so that could
also be improved.</p>
<p>But aside from these initial stumbling blocks, I have no complaints.</p>
<div id="bmc"> <p> Did you like this post? <a href="https://michael.stapelberg.ch/feed.xml">Subscribe to this blog’s RSS feed</a> to not miss any new posts! </p> <p> I run a blog since 2005, spreading knowledge and experience for over 20 years! :) </p> <p> If you want to support my work, you can <a href="https://www.buymeacoffee.com/stapelberg">buy me a coffee</a>. </p> <p> Thank you for your support! ❤️ </p>
</div> </div>]]></description>
      <pubDate>Sat, 29 Nov 2025 00:00:00 +0000</pubDate>
      <link>https://michael.stapelberg.ch/posts/2025-11-29-self-hosting-photos-with-immich/</link>
      <dc:creator>Pages</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5039545141</guid>
    </item>
    <item>
      <title><![CDATA[Turning my reading list into podcasts]]></title>
      <description><![CDATA[
                    <p><a href="https://linkding.link">linkding</a> is one of my favorite applications that I self-host and the place where I save everything I want to read later. The catch being that what little time I can dedicate to <em>actually</em> reading is spent on books. What I <em>do</em> have is time where I can listen to things while doing chores around the house, out on walks or otherwise engaged in an activity that doesn't demand my full, undivided attention.</p>
<p>I typically consume short form writing by listening to it. In the past, I've used <a href="https://en.wikipedia.org/wiki/Pocket_(service)">Pocket</a><sup id="fnref:1"><span>1</span></sup>, <a href="https://www.instapaper.com">Instapaper</a>, <a href="https://readwise.io/read">Readwise's Reader</a> and Safari's listen to page feature for this. Safari had become something of a default choice for this as I didn't have a compelling, self-hosted option available. So I built one.</p>
<p>I use <a href="https://www.audiobookshelf.org">Audiobookshelf</a> to listen to audiobooks and podcasts, making it a natural fit to listen to the articles I save. Neither Safari nor <a href="https://readwise.io/read">Readwise's Reader</a> support queueing articles to listen to<sup id="fnref:2"><span>2</span></sup>, which meant starting playback manually for each article listened to. <a href="https://www.audiobookshelf.org">Audiobookshelf</a> and the clients I've used all support queueing, which solves that particular usability pain point.</p>
<p>I have text, I have a solution for playing audio which left me needing to coerce the former into the latter. Rather than dump my entire bespoke project repo into a blog post, the high level flow looks like this:</p>
<ol>
<li>Deploy the project to my <a href="https://www.audiobookshelf.org">Audiobookshelf</a> server using <a href="https://coolify.io">Coolify</a>.
<ul>
<li>The deploy clones the project, installs dependencies, downloads a <a href="https://github.com/OHF-Voice/piper1-gpl">Piper</a> voice model from <a href="https://huggingface.co">Hugging Face</a><sup id="fnref:3"><span>3</span></sup>, and configures a cron job to run every 5 minutes.</li>
</ul>
</li>
<li>Every 5 minutes the deployed application will check <a href="https://linkding.link">linkding</a> for new items.</li>
<li>If an item has not been converted to audio, is unread and is not tagged with <code>video</code> or <code>podcast</code>, the article text is fetched and parsed using Mozilla's <a href="https://github.com/mozilla/readability">Readability.js</a>.<sup id="fnref:4"><span>4</span></sup></li>
<li>Once parsed, the article is processed further into a string that will be converted to speech. This string is simply the following concatenated together: article title, author and content. Pauses are added after periods that don't otherwise have one, URLs are cleaned up and email addresses are removed.</li>
<li>The processed text is written to a temporary text file and passed to <a href="https://github.com/OHF-Voice/piper1-gpl">Piper</a> to generate a WAV file using the voice model downloaded when the application is deployed. <a href="https://www.ffmpeg.org">ffmpeg</a> is used to convert the WAV file to an mp3 and <a href="https://www.npmjs.com/package/node-id3">node-id3</a> is used to tag the resulting file.
<ul>
<li>The tag data is fairly simple — the article title is the episode title, a consistent, static podcast title (<code>Linkding Articles</code>) and artist (<code>Linkding TTS Bot</code>) and the article author and description are saved to the episode description. Year and date correspond to when the item was saved, the show art is the <a href="https://linkding.link">linkding</a> logo/icon and the rest is generic podcast metadata expected by <a href="https://www.audiobookshelf.org">Audiobookshelf</a>.</li>
</ul>
</li>
<li>Once the audio is generated and tagged, the app triggers a scan of my podcast library using the <a href="https://www.audiobookshelf.org">Audiobookshelf</a> API.</li>
</ol>
<p>With all of this in place, I can save things to <a href="https://linkding.link">linkding</a> and there will be an automatically generated queue waiting when I open my preferred <a href="https://www.audiobookshelf.org">Audiobookshelf</a> client.<sup id="fnref:5"><span>5</span></sup><sup id="fnref:6"><span>6</span></sup></p>
<div class="footnotes" role="doc-endnotes"><hr><ol><li class="footnote" id="fn:1" role="doc-endnote"><p>RIP.&nbsp;<span>↩</span></p></li>
<li class="footnote" id="fn:2" role="doc-endnote"><p>Or didn't when I last used them.&nbsp;<span>↩</span></p></li>
<li class="footnote" id="fn:3" role="doc-endnote"><p>Gross! I know.&nbsp;<span>↩</span></p></li>
<li class="footnote" id="fn:4" role="doc-endnote"><p>To avoid generating duplicate audio files, the ID for each processed article is stored in an array in a JSON file available to the application. The current episode number is also stored in this file.&nbsp;<span>↩</span></p></li>
<li class="footnote" id="fn:5" role="doc-endnote"><p>At the moment, this is <a href="https://github.com/rasmuslos/ShelfPlayer">ShelfPlayer</a>.&nbsp;<span>↩</span></p></li>
<li class="footnote" id="fn:6" role="doc-endnote"><p>If a site happens to disallow access when I try and fetch the article text, I'll stop trying to fetch it. If I encounter an error (e.g. a <code>5XX</code> response), I'll retry periodically using an exponential backoff mechanism.&nbsp;<span>↩</span></p></li></ol></div>

                ]]></description>
      <pubDate>Fri, 05 Dec 2025 00:23:00 +0000</pubDate>
      <link>https://www.coryd.dev/posts/2025/turning-my-reading-list-into-podcasts</link>
      <dc:creator>Posts feed • Cory Dransfeldt</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5037973146</guid>
    </item>
    <item>
      <title><![CDATA[Stacked Diffs with git rebase —onto | Hacker News]]></title>
      <description><![CDATA[<div class="commtext c00">Wish i could remember my issues with jj. I tried it, i wanted to stick with it because i loved the fact that i could reorder commits while deferring the actual conflicts.. but something eventually prevented me from switching. Searching my slack history where i talked about this with a coworker who actually used jj:<p>1. I had quite a bit of trouble figuring out a workflow for branches. Since my companies unit of work is the branch, with specifically named branches, my `jj ls` was confusing as hell.</p><p>`jj st` might have helped a bit, but there were scenarios where creating an commit would abandon the branch... if i'm reading my post history correctly. My coworker who was more familiar explained my jj problems away with "definitely pre-release software", so at the time neither of us were aware of a workflow which considered branches more core.</p><p>Fwiw, I don't even remember when the jj workflow had branches come into play.. but i was not happy with the UX around branches.</p><p>2. iirc i didn't like how it auto stashed/committed things. I found random `dbg!` statements could slip in more easily and i had to be on guard about what is committed, since everything just auto pushed. My normal workflow has me purposefully stashing chunks when i'm satisfied with them, and i use that as the visual metric. That felt less solid with jj.</p><p>Please take this with a huge grain of salt, this is 10 month old memory i scavenged from slack history. Plus as my coworker was saying, jj was changing a lot.. so maybe my issues are less relevant now? Or just flat out wrong, but nonetheless i bounced off of jj despite wanting to stick with it.</p></div>]]></description>
      <pubDate>Fri, 05 Dec 2025 21:45:33 +0000</pubDate>
      <link>https://news.ycombinator.com/item?id=46103571</link>
      <dc:creator>Pages</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5039042418</guid>
    </item>
    <item>
      <title><![CDATA['Tis the season]]></title>
      <description><![CDATA[ <p>Nearly 15 years ago, <a href="https://gomakethings.com/the-advent-conspiracy/">I shared a video</a> produced by a group called <a href="https://adventconspiracy.org">the Advent Conspiracy</a> about how the holiday season has lost all of its original meaning, and is now just a celebration at the alter of capitalism.</p>

<p>They encouraged people to spend less money and give more time, attention, and love.</p>

<p>They’re a religious organization, and I’m very much an atheist. But they didn’t lean very heavy into Jesus at the time.</p>

<p>They’ve released <a href="https://youtu.be/2yfnlhvmr-k">an updated video</a> with new numbers (we now spend over $1 TRILLION globally at Christmas!). They’ve dialed up the religious aspects a lot, but I think the core message is still a really important one.</p>

<iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/2yfnlhvmr-k" 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>In particular, <a href="https://adventconspiracy.org/give-more/">their blog</a> has a ton of great ideas on how to make the holidays <em><strong>more</strong> about building community</em> and <em><strong>less</strong> about buying shit</em>.</p>

<blockquote>
<ul>
<li>Invest in experiences you can share. Sign up for dance lessons, go camping, plan a vacation, take cooking classes.</li>
<li>Pass down memories. Make a recipe book or photo album to give to your family.</li>
<li>Get a blank journal and write notes and prayers to your kids.</li>
<li>Give a copy of your favorite book to a friend and then meet up for coffee and discuss it. Then switch and read their favorite book.</li>
<li>Buy someone a gift that relieves a burden: babysitting money, help out with yard work, make a meal.</li>
<li>Give the gift of hospitality. Invite a new family over for dinner and games.</li>
<li>Do an activity with your kids – puzzles, jewelry making, baking, hiking, fishing, sports. Be present.</li>
</ul>
</blockquote>

<p>This is the kind of energy I’m trying to channel this year!</p>
<p><em><strong>Like this?</strong> A <a href="https://members.gomakethings.com">Lean Web Club</a> membership is the best way to support my work and help me create more free content.</em></p>]]></description>
      <pubDate>Thu, 04 Dec 2025 14:30:00 +0000</pubDate>
      <link>https://gomakethings.com/tis-the-season/</link>
      <dc:creator>Go Make Things</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5037487911</guid>
    </item>
    <item>
      <title><![CDATA[Grow slowly, stay small]]></title>
      <description><![CDATA[<p><em>Quick announcement: I'll be visiting Japan in April, 2026 for about a month and will be on Honshu for most of the trip. Please email me recommendations. If you live nearby, let's have coffee?</em></p>
<hr>
<p>I've always been fascinated by old, multi-generational Japanese businesses. My leisure-watching on YouTube is usually a long video of a Japanese craftsman—sometimes a 10th or 11th generation—making iron tea kettles, or soy sauce, or pottery, or furniture.</p>
<p>Their dedication to craft—and acknowledgment that perfection is unattainable—resonates with me deeply. Improving in their craft is an almost spiritual endeavour, and it inspires me to engage in my crafts with a similar passion and focus.</p>
<p>Slow, consistent investment over many years is how beautiful things are made, learnt, or grown. As a society we forget this truth—especially with the rise of social media and the proliferation of instant gratification. Good things take time.</p>
<p>Dedication to craft in this manner comes with incredible longevity (survivorship bias plays a role, but the density of long-lived businesses in Japan is an outlier). So many of these small businesses have been around for hundreds, and sometimes over a thousand years, passed from generation to generation. Modern companies have a hard time retaining employees for 2 years, let alone a lifetime.</p>
<p>This longevity stems from a counter-intuitive idea of growing slowly (or not at all) and choosing to stay small. In most modern economies if you were to start a bakery, the goal would be to set it up, hire and train a bunch of staff, and expand operations to a second location. Potentially, if you play your cards right, you could create a national (or international) chain or franchise. Corporatise the shit out of it, go public or sell, make bank.</p>
<p>While this is a potential path to becoming filthy rich, the odds of achieving this become vanishingly small. The organisation becomes brittle due to thinly-spread resources and care, hiring becomes risky, and leverage, whether in the form of loans or investors, imposes unwanted directionality.</p>
<p>There's a well known parable of the fisherman and the businessman that goes something like this:</p>
<p>A businessman meets a fisherman who is selling fish at his stall one morning. The businessman enquires of the fisherman what he does after he finishes selling his fish for the day. The fisherman responds that he spends time with his friends and family, cooks good food, and watches the sunset with his wife. Then in the morning he wakes up early, takes his boat out on the ocean, and catches some fish.</p>
<p>The businessman, shocked that the fisherman was wasting so much time encourages him fish for longer in the morning, increasing his yield and maximising the utility of his boat. Then he should sell those extra fish in the afternoon and save up until he has enough money to buy a second fishing boat and potentially employ some other fishermen. Focus on the selling side of the business, set up a permanent store, and possibly, if he does everything correctly, get a loan to expand the operation even further.</p>
<p>In 10 to 20 years he could own an entire fishing fleet, make a lot of money, and finally retire. The fisherman then asks the businessman what he would do with his days once retired, to which the businessman responds: "Well, you could spend more time with your friends and family, cook good food, watch the sunset with your wife, and wake up early in the morning and go fishing, if you want."</p>
<p>I love this parable, even if it is a bit of an oversimplification. There is something to be said about affording comforts and financial stability that a fisherman may not have access to. But I think it illustrates the point that when it comes to running a business, bigger is not always better. This is especially true for consultancies or agencies which suffer from bad horizontal scaling economics.</p>
<p>The trick is figuring out what is "enough". At what point are we chasing status instead of contentment?</p>
<p>A smaller, slower growing company is less risky, less fragile, less stressful, and still a rewarding endeavour.</p>
<p>This is how I run Bear. The project covers its own expenses and compensates me enough to have a decent quality of life. It grows slowly and sustainably. It isn't leveraged and I control its direction and fate. The most important factor, however, is that I don't need it to be something grander. It affords me a life that I love, and provides me with a craft to practise.</p>
]]></description>
      <pubDate>Wed, 03 Dec 2025 10:14:00 +0000</pubDate>
      <link>https://herman.bearblog.dev/grow-slowly-stay-small/</link>
      <dc:creator>Herman&#39;s blog</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5035935371</guid>
    </item>
    <item>
      <title><![CDATA[Using CSS to fix the irradiation illusion]]></title>
      <description><![CDATA[
          <img style="display: none" src="https://nerdy.dev/media/typography-illusion.jpg" alt="The letter A is shown on white and on dark, as an exaggerated example of the irradiation illusion" height="1072" width="1920">
        <p>Ever noticed how white text on a black background <strong>looks thicker</strong> than black text on a white background, even though the weights are the same? </p>
<p>This post teaches you how to account for this and adjust perceived font weight for dark mode <strong>without layout shift</strong>, with variable fonts and the <a href="https://fonts.google.com/knowledge/glossary/grade_axis"><code>GRAD</code></a> axis.</p>
<p><q>g…RAD! 👈😎👈</q></p>

        <h2>
          Feel out the problem space
          <a name="feel-out-the-problem-space" href="https://nerdy.dev/adjust-perceived-typepace-weight-for-dark-mode-without-layout-shift?utm_source=rss#feel-out-the-problem-space">#</a>
        </h2>
       <p>Test your eyes and feel the problem space with the following demo <br>(def <a href="https://codepen.io/argyleink/full/ZYWavgP">open it up larger</a>):</p>
<p>
          <iframe class="codepen-embed" scrolling="no" title="null" src="https://codepen.io/argyleink/embed/preview/ZYWavgP?default-tab=result&amp;editable=true&amp;theme-id=43079" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen="true">
            See the Pen <a href="https://codepen.io/argyleink/embed/preview/ZYWavgP"> by Adam Argyle (<a href="https://codepen.io/argyleink">@argyleink</a>)
            on <a href="https://codepen.io">CodePen</a>.
          </iframe>
        </p>
<p><strong>To test:</strong> <br>Flip between light and dark. Look closely at font weights in the "un-adjusted" and "adjusted" example content. </p>
<p><strong>The goal:</strong> <br>The visual weight to not perceptively change when switching between light and dark. This means, when in dark mode, using a negative value for <code>GRAD</code> to reduce the perceived thickness.</p>
<p><strong>Some play:</strong> <br>In dark mode, use the slider to adjust the <code>GRAD</code> axis to see how it changes the perceived weight of the font in the adjusted example. Feel free to fine tune the slider to find the perfect balance for your eyes, flipping back and forth, adjust the slider until you see no weight change when flipping themes.</p>
<p>I think it's easier to notice the difference in the paragraph text than the heading text, but you find which is easier to notice for yourself. </p>
<p><strong>It's subtle:</strong> <br>Don't expect this to jump out at you, but <strong>you'll struggle to unsee once you see</strong>.</p>

        <h2>
          Irradiation illusion
          <a name="irradiation-illusion" href="https://nerdy.dev/adjust-perceived-typepace-weight-for-dark-mode-without-layout-shift?utm_source=rss#irradiation-illusion">#</a>
        </h2>
       <p><a href="https://pmc.ncbi.nlm.nih.gov/articles/PMC2041963/">Humans perceive white on black vs black on white differently.</a> We learned it from the world, the way shadows and light have given us biases and expectations. <a href="https://evidentscientific.com/en/microscope-resource/knowledge-hub/lightandcolor/humanvisionintro">Our squishy human eye</a> has been through some things you know?</p>
<p><img loading="lazy" src="https://upload.wikimedia.org/wikipedia/commons/4/4d/Black_and_white_squares.jpg" alt="Irradiation illusion" title="null" decoding="async" undefined=""></p>
<p>Read more about <a href="https://en.wikipedia.org/wiki/Irradiation_illusion">irradiation illusion</a>.</p>
<p>This impacts typography in dark mode, as the perceived weight of a font changes in dark mode, without intention from the CSS authors. The light text appears thicker than the dark text counterpart, even though the font weight is the same.</p>
<p><strong>CSS can account for this illusion</strong> 🤓</p>

        <h2>
          GRAD in variable fonts to the rescue
          <a name="grad-in-variable-fonts-to-the-rescue" href="https://nerdy.dev/adjust-perceived-typepace-weight-for-dark-mode-without-layout-shift?utm_source=rss#grad-in-variable-fonts-to-the-rescue">#</a>
        </h2>
       <p>Before variable fonts, font weight was the only way to adjust the perceived weight of a font, but this changes the glyph size. This can cause accidental layout shift, aka a janky experience. </p>
<p><a href="https://fonts.google.com/knowledge/glossary/grade_axis"><code>GRAD</code></a> stands for "grade", and it's a variable font axis that allows us to adjust the perceived weight of a font without changing the glyph size.</p>

        <h3>
          Fixing bold link hovers
          <a name="fixing-bold-link-hovers" href="https://nerdy.dev/adjust-perceived-typepace-weight-for-dark-mode-without-layout-shift?utm_source=rss#fixing-bold-link-hovers">#</a>
        </h3>
       <p>Here, you'll recognize this classic issue. Hover the links in the below demo to see a small preview of the type of layout shift that can occur. </p>
<p>
          <iframe class="codepen-embed" scrolling="no" title="null" src="https://codepen.io/argyleink/embed/preview/qEZdOPE?default-tab=result&amp;editable=true&amp;theme-id=43079" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen="true">
            See the Pen <a href="https://codepen.io/argyleink/embed/preview/qEZdOPE"> by Adam Argyle (<a href="https://codepen.io/argyleink">@argyleink</a>)
            on <a href="https://codepen.io">CodePen</a>.
          </iframe>
        </p>
<p><strong>With variable fonts, we can use the <code>GRAD</code> axis to adjust the perceived weight of a font without changing the glyph size.</strong></p>
<p>This allows us to adjust the perceived weight of a font without causing layout shift. That's what the "fixed" example is using.</p>

        <h2>
          One step further
          <a name="one-step-further" href="https://nerdy.dev/adjust-perceived-typepace-weight-for-dark-mode-without-layout-shift?utm_source=rss#one-step-further">#</a>
        </h2>
       <p>So far we've only adjusted the perceived weight of a font in dark mode with a negative value for <code>GRAD</code>, and for links being hovered that use a positive value for <code>GRAD</code>, but we can go even further:</p>
<p>
          <iframe class="codepen-embed" scrolling="no" title="null" src="https://codepen.io/argyleink/embed/preview/mdQrqvj?default-tab=result&amp;editable=true&amp;theme-id=43079" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen="true">
            See the Pen <a href="https://codepen.io/argyleink/embed/preview/mdQrqvj"> by Adam Argyle (<a href="https://codepen.io/argyleink">@argyleink</a>)
            on <a href="https://codepen.io">CodePen</a>.
          </iframe>
        </p>
<p>This allows us to adjust the perceived weight of a font based on the <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@media/prefers-contrast">users contrast preferences</a>. </p>
<p>If the user prefers more contrast, without layout shift, we can use a positive value for <code>GRAD</code> to increase the perceived thickness. In dark mode, we can use a negative value for <code>GRAD</code> to reduce the perceived thickness while still maintaining their preference.</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-string-expression)">body</span><span style="color:var(--shiki-foreground)"> {</span></span>
<span class="line"><span style="color:var(--shiki-foreground)">  --GRAD</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> 0</span><span style="color:var(--shiki-foreground)">;</span></span>
<span class="line"><span style="color:var(--shiki-token-constant)">  font-variation-settings</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-string-expression)"> "GRAD"</span><span style="color:var(--shiki-token-function)"> var</span><span style="color:var(--shiki-token-constant)">(--GRAD)</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-keyword)">@media</span><span style="color:var(--shiki-foreground)"> (prefers-contrast</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-foreground)"> more) { </span><span style="color:var(--shiki-token-string-expression)">body</span><span style="color:var(--shiki-foreground)"> { --GRAD</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> 700</span><span style="color:var(--shiki-foreground)"> } }</span></span>
<span class="line"><span style="color:var(--shiki-token-keyword)">@media</span><span style="color:var(--shiki-foreground)"> (prefers-contrast</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-foreground)"> less) { </span><span style="color:var(--shiki-token-string-expression)">body</span><span style="color:var(--shiki-foreground)"> { --GRAD</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> -50</span><span style="color:var(--shiki-foreground)"> } }</span></span>
<span class="line"></span>
<span class="line"><span style="color:var(--shiki-token-keyword)">@media</span><span style="color:var(--shiki-foreground)"> (prefers-color-scheme</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-foreground)"> dark)                              { </span><span style="color:var(--shiki-token-string-expression)">body</span><span style="color:var(--shiki-foreground)"> { --GRAD</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> -50</span><span style="color:var(--shiki-foreground)"> } }</span></span>
<span class="line"><span style="color:var(--shiki-token-keyword)">@media</span><span style="color:var(--shiki-foreground)"> (prefers-color-scheme</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-foreground)"> dark) </span><span style="color:var(--shiki-token-keyword)">and</span><span style="color:var(--shiki-foreground)"> (prefers-contrast</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-foreground)"> more) { </span><span style="color:var(--shiki-token-string-expression)">body</span><span style="color:var(--shiki-foreground)"> { --GRAD</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> 150</span><span style="color:var(--shiki-foreground)"> } }</span></span>
<span class="line"><span style="color:var(--shiki-token-keyword)">@media</span><span style="color:var(--shiki-foreground)"> (prefers-color-scheme</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-foreground)"> dark) </span><span style="color:var(--shiki-token-keyword)">and</span><span style="color:var(--shiki-foreground)"> (prefers-contrast</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-foreground)"> less) { </span><span style="color:var(--shiki-token-string-expression)">body</span><span style="color:var(--shiki-foreground)"> { --GRAD</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> -150</span><span style="color:var(--shiki-foreground)"> } }</span></span></code></pre>
</code></pre>
<p>Users will never ask for something like this. They'll just be able to easily read the content in your design in a way that feels natural.</p>
<p>Time to check if your favorite font has a <code>GRAD</code> axis.</p>
<p>Also, this is just the beginning, a foot in the door. It’s worth learning more about the relationship between grade and weight, and fine tune instances to produce the results your users and you will want.</p>
]]></description>
      <pubDate>Sat, 29 Nov 2025 21:47:37 +0000</pubDate>
      <link>https://nerdy.dev/adjust-perceived-typepace-weight-for-dark-mode-without-layout-shift?utm_source=rss</link>
      <dc:creator>Adam Argyle</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5032320642</guid>
    </item>
    <item>
      <title><![CDATA[Boing]]></title>
      <description><![CDATA[ ]]></description>
      <pubDate>Sun, 30 Nov 2025 07:05:43 +0000</pubDate>
      <link>https://boing.greg.technology/</link>
      <dc:creator>Pages</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5032557600</guid>
    </item>
    <item>
      <title><![CDATA[Deploy on push with Forgejo and Coolify]]></title>
      <description><![CDATA[
                    <p>All of my projects are now stored on my <a href="https://forgejo.org">Forgejo</a> instance rather than GitHub as the latter continues to speed run the enshittification curve. I've implemented a manual deploy button in my site's admin but for other, lighter-weight projects, I prefer to deploy changes whenever I push them up.</p>
<p><a href="https://coolify.io">Coolify</a> has a native integration with GitHub, but not with other git hosts. For deploys, I've worked around this by cloning my projects using an API key within the Dockerfile for the project as a sort of lightweight continuous integration.</p>
<p>Within each <a href="https://coolify.io">Coolify</a> resource you'll find a webhooks section. There's a unique deploy webhook for each resource that accepts <code>POST</code> requests to trigger a new deployment. Rather than use a <a href="https://forgejo.org">Forgejo</a> action, I'm having the repositories that I want to deploy on push send a webhook event to the appropriate <a href="https://coolify.io">Coolify</a> deploy URL. You can do this in the <a href="https://forgejo.org">Forgejo</a> repository's webhooks settings by adding a webhook, selecting a target (I simply selected <a href="https://forgejo.org">Forgejo</a>, the first option in the list), adding the <a href="https://coolify.io">Coolify</a> URL, changing the <code>force</code> parameter value at the end of the URL from <code>false</code> to <code>true</code> to ensure the build is performed without using the cache and add a <code>Bearer &lt;COOLIFY-DEPLOY-TOKEN&gt;</code>. The Coolify API token only needs <code>deploy</code> permissions.</p>
<p>Now, when I push to Forgejo, it sends a <code>POST</code> request to the Coolify webhook and deploys a fresh build of the project.</p>

                ]]></description>
      <pubDate>Fri, 28 Nov 2025 19:48:00 +0000</pubDate>
      <link>https://www.coryd.dev/posts/2025/deploy-on-push-with-forgejo-and-coolify</link>
      <dc:creator>Posts feed • Cory Dransfeldt</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5031359607</guid>
    </item>
    <item>
      <title><![CDATA[]]></title>
      <description><![CDATA[With the other personalities, you’re choosing between flavors of bullshit. With Efficient, you’re choosing no bullshit.]]></description>
      <pubDate>Wed, 12 Nov 2025 23:09:10 +0000</pubDate>
      <link>https://daringfireball.net/2025/11/chatgpt_5-1_with_renamed_and_new_personalities</link>
      <dc:creator>Daring Fireball</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/5013746136</guid>
    </item>
  </channel>
</rss>
