2026-05-15 09:20:43 -07:00
<!doctype html>
< html lang = "en" >
< head >
< meta charset = "utf-8" >
< meta name = "viewport" content = "width=device-width, initial-scale=1.0" >
< link rel = "icon" type = "image/x-icon" href = "/favicon.ico" >
< title > leatherworking favorites | 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 = "leatherworking favorites | hello hello" >
< meta property = "og:type" content = "website" >
< meta property = "og:description" content = "Lee Cattarin... on the internet!" >
< meta property = "og:site_name" content = "hello hello" >
2026-08-26 23:55:56 +00:00
< meta property = "og:url" content = "https://leecat.art/leatherworking-favorites/" >
2026-05-15 09:20:43 -07:00
2026-09-15 01:17:56 +00:00
< meta property = "og:image" content = "https://leecat.art/img/leather-harness-wip.jpg" >
2026-05-15 09:20:43 -07:00
< meta property = "og:image:alt" content = "two pieces of a chest harness sitting on a messy workbench. both pieces are about 8 inches long total and consist of two large o-rings joined by a dark teal leather strap. the o rings and rivets are matte black." >
2026-10-06 16:54:14 +00:00
< meta name = "generator" content = "Eleventy v3.1.6" >
2026-05-15 09:20:43 -07:00
2026-05-21 23:35:46 +00:00
< meta itemprop = "ai-content-declaration:version" content = "1.0.0" >
< meta itemprop = "ai-content-declaration:level" content = "none" >
2026-05-15 09:20:43 -07:00
< 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 >
2026-09-08 17:40:59 +00:00
2026-09-11 18:57:10 +00:00
2026-09-20 22:08:31 +00:00
< style ># toc {
float : right ;
margin : 1 rem 0 1.5 rem 1.5 rem ;
2026-09-21 22:56:13 +00:00
width : fit-content ;
max-width : 55 % ;
2026-09-20 22:08:31 +00:00
}
2026-09-21 23:08:43 +00:00
@ media ( max-width : 900px ) {
2026-09-20 22:08:31 +00:00
# toc {
float : unset ;
margin-top : 2 rem ;
margin-left : .5 rem ;
width : calc ( 100 % - .5 rem );
2026-09-21 22:56:13 +00:00
max-width : unset ;
2026-09-20 22:08:31 +00:00
}
}
# toc details {
border : thick solid var ( -- color - pink );
border-radius : 1 rem ;
margin-top : 1 rem ;
2026-09-21 02:02:27 +00:00
background-color : var ( -- color - bg );
2026-09-20 22:08:31 +00:00
}
# toc summary {
position : relative ;
top : -1.5 rem ;
left : -.75 rem ;
width : fit-content ;
background : var ( -- color - bg );
border-radius : .5 rem ;
2026-09-21 01:46:44 +00:00
cursor : pointer ;
2026-09-21 22:56:13 +00:00
padding-inline-end : .25 rem ;
2026-09-20 22:08:31 +00:00
}
# toc summary : focus-visible {
outline : .15 rem solid var ( -- color - teal );
}
# toc summary :: marker {
font-size : 2.35 rem ;
}
# toc h2 {
margin-top : 0 ;
display : inline ;
line-height : normal ;
}
# toc nav {
position : relative ;
top : -1.25 rem ;
}
2026-09-21 22:40:57 +00:00
/* `tree` style toc */
# toc nav > ol {
2026-09-21 22:56:13 +00:00
margin-inline : 1 rem ;
2026-09-20 22:08:31 +00:00
}
2026-09-21 01:46:44 +00:00
# toc ol {
2026-09-21 22:40:57 +00:00
margin : 0 ;
2026-09-20 22:08:31 +00:00
}
2026-09-21 01:46:44 +00:00
# toc li {
2026-09-21 22:40:57 +00:00
position : relative ;
padding-inline-start : 1 em ;
list-style : none ;
margin-block : 0 ;
margin-left : .35 rem ;
}
# toc li :: before ,
# toc li :: after {
display : block ;
position : absolute ;
box-sizing : border-box ;
inset-block : 0 auto ;
inset-inline : 0 auto ;
inline-size : .75 em ;
border-inline-start : .1 rem solid var ( -- color - pink );
2026-09-20 22:08:31 +00:00
}
2026-09-21 01:46:44 +00:00
# toc li :: before {
2026-09-21 22:40:57 +00:00
block-size : .5 lh ;
border-block-end : .1 rem solid var ( -- color - pink );
content : "" ;
2026-09-20 22:08:31 +00:00
}
2026-09-21 22:40:57 +00:00
# toc li : not ( : last-child ) :: after {
block-size : 100 % ;
content : "" ;
2026-09-21 01:46:44 +00:00
}
/* underlines are unnecessary in this context */
# toc a {
text-decoration : none ;
color : var ( -- color - blue );
font-weight : bold ;
2026-09-20 22:08:31 +00:00
}
2026-09-21 15:39:30 +00:00
# toc a : focus-visible {
outline-color : var ( -- color - pink );
}
2026-09-20 22:08:31 +00:00
. post-metadata {
2026-05-15 09:20:43 -07:00
display : flex ;
flex-flow : row wrap ;
justify-content : space-between ;
align-items : baseline ;
margin : 1.5 rem 0 .5 rem ;
}
. post-metadata p {
font-size : .9 rem ;
margin : 0 ;
}
. post-tags {
display : flex ;
flex-flow : row wrap ;
gap : .5 rem ;
list-style : none ;
margin : 0 ;
2026-08-25 20:13:52 +00:00
margin-left : auto ;
2026-05-15 09:20:43 -07:00
}
. post-tags li {
margin : 0 ;
}
. post-tags li a {
text-decoration : none ;
color : var ( -- color - teal );
padding : 0 .5 rem ;
border-radius : 1 rem ;
box-shadow : .15 rem .15 rem var ( -- color - shadow );
border : .08 rem solid var ( -- color - teal );
line-height : 2 ;
/* Click animation handling */
position : relative ;
top : 0 ;
left : 0 ;
transition : top .1 s ease-in , left .1 s 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 : .08 rem ;
outline : .08 rem solid ;
}
@ media ( any-hover : hover ) {
. post-tags li a : hover {
outline-offset : .08 rem ;
outline : .08 rem solid ;
}
}
}
/* Click animation */
. post-tags li a : active {
top : .1 rem ;
left : .1 rem ;
box-shadow : .05 rem .05 rem var ( -- color - shadow );
}
# 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 : .25 rem solid var ( -- color - primary );
border-radius : 1.25 rem ;
box-shadow : .35 rem .35 rem var ( -- color - shadow );
margin : 2 rem 0 ;
text-decoration : none ;
/* Click animation handling */
position : relative ;
top : 0 ;
left : 0 ;
transition : top .05 s ease-in , left .05 s 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 : .25 rem ;
outline : .25 rem solid ;
}
@ media ( any-hover : hover ) {
. postlink : hover ,
. taglink : hover {
outline-offset : .25 rem ;
outline : .25 rem solid ;
}
}
}
/* Click animation */
. postlink : active ,
. taglink : active {
top : .2 rem ;
left : .2 rem ;
box-shadow : .15 rem .15 rem 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 : .25 rem .5 rem ;
text-transform : uppercase ;
2026-09-20 00:41:04 +00:00
font-family : var ( -- font - family );
2026-05-15 09:20:43 -07:00
font-size : 1.5 rem ;
color : var ( -- color - primary );
border-radius : 1 rem 1 rem 0 0 ;
border-bottom : .25 rem 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 ;
}
. blur {
filter : blur ( .75 rem );
clip-path : inset ( 0 round 1 rem );
}
. tag-imgs {
display : grid ;
grid-template-columns : repeat ( 2 , 1 fr );
gap : .15 rem ;
}
. 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 : 1 rem ;
}
. 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 : .5 rem ;
padding : .5 rem ;
}
. 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 .5 rem ;
border-radius : 1 rem ;
}
. 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 : .5 rem ;
}
. tag : nth-child ( odd ) p {
text-align : right ;
}
img [ tabindex = "0" ] {
cursor : pointer ;
}
img [ tabindex = "0" ] : focus-visible {
outline : .15 rem solid var ( -- color - teal );
}
dialog {
margin : auto ;
flex-flow : column ;
background : transparent ;
border : none ;
position : absolute ;
}
dialog [ open ] {
display : flex ;
}
dialog :: backdrop {
background-color : rgba ( from var ( -- color - bg ) r g b / .8 );
backdrop-filter : blur ( 4 px );
}
body : has ( dialog [ open ]) {
overflow : hidden ;
}
. close-dialog {
background-color : rgba ( from var ( -- color - teal ) r g b / .35 );
font-size : 1.5 rem ;
padding : 0 .5 rem .15 rem ;
border-radius : 1 rem ;
color : var ( -- color - teal );
box-shadow : .15 rem .15 rem var ( -- color - shadow );
border : .08 rem solid var ( -- color - teal );
align-self : flex-end ;
justify-self : flex-start ;
margin : 0 .1 rem ;
/* Click animation handling */
position : relative ;
top : 1 rem ;
left : -.15 rem ;
transition : top .1 s ease-in , left .1 s ease-in ;
}
. close-dialog : focus-visible {
outline : none ;
background-color : var ( -- color - teal );
color : var ( -- color - bg );
}
@ media ( any-hover : hover ) {
. close-dialog : hover {
outline : none ;
background-color : var ( -- color - teal );
color : var ( -- color - bg );
}
}
@ media ( forced-colors : active ) {
. close-dialog : focus-visible {
outline-offset : .08 rem ;
outline : .08 rem solid ;
}
@ media ( any-hover : hover ) {
. close-dialog : hover {
outline-offset : .08 rem ;
outline : .08 rem solid ;
}
}
}
/* Click animation */
. close-dialog : active {
top : 1.1 rem ;
left : -.05 rem ;
box-shadow : .05 rem .05 rem var ( -- color - shadow );
}
. modal-img {
2026-08-21 00:13:19 +00:00
max-height : calc ( 100 vh - 2 rem );
2026-05-15 09:20:43 -07:00
object-fit : contain ;
width : auto ;
max-width : 90 % ;
margin : 0 auto ;
}
2026-09-20 00:41:04 +00:00
@ font-face {
font-family : "Peritel" ;
src :
url ( "/fonts/Peritel/Peritel-Regular.woff2" ) format ( "woff2" ),
url ( "/fonts/Peritel/Peritel-Regular.otf" ) format ( "opentype" );
}
2026-05-15 09:20:43 -07:00
: root {
color-scheme : light dark ;
--font-family : 'Atkinson Hyperlegible Next' , sans-serif ;
2026-09-20 22:08:31 +00:00
/* if you change the header font, go to post-toc.css and fuck about with the width
of the table of contents */
2026-09-20 00:41:04 +00:00
--font-family-headers : 'Peritel' , 'Atkinson Hyperlegible Next' , sans-serif ;
2026-05-15 09:20:43 -07:00
--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 : #f195aa ;
--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 ));
}
2026-09-09 20:38:19 +00:00
: root : has ( # theme-light : checked ) {
color-scheme : light ;
}
: root : has ( # theme-dark : checked ) {
color-scheme : dark ;
}
2026-05-15 09:20:43 -07:00
/* 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 : 60 vw ;
max-width : 1000 px ;
margin : 0 auto ;
}
2026-09-21 23:08:43 +00:00
@ media ( max-width : 1100px ) {
2026-05-15 09:20:43 -07:00
main {
width : 75 vw ;
}
}
@ media ( max-width : 650px ) {
main {
width : 92 vw ;
}
}
/* Headers */
h1 , h2 , h3 , h4 , h5 , h6 {
line-height : 1.25 ;
color : var ( -- color - teal );
2026-09-20 00:41:04 +00:00
font-family : var ( -- font - family - headers );
letter-spacing : 0.1 rem ;
2026-05-15 09:20:43 -07:00
}
h1 {
margin-top : 3 rem ;
2026-09-20 00:41:04 +00:00
font-size : 3.7 rem ;
2026-05-15 09:20:43 -07:00
text-align : center ;
}
h2 {
margin-top : 2 rem ;
2026-09-20 00:41:04 +00:00
font-size : 2.35 rem ;
2026-05-15 09:20:43 -07:00
}
h3 {
margin-top : 1.5 rem ;
2026-09-20 00:41:04 +00:00
font-size : 1.7 rem ;
2026-05-15 09:20:43 -07:00
}
@ media ( max-width : 650px ) {
2026-10-05 23:59:36 +00:00
h1 {
2026-10-06 16:05:42 +00:00
margin-top : 1.5 rem ;
2026-10-05 23:59:36 +00:00
font-size : 2.25 rem ;
}
h2 { font-size : 1.8 rem ; }
2026-09-20 00:41:04 +00:00
h3 { font-size : 1.4 rem ; }
2026-05-15 09:20:43 -07:00
}
h4 , h5 , h6 {
margin-top : 1 rem ;
2026-09-20 00:41:04 +00:00
font-size : 1.25 rem ;
2026-05-15 09:20:43 -07:00
}
/* Images */
img {
display : block ;
max-width : 100 % ;
height : auto ;
border-radius : 1 rem ;
margin : 0 auto ;
}
/* Paragraphs */
p {
margin : 1.25 rem 0 ;
line-height : 1.4 ;
}
strong ,
b {
font-weight : 900 ;
}
/* Links */
a {
color : var ( -- color - text );
border-radius : .25 rem ;
text-decoration : underline ;
text-decoration-style : solid ;
text-decoration-thickness : .2 em ;
text-decoration-color : var ( -- color - teal );
transition : text - decoration-thickness .5 s ;
}
a : focus-visible {
text-decoration : none ;
outline : .15 rem solid var ( -- color - teal );
}
@ media ( any-hover : hover ) {
a : hover {
text-decoration-thickness : .4 em ;
}
}
a : active {
text-decoration-thickness : .4 em ;
}
/* Heading anchors */
a . ha ,
span . ha-placeholder {
color : var ( -- color - pink );
font-size : .8 em ;
vertical-align : .1 em ;
}
span . ha-placeholder {
opacity : .55 ;
}
/* Lists */
:: marker {
color : var ( -- color - pink );
}
ul , ol , dl , li {
margin-left : 1 rem ;
}
@ media ( max-width : 650px ) {
ul , ol , dl {
margin-left : 0 ;
}
}
li {
2026-09-20 22:13:55 +00:00
line-height : 1.4 ;
2026-05-15 09:20:43 -07:00
margin-top : .65 rem ;
margin-bottom : .65 rem ;
}
li ul , li ol {
margin : .5 rem 0 ;
2026-09-08 17:34:46 +00:00
margin-left : 1 rem ;
}
@ media ( max-width : 650px ) {
li ul , li ol {
margin-left : 0 ;
}
2026-05-15 09:20:43 -07:00
}
dt {
font-weight : 900 ;
margin-top : .5 rem ;
}
dd {
margin-left : 2 rem ;
margin-bottom : .75 rem ;
}
/* Blockquotes */
blockquote {
margin : .5 rem 0 ;
padding : 0 1 rem ;
2026-10-14 17:40:43 +00:00
border-radius : .5 rem ;
2026-05-15 09:20:43 -07:00
line-height : 1.25 ;
border-left : .5 rem solid var ( -- color - pink );
}
blockquote ,
blockquote p ,
blockquote ol ,
blockquote ul {
background-color : var ( -- color - bg - alt );
padding : .5 rem ;
}
blockquote p {
margin : 0 ;
}
/* Tables */
table {
width : 100 % ;
border-spacing : 0 ; /* border collapse doesn't play nice with radii */
border-radius : .3 rem ;
border : thin solid var ( -- color - pink );
2026-08-06 18:38:23 +00:00
margin : 1.25 rem 0 ;
2026-05-15 09:20:43 -07:00
}
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 : .5 rem ;
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 : .25 rem ; }
th : last-child { border-top-right-radius : .25 rem ; }
td : not ( : first-child ) { border-left : thin solid var ( -- color - pink ); }
/* Times */
time {
color : var ( -- color - grey );
}
/* Horizontal rules */
hr {
border : .25 rem solid var ( -- color - pink );
margin : 2 rem 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 {
background-color : var ( -- color - bg );
padding : .75 rem 0 ;
border-bottom : thick solid var ( -- color - teal );
box-shadow : 0 .25 rem .15 rem var ( -- color - shadow );
2026-09-11 18:57:10 +00:00
display : grid ;
grid-template-columns : auto ;
2026-05-15 09:20:43 -07:00
}
/* Header links, pagination links */
header a ,
. pagination a ,
. webring ul a {
border-radius : 1 rem ;
border : .125 rem solid var ( -- color - pink );
color : var ( -- color - pink );
text-decoration : none ;
padding : 0 .25 rem ;
box-shadow : .15 rem .15 rem var ( -- color - shadow );
font-size : 1.2 rem ;
/* Click animation handling */
position : relative ;
top : 0 ;
left : 0 ;
transition : top .05 s ease-in , left .05 s ease-in ;
}
header a ,
. pagination . older a ,
. webring . prev a ,
. webring . rand a {
padding-right : .35 rem ;
}
. pagination . newer a ,
. webring . next a ,
. webring . rand a {
padding-left : .35 rem ;
}
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 : .125 rem ;
outline : .125 rem solid ;
}
@ media ( any-hover : hover ) {
header a : hover ,
. pagination a : hover ,
. webring ul a : hover {
outline-offset : .125 rem ;
outline : .125 rem solid ;
}
}
}
/* Click animation */
header a : active ,
. pagination a : active ,
. webring ul a : active {
top : .1 rem ;
left : .1 rem ;
box-shadow : .05 rem .05 rem 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 : .25 rem ;
}
. pagination . newer i ,
. webring . next i ,
. webring . rand i : nth-child ( 2 ) {
padding-right : .25 rem ;
}
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 : -999 px ;
position : absolute ;
top : auto ;
width : 1 px ;
height : 1 px ;
overflow : hidden ;
z-index : -99 ;
}
# skip : focus-visible {
display : inline-block ;
left : auto ;
top : auto ;
width : auto ;
height : auto ;
overflow : auto ;
2026-09-11 18:57:10 +00:00
margin : -.125 rem 10 % ;
2026-05-15 09:20:43 -07:00
z-index : 999 ;
}
/* Nav */
header ul {
display : flex ;
list-style : none ;
gap : 1 rem ;
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 : .15 rem .5 rem ;
}
header i {
padding : 0 ;
}
}
/* Footer */
footer {
padding : 1 rem 0 ;
font-size : .9 rem ;
border-top : thick solid var ( -- color - pink );
margin-top : 2 rem ;
}
footer ul {
display : flex ;
list-style : none ;
gap : .5 rem ;
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 );
}
2026-09-09 20:38:19 +00:00
/* color picker */
# theme {
display : flex ;
justify-content : center ;
margin : .5 rem auto 0 ;
padding : 0 .75 rem .5 rem ;
width : fit-content ;
border : .125 rem solid var ( -- color - teal );
border-radius : 2 rem ;
}
# theme legend {
margin : 0 auto ;
color : var ( -- color - teal );
}
footer label {
cursor : pointer ;
border : .125 rem solid var ( -- color - pink );
padding : 0 .25 rem ;
color : var ( -- color - pink );
background-color : var ( -- color - bg ); /* this stops the box shadows from showing through */
box-shadow : .15 em .15 em var ( -- color - shadow );
/* Click animation handling */
position : relative ;
top : 0 ;
left : 0 ;
transition : top .05 s ease-in , left .05 s ease-in ;
}
footer input : checked + label {
color : var ( -- color - blue );
border-color : var ( -- color - blue );
}
footer label i {
color : var ( -- color - teal );
}
footer input : checked + label i {
color : var ( -- color - pink );
}
@ media ( any-hover : hover ) {
footer label : hover ,
footer input : checked + label : hover ,
footer label : hover i ,
footer input : checked + label : hover i {
color : var ( -- color - bg );
}
footer label : hover {
background-color : var ( -- color - pink );
}
footer input : checked + label : hover {
background-color : var ( -- color - teal );
}
}
footer input : focus-visible + label ,
footer input : focus-visible + label i {
color : var ( -- color - bg );
background-color : var ( -- color - pink );
border-color : var ( -- color - pink );
}
footer input : checked : focus-visible + label ,
footer input : checked : focus-visible + label i {
color : var ( -- color - bg );
background-color : var ( -- color - teal );
border-color : var ( -- color - teal );
}
footer label : active {
top : .09 em ;
left : .09 em ;
box-shadow : .05 em .05 em var ( -- color - shadow );
}
label . theme-light {
border-top-left-radius : 1 rem ;
border-bottom-left-radius : 1 rem ;
border-right : none ;
padding-left : .35 rem ;
}
label . theme-auto {
}
label . theme-dark {
border-top-right-radius : 1 rem ;
border-bottom-right-radius : 1 rem ;
border-left : none ;
padding-right : .35 rem ;
}
footer input {
/* To allow screen reader to still access these. */
/* From https://lyra.horse/blog/2025/08/you-dont-need-js/ */
opacity : 0 ;
position : absolute ;
pointer-events : none ;
}
2026-05-15 09:20:43 -07:00
/* Pagination */
. pagination ,
. pagination li {
margin : 0 ;
}
. pagination {
display : grid ;
grid-template-columns : repeat ( 2 , 1 fr );
grid-template-areas : "older newer" ;
list-style : none ;
margin-top : 3 rem ;
}
@ media ( max-width : 650px ) {
. post-pagination {
grid-template-columns : 1 fr ;
grid-template-areas :
"older"
"newer" ;
gap : .75 rem ;
}
}
. pagination . older {
grid-area : older ;
}
. pagination . newer {
grid-area : newer ;
text-align : right ;
}
/* webring navigation */
. webring {
margin-bottom : 3 rem ;
}
. webring ul {
display : flex ;
flex-flow : row wrap ;
list-style : none ;
2026-08-28 19:00:43 +00:00
justify-content : center ;
gap : .5 rem ;
2026-05-15 09:20:43 -07:00
}
. webring ul ,
. webring li {
margin-left : 0 ;
}
2026-09-11 18:57:10 +00:00
/* Handle color schemes */
: root : has ( # theme-light : checked ) . dark {
display : none ;
}
: root : has ( # theme-dark : checked ) . light {
display : none ;
}
@ media ( prefers-color-scheme : light ) {
: root : not ( : has ( # theme-dark : checked )) . dark {
display : none ;
}
}
@ media ( prefers-color-scheme : dark ) {
: root : not ( : has ( # theme-light : checked )) . light {
display : none ;
}
}
. searchbox {
margin : 1 rem auto 0 ;
}
pagefind-searchbox {
/* Shadows */
--pf-scroll-shadow : var ( -- color - shadow ) !important ;
--pf-shadow-sm : .15 rem .15 rem 0 var ( -- color - shadow ) !important ;
--pf-shadow-md : .25 rem .25 rem 0 var ( -- color - shadow ) !important ;
--pf-shadow-lg : 1 rem 1 rem 0 var ( -- color - shadow ) !important ;
/* Typography */
--pf-font : var ( -- font - family ) !important ;
/* Border */
--pf-border-radius : 1.5 rem !important ;
/* Focus */
--pf-outline-width : .125 rem !important ;
--pf-outline-offset : 0 !important ;
/* Width */
--pf-searchbox-max-width : min ( 550 px , 92 vw ) !important ;
}
. light pagefind-searchbox {
--pf-text : var ( -- color - dark ) !important ;
--pf-text-secondary : var ( -- color - dark - alt ) !important ;
--pf-text-muted : var ( -- color - dark - alt ) !important ;
--pf-background : var ( -- color - light - alt ) !important ;
--pf-border : var ( -- color - pink - dark ) !important ;
--pf-border-focus : var ( -- color - pink - dark ) !important ;
--pf-skeleton : var ( -- color - light ) !important ;
--pf-skeleton-shine : var ( -- color - dark ) !important ;
--pf-hover : var ( -- color - light ) !important ;
--pf-mark : var ( -- color - teal - dark ) !important ;
--pf-outline-focus : var ( -- color - teal - dark ) !important ;
/* Error */
--pf-error-bg : var ( -- color - light - alt ) !important ;
--pf-error-border : var ( -- color - red - dark ) !important ;
--pf-error-text : var ( -- color - red - dark ) !important ;
--pf-error-text-secondary : var ( -- color - red - dark ) !important ;
}
. dark pagefind-searchbox {
--pf-text : var ( -- color - light ) !important ;
--pf-text-secondary : var ( -- color - light - alt ) !important ;
--pf-text-muted : var ( -- color - light - alt ) !important ;
--pf-background : var ( -- color - dark - alt ) !important ;
--pf-border : var ( -- color - pink - light ) !important ;
--pf-border-focus : var ( -- color - pink - light ) !important ;
--pf-skeleton : var ( -- color - dark ) !important ;
--pf-skeleton-shine : var ( -- color - dark ) !important ;
--pf-hover : var ( -- color - dark ) !important ;
--pf-mark : var ( -- color - teal - light ) !important ;
--pf-outline-focus : var ( -- color - teal - light ) !important ;
/* Error */
--pf-error-bg : var ( -- color - dark - alt ) !important ;
--pf-error-border : var ( -- color - red - light ) !important ;
--pf-error-text : var ( -- color - red - light ) !important ;
--pf-error-text-secondary : var ( -- color - red - light ) !important ;
}
pagefind-searchbox input {
border-width : .125 rem !important ;
}
pagefind-searchbox input : not ( : focus-visible ) {
box-shadow : var ( -- pf - shadow - md ) !important ;
}
2026-09-12 17:51:15 +00:00
@ media ( max-width : 650px ) {
. pf-trigger-shortcut {
display : none !important ;
}
}
2026-09-11 18:57:10 +00:00
. pf-searchbox-dropdown ,
. pf-searchbox-footer {
border-width : .125 rem !important ;
}
. pf-error {
margin-top : .5 rem !important ;
}
2026-09-08 17:40:59 +00:00
/* 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 : .9 em ;
text-shadow : 0 1 px 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 : 1 rem 0 ;
padding : 1 rem ;
line-height : 1.5 ;
white-space : pre-wrap ;
word-break : break-word ;
overflow-wrap : break-word ;
border-radius : .5 rem ;
overflow : auto ;
}
: not ( pre )> code ,
: not ( pre )> code [ class *= language- ] {
padding : .2 rem ;
border-radius : .25 rem ;
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 );
}
2026-05-15 09:20:43 -07:00
@ media print {
/* Nav elements */
header ,
footer ,
nav {
display : none !important ;
}
/* Base */
body {
background-color : #fff ;
color : #000 ;
}
main {
width : 95 vw ;
}
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 : .1 rem ;
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 >
2026-09-09 20:38:19 +00:00
2026-05-15 09:20:43 -07:00
< 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 );
}
}
}
/* no longer used, using aria-labelledby instead - see getAnchorElement
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 aria-labelledby= ${ heading . id } > ${ content } </span>` ; // CHANGED HERE
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 }
/* don't even bother on mobile */
if ( window . innerWidth > 650 ) {
const dialog = document . querySelector ( "dialog" );
const closeButton = document . querySelector ( ".close-dialog" );
const hero = document . querySelector ( ".hero" );
hero . tabIndex = 0 ;
hero . addEventListener ( "click" , ( e ) => dialog . showModal ());
hero . addEventListener ( "keydown" , ( e ) => {
if ( e . key === "Enter" || e . key === " " ) {
e . preventDefault ();
dialog . showModal ();
}
});
closeButton . addEventListener ( "click" , ( e ) => dialog . close ());
closeButton . addEventListener ( "keydown" , ( e ) => {
if ( e . key === "Enter" || e . key === " " ) {
e . preventDefault ();
dialog . close ();
}
});
2026-09-09 20:38:19 +00:00
}
const THEME = "theme" ;
const saveTheme = ( themeValue ) => {
localStorage . setItem ( THEME , themeValue );
};
const themeLight = document . getElementById ( "theme-light" );
themeLight . addEventListener ( "input" , ( e ) => {
saveTheme ( themeLight . value );
});
const themeAuto = document . getElementById ( "theme-auto" );
themeAuto . addEventListener ( "input" , ( e ) => {
saveTheme ( themeAuto . auto );
});
const themeDark = document . getElementById ( "theme-dark" );
themeDark . addEventListener ( "input" , ( e ) => {
saveTheme ( themeDark . value );
});
var currentThemeValue = localStorage . getItem ( THEME );
switch ( currentThemeValue ) {
case "light" :
themeLight . checked = true ;
2026-09-09 21:03:37 +00:00
themeAuto . checked = false ;
2026-09-09 20:38:19 +00:00
break ;
case "dark" :
themeDark . checked = true ;
2026-09-09 21:03:37 +00:00
themeAuto . checked = false ;
2026-09-09 20:38:19 +00:00
break ;
2026-09-09 21:03:37 +00:00
case "auto" :
2026-09-09 20:38:19 +00:00
default :
themeAuto . checked = true ;
2026-05-15 09:20:43 -07:00
}</ script >
2026-09-11 18:57:10 +00:00
< link href = "/pagefind/pagefind-component-ui.css" rel = "stylesheet" >
< script src = "/pagefind/pagefind-component-ui.js" type = "module" ></ script >
2026-05-15 09:20:43 -07:00
</ head >
< body >
< header >
< a href = "#main" id = "skip" title = "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" >
< 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" >
< i class = "fa-regular fa-images" aria-hidden = "true" ></ i >
< span class = "menu-text" > gallery</ span >
</ a >
</ li >
< li >
< a href = "/" title = "home" >
< 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" >
< 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" >
< i class = "fa-solid fa-envelope-open-text" aria-hidden = "true" ></ i >
< span class = "menu-text" > contact</ span >
</ a >
</ li >
</ ul >
</ nav >
2026-09-11 18:57:10 +00:00
< div class = "dark searchbox" data-pf-theme = "dark" >
< pagefind-searchbox ></ pagefind-searchbox >
</ div >
< div class = "light searchbox" >
< pagefind-searchbox ></ pagefind-searchbox >
</ div >
2026-05-15 09:20:43 -07:00
</ header >
2026-09-12 17:14:18 +00:00
< main id = "main" >
2026-05-15 09:20:43 -07:00
< heading-anchors content = "<i class='fa-solid fa-anchor'></i>" >
2026-09-12 17:14:18 +00:00
< article data-pagefind-body = "" >
2026-09-20 22:08:31 +00:00
< h1 > leatherworking favorites</ h1 >
2026-05-15 09:20:43 -07:00
< div class = "post-metadata" >
< p >
posted on
< time datetime = "2023-10-08" > October 8, 2023</ time >
by Lee Cattarin
</ p >
< ul class = "post-tags" >
< li >
< a href = "/tags/leather/" > leather</ a >
</ li >
</ ul >
</ div >
< dialog closedby = "any" aria-label = "image modal" tabindex = "-1" >
< button class = "close-dialog" autofocus = "" aria-label = "close the image modal" > × </ button >
< img class = "modal-img" src = "/img/leather-harness-wip.jpg" alt = "two pieces of a chest harness sitting on a messy workbench. both pieces are about 8 inches long total and consist of two large o-rings joined by a dark teal leather strap. the o rings and rivets are matte black." loading = "lazy" decoding = "async" width = "1000" height = "750" >
</ dialog >
< img class = "hero" src = "/img/leather-harness-wip.jpg" alt = "two pieces of a chest harness sitting on a messy workbench. both pieces are about 8 inches long total and consist of two large o-rings joined by a dark teal leather strap. the o rings and rivets are matte black." loading = "lazy" decoding = "async" width = "1000" height = "750" >
2026-09-20 22:08:31 +00:00
< aside id = "toc" >
< details >
< summary >< h2 > table of contents</ h2 ></ summary >
< nav class = "toc" >
< ol >
< li >< a href = "#practices" > practices</ a >
</ li >
< li >< a href = "#tools" > tools</ a >
< ol >
< li >< a href = "#cutting" > cutting</ a >
</ li >
< li >< a href = "#edging-and-burnishing" > edging and burnishing</ a >
</ li >
< li >< a href = "#punching" > punching</ a >
</ li >
< li >< a href = "#riveting%2C-snapping%2C-etc" > riveting, snapping, etc</ a >
</ li >
< li >< a href = "#stitching" > stitching</ a >
</ li >
< li >< a href = "#tool-care" > tool care</ a >
</ li >
< li >< a href = "#tooling" > tooling</ a >
</ li >
< li >< a href = "#zippers" > zippers</ a >
</ li >
</ ol >
</ li >
< li >< a href = "#resources" > resources</ a >
</ li >
< li >< a href = "#retailers" > retailers</ a >
</ li >
</ ol >
</ nav >
</ details >
</ aside >
< h2 id = "practices" > practices</ h2 >
2026-05-15 09:20:43 -07:00
< ul >
< li > cardboard templates have been really handy for the spacing of rivets and such. I use cereal boxes since they are not corrugated.</ li >
</ ul >
< h2 id = "tools" > tools</ h2 >
< p > If I have linked a tool, I really like that specific tool and find it stands out. Otherwise, idk, I've used precisely one edge beveler, I have no idea how it compares.</ p >
< ul >
< li > (need to have) some kind of scratch awl for marking</ li >
< li > (need to have) cutproof mat</ li >
< li > (need to have basically whenever you are using a mallet) < a href = "https://www.buckleguy.com/cutting-board-multiple-sizes/" target = "_blank" rel = "external" > " poundo board" or cutting board</ a > (this is just an example, I got mine elsewhere) - importantly, this is distinct from your cutproof mat. you < em > can</ em > use this in place of your cutproof mat but it tends to pick up a lot of punctures which can leave indents in your leather with pressure - so I only use mine for mallet-ing and otherwise use my cutproof mat</ li >
< li > (nice to have) < a href = "https://www.weaverleathersupply.com/products/pattern-sheet-24-x-45" target = "_blank" rel = "external" > pattern plastic</ a > - but like I said above you can use non-corrugated cardboard</ li >
< li > (nice to have) < a href = "https://www.buckleguy.com/bg-acrylic-tool-leather-thickness-gauge/" target = "_blank" rel = "external" > acrylic leather thickness gauge</ a > - but you can also just use a ruler or calipers, or learn to gauge by eye/feel</ li >
</ ul >
< h3 id = "cutting" > cutting</ h3 >
< ul >
< li > (need to have) metal cork-backed ruler (cork backing will keep it from slip-sliding around)</ li >
< li > (nice to have, but any sharp blade will do) < a href = "https://www.buckleguy.com/ergo-kiwi-knife-2-0/" target = "_blank" rel = "external" > ergo kiwi knife</ a ></ li >
< li > (nice to have for harnesses, belts) strap cutter</ li >
< li > (nice to have for small objects with a lot of layers at the seams, like wallets) skiving knife of some kind. there's a lot of different types out there. I have a CS Osborne leather knife and I don't love it, but idk if that's just because I am still learning how to skive.
< ul >
< li > you're supposed to skive on a fucken < a href = "https://www.weaverleathersupply.com/products/marble-tooling-slab" target = "_blank" rel = "external" > rock</ a > so as not to absolutely wreck your cutproof mat. I haven't got my rock yet but I haven't ruined my mat yet either.</ li >
< li > ok so I got a < a href = "https://www.weaverleathersupply.com/products/flat-leather-knife" target = "_blank" rel = "external" > skiving knife</ a > from Weaver and it < em > definitely</ em > beats out the CS Osborne leather knife</ li >
</ ul >
</ li >
</ ul >
< h3 id = "edging-and-burnishing" > edging and burnishing</ h3 >
< ul >
< li > (nice to have) edge beveler</ li >
< li > (need to have) sandpaper in maybe two grits. I have 100 and 600 sitting around right now</ li >
< li > (need to have) some small pieces of heavy canvas</ li >
< li > (nice to have) < a href = "https://www.buckleguy.com/seiwa-tokonole-leather-burnishing-gum-multiple-sizes-colors/" target = "_blank" rel = "external" > tokonole!!!</ a >
< ul >
< li > tokonole can even burnish chrome tan leather and flesh side of veg tan</ li >
</ ul >
</ li >
< li > (nice to have) < a href = "https://www.buckleguy.com/leather-edge-slicker-burnishing-tool-beech-wood/" target = "_blank" rel = "external" > wooden edge burnishing tool</ a > but tbh I find myself just using canvas instead of my burnishing tool
< ul >
< li > you can also use wooden tool handles, if you have for example an edge beveler with a wooden handle sitting around</ li >
</ ul >
</ li >
< li > (nice to have/need to have if you don't get tokonole) beeswax</ li >
< li > (nice to have) < a href = "https://www.dickblick.com/items/lineco-micro-spatula-flat/" target = "_blank" rel = "external" > micro spatula</ a > - I bought this for bookbinding but I find it useful for applying tokonole or doing other odd lil jobs</ li >
</ ul >
< h3 id = "punching" > punching</ h3 >
< ul >
< li > (need to have) a mallet or something for hitting things. yes there are $100 mauls you can buy. idk a shitty mallet is working fine for me so far
< ul >
< li > alright i splurged on a maul and it was worth it. dammit.</ li >
</ ul >
</ li >
2026-09-12 17:51:15 +00:00
< li > (nice to have) < a href = "https://www.talasonline.com/Japanese-Push-Drill" target = "_blank" rel = "external" > japanese push drill</ a > and < a href = "https://www.talasonline.com/Japanese-Push-Drill-Replacement-Bits" target = "_blank" rel = "external" > bits</ a ></ li >
< li > if you don't want to go in on a $50 push drill, start with just some simple < a href = "https://www.buckleguy.com/245-belt-punch-multiple-sizes/" target = "_blank" rel = "external" > single-hole punches</ a > or a < a href = "https://www.buckleguy.com/223-revolving-punch/" target = "_blank" rel = "external" > rotary punch</ a > . rotary punches are pretty heavily recommended since they come with a range of punch sizes; I avoid them like the plague because I have small shitty hands and I think those would pair poorly with a rotary punch.</ li >
2026-05-15 09:20:43 -07:00
< li > if you're going to be attaching buckles, you'll need oblong holes for the buckle tongue. two options:
< ul >
< li > punch a standard hole at each end of your eventual oblong, then using a ruler and sharp small blade cut between those holes to create your oblong
< img src = "/img/oblong-punch-alternative.jpg" alt = "A small piece of leather showing the steps to creating an oblong hole. First there is just a straight 1-inch line drawn by scratch awl. Holes are punched at both ends of that line. Two cuts are made coming from the right-side hole inwards (shown by arrows), about halfway. Similar cuts are then made from the left-hand hole inwards to meet them." loading = "lazy" decoding = "async" width = "1000" height = "1000" ></ li >
< li > get an < a href = "https://www.buckleguy.com/151-bag-punch-multiple-sizes/" target = "_blank" rel = "external" > oblong punch</ a > . shaped punches are < em > expensive as shit</ em > but if you're doing more than a few buckles this is worth it.</ li >
</ ul >
</ li >
< li > another shaped punch: the < a href = "https://www.buckleguy.com/0150-strap-end-punch-english-point-multiple-sizes-1/" target = "_blank" rel = "external" > english point strap end punch</ a > . I don't own this yet but am heavily considering it if I am going to make more harnesses (and I am). you don't specifically need english point - there's other strap end styles - but having < em > a</ em > strap end punch in the size of your choosing is useful for harnesses, belts, anything with a lot of straps.
< ul >
< li > with strap end punches, you can use a larger punch on a smaller strap (for an imperfect result, tbf) but not vice versa</ li >
< li > there are also strap end acrylic templates out there on several retailers sites - I have a tandy one</ li >
</ ul >
</ li >
</ ul >
2026-09-20 22:08:31 +00:00
< h3 id = "riveting%2C-snapping%2C-etc" > riveting, snapping, etc</ h3 >
2026-05-15 09:20:43 -07:00
< ul >
2026-09-12 17:51:15 +00:00
< li > for any given size/brand of rivets, you can usually buy a < a href = "https://www.buckleguy.com/bg-rivet-setter-9-2mm-concave-end/" target = "_blank" rel = "external" > setter</ a > and < a href = "https://www.buckleguy.com/bg-rivet-setter-base-for-9-2-11-13mm-cap-rivets/" target = "_blank" rel = "external" > anvil</ a > for maybe $25 together
2026-05-15 09:20:43 -07:00
< ul >
< li > also I have one extra of both of those specific linked items, so if you're short on cash lmk and I will ship em to ya - since I have switched over to the next option</ li >
</ ul >
</ li >
2026-09-12 17:51:15 +00:00
< li > however, if you think you might get really into leatherworking, or if you think you will be getting multiple sizes of rivets, attaching snaps, attaching other types of rivet-style clasps, whatever, I would highly recommend < a href = "https://www.buckleguy.com/bg-setter-post-base-for-screw-in-dies/" target = "_blank" rel = "external" > this setter post and base</ a > and < a href = "https://www.buckleguy.com/bg-rivet-setter-die-multiple-sizes-requires-hand-tool-post-base-or-bg-hand-press/" target = "_blank" rel = "external" > a rivet setter die</ a > or whichever setter die you need for your specific thing
2026-05-15 09:20:43 -07:00
< ul >
< li > these dies also work with < a href = "https://www.buckleguy.com/bg-180-convertible-hand-or-foot-press/" target = "_blank" rel = "external" > larger presses</ a > and make it much more possible to transition from a hand press to a full press without having to re-buy all your dies</ li >
< li > according to Corter Leather at the very least (I don't really have the experience to make a full comparison) this system is much more reliable for well-balanced, well-rounded rivets, snaps, etc</ li >
</ ul >
</ li >
</ ul >
< h3 id = "stitching" > stitching</ h3 >
< ul >
< li > (nice to have) wing divider for drawing stitching lines</ li >
< li > (need to have) stitching chisels! if you want to save some money, you mostly need the 6-tooth and the 2-tooth - 6 for straightaways, 2 for curves. I occasionally use my 4-tooth and < em > very</ em > occasionally use my 1-tooth (except for zippers. if you wanna do zippers get a 1-tooth).
< ul >
< li > my stitching chisels are 4mm spacing. I picked this pretty arbitrarily :)</ li >
< li > technically you could just buy a diamond awl or a 1-tooth chisel and poke each hole individually and that < em > would</ em > be cheaper but oh my god love yourself</ li >
</ ul >
</ li >
< li > (nice to have, any needles will do but oh my god these are pretty hashtag worth) < a href = "https://www.buckleguy.com/517-harness-needles-for-hand-sewing-blunt-tip-5-needles/" target = "_blank" rel = "external" > cs osborne harness needles, blunt tip</ a ></ li >
2026-09-12 17:51:15 +00:00
< li > (need to have) thread - I am using < a href = "https://www.buckleguy.com/ritza-25-tiger-thread/" target = "_blank" rel = "external" > Ritza 25 Tiger Thread</ a > (currently I have the 1.0mm size. I find it a little large and will probably size down when I order other colors - update, sized down to .8 and even have some .6 - much preferable) and have also used < a href = "https://threadsofmeisi.com/collections/meisi-superfine-linen-threads" target = "_blank" rel = "external" > MeiSi Superfine Linen Thread</ a > but polyester threads are easier to seal off with a lighter</ li >
2026-05-15 09:20:43 -07:00
< li > (nice to have) some kind of < a href = "https://www.joann.com/needle-pullers-3-pkg/3500295.html" target = "_blank" rel = "external" > silicone needle puller</ a > (I don't actually have this one, I was having trouble finding what I have - little pink finger covers that are all grippy) for gripping needles. or just use the back of a mousepad? or something?</ li >
</ ul >
< h3 id = "tool-care" > tool care</ h3 >
< ul >
< li > (need to have for certain blades such as skiving knife) jeweler's rouge for stropping
< ul >
< li > I am not an expert here so I am not making firm recommendations. I got mine from Springfield Leather</ li >
</ ul >
</ li >
</ ul >
< h3 id = "tooling" > tooling</ h3 >
< ul >
< li > (need to have) swivel knife</ li >
< li > (need to have) a beveler. I have a smooth beveler at the moment, but am waiting on a checked beveler in the mail as I think I will like that look better</ li >
< li > (need to have) as mentioned previously, mallet or maul</ li >
< li > (nice to have) literally any other stamps i guess</ li >
< li > (nice to have) templates! it really helps to start out with some designs made for swivel knife. I got a few (both free and paid) from < a href = "https://www.dadhands.ca/" target = "_blank" rel = "external" > dadhands</ a ></ li >
</ ul >
< h3 id = "zippers" > zippers</ h3 >
< ul >
< li > okay so I bought them because they are pretty but these < a href = "https://www.buckleguy.com/riri-zipper-continuous-chain-combi-eloxal-teeth-elox/" target = "_blank" rel = "external" > rainbow zippers</ a > actually make it so much easier because you can differentiate each tooth from the next!!!</ li >
< li > (nice to have) tape such as < a href = "https://www.buckleguy.com/double-sided-tape/" target = "_blank" rel = "external" > ted's tape</ a ></ li >
</ ul >
< h2 id = "resources" > resources</ h2 >
< ul >
< li >< a href = "https://www.youtube.com/@Corter" target = "_blank" rel = "external" > Corter Leather on YouTube</ a ></ li >
< li >< a href = "https://www.youtube.com/@Leodis.Leather" target = "_blank" rel = "external" > Leodis Leather on YouTube</ a ></ li >
< li >< a href = "https://www.youtube.com/@dadhands" target = "_blank" rel = "external" > dadhands on YouTube</ a ></ li >
< li >< a href = "https://www.youtube.com/@majutworks" target = "_blank" rel = "external" > Maju T Works on YouTube</ a ></ li >
</ ul >
< h2 id = "retailers" > retailers</ h2 >
< ul >
< li >< a href = "https://www.buckleguy.com/" target = "_blank" rel = "external" > buckleguy</ a > has been super speedy and reliable so far - for sure my favorite</ li >
< li >< a href = "https://www.springfieldleather.com/" target = "_blank" rel = "external" > springfield leather</ a ></ li >
< li >< a href = "https://tandyleather.com/" target = "_blank" rel = "external" > tandy</ a ></ li >
< li >< a href = "https://www.weaverleathersupply.com/" target = "_blank" rel = "external" > weaver leather supply</ a ></ li >
< li >< a href = "https://www.longviewleather.com/" target = "_blank" rel = "external" > longview leather (canada)</ a ></ li >
</ ul >
2026-09-20 22:08:31 +00:00
2026-05-15 09:20:43 -07:00
</ article >
< nav aria-label = "pagination" >
< ol class = "pagination post-pagination" >
< li class = "older" >
< a href = "/anarchy-autism/" >
< i class = "fa-solid fa-hand-point-left" aria-hidden = "true" ></ i > anarchy autism
</ a >
</ li >
< li class = "newer" >
< a href = "/leather-chest-harness/" >
leather chest harness < i class = "fa-solid fa-hand-point-right" aria-hidden = "true" ></ i >
</ a >
</ li >
</ ol >
</ nav >
< hr >
< section class = "related-posts" >
2026-09-20 22:08:31 +00:00
< h2 data-ha-exclude = "" > related posts</ h2 >
2026-05-15 09:20:43 -07:00
< ol id = "postlist" >
2026-10-14 15:29:48 +00:00
< li class = "post" >
2026-11-06 20:48:54 +00:00
< a class = "postlink" href = "/designing-a-bag/" >
< h2 data-ha-exclude = "" > designing a bag </ h2 >
2026-10-14 15:29:48 +00:00
< ul class = "postlist-tags" >
< li > leather</ li >
2026-11-06 20:48:54 +00:00
< li > software</ li >
2026-10-14 15:29:48 +00:00
</ ul >
2026-11-06 20:48:54 +00:00
< img src = "/img/shoelace-bag.jpg" alt = "a 3-image collage showcasing a leather crossbody bag. the leather body is brown and fairly simple. up the narrow sides, rope is laced through grommets in a style resembling a shoe lacing. the rope forms the handle and loops seamlessly through the other side of the bag, joining in one point in a figure-8 follow-through knot. At the bottom corners, there are small diagonal lines of stitching to give the bag a small lip around the base and ensure small objects don't slide out." loading = "lazy" decoding = "async" width = "1000" height = "1777" >
2026-10-14 15:29:48 +00:00
</ a >
</ li >
2026-10-05 23:59:36 +00:00
< li class = "post" >
2026-11-06 20:48:54 +00:00
< a class = "postlink" href = "/brookes-collar/" >
< h2 data-ha-exclude = "" > brooke' s collar </ h2 >
2026-10-05 23:59:36 +00:00
< ul class = "postlist-tags" >
< li > leather</ li >
</ ul >
2026-11-06 20:48:54 +00:00
< img src = "/img/lined-shearling-collar.jpg" alt = "A green leather collar lined with brown/grey shearling and fitted with two sizes of silver-toned spikes." loading = "lazy" decoding = "async" width = "1000" height = "750" >
2026-10-05 23:59:36 +00:00
</ a >
</ li >
< li class = "post" >
2026-11-06 20:48:54 +00:00
< a class = "postlink" href = "/leatherworking-zine/" >
< h2 data-ha-exclude = "" > leatherworking zine </ h2 >
2026-10-05 23:59:36 +00:00
< ul class = "postlist-tags" >
2026-11-06 20:48:54 +00:00
< li > zine</ li >
2026-10-05 23:59:36 +00:00
< li > leather</ li >
</ ul >
2026-11-06 20:48:54 +00:00
< img src = "/img/leatherworking-zine.jpg" alt = "3 copies of the same zine with different colored cardstock covers. The cover reads 'Leatherworking Tools' and lists myself as the author. Surrounding the text are many small icons of tools and explanatory diagrams. The text is dark teal and the icons are dark pink." loading = "lazy" decoding = "async" width = "1000" height = "750" >
2026-09-21 20:56:03 +00:00
</ a >
</ li >
2026-05-15 09:20:43 -07:00
</ ol >
</ section >
</ heading-anchors >
</ main >
< footer >
< ul >
< li >
< a href = "/colophon/" >
colophon
</ a >
</ li >
< li >
< a href = "/" title = "go home" aria-label = "go home | hello hello from Lee Cattarin, copyright 2022-2026" >
hello hello from Lee Cattarin, © 2022-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 >
2026-09-09 20:38:19 +00:00
< fieldset id = "theme" >
< legend > color scheme</ legend >
< input type = "radio" id = "theme-light" name = "theme" value = "light" >
< label class = "theme-light" for = "theme-light" >
< i class = "fa-regular fa-sun" aria-hidden = "true" ></ i >
light
</ label >
2026-09-09 21:03:37 +00:00
< input type = "radio" id = "theme-auto" name = "theme" value = "auto" checked = "" >
2026-09-09 20:38:19 +00:00
< label class = "theme-auto" for = "theme-auto" >
auto
</ label >
< input type = "radio" id = "theme-dark" name = "theme" value = "dark" >
< label class = "theme-dark" for = "theme-dark" >
dark
< i class = "fa-regular fa-moon" aria-hidden = "true" ></ i >
</ label >
</ fieldset >
2026-05-15 09:20:43 -07:00
</ footer >
2026-11-06 20:48:54 +00:00
<!-- This page `/leatherworking-favorites/` was built on 2026-11-06T20:48:10.475Z -->
2026-05-15 09:20:43 -07:00
</ body >
</ html >