2302 lines
50 KiB
HTML
2302 lines
50 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>visual site builders and accessibility | 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="visual site builders and accessibility | 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:image" content="/img/roses.jpg">
|
||
|
|
<meta property="og:image:alt" content="Image unrelated to post. Two peach-colored roses, the farther away one a bit out of focus.">
|
||
|
|
|
||
|
|
|
||
|
|
<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>.post-metadata {
|
||
|
|
display: flex;
|
||
|
|
flex-flow: row wrap;
|
||
|
|
justify-content: space-between;
|
||
|
|
align-items: baseline;
|
||
|
|
margin: 1.5rem 0 .5rem;
|
||
|
|
}
|
||
|
|
|
||
|
|
.post-metadata p {
|
||
|
|
font-size: .9rem;
|
||
|
|
margin: 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
.post-tags {
|
||
|
|
display: flex;
|
||
|
|
flex-flow: row wrap;
|
||
|
|
gap: .5rem;
|
||
|
|
list-style: none;
|
||
|
|
margin: 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
.post-tags li {
|
||
|
|
margin: 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
.post-tags li a {
|
||
|
|
text-decoration: none;
|
||
|
|
color: var(--color-teal);
|
||
|
|
padding: 0 .5rem;
|
||
|
|
border-radius: 1rem;
|
||
|
|
box-shadow: .15rem .15rem var(--color-shadow);
|
||
|
|
border: .08rem solid var(--color-teal);
|
||
|
|
line-height: 2;
|
||
|
|
/* Click animation handling */
|
||
|
|
position: relative;
|
||
|
|
top: 0;
|
||
|
|
left: 0;
|
||
|
|
transition: top .1s ease-in, left .1s ease-in;
|
||
|
|
}
|
||
|
|
|
||
|
|
.post-tags li a:focus-visible {
|
||
|
|
outline: none;
|
||
|
|
background-color: var(--color-teal);
|
||
|
|
color: var(--color-bg);
|
||
|
|
}
|
||
|
|
|
||
|
|
@media (any-hover: hover) {
|
||
|
|
.post-tags li a:hover {
|
||
|
|
outline: none;
|
||
|
|
background-color: var(--color-teal);
|
||
|
|
color: var(--color-bg);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
@media (forced-colors: active) {
|
||
|
|
.post-tags li a:focus-visible {
|
||
|
|
outline-offset: .08rem;
|
||
|
|
outline: .08rem solid;
|
||
|
|
}
|
||
|
|
|
||
|
|
@media (any-hover: hover) {
|
||
|
|
.post-tags li a:hover {
|
||
|
|
outline-offset: .08rem;
|
||
|
|
outline: .08rem solid;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Click animation */
|
||
|
|
.post-tags li a:active {
|
||
|
|
top: .1rem;
|
||
|
|
left: .1rem;
|
||
|
|
box-shadow: .05rem .05rem var(--color-shadow);
|
||
|
|
}
|
||
|
|
/* Adapted from PrismJS 1.30.0 Tomorrow Night theme
|
||
|
|
https://prismjs.com/download
|
||
|
|
*/
|
||
|
|
code,
|
||
|
|
pre,
|
||
|
|
code[class*=language-],
|
||
|
|
pre[class*=language-] {
|
||
|
|
font-family: var(--font-family-code);
|
||
|
|
background-color: var(--color-bg-alt);
|
||
|
|
font-size: .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);
|
||
|
|
}
|
||
|
|
#postlist,
|
||
|
|
#taglist {
|
||
|
|
list-style: none;
|
||
|
|
}
|
||
|
|
|
||
|
|
#postlist, .post,
|
||
|
|
#taglist, .tag {
|
||
|
|
margin: 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Odd-numbered posts & tag layout/coloration */
|
||
|
|
.post:nth-child(odd) .postlink,
|
||
|
|
.tag:nth-child(odd) .taglink {
|
||
|
|
grid-template-areas:
|
||
|
|
'img h2'
|
||
|
|
'img info'
|
||
|
|
'img .';
|
||
|
|
grid-template-columns: 45% auto;;
|
||
|
|
--color-primary: var(--color-teal);
|
||
|
|
--color-accent: var(--color-pink);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Even-numbered posts & tags layout/coloration */
|
||
|
|
.post:nth-child(even) .postlink,
|
||
|
|
.tag:nth-child(even) .taglink {
|
||
|
|
grid-template-areas:
|
||
|
|
'h2 img'
|
||
|
|
'info img'
|
||
|
|
'. img';
|
||
|
|
grid-template-columns: auto 45%;
|
||
|
|
--color-primary: var(--color-pink);
|
||
|
|
--color-accent: var(--color-teal);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Layout for all posts on mobile */
|
||
|
|
@media (max-width: 650px) {
|
||
|
|
.post:nth-child(n) .postlink,
|
||
|
|
.tag:nth-child(n) .taglink {
|
||
|
|
grid-template-areas:
|
||
|
|
'img'
|
||
|
|
'h2'
|
||
|
|
'info';
|
||
|
|
grid-template-columns: auto;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Link */
|
||
|
|
.postlink,
|
||
|
|
.taglink {
|
||
|
|
display: grid;
|
||
|
|
border: .25rem solid var(--color-primary);
|
||
|
|
border-radius: 1.25rem;
|
||
|
|
box-shadow: .35rem .35rem var(--color-shadow);
|
||
|
|
margin: 2rem 0;
|
||
|
|
text-decoration: none;
|
||
|
|
/* Click animation handling */
|
||
|
|
position: relative;
|
||
|
|
top: 0;
|
||
|
|
left: 0;
|
||
|
|
transition: top .05s ease-in, left .05s ease-in;
|
||
|
|
}
|
||
|
|
|
||
|
|
.postlink:focus-visible,
|
||
|
|
.taglink:focus-visible {
|
||
|
|
background-color: var(--color-primary);
|
||
|
|
outline: none;
|
||
|
|
}
|
||
|
|
|
||
|
|
@media (any-hover: hover) {
|
||
|
|
.postlink:hover,
|
||
|
|
.taglink:hover {
|
||
|
|
background-color: var(--color-primary);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Forced colors */
|
||
|
|
@media (forced-colors: active) {
|
||
|
|
.postlink:focus-visible,
|
||
|
|
.taglink:focus-visible {
|
||
|
|
outline-offset: .25rem;
|
||
|
|
outline: .25rem solid;
|
||
|
|
}
|
||
|
|
|
||
|
|
@media (any-hover: hover) {
|
||
|
|
.postlink:hover,
|
||
|
|
.taglink:hover {
|
||
|
|
outline-offset: .25rem;
|
||
|
|
outline: .25rem solid;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Click animation */
|
||
|
|
.postlink:active,
|
||
|
|
.taglink:active {
|
||
|
|
box-shadow: none;
|
||
|
|
top: .2rem;
|
||
|
|
left: .2rem;
|
||
|
|
box-shadow: .15rem .15rem var(--color-shadow);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Post & tag elements */
|
||
|
|
.post h2, .post img,
|
||
|
|
.post ul, .post li,
|
||
|
|
.tag h2, .tag p,
|
||
|
|
.tag img {
|
||
|
|
margin: 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
.post h2,
|
||
|
|
.tag h2 {
|
||
|
|
grid-area: h2;
|
||
|
|
padding: .25rem .5rem;
|
||
|
|
text-transform: uppercase;
|
||
|
|
font-size: 1.5rem;
|
||
|
|
color: var(--color-primary);
|
||
|
|
border-radius: 1rem 1rem 0 0;
|
||
|
|
border-bottom: .25rem solid var(--color-accent);
|
||
|
|
}
|
||
|
|
|
||
|
|
.post:nth-child(even) h2,
|
||
|
|
.tag:nth-child(even) h2 {
|
||
|
|
text-align: right;
|
||
|
|
}
|
||
|
|
|
||
|
|
.postlink:focus-visible h2,
|
||
|
|
.taglink:focus-visible h2 {
|
||
|
|
color: var(--color-bg);
|
||
|
|
border-color: var(--color-bg);
|
||
|
|
}
|
||
|
|
|
||
|
|
@media (any-hover: hover) {
|
||
|
|
.postlink:hover h2,
|
||
|
|
.taglink:hover h2 {
|
||
|
|
color: var(--color-bg);
|
||
|
|
border-color: var(--color-bg);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Images */
|
||
|
|
.post img,
|
||
|
|
.tag-imgs {
|
||
|
|
grid-area: img;
|
||
|
|
}
|
||
|
|
|
||
|
|
.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(90vh - 2rem);
|
||
|
|
object-fit: contain;
|
||
|
|
width: auto;
|
||
|
|
max-width: 90%;
|
||
|
|
margin: 0 auto;
|
||
|
|
}
|
||
|
|
:root {
|
||
|
|
color-scheme: light dark;
|
||
|
|
|
||
|
|
--font-family: 'Atkinson Hyperlegible Next', sans-serif;
|
||
|
|
--font-family-code: 'Atkinson Hyperlegible Mono', monospace;
|
||
|
|
|
||
|
|
--color-dark: #2e303e;
|
||
|
|
--color-dark-alt: #3c3f52;
|
||
|
|
--color-light: #ebeeef;
|
||
|
|
--color-light-alt: #dbe1e3;
|
||
|
|
|
||
|
|
--color-teal-dark: #18737b;
|
||
|
|
--color-teal-light: #25b0bc;
|
||
|
|
--color-pink-dark: #94195d;
|
||
|
|
--color-pink-light: #ee9fcb;
|
||
|
|
|
||
|
|
--color-shadow: rgba(2, 10, 40, .25);
|
||
|
|
|
||
|
|
/* Used for syntax highlighting */
|
||
|
|
--color-red-light: #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));
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Base */
|
||
|
|
|
||
|
|
* {
|
||
|
|
box-sizing: border-box;
|
||
|
|
margin: 0;
|
||
|
|
padding: 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
body {
|
||
|
|
font-family: var(--font-family);
|
||
|
|
color: var(--color-text);
|
||
|
|
background-color: var(--color-bg);
|
||
|
|
}
|
||
|
|
|
||
|
|
main {
|
||
|
|
width: 60vw;
|
||
|
|
max-width: 1000px;
|
||
|
|
margin: 0 auto;
|
||
|
|
scroll-margin-top: 7rem;
|
||
|
|
}
|
||
|
|
|
||
|
|
@media (max-width: 1050px) {
|
||
|
|
main {
|
||
|
|
width: 75vw;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
@media (max-width: 650px) {
|
||
|
|
main {
|
||
|
|
width: 92vw;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Headers */
|
||
|
|
h1, h2, h3, h4, h5, h6 {
|
||
|
|
line-height: 1.25;
|
||
|
|
color: var(--color-teal);
|
||
|
|
}
|
||
|
|
|
||
|
|
h1 {
|
||
|
|
margin-top: 3rem;
|
||
|
|
font-size: 3.5rem;
|
||
|
|
text-align: center;
|
||
|
|
}
|
||
|
|
|
||
|
|
h2, h3, h4, h5, h6 {
|
||
|
|
scroll-margin-top: 5rem;
|
||
|
|
}
|
||
|
|
|
||
|
|
h2 {
|
||
|
|
margin-top: 2rem;
|
||
|
|
font-size: 2.2rem;
|
||
|
|
}
|
||
|
|
|
||
|
|
h3 {
|
||
|
|
margin-top: 1.5rem;
|
||
|
|
font-size: 1.6rem;
|
||
|
|
}
|
||
|
|
|
||
|
|
@media (max-width: 650px) {
|
||
|
|
h1 { font-size: 2.8rem; }
|
||
|
|
h2 { font-size: 1.8rem; }
|
||
|
|
h3 { font-size: 1.35rem; }
|
||
|
|
}
|
||
|
|
|
||
|
|
h4, h5, h6 {
|
||
|
|
margin-top: 1rem;
|
||
|
|
font-size: 1.2rem;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Images */
|
||
|
|
img {
|
||
|
|
display: block;
|
||
|
|
max-width: 100%;
|
||
|
|
height: auto;
|
||
|
|
border-radius: 1rem;
|
||
|
|
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.2 5;
|
||
|
|
margin-top: .65rem;
|
||
|
|
margin-bottom: .65rem;
|
||
|
|
}
|
||
|
|
|
||
|
|
li ul, li ol {
|
||
|
|
margin: .5rem 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
dt {
|
||
|
|
font-weight: 900;
|
||
|
|
margin-top: .5rem;
|
||
|
|
}
|
||
|
|
|
||
|
|
dd {
|
||
|
|
margin-left: 2rem;
|
||
|
|
margin-bottom: .75rem;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Blockquotes */
|
||
|
|
blockquote {
|
||
|
|
margin: .5rem 0;
|
||
|
|
padding: 0 1rem;
|
||
|
|
border-radius: .25rem 1rem 1rem .25rem;
|
||
|
|
line-height: 1.25;
|
||
|
|
border-left: .5rem solid var(--color-pink);
|
||
|
|
}
|
||
|
|
|
||
|
|
blockquote,
|
||
|
|
blockquote p,
|
||
|
|
blockquote ol,
|
||
|
|
blockquote ul {
|
||
|
|
background-color: var(--color-bg-alt);
|
||
|
|
padding: .5rem;
|
||
|
|
}
|
||
|
|
|
||
|
|
blockquote p {
|
||
|
|
margin: 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Tables */
|
||
|
|
table {
|
||
|
|
width: 100%;
|
||
|
|
border-spacing: 0; /* border collapse doesn't play nice with radii */
|
||
|
|
border-radius: .3rem;
|
||
|
|
border: thin solid var(--color-pink);
|
||
|
|
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 {
|
||
|
|
position: sticky;
|
||
|
|
top: 0;
|
||
|
|
background-color: var(--color-bg);
|
||
|
|
padding: .75rem 0;
|
||
|
|
z-index: 10;
|
||
|
|
border-bottom: thick solid var(--color-teal);
|
||
|
|
box-shadow: 0 .25rem .15rem var(--color-shadow);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Header links, pagination links */
|
||
|
|
header a,
|
||
|
|
.pagination a,
|
||
|
|
.webring ul a {
|
||
|
|
border-radius: 1rem;
|
||
|
|
border: .125rem solid var(--color-pink);
|
||
|
|
color: var(--color-pink);
|
||
|
|
text-decoration: none;
|
||
|
|
padding: 0 .25rem;
|
||
|
|
box-shadow: .15rem .15rem var(--color-shadow);
|
||
|
|
font-size: 1.2rem;
|
||
|
|
/* Click animation handling */
|
||
|
|
position: relative;
|
||
|
|
top: 0;
|
||
|
|
left: 0;
|
||
|
|
transition: top .05s ease-in, left .05s ease-in;
|
||
|
|
}
|
||
|
|
|
||
|
|
header a,
|
||
|
|
.pagination .older a,
|
||
|
|
.webring .prev a,
|
||
|
|
.webring .rand a {
|
||
|
|
padding-right: .35rem;
|
||
|
|
}
|
||
|
|
|
||
|
|
.pagination .newer a,
|
||
|
|
.webring .next a,
|
||
|
|
.webring .rand a {
|
||
|
|
padding-left: .35rem;
|
||
|
|
}
|
||
|
|
|
||
|
|
header a:focus-visible,
|
||
|
|
.pagination a:focus-visible,
|
||
|
|
.webring ul a:focus-visible {
|
||
|
|
color: var(--color-bg);
|
||
|
|
border-color: var(--color-pink);
|
||
|
|
background-color: var(--color-pink);
|
||
|
|
outline: none;
|
||
|
|
}
|
||
|
|
|
||
|
|
@media (any-hover: hover) {
|
||
|
|
header a:hover,
|
||
|
|
.pagination a:hover,
|
||
|
|
.webring ul a:hover {
|
||
|
|
color: var(--color-bg);
|
||
|
|
border-color: var(--color-pink);
|
||
|
|
background-color: var(--color-pink);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
@media (forced-colors: active) {
|
||
|
|
header a:focus-visible,
|
||
|
|
.pagination a:focus-visible,
|
||
|
|
.webring ul a:focus-visible {
|
||
|
|
outline-offset: .125rem;
|
||
|
|
outline: .125rem solid;
|
||
|
|
}
|
||
|
|
|
||
|
|
@media (any-hover: hover) {
|
||
|
|
header a:hover,
|
||
|
|
.pagination a:hover,
|
||
|
|
.webring ul a:hover {
|
||
|
|
outline-offset: .125rem;
|
||
|
|
outline: .125rem solid;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Click animation */
|
||
|
|
header a:active,
|
||
|
|
.pagination a:active,
|
||
|
|
.webring ul a:active {
|
||
|
|
top: .1rem;
|
||
|
|
left: .1rem;
|
||
|
|
box-shadow: .05rem .05rem var(--color-shadow);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Current page */
|
||
|
|
header a[aria-current="page"] {
|
||
|
|
border-color: var(--color-teal);
|
||
|
|
color: var(--color-teal);
|
||
|
|
}
|
||
|
|
|
||
|
|
header a[aria-current="page"]:focus-visible {
|
||
|
|
color: var(--color-bg);
|
||
|
|
border-color: var(--color-teal);
|
||
|
|
background-color: var(--color-teal);
|
||
|
|
}
|
||
|
|
|
||
|
|
@media (any-hover: hover) {
|
||
|
|
header a[aria-current="page"]:hover {
|
||
|
|
color: var(--color-bg);
|
||
|
|
background-color: var(--color-teal);
|
||
|
|
border-color: var(--color-teal);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Header link icons, pagination icons */
|
||
|
|
header i,
|
||
|
|
.pagination i,
|
||
|
|
.webring ul i {
|
||
|
|
color: var(--color-teal);
|
||
|
|
}
|
||
|
|
|
||
|
|
header i,
|
||
|
|
.pagination .older i,
|
||
|
|
.webring .prev i,
|
||
|
|
.webring .rand i:nth-child(1) {
|
||
|
|
padding-left: .25rem;
|
||
|
|
}
|
||
|
|
|
||
|
|
.pagination .newer i,
|
||
|
|
.webring .next i,
|
||
|
|
.webring .rand i:nth-child(2) {
|
||
|
|
padding-right: .25rem;
|
||
|
|
}
|
||
|
|
|
||
|
|
header a[aria-current="page"] i {
|
||
|
|
color: var(--color-pink);
|
||
|
|
}
|
||
|
|
|
||
|
|
header a:focus-visible i,
|
||
|
|
a[aria-current="page"] a:focus-visible i,
|
||
|
|
.pagination a:focus-visible i,
|
||
|
|
.webring ul a:focus-visible i {
|
||
|
|
color: var(--color-bg);
|
||
|
|
}
|
||
|
|
|
||
|
|
@media (any-hover: hover) {
|
||
|
|
header a:hover i,
|
||
|
|
header a[aria-current="page"]:hover i,
|
||
|
|
.pagination a:hover i,
|
||
|
|
.webring ul a:hover i {
|
||
|
|
color: var(--color-bg);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Skip link */
|
||
|
|
#skip {
|
||
|
|
left: -999px;
|
||
|
|
position: absolute;
|
||
|
|
top: auto;
|
||
|
|
width: 1px;
|
||
|
|
height: 1px;
|
||
|
|
overflow: hidden;
|
||
|
|
z-index: -99;
|
||
|
|
}
|
||
|
|
|
||
|
|
#skip:focus-visible {
|
||
|
|
display: inline-block;
|
||
|
|
left: auto;
|
||
|
|
top: auto;
|
||
|
|
width: auto;
|
||
|
|
height: auto;
|
||
|
|
overflow: auto;
|
||
|
|
margin: 0 10%;
|
||
|
|
z-index: 999;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Nav */
|
||
|
|
header ul {
|
||
|
|
display: flex;
|
||
|
|
list-style: none;
|
||
|
|
gap: 1rem;
|
||
|
|
justify-content: center;
|
||
|
|
}
|
||
|
|
|
||
|
|
header ul,
|
||
|
|
header li {
|
||
|
|
margin: 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
@media (max-width: 650px) {
|
||
|
|
.menu-text {
|
||
|
|
display: none; /* Icons only on small screens */
|
||
|
|
}
|
||
|
|
|
||
|
|
header a {
|
||
|
|
padding: .15rem .5rem;
|
||
|
|
}
|
||
|
|
|
||
|
|
header i {
|
||
|
|
padding: 0;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Footer */
|
||
|
|
footer {
|
||
|
|
padding: 1rem 0;
|
||
|
|
font-size: .9rem;
|
||
|
|
border-top: thick solid var(--color-pink);
|
||
|
|
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);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Pagination */
|
||
|
|
.pagination,
|
||
|
|
.pagination li {
|
||
|
|
margin: 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
.pagination {
|
||
|
|
display: grid;
|
||
|
|
grid-template-columns: repeat(2, 1fr);
|
||
|
|
grid-template-areas: "older newer";
|
||
|
|
list-style: none;
|
||
|
|
margin-top: 3rem;
|
||
|
|
}
|
||
|
|
|
||
|
|
@media (max-width: 650px) {
|
||
|
|
.post-pagination {
|
||
|
|
grid-template-columns: 1fr;
|
||
|
|
grid-template-areas:
|
||
|
|
"older"
|
||
|
|
"newer";
|
||
|
|
gap: .75rem;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
.pagination .older {
|
||
|
|
grid-area: older;
|
||
|
|
}
|
||
|
|
|
||
|
|
.pagination .newer {
|
||
|
|
grid-area: newer;
|
||
|
|
text-align: right;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* webring navigation */
|
||
|
|
.webring {
|
||
|
|
margin-bottom: 3rem;
|
||
|
|
}
|
||
|
|
|
||
|
|
.webring ul {
|
||
|
|
display: flex;
|
||
|
|
flex-flow: row wrap;
|
||
|
|
list-style: none;
|
||
|
|
justify-content: space-around;
|
||
|
|
gap: .35rem;
|
||
|
|
}
|
||
|
|
|
||
|
|
.webring ul,
|
||
|
|
.webring li {
|
||
|
|
margin-left: 0;
|
||
|
|
}
|
||
|
|
@media print {
|
||
|
|
/* Nav elements */
|
||
|
|
header,
|
||
|
|
footer,
|
||
|
|
nav {
|
||
|
|
display: none !important;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Base */
|
||
|
|
body {
|
||
|
|
background-color: #fff;
|
||
|
|
color: #000;
|
||
|
|
}
|
||
|
|
|
||
|
|
main {
|
||
|
|
width: 95vw;
|
||
|
|
}
|
||
|
|
|
||
|
|
h1,h2,h3,h4,h5,h6 {
|
||
|
|
color: #000;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Links */
|
||
|
|
/* Hover is not really necessary, but it's annoying when testing otherwise */
|
||
|
|
a,
|
||
|
|
a:hover {
|
||
|
|
text-decoration-style: dotted;
|
||
|
|
text-decoration-thickness: .1rem;
|
||
|
|
text-decoration-color: #000;
|
||
|
|
}
|
||
|
|
|
||
|
|
a::after{
|
||
|
|
content: " (" attr(href) ")";
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
/* Code */
|
||
|
|
code,
|
||
|
|
pre,
|
||
|
|
code[class*=language-],
|
||
|
|
pre[class*=language-] {
|
||
|
|
text-shadow: none;
|
||
|
|
background-color: var(--color-light);
|
||
|
|
color: #000 !important;
|
||
|
|
}
|
||
|
|
|
||
|
|
.token.namespace {
|
||
|
|
opacity: 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
.token.block-comment,
|
||
|
|
.token.cdata,
|
||
|
|
.token.comment,
|
||
|
|
.token.doctype,
|
||
|
|
.token.prolog,
|
||
|
|
.token.punctuation,
|
||
|
|
.token.attr-name,
|
||
|
|
.token.deleted,
|
||
|
|
.token.namespace,
|
||
|
|
.token.tag,
|
||
|
|
.token.boolean,
|
||
|
|
.token.function,
|
||
|
|
.token.number,
|
||
|
|
.token.class-name,
|
||
|
|
.token.constant,
|
||
|
|
.token.property,
|
||
|
|
.token.symbol,
|
||
|
|
.token.attr-value,
|
||
|
|
.token.char,
|
||
|
|
.token.regex,
|
||
|
|
.token.string,
|
||
|
|
.token.variable,
|
||
|
|
.token.inserted,
|
||
|
|
.token.entity,
|
||
|
|
.token.operator,
|
||
|
|
.token.url,
|
||
|
|
.token.function-name,
|
||
|
|
.token.atrule,
|
||
|
|
.token.builtin,
|
||
|
|
.token.important,
|
||
|
|
.token.keyword,
|
||
|
|
.token.selector {
|
||
|
|
color: #000;
|
||
|
|
}
|
||
|
|
}</style>
|
||
|
|
|
||
|
|
<script type="module">// Thank you to https://github.com/daviddarnes/heading-anchors
|
||
|
|
// Thank you to https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||
|
|
|
||
|
|
let globalInstanceIndex = 0;
|
||
|
|
|
||
|
|
class HeadingAnchors extends HTMLElement {
|
||
|
|
static register(tagName = "heading-anchors", registry = window.customElements) {
|
||
|
|
if(registry && !registry.get(tagName)) {
|
||
|
|
registry.define(tagName, this);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
static attributes = {
|
||
|
|
exclude: "data-ha-exclude",
|
||
|
|
prefix: "prefix",
|
||
|
|
content: "content",
|
||
|
|
}
|
||
|
|
|
||
|
|
static classes = {
|
||
|
|
anchor: "ha",
|
||
|
|
placeholder: "ha-placeholder",
|
||
|
|
srOnly: "ha-visualhide",
|
||
|
|
}
|
||
|
|
|
||
|
|
static defaultSelector = "h2,h3,h4,h5,h6";
|
||
|
|
|
||
|
|
static css = `
|
||
|
|
.${HeadingAnchors.classes.srOnly} {
|
||
|
|
clip: rect(0 0 0 0);
|
||
|
|
height: 1px;
|
||
|
|
overflow: hidden;
|
||
|
|
position: absolute;
|
||
|
|
width: 1px;
|
||
|
|
}
|
||
|
|
.${HeadingAnchors.classes.anchor} {
|
||
|
|
position: absolute;
|
||
|
|
left: var(--ha_offsetx);
|
||
|
|
top: var(--ha_offsety);
|
||
|
|
text-decoration: none;
|
||
|
|
opacity: 0;
|
||
|
|
}
|
||
|
|
.${HeadingAnchors.classes.placeholder} {
|
||
|
|
opacity: .3;
|
||
|
|
}
|
||
|
|
.${HeadingAnchors.classes.anchor}:is(:focus-within, :hover) {
|
||
|
|
opacity: 1;
|
||
|
|
}
|
||
|
|
.${HeadingAnchors.classes.anchor},
|
||
|
|
.${HeadingAnchors.classes.placeholder} {
|
||
|
|
display: inline-block;
|
||
|
|
padding: 0 .25em;
|
||
|
|
|
||
|
|
/* Disable selection of visually hidden label */
|
||
|
|
-webkit-user-select: none;
|
||
|
|
user-select: none;
|
||
|
|
}
|
||
|
|
|
||
|
|
@supports (anchor-name: none) {
|
||
|
|
.${HeadingAnchors.classes.anchor} {
|
||
|
|
position: absolute;
|
||
|
|
left: anchor(left);
|
||
|
|
top: anchor(top);
|
||
|
|
}
|
||
|
|
}`;
|
||
|
|
|
||
|
|
get supports() {
|
||
|
|
return "replaceSync" in CSSStyleSheet.prototype;
|
||
|
|
}
|
||
|
|
|
||
|
|
get supportsAnchorPosition() {
|
||
|
|
return CSS.supports("anchor-name: none");
|
||
|
|
}
|
||
|
|
|
||
|
|
constructor() {
|
||
|
|
super();
|
||
|
|
|
||
|
|
if(!this.supports) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
let sheet = new CSSStyleSheet();
|
||
|
|
sheet.replaceSync(HeadingAnchors.css);
|
||
|
|
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
|
||
|
|
|
||
|
|
this.headingStyles = {};
|
||
|
|
this.instanceIndex = globalInstanceIndex++;
|
||
|
|
}
|
||
|
|
|
||
|
|
connectedCallback() {
|
||
|
|
if (!this.supports) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
this.headings.forEach((heading, index) => {
|
||
|
|
if(!heading.hasAttribute(HeadingAnchors.attributes.exclude)) {
|
||
|
|
let anchor = this.getAnchorElement(heading);
|
||
|
|
let placeholder = this.getPlaceholderElement();
|
||
|
|
|
||
|
|
// Prefers anchor position approach for better accessibility
|
||
|
|
// https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||
|
|
if(this.supportsAnchorPosition) {
|
||
|
|
let anchorName = `--ha_${this.instanceIndex}_${index}`;
|
||
|
|
placeholder.style.setProperty("anchor-name", anchorName);
|
||
|
|
anchor.style.positionAnchor = anchorName;
|
||
|
|
}
|
||
|
|
|
||
|
|
heading.appendChild(placeholder);
|
||
|
|
heading.after(anchor);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// Polyfill-only
|
||
|
|
positionAnchorFromPlaceholder(placeholder) {
|
||
|
|
if(!placeholder) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
let heading = placeholder.closest("h1,h2,h3,h4,h5,h6");
|
||
|
|
if(!heading.nextElementSibling) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
// TODO next element could be more defensive
|
||
|
|
this.positionAnchor(heading.nextElementSibling);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Polyfill-only
|
||
|
|
positionAnchor(anchor) {
|
||
|
|
if(!anchor || !anchor.previousElementSibling) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
// TODO previous element could be more defensive
|
||
|
|
let heading = anchor.previousElementSibling;
|
||
|
|
this.setFontProp(heading, anchor);
|
||
|
|
|
||
|
|
if(this.supportsAnchorPosition) {
|
||
|
|
// quit early
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||
|
|
if(placeholder) {
|
||
|
|
anchor.style.setProperty("--ha_offsetx", `${placeholder.offsetLeft}px`);
|
||
|
|
anchor.style.setProperty("--ha_offsety", `${placeholder.offsetTop}px`);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
setFontProp(heading, anchor) {
|
||
|
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||
|
|
if(placeholder) {
|
||
|
|
let style = getComputedStyle(placeholder);
|
||
|
|
let props = ["font-weight", "font-size", "line-height", "font-family"];
|
||
|
|
let [weight, size, lh, family] = props.map(name => style.getPropertyValue(name));
|
||
|
|
anchor.style.setProperty("font", `${weight} ${size}/${lh} ${family}`);
|
||
|
|
let vars = style.getPropertyValue("font-variation-settings");
|
||
|
|
if(vars) {
|
||
|
|
anchor.style.setProperty("font-variation-settings", vars);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
/* 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();
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}</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>
|
||
|
|
|
||
|
|
</header>
|
||
|
|
|
||
|
|
|
||
|
|
<main id="main">
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
<heading-anchors content="<i class='fa-solid fa-anchor'></i>">
|
||
|
|
<article>
|
||
|
|
<h1 id="visual-site-builders-and-accessibility">visual site builders and accessibility</h1>
|
||
|
|
|
||
|
|
<div class="post-metadata">
|
||
|
|
<p>
|
||
|
|
posted on
|
||
|
|
<time datetime="2026-08-05">August 5, 2026</time>
|
||
|
|
by Lee Cattarin
|
||
|
|
</p>
|
||
|
|
|
||
|
|
|
||
|
|
<ul class="post-tags">
|
||
|
|
|
||
|
|
<li>
|
||
|
|
|
||
|
|
<a href="/tags/software/">software</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/roses.jpg" alt="Image unrelated to post. Two peach-colored roses, the farther away one a bit out of focus." loading="lazy" decoding="async" width="1000" height="666">
|
||
|
|
</dialog>
|
||
|
|
<img class="hero" src="/img/roses.jpg" alt="Image unrelated to post. Two peach-colored roses, the farther away one a bit out of focus." loading="lazy" decoding="async" width="1000" height="666">
|
||
|
|
|
||
|
|
|
||
|
|
<p>Recently, I <a href="https://flipping.rocks/@inherentlee/117037989967706516" target="_blank" rel="external">asked on the Fediverse</a> about visual/drag-and-drop website builders and got a wealth of answers.</p>
|
||
|
|
<p>I want to evaluate based on some accessibility factors:</p>
|
||
|
|
<ol>
|
||
|
|
<li><strong>Semantic HTML:</strong> how well does the builder adhere to properly formed semantic HTML?</li>
|
||
|
|
<li><strong>Keyboard navigation:</strong> is keyboard focus visible? Can it be customized? (I was going to review skip links, but I'm a half dozen sites in and none have supported them thus far, at least not without entirely custom code)</li>
|
||
|
|
<li><strong>Reading order:</strong> is reading order automatically correct? If not, what does the user have to do to ensure correctness?</li>
|
||
|
|
<li><strong>Text alternatives:</strong> is there support for alt text?</li>
|
||
|
|
<li><strong>Color:</strong> does the builder support light and dark modes? (Note that I am looking for builders for those with no HTML/CSS experience, and "you can customize with CSS" is not acceptable)</li>
|
||
|
|
<li><strong>Other accessibility concerns:</strong> anything else of note?</li>
|
||
|
|
</ol>
|
||
|
|
<p>I will score <strong>1</strong> (bad), <strong>2</strong> (mixed), or <strong>3</strong> (good). If there is nothing to note for the "other" factor, I will score it as a 2. These scores <strong>will not be weighted</strong> according to priorities, but purely summed for a final ranking.</p>
|
||
|
|
<p>There are, of course, many more factors that could be evaluated, but I will be focusing on these high-level ones.</p>
|
||
|
|
<p>Want to just see the final ranking? Pop down to the <a href="#summary">summary</a>.</p>
|
||
|
|
<h2 id="blocs">Blocs</h2>
|
||
|
|
<p><a href="https://blocsapp.com/" target="_blank" rel="external">Blocs</a> is a builder for MacOS, iPad, and iPhone. I had trouble getting a template loaded on my iPad, but I reviewed the Blocs website - built with Blocs, of course - for major issues. This is, I would assume, the shiniest version possible.</p>
|
||
|
|
<table>
|
||
|
|
<thead>
|
||
|
|
<tr>
|
||
|
|
<th>Factor</th>
|
||
|
|
<th>Notes</th>
|
||
|
|
<th>Score</th>
|
||
|
|
</tr>
|
||
|
|
</thead>
|
||
|
|
<tbody>
|
||
|
|
<tr>
|
||
|
|
<td>Semantic HTML</td>
|
||
|
|
<td>Most major elements missing (no <code><main></code>, <code><header></code>, <code><footer></code>; <code><nav></code> present for the top nav but not bottom).</td>
|
||
|
|
<td>1</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td>Keyboard navigation</td>
|
||
|
|
<td>Custom focus, missing in many areas on the Blocs site and turned off by default in the CSS - not sure if that is for this site or is the standard. The ability to potentially customize focus is interesting.</td>
|
||
|
|
<td>1</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td>Reading order</td>
|
||
|
|
<td>Good! Block-based layout helps significantly here I would guess.</td>
|
||
|
|
<td>3</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td>Text alternatives</td>
|
||
|
|
<td>Supported.</td>
|
||
|
|
<td>3</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td>Color</td>
|
||
|
|
<td>Light and dark modes are possible - <a href="https://help.blocsapp.com/knowledge-base/dark-theme/" target="_blank" rel="external">Blocs dark mode documentation</a>.</td>
|
||
|
|
<td>3</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td>Other</td>
|
||
|
|
<td>Difficulty with iPad app.</td>
|
||
|
|
<td>1</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td></td>
|
||
|
|
<td></td>
|
||
|
|
<td><strong>12</strong></td>
|
||
|
|
</tr>
|
||
|
|
</tbody>
|
||
|
|
</table>
|
||
|
|
<h2 id="cowsites">Cowsites</h2>
|
||
|
|
<p><a href="https://cowsites.net" target="_blank" rel="external">Cowsites</a> is a simple site editor with no drag-and-drop elements.</p>
|
||
|
|
<table>
|
||
|
|
<thead>
|
||
|
|
<tr>
|
||
|
|
<th>Factor</th>
|
||
|
|
<th>Notes</th>
|
||
|
|
<th>Score</th>
|
||
|
|
</tr>
|
||
|
|
</thead>
|
||
|
|
<tbody>
|
||
|
|
<tr>
|
||
|
|
<td>Semantic HTML</td>
|
||
|
|
<td>Some oddities, like the <code><header></code> being inside the <code><main></code> while the <code><footer></code> was not. Repeated <code><h1></code>s.</td>
|
||
|
|
<td>2</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td>Keyboard navigation</td>
|
||
|
|
<td>Browser default focus outlines. Some clipping of focus outlines.</td>
|
||
|
|
<td>2</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td>Reading order</td>
|
||
|
|
<td>Good. Difficult to mess up when there are no drag-and-drop elements.</td>
|
||
|
|
<td>3</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td>Text alternatives</td>
|
||
|
|
<td>Both image title and alt can be set in the editor, but don't appear to be surfaced in the actual site.</td>
|
||
|
|
<td>1</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td>Color</td>
|
||
|
|
<td>Single color palette.</td>
|
||
|
|
<td>1</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td>Other</td>
|
||
|
|
<td>No <code>lang</code> attribute set. Gallery page links are three separate links to the same place, with lacking link text for links 2 and 3.</td>
|
||
|
|
<td>1</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td></td>
|
||
|
|
<td></td>
|
||
|
|
<td><strong>10</strong></td>
|
||
|
|
</tr>
|
||
|
|
</tbody>
|
||
|
|
</table>
|
||
|
|
<h2 id="figma-sites">Figma Sites</h2>
|
||
|
|
<p><a href="https://www.figma.com/sites/" target="_blank" rel="external">Figma Sites</a> allows you to turn a Figma design into a functioning website. It offers gen AI features. I tested two themes from Figma Community.</p>
|
||
|
|
<table>
|
||
|
|
<thead>
|
||
|
|
<tr>
|
||
|
|
<th>Factor</th>
|
||
|
|
<th>Notes</th>
|
||
|
|
<th>Score</th>
|
||
|
|
</tr>
|
||
|
|
</thead>
|
||
|
|
<tbody>
|
||
|
|
<tr>
|
||
|
|
<td>Semantic HTML</td>
|
||
|
|
<td>Depends <em>heavily</em> on developer. One was <code><div></code>s all the way down, one was built with actual semantic HTML using the "Accessibility" section of one of the sidebars (it allows you to specify the tag a design element corresponds to). Notably, not a lot or a total lack of links? One theme had some <code><buttons></code>s, the other was littered with click events and <code>tabindex="0"</code>.</td>
|
||
|
|
<td>1</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td>Keyboard navigation</td>
|
||
|
|
<td>Browser default focus outlines. One template <em>actually</em> had a skip link!</td>
|
||
|
|
<td>2</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td>Reading order</td>
|
||
|
|
<td>Not clear how order is determined. Potentially partially influenced by order of elements in the sidebar, but one theme tested did not adhere to that and the nav was the last in the reading order despite being top of the sidebar order. I can't publish on the starter plan, so I can't see how my own changes influence the site, but with a well-built theme the organization helps ensure that adding new elements puts them in a reasonable place in the sidebar order.</td>
|
||
|
|
<td>2</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td>Text alternatives</td>
|
||
|
|
<td>Supported.</td>
|
||
|
|
<td>3</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td>Color</td>
|
||
|
|
<td>Light and dark modes are possible - <a href="https://help.figma.com/hc/en-us/articles/15343816063383-Modes-for-variables" target="_blank" rel="external">Figma variable modes documentation</a>.</td>
|
||
|
|
<td>3</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td>Other</td>
|
||
|
|
<td>n/a</td>
|
||
|
|
<td>2</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td></td>
|
||
|
|
<td></td>
|
||
|
|
<td><strong>12</strong></td>
|
||
|
|
</tr>
|
||
|
|
</tbody>
|
||
|
|
</table>
|
||
|
|
<h2 id="kiki">Kiki</h2>
|
||
|
|
<p><a href="https://tomotama.com/kiki" target="_blank" rel="external">Kiki</a> is a simple site builder with no drag-and-drop elements. You can customize the CSS to your liking. I played with the demo site.</p>
|
||
|
|
<table>
|
||
|
|
<thead>
|
||
|
|
<tr>
|
||
|
|
<th>Factor</th>
|
||
|
|
<th>Notes</th>
|
||
|
|
<th>Score</th>
|
||
|
|
</tr>
|
||
|
|
</thead>
|
||
|
|
<tbody>
|
||
|
|
<tr>
|
||
|
|
<td>Semantic HTML</td>
|
||
|
|
<td>It's <code><div></code>s all the way down. No headers. <code><p></code> tags used, at least.</td>
|
||
|
|
<td>1</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td>Keyboard navigation</td>
|
||
|
|
<td>Browser default focus outlines. Significant focus outline clipping. Should be fixable with CSS... if we wanted to write CSS.</td>
|
||
|
|
<td>2</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td>Reading order</td>
|
||
|
|
<td>Good. Difficult to mess up when there are no drag-and-drop elements.</td>
|
||
|
|
<td>3</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td>Text alternatives</td>
|
||
|
|
<td>Seemingly not present, though there are no images in the demo site.</td>
|
||
|
|
<td>1</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td>Color</td>
|
||
|
|
<td>Since you can write your own CSS, all color is customizable... if we wanted to write CSS.</td>
|
||
|
|
<td>1</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td>Other</td>
|
||
|
|
<td>No <code>lang</code> attribute set. No <code>title</code>.</td>
|
||
|
|
<td>1</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td></td>
|
||
|
|
<td></td>
|
||
|
|
<td><strong>9</strong></td>
|
||
|
|
</tr>
|
||
|
|
</tbody>
|
||
|
|
</table>
|
||
|
|
<h2 id="mmm-page">mmm.page</h2>
|
||
|
|
<p><a href="https://mmm.page/" target="_blank" rel="external">mmm.page</a> is a drag-and-drop builder. The preview doesn't show you a standalone page (it's a page that also has the mmm.page login/signup menu bar), so I looked at a few sites to get a sense for how it worked.</p>
|
||
|
|
<table>
|
||
|
|
<thead>
|
||
|
|
<tr>
|
||
|
|
<th>Factor</th>
|
||
|
|
<th>Notes</th>
|
||
|
|
<th>Score</th>
|
||
|
|
</tr>
|
||
|
|
</thead>
|
||
|
|
<tbody>
|
||
|
|
<tr>
|
||
|
|
<td>Semantic HTML</td>
|
||
|
|
<td>While there is a <code><main></code>, it's otherwise <code><div></code>s all the way down. Generally no headers, though sites seem to have a visually hidden <code><h1></code> that matches the site title. <code><p></code> tags used, at least.</td>
|
||
|
|
<td>1</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td>Keyboard navigation</td>
|
||
|
|
<td>Browser default focus outlines.</td>
|
||
|
|
<td>2</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td>Reading order</td>
|
||
|
|
<td>Order of elements added determines reading order.</td>
|
||
|
|
<td>1</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td>Text alternatives</td>
|
||
|
|
<td>Supported.</td>
|
||
|
|
<td>3</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td>Color</td>
|
||
|
|
<td>Single color palette.</td>
|
||
|
|
<td>1</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td>Other</td>
|
||
|
|
<td>No <code>lang</code> attribute. Preponderance of empty <code>aria-label</code>s.</td>
|
||
|
|
<td>1</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td></td>
|
||
|
|
<td></td>
|
||
|
|
<td><strong>9</strong></td>
|
||
|
|
</tr>
|
||
|
|
</tbody>
|
||
|
|
</table>
|
||
|
|
<h2 id="rapidweaver">Rapidweaver</h2>
|
||
|
|
<p><a href="https://www.realmacsoftware.com/rapidweaver/" target="_blank" rel="external">Rapidweaver</a> is built exclusively for Mac. Ah, well.</p>
|
||
|
|
<h2 id="showit">Showit</h2>
|
||
|
|
<p><a href="https://showit.com/" target="_blank" rel="external">Showit</a> is a drag-and-drop builder with a Wordpress backend for content management. I reviewed several different premade themes. Custom CSS is allowed.</p>
|
||
|
|
<table>
|
||
|
|
<thead>
|
||
|
|
<tr>
|
||
|
|
<th>Factor</th>
|
||
|
|
<th>Notes</th>
|
||
|
|
<th>Score</th>
|
||
|
|
</tr>
|
||
|
|
</thead>
|
||
|
|
<tbody>
|
||
|
|
<tr>
|
||
|
|
<td>Semantic HTML</td>
|
||
|
|
<td>Depending on theme, <em>some</em> landmark tags might be present. One theme reviewed enclosed each individual nav link in a <code><nav></code> tag. Ew. Text can be set to be wrapped in a variety of tags, including bare <code><div></code>s.</td>
|
||
|
|
<td>1</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td>Keyboard navigation</td>
|
||
|
|
<td>The default CSS reset for Showit removes focus outlines on some elements (form fields and buttons, but not links). In one tested theme, further focus outlines were missing. Browser default focus outlines. Despite detailed ability to customize hover states in-editor, no ability to customize focus states.</td>
|
||
|
|
<td>1</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td>Reading order</td>
|
||
|
|
<td>Determined by the user via reordering elements in the sidebar.</td>
|
||
|
|
<td>2</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td>Text alternatives</td>
|
||
|
|
<td>Supported.</td>
|
||
|
|
<td>3</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td>Color</td>
|
||
|
|
<td>Single color palette.</td>
|
||
|
|
<td>1</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td>Other</td>
|
||
|
|
<td>n/a</td>
|
||
|
|
<td>2</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td></td>
|
||
|
|
<td></td>
|
||
|
|
<td><strong>10</strong></td>
|
||
|
|
</tr>
|
||
|
|
</tbody>
|
||
|
|
</table>
|
||
|
|
<h2 id="startup">Startup</h2>
|
||
|
|
<p><a href="https://designmodo.com/startup/" target="_blank" rel="external">Startup</a> is a block-based builder with pre-defined components and less freedom than some others on this list.</p>
|
||
|
|
<table>
|
||
|
|
<thead>
|
||
|
|
<tr>
|
||
|
|
<th>Factor</th>
|
||
|
|
<th>Notes</th>
|
||
|
|
<th>Score</th>
|
||
|
|
</tr>
|
||
|
|
</thead>
|
||
|
|
<tbody>
|
||
|
|
<tr>
|
||
|
|
<td>Semantic HTML</td>
|
||
|
|
<td>Pretty good! They've seemingly dropped <code><main></code> and instead used an array of <code><section></code>s, but components are automatically associated with <code><header></code>, <code><nav></code>, <code><footer></code>, etc.</td>
|
||
|
|
<td>3</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td>Keyboard navigation</td>
|
||
|
|
<td>Browser default focus outlines.</td>
|
||
|
|
<td>2</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td>Reading order</td>
|
||
|
|
<td>Very orderly thanks to the strictness of the components - it's one component after another. Moving pieces around within the components maintains correct reading order.</td>
|
||
|
|
<td>3</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td>Text alternatives</td>
|
||
|
|
<td>Supported.</td>
|
||
|
|
<td>3</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td>Color</td>
|
||
|
|
<td>Per-component color palette.</td>
|
||
|
|
<td>1</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td>Other</td>
|
||
|
|
<td>Forms are weak - unlabeled fields and buttons. For a built in search field, I'd expect better. The components by default are <em>super</em> animation-heavy and don't respect <code>prefers-reduced-motion</code>.</td>
|
||
|
|
<td>1</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td></td>
|
||
|
|
<td></td>
|
||
|
|
<td><strong>13</strong></td>
|
||
|
|
</tr>
|
||
|
|
</tbody>
|
||
|
|
</table>
|
||
|
|
<h2 id="straw-page">Straw.page</h2>
|
||
|
|
<p><a href="https://straw.page/" target="_blank" rel="external">Straw.Page</a> is a drag-and-drop builder that bills itself as mobile-friendly. I looked at one of the offered themes, intentionally choosing one that should have intuitive reading order (spoiler: didn't matter).</p>
|
||
|
|
<table>
|
||
|
|
<thead>
|
||
|
|
<tr>
|
||
|
|
<th>Factor</th>
|
||
|
|
<th>Notes</th>
|
||
|
|
<th>Score</th>
|
||
|
|
</tr>
|
||
|
|
</thead>
|
||
|
|
<tbody>
|
||
|
|
<tr>
|
||
|
|
<td>Semantic HTML</td>
|
||
|
|
<td>It's <code><div></code>s all the way down. No headers. <code><p></code> tags used, at least. Lists are just <code><p></code>s.</td>
|
||
|
|
<td>1</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td>Keyboard navigation</td>
|
||
|
|
<td>Focus actively turned off.</td>
|
||
|
|
<td>1</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td>Reading order</td>
|
||
|
|
<td>Order of elements added determines reading order.</td>
|
||
|
|
<td>1</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td>Text alternatives</td>
|
||
|
|
<td>Not possible.</td>
|
||
|
|
<td>1</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td>Color</td>
|
||
|
|
<td>Single color palette.</td>
|
||
|
|
<td>1</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td>Other</td>
|
||
|
|
<td>n/a</td>
|
||
|
|
<td>2</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td></td>
|
||
|
|
<td></td>
|
||
|
|
<td><strong>7</strong></td>
|
||
|
|
</tr>
|
||
|
|
</tbody>
|
||
|
|
</table>
|
||
|
|
<h2 id="univer-se">Univer.se</h2>
|
||
|
|
<p><a href="https://univer.se/" target="_blank" rel="external">Univer.se</a> is a drag-and-drop builder with plenty of premade themes. I reviewed a premade theme to get started.</p>
|
||
|
|
<table>
|
||
|
|
<thead>
|
||
|
|
<tr>
|
||
|
|
<th>Factor</th>
|
||
|
|
<th>Notes</th>
|
||
|
|
<th>Score</th>
|
||
|
|
</tr>
|
||
|
|
</thead>
|
||
|
|
<tbody>
|
||
|
|
<tr>
|
||
|
|
<td>Semantic HTML</td>
|
||
|
|
<td>It's <code><div></code>s all the way down. Headers exist, but there are multiple <code><h1></code>s and the headers skip levels. <code><p></code> tags used, at least.</td>
|
||
|
|
<td>1</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td>Keyboard navigation</td>
|
||
|
|
<td>Browser default focus outlines.</td>
|
||
|
|
<td>2</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td>Reading order</td>
|
||
|
|
<td>Surprisingly intuitive! Univer.se uses a grid-based layout system and that seems to help programmatically determine reading order.</td>
|
||
|
|
<td>3</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td>Text alternatives</td>
|
||
|
|
<td>Supported.</td>
|
||
|
|
<td>3</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td>Color</td>
|
||
|
|
<td>Single color palette.</td>
|
||
|
|
<td>1</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td>Other</td>
|
||
|
|
<td>No <code>lang</code> attribute.</td>
|
||
|
|
<td>1</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td></td>
|
||
|
|
<td></td>
|
||
|
|
<td><strong>11</strong></td>
|
||
|
|
</tr>
|
||
|
|
</tbody>
|
||
|
|
</table>
|
||
|
|
<h2 id="webflow">Webflow</h2>
|
||
|
|
<p><a href="https://webflow.com/" target="_blank" rel="external">Webflow</a> is a drag-and-drop builder. Currently bottom of my review list due to AI features.</p>
|
||
|
|
<h2 id="webnode">Webnode</h2>
|
||
|
|
<p><a href="https://https://www.webnode.com">Webnode</a> is a drag-and-drop builder (with gen AI features). I reviewed a site built in it sent by the recommender as well as one of the premade themes.</p>
|
||
|
|
<table>
|
||
|
|
<thead>
|
||
|
|
<tr>
|
||
|
|
<th>Factor</th>
|
||
|
|
<th>Notes</th>
|
||
|
|
<th>Score</th>
|
||
|
|
</tr>
|
||
|
|
</thead>
|
||
|
|
<tbody>
|
||
|
|
<tr>
|
||
|
|
<td>Semantic HTML</td>
|
||
|
|
<td>Properly uses <code><header></code>, <code><nav></code>, <code><main></code>, <code><footer></code>. Nice.</td>
|
||
|
|
<td>3</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td>Keyboard navigation</td>
|
||
|
|
<td>Browser default focus outlines.</td>
|
||
|
|
<td>2</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td>Reading order</td>
|
||
|
|
<td>Decent, thanks to a block-based editor.</td>
|
||
|
|
<td>3</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td>Text alternatives</td>
|
||
|
|
<td>Supported.</td>
|
||
|
|
<td>3</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td>Color</td>
|
||
|
|
<td>Single color palette.</td>
|
||
|
|
<td>1</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td>Other</td>
|
||
|
|
<td>n/a</td>
|
||
|
|
<td>2</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td></td>
|
||
|
|
<td></td>
|
||
|
|
<td><strong>14</strong></td>
|
||
|
|
</tr>
|
||
|
|
</tbody>
|
||
|
|
</table>
|
||
|
|
<h2 id="wobble-web">Wobble Web</h2>
|
||
|
|
<p><a href="https://wwwobble.org/">Wobble Web</a> is a drag-and-drop editor that allows you to edit the code directly in browser as well. There's no preview site; you can download the code and view it locally or publish it yourself. While there's a lot of flexibility for text styling, it's all tied directly to CSS rules, so you need to know some CSS to be effective.</p>
|
||
|
|
<table>
|
||
|
|
<thead>
|
||
|
|
<tr>
|
||
|
|
<th>Factor</th>
|
||
|
|
<th>Notes</th>
|
||
|
|
<th>Score</th>
|
||
|
|
</tr>
|
||
|
|
</thead>
|
||
|
|
<tbody>
|
||
|
|
<tr>
|
||
|
|
<td>Semantic HTML</td>
|
||
|
|
<td>It's <code><div></code>s all the way down. No headers. <code><p></code> tags used, at least.</td>
|
||
|
|
<td>1</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td>Keyboard navigation</td>
|
||
|
|
<td>Browser default focus outlines. Should be editable with CSS... if we wanted to write CSS.</td>
|
||
|
|
<td>2</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td>Reading order</td>
|
||
|
|
<td>Order of elements added determines reading order.</td>
|
||
|
|
<td>1</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td>Text alternatives</td>
|
||
|
|
<td>Supported.</td>
|
||
|
|
<td>3</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td>Color</td>
|
||
|
|
<td>Single color palette.</td>
|
||
|
|
<td>1</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td>Other</td>
|
||
|
|
<td>n/a</td>
|
||
|
|
<td>2</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td></td>
|
||
|
|
<td></td>
|
||
|
|
<td><strong>10</strong></td>
|
||
|
|
</tr>
|
||
|
|
</tbody>
|
||
|
|
</table>
|
||
|
|
<h2 id="wordpress-builders">Wordpress builders</h2>
|
||
|
|
<p>Gotta dive into Wordpress to explore these and haven't yet.</p>
|
||
|
|
<h3 id="beaver-builder">Beaver Builder</h3>
|
||
|
|
<p><a href="https://www.wpbeaverbuilder.com/" target="_blank" rel="external">Beaver Builder</a>.</p>
|
||
|
|
<h3 id="divi">Divi</h3>
|
||
|
|
<p><a href="https://www.elegantthemes.com/gallery/divi/" target="_blank" rel="external">Divi</a>.</p>
|
||
|
|
<hr>
|
||
|
|
<h2 id="summary">summary</h2>
|
||
|
|
<p>Overall, I explored 11 builders, couldn't test 1 due to OS restrictions, and have 3 left for future review.</p>
|
||
|
|
<table>
|
||
|
|
<thead>
|
||
|
|
<tr>
|
||
|
|
<th>Builder</th>
|
||
|
|
<th>Score</th>
|
||
|
|
</tr>
|
||
|
|
</thead>
|
||
|
|
<tbody>
|
||
|
|
<tr>
|
||
|
|
<td>Webnode*</td>
|
||
|
|
<td>14</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td>Figma Sites*</td>
|
||
|
|
<td>13</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td>Startup</td>
|
||
|
|
<td>13</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td>Blocs**</td>
|
||
|
|
<td>12</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td>Univer.se</td>
|
||
|
|
<td>11</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td>Cowsites</td>
|
||
|
|
<td>10</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td>Showit</td>
|
||
|
|
<td>10</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td>Wobble Web</td>
|
||
|
|
<td>10</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td>Kiki</td>
|
||
|
|
<td>9</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td>mmm.page</td>
|
||
|
|
<td>9</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td>Straw.page</td>
|
||
|
|
<td>7</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td>Rapidweaver</td>
|
||
|
|
<td>Couldn't test (MacOS only).</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td>Beaver Builder (Wordpress)</td>
|
||
|
|
<td>Yet to review.</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td>Divi (Wordpress)</td>
|
||
|
|
<td>Yet to review.</td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td>Webflow*</td>
|
||
|
|
<td>Yet to review.</td>
|
||
|
|
</tr>
|
||
|
|
</tbody>
|
||
|
|
</table>
|
||
|
|
<p>*Promotes gen AI features.</p>
|
||
|
|
<p>**Usability concerns on iPad.</p>
|
||
|
|
|
||
|
|
</article>
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
<nav aria-label="pagination">
|
||
|
|
<ol class="pagination post-pagination">
|
||
|
|
|
||
|
|
<li class="older">
|
||
|
|
<a href="/loud-table-runner/">
|
||
|
|
<i class="fa-solid fa-hand-point-left" aria-hidden="true"></i> loud table runner
|
||
|
|
</a>
|
||
|
|
</li>
|
||
|
|
|
||
|
|
|
||
|
|
</ol>
|
||
|
|
</nav>
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
<hr>
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
<section class="related-posts">
|
||
|
|
<h2 data-ha-exclude="" id="related-posts">related posts</h2>
|
||
|
|
<ol id="postlist">
|
||
|
|
|
||
|
|
<li class="post">
|
||
|
|
<a class="postlink" href="/my-favorite-git-flag/">
|
||
|
|
<h2 data-ha-exclude="" id="my-favorite-git-flag">my favorite git flag </h2>
|
||
|
|
|
||
|
|
<ul class="postlist-tags">
|
||
|
|
|
||
|
|
<li>software</li>
|
||
|
|
|
||
|
|
</ul>
|
||
|
|
<img src="/img/shelf-mushrooms.jpg" alt="Picture unrelated to post. Creamy beige shelf mushrooms on a mossy tree trunk." loading="lazy" decoding="async" width="1000" height="666">
|
||
|
|
|
||
|
|
</a>
|
||
|
|
</li>
|
||
|
|
|
||
|
|
<li class="post">
|
||
|
|
<a class="postlink" href="/accessible-image-modals/">
|
||
|
|
<h2 data-ha-exclude="" id="accessible-image-modals">accessible image modals </h2>
|
||
|
|
|
||
|
|
<ul class="postlist-tags">
|
||
|
|
|
||
|
|
<li>software</li>
|
||
|
|
|
||
|
|
</ul>
|
||
|
|
<img src="/img/snacking-seagull.jpg" alt="Image unrelated to post. A seagull floating in the water with a starfish hanging out of eir mouth." loading="lazy" decoding="async" width="1000" height="666">
|
||
|
|
|
||
|
|
</a>
|
||
|
|
</li>
|
||
|
|
|
||
|
|
<li class="post">
|
||
|
|
<a class="postlink" href="/designing-a-bag/">
|
||
|
|
<h2 data-ha-exclude="" id="designing-a-bag">designing a bag </h2>
|
||
|
|
|
||
|
|
<ul class="postlist-tags">
|
||
|
|
|
||
|
|
<li>leather</li>
|
||
|
|
|
||
|
|
<li>software</li>
|
||
|
|
|
||
|
|
</ul>
|
||
|
|
<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">
|
||
|
|
|
||
|
|
</a>
|
||
|
|
</li>
|
||
|
|
|
||
|
|
</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>
|
||
|
|
</footer>
|
||
|
|
|
||
|
|
|
||
|
|
<!-- This page `/visual-site-builders-and-accessibility/` was built on 2026-08-06T18:38:07.769Z -->
|
||
|
|
</body>
|
||
|
|
</html>
|