Hello 2023

Oh cool, it’s 2023 and running your own site is suddenly cool again, and not at all weird. Well that’s neat.

I realize I haven’t written anything in like ten months, but in my defense, I had an exceptionally shitty year. On top of enduring a multitude of tragedies, I’ve been absolutely crushed by work for the last seven or eight months. I’m burnt out.

I actually have a long list of things I want to write about, but I just haven’t had the time. I have material partially written, and photos ready to go and everything. I hope I can get through some of that stuff soon because it’s a new year, and I’m making a resolution to work less after hours.

Rather than recap a bottom-tier year, I’ll quickly recap what I spent the last day or so working on for this site, getting it in shape to see a lot more action.

Previously on…

Just about a year ago, I wrapped up some work I had been doing on the site. I did a lot of cleanup, but left some things broken. My responsive image setup was actually a mess, resulting in blurry, mis-sized images everywhere. Well, that’s a thing of the past. I’m now really happy with how I’ve got this set up. This post from Aleksandr Hovhannisyan was a big help, but since I’m using Nunjucks macros, I can’t do it asynchronously. I adapted Aleksandr’s shortcode to work synchronously, and I’m srcset and sizes attributes, rather than the picture element. I also set it up so that I can pass in different image types and generate a completely different set of images with the correct attributes.

Here’s a very slightly simplified shortcode, so if you feel like grabbing this, be sure to read Aleksandr’s very clear explanation of the whole thing, as well as to get his useful stringifyAttributes function.

const imageShortcode = (
  src,
  alt,
  imageType,
  loading = "lazy",
  className = undefined,
  widths = [400, 800, 1280],
  formats = ["webp"],
  sizes = ["100vw"],

) => {
  
  // Account for how 11ty handles the input path
  src = ("src/" + src).replace("src/src", "src")

  const options = {
    widths: [...widths, null],
    formats: [...formats],
    sizes: [...sizes, null],
    useCache: true,
    outputDir: "./dist/images",
    urlPath: "/images",
  };

  switch (imageType) {
    
  case 'hero':
    options.widths = [450, 800, 1200, 2000, 2400]
    options.sizes = ['100vw']
    break;

  case 'lead':
    options.widths = [450, 800, 1200, 2000],
    options.sizes = [      
      '(min-width: 2400px) calc((100vw - (var(--grid-gap) * 4) - 850) / 2 / 3)', 
      '(min-width: 1440px) calc((100vw - (var(--grid-gap) * 3)) / 2 / 4)',
      '(min-width: 900px) calc((100vw - (var(--grid-gap) * 3)) / 2 / 3)',
      '(min-width: 300px) calc(100vw / 3)', 
      '50vw'
    ]
    break;

  case 'avatar-large':
    options.widths = [150, 300, 450]
    options.sizes = [
      '50vw',
      '(min-width: 80rem) 425px'
    ]
    break;

  case 'featured':
    options.widths = [600, 900, 1400]
    options.sizes = [
      '100vw',
      '(min-width: 900px) 50vw',
      '(min-width: 90rem) 700px'
    ]
    break;
  
  case 'gallery-mini':
    options.widths = [200, 500]
    options.sizes = [
      '50vw',
      '(min-width: 700px) 34vw',
      '(min-width: 900px) 20vw',
      '(min-width: 90rem) 250px'
    ]
    break;

  default:
    options.widths = [400, 800, 1280],
    options.sizes = [
      '100vw',
      '(min-width: 800px) 90vw'
    ]
  }

  Image(src, options);

  const metadata = Image.statsSync(src, options);
  const srcset = Object.values(metadata).map((images) => {

    console.log(`[11ty image] Generating ${images.length} images using ${images[0].filename.split("-")[0]} from ${src}`);
    return images.map((image) => image.srcset).join(", ");
  });

  const getLargestImage = (format) => {
    const images = metadata[format];
    return images[images.length - 1];
  }

  const largestUnoptimizedImg = getLargestImage(formats[0]);

  const imgAttributes = stringifyAttributes({
    src: largestUnoptimizedImg.url,
    width: largestUnoptimizedImg.width,
    height: largestUnoptimizedImg.height,
    alt,
    loading: loading,
    decoding: "async",
    sizes: options.sizes,
    srcset,
    class: className,
    style: `view-transition-name: "image-${src}";`
  });

  const imgHtmlString = `<img ${imgAttributes}>`;
  return outdent`${imgHtmlString}`;
};

This took a big chunk of time to get right, but I’m really happy with it.

OK, what else?

  • I spent some time cleaning up a few templates
  • I fixed Webmentions, which I broke at some point
  • I guess that’s it?

What’s next?

A year ago, I mentioned I wanted to deal with the CMS stuff, and was considering going back to NetlifyCMS. Well, NetlifyCMS is basically confirmed to be dead. There is a promising community fork with Static CMS which recently had its first release. I’m excited to check it out soon.

Last year, I also mentioned using Slinkity for something I wanted to build. Slinkity also seems pretty dead. Luckily, is-land seems like it will fill the same need cleanly.

I recently realized I have no good way to show off any actual work I’ve done, so I’d also like to start working on adding a work portfolio.

Finally, I’d like to add a color palette chooser. I’ve soured on automatic dark modes. My view is that most sites should have a dark mode, but that it should be optional. When you think about it, it makes sense. The prefers-color-scheme media query is based on your operating system’s setting, which is for controlling the interface of your device, not your content. It’s awfully presumptuous to think that someone wants all of their content to be dark, just because that’s how they like their device to look.