grant.codes

Jump to menu
  • About
  • Contact
  • Projects
  • Updates
  • More...Likes
  • Photos
  • Galleries
  • Replies
👈🕸💍👉

Liked https://www.keithcirkel.co.uk/css-or-bs/?r=KBTv3rA

CSS or BS?

Play solo

We show you a CSS property name. You tell us if it's real or if we made it up. That's it. It starts easy. It does not stay easy.

The CSS spec has over 600 properties. Some of them sound made up. Some of our fakes sound terrifyingly real. Good luck.

20 rounds. No take-backs. No dignity.

← CSS   → BS

Posted Permalink
Liked 3 days ago

Liked https://tools.rmv.fyi/

delphitools

delphitools logo

A collection of small, low stakes and low effort tools.

No logins, no registration, no data collection. I can't believe I have to say that. Long live the handmade web.

If you find these tools useful, I'm glad. You don't owe me anything. But if you're an artist, feel free to email me your work. I'd love to see it.

If you would like to donate to delphitools, I ask that you don't. Make a donation to Wikipedia (opens in new tab) or the EFF (opens in new tab) instead. Email me your proof of donation and I'll put you in the credits.

delphitools app icon

The same privacy-first tools you rely on, built natively for iOS.

The same privacy-first tools you rely on, built natively for iOS. No accounts, no tracking, no compromises.

delphi carrying a stack of tool boxes

Delphi's Greatest Hits

Posted Permalink
Liked 3 days ago

Liked https://tylergaw.com/blog/the-old-internet-is-still-here/

Liked 16 days ago

Liked https://sweetfont.com

Sweetfont  The sweetest way to find Google Fonts

Playful Formal Warm Futuristic

Flavor

Elegant Rugged Loud Quiet

Vibe

Posted Permalink
Liked 3 months ago

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

- YouTube

Posted Permalink
Liked 3 months ago

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

Eric Newport (@kethinov@mastodon.social)

Posted Permalink
Liked 6 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 7 months ago by
S
Liked 7 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 9 months ago by ● Also on: news.indieweb.org
Liked 9 months ago

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

Ente (@ente@fosstodon.org)

Posted Permalink
Liked 10 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 a year ago
Older
EmailInstagramGithub

Grant Richmond
grant.codesmail@grant.codes