{"type":"feed","url":"/jf2","name":"https://grant.codes JF2 Feed","author":{"type":"card","name":"Grant Richmond","photo":"https://grant.codes/img/me.jpg","url":"https://grant.codes"},"children":[{"type":"entry","uid":"https://grant.codes/2026/02/06/10-41-19","like-of":"https://sweetfont.com","published":"2026-02-06T10:41:19.489Z","url":"https://grant.codes/2026/02/06/10-41-19","references":{"https://sweetfont.com":{"type":"entry","uid":"https://sweetfont.com","url":"https://sweetfont.com","name":"Sweetfont \u0014 The sweetest way to find Google Fonts","summary":"Explore Google Fonts by personality, vibe, and style using interactive controls. Find your perfect typeface.","featured":"https://sweetfont.com/og-image.png"}}},{"type":"entry","uid":"https://grant.codes/2026/01/23/03-05-42","like-of":"https://youtu.be/s8UXyOL7-N4","published":"2026-01-23T15:05:42.746Z","url":"https://grant.codes/2026/01/23/03-05-42","references":{"https://youtu.be/s8UXyOL7-N4":{"type":"entry","uid":"https://youtu.be/s8UXyOL7-N4","url":"https://youtu.be/s8UXyOL7-N4","name":"- YouTube"}}},{"type":"entry","uid":"https://grant.codes/2025/10/09/06-15-47","like-of":"https://mastodon.social/@kethinov/115340787570739774","published":"2025-10-09T06:15:47.170Z","url":"https://grant.codes/2025/10/09/06-15-47","references":{"https://mastodon.social/@kethinov/115340787570739774":{"type":"entry","uid":"https://mastodon.social/@kethinov/115340787570739774","url":"https://mastodon.social/@kethinov/115340787570739774","name":"Eric Newport (@kethinov@mastodon.social)","summary":"Attached: 1 image @slightlyoff@toot.cafe","featured":"https://files.mastodon.social/media_attachments/files/115/340/787/223/203/040/original/34c87d3457c06721.png"}}},{"type":"entry","uid":"https://grant.codes/2025/10/02/12-58-27","like-of":"https://www.yankodesign.com/2025/10/01/the-tiny-workshop-that-conquered-britain-how-an-engineers-space-saving-genius-won-shed-of-the-year/?utm_source=rss&utm_medium=rss&utm_campaign=the-tiny-workshop-that-conquered-britain-how-an-engineers-space-saving-genius-won-shed-of-the-year","published":"2025-10-02T12:58:27.372Z","url":"https://grant.codes/2025/10/02/12-58-27","references":{"https://www.yankodesign.com/2025/10/01/the-tiny-workshop-that-conquered-britain-how-an-engineers-space-saving-genius-won-shed-of-the-year/?utm_source=rss&utm_medium=rss&utm_campaign=the-tiny-workshop-that-conquered-britain-how-an-engineers-space-saving-genius-won-shed-of-the-year":{"type":"entry","uid":"https://www.yankodesign.com/2025/10/01/the-tiny-workshop-that-conquered-britain-how-an-engineers-space-saving-genius-won-shed-of-the-year/?utm_source=rss&utm_medium=rss&utm_campaign=the-tiny-workshop-that-conquered-britain-how-an-engineers-space-saving-genius-won-shed-of-the-year","url":"https://www.yankodesign.com/2025/10/01/the-tiny-workshop-that-conquered-britain-how-an-engineers-space-saving-genius-won-shed-of-the-year/?utm_source=rss&utm_medium=rss&utm_campaign=the-tiny-workshop-that-conquered-britain-how-an-engineers-space-saving-genius-won-shed-of-the-year","name":"The Tiny Workshop That Conquered Britain: How An Engineer\u0019s Space-Saving Genius Won Shed Of The Year","summary":"In a world where garden space comes at a premium, Mike Robinson proved that size doesn't matter when innovation takes center stage. The Plumstead engineer's creation, aptly named \"The Tiny Workshop,\"&hellip;","featured":"https://www.yankodesign.com/images/design_news/2025/09/tiny-workshop/tiny_workshop_yanko_design_01.jpg","author":{"type":"card","uid":"h"},"published":"2025-10-01T19:15:12.000Z"}}},{"type":"entry","uid":"https://grant.codes/2025/08/04/03-15-48","like-of":"https://sugardave.cloud/posts/astro/using-live-collection-for-webmentions","published":"2025-08-04T15:15:48.177Z","url":"https://grant.codes/2025/08/04/03-15-48","references":{"https://sugardave.cloud/posts/astro/using-live-collection-for-webmentions":{"type":"entry","uid":"https://sugardave.cloud/posts/astro/using-live-collection-for-webmentions","name":"Astro's Live Content Collection - Display Webmentions 🚀","url":"https://sugardave.cloud/posts/astro/using-live-collection-for-webmentions","author":"sugardave","published":"2025-07-23T00:00:00.000Z","syndication":"https://news.indieweb.org/en","summary":"Using Astro's live content collection to display webmentions for blog posts","content":{"html":"  <p>Today I would like to speak about a new experimental feature available in Astro 5.10 and beyond: <a href=\"https://docs.astro.build/en/reference/experimental-flags/live-content-collections/\">live content collections</a>.  A live content collection allows you to fetch its data at runtime rather than build time, which is how a regular content collection would normally be used.</p>\n<h2 id=\"content-collections\">Content Collections</h2>\n<p>A well-known use case for a content collection is retrieving all the blog posts you have written and then rendering the matching post based on path processing in a “slug page”.  Anyone who has gone through the complete <a href=\"https://docs.astro.build/en/tutorial/0-introduction/\">“Build your first Astro Blog” tutorial</a> should be familiar with how that works.</p>\n<p>When running your Astro site in static mode, this requires all potential post paths to be either manually specified in the slug page or dynamically built using the <code>getStaticPaths</code> method.  In either case, these paths are created at build time and cannot be changed afterwards.  This is also true when you use server-side rendering (SSR), but the method for matching a path to a post is slightly different and not part of the goal of this post.</p>\n<p>The important thing to realize is that <strong>all</strong> “regular” content collections need to have their matching paths resolved at build time for static sites and all the content must be present at build time when using either static builds or SSR.</p>\n<h2 id=\"live-content-collections\">Live Content Collections</h2>\n<p>With live content collections, developers can take more control over how they obtain content for rendering.  One thing you cannot do with regular content collections is have them fetch their data on-demand when a page is requested.  Live content collections enable this.</p>\n<p>For this site, I have been slowly integrating bits and pieces of interesting concepts from <a href=\"https://indieweb.org/IndieWeb\">the IndieWeb</a>.  One of these is <a href=\"https://indieweb.org/Webmention\">“webmentions”</a>.  Without going into too much detail, webmentions are based on <a href=\"https://indieweb.org/microformats\">microformats</a> and enable disparate, unconnected sites and services to “mention” your content and for your site to be able to discover those mentions and process them how you see fit.</p>\n<h3 id=\"implementation\">Implementation</h3>\n<p>I am using a custom content loader to fetch webmentions from <a href=\"https://webmention.io/\">Webmention.io</a>.  I call it <code>webmentionLoader</code> because that seems appropriate.  I have defined a <code>Webmention</code> interface based on suggestions from Claude Sonnet 4.  This is a naive first pass, so I’m only going to show the parts that I actually needed to get this all to work:</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"typescript\"><code><span class=\"line\"><span style=\"color:#F97583\">export</span><span style=\"color:#F97583\"> interface</span><span style=\"color:#B392F0\"> Webmention</span><span style=\"color:#E1E4E8\"> {</span></span>\n<span class=\"line\"><span style=\"color:#6A737D\">  // Core webmention properties</span></span>\n<span class=\"line\"><span style=\"color:#FFAB70\">  id</span><span style=\"color:#F97583\">:</span><span style=\"color:#79B8FF\"> string</span><span style=\"color:#E1E4E8\">;</span></span>\n<span class=\"line\"><span style=\"color:#FFAB70\">  source</span><span style=\"color:#F97583\">:</span><span style=\"color:#79B8FF\"> string</span><span style=\"color:#E1E4E8\">; </span><span style=\"color:#6A737D\">// URL of the page that mentions your content</span></span>\n<span class=\"line\"><span style=\"color:#FFAB70\">  target</span><span style=\"color:#F97583\">:</span><span style=\"color:#79B8FF\"> string</span><span style=\"color:#E1E4E8\">; </span><span style=\"color:#6A737D\">// URL of your content being mentioned</span></span>\n<span class=\"line\"><span style=\"color:#FFAB70\">  targetPath</span><span style=\"color:#F97583\">:</span><span style=\"color:#79B8FF\"> string</span><span style=\"color:#E1E4E8\">; </span><span style=\"color:#6A737D\">// Path of the target URL (for easier filtering)</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">  ...</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">}</span></span></code></pre>\n<p>I also have a helper function to retrieve the webmentions for my domain.  An API key is generated for each site you add to Webmention.io.</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"typescript\"><code><span class=\"line\"><span style=\"color:#F97583\">const</span><span style=\"color:#B392F0\"> getMentionsHTML</span><span style=\"color:#F97583\"> =</span><span style=\"color:#F97583\"> async</span><span style=\"color:#E1E4E8\"> (</span><span style=\"color:#FFAB70\">apiKey</span><span style=\"color:#F97583\">:</span><span style=\"color:#79B8FF\"> string</span><span style=\"color:#E1E4E8\">) </span><span style=\"color:#F97583\">=&gt;</span><span style=\"color:#E1E4E8\"> {</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">  const</span><span style=\"color:#79B8FF\"> response</span><span style=\"color:#F97583\"> =</span><span style=\"color:#F97583\"> await</span><span style=\"color:#B392F0\"> fetch</span><span style=\"color:#E1E4E8\">(</span></span>\n<span class=\"line\"><span style=\"color:#9ECBFF\">    `https://webmention.io/api/mentions.html?token=${</span><span style=\"color:#E1E4E8\">apiKey</span><span style=\"color:#9ECBFF\">}`</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">  );</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">  if</span><span style=\"color:#E1E4E8\"> (</span><span style=\"color:#F97583\">!</span><span style=\"color:#E1E4E8\">response.ok) {</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">    throw</span><span style=\"color:#F97583\"> new</span><span style=\"color:#B392F0\"> Error</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#9ECBFF\">`Failed to fetch webmentions: ${</span><span style=\"color:#E1E4E8\">response</span><span style=\"color:#9ECBFF\">.</span><span style=\"color:#E1E4E8\">statusText</span><span style=\"color:#9ECBFF\">}`</span><span style=\"color:#E1E4E8\">);</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">  }</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">  return</span><span style=\"color:#E1E4E8\"> response.</span><span style=\"color:#B392F0\">text</span><span style=\"color:#E1E4E8\">();</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">};</span></span></code></pre>\n<p>The actual live content loader function needs to return an object with three required properties:</p>\n<ul>\n<li><code>name</code>: a string to represent the name of the loader</li>\n<li><code>loadCollection</code>: an async function that will return the entire collection of data or a filtered set</li>\n<li><code>loadEntry</code>: an async function that will return a single item in the collection based on a filter</li>\n</ul>\n<p>Since I want to customize the display of webmention data, I am using <code>JSDOM</code> to parse the HTML returned from the fetch in <code>loadCollection</code>.  Another thing to note is that since webmentions are a many-to-one relation with posts, <code>loadEntry</code> will never be used, so I am only returning an error indicating it is not supported.</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"typescript\"><code><span class=\"line\"><span style=\"color:#F97583\">export</span><span style=\"color:#F97583\"> const</span><span style=\"color:#B392F0\"> webmentionLoader</span><span style=\"color:#F97583\"> =</span><span style=\"color:#E1E4E8\"> ({</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">  apiKey</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">}</span><span style=\"color:#F97583\">:</span><span style=\"color:#E1E4E8\"> {</span></span>\n<span class=\"line\"><span style=\"color:#FFAB70\">  apiKey</span><span style=\"color:#F97583\">:</span><span style=\"color:#79B8FF\"> string</span><span style=\"color:#E1E4E8\">;</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">})</span><span style=\"color:#F97583\">:</span><span style=\"color:#B392F0\"> LiveLoader</span><span style=\"color:#E1E4E8\">&lt;</span><span style=\"color:#B392F0\">Webmention</span><span style=\"color:#E1E4E8\">&gt; </span><span style=\"color:#F97583\">=&gt;</span><span style=\"color:#E1E4E8\"> {</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">  return</span><span style=\"color:#E1E4E8\"> {</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">    name: </span><span style=\"color:#9ECBFF\">&#39;webmention-loader&#39;</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#B392F0\">    loadCollection</span><span style=\"color:#E1E4E8\">: </span><span style=\"color:#F97583\">async</span><span style=\"color:#E1E4E8\"> ({</span><span style=\"color:#FFAB70\">filter</span><span style=\"color:#E1E4E8\">}) </span><span style=\"color:#F97583\">=&gt;</span><span style=\"color:#E1E4E8\"> {</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">      const</span><span style=\"color:#E1E4E8\"> {</span><span style=\"color:#79B8FF\">targetPath</span><span style=\"color:#E1E4E8\">} </span><span style=\"color:#F97583\">=</span><span style=\"color:#E1E4E8\"> filter </span><span style=\"color:#F97583\">||</span><span style=\"color:#E1E4E8\"> {targetPath: </span><span style=\"color:#9ECBFF\">&#39;&#39;</span><span style=\"color:#E1E4E8\">};</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">      try</span><span style=\"color:#E1E4E8\"> {</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">        const</span><span style=\"color:#79B8FF\"> webmentions</span><span style=\"color:#F97583\">:</span><span style=\"color:#B392F0\"> Webmention</span><span style=\"color:#E1E4E8\">[] </span><span style=\"color:#F97583\">=</span><span style=\"color:#F97583\"> await</span><span style=\"color:#B392F0\"> getMentionsHTML</span><span style=\"color:#E1E4E8\">(apiKey).</span><span style=\"color:#B392F0\">then</span><span style=\"color:#E1E4E8\">(</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">          (</span><span style=\"color:#FFAB70\">html</span><span style=\"color:#E1E4E8\">) </span><span style=\"color:#F97583\">=&gt;</span><span style=\"color:#E1E4E8\"> {</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">            const</span><span style=\"color:#79B8FF\"> dom</span><span style=\"color:#F97583\"> =</span><span style=\"color:#F97583\"> new</span><span style=\"color:#B392F0\"> JSDOM</span><span style=\"color:#E1E4E8\">(html);</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">            const</span><span style=\"color:#79B8FF\"> doc</span><span style=\"color:#F97583\"> =</span><span style=\"color:#E1E4E8\"> dom.window.document;</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">            const</span><span style=\"color:#79B8FF\"> mentions</span><span style=\"color:#F97583\"> =</span><span style=\"color:#E1E4E8\"> Array.</span><span style=\"color:#B392F0\">from</span><span style=\"color:#E1E4E8\">(</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">              doc.</span><span style=\"color:#B392F0\">querySelectorAll</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#9ECBFF\">&#39;.h-entry.mention&#39;</span><span style=\"color:#E1E4E8\">)</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">            );</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">            const</span><span style=\"color:#79B8FF\"> items</span><span style=\"color:#F97583\"> =</span><span style=\"color:#E1E4E8\"> mentions.</span><span style=\"color:#B392F0\">map</span><span style=\"color:#E1E4E8\">((</span><span style=\"color:#FFAB70\">element</span><span style=\"color:#E1E4E8\">, </span><span style=\"color:#FFAB70\">index</span><span style=\"color:#E1E4E8\">) </span><span style=\"color:#F97583\">=&gt;</span><span style=\"color:#E1E4E8\"> {</span></span>\n<span class=\"line\"><span style=\"color:#6A737D\">              // construct a target path from the target URL</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">              const</span><span style=\"color:#79B8FF\"> target</span><span style=\"color:#F97583\"> =</span><span style=\"color:#E1E4E8\"> element</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">                .</span><span style=\"color:#B392F0\">querySelector</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#9ECBFF\">&#39;.u-mention-of&#39;</span><span style=\"color:#E1E4E8\">)</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">                ?.</span><span style=\"color:#B392F0\">getAttribute</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#9ECBFF\">&#39;href&#39;</span><span style=\"color:#E1E4E8\">) </span><span style=\"color:#F97583\">as</span><span style=\"color:#79B8FF\"> string</span><span style=\"color:#E1E4E8\">;</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">              const</span><span style=\"color:#79B8FF\"> postPath</span><span style=\"color:#F97583\"> =</span><span style=\"color:#E1E4E8\"> target </span><span style=\"color:#F97583\">?</span><span style=\"color:#F97583\"> new</span><span style=\"color:#B392F0\"> URL</span><span style=\"color:#E1E4E8\">(target).pathname </span><span style=\"color:#F97583\">:</span><span style=\"color:#9ECBFF\"> &#39;&#39;</span><span style=\"color:#E1E4E8\">;</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">              return</span><span style=\"color:#E1E4E8\"> {</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">                id: </span><span style=\"color:#9ECBFF\">`${</span><span style=\"color:#E1E4E8\">postPath</span><span style=\"color:#9ECBFF\">}:${</span><span style=\"color:#E1E4E8\">index</span><span style=\"color:#9ECBFF\">}`</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">                data: {</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">                  author: {</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">                    name: element.</span><span style=\"color:#B392F0\">querySelector</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#9ECBFF\">&#39;.p-author&#39;</span><span style=\"color:#E1E4E8\">)</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">                      ?.textContent </span><span style=\"color:#F97583\">as</span><span style=\"color:#79B8FF\"> string</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">                  }</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">                },</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">                source: element</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">                  .</span><span style=\"color:#B392F0\">querySelector</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#9ECBFF\">&#39;.u-url&#39;</span><span style=\"color:#E1E4E8\">)</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">                  ?.</span><span style=\"color:#B392F0\">getAttribute</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#9ECBFF\">&#39;href&#39;</span><span style=\"color:#E1E4E8\">) </span><span style=\"color:#F97583\">as</span><span style=\"color:#79B8FF\"> string</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">                target,</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">                targetPath: postPath</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">              };</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">            });</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">            if</span><span style=\"color:#E1E4E8\"> (targetPath) {</span></span>\n<span class=\"line\"><span style=\"color:#6A737D\">              // Filter by targetPath if provided</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">              return</span><span style=\"color:#E1E4E8\"> items.</span><span style=\"color:#B392F0\">filter</span><span style=\"color:#E1E4E8\">((</span><span style=\"color:#FFAB70\">item</span><span style=\"color:#E1E4E8\">) </span><span style=\"color:#F97583\">=&gt;</span><span style=\"color:#E1E4E8\"> item.targetPath </span><span style=\"color:#F97583\">===</span><span style=\"color:#E1E4E8\"> targetPath);</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">            }</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">            return</span><span style=\"color:#E1E4E8\"> items;</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">          }</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">        );</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#F97583\">        return</span><span style=\"color:#E1E4E8\"> {</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">          entries: webmentions.</span><span style=\"color:#B392F0\">map</span><span style=\"color:#E1E4E8\">((</span><span style=\"color:#FFAB70\">mention</span><span style=\"color:#E1E4E8\">) </span><span style=\"color:#F97583\">=&gt;</span><span style=\"color:#E1E4E8\"> ({</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">            id: mention.id,</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">            data: mention</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">          }))</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">        };</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">      } </span><span style=\"color:#F97583\">catch</span><span style=\"color:#E1E4E8\"> (error) {</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">        return</span><span style=\"color:#E1E4E8\"> {</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">          error: </span><span style=\"color:#F97583\">new</span><span style=\"color:#B392F0\"> Error</span><span style=\"color:#E1E4E8\">(</span></span>\n<span class=\"line\"><span style=\"color:#9ECBFF\">            `Failed to retrieve webmentions: ${</span><span style=\"color:#E1E4E8\">error</span><span style=\"color:#F97583\"> instanceof</span><span style=\"color:#B392F0\"> Error</span><span style=\"color:#F97583\"> ?</span><span style=\"color:#E1E4E8\"> error</span><span style=\"color:#9ECBFF\">.</span><span style=\"color:#E1E4E8\">message</span><span style=\"color:#F97583\"> :</span><span style=\"color:#9ECBFF\"> &#39;Unknown error&#39;}`</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">          )</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">        };</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">      }</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">    },</span></span>\n<span class=\"line\"><span style=\"color:#6A737D\">    // there can be multiple webmentions for the same target, so we will never use loadEntry</span></span>\n<span class=\"line\"><span style=\"color:#B392F0\">    loadEntry</span><span style=\"color:#E1E4E8\">: </span><span style=\"color:#F97583\">async</span><span style=\"color:#E1E4E8\"> () </span><span style=\"color:#F97583\">=&gt;</span><span style=\"color:#E1E4E8\"> {</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">      return</span><span style=\"color:#E1E4E8\"> {</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">        error: </span><span style=\"color:#F97583\">new</span><span style=\"color:#B392F0\"> Error</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#9ECBFF\">`webmentionLoader does not support loadEntry`</span><span style=\"color:#E1E4E8\">)</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">      };</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">    }</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">  };</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">};</span></span></code></pre>\n<p>Now, in my slug page I can handle blog posts as a regular content collection just like I’ve been doing and also grab any webmentions that match them.</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8;overflow-x:auto\" tabindex=\"0\" data-language=\"typescript\"><code><span class=\"line\"><span style=\"color:#F97583\">---</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">import</span><span style=\"color:#F97583\"> type</span><span style=\"color:#E1E4E8\"> {CollectionEntry} </span><span style=\"color:#F97583\">from</span><span style=\"color:#9ECBFF\"> &#39;astro:content&#39;</span><span style=\"color:#E1E4E8\">;</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">import</span><span style=\"color:#F97583\"> type</span><span style=\"color:#E1E4E8\"> {LiveDataEntry} </span><span style=\"color:#F97583\">from</span><span style=\"color:#9ECBFF\"> &#39;astro&#39;</span><span style=\"color:#E1E4E8\">;</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">import</span><span style=\"color:#F97583\"> type</span><span style=\"color:#E1E4E8\"> {Webmention} </span><span style=\"color:#F97583\">from</span><span style=\"color:#9ECBFF\"> &#39;@lib/webmentionLoader&#39;</span><span style=\"color:#E1E4E8\">;</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">import</span><span style=\"color:#E1E4E8\"> {getCollection, getLiveCollection, render} </span><span style=\"color:#F97583\">from</span><span style=\"color:#9ECBFF\"> &#39;astro:content&#39;</span><span style=\"color:#E1E4E8\">;</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">import</span><span style=\"color:#E1E4E8\"> MarkdownPostLayout </span><span style=\"color:#F97583\">from</span><span style=\"color:#9ECBFF\"> &#39;@layouts/MarkdownPostLayout.astro&#39;</span><span style=\"color:#E1E4E8\">;</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#F97583\">interface</span><span style=\"color:#B392F0\"> Props</span><span style=\"color:#E1E4E8\"> {</span></span>\n<span class=\"line\"><span style=\"color:#FFAB70\">  post</span><span style=\"color:#F97583\">:</span><span style=\"color:#B392F0\"> CollectionEntry</span><span style=\"color:#E1E4E8\">&lt;</span><span style=\"color:#9ECBFF\">&#39;blog&#39;</span><span style=\"color:#E1E4E8\">&gt;;</span></span>\n<span class=\"line\"><span style=\"color:#FFAB70\">  postMentions</span><span style=\"color:#F97583\">:</span><span style=\"color:#B392F0\"> LiveDataEntry</span><span style=\"color:#E1E4E8\">&lt;</span><span style=\"color:#B392F0\">Webmention</span><span style=\"color:#E1E4E8\">&gt;[];</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#F97583\">const</span><span style=\"color:#E1E4E8\"> {</span></span>\n<span class=\"line\"><span style=\"color:#FFAB70\">  params</span><span style=\"color:#E1E4E8\">: {</span><span style=\"color:#79B8FF\">slug</span><span style=\"color:#E1E4E8\">},</span></span>\n<span class=\"line\"><span style=\"color:#79B8FF\">  url</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">} </span><span style=\"color:#F97583\">=</span><span style=\"color:#E1E4E8\"> Astro;</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">const</span><span style=\"color:#79B8FF\"> currentPath</span><span style=\"color:#F97583\"> =</span><span style=\"color:#E1E4E8\"> url.pathname.</span><span style=\"color:#B392F0\">endsWith</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#9ECBFF\">&#39;/&#39;</span><span style=\"color:#E1E4E8\">)</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">  ?</span><span style=\"color:#E1E4E8\"> url.pathname.</span><span style=\"color:#B392F0\">slice</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#79B8FF\">0</span><span style=\"color:#E1E4E8\">, </span><span style=\"color:#F97583\">-</span><span style=\"color:#79B8FF\">1</span><span style=\"color:#E1E4E8\">)</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">  :</span><span style=\"color:#E1E4E8\"> url.pathname;</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">const</span><span style=\"color:#E1E4E8\"> [</span><span style=\"color:#79B8FF\">post</span><span style=\"color:#E1E4E8\">] </span><span style=\"color:#F97583\">=</span><span style=\"color:#E1E4E8\"> (</span><span style=\"color:#F97583\">await</span><span style=\"color:#B392F0\"> getCollection</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#9ECBFF\">&#39;blog&#39;</span><span style=\"color:#E1E4E8\">, ({</span><span style=\"color:#FFAB70\">id</span><span style=\"color:#E1E4E8\">}) </span><span style=\"color:#F97583\">=&gt;</span><span style=\"color:#E1E4E8\"> {</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">  return</span><span style=\"color:#E1E4E8\"> id </span><span style=\"color:#F97583\">===</span><span style=\"color:#E1E4E8\"> slug </span><span style=\"color:#F97583\">||</span><span style=\"color:#E1E4E8\"> id.</span><span style=\"color:#B392F0\">startsWith</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#9ECBFF\">`${</span><span style=\"color:#E1E4E8\">slug</span><span style=\"color:#9ECBFF\">}/`</span><span style=\"color:#E1E4E8\">);</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">})) </span><span style=\"color:#F97583\">as</span><span style=\"color:#B392F0\"> Props</span><span style=\"color:#E1E4E8\">[</span><span style=\"color:#9ECBFF\">&#39;post&#39;</span><span style=\"color:#E1E4E8\">][];</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">const</span><span style=\"color:#79B8FF\"> frontmatter</span><span style=\"color:#F97583\"> =</span><span style=\"color:#E1E4E8\"> post?.data;</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">const</span><span style=\"color:#E1E4E8\"> {</span><span style=\"color:#FFAB70\">entries</span><span style=\"color:#E1E4E8\">: </span><span style=\"color:#79B8FF\">webmentions</span><span style=\"color:#E1E4E8\">, </span><span style=\"color:#79B8FF\">error</span><span style=\"color:#E1E4E8\">} </span><span style=\"color:#F97583\">=</span><span style=\"color:#F97583\"> await</span><span style=\"color:#B392F0\"> getLiveCollection</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#9ECBFF\">&#39;webmentions&#39;</span><span style=\"color:#E1E4E8\">, {</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">  targetPath: currentPath</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">});</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">const</span><span style=\"color:#79B8FF\"> postMentions</span><span style=\"color:#F97583\">:</span><span style=\"color:#B392F0\"> Props</span><span style=\"color:#E1E4E8\">[</span><span style=\"color:#9ECBFF\">&#39;postMentions&#39;</span><span style=\"color:#E1E4E8\">] </span><span style=\"color:#F97583\">=</span><span style=\"color:#E1E4E8\"> [];</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#F97583\">if</span><span style=\"color:#E1E4E8\"> (error) {</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">  console.</span><span style=\"color:#B392F0\">error</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#9ECBFF\">`Error fetching webmentions:, ${</span><span style=\"color:#E1E4E8\">error</span><span style=\"color:#9ECBFF\">}`</span><span style=\"color:#E1E4E8\">);</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">} </span><span style=\"color:#F97583\">else</span><span style=\"color:#F97583\"> if</span><span style=\"color:#E1E4E8\"> (webmentions </span><span style=\"color:#F97583\">&amp;&amp;</span><span style=\"color:#E1E4E8\"> webmentions.</span><span style=\"color:#79B8FF\">length</span><span style=\"color:#F97583\"> &gt;</span><span style=\"color:#79B8FF\"> 0</span><span style=\"color:#E1E4E8\">) {</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">  postMentions.</span><span style=\"color:#B392F0\">push</span><span style=\"color:#E1E4E8\">(</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">    ...</span><span style=\"color:#E1E4E8\">(webmentions </span><span style=\"color:#F97583\">as</span><span style=\"color:#B392F0\"> LiveDataEntry</span><span style=\"color:#E1E4E8\">&lt;</span><span style=\"color:#B392F0\">Webmention</span><span style=\"color:#E1E4E8\">&gt;[]).</span><span style=\"color:#B392F0\">filter</span><span style=\"color:#E1E4E8\">((</span><span style=\"color:#FFAB70\">mention</span><span style=\"color:#E1E4E8\">) </span><span style=\"color:#F97583\">=&gt;</span><span style=\"color:#E1E4E8\"> {</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">      const</span><span style=\"color:#E1E4E8\"> {</span><span style=\"color:#79B8FF\">source</span><span style=\"color:#E1E4E8\">, </span><span style=\"color:#79B8FF\">target</span><span style=\"color:#E1E4E8\">} </span><span style=\"color:#F97583\">=</span><span style=\"color:#E1E4E8\"> mention.data;</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">      if</span><span style=\"color:#E1E4E8\"> (</span><span style=\"color:#F97583\">!</span><span style=\"color:#E1E4E8\">target </span><span style=\"color:#F97583\">||</span><span style=\"color:#F97583\"> !</span><span style=\"color:#E1E4E8\">source) {</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">        return</span><span style=\"color:#79B8FF\"> false</span><span style=\"color:#E1E4E8\">;</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">      }</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">      try</span><span style=\"color:#E1E4E8\"> {</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">        const</span><span style=\"color:#79B8FF\"> targetUrl</span><span style=\"color:#F97583\"> =</span><span style=\"color:#F97583\"> new</span><span style=\"color:#B392F0\"> URL</span><span style=\"color:#E1E4E8\">(target);</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">        return</span><span style=\"color:#E1E4E8\"> targetUrl.pathname </span><span style=\"color:#F97583\">===</span><span style=\"color:#E1E4E8\"> currentPath;</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">      } </span><span style=\"color:#F97583\">catch</span><span style=\"color:#E1E4E8\"> {</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">        return</span><span style=\"color:#E1E4E8\"> target </span><span style=\"color:#F97583\">===</span><span style=\"color:#E1E4E8\"> currentPath;</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">      }</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">    })</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">  );</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">}</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">let</span><span style=\"color:#E1E4E8\"> Content;</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">if</span><span style=\"color:#E1E4E8\"> (post) {</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">  ({Content} </span><span style=\"color:#F97583\">=</span><span style=\"color:#F97583\"> await</span><span style=\"color:#B392F0\"> render</span><span style=\"color:#E1E4E8\">(post));</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#F97583\">export</span><span style=\"color:#F97583\"> const</span><span style=\"color:#79B8FF\"> prerender</span><span style=\"color:#F97583\"> =</span><span style=\"color:#79B8FF\"> false</span><span style=\"color:#E1E4E8\">;</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">---</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#F97583\">&lt;</span><span style=\"color:#E1E4E8\">MarkdownPostLayout</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">  frontmatter</span><span style=\"color:#F97583\">=</span><span style=\"color:#E1E4E8\">{frontmatter ?? {}}</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">  mentions</span><span style=\"color:#F97583\">=</span><span style=\"color:#E1E4E8\">{postMentions.map((m) =&gt; m.data)}</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">&gt;</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">  {Content </span><span style=\"color:#F97583\">&amp;&amp;</span><span style=\"color:#E1E4E8\"> &lt;</span><span style=\"color:#B392F0\">Content</span><span style=\"color:#E1E4E8\"> /&gt;}</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">&lt;/</span><span style=\"color:#E1E4E8\">MarkdownPostLayout</span><span style=\"color:#F97583\">&gt;</span></span></code></pre>\n<p>It may be a little messy, but it gets the job done.  If you want to see it in action, why not link to this page from your site and submit a mention to the handy <a href=\"https://webmention.io/sugardave.cloud/webmention\">manual submission form</a>?  The <code>source URL</code> will be the page where you linked to this page and the <code>target URL</code> will be this page.  After a successful submission, reload this page and you should see it displayed above the footer.  Hopefully 😁</p>\n<h2 id=\"wrapping-up\">Wrapping Up</h2>\n<p>After putting it all together, I can now retrieve and display webmentions associated with any blog post on <strong>sugardave.cloud</strong>, huzzah!  In the future, I hope to implement other appropriate webmention collections for my posts.  My next target is the <code>u-in-reply-to</code> type so I can see what others think about my posts.  Once I have that, I am sure I will have another blog post about that journey.</p>  ","text":"Today I would like to speak about a new experimental feature available in Astro 5.10 and beyond: live content collections.  A live content collection allows you to fetch its data at runtime rather than build time, which is how a regular content collection would normally be used.\nContent Collections\nA well-known use case for a content collection is retrieving all the blog posts you have written and then rendering the matching post based on path processing in a “slug page”.  Anyone who has gone through the complete “Build your first Astro Blog” tutorial should be familiar with how that works.\nWhen running your Astro site in static mode, this requires all potential post paths to be either manually specified in the slug page or dynamically built using the getStaticPaths method.  In either case, these paths are created at build time and cannot be changed afterwards.  This is also true when you use server-side rendering (SSR), but the method for matching a path to a post is slightly different and not part of the goal of this post.\nThe important thing to realize is that all “regular” content collections need to have their matching paths resolved at build time for static sites and all the content must be present at build time when using either static builds or SSR.\nLive Content Collections\nWith live content collections, developers can take more control over how they obtain content for rendering.  One thing you cannot do with regular content collections is have them fetch their data on-demand when a page is requested.  Live content collections enable this.\nFor this site, I have been slowly integrating bits and pieces of interesting concepts from the IndieWeb.  One of these is “webmentions”.  Without going into too much detail, webmentions are based on microformats and enable disparate, unconnected sites and services to “mention” your content and for your site to be able to discover those mentions and process them how you see fit.\nImplementation\nI am using a custom content loader to fetch webmentions from Webmention.io.  I call it webmentionLoader because that seems appropriate.  I have defined a Webmention interface based on suggestions from Claude Sonnet 4.  This is a naive first pass, so I’m only going to show the parts that I actually needed to get this all to work:\nexport interface Webmention {\n  // Core webmention properties\n  id: string;\n  source: string; // URL of the page that mentions your content\n  target: string; // URL of your content being mentioned\n  targetPath: string; // Path of the target URL (for easier filtering)\n  ...\n}\nI also have a helper function to retrieve the webmentions for my domain.  An API key is generated for each site you add to Webmention.io.\nconst getMentionsHTML = async (apiKey: string) => {\n  const response = await fetch(\n    `https://webmention.io/api/mentions.html?token=${apiKey}`\n  );\n  if (!response.ok) {\n    throw new Error(`Failed to fetch webmentions: ${response.statusText}`);\n  }\n  return response.text();\n};\nThe actual live content loader function needs to return an object with three required properties:\n\nname: a string to represent the name of the loader\nloadCollection: an async function that will return the entire collection of data or a filtered set\nloadEntry: an async function that will return a single item in the collection based on a filter\n\nSince I want to customize the display of webmention data, I am using JSDOM to parse the HTML returned from the fetch in loadCollection.  Another thing to note is that since webmentions are a many-to-one relation with posts, loadEntry will never be used, so I am only returning an error indicating it is not supported.\nexport const webmentionLoader = ({\n  apiKey\n}: {\n  apiKey: string;\n}): LiveLoader<Webmention> => {\n  return {\n    name: 'webmention-loader',\n    loadCollection: async ({filter}) => {\n      const {targetPath} = filter || {targetPath: ''};\n      try {\n        const webmentions: Webmention[] = await getMentionsHTML(apiKey).then(\n          (html) => {\n            const dom = new JSDOM(html);\n            const doc = dom.window.document;\n            const mentions = Array.from(\n              doc.querySelectorAll('.h-entry.mention')\n            );\n            const items = mentions.map((element, index) => {\n              // construct a target path from the target URL\n              const target = element\n                .querySelector('.u-mention-of')\n                ?.getAttribute('href') as string;\n              const postPath = target ? new URL(target).pathname : '';\n              return {\n                id: `${postPath}:${index}`,\n                data: {\n                  author: {\n                    name: element.querySelector('.p-author')\n                      ?.textContent as string\n                  }\n                },\n                source: element\n                  .querySelector('.u-url')\n                  ?.getAttribute('href') as string,\n                target,\n                targetPath: postPath\n              };\n            });\n            if (targetPath) {\n              // Filter by targetPath if provided\n              return items.filter((item) => item.targetPath === targetPath);\n            }\n            return items;\n          }\n        );\n\n        return {\n          entries: webmentions.map((mention) => ({\n            id: mention.id,\n            data: mention\n          }))\n        };\n      } catch (error) {\n        return {\n          error: new Error(\n            `Failed to retrieve webmentions: ${error instanceof Error ? error.message : 'Unknown error'}`\n          )\n        };\n      }\n    },\n    // there can be multiple webmentions for the same target, so we will never use loadEntry\n    loadEntry: async () => {\n      return {\n        error: new Error(`webmentionLoader does not support loadEntry`)\n      };\n    }\n  };\n};\nNow, in my slug page I can handle blog posts as a regular content collection just like I’ve been doing and also grab any webmentions that match them.\n---\nimport type {CollectionEntry} from 'astro:content';\nimport type {LiveDataEntry} from 'astro';\nimport type {Webmention} from '@lib/webmentionLoader';\nimport {getCollection, getLiveCollection, render} from 'astro:content';\nimport MarkdownPostLayout from '@layouts/MarkdownPostLayout.astro';\n\ninterface Props {\n  post: CollectionEntry<'blog'>;\n  postMentions: LiveDataEntry<Webmention>[];\n}\n\nconst {\n  params: {slug},\n  url\n} = Astro;\nconst currentPath = url.pathname.endsWith('/')\n  ? url.pathname.slice(0, -1)\n  : url.pathname;\nconst [post] = (await getCollection('blog', ({id}) => {\n  return id === slug || id.startsWith(`${slug}/`);\n})) as Props['post'][];\nconst frontmatter = post?.data;\nconst {entries: webmentions, error} = await getLiveCollection('webmentions', {\n  targetPath: currentPath\n});\nconst postMentions: Props['postMentions'] = [];\n\nif (error) {\n  console.error(`Error fetching webmentions:, ${error}`);\n} else if (webmentions && webmentions.length > 0) {\n  postMentions.push(\n    ...(webmentions as LiveDataEntry<Webmention>[]).filter((mention) => {\n      const {source, target} = mention.data;\n      if (!target || !source) {\n        return false;\n      }\n      try {\n        const targetUrl = new URL(target);\n        return targetUrl.pathname === currentPath;\n      } catch {\n        return target === currentPath;\n      }\n    })\n  );\n}\nlet Content;\nif (post) {\n  ({Content} = await render(post));\n}\n\nexport const prerender = false;\n---\n\n<MarkdownPostLayout\n  frontmatter={frontmatter ?? {}}\n  mentions={postMentions.map((m) => m.data)}\n>\n  {Content && <Content />}\n</MarkdownPostLayout>\nIt may be a little messy, but it gets the job done.  If you want to see it in action, why not link to this page from your site and submit a mention to the handy manual submission form?  The source URL will be the page where you linked to this page and the target URL will be this page.  After a successful submission, reload this page and you should see it displayed above the footer.  Hopefully 😁\nWrapping Up\nAfter putting it all together, I can now retrieve and display webmentions associated with any blog post on sugardave.cloud, huzzah!  In the future, I hope to implement other appropriate webmention collections for my posts.  My next target is the u-in-reply-to type so I can see what others think about my posts.  Once I have that, I am sure I will have another blog post about that journey."},"children":[{"type":["h-px"],"properties":{}}]}}},{"type":"entry","uid":"https://grant.codes/2025/06/18/10-35-11","like-of":"https://fosstodon.org/@ente/114562028846761783","published":"2025-06-18T10:35:11.177Z","url":"https://grant.codes/2025/06/18/10-35-11","references":{"https://fosstodon.org/@ente/114562028846761783":{"type":"entry","uid":"https://fosstodon.org/@ente/114562028846761783","url":"https://fosstodon.org/@ente/114562028846761783","name":"Ente (@ente@fosstodon.org)","summary":"Attached: 1 image Eruption on Mount Etna (Sicily) giving the illusion of a Phoenix in the sky. #Photography","featured":"https://cdn.fosstodon.org/media_attachments/files/114/562/021/494/836/601/original/2514eb20fc970b07.jpg"}}},{"type":"entry","uid":"https://grant.codes/2025/05/29/08-32-20","like-of":"https://briefs.video/videos/introducing-webbed-sites/","published":"2025-05-29T08:32:20.870Z","url":"https://grant.codes/2025/05/29/08-32-20","references":{"https://briefs.video/videos/introducing-webbed-sites/":{"type":"entry","uid":"https://briefs.video/videos/introducing-webbed-sites/","url":"https://briefs.video/videos/introducing-webbed-sites/","name":"Introducing: Webbed Sites","summary":"A video from Webbed Briefs","featured":"https://briefs.video/assets/images/thumbnails/introducing-webbed-sites-card.png"}}},{"type":"entry","uid":"https://grant.codes/2025/05/22/09-13-05","like-of":"https://tilde.zone/@xandra/114547017410429628","published":"2025-05-22T09:13:05.937Z","url":"https://grant.codes/2025/05/22/09-13-05","references":{"https://tilde.zone/@xandra/114547017410429628":{"type":"entry","uid":"https://tilde.zone/@xandra/114547017410429628","url":"https://tilde.zone/@xandra/114547017410429628","name":"alexandra (@xandra@tilde.zone)","summary":"Attached: 1 image woo\u0014last looks (and fixes) on the print proof before final printing! IT\u0019S HAPPENING","featured":"https://media.tilde.zone/media_attachments/files/114/547/003/957/747/062/original/19d290d7fc702d2b.png"}}},{"type":"entry","uid":"https://grant.codes/2025/05/07/01-57-50","like-of":"https://dbushell.com/2025/05/07/glossary-web-component/","published":"2025-05-07T13:57:50.208Z","url":"https://grant.codes/2025/05/07/01-57-50","references":{"https://dbushell.com/2025/05/07/glossary-web-component/":{"type":"entry","uid":"https://dbushell.com/2025/05/07/glossary-web-component/","url":"https://dbushell.com/2025/05/07/glossary-web-component/","name":"Glossary Web Component","summary":"The one where I put the hypercard in the hyperlink","featured":"https://dbushell.com/images/articles/2025-05-07-glossary-web-component.png","published":"2025-05-07T00:00:00.000Z"}}},{"type":"entry","uid":"https://grant.codes/2025/05/02/07-28-20","like-of":"https://hachyderm.io/@charliewilco/114435540754888601","published":"2025-05-02T07:28:20.955Z","url":"https://grant.codes/2025/05/02/07-28-20","references":{"https://hachyderm.io/@charliewilco/114435540754888601":{"type":"entry","uid":"https://hachyderm.io/@charliewilco/114435540754888601","url":"https://hachyderm.io/@charliewilco/114435540754888601","name":"Charlie �\u000f (@charliewilco@hachyderm.io)","summary":"web components need a next.js they need something bigger than astro there has to be an application model for web components"}}},{"type":"entry","uid":"https://grant.codes/2025/04/22/10-02-54","like-of":"https://mastodon.social/@davatron5000/114379441761385823","published":"2025-04-22T10:02:54.987Z","url":"https://grant.codes/2025/04/22/10-02-54","references":{"https://mastodon.social/@davatron5000/114379441761385823":{"type":"entry","uid":"https://mastodon.social/@davatron5000/114379441761385823","url":"https://mastodon.social/@davatron5000/114379441761385823","name":"Dave Rupert (@davatron5000@mastodon.social)","summary":"Final step of my Notion-to-Obsidian migration: Exporting 587 blog posts. Whew."}}},{"type":"entry","uid":"https://grant.codes/2025/04/11/08-43-47","like-of":"https://velvetshark.com/ai-company-logos-that-look-like-buttholes","published":"2025-04-11T08:43:47.618Z","url":"https://grant.codes/2025/04/11/08-43-47","references":{"https://velvetshark.com/ai-company-logos-that-look-like-buttholes":{"type":"entry","uid":"https://velvetshark.com/ai-company-logos-that-look-like-buttholes","url":"https://velvetshark.com/ai-company-logos-that-look-like-buttholes","name":"Why do AI company logos look like buttholes?","summary":"A humorous exploration of the uncanny resemblance between AI company logos and human anatomy. Discover why circular, gradient-based designs dominate the AI industry, and what this design convergence&hellip;","featured":"https://velvetshark.com/images/ai-company-logos-that-look-like-buttholes/why-do-AI-company-logos-look-like-buttholes.png"}}},{"type":"entry","uid":"https://grant.codes/2025/03/21/10-46-38","like-of":"https://mastodon.social/@owa/114199424779247989","published":"2025-03-21T10:46:38.525Z","url":"https://grant.codes/2025/03/21/10-46-38","references":{"https://mastodon.social/@owa/114199424779247989":{"type":"entry","uid":"https://mastodon.social/@owa/114199424779247989","url":"https://mastodon.social/@owa/114199424779247989","name":"Open Web Advocacy (@owa@mastodon.social)","summary":"1/10 =� The � https://open-web-advocacy.org/blog/uk-regulators-final-verdict--apples-browser-engine-ban-harms-competition/"}}},{"type":"entry","uid":"https://grant.codes/2025/02/27/10-16-37","like-of":"https://fediverse.zachleat.com/@zachleat/114072799841101889","published":"2025-02-27T10:16:37.545Z","url":"https://grant.codes/2025/02/27/10-16-37","references":{"https://fediverse.zachleat.com/@zachleat/114072799841101889":{"type":"entry","uid":"https://fediverse.zachleat.com/@zachleat/114072799841101889","url":"https://fediverse.zachleat.com/@zachleat/114072799841101889","name":"Zach Leatherman :11ty: (@zachleat@zachleat.com)","summary":"Attached: 1 image automated themed screenshot border colors","featured":"https://cdn.masto.host/fediversezachleatcom/media_attachments/files/114/072/797/367/820/623/original/0550a7ace24fe33e.png"}}},{"type":"entry","uid":"https://grant.codes/2025/02/26/09-31-20","like-of":"https://mastodon.social/@Daojoan/114067260426518351","published":"2025-02-26T09:31:20.771Z","url":"https://grant.codes/2025/02/26/09-31-20","references":{"https://mastodon.social/@Daojoan/114067260426518351":{"type":"entry","uid":"https://mastodon.social/@Daojoan/114067260426518351","url":"https://mastodon.social/@Daojoan/114067260426518351","name":"JA Westenberg (@Daojoan@mastodon.social)","summary":"Why do we talk about walled gardens? That implies something beautiful, something worth defending. It conjures images of beautifully maintained flowerbeds protected from the outside world. But that\u0019s&hellip;"}}},{"type":"entry","uid":"https://grant.codes/2025/02/17/03-01-25","like-of":"https://rabbit-rabbit.quest","published":"2025-02-17T15:01:25.403Z","url":"https://grant.codes/2025/02/17/03-01-25","references":{"https://rabbit-rabbit.quest":{"type":"entry","uid":"https://rabbit-rabbit.quest","url":"https://rabbit-rabbit.quest","name":"Rabbit Rabbit","summary":"Rabbit Rabbit is an alternative interpretation of Geohashing. Born out of the xkcd #426 comic strip, Geohashing is a game of spontaneous adventure generation played around the world since 2008. This&hellip;"}}},{"type":"entry","uid":"https://grant.codes/2025/02/17/03-01-06","like-of":"https://shademap.app/","published":"2025-02-17T15:01:06.862Z","url":"https://grant.codes/2025/02/17/03-01-06","references":{"https://shademap.app/":{"type":"entry","uid":"https://shademap.app/","url":"https://shademap.app/","name":"Simulate sun shadows","summary":"Every mountain, building and tree shadow in the world simulated for any date and time","featured":"https://shademap.app/og-image.png"}}},{"type":"entry","uid":"https://grant.codes/2025/02/17/07-07-39","like-of":"https://dice.camp/@starhawk/113945849415768787","published":"2025-02-17T07:07:39.491Z","url":"https://grant.codes/2025/02/17/07-07-39","references":{"https://dice.camp/@starhawk/113945849415768787":{"type":"entry","uid":"https://dice.camp/@starhawk/113945849415768787","url":"https://dice.camp/@starhawk/113945849415768787","name":"Starhawk (@starhawk@dice.camp)","summary":"The Internet is mostly shit these days, but every once in a great while I stumble across an incredible, passion-project website. This one is hand-animated weird art and it is worth your time as a fun&hellip;"}}},{"type":"entry","uid":"https://grant.codes/2025/02/11/12-28-47","like-of":"https://heatmap.news/technology/deepseek-ai-energy-demand","published":"2025-02-11T12:28:47.546Z","url":"https://grant.codes/2025/02/11/12-28-47","references":{"https://heatmap.news/technology/deepseek-ai-energy-demand":{"type":"entry","uid":"https://heatmap.news/technology/deepseek-ai-energy-demand","url":"https://heatmap.news/technology/deepseek-ai-energy-demand","name":"What DeepSeek Means for AI Energy Demand","summary":"It\u0019s complicated!","featured":"https://heatmap.news/media-library/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpbWFnZSI6Imh0dHBzOi8vYXNzZXRzLnJibC5tcy81NjE3ODIxNS9vcmlnaW4uanBnIiwiZXhwaXJlc19hdCI6MTc2MDUwNjU5Mn0.bctc4qAimxY6rWQj7YZKBFJxfqBJ6Ii0JrAxxT0uMjI/image.jpg?width=1200&height=600&coordinates=0%2C100%2C0%2C100","published":"2025-02-04T21:11:08.000Z"}}},{"type":"entry","uid":"https://grant.codes/2025/02/10/11-20-08","like-of":"https://web.dev/blog/baseline-scrollbar-props?hl=en","published":"2025-02-10T11:20:08.351Z","url":"https://grant.codes/2025/02/10/11-20-08","references":{"https://web.dev/blog/baseline-scrollbar-props?hl=en":{"type":"entry","uid":"https://web.dev/blog/baseline-scrollbar-props?hl=en","url":"https://web.dev/blog/baseline-scrollbar-props?hl=en","name":"CSS scrollbar-color and scrollbar-gutter are Baseline Newly available | Articles | web.dev","summary":"CSS scrollbar-color and scrollbar-gutter have landed in all major browser engines, making it Baseline Newly available.","featured":"https://web.dev/static/blog/baseline-scrollbar-props/image/scrollbar-styling-baseline.png"}}},{"type":"entry","uid":"https://grant.codes/2025/01/30/05-55-54","like-of":"https://somepx.itch.io","published":"2025-01-30T17:55:54.438Z","url":"https://grant.codes/2025/01/30/05-55-54","references":{"https://somepx.itch.io":{"type":"entry","uid":"https://somepx.itch.io","url":"https://somepx.itch.io","name":"somepx","summary":"a bold, boxy, 10px tall pixel font!a tiny, smallcaps, 6px tall pixel font!","featured":"https://img.itch.zone/aW1nLzEzNDcyNjU0LnBuZw==/508x254%23mb/%2FKebc9.png"}}},{"type":"entry","uid":"https://grant.codes/2025/01/28/06-49-04","like-of":"https://mastodon.social/@mdn/113901386762398350","published":"2025-01-28T06:49:04.935Z","url":"https://grant.codes/2025/01/28/06-49-04","references":{"https://mastodon.social/@mdn/113901386762398350":{"type":"entry","uid":"https://mastodon.social/@mdn/113901386762398350","url":"https://mastodon.social/@mdn/113901386762398350","name":"MDN Web Docs (@mdn@mastodon.social)","summary":"JavaScript's date object has been tricky for years, but that will change soon. The NEW Temporal API brings, < Easily handle time zones =� Precise date math =R Parse ISO strings without errors \u001b&hellip;"}}},{"type":"entry","uid":"https://grant.codes/2025/01/20/10-15-23","like-of":"https://nokiadesignarchive.aalto.fi","published":"2025-01-20T10:15:23.544Z","url":"https://grant.codes/2025/01/20/10-15-23","references":{"https://nokiadesignarchive.aalto.fi":{"type":"entry","uid":"https://nokiadesignarchive.aalto.fi","url":"https://nokiadesignarchive.aalto.fi","name":"Nokia Design Archive","summary":"Exploring unseen concepts of design and opportunities of design-driven transformation and change","featured":"https://nokiadesignarchive.aalto.fi/images/preview--nokia-design-archive.jpg"}}},{"type":"entry","uid":"https://grant.codes/2025/01/16/10-32-04","like-of":"http://estacions.albertguillaumes.cat","published":"2025-01-16T10:32:04.032Z","url":"https://grant.codes/2025/01/16/10-32-04","references":{"http://estacions.albertguillaumes.cat":{"type":"entry","uid":"http://estacions.albertguillaumes.cat","url":"http://estacions.albertguillaumes.cat","name":"Estacions i intercanviadors","summary":"Una galeria de perspectives amb la topologia de diverses estacions de metro d'Europa.","featured":"http://estacions.albertguillaumes.cat/img/barcelona/collblanc.png"}}},{"type":"entry","uid":"https://grant.codes/2025/01/16/10-22-54","like-of":"https://front-end.social/@argyleink/113834229298080017","published":"2025-01-16T10:22:54.585Z","url":"https://grant.codes/2025/01/16/10-22-54","references":{"https://front-end.social/@argyleink/113834229298080017":{"type":"entry","uid":"https://front-end.social/@argyleink/113834229298080017","url":"https://front-end.social/@argyleink/113834229298080017","name":"Adam Argyle (@argyleink@front-end.social)","summary":"Attached: 3 images #CSS **`scroll-state()` container queries** in Chrome 133 ```css @container scroll-state(stuck: top) { & } ``` Read all about snapped, stuck and scrollable in this post:&hellip;"}}},{"type":"entry","uid":"https://grant.codes/2025/01/14/10-57-04","like-of":"https://cartosvg.com","published":"2025-01-14T10:57:04.166Z","url":"https://grant.codes/2025/01/14/10-57-04","references":{"https://cartosvg.com":{"type":"entry","uid":"https://cartosvg.com","url":"https://cartosvg.com","name":"Examples","summary":"CartoSVG uses SVG elements, which are interactive by nature.The maps are given depth through filters and rich styling possibilities.Just copy / paste the exported file into your HTML, and you're&hellip;"}}},{"type":"entry","uid":"https://grant.codes/2025/01/14/10-55-02","like-of":"https://www.mshr.app","published":"2025-01-14T10:55:02.161Z","url":"https://grant.codes/2025/01/14/10-55-02","references":{"https://www.mshr.app":{"type":"entry","uid":"https://www.mshr.app","url":"https://www.mshr.app","name":"CSS mesh gradients","summary":"A collection of 732 vanilla CSS mesh gradients free for you to use in any of your projects. Browse our generated meshes or create your own custom mesh with our App."}}},{"type":"entry","uid":"https://grant.codes/2025/01/14/10-52-29","like-of":"https://developer.chrome.com/docs/css-ui/css-field-sizing","published":"2025-01-14T10:52:29.842Z","url":"https://grant.codes/2025/01/14/10-52-29","references":{"https://developer.chrome.com/docs/css-ui/css-field-sizing":{"type":"entry","uid":"https://developer.chrome.com/docs/css-ui/css-field-sizing","url":"https://developer.chrome.com/docs/css-ui/css-field-sizing","name":"CSS field-sizing | Chrome for Developers","summary":"One line of code for auto sizing elements with editable content."}}},{"type":"entry","uid":"https://grant.codes/2024/11/08/09-22-10","like-of":"https://toot.cafe/@tomayac/113446179350621117","published":"2024-11-08T09:22:10.182Z","url":"https://grant.codes/2024/11/08/09-22-10","references":{"https://toot.cafe/@tomayac/113446179350621117":{"type":"entry","uid":"https://toot.cafe/@tomayac/113446179350621117","url":"https://toot.cafe/@tomayac/113446179350621117","name":"Thomas Steiner :chrome","summary":"Finally more options for styling the `` element, for example, to animate its opening. @bramus@front-end.social's article has all the details: https://developer.chrome.com/blog/styling-details. >�"}}},{"type":"entry","uid":"https://grant.codes/2024/10/30/01-40-33","like-of":"https://front-end.social/@zeldman/113396308709565733","published":"2024-10-30T13:40:33.318Z","url":"https://grant.codes/2024/10/30/01-40-33","references":{"https://front-end.social/@zeldman/113396308709565733":{"type":"entry","uid":"https://front-end.social/@zeldman/113396308709565733","url":"https://front-end.social/@zeldman/113396308709565733","name":"zeldman (@zeldman@front-end.social)","summary":"\u001cHTML Forms have powerful validation mechanisms, but they are heavily underused. In fact, not many people even know much about them. Is this because of some flaw in their design? Let\u0019s explore.\u001d&hellip;"}}},{"type":"entry","uid":"https://grant.codes/2024/10/29/01-01-02","like-of":"https://smarthomescene.com/diy/diy-the-smallest-bluetooth-proxy-with-esp32-c3-supermini/","published":"2024-10-29T13:01:02.290Z","url":"https://grant.codes/2024/10/29/01-01-02"},{"type":"entry","uid":"https://grant.codes/2024/10/29/08-00-13","like-of":"https://floss.social/@downey/113387242711741163","published":"2024-10-29T08:00:13.043Z","url":"https://grant.codes/2024/10/29/08-00-13","references":{"https://floss.social/@downey/113387242711741163":{"type":"entry","uid":"https://floss.social/@downey/113387242711741163","url":"https://floss.social/@downey/113387242711741163","name":"Michael Downey =� (@downey@floss.social)","summary":"No, dear website, {task} is not \"better using the app\"."}}},{"type":"entry","uid":"https://grant.codes/2024/10/23/12-42-48","like-of":"https://www.nytimes.com/2024/10/17/climate/carbon-fires-forests-global-warming.html#commentsContainer","published":"2024-10-23T12:42:48.169Z","url":"https://grant.codes/2024/10/23/12-42-48"},{"type":"entry","uid":"https://grant.codes/2024/10/15/02-25-59","like-of":"https://www.jvt.me/posts/2024/10/15/slashes/?utm_medium=rss&utm_source=rss","published":"2024-10-15T14:25:59.025Z","url":"https://grant.codes/2024/10/15/02-25-59","references":{"https://www.jvt.me/posts/2024/10/15/slashes/":{"type":"entry","uid":"https://www.jvt.me/posts/2024/10/15/slashes/","name":"Creating a /slashes page","featured":"https://www.jvt.me/img/profile.jpg","content":{"html":"<p>Last week I found out that <span class=\"h-card\"><a class=\"u-url\" href=\"https://rknight.me\">Robb Knight</a></span> has a site called called <a href=\"https://slashpages.net/\">Slash Pages</a>, which details common top-level URLs that are found at <code>/&lt;name></code>.</p><p>I'd found out about this as <a href=\"https://github.com/rknightuk/slashpages/pull/6\">someone had started contributing a /salary page section</a>, and looking at both my own site, and the list of pages that have already been collected, I thought I'd create my own <a href=\"/slashes/\">/slashes page</a>.</p><p>I'll also be adding a few new options to the upstream <a href=\"https://slashpages.net/\">Slash Pages</a> site to add some things I've been doing too.</p>","text":"Last week I found out that Robb Knight has a site called called Slash Pages, which details common top-level URLs that are found at /<name>.I'd found out about this as someone had started contributing a /salary page section, and looking at both my own site, and the list of pages that have already been collected, I thought I'd create my own /slashes page.I'll also be adding a few new options to the upstream Slash Pages site to add some things I've been doing too."},"url":"https://www.jvt.me/posts/2024/10/15/slashes/","summary":"Creating a collection of all the /slash pages I have across my site.","author":{"type":"card","uid":"https://www.jvt.me","photo":"https://www.jvt.me/img/profile.png","name":"Jamie Tanna","url":"https://www.jvt.me"},"published":"2024-10-15T14:08:53+01:00","updated":"2024-10-15T14:08:53+01:00","category":"www.jvt.me","children":[{"value":"Robb Knight","type":["h-card"],"properties":{"url":["https://rknight.me"],"name":["Robb Knight"]}}]}}},{"type":"entry","uid":"https://grant.codes/2024/10/15/09-52-51","like-of":"https://beeps.website/blog/2024-10-09-why-govuk-exit-this-page-doesnt-use-escape/","published":"2024-10-15T09:52:51.890Z","url":"https://grant.codes/2024/10/15/09-52-51","references":{"https://beeps.website/blog/2024-10-09-why-govuk-exit-this-page-doesnt-use-escape/":{"type":"entry","uid":"https://beeps.website/blog/2024-10-09-why-govuk-exit-this-page-doesnt-use-escape/","url":"https://beeps.website/blog/2024-10-09-why-govuk-exit-this-page-doesnt-use-escape/","name":"Why GOV.UK's Exit this Page component doesn't use the Escape key","summary":"A very specific question, answered very longwindedly.","featured":"https://v1.screenshot.11ty.dev/https%3A%2F%2Fbeeps.website%2Fblog%2F2024-10-09-why-govuk-exit-this-page-doesnt-use-escape%2Fopengraph%2F/opengraph/_1728556120/","published":"2024-10-09T00:00:00.000Z"}}},{"type":"entry","uid":"https://grant.codes/2024/10/11/02-33-06","like-of":"https://front-end.social/@keithjgrant/113286756123288212","published":"2024-10-11T14:33:06.690Z","url":"https://grant.codes/2024/10/11/02-33-06","references":{"https://front-end.social/@keithjgrant/113286756123288212":{"type":"entry","uid":"https://front-end.social/@keithjgrant/113286756123288212","url":"https://front-end.social/@keithjgrant/113286756123288212","name":"Keith J Grant (@keithjgrant@front-end.social)","summary":"Attached: 1 image We just threw a 7-ender! >L #curling","featured":"https://cdn.masto.host/frontendsocial/media_attachments/files/113/286/752/644/620/046/original/43d4213cfbbada04.jpg"}}},{"type":"entry","uid":"https://grant.codes/2024/10/07/09-37-45","like-of":"https://mstdn.social/@rysiek/113243932147763182","published":"2024-10-07T09:37:45.613Z","url":"https://grant.codes/2024/10/07/09-37-45","references":{"https://mstdn.social/@rysiek/113243932147763182":{"type":"entry","uid":"https://mstdn.social/@rysiek/113243932147763182","url":"https://mstdn.social/@rysiek/113243932147763182","name":"MichaB \"rysiek\" Wozniak �","summary":"As I am seeing some Medium links in my timeline today, and Medium is pretty annoying (pop-overs and all). So reminder that you can just replace: > medium.com &with: > scribe.rip In any Medium link and&hellip;"}}},{"type":"entry","uid":"https://grant.codes/2024/10/02/06-02-49","like-of":"https://dice.camp/@miriamrobern/113228532711715509","published":"2024-10-02T06:02:49.171Z","url":"https://grant.codes/2024/10/02/06-02-49","references":{"https://dice.camp/@miriamrobern/113228532711715509":{"type":"entry","uid":"https://dice.camp/@miriamrobern/113228532711715509","url":"https://dice.camp/@miriamrobern/113228532711715509","name":"Miriam \"Scary Username\" Robern (@miriamrobern@dice.camp)","summary":"Attached: 1 image OMFG this. #ai #meta #metaAI #aiart #chatgpt","featured":"https://cdn.masto.host/dicecamp/media_attachments/files/113/228/523/234/959/494/original/fe03a06e08e07b05.png"}}},{"type":"entry","uid":"https://grant.codes/2024/09/27/02-44-20","like-of":"https://fediverse.zachleat.com/@zachleat/113209829620153665","published":"2024-09-27T14:44:20.260Z","url":"https://grant.codes/2024/09/27/02-44-20","references":{"https://fediverse.zachleat.com/@zachleat/113209829620153665":{"type":"entry","uid":"https://fediverse.zachleat.com/@zachleat/113209829620153665","url":"https://fediverse.zachleat.com/@zachleat/113209829620153665","name":"Zach Leatherman :11ty: (@zachleat@zachleat.com)","summary":"If your JavaScript framework doesn\u0019t support web components, it doesn\u0019t support the web platform."}}},{"type":"entry","uid":"https://grant.codes/2024/09/26/10-14-33","like-of":"https://alvaromontoro.com/blog/68063/bad-css-dad-jokes-ii","published":"2024-09-26T10:14:33.730Z","url":"https://grant.codes/2024/09/26/10-14-33","references":{"https://alvaromontoro.com/blog/68063/bad-css-dad-jokes-ii":{"type":"entry","uid":"https://alvaromontoro.com/blog/68063/bad-css-dad-jokes-ii","url":"https://alvaromontoro.com/blog/68063/bad-css-dad-jokes-ii","name":"Bad CSS-Dad Jokes (II)","summary":"After the terrible reception great success of my first post with Bad CSS-Dad Jokes, I am here to torture entertain you with more terrible amazing CSS jokes. Enjoy! :: Blog post at Alvaro Montoro's&hellip;","featured":"https://alvaromontoro.com/images/blog/bad-dad-jokes-2-0.webp"}}},{"type":"entry","uid":"https://grant.codes/2024/09/24/07-46-39","like-of":"https://front-end.social/@bramus/113192802274531981","published":"2024-09-24T19:46:39.263Z","url":"https://grant.codes/2024/09/24/07-46-39","references":{"https://front-end.social/@bramus/113192802274531981":{"type":"entry","uid":"https://front-end.social/@bramus/113192802274531981","url":"https://front-end.social/@bramus/113192802274531981","name":"Bramus (@bramus@front-end.social)","summary":"Attached: 1 image Something to add to your CSS reset from now on: ``` :root { interpolate-size: allow-keywords; } ``` It enables things like transitions from `height: 0` to `height: auto`.&hellip;","featured":"https://cdn.masto.host/frontendsocial/media_attachments/files/113/192/800/068/802/496/small/584e1829c2091c91.png"}}},{"type":"entry","uid":"https://grant.codes/2024/09/24/04-27-13","like-of":"https://mastodon.social/@brad_frost/113192784039854930","published":"2024-09-24T16:27:13.848Z","url":"https://grant.codes/2024/09/24/04-27-13","references":{"https://mastodon.social/@brad_frost/113192784039854930":{"type":"entry","uid":"https://mastodon.social/@brad_frost/113192784039854930","url":"https://mastodon.social/@brad_frost/113192784039854930","name":"brad_frost (@brad_frost@mastodon.social)","summary":"Now that social media is scattered, I think it's finally time to set up an Indie Web-esque \"post (even short stuff) on my own site and automatically post it to Mastodon, Threads, et al. I've got a&hellip;"}}},{"type":"entry","uid":"https://grant.codes/2024/09/08/06-08-51","like-of":"https://front-end.social/@mxbck/113101104768024484","published":"2024-09-08T18:08:51.088Z","url":"https://grant.codes/2024/09/08/06-08-51","references":{"https://front-end.social/@mxbck/113101104768024484":{"type":"entry","uid":"https://front-end.social/@mxbck/113101104768024484","url":"https://front-end.social/@mxbck/113101104768024484","name":"Max B�ck (@mxbck@front-end.social)","summary":"\u000f\u000f New post: Remember when the code in your editor was exactly the same code delivered to the browser? Now there's usually a build process in between, and that can get ... complicated. Do we still&hellip;"}}},{"type":"entry","uid":"https://grant.codes/2024/08/19/10-07-13","like-of":"https://mastodon.social/@Daojoan/112980995791841731","published":"2024-08-19T10:07:13.818Z","url":"https://grant.codes/2024/08/19/10-07-13","references":{"https://mastodon.social/@Daojoan/112980995791841731":{"type":"entry","uid":"https://mastodon.social/@Daojoan/112980995791841731","url":"https://mastodon.social/@Daojoan/112980995791841731","name":"Joan Westenberg (@Daojoan@mastodon.social)","summary":"Attached: 1 image If anyone\u0019s wondering what the dude who sang \u001cchocolate rain\u001d is up to these days, he\u0019s dropping absolute bangers about housing and he\u0019s fucking right","featured":"https://files.mastodon.social/media_attachments/files/112/980/995/680/625/701/original/02e11d4478f3f48c.jpeg"}}},{"type":"entry","uid":"https://grant.codes/2024/06/23/09-13-48","like-of":"https://toot.cafe/@slightlyoff/112656920041897216","published":"2024-06-23T09:13:48.250Z","url":"https://grant.codes/2024/06/23/09-13-48","references":{"https://toot.cafe/@slightlyoff/112656920041897216":{"type":"entry","uid":"https://toot.cafe/@slightlyoff/112656920041897216","url":"https://toot.cafe/@slightlyoff/112656920041897216","name":"Alex Russell (@slightlyoff@toot.cafe)","summary":"Processing JS, e.g., is at least 3x more expensive per byte than HTML and CSS. That means that you can afford three times as much declarative content given a constant latency budget. Including network&hellip;"}}},{"type":"entry","uid":"https://grant.codes/2024/06/18/09-11-29","like-of":"https://typetura.social/@scott/112632899898395667","published":"2024-06-18T09:11:29.939Z","url":"https://grant.codes/2024/06/18/09-11-29","references":{"https://typetura.social/@scott/112632899898395667":{"type":"entry","uid":"https://typetura.social/@scott/112632899898395667","url":"https://typetura.social/@scott/112632899898395667","name":"Scott Kellum :typetura","summary":"Do you use `font-smoothing: antialiased` in your CSS? I\u0019ve got some NEWS for you. MacOS Mojave made this the default in 2018. Also text rendering on Windows heavily relies on subpixel antialiasing. I&hellip;"}}},{"type":"entry","uid":"https://grant.codes/2024/06/03/08-28-31","like-of":"https://wa.rner.me/2024/06/02/firstweek-on-my.html","published":"2024-06-03T08:28:31.920Z","url":"https://grant.codes/2024/06/03/08-28-31"},{"type":"entry","uid":"https://grant.codes/2024/05/24/12-47-52","like-of":"https://smashingmagazine.com/2024/05/naming-best-practices/","published":"2024-05-24T12:47:52.920Z","url":"https://grant.codes/2024/05/24/12-47-52","references":{"https://smashingmagazine.com/2024/05/naming-best-practices/":{"type":"entry","uid":"https://smashingmagazine.com/2024/05/naming-best-practices/","url":"https://smashingmagazine.com/2024/05/naming-best-practices/","name":"Best Practices For Naming Design Tokens, Components, Variables, And More \u0014 Smashing Magazine","summary":"How can we get better at naming? This post is dedicated to naming conventions, tips, and real-world examples that help you name things in a robust and flexible way.","featured":"https://files.smashing.media/articles/naming-best-practices/naming-good-practices-opt.png","published":"0000-05-23T09:00:00.000Z"}}},{"type":"entry","uid":"https://grant.codes/2024/05/07/09-16-13","like-of":"https://typetura.social/@scott/112397536211218435","published":"2024-05-07T09:16:13.718Z","url":"https://grant.codes/2024/05/07/09-16-13","references":{"https://typetura.social/@scott/112397536211218435":{"type":"entry","uid":"https://typetura.social/@scott/112397536211218435","url":"https://typetura.social/@scott/112397536211218435","name":"Scott Kellum :typetura","summary":"Always reject the cookies, even if it costs a few more clicks."}}},{"type":"entry","uid":"https://grant.codes/2024/05/07/09-14-46","like-of":"https://adactio.com/links/21109","published":"2024-05-07T09:14:46.813Z","url":"https://grant.codes/2024/05/07/09-14-46","references":{"https://adactio.com/links/21109":{"type":"entry","uid":"https://adactio.com/links/21109","url":"https://adactio.com/links/21109","name":"Web Components from early 2024 � Chris Burnell","summary":"Some lovely HTML web components\u0014perfect for progressive enhancement!","featured":"https://adactio.com/images/photo-300.jpg"}}}]}