Cool Site Spotlight

Did you know my Links page has an ever-changing Cool Site Spotlight? I’ve mentioned it tangentially before, but I want to mention both it and my Links page specifically now because I think both are pretty cool.

I started my Links page in January of 2024 with the intention of making it a blogroll, but it’s much more than that now. For one thing, right at the top is a Cool Site Spotlight highlighting one site I’ve stumbled across that I think is unusually neat. I rotate entries out into the Cool Sites Archive section of the Links page as I highlight a new one.

Not only does this keep me interested and looking around me, it should serve as a good idea and interest generator for my readers too.

If the web is ever going to be cool again, this is how it starts: finding and linking to things that make it a better place.

Really Specifically

Here’s a pitch for you: how’d you like to listen to a podcast that is both fascinating from the perspective of being about tech nerdery and podcasts in general AND that is also part of a PhD project? There can only be one that fits this definition, and it’s Martin Feld’s Really Specific Stories podcast.

If you haven’t listened, you should. Don’t start with the latest episode though (featuring yours truly), start from the beginning or pick any other episode at random and get a feel for what this project is all about.

I really enjoy the intentional way that Martin delves into the topic of what podcasts mean to people and how they interact with and/or produce them. He digs deeply into people’s relationships with podcasting in a way that I haven’t heard anywhere else.

I’m sure Adam Curry has uttered more words on the concept of podcasting, but I’d be hard pressed to imagine any of it is as illuminating or focused on understanding what podcasting is all about as Really Specific Stories is.

ScreenFloat Your Screenshots

One of the brilliant features of macOS is its built-in screenshot tools. Not all operating systems are as aware of the fact that people need to take screenshots and edit and annotate them quickly as macOS appears to be. Even so, many excellent third-party utilities exist to take this to eleven.

The App – ScreenFloat

If I start talking about the options and making comparisons, I’m going to get into a holy war and I don’t want that.1 Therefore, I’m treating my coverage of macOS screenshot utilities like it’s a Highlander. THERE CAN BE ONLY ONE! And that one’s name is ScreenFloat.

ScreenFloat Windows

ScreenFloat has a lot of ideas, and those ideas are translated into features. It can be a bit overwhelming, but in reality it’s fairly easy to wrap your head around the features you’ll use most, and then you can dig into customizing the app and really getting some work done. That second point is important because when I first started using ScreenFloat, I was missing some of my workflow from Shottr. But the good news is I was able to get back quick annotation and copy to clipboard functionality with ScreenFloat just by making a couple of tweaks.

The Premise

ScreenFloat’s main premise is that you may want to take a screenshot and then float it above other windows so you can use it as a reference or so that you can drag it easily into other apps. The good news is that this can be very helpful. The bad news is that sometimes floating windows get in the way. The good news is that you can close a floating window with a keyboard shortcut or by clicking on its window close control, and ScreenFloat will keep it in the Shots Browser to be recalled at any time.

ScreenFloat Shots Browser

If you use the keyboard shortcut to close (actually hide) the floating screenshot, you can open it again (unhide, technically) with the same keyboard shortcut. Once you close by clicking the window close control, however, you’ll have to open the Shots Browser window and double-click it to get it back into Floating Shot mode.

The Features

Floating Shots can have actions performed on them – annotating, exporting, cropping, sharing, dragging, and dropping, and more.

Dragging and Dropping

Dragging and dropping and super quick file saving can be performed by the Floating Shot’s File Drag Control2.

ScreenFloat Drag and Drop Control

This lets you quickly drag the screenshot into Messages , an email, or even into Finder to quick save the image to disk.

Note that you can still export the file with options by clicking on the Floating Shot’s Action Menu3, clicking Export… and making some simple choices.

ScreenFloat Action Menu

Speaking of that Action Menu…

Action Menu

The Action Menu has a lot of the things you’d expect – Copy, Open Copy With, Export, Crop, Annotate, and more.

ScreenFloat Action Menu Options

One of the “more” things is Data Detection. ScreenFloat lets you detect text and copy it or redact it as desired. It’s really pretty magical.

Another thing I use all the time in the Action Menu is Annotate. The annotation options are comprehensive and are customizable before and after creating an annotation. For example, you can double click the rectangle outline tool to customize it or you can just click it once, draw a rectangle, and then click the Select tool to edit the rectangle you’ve just drawn. This same methodology holds true for all the annotation tools: Draw, Rectangle, Oval, Line, Arrow, Star, Checkmark, X-mark, Text, Numbered List, Callout, and Redact.

ScreenFloat Rectangle Options

ScreenFloat Rectangle Editing

Numbered List isn’t exactly what it sounds like. It lets you drop numbers on an image in order to call out steps or set the order of items of interest. If you’re making a screenshot to show someone the order to click things on a UI, this is what you want.

ScreenFloat Numbered List Options

Redact lets you redact with a solid color, complete with the ability to set the color from swatches or a color picker, or the common pixelation redaction, or with a strange gradient looking blur effect that I don’t think I’ll ever use. This is in addition to the Data Detection and its ability to redact, although the redactions are the same style and can also be edited in the same way as the manual redactions by selecting them with the Select tool.

ScreenFloat Redaction Options

Double-Click Workflows

The eagle-eyed amongst you may have noticed that Annotate is stuck in the Action Menu and therefore it takes extra clicking after grabbing a screenshot to be able to annotate it. Other apps, like Shottr, drop you into a view that shows you annotation tools immediately after capturing the screenshot. The good news is, ScreenFloat lets you create double-click workflows that get you very close to that simplicity.

In ScreenFloat’s settings, on the Floating Shots tab, you can set actions that occur when double-clicking with or without modifier keys. In my case, if I double-click a floating shot without modifiers, it copies the image to the clipboard. If I double-click it while holding command, it immediately jumps into Annotate mode without me having to use the Action Menu to select it.

ScreenFloat Double-Click Copy Workflow

ScreenFloat Double-Click Annotate Workflow

I also have modifiers to double-click and resize the floating window up or down by 50%.

It’s a very clever and endlessly customizable way to create shortcuts to editing actions that you’ll use all the time.

Shots Browser

Floating screen shots are one thing, but what about if you want to recall one, or annotate it or export or otherwise save it later? ScreenFloat has you covered. Every snapshot you take goes into the Shots Browser, which can be recalled at any time with a keyboard shortcut, such as ⌘-⇧-1.

ScreenFloat Shots Browser

With Shots Browser, you can double-click an image to make it a Floating Shot again, so you can annotate or do whatever you want with it. But you can also drag images straight from the Shots Browser to other apps, which makes it handy for taking a bunch of screenshots for, say, a blog post, closing the resulting Floating Shot, and then getting them from the Shots Browser later when you’re ready to use them in your writing.

The Shots Browser feature is really nice if you’re using screenshots to document a procedure and you want to get all your shots in one place to edit and choose from later.

You can also go really crazy and create folders, smart folders, tags, descriptions, and ratings. Like I said, ScreenFloat does not skimp on features.

Export Options

Of course besides the usual drag and drop and open copy in options, you can export a Floating Shot using the Actions menu and Choosing Export. You can choose options related to naming, format, whether or not to include annotations, size, alpha, and whether or not to reduce dpi resolution.

ScreenFloat Export Options

The Conclusion

Honestly, what pushed me back to using ScreenFloat wholeheartedly was discovering the customization options for all the annotations and the double-click workflows. Being able to quickly copy something to the clipboard or jump right into annotating it are things I can’t live without.

ScreenFloat is a very powerful, very customizable screenshot tool for the Mac that I think trounces most of the competition. The other one I’d recommend is Shottr, which takes a much simpler but still feature-rich approach. It’s another great application. For me though, ScreenFloat wins easily with its Shots Browser, Floating Shots, and easy customization with workflows and tool options.

Footnotes

  1. xNapper fans are especially zealous, in my experience. I understand – it’s a great app, but still. Breathe, people.

  2. I don’t know if that’s what Matthias calls it, but it’s what I call it until I find out otherwise.

  3. Again, I have no idea if this is the correct name for this UI element, but it sounds good.

Raycast Script Command for Text Parsing

Raycast

Part of the Raycast series

I previously wrote about using Raycast script commands to switch default browsers. Raycast script commands are really good for scripting all kinds of other things too. One example is a Python script command I created yesterday for the purpose of grabbing a couple values from a JSON file and formatting them into a markdown hyperlink and putting it on the clipboard.

On my site’s Links page, I have a Cool Site Spotlight section at the top which I use to showcase some cool site or other for a short, random period of time. The data for this Cool Site Spotlight is saved in a JSON file that looks like this:

src/data/spotlight.json
{
"Site": "https://ericportis.com/posts/2024/okay-color-spaces/",
"Title": "Okay, Color Spaces",
"Description": "What is a color space? Why should you care? Do you like interactive visualizations to help you learn things? Do you like cool sites regardless of topic? If the answer to at least one of these questions is \"YEAH, I LIKE THAT!!!!!?!!!\", then you need to spend some time here.",
"Image": "OkColorSpaces-036630FA-D437-4101-A5AB-7C7338DCD953"
}

My Astro Links page uses an Astro component to create the web view based on this data in spotlight.json.

src/components/Spotlight.astro
---
import { Icon } from "astro-icon/components";
import { Image } from "astro:assets";
import spotlight from "../data/spotlight.json";
---
<div class="spotlight">
<h1>Cool Site Spotlight</h1>
<div class="spotlight-details">
<div>
<a href={spotlight.Site}>
<Image
src={import(`../assets/images/posts/${spotlight.Image}.png`)}
width="300"
densities={[2, 3]}
alt={spotlight.Title}
/>
</a>
</div>
<div>
<h4><a href={spotlight.Site}>{spotlight.Title}</a></h4>
<p>{spotlight.Description}</p>
<p>
<a href={spotlight.Site}><Icon name="fluent:globe-star-20-regular" /></a
>
<a href={spotlight.Site}>{spotlight.Site}</a>
</p>
</div>
</div>
</div>

You can probably argue about why I used a JSON file for this section, but basically it’s because the view is complicated enough that letting Astro create it from Markdown wasn’t going to work and I did not want to mix data and UI in my Spotlight.astro component. Having an Astro component map JSON file data, on the other hand, is super easy and lets me keep the site info separate from the Cool Site Spotlight UI implementation.

Everything else in the Links page comes from a standard markdown content page located in src/content/links/links.md. The Cool Sites section of the Links page comes from this portion of links.md:

src/content/links/links.md
## Cool Sites
- [Lene Saile, developer and designer living in Madrid](https://www.lenesaile.com/)
- [omg.lol - A lovable web page and email address, just for you](https://home.omg.lol/)
- [PDX Food Weeks](https://pdx-food-weeks.vercel.app/)
- [samwho](https://samwho.dev/)
- [VKC.sh | Veronica Explains – Veronica explains even more](https://vkc.sh/)
- [The Yesterweb - Reclaiming the Internet](https://yesterweb.org/)

When I want to change the site that I’m spotlighting, I put the currently spotlighted site in this section of links.md and then put the new site data in the JSON file. Up until now, this has meant manually copying the Title value and the Site value out of spotlight.json and manually writing out the markdown link in the Cool Sites section of links.md.

Yesterday I finally realized I was tired of copying twice and pasting twice and typing all the markdown formatting characters just to move a link to a website from one part of my Links page to another. My solution was to create a Raycast script command that does it for me.

I created my script command the standard way by using the Raycast “Create Script Command” command and filling in some information. I chose the Python template, set it to Full Output just so I can see what the transformation looks like, gave it the title Spotlight Site to Cool Site, which is what this command will show up in Raycast as, and gave it a description and a package name.

I don’t actually know what the package name does for me, but I like to set similar items to the same Package Name just to be sure. All script commands that are for working on the website go in the Website package name.

Creating the Spotlight Site to Cool Site script command

This creates a relatively empty command script in the directory of my choosing (I put them all in a folder called ~/Scripts/Raycast/script commands) containing one line of Python. I tend to edit my script commands in BBEdit (as opposed to VSCode, which I use for website development and some other types of programming), so I fired it up and made the following very small, very simple Python script.

~/Scripts/Raycast/script commands/spotlight-site-to-cool-site.py
#!/usr/bin/env python3
# Required parameters:
# @raycast.schemaVersion 1
# @raycast.title Spotlight site to Cool Site
# @raycast.mode fullOutput
# Optional parameters:
# @raycast.packageName Website
# Documentation:
# @raycast.description Coverts Spotlight.json to markdown link
# @raycast.author scott_willsey
# @raycast.authorURL https://raycast.com/scott_willsey
import pyperclip
import json
# Open the Spotlight JSON file
with open('/Users/scott/Sites/scottwillsey/src/data/spotlight.json', 'r') as f:
data = json.load(f)
# Get the 'Site' and 'Title' values
site = data.get('Site')
title = data.get('Title')
# Create a markdown link
markdown_link = f'- [{title}]({site})'
pyperclip.copy(markdown_link)

A full half of it is headers for things Raycast needs to know. The other half is the Python script. It’s very simple.

First it opens /Users/scott/Sites/scottwillsey/src/data/spotlight.json for reading and loads the data as JSON. It then grabs the values for the “Site” and “Title” keys. It then creates a variable called markdown_link formatted as - [{title}]({site}) with those values inserted correctly to create a markdown hyperlink, and copies that to the clipboard using pyperclip.copy.

Regarding pyperclip: “Pyperclip is a cross-platform Python module for copy and paste clipboard functions. It works with Python 2 and 3.”.

You can install pyperclip using pip, which brings up a whole thing about Python and how you install it and how that affects importing things using pip, but I won’t go there today. But suffice it to say that the Python world is a mess when it comes to ease of use and maintainability. A whole lot of someones should be super ashamed of themselves for letting this situation occur and then fester.

Suffice it to say that you need to install pyperclip on your Mac for this to work. Good luck.

To run the command script, I hit the Raycast hotkey (⌘-, in my case), and start typing “Spotlight”. When I see what I want, I hit Return if it’s the first in the list, or ⌘ and the number of its place in the list if not. Bam – it executes and I have my link in my clipboard.

Running the Spotlight Site to Cool Site script command

This is an extremely simple example of a Raycast script command, but it illustrates that you can very easily perform all kinds of scripted actions without having to write a full Raycast extension or without having to open the Finder or terminal, go to a script directory, and running a script manually. Running a command script through Raycast will always be quicker.

Using Git Hooks for Displaying Last Modified Dates

Part of the Astro series

Not so very long ago, I wrote about using remark and a script called remark-modified-time.mjs to update a page’s front matter Date value for Auto-Generated Last Modified Dates in Astro. This approach worked pretty well until I moved the content for my /Uses page out of a markdown file and into a json file. I didn’t want to have to keep modifying essentially an associated empty markdown file to get the last modified date to change.

What I wanted was a way to see if the json file itself had changed, and then enter THAT date into the /Uses markdown file front matter. In essence, change one file, and another one gets its updated timestamp. This would give me a self-updating last modified date value for everything and I wouldn’t have to remember to manually change anything to make it happen.

Enter Git Hooks.

Git hooks let you run a script when some git action occurs. One git related action that occurs all the time is the git commit. Git commit turns out to be a very good time to look at when files were last modified, since git’s entire job is paying attention to when and how files are modified. The git hook to use if you want it to happen when you perform a git commit is called pre-commit.

Although it’s called pre-commit, it happens during a commit but before you enter the commit message. It can be used to verify commits before allowing them to happen, but instead I use it to modify files and commit those new changes along with the existing ones.

Implementing a pre-commit git hook is simple. Go into your project’s .git/hooks directory and create a file called pre-commit, with no file extension. There should already be a file in there called pre-commit.sample – you can either rename it without the extension and edit it, or just create a new one with the correct name.

Here are the contents of my .git/hooks/pre-commit file:

#!/bin/sh
git diff --cached --name-status |
grep -iE '^M.*src/(content/links/links\.md|content/now/now\.md|content/pins/pins\.md|data/uses\.json|data/spotlight\.json)$' |
while read _ file; do
if [[ "$file" == "src/data/uses.json" ]]; then
file="src/content/uses/uses.md"
elif [[ "$file" == "src/data/spotlight.json" ]]; then
file="src/content/links/links.md"
fi
filecontent=$(cat "$file")
frontmatter=$(echo "$filecontent" | awk -v RS='---' 'NR==2{print}')
cat $file | sed "/---.*/,/---.*/s/^date:.*$/date: \"$(TZ='America/Los_Angeles' date "+%Y-%m-%dT%H:%M:%S-08:00")\"/" > tmp
mv tmp $file
git add $file
done

Two points if you noticed that it’s just a bash script and that it clearly is running a git command to see what files have changed since the last commit!

The output of that command is piped to grep to look to see if any of the modified files are src/content/links/links.md, src/content/now/now.md, src/content/pins/pins.md, src/data/uses.json, or src/data/spotlight.json.1

Once it finds any modified files that match my list, it uses awk and sed to find the front matter and change the date value to git’s modified timestamp. I leave learning all about awk and sed as an exercise for the reader – see you in about 10 years.

For the most part, the files that are modified are the files that get their front matter date value updated, except in the case of the two json files. If src/data/spotlight.json is modified, src/content/links/links.md gets its timestamp instead. And if src/data/uses.json is changed, src/content/uses/uses.md gets that timestamp in its front matter date field.

Another way to look at it is that if any of the markdown files I’m looking for are updated, they get their timestamp set accordingly. In addition, links.md also gets an updated timestamp if src/data/spotlight.json is modified. This is because both links.md and spotlight.json contain data that shows up on Links.

But in the case of my /Uses page, I never look to see if src/content/uses/uses.md gets updated. That’s because I’m really only using it for its front matter at this point. All of the data displayed on the page itself comes from src/data/uses.json. So I only look to see if uses.json was modified, and if so, I update the timestamp in uses.md. Then I can use that value to display on the compiled page as the last modified date and time.

That’s it. It really is that simple to implement git hooks. They’re sitting inside .git/hooks in your repo just waiting to be used for exciting things like telling people when you added your mouse to the list of computer hardware that you gaze at lovingly on a daily basis.

Footnotes

  1. Spotlight.json is used for the Cool Site Spotlight on my Links page.

Astro Templates for JSON Data

Part of the Astro series

Web frameworks like Astro often allow the use of Markdown for blog post “content”, that is, the actual blog post, and then the page design and programming is in a page template which takes the Markdown and renders its contents in the appropriate place for a fully rendered HTML page. This is convenient and allows excellent separation of written words (“content”) and site rendering mechanisms.

Sometimes, though, what you want to put on the page is more complicated than you can easily do in Markdown. Such is the case with my /uses page on this website.

Contents

Background

When I first created my /uses page, I thought I was going to do the same thing I do for my /links and /now pages… namely, use Markdown for the “data” (please don’t make me say content again) which would go into the Astro <Content /> component to render the Markdown into HTML.

But…

As Jason from Hemispheric Views stated rather enthusiastically during a recent episode, Markdown isn’t always the right tool for the job. And it’s NOT the right tool for the /uses job, because I want a format and flexibility for this page that Markdown makes difficult, if not impossible.

The result is that I created the /now page as an Astro page template with all the “data” (no, I will NOT) thrown in amongst the html tags, just like 1990 calling for its beautiful, manually handwritten pages back. But that’s gross. It’s gross because it’s harder to work with from an adding and editing perspective, and it just seems dumb.

The solution is very simple, and it’s what I do on Friends with Beer for the drinks. I use a JSON data file and render it in different Astro page templates.

The Starting Point

My original uses.astro file looks like this:

uses.astro
<Base title={title} description={description}>
<article>
<h1>{title}</h1>
<div class="use-header">
<a href={bigmac}
><Image src={mac} width="300" densities={[2, 3]} alt="My Computer" /></a
>
<p class="uses">{description}</p>
</div>
<div class="time">
<Icon name="bi:calendar2-week-fill" />
<time datetime={uses.data.date}>
<a href={`/${uses.slug}`}
>Last updated {modifieddate(remarkPluginFrontmatter.lastModified)}</a
>
</time>
</div>
<div class="uses">
<h2>Computer</h2>
<div class="item">
<div class="the-thing">
<a href={bigmbp}
><Image
src={mbp}
width="300"
densities={[2, 3]}
alt="2021 M1 Pro Macbook Pro"
/></a
>
<div>
<h3>2021 14" MacBook Pro M1 Pro</h3>
<ul>
<li>M1 Pro 10-core CPU, 16-core GPU</li>
<li>16GB RAM</li>
<li>1TB SSD</li>
</ul>
</div>
</div>
<p class="uses">
It might not be THE most amazing computer, spec-wise, but Apple
Silicon Macs are game changers. It's quiet, it's cool, it's fast, and
it's instant-on. I've traditionally hated laptops because of their
massive compromises, and this removes them all.
</p>
</div>
... a bunch more items ...
</div>
</article>
</Base>

The text that is the whole point of the page, namely the items that I use that I wish to present, are just more stuff in a sea of rendering implementation.

You can see for yourself how un-fun it would be to add new items to or edit existing ones. It requires carefully trawling through everything and make sure I’m not messing up nested divs and all kinds of junk. Even with VSCode’s code folding and syntax highlighting, it’s still ugly and nightmarish.

Let’s fix this mess by breaking out the data from the drawing, so to speak.

The Modifications

A JSON Is Born

Step 1 is creating a data file for the stuff I use. I’m calling it uses.json, and I’m putting it in my data folder at src/data/uses.json.

Step 2 is figuring out the data structure. I break my stuff up into categories or sections, like “Computer” and “Podcast Recording”.1

You can see that in each category, there are multiple items. Each item consists of:

  • The name of the item,
  • Some specs,
  • An optional link to a website for the item,
  • My thoughts about it, aka a description,
  • An image of the item.

Based on that, here’s how I’ll structure my JSON data file.

uses.json
[
{
"Category": "Computer",
"Items": [
{
"Name": "Keychron Q1 Keyboard",
"Specs": [
"84 key (75%)",
"BT and wired",
"QMK for greater customization",
"Gateron Pro Brown switches",
"6063 aluminum CNC machined body"
],
"Link": "https://www.keychron.com/products/keychron-q1",
"Description": "Shhh... no one tell my K2 (see below), but the Keychron Q1 is in a whole other leaque. Wow, built like a TANK, as Jason from Hemispheric Views says. I love the feel of the switches, the sound, the weight, the look, the feel, the everything. I'm not sure I'll ever use another keyboard again. Also... come on. Look at those shades of blue. It is available in other colors, but why??",
"Image": "Keychron-Q1-encoder-D633B1F6-73A7-472F-90C9-680982767284"
}
]
}
]

This lets me have multiple categories, each with multiple items, each with the item data listed above.

Then I just have to modify my uses.astro page template to read it in and map it out to the template I’m using for the “data” section.

An Astro Page Template Gets Smarter

I lied just a little bit. I am going to modify uses.astro to parse the JSON data from uses.json, but I’m also going to create an Astro component called UsesItem.astro that will be used to render the individual items. So uses.astro is going to load the JSON file, map through the categories, and every time it hits a list of items, it’s going to hand the rendering for them over to the UsesItem.astro component.

This will break it things up, make re-use easier if I ever want to render any of these items elsewhere, and generally make me happier. And I like being happier.

Here’s what the /uses page template, uses.astro, looks like now with this approach:

uses.astro
---
import { getEntry } from "astro:content";
import { Icon } from "astro-icon/components";
import { Image } from "astro:assets";
import UsesItem from "../components/UsesItem.astro";
import Base from "../layouts/Base.astro";
import { modifieddate } from "../components/utilities/DateFormat.js";
import usesdata from "../data/uses.json";
import mac from "../assets/images/posts/macsetup-7780B721-09D1-44CC-82B1-E083D8F4A7C9.png";
const bigmac =
"/images/posts/macsetup-7780B721-09D1-44CC-82B1-E083D8F4A7C9.png";
const uses = await getEntry("uses", "uses");
let title = uses.data.title;
let description = uses.data.description;
---
<Base title={title} description={description}>
<article>
<h1>{title}</h1>
<div class="use-header">
<a href={bigmac}
><Image src={mac} width="300" densities={[2, 3]} alt="My Computer" /></a
>
<p class="uses">{description}</p>
</div>
<div class="time">
<Icon name="bi:calendar2-week-fill" />
<time datetime={uses.data.date}>
<a href={`/${uses.slug}`}>Last updated {modifieddate(uses.data.date)}</a
>
</time>
</div>
{
usesdata.map((use) => {
return (
<div class="uses">
<h2>{use.Category}</h2>
{use.Items.map((item) => (
<UsesItem item={item} />
))}
</div>
);
})
}
</article>
</Base>
<style>
div.use-header {
display: flex;
flex-direction: row;
column-gap: 3rem;
justify-content: center;
align-items: center;
margin: 0 auto;
padding: 0.5rem 2rem;
border-radius: 10px;
font-weight: 400;
font-size: 1.2em;
font-style: italic;
}
div.use-header {
background-color: var(--surface-menu);
}
div.use-header img {
min-width: 225px;
}
div.uses {
background-color: var(--surface-menu);
padding: 0.1em 2rem;
margin: 2.25em 0;
border-radius: 10px;
}
div.item {
margin-bottom: 5rem;
}
@media only screen and (max-width: 699px) {
div.use-header {
flex-direction: column;
}
div.uses {
padding: 0.1em 0.5rem;
}
}
div.time,
div.time a {
font-size: 0.8em;
}
div.time {
margin-top: 2.5rem;
}
[data-icon="bi:calendar2-week-fill"] {
font-size: 0.75rem;
margin-bottom: -0.05rem;
}
</style>

All the information, or data, or content, about the items is GONE from the html template. It’s all in the JSON file, and uses.astro is just a set of display instructions.

Now the only part that’s hard-coded into the page template is the page introduction area indicated by the rectangle marker below, which is right at the beginning of the <article> section.

Uses page header section

Everything following it comes from the JSON file and is mapped out in this section:

{
usesdata.map((use) => {
return (
<div class="uses">
<h2>{use.Category}</h2>
{use.Items.map((item) => (
<UsesItem item={item} />
))}
</div>
);
})
}

Very little of the work of rendering the items is done in uses.astro. Instead, it relies on the UsesItem component which it imports from UsesItem.astro. The current item in the map loop is passed as a prop to the component, which then renders the item detail view. Here’s my UsesItem.astro component in its entirety, and half of it is CSS:

UsesItem.astro
---
import { Icon } from "astro-icon/components";
import { Image } from "astro:assets";
const { item } = Astro.props;
---
<div class="item">
<div class="the-thing">
<a href={`/images/posts/${item.Image}.jpeg`}>
<Image
src={import(`../assets/images/posts/${item.Image}.jpeg`)}
width="300"
densities={[2, 3]}
alt={item.Name}
/>
</a>
<div>
<h3>{item.Name}</h3>
<ul>
{item.Specs.map((spec, index) => <li key={index}>{spec}</li>)}
</ul>
</div>
</div>
<p class="uses">
{item.Description}
</p>
{
item.Link && (
<p class="link">
<a href={item.Link}>
<Icon name="fluent:globe-star-20-regular" />
</a>
<a href={item.Link}>{item.Link}</a>
</p>
)
}
</div>
<style>
div.the-thing {
display: flex;
flex-direction: row;
column-gap: 3rem;
border-radius: 10px;
justify-content: flex-start;
align-items: flex-start;
font-size: 1em;
font-style: normal;
font-weight: 400;
column-gap: 2rem;
margin: 0 auto;
padding: 0;
}
p.uses {
font-style: italic;
}
p.link {
margin-top: 2em;
font-size: 0.8em;
}
p.link [data-icon] {
margin-bottom: -0.15em;
}
div.item {
margin-bottom: 5rem;
}
@media only screen and (max-width: 699px) {
div.the-thing {
flex-direction: column;
padding: 0 0.5rem;
}
}
</style>

This uses the astro:assets Image component to optimize the item’s image as specified in uses.json. It puts this next to the item name and a list of item features of specs. Under that is my description of what it is or why I like it, followed by an optional link to a website for the item.

You can see that item.Specs is an array and so I use item.Specs.map to create the list of specs. Also notice where the item URL link is rendered starts with item.Link && which means the following href rendering only happens if item.Link indeed exists.

The end result that is rendered is something like the following:

Keychron Q1 item view

Summarium

You can definitely use Markdown for site content when appropriate, such as for a standard blog post. For anything more complicated, though, you’ll want to use a data source, like a JSON file, and a page template to parse it and map it. This way your data, or “content”, is separate from the rendering mechanics, but can be laid out on the page more intricately than is possible with a straight Markdown to HTML rendering.

The good news is that Astro page templates and components are super flexible in terms of easily mixing HTML, CSS, and JavaScript, and they’re the right tool to use for a one-off page with a custom layout.

Footnotes

  1. That’s all I have right now because instead of adding things I use, I’m busy rewriting how the page is created.

Mac 40

I don’t know if you’ve heard, but the Mac turned 40. And what a glorious 40 it is – with Apple Silicon and the return to some amount of function over form in the laptops and (most of the) desktops, the Mac is better than ever.

Personally, I started early on the Mac. My dad bought us matching 128K Macs the first year they came out. My Mac was upgraded to Fat Mac and then Mac Plus, and it served me far longer than it had any right to. By the time I got a current at the time PC at a discount from my employer, my Mac was way past its useful lifespan.

People have been telling a lot of fun stories about the Mac for its 40th anniversary. I highly encourage you listen to Upgrade #496: 40th Anniversary of the Mac Draft even if you’re not an Upgrade listener. Yes, it’s a draft, yes, John Siracusa actually drafts the current Mac Pro as the worst Mac ever, and yes, it is glorious. There’s also this peak Siracusa moment

Here’s a list of some articles, podcast episodes, and videos I’ve enjoyed this week.

Apple Shares the Secret of Why the 40-Year-Old Mac Still Rules | WIRED

Greg Joswiak on the Mac’s enduring appeal – Six Colors

Happy 40th Birthday, Macintosh

Lost Apple History… FOUND on a Floppy Disk! 💾 Macintosh 40th Birthday Surprise (1984-2024) - YouTube

Mac at 40: The eras tour – Six Colors

The Mac that Steve Jobs unveiled in 1984 is 40, and as amazing as ever

The Mac turns 40 — and keeps on moving - The Verge

Wild Apples: The 12 weirdest and rarest Macs ever made | Ars Technica

Upgrade #496: 40th Anniversary of the Mac Draft - Relay FM

Upgrade 496: 40th Anniversary of the Mac Draft - YouTube