Files
leecat.art/_site/handedness-toggle/index.html
2026-02-20 08:46:50 -08:00

1768 lines
62 KiB
HTML

<!doctype html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>handedness toggle | hello hello</title>
<meta name="description" content="Lee Cattarin... on the internet!">
<link rel="alternate" href="/feed.xml" type="application/atom+xml" title="hello hello">
<meta property="og:title" content="handedness toggle">
<meta property="og:type" content="website">
<meta property="og:description" content="Lee Cattarin... on the internet!">
<meta property="og:site_name" content="hello hello">
<meta property="og:image" content="/img/handedness-toggle-0.png">
<meta property="og:image:alt" content="A screenshot of the rescue trans rescue navbar centered on a button that shows a hand pointing left.">
<meta name="generator" content="Eleventy v3.1.2">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="">
<link href="https://fonts.googleapis.com/css2?family=Atkinson+Hyperlegible+Mono:ital,wght@0,200..800;1,200..800&family=Atkinson+Hyperlegible+Next:ital,wght@0,200..800;1,200..800&display=swap" rel="stylesheet">
<script src="https://kit.fontawesome.com/884dded219.js" crossorigin="anonymous"></script>
<style>.post-metadata {
display: flex;
flex-flow: row wrap;
justify-content: space-between;
align-items: baseline;
margin: 1.5rem 0 .5rem;
}
.post-metadata p {
font-size: .9rem;
margin: 0;
}
.post-tags {
display: flex;
flex-flow: row wrap;
gap: .5rem;
list-style: none;
margin: 0;
}
.post-tags li {
margin: 0;
}
.post-tags li a {
text-decoration: none;
color: var(--color-teal);
padding: 0 .5rem;
border-radius: 1rem;
box-shadow: .15rem .15rem var(--color-shadow);
border: .08rem solid var(--color-teal);
line-height: 2;
/* Click animation handling */
position: relative;
top: 0;
left: 0;
transition: top .1s ease-in, left .1s ease-in;
}
.post-tags li a:focus-visible {
outline: none;
background-color: var(--color-teal);
color: var(--color-bg);
}
@media (any-hover: hover) {
.post-tags li a:hover {
outline: none;
background-color: var(--color-teal);
color: var(--color-bg);
}
}
@media (forced-colors: active) {
.post-tags li a:focus-visible {
outline-offset: .08rem;
outline: .08rem solid;
}
@media (any-hover: hover) {
.post-tags li a:hover {
outline-offset: .08rem;
outline: .08rem solid;
}
}
}
/* Click animation */
.post-tags li a:active {
top: .1rem;
left: .1rem;
box-shadow: .05rem .05rem var(--color-shadow);
}
/* Adapted from PrismJS 1.30.0 Tomorrow Night theme
https://prismjs.com/download
*/
code,
pre,
code[class*=language-],
pre[class*=language-] {
font-family: var(--font-family-code);
background-color: var(--color-bg-alt);
font-size: .9rem;
text-shadow: 0 1px var(--color-shadow);
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
pre,
pre[class*=language-] {
margin: 1rem 0;
padding: 1rem;
line-height: 1.5;
white-space: pre-wrap;
word-break: break-word;
overflow-wrap: break-word;
border-radius: .5rem;
overflow: auto;
}
:not(pre)>code,
:not(pre)>code[class*=language-] {
padding: .2rem;
border-radius: .25rem;
white-space: normal;
}
/* Selected text */
code ::-moz-selection,
code::-moz-selection,
pre ::-moz-selection,
pre::-moz-selection,
code[class*=language-] ::-moz-selection,
code[class*=language-]::-moz-selection,
pre[class*=language-] ::-moz-selection,
pre[class*=language-]::-moz-selection,
code ::selection,
code::selection,
pre ::selection,
pre::selection,
code[class*=language-] ::selection,
code[class*=language-]::selection,
pre[class*=language-] ::selection,
pre[class*=language-]::selection {
text-shadow: none;
background-color: var(--color-bg);
}
/* Syntax highlighting */
.token.namespace {
opacity: .7;
}
.token.bold,
.token.important {
font-weight:700
}
.token.italic {
font-style:italic
}
.token.block-comment,
.token.cdata,
.token.comment,
.token.doctype,
.token.prolog,
.token.punctuation {
color: var(--color-grey);
}
.token.attr-name,
.token.deleted,
.token.namespace,
.token.tag {
color: var(--color-red);
}
.token.boolean,
.token.function,
.token.number {
color: var(--color-orange);
}
.token.class-name,
.token.constant,
.token.property,
.token.symbol {
color: var(--color-yellow);
}
.token.attr-value,
.token.char,
.token.regex,
.token.string,
.token.variable,
.token.inserted {
color: var(--color-green);
}
.token.entity,
.token.operator,
.token.url,
.token.function-name {
color:var(--color-blue);
}
.token.atrule,
.token.builtin,
.token.important,
.token.keyword,
.token.selector {
color: var(--color-purple);
}
#postlist,
#taglist {
list-style: none;
}
#postlist, .post,
#taglist, .tag {
margin: 0;
}
/* Odd-numbered posts & tag layout/coloration */
.post:nth-child(odd) .postlink,
.tag:nth-child(odd) .taglink {
grid-template-areas:
'img h2'
'img info'
'img .';
grid-template-columns: 45% auto;;
--color-primary: var(--color-teal);
--color-accent: var(--color-pink);
}
/* Even-numbered posts & tags layout/coloration */
.post:nth-child(even) .postlink,
.tag:nth-child(even) .taglink {
grid-template-areas:
'h2 img'
'info img'
'. img';
grid-template-columns: auto 45%;
--color-primary: var(--color-pink);
--color-accent: var(--color-teal);
}
/* Layout for all posts on mobile */
@media (max-width: 650px) {
.post:nth-child(n) .postlink,
.tag:nth-child(n) .taglink {
grid-template-areas:
'img'
'h2'
'info';
grid-template-columns: auto;
}
}
/* Link */
.postlink,
.taglink {
display: grid;
border: .25rem solid var(--color-primary);
border-radius: 1.25rem;
box-shadow: .35rem .35rem var(--color-shadow);
margin: 2rem 0;
text-decoration: none;
/* Click animation handling */
position: relative;
top: 0;
left: 0;
transition: top .05s ease-in, left .05s ease-in;
}
.postlink:focus-visible,
.taglink:focus-visible {
background-color: var(--color-primary);
outline: none;
}
@media (any-hover: hover) {
.postlink:hover,
.taglink:hover {
background-color: var(--color-primary);
}
}
/* Forced colors */
@media (forced-colors: active) {
.postlink:focus-visible,
.taglink:focus-visible {
outline-offset: .25rem;
outline: .25rem solid;
}
@media (any-hover: hover) {
.postlink:hover,
.taglink:hover {
outline-offset: .25rem;
outline: .25rem solid;
}
}
}
/* Click animation */
.postlink:active,
.taglink:active {
box-shadow: none;
top: .2rem;
left: .2rem;
box-shadow: .15rem .15rem var(--color-shadow);
}
/* Post & tag elements */
.post h2, .post img,
.post ul, .post li,
.tag h2, .tag p,
.tag img {
margin: 0;
}
.post h2,
.tag h2 {
grid-area: h2;
padding: .25rem .5rem;
text-transform: uppercase;
font-size: 1.5rem;
color: var(--color-primary);
border-radius: 1rem 1rem 0 0;
border-bottom: .25rem solid var(--color-accent);
}
.post:nth-child(even) h2,
.tag:nth-child(even) h2 {
text-align: right;
}
.postlink:focus-visible h2,
.taglink:focus-visible h2 {
color: var(--color-bg);
border-color: var(--color-bg);
}
@media (any-hover: hover) {
.postlink:hover h2,
.taglink:hover h2 {
color: var(--color-bg);
border-color: var(--color-bg);
}
}
/* Images */
.post img,
.tag-imgs {
grid-area: img;
}
.tag-imgs {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: .15rem;
}
.tag-imgs img {
aspect-ratio: 3 / 2;
object-fit: cover;
}
.missing-image {
width: 100%;
aspect-ratio: 3 / 2;
background-color: var(--color-bg-alt);
border-radius: calc(1rem);
}
.taglink:focus-visible .missing-image {
opacity: .7;
}
@media (any-hover: hover) {
.taglink:hover .missing-image {
opacity: .7;
}
}
/* Post tags */
.postlist-tags {
grid-area: info;
list-style: none;
display: flex;
flex-flow: row wrap;
gap: .5rem;
padding: .5rem;
}
.post:nth-child(odd) .postlist-tags {
justify-content: flex-end;
}
.postlist-tags li,
.tagcount {
background-color: var(--color-primary);
color: var(--color-bg);
padding: 0 .5rem;
border-radius: 1rem;
}
.postlink:focus-visible .postlist-tags li,
.taglink:focus-visible .tagcount {
background-color: var(--color-bg);
color: var(--color-primary);
}
@media (any-hover: hover) {
.postlink:hover .postlist-tags li,
.taglink:hover .tagcount {
background-color: var(--color-bg);
color: var(--color-primary);
}
}
/* Tag count */
.tag p {
grid-area: info;
padding: .5rem;
}
.tag:nth-child(odd) p {
text-align: right;
}
:root {
color-scheme: light dark;
--font-family: 'Atkinson Hyperlegible Next', sans-serif;
--font-family-code: 'Atkinson Hyperlegible Mono', monospace;
--color-dark: #2e303e;
--color-dark-alt: #3c3f52;
--color-light: #ebeeef;
--color-light-alt: #dbe1e3;
--color-teal-dark: #18737b;
--color-teal-light: #25b0bc;
--color-pink-dark: #94195d;
--color-pink-light: #ee9fcb;
--color-shadow: rgba(2, 10, 40, .25);
/* Used for syntax highlighting */
--color-red-light: #e95678;
--color-orange-light: #fab795;
--color-yellow-light: #fbe6bc;
--color-green-light: #29d398;
--color-blue-light: #26bbd9;
--color-purple-light: #ddaeea;
--color-grey-light: #b9c3c6;
--color-red-dark: #991433;
--color-orange-dark: #883206;
--color-yellow-dark: #6a4906;
--color-green-dark: #125940;
--color-blue-dark: #125663;
--color-purple-dark: #722999;
--color-grey-dark: #4a4b64;
--color-text: light-dark(var(--color-dark), var(--color-light));
--color-bg: light-dark(var(--color-light), var(--color-dark));
--color-text-alt: light-dark(var(--color-dark-alt), var(--color-light-alt));
--color-bg-alt: light-dark(var(--color-light-alt), var(--color-dark-alt));
--color-teal: light-dark(var(--color-teal-dark), var(--color-teal-light));
--color-pink: light-dark(var(--color-pink-dark), var(--color-pink-light));
--color-red: light-dark(var(--color-red-dark), var(--color-red-light));
--color-orange: light-dark(var(--color-orange-dark), var(--color-orange-light));
--color-yellow: light-dark(var(--color-yellow-dark), var(--color-yellow-light));
--color-green: light-dark(var(--color-green-dark), var(--color-green-light));
--color-blue: light-dark(var(--color-blue-dark), var(--color-blue-light));
--color-purple: light-dark(var(--color-purple-dark), var(--color-purple-light));
--color-grey: light-dark(var(--color-grey-dark), var(--color-grey-light));
}
/* Base */
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: var(--font-family);
color: var(--color-text);
background-color: var(--color-bg);
}
main {
width: 60vw;
max-width: 1000px;
margin: 0 auto;
scroll-margin-top: 7rem;
}
@media (max-width: 1050px) {
main {
width: 75vw;
}
}
@media (max-width: 650px) {
main {
width: 92vw;
}
}
/* Headers */
h1, h2, h3, h4, h5, h6 {
line-height: 1.25;
color: var(--color-teal);
}
h1 {
margin-top: 3rem;
font-size: 3.5rem;
text-align: center;
}
h2, h3, h4, h5, h6 {
scroll-margin-top: 5rem;
}
h2 {
margin-top: 2rem;
font-size: 2.2rem;
}
h3 {
margin-top: 1.5rem;
font-size: 1.6rem;
}
@media (max-width: 650px) {
h1 { font-size: 2.8rem; }
h2 { font-size: 1.8rem; }
h3 { font-size: 1.35rem; }
}
h4, h5, h6 {
margin-top: 1rem;
font-size: 1.2rem;
}
/* Images */
img {
display: block;
max-width: 100%;
height: auto;
border-radius: 1rem;
}
/* Paragraphs */
p {
margin: 1.25rem 0;
line-height: 1.4;
}
strong,
b {
font-weight: 900;
}
/* Links */
a {
color: var(--color-text);
border-radius: .25rem;
text-decoration: underline;
text-decoration-style: solid;
text-decoration-thickness: .2em;
text-decoration-color: var(--color-teal);
transition: text-decoration-thickness .5s;
}
a:focus-visible {
text-decoration: none;
outline: .15rem solid var(--color-teal);
}
@media (any-hover: hover) {
a:hover {
text-decoration-thickness: .4em;
}
}
a:active {
text-decoration-thickness: .4em;
}
/* Heading anchors */
a.ha,
span.ha-placeholder {
color: var(--color-pink);
}
span.ha-placeholder {
opacity: .55;
}
/* Lists */
::marker {
color: var(--color-pink);
}
ul, ol, dl, li {
margin-left: 1rem;
}
li {
line-height: 1.2 5;
margin-top: .65rem;
margin-bottom: .65rem;
}
li ul, li ol {
margin: .5rem 0;
}
dt {
font-weight: 900;
margin-top: .5rem;
}
dd {
margin-left: 2rem;
margin-bottom: .75rem;
}
/* Blockquotes */
blockquote {
margin: .5rem 0;
padding: 0 1rem;
border-radius: .25rem 1rem 1rem .25rem;
line-height: 1.25;
border-left: .5rem solid var(--color-pink);
}
blockquote,
blockquote p,
blockquote ol,
blockquote ul {
background-color: var(--color-bg-alt);
padding: .5rem;
}
blockquote p {
margin: 0;
}
/* Tables */
table {
width: 100%;
border-spacing: 0; /* border collapse doesn't play nice with radii */
border-radius: .3rem;
border: thin solid var(--color-pink);
}
th {
color: var(--color-bg);
background-color: var(--color-pink);
}
th code {
color: var(--color-text); /* Yes, I actually do this somewhere */
}
th, td {
padding: .5rem;
text-align: left;
}
tr:nth-child(even) { background-color: var(--color-bg-alt); }
th:not(:first-child) { border-left: thin solid var(--color-bg); }
th:first-child { border-top-left-radius: .25rem; }
th:last-child { border-top-right-radius: .25rem; }
td:not(:first-child) { border-left: thin solid var(--color-pink); }
/* Times */
time {
color: var(--color-grey);
}
/* Horizontal rules */
hr {
border: .25rem solid var(--color-pink);
margin: 2rem 0;
}
/* Used on home, reference, gallery pages */
.centered {
text-align: center;
}
/* Currently only used for resume, but it's generalizable */
.upper {
text-transform: uppercase;
}
/* Header */
header {
position: sticky;
top: 0;
background-color: var(--color-bg);
padding: .75rem 0;
z-index: 10;
border-bottom: thick solid var(--color-teal);
box-shadow: 0 .25rem .15rem var(--color-shadow);
}
/* Header links, pagination links */
header a,
.pagination a,
.webring ul a {
border-radius: 1rem;
border: .125rem solid var(--color-pink);
color: var(--color-pink);
text-decoration: none;
padding: 0 .25rem;
box-shadow: .15rem .15rem var(--color-shadow);
font-size: 1.2rem;
/* Click animation handling */
position: relative;
top: 0;
left: 0;
transition: top .05s ease-in, left .05s ease-in;
}
header a,
.pagination .older a,
.webring .prev a,
.webring .rand a {
padding-right: .35rem;
}
.pagination .newer a,
.webring .next a,
.webring .rand a {
padding-left: .35rem;
}
header a:focus-visible,
.pagination a:focus-visible,
.webring ul a:focus-visible {
color: var(--color-bg);
border-color: var(--color-pink);
background-color: var(--color-pink);
outline: none;
}
@media (any-hover: hover) {
header a:hover,
.pagination a:hover,
.webring ul a:hover {
color: var(--color-bg);
border-color: var(--color-pink);
background-color: var(--color-pink);
}
}
@media (forced-colors: active) {
header a:focus-visible,
.pagination a:focus-visible,
.webring ul a:focus-visible {
outline-offset: .125rem;
outline: .125rem solid;
}
@media (any-hover: hover) {
header a:hover,
.pagination a:hover,
.webring ul a:hover {
outline-offset: .125rem;
outline: .125rem solid;
}
}
}
/* Click animation */
header a:active,
.pagination a:active,
.webring ul a:active {
top: .1rem;
left: .1rem;
box-shadow: .05rem .05rem var(--color-shadow);
}
/* Current page */
header a[aria-current="page"] {
border-color: var(--color-teal);
color: var(--color-teal);
}
header a[aria-current="page"]:focus-visible {
color: var(--color-bg);
border-color: var(--color-teal);
background-color: var(--color-teal);
}
@media (any-hover: hover) {
header a[aria-current="page"]:hover {
color: var(--color-bg);
background-color: var(--color-teal);
border-color: var(--color-teal);
}
}
/* Header link icons, pagination icons */
header i,
.pagination i,
.webring ul i {
color: var(--color-teal);
}
header i,
.pagination .older i,
.webring .prev i,
.webring .rand i:nth-child(1) {
padding-left: .25rem;
}
.pagination .newer i,
.webring .next i,
.webring .rand i:nth-child(2) {
padding-right: .25rem;
}
header a[aria-current="page"] i {
color: var(--color-pink);
}
header a:focus-visible i,
a[aria-current="page"] a:focus-visible i,
.pagination a:focus-visible i,
.webring ul a:focus-visible i {
color: var(--color-bg);
}
@media (any-hover: hover) {
header a:hover i,
header a[aria-current="page"]:hover i,
.pagination a:hover i,
.webring ul a:hover i {
color: var(--color-bg);
}
}
/* Skip link */
#skip {
left: -999px;
position: absolute;
top: auto;
width: 1px;
height: 1px;
overflow: hidden;
z-index: -99;
}
#skip:focus-visible {
display: inline-block;
left: auto;
top: auto;
width: auto;
height: auto;
overflow: auto;
margin: 0 10%;
z-index: 999;
}
/* Nav */
header ul {
display: flex;
list-style: none;
gap: 1rem;
justify-content: center;
}
header ul,
header li {
margin: 0;
}
@media (max-width: 650px) {
.menu-text {
display: none; /* Icons only on small screens */
}
header a {
padding: .15rem .5rem;
}
header i {
padding: 0;
}
}
/* Footer */
footer {
padding: 1rem 0;
font-size: .9rem;
border-top: thick solid var(--color-pink);
}
footer ul {
display: flex;
list-style: none;
gap: .5rem;
justify-content: center;
margin: 0;
}
footer li {
margin: 0;
}
footer li:nth-child(2)::before,
footer li:nth-child(2)::after {
content: " ● " / "";
color: var(--color-teal);
}
@media (max-width: 650px) {
footer ul {
flex-flow: column;
text-align: center;
}
footer li:nth-child(2)::before,
footer li:nth-child(2)::after {
content: none;
}
}
footer a {
text-decoration-color: var(--color-pink);
}
footer a:focus-visible {
outline-color: var(--color-pink);
}
/* Pagination */
.pagination,
.pagination li {
margin: 0;
}
.pagination {
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-template-areas: "older newer";
list-style: none;
margin-top: 3rem;
}
@media (max-width: 650px) {
.post-pagination {
grid-template-columns: 1fr;
grid-template-areas:
"older"
"newer";
gap: .75rem;
}
}
.pagination .older {
grid-area: older;
}
.pagination .newer {
grid-area: newer;
text-align: right;
}
/* webring navigation */
.webring {
margin-bottom: 3rem;
}
.webring ul {
display: flex;
flex-flow: row wrap;
list-style: none;
justify-content: space-around;
gap: .35rem;
}
.webring ul,
.webring li {
margin-left: 0;
}
@media print {
/* Nav elements */
header,
footer,
nav {
display: none !important;
}
/* Base */
body {
background-color: #fff;
color: #000;
}
main {
width: 95vw;
}
h1,h2,h3,h4,h5,h6 {
color: #000;
}
/* Links */
/* Hover is not really necessary, but it's annoying when testing otherwise */
a,
a:hover {
text-decoration-style: dotted;
text-decoration-thickness: .1rem;
text-decoration-color: #000;
}
a::after{
content: " (" attr(href) ")";
}
/* Code */
code,
pre,
code[class*=language-],
pre[class*=language-] {
text-shadow: none;
background-color: var(--color-light);
color: #000 !important;
}
.token.namespace {
opacity: 1;
}
.token.block-comment,
.token.cdata,
.token.comment,
.token.doctype,
.token.prolog,
.token.punctuation,
.token.attr-name,
.token.deleted,
.token.namespace,
.token.tag,
.token.boolean,
.token.function,
.token.number,
.token.class-name,
.token.constant,
.token.property,
.token.symbol,
.token.attr-value,
.token.char,
.token.regex,
.token.string,
.token.variable,
.token.inserted,
.token.entity,
.token.operator,
.token.url,
.token.function-name,
.token.atrule,
.token.builtin,
.token.important,
.token.keyword,
.token.selector {
color: #000;
}
}</style>
<script type="module">// Thank you to https://github.com/daviddarnes/heading-anchors
// Thank you to https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
let globalInstanceIndex = 0;
class HeadingAnchors extends HTMLElement {
static register(tagName = "heading-anchors", registry = window.customElements) {
if(registry && !registry.get(tagName)) {
registry.define(tagName, this);
}
}
static attributes = {
exclude: "data-ha-exclude",
prefix: "prefix",
content: "content",
}
static classes = {
anchor: "ha",
placeholder: "ha-placeholder",
srOnly: "ha-visualhide",
}
static defaultSelector = "h2,h3,h4,h5,h6";
static css = `
.${HeadingAnchors.classes.srOnly} {
clip: rect(0 0 0 0);
height: 1px;
overflow: hidden;
position: absolute;
width: 1px;
}
.${HeadingAnchors.classes.anchor} {
position: absolute;
left: var(--ha_offsetx);
top: var(--ha_offsety);
text-decoration: none;
opacity: 0;
}
.${HeadingAnchors.classes.placeholder} {
opacity: .3;
}
.${HeadingAnchors.classes.anchor}:is(:focus-within, :hover) {
opacity: 1;
}
.${HeadingAnchors.classes.anchor},
.${HeadingAnchors.classes.placeholder} {
display: inline-block;
padding: 0 .25em;
/* Disable selection of visually hidden label */
-webkit-user-select: none;
user-select: none;
}
@supports (anchor-name: none) {
.${HeadingAnchors.classes.anchor} {
position: absolute;
left: anchor(left);
top: anchor(top);
}
}`;
get supports() {
return "replaceSync" in CSSStyleSheet.prototype;
}
get supportsAnchorPosition() {
return CSS.supports("anchor-name: none");
}
constructor() {
super();
if(!this.supports) {
return;
}
let sheet = new CSSStyleSheet();
sheet.replaceSync(HeadingAnchors.css);
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
this.headingStyles = {};
this.instanceIndex = globalInstanceIndex++;
}
connectedCallback() {
if (!this.supports) {
return;
}
this.headings.forEach((heading, index) => {
if(!heading.hasAttribute(HeadingAnchors.attributes.exclude)) {
let anchor = this.getAnchorElement(heading);
let placeholder = this.getPlaceholderElement();
// Prefers anchor position approach for better accessibility
// https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
if(this.supportsAnchorPosition) {
let anchorName = `--ha_${this.instanceIndex}_${index}`;
placeholder.style.setProperty("anchor-name", anchorName);
anchor.style.positionAnchor = anchorName;
}
heading.appendChild(placeholder);
heading.after(anchor);
}
});
}
// Polyfill-only
positionAnchorFromPlaceholder(placeholder) {
if(!placeholder) {
return;
}
let heading = placeholder.closest("h1,h2,h3,h4,h5,h6");
if(!heading.nextElementSibling) {
return;
}
// TODO next element could be more defensive
this.positionAnchor(heading.nextElementSibling);
}
// Polyfill-only
positionAnchor(anchor) {
if(!anchor || !anchor.previousElementSibling) {
return;
}
// TODO previous element could be more defensive
let heading = anchor.previousElementSibling;
this.setFontProp(heading, anchor);
if(this.supportsAnchorPosition) {
// quit early
return;
}
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
if(placeholder) {
anchor.style.setProperty("--ha_offsetx", `${placeholder.offsetLeft}px`);
anchor.style.setProperty("--ha_offsety", `${placeholder.offsetTop}px`);
}
}
setFontProp(heading, anchor) {
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
if(placeholder) {
let style = getComputedStyle(placeholder);
let props = ["font-weight", "font-size", "line-height", "font-family"];
let [weight, size, lh, family] = props.map(name => style.getPropertyValue(name));
anchor.style.setProperty("font", `${weight} ${size}/${lh} ${family}`);
let vars = style.getPropertyValue("font-variation-settings");
if(vars) {
anchor.style.setProperty("font-variation-settings", vars);
}
}
}
getAccessibleTextPrefix() {
// Useful for i18n
return this.getAttribute(HeadingAnchors.attributes.prefix) || "Jump to section titled";
}
getContent() {
if(this.hasAttribute(HeadingAnchors.attributes.content)) {
return this.getAttribute(HeadingAnchors.attributes.content);
}
return "#";
}
// Placeholder nests inside of heading
getPlaceholderElement() {
let ph = document.createElement("span");
ph.setAttribute("aria-hidden", true);
ph.classList.add(HeadingAnchors.classes.placeholder);
let content = this.getContent();
if(content) {
ph.innerHTML = content; // CHANGED HERE
}
ph.addEventListener("mouseover", (e) => {
let placeholder = e.target.closest(`.${HeadingAnchors.classes.placeholder}`);
if(placeholder) {
this.positionAnchorFromPlaceholder(placeholder);
}
});
return ph;
}
getAnchorElement(heading) {
let anchor = document.createElement("a");
anchor.href = `#${heading.id}`;
anchor.classList.add(HeadingAnchors.classes.anchor);
let content = this.getContent();
anchor.innerHTML = `<span class="${HeadingAnchors.classes.srOnly}">${this.getAccessibleTextPrefix()}: ${heading.textContent}</span>${content ? `<span aria-hidden="true">${content}</span>` : ""}`;
anchor.addEventListener("focus", e => {
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
if(anchor) {
this.positionAnchor(anchor);
}
});
anchor.addEventListener("mouseover", (e) => {
// when CSS anchor positioning is supported, this is only used to set the font
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
this.positionAnchor(anchor);
});
return anchor;
}
get headings() {
return this.querySelectorAll(this.selector.split(",").map(entry => `${entry.trim()}[id]`));
}
get selector() {
return this.getAttribute("selector") || HeadingAnchors.defaultSelector;
}
}
HeadingAnchors.register();
export { HeadingAnchors }</script>
</head>
<body>
<header>
<a href="#main" id="skip" title="skip to main content" aria-label="skip to main content">
<i class="fa-solid fa-forward" aria-hidden="true"></i> skip
</a>
<nav aria-label="main navigation">
<ul>
<li>
<a href="/reference/" title="read reference posts" aria-label="read reference posts">
<i class="fa-regular fa-folder-open" aria-hidden="true"></i>
<span class="menu-text">reference</span>
</a>
</li>
<li>
<a href="/gallery/" title="view the gallery" aria-label="view the gallery">
<i class="fa-regular fa-images" aria-hidden="true"></i>
<span class="menu-text">gallery</span>
</a>
</li>
<li>
<a href="/" title="" aria-label="">
<i class="fa fa-solid fa-crow" aria-hidden="true"></i>
<span class="menu-text">home</span>
</a>
</li>
<li>
<a href="/about/" title="about Lee" aria-label="about Lee">
<i class="fa-regular fa-user" aria-hidden="true"></i>
<span class="menu-text">about</span>
</a>
</li>
<li>
<a href="/contact/" title="contact Lee" aria-label="contact Lee">
<i class="fa-solid fa-envelope-open-text" aria-hidden="true"></i>
<span class="menu-text">contact</span>
</a>
</li>
</ul>
</nav>
</header>
<main id="main">
<heading-anchors content="<i class='fa-solid fa-anchor'></i>">
<article>
<h1 id="handedness-toggle">handedness toggle</h1>
<div class="post-metadata">
<p>
posted on
<time datetime="2024-09-01">September 1, 2024</time>
by Lee Cattarin
</p>
<ul class="post-tags">
<li>
<a href="/tags/software/">software</a>
</li>
</ul>
</div>
<img src="/img/handedness-toggle-0.png" alt="A screenshot of the rescue trans rescue navbar centered on a button that shows a hand pointing left." loading="lazy" decoding="async" width="1000" height="257">
<p>Recently, I got an iPad for art and immediately fell down the rabbit hole of <a href="https://glitch.com" target="_blank" rel="external">Glitch</a> and web development.</p>
<p>When creating the <a href="https://rescue-trans-rescue.glitch.com" target="_blank" rel="external">rescue Trans rescue site</a>, 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.</p>
<p>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?</p>
<p>Ok, let's make a toggle!</p>
<p>(Want to just see the outcome? Head on down the page to the <a href="#summary">summary</a>.)</p>
<h2 id="the-button">the button</h2>
<p>First we'll need some HTML for our button. I added this to my menu:</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>alignment<span class="token punctuation">"</span></span>
<span class="token attr-name">title</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>toggle left/right navbar alignment<span class="token punctuation">"</span></span>
<span class="token attr-name">aria-label</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>toggle left/right navbar alignment<span class="token punctuation">"</span></span>
<span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>menu-link<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
<span class="token comment">&lt;!-- autopopulated by nav.js --></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">></span></span></code></pre>
<p>It's important to note here that I added it to the <em>beginning</em> 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.</p>
<h3 id="handling-narrow-screens">handling narrow screens</h3>
<p>Let's quickly take a moment to ensure that the toggle only shows up on wider screens with a bit of CSS:</p>
<pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@media</span> <span class="token punctuation">(</span><span class="token property">max-width</span><span class="token punctuation">:</span> 500px<span class="token punctuation">)</span></span> <span class="token punctuation">{</span>
<span class="token selector">#alignment</span> <span class="token punctuation">{</span>
<span class="token property">display</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span></code></pre>
<p>Now users on phones won't have an unhelpful button taking screen space.</p>
<h2 id="nav-js">nav.js</h2>
<p>Now let's move to <code>nav.js</code> and define some consts for ease of use. We're going to use <a href="https://fontawesome.com/icons" target="_blank" rel="external">Font Awesome icons</a> for this button, so we'll go grab their HTML for the left and right pointing hands.</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> <span class="token constant">ALIGN</span> <span class="token operator">=</span> <span class="token string">"alignment"</span>
<span class="token keyword">const</span> <span class="token constant">LEFT</span> <span class="token operator">=</span> <span class="token string">"left"</span>
<span class="token keyword">const</span> <span class="token constant">RIGHT</span> <span class="token operator">=</span> <span class="token string">"right"</span>
<span class="token keyword">const</span> <span class="token constant">LEFT_ICON</span> <span class="token operator">=</span> <span class="token string">'&lt;i class="fa-regular fa-hand-point-left" aria-hidden="true">&lt;/i>'</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> <span class="token constant">RIGHT_ICON</span> <span class="token operator">=</span> <span class="token string">'&lt;i class="fa-regular fa-hand-point-right" aria-hidden="true">&lt;/i>'</span><span class="token punctuation">;</span></code></pre>
<p>We'll use <code>localStorage</code> to store and retrieve alignment preferences:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">let</span> align <span class="token operator">=</span> localStorage<span class="token punctuation">.</span><span class="token function">getItem</span><span class="token punctuation">(</span><span class="token constant">ALIGN</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>and we'll grab the button we defined in HTML:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">let</span> alignToggle <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">getElementById</span><span class="token punctuation">(</span><span class="token constant">ALIGN</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<h3 id="setting-icons">setting icons</h3>
<p>Next, let's structure out some functions. We'll fill them in more as we figure out what we need.</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">function</span> <span class="token function">setAlignRight</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token comment">// If we're aligned on the right, the toggle should point to the left</span>
alignToggle<span class="token punctuation">.</span>innerHTML <span class="token operator">=</span> <span class="token constant">LEFT_ICON</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">function</span> <span class="token function">setAlignLeft</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token comment">// If we're aligned on the left, the toggle should point to the right</span>
alignToggle<span class="token punctuation">.</span>innerHTML <span class="token operator">=</span> <span class="token constant">RIGHT_ICON</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token comment">// This function changes the alignment to the given value</span>
<span class="token comment">// It also runs at startup to set alignment</span>
<span class="token keyword">function</span> <span class="token function">changeAlign</span><span class="token punctuation">(</span><span class="token parameter">align</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">switch</span> <span class="token punctuation">(</span>align<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">case</span> <span class="token constant">LEFT</span><span class="token operator">:</span>
<span class="token function">setAlignLeft</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">break</span><span class="token punctuation">;</span>
<span class="token keyword">case</span> <span class="token keyword">null</span><span class="token operator">:</span>
<span class="token comment">// If nothing is set, default to right alignment</span>
align <span class="token operator">=</span> <span class="token constant">RIGHT</span><span class="token punctuation">;</span>
<span class="token keyword">case</span> <span class="token constant">RIGHT</span><span class="token operator">:</span>
<span class="token function">setAlignRight</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">break</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token comment">// Set localStorage for next time</span>
localStorage<span class="token punctuation">.</span><span class="token function">setItem</span><span class="token punctuation">(</span><span class="token constant">ALIGN</span><span class="token punctuation">,</span> align<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token comment">// Run at startup</span>
<span class="token function">changeAlign</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// This function handles the actual flip-flopping of the alignment value</span>
<span class="token keyword">function</span> <span class="token function">toggleAlign</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>align <span class="token operator">===</span> <span class="token constant">LEFT</span><span class="token punctuation">)</span> align <span class="token operator">=</span> <span class="token constant">RIGHT</span><span class="token punctuation">;</span>
<span class="token keyword">else</span> align <span class="token operator">=</span> <span class="token constant">LEFT</span><span class="token punctuation">;</span>
<span class="token function">changeAlign</span><span class="token punctuation">(</span>align<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token comment">// Attach the toggle function to the alignToggle as a click listener</span>
alignToggle<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">"click"</span><span class="token punctuation">,</span> toggleAlign<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>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?</p>
<h3 id="alignment">alignment</h3>
<p>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:</p>
<pre class="language-css"><code class="language-css"><span class="token selector">#navbar</span> <span class="token punctuation">{</span>
<span class="token property">position</span><span class="token punctuation">:</span> sticky<span class="token punctuation">;</span>
<span class="token property">top</span><span class="token punctuation">:</span> 0 px<span class="token punctuation">;</span>
<span class="token property">width</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span>
<span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">;</span>
<span class="token comment">/* This is the line that matters to us */</span>
<span class="token comment">/* we'll want to swap between flex-start and flex-end */</span>
<span class="token property">justify-content</span><span class="token punctuation">:</span> flex-end<span class="token punctuation">;</span>
<span class="token punctuation">}</span></code></pre>
<p>Let's add that <code>justify-content</code> setting to our JS (as well as a line to fetch the navbar by id):</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">let</span> navbar <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">getElementById</span><span class="token punctuation">(</span><span class="token string">"navbar"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">function</span> <span class="token function">setAlignRight</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
alignToggle<span class="token punctuation">.</span>innerHTML <span class="token operator">=</span> <span class="token constant">LEFT_ICON</span><span class="token punctuation">;</span>
navbar<span class="token punctuation">.</span>style<span class="token punctuation">.</span>justifyContent <span class="token operator">=</span> <span class="token string">"flex-end"</span><span class="token punctuation">;</span>
<span class="token comment">// If you have other necessary style changes, add them here</span>
<span class="token punctuation">}</span>
<span class="token keyword">function</span> <span class="token function">setAlignLeft</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
alignToggle<span class="token punctuation">.</span>innerHTML <span class="token operator">=</span> <span class="token constant">RIGHT_ICON</span><span class="token punctuation">;</span>
navbar<span class="token punctuation">.</span>style<span class="token punctuation">.</span>justifyContent <span class="token operator">=</span> <span class="token string">"flex-start"</span><span class="token punctuation">;</span>
<span class="token comment">// If you have other necessary style changes, add them here</span>
<span class="token punctuation">}</span></code></pre>
<p>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.</p>
<p>(Forgive the lack of continuity with the header image.)</p>
<p><img src="/img/handedness-toggle-1.png" alt="the navbar of this site in dark mode. from left to right, there's pink and blue icons of: a hand pointing right, images, file folder, user/person, opened mail, and a lightbulb." loading="lazy" decoding="async" width="1000" height="315"></p>
<h3 id="moving-the-button">moving the button</h3>
<p>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 <code>prepend()</code> and <code>append()</code>.</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">function</span> <span class="token function">setAlignRight</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
alignToggle<span class="token punctuation">.</span>innerHTML <span class="token operator">=</span> <span class="token constant">LEFT_ICON</span><span class="token punctuation">;</span>
navbar<span class="token punctuation">.</span>style<span class="token punctuation">.</span>justifyContent <span class="token operator">=</span> <span class="token string">"flex-end"</span><span class="token punctuation">;</span>
navbar<span class="token punctuation">.</span><span class="token function">prepend</span><span class="token punctuation">(</span>alignToggle<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">function</span> <span class="token function">setAlignLeft</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
alignToggle<span class="token punctuation">.</span>innerHTML <span class="token operator">=</span> <span class="token constant">RIGHT_ICON</span><span class="token punctuation">;</span>
navbar<span class="token punctuation">.</span>style<span class="token punctuation">.</span>justifyContent <span class="token operator">=</span> <span class="token string">"flex-start"</span><span class="token punctuation">;</span>
navbar<span class="token punctuation">.</span><span class="token function">append</span><span class="token punctuation">(</span>alignToggle<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span></code></pre>
<p>Cool! Now we have a menu that re-aligns itself <em>and</em> repositions the alignment button.</p>
<p><img src="/img/handedness-toggle-2.png" alt="The same navbar, now with elements reordered. The hand pointing right now lands at the end of the menu. Incidentally, the theme button has also been swapped and is at the start of the menu but that's not relevant." loading="lazy" decoding="async" width="1000" height="300"></p>
<h3 id="keyboard-navigation">keyboard navigation</h3>
<p>Oooh, but wait: keyboard navigation is broken.</p>
<p>When I tab over to the alignment button and hit <code>Enter</code>/<code>space</code>, it does what we expect, but it <em>also</em> loses keyboard focus. Because of that little <code>prepend()</code>/<code>append()</code> 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 <code>alignToggle</code> manually, so it's not lost.</p>
<p>We can do that with the <code>.focus()</code> function:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">function</span> <span class="token function">toggleAlign</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>align <span class="token operator">===</span> <span class="token constant">LEFT</span><span class="token punctuation">)</span> align <span class="token operator">=</span> <span class="token constant">RIGHT</span><span class="token punctuation">;</span>
<span class="token keyword">else</span> align <span class="token operator">=</span> <span class="token constant">LEFT</span><span class="token punctuation">;</span>
<span class="token function">changeAlign</span><span class="token punctuation">(</span>align<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// Replace focus on the toggle that's been moved</span>
alignToggle<span class="token punctuation">.</span><span class="token function">focus</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span></code></pre>
<p>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?</p>
<h3 id="managing-focus">managing focus</h3>
<p><code>toggleAlign()</code> is an event handler, which means it can optionally take an <code>event</code> var. For &quot;click&quot; events, that <code>event</code> var includes a field <code>detail</code> which provides the <em>click count</em>. This can be used to disambiguate single vs double clicks, <em>or</em> it can be used to test for keyboard interaction, which creates <em>zero clicks</em>.</p>
<p>Let's add that in:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">function</span> <span class="token function">toggleAlign</span><span class="token punctuation">(</span><span class="token parameter">event</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>align <span class="token operator">===</span> <span class="token constant">LEFT</span><span class="token punctuation">)</span> align <span class="token operator">=</span> <span class="token constant">RIGHT</span><span class="token punctuation">;</span>
<span class="token keyword">else</span> align <span class="token operator">=</span> <span class="token constant">LEFT</span><span class="token punctuation">;</span>
<span class="token function">changeAlign</span><span class="token punctuation">(</span>align<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// Zero clicks means this was a keyboard interaction</span>
<span class="token comment">// Replace focus on the toggle that's been moved</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>event<span class="token punctuation">.</span>detail <span class="token operator">===</span> <span class="token number">0</span><span class="token punctuation">)</span> alignToggle<span class="token punctuation">.</span><span class="token function">focus</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span></code></pre>
<p>Now we should only replace visible focus for keyboard interactions.</p>
<p><img src="/img/handedness-toggle-3.png" alt="Navbar showing handedness toggle in focus, pointing right. It has a bright blue outline, pink background, and dark icon image." loading="lazy" decoding="async" width="1000" height="270"></p>
<h2 id="thanks-for-reading">thanks for reading</h2>
<p>Want to see further changes? Found a bug with this implementation? <a href="/contact">Contact me</a>!</p>
<hr>
<h2 id="summary">summary</h2>
<p>Here's the referenced HTML, CSS, and JS in full:</p>
<h3 id="html">HTML</h3>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>alignment<span class="token punctuation">"</span></span>
<span class="token attr-name">title</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>toggle left/right navbar alignment<span class="token punctuation">"</span></span>
<span class="token attr-name">aria-label</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>toggle left/right navbar alignment<span class="token punctuation">"</span></span>
<span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>menu-link<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
<span class="token comment">&lt;!-- autopopulated by nav.js --></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">></span></span></code></pre>
<h3 id="css">CSS</h3>
<pre class="language-css"><code class="language-css"><span class="token selector">#navbar</span> <span class="token punctuation">{</span>
<span class="token property">position</span><span class="token punctuation">:</span> sticky<span class="token punctuation">;</span>
<span class="token property">top</span><span class="token punctuation">:</span> 0 px<span class="token punctuation">;</span>
<span class="token property">width</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span>
<span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">;</span>
<span class="token comment">/* nav.js handles justify-content instead */</span>
<span class="token comment">/* justify-content: flex-end; */</span>
<span class="token punctuation">}</span>
<span class="token comment">/* Remove the handedness toggle on narrow screens */</span>
<span class="token atrule"><span class="token rule">@media</span> <span class="token punctuation">(</span><span class="token property">max-width</span><span class="token punctuation">:</span> 500px<span class="token punctuation">)</span></span> <span class="token punctuation">{</span>
<span class="token selector">#alignment</span> <span class="token punctuation">{</span>
<span class="token property">display</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span></code></pre>
<h3 id="js">JS</h3>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> <span class="token constant">ALIGN</span> <span class="token operator">=</span> <span class="token string">"alignment"</span>
<span class="token keyword">const</span> <span class="token constant">LEFT</span> <span class="token operator">=</span> <span class="token string">"left"</span>
<span class="token keyword">const</span> <span class="token constant">RIGHT</span> <span class="token operator">=</span> <span class="token string">"right"</span>
<span class="token keyword">const</span> <span class="token constant">LEFT_ICON</span> <span class="token operator">=</span> <span class="token string">'&lt;i class="fa-regular fa-hand-point-left" aria-hidden="true">&lt;/i>'</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> <span class="token constant">RIGHT_ICON</span> <span class="token operator">=</span> <span class="token string">'&lt;i class="fa-regular fa-hand-point-right" aria-hidden="true">&lt;/i>'</span><span class="token punctuation">;</span>
<span class="token keyword">let</span> align <span class="token operator">=</span> localStorage<span class="token punctuation">.</span><span class="token function">getItem</span><span class="token punctuation">(</span><span class="token constant">ALIGN</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">let</span> alignToggle <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">getElementById</span><span class="token punctuation">(</span><span class="token constant">ALIGN</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">function</span> <span class="token function">setAlignRight</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
alignToggle<span class="token punctuation">.</span>innerHTML <span class="token operator">=</span> <span class="token constant">LEFT_ICON</span><span class="token punctuation">;</span>
navbar<span class="token punctuation">.</span>style<span class="token punctuation">.</span>justifyContent <span class="token operator">=</span> <span class="token string">"flex-end"</span><span class="token punctuation">;</span>
navbar<span class="token punctuation">.</span><span class="token function">prepend</span><span class="token punctuation">(</span>alignToggle<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">function</span> <span class="token function">setAlignLeft</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
alignToggle<span class="token punctuation">.</span>innerHTML <span class="token operator">=</span> <span class="token constant">RIGHT_ICON</span><span class="token punctuation">;</span>
navbar<span class="token punctuation">.</span>style<span class="token punctuation">.</span>justifyContent <span class="token operator">=</span> <span class="token string">"flex-start"</span><span class="token punctuation">;</span>
navbar<span class="token punctuation">.</span><span class="token function">append</span><span class="token punctuation">(</span>alignToggle<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">function</span> <span class="token function">changeAlign</span><span class="token punctuation">(</span><span class="token parameter">align</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">switch</span> <span class="token punctuation">(</span>align<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">case</span> <span class="token constant">LEFT</span><span class="token operator">:</span>
<span class="token function">setAlignLeft</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">break</span><span class="token punctuation">;</span>
<span class="token keyword">case</span> <span class="token keyword">null</span><span class="token operator">:</span>
align <span class="token operator">=</span> <span class="token constant">RIGHT</span><span class="token punctuation">;</span>
<span class="token keyword">case</span> <span class="token constant">RIGHT</span><span class="token operator">:</span>
<span class="token function">setAlignRight</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">break</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
localStorage<span class="token punctuation">.</span><span class="token function">setItem</span><span class="token punctuation">(</span><span class="token constant">ALIGN</span><span class="token punctuation">,</span> align<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token function">changeAlign</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">function</span> <span class="token function">toggleAlign</span><span class="token punctuation">(</span><span class="token parameter">event</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>align <span class="token operator">===</span> <span class="token constant">LEFT</span><span class="token punctuation">)</span> align <span class="token operator">=</span> <span class="token constant">RIGHT</span><span class="token punctuation">;</span>
<span class="token keyword">else</span> align <span class="token operator">=</span> <span class="token constant">LEFT</span><span class="token punctuation">;</span>
<span class="token function">changeAlign</span><span class="token punctuation">(</span>align<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>event<span class="token punctuation">.</span>detail <span class="token operator">===</span> <span class="token number">0</span><span class="token punctuation">)</span> alignToggle<span class="token punctuation">.</span><span class="token function">focus</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
alignToggle<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">"click"</span><span class="token punctuation">,</span> toggleAlign<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
</article>
<nav aria-label="pagination">
<ol class="pagination post-pagination">
<li class="older">
<a href="/rescue-trans-rescue/">
<i class="fa-solid fa-hand-point-left" aria-hidden="true"></i> rescue Trans Rescue
</a>
</li>
<li class="newer">
<a href="/on-the-ubiquity-of-enby/">
on the ubiquity of &#39;enby&#39; <i class="fa-solid fa-hand-point-right" aria-hidden="true"></i>
</a>
</li>
</ol>
</nav>
<hr>
<section class="related-posts">
<h2 id="related-posts">related posts</h2>
<ol id="postlist">
<li class="post">
<a class="postlink" href="/domain-and-site-setup/">
<img src="/img/crinkly-mushrooms.jpg" alt="Picture unrelated to post. Some crinkly brown-orange mushrooms in vibrant green grass." loading="lazy" decoding="async" width="1000" height="750">
<h2 id="domain-and-site-setup">domain and site setup</h2>
<ul class="postlist-tags">
<li>software</li>
</ul>
</a>
</li>
<li class="post">
<a class="postlink" href="/an-intro-to-git/">
<img src="/img/goldeneye-tail.jpg" alt="Image unrelated to post. The tail of a diving duck pokes out from the water with a small splash." loading="lazy" decoding="async" width="1000" height="666">
<h2 id="an-intro-to-git">an intro to git</h2>
<ul class="postlist-tags">
<li>software</li>
</ul>
</a>
</li>
<li class="post">
<a class="postlink" href="/azure-locations-and-file-crawling/">
<img src="/img/azure-locations.jpg" alt="A Linux terminal. There is a fun rainbow flag in ascii art at the top, and then the user has called a command asking Azure for a list of resources applicable to a specific resource type" loading="lazy" decoding="async" width="1000" height="827">
<h2 id="azure-locations-and-file-crawling">azure locations and file crawling</h2>
<ul class="postlist-tags">
<li>software</li>
<li>highlight</li>
</ul>
</a>
</li>
</ol>
</section>
</heading-anchors>
</main>
<footer>
<ul>
<li>
<a href="/colophon" title="colophon" aria-label="colophon">
colophon
</a>
</li>
<li>
<a href="/" title="go home" aria-label="go home | hello hello from Lee Cattarin in 2026">
hello hello from Lee Cattarin in 2026</a>
</li>
<li>
<a href="https://heckin.technology/inherentlee/leecat.art" title="source code" aria-label="source code" target="_blank" rel="external">
src
</a>
</li>
</ul>
</footer>
<!-- This page `/handedness-toggle/` was built on 2026-02-20T16:46:10.318Z -->
<body>
</body></body>