2081 lines
47 KiB
HTML
2081 lines
47 KiB
HTML
<!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>intro to the fediverse | 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="intro to the fediverse | 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">
|
|
<meta property="og:url" content="https://leecat.art/intro-to-the-fediverse/">
|
|
|
|
<meta property="og:image" content="https://leecat.art/img/madrone-bark.jpg">
|
|
<meta property="og:image:alt" content="Image unrelated to post. A close up shot of the curling, shedding bark of the madrone tree. The shedding bark is a beautiful rusty brown, and the tree underneath light green.">
|
|
|
|
|
|
<meta name="generator" content="Eleventy v3.1.2">
|
|
|
|
<meta itemprop="ai-content-declaration:version" content="1.0.0">
|
|
<meta itemprop="ai-content-declaration:level" content="none">
|
|
|
|
|
|
<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>#toc {
|
|
float: right;
|
|
margin: 1rem 0 1.5rem 1.5rem;
|
|
width: fit-content;
|
|
max-width: 55%;
|
|
}
|
|
|
|
@media (max-width: 900px) {
|
|
#toc {
|
|
float: unset;
|
|
margin-top: 2rem;
|
|
margin-left: .5rem;
|
|
width: calc(100% - .5rem);
|
|
max-width: unset;
|
|
}
|
|
}
|
|
|
|
#toc details {
|
|
border: thick solid var(--color-pink);
|
|
border-radius: 1rem;
|
|
margin-top: 1rem;
|
|
background-color: var(--color-bg);
|
|
}
|
|
|
|
#toc summary {
|
|
position: relative;
|
|
top: -1.5rem;
|
|
left: -.75rem;
|
|
width: fit-content;
|
|
background: var(--color-bg);
|
|
border-radius: .5rem;
|
|
cursor: pointer;
|
|
padding-inline-end: .25rem;
|
|
}
|
|
|
|
#toc summary:focus-visible {
|
|
outline: .15rem solid var(--color-teal);
|
|
}
|
|
|
|
#toc summary::marker {
|
|
font-size: 2.35rem;
|
|
}
|
|
|
|
#toc h2 {
|
|
margin-top: 0;
|
|
display: inline;
|
|
line-height: normal;
|
|
}
|
|
|
|
#toc nav {
|
|
position: relative;
|
|
top: -1.25rem;
|
|
}
|
|
|
|
/* `tree` style toc */
|
|
#toc nav > ol {
|
|
margin-inline: 1rem;
|
|
}
|
|
|
|
#toc ol {
|
|
margin: 0;
|
|
}
|
|
|
|
#toc li {
|
|
position: relative;
|
|
padding-inline-start: 1em;
|
|
list-style: none;
|
|
margin-block: 0;
|
|
margin-left: .35rem;
|
|
}
|
|
|
|
#toc li::before,
|
|
#toc li::after {
|
|
display: block;
|
|
position: absolute;
|
|
box-sizing: border-box;
|
|
inset-block: 0 auto;
|
|
inset-inline: 0 auto;
|
|
inline-size: .75em;
|
|
border-inline-start: .1rem solid var(--color-pink);
|
|
}
|
|
|
|
#toc li::before {
|
|
block-size: .5lh;
|
|
border-block-end: .1rem solid var(--color-pink);
|
|
content: "";
|
|
}
|
|
|
|
#toc li:not(:last-child)::after {
|
|
block-size: 100%;
|
|
content: "";
|
|
}
|
|
|
|
/* underlines are unnecessary in this context */
|
|
#toc a {
|
|
text-decoration: none;
|
|
color: var(--color-blue);
|
|
font-weight: bold;
|
|
}
|
|
|
|
#toc a:focus-visible {
|
|
outline-color: var(--color-pink);
|
|
}
|
|
.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;
|
|
margin-left: auto;
|
|
}
|
|
|
|
.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);
|
|
}
|
|
#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 {
|
|
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-family: var(--font-family);
|
|
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;
|
|
}
|
|
|
|
.blur {
|
|
filter: blur(.75rem);
|
|
clip-path: inset(0 round 1rem);
|
|
}
|
|
|
|
.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: 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;
|
|
}
|
|
img[tabindex="0"] {
|
|
cursor: pointer;
|
|
}
|
|
|
|
img[tabindex="0"]:focus-visible {
|
|
outline: .15rem 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(4px);
|
|
}
|
|
|
|
body:has(dialog[open]) {
|
|
overflow: hidden;
|
|
}
|
|
|
|
.close-dialog {
|
|
background-color: rgba(from var(--color-teal) r g b / .35);
|
|
font-size: 1.5rem;
|
|
padding: 0 .5rem .15rem;
|
|
border-radius: 1rem;
|
|
color: var(--color-teal);
|
|
box-shadow: .15rem .15rem var(--color-shadow);
|
|
border: .08rem solid var(--color-teal);
|
|
align-self: flex-end;
|
|
justify-self: flex-start;
|
|
margin: 0 .1rem;
|
|
/* Click animation handling */
|
|
position: relative;
|
|
top: 1rem;
|
|
left: -.15rem;
|
|
transition: top .1s ease-in, left .1s 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: .08rem;
|
|
outline: .08rem solid;
|
|
}
|
|
|
|
@media (any-hover: hover) {
|
|
.close-dialog:hover {
|
|
outline-offset: .08rem;
|
|
outline: .08rem solid;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Click animation */
|
|
.close-dialog:active {
|
|
top: 1.1rem;
|
|
left: -.05rem;
|
|
box-shadow: .05rem .05rem var(--color-shadow);
|
|
}
|
|
|
|
.modal-img {
|
|
max-height: calc(100vh - 2rem);
|
|
object-fit: contain;
|
|
width: auto;
|
|
max-width: 90%;
|
|
margin: 0 auto;
|
|
}
|
|
@font-face {
|
|
font-family: "Peritel";
|
|
src:
|
|
url("/fonts/Peritel/Peritel-Regular.woff2") format("woff2"),
|
|
url("/fonts/Peritel/Peritel-Regular.otf") format("opentype");
|
|
}
|
|
|
|
:root {
|
|
color-scheme: light dark;
|
|
|
|
--font-family: 'Atkinson Hyperlegible Next', sans-serif;
|
|
/* if you change the header font, go to post-toc.css and fuck about with the width
|
|
of the table of contents */
|
|
--font-family-headers: 'Peritel', '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: #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));
|
|
}
|
|
|
|
:root:has(#theme-light:checked) {
|
|
color-scheme: light;
|
|
}
|
|
:root:has(#theme-dark:checked) {
|
|
color-scheme: dark;
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
|
|
@media (max-width: 1100px) {
|
|
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);
|
|
font-family: var(--font-family-headers);
|
|
letter-spacing: 0.1rem;
|
|
}
|
|
|
|
h1 {
|
|
margin-top: 3rem;
|
|
font-size: 3.7rem;
|
|
text-align: center;
|
|
}
|
|
|
|
h2 {
|
|
margin-top: 2rem;
|
|
font-size: 2.35rem;
|
|
}
|
|
|
|
h3 {
|
|
margin-top: 1.5rem;
|
|
font-size: 1.7rem;
|
|
}
|
|
|
|
@media (max-width: 650px) {
|
|
h1 { font-size: 2.95rem; }
|
|
h2 { font-size: 1.9rem; }
|
|
h3 { font-size: 1.4rem; }
|
|
}
|
|
|
|
h4, h5, h6 {
|
|
margin-top: 1rem;
|
|
font-size: 1.25rem;
|
|
}
|
|
|
|
/* Images */
|
|
img {
|
|
display: block;
|
|
max-width: 100%;
|
|
height: auto;
|
|
border-radius: 1rem;
|
|
margin: 0 auto;
|
|
}
|
|
|
|
/* 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);
|
|
font-size: .8em;
|
|
vertical-align: .1em;
|
|
}
|
|
span.ha-placeholder {
|
|
opacity: .55;
|
|
}
|
|
|
|
/* Lists */
|
|
::marker {
|
|
color: var(--color-pink);
|
|
}
|
|
|
|
ul, ol, dl, li {
|
|
margin-left: 1rem;
|
|
}
|
|
|
|
@media (max-width: 650px) {
|
|
ul, ol, dl {
|
|
margin-left: 0;
|
|
}
|
|
}
|
|
|
|
li {
|
|
line-height: 1.4;
|
|
margin-top: .65rem;
|
|
margin-bottom: .65rem;
|
|
}
|
|
|
|
li ul, li ol {
|
|
margin: .5rem 0;
|
|
margin-left: 1rem;
|
|
}
|
|
|
|
@media (max-width: 650px) {
|
|
li ul, li ol {
|
|
margin-left: 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);
|
|
margin: 1.25rem 0;
|
|
}
|
|
|
|
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 {
|
|
background-color: var(--color-bg);
|
|
padding: .75rem 0;
|
|
border-bottom: thick solid var(--color-teal);
|
|
box-shadow: 0 .25rem .15rem var(--color-shadow);
|
|
display: grid;
|
|
grid-template-columns: auto;
|
|
}
|
|
|
|
/* 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: -.125rem 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);
|
|
margin-top: 2rem;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
/* color picker */
|
|
#theme {
|
|
display: flex;
|
|
justify-content: center;
|
|
margin: .5rem auto 0;
|
|
padding: 0 .75rem .5rem;
|
|
width: fit-content;
|
|
border: .125rem solid var(--color-teal);
|
|
border-radius: 2rem;
|
|
}
|
|
|
|
#theme legend {
|
|
margin: 0 auto;
|
|
color: var(--color-teal);
|
|
}
|
|
|
|
footer label {
|
|
cursor: pointer;
|
|
border: .125rem solid var(--color-pink);
|
|
padding: 0 .25rem;
|
|
color: var(--color-pink);
|
|
background-color: var(--color-bg); /* this stops the box shadows from showing through */
|
|
box-shadow: .15em .15em var(--color-shadow);
|
|
/* Click animation handling */
|
|
position: relative;
|
|
top: 0;
|
|
left: 0;
|
|
transition: top .05s ease-in, left .05s 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: .09em;
|
|
left: .09em;
|
|
box-shadow: .05em .05em var(--color-shadow);
|
|
}
|
|
|
|
label.theme-light {
|
|
border-top-left-radius: 1rem;
|
|
border-bottom-left-radius: 1rem;
|
|
border-right: none;
|
|
padding-left: .35rem;
|
|
}
|
|
|
|
label.theme-auto {
|
|
}
|
|
|
|
label.theme-dark {
|
|
border-top-right-radius: 1rem;
|
|
border-bottom-right-radius: 1rem;
|
|
border-left: none;
|
|
padding-right: .35rem;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
/* 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: center;
|
|
gap: .5rem;
|
|
}
|
|
|
|
.webring ul,
|
|
.webring li {
|
|
margin-left: 0;
|
|
}
|
|
/* 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: 1rem auto 0;
|
|
}
|
|
|
|
pagefind-searchbox {
|
|
/* Shadows */
|
|
--pf-scroll-shadow: var(--color-shadow) !important;
|
|
--pf-shadow-sm: .15rem .15rem 0 var(--color-shadow) !important;
|
|
--pf-shadow-md: .25rem .25rem 0 var(--color-shadow) !important;
|
|
--pf-shadow-lg: 1rem 1rem 0 var(--color-shadow) !important;
|
|
|
|
/* Typography */
|
|
--pf-font: var(--font-family) !important;
|
|
|
|
/* Border */
|
|
--pf-border-radius: 1.5rem !important;
|
|
|
|
/* Focus */
|
|
--pf-outline-width: .125rem !important;
|
|
--pf-outline-offset: 0 !important;
|
|
|
|
/* Width */
|
|
--pf-searchbox-max-width: min(550px, 92vw) !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: .125rem !important;
|
|
}
|
|
|
|
pagefind-searchbox input:not(:focus-visible) {
|
|
box-shadow: var(--pf-shadow-md) !important;
|
|
}
|
|
|
|
@media (max-width: 650px) {
|
|
.pf-trigger-shortcut {
|
|
display: none !important;
|
|
}
|
|
}
|
|
|
|
.pf-searchbox-dropdown,
|
|
.pf-searchbox-footer {
|
|
border-width: .125rem !important;
|
|
}
|
|
|
|
.pf-error {
|
|
margin-top: .5rem !important;
|
|
}
|
|
/* 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: .9em;
|
|
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);
|
|
}
|
|
@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);
|
|
}
|
|
}
|
|
}
|
|
/* 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();
|
|
}
|
|
});
|
|
}
|
|
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;
|
|
themeAuto.checked = false;
|
|
break;
|
|
case "dark":
|
|
themeDark.checked = true;
|
|
themeAuto.checked = false;
|
|
break;
|
|
case "auto":
|
|
default:
|
|
themeAuto.checked = true;
|
|
}</script>
|
|
|
|
|
|
<link href="/pagefind/pagefind-component-ui.css" rel="stylesheet">
|
|
<script src="/pagefind/pagefind-component-ui.js" type="module"></script>
|
|
|
|
</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>
|
|
|
|
<div class="dark searchbox" data-pf-theme="dark">
|
|
<pagefind-searchbox></pagefind-searchbox>
|
|
</div>
|
|
<div class="light searchbox">
|
|
<pagefind-searchbox></pagefind-searchbox>
|
|
</div>
|
|
</header>
|
|
|
|
|
|
<main id="main">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<heading-anchors content="<i class='fa-solid fa-anchor'></i>">
|
|
<article data-pagefind-body="">
|
|
<h1>intro to the fediverse</h1>
|
|
|
|
<div class="post-metadata">
|
|
<p>
|
|
posted on
|
|
<time datetime="2026-09-28">September 28, 2026</time>
|
|
by Lee Cattarin
|
|
</p>
|
|
|
|
|
|
</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/madrone-bark.jpg" alt="Image unrelated to post. A close up shot of the curling, shedding bark of the madrone tree. The shedding bark is a beautiful rusty brown, and the tree underneath light green." loading="lazy" decoding="async" width="1000" height="666">
|
|
</dialog>
|
|
<img class="hero" src="/img/madrone-bark.jpg" alt="Image unrelated to post. A close up shot of the curling, shedding bark of the madrone tree. The shedding bark is a beautiful rusty brown, and the tree underneath light green." loading="lazy" decoding="async" width="1000" height="666">
|
|
|
|
|
|
|
|
|
|
<aside id="toc">
|
|
<details>
|
|
<summary><h2>table of contents</h2></summary>
|
|
<nav class="toc">
|
|
<ol>
|
|
|
|
<li><a href="#general">general</a>
|
|
</li>
|
|
|
|
<li><a href="#servers">servers</a>
|
|
</li>
|
|
|
|
<li><a href="#moving-between-servers">moving between servers</a>
|
|
</li>
|
|
|
|
<li><a href="#other-benefits-and-community-norms">other benefits and community norms</a>
|
|
</li>
|
|
|
|
<li><a href="#choosing-a-server">choosing a server</a>
|
|
</li>
|
|
|
|
<li><a href="#getting-started">getting started</a>
|
|
</li>
|
|
</ol>
|
|
</nav>
|
|
</details>
|
|
</aside>
|
|
|
|
<p>this is a casual introduction to the fediverse (mastodon et al.) I wrote for a friend. I have shared it a few times, so I'm putting it here for ease of reference.</p>
|
|
<h2 id="general">general</h2>
|
|
<p>the general style of fedi is microblogging (and sometimes more macroblogging). There's posts (image/video/audio and polls supported) and hashtags. Depending on your server, your post character limit will vary - some servers allow posts as long as you would ever want, but servers that haven't customized their post limit will be at 500 if I recall correctly. My favorite features of fedi are a) it is not corporate-run and b) there are <strong>no ads!</strong></p>
|
|
<p>usernames are @user@server. Your server determines things like your post limit, what your UI looks like, who your mods are, and what rules you have to follow. Most of the UIs are pretty similar, so don't worry about that - just know that if you hop servers the interface may be a new color or have a few small changes. (You can also on mobile use an app - I don't recommend the "main" mastodon app. I use tusky on android and like it. Idk about apple, maybe ask for recs.)</p>
|
|
<h2 id="servers">servers</h2>
|
|
<p>all servers can, in theory, talk to each other. It's like email, using gmail doesn't mean you can only communicate with other gmail users. <strong>However,</strong> in practice, some servers have "defederated" from others, or have limitations in place that mean you'll review notifications from people on that server before accepting them. "Defederation" basically just means blocking - when two servers are connected, we say they are "federated" - this is where the "fed" part of "fediverse" comes from. (Mastodon, on the other hand, is just one of many apps that make up the fediverse.)</p>
|
|
<p>by and large, all (good) servers have defederated from the really trashy ones that host bigoted shit. But there's some borderline cases. For example, the "main" mastodon server run by the people who develop mastodon - mastodon.social - is huge and has signups totally open to anyone with no review. That means it is very vulnerable to waves of spam or rather unfriendly content. Because of this, some very solid servers consider limiting or blocking mastodon.social (or already have) despite its relevance as a huge presence in the fediverse.</p>
|
|
<p>because of the size, I would <strong>never</strong> recommend signing up with mastodon.social. Your moderation experience is likely to be <strong>terrible</strong>. Being on a smaller server allows you to have a more personal relationship with your mods!</p>
|
|
<p>speaking of your mods, they are usually moderating and paying for the server at no cost to the users, and it's good practice to chuck them some change every once and a while.</p>
|
|
<h2 id="moving-between-servers">moving between servers</h2>
|
|
<p>you can migrate between servers, and your old account will point visitors over to your new account - but be aware that <strong>your posts don't migrate with you</strong>. This is very annoying for many people. The posts won't go away (they're still attached to the old account), but you won't be able to continue to edit them or see notifications for them. If you're really attached to a particular post, I recommend re-making it on the new account.</p>
|
|
<h2 id="other-benefits-and-community-norms">other benefits and community norms</h2>
|
|
<p>you can edit posts! You can pin posts to your profile! And if you're invested in digital privacy, you can set a deletion timeline for your posts (with granular settings like "posts with at least this many likes/boosts don't get deleted" and similar rules). You can also set your account up so you have to manually approve people before they can follow you (because of this, it's a good idea to fill in some basic profile info before following people - a lot of people who manually approve followers will deny requests from empty profiles).</p>
|
|
<p>image/video/audio content supports alt text and I highly recommend writing it, as many users won't boost posts that lack alt text. Depending on what software your server is running, you may or may not be able to set alt text for your profile/header photos - this is a newer feature that not all servers have yet, so I would recommend adding alt text for those pictures somewhere in your profile even if you are able to set it.</p>
|
|
<p>posts also include a content warning or subject field, and it's generally good practice to use it for sensitive topics - some people also just use it more like an actual subject field. Some common abbreviations for CWs are "mh/ph" - mental health/physical health - and "pol" - politics. Idk why people abbreviate their CWs when the character limits are often so big.</p>
|
|
<h2 id="choosing-a-server">choosing a server</h2>
|
|
<p>choosing a server can be daunting. It's helpful to look at things like the rules and the size of the moderation team compared to the size of the userbase. The server name can be part of your choice! That's a tiny but fun benefit of unique servers. For example, I am at <a href="https://flipping.rocks" target="_blank" rel="external">flipping.rocks</a> and we love the outdoors, critters, etc. It's not as crafty of a server, but my photography and general love of nature fits in really well (and often you don't need to "fit" a server really - it's just nice to have a theme).</p>
|
|
<p>I generally recommend that people use their first 3-6 months looking at other servers and listening to the folks around them about whether they like their server. You may feel right at home in your starting server, or you may find something you like better, in which case you can learn how to migrate (happy to help).</p>
|
|
<h2 id="getting-started">getting started</h2>
|
|
<p>your feed will be oh so empty! There is no "algorithm" in the sense that we think of one in other corporate-run social medias. What you see is the posts/boosts of people you follow, as well as hashtags you follow, in reverse chronological order.</p>
|
|
<p>I recommend a few things to get started:</p>
|
|
<ul>
|
|
<li>make a post with the hashtag #introduction as well as #hashtags of your interests, and say hi! That lets people meet you and introduce themselves</li>
|
|
<li>check out the "local" feed for your server and see who is around on there to follow</li>
|
|
<li>use tools like <a href="https://communitywiki.org/trunk" target="_blank" rel="external">communitywiki.org/trunk</a> or <a href="https://fedi.directory/" target="_blank" rel="external">fedi.directory/</a> to find people based on topic</li>
|
|
</ul>
|
|
|
|
|
|
</article>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<nav aria-label="pagination">
|
|
<ol class="pagination post-pagination">
|
|
|
|
<li class="older">
|
|
<a href="/fedizinefest-2026-2027/">
|
|
<i class="fa-solid fa-hand-point-left" aria-hidden="true"></i> fediZineFest 2026-2027
|
|
</a>
|
|
</li>
|
|
|
|
|
|
<li class="newer">
|
|
<a href="/sticker-vendors/">
|
|
sticker vendors <i class="fa-solid fa-hand-point-right" aria-hidden="true"></i>
|
|
</a>
|
|
</li>
|
|
|
|
</ol>
|
|
</nav>
|
|
|
|
|
|
|
|
<hr>
|
|
|
|
|
|
|
|
|
|
|
|
</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>
|
|
|
|
<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>
|
|
|
|
<input type="radio" id="theme-auto" name="theme" value="auto" checked="">
|
|
<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>
|
|
</footer>
|
|
|
|
|
|
<!-- This page `/intro-to-the-fediverse/` was built on 2026-10-02T16:58:27.826Z -->
|
|
</body>
|
|
</html>
|