grant.codes

Jump to menu
  • About
  • Contact
  • Projects
  • Updates
  • More...Likes
  • Photos
  • Galleries
  • Replies
๐Ÿ‘ˆ๐Ÿ•ธ๐Ÿ’๐Ÿ‘‰

Liked https://youtu.be/s8UXyOL7-N4

- YouTube

Posted Permalink
Liked an hour ago

Liked https://mastodon.social/@kethinov/115340787570739774

Eric Newport (@kethinov@mastodon.social)

Posted Permalink
Liked 3 months ago

Liked 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

The Tiny Workshop That Conquered Britain: How An Engineers Space-Saving Genius Won Shed Of The Year

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,” has just claimed the coveted Cuprinol Shed of the Year 2025 title, demonstrating that brilliant design can triumph over grand scale.

Robinson’s journey began with a familiar problem: his compact courtyard garden needed storage and workspace, but conventional shed solutions would overwhelm the limited area. “We needed a good bit of storage for the garden necessities,” Robinson explained. “Off-the-shelf options would have taken up too much room, so I went with a DIY design and build.”

Designer: Mike Robinson

The solution he crafted over several weekends defies traditional shed expectations. Starting with two sets of heavy-duty steel shelving, Robinson bolted them together and clad the structure in wood painted with black ash outdoor paint. What emerged was a marvel of compact engineering that judges described as “ingenious” and “like nothing we’ve seen before in competition history.”

The Tiny Workshop’s genius lies in its multifunctional design. Two ingenious flaps transform the unit’s usabilityone drops down to create a work floor, while another flips up to provide weather protection. Inside, Robinson has maximized every inch with carefully planned drawers, shelves, and hooks that keep tools organized and accessible. Even the brackets holding old spanner handles were custom-designed using a 3D printer, providing both function and visual clues to the contents within.

Perhaps most impressively, the structure features a living green roof that stores garden tools while creating habitat for wildlife. “The green roof will come into its own in the next year or so,” Robinson noted, “and I’m looking forward to seeing it come to lifethe shed really is the gift that keeps on giving.”The workshop serves as a triple-duty toolshed, creative workspace, and storage solution for everything from garden necessities to children’s games.

Robinson describes it as “a creative space where I can tinker and mend,” embodying the traditional shed’s role while pushing boundaries of what’s possible in minimal space. Head judge Andrew Wilcox praised the design’s return to practical roots: “Traditionally, sheds have just been seen as somewhere to store your tools, or perhaps somewhere to potter and playThe Tiny Workshop takes this seemingly basic concept and turns it into something so inspired but also still very practical.” The �1,000 prize recognizes more than clever carpentry; it celebrates the democratization of good design. In an era of increasing housing density and shrinking gardens, Robinson’s creation offers hope that thoughtful planning can maximize utility without sacrificing aesthetics or function.

Posted 4 months ago by
S
Liked 4 months ago

Liked https://sugardave.cloud/posts/astro/using-live-collection-for-webmentions

Astro's Live Content Collection - Display Webmentions ๐Ÿš€

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.

Content Collections

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 โ€œBuild your first Astro Blogโ€ tutorial should be familiar with how that works.

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 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.

The 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.

Live Content Collections

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.

For 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.

Implementation

I 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:

export interface Webmention {
  // Core webmention properties
  id: string;
  source: string; // URL of the page that mentions your content
  target: string; // URL of your content being mentioned
  targetPath: string; // Path of the target URL (for easier filtering)
  ...
}

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.

const getMentionsHTML = async (apiKey: string) => {
  const response = await fetch(
    `https://webmention.io/api/mentions.html?token=${apiKey}`
  );
  if (!response.ok) {
    throw new Error(`Failed to fetch webmentions: ${response.statusText}`);
  }
  return response.text();
};

The actual live content loader function needs to return an object with three required properties:

  • name: a string to represent the name of the loader
  • loadCollection: an async function that will return the entire collection of data or a filtered set
  • loadEntry: an async function that will return a single item in the collection based on a filter

Since 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.

export const webmentionLoader = ({
  apiKey
}: {
  apiKey: string;
}): LiveLoader<Webmention> => {
  return {
    name: 'webmention-loader',
    loadCollection: async ({filter}) => {
      const {targetPath} = filter || {targetPath: ''};
      try {
        const webmentions: Webmention[] = await getMentionsHTML(apiKey).then(
          (html) => {
            const dom = new JSDOM(html);
            const doc = dom.window.document;
            const mentions = Array.from(
              doc.querySelectorAll('.h-entry.mention')
            );
            const items = mentions.map((element, index) => {
              // construct a target path from the target URL
              const target = element
                .querySelector('.u-mention-of')
                ?.getAttribute('href') as string;
              const postPath = target ? new URL(target).pathname : '';
              return {
                id: `${postPath}:${index}`,
                data: {
                  author: {
                    name: element.querySelector('.p-author')
                      ?.textContent as string
                  }
                },
                source: element
                  .querySelector('.u-url')
                  ?.getAttribute('href') as string,
                target,
                targetPath: postPath
              };
            });
            if (targetPath) {
              // Filter by targetPath if provided
              return items.filter((item) => item.targetPath === targetPath);
            }
            return items;
          }
        );

        return {
          entries: webmentions.map((mention) => ({
            id: mention.id,
            data: mention
          }))
        };
      } catch (error) {
        return {
          error: new Error(
            `Failed to retrieve webmentions: ${error instanceof Error ? error.message : 'Unknown error'}`
          )
        };
      }
    },
    // there can be multiple webmentions for the same target, so we will never use loadEntry
    loadEntry: async () => {
      return {
        error: new Error(`webmentionLoader does not support loadEntry`)
      };
    }
  };
};

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.

---
import type {CollectionEntry} from 'astro:content';
import type {LiveDataEntry} from 'astro';
import type {Webmention} from '@lib/webmentionLoader';
import {getCollection, getLiveCollection, render} from 'astro:content';
import MarkdownPostLayout from '@layouts/MarkdownPostLayout.astro';

interface Props {
  post: CollectionEntry<'blog'>;
  postMentions: LiveDataEntry<Webmention>[];
}

const {
  params: {slug},
  url
} = Astro;
const currentPath = url.pathname.endsWith('/')
  ? url.pathname.slice(0, -1)
  : url.pathname;
const [post] = (await getCollection('blog', ({id}) => {
  return id === slug || id.startsWith(`${slug}/`);
})) as Props['post'][];
const frontmatter = post?.data;
const {entries: webmentions, error} = await getLiveCollection('webmentions', {
  targetPath: currentPath
});
const postMentions: Props['postMentions'] = [];

if (error) {
  console.error(`Error fetching webmentions:, ${error}`);
} else if (webmentions && webmentions.length > 0) {
  postMentions.push(
    ...(webmentions as LiveDataEntry<Webmention>[]).filter((mention) => {
      const {source, target} = mention.data;
      if (!target || !source) {
        return false;
      }
      try {
        const targetUrl = new URL(target);
        return targetUrl.pathname === currentPath;
      } catch {
        return target === currentPath;
      }
    })
  );
}
let Content;
if (post) {
  ({Content} = await render(post));
}

export const prerender = false;
---

<MarkdownPostLayout
  frontmatter={frontmatter ?? {}}
  mentions={postMentions.map((m) => m.data)}
>
  {Content && <Content />}
</MarkdownPostLayout>

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 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 ๐Ÿ˜

Wrapping Up

After 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.

Posted 6 months ago by โ— Also on: news.indieweb.org
Liked 6 months ago

Liked https://fosstodon.org/@ente/114562028846761783

Ente (@ente@fosstodon.org)

Posted Permalink
Liked 7 months ago

Liked https://briefs.video/videos/introducing-webbed-sites/

Introducing: Webbed Sites

Introducing Webbed Sites: Everything you need to build and publish your website, without having to write a single line of code.

Just paste your existing design into your Webbed Sites account. From there, your state of the art AI assistant, Martin, takes over.

Martin was trained on a selected corpus of uncensored Hentai, Adolf Hitlers Mein Kampf, and the source code of Microsoft Frontpage 98. He knows everything a late middle-aged Java programmer knows about coding websites.

In mere moments, Martin processes all your design assets, including your buttons, your links and your headings, and coverts them, magically, into <div>s.

But not just <div>s, of course! There are also <div>s next to <div>s, <div>s inside other <div>s, and <div>s outside <div>s that have <div>s inside them. Our builder also has the capability for <div>s with nonconforming href attributes and <div>s with superfluous and incompatible aria-labels.

Webbed Sites: The no code website building platform that really hopes you like&

<div>s.

Since launching Webbed Sites we've received a lot of feedback. Firstly, I just want to thank everyone for taking the time to reach out. Your comments are highly valued and appreciated. And secondly, I just want to say we are deeply sorry. The accessibility of Webbed Sites is not where it should be right now and that's on us, the Webbed Sites team.

We know weve let you down, and it hurts. But please believe us when we say that wed never deliberately rush out an inaccessible release, to save time and money, at the expense of disabled web users. We just didnt deliberately not rush out a release, to save time and money, at the expense of disabled users, either. Disableds are a priority to us, and always have been. In fact, many of my wives are disabled. Some since before I even met them.

I don't really know what else to say. It's not an excuse, of course, but please understand that we were under a lot of pressure and had very little time. Plus, of course, we had an enormous surfeit of&

<div>s.

Posted Permalink
Liked 8 months ago

Liked https://tilde.zone/@xandra/114547017410429628

alexandra (@xandra@tilde.zone)

Posted Permalink
Liked 8 months ago

Liked https://dbushell.com/2025/05/07/glossary-web-component/

Glossary Web Component

Wednesday 7 May 2025

Ive added a secret glossary to my blog! You might find it by hovering over special links. Dont tell anyone, its a secret. At least until I can find a way to style the links without them being a distraction. Do I need to, or can they remain easter eggs?

The Idea

This project came about when I noted concern over my reliance on MDN. I always favour MDN over other sources. Im lazy. I feel guilty for not linking to the small web, the indie web, the weird web. At first I banned myself from linking to MDN. Later I mulled over the glossary idea. I think Ive solved it!

The Implementation

I write my blog posts in Markdown. For new glossary terms I now link to a placeholder rather than an external source. For example:

[React](/glossary/react.json)

This references a JSON file that has the following format:

{
  "title": "React",
  "description": "A legacy JavaScript framework (turned religion). Favoured by tech bros and famous for bloated bundles and crippling web performance.",
  "links": [
    {
      "name": "JSX.lol",
      "title": "Does anybody actually like React?",
      "url": "https://jsx.lol"
    },
    {
      "name": "React",
      "url": "https://react.dev"
    }
  ]
}

My build script replaces the markdown link with a web component:

<glossary-term id="--term-react">
  <a href="https://jsx.lol">React</a>
</glossary-term>

The first link in the JSON is used as the canonical source.

HTML wrapped in a custom element is a perfect example of progressive enhancement. For unsupported browsers there is still an accessible link inside. For browsers that support the Popover API each <glossary-term> element is enhanced with a fancy popover.

If youve missed every example so far here is the React link.

I captured a screen recording of how it should appear:

Download video.

The popover is activated by either hover or keyboard focus. The escape key can also dismiss them. For touchscreens Im going to test that live&

[INSERT TEST RESULTS]

Test results: it works fine. If youve got one of those new Apple Pencils  the new new one, not the new old one  the hover effect is magical. Touch taps are taken straight to the canonical link. The popover might be open upon return. Maybe I should cancel the popover based on touch events to avoid confusion?

Failures

Initially I tried to use CSS anchor positioning.

I really tried. It left me in tears and I rage quit. It might be the most unintuitive, doesnt work like it says, infuriating web standard ever. When anchoring a popover its impossible to ensure elements stay inside the viewport (without JavaScript). Please prove me wrong!

I tried to get creative with view transitions and failed. Safari had some pixel-shifting jank going on. I tried normal transitions with allow-discrete and @starting-style and failed. In an isolated demo its all gravy but together this new CSS stuff doesnt plays nice. Its not all baseline yet so lets hope it improves. >

CSS anchors are Chrome and Chrome derivatives only right now. Safari Technical Preview claims to have support but Apple lies under oath so who knows?

Ultimately I gave up and used JavaScript to calculate position so I can support all browsers. Ill recharge morale and tackle a v2.0 at a later date.

I dont plan to immediately retrofit older blog posts with glossary links. Although that could be a quick find & replace if Im careful. Im looking for positive feedback before I do. So let me know if you like it (or not). If no one hates it Ill plough ahead, because I like it. This glossary may just be the third incarnation of my bookmarks blog.

Im tempted to use a similar technique to create popover cards for linked notes. They have more content so I need to consider that more.

Posted 9 months ago
Liked 9 months ago

Liked https://hachyderm.io/@charliewilco/114435540754888601

Charlie ๏ฟฝ (@charliewilco@hachyderm.io)

Posted Permalink
Liked 9 months ago

Liked https://mastodon.social/@davatron5000/114379441761385823

Dave Rupert (@davatron5000@mastodon.social)

Posted Permalink
Liked 9 months ago
Older
EmailInstagramGithub

Grant Richmond
grant.codesmail@grant.codes