aaron's mask
+ + + + +
+
+
+ Commission piece; based on an existing pattern. The red detailing and teeth are my own design.
+ +diff --git a/_site/aarons-mask/index.html b/_site/aarons-mask/index.html new file mode 100644 index 0000000..12550ad --- /dev/null +++ b/_site/aarons-mask/index.html @@ -0,0 +1,1267 @@ + +
+ + + + +
+
+
+ Commission piece; based on an existing pattern. The red detailing and teeth are my own design.
+ +My name is Lee Cattarin. I use he or ze pronouns.
+ +I'm a programmer (are you looking for a resume?), artist&crafter - knitting, spinning yarn, stamp carving/printmaking, cardmaking & papercrafts, bookbinding, leatherworking - bread baker, guitar player, and probably a lot of other things.
+ +
+
+As of November 2023, my wife Brooke Osment and I have a art store: Riverside Refuge Studio. You can find various store links, or other ways to connect, on my contact page. We're based out of Vashon, WA, USA, and ship internationally.
+ +We have a dog...
+ +
+
+...and six ducks.
+ +
+
+
+
+
+ A green journal with a blue leather spine. The coloring pages are from the book Color Acadia with art by Katie Dube and Keri Kimura.
+
+
+
+ Yarn: The Fibre Co. Acadia
+ +
+
+
+ alrighty, this one's a real doozy. Strap in.
+have you ever tried to revert to a previous version of a document in MS Office or Google Docs and found that your revision history is cluttered with small changes that by all rights should be grouped into one set of edits, but they aren't, and it's tedious to pick through all the versions?
+or, more uniquely and/or uncommonly:
+or, websites. Maybe you're building something in Squarespace and find out that in current versions of Squarespace, it doesn't support version history..
+I started writing this to help a friend. She's getting started with a website, and we're using the static site generator 11ty as she wants to have a lot of easy-to-write posts. She needs a single-user workflow that allows her to publish her website without hosting it herself, and that's the use case where this was born.
+this walkthrough is best suited for people who want to use git in single-person projects, or perhaps with one or two other close collaborators. There's quite a few topics it doesn't cover that are vitally important in large collaborative projects, such as branching and merging.
this walkthrough also focuses on the "happy path," without much discussion of troubleshooting. I may write more on the topic in the future, but we're already over 4,500 words, so we're calling it a day.
+finally, I wrote this walkthrough primarily with knowledge from using WSL [more on this later] on Windows and with Zed as my text editor. While I've tried to cover my bases with other OSes and options, there's a solid chance I'm missing things!
+that all said, let's get (git?) into it!
+git is a version control system. We can use it to track changes we make to a set of files.
++tip: it's important to understand that despite the examples of MS Office and Google Docs above,
+gitisn't useful with word documents.gitshines with plain text files - .txt, .md, or basically any type of code.
many, many tools interact with git:
git operations, like VSCode, Zed, or Sublime Textgit GUIsgit users use the git command line interface (CLI), which is fully text-basedtoday we're going to talk about the git CLI... technically. But don't let that scare you - we'll talk about concepts and actions that can be applied to other git interfaces as well.
a CLI a way to interact with your computer and with software in text-only form. Rather than using the mouse and clicking on things, you type in commands and see output.
+if you want to use the git CLI, you'll need a terminal. You've got a couple options here:
git generally ships with these systems, so there's no installation required. Search for an application called 'Terminal' or similar
+git for Windows packages a Linux-like terminal with a git GUI. This may be slightly friendlier for people who aren't at all familiar with Linux. If choosing git for Windows, see my installation instructions belowon the "Releases" page, scroll down to "Assets" and pick the .exe file.
during installation, you'll be asked to choose some things by the installer. Here's my recommendations:
+nano, an in-terminal editoryou can open git for Windows via the start menu by searching for 'git bash.' If you chose to install Windows Terminal, one of the dropdown options will be for a new tab will be 'Git Bash.'
while installing WSL is a single command, here's a couple notes about getting started once inside WSL:
+Ctrl+C and Ctrl+V won't work like they do on Windows. If you want to copy/paste, right-click (there won't be a context menu, it'll just happen)you'll also need to be careful of a few things regarding text editors to make them work with WSL:
+you must start your editor of choice within WSL. Don't use the Windows start menu! Instead, you'll type code . or zed . (note the .) while in WSL.
you can open WSL via the start menu by searching for 'WSL.' If you chose to install Windows Terminal, one of the dropdown options for a new tab will be your WSL distribution, usually 'Ubuntu.'
+here's three vital terminal commands:
+pwd prints the working (current) directory. A lot of terminals will just show you what your current directory on every line, but if they don't, try pwd.++tip: directory is just another word for folder
+
cd lets us change directories. If we type only cd, we'll be brought back to the home directory; if we provide a directory path, we'll be taken to the provided directoryls lists files in the current directory (including other directories)we'll want to edit files, right? How do we open our editor from the terminal?
+there's usually a terminal command for the editor. For VSCode, it's code; for Zed, it's zed. If we want to open the current directory in our editor of choice (and we do!), we'll write <editor command> . (note the .), where . means "the current directory."
let's check that you have git installed with git version. You might see something like git version 2.34.1 printed out in response. If you don't get a version number, but instead get an error saying you don't have git, install git.
before we really start, we're going to set a few basics to make it easier for ourselves.
+# skip this one if you installed git for windows
+# this means that if git wants us to edit something,
+# it'll open in the built-in terminal editor 'nano'
+# the default is vim, which can be pretty unfriendly to newcomers
+# nano, on the other hand, will tell you how to do basic
+# operations at the bottom of the editor
+git config --global core.editor nano
+
+# this uses the autocorrect
+# the value specifies how many *tenths* of a second
+# so 10 => 1 second
+git config --global help.autocorrect 10
+
+# skip this one if you installed git for windows
+# the default branch name is "master" due to older computer terminology
+# older language used to explain some computing relationships as master/slave
+# some people consider this outdated and harmful, so "main" is a more common these days
+# also, I'll be using main, so this will help make your output look like mine
+git config --global init.defaultbranch main
+
+# this sets our information
+# if we don't set this, git will prompt us to set it later
+git config --global user.name <your-name>
+git config --global user.email <your-email>
+(no, that's not a real git command)
there's two main ways to start:
+++tip:
+gitand associated tooling refer to projects as repositories. I'll be sticking with the word project here as I find it a bit friendlier, but you'll probably run across the word repository in the wider world ofgit
git init <project> will create a new directory named project ready to be used with git. We can then use cd <project> to enter the directory.
++tip: don't use spaces in your project name!
+
git clone <project URL> will pull in an existing project. We're not going to talk about this right now; instead, we're going forward assuming with git init.
before we do anything, let's see what git will tell us about our project. Type git status and we might see the following:
On branch main
+
+No commits yet
+
+nothing to commit (create/copy files and use "git add" to track)
+let's dissect this.
+git has a concept of branches, which are different paths our file history has taken. While branches are incredibly powerful, we're going to stay away from branches during this walkthrough and focus on working on a single branch - in this case, main.
"no commits" means that the project has no history whatsoever. "Nothing to commit" means we've made no changes. But what is a commit?
+a commit is one set of changes made to our work. We get to choose which changes are part of any given commit, and we write a message describing the commit so that future-us knows what we did if for some reason we need to undo something.
+in an established project, we can use git log to look at our commit history. By default, one commit will output like this:
commit e2fd6c4772e61f9c074638a933eb92fc1ea885ef
+Author: Lee Cattarin <lee.cattarin@gmail.com>
+Date: Sun Dec 28 18:47:00 2025 -0800
+
+ fix syntax err in alt
+In order there, we have:
+commits are made with the command git commit, but if we try to create a commit right now we'll be told "nothing to commit."
okay, what if we edit a file?
+++tip: if you don't want to actually open your editor, just use
+touch file.txtto create a new empty file namedfile.txt
hmmm, there's still nothing to commit! What happens if we check git status? There's some new output!
Untracked files:
+ (use "git add <file>..." to include in what will be committed)
+ file.txt
+git tells us that we have "untracked" files - a.k.a. files that git hasn't got in its history yet. It also tells us to use git add if we want to be able to commit that file.
git has a concept called the staging environment or staging area. This captures the set of changes we're adding to a single commit. When we add something to the staging area, we say we are staging it or that it is staged. In order to stage changes, we'll use git add.
why a staging area? Why not just commit our changes?
+well, imagine we're writing a blog post (easy for me to imagine right now). We start reviewing it, and notice that there's a bit of page styling we don't like - not something tied to the content of the post, but the styling of the overall site. We fix it, and want to save that change while continuing to work on our post draft. git add and the staging area allow that kind of choice.
git add <filename> lets us add all changes in the given file to the staging area. Sometimes this is really useful - if we just created a new file (by, say, using touch file.txt), we probably want to add the whole thing.
personally, I really like using git add -p, so much so that I wrote an entire blog post about it. It lets us review changes piece-by-piece and pick only the pieces we want.
for now, we'll try git add file.txt. We'll notice there's no output by default, but we can run git status to see where things are at. git will now tell us:
Changes to be committed:
+ (use "git rm --cached <file>..." to unstage)
+ new file: file.txt
+now we're ready to create a commit!
+if we just write git commit, it'll open an editor for us to edit the commit message - our description of the changes. This can be handy if we want to write a lengthy description, but if we want to just write a one-liner, we can use git commit -m "<message>". It's quicker and doesn't involve opening an editor.
let's create a super basic commit:
+git commit -m "baby's first commit"
+++tip: as excited as you may be, don't use '!' in your commit messages
+
we'll see output like this:
+[main (root-commit) 3dcf1ca] baby's first commit
+ 1 file changed, 1 insertion(+)
+trying git log now will show us our single commit!
++tip: type
+qto exit thegit logoutput
trying git status will tell us:
On branch main
+nothing to commit, working tree clean
+so, we've added a new file - that wasn't bad. Things get a little more interesting when we edit files git already knows about. Let's use <editor-command> . to open the current directory and write a sentence or two in file.txt.
after saving file.txt, try git status again.
++tip: Ctrl+S (or Cmd+S on Mac) is the shortcut for saving basically everywhere
+
Changes not staged for commit:
+ (use "git add <file>..." to update what will be committed)
+ (use "git restore <file>..." to discard changes in working directory)
+ modified: file.txt
+git restore is new! That lets us get rid of our changes and go back to the last version of the file committed. Be careful with this - we should only do it if we really want to get rid of those changes.
let's not restore, and instead stage and commit our new changes:
+git add file.txt
+git commit -m "added a new sentence"
+again, we can use git status or git log as needed.
ooooh... I don't actually like that change. What if I want to undo something?
+run git log again, and copy the first 6-8 characters in the commit string (we can copy more, including the whole string if we want, but it's not necessary):
commit 8b5dd7838f8c8423cfa445b6cddbed88e9c32511 (HEAD -> main)
+Author: Lee Cattarin <lee.cattarin@gmail.com>
+Date: Wed Jan 7 15:18:45 2026 -0800
+
+ added a new sentence
+
+in this case, 8b5dd7.
now we can try git revert <commit-string>. It'll open our editor to write a message about the change. It's important to know that git revert doesn't delete the old commit - it creates a new commit that undoes the previous work.
[main 9268d5c] Revert "added a new sentence"
+ 1 file changed, 1 deletion(-)
+I didn't edit the message - we can tell because it just says "Revert" and then the old commit message. But we can edit and add lots of detail about why we're doing it.
+let's try a new command: git remote. Hmm, nothing happened... what's a "remote"?
remember how I said we could use git clone to work on an existing project? If we did that, we'd be getting that project from a remote server - not our local machine.
the world of git servers is vast - hell, you can run your own! - but we're going to just mention a few major hosts: GitHub, GitLab, and Codeberg. For this walkthrough, we're going to work with Codeberg, but you'll find that the UI is pretty similar across all three, so if you've got a GitHub or GitLab account feel free to use that.
+let's head on over to Codeberg First off, we'll make an account.
+now we'll make a new project using the + in the upper right. Choose 'New repository,' then pick a repository name. You can leave the other settings be.
with the project created, Codeberg will tell us three things we can do: clone the repository, create a new repository, or push an existing repository. We'll push an existing one.
+git remote add origin https://codeberg.org/inherentlee/testing.git
+git push -u origin main
+first, we'll add a remote. Across from the project title, we should see a button that says Code with a dropdown indicator. It'll offer a few choices, the first two being SSH and HTTPS. I'll talk about SSH in a bit, but let's try HTTPS first. Copy that URL; we're about to use it in a command.
++tip: the remote can be named whatever you want! Traditionally, it's called
+origin, but if it's easier for you to remember, you might call itcodebergor mayberemote
git remote add <remote-name> <url>
+for this walkthrough, we'll call our remote codeberg.
there's no feedback, but that's ok. Re-running git remote shows that we have a remote now: codeberg. That really doesn't tell us much, does it! Let's try a more talkative command: git remote --verbose or, more simply, git remote -v. Now it tells us the following:
codeberg https://codeberg.org/inherentlee/git-intro.git (fetch)
+codeberg https://codeberg.org/inherentlee/git-intro.git (push)
+cool! we have a remote set up. What does "fetch" and "push" mean?
+git fetch brings remote changes to our local machine. So does a command called git pull. Why are there two?
fetch brings the remote changes down, but doesn't combine them yet with our local work. This gives us a chance to explore what those changes are before we actually integrate them into our work!
this may seem unhelpful if we're thinking about this project as something only we work on, but imagine there's a team of people all contributing to the project. What if we and another person both work on the same file? Our changes might overlap!
+if we're working alone and from one machine, we'll pretty much never have to use git fetch or git pull! If we happen to do our work on multiple machines - for example, I do some work on my PC and some on my fruitpad (using an app called Working Copy) - we'll probably update the remote from one machine, then need to pull that work down onto the other machine.
for our use case, we can pretty safely stick to git pull (if we ever even need to use it!), but if you're working in a larger collaborative project, git fetch is your friend!
git push is the opposite of git pull - it takes your local changes and adds them to the remote.
the first time we use it on any given branch, we'll want to set what's called the upstream - the remote branch that our local branch is connected to by default. We can do this with the following command:
+git push --set-upstream codeberg main
+# or, for brevity
+git push -u codeberg main
+when we call git push, we're prompted for our Codeberg username and password.
personally, I find constantly authenticating tremendously annoying! There's a couple of ways to handle this.
+git config --global credential.helper cache will store our username and password in memory. You'll be re-prompted every 15 minutes. I work in long enough sessions that this is still a pain for me, but it may work for yougit config --global credential.helper store will save our username and password in a file on our machine, and only re-prompt if we change either value. Importantly, this method does not encrypt our password in any way! While it's convenient, it's not very securegit config --global credential.helper osxkeychain is a secure method for saving credentialsgit for Windows, we should have Git Credential Manager (GCM)while I'm not going to go into a lot of the technical concepts behind SSH keys, I will talk a bit about how my setup works. If you're happy with one of the other credential management setups above, feel free to skip past this section cause it's a bit chunky.
+the SSH (secure shell) protocol allows for secure communication on an insecure network.
+when we generate an SSH key, we get a public and a private key. These are mathematically related, and if we encrypt something with the private key, it can be decrypted with the public key, and vice versa.
+the important thing to know is you should never share your private key. Also, while you aren't forced to set a password when creating these keys, I strongly recommend doing so.
+running ssh-keygen will take us through a series of prompts. Assuming we don't already have an SSH key, the default file location is fine.
when you choose a passphrase, write it down. If you lose it, there is no recourse. You will have to generate a new SSH key.
+after generation, we will have two files at the location specified by the tool (or the custom location we chose). Generally, that's the folder .ssh in our home directory. If we navigate to that directory (cd $HOME/.ssh) and look at the files (ls), we'll see files named id_rsa and id_rsa.pub. The one that ends with .pub is the public key.
time for a little more terminal knowledge!
+I have two handy pieces of tooling in my terminal that I use for SSH operations.
+the first one is an alias - basically a simple shortcut for a command. I've written an alias for outputting my public key so that when I need it, I can get it without having to write out the path to the key. Laziness is a virtue, okay?
+there's a file in the home directory called .bashrc. It sets a lot of terminal-wide functionality. We're going to add an alias to it!
the command to output a file's contents is cat <filename>. My alias name of choice to cat my public SSH key is sshcat - but feel free to name yours something else.
navigate to the home directory (cd) and open your .bashrc file in your editor (<editor-command> .bashrc). Add the following to the bottom before saving and exiting.
alias sshcat="cat $HOME/.ssh/id_rsa.pub"
+++tip: don't be alarmed if you can't use this right away! Your
+.bashrcfile takes effect when the terminal starts up. If you want to test it, either restart your terminal, or typesource $HOME/.bashrc
the other piece of shortcut SSH key tooling I use is a function that I call ssa, short for ssh-agent. ssh-agent manages SSH keys and keeps us logged in during a session.
# SSH agent
+ssa() {
+ ssa_pid=$(pgrep ssh-agent)
+ if [[ $ssa_pid ]]; then kill $ssa_pid; fi
+
+ echo -n "$fg[green]"
+ eval $(ssh-agent -s)
+ ssh-add ~/.ssh/id_rsa
+}
+look, I'll be honest... we're not going to explain this one in detail. In short, though, it removes any existing instance of ssh-agent, then prompts us to put in our SSH key password so it can authenticate us.
open the .bashrc file again for editing, and add the above function to the bottom before saving and exiting. Again, either quit the terminal or type source $HOME/.bashrc to reload and use this new function by typing in ssa.
when we added a remote, we used the HTTPS URL. Let's update to using the SSH URL - you can find this on the main project page under the dropdown button that reads Code.
git remote set-url codeberg <new-url>
+we'll notice that the SSH URL starts with git@, whereas the HTTPS URL started with https://.
in order for all this to be useful, we need to tell Codeberg about our SSH key. In Codeberg, navigate to settings, then find the left-hand tab for SSH keys. Choose 'Add key' and paste in the public key (if you set up that sshcat alias, use it now to output your key for ease of copying). Save and we'll now be set up to authenticate with SSH!
we can now call git push again and again now without having to repeat our credentials every time. We can also call git pull for our private repositories.
let's talk about what we've done.
+git initgit statusgit log to view our commit historygit add to add new files or file changes to a commitgit commitgit revertgit remote to link that project to our local workgit fetch and git pullgit pushgit push!congratulations, and welcome to git!
+
+
+ my therapist's idea
+7.5" x 4"
+default: black
+patch, print, sticker, shirt, card, pin
+ +
+
+
+
+
+
+ Learn more about Artisans Cooperative, a new platform for makers and supporters.
+I've joined the "coop" and have made these cards/prints to support their fundraiser.
+ + +
+
+
+ Learn more about Artisans Cooperative, a new platform for makers and supporters.
+I've joined the "coop" and will be making shirts to support their fundraiser.
+Buy them on Artisans Cooperative's new marketplace.
+Buy them via my order form. (Sep 2024: No longer accepting orders via this method)
+
+
+ did you know you can draw on (certain) mushrooms
+ +
+
+
+ Azure is Microsoft's cloud offering. Each possible resource that can be deployed in Azure has a location it's deployed in, such as "East US" or "Italy." While some resources can be deployed in all locations, other resources have location constraints.
+It's common, when deploying, to have a whole ecosystem of resources that will work together. However, this introduces a problem: which locations work for all resources in a deployment?
+Let's dig in. (Want just the outcome? Check the summary.)
+Bicep is a language for describing Azure resources. A Bicep file sets out a series of resources with preset or parameterized properties in order to deploy said resources.
+A minimal Bicep file that creates a resource group might look like this:
+param resourceGroupName string = 'myResourceGroup'
+param location string = "westus2"
+
+resource resourceGroup 'Microsoft.Resources/resourceGroups@2022-09-01' = {
+ name: resourceGroupName
+ location: location
+}
+This is easy to start parsing - I can use grep to find that Microsoft.Resources/resourceGroups@2022-09-01 string and go from there.
However, minimal is uncommon. As stated above, deployments of multiple resources are much more common.
+When working with large deployments, certain resources may be needed more than once. You can repeat your earlier storage account declaration, or, instead, you can template out how to deploy a storage account with given parameters, then reuse that template. This is called a module, and it's fundamental to organizing Bicep files.
Let's say this is our file structure. Ignore the lack of parameter files or READMEs, this is just an example.
+.
+|--infra
+ |--env
+ | |--dev
+ | | |--main.bicep
+ | |--prod
+ | |--main.bicep
+ |--modules
+ |--rg
+ | |--main.bicep
+ |--vm
+ | |--modules
+ | | |--network.bicep
+ | | |--virtual-machine.bicep
+ | |--main.bicep
+ |--kv
+ |--modules
+ | |--role-assignment.bicep
+ | |--key-vault.bicep
+ |--main.bicep
+Bicep files use relative references for local modules, so infra/env/dev/main.bicep references ../../modules/vm/main.bicep, which references ./modules/network.bicep. While the directory structure in this example could be flattened, my point is: modules can nest, and each module refers relatively to the module(s) it relies on.
Okay, let's backtrack. From a given Bicep file, we want:
+Resources and modules both have patterns in how they are declared. Thankfully, they're pretty simple regexes. grep will spit out lines in a file that match a given regex.
# this gets us strings like
+# resource resourceGroup 'Microsoft.Resources/resourceGroups@2022-09-01' = {
+grep -E "^resource " "$file"
+
+# this gets us strings like
+# module vm '../../modules/vm/main.bicep' = {
+grep -E "^module " "$file"
+From there, let's use cut to strip off the parts we don't want.
# this gets us strings like
+# Microsoft.Resources/resourceGroups
+grep -E "^resource " "$file" \
+ | cut -d "'" -f 2 - \
+ | cut -d "@" -f 1 -
+
+# this gets us strings like
+# ../../modules/vm/main.bicep
+grep -E "^module " "$file" \
+ | cut -d "'" -f 2 -
+These calls are a little opaque. -d sets a delimiter (what to split on). -f picks a field to return, numbered from 1.
We'll save these values to variables. mapfile reads a file, putting each line into a new array element. -t trims newline characters. The <s do some redirection, and yes, the space between them matters.
mapfile -t resources < <(grep -E "^resource " "$file" \
+ | cut -d "'" -f 2 - \
+ | cut -d "@" -f 1 -)
+mapfile -t modules < <(grep -E "^module " "$file" \
+ | cut -d "'" -f 2 -)
+We can't just stop there. We need to search each module in turn. Using dirname, we can get the directory of the file we're searching, then append the relative module path.
get_resources () {
+ # ... grep, cut, etc ...
+
+ directory=$(dirname "$file")
+
+ for module in "${modules[@]}"
+ do
+ mapfile -t -O "${#resources[@]}" resources < <(get_resources "$directory/$module")
+ done
+}
+A lot just happened there besides dirname. {modules[@]} is all the array elements (as opposed to just $modules, which evaluates to the first element). ${#modules[@]}, on the other hand - note the pound sign - is the number of elements in the array.
Additionally, mapfile usually writes from index 0 onwards. But with the -O argument, we can specify an origin. By setting the starting point to the length of the array, we append to the array rather than writing over existing data.
Finally, we got some recursion going! get_resources calls get_resources for every module found.
So far, our code looks like this:
+get_resources () {
+ mapfile -t resources < <(grep -E "^resource " "$file" \
+ | cut -d "'" -f 2 - \
+ | cut -d "@" -f 1 -)
+ mapfile -t modules < <(grep -E "^module " "$file" \
+ | cut -d "'" -f 2 -)
+
+ directory=$(dirname "$file")
+
+ for module in "${modules[@]}"
+ do
+ mapfile -t -O "${#resources[@]}" resources < <(get_resources "$directory/$module")
+ done
+
+ for resource in "${resources[@]}"; do; echo "$resource"; done
+}
+That last one-liner just returns our results. Note that we don't just echo "${resources[@]}" - this results in a space-delimited string and it'll be helpful later to have a newline-delimited string.
Now we need to use these resource types to get available locations. First, actually call our function from above. We'll assume we're in a directory with a top-level main.bicep file.
mapfile -t resources < <(get_resources "main.bicep")
+Does sorting matter? Not really, but sort has a useful feature, -u, which returns unique items (aka, it deduplicates). Looking up the same resource type twice slows us down.
mapfile -t resources < <(get_resources "main.bicep" | sort -u)
+sort is one reason it helps to have newlines as delimiters - it expects that.
We'll use az to list all the locations - just to give ourselves a starting point. You could also use the locations for the first resource type.
mapfile -t locations < <(az account list-locations --query "[].displayName" \
+ --out tsv)
+We can then use an az command to find available locations for a given resource type:
mapfile -t newLocations < <(az provider show --namespace "$namespace" \
+ --query "resourceTypes[?resourceType=='$resourceType'].locations | [0]" \
+ --out tsv)
+--out tsv means we will get a list with no decoration whatsoever - it's vital for programmatic handling of az command output.
We'll need to get those $namespace and $resourceType variables. cut comes back in handy:
# remember, $resource is something like Microsoft.Resources/resourceGroups
+
+# this gets us strings like
+# Microsoft.Resources
+namespace=$(echo "$resource" | cut -d "/" -f 1 -)
+
+# this gets us strings like
+# resourceGroups
+resourceType=$(echo "$resource" | cut -d "/" -f 2 -)
+Okay, we can get locations. How do we handle finding their intersection?
+comm to the rescue. It finds common lines between two sorted files. Its default output is three columns - lines only in file 1, lines only in file 2, and lines common to both. We can suppress the first two columns with -12.
comm expects files, so we'll reuse our redirection <(someCommand) from earlier.
mapfile -t locations < <(comm -12 \
+ <(for location in "${locations[@]}"; do echo "$location"; done) \
+ <(for location in "${newLocations[@]}"; do echo "$location"; done) )
+comm also likes newline-delimited input, so we're again looping through the array rather than echoing all values at once.
With functionality as it is, many deployments will come back with 0 locations available. Turns out some basic resource types, like role assignments, don't have locations. So let's filter those.
+if [[ ${#newLocations[@]} -eq 0 ]]
+then
+ # handle
+fi
+We'll print the locations to the shell. We can even use tee to print them to a file for good measure:
for location in "${locations[@]}"; do echo "$location"; done | tee locations.txt
+Here's our code for this section:
+mapfile -t resources < <(get_resources "main.bicep" | sort -u)
+
+mapfile -t locations < <(az account list-locations --query "[].displayName" \
+ --out tsv)
+
+for resource in "${resources[@]}"
+do
+ namespace=$(echo "$resource" | cut -d "/" -f 1 -)
+ resourceType=$(echo "$resource" | cut -d "/" -f 2 -)
+
+ mapfile -t newLocations < <(az provider show --namespace "$namespace" \
+ --query "resourceTypes[?resourceType=='$resourceType'].locations | [0]" \
+ --out tsv)
+
+ if [[ ${#newLocations[@]} -eq 0 ]]
+ then
+ continue
+ fi
+
+ mapfile -t locations < <(comm -12 \
+ <(for location in "${locations[@]}"; do echo "$location"; done) \
+ <(for location in "${newLocations[@]}"; do echo "$location"; done) )
+done
+
+for location in "${locations[@]}"; do echo "$location"; done | tee locations.txt
+Here's our final script:
+# Recursively crawls bicep files to find all referenced resources
+get_resources () {
+ mapfile -t resources < <(grep -E "^resource " "$file" \
+ | cut -d "'" -f 2 - \
+ | cut -d "@" -f 1 -)
+ mapfile -t modules < <(grep -E "^module " "$file" \
+ | cut -d "'" -f 2 -)
+
+ directory=$(dirname "$file")
+
+ for module in "${modules[@]}"
+ do
+ mapfile -t -O "${#resources[@]}" resources < <(get_resources "$directory/module")
+ done
+
+ for resource in "${resources[@]}"; do echo "$resource"; done
+}
+
+# Execution starts here
+mapfile -t resources < <(get_resources "main.bicep" | sort -u)
+
+mapfile -t locations < <(az account list-locations --query "[].displayName" \
+ --out tsv)
+
+for resource in "${resources[@]}"
+do
+ namespace=$(echo "$resource" | cut -d "/" -f 1 -)
+ resourceType=$(echo "$resource" | cut -d "/" -f 2 -)
+
+ mapfile -t newLocations < <(az provider show --namespace "$namespace" \
+ --query "resourceTypes[?resourceType=='$resourceType'].locations | [0]" \
+ --out tsv)
+
+ if [[ ${#newLocations[@]} -eq 0 ]]
+ then
+ continue
+ fi
+
+ mapfile -t locations < <(comm -12 \
+ <(for location in "${locations[@]}"; do echo "$location"; done) \
+ <(for location in "${newLocations[@]}"; do echo "$location"; done) )
+done
+
+for location in "${locations[@]}"; do echo "$location"; done | tee locations.txt
+
+
+
+
+ ++These notes are from a talk I gave at work. If you think something is missing or incorrect, please let me know!
+
Backend developers still have users: other developers. We're all human, with human limitations and differences. Design for those limitations and you'll find you improve the end result for everyone.
+The first thing you can do: document. By this I don't just mean standalone text documentation; I also include code comments, clear variable and file naming, and pipeline or script outputs that report success or failure and give details.
+Rely on existing standards where possible. Style guides, spell checkers, linters, and formatters are all great. Make standards easy to follow with linter, editor, etc. config files in your repo, or add a devcontainer so others can easily get started with the project. When there's no standard, create one; for example, set up github issue and PR templates. And whenever you can, automate tests and fixes.
+Sighted users may take for granted the ability to skim a page by glancing at headers or highlights. Users with screen readers rely on several features for the same functionality.
+For example, screen readers can summarize the headers on a page. To leverage this, break up content with headers and avoid using other formatting to achieve similar effects visually. Stick to one h1 per page, and don't skip levels (e.g. go from an h1 to an h3).
+Screen readers can also summarize a page's links. In order for this to be effective, links need descriptive text attached to them (and always avoid bare links!). Compare these examples:
+++Read more about accessibility patterns on the web
+
vs
+++Read more about accessibility patterns on the web
+
The second example has text directly attached to the link that describes its content. This is a vast improvement over the first example, where the link would just be read out as "more". Since links are visually highlighted, good link text also improves readability for everyone.
+A table of contents can be helpful for a broad set of users, from the power user who knows exactly what she needs from the page to the newbie who just wants to see what the major topics are.
+Alt text/image descriptions for image and transcription/audio descriptions for videos are essential (and not just for screen readers - they're really useful if you've got poor internet connection). The references section of this document will link to more information on writing good alt text, but in general, focus on why the image/video is there and what it is conveying.
+(If you find you are simply transcribing text in an image, remove the image unless it conveys additional detail - images showing text intended to be read are less user-friendly than the same content conveyed as text. If the text is purely decorative and not intended to be read, carry on - just make sure you don't transcribe it since it's not meaningful!)
+I find this comes up the most in backend as diagrams. We love diagrams in place of words! Unfortunately, you'll want those words for some users eventually. Avoid alt text like "diagram of components" or "flow chart showing pipeline" - either write out a more direct explanation that mentions all entities contained in the diagram, or direct the reader to a section of text that covers the same content (e.g. "flow chart of the pipeline described below"). If you're writing a direct explanation, don't feel the need to describe each shape or arrow - focus on describing the relationships and entities those shapes and arrows represent.
+These are bits of feedback or further thoughts that have yet to be integrated into this.
+
+
+
+ adjustable! also can be made with rainbow fittings.
+ +
+
+
+ Baseball stitched book!
+3" x 2" ish
+drawing paper
+ +
+
+
+ Hand carved stamp based on a photo of Jorts the cat.
+roughly 4.5" x 3.5"
+default: jalapeno
+patch, print, greeting card
+ +
+
+
+ Fiber from Woolgatherings. 70% Blue-Faced Leicester 30% tussah silk.
+ +
+
+
+ A large, large pigeon. Based on a fantastic photograph by Chris Price.
+5" x 5" or so
+default: black, sometimes with some blue, purple, green "iridescence"
+patch, print, greeting card, shirt
+ +
+
+
+
+
+
+ A two-tone blue and brown leather journal closed by a button. The inside front cover has a small pocket.
+
+
+
+ cheeky lil congrats on the top surgery card :D
+ +
+
+
+ Celebration of testosterone-driven bottom growth. Submitted for T! the Zine.
+ +
+
+
+ Handstitched leather bowtie with standard clasp.
+ +
+
+
+ To order this as a shirt, please fill out the order form (Sep 2024: no longer taking orders via this method).
3" x 8"
+default: pink
+patch, print, greeting card, sticker, shirt, pin
+ +
+
+
+ 3/4" wide collar with stainless/nickel fittings.
+ +
+
+
+ To match brooke's collar.
+ +
+
+
+ A collage book full of different paper, art, and more.
+
+
+
+
+
+
+ My second pair of socks, knit for my partner. They use Schoppel-Wolle Das Paar yarn in the colorway Fruhjahrsputz / Spring Cleaning. This yarn is designed to produce identical striping for each sock, and it very nearly did, with only a tiny discrepancy notable as I got to the toes.
+ +
+
+
+ for our 3 year anniversary :)
+ +
+
+
+ Created for Stanza 2024, a show highlighting nonbinary artists and poets.
+My art was inspired by the fantastic poem "A blurred arboreal" by Lore Kahuapāʻani.
+ +
+
+
+ This pattern is a Work in Progress and will be updated with more information as I make a second pair of these.
+This pattern was built on the Podster Gloves pattern by Glenna C. on Ravelry. I highly recommend reading and following that pattern to fill in gaps with this one.
+These gloves are built in two parts: bulky yarn section, then fingering. First, with bulky yarn and larger needles, the ribbed cuff is knit in the round. From the cuff we build the back of the hand by working a portion of the stitches flat in stockinette. The back of the hand then forms the mitten top by making additional stitches and joining to work in the round again, then knitting until a taper and grafting the ends with Kitchener stitch.
+With fingering yarn and smaller needles, we work from the leftover cuff stitches up to build the palm, using decreases to connect the edges of the bulky and fingering sections. A thumb gusset is built with increases, then put onto waste yarn to work later. When the palm is knit to the point it aligns with the start of the mitten top, we pick up stitches from the inside edge of the mitten top and join to knit the rest of the hand and fingers in the round. Finally, we split off fingers and work each separately, including returning to finish the thumb.
+With larger needles and bulky yarn, CO 28 (N) stitches and join to work in the round, placing a BOR marker. Work in k1p1 rib or your preferred rib until desired length of cuff is reached, then stop at BOR.
+Remove BOR marker. Turn to work WS.
+p15 (N/2 + 1) stitches. Place a locking stitch marker into the first and last stitches of this row for a reference point later. Turn to work RS.
+Back of hand repeat:
+Repeat until work sits just below the knuckles, ending with a RS row.
+Thread a piece of waste yarn through all back of hand stitches. This is used as a reference point later to pick up stitches for the hand.
+At the end of a RS row, place a BOR marker, then make 13 (N/2 -1) stitches using the backwards loop cast on or your preferred method. Join to work in the round.
+Establish a ribbed cuff on the palm side of the mitten top while leaving the back of the hand in stockinette:
+Repeat for 4 rows or your desired length.
+Knit in stockinette until work is about level with the end of the pinky finger.
+Mitten top decreases:
+Place first 7 stitches onto one needle and second 7 stitches on to another. Graft together using Kitchener stitch or your preferred method.
+You can alternately begin the decreases on the pinky side of the hand earlier than on the pointer side - this will make the mitten top fit the hand closer.
+You will now start to work with the other stitches left by the cuff, on the RS. Use smaller needles and fingering weight yarn.
+Start by making 1 from the yarn between the back of hand (BOH) stitches and your first stitch. Then kfb (or your preferred increase) across all remaining cuff stitches. Make 1 more stitch from the yarn between your stitches and the BOH. You should have 28 (N) stitches.
+At the end of your RS row, pick up the selvedge stich below the stitch we marked earlier. Then turn your work.
+k2tog - the selvedge stitch and your last stitch. purl across to the last stitch in the row, then ssk that stitch together with, again, the selvedge stitch below the marked stitch. Turn your work.
+Palm repeat:
+You only need to do this one or two times before starting the thumb gusset.
+I diverge slightly from the Podster Gloves pattern here in that there is only one line of increases going up this thumb, and so we increase more frequently.
+Follow right or left hand instructions accordingly until you have added 16-20 stitches depending on thumb size.
+If you would like to place a marker for the increases, I recommend setting that up as follows:
+and then on future RS:
+This ensures that you are not doing your make ones with a marker in the way. But I generally don't use the marker and just knit all the "new" stitches before doing the m1R.
+To be continued...
+ +
+
+
+ Hand carved stamp based on a photo of chanterelle mushrooms.
+about 2" square
+default: 2-tone yellow
+patch, print, greeting card
+ +
+
+
+ Fiber from Circle R Ranch. 100% alpaca, from Charlie the alpaca.
+ +
+
+
+ it's roumd
+ +This is v2 of my personal website, build with Eleventy v3.1.2. It's been hand-coded from the ground up.
+v1 of this site began in 2022 and was based on Millennial, a minimalist Jekyll theme for running a blog or publication by Paul Le.
+The fonts are Atkinson Hyperlegible Next and Atkinson Hyperlegible Mono for standard text and monospace respectively, specifically designed for low-vision readers to improve character recognition. Also they look neat :)
+Thank you to some lovely friends for their feedback and help with the site! You should hire them. Yes, you.
+ +You can find the accessibility statement here. You can also explore the sitemap. If you'd like, you can view the site's palette or the style overview.
+This site is created without the use of generative AI.
+ +
+
+
+ 3" x 5"
+default: black
+patch, print, greeting card
+ +
+
+
+ I'm fucking depressed. No, not like mental health depressed (okay, look, that too, but that's not relevant here). Looking to switch text editors, I reviewed 6 different options...and what I found didn't thrill me.
+++this post contains comparison tables that are far more viewable on desktop/tablet
+
the editors I reviewed, in no particular order, are:
+I reviewed looking for 5 major functional qualities that I considered to be my most useful or heavily-used features:
+.editorconfig support3 less important, but preferred, aesthetic qualities:
+and finally, 3 ethical and trustworthiness qualities:
+(in other words: no LLMs).
+| + | .editorconfig |
+find-and-replace | +WSL | +multi-edit | +.md preview |
+
|---|---|---|---|---|---|
| VSCode | +yes[1] | +yes | +yes[1] | +yes | +yes | +
| Zed | +yes[1] | +yes | +yes | +yes | +yes | +
| Kate | +yes | +yes | +no[2] | +yes | +no | +
| Lapce | +no | +yes | +no[3] | +yes | +no | +
| Pulsar | +no | +yes | +no[3] | +yes | +yes | +
| Sublime Text | +yes | +yes | +no[3][4] | +yes | +no[5] | +
[1] extension needed: VSCode .editorconfig, VSCode WSL, Zed .editorconfig
[2] I could open a WSL directory in Kate, but couldn't see any files. I confirmed that opening a Windows directory worked as expected.
+[3] opening the project worked fine, but I couldn't delete files. In Sublime Text's case, they were deleted but still shown in the file view.
+[4] saving a new file opens the save menu in the Windows File Explorer, which frankly makes me a bit afraid. Touching your WSL files from Windows is generally a bad idea.
+[5] I explored two different add-on packages for Markdown preview support. Markdown Live Preview and MarkdownPreview. Markdown Live Preview opened a whole new window scoped only to the specific .md file. MarkdownPreview previewed in browser. Neither of these match the behavior I am looking for.
| + | UI | +file icons | +color scheme | +
|---|---|---|---|
| VSCode | +yes | +yes | +yes[1] | +
| Zed | +yes | +yes | +yes[2] | +
| Kate | +no | +yes | +yes[2] | +
| Lapce | +yes | +no | +yes[3] | +
| Pulsar | +yes | +no | +yes[1] | +
| Sublime Text | +yes | +yes | +yes | +
[1] several color schemes available. Further extensions available.
+[2] several color schemes available.
+[3] only light and dark schemes available. Further plugins available.
+this is my best guess based on searching online and reviewing the settings; it's kind of hard to really confirm these things. I am judging the last quality - whether or not the devs are using LLMs - based on the existence of prompts directories in the projects' repositories.
| + | no LLM features | +LLM kill switch | +no LLM code | +
|---|---|---|---|
| VSCode | +no | +no | +no[1] | +
| Zed | +no | +yes | +no[2] | +
| Kate | +yes | +n/a | +yes | +
| Lapce | +yes | +n/a | +yes | +
| Pulsar | +yes | +n/a | +yes[3] | +
| Sublime Text | +yes | +n/a | +unknown[4] | +
[2] looks like at current LLMs are only used for documentation
+[3] it's been discussed and sounds currently up for debate: Pulsar, Sublime Text
+[4] I couldn't find the source code for Sublime Text online; I assume it's not OSS. If you know where it is, point me in that direction.
+I'll be honest, I just don't know. The functionality is not something I can easily compromise on. TBH, I figured I had pretty basic needs as a developer, but it seems that's not the case! The only editors that meet my functionality needs across the board are also the worst offenders on the LLM front.
+at the end of the day, I might just have to keep looking... but regardless, I wanted to publish what I found to help anyone else with similar needs.
+.editorconfig support requires an extensionI learned that WSL2 can support GUI apps so I tried this for a few. Pulsar did not work; Sublime Text worked but the UI scale was teensy and was not affected by the ui_scale setting.
+
+
+ Hand carved modular stamps to congratulate your friend or yourself on your fantastic neurodivergence.
+As these are modular, I can expand the range of terms offered if there is demand for it.
+roughly 4" x 3"
+default: onyx black
+patch, print, greeting card
+ +
+
+
+ A variation on my congrats on the autism/adhd cards.
+Hand carved modular stamps to congratulate your friend or yourself on your fantastic gayness.
+As these are modular, I can expand the range of terms offered if there is demand for it.
+roughly 4" x 3"
+default: onyx black with rainbow lettering on 'Gay!'
+print, greeting card
+ +if pricing is an issue for you, reach out and we can work out sliding scale options - or an art trade!
+
+
+
+ Fiber from Jakira Farms in Coral Reef colorway. 100% merino.
+ +
+
+
+
+
+
+
+ the first step in any design is small tests. For leatherwork, this usually means working with paper - cheaper than leather, but with some of the same inflexibility.
+my grommets, laid out on paper, spaced out cleanly at 1/2" away from edges and 1" apart. My tablet is ~10"x8". I started with these facts.
+don't measure out the same spacing any more than you have to. Templating is your friend for both ease of use and regularity.
+the corners should be stitched before lacing in order to get into tight spaces easily. Grommets are set most easily before the work is 3-dimensional. It's cleanest to cut before grommets are installed so as to work on a smooth and level surface. Hold all this in your mind when choosing your next move, and ensure you don't step on your own toes.
+even if you won't be making one, what would you do differently? Where does your design fail? Where does it succeed?
+easier said than done.
+ +
+
+
+ This was written as a skill share for the Artisans Cooperative blog.
+In this writeup, I will walk through one (of many!) ways to set up and utilize a custom domain and website.
+This is aimed at a non-technical audience, but my own perspective is technical and some of the articles I link to will get technical. Because of this, it's possible that I will miss things that you have questions on - please reach out to me and ask questions!
+After following this writeup, you should have:
+This writeup will not cover every possible route to getting your own home on the web - there's far too many options out there. It's just meant to give you a starting point and a few ideas.
+I started planning my domain name by reviewing the list of TLDs - things like ".org" or ".com". ICANN (the Internet Corporation for Assigned Names and Numbers) maintains a list of all TLDs - it's long! Reviewing this list can help you think of potential domain names. You can also look at a list like this one Wikipedia maintains - it has more detail and can tell you if a specific TLD is for a given country, reserved for specific uses, or (what you probably want) for general use.
+Personally, I narrowed it down to two - ".art" or ".gay". From there, it's off to the domain registrars! These are companies that offer domain names for sale. Here's an article from Forbes Advisor reviewing some of the major names in the space. I use Namecheap, but don't let that bias you. Look around at pricing (pay attention to the rate for year 2 onward - domain registrars often offer super low year 1 rates to hook customers) and consider other features, like support availability.
+Narrowing down a TLD isn't the only choice - you also need to decide what goes in front of that! Some things to think about:
+In my case, I went with leecat.art. The ".art" TLD was cheaper than the ".gay" option long-term, and I shortened my full, somewhat hard to spell name to a quick two syllables, 3 characters each.
+Pick a domain registrar that offers the domain name you want, and pay (usually for a year). Next, we'll talk about some uses for this domain name.
+The easiest way to utilize a domain name is to have it redirect to another URL. I'm not going to go through how to set up a domain redirect with every possible provider, but if you search up "[your domain registrar] redirect" you should find useful documentation.
+Link trees (popularized by linktree) are a single page with a collection of links maintained by an owner. You can use linktree or check out this WIRED article with alternatives. Some are paid, usually small monthly fees, and some are free.
+GitHub - wait, no.
+First, let's talk about git. Git is a version control system, a type of software that manages changes to a set of files. This allows the owner(s) of those files to do things like revert changes or compare current and historical versions of files. It also allows for multiple people to work on the same shared file repository without creating conflicts.
+GitHub is a centralized source for many, many git repositories. It essentially allows you to back up both your code and the log of all changes to the cloud (someone else's computer). It also supports GitHub Pages, a free way to host a static site of your own.
+To get started with GitHub, you'll first need to create an account if you don't have one. If you want to learn some GitHub basics, the GitHub team has created this handy introduction to GitHub that walks you through some basic git and GitHub concepts.
+The GitHub documentation is pretty thorough, so let's point to some articles over there.
+
+
+
+ continuing to iterate on bifolds. made of a variety of discount leather with various imperfections. fully hand-stitched.
+ +
+
+
+ pattern by PaintYee
+ +
+
+
+ hand-dyed with acid dyes
+ +
+
+
+
+
+
+ a slight change to the first wallet design. made of a variety of discount leather with various imperfections. fully hand-stitched.
+ +
+
+
+ I am euphorbic.
+ +
+
+
+ A friendly neighborhood visitor.
+5" x 6"
+default: black
+patch, print, greeting card, shirt
+ +
+
+
+ This is in response to an F.D Signifier video, How to get RICH off weak men! (go follow him!). It is written as a response video and the script has not been changed to fit this format.
+As much as it's a response video, I think the content should also stand on its own.
+Recently I watched an FD Signifier video that I'll link in the description, currently titled "How to get rich off weak men"
+Now, overall, fucking excellent, you're great FD, you're very much someone I look up to. So let's ground things by stating I don't say any of this to disagree or denigrate any of your points, but rather to build on them.
+what we're going to start and end with today is a concept called oppositional sexism. This comes from Julia Serano writing in Whipping Girl.
+In addition to traditional sexism, where men are better than women and masculinity better than femininity, Serano argues that we also experience and propagate oppositional sexism, where men are seen as women's opposites. from Whipping Girl:
+++the belief that female and male are rigid, mutually exclusive categories, each possessing a unique and nonoverlapping set of attributes, aptitudes, abilities, and desires
+
oppositional sexism positions men as opposed to women, rather than understanding both groups as heavily overlapping in characteristics, abilities, interests, physicality, etc.
+where analysis based in traditional sexism takes the gender binary as fact and analyzes how we move within it, oppositional sexism takes aim at the gender binary itself and looks at the ways that it is constructed.
+now what does this have to do with FD's video? Well, I would characterize his analysis as grounded in, among many other things, an understanding of traditional sexism. He talks about the ways boys learn about maleness and masculinity and its dominence over femaleness and femininity, but never questions the existence of the category "boy" in opposition to "girl".
+And I think we have to! we can't just examine misogyny and toxic masculinity while unconsciously accepting the creation of binary gender, because the creation of and pressure on binary gender helps create misogyny and toxic masculinity. When boys are continuously told they are wildly different from girls, when they are continuously split up and separated, when girls are portrayed as almost a different species, we invite unhealthy attitudes about women and girls.
+this is of course hypothetical - we live in a society and can't escape it - but if instead we stressed that all children are human, that they are part of a unified group, i can't help but imagine that we would have much healthier attitudes towards sex and dating.
+now, obviously, the deconstruction of misogyny is enough reason to use this for analysis. we want to minimize misogyny and toxic masculinity, and this supports that goal. but let's also talk about the way bioessentialism and binary gender roles foster other biases.
+transphobia and particularly anti-nonbinary sentiments are probably the most obvious ones propped up by this, but there's also:
+homophobia or biphobia: when we view men and women as two discrete and opposing groups, there's a large difference between a male-female relationship and a male-male or female-female one.
+intersex erasure and discrimination: the coercive and corrective sexing of intersex infants, up to and including nonconsensual surgical modification, should be the obvious example, but let's also consider the ways that intersex people are erased by the gender binary. Intersex people are not uncommon, with various estimates ranging from one in two thousand to one in about sixty. But the structure of the gender binary must insist that they are essentially one in a million. it makes no room for them - they must be outliers.
+and, interestingly,
+Now, I want to be clear: I am not claiming that misogyny or the gender binary is the root of all other biases. I think the world is far more complex than that. But I do believe that the gender binary, and more broadly, a binarist mindset, is a fertile breeding ground for other biases.
+going forward, I want you to have the concept of oppositional sexism in your analysis toolbox. I want you to think critically about the way that the gender binary is not a natural outgrowth of humanity, but rather created and enforced. And I want you to question and push back against the way that you - as a man or woman - see yourself in opposition to, rather than in alliance with, women or men (nonbinary people, you get a pass here, I figure you're already thinking about this).
+ +
+
+
+ FediZineFest returns for its second year. Read more about it and sign up on the fediZineFest 2025 website.
+ +
+
+
+ Fiber from Jakira Farms in Fire & Ice colorway. 100% merino.
+ +
+
+
+ Available for lots of flags! Except the lesbian sunset flag because it's really hard to color match :(
+ +
+
+
+ Photographed in the backyard.
+6" x 2.5"
+default: green and brown
+patch, print, greeting card, shirt
+ +
+
+
+
+
+
+
+ Hand carved stamp of a slightly goofy looking flatfish.
+about 2" around
+default: sepia
+patch, print, greeting card
+ +
+
+
+ Hand carved set of stamps of a northern flicker, hungrily eyeing our bird feeder.
+roughly 6" square
+default: black, sepia brown, and satin red
+print, greeting card, patch, shirt
+ +
+
+
+ A landscape-oriented notebook with black paper and a fuzzy cover. yes. it's fuzzy.
+~ 4" x 6"
+Designed for standard credit cards, not business cards.
+ +
+
+
+ Single piece construction for easy care - just unfold/pull out the tabs!
+ +
+
+
+ Wax seals pictured from boygirlparty.
+
+
+
+ 6" x 6"
+default: green and pink
+patch, print, greeting card, shirt
+ +the gallery page is for finished art
+(or browse by tags)
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ the gallery page is for finished art
+(or browse by tags)
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ the gallery page is for finished art
+(or browse by tags)
+ +
+
+
+
+
+
+
+
+
+
+ the gallery page is for finished art
+(or browse by tags)
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ the gallery page is for finished art
+(or browse by tags)
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ the gallery page is for finished art
+(or browse by tags)
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ the gallery page is for finished art
+(or browse by tags)
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ the gallery page is for finished art
+(or browse by tags)
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ the gallery page is for finished art
+(or browse by tags)
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ the gallery page is for finished art
+(or browse by tags)
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ the gallery page is for finished art
+(or browse by tags)
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ the gallery page is for finished art
+(or browse by tags)
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Combination of trans wrongs skull and the geese.
+6" x 4"
+default: black
+patch, print, shirt
+ +
+
+
+ Based on a fantastic picture by handmade ghost
+6" x 3"
+default: black
+patch, print, greeting card, shirt
+ +
+
+
+ Look through a scan of the zine. (Be aware that the PDF is not as up to date as the web version below.)
+ +The below material is a web page reproduction of my zine Gender as a Proxy Variable.
+Authored by Lee Cattarin
+August - December 2023 -> February 2024
+++I would argue that you almost never have to ask for gender…
+If you’re collecting gender identity data to personalize user-facing copy, try asking for preferred* pronouns instead. If you’re asking because you want to make in-app content recommendations, try asking about the user’s content preferences. If you’re asking to generate a user avatar, let the user generate their own. Gender identity is a poor proxy variable — stick to asking for the information you actually want.
+
-- Nikki Stevens, quoted in “How to Make Your Software More Trans-Inclusive,” emphasis mine
+*pro tip: just say "pronouns" and drop the "preferred"
+So, what are you asking for when you ask for
+(this section and its effects have been altered to be functional in this medium - in the print zine, checkboxes and radio buttons were used. But Markdown only has one type of list.)
+Questions that require users pick a single answer will be followed with "(pick one)".
+Questions that allow users to choose multiple answers will be followed with "(pick any)".
+Write-in fields may have italicized suggestions in them.
+What pronouns should we use for you? (pick any)
+Worried about parsing that free text field? Try:
+What title or prefix should we use for you? (pick one)
+(et cetera)
+Do you require private facilities for breastfeeding or other health and wellness needs? (pick one)
+Provide a write-in space for specific needs, such as refrigeration or running water.
+Which style of shirt would you prefer? (pick one)
+Do you require any of the following restrooms? (pick any)
+All restrooms should be provided with menstrual products.
+What is your gender? This information will be viewable on your profile by all logged-in users. (pick any)
+Make sure to note the visibility level!
+What is your gender? This information is collected for demographic analysis only. (pick one)
+What is your legal sex as marked on government-issued identification? (pick one)
+Explain where this information will be used. If you need something more specific than any legal identification, say so: "This is used for insurance purposes and must match your gender on insurance paperwork."
+What is your gender? (pick any)
+What sex were you assigned at birth? (pick one)
+An organ inventory and/or surgical history is a useful tool.
+But let’s talk more about that
+Is:
+enough?
+Maybe not! Probably not, in fact! But what are our other options?
+Having a few primary options and a write-in, as shown above, is a good balance!
+A little more on
+Consider the following options:
+Aside from the lack of nonbinary gender choices, did you notice that?
+These exclusive choices present “man” and “trans man” (and “woman” and “trans woman”) as separate genders, when what they’re most likely trying to convey is:
+Avoid treating “cis” as the unspoken default.
+Other anti-patterns include:
+Here’s some additional positive patterns that can be appended to many of the earlier examples:
+This zine draws from material I gathered for a longer blog post: leecat.art/gender-in-data
+Linked in that blog post are numerous sources; the most heavily relied on here was from Drupal’s documentation and is found under the section headed “Do you need gender data?”
+Lee Cattarin is a transgender software developer and artist based out of Vashon, WA, USA. All hir creative work can be found at leecat.art
+Get in touch with hir via any of the methods listed on leecat.art/contact
+
+
+
+
+As transgender and nonbinary people gain visibility and legal identification expands gender fields to include nonbinary genders and/or "X" markers, data models that only support "male" and "female" (or "M"/"F") are no longer sufficient to align with individuals' legal records or personal identity. However, with the large range of terminology used by transgender and nonbinary people, tackling this problem can seem challenging and brings the potential to further harm or exclude already marginalized individuals.
+Proofreading and commentary from all readers!
+If you're knowledgeable on this topic, additional information and viewpoints are tremendously useful.
+If you're new, where do you have questions? What hasn't been explained enough? What do you leave this document still wondering about?
+This document will look at existing and potential solutions to modeling gender in data for use by software systems, primarily via presenting and analyzing patterns and anti-patterns.
+In order to more easily discuss these topics, I will note here my working definitions for some terms below.
+++A note on language… Please do not assume that these definitions are fixed or can be broadly applied across cultures and countries. The language around gender has continued to change as the trans community is more able to connect, have in depth conversations, and define for ourselves how we use language. On an individual, person-to-person level, it's always preferable to mirror the language people use for themselves rather than prescriptively apply terms.
+
++Language note: trans and cis are derived from Latin, meaning "on the other side of" and "on this side of" respectively.
+
There's a dearth of data on trans individuals. That said, we do have some relevant sources that have given us broad insight into trans communities.
+Both of these sources use ‘nonbinary’ as their usual spelling; I am mirroring that here.
+It's always worthwhile to stop and look at how relevant or necessary gender information is to your goals.
+++ +"'I would argue that you almost never have to ask for gender,' Stevens said.
+"If you’re collecting gender identity data to personalize user-facing copy, try asking for preferred pronouns instead. If you’re asking because you want to make in-app content recommendations, try asking about the user’s content preferences. If you’re asking to generate a user avatar, let the user generate their own. Gender identity is a poor proxy variable — stick to asking for the information you actually want.
+
Drupal's gender field includes the following guidance in its description:
+++ ++
+- Do you need to address a person with pronouns? Genders do not necessarily map easily onto the pronouns a person uses. If you need to associate pronouns with a person, ask for those pronouns directly.
+- Do you need to address a person with a title or prefix (such as Mr./Ms./Mx.)? Genders also do not necessarily correspond to a person's preferred title, and moreover would leave out honorifics related to profession, such as Dr., Rev., or Capt. If you need to associate titles with a person, ask for those titles directly.
+- Do you need to collect gender information for demographic data reasons? If you do, make sure you are able to accurately record a person's gender, rather than forcing them into choosing from limited options. If you need to use the data for recording trends, writing reports, or segmentation for advertising or other reasons, consider post-processing the data to group related genders depending on your specific use-case.
+- Do you need to know a person's health needs, clothing preferences or bathroom use? If you are organizing an event, for instance, you might want to know what sort of facilities to provide or what sorts of t-shirts to order. Genders, however, do not necessarily correspond to specific body types, body functions, health requirements (such as menstrual supplies) or reflect what types of facilities a person would feel safest using in a public environment. If you are collecting gender data for this purpose, ask the more precise questions specifically.
+- Do you want to publicly display a person's gender on a profile? This is often a choice made by social media and dating/relationship sites. If you do this, consider making the field optional altogether. If you are providing user avatars, remember that human bodies come in all sorts, and allow individuals to choose an avatar separately from collecting this data.
+- Do you need to know assigned gender for legal, medical or regulatory reasons? Current gender does not necessarily correspond to assigned sex at birth or legal gender marker, so be sure you are clear in what you are requesting of a person. It's particularly critical to be transparent about your privacy policy and the how this data will be used.
+
++ +....if this information is displayed publicly there is potential for abuse by people who like to make discriminatory jokes about gender identity, and any such system would need to put steps in place to prevent such abuse.
+...
+Instead of putting the burden on a user to fully understand the risks of sharing their highly personal information, let’s put the burden on ourselves to treat that information right. If we have no strong reason to collect it, or can’t guarantee its safety, we shouldn’t collect it.
+
If you do collect gender data, inform the user what it is used for and who it will be shared with.
+The availability of nonbinary gender options in legal systems varies by location. The ability to change one's gender marker (from one binary category to another or from binary to nonbinary) varies even more widely, with requirements ranging from simple voluntary declaration, to verification by medical professional(s) of varying treatments which vary in how accessible they are and whether they are actually desired by individuals for whom this is relevant, to only changing in response to proven "error," etc.
+(Transgender Law Center, 2017) (Movement Advancement Project) (Knight and Ghosal, 2016)
+If you are not specifically trying to refer to an individual’s legal identification, don’t tie gender to it.
+This is variously defined to refer to sex assigned at birth, legal gender/sex markers, or current physical sex based on any number of characteristics, which may include
+and more. This identifier may or may not be treated as binary and may or may not take into account an individual's medical history.
+ +Outside of healthcare, this is largely unnecessary. For healthcare, see Two- or multi- step approach and Organ inventory.
+As a way to accurately represent gender diverse people, "other" is, quite literally, othering. "Prefer not to say" or similar wording can be an excellent option, but not if it is the only option outside the traditional gender binary that is available to users - in that case, it becomes no longer a preference but a requirement not to say.
+++ +"Researchers have also developed methods to respond to challenges involved in data collection about sexual and gender identities which are culturally specific and unique. For example, in 2011, the government of Nepal attempted the world’s first census in which respondents had the option of choosing 'Male,' 'Female,' or 'Third Gender.' The effort was not successful for a number of reasons, among which was that large proportions of the gender minority population did not identify with the term 'third gender.' Subsequent research determined that the use of culturally specific terms such as 'Methi' and 'Kothi' would have increased the effectiveness of the census effort."
+
While these can be appealing for the choice they afford users, they come with notable use and implementation considerations.
+This approach provides much of the benefits of an expansive list, and removes the drawbacks discussed above. However, it comes at the cost of making data storage and analysis more expensive, challenging, and time-consuming. It may additionally still contribute to user confusion for anyone who is uncertain where or how the information will be used or whether there are expected responses.
+For smaller projects, however, this is still an option. Or you can offer this to a small subset of users who find that the available options don't fit - see Not listed here.
+Especially when providing expansive lists or allowing write in fields, ensure that terms that are not mutually exclusive must be chosen as though they are. This is the reasoning behind guidance for a two-step approach, described in more detail in Two- or multi- step approach, which "measures assigned sex at birth and self-reported gender identity at the time of the survey" (Park, 2016)
+++"...some terms aren’t mutually exclusive, and framing them as such is offensive, Mons said: 'For example, if we list Man, Woman, Non-Binary, Trans Man and Trans Woman, does that separation imply that someone who is trans and identifies as a man is not a man?'"
+
(Gossett, 2020) emphasis mine
+In addition to reinforcing binarism, this approaches tends to reinforce sexism as well, and can further reinforce other prejudices as they intersect with gender.
+++ +"Moreover, these works tend to codify (literally, to write into code) essentialist, stereotypical characterizations of male and female communication patterns and present them as universal, context-free, scientific truths."
+
For transgender individuals, automatic categorization as a gender they are not is often a frequent and painful occurrence. We should take care not to introduce unnecessary causes of harm into our systems, especially when they are less accurate and helpful than user self-identification.
+Gender isn’t one-to-one with pronouns (or terms of address). If you want to know how to refer to someone, ask pronouns separately.
+Including "decline to specify" or a similar opt-out response is always a positive addition unless the information is actually necessary. However, as mentioned above, don't use this as a replacement for the inclusion of terms that actually match the identity of the individual. Opting out should be a choice, not forced due to lack of other options.
+Including "gender not listed here" or a similar response provides an out for anyone who does not feel represented by the available options. You could follow this up with a free text entry field.
+Avoid treating gender as an immutable category - make sure users have the ability to edit it.
+As mentioned briefly above, a two-step approach separates gender and assigned sex at birth, allowing healthcare systems additional information about the patient. See WPATH guidelines on EMR and associated reference for more details. +This may also be expanded to a multi-step approach, with questions covering some/all of
+…but only if the data is necessary.
+++ +Provide a means to maintain an inventory of a patient's medical transition history and current anatomy. An anatomical inventory would allow providers to record into the chart (and/or update as needed) the organs each individual patient has at any given point in time; this inventory would then drive any individualized auto-population of history and physical exam templates. This inventory should be uncoupled from the patient's recorded gender identity, assigned sex, or preferred pronouns.
+...
+These procedures, however, also should also be un-coupled from any gender-coded template so that an individual coded as male who has had a hysterectomy, for example, could have that history documented. In addition, sex-specific organ procedures and diagnoses relating to these organs should be un-coupled, so that (as an example) a prostatic ultrasound may be ordered on a patient registered as female, or a cervical pap smear ordered on a patient registered as male. Such practices would allow enhanced decision support for transgender-specific care, such as medication interactions, organ- and sex-specific preventive health alerts, or accommodations for sex-specific laboratory normal value ranges.
+
Refer to the reference linked above for detailed examples of organ or surgical history inventories.
+Additional reference (added Sep 2024): (Bourns, 2023)
+Avoid questions that contrast ‘male’ and ‘trans male’ or any parallel set of terms. Either contrast ‘cis male’ and ‘trans male,' or allow respondents to choose multiple responses.
+These systems add additional requirements and restrictions. In cases like these, it may be helpful to have an additional field for legal gender/sex designation (usually set to F, M, or X) in order to allow for alignment with existing documentation without preventing self-identification on the part of the individual.
+Healthcare systems in particular may benefit from Organ inventory and Two- or multi- step approach.
+++ +Preferred name, gender identity, and pronoun preference, as identified by patients, should be included as demographic variables (such as with ethnicity). These would be captured in readily amendable, optional fields that are separate from the patient’s state-listed name and sex or gender designation, which may continue to be used for billing purposes in circumstances when the patient has not yet obtained legal change of name and/or sex or gender designation. Note that some patients may identify as ‘genderqueer’ and prefer the use of neither pronoun. While lists of current common gender identities, sex options, and pronoun options are provided [in original document, see source], ideally field parameters would be easily amended to reflect changing paradigms and social trends within transgender communities.
+
++ +"In 2014, Facebook expanded their gender options from 2 to 58 for English speakers in the US and UK. The gender options they added were created in consultation with the LGBTQIA community and range from 'gender non-conforming' to 'two-spirit' to 'trans female'. The corporation later added the ability to identify as more than one gender and to input a custom gender. ... While these changes may appear to be progressive, Facebook’s databases still resolve custom and non-binary genders to Male and Female on the backend based on the binary gender that users select at sign-up where the custom option is not available. Here is how the Facebook Marketing API views gender: 1 = Male, 2 = Female. So while a user and her friends may see her presented as the gender she elects, she is a 1 = Male or 2 = Female to any advertisers looking to purchase her attention."
+
Uses a list of genders developed by the Open Demographics project. (McCabe and Beach, 2019) (Stevens)
+Includes Male, Female, Other, and Decline to Specify as options for a gender field, and the field can be null. (Salesforce)
+Includes Male, Female, Not Specified, and Non-specific. Defaults to not specifed. (Adobe) (Adobe)
+Uses three different fields: sex assigned at birth, legal sex and gender identity. (Landman, 2017)
+ +
+
+
+ Headers and text in blockquotes are handwritten; plain text is image description.
+There are two dots spaced a bit apart, labeled 'Male' and 'Female'
+The same two dots from the previous diagram, now with a horizontal line connecting them.
+++Let's think about expanding this in two ways...
+
Following this, two arrows point downward to two columns titled Models 3a and 3b, separated by a squiggle.
+The connected dots from Model 2. Now, a vertical axis rises midway between male and female, and its extreme is labeled 'agender.' Arrows helpfully indicate the directions of 'less gender' and 'more gender.' There is a dotted line between agender and male, suggesting a triangle shape.
+The connected dots from Model 2, but instead of the horizontal line ending at the points marked male and female, it continues off into seeming infinity.
+++These are just two points, not "extremes"
+
A grayscale circle simulating a color wheel, with a slice scaled up to examine. "Type" of gender is labeled as varying as the color wheel is circled, while "amount" of gendered is labeled as varying between the center and the edge of the circle.
+++If we wanted to "place" male and female here, they're probably regions, not discrete points.
+Just as "sky blue" and "navy" are both blue, we have umbrella terms that contain or overlap other terms"
+
In correspondence with this text, the color wheel image has two areas circled in dotted lines, labeled 'male?' and 'female?'
+ +
+
+
+ To order this as a shirt, please fill out the order form. (Sep 2024: no longer taking orders via this method)
3" x 8"
+default: blue
+patch, print, greeting card, sticker, shirt, pin
+ +
+
+
+ This is a collation of responses to a fediverse post I made.
+For reference, I also have a list set up at /give that lists a number of great nonprofits.
+If you are listed here and would like to be removed, just get in touch with me.
+Thank you to these wonderful folks (and many more who chose to remain uncredited) for the links/orgs:
+ + +
+
+
+ 40-something inches long, brass hardware, custom dyed.
+ +
+
+
+ A green memo pad. The text block is held on by a piece of waxed thread around the spine, which allows it to be replaced if desired.
+
+
+
+ Original print greeting cards.
+ +
+
+
+ Original print greeting cards.
+ +guestbook entries are manually added and moderated :)
+yes, you may sign again even if you've been here before - just no spamming please!
+if the embed below is not working for you, you can open the guestbook form in a new tab or just contact me any way you want with your desired message
+ +J'myle Koretz on :
+++It was delightful to meet you at the Vashon art walk a couple weekends back. Kestrel was awesome, and your [Brooke's] jackets were so cool!
+
Jack on :
+++All the things you make and do are so cool and I feel so lucky every time I get to see them (including the ones that now live in my home!)
+
hello on :
+++thanks for being gay and weird pal
+
Fén (Spirits) on :
+++Hi you make really good art and crafts and have been awesome to get to know ^^ Keep on keeping on~
+
Olive on :
+++Hi! You're cool :))
+
Fern (Rainbow) on :
+++I think you're awesome and this is really impressive 🥺
+
handmade ghost on :
+++Everything Lee makes is a gift to the world--just knowing ze made something new brings me joy and sends me scrambling to this site!
+
jay on :
+++EGG
+
pqqq on :
+++Hello from Fedi! Your site is absolutely wonderful, and I love that you're adding a guestbook, too!!
+
Morgan on :
+++You make good art and good takes on gender 💜
+
Lisa on :
+++Ask not for whom the snoot is booped. It is booped for weevils.
+
nathanlovestrees on :
+++honk!
+
✨pencilears✨ on :
+++Hello! I'm glad we're still doing guest books on websites.
+
brhfl on :
+++hey hi! just giving you some test data and also basking in the nostalgia of a guestbook :)
+
Eedlipherus C. Bigribs on :
+++Signing the guestbook so you have data to format on the page lyao.
+And this is a second paragraph, because, honestly, this is more interesting than the QA I get paid to do, so I'll just make this bit long enough to wrap for a few lines, yep, maybe a little bit more, uh huh, that's right, eat your heart out, Lorem Ipsum.
+
jade on :
+++Hi Lee!
+
alex tax1a on :
+++ +Hi!! signing the guestbook, so you have at least one datum.
+
+
+
+ Hand carved stamp of some tummy hair, in celebration of testosterone.
+4" x 6"
+default: black
+patch, print, greeting card??
+ +
+
+
+ Fiber from Paradise Fibers. 70% merino/30% nylon. Hand-dyed by me.
+ +
+
+
+ Fiber: Jacobs wool that I scoured and combed myself.
+ +
+
+
+ Recently, I got an iPad for art and immediately fell down the rabbit hole of Glitch and web development.
+When creating the rescue Trans rescue site, I started with a right-aligned navbar. While developing and testing on my iPad, I got in the habit of hovering my hand over the top-right corner of the tablet, always ready to try the light/dark toggle or switch pages.
+And then I thought about left-handed people - left handed tablet or touchscreen users in particular. Reaching across the screen repeatedly would start to be a real drag, wouldn't it?
+Ok, let's make a toggle!
+(Want to just see the outcome? Head on down the page to the summary.)
+First we'll need some HTML for our button. I added this to my menu:
+<button id="alignment"
+ title="toggle left/right navbar alignment"
+ aria-label="toggle left/right navbar alignment"
+ class="menu-link">
+ <!-- autopopulated by nav.js -->
+</button>
+It's important to note here that I added it to the beginning of the menu list. I want it to be the first item in the list so that it points over the left side of the screen with nothing obstructing it.
+Let's quickly take a moment to ensure that the toggle only shows up on wider screens with a bit of CSS:
+@media (max-width: 500px) {
+ #alignment {
+ display: none;
+ }
+}
+Now users on phones won't have an unhelpful button taking screen space.
+Now let's move to nav.js and define some consts for ease of use. We're going to use Font Awesome icons for this button, so we'll go grab their HTML for the left and right pointing hands.
const ALIGN = "alignment"
+const LEFT = "left"
+const RIGHT = "right"
+
+const LEFT_ICON = '<i class="fa-regular fa-hand-point-left" aria-hidden="true"></i>';
+const RIGHT_ICON = '<i class="fa-regular fa-hand-point-right" aria-hidden="true"></i>';
+We'll use localStorage to store and retrieve alignment preferences:
let align = localStorage.getItem(ALIGN);
+and we'll grab the button we defined in HTML:
+let alignToggle = document.getElementById(ALIGN);
+Next, let's structure out some functions. We'll fill them in more as we figure out what we need.
+function setAlignRight() {
+ // If we're aligned on the right, the toggle should point to the left
+ alignToggle.innerHTML = LEFT_ICON;
+}
+
+function setAlignLeft() {
+ // If we're aligned on the left, the toggle should point to the right
+ alignToggle.innerHTML = RIGHT_ICON;
+}
+
+// This function changes the alignment to the given value
+// It also runs at startup to set alignment
+function changeAlign(align) {
+ switch (align) {
+ case LEFT:
+ setAlignLeft();
+ break;
+ case null:
+ // If nothing is set, default to right alignment
+ align = RIGHT;
+ case RIGHT:
+ setAlignRight();
+ break;
+ }
+ // Set localStorage for next time
+ localStorage.setItem(ALIGN, align);
+}
+
+// Run at startup
+changeAlign();
+
+// This function handles the actual flip-flopping of the alignment value
+function toggleAlign() {
+ if (align === LEFT) align = RIGHT;
+ else align = LEFT;
+
+ changeAlign(align);
+}
+
+// Attach the toggle function to the alignToggle as a click listener
+alignToggle.addEventListener("click", toggleAlign);
+So that gets us the basic functionality of changing the icon when the toggle is clicked. However, it does nothing for the navbar alignment. What do we need for that?
+Well, that depends on your navbar CSS. For this, let's run through the simplest possible version: your navbar is a flexbox and all items are treated equally. Maybe your CSS looks sorta like this:
+#navbar {
+ position: sticky;
+ top: 0 px;
+ width: 100%;
+ display: flex;
+ /* This is the line that matters to us */
+ /* we'll want to swap between flex-start and flex-end */
+ justify-content: flex-end;
+}
+Let's add that justify-content setting to our JS (as well as a line to fetch the navbar by id):
let navbar = document.getElementById("navbar");
+
+function setAlignRight() {
+ alignToggle.innerHTML = LEFT_ICON;
+ navbar.style.justifyContent = "flex-end";
+ // If you have other necessary style changes, add them here
+}
+
+function setAlignLeft() {
+ alignToggle.innerHTML = RIGHT_ICON;
+ navbar.style.justifyContent = "flex-start";
+ // If you have other necessary style changes, add them here
+}
+Now, the menu should re-orient itself when we interact with the toggle. However, you'll notice that the left-aligned menu shows up in the same order as the right aligned menu: handedness toggle first, then the rest of the menu.
+(Forgive the lack of continuity with the header image.)
+
I don't want that; I want the handedness toggle to always point, unobstructed, to the side of the screen it moves things to. So let's move it around when we set alignment. It'll need to be the first item in the menu list for right-handed alignment, and the last item for left-handed. We can do that with prepend() and append().
function setAlignRight() {
+ alignToggle.innerHTML = LEFT_ICON;
+ navbar.style.justifyContent = "flex-end";
+ navbar.prepend(alignToggle);
+}
+
+function setAlignLeft() {
+ alignToggle.innerHTML = RIGHT_ICON;
+ navbar.style.justifyContent = "flex-start";
+ navbar.append(alignToggle);
+}
+Cool! Now we have a menu that re-aligns itself and repositions the alignment button.
+
Oooh, but wait: keyboard navigation is broken.
+When I tab over to the alignment button and hit Enter/space, it does what we expect, but it also loses keyboard focus. Because of that little prepend()/append() move, the element is removed from the DOM and re-added in a new location - now without focus. We'll need to add focus back to the alignToggle manually, so it's not lost.
We can do that with the .focus() function:
function toggleAlign() {
+ if (align === LEFT) align = RIGHT;
+ else align = LEFT;
+
+ changeAlign(align);
+
+ // Replace focus on the toggle that's been moved
+ alignToggle.focus();
+}
+Ok, now focus is maintained... but it also shows up after a mouse click, not just a keyboard interaction. That's a little irritating. How do we fix that?
+toggleAlign() is an event handler, which means it can optionally take an event var. For "click" events, that event var includes a field detail which provides the click count. This can be used to disambiguate single vs double clicks, or it can be used to test for keyboard interaction, which creates zero clicks.
Let's add that in:
+function toggleAlign(event) {
+ if (align === LEFT) align = RIGHT;
+ else align = LEFT;
+
+ changeAlign(align);
+
+ // Zero clicks means this was a keyboard interaction
+ // Replace focus on the toggle that's been moved
+ if (event.detail === 0) alignToggle.focus();
+}
+Now we should only replace visible focus for keyboard interactions.
+
Want to see further changes? Found a bug with this implementation? Contact me!
+Here's the referenced HTML, CSS, and JS in full:
+<button id="alignment"
+ title="toggle left/right navbar alignment"
+ aria-label="toggle left/right navbar alignment"
+ class="menu-link">
+ <!-- autopopulated by nav.js -->
+</button>
+#navbar {
+ position: sticky;
+ top: 0 px;
+ width: 100%;
+ display: flex;
+ /* nav.js handles justify-content instead */
+ /* justify-content: flex-end; */
+}
+
+/* Remove the handedness toggle on narrow screens */
+@media (max-width: 500px) {
+ #alignment {
+ display: none;
+ }
+}
+const ALIGN = "alignment"
+const LEFT = "left"
+const RIGHT = "right"
+
+const LEFT_ICON = '<i class="fa-regular fa-hand-point-left" aria-hidden="true"></i>';
+const RIGHT_ICON = '<i class="fa-regular fa-hand-point-right" aria-hidden="true"></i>';
+
+let align = localStorage.getItem(ALIGN);
+let alignToggle = document.getElementById(ALIGN);
+
+function setAlignRight() {
+ alignToggle.innerHTML = LEFT_ICON;
+ navbar.style.justifyContent = "flex-end";
+ navbar.prepend(alignToggle);
+}
+
+function setAlignLeft() {
+ alignToggle.innerHTML = RIGHT_ICON;
+ navbar.style.justifyContent = "flex-start";
+ navbar.append(alignToggle);
+}
+
+function changeAlign(align) {
+ switch (align) {
+ case LEFT:
+ setAlignLeft();
+ break;
+ case null:
+ align = RIGHT;
+ case RIGHT:
+ setAlignRight();
+ break;
+ }
+ localStorage.setItem(ALIGN, align);
+}
+
+changeAlign();
+
+function toggleAlign(event) {
+ if (align === LEFT) align = RIGHT;
+ else align = LEFT;
+
+ changeAlign(align);
+
+ if (event.detail === 0) alignToggle.focus();
+}
+
+alignToggle.addEventListener("click", toggleAlign);
+
+
+
+
+ 4 skeins of handspun yarn.
+
+
+
+ 3" x 5"
+default: black and a random accent color
+patch, print, greeting card
+ +
+
+
+ Hand-lettered digitally. Happy Solstice to you and yours.
+ +
+
+
+ Happy Solstice to you and yours.
+ +
+
+
+ Hand carved stamp of a great blue heron. Based on a photo from birdpixel.
+4" x 8"
+default: black
+patch, print, greeting card, shirt
+ +
+
+
+ Rufous hummingbirds are remarkably aggressive.
+6" x 6"
+default: black and orange
+patch, print, greeting card, shirt
+ +