Initial commit
417
404.html
Normal file
|
|
@ -0,0 +1,417 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Notchee</title>
|
||||||
|
<meta name="description" content="I am writing about my experiences as a naval navel-gazer.">
|
||||||
|
<link rel="alternate" href="/feed/feed.xml" type="application/atom+xml" title="Notchee">
|
||||||
|
<link rel="icon" type="image/x-icon" href="/img/notchee-logo.jpg">
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<style>/* This is an arbitrary CSS string added to the bundle */
|
||||||
|
/* Defaults */
|
||||||
|
:root {
|
||||||
|
--font-family: -apple-system, system-ui, sans-serif;
|
||||||
|
--font-family-monospace: Consolas, Menlo, Monaco, Andale Mono WT, Andale Mono, Lucida Console, Lucida Sans Typewriter, DejaVu Sans Mono, Bitstream Vera Sans Mono, Liberation Mono, Nimbus Mono L, Courier New, Courier, monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Theme colors */
|
||||||
|
:root {
|
||||||
|
--color-gray-20: #e0e0e0;
|
||||||
|
--color-gray-50: #C0C0C0;
|
||||||
|
--color-gray-90: #333;
|
||||||
|
|
||||||
|
--background-color: #fff;
|
||||||
|
|
||||||
|
--text-color: var(--color-gray-90);
|
||||||
|
--text-color-link: #082840;
|
||||||
|
--text-color-link-active: #5f2b48;
|
||||||
|
--text-color-link-visited: #17050F;
|
||||||
|
|
||||||
|
--syntax-tab-size: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
:root {
|
||||||
|
--color-gray-20: #e0e0e0;
|
||||||
|
--color-gray-50: #C0C0C0;
|
||||||
|
--color-gray-90: #dad8d8;
|
||||||
|
|
||||||
|
/* --text-color is assigned to --color-gray-_ above */
|
||||||
|
--text-color-link: #1493fb;
|
||||||
|
--text-color-link-active: #6969f7;
|
||||||
|
--text-color-link-visited: #a6a6f8;
|
||||||
|
|
||||||
|
--background-color: #15202b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Global stylesheet */
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
@view-transition {
|
||||||
|
navigation: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0 auto;
|
||||||
|
font-family: var(--font-family);
|
||||||
|
color: var(--text-color);
|
||||||
|
background-color: var(--background-color);
|
||||||
|
}
|
||||||
|
html {
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
max-width: 40em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* https://www.a11yproject.com/posts/how-to-hide-content/ */
|
||||||
|
.visually-hidden:not(:focus):not(:active) {
|
||||||
|
clip: rect(0 0 0 0);
|
||||||
|
clip-path: inset(50%);
|
||||||
|
height: 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: absolute;
|
||||||
|
white-space: nowrap;
|
||||||
|
width: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fluid images via https://www.zachleat.com/web/fluid-images/ */
|
||||||
|
img{
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
img[width][height] {
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
img[src$=".svg"] {
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
max-width: none;
|
||||||
|
}
|
||||||
|
video,
|
||||||
|
iframe {
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
iframe {
|
||||||
|
aspect-ratio: 16/9;
|
||||||
|
}
|
||||||
|
|
||||||
|
p:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
a[href] {
|
||||||
|
color: var(--text-color-link);
|
||||||
|
}
|
||||||
|
a[href]:visited {
|
||||||
|
color: var(--text-color-link-visited);
|
||||||
|
}
|
||||||
|
a[href]:hover,
|
||||||
|
a[href]:active {
|
||||||
|
color: var(--text-color-link-active);
|
||||||
|
}
|
||||||
|
|
||||||
|
main,
|
||||||
|
footer {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
main :first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
border-bottom: 1px dashed var(--color-gray-20);
|
||||||
|
}
|
||||||
|
|
||||||
|
#skip-link {
|
||||||
|
text-decoration: none;
|
||||||
|
background: var(--background-color);
|
||||||
|
color: var(--text-color);
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border: 1px solid var(--color-gray-90);
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Prevent visually-hidden skip link fom pushing content around when focused */
|
||||||
|
#skip-link.visually-hidden:focus {
|
||||||
|
position: absolute;
|
||||||
|
top: 1rem;
|
||||||
|
left: 1rem;
|
||||||
|
/* Ensure it is positioned on top of everything else when it is shown */
|
||||||
|
z-index: 999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.links-nextprev {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: .5em 1em;
|
||||||
|
list-style: "";
|
||||||
|
border-top: 1px dashed var(--color-gray-20);
|
||||||
|
padding: 1em 0;
|
||||||
|
}
|
||||||
|
.links-nextprev > * {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
.links-nextprev-next {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
margin: 1em 0;
|
||||||
|
}
|
||||||
|
table td,
|
||||||
|
table th {
|
||||||
|
padding-right: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre,
|
||||||
|
code {
|
||||||
|
font-family: var(--font-family-monospace);
|
||||||
|
}
|
||||||
|
pre:not([class*="language-"]) {
|
||||||
|
margin: .5em 0;
|
||||||
|
line-height: 1.375; /* 22px /16 */
|
||||||
|
-moz-tab-size: var(--syntax-tab-size);
|
||||||
|
-o-tab-size: var(--syntax-tab-size);
|
||||||
|
tab-size: var(--syntax-tab-size);
|
||||||
|
-webkit-hyphens: none;
|
||||||
|
-ms-hyphens: none;
|
||||||
|
hyphens: none;
|
||||||
|
direction: ltr;
|
||||||
|
text-align: left;
|
||||||
|
white-space: pre;
|
||||||
|
word-spacing: normal;
|
||||||
|
word-break: normal;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
code {
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Header */
|
||||||
|
header {
|
||||||
|
display: flex;
|
||||||
|
gap: 1em;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 1em;
|
||||||
|
}
|
||||||
|
.home-link {
|
||||||
|
flex-grow: 1;
|
||||||
|
font-size: 1em; /* 16px /16 */
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
.home-link:link:not(:hover) {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Nav */
|
||||||
|
.nav {
|
||||||
|
display: flex;
|
||||||
|
gap: .5em 1em;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
.nav-item {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
.nav-item a[href]:not(:hover) {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
.nav a[href][aria-current="page"] {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Posts list */
|
||||||
|
.postlist {
|
||||||
|
counter-reset: start-from var(--postlist-index);
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
padding-left: 1.5rem;
|
||||||
|
}
|
||||||
|
.postlist-item {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: baseline;
|
||||||
|
counter-increment: start-from -1;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
.postlist-item:before {
|
||||||
|
display: inline-block;
|
||||||
|
pointer-events: none;
|
||||||
|
content: "" counter(start-from, decimal-leading-zero) ". ";
|
||||||
|
line-height: 100%;
|
||||||
|
text-align: right;
|
||||||
|
margin-left: -1.5rem;
|
||||||
|
}
|
||||||
|
.postlist-date,
|
||||||
|
.postlist-item:before {
|
||||||
|
font-size: 0.8125em; /* 13px /16 */
|
||||||
|
color: var(--color-gray-90);
|
||||||
|
}
|
||||||
|
.postlist-date {
|
||||||
|
word-spacing: -0.5px;
|
||||||
|
}
|
||||||
|
.postlist-link {
|
||||||
|
font-size: 1.1875em; /* 19px /16 */
|
||||||
|
font-weight: 700;
|
||||||
|
flex-basis: calc(100% - 1.5rem);
|
||||||
|
padding-left: .25em;
|
||||||
|
padding-right: .5em;
|
||||||
|
text-underline-position: from-font;
|
||||||
|
text-underline-offset: 0;
|
||||||
|
text-decoration-thickness: 1px;
|
||||||
|
}
|
||||||
|
.postlist-item-active .postlist-link {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tags */
|
||||||
|
.post-tag {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
text-transform: capitalize;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
.postlist-item > .post-tag {
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tags list */
|
||||||
|
.post-metadata {
|
||||||
|
display: inline-flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: .5em;
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.post-metadata time {
|
||||||
|
margin-right: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fib ::selection {
|
||||||
|
color:#FFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fib ::-webkit-selection {
|
||||||
|
color:#FFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fib {
|
||||||
|
-webkit-tap-highlight-color: white;
|
||||||
|
}
|
||||||
|
/* Message Box */
|
||||||
|
.message-box {
|
||||||
|
--color-message-box: #ffc;
|
||||||
|
|
||||||
|
display: block;
|
||||||
|
background-color: var(--color-message-box);
|
||||||
|
color: var(--color-gray-90);
|
||||||
|
padding: 1em 0.625em; /* 16px 10px /16 */
|
||||||
|
}
|
||||||
|
.message-box ol {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
.message-box {
|
||||||
|
/* Default */
|
||||||
|
/* --color-message-box: #082840; */
|
||||||
|
--color-message-box: none;
|
||||||
|
}
|
||||||
|
}</style>
|
||||||
|
|
||||||
|
<script src="/bundle.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<a href="#main" id="skip-link" class="visually-hidden">Skip to main content</a>
|
||||||
|
|
||||||
|
<header>
|
||||||
|
|
||||||
|
<picture><source type="image/avif" srcset="/img/eCT5mOL4LD-60.avif 60w"><source type="image/webp" srcset="/img/eCT5mOL4LD-60.webp 60w"><img loading="lazy" decoding="async" src="/img/eCT5mOL4LD-60.jpeg" alt="notchee logo" style="width:2rem;" width="60" height="60"></picture> <a href="/" class="home-link">Notchee</a>
|
||||||
|
<nav>
|
||||||
|
<h2 class="visually-hidden" id="top-level-navigation-menu">Top level navigation menu</h2>
|
||||||
|
<ul class="nav">
|
||||||
|
<li class="nav-item"><a href="/">Home</a></li>
|
||||||
|
<li class="nav-item"><a href="/blog/">Archive</a></li>
|
||||||
|
<li class="nav-item"><a href="/about/">About</a></li>
|
||||||
|
<li class="nav-item"><a href="/feed/feed.xml">Feed</a></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main id="main">
|
||||||
|
<heading-anchors>
|
||||||
|
<!-- Delete this block, which will also remove the component CSS from the bundle -->
|
||||||
|
|
||||||
|
<div class="message-box">
|
||||||
|
|
||||||
|
<section class="latest-post">
|
||||||
|
<article>
|
||||||
|
<p style="font-size:1.75rem; text-align:center; font-weight:bold">Notchee #1: <span style="font-weight:normal">Good Governance</span></p>
|
||||||
|
<p style="text-align:center; font-size:1rem; margin-top:-1.5rem; margin-bottom:2.5rem;"><time datetime="2025-10-24">24 October 2025</time></p>
|
||||||
|
<p><picture><source type="image/avif" srcset="/img/uOBi7x7dUh-1871.avif 1871w"><source type="image/webp" srcset="/img/uOBi7x7dUh-1871.webp 1871w"><img loading="lazy" decoding="async" src="/img/uOBi7x7dUh-1871.jpeg" alt="Four square heads look on suspiciously at another square head with rounded corners." width="1871" height="1171"></picture></p>
|
||||||
|
<p style="text-align:center;"><em>Gary's new haircut manages to completely derail the meeting of the Society Questioning UnApproved Rounded Entities</em></p>
|
||||||
|
</article>
|
||||||
|
<div class="fib">
|
||||||
|
<p style="text-align:center; font-size:0.75rem; color:#15202b;">F<sub id="fib_seq"></sub> = <span id="fib_num"></span></p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
<!-- Stop deleting -->
|
||||||
|
|
||||||
|
<h1 id="content-not-found">Content not found.</h1>
|
||||||
|
<p>Go <a href="/">home</a>.</p>
|
||||||
|
<!--
|
||||||
|
|
||||||
|
Read more: https://www.11ty.dev/docs/quicktips/not-found/
|
||||||
|
|
||||||
|
This is compatible with:
|
||||||
|
|
||||||
|
- GitHub Pages: https://help.github.com/articles/creating-a-custom-404-page-for-your-github-pages-site/
|
||||||
|
- GitLab Pages: https://docs.gitlab.com/ee/user/project/pages/introduction.html#custom-error-codes-pages
|
||||||
|
- Netlify: https://www.netlify.com/docs/redirects/#custom-404
|
||||||
|
- Cloudflare Pages: https://developers.cloudflare.com/pages/platform/serving-pages/#not-found-behavior
|
||||||
|
- Vercel: https://vercel.com/guides/custom-404-page#static-site-generator-ssg
|
||||||
|
-->
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</heading-anchors>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer style="border-top: 1px dashed var(--color-gray-20);">
|
||||||
|
<p style="text-align:center">
|
||||||
|
<em>Built with <a href="https://www.11ty.dev/">Eleventy v3.1.2</a> and <a href="https://github.com/11ty/eleventy-base-blog">Eleventy Base Blog v9 theme</a></em>
|
||||||
|
</p>
|
||||||
|
<p style="text-align:center">
|
||||||
|
<em>© 2025. All content is licensed <a href="https://creativecommons.org/licenses/by-nc-nd/4.0/">CC BY-NC-ND 4.0</a> unless otherwise indicated. You are encouraged to share this work without modification so long as you give credit to <a href="https://notchee.art">notchee.art</a>. To request commercial use of any part of this work please <a href="mailto:hello@notchee.art">contact me</a>.</em>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<!-- This page `/404.html` was built on 2025-10-26T14:26:36.247Z -->
|
||||||
|
<script type="module" src="/dist/k7YWH9wzXb.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
405
about/index.html
Normal file
|
|
@ -0,0 +1,405 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Notchee</title>
|
||||||
|
<meta name="description" content="I am writing about my experiences as a naval navel-gazer.">
|
||||||
|
<link rel="alternate" href="/feed/feed.xml" type="application/atom+xml" title="Notchee">
|
||||||
|
<link rel="icon" type="image/x-icon" href="/img/notchee-logo.jpg">
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<style>/* This is an arbitrary CSS string added to the bundle */
|
||||||
|
/* Defaults */
|
||||||
|
:root {
|
||||||
|
--font-family: -apple-system, system-ui, sans-serif;
|
||||||
|
--font-family-monospace: Consolas, Menlo, Monaco, Andale Mono WT, Andale Mono, Lucida Console, Lucida Sans Typewriter, DejaVu Sans Mono, Bitstream Vera Sans Mono, Liberation Mono, Nimbus Mono L, Courier New, Courier, monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Theme colors */
|
||||||
|
:root {
|
||||||
|
--color-gray-20: #e0e0e0;
|
||||||
|
--color-gray-50: #C0C0C0;
|
||||||
|
--color-gray-90: #333;
|
||||||
|
|
||||||
|
--background-color: #fff;
|
||||||
|
|
||||||
|
--text-color: var(--color-gray-90);
|
||||||
|
--text-color-link: #082840;
|
||||||
|
--text-color-link-active: #5f2b48;
|
||||||
|
--text-color-link-visited: #17050F;
|
||||||
|
|
||||||
|
--syntax-tab-size: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
:root {
|
||||||
|
--color-gray-20: #e0e0e0;
|
||||||
|
--color-gray-50: #C0C0C0;
|
||||||
|
--color-gray-90: #dad8d8;
|
||||||
|
|
||||||
|
/* --text-color is assigned to --color-gray-_ above */
|
||||||
|
--text-color-link: #1493fb;
|
||||||
|
--text-color-link-active: #6969f7;
|
||||||
|
--text-color-link-visited: #a6a6f8;
|
||||||
|
|
||||||
|
--background-color: #15202b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Global stylesheet */
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
@view-transition {
|
||||||
|
navigation: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0 auto;
|
||||||
|
font-family: var(--font-family);
|
||||||
|
color: var(--text-color);
|
||||||
|
background-color: var(--background-color);
|
||||||
|
}
|
||||||
|
html {
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
max-width: 40em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* https://www.a11yproject.com/posts/how-to-hide-content/ */
|
||||||
|
.visually-hidden:not(:focus):not(:active) {
|
||||||
|
clip: rect(0 0 0 0);
|
||||||
|
clip-path: inset(50%);
|
||||||
|
height: 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: absolute;
|
||||||
|
white-space: nowrap;
|
||||||
|
width: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fluid images via https://www.zachleat.com/web/fluid-images/ */
|
||||||
|
img{
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
img[width][height] {
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
img[src$=".svg"] {
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
max-width: none;
|
||||||
|
}
|
||||||
|
video,
|
||||||
|
iframe {
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
iframe {
|
||||||
|
aspect-ratio: 16/9;
|
||||||
|
}
|
||||||
|
|
||||||
|
p:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
a[href] {
|
||||||
|
color: var(--text-color-link);
|
||||||
|
}
|
||||||
|
a[href]:visited {
|
||||||
|
color: var(--text-color-link-visited);
|
||||||
|
}
|
||||||
|
a[href]:hover,
|
||||||
|
a[href]:active {
|
||||||
|
color: var(--text-color-link-active);
|
||||||
|
}
|
||||||
|
|
||||||
|
main,
|
||||||
|
footer {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
main :first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
border-bottom: 1px dashed var(--color-gray-20);
|
||||||
|
}
|
||||||
|
|
||||||
|
#skip-link {
|
||||||
|
text-decoration: none;
|
||||||
|
background: var(--background-color);
|
||||||
|
color: var(--text-color);
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border: 1px solid var(--color-gray-90);
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Prevent visually-hidden skip link fom pushing content around when focused */
|
||||||
|
#skip-link.visually-hidden:focus {
|
||||||
|
position: absolute;
|
||||||
|
top: 1rem;
|
||||||
|
left: 1rem;
|
||||||
|
/* Ensure it is positioned on top of everything else when it is shown */
|
||||||
|
z-index: 999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.links-nextprev {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: .5em 1em;
|
||||||
|
list-style: "";
|
||||||
|
border-top: 1px dashed var(--color-gray-20);
|
||||||
|
padding: 1em 0;
|
||||||
|
}
|
||||||
|
.links-nextprev > * {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
.links-nextprev-next {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
margin: 1em 0;
|
||||||
|
}
|
||||||
|
table td,
|
||||||
|
table th {
|
||||||
|
padding-right: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre,
|
||||||
|
code {
|
||||||
|
font-family: var(--font-family-monospace);
|
||||||
|
}
|
||||||
|
pre:not([class*="language-"]) {
|
||||||
|
margin: .5em 0;
|
||||||
|
line-height: 1.375; /* 22px /16 */
|
||||||
|
-moz-tab-size: var(--syntax-tab-size);
|
||||||
|
-o-tab-size: var(--syntax-tab-size);
|
||||||
|
tab-size: var(--syntax-tab-size);
|
||||||
|
-webkit-hyphens: none;
|
||||||
|
-ms-hyphens: none;
|
||||||
|
hyphens: none;
|
||||||
|
direction: ltr;
|
||||||
|
text-align: left;
|
||||||
|
white-space: pre;
|
||||||
|
word-spacing: normal;
|
||||||
|
word-break: normal;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
code {
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Header */
|
||||||
|
header {
|
||||||
|
display: flex;
|
||||||
|
gap: 1em;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 1em;
|
||||||
|
}
|
||||||
|
.home-link {
|
||||||
|
flex-grow: 1;
|
||||||
|
font-size: 1em; /* 16px /16 */
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
.home-link:link:not(:hover) {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Nav */
|
||||||
|
.nav {
|
||||||
|
display: flex;
|
||||||
|
gap: .5em 1em;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
.nav-item {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
.nav-item a[href]:not(:hover) {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
.nav a[href][aria-current="page"] {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Posts list */
|
||||||
|
.postlist {
|
||||||
|
counter-reset: start-from var(--postlist-index);
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
padding-left: 1.5rem;
|
||||||
|
}
|
||||||
|
.postlist-item {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: baseline;
|
||||||
|
counter-increment: start-from -1;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
.postlist-item:before {
|
||||||
|
display: inline-block;
|
||||||
|
pointer-events: none;
|
||||||
|
content: "" counter(start-from, decimal-leading-zero) ". ";
|
||||||
|
line-height: 100%;
|
||||||
|
text-align: right;
|
||||||
|
margin-left: -1.5rem;
|
||||||
|
}
|
||||||
|
.postlist-date,
|
||||||
|
.postlist-item:before {
|
||||||
|
font-size: 0.8125em; /* 13px /16 */
|
||||||
|
color: var(--color-gray-90);
|
||||||
|
}
|
||||||
|
.postlist-date {
|
||||||
|
word-spacing: -0.5px;
|
||||||
|
}
|
||||||
|
.postlist-link {
|
||||||
|
font-size: 1.1875em; /* 19px /16 */
|
||||||
|
font-weight: 700;
|
||||||
|
flex-basis: calc(100% - 1.5rem);
|
||||||
|
padding-left: .25em;
|
||||||
|
padding-right: .5em;
|
||||||
|
text-underline-position: from-font;
|
||||||
|
text-underline-offset: 0;
|
||||||
|
text-decoration-thickness: 1px;
|
||||||
|
}
|
||||||
|
.postlist-item-active .postlist-link {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tags */
|
||||||
|
.post-tag {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
text-transform: capitalize;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
.postlist-item > .post-tag {
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tags list */
|
||||||
|
.post-metadata {
|
||||||
|
display: inline-flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: .5em;
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.post-metadata time {
|
||||||
|
margin-right: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fib ::selection {
|
||||||
|
color:#FFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fib ::-webkit-selection {
|
||||||
|
color:#FFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fib {
|
||||||
|
-webkit-tap-highlight-color: white;
|
||||||
|
}
|
||||||
|
/* Message Box */
|
||||||
|
.message-box {
|
||||||
|
--color-message-box: #ffc;
|
||||||
|
|
||||||
|
display: block;
|
||||||
|
background-color: var(--color-message-box);
|
||||||
|
color: var(--color-gray-90);
|
||||||
|
padding: 1em 0.625em; /* 16px 10px /16 */
|
||||||
|
}
|
||||||
|
.message-box ol {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
.message-box {
|
||||||
|
/* Default */
|
||||||
|
/* --color-message-box: #082840; */
|
||||||
|
--color-message-box: none;
|
||||||
|
}
|
||||||
|
}</style>
|
||||||
|
|
||||||
|
<script src="/bundle.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<a href="#main" id="skip-link" class="visually-hidden">Skip to main content</a>
|
||||||
|
|
||||||
|
<header>
|
||||||
|
|
||||||
|
<picture><source type="image/avif" srcset="/img/eCT5mOL4LD-60.avif 60w"><source type="image/webp" srcset="/img/eCT5mOL4LD-60.webp 60w"><img loading="lazy" decoding="async" src="/img/eCT5mOL4LD-60.jpeg" alt="notchee logo" style="width:2rem;" width="60" height="60"></picture> <a href="/" class="home-link">Notchee</a>
|
||||||
|
<nav>
|
||||||
|
<h2 class="visually-hidden" id="top-level-navigation-menu">Top level navigation menu</h2>
|
||||||
|
<ul class="nav">
|
||||||
|
<li class="nav-item"><a href="/">Home</a></li>
|
||||||
|
<li class="nav-item"><a href="/blog/">Archive</a></li>
|
||||||
|
<li class="nav-item"><a href="/about/" aria-current="page">About</a></li>
|
||||||
|
<li class="nav-item"><a href="/feed/feed.xml">Feed</a></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main id="main">
|
||||||
|
<heading-anchors>
|
||||||
|
<!-- Delete this block, which will also remove the component CSS from the bundle -->
|
||||||
|
|
||||||
|
<div class="message-box">
|
||||||
|
|
||||||
|
<section class="latest-post">
|
||||||
|
<article>
|
||||||
|
<p style="font-size:1.75rem; text-align:center; font-weight:bold">Notchee #1: <span style="font-weight:normal">Good Governance</span></p>
|
||||||
|
<p style="text-align:center; font-size:1rem; margin-top:-1.5rem; margin-bottom:2.5rem;"><time datetime="2025-10-24">24 October 2025</time></p>
|
||||||
|
<p><picture><source type="image/avif" srcset="/img/uOBi7x7dUh-1871.avif 1871w"><source type="image/webp" srcset="/img/uOBi7x7dUh-1871.webp 1871w"><img loading="lazy" decoding="async" src="/img/uOBi7x7dUh-1871.jpeg" alt="Four square heads look on suspiciously at another square head with rounded corners." width="1871" height="1171"></picture></p>
|
||||||
|
<p style="text-align:center;"><em>Gary's new haircut manages to completely derail the meeting of the Society Questioning UnApproved Rounded Entities</em></p>
|
||||||
|
</article>
|
||||||
|
<div class="fib">
|
||||||
|
<p style="text-align:center; font-size:0.75rem; color:#15202b;">F<sub id="fib_seq"></sub> = <span id="fib_num"></span></p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
<!-- Stop deleting -->
|
||||||
|
|
||||||
|
<h1 id="about">About</h1>
|
||||||
|
<p>I am a person that writes stuff.</p>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</heading-anchors>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer style="border-top: 1px dashed var(--color-gray-20);">
|
||||||
|
<p style="text-align:center">
|
||||||
|
<em>Built with <a href="https://www.11ty.dev/">Eleventy v3.1.2</a> and <a href="https://github.com/11ty/eleventy-base-blog">Eleventy Base Blog v9 theme</a></em>
|
||||||
|
</p>
|
||||||
|
<p style="text-align:center">
|
||||||
|
<em>© 2025. All content is licensed <a href="https://creativecommons.org/licenses/by-nc-nd/4.0/">CC BY-NC-ND 4.0</a> unless otherwise indicated. You are encouraged to share this work without modification so long as you give credit to <a href="https://notchee.art">notchee.art</a>. To request commercial use of any part of this work please <a href="mailto:hello@notchee.art">contact me</a>.</em>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<!-- This page `/about/` was built on 2025-10-26T14:26:36.247Z -->
|
||||||
|
<script type="module" src="/dist/k7YWH9wzXb.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
413
blog/index.html
Normal file
|
|
@ -0,0 +1,413 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Notchee</title>
|
||||||
|
<meta name="description" content="I am writing about my experiences as a naval navel-gazer.">
|
||||||
|
<link rel="alternate" href="/feed/feed.xml" type="application/atom+xml" title="Notchee">
|
||||||
|
<link rel="icon" type="image/x-icon" href="/img/notchee-logo.jpg">
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<style>/* This is an arbitrary CSS string added to the bundle */
|
||||||
|
/* Defaults */
|
||||||
|
:root {
|
||||||
|
--font-family: -apple-system, system-ui, sans-serif;
|
||||||
|
--font-family-monospace: Consolas, Menlo, Monaco, Andale Mono WT, Andale Mono, Lucida Console, Lucida Sans Typewriter, DejaVu Sans Mono, Bitstream Vera Sans Mono, Liberation Mono, Nimbus Mono L, Courier New, Courier, monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Theme colors */
|
||||||
|
:root {
|
||||||
|
--color-gray-20: #e0e0e0;
|
||||||
|
--color-gray-50: #C0C0C0;
|
||||||
|
--color-gray-90: #333;
|
||||||
|
|
||||||
|
--background-color: #fff;
|
||||||
|
|
||||||
|
--text-color: var(--color-gray-90);
|
||||||
|
--text-color-link: #082840;
|
||||||
|
--text-color-link-active: #5f2b48;
|
||||||
|
--text-color-link-visited: #17050F;
|
||||||
|
|
||||||
|
--syntax-tab-size: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
:root {
|
||||||
|
--color-gray-20: #e0e0e0;
|
||||||
|
--color-gray-50: #C0C0C0;
|
||||||
|
--color-gray-90: #dad8d8;
|
||||||
|
|
||||||
|
/* --text-color is assigned to --color-gray-_ above */
|
||||||
|
--text-color-link: #1493fb;
|
||||||
|
--text-color-link-active: #6969f7;
|
||||||
|
--text-color-link-visited: #a6a6f8;
|
||||||
|
|
||||||
|
--background-color: #15202b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Global stylesheet */
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
@view-transition {
|
||||||
|
navigation: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0 auto;
|
||||||
|
font-family: var(--font-family);
|
||||||
|
color: var(--text-color);
|
||||||
|
background-color: var(--background-color);
|
||||||
|
}
|
||||||
|
html {
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
max-width: 40em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* https://www.a11yproject.com/posts/how-to-hide-content/ */
|
||||||
|
.visually-hidden:not(:focus):not(:active) {
|
||||||
|
clip: rect(0 0 0 0);
|
||||||
|
clip-path: inset(50%);
|
||||||
|
height: 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: absolute;
|
||||||
|
white-space: nowrap;
|
||||||
|
width: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fluid images via https://www.zachleat.com/web/fluid-images/ */
|
||||||
|
img{
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
img[width][height] {
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
img[src$=".svg"] {
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
max-width: none;
|
||||||
|
}
|
||||||
|
video,
|
||||||
|
iframe {
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
iframe {
|
||||||
|
aspect-ratio: 16/9;
|
||||||
|
}
|
||||||
|
|
||||||
|
p:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
a[href] {
|
||||||
|
color: var(--text-color-link);
|
||||||
|
}
|
||||||
|
a[href]:visited {
|
||||||
|
color: var(--text-color-link-visited);
|
||||||
|
}
|
||||||
|
a[href]:hover,
|
||||||
|
a[href]:active {
|
||||||
|
color: var(--text-color-link-active);
|
||||||
|
}
|
||||||
|
|
||||||
|
main,
|
||||||
|
footer {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
main :first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
border-bottom: 1px dashed var(--color-gray-20);
|
||||||
|
}
|
||||||
|
|
||||||
|
#skip-link {
|
||||||
|
text-decoration: none;
|
||||||
|
background: var(--background-color);
|
||||||
|
color: var(--text-color);
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border: 1px solid var(--color-gray-90);
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Prevent visually-hidden skip link fom pushing content around when focused */
|
||||||
|
#skip-link.visually-hidden:focus {
|
||||||
|
position: absolute;
|
||||||
|
top: 1rem;
|
||||||
|
left: 1rem;
|
||||||
|
/* Ensure it is positioned on top of everything else when it is shown */
|
||||||
|
z-index: 999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.links-nextprev {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: .5em 1em;
|
||||||
|
list-style: "";
|
||||||
|
border-top: 1px dashed var(--color-gray-20);
|
||||||
|
padding: 1em 0;
|
||||||
|
}
|
||||||
|
.links-nextprev > * {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
.links-nextprev-next {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
margin: 1em 0;
|
||||||
|
}
|
||||||
|
table td,
|
||||||
|
table th {
|
||||||
|
padding-right: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre,
|
||||||
|
code {
|
||||||
|
font-family: var(--font-family-monospace);
|
||||||
|
}
|
||||||
|
pre:not([class*="language-"]) {
|
||||||
|
margin: .5em 0;
|
||||||
|
line-height: 1.375; /* 22px /16 */
|
||||||
|
-moz-tab-size: var(--syntax-tab-size);
|
||||||
|
-o-tab-size: var(--syntax-tab-size);
|
||||||
|
tab-size: var(--syntax-tab-size);
|
||||||
|
-webkit-hyphens: none;
|
||||||
|
-ms-hyphens: none;
|
||||||
|
hyphens: none;
|
||||||
|
direction: ltr;
|
||||||
|
text-align: left;
|
||||||
|
white-space: pre;
|
||||||
|
word-spacing: normal;
|
||||||
|
word-break: normal;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
code {
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Header */
|
||||||
|
header {
|
||||||
|
display: flex;
|
||||||
|
gap: 1em;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 1em;
|
||||||
|
}
|
||||||
|
.home-link {
|
||||||
|
flex-grow: 1;
|
||||||
|
font-size: 1em; /* 16px /16 */
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
.home-link:link:not(:hover) {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Nav */
|
||||||
|
.nav {
|
||||||
|
display: flex;
|
||||||
|
gap: .5em 1em;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
.nav-item {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
.nav-item a[href]:not(:hover) {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
.nav a[href][aria-current="page"] {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Posts list */
|
||||||
|
.postlist {
|
||||||
|
counter-reset: start-from var(--postlist-index);
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
padding-left: 1.5rem;
|
||||||
|
}
|
||||||
|
.postlist-item {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: baseline;
|
||||||
|
counter-increment: start-from -1;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
.postlist-item:before {
|
||||||
|
display: inline-block;
|
||||||
|
pointer-events: none;
|
||||||
|
content: "" counter(start-from, decimal-leading-zero) ". ";
|
||||||
|
line-height: 100%;
|
||||||
|
text-align: right;
|
||||||
|
margin-left: -1.5rem;
|
||||||
|
}
|
||||||
|
.postlist-date,
|
||||||
|
.postlist-item:before {
|
||||||
|
font-size: 0.8125em; /* 13px /16 */
|
||||||
|
color: var(--color-gray-90);
|
||||||
|
}
|
||||||
|
.postlist-date {
|
||||||
|
word-spacing: -0.5px;
|
||||||
|
}
|
||||||
|
.postlist-link {
|
||||||
|
font-size: 1.1875em; /* 19px /16 */
|
||||||
|
font-weight: 700;
|
||||||
|
flex-basis: calc(100% - 1.5rem);
|
||||||
|
padding-left: .25em;
|
||||||
|
padding-right: .5em;
|
||||||
|
text-underline-position: from-font;
|
||||||
|
text-underline-offset: 0;
|
||||||
|
text-decoration-thickness: 1px;
|
||||||
|
}
|
||||||
|
.postlist-item-active .postlist-link {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tags */
|
||||||
|
.post-tag {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
text-transform: capitalize;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
.postlist-item > .post-tag {
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tags list */
|
||||||
|
.post-metadata {
|
||||||
|
display: inline-flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: .5em;
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.post-metadata time {
|
||||||
|
margin-right: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fib ::selection {
|
||||||
|
color:#FFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fib ::-webkit-selection {
|
||||||
|
color:#FFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fib {
|
||||||
|
-webkit-tap-highlight-color: white;
|
||||||
|
}
|
||||||
|
/* Message Box */
|
||||||
|
.message-box {
|
||||||
|
--color-message-box: #ffc;
|
||||||
|
|
||||||
|
display: block;
|
||||||
|
background-color: var(--color-message-box);
|
||||||
|
color: var(--color-gray-90);
|
||||||
|
padding: 1em 0.625em; /* 16px 10px /16 */
|
||||||
|
}
|
||||||
|
.message-box ol {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
.message-box {
|
||||||
|
/* Default */
|
||||||
|
/* --color-message-box: #082840; */
|
||||||
|
--color-message-box: none;
|
||||||
|
}
|
||||||
|
}</style>
|
||||||
|
|
||||||
|
<script src="/bundle.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<a href="#main" id="skip-link" class="visually-hidden">Skip to main content</a>
|
||||||
|
|
||||||
|
<header>
|
||||||
|
|
||||||
|
<picture><source type="image/avif" srcset="/img/eCT5mOL4LD-60.avif 60w"><source type="image/webp" srcset="/img/eCT5mOL4LD-60.webp 60w"><img loading="lazy" decoding="async" src="/img/eCT5mOL4LD-60.jpeg" alt="notchee logo" style="width:2rem;" width="60" height="60"></picture> <a href="/" class="home-link">Notchee</a>
|
||||||
|
<nav>
|
||||||
|
<h2 class="visually-hidden" id="top-level-navigation-menu">Top level navigation menu</h2>
|
||||||
|
<ul class="nav">
|
||||||
|
<li class="nav-item"><a href="/">Home</a></li>
|
||||||
|
<li class="nav-item"><a href="/blog/" aria-current="page">Archive</a></li>
|
||||||
|
<li class="nav-item"><a href="/about/">About</a></li>
|
||||||
|
<li class="nav-item"><a href="/feed/feed.xml">Feed</a></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main id="main">
|
||||||
|
<heading-anchors>
|
||||||
|
<!-- Delete this block, which will also remove the component CSS from the bundle -->
|
||||||
|
|
||||||
|
<div class="message-box">
|
||||||
|
|
||||||
|
<section class="latest-post">
|
||||||
|
<article>
|
||||||
|
<p style="font-size:1.75rem; text-align:center; font-weight:bold">Notchee #1: <span style="font-weight:normal">Good Governance</span></p>
|
||||||
|
<p style="text-align:center; font-size:1rem; margin-top:-1.5rem; margin-bottom:2.5rem;"><time datetime="2025-10-24">24 October 2025</time></p>
|
||||||
|
<p><picture><source type="image/avif" srcset="/img/uOBi7x7dUh-1871.avif 1871w"><source type="image/webp" srcset="/img/uOBi7x7dUh-1871.webp 1871w"><img loading="lazy" decoding="async" src="/img/uOBi7x7dUh-1871.jpeg" alt="Four square heads look on suspiciously at another square head with rounded corners." width="1871" height="1171"></picture></p>
|
||||||
|
<p style="text-align:center;"><em>Gary's new haircut manages to completely derail the meeting of the Society Questioning UnApproved Rounded Entities</em></p>
|
||||||
|
</article>
|
||||||
|
<div class="fib">
|
||||||
|
<p style="text-align:center; font-size:0.75rem; color:#15202b;">F<sub id="fib_seq"></sub> = <span id="fib_num"></span></p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
<!-- Stop deleting -->
|
||||||
|
|
||||||
|
<h1 id="archive">Archive</h1>
|
||||||
|
|
||||||
|
|
||||||
|
<ol reversed="" class="postlist" style="--postlist-index: 2;">
|
||||||
|
<li class="postlist-item">
|
||||||
|
<a href="/blog/notchee001/" class="postlist-link">Notchee #1: Good Governance</a>
|
||||||
|
<time class="postlist-date" datetime="2025-10-24">Friday, October 24, 2025</time>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</heading-anchors>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer style="border-top: 1px dashed var(--color-gray-20);">
|
||||||
|
<p style="text-align:center">
|
||||||
|
<em>Built with <a href="https://www.11ty.dev/">Eleventy v3.1.2</a> and <a href="https://github.com/11ty/eleventy-base-blog">Eleventy Base Blog v9 theme</a></em>
|
||||||
|
</p>
|
||||||
|
<p style="text-align:center">
|
||||||
|
<em>© 2025. All content is licensed <a href="https://creativecommons.org/licenses/by-nc-nd/4.0/">CC BY-NC-ND 4.0</a> unless otherwise indicated. You are encouraged to share this work without modification so long as you give credit to <a href="https://notchee.art">notchee.art</a>. To request commercial use of any part of this work please <a href="mailto:hello@notchee.art">contact me</a>.</em>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<!-- This page `/blog/` was built on 2025-10-26T14:26:36.246Z -->
|
||||||
|
<script type="module" src="/dist/k7YWH9wzXb.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
BIN
blog/notchee001/2O9wlKzLwf-1984.avif
Normal file
BIN
blog/notchee001/2O9wlKzLwf-1984.jpeg
Normal file
|
After Width: | Height: | Size: 418 KiB |
BIN
blog/notchee001/2O9wlKzLwf-1984.webp
Normal file
|
After Width: | Height: | Size: 303 KiB |
556
blog/notchee001/index.html
Normal file
|
|
@ -0,0 +1,556 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Notchee #1</title>
|
||||||
|
<meta name="description" content="I am writing about my experiences as a naval navel-gazer.">
|
||||||
|
<link rel="alternate" href="/feed/feed.xml" type="application/atom+xml" title="Notchee">
|
||||||
|
<link rel="icon" type="image/x-icon" href="/img/notchee-logo.jpg">
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<style>/* This is an arbitrary CSS string added to the bundle */
|
||||||
|
/* Defaults */
|
||||||
|
:root {
|
||||||
|
--font-family: -apple-system, system-ui, sans-serif;
|
||||||
|
--font-family-monospace: Consolas, Menlo, Monaco, Andale Mono WT, Andale Mono, Lucida Console, Lucida Sans Typewriter, DejaVu Sans Mono, Bitstream Vera Sans Mono, Liberation Mono, Nimbus Mono L, Courier New, Courier, monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Theme colors */
|
||||||
|
:root {
|
||||||
|
--color-gray-20: #e0e0e0;
|
||||||
|
--color-gray-50: #C0C0C0;
|
||||||
|
--color-gray-90: #333;
|
||||||
|
|
||||||
|
--background-color: #fff;
|
||||||
|
|
||||||
|
--text-color: var(--color-gray-90);
|
||||||
|
--text-color-link: #082840;
|
||||||
|
--text-color-link-active: #5f2b48;
|
||||||
|
--text-color-link-visited: #17050F;
|
||||||
|
|
||||||
|
--syntax-tab-size: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
:root {
|
||||||
|
--color-gray-20: #e0e0e0;
|
||||||
|
--color-gray-50: #C0C0C0;
|
||||||
|
--color-gray-90: #dad8d8;
|
||||||
|
|
||||||
|
/* --text-color is assigned to --color-gray-_ above */
|
||||||
|
--text-color-link: #1493fb;
|
||||||
|
--text-color-link-active: #6969f7;
|
||||||
|
--text-color-link-visited: #a6a6f8;
|
||||||
|
|
||||||
|
--background-color: #15202b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Global stylesheet */
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
@view-transition {
|
||||||
|
navigation: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0 auto;
|
||||||
|
font-family: var(--font-family);
|
||||||
|
color: var(--text-color);
|
||||||
|
background-color: var(--background-color);
|
||||||
|
}
|
||||||
|
html {
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
max-width: 40em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* https://www.a11yproject.com/posts/how-to-hide-content/ */
|
||||||
|
.visually-hidden:not(:focus):not(:active) {
|
||||||
|
clip: rect(0 0 0 0);
|
||||||
|
clip-path: inset(50%);
|
||||||
|
height: 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: absolute;
|
||||||
|
white-space: nowrap;
|
||||||
|
width: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fluid images via https://www.zachleat.com/web/fluid-images/ */
|
||||||
|
img{
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
img[width][height] {
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
img[src$=".svg"] {
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
max-width: none;
|
||||||
|
}
|
||||||
|
video,
|
||||||
|
iframe {
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
iframe {
|
||||||
|
aspect-ratio: 16/9;
|
||||||
|
}
|
||||||
|
|
||||||
|
p:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
a[href] {
|
||||||
|
color: var(--text-color-link);
|
||||||
|
}
|
||||||
|
a[href]:visited {
|
||||||
|
color: var(--text-color-link-visited);
|
||||||
|
}
|
||||||
|
a[href]:hover,
|
||||||
|
a[href]:active {
|
||||||
|
color: var(--text-color-link-active);
|
||||||
|
}
|
||||||
|
|
||||||
|
main,
|
||||||
|
footer {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
main :first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
border-bottom: 1px dashed var(--color-gray-20);
|
||||||
|
}
|
||||||
|
|
||||||
|
#skip-link {
|
||||||
|
text-decoration: none;
|
||||||
|
background: var(--background-color);
|
||||||
|
color: var(--text-color);
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border: 1px solid var(--color-gray-90);
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Prevent visually-hidden skip link fom pushing content around when focused */
|
||||||
|
#skip-link.visually-hidden:focus {
|
||||||
|
position: absolute;
|
||||||
|
top: 1rem;
|
||||||
|
left: 1rem;
|
||||||
|
/* Ensure it is positioned on top of everything else when it is shown */
|
||||||
|
z-index: 999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.links-nextprev {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: .5em 1em;
|
||||||
|
list-style: "";
|
||||||
|
border-top: 1px dashed var(--color-gray-20);
|
||||||
|
padding: 1em 0;
|
||||||
|
}
|
||||||
|
.links-nextprev > * {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
.links-nextprev-next {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
margin: 1em 0;
|
||||||
|
}
|
||||||
|
table td,
|
||||||
|
table th {
|
||||||
|
padding-right: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre,
|
||||||
|
code {
|
||||||
|
font-family: var(--font-family-monospace);
|
||||||
|
}
|
||||||
|
pre:not([class*="language-"]) {
|
||||||
|
margin: .5em 0;
|
||||||
|
line-height: 1.375; /* 22px /16 */
|
||||||
|
-moz-tab-size: var(--syntax-tab-size);
|
||||||
|
-o-tab-size: var(--syntax-tab-size);
|
||||||
|
tab-size: var(--syntax-tab-size);
|
||||||
|
-webkit-hyphens: none;
|
||||||
|
-ms-hyphens: none;
|
||||||
|
hyphens: none;
|
||||||
|
direction: ltr;
|
||||||
|
text-align: left;
|
||||||
|
white-space: pre;
|
||||||
|
word-spacing: normal;
|
||||||
|
word-break: normal;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
code {
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Header */
|
||||||
|
header {
|
||||||
|
display: flex;
|
||||||
|
gap: 1em;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 1em;
|
||||||
|
}
|
||||||
|
.home-link {
|
||||||
|
flex-grow: 1;
|
||||||
|
font-size: 1em; /* 16px /16 */
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
.home-link:link:not(:hover) {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Nav */
|
||||||
|
.nav {
|
||||||
|
display: flex;
|
||||||
|
gap: .5em 1em;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
.nav-item {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
.nav-item a[href]:not(:hover) {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
.nav a[href][aria-current="page"] {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Posts list */
|
||||||
|
.postlist {
|
||||||
|
counter-reset: start-from var(--postlist-index);
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
padding-left: 1.5rem;
|
||||||
|
}
|
||||||
|
.postlist-item {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: baseline;
|
||||||
|
counter-increment: start-from -1;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
.postlist-item:before {
|
||||||
|
display: inline-block;
|
||||||
|
pointer-events: none;
|
||||||
|
content: "" counter(start-from, decimal-leading-zero) ". ";
|
||||||
|
line-height: 100%;
|
||||||
|
text-align: right;
|
||||||
|
margin-left: -1.5rem;
|
||||||
|
}
|
||||||
|
.postlist-date,
|
||||||
|
.postlist-item:before {
|
||||||
|
font-size: 0.8125em; /* 13px /16 */
|
||||||
|
color: var(--color-gray-90);
|
||||||
|
}
|
||||||
|
.postlist-date {
|
||||||
|
word-spacing: -0.5px;
|
||||||
|
}
|
||||||
|
.postlist-link {
|
||||||
|
font-size: 1.1875em; /* 19px /16 */
|
||||||
|
font-weight: 700;
|
||||||
|
flex-basis: calc(100% - 1.5rem);
|
||||||
|
padding-left: .25em;
|
||||||
|
padding-right: .5em;
|
||||||
|
text-underline-position: from-font;
|
||||||
|
text-underline-offset: 0;
|
||||||
|
text-decoration-thickness: 1px;
|
||||||
|
}
|
||||||
|
.postlist-item-active .postlist-link {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tags */
|
||||||
|
.post-tag {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
text-transform: capitalize;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
.postlist-item > .post-tag {
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tags list */
|
||||||
|
.post-metadata {
|
||||||
|
display: inline-flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: .5em;
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.post-metadata time {
|
||||||
|
margin-right: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fib ::selection {
|
||||||
|
color:#FFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fib ::-webkit-selection {
|
||||||
|
color:#FFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fib {
|
||||||
|
-webkit-tap-highlight-color: white;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* okaidia theme for JavaScript, CSS and HTML
|
||||||
|
* Loosely based on Monokai textmate theme by http://www.monokai.nl/
|
||||||
|
* @author ocodia
|
||||||
|
*/
|
||||||
|
|
||||||
|
code[class*="language-"],
|
||||||
|
pre[class*="language-"] {
|
||||||
|
color: #f8f8f2;
|
||||||
|
background: none;
|
||||||
|
text-shadow: 0 1px rgba(0, 0, 0, 0.3);
|
||||||
|
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
|
||||||
|
font-size: 1em;
|
||||||
|
text-align: left;
|
||||||
|
white-space: pre;
|
||||||
|
word-spacing: normal;
|
||||||
|
word-break: normal;
|
||||||
|
word-wrap: normal;
|
||||||
|
line-height: 1.5;
|
||||||
|
|
||||||
|
-moz-tab-size: 4;
|
||||||
|
-o-tab-size: 4;
|
||||||
|
tab-size: 4;
|
||||||
|
|
||||||
|
-webkit-hyphens: none;
|
||||||
|
-moz-hyphens: none;
|
||||||
|
-ms-hyphens: none;
|
||||||
|
hyphens: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Code blocks */
|
||||||
|
pre[class*="language-"] {
|
||||||
|
padding: 1em;
|
||||||
|
margin: .5em 0;
|
||||||
|
overflow: auto;
|
||||||
|
border-radius: 0.3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
:not(pre) > code[class*="language-"],
|
||||||
|
pre[class*="language-"] {
|
||||||
|
background: #272822;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Inline code */
|
||||||
|
:not(pre) > code[class*="language-"] {
|
||||||
|
padding: .1em;
|
||||||
|
border-radius: .3em;
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.comment,
|
||||||
|
.token.prolog,
|
||||||
|
.token.doctype,
|
||||||
|
.token.cdata {
|
||||||
|
color: #8292a2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.punctuation {
|
||||||
|
color: #f8f8f2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.namespace {
|
||||||
|
opacity: .7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.property,
|
||||||
|
.token.tag,
|
||||||
|
.token.constant,
|
||||||
|
.token.symbol,
|
||||||
|
.token.deleted {
|
||||||
|
color: #f92672;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.boolean,
|
||||||
|
.token.number {
|
||||||
|
color: #ae81ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.selector,
|
||||||
|
.token.attr-name,
|
||||||
|
.token.string,
|
||||||
|
.token.char,
|
||||||
|
.token.builtin,
|
||||||
|
.token.inserted {
|
||||||
|
color: #a6e22e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.operator,
|
||||||
|
.token.entity,
|
||||||
|
.token.url,
|
||||||
|
.language-css .token.string,
|
||||||
|
.style .token.string,
|
||||||
|
.token.variable {
|
||||||
|
color: #f8f8f2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.atrule,
|
||||||
|
.token.attr-value,
|
||||||
|
.token.function,
|
||||||
|
.token.class-name {
|
||||||
|
color: #e6db74;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.keyword {
|
||||||
|
color: #66d9ef;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.regex,
|
||||||
|
.token.important {
|
||||||
|
color: #fd971f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.important,
|
||||||
|
.token.bold {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.token.italic {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.entity {
|
||||||
|
cursor: help;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* New diff- syntax
|
||||||
|
*/
|
||||||
|
|
||||||
|
pre[class*="language-diff-"] {
|
||||||
|
--eleventy-code-padding: 1.25em;
|
||||||
|
padding-left: var(--eleventy-code-padding);
|
||||||
|
padding-right: var(--eleventy-code-padding);
|
||||||
|
}
|
||||||
|
.token.deleted {
|
||||||
|
background-color: hsl(0, 51%, 37%);
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
.token.inserted {
|
||||||
|
background-color: hsl(126, 31%, 39%);
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Make the + and - characters unselectable for copy/paste */
|
||||||
|
.token.prefix.unchanged,
|
||||||
|
.token.prefix.inserted,
|
||||||
|
.token.prefix.deleted {
|
||||||
|
-webkit-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding-top: 2px;
|
||||||
|
padding-bottom: 2px;
|
||||||
|
}
|
||||||
|
.token.prefix.inserted,
|
||||||
|
.token.prefix.deleted {
|
||||||
|
width: var(--eleventy-code-padding);
|
||||||
|
background-color: rgba(0,0,0,.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Optional: full-width background color */
|
||||||
|
.token.inserted:not(.prefix),
|
||||||
|
.token.deleted:not(.prefix) {
|
||||||
|
display: block;
|
||||||
|
margin-left: calc(-1 * var(--eleventy-code-padding));
|
||||||
|
margin-right: calc(-1 * var(--eleventy-code-padding));
|
||||||
|
text-decoration: none; /* override del, ins, mark defaults */
|
||||||
|
color: inherit; /* override del, ins, mark defaults */
|
||||||
|
}</style>
|
||||||
|
|
||||||
|
<script src="/bundle.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<a href="#main" id="skip-link" class="visually-hidden">Skip to main content</a>
|
||||||
|
|
||||||
|
<header>
|
||||||
|
|
||||||
|
<picture><source type="image/avif" srcset="/img/eCT5mOL4LD-60.avif 60w"><source type="image/webp" srcset="/img/eCT5mOL4LD-60.webp 60w"><img loading="lazy" decoding="async" src="/img/eCT5mOL4LD-60.jpeg" alt="notchee logo" style="width:2rem;" width="60" height="60"></picture> <a href="/" class="home-link">Notchee</a>
|
||||||
|
<nav>
|
||||||
|
<h2 class="visually-hidden" id="top-level-navigation-menu">Top level navigation menu</h2>
|
||||||
|
<ul class="nav">
|
||||||
|
<li class="nav-item"><a href="/">Home</a></li>
|
||||||
|
<li class="nav-item"><a href="/blog/">Archive</a></li>
|
||||||
|
<li class="nav-item"><a href="/about/">About</a></li>
|
||||||
|
<li class="nav-item"><a href="/feed/feed.xml">Feed</a></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main id="main">
|
||||||
|
<heading-anchors>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<p style="font-size:1.75rem; text-align:center; font-weight:bold">Notchee #1: <span style="font-weight:normal">Good Governance</span></p>
|
||||||
|
<p style="text-align:center; font-size:1rem; margin-top:-1.5rem; margin-bottom:2.5rem;"><time datetime="2025-10-24">24 October 2025</time></p>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<p><picture><source type="image/avif" srcset="/blog/notchee001/uOBi7x7dUh-1871.avif 1871w"><source type="image/webp" srcset="/blog/notchee001/uOBi7x7dUh-1871.webp 1871w"><img loading="lazy" decoding="async" src="/blog/notchee001/uOBi7x7dUh-1871.jpeg" alt="Four square heads look on suspiciously at another square head with rounded corners." width="1871" height="1171"></picture></p>
|
||||||
|
<p style="text-align:center;"><em>
|
||||||
|
Gary's new haircut manages to completely derail the meeting of the Society Questioning UnApproved Rounded Entities
|
||||||
|
</em></p>
|
||||||
|
|
||||||
|
<div class="fib">
|
||||||
|
<p style="text-align:center; font-size:0.75rem; color:#15202b;">F<sub id="fib_seq"></sub> = <span id="fib_num"></span></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<!--
|
||||||
|
<ul class="post-metadata">
|
||||||
|
<li><time datetime="2025-10-24">24 October 2025</time></li>
|
||||||
|
</ul>
|
||||||
|
-->
|
||||||
|
|
||||||
|
</heading-anchors>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer style="border-top: 1px dashed var(--color-gray-20);">
|
||||||
|
<p style="text-align:center">
|
||||||
|
<em>Built with <a href="https://www.11ty.dev/">Eleventy v3.1.2</a> and <a href="https://github.com/11ty/eleventy-base-blog">Eleventy Base Blog v9 theme</a></em>
|
||||||
|
</p>
|
||||||
|
<p style="text-align:center">
|
||||||
|
<em>© 2025. All content is licensed <a href="https://creativecommons.org/licenses/by-nc-nd/4.0/">CC BY-NC-ND 4.0</a> unless otherwise indicated. You are encouraged to share this work without modification so long as you give credit to <a href="https://notchee.art">notchee.art</a>. To request commercial use of any part of this work please <a href="mailto:hello@notchee.art">contact me</a>.</em>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<!-- This page `/blog/notchee001/` was built on 2025-10-26T14:26:36.247Z -->
|
||||||
|
<script type="module" src="/dist/fqsEuG6ZcE.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
BIN
blog/notchee001/uOBi7x7dUh-1871.avif
Normal file
BIN
blog/notchee001/uOBi7x7dUh-1871.jpeg
Normal file
|
After Width: | Height: | Size: 343 KiB |
BIN
blog/notchee001/uOBi7x7dUh-1871.webp
Normal file
|
After Width: | Height: | Size: 218 KiB |
10
bundle.js
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
// Efficient iterative Fibonacci
|
||||||
|
function fib(n) {
|
||||||
|
if (n < 2) return n;
|
||||||
|
let a = 0, b = 1;
|
||||||
|
for (let i = 2; i <= n; i++) {
|
||||||
|
[a, b] = [b, a + b];
|
||||||
|
}
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
89
content/feed/pretty-atom-feed.xsl
Normal file
247
dist/0q5GqSX7mC.js
vendored
Normal file
|
|
@ -0,0 +1,247 @@
|
||||||
|
// Thank you to https://github.com/daviddarnes/heading-anchors
|
||||||
|
// Thank you to https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
|
||||||
|
let globalInstanceIndex = 0;
|
||||||
|
|
||||||
|
class HeadingAnchors extends HTMLElement {
|
||||||
|
static register(tagName = "heading-anchors", registry = window.customElements) {
|
||||||
|
if(registry && !registry.get(tagName)) {
|
||||||
|
registry.define(tagName, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static attributes = {
|
||||||
|
exclude: "data-ha-exclude",
|
||||||
|
prefix: "prefix",
|
||||||
|
content: "content",
|
||||||
|
}
|
||||||
|
|
||||||
|
static classes = {
|
||||||
|
anchor: "ha",
|
||||||
|
placeholder: "ha-placeholder",
|
||||||
|
srOnly: "ha-visualhide",
|
||||||
|
}
|
||||||
|
|
||||||
|
static defaultSelector = "h2,h3,h4,h5,h6";
|
||||||
|
|
||||||
|
static css = `
|
||||||
|
.${HeadingAnchors.classes.srOnly} {
|
||||||
|
clip: rect(0 0 0 0);
|
||||||
|
height: 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: absolute;
|
||||||
|
width: 1px;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: var(--ha_offsetx);
|
||||||
|
top: var(--ha_offsety);
|
||||||
|
text-decoration: none;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
opacity: .3;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor}:is(:focus-within, :hover) {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor},
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 .25em;
|
||||||
|
|
||||||
|
/* Disable selection of visually hidden label */
|
||||||
|
-webkit-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@supports (anchor-name: none) {
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: anchor(left);
|
||||||
|
top: anchor(top);
|
||||||
|
}
|
||||||
|
}`;
|
||||||
|
|
||||||
|
get supports() {
|
||||||
|
return "replaceSync" in CSSStyleSheet.prototype;
|
||||||
|
}
|
||||||
|
|
||||||
|
get supportsAnchorPosition() {
|
||||||
|
return CSS.supports("anchor-name: none");
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
if(!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sheet = new CSSStyleSheet();
|
||||||
|
sheet.replaceSync(HeadingAnchors.css);
|
||||||
|
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
|
||||||
|
|
||||||
|
this.headingStyles = {};
|
||||||
|
this.instanceIndex = globalInstanceIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
if (!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.headings.forEach((heading, index) => {
|
||||||
|
if(!heading.hasAttribute(HeadingAnchors.attributes.exclude)) {
|
||||||
|
let anchor = this.getAnchorElement(heading);
|
||||||
|
let placeholder = this.getPlaceholderElement();
|
||||||
|
|
||||||
|
// Prefers anchor position approach for better accessibility
|
||||||
|
// https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
let anchorName = `--ha_${this.instanceIndex}_${index}`;
|
||||||
|
placeholder.style.setProperty("anchor-name", anchorName);
|
||||||
|
anchor.style.positionAnchor = anchorName;
|
||||||
|
}
|
||||||
|
|
||||||
|
heading.appendChild(placeholder);
|
||||||
|
heading.after(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchorFromPlaceholder(placeholder) {
|
||||||
|
if(!placeholder) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let heading = placeholder.closest("h1,h2,h3,h4,h5,h6");
|
||||||
|
if(!heading.nextElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO next element could be more defensive
|
||||||
|
this.positionAnchor(heading.nextElementSibling);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchor(anchor) {
|
||||||
|
if(!anchor || !anchor.previousElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO previous element could be more defensive
|
||||||
|
let heading = anchor.previousElementSibling;
|
||||||
|
this.setFontProp(heading, anchor);
|
||||||
|
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
// quit early
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
anchor.style.setProperty("--ha_offsetx", `${placeholder.offsetLeft}px`);
|
||||||
|
anchor.style.setProperty("--ha_offsety", `${placeholder.offsetTop}px`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setFontProp(heading, anchor) {
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
let style = getComputedStyle(placeholder);
|
||||||
|
let props = ["font-weight", "font-size", "line-height", "font-family"];
|
||||||
|
let [weight, size, lh, family] = props.map(name => style.getPropertyValue(name));
|
||||||
|
anchor.style.setProperty("font", `${weight} ${size}/${lh} ${family}`);
|
||||||
|
let vars = style.getPropertyValue("font-variation-settings");
|
||||||
|
if(vars) {
|
||||||
|
anchor.style.setProperty("font-variation-settings", vars);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getAccessibleTextPrefix() {
|
||||||
|
// Useful for i18n
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.prefix) || "Jump to section titled";
|
||||||
|
}
|
||||||
|
|
||||||
|
getContent() {
|
||||||
|
if(this.hasAttribute(HeadingAnchors.attributes.content)) {
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.content);
|
||||||
|
}
|
||||||
|
return "#";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Placeholder nests inside of heading
|
||||||
|
getPlaceholderElement() {
|
||||||
|
let ph = document.createElement("span");
|
||||||
|
ph.setAttribute("aria-hidden", true);
|
||||||
|
ph.classList.add(HeadingAnchors.classes.placeholder);
|
||||||
|
let content = this.getContent();
|
||||||
|
if(content) {
|
||||||
|
ph.textContent = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
ph.addEventListener("mouseover", (e) => {
|
||||||
|
let placeholder = e.target.closest(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
this.positionAnchorFromPlaceholder(placeholder);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return ph;
|
||||||
|
}
|
||||||
|
|
||||||
|
getAnchorElement(heading) {
|
||||||
|
let anchor = document.createElement("a");
|
||||||
|
anchor.href = `#${heading.id}`;
|
||||||
|
anchor.classList.add(HeadingAnchors.classes.anchor);
|
||||||
|
|
||||||
|
let content = this.getContent();
|
||||||
|
anchor.innerHTML = `<span class="${HeadingAnchors.classes.srOnly}">${this.getAccessibleTextPrefix()}: ${heading.textContent}</span>${content ? `<span aria-hidden="true">${content}</span>` : ""}`;
|
||||||
|
|
||||||
|
anchor.addEventListener("focus", e => {
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
if(anchor) {
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
anchor.addEventListener("mouseover", (e) => {
|
||||||
|
// when CSS anchor positioning is supported, this is only used to set the font
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
});
|
||||||
|
|
||||||
|
return anchor;
|
||||||
|
}
|
||||||
|
|
||||||
|
get headings() {
|
||||||
|
return this.querySelectorAll(this.selector.split(",").map(entry => `${entry.trim()}[id]`));
|
||||||
|
}
|
||||||
|
|
||||||
|
get selector() {
|
||||||
|
return this.getAttribute("selector") || HeadingAnchors.defaultSelector;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HeadingAnchors.register();
|
||||||
|
|
||||||
|
export { HeadingAnchors }
|
||||||
|
// Efficient iterative Fibonacci
|
||||||
|
function fib(n) {
|
||||||
|
if (n < 2) return n;
|
||||||
|
let a = 0, b = 1;
|
||||||
|
for (let i = 2; i <= n; i++) {
|
||||||
|
[a, b] = [b, a + b];
|
||||||
|
}
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
window.onload = function() {
|
||||||
|
const n = 1; // Example: change this number or get it dynamically
|
||||||
|
const result = fib(n);
|
||||||
|
document.getElementById("fib").textContent = `F(${n}) = ${result}`;
|
||||||
|
};
|
||||||
247
dist/1c3PUgvo-U.js
vendored
Normal file
|
|
@ -0,0 +1,247 @@
|
||||||
|
// Thank you to https://github.com/daviddarnes/heading-anchors
|
||||||
|
// Thank you to https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
|
||||||
|
let globalInstanceIndex = 0;
|
||||||
|
|
||||||
|
class HeadingAnchors extends HTMLElement {
|
||||||
|
static register(tagName = "heading-anchors", registry = window.customElements) {
|
||||||
|
if(registry && !registry.get(tagName)) {
|
||||||
|
registry.define(tagName, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static attributes = {
|
||||||
|
exclude: "data-ha-exclude",
|
||||||
|
prefix: "prefix",
|
||||||
|
content: "content",
|
||||||
|
}
|
||||||
|
|
||||||
|
static classes = {
|
||||||
|
anchor: "ha",
|
||||||
|
placeholder: "ha-placeholder",
|
||||||
|
srOnly: "ha-visualhide",
|
||||||
|
}
|
||||||
|
|
||||||
|
static defaultSelector = "h2,h3,h4,h5,h6";
|
||||||
|
|
||||||
|
static css = `
|
||||||
|
.${HeadingAnchors.classes.srOnly} {
|
||||||
|
clip: rect(0 0 0 0);
|
||||||
|
height: 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: absolute;
|
||||||
|
width: 1px;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: var(--ha_offsetx);
|
||||||
|
top: var(--ha_offsety);
|
||||||
|
text-decoration: none;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
opacity: .3;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor}:is(:focus-within, :hover) {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor},
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 .25em;
|
||||||
|
|
||||||
|
/* Disable selection of visually hidden label */
|
||||||
|
-webkit-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@supports (anchor-name: none) {
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: anchor(left);
|
||||||
|
top: anchor(top);
|
||||||
|
}
|
||||||
|
}`;
|
||||||
|
|
||||||
|
get supports() {
|
||||||
|
return "replaceSync" in CSSStyleSheet.prototype;
|
||||||
|
}
|
||||||
|
|
||||||
|
get supportsAnchorPosition() {
|
||||||
|
return CSS.supports("anchor-name: none");
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
if(!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sheet = new CSSStyleSheet();
|
||||||
|
sheet.replaceSync(HeadingAnchors.css);
|
||||||
|
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
|
||||||
|
|
||||||
|
this.headingStyles = {};
|
||||||
|
this.instanceIndex = globalInstanceIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
if (!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.headings.forEach((heading, index) => {
|
||||||
|
if(!heading.hasAttribute(HeadingAnchors.attributes.exclude)) {
|
||||||
|
let anchor = this.getAnchorElement(heading);
|
||||||
|
let placeholder = this.getPlaceholderElement();
|
||||||
|
|
||||||
|
// Prefers anchor position approach for better accessibility
|
||||||
|
// https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
let anchorName = `--ha_${this.instanceIndex}_${index}`;
|
||||||
|
placeholder.style.setProperty("anchor-name", anchorName);
|
||||||
|
anchor.style.positionAnchor = anchorName;
|
||||||
|
}
|
||||||
|
|
||||||
|
heading.appendChild(placeholder);
|
||||||
|
heading.after(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchorFromPlaceholder(placeholder) {
|
||||||
|
if(!placeholder) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let heading = placeholder.closest("h1,h2,h3,h4,h5,h6");
|
||||||
|
if(!heading.nextElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO next element could be more defensive
|
||||||
|
this.positionAnchor(heading.nextElementSibling);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchor(anchor) {
|
||||||
|
if(!anchor || !anchor.previousElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO previous element could be more defensive
|
||||||
|
let heading = anchor.previousElementSibling;
|
||||||
|
this.setFontProp(heading, anchor);
|
||||||
|
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
// quit early
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
anchor.style.setProperty("--ha_offsetx", `${placeholder.offsetLeft}px`);
|
||||||
|
anchor.style.setProperty("--ha_offsety", `${placeholder.offsetTop}px`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setFontProp(heading, anchor) {
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
let style = getComputedStyle(placeholder);
|
||||||
|
let props = ["font-weight", "font-size", "line-height", "font-family"];
|
||||||
|
let [weight, size, lh, family] = props.map(name => style.getPropertyValue(name));
|
||||||
|
anchor.style.setProperty("font", `${weight} ${size}/${lh} ${family}`);
|
||||||
|
let vars = style.getPropertyValue("font-variation-settings");
|
||||||
|
if(vars) {
|
||||||
|
anchor.style.setProperty("font-variation-settings", vars);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getAccessibleTextPrefix() {
|
||||||
|
// Useful for i18n
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.prefix) || "Jump to section titled";
|
||||||
|
}
|
||||||
|
|
||||||
|
getContent() {
|
||||||
|
if(this.hasAttribute(HeadingAnchors.attributes.content)) {
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.content);
|
||||||
|
}
|
||||||
|
return "#";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Placeholder nests inside of heading
|
||||||
|
getPlaceholderElement() {
|
||||||
|
let ph = document.createElement("span");
|
||||||
|
ph.setAttribute("aria-hidden", true);
|
||||||
|
ph.classList.add(HeadingAnchors.classes.placeholder);
|
||||||
|
let content = this.getContent();
|
||||||
|
if(content) {
|
||||||
|
ph.textContent = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
ph.addEventListener("mouseover", (e) => {
|
||||||
|
let placeholder = e.target.closest(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
this.positionAnchorFromPlaceholder(placeholder);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return ph;
|
||||||
|
}
|
||||||
|
|
||||||
|
getAnchorElement(heading) {
|
||||||
|
let anchor = document.createElement("a");
|
||||||
|
anchor.href = `#${heading.id}`;
|
||||||
|
anchor.classList.add(HeadingAnchors.classes.anchor);
|
||||||
|
|
||||||
|
let content = this.getContent();
|
||||||
|
anchor.innerHTML = `<span class="${HeadingAnchors.classes.srOnly}">${this.getAccessibleTextPrefix()}: ${heading.textContent}</span>${content ? `<span aria-hidden="true">${content}</span>` : ""}`;
|
||||||
|
|
||||||
|
anchor.addEventListener("focus", e => {
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
if(anchor) {
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
anchor.addEventListener("mouseover", (e) => {
|
||||||
|
// when CSS anchor positioning is supported, this is only used to set the font
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
});
|
||||||
|
|
||||||
|
return anchor;
|
||||||
|
}
|
||||||
|
|
||||||
|
get headings() {
|
||||||
|
return this.querySelectorAll(this.selector.split(",").map(entry => `${entry.trim()}[id]`));
|
||||||
|
}
|
||||||
|
|
||||||
|
get selector() {
|
||||||
|
return this.getAttribute("selector") || HeadingAnchors.defaultSelector;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HeadingAnchors.register();
|
||||||
|
|
||||||
|
export { HeadingAnchors }
|
||||||
|
// Efficient iterative Fibonacci
|
||||||
|
function fib(n) {
|
||||||
|
if (n < 2) return n;
|
||||||
|
let a = 0, b = 1;
|
||||||
|
for (let i = 2; i <= n; i++) {
|
||||||
|
[a, b] = [b, a + b];
|
||||||
|
}
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
window.onload = function() {
|
||||||
|
const n = 2; // Example: change this number or get it dynamically
|
||||||
|
const result = fib(n);
|
||||||
|
document.getElementById("fib").textContent = `F(${n}) = ${result}`;
|
||||||
|
};
|
||||||
247
dist/5D6kdO0bmj.js
vendored
Normal file
|
|
@ -0,0 +1,247 @@
|
||||||
|
// Thank you to https://github.com/daviddarnes/heading-anchors
|
||||||
|
// Thank you to https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
|
||||||
|
let globalInstanceIndex = 0;
|
||||||
|
|
||||||
|
class HeadingAnchors extends HTMLElement {
|
||||||
|
static register(tagName = "heading-anchors", registry = window.customElements) {
|
||||||
|
if(registry && !registry.get(tagName)) {
|
||||||
|
registry.define(tagName, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static attributes = {
|
||||||
|
exclude: "data-ha-exclude",
|
||||||
|
prefix: "prefix",
|
||||||
|
content: "content",
|
||||||
|
}
|
||||||
|
|
||||||
|
static classes = {
|
||||||
|
anchor: "ha",
|
||||||
|
placeholder: "ha-placeholder",
|
||||||
|
srOnly: "ha-visualhide",
|
||||||
|
}
|
||||||
|
|
||||||
|
static defaultSelector = "h2,h3,h4,h5,h6";
|
||||||
|
|
||||||
|
static css = `
|
||||||
|
.${HeadingAnchors.classes.srOnly} {
|
||||||
|
clip: rect(0 0 0 0);
|
||||||
|
height: 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: absolute;
|
||||||
|
width: 1px;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: var(--ha_offsetx);
|
||||||
|
top: var(--ha_offsety);
|
||||||
|
text-decoration: none;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
opacity: .3;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor}:is(:focus-within, :hover) {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor},
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 .25em;
|
||||||
|
|
||||||
|
/* Disable selection of visually hidden label */
|
||||||
|
-webkit-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@supports (anchor-name: none) {
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: anchor(left);
|
||||||
|
top: anchor(top);
|
||||||
|
}
|
||||||
|
}`;
|
||||||
|
|
||||||
|
get supports() {
|
||||||
|
return "replaceSync" in CSSStyleSheet.prototype;
|
||||||
|
}
|
||||||
|
|
||||||
|
get supportsAnchorPosition() {
|
||||||
|
return CSS.supports("anchor-name: none");
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
if(!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sheet = new CSSStyleSheet();
|
||||||
|
sheet.replaceSync(HeadingAnchors.css);
|
||||||
|
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
|
||||||
|
|
||||||
|
this.headingStyles = {};
|
||||||
|
this.instanceIndex = globalInstanceIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
if (!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.headings.forEach((heading, index) => {
|
||||||
|
if(!heading.hasAttribute(HeadingAnchors.attributes.exclude)) {
|
||||||
|
let anchor = this.getAnchorElement(heading);
|
||||||
|
let placeholder = this.getPlaceholderElement();
|
||||||
|
|
||||||
|
// Prefers anchor position approach for better accessibility
|
||||||
|
// https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
let anchorName = `--ha_${this.instanceIndex}_${index}`;
|
||||||
|
placeholder.style.setProperty("anchor-name", anchorName);
|
||||||
|
anchor.style.positionAnchor = anchorName;
|
||||||
|
}
|
||||||
|
|
||||||
|
heading.appendChild(placeholder);
|
||||||
|
heading.after(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchorFromPlaceholder(placeholder) {
|
||||||
|
if(!placeholder) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let heading = placeholder.closest("h1,h2,h3,h4,h5,h6");
|
||||||
|
if(!heading.nextElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO next element could be more defensive
|
||||||
|
this.positionAnchor(heading.nextElementSibling);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchor(anchor) {
|
||||||
|
if(!anchor || !anchor.previousElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO previous element could be more defensive
|
||||||
|
let heading = anchor.previousElementSibling;
|
||||||
|
this.setFontProp(heading, anchor);
|
||||||
|
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
// quit early
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
anchor.style.setProperty("--ha_offsetx", `${placeholder.offsetLeft}px`);
|
||||||
|
anchor.style.setProperty("--ha_offsety", `${placeholder.offsetTop}px`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setFontProp(heading, anchor) {
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
let style = getComputedStyle(placeholder);
|
||||||
|
let props = ["font-weight", "font-size", "line-height", "font-family"];
|
||||||
|
let [weight, size, lh, family] = props.map(name => style.getPropertyValue(name));
|
||||||
|
anchor.style.setProperty("font", `${weight} ${size}/${lh} ${family}`);
|
||||||
|
let vars = style.getPropertyValue("font-variation-settings");
|
||||||
|
if(vars) {
|
||||||
|
anchor.style.setProperty("font-variation-settings", vars);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getAccessibleTextPrefix() {
|
||||||
|
// Useful for i18n
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.prefix) || "Jump to section titled";
|
||||||
|
}
|
||||||
|
|
||||||
|
getContent() {
|
||||||
|
if(this.hasAttribute(HeadingAnchors.attributes.content)) {
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.content);
|
||||||
|
}
|
||||||
|
return "#";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Placeholder nests inside of heading
|
||||||
|
getPlaceholderElement() {
|
||||||
|
let ph = document.createElement("span");
|
||||||
|
ph.setAttribute("aria-hidden", true);
|
||||||
|
ph.classList.add(HeadingAnchors.classes.placeholder);
|
||||||
|
let content = this.getContent();
|
||||||
|
if(content) {
|
||||||
|
ph.textContent = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
ph.addEventListener("mouseover", (e) => {
|
||||||
|
let placeholder = e.target.closest(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
this.positionAnchorFromPlaceholder(placeholder);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return ph;
|
||||||
|
}
|
||||||
|
|
||||||
|
getAnchorElement(heading) {
|
||||||
|
let anchor = document.createElement("a");
|
||||||
|
anchor.href = `#${heading.id}`;
|
||||||
|
anchor.classList.add(HeadingAnchors.classes.anchor);
|
||||||
|
|
||||||
|
let content = this.getContent();
|
||||||
|
anchor.innerHTML = `<span class="${HeadingAnchors.classes.srOnly}">${this.getAccessibleTextPrefix()}: ${heading.textContent}</span>${content ? `<span aria-hidden="true">${content}</span>` : ""}`;
|
||||||
|
|
||||||
|
anchor.addEventListener("focus", e => {
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
if(anchor) {
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
anchor.addEventListener("mouseover", (e) => {
|
||||||
|
// when CSS anchor positioning is supported, this is only used to set the font
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
});
|
||||||
|
|
||||||
|
return anchor;
|
||||||
|
}
|
||||||
|
|
||||||
|
get headings() {
|
||||||
|
return this.querySelectorAll(this.selector.split(",").map(entry => `${entry.trim()}[id]`));
|
||||||
|
}
|
||||||
|
|
||||||
|
get selector() {
|
||||||
|
return this.getAttribute("selector") || HeadingAnchors.defaultSelector;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HeadingAnchors.register();
|
||||||
|
|
||||||
|
export { HeadingAnchors }
|
||||||
|
// Efficient iterative Fibonacci
|
||||||
|
function fib(n) {
|
||||||
|
if (n < 2) return n;
|
||||||
|
let a = 0, b = 1;
|
||||||
|
for (let i = 2; i <= n; i++) {
|
||||||
|
[a, b] = [b, a + b];
|
||||||
|
}
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.onload = function() {
|
||||||
|
const n = 1; // Example: change this number or get it dynamically
|
||||||
|
const result = fib(n);
|
||||||
|
document.getElementById("fib_seq").textContent = "${n}";
|
||||||
|
document.getElementById("fib_num").textContent = "${result}"; // Doesn't render properly on mobile; figure out subscript numbers...
|
||||||
|
};
|
||||||
248
dist/5jPJNnHVHc.js
vendored
Normal file
|
|
@ -0,0 +1,248 @@
|
||||||
|
// Thank you to https://github.com/daviddarnes/heading-anchors
|
||||||
|
// Thank you to https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
|
||||||
|
let globalInstanceIndex = 0;
|
||||||
|
|
||||||
|
class HeadingAnchors extends HTMLElement {
|
||||||
|
static register(tagName = "heading-anchors", registry = window.customElements) {
|
||||||
|
if(registry && !registry.get(tagName)) {
|
||||||
|
registry.define(tagName, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static attributes = {
|
||||||
|
exclude: "data-ha-exclude",
|
||||||
|
prefix: "prefix",
|
||||||
|
content: "content",
|
||||||
|
}
|
||||||
|
|
||||||
|
static classes = {
|
||||||
|
anchor: "ha",
|
||||||
|
placeholder: "ha-placeholder",
|
||||||
|
srOnly: "ha-visualhide",
|
||||||
|
}
|
||||||
|
|
||||||
|
static defaultSelector = "h2,h3,h4,h5,h6";
|
||||||
|
|
||||||
|
static css = `
|
||||||
|
.${HeadingAnchors.classes.srOnly} {
|
||||||
|
clip: rect(0 0 0 0);
|
||||||
|
height: 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: absolute;
|
||||||
|
width: 1px;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: var(--ha_offsetx);
|
||||||
|
top: var(--ha_offsety);
|
||||||
|
text-decoration: none;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
opacity: .3;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor}:is(:focus-within, :hover) {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor},
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 .25em;
|
||||||
|
|
||||||
|
/* Disable selection of visually hidden label */
|
||||||
|
-webkit-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@supports (anchor-name: none) {
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: anchor(left);
|
||||||
|
top: anchor(top);
|
||||||
|
}
|
||||||
|
}`;
|
||||||
|
|
||||||
|
get supports() {
|
||||||
|
return "replaceSync" in CSSStyleSheet.prototype;
|
||||||
|
}
|
||||||
|
|
||||||
|
get supportsAnchorPosition() {
|
||||||
|
return CSS.supports("anchor-name: none");
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
if(!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sheet = new CSSStyleSheet();
|
||||||
|
sheet.replaceSync(HeadingAnchors.css);
|
||||||
|
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
|
||||||
|
|
||||||
|
this.headingStyles = {};
|
||||||
|
this.instanceIndex = globalInstanceIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
if (!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.headings.forEach((heading, index) => {
|
||||||
|
if(!heading.hasAttribute(HeadingAnchors.attributes.exclude)) {
|
||||||
|
let anchor = this.getAnchorElement(heading);
|
||||||
|
let placeholder = this.getPlaceholderElement();
|
||||||
|
|
||||||
|
// Prefers anchor position approach for better accessibility
|
||||||
|
// https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
let anchorName = `--ha_${this.instanceIndex}_${index}`;
|
||||||
|
placeholder.style.setProperty("anchor-name", anchorName);
|
||||||
|
anchor.style.positionAnchor = anchorName;
|
||||||
|
}
|
||||||
|
|
||||||
|
heading.appendChild(placeholder);
|
||||||
|
heading.after(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchorFromPlaceholder(placeholder) {
|
||||||
|
if(!placeholder) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let heading = placeholder.closest("h1,h2,h3,h4,h5,h6");
|
||||||
|
if(!heading.nextElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO next element could be more defensive
|
||||||
|
this.positionAnchor(heading.nextElementSibling);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchor(anchor) {
|
||||||
|
if(!anchor || !anchor.previousElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO previous element could be more defensive
|
||||||
|
let heading = anchor.previousElementSibling;
|
||||||
|
this.setFontProp(heading, anchor);
|
||||||
|
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
// quit early
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
anchor.style.setProperty("--ha_offsetx", `${placeholder.offsetLeft}px`);
|
||||||
|
anchor.style.setProperty("--ha_offsety", `${placeholder.offsetTop}px`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setFontProp(heading, anchor) {
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
let style = getComputedStyle(placeholder);
|
||||||
|
let props = ["font-weight", "font-size", "line-height", "font-family"];
|
||||||
|
let [weight, size, lh, family] = props.map(name => style.getPropertyValue(name));
|
||||||
|
anchor.style.setProperty("font", `${weight} ${size}/${lh} ${family}`);
|
||||||
|
let vars = style.getPropertyValue("font-variation-settings");
|
||||||
|
if(vars) {
|
||||||
|
anchor.style.setProperty("font-variation-settings", vars);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getAccessibleTextPrefix() {
|
||||||
|
// Useful for i18n
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.prefix) || "Jump to section titled";
|
||||||
|
}
|
||||||
|
|
||||||
|
getContent() {
|
||||||
|
if(this.hasAttribute(HeadingAnchors.attributes.content)) {
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.content);
|
||||||
|
}
|
||||||
|
return "#";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Placeholder nests inside of heading
|
||||||
|
getPlaceholderElement() {
|
||||||
|
let ph = document.createElement("span");
|
||||||
|
ph.setAttribute("aria-hidden", true);
|
||||||
|
ph.classList.add(HeadingAnchors.classes.placeholder);
|
||||||
|
let content = this.getContent();
|
||||||
|
if(content) {
|
||||||
|
ph.textContent = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
ph.addEventListener("mouseover", (e) => {
|
||||||
|
let placeholder = e.target.closest(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
this.positionAnchorFromPlaceholder(placeholder);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return ph;
|
||||||
|
}
|
||||||
|
|
||||||
|
getAnchorElement(heading) {
|
||||||
|
let anchor = document.createElement("a");
|
||||||
|
anchor.href = `#${heading.id}`;
|
||||||
|
anchor.classList.add(HeadingAnchors.classes.anchor);
|
||||||
|
|
||||||
|
let content = this.getContent();
|
||||||
|
anchor.innerHTML = `<span class="${HeadingAnchors.classes.srOnly}">${this.getAccessibleTextPrefix()}: ${heading.textContent}</span>${content ? `<span aria-hidden="true">${content}</span>` : ""}`;
|
||||||
|
|
||||||
|
anchor.addEventListener("focus", e => {
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
if(anchor) {
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
anchor.addEventListener("mouseover", (e) => {
|
||||||
|
// when CSS anchor positioning is supported, this is only used to set the font
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
});
|
||||||
|
|
||||||
|
return anchor;
|
||||||
|
}
|
||||||
|
|
||||||
|
get headings() {
|
||||||
|
return this.querySelectorAll(this.selector.split(",").map(entry => `${entry.trim()}[id]`));
|
||||||
|
}
|
||||||
|
|
||||||
|
get selector() {
|
||||||
|
return this.getAttribute("selector") || HeadingAnchors.defaultSelector;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HeadingAnchors.register();
|
||||||
|
|
||||||
|
export { HeadingAnchors }
|
||||||
|
// Efficient iterative Fibonacci
|
||||||
|
function fib(n) {
|
||||||
|
if (n < 2) return n;
|
||||||
|
let a = 0, b = 1;
|
||||||
|
for (let i = 2; i <= n; i++) {
|
||||||
|
[a, b] = [b, a + b];
|
||||||
|
}
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
window.onload = function() {
|
||||||
|
const n = 8; // Example: change this number or get it dynamically
|
||||||
|
const result = fib(n-1);
|
||||||
|
document.getElementById("fib_seq").textContent = n;
|
||||||
|
document.getElementById("fib_num").textContent = result; // Doesn't render properly on mobile; figure out subscript numbers...
|
||||||
|
};
|
||||||
232
dist/8XCmg52eCu.js
vendored
Normal file
|
|
@ -0,0 +1,232 @@
|
||||||
|
// Thank you to https://github.com/daviddarnes/heading-anchors
|
||||||
|
// Thank you to https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
|
||||||
|
let globalInstanceIndex = 0;
|
||||||
|
|
||||||
|
class HeadingAnchors extends HTMLElement {
|
||||||
|
static register(tagName = "heading-anchors", registry = window.customElements) {
|
||||||
|
if(registry && !registry.get(tagName)) {
|
||||||
|
registry.define(tagName, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static attributes = {
|
||||||
|
exclude: "data-ha-exclude",
|
||||||
|
prefix: "prefix",
|
||||||
|
content: "content",
|
||||||
|
}
|
||||||
|
|
||||||
|
static classes = {
|
||||||
|
anchor: "ha",
|
||||||
|
placeholder: "ha-placeholder",
|
||||||
|
srOnly: "ha-visualhide",
|
||||||
|
}
|
||||||
|
|
||||||
|
static defaultSelector = "h2,h3,h4,h5,h6";
|
||||||
|
|
||||||
|
static css = `
|
||||||
|
.${HeadingAnchors.classes.srOnly} {
|
||||||
|
clip: rect(0 0 0 0);
|
||||||
|
height: 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: absolute;
|
||||||
|
width: 1px;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: var(--ha_offsetx);
|
||||||
|
top: var(--ha_offsety);
|
||||||
|
text-decoration: none;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
opacity: .3;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor}:is(:focus-within, :hover) {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor},
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 .25em;
|
||||||
|
|
||||||
|
/* Disable selection of visually hidden label */
|
||||||
|
-webkit-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@supports (anchor-name: none) {
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: anchor(left);
|
||||||
|
top: anchor(top);
|
||||||
|
}
|
||||||
|
}`;
|
||||||
|
|
||||||
|
get supports() {
|
||||||
|
return "replaceSync" in CSSStyleSheet.prototype;
|
||||||
|
}
|
||||||
|
|
||||||
|
get supportsAnchorPosition() {
|
||||||
|
return CSS.supports("anchor-name: none");
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
if(!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sheet = new CSSStyleSheet();
|
||||||
|
sheet.replaceSync(HeadingAnchors.css);
|
||||||
|
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
|
||||||
|
|
||||||
|
this.headingStyles = {};
|
||||||
|
this.instanceIndex = globalInstanceIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
if (!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.headings.forEach((heading, index) => {
|
||||||
|
if(!heading.hasAttribute(HeadingAnchors.attributes.exclude)) {
|
||||||
|
let anchor = this.getAnchorElement(heading);
|
||||||
|
let placeholder = this.getPlaceholderElement();
|
||||||
|
|
||||||
|
// Prefers anchor position approach for better accessibility
|
||||||
|
// https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
let anchorName = `--ha_${this.instanceIndex}_${index}`;
|
||||||
|
placeholder.style.setProperty("anchor-name", anchorName);
|
||||||
|
anchor.style.positionAnchor = anchorName;
|
||||||
|
}
|
||||||
|
|
||||||
|
heading.appendChild(placeholder);
|
||||||
|
heading.after(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchorFromPlaceholder(placeholder) {
|
||||||
|
if(!placeholder) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let heading = placeholder.closest("h1,h2,h3,h4,h5,h6");
|
||||||
|
if(!heading.nextElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO next element could be more defensive
|
||||||
|
this.positionAnchor(heading.nextElementSibling);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchor(anchor) {
|
||||||
|
if(!anchor || !anchor.previousElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO previous element could be more defensive
|
||||||
|
let heading = anchor.previousElementSibling;
|
||||||
|
this.setFontProp(heading, anchor);
|
||||||
|
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
// quit early
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
anchor.style.setProperty("--ha_offsetx", `${placeholder.offsetLeft}px`);
|
||||||
|
anchor.style.setProperty("--ha_offsety", `${placeholder.offsetTop}px`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setFontProp(heading, anchor) {
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
let style = getComputedStyle(placeholder);
|
||||||
|
let props = ["font-weight", "font-size", "line-height", "font-family"];
|
||||||
|
let [weight, size, lh, family] = props.map(name => style.getPropertyValue(name));
|
||||||
|
anchor.style.setProperty("font", `${weight} ${size}/${lh} ${family}`);
|
||||||
|
let vars = style.getPropertyValue("font-variation-settings");
|
||||||
|
if(vars) {
|
||||||
|
anchor.style.setProperty("font-variation-settings", vars);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getAccessibleTextPrefix() {
|
||||||
|
// Useful for i18n
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.prefix) || "Jump to section titled";
|
||||||
|
}
|
||||||
|
|
||||||
|
getContent() {
|
||||||
|
if(this.hasAttribute(HeadingAnchors.attributes.content)) {
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.content);
|
||||||
|
}
|
||||||
|
return "#";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Placeholder nests inside of heading
|
||||||
|
getPlaceholderElement() {
|
||||||
|
let ph = document.createElement("span");
|
||||||
|
ph.setAttribute("aria-hidden", true);
|
||||||
|
ph.classList.add(HeadingAnchors.classes.placeholder);
|
||||||
|
let content = this.getContent();
|
||||||
|
if(content) {
|
||||||
|
ph.textContent = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
ph.addEventListener("mouseover", (e) => {
|
||||||
|
let placeholder = e.target.closest(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
this.positionAnchorFromPlaceholder(placeholder);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return ph;
|
||||||
|
}
|
||||||
|
|
||||||
|
getAnchorElement(heading) {
|
||||||
|
let anchor = document.createElement("a");
|
||||||
|
anchor.href = `#${heading.id}`;
|
||||||
|
anchor.classList.add(HeadingAnchors.classes.anchor);
|
||||||
|
|
||||||
|
let content = this.getContent();
|
||||||
|
anchor.innerHTML = `<span class="${HeadingAnchors.classes.srOnly}">${this.getAccessibleTextPrefix()}: ${heading.textContent}</span>${content ? `<span aria-hidden="true">${content}</span>` : ""}`;
|
||||||
|
|
||||||
|
anchor.addEventListener("focus", e => {
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
if(anchor) {
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
anchor.addEventListener("mouseover", (e) => {
|
||||||
|
// when CSS anchor positioning is supported, this is only used to set the font
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
});
|
||||||
|
|
||||||
|
return anchor;
|
||||||
|
}
|
||||||
|
|
||||||
|
get headings() {
|
||||||
|
return this.querySelectorAll(this.selector.split(",").map(entry => `${entry.trim()}[id]`));
|
||||||
|
}
|
||||||
|
|
||||||
|
get selector() {
|
||||||
|
return this.getAttribute("selector") || HeadingAnchors.defaultSelector;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HeadingAnchors.register();
|
||||||
|
|
||||||
|
export { HeadingAnchors }
|
||||||
|
window.onload = fib(1);
|
||||||
249
dist/8jwLSGQpSi.js
vendored
Normal file
|
|
@ -0,0 +1,249 @@
|
||||||
|
// Thank you to https://github.com/daviddarnes/heading-anchors
|
||||||
|
// Thank you to https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
|
||||||
|
let globalInstanceIndex = 0;
|
||||||
|
|
||||||
|
class HeadingAnchors extends HTMLElement {
|
||||||
|
static register(tagName = "heading-anchors", registry = window.customElements) {
|
||||||
|
if(registry && !registry.get(tagName)) {
|
||||||
|
registry.define(tagName, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static attributes = {
|
||||||
|
exclude: "data-ha-exclude",
|
||||||
|
prefix: "prefix",
|
||||||
|
content: "content",
|
||||||
|
}
|
||||||
|
|
||||||
|
static classes = {
|
||||||
|
anchor: "ha",
|
||||||
|
placeholder: "ha-placeholder",
|
||||||
|
srOnly: "ha-visualhide",
|
||||||
|
}
|
||||||
|
|
||||||
|
static defaultSelector = "h2,h3,h4,h5,h6";
|
||||||
|
|
||||||
|
static css = `
|
||||||
|
.${HeadingAnchors.classes.srOnly} {
|
||||||
|
clip: rect(0 0 0 0);
|
||||||
|
height: 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: absolute;
|
||||||
|
width: 1px;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: var(--ha_offsetx);
|
||||||
|
top: var(--ha_offsety);
|
||||||
|
text-decoration: none;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
opacity: .3;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor}:is(:focus-within, :hover) {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor},
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 .25em;
|
||||||
|
|
||||||
|
/* Disable selection of visually hidden label */
|
||||||
|
-webkit-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@supports (anchor-name: none) {
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: anchor(left);
|
||||||
|
top: anchor(top);
|
||||||
|
}
|
||||||
|
}`;
|
||||||
|
|
||||||
|
get supports() {
|
||||||
|
return "replaceSync" in CSSStyleSheet.prototype;
|
||||||
|
}
|
||||||
|
|
||||||
|
get supportsAnchorPosition() {
|
||||||
|
return CSS.supports("anchor-name: none");
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
if(!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sheet = new CSSStyleSheet();
|
||||||
|
sheet.replaceSync(HeadingAnchors.css);
|
||||||
|
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
|
||||||
|
|
||||||
|
this.headingStyles = {};
|
||||||
|
this.instanceIndex = globalInstanceIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
if (!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.headings.forEach((heading, index) => {
|
||||||
|
if(!heading.hasAttribute(HeadingAnchors.attributes.exclude)) {
|
||||||
|
let anchor = this.getAnchorElement(heading);
|
||||||
|
let placeholder = this.getPlaceholderElement();
|
||||||
|
|
||||||
|
// Prefers anchor position approach for better accessibility
|
||||||
|
// https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
let anchorName = `--ha_${this.instanceIndex}_${index}`;
|
||||||
|
placeholder.style.setProperty("anchor-name", anchorName);
|
||||||
|
anchor.style.positionAnchor = anchorName;
|
||||||
|
}
|
||||||
|
|
||||||
|
heading.appendChild(placeholder);
|
||||||
|
heading.after(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchorFromPlaceholder(placeholder) {
|
||||||
|
if(!placeholder) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let heading = placeholder.closest("h1,h2,h3,h4,h5,h6");
|
||||||
|
if(!heading.nextElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO next element could be more defensive
|
||||||
|
this.positionAnchor(heading.nextElementSibling);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchor(anchor) {
|
||||||
|
if(!anchor || !anchor.previousElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO previous element could be more defensive
|
||||||
|
let heading = anchor.previousElementSibling;
|
||||||
|
this.setFontProp(heading, anchor);
|
||||||
|
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
// quit early
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
anchor.style.setProperty("--ha_offsetx", `${placeholder.offsetLeft}px`);
|
||||||
|
anchor.style.setProperty("--ha_offsety", `${placeholder.offsetTop}px`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setFontProp(heading, anchor) {
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
let style = getComputedStyle(placeholder);
|
||||||
|
let props = ["font-weight", "font-size", "line-height", "font-family"];
|
||||||
|
let [weight, size, lh, family] = props.map(name => style.getPropertyValue(name));
|
||||||
|
anchor.style.setProperty("font", `${weight} ${size}/${lh} ${family}`);
|
||||||
|
let vars = style.getPropertyValue("font-variation-settings");
|
||||||
|
if(vars) {
|
||||||
|
anchor.style.setProperty("font-variation-settings", vars);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getAccessibleTextPrefix() {
|
||||||
|
// Useful for i18n
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.prefix) || "Jump to section titled";
|
||||||
|
}
|
||||||
|
|
||||||
|
getContent() {
|
||||||
|
if(this.hasAttribute(HeadingAnchors.attributes.content)) {
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.content);
|
||||||
|
}
|
||||||
|
return "#";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Placeholder nests inside of heading
|
||||||
|
getPlaceholderElement() {
|
||||||
|
let ph = document.createElement("span");
|
||||||
|
ph.setAttribute("aria-hidden", true);
|
||||||
|
ph.classList.add(HeadingAnchors.classes.placeholder);
|
||||||
|
let content = this.getContent();
|
||||||
|
if(content) {
|
||||||
|
ph.textContent = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
ph.addEventListener("mouseover", (e) => {
|
||||||
|
let placeholder = e.target.closest(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
this.positionAnchorFromPlaceholder(placeholder);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return ph;
|
||||||
|
}
|
||||||
|
|
||||||
|
getAnchorElement(heading) {
|
||||||
|
let anchor = document.createElement("a");
|
||||||
|
anchor.href = `#${heading.id}`;
|
||||||
|
anchor.classList.add(HeadingAnchors.classes.anchor);
|
||||||
|
|
||||||
|
let content = this.getContent();
|
||||||
|
anchor.innerHTML = `<span class="${HeadingAnchors.classes.srOnly}">${this.getAccessibleTextPrefix()}: ${heading.textContent}</span>${content ? `<span aria-hidden="true">${content}</span>` : ""}`;
|
||||||
|
|
||||||
|
anchor.addEventListener("focus", e => {
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
if(anchor) {
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
anchor.addEventListener("mouseover", (e) => {
|
||||||
|
// when CSS anchor positioning is supported, this is only used to set the font
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
});
|
||||||
|
|
||||||
|
return anchor;
|
||||||
|
}
|
||||||
|
|
||||||
|
get headings() {
|
||||||
|
return this.querySelectorAll(this.selector.split(",").map(entry => `${entry.trim()}[id]`));
|
||||||
|
}
|
||||||
|
|
||||||
|
get selector() {
|
||||||
|
return this.getAttribute("selector") || HeadingAnchors.defaultSelector;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HeadingAnchors.register();
|
||||||
|
|
||||||
|
export { HeadingAnchors }
|
||||||
|
// Efficient iterative Fibonacci
|
||||||
|
function fib(n) {
|
||||||
|
if (n < 2) return n;
|
||||||
|
let a = 0, b = 1;
|
||||||
|
for (let i = 2; i <= n; i++) {
|
||||||
|
[a, b] = [b, a + b];
|
||||||
|
}
|
||||||
|
document.getElementById("fib_seq").textContent = n;
|
||||||
|
document.getElementById("fib_num").textContent = result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
window.onload = fib(1) {
|
||||||
|
const n = 1; // Example: change this number or get it dynamically
|
||||||
|
const result = fib(n);
|
||||||
|
document.getElementById("fib_seq").textContent = n;
|
||||||
|
document.getElementById("fib_num").textContent = result;
|
||||||
|
};
|
||||||
249
dist/8touSeoYz_.js
vendored
Normal file
|
|
@ -0,0 +1,249 @@
|
||||||
|
// Thank you to https://github.com/daviddarnes/heading-anchors
|
||||||
|
// Thank you to https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
|
||||||
|
let globalInstanceIndex = 0;
|
||||||
|
|
||||||
|
class HeadingAnchors extends HTMLElement {
|
||||||
|
static register(tagName = "heading-anchors", registry = window.customElements) {
|
||||||
|
if(registry && !registry.get(tagName)) {
|
||||||
|
registry.define(tagName, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static attributes = {
|
||||||
|
exclude: "data-ha-exclude",
|
||||||
|
prefix: "prefix",
|
||||||
|
content: "content",
|
||||||
|
}
|
||||||
|
|
||||||
|
static classes = {
|
||||||
|
anchor: "ha",
|
||||||
|
placeholder: "ha-placeholder",
|
||||||
|
srOnly: "ha-visualhide",
|
||||||
|
}
|
||||||
|
|
||||||
|
static defaultSelector = "h2,h3,h4,h5,h6";
|
||||||
|
|
||||||
|
static css = `
|
||||||
|
.${HeadingAnchors.classes.srOnly} {
|
||||||
|
clip: rect(0 0 0 0);
|
||||||
|
height: 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: absolute;
|
||||||
|
width: 1px;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: var(--ha_offsetx);
|
||||||
|
top: var(--ha_offsety);
|
||||||
|
text-decoration: none;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
opacity: .3;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor}:is(:focus-within, :hover) {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor},
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 .25em;
|
||||||
|
|
||||||
|
/* Disable selection of visually hidden label */
|
||||||
|
-webkit-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@supports (anchor-name: none) {
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: anchor(left);
|
||||||
|
top: anchor(top);
|
||||||
|
}
|
||||||
|
}`;
|
||||||
|
|
||||||
|
get supports() {
|
||||||
|
return "replaceSync" in CSSStyleSheet.prototype;
|
||||||
|
}
|
||||||
|
|
||||||
|
get supportsAnchorPosition() {
|
||||||
|
return CSS.supports("anchor-name: none");
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
if(!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sheet = new CSSStyleSheet();
|
||||||
|
sheet.replaceSync(HeadingAnchors.css);
|
||||||
|
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
|
||||||
|
|
||||||
|
this.headingStyles = {};
|
||||||
|
this.instanceIndex = globalInstanceIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
if (!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.headings.forEach((heading, index) => {
|
||||||
|
if(!heading.hasAttribute(HeadingAnchors.attributes.exclude)) {
|
||||||
|
let anchor = this.getAnchorElement(heading);
|
||||||
|
let placeholder = this.getPlaceholderElement();
|
||||||
|
|
||||||
|
// Prefers anchor position approach for better accessibility
|
||||||
|
// https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
let anchorName = `--ha_${this.instanceIndex}_${index}`;
|
||||||
|
placeholder.style.setProperty("anchor-name", anchorName);
|
||||||
|
anchor.style.positionAnchor = anchorName;
|
||||||
|
}
|
||||||
|
|
||||||
|
heading.appendChild(placeholder);
|
||||||
|
heading.after(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchorFromPlaceholder(placeholder) {
|
||||||
|
if(!placeholder) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let heading = placeholder.closest("h1,h2,h3,h4,h5,h6");
|
||||||
|
if(!heading.nextElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO next element could be more defensive
|
||||||
|
this.positionAnchor(heading.nextElementSibling);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchor(anchor) {
|
||||||
|
if(!anchor || !anchor.previousElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO previous element could be more defensive
|
||||||
|
let heading = anchor.previousElementSibling;
|
||||||
|
this.setFontProp(heading, anchor);
|
||||||
|
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
// quit early
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
anchor.style.setProperty("--ha_offsetx", `${placeholder.offsetLeft}px`);
|
||||||
|
anchor.style.setProperty("--ha_offsety", `${placeholder.offsetTop}px`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setFontProp(heading, anchor) {
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
let style = getComputedStyle(placeholder);
|
||||||
|
let props = ["font-weight", "font-size", "line-height", "font-family"];
|
||||||
|
let [weight, size, lh, family] = props.map(name => style.getPropertyValue(name));
|
||||||
|
anchor.style.setProperty("font", `${weight} ${size}/${lh} ${family}`);
|
||||||
|
let vars = style.getPropertyValue("font-variation-settings");
|
||||||
|
if(vars) {
|
||||||
|
anchor.style.setProperty("font-variation-settings", vars);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getAccessibleTextPrefix() {
|
||||||
|
// Useful for i18n
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.prefix) || "Jump to section titled";
|
||||||
|
}
|
||||||
|
|
||||||
|
getContent() {
|
||||||
|
if(this.hasAttribute(HeadingAnchors.attributes.content)) {
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.content);
|
||||||
|
}
|
||||||
|
return "#";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Placeholder nests inside of heading
|
||||||
|
getPlaceholderElement() {
|
||||||
|
let ph = document.createElement("span");
|
||||||
|
ph.setAttribute("aria-hidden", true);
|
||||||
|
ph.classList.add(HeadingAnchors.classes.placeholder);
|
||||||
|
let content = this.getContent();
|
||||||
|
if(content) {
|
||||||
|
ph.textContent = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
ph.addEventListener("mouseover", (e) => {
|
||||||
|
let placeholder = e.target.closest(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
this.positionAnchorFromPlaceholder(placeholder);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return ph;
|
||||||
|
}
|
||||||
|
|
||||||
|
getAnchorElement(heading) {
|
||||||
|
let anchor = document.createElement("a");
|
||||||
|
anchor.href = `#${heading.id}`;
|
||||||
|
anchor.classList.add(HeadingAnchors.classes.anchor);
|
||||||
|
|
||||||
|
let content = this.getContent();
|
||||||
|
anchor.innerHTML = `<span class="${HeadingAnchors.classes.srOnly}">${this.getAccessibleTextPrefix()}: ${heading.textContent}</span>${content ? `<span aria-hidden="true">${content}</span>` : ""}`;
|
||||||
|
|
||||||
|
anchor.addEventListener("focus", e => {
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
if(anchor) {
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
anchor.addEventListener("mouseover", (e) => {
|
||||||
|
// when CSS anchor positioning is supported, this is only used to set the font
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
});
|
||||||
|
|
||||||
|
return anchor;
|
||||||
|
}
|
||||||
|
|
||||||
|
get headings() {
|
||||||
|
return this.querySelectorAll(this.selector.split(",").map(entry => `${entry.trim()}[id]`));
|
||||||
|
}
|
||||||
|
|
||||||
|
get selector() {
|
||||||
|
return this.getAttribute("selector") || HeadingAnchors.defaultSelector;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HeadingAnchors.register();
|
||||||
|
|
||||||
|
export { HeadingAnchors }
|
||||||
|
// Efficient iterative Fibonacci
|
||||||
|
function fib(n) {
|
||||||
|
if (n < 2) return n;
|
||||||
|
let a = 0, b = 1;
|
||||||
|
for (let i = 2; i <= n; i++) {
|
||||||
|
[a, b] = [b, a + b];
|
||||||
|
}
|
||||||
|
document.getElementById("fib_seq").textContent = n;
|
||||||
|
document.getElementById("fib_num").textContent = result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
window.onload = function() {
|
||||||
|
const n = 3; // Example: change this number or get it dynamically
|
||||||
|
const result = fib(n);
|
||||||
|
document.getElementById("fib_seq").textContent = n;
|
||||||
|
document.getElementById("fib_num").textContent = result; // Doesn't render properly on mobile; figure out subscript numbers...
|
||||||
|
};
|
||||||
247
dist/B8sDgsMUsD.js
vendored
Normal file
|
|
@ -0,0 +1,247 @@
|
||||||
|
// Thank you to https://github.com/daviddarnes/heading-anchors
|
||||||
|
// Thank you to https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
|
||||||
|
let globalInstanceIndex = 0;
|
||||||
|
|
||||||
|
class HeadingAnchors extends HTMLElement {
|
||||||
|
static register(tagName = "heading-anchors", registry = window.customElements) {
|
||||||
|
if(registry && !registry.get(tagName)) {
|
||||||
|
registry.define(tagName, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static attributes = {
|
||||||
|
exclude: "data-ha-exclude",
|
||||||
|
prefix: "prefix",
|
||||||
|
content: "content",
|
||||||
|
}
|
||||||
|
|
||||||
|
static classes = {
|
||||||
|
anchor: "ha",
|
||||||
|
placeholder: "ha-placeholder",
|
||||||
|
srOnly: "ha-visualhide",
|
||||||
|
}
|
||||||
|
|
||||||
|
static defaultSelector = "h2,h3,h4,h5,h6";
|
||||||
|
|
||||||
|
static css = `
|
||||||
|
.${HeadingAnchors.classes.srOnly} {
|
||||||
|
clip: rect(0 0 0 0);
|
||||||
|
height: 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: absolute;
|
||||||
|
width: 1px;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: var(--ha_offsetx);
|
||||||
|
top: var(--ha_offsety);
|
||||||
|
text-decoration: none;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
opacity: .3;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor}:is(:focus-within, :hover) {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor},
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 .25em;
|
||||||
|
|
||||||
|
/* Disable selection of visually hidden label */
|
||||||
|
-webkit-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@supports (anchor-name: none) {
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: anchor(left);
|
||||||
|
top: anchor(top);
|
||||||
|
}
|
||||||
|
}`;
|
||||||
|
|
||||||
|
get supports() {
|
||||||
|
return "replaceSync" in CSSStyleSheet.prototype;
|
||||||
|
}
|
||||||
|
|
||||||
|
get supportsAnchorPosition() {
|
||||||
|
return CSS.supports("anchor-name: none");
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
if(!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sheet = new CSSStyleSheet();
|
||||||
|
sheet.replaceSync(HeadingAnchors.css);
|
||||||
|
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
|
||||||
|
|
||||||
|
this.headingStyles = {};
|
||||||
|
this.instanceIndex = globalInstanceIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
if (!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.headings.forEach((heading, index) => {
|
||||||
|
if(!heading.hasAttribute(HeadingAnchors.attributes.exclude)) {
|
||||||
|
let anchor = this.getAnchorElement(heading);
|
||||||
|
let placeholder = this.getPlaceholderElement();
|
||||||
|
|
||||||
|
// Prefers anchor position approach for better accessibility
|
||||||
|
// https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
let anchorName = `--ha_${this.instanceIndex}_${index}`;
|
||||||
|
placeholder.style.setProperty("anchor-name", anchorName);
|
||||||
|
anchor.style.positionAnchor = anchorName;
|
||||||
|
}
|
||||||
|
|
||||||
|
heading.appendChild(placeholder);
|
||||||
|
heading.after(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchorFromPlaceholder(placeholder) {
|
||||||
|
if(!placeholder) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let heading = placeholder.closest("h1,h2,h3,h4,h5,h6");
|
||||||
|
if(!heading.nextElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO next element could be more defensive
|
||||||
|
this.positionAnchor(heading.nextElementSibling);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchor(anchor) {
|
||||||
|
if(!anchor || !anchor.previousElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO previous element could be more defensive
|
||||||
|
let heading = anchor.previousElementSibling;
|
||||||
|
this.setFontProp(heading, anchor);
|
||||||
|
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
// quit early
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
anchor.style.setProperty("--ha_offsetx", `${placeholder.offsetLeft}px`);
|
||||||
|
anchor.style.setProperty("--ha_offsety", `${placeholder.offsetTop}px`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setFontProp(heading, anchor) {
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
let style = getComputedStyle(placeholder);
|
||||||
|
let props = ["font-weight", "font-size", "line-height", "font-family"];
|
||||||
|
let [weight, size, lh, family] = props.map(name => style.getPropertyValue(name));
|
||||||
|
anchor.style.setProperty("font", `${weight} ${size}/${lh} ${family}`);
|
||||||
|
let vars = style.getPropertyValue("font-variation-settings");
|
||||||
|
if(vars) {
|
||||||
|
anchor.style.setProperty("font-variation-settings", vars);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getAccessibleTextPrefix() {
|
||||||
|
// Useful for i18n
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.prefix) || "Jump to section titled";
|
||||||
|
}
|
||||||
|
|
||||||
|
getContent() {
|
||||||
|
if(this.hasAttribute(HeadingAnchors.attributes.content)) {
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.content);
|
||||||
|
}
|
||||||
|
return "#";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Placeholder nests inside of heading
|
||||||
|
getPlaceholderElement() {
|
||||||
|
let ph = document.createElement("span");
|
||||||
|
ph.setAttribute("aria-hidden", true);
|
||||||
|
ph.classList.add(HeadingAnchors.classes.placeholder);
|
||||||
|
let content = this.getContent();
|
||||||
|
if(content) {
|
||||||
|
ph.textContent = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
ph.addEventListener("mouseover", (e) => {
|
||||||
|
let placeholder = e.target.closest(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
this.positionAnchorFromPlaceholder(placeholder);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return ph;
|
||||||
|
}
|
||||||
|
|
||||||
|
getAnchorElement(heading) {
|
||||||
|
let anchor = document.createElement("a");
|
||||||
|
anchor.href = `#${heading.id}`;
|
||||||
|
anchor.classList.add(HeadingAnchors.classes.anchor);
|
||||||
|
|
||||||
|
let content = this.getContent();
|
||||||
|
anchor.innerHTML = `<span class="${HeadingAnchors.classes.srOnly}">${this.getAccessibleTextPrefix()}: ${heading.textContent}</span>${content ? `<span aria-hidden="true">${content}</span>` : ""}`;
|
||||||
|
|
||||||
|
anchor.addEventListener("focus", e => {
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
if(anchor) {
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
anchor.addEventListener("mouseover", (e) => {
|
||||||
|
// when CSS anchor positioning is supported, this is only used to set the font
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
});
|
||||||
|
|
||||||
|
return anchor;
|
||||||
|
}
|
||||||
|
|
||||||
|
get headings() {
|
||||||
|
return this.querySelectorAll(this.selector.split(",").map(entry => `${entry.trim()}[id]`));
|
||||||
|
}
|
||||||
|
|
||||||
|
get selector() {
|
||||||
|
return this.getAttribute("selector") || HeadingAnchors.defaultSelector;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HeadingAnchors.register();
|
||||||
|
|
||||||
|
export { HeadingAnchors }
|
||||||
|
// Efficient iterative Fibonacci
|
||||||
|
function fib(n) {
|
||||||
|
if (n < 2) return n;
|
||||||
|
let a = 0, b = 1;
|
||||||
|
for (let i = 2; i <= n; i++) {
|
||||||
|
[a, b] = [b, a + b];
|
||||||
|
}
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
window.onload = function() {
|
||||||
|
const n = 3; // Example: change this number or get it dynamically
|
||||||
|
const result = fib(n);
|
||||||
|
document.getElementById("fib").textContent = `F(${n}) = ${result}`;
|
||||||
|
};
|
||||||
249
dist/EtgvdWNsiS.js
vendored
Normal file
|
|
@ -0,0 +1,249 @@
|
||||||
|
// Thank you to https://github.com/daviddarnes/heading-anchors
|
||||||
|
// Thank you to https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
|
||||||
|
let globalInstanceIndex = 0;
|
||||||
|
|
||||||
|
class HeadingAnchors extends HTMLElement {
|
||||||
|
static register(tagName = "heading-anchors", registry = window.customElements) {
|
||||||
|
if(registry && !registry.get(tagName)) {
|
||||||
|
registry.define(tagName, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static attributes = {
|
||||||
|
exclude: "data-ha-exclude",
|
||||||
|
prefix: "prefix",
|
||||||
|
content: "content",
|
||||||
|
}
|
||||||
|
|
||||||
|
static classes = {
|
||||||
|
anchor: "ha",
|
||||||
|
placeholder: "ha-placeholder",
|
||||||
|
srOnly: "ha-visualhide",
|
||||||
|
}
|
||||||
|
|
||||||
|
static defaultSelector = "h2,h3,h4,h5,h6";
|
||||||
|
|
||||||
|
static css = `
|
||||||
|
.${HeadingAnchors.classes.srOnly} {
|
||||||
|
clip: rect(0 0 0 0);
|
||||||
|
height: 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: absolute;
|
||||||
|
width: 1px;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: var(--ha_offsetx);
|
||||||
|
top: var(--ha_offsety);
|
||||||
|
text-decoration: none;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
opacity: .3;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor}:is(:focus-within, :hover) {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor},
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 .25em;
|
||||||
|
|
||||||
|
/* Disable selection of visually hidden label */
|
||||||
|
-webkit-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@supports (anchor-name: none) {
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: anchor(left);
|
||||||
|
top: anchor(top);
|
||||||
|
}
|
||||||
|
}`;
|
||||||
|
|
||||||
|
get supports() {
|
||||||
|
return "replaceSync" in CSSStyleSheet.prototype;
|
||||||
|
}
|
||||||
|
|
||||||
|
get supportsAnchorPosition() {
|
||||||
|
return CSS.supports("anchor-name: none");
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
if(!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sheet = new CSSStyleSheet();
|
||||||
|
sheet.replaceSync(HeadingAnchors.css);
|
||||||
|
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
|
||||||
|
|
||||||
|
this.headingStyles = {};
|
||||||
|
this.instanceIndex = globalInstanceIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
if (!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.headings.forEach((heading, index) => {
|
||||||
|
if(!heading.hasAttribute(HeadingAnchors.attributes.exclude)) {
|
||||||
|
let anchor = this.getAnchorElement(heading);
|
||||||
|
let placeholder = this.getPlaceholderElement();
|
||||||
|
|
||||||
|
// Prefers anchor position approach for better accessibility
|
||||||
|
// https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
let anchorName = `--ha_${this.instanceIndex}_${index}`;
|
||||||
|
placeholder.style.setProperty("anchor-name", anchorName);
|
||||||
|
anchor.style.positionAnchor = anchorName;
|
||||||
|
}
|
||||||
|
|
||||||
|
heading.appendChild(placeholder);
|
||||||
|
heading.after(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchorFromPlaceholder(placeholder) {
|
||||||
|
if(!placeholder) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let heading = placeholder.closest("h1,h2,h3,h4,h5,h6");
|
||||||
|
if(!heading.nextElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO next element could be more defensive
|
||||||
|
this.positionAnchor(heading.nextElementSibling);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchor(anchor) {
|
||||||
|
if(!anchor || !anchor.previousElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO previous element could be more defensive
|
||||||
|
let heading = anchor.previousElementSibling;
|
||||||
|
this.setFontProp(heading, anchor);
|
||||||
|
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
// quit early
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
anchor.style.setProperty("--ha_offsetx", `${placeholder.offsetLeft}px`);
|
||||||
|
anchor.style.setProperty("--ha_offsety", `${placeholder.offsetTop}px`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setFontProp(heading, anchor) {
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
let style = getComputedStyle(placeholder);
|
||||||
|
let props = ["font-weight", "font-size", "line-height", "font-family"];
|
||||||
|
let [weight, size, lh, family] = props.map(name => style.getPropertyValue(name));
|
||||||
|
anchor.style.setProperty("font", `${weight} ${size}/${lh} ${family}`);
|
||||||
|
let vars = style.getPropertyValue("font-variation-settings");
|
||||||
|
if(vars) {
|
||||||
|
anchor.style.setProperty("font-variation-settings", vars);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getAccessibleTextPrefix() {
|
||||||
|
// Useful for i18n
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.prefix) || "Jump to section titled";
|
||||||
|
}
|
||||||
|
|
||||||
|
getContent() {
|
||||||
|
if(this.hasAttribute(HeadingAnchors.attributes.content)) {
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.content);
|
||||||
|
}
|
||||||
|
return "#";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Placeholder nests inside of heading
|
||||||
|
getPlaceholderElement() {
|
||||||
|
let ph = document.createElement("span");
|
||||||
|
ph.setAttribute("aria-hidden", true);
|
||||||
|
ph.classList.add(HeadingAnchors.classes.placeholder);
|
||||||
|
let content = this.getContent();
|
||||||
|
if(content) {
|
||||||
|
ph.textContent = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
ph.addEventListener("mouseover", (e) => {
|
||||||
|
let placeholder = e.target.closest(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
this.positionAnchorFromPlaceholder(placeholder);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return ph;
|
||||||
|
}
|
||||||
|
|
||||||
|
getAnchorElement(heading) {
|
||||||
|
let anchor = document.createElement("a");
|
||||||
|
anchor.href = `#${heading.id}`;
|
||||||
|
anchor.classList.add(HeadingAnchors.classes.anchor);
|
||||||
|
|
||||||
|
let content = this.getContent();
|
||||||
|
anchor.innerHTML = `<span class="${HeadingAnchors.classes.srOnly}">${this.getAccessibleTextPrefix()}: ${heading.textContent}</span>${content ? `<span aria-hidden="true">${content}</span>` : ""}`;
|
||||||
|
|
||||||
|
anchor.addEventListener("focus", e => {
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
if(anchor) {
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
anchor.addEventListener("mouseover", (e) => {
|
||||||
|
// when CSS anchor positioning is supported, this is only used to set the font
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
});
|
||||||
|
|
||||||
|
return anchor;
|
||||||
|
}
|
||||||
|
|
||||||
|
get headings() {
|
||||||
|
return this.querySelectorAll(this.selector.split(",").map(entry => `${entry.trim()}[id]`));
|
||||||
|
}
|
||||||
|
|
||||||
|
get selector() {
|
||||||
|
return this.getAttribute("selector") || HeadingAnchors.defaultSelector;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HeadingAnchors.register();
|
||||||
|
|
||||||
|
export { HeadingAnchors }
|
||||||
|
// Efficient iterative Fibonacci
|
||||||
|
function fib(n) {
|
||||||
|
if (n < 2) return n;
|
||||||
|
let a = 0, b = 1;
|
||||||
|
for (let i = 2; i <= n; i++) {
|
||||||
|
[a, b] = [b, a + b];
|
||||||
|
}
|
||||||
|
document.getElementById("fib_seq").textContent = n;
|
||||||
|
document.getElementById("fib_num").textContent = result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
window.onload = function() {
|
||||||
|
const n = 1; // Example: change this number or get it dynamically
|
||||||
|
const result = fib(n);
|
||||||
|
document.getElementById("fib_seq").textContent = n;
|
||||||
|
document.getElementById("fib_num").textContent = result; // Doesn't render properly on mobile; figure out subscript numbers...
|
||||||
|
};
|
||||||
248
dist/FiU03Pf7Pn.js
vendored
Normal file
|
|
@ -0,0 +1,248 @@
|
||||||
|
// Thank you to https://github.com/daviddarnes/heading-anchors
|
||||||
|
// Thank you to https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
|
||||||
|
let globalInstanceIndex = 0;
|
||||||
|
|
||||||
|
class HeadingAnchors extends HTMLElement {
|
||||||
|
static register(tagName = "heading-anchors", registry = window.customElements) {
|
||||||
|
if(registry && !registry.get(tagName)) {
|
||||||
|
registry.define(tagName, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static attributes = {
|
||||||
|
exclude: "data-ha-exclude",
|
||||||
|
prefix: "prefix",
|
||||||
|
content: "content",
|
||||||
|
}
|
||||||
|
|
||||||
|
static classes = {
|
||||||
|
anchor: "ha",
|
||||||
|
placeholder: "ha-placeholder",
|
||||||
|
srOnly: "ha-visualhide",
|
||||||
|
}
|
||||||
|
|
||||||
|
static defaultSelector = "h2,h3,h4,h5,h6";
|
||||||
|
|
||||||
|
static css = `
|
||||||
|
.${HeadingAnchors.classes.srOnly} {
|
||||||
|
clip: rect(0 0 0 0);
|
||||||
|
height: 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: absolute;
|
||||||
|
width: 1px;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: var(--ha_offsetx);
|
||||||
|
top: var(--ha_offsety);
|
||||||
|
text-decoration: none;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
opacity: .3;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor}:is(:focus-within, :hover) {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor},
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 .25em;
|
||||||
|
|
||||||
|
/* Disable selection of visually hidden label */
|
||||||
|
-webkit-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@supports (anchor-name: none) {
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: anchor(left);
|
||||||
|
top: anchor(top);
|
||||||
|
}
|
||||||
|
}`;
|
||||||
|
|
||||||
|
get supports() {
|
||||||
|
return "replaceSync" in CSSStyleSheet.prototype;
|
||||||
|
}
|
||||||
|
|
||||||
|
get supportsAnchorPosition() {
|
||||||
|
return CSS.supports("anchor-name: none");
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
if(!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sheet = new CSSStyleSheet();
|
||||||
|
sheet.replaceSync(HeadingAnchors.css);
|
||||||
|
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
|
||||||
|
|
||||||
|
this.headingStyles = {};
|
||||||
|
this.instanceIndex = globalInstanceIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
if (!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.headings.forEach((heading, index) => {
|
||||||
|
if(!heading.hasAttribute(HeadingAnchors.attributes.exclude)) {
|
||||||
|
let anchor = this.getAnchorElement(heading);
|
||||||
|
let placeholder = this.getPlaceholderElement();
|
||||||
|
|
||||||
|
// Prefers anchor position approach for better accessibility
|
||||||
|
// https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
let anchorName = `--ha_${this.instanceIndex}_${index}`;
|
||||||
|
placeholder.style.setProperty("anchor-name", anchorName);
|
||||||
|
anchor.style.positionAnchor = anchorName;
|
||||||
|
}
|
||||||
|
|
||||||
|
heading.appendChild(placeholder);
|
||||||
|
heading.after(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchorFromPlaceholder(placeholder) {
|
||||||
|
if(!placeholder) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let heading = placeholder.closest("h1,h2,h3,h4,h5,h6");
|
||||||
|
if(!heading.nextElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO next element could be more defensive
|
||||||
|
this.positionAnchor(heading.nextElementSibling);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchor(anchor) {
|
||||||
|
if(!anchor || !anchor.previousElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO previous element could be more defensive
|
||||||
|
let heading = anchor.previousElementSibling;
|
||||||
|
this.setFontProp(heading, anchor);
|
||||||
|
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
// quit early
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
anchor.style.setProperty("--ha_offsetx", `${placeholder.offsetLeft}px`);
|
||||||
|
anchor.style.setProperty("--ha_offsety", `${placeholder.offsetTop}px`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setFontProp(heading, anchor) {
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
let style = getComputedStyle(placeholder);
|
||||||
|
let props = ["font-weight", "font-size", "line-height", "font-family"];
|
||||||
|
let [weight, size, lh, family] = props.map(name => style.getPropertyValue(name));
|
||||||
|
anchor.style.setProperty("font", `${weight} ${size}/${lh} ${family}`);
|
||||||
|
let vars = style.getPropertyValue("font-variation-settings");
|
||||||
|
if(vars) {
|
||||||
|
anchor.style.setProperty("font-variation-settings", vars);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getAccessibleTextPrefix() {
|
||||||
|
// Useful for i18n
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.prefix) || "Jump to section titled";
|
||||||
|
}
|
||||||
|
|
||||||
|
getContent() {
|
||||||
|
if(this.hasAttribute(HeadingAnchors.attributes.content)) {
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.content);
|
||||||
|
}
|
||||||
|
return "#";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Placeholder nests inside of heading
|
||||||
|
getPlaceholderElement() {
|
||||||
|
let ph = document.createElement("span");
|
||||||
|
ph.setAttribute("aria-hidden", true);
|
||||||
|
ph.classList.add(HeadingAnchors.classes.placeholder);
|
||||||
|
let content = this.getContent();
|
||||||
|
if(content) {
|
||||||
|
ph.textContent = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
ph.addEventListener("mouseover", (e) => {
|
||||||
|
let placeholder = e.target.closest(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
this.positionAnchorFromPlaceholder(placeholder);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return ph;
|
||||||
|
}
|
||||||
|
|
||||||
|
getAnchorElement(heading) {
|
||||||
|
let anchor = document.createElement("a");
|
||||||
|
anchor.href = `#${heading.id}`;
|
||||||
|
anchor.classList.add(HeadingAnchors.classes.anchor);
|
||||||
|
|
||||||
|
let content = this.getContent();
|
||||||
|
anchor.innerHTML = `<span class="${HeadingAnchors.classes.srOnly}">${this.getAccessibleTextPrefix()}: ${heading.textContent}</span>${content ? `<span aria-hidden="true">${content}</span>` : ""}`;
|
||||||
|
|
||||||
|
anchor.addEventListener("focus", e => {
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
if(anchor) {
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
anchor.addEventListener("mouseover", (e) => {
|
||||||
|
// when CSS anchor positioning is supported, this is only used to set the font
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
});
|
||||||
|
|
||||||
|
return anchor;
|
||||||
|
}
|
||||||
|
|
||||||
|
get headings() {
|
||||||
|
return this.querySelectorAll(this.selector.split(",").map(entry => `${entry.trim()}[id]`));
|
||||||
|
}
|
||||||
|
|
||||||
|
get selector() {
|
||||||
|
return this.getAttribute("selector") || HeadingAnchors.defaultSelector;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HeadingAnchors.register();
|
||||||
|
|
||||||
|
export { HeadingAnchors }
|
||||||
|
// Efficient iterative Fibonacci
|
||||||
|
function fib(n) {
|
||||||
|
if (n < 2) return n;
|
||||||
|
let a = 0, b = 1;
|
||||||
|
for (let i = 2; i <= n; i++) {
|
||||||
|
[a, b] = [b, a + b];
|
||||||
|
}
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
window.onload = function() {
|
||||||
|
const n = 4; // Example: change this number or get it dynamically
|
||||||
|
const result = fib(n);
|
||||||
|
document.getElementById("fib_seq").textContent = n;
|
||||||
|
document.getElementById("fib_num").textContent = result; // Doesn't render properly on mobile; figure out subscript numbers...
|
||||||
|
};
|
||||||
236
dist/I8MnV9ZEzM.js
vendored
Normal file
|
|
@ -0,0 +1,236 @@
|
||||||
|
// Thank you to https://github.com/daviddarnes/heading-anchors
|
||||||
|
// Thank you to https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
|
||||||
|
let globalInstanceIndex = 0;
|
||||||
|
|
||||||
|
class HeadingAnchors extends HTMLElement {
|
||||||
|
static register(tagName = "heading-anchors", registry = window.customElements) {
|
||||||
|
if(registry && !registry.get(tagName)) {
|
||||||
|
registry.define(tagName, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static attributes = {
|
||||||
|
exclude: "data-ha-exclude",
|
||||||
|
prefix: "prefix",
|
||||||
|
content: "content",
|
||||||
|
}
|
||||||
|
|
||||||
|
static classes = {
|
||||||
|
anchor: "ha",
|
||||||
|
placeholder: "ha-placeholder",
|
||||||
|
srOnly: "ha-visualhide",
|
||||||
|
}
|
||||||
|
|
||||||
|
static defaultSelector = "h2,h3,h4,h5,h6";
|
||||||
|
|
||||||
|
static css = `
|
||||||
|
.${HeadingAnchors.classes.srOnly} {
|
||||||
|
clip: rect(0 0 0 0);
|
||||||
|
height: 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: absolute;
|
||||||
|
width: 1px;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: var(--ha_offsetx);
|
||||||
|
top: var(--ha_offsety);
|
||||||
|
text-decoration: none;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
opacity: .3;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor}:is(:focus-within, :hover) {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor},
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 .25em;
|
||||||
|
|
||||||
|
/* Disable selection of visually hidden label */
|
||||||
|
-webkit-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@supports (anchor-name: none) {
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: anchor(left);
|
||||||
|
top: anchor(top);
|
||||||
|
}
|
||||||
|
}`;
|
||||||
|
|
||||||
|
get supports() {
|
||||||
|
return "replaceSync" in CSSStyleSheet.prototype;
|
||||||
|
}
|
||||||
|
|
||||||
|
get supportsAnchorPosition() {
|
||||||
|
return CSS.supports("anchor-name: none");
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
if(!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sheet = new CSSStyleSheet();
|
||||||
|
sheet.replaceSync(HeadingAnchors.css);
|
||||||
|
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
|
||||||
|
|
||||||
|
this.headingStyles = {};
|
||||||
|
this.instanceIndex = globalInstanceIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
if (!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.headings.forEach((heading, index) => {
|
||||||
|
if(!heading.hasAttribute(HeadingAnchors.attributes.exclude)) {
|
||||||
|
let anchor = this.getAnchorElement(heading);
|
||||||
|
let placeholder = this.getPlaceholderElement();
|
||||||
|
|
||||||
|
// Prefers anchor position approach for better accessibility
|
||||||
|
// https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
let anchorName = `--ha_${this.instanceIndex}_${index}`;
|
||||||
|
placeholder.style.setProperty("anchor-name", anchorName);
|
||||||
|
anchor.style.positionAnchor = anchorName;
|
||||||
|
}
|
||||||
|
|
||||||
|
heading.appendChild(placeholder);
|
||||||
|
heading.after(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchorFromPlaceholder(placeholder) {
|
||||||
|
if(!placeholder) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let heading = placeholder.closest("h1,h2,h3,h4,h5,h6");
|
||||||
|
if(!heading.nextElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO next element could be more defensive
|
||||||
|
this.positionAnchor(heading.nextElementSibling);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchor(anchor) {
|
||||||
|
if(!anchor || !anchor.previousElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO previous element could be more defensive
|
||||||
|
let heading = anchor.previousElementSibling;
|
||||||
|
this.setFontProp(heading, anchor);
|
||||||
|
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
// quit early
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
anchor.style.setProperty("--ha_offsetx", `${placeholder.offsetLeft}px`);
|
||||||
|
anchor.style.setProperty("--ha_offsety", `${placeholder.offsetTop}px`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setFontProp(heading, anchor) {
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
let style = getComputedStyle(placeholder);
|
||||||
|
let props = ["font-weight", "font-size", "line-height", "font-family"];
|
||||||
|
let [weight, size, lh, family] = props.map(name => style.getPropertyValue(name));
|
||||||
|
anchor.style.setProperty("font", `${weight} ${size}/${lh} ${family}`);
|
||||||
|
let vars = style.getPropertyValue("font-variation-settings");
|
||||||
|
if(vars) {
|
||||||
|
anchor.style.setProperty("font-variation-settings", vars);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getAccessibleTextPrefix() {
|
||||||
|
// Useful for i18n
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.prefix) || "Jump to section titled";
|
||||||
|
}
|
||||||
|
|
||||||
|
getContent() {
|
||||||
|
if(this.hasAttribute(HeadingAnchors.attributes.content)) {
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.content);
|
||||||
|
}
|
||||||
|
return "#";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Placeholder nests inside of heading
|
||||||
|
getPlaceholderElement() {
|
||||||
|
let ph = document.createElement("span");
|
||||||
|
ph.setAttribute("aria-hidden", true);
|
||||||
|
ph.classList.add(HeadingAnchors.classes.placeholder);
|
||||||
|
let content = this.getContent();
|
||||||
|
if(content) {
|
||||||
|
ph.textContent = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
ph.addEventListener("mouseover", (e) => {
|
||||||
|
let placeholder = e.target.closest(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
this.positionAnchorFromPlaceholder(placeholder);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return ph;
|
||||||
|
}
|
||||||
|
|
||||||
|
getAnchorElement(heading) {
|
||||||
|
let anchor = document.createElement("a");
|
||||||
|
anchor.href = `#${heading.id}`;
|
||||||
|
anchor.classList.add(HeadingAnchors.classes.anchor);
|
||||||
|
|
||||||
|
let content = this.getContent();
|
||||||
|
anchor.innerHTML = `<span class="${HeadingAnchors.classes.srOnly}">${this.getAccessibleTextPrefix()}: ${heading.textContent}</span>${content ? `<span aria-hidden="true">${content}</span>` : ""}`;
|
||||||
|
|
||||||
|
anchor.addEventListener("focus", e => {
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
if(anchor) {
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
anchor.addEventListener("mouseover", (e) => {
|
||||||
|
// when CSS anchor positioning is supported, this is only used to set the font
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
});
|
||||||
|
|
||||||
|
return anchor;
|
||||||
|
}
|
||||||
|
|
||||||
|
get headings() {
|
||||||
|
return this.querySelectorAll(this.selector.split(",").map(entry => `${entry.trim()}[id]`));
|
||||||
|
}
|
||||||
|
|
||||||
|
get selector() {
|
||||||
|
return this.getAttribute("selector") || HeadingAnchors.defaultSelector;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HeadingAnchors.register();
|
||||||
|
|
||||||
|
export { HeadingAnchors }
|
||||||
|
window.onload = function() {
|
||||||
|
const n = 1; // Example: change this number or get it dynamically
|
||||||
|
const result = fib(n);
|
||||||
|
document.getElementById("fib").textContent = `F(${n}) = ${result}`;
|
||||||
|
};
|
||||||
246
dist/IRg55wT5QS.js
vendored
Normal file
|
|
@ -0,0 +1,246 @@
|
||||||
|
// Thank you to https://github.com/daviddarnes/heading-anchors
|
||||||
|
// Thank you to https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
|
||||||
|
let globalInstanceIndex = 0;
|
||||||
|
|
||||||
|
class HeadingAnchors extends HTMLElement {
|
||||||
|
static register(tagName = "heading-anchors", registry = window.customElements) {
|
||||||
|
if(registry && !registry.get(tagName)) {
|
||||||
|
registry.define(tagName, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static attributes = {
|
||||||
|
exclude: "data-ha-exclude",
|
||||||
|
prefix: "prefix",
|
||||||
|
content: "content",
|
||||||
|
}
|
||||||
|
|
||||||
|
static classes = {
|
||||||
|
anchor: "ha",
|
||||||
|
placeholder: "ha-placeholder",
|
||||||
|
srOnly: "ha-visualhide",
|
||||||
|
}
|
||||||
|
|
||||||
|
static defaultSelector = "h2,h3,h4,h5,h6";
|
||||||
|
|
||||||
|
static css = `
|
||||||
|
.${HeadingAnchors.classes.srOnly} {
|
||||||
|
clip: rect(0 0 0 0);
|
||||||
|
height: 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: absolute;
|
||||||
|
width: 1px;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: var(--ha_offsetx);
|
||||||
|
top: var(--ha_offsety);
|
||||||
|
text-decoration: none;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
opacity: .3;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor}:is(:focus-within, :hover) {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor},
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 .25em;
|
||||||
|
|
||||||
|
/* Disable selection of visually hidden label */
|
||||||
|
-webkit-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@supports (anchor-name: none) {
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: anchor(left);
|
||||||
|
top: anchor(top);
|
||||||
|
}
|
||||||
|
}`;
|
||||||
|
|
||||||
|
get supports() {
|
||||||
|
return "replaceSync" in CSSStyleSheet.prototype;
|
||||||
|
}
|
||||||
|
|
||||||
|
get supportsAnchorPosition() {
|
||||||
|
return CSS.supports("anchor-name: none");
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
if(!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sheet = new CSSStyleSheet();
|
||||||
|
sheet.replaceSync(HeadingAnchors.css);
|
||||||
|
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
|
||||||
|
|
||||||
|
this.headingStyles = {};
|
||||||
|
this.instanceIndex = globalInstanceIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
if (!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.headings.forEach((heading, index) => {
|
||||||
|
if(!heading.hasAttribute(HeadingAnchors.attributes.exclude)) {
|
||||||
|
let anchor = this.getAnchorElement(heading);
|
||||||
|
let placeholder = this.getPlaceholderElement();
|
||||||
|
|
||||||
|
// Prefers anchor position approach for better accessibility
|
||||||
|
// https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
let anchorName = `--ha_${this.instanceIndex}_${index}`;
|
||||||
|
placeholder.style.setProperty("anchor-name", anchorName);
|
||||||
|
anchor.style.positionAnchor = anchorName;
|
||||||
|
}
|
||||||
|
|
||||||
|
heading.appendChild(placeholder);
|
||||||
|
heading.after(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchorFromPlaceholder(placeholder) {
|
||||||
|
if(!placeholder) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let heading = placeholder.closest("h1,h2,h3,h4,h5,h6");
|
||||||
|
if(!heading.nextElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO next element could be more defensive
|
||||||
|
this.positionAnchor(heading.nextElementSibling);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchor(anchor) {
|
||||||
|
if(!anchor || !anchor.previousElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO previous element could be more defensive
|
||||||
|
let heading = anchor.previousElementSibling;
|
||||||
|
this.setFontProp(heading, anchor);
|
||||||
|
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
// quit early
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
anchor.style.setProperty("--ha_offsetx", `${placeholder.offsetLeft}px`);
|
||||||
|
anchor.style.setProperty("--ha_offsety", `${placeholder.offsetTop}px`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setFontProp(heading, anchor) {
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
let style = getComputedStyle(placeholder);
|
||||||
|
let props = ["font-weight", "font-size", "line-height", "font-family"];
|
||||||
|
let [weight, size, lh, family] = props.map(name => style.getPropertyValue(name));
|
||||||
|
anchor.style.setProperty("font", `${weight} ${size}/${lh} ${family}`);
|
||||||
|
let vars = style.getPropertyValue("font-variation-settings");
|
||||||
|
if(vars) {
|
||||||
|
anchor.style.setProperty("font-variation-settings", vars);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getAccessibleTextPrefix() {
|
||||||
|
// Useful for i18n
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.prefix) || "Jump to section titled";
|
||||||
|
}
|
||||||
|
|
||||||
|
getContent() {
|
||||||
|
if(this.hasAttribute(HeadingAnchors.attributes.content)) {
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.content);
|
||||||
|
}
|
||||||
|
return "#";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Placeholder nests inside of heading
|
||||||
|
getPlaceholderElement() {
|
||||||
|
let ph = document.createElement("span");
|
||||||
|
ph.setAttribute("aria-hidden", true);
|
||||||
|
ph.classList.add(HeadingAnchors.classes.placeholder);
|
||||||
|
let content = this.getContent();
|
||||||
|
if(content) {
|
||||||
|
ph.textContent = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
ph.addEventListener("mouseover", (e) => {
|
||||||
|
let placeholder = e.target.closest(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
this.positionAnchorFromPlaceholder(placeholder);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return ph;
|
||||||
|
}
|
||||||
|
|
||||||
|
getAnchorElement(heading) {
|
||||||
|
let anchor = document.createElement("a");
|
||||||
|
anchor.href = `#${heading.id}`;
|
||||||
|
anchor.classList.add(HeadingAnchors.classes.anchor);
|
||||||
|
|
||||||
|
let content = this.getContent();
|
||||||
|
anchor.innerHTML = `<span class="${HeadingAnchors.classes.srOnly}">${this.getAccessibleTextPrefix()}: ${heading.textContent}</span>${content ? `<span aria-hidden="true">${content}</span>` : ""}`;
|
||||||
|
|
||||||
|
anchor.addEventListener("focus", e => {
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
if(anchor) {
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
anchor.addEventListener("mouseover", (e) => {
|
||||||
|
// when CSS anchor positioning is supported, this is only used to set the font
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
});
|
||||||
|
|
||||||
|
return anchor;
|
||||||
|
}
|
||||||
|
|
||||||
|
get headings() {
|
||||||
|
return this.querySelectorAll(this.selector.split(",").map(entry => `${entry.trim()}[id]`));
|
||||||
|
}
|
||||||
|
|
||||||
|
get selector() {
|
||||||
|
return this.getAttribute("selector") || HeadingAnchors.defaultSelector;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HeadingAnchors.register();
|
||||||
|
|
||||||
|
export { HeadingAnchors }
|
||||||
|
// Efficient iterative Fibonacci
|
||||||
|
function fib(n) {
|
||||||
|
if (n < 2) return n;
|
||||||
|
let a = 0, b = 1;
|
||||||
|
for (let i = 2; i <= n; i++) {
|
||||||
|
[a, b] = [b, a + b];
|
||||||
|
}
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.onload = function() {
|
||||||
|
const n = 1; // Example: change this number or get it dynamically
|
||||||
|
const result = fib(n);
|
||||||
|
document.getElementById("fib").textContent = `F<sub>${n}</sub> = ${result}`;
|
||||||
|
};
|
||||||
248
dist/JYf_9DlK1Z.js
vendored
Normal file
|
|
@ -0,0 +1,248 @@
|
||||||
|
// Thank you to https://github.com/daviddarnes/heading-anchors
|
||||||
|
// Thank you to https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
|
||||||
|
let globalInstanceIndex = 0;
|
||||||
|
|
||||||
|
class HeadingAnchors extends HTMLElement {
|
||||||
|
static register(tagName = "heading-anchors", registry = window.customElements) {
|
||||||
|
if(registry && !registry.get(tagName)) {
|
||||||
|
registry.define(tagName, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static attributes = {
|
||||||
|
exclude: "data-ha-exclude",
|
||||||
|
prefix: "prefix",
|
||||||
|
content: "content",
|
||||||
|
}
|
||||||
|
|
||||||
|
static classes = {
|
||||||
|
anchor: "ha",
|
||||||
|
placeholder: "ha-placeholder",
|
||||||
|
srOnly: "ha-visualhide",
|
||||||
|
}
|
||||||
|
|
||||||
|
static defaultSelector = "h2,h3,h4,h5,h6";
|
||||||
|
|
||||||
|
static css = `
|
||||||
|
.${HeadingAnchors.classes.srOnly} {
|
||||||
|
clip: rect(0 0 0 0);
|
||||||
|
height: 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: absolute;
|
||||||
|
width: 1px;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: var(--ha_offsetx);
|
||||||
|
top: var(--ha_offsety);
|
||||||
|
text-decoration: none;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
opacity: .3;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor}:is(:focus-within, :hover) {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor},
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 .25em;
|
||||||
|
|
||||||
|
/* Disable selection of visually hidden label */
|
||||||
|
-webkit-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@supports (anchor-name: none) {
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: anchor(left);
|
||||||
|
top: anchor(top);
|
||||||
|
}
|
||||||
|
}`;
|
||||||
|
|
||||||
|
get supports() {
|
||||||
|
return "replaceSync" in CSSStyleSheet.prototype;
|
||||||
|
}
|
||||||
|
|
||||||
|
get supportsAnchorPosition() {
|
||||||
|
return CSS.supports("anchor-name: none");
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
if(!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sheet = new CSSStyleSheet();
|
||||||
|
sheet.replaceSync(HeadingAnchors.css);
|
||||||
|
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
|
||||||
|
|
||||||
|
this.headingStyles = {};
|
||||||
|
this.instanceIndex = globalInstanceIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
if (!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.headings.forEach((heading, index) => {
|
||||||
|
if(!heading.hasAttribute(HeadingAnchors.attributes.exclude)) {
|
||||||
|
let anchor = this.getAnchorElement(heading);
|
||||||
|
let placeholder = this.getPlaceholderElement();
|
||||||
|
|
||||||
|
// Prefers anchor position approach for better accessibility
|
||||||
|
// https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
let anchorName = `--ha_${this.instanceIndex}_${index}`;
|
||||||
|
placeholder.style.setProperty("anchor-name", anchorName);
|
||||||
|
anchor.style.positionAnchor = anchorName;
|
||||||
|
}
|
||||||
|
|
||||||
|
heading.appendChild(placeholder);
|
||||||
|
heading.after(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchorFromPlaceholder(placeholder) {
|
||||||
|
if(!placeholder) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let heading = placeholder.closest("h1,h2,h3,h4,h5,h6");
|
||||||
|
if(!heading.nextElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO next element could be more defensive
|
||||||
|
this.positionAnchor(heading.nextElementSibling);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchor(anchor) {
|
||||||
|
if(!anchor || !anchor.previousElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO previous element could be more defensive
|
||||||
|
let heading = anchor.previousElementSibling;
|
||||||
|
this.setFontProp(heading, anchor);
|
||||||
|
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
// quit early
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
anchor.style.setProperty("--ha_offsetx", `${placeholder.offsetLeft}px`);
|
||||||
|
anchor.style.setProperty("--ha_offsety", `${placeholder.offsetTop}px`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setFontProp(heading, anchor) {
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
let style = getComputedStyle(placeholder);
|
||||||
|
let props = ["font-weight", "font-size", "line-height", "font-family"];
|
||||||
|
let [weight, size, lh, family] = props.map(name => style.getPropertyValue(name));
|
||||||
|
anchor.style.setProperty("font", `${weight} ${size}/${lh} ${family}`);
|
||||||
|
let vars = style.getPropertyValue("font-variation-settings");
|
||||||
|
if(vars) {
|
||||||
|
anchor.style.setProperty("font-variation-settings", vars);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getAccessibleTextPrefix() {
|
||||||
|
// Useful for i18n
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.prefix) || "Jump to section titled";
|
||||||
|
}
|
||||||
|
|
||||||
|
getContent() {
|
||||||
|
if(this.hasAttribute(HeadingAnchors.attributes.content)) {
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.content);
|
||||||
|
}
|
||||||
|
return "#";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Placeholder nests inside of heading
|
||||||
|
getPlaceholderElement() {
|
||||||
|
let ph = document.createElement("span");
|
||||||
|
ph.setAttribute("aria-hidden", true);
|
||||||
|
ph.classList.add(HeadingAnchors.classes.placeholder);
|
||||||
|
let content = this.getContent();
|
||||||
|
if(content) {
|
||||||
|
ph.textContent = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
ph.addEventListener("mouseover", (e) => {
|
||||||
|
let placeholder = e.target.closest(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
this.positionAnchorFromPlaceholder(placeholder);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return ph;
|
||||||
|
}
|
||||||
|
|
||||||
|
getAnchorElement(heading) {
|
||||||
|
let anchor = document.createElement("a");
|
||||||
|
anchor.href = `#${heading.id}`;
|
||||||
|
anchor.classList.add(HeadingAnchors.classes.anchor);
|
||||||
|
|
||||||
|
let content = this.getContent();
|
||||||
|
anchor.innerHTML = `<span class="${HeadingAnchors.classes.srOnly}">${this.getAccessibleTextPrefix()}: ${heading.textContent}</span>${content ? `<span aria-hidden="true">${content}</span>` : ""}`;
|
||||||
|
|
||||||
|
anchor.addEventListener("focus", e => {
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
if(anchor) {
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
anchor.addEventListener("mouseover", (e) => {
|
||||||
|
// when CSS anchor positioning is supported, this is only used to set the font
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
});
|
||||||
|
|
||||||
|
return anchor;
|
||||||
|
}
|
||||||
|
|
||||||
|
get headings() {
|
||||||
|
return this.querySelectorAll(this.selector.split(",").map(entry => `${entry.trim()}[id]`));
|
||||||
|
}
|
||||||
|
|
||||||
|
get selector() {
|
||||||
|
return this.getAttribute("selector") || HeadingAnchors.defaultSelector;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HeadingAnchors.register();
|
||||||
|
|
||||||
|
export { HeadingAnchors }
|
||||||
|
// Efficient iterative Fibonacci
|
||||||
|
function fib(n) {
|
||||||
|
if (n < 2) return n;
|
||||||
|
let a = 0, b = 1;
|
||||||
|
for (let i = 2; i <= n; i++) {
|
||||||
|
[a, b] = [b, a + b];
|
||||||
|
}
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
window.onload = function() {
|
||||||
|
const n = 1; // Example: change this number or get it dynamically
|
||||||
|
const result = fib(n-1);
|
||||||
|
document.getElementById("fib_seq").textContent = n;
|
||||||
|
document.getElementById("fib_num").textContent = result; // Doesn't render properly on mobile; figure out subscript numbers...
|
||||||
|
};
|
||||||
247
dist/KulkScvkSc.js
vendored
Normal file
|
|
@ -0,0 +1,247 @@
|
||||||
|
// Thank you to https://github.com/daviddarnes/heading-anchors
|
||||||
|
// Thank you to https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
|
||||||
|
let globalInstanceIndex = 0;
|
||||||
|
|
||||||
|
class HeadingAnchors extends HTMLElement {
|
||||||
|
static register(tagName = "heading-anchors", registry = window.customElements) {
|
||||||
|
if(registry && !registry.get(tagName)) {
|
||||||
|
registry.define(tagName, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static attributes = {
|
||||||
|
exclude: "data-ha-exclude",
|
||||||
|
prefix: "prefix",
|
||||||
|
content: "content",
|
||||||
|
}
|
||||||
|
|
||||||
|
static classes = {
|
||||||
|
anchor: "ha",
|
||||||
|
placeholder: "ha-placeholder",
|
||||||
|
srOnly: "ha-visualhide",
|
||||||
|
}
|
||||||
|
|
||||||
|
static defaultSelector = "h2,h3,h4,h5,h6";
|
||||||
|
|
||||||
|
static css = `
|
||||||
|
.${HeadingAnchors.classes.srOnly} {
|
||||||
|
clip: rect(0 0 0 0);
|
||||||
|
height: 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: absolute;
|
||||||
|
width: 1px;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: var(--ha_offsetx);
|
||||||
|
top: var(--ha_offsety);
|
||||||
|
text-decoration: none;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
opacity: .3;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor}:is(:focus-within, :hover) {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor},
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 .25em;
|
||||||
|
|
||||||
|
/* Disable selection of visually hidden label */
|
||||||
|
-webkit-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@supports (anchor-name: none) {
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: anchor(left);
|
||||||
|
top: anchor(top);
|
||||||
|
}
|
||||||
|
}`;
|
||||||
|
|
||||||
|
get supports() {
|
||||||
|
return "replaceSync" in CSSStyleSheet.prototype;
|
||||||
|
}
|
||||||
|
|
||||||
|
get supportsAnchorPosition() {
|
||||||
|
return CSS.supports("anchor-name: none");
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
if(!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sheet = new CSSStyleSheet();
|
||||||
|
sheet.replaceSync(HeadingAnchors.css);
|
||||||
|
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
|
||||||
|
|
||||||
|
this.headingStyles = {};
|
||||||
|
this.instanceIndex = globalInstanceIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
if (!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.headings.forEach((heading, index) => {
|
||||||
|
if(!heading.hasAttribute(HeadingAnchors.attributes.exclude)) {
|
||||||
|
let anchor = this.getAnchorElement(heading);
|
||||||
|
let placeholder = this.getPlaceholderElement();
|
||||||
|
|
||||||
|
// Prefers anchor position approach for better accessibility
|
||||||
|
// https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
let anchorName = `--ha_${this.instanceIndex}_${index}`;
|
||||||
|
placeholder.style.setProperty("anchor-name", anchorName);
|
||||||
|
anchor.style.positionAnchor = anchorName;
|
||||||
|
}
|
||||||
|
|
||||||
|
heading.appendChild(placeholder);
|
||||||
|
heading.after(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchorFromPlaceholder(placeholder) {
|
||||||
|
if(!placeholder) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let heading = placeholder.closest("h1,h2,h3,h4,h5,h6");
|
||||||
|
if(!heading.nextElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO next element could be more defensive
|
||||||
|
this.positionAnchor(heading.nextElementSibling);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchor(anchor) {
|
||||||
|
if(!anchor || !anchor.previousElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO previous element could be more defensive
|
||||||
|
let heading = anchor.previousElementSibling;
|
||||||
|
this.setFontProp(heading, anchor);
|
||||||
|
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
// quit early
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
anchor.style.setProperty("--ha_offsetx", `${placeholder.offsetLeft}px`);
|
||||||
|
anchor.style.setProperty("--ha_offsety", `${placeholder.offsetTop}px`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setFontProp(heading, anchor) {
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
let style = getComputedStyle(placeholder);
|
||||||
|
let props = ["font-weight", "font-size", "line-height", "font-family"];
|
||||||
|
let [weight, size, lh, family] = props.map(name => style.getPropertyValue(name));
|
||||||
|
anchor.style.setProperty("font", `${weight} ${size}/${lh} ${family}`);
|
||||||
|
let vars = style.getPropertyValue("font-variation-settings");
|
||||||
|
if(vars) {
|
||||||
|
anchor.style.setProperty("font-variation-settings", vars);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getAccessibleTextPrefix() {
|
||||||
|
// Useful for i18n
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.prefix) || "Jump to section titled";
|
||||||
|
}
|
||||||
|
|
||||||
|
getContent() {
|
||||||
|
if(this.hasAttribute(HeadingAnchors.attributes.content)) {
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.content);
|
||||||
|
}
|
||||||
|
return "#";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Placeholder nests inside of heading
|
||||||
|
getPlaceholderElement() {
|
||||||
|
let ph = document.createElement("span");
|
||||||
|
ph.setAttribute("aria-hidden", true);
|
||||||
|
ph.classList.add(HeadingAnchors.classes.placeholder);
|
||||||
|
let content = this.getContent();
|
||||||
|
if(content) {
|
||||||
|
ph.textContent = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
ph.addEventListener("mouseover", (e) => {
|
||||||
|
let placeholder = e.target.closest(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
this.positionAnchorFromPlaceholder(placeholder);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return ph;
|
||||||
|
}
|
||||||
|
|
||||||
|
getAnchorElement(heading) {
|
||||||
|
let anchor = document.createElement("a");
|
||||||
|
anchor.href = `#${heading.id}`;
|
||||||
|
anchor.classList.add(HeadingAnchors.classes.anchor);
|
||||||
|
|
||||||
|
let content = this.getContent();
|
||||||
|
anchor.innerHTML = `<span class="${HeadingAnchors.classes.srOnly}">${this.getAccessibleTextPrefix()}: ${heading.textContent}</span>${content ? `<span aria-hidden="true">${content}</span>` : ""}`;
|
||||||
|
|
||||||
|
anchor.addEventListener("focus", e => {
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
if(anchor) {
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
anchor.addEventListener("mouseover", (e) => {
|
||||||
|
// when CSS anchor positioning is supported, this is only used to set the font
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
});
|
||||||
|
|
||||||
|
return anchor;
|
||||||
|
}
|
||||||
|
|
||||||
|
get headings() {
|
||||||
|
return this.querySelectorAll(this.selector.split(",").map(entry => `${entry.trim()}[id]`));
|
||||||
|
}
|
||||||
|
|
||||||
|
get selector() {
|
||||||
|
return this.getAttribute("selector") || HeadingAnchors.defaultSelector;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HeadingAnchors.register();
|
||||||
|
|
||||||
|
export { HeadingAnchors }
|
||||||
|
// Efficient iterative Fibonacci
|
||||||
|
function fib(n) {
|
||||||
|
if (n < 2) return n;
|
||||||
|
let a = 0, b = 1;
|
||||||
|
for (let i = 2; i <= n; i++) {
|
||||||
|
[a, b] = [b, a + b];
|
||||||
|
}
|
||||||
|
document.getElementById("fib_seq").textContent = n;
|
||||||
|
document.getElementById("fib_num").textContent = result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
window.onload = function() {
|
||||||
|
const n = 1; // Example: change this number or get it dynamically
|
||||||
|
const result = fib(n);
|
||||||
|
};
|
||||||
246
dist/LMRliuEw15.js
vendored
Normal file
|
|
@ -0,0 +1,246 @@
|
||||||
|
// Thank you to https://github.com/daviddarnes/heading-anchors
|
||||||
|
// Thank you to https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
|
||||||
|
let globalInstanceIndex = 0;
|
||||||
|
|
||||||
|
class HeadingAnchors extends HTMLElement {
|
||||||
|
static register(tagName = "heading-anchors", registry = window.customElements) {
|
||||||
|
if(registry && !registry.get(tagName)) {
|
||||||
|
registry.define(tagName, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static attributes = {
|
||||||
|
exclude: "data-ha-exclude",
|
||||||
|
prefix: "prefix",
|
||||||
|
content: "content",
|
||||||
|
}
|
||||||
|
|
||||||
|
static classes = {
|
||||||
|
anchor: "ha",
|
||||||
|
placeholder: "ha-placeholder",
|
||||||
|
srOnly: "ha-visualhide",
|
||||||
|
}
|
||||||
|
|
||||||
|
static defaultSelector = "h2,h3,h4,h5,h6";
|
||||||
|
|
||||||
|
static css = `
|
||||||
|
.${HeadingAnchors.classes.srOnly} {
|
||||||
|
clip: rect(0 0 0 0);
|
||||||
|
height: 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: absolute;
|
||||||
|
width: 1px;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: var(--ha_offsetx);
|
||||||
|
top: var(--ha_offsety);
|
||||||
|
text-decoration: none;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
opacity: .3;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor}:is(:focus-within, :hover) {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor},
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 .25em;
|
||||||
|
|
||||||
|
/* Disable selection of visually hidden label */
|
||||||
|
-webkit-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@supports (anchor-name: none) {
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: anchor(left);
|
||||||
|
top: anchor(top);
|
||||||
|
}
|
||||||
|
}`;
|
||||||
|
|
||||||
|
get supports() {
|
||||||
|
return "replaceSync" in CSSStyleSheet.prototype;
|
||||||
|
}
|
||||||
|
|
||||||
|
get supportsAnchorPosition() {
|
||||||
|
return CSS.supports("anchor-name: none");
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
if(!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sheet = new CSSStyleSheet();
|
||||||
|
sheet.replaceSync(HeadingAnchors.css);
|
||||||
|
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
|
||||||
|
|
||||||
|
this.headingStyles = {};
|
||||||
|
this.instanceIndex = globalInstanceIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
if (!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.headings.forEach((heading, index) => {
|
||||||
|
if(!heading.hasAttribute(HeadingAnchors.attributes.exclude)) {
|
||||||
|
let anchor = this.getAnchorElement(heading);
|
||||||
|
let placeholder = this.getPlaceholderElement();
|
||||||
|
|
||||||
|
// Prefers anchor position approach for better accessibility
|
||||||
|
// https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
let anchorName = `--ha_${this.instanceIndex}_${index}`;
|
||||||
|
placeholder.style.setProperty("anchor-name", anchorName);
|
||||||
|
anchor.style.positionAnchor = anchorName;
|
||||||
|
}
|
||||||
|
|
||||||
|
heading.appendChild(placeholder);
|
||||||
|
heading.after(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchorFromPlaceholder(placeholder) {
|
||||||
|
if(!placeholder) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let heading = placeholder.closest("h1,h2,h3,h4,h5,h6");
|
||||||
|
if(!heading.nextElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO next element could be more defensive
|
||||||
|
this.positionAnchor(heading.nextElementSibling);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchor(anchor) {
|
||||||
|
if(!anchor || !anchor.previousElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO previous element could be more defensive
|
||||||
|
let heading = anchor.previousElementSibling;
|
||||||
|
this.setFontProp(heading, anchor);
|
||||||
|
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
// quit early
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
anchor.style.setProperty("--ha_offsetx", `${placeholder.offsetLeft}px`);
|
||||||
|
anchor.style.setProperty("--ha_offsety", `${placeholder.offsetTop}px`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setFontProp(heading, anchor) {
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
let style = getComputedStyle(placeholder);
|
||||||
|
let props = ["font-weight", "font-size", "line-height", "font-family"];
|
||||||
|
let [weight, size, lh, family] = props.map(name => style.getPropertyValue(name));
|
||||||
|
anchor.style.setProperty("font", `${weight} ${size}/${lh} ${family}`);
|
||||||
|
let vars = style.getPropertyValue("font-variation-settings");
|
||||||
|
if(vars) {
|
||||||
|
anchor.style.setProperty("font-variation-settings", vars);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getAccessibleTextPrefix() {
|
||||||
|
// Useful for i18n
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.prefix) || "Jump to section titled";
|
||||||
|
}
|
||||||
|
|
||||||
|
getContent() {
|
||||||
|
if(this.hasAttribute(HeadingAnchors.attributes.content)) {
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.content);
|
||||||
|
}
|
||||||
|
return "#";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Placeholder nests inside of heading
|
||||||
|
getPlaceholderElement() {
|
||||||
|
let ph = document.createElement("span");
|
||||||
|
ph.setAttribute("aria-hidden", true);
|
||||||
|
ph.classList.add(HeadingAnchors.classes.placeholder);
|
||||||
|
let content = this.getContent();
|
||||||
|
if(content) {
|
||||||
|
ph.textContent = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
ph.addEventListener("mouseover", (e) => {
|
||||||
|
let placeholder = e.target.closest(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
this.positionAnchorFromPlaceholder(placeholder);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return ph;
|
||||||
|
}
|
||||||
|
|
||||||
|
getAnchorElement(heading) {
|
||||||
|
let anchor = document.createElement("a");
|
||||||
|
anchor.href = `#${heading.id}`;
|
||||||
|
anchor.classList.add(HeadingAnchors.classes.anchor);
|
||||||
|
|
||||||
|
let content = this.getContent();
|
||||||
|
anchor.innerHTML = `<span class="${HeadingAnchors.classes.srOnly}">${this.getAccessibleTextPrefix()}: ${heading.textContent}</span>${content ? `<span aria-hidden="true">${content}</span>` : ""}`;
|
||||||
|
|
||||||
|
anchor.addEventListener("focus", e => {
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
if(anchor) {
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
anchor.addEventListener("mouseover", (e) => {
|
||||||
|
// when CSS anchor positioning is supported, this is only used to set the font
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
});
|
||||||
|
|
||||||
|
return anchor;
|
||||||
|
}
|
||||||
|
|
||||||
|
get headings() {
|
||||||
|
return this.querySelectorAll(this.selector.split(",").map(entry => `${entry.trim()}[id]`));
|
||||||
|
}
|
||||||
|
|
||||||
|
get selector() {
|
||||||
|
return this.getAttribute("selector") || HeadingAnchors.defaultSelector;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HeadingAnchors.register();
|
||||||
|
|
||||||
|
export { HeadingAnchors }
|
||||||
|
// Efficient iterative Fibonacci
|
||||||
|
function fib(n) {
|
||||||
|
if (n < 2) return n;
|
||||||
|
let a = 0, b = 1;
|
||||||
|
for (let i = 2; i <= n; i++) {
|
||||||
|
[a, b] = [b, a + b];
|
||||||
|
}
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.onload = function() {
|
||||||
|
const n = 3; // Example: change this number or get it dynamically
|
||||||
|
const result = fib(n);
|
||||||
|
document.getElementById("fib").textContent = `F(${n}) = ${result}`;
|
||||||
|
};
|
||||||
247
dist/Lj0TJmfSgz.js
vendored
Normal file
|
|
@ -0,0 +1,247 @@
|
||||||
|
// Thank you to https://github.com/daviddarnes/heading-anchors
|
||||||
|
// Thank you to https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
|
||||||
|
let globalInstanceIndex = 0;
|
||||||
|
|
||||||
|
class HeadingAnchors extends HTMLElement {
|
||||||
|
static register(tagName = "heading-anchors", registry = window.customElements) {
|
||||||
|
if(registry && !registry.get(tagName)) {
|
||||||
|
registry.define(tagName, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static attributes = {
|
||||||
|
exclude: "data-ha-exclude",
|
||||||
|
prefix: "prefix",
|
||||||
|
content: "content",
|
||||||
|
}
|
||||||
|
|
||||||
|
static classes = {
|
||||||
|
anchor: "ha",
|
||||||
|
placeholder: "ha-placeholder",
|
||||||
|
srOnly: "ha-visualhide",
|
||||||
|
}
|
||||||
|
|
||||||
|
static defaultSelector = "h2,h3,h4,h5,h6";
|
||||||
|
|
||||||
|
static css = `
|
||||||
|
.${HeadingAnchors.classes.srOnly} {
|
||||||
|
clip: rect(0 0 0 0);
|
||||||
|
height: 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: absolute;
|
||||||
|
width: 1px;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: var(--ha_offsetx);
|
||||||
|
top: var(--ha_offsety);
|
||||||
|
text-decoration: none;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
opacity: .3;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor}:is(:focus-within, :hover) {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor},
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 .25em;
|
||||||
|
|
||||||
|
/* Disable selection of visually hidden label */
|
||||||
|
-webkit-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@supports (anchor-name: none) {
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: anchor(left);
|
||||||
|
top: anchor(top);
|
||||||
|
}
|
||||||
|
}`;
|
||||||
|
|
||||||
|
get supports() {
|
||||||
|
return "replaceSync" in CSSStyleSheet.prototype;
|
||||||
|
}
|
||||||
|
|
||||||
|
get supportsAnchorPosition() {
|
||||||
|
return CSS.supports("anchor-name: none");
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
if(!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sheet = new CSSStyleSheet();
|
||||||
|
sheet.replaceSync(HeadingAnchors.css);
|
||||||
|
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
|
||||||
|
|
||||||
|
this.headingStyles = {};
|
||||||
|
this.instanceIndex = globalInstanceIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
if (!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.headings.forEach((heading, index) => {
|
||||||
|
if(!heading.hasAttribute(HeadingAnchors.attributes.exclude)) {
|
||||||
|
let anchor = this.getAnchorElement(heading);
|
||||||
|
let placeholder = this.getPlaceholderElement();
|
||||||
|
|
||||||
|
// Prefers anchor position approach for better accessibility
|
||||||
|
// https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
let anchorName = `--ha_${this.instanceIndex}_${index}`;
|
||||||
|
placeholder.style.setProperty("anchor-name", anchorName);
|
||||||
|
anchor.style.positionAnchor = anchorName;
|
||||||
|
}
|
||||||
|
|
||||||
|
heading.appendChild(placeholder);
|
||||||
|
heading.after(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchorFromPlaceholder(placeholder) {
|
||||||
|
if(!placeholder) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let heading = placeholder.closest("h1,h2,h3,h4,h5,h6");
|
||||||
|
if(!heading.nextElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO next element could be more defensive
|
||||||
|
this.positionAnchor(heading.nextElementSibling);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchor(anchor) {
|
||||||
|
if(!anchor || !anchor.previousElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO previous element could be more defensive
|
||||||
|
let heading = anchor.previousElementSibling;
|
||||||
|
this.setFontProp(heading, anchor);
|
||||||
|
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
// quit early
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
anchor.style.setProperty("--ha_offsetx", `${placeholder.offsetLeft}px`);
|
||||||
|
anchor.style.setProperty("--ha_offsety", `${placeholder.offsetTop}px`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setFontProp(heading, anchor) {
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
let style = getComputedStyle(placeholder);
|
||||||
|
let props = ["font-weight", "font-size", "line-height", "font-family"];
|
||||||
|
let [weight, size, lh, family] = props.map(name => style.getPropertyValue(name));
|
||||||
|
anchor.style.setProperty("font", `${weight} ${size}/${lh} ${family}`);
|
||||||
|
let vars = style.getPropertyValue("font-variation-settings");
|
||||||
|
if(vars) {
|
||||||
|
anchor.style.setProperty("font-variation-settings", vars);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getAccessibleTextPrefix() {
|
||||||
|
// Useful for i18n
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.prefix) || "Jump to section titled";
|
||||||
|
}
|
||||||
|
|
||||||
|
getContent() {
|
||||||
|
if(this.hasAttribute(HeadingAnchors.attributes.content)) {
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.content);
|
||||||
|
}
|
||||||
|
return "#";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Placeholder nests inside of heading
|
||||||
|
getPlaceholderElement() {
|
||||||
|
let ph = document.createElement("span");
|
||||||
|
ph.setAttribute("aria-hidden", true);
|
||||||
|
ph.classList.add(HeadingAnchors.classes.placeholder);
|
||||||
|
let content = this.getContent();
|
||||||
|
if(content) {
|
||||||
|
ph.textContent = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
ph.addEventListener("mouseover", (e) => {
|
||||||
|
let placeholder = e.target.closest(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
this.positionAnchorFromPlaceholder(placeholder);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return ph;
|
||||||
|
}
|
||||||
|
|
||||||
|
getAnchorElement(heading) {
|
||||||
|
let anchor = document.createElement("a");
|
||||||
|
anchor.href = `#${heading.id}`;
|
||||||
|
anchor.classList.add(HeadingAnchors.classes.anchor);
|
||||||
|
|
||||||
|
let content = this.getContent();
|
||||||
|
anchor.innerHTML = `<span class="${HeadingAnchors.classes.srOnly}">${this.getAccessibleTextPrefix()}: ${heading.textContent}</span>${content ? `<span aria-hidden="true">${content}</span>` : ""}`;
|
||||||
|
|
||||||
|
anchor.addEventListener("focus", e => {
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
if(anchor) {
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
anchor.addEventListener("mouseover", (e) => {
|
||||||
|
// when CSS anchor positioning is supported, this is only used to set the font
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
});
|
||||||
|
|
||||||
|
return anchor;
|
||||||
|
}
|
||||||
|
|
||||||
|
get headings() {
|
||||||
|
return this.querySelectorAll(this.selector.split(",").map(entry => `${entry.trim()}[id]`));
|
||||||
|
}
|
||||||
|
|
||||||
|
get selector() {
|
||||||
|
return this.getAttribute("selector") || HeadingAnchors.defaultSelector;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HeadingAnchors.register();
|
||||||
|
|
||||||
|
export { HeadingAnchors }
|
||||||
|
// Efficient iterative Fibonacci
|
||||||
|
function fib(n) {
|
||||||
|
if (n < 2) return n;
|
||||||
|
let a = 0, b = 1;
|
||||||
|
for (let i = 2; i <= n; i++) {
|
||||||
|
[a, b] = [b, a + b];
|
||||||
|
}
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
window.onload = function() {
|
||||||
|
const n = 5; // Example: change this number or get it dynamically
|
||||||
|
const result = fib(n);
|
||||||
|
document.getElementById("fib").textContent = `F(${n}) = ${result}`;
|
||||||
|
};
|
||||||
247
dist/MwbreJDqDS.js
vendored
Normal file
|
|
@ -0,0 +1,247 @@
|
||||||
|
// Thank you to https://github.com/daviddarnes/heading-anchors
|
||||||
|
// Thank you to https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
|
||||||
|
let globalInstanceIndex = 0;
|
||||||
|
|
||||||
|
class HeadingAnchors extends HTMLElement {
|
||||||
|
static register(tagName = "heading-anchors", registry = window.customElements) {
|
||||||
|
if(registry && !registry.get(tagName)) {
|
||||||
|
registry.define(tagName, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static attributes = {
|
||||||
|
exclude: "data-ha-exclude",
|
||||||
|
prefix: "prefix",
|
||||||
|
content: "content",
|
||||||
|
}
|
||||||
|
|
||||||
|
static classes = {
|
||||||
|
anchor: "ha",
|
||||||
|
placeholder: "ha-placeholder",
|
||||||
|
srOnly: "ha-visualhide",
|
||||||
|
}
|
||||||
|
|
||||||
|
static defaultSelector = "h2,h3,h4,h5,h6";
|
||||||
|
|
||||||
|
static css = `
|
||||||
|
.${HeadingAnchors.classes.srOnly} {
|
||||||
|
clip: rect(0 0 0 0);
|
||||||
|
height: 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: absolute;
|
||||||
|
width: 1px;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: var(--ha_offsetx);
|
||||||
|
top: var(--ha_offsety);
|
||||||
|
text-decoration: none;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
opacity: .3;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor}:is(:focus-within, :hover) {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor},
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 .25em;
|
||||||
|
|
||||||
|
/* Disable selection of visually hidden label */
|
||||||
|
-webkit-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@supports (anchor-name: none) {
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: anchor(left);
|
||||||
|
top: anchor(top);
|
||||||
|
}
|
||||||
|
}`;
|
||||||
|
|
||||||
|
get supports() {
|
||||||
|
return "replaceSync" in CSSStyleSheet.prototype;
|
||||||
|
}
|
||||||
|
|
||||||
|
get supportsAnchorPosition() {
|
||||||
|
return CSS.supports("anchor-name: none");
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
if(!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sheet = new CSSStyleSheet();
|
||||||
|
sheet.replaceSync(HeadingAnchors.css);
|
||||||
|
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
|
||||||
|
|
||||||
|
this.headingStyles = {};
|
||||||
|
this.instanceIndex = globalInstanceIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
if (!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.headings.forEach((heading, index) => {
|
||||||
|
if(!heading.hasAttribute(HeadingAnchors.attributes.exclude)) {
|
||||||
|
let anchor = this.getAnchorElement(heading);
|
||||||
|
let placeholder = this.getPlaceholderElement();
|
||||||
|
|
||||||
|
// Prefers anchor position approach for better accessibility
|
||||||
|
// https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
let anchorName = `--ha_${this.instanceIndex}_${index}`;
|
||||||
|
placeholder.style.setProperty("anchor-name", anchorName);
|
||||||
|
anchor.style.positionAnchor = anchorName;
|
||||||
|
}
|
||||||
|
|
||||||
|
heading.appendChild(placeholder);
|
||||||
|
heading.after(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchorFromPlaceholder(placeholder) {
|
||||||
|
if(!placeholder) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let heading = placeholder.closest("h1,h2,h3,h4,h5,h6");
|
||||||
|
if(!heading.nextElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO next element could be more defensive
|
||||||
|
this.positionAnchor(heading.nextElementSibling);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchor(anchor) {
|
||||||
|
if(!anchor || !anchor.previousElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO previous element could be more defensive
|
||||||
|
let heading = anchor.previousElementSibling;
|
||||||
|
this.setFontProp(heading, anchor);
|
||||||
|
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
// quit early
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
anchor.style.setProperty("--ha_offsetx", `${placeholder.offsetLeft}px`);
|
||||||
|
anchor.style.setProperty("--ha_offsety", `${placeholder.offsetTop}px`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setFontProp(heading, anchor) {
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
let style = getComputedStyle(placeholder);
|
||||||
|
let props = ["font-weight", "font-size", "line-height", "font-family"];
|
||||||
|
let [weight, size, lh, family] = props.map(name => style.getPropertyValue(name));
|
||||||
|
anchor.style.setProperty("font", `${weight} ${size}/${lh} ${family}`);
|
||||||
|
let vars = style.getPropertyValue("font-variation-settings");
|
||||||
|
if(vars) {
|
||||||
|
anchor.style.setProperty("font-variation-settings", vars);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getAccessibleTextPrefix() {
|
||||||
|
// Useful for i18n
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.prefix) || "Jump to section titled";
|
||||||
|
}
|
||||||
|
|
||||||
|
getContent() {
|
||||||
|
if(this.hasAttribute(HeadingAnchors.attributes.content)) {
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.content);
|
||||||
|
}
|
||||||
|
return "#";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Placeholder nests inside of heading
|
||||||
|
getPlaceholderElement() {
|
||||||
|
let ph = document.createElement("span");
|
||||||
|
ph.setAttribute("aria-hidden", true);
|
||||||
|
ph.classList.add(HeadingAnchors.classes.placeholder);
|
||||||
|
let content = this.getContent();
|
||||||
|
if(content) {
|
||||||
|
ph.textContent = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
ph.addEventListener("mouseover", (e) => {
|
||||||
|
let placeholder = e.target.closest(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
this.positionAnchorFromPlaceholder(placeholder);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return ph;
|
||||||
|
}
|
||||||
|
|
||||||
|
getAnchorElement(heading) {
|
||||||
|
let anchor = document.createElement("a");
|
||||||
|
anchor.href = `#${heading.id}`;
|
||||||
|
anchor.classList.add(HeadingAnchors.classes.anchor);
|
||||||
|
|
||||||
|
let content = this.getContent();
|
||||||
|
anchor.innerHTML = `<span class="${HeadingAnchors.classes.srOnly}">${this.getAccessibleTextPrefix()}: ${heading.textContent}</span>${content ? `<span aria-hidden="true">${content}</span>` : ""}`;
|
||||||
|
|
||||||
|
anchor.addEventListener("focus", e => {
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
if(anchor) {
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
anchor.addEventListener("mouseover", (e) => {
|
||||||
|
// when CSS anchor positioning is supported, this is only used to set the font
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
});
|
||||||
|
|
||||||
|
return anchor;
|
||||||
|
}
|
||||||
|
|
||||||
|
get headings() {
|
||||||
|
return this.querySelectorAll(this.selector.split(",").map(entry => `${entry.trim()}[id]`));
|
||||||
|
}
|
||||||
|
|
||||||
|
get selector() {
|
||||||
|
return this.getAttribute("selector") || HeadingAnchors.defaultSelector;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HeadingAnchors.register();
|
||||||
|
|
||||||
|
export { HeadingAnchors }
|
||||||
|
// Efficient iterative Fibonacci
|
||||||
|
function fib(n) {
|
||||||
|
if (n < 2) return n;
|
||||||
|
let a = 0, b = 1;
|
||||||
|
for (let i = 2; i <= n; i++) {
|
||||||
|
[a, b] = [b, a + b];
|
||||||
|
}
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run on page load
|
||||||
|
window.onload = function() {
|
||||||
|
const n = 10; // Example: change this number or get it dynamically
|
||||||
|
const result = fib(n);
|
||||||
|
document.getElementById("fib").textContent = `F(${n}) = ${result}`;
|
||||||
|
};
|
||||||
248
dist/N1XsrSkSKU.js
vendored
Normal file
|
|
@ -0,0 +1,248 @@
|
||||||
|
// Thank you to https://github.com/daviddarnes/heading-anchors
|
||||||
|
// Thank you to https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
|
||||||
|
let globalInstanceIndex = 0;
|
||||||
|
|
||||||
|
class HeadingAnchors extends HTMLElement {
|
||||||
|
static register(tagName = "heading-anchors", registry = window.customElements) {
|
||||||
|
if(registry && !registry.get(tagName)) {
|
||||||
|
registry.define(tagName, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static attributes = {
|
||||||
|
exclude: "data-ha-exclude",
|
||||||
|
prefix: "prefix",
|
||||||
|
content: "content",
|
||||||
|
}
|
||||||
|
|
||||||
|
static classes = {
|
||||||
|
anchor: "ha",
|
||||||
|
placeholder: "ha-placeholder",
|
||||||
|
srOnly: "ha-visualhide",
|
||||||
|
}
|
||||||
|
|
||||||
|
static defaultSelector = "h2,h3,h4,h5,h6";
|
||||||
|
|
||||||
|
static css = `
|
||||||
|
.${HeadingAnchors.classes.srOnly} {
|
||||||
|
clip: rect(0 0 0 0);
|
||||||
|
height: 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: absolute;
|
||||||
|
width: 1px;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: var(--ha_offsetx);
|
||||||
|
top: var(--ha_offsety);
|
||||||
|
text-decoration: none;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
opacity: .3;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor}:is(:focus-within, :hover) {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor},
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 .25em;
|
||||||
|
|
||||||
|
/* Disable selection of visually hidden label */
|
||||||
|
-webkit-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@supports (anchor-name: none) {
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: anchor(left);
|
||||||
|
top: anchor(top);
|
||||||
|
}
|
||||||
|
}`;
|
||||||
|
|
||||||
|
get supports() {
|
||||||
|
return "replaceSync" in CSSStyleSheet.prototype;
|
||||||
|
}
|
||||||
|
|
||||||
|
get supportsAnchorPosition() {
|
||||||
|
return CSS.supports("anchor-name: none");
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
if(!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sheet = new CSSStyleSheet();
|
||||||
|
sheet.replaceSync(HeadingAnchors.css);
|
||||||
|
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
|
||||||
|
|
||||||
|
this.headingStyles = {};
|
||||||
|
this.instanceIndex = globalInstanceIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
if (!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.headings.forEach((heading, index) => {
|
||||||
|
if(!heading.hasAttribute(HeadingAnchors.attributes.exclude)) {
|
||||||
|
let anchor = this.getAnchorElement(heading);
|
||||||
|
let placeholder = this.getPlaceholderElement();
|
||||||
|
|
||||||
|
// Prefers anchor position approach for better accessibility
|
||||||
|
// https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
let anchorName = `--ha_${this.instanceIndex}_${index}`;
|
||||||
|
placeholder.style.setProperty("anchor-name", anchorName);
|
||||||
|
anchor.style.positionAnchor = anchorName;
|
||||||
|
}
|
||||||
|
|
||||||
|
heading.appendChild(placeholder);
|
||||||
|
heading.after(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchorFromPlaceholder(placeholder) {
|
||||||
|
if(!placeholder) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let heading = placeholder.closest("h1,h2,h3,h4,h5,h6");
|
||||||
|
if(!heading.nextElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO next element could be more defensive
|
||||||
|
this.positionAnchor(heading.nextElementSibling);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchor(anchor) {
|
||||||
|
if(!anchor || !anchor.previousElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO previous element could be more defensive
|
||||||
|
let heading = anchor.previousElementSibling;
|
||||||
|
this.setFontProp(heading, anchor);
|
||||||
|
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
// quit early
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
anchor.style.setProperty("--ha_offsetx", `${placeholder.offsetLeft}px`);
|
||||||
|
anchor.style.setProperty("--ha_offsety", `${placeholder.offsetTop}px`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setFontProp(heading, anchor) {
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
let style = getComputedStyle(placeholder);
|
||||||
|
let props = ["font-weight", "font-size", "line-height", "font-family"];
|
||||||
|
let [weight, size, lh, family] = props.map(name => style.getPropertyValue(name));
|
||||||
|
anchor.style.setProperty("font", `${weight} ${size}/${lh} ${family}`);
|
||||||
|
let vars = style.getPropertyValue("font-variation-settings");
|
||||||
|
if(vars) {
|
||||||
|
anchor.style.setProperty("font-variation-settings", vars);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getAccessibleTextPrefix() {
|
||||||
|
// Useful for i18n
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.prefix) || "Jump to section titled";
|
||||||
|
}
|
||||||
|
|
||||||
|
getContent() {
|
||||||
|
if(this.hasAttribute(HeadingAnchors.attributes.content)) {
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.content);
|
||||||
|
}
|
||||||
|
return "#";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Placeholder nests inside of heading
|
||||||
|
getPlaceholderElement() {
|
||||||
|
let ph = document.createElement("span");
|
||||||
|
ph.setAttribute("aria-hidden", true);
|
||||||
|
ph.classList.add(HeadingAnchors.classes.placeholder);
|
||||||
|
let content = this.getContent();
|
||||||
|
if(content) {
|
||||||
|
ph.textContent = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
ph.addEventListener("mouseover", (e) => {
|
||||||
|
let placeholder = e.target.closest(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
this.positionAnchorFromPlaceholder(placeholder);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return ph;
|
||||||
|
}
|
||||||
|
|
||||||
|
getAnchorElement(heading) {
|
||||||
|
let anchor = document.createElement("a");
|
||||||
|
anchor.href = `#${heading.id}`;
|
||||||
|
anchor.classList.add(HeadingAnchors.classes.anchor);
|
||||||
|
|
||||||
|
let content = this.getContent();
|
||||||
|
anchor.innerHTML = `<span class="${HeadingAnchors.classes.srOnly}">${this.getAccessibleTextPrefix()}: ${heading.textContent}</span>${content ? `<span aria-hidden="true">${content}</span>` : ""}`;
|
||||||
|
|
||||||
|
anchor.addEventListener("focus", e => {
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
if(anchor) {
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
anchor.addEventListener("mouseover", (e) => {
|
||||||
|
// when CSS anchor positioning is supported, this is only used to set the font
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
});
|
||||||
|
|
||||||
|
return anchor;
|
||||||
|
}
|
||||||
|
|
||||||
|
get headings() {
|
||||||
|
return this.querySelectorAll(this.selector.split(",").map(entry => `${entry.trim()}[id]`));
|
||||||
|
}
|
||||||
|
|
||||||
|
get selector() {
|
||||||
|
return this.getAttribute("selector") || HeadingAnchors.defaultSelector;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HeadingAnchors.register();
|
||||||
|
|
||||||
|
export { HeadingAnchors }
|
||||||
|
// Efficient iterative Fibonacci
|
||||||
|
function fib(n) {
|
||||||
|
if (n < 2) return n;
|
||||||
|
let a = 0, b = 1;
|
||||||
|
for (let i = 2; i <= n; i++) {
|
||||||
|
[a, b] = [b, a + b];
|
||||||
|
}
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
window.onload = function() {
|
||||||
|
const n = 8; // Example: change this number or get it dynamically
|
||||||
|
const result = fib(n);
|
||||||
|
document.getElementById("fib_seq").textContent = n;
|
||||||
|
document.getElementById("fib_num").textContent = result; // Doesn't render properly on mobile; figure out subscript numbers...
|
||||||
|
};
|
||||||
246
dist/QJCUSo7_oL.js
vendored
Normal file
|
|
@ -0,0 +1,246 @@
|
||||||
|
// Thank you to https://github.com/daviddarnes/heading-anchors
|
||||||
|
// Thank you to https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
|
||||||
|
let globalInstanceIndex = 0;
|
||||||
|
|
||||||
|
class HeadingAnchors extends HTMLElement {
|
||||||
|
static register(tagName = "heading-anchors", registry = window.customElements) {
|
||||||
|
if(registry && !registry.get(tagName)) {
|
||||||
|
registry.define(tagName, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static attributes = {
|
||||||
|
exclude: "data-ha-exclude",
|
||||||
|
prefix: "prefix",
|
||||||
|
content: "content",
|
||||||
|
}
|
||||||
|
|
||||||
|
static classes = {
|
||||||
|
anchor: "ha",
|
||||||
|
placeholder: "ha-placeholder",
|
||||||
|
srOnly: "ha-visualhide",
|
||||||
|
}
|
||||||
|
|
||||||
|
static defaultSelector = "h2,h3,h4,h5,h6";
|
||||||
|
|
||||||
|
static css = `
|
||||||
|
.${HeadingAnchors.classes.srOnly} {
|
||||||
|
clip: rect(0 0 0 0);
|
||||||
|
height: 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: absolute;
|
||||||
|
width: 1px;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: var(--ha_offsetx);
|
||||||
|
top: var(--ha_offsety);
|
||||||
|
text-decoration: none;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
opacity: .3;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor}:is(:focus-within, :hover) {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor},
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 .25em;
|
||||||
|
|
||||||
|
/* Disable selection of visually hidden label */
|
||||||
|
-webkit-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@supports (anchor-name: none) {
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: anchor(left);
|
||||||
|
top: anchor(top);
|
||||||
|
}
|
||||||
|
}`;
|
||||||
|
|
||||||
|
get supports() {
|
||||||
|
return "replaceSync" in CSSStyleSheet.prototype;
|
||||||
|
}
|
||||||
|
|
||||||
|
get supportsAnchorPosition() {
|
||||||
|
return CSS.supports("anchor-name: none");
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
if(!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sheet = new CSSStyleSheet();
|
||||||
|
sheet.replaceSync(HeadingAnchors.css);
|
||||||
|
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
|
||||||
|
|
||||||
|
this.headingStyles = {};
|
||||||
|
this.instanceIndex = globalInstanceIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
if (!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.headings.forEach((heading, index) => {
|
||||||
|
if(!heading.hasAttribute(HeadingAnchors.attributes.exclude)) {
|
||||||
|
let anchor = this.getAnchorElement(heading);
|
||||||
|
let placeholder = this.getPlaceholderElement();
|
||||||
|
|
||||||
|
// Prefers anchor position approach for better accessibility
|
||||||
|
// https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
let anchorName = `--ha_${this.instanceIndex}_${index}`;
|
||||||
|
placeholder.style.setProperty("anchor-name", anchorName);
|
||||||
|
anchor.style.positionAnchor = anchorName;
|
||||||
|
}
|
||||||
|
|
||||||
|
heading.appendChild(placeholder);
|
||||||
|
heading.after(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchorFromPlaceholder(placeholder) {
|
||||||
|
if(!placeholder) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let heading = placeholder.closest("h1,h2,h3,h4,h5,h6");
|
||||||
|
if(!heading.nextElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO next element could be more defensive
|
||||||
|
this.positionAnchor(heading.nextElementSibling);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchor(anchor) {
|
||||||
|
if(!anchor || !anchor.previousElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO previous element could be more defensive
|
||||||
|
let heading = anchor.previousElementSibling;
|
||||||
|
this.setFontProp(heading, anchor);
|
||||||
|
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
// quit early
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
anchor.style.setProperty("--ha_offsetx", `${placeholder.offsetLeft}px`);
|
||||||
|
anchor.style.setProperty("--ha_offsety", `${placeholder.offsetTop}px`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setFontProp(heading, anchor) {
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
let style = getComputedStyle(placeholder);
|
||||||
|
let props = ["font-weight", "font-size", "line-height", "font-family"];
|
||||||
|
let [weight, size, lh, family] = props.map(name => style.getPropertyValue(name));
|
||||||
|
anchor.style.setProperty("font", `${weight} ${size}/${lh} ${family}`);
|
||||||
|
let vars = style.getPropertyValue("font-variation-settings");
|
||||||
|
if(vars) {
|
||||||
|
anchor.style.setProperty("font-variation-settings", vars);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getAccessibleTextPrefix() {
|
||||||
|
// Useful for i18n
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.prefix) || "Jump to section titled";
|
||||||
|
}
|
||||||
|
|
||||||
|
getContent() {
|
||||||
|
if(this.hasAttribute(HeadingAnchors.attributes.content)) {
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.content);
|
||||||
|
}
|
||||||
|
return "#";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Placeholder nests inside of heading
|
||||||
|
getPlaceholderElement() {
|
||||||
|
let ph = document.createElement("span");
|
||||||
|
ph.setAttribute("aria-hidden", true);
|
||||||
|
ph.classList.add(HeadingAnchors.classes.placeholder);
|
||||||
|
let content = this.getContent();
|
||||||
|
if(content) {
|
||||||
|
ph.textContent = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
ph.addEventListener("mouseover", (e) => {
|
||||||
|
let placeholder = e.target.closest(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
this.positionAnchorFromPlaceholder(placeholder);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return ph;
|
||||||
|
}
|
||||||
|
|
||||||
|
getAnchorElement(heading) {
|
||||||
|
let anchor = document.createElement("a");
|
||||||
|
anchor.href = `#${heading.id}`;
|
||||||
|
anchor.classList.add(HeadingAnchors.classes.anchor);
|
||||||
|
|
||||||
|
let content = this.getContent();
|
||||||
|
anchor.innerHTML = `<span class="${HeadingAnchors.classes.srOnly}">${this.getAccessibleTextPrefix()}: ${heading.textContent}</span>${content ? `<span aria-hidden="true">${content}</span>` : ""}`;
|
||||||
|
|
||||||
|
anchor.addEventListener("focus", e => {
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
if(anchor) {
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
anchor.addEventListener("mouseover", (e) => {
|
||||||
|
// when CSS anchor positioning is supported, this is only used to set the font
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
});
|
||||||
|
|
||||||
|
return anchor;
|
||||||
|
}
|
||||||
|
|
||||||
|
get headings() {
|
||||||
|
return this.querySelectorAll(this.selector.split(",").map(entry => `${entry.trim()}[id]`));
|
||||||
|
}
|
||||||
|
|
||||||
|
get selector() {
|
||||||
|
return this.getAttribute("selector") || HeadingAnchors.defaultSelector;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HeadingAnchors.register();
|
||||||
|
|
||||||
|
export { HeadingAnchors }
|
||||||
|
// Efficient iterative Fibonacci
|
||||||
|
function fib(n) {
|
||||||
|
if (n < 2) return n;
|
||||||
|
let a = 0, b = 1;
|
||||||
|
for (let i = 2; i <= n; i++) {
|
||||||
|
[a, b] = [b, a + b];
|
||||||
|
}
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.onload = function() {
|
||||||
|
const n = 1; // Example: change this number or get it dynamically
|
||||||
|
const result = fib(n);
|
||||||
|
document.getElementById("fib").textContent = `F(${n}) = ${result}`; // Doesn't render properly on mobile; figure out subscript numbers...
|
||||||
|
};
|
||||||
248
dist/UWIeyGsVja.js
vendored
Normal file
|
|
@ -0,0 +1,248 @@
|
||||||
|
// Thank you to https://github.com/daviddarnes/heading-anchors
|
||||||
|
// Thank you to https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
|
||||||
|
let globalInstanceIndex = 0;
|
||||||
|
|
||||||
|
class HeadingAnchors extends HTMLElement {
|
||||||
|
static register(tagName = "heading-anchors", registry = window.customElements) {
|
||||||
|
if(registry && !registry.get(tagName)) {
|
||||||
|
registry.define(tagName, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static attributes = {
|
||||||
|
exclude: "data-ha-exclude",
|
||||||
|
prefix: "prefix",
|
||||||
|
content: "content",
|
||||||
|
}
|
||||||
|
|
||||||
|
static classes = {
|
||||||
|
anchor: "ha",
|
||||||
|
placeholder: "ha-placeholder",
|
||||||
|
srOnly: "ha-visualhide",
|
||||||
|
}
|
||||||
|
|
||||||
|
static defaultSelector = "h2,h3,h4,h5,h6";
|
||||||
|
|
||||||
|
static css = `
|
||||||
|
.${HeadingAnchors.classes.srOnly} {
|
||||||
|
clip: rect(0 0 0 0);
|
||||||
|
height: 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: absolute;
|
||||||
|
width: 1px;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: var(--ha_offsetx);
|
||||||
|
top: var(--ha_offsety);
|
||||||
|
text-decoration: none;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
opacity: .3;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor}:is(:focus-within, :hover) {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor},
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 .25em;
|
||||||
|
|
||||||
|
/* Disable selection of visually hidden label */
|
||||||
|
-webkit-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@supports (anchor-name: none) {
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: anchor(left);
|
||||||
|
top: anchor(top);
|
||||||
|
}
|
||||||
|
}`;
|
||||||
|
|
||||||
|
get supports() {
|
||||||
|
return "replaceSync" in CSSStyleSheet.prototype;
|
||||||
|
}
|
||||||
|
|
||||||
|
get supportsAnchorPosition() {
|
||||||
|
return CSS.supports("anchor-name: none");
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
if(!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sheet = new CSSStyleSheet();
|
||||||
|
sheet.replaceSync(HeadingAnchors.css);
|
||||||
|
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
|
||||||
|
|
||||||
|
this.headingStyles = {};
|
||||||
|
this.instanceIndex = globalInstanceIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
if (!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.headings.forEach((heading, index) => {
|
||||||
|
if(!heading.hasAttribute(HeadingAnchors.attributes.exclude)) {
|
||||||
|
let anchor = this.getAnchorElement(heading);
|
||||||
|
let placeholder = this.getPlaceholderElement();
|
||||||
|
|
||||||
|
// Prefers anchor position approach for better accessibility
|
||||||
|
// https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
let anchorName = `--ha_${this.instanceIndex}_${index}`;
|
||||||
|
placeholder.style.setProperty("anchor-name", anchorName);
|
||||||
|
anchor.style.positionAnchor = anchorName;
|
||||||
|
}
|
||||||
|
|
||||||
|
heading.appendChild(placeholder);
|
||||||
|
heading.after(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchorFromPlaceholder(placeholder) {
|
||||||
|
if(!placeholder) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let heading = placeholder.closest("h1,h2,h3,h4,h5,h6");
|
||||||
|
if(!heading.nextElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO next element could be more defensive
|
||||||
|
this.positionAnchor(heading.nextElementSibling);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchor(anchor) {
|
||||||
|
if(!anchor || !anchor.previousElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO previous element could be more defensive
|
||||||
|
let heading = anchor.previousElementSibling;
|
||||||
|
this.setFontProp(heading, anchor);
|
||||||
|
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
// quit early
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
anchor.style.setProperty("--ha_offsetx", `${placeholder.offsetLeft}px`);
|
||||||
|
anchor.style.setProperty("--ha_offsety", `${placeholder.offsetTop}px`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setFontProp(heading, anchor) {
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
let style = getComputedStyle(placeholder);
|
||||||
|
let props = ["font-weight", "font-size", "line-height", "font-family"];
|
||||||
|
let [weight, size, lh, family] = props.map(name => style.getPropertyValue(name));
|
||||||
|
anchor.style.setProperty("font", `${weight} ${size}/${lh} ${family}`);
|
||||||
|
let vars = style.getPropertyValue("font-variation-settings");
|
||||||
|
if(vars) {
|
||||||
|
anchor.style.setProperty("font-variation-settings", vars);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getAccessibleTextPrefix() {
|
||||||
|
// Useful for i18n
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.prefix) || "Jump to section titled";
|
||||||
|
}
|
||||||
|
|
||||||
|
getContent() {
|
||||||
|
if(this.hasAttribute(HeadingAnchors.attributes.content)) {
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.content);
|
||||||
|
}
|
||||||
|
return "#";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Placeholder nests inside of heading
|
||||||
|
getPlaceholderElement() {
|
||||||
|
let ph = document.createElement("span");
|
||||||
|
ph.setAttribute("aria-hidden", true);
|
||||||
|
ph.classList.add(HeadingAnchors.classes.placeholder);
|
||||||
|
let content = this.getContent();
|
||||||
|
if(content) {
|
||||||
|
ph.textContent = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
ph.addEventListener("mouseover", (e) => {
|
||||||
|
let placeholder = e.target.closest(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
this.positionAnchorFromPlaceholder(placeholder);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return ph;
|
||||||
|
}
|
||||||
|
|
||||||
|
getAnchorElement(heading) {
|
||||||
|
let anchor = document.createElement("a");
|
||||||
|
anchor.href = `#${heading.id}`;
|
||||||
|
anchor.classList.add(HeadingAnchors.classes.anchor);
|
||||||
|
|
||||||
|
let content = this.getContent();
|
||||||
|
anchor.innerHTML = `<span class="${HeadingAnchors.classes.srOnly}">${this.getAccessibleTextPrefix()}: ${heading.textContent}</span>${content ? `<span aria-hidden="true">${content}</span>` : ""}`;
|
||||||
|
|
||||||
|
anchor.addEventListener("focus", e => {
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
if(anchor) {
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
anchor.addEventListener("mouseover", (e) => {
|
||||||
|
// when CSS anchor positioning is supported, this is only used to set the font
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
});
|
||||||
|
|
||||||
|
return anchor;
|
||||||
|
}
|
||||||
|
|
||||||
|
get headings() {
|
||||||
|
return this.querySelectorAll(this.selector.split(",").map(entry => `${entry.trim()}[id]`));
|
||||||
|
}
|
||||||
|
|
||||||
|
get selector() {
|
||||||
|
return this.getAttribute("selector") || HeadingAnchors.defaultSelector;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HeadingAnchors.register();
|
||||||
|
|
||||||
|
export { HeadingAnchors }
|
||||||
|
// Efficient iterative Fibonacci
|
||||||
|
function fib(n) {
|
||||||
|
if (n < 2) return n;
|
||||||
|
let a = 0, b = 1;
|
||||||
|
for (let i = 2; i <= n; i++) {
|
||||||
|
[a, b] = [b, a + b];
|
||||||
|
}
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
window.onload = function() {
|
||||||
|
const n = 1; // Example: change this number or get it dynamically
|
||||||
|
const result = fib(n);
|
||||||
|
document.getElementById("fib_seq").textContent = n;
|
||||||
|
document.getElementById("fib_num").textContent = result; // Doesn't render properly on mobile; figure out subscript numbers...
|
||||||
|
};
|
||||||
246
dist/Vqt3TRrqGx.js
vendored
Normal file
|
|
@ -0,0 +1,246 @@
|
||||||
|
// Thank you to https://github.com/daviddarnes/heading-anchors
|
||||||
|
// Thank you to https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
|
||||||
|
let globalInstanceIndex = 0;
|
||||||
|
|
||||||
|
class HeadingAnchors extends HTMLElement {
|
||||||
|
static register(tagName = "heading-anchors", registry = window.customElements) {
|
||||||
|
if(registry && !registry.get(tagName)) {
|
||||||
|
registry.define(tagName, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static attributes = {
|
||||||
|
exclude: "data-ha-exclude",
|
||||||
|
prefix: "prefix",
|
||||||
|
content: "content",
|
||||||
|
}
|
||||||
|
|
||||||
|
static classes = {
|
||||||
|
anchor: "ha",
|
||||||
|
placeholder: "ha-placeholder",
|
||||||
|
srOnly: "ha-visualhide",
|
||||||
|
}
|
||||||
|
|
||||||
|
static defaultSelector = "h2,h3,h4,h5,h6";
|
||||||
|
|
||||||
|
static css = `
|
||||||
|
.${HeadingAnchors.classes.srOnly} {
|
||||||
|
clip: rect(0 0 0 0);
|
||||||
|
height: 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: absolute;
|
||||||
|
width: 1px;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: var(--ha_offsetx);
|
||||||
|
top: var(--ha_offsety);
|
||||||
|
text-decoration: none;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
opacity: .3;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor}:is(:focus-within, :hover) {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor},
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 .25em;
|
||||||
|
|
||||||
|
/* Disable selection of visually hidden label */
|
||||||
|
-webkit-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@supports (anchor-name: none) {
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: anchor(left);
|
||||||
|
top: anchor(top);
|
||||||
|
}
|
||||||
|
}`;
|
||||||
|
|
||||||
|
get supports() {
|
||||||
|
return "replaceSync" in CSSStyleSheet.prototype;
|
||||||
|
}
|
||||||
|
|
||||||
|
get supportsAnchorPosition() {
|
||||||
|
return CSS.supports("anchor-name: none");
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
if(!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sheet = new CSSStyleSheet();
|
||||||
|
sheet.replaceSync(HeadingAnchors.css);
|
||||||
|
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
|
||||||
|
|
||||||
|
this.headingStyles = {};
|
||||||
|
this.instanceIndex = globalInstanceIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
if (!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.headings.forEach((heading, index) => {
|
||||||
|
if(!heading.hasAttribute(HeadingAnchors.attributes.exclude)) {
|
||||||
|
let anchor = this.getAnchorElement(heading);
|
||||||
|
let placeholder = this.getPlaceholderElement();
|
||||||
|
|
||||||
|
// Prefers anchor position approach for better accessibility
|
||||||
|
// https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
let anchorName = `--ha_${this.instanceIndex}_${index}`;
|
||||||
|
placeholder.style.setProperty("anchor-name", anchorName);
|
||||||
|
anchor.style.positionAnchor = anchorName;
|
||||||
|
}
|
||||||
|
|
||||||
|
heading.appendChild(placeholder);
|
||||||
|
heading.after(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchorFromPlaceholder(placeholder) {
|
||||||
|
if(!placeholder) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let heading = placeholder.closest("h1,h2,h3,h4,h5,h6");
|
||||||
|
if(!heading.nextElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO next element could be more defensive
|
||||||
|
this.positionAnchor(heading.nextElementSibling);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchor(anchor) {
|
||||||
|
if(!anchor || !anchor.previousElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO previous element could be more defensive
|
||||||
|
let heading = anchor.previousElementSibling;
|
||||||
|
this.setFontProp(heading, anchor);
|
||||||
|
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
// quit early
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
anchor.style.setProperty("--ha_offsetx", `${placeholder.offsetLeft}px`);
|
||||||
|
anchor.style.setProperty("--ha_offsety", `${placeholder.offsetTop}px`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setFontProp(heading, anchor) {
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
let style = getComputedStyle(placeholder);
|
||||||
|
let props = ["font-weight", "font-size", "line-height", "font-family"];
|
||||||
|
let [weight, size, lh, family] = props.map(name => style.getPropertyValue(name));
|
||||||
|
anchor.style.setProperty("font", `${weight} ${size}/${lh} ${family}`);
|
||||||
|
let vars = style.getPropertyValue("font-variation-settings");
|
||||||
|
if(vars) {
|
||||||
|
anchor.style.setProperty("font-variation-settings", vars);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getAccessibleTextPrefix() {
|
||||||
|
// Useful for i18n
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.prefix) || "Jump to section titled";
|
||||||
|
}
|
||||||
|
|
||||||
|
getContent() {
|
||||||
|
if(this.hasAttribute(HeadingAnchors.attributes.content)) {
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.content);
|
||||||
|
}
|
||||||
|
return "#";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Placeholder nests inside of heading
|
||||||
|
getPlaceholderElement() {
|
||||||
|
let ph = document.createElement("span");
|
||||||
|
ph.setAttribute("aria-hidden", true);
|
||||||
|
ph.classList.add(HeadingAnchors.classes.placeholder);
|
||||||
|
let content = this.getContent();
|
||||||
|
if(content) {
|
||||||
|
ph.textContent = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
ph.addEventListener("mouseover", (e) => {
|
||||||
|
let placeholder = e.target.closest(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
this.positionAnchorFromPlaceholder(placeholder);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return ph;
|
||||||
|
}
|
||||||
|
|
||||||
|
getAnchorElement(heading) {
|
||||||
|
let anchor = document.createElement("a");
|
||||||
|
anchor.href = `#${heading.id}`;
|
||||||
|
anchor.classList.add(HeadingAnchors.classes.anchor);
|
||||||
|
|
||||||
|
let content = this.getContent();
|
||||||
|
anchor.innerHTML = `<span class="${HeadingAnchors.classes.srOnly}">${this.getAccessibleTextPrefix()}: ${heading.textContent}</span>${content ? `<span aria-hidden="true">${content}</span>` : ""}`;
|
||||||
|
|
||||||
|
anchor.addEventListener("focus", e => {
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
if(anchor) {
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
anchor.addEventListener("mouseover", (e) => {
|
||||||
|
// when CSS anchor positioning is supported, this is only used to set the font
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
});
|
||||||
|
|
||||||
|
return anchor;
|
||||||
|
}
|
||||||
|
|
||||||
|
get headings() {
|
||||||
|
return this.querySelectorAll(this.selector.split(",").map(entry => `${entry.trim()}[id]`));
|
||||||
|
}
|
||||||
|
|
||||||
|
get selector() {
|
||||||
|
return this.getAttribute("selector") || HeadingAnchors.defaultSelector;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HeadingAnchors.register();
|
||||||
|
|
||||||
|
export { HeadingAnchors }
|
||||||
|
// Efficient iterative Fibonacci
|
||||||
|
function fib(n) {
|
||||||
|
if (n < 2) return n;
|
||||||
|
let a = 0, b = 1;
|
||||||
|
for (let i = 2; i <= n; i++) {
|
||||||
|
[a, b] = [b, a + b];
|
||||||
|
}
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.onload = function() {
|
||||||
|
const n = 1; // Example: change this number or get it dynamically
|
||||||
|
const result = fib(n);
|
||||||
|
document.getElementById("fib").textContent = `F(${n}) = ${result}`;
|
||||||
|
};
|
||||||
248
dist/YZd7GbE6Kh.js
vendored
Normal file
|
|
@ -0,0 +1,248 @@
|
||||||
|
// Thank you to https://github.com/daviddarnes/heading-anchors
|
||||||
|
// Thank you to https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
|
||||||
|
let globalInstanceIndex = 0;
|
||||||
|
|
||||||
|
class HeadingAnchors extends HTMLElement {
|
||||||
|
static register(tagName = "heading-anchors", registry = window.customElements) {
|
||||||
|
if(registry && !registry.get(tagName)) {
|
||||||
|
registry.define(tagName, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static attributes = {
|
||||||
|
exclude: "data-ha-exclude",
|
||||||
|
prefix: "prefix",
|
||||||
|
content: "content",
|
||||||
|
}
|
||||||
|
|
||||||
|
static classes = {
|
||||||
|
anchor: "ha",
|
||||||
|
placeholder: "ha-placeholder",
|
||||||
|
srOnly: "ha-visualhide",
|
||||||
|
}
|
||||||
|
|
||||||
|
static defaultSelector = "h2,h3,h4,h5,h6";
|
||||||
|
|
||||||
|
static css = `
|
||||||
|
.${HeadingAnchors.classes.srOnly} {
|
||||||
|
clip: rect(0 0 0 0);
|
||||||
|
height: 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: absolute;
|
||||||
|
width: 1px;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: var(--ha_offsetx);
|
||||||
|
top: var(--ha_offsety);
|
||||||
|
text-decoration: none;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
opacity: .3;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor}:is(:focus-within, :hover) {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor},
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 .25em;
|
||||||
|
|
||||||
|
/* Disable selection of visually hidden label */
|
||||||
|
-webkit-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@supports (anchor-name: none) {
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: anchor(left);
|
||||||
|
top: anchor(top);
|
||||||
|
}
|
||||||
|
}`;
|
||||||
|
|
||||||
|
get supports() {
|
||||||
|
return "replaceSync" in CSSStyleSheet.prototype;
|
||||||
|
}
|
||||||
|
|
||||||
|
get supportsAnchorPosition() {
|
||||||
|
return CSS.supports("anchor-name: none");
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
if(!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sheet = new CSSStyleSheet();
|
||||||
|
sheet.replaceSync(HeadingAnchors.css);
|
||||||
|
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
|
||||||
|
|
||||||
|
this.headingStyles = {};
|
||||||
|
this.instanceIndex = globalInstanceIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
if (!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.headings.forEach((heading, index) => {
|
||||||
|
if(!heading.hasAttribute(HeadingAnchors.attributes.exclude)) {
|
||||||
|
let anchor = this.getAnchorElement(heading);
|
||||||
|
let placeholder = this.getPlaceholderElement();
|
||||||
|
|
||||||
|
// Prefers anchor position approach for better accessibility
|
||||||
|
// https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
let anchorName = `--ha_${this.instanceIndex}_${index}`;
|
||||||
|
placeholder.style.setProperty("anchor-name", anchorName);
|
||||||
|
anchor.style.positionAnchor = anchorName;
|
||||||
|
}
|
||||||
|
|
||||||
|
heading.appendChild(placeholder);
|
||||||
|
heading.after(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchorFromPlaceholder(placeholder) {
|
||||||
|
if(!placeholder) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let heading = placeholder.closest("h1,h2,h3,h4,h5,h6");
|
||||||
|
if(!heading.nextElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO next element could be more defensive
|
||||||
|
this.positionAnchor(heading.nextElementSibling);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchor(anchor) {
|
||||||
|
if(!anchor || !anchor.previousElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO previous element could be more defensive
|
||||||
|
let heading = anchor.previousElementSibling;
|
||||||
|
this.setFontProp(heading, anchor);
|
||||||
|
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
// quit early
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
anchor.style.setProperty("--ha_offsetx", `${placeholder.offsetLeft}px`);
|
||||||
|
anchor.style.setProperty("--ha_offsety", `${placeholder.offsetTop}px`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setFontProp(heading, anchor) {
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
let style = getComputedStyle(placeholder);
|
||||||
|
let props = ["font-weight", "font-size", "line-height", "font-family"];
|
||||||
|
let [weight, size, lh, family] = props.map(name => style.getPropertyValue(name));
|
||||||
|
anchor.style.setProperty("font", `${weight} ${size}/${lh} ${family}`);
|
||||||
|
let vars = style.getPropertyValue("font-variation-settings");
|
||||||
|
if(vars) {
|
||||||
|
anchor.style.setProperty("font-variation-settings", vars);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getAccessibleTextPrefix() {
|
||||||
|
// Useful for i18n
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.prefix) || "Jump to section titled";
|
||||||
|
}
|
||||||
|
|
||||||
|
getContent() {
|
||||||
|
if(this.hasAttribute(HeadingAnchors.attributes.content)) {
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.content);
|
||||||
|
}
|
||||||
|
return "#";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Placeholder nests inside of heading
|
||||||
|
getPlaceholderElement() {
|
||||||
|
let ph = document.createElement("span");
|
||||||
|
ph.setAttribute("aria-hidden", true);
|
||||||
|
ph.classList.add(HeadingAnchors.classes.placeholder);
|
||||||
|
let content = this.getContent();
|
||||||
|
if(content) {
|
||||||
|
ph.textContent = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
ph.addEventListener("mouseover", (e) => {
|
||||||
|
let placeholder = e.target.closest(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
this.positionAnchorFromPlaceholder(placeholder);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return ph;
|
||||||
|
}
|
||||||
|
|
||||||
|
getAnchorElement(heading) {
|
||||||
|
let anchor = document.createElement("a");
|
||||||
|
anchor.href = `#${heading.id}`;
|
||||||
|
anchor.classList.add(HeadingAnchors.classes.anchor);
|
||||||
|
|
||||||
|
let content = this.getContent();
|
||||||
|
anchor.innerHTML = `<span class="${HeadingAnchors.classes.srOnly}">${this.getAccessibleTextPrefix()}: ${heading.textContent}</span>${content ? `<span aria-hidden="true">${content}</span>` : ""}`;
|
||||||
|
|
||||||
|
anchor.addEventListener("focus", e => {
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
if(anchor) {
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
anchor.addEventListener("mouseover", (e) => {
|
||||||
|
// when CSS anchor positioning is supported, this is only used to set the font
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
});
|
||||||
|
|
||||||
|
return anchor;
|
||||||
|
}
|
||||||
|
|
||||||
|
get headings() {
|
||||||
|
return this.querySelectorAll(this.selector.split(",").map(entry => `${entry.trim()}[id]`));
|
||||||
|
}
|
||||||
|
|
||||||
|
get selector() {
|
||||||
|
return this.getAttribute("selector") || HeadingAnchors.defaultSelector;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HeadingAnchors.register();
|
||||||
|
|
||||||
|
export { HeadingAnchors }
|
||||||
|
// Efficient iterative Fibonacci
|
||||||
|
function fib(n) {
|
||||||
|
if (n < 2) return n;
|
||||||
|
let a = 0, b = 1;
|
||||||
|
for (let i = 2; i <= n; i++) {
|
||||||
|
[a, b] = [b, a + b];
|
||||||
|
}
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
window.onload = function() {
|
||||||
|
const n = 5; // Example: change this number or get it dynamically
|
||||||
|
const result = fib(n);
|
||||||
|
document.getElementById("fib_seq").textContent = n;
|
||||||
|
document.getElementById("fib_num").textContent = result; // Doesn't render properly on mobile; figure out subscript numbers...
|
||||||
|
};
|
||||||
236
dist/YuGBzgYfsj.js
vendored
Normal file
|
|
@ -0,0 +1,236 @@
|
||||||
|
// Thank you to https://github.com/daviddarnes/heading-anchors
|
||||||
|
// Thank you to https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
|
||||||
|
let globalInstanceIndex = 0;
|
||||||
|
|
||||||
|
class HeadingAnchors extends HTMLElement {
|
||||||
|
static register(tagName = "heading-anchors", registry = window.customElements) {
|
||||||
|
if(registry && !registry.get(tagName)) {
|
||||||
|
registry.define(tagName, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static attributes = {
|
||||||
|
exclude: "data-ha-exclude",
|
||||||
|
prefix: "prefix",
|
||||||
|
content: "content",
|
||||||
|
}
|
||||||
|
|
||||||
|
static classes = {
|
||||||
|
anchor: "ha",
|
||||||
|
placeholder: "ha-placeholder",
|
||||||
|
srOnly: "ha-visualhide",
|
||||||
|
}
|
||||||
|
|
||||||
|
static defaultSelector = "h2,h3,h4,h5,h6";
|
||||||
|
|
||||||
|
static css = `
|
||||||
|
.${HeadingAnchors.classes.srOnly} {
|
||||||
|
clip: rect(0 0 0 0);
|
||||||
|
height: 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: absolute;
|
||||||
|
width: 1px;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: var(--ha_offsetx);
|
||||||
|
top: var(--ha_offsety);
|
||||||
|
text-decoration: none;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
opacity: .3;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor}:is(:focus-within, :hover) {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor},
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 .25em;
|
||||||
|
|
||||||
|
/* Disable selection of visually hidden label */
|
||||||
|
-webkit-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@supports (anchor-name: none) {
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: anchor(left);
|
||||||
|
top: anchor(top);
|
||||||
|
}
|
||||||
|
}`;
|
||||||
|
|
||||||
|
get supports() {
|
||||||
|
return "replaceSync" in CSSStyleSheet.prototype;
|
||||||
|
}
|
||||||
|
|
||||||
|
get supportsAnchorPosition() {
|
||||||
|
return CSS.supports("anchor-name: none");
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
if(!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sheet = new CSSStyleSheet();
|
||||||
|
sheet.replaceSync(HeadingAnchors.css);
|
||||||
|
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
|
||||||
|
|
||||||
|
this.headingStyles = {};
|
||||||
|
this.instanceIndex = globalInstanceIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
if (!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.headings.forEach((heading, index) => {
|
||||||
|
if(!heading.hasAttribute(HeadingAnchors.attributes.exclude)) {
|
||||||
|
let anchor = this.getAnchorElement(heading);
|
||||||
|
let placeholder = this.getPlaceholderElement();
|
||||||
|
|
||||||
|
// Prefers anchor position approach for better accessibility
|
||||||
|
// https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
let anchorName = `--ha_${this.instanceIndex}_${index}`;
|
||||||
|
placeholder.style.setProperty("anchor-name", anchorName);
|
||||||
|
anchor.style.positionAnchor = anchorName;
|
||||||
|
}
|
||||||
|
|
||||||
|
heading.appendChild(placeholder);
|
||||||
|
heading.after(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchorFromPlaceholder(placeholder) {
|
||||||
|
if(!placeholder) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let heading = placeholder.closest("h1,h2,h3,h4,h5,h6");
|
||||||
|
if(!heading.nextElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO next element could be more defensive
|
||||||
|
this.positionAnchor(heading.nextElementSibling);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchor(anchor) {
|
||||||
|
if(!anchor || !anchor.previousElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO previous element could be more defensive
|
||||||
|
let heading = anchor.previousElementSibling;
|
||||||
|
this.setFontProp(heading, anchor);
|
||||||
|
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
// quit early
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
anchor.style.setProperty("--ha_offsetx", `${placeholder.offsetLeft}px`);
|
||||||
|
anchor.style.setProperty("--ha_offsety", `${placeholder.offsetTop}px`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setFontProp(heading, anchor) {
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
let style = getComputedStyle(placeholder);
|
||||||
|
let props = ["font-weight", "font-size", "line-height", "font-family"];
|
||||||
|
let [weight, size, lh, family] = props.map(name => style.getPropertyValue(name));
|
||||||
|
anchor.style.setProperty("font", `${weight} ${size}/${lh} ${family}`);
|
||||||
|
let vars = style.getPropertyValue("font-variation-settings");
|
||||||
|
if(vars) {
|
||||||
|
anchor.style.setProperty("font-variation-settings", vars);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getAccessibleTextPrefix() {
|
||||||
|
// Useful for i18n
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.prefix) || "Jump to section titled";
|
||||||
|
}
|
||||||
|
|
||||||
|
getContent() {
|
||||||
|
if(this.hasAttribute(HeadingAnchors.attributes.content)) {
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.content);
|
||||||
|
}
|
||||||
|
return "#";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Placeholder nests inside of heading
|
||||||
|
getPlaceholderElement() {
|
||||||
|
let ph = document.createElement("span");
|
||||||
|
ph.setAttribute("aria-hidden", true);
|
||||||
|
ph.classList.add(HeadingAnchors.classes.placeholder);
|
||||||
|
let content = this.getContent();
|
||||||
|
if(content) {
|
||||||
|
ph.textContent = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
ph.addEventListener("mouseover", (e) => {
|
||||||
|
let placeholder = e.target.closest(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
this.positionAnchorFromPlaceholder(placeholder);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return ph;
|
||||||
|
}
|
||||||
|
|
||||||
|
getAnchorElement(heading) {
|
||||||
|
let anchor = document.createElement("a");
|
||||||
|
anchor.href = `#${heading.id}`;
|
||||||
|
anchor.classList.add(HeadingAnchors.classes.anchor);
|
||||||
|
|
||||||
|
let content = this.getContent();
|
||||||
|
anchor.innerHTML = `<span class="${HeadingAnchors.classes.srOnly}">${this.getAccessibleTextPrefix()}: ${heading.textContent}</span>${content ? `<span aria-hidden="true">${content}</span>` : ""}`;
|
||||||
|
|
||||||
|
anchor.addEventListener("focus", e => {
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
if(anchor) {
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
anchor.addEventListener("mouseover", (e) => {
|
||||||
|
// when CSS anchor positioning is supported, this is only used to set the font
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
});
|
||||||
|
|
||||||
|
return anchor;
|
||||||
|
}
|
||||||
|
|
||||||
|
get headings() {
|
||||||
|
return this.querySelectorAll(this.selector.split(",").map(entry => `${entry.trim()}[id]`));
|
||||||
|
}
|
||||||
|
|
||||||
|
get selector() {
|
||||||
|
return this.getAttribute("selector") || HeadingAnchors.defaultSelector;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HeadingAnchors.register();
|
||||||
|
|
||||||
|
export { HeadingAnchors }
|
||||||
|
window.onload = function() {
|
||||||
|
const n = ; // Example: change this number or get it dynamically
|
||||||
|
const result = fib(n);
|
||||||
|
document.getElementById("fib").textContent = `F(${n}) = ${result}`;
|
||||||
|
};
|
||||||
248
dist/_gbQ9z1H4C.js
vendored
Normal file
|
|
@ -0,0 +1,248 @@
|
||||||
|
// Thank you to https://github.com/daviddarnes/heading-anchors
|
||||||
|
// Thank you to https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
|
||||||
|
let globalInstanceIndex = 0;
|
||||||
|
|
||||||
|
class HeadingAnchors extends HTMLElement {
|
||||||
|
static register(tagName = "heading-anchors", registry = window.customElements) {
|
||||||
|
if(registry && !registry.get(tagName)) {
|
||||||
|
registry.define(tagName, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static attributes = {
|
||||||
|
exclude: "data-ha-exclude",
|
||||||
|
prefix: "prefix",
|
||||||
|
content: "content",
|
||||||
|
}
|
||||||
|
|
||||||
|
static classes = {
|
||||||
|
anchor: "ha",
|
||||||
|
placeholder: "ha-placeholder",
|
||||||
|
srOnly: "ha-visualhide",
|
||||||
|
}
|
||||||
|
|
||||||
|
static defaultSelector = "h2,h3,h4,h5,h6";
|
||||||
|
|
||||||
|
static css = `
|
||||||
|
.${HeadingAnchors.classes.srOnly} {
|
||||||
|
clip: rect(0 0 0 0);
|
||||||
|
height: 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: absolute;
|
||||||
|
width: 1px;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: var(--ha_offsetx);
|
||||||
|
top: var(--ha_offsety);
|
||||||
|
text-decoration: none;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
opacity: .3;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor}:is(:focus-within, :hover) {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor},
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 .25em;
|
||||||
|
|
||||||
|
/* Disable selection of visually hidden label */
|
||||||
|
-webkit-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@supports (anchor-name: none) {
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: anchor(left);
|
||||||
|
top: anchor(top);
|
||||||
|
}
|
||||||
|
}`;
|
||||||
|
|
||||||
|
get supports() {
|
||||||
|
return "replaceSync" in CSSStyleSheet.prototype;
|
||||||
|
}
|
||||||
|
|
||||||
|
get supportsAnchorPosition() {
|
||||||
|
return CSS.supports("anchor-name: none");
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
if(!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sheet = new CSSStyleSheet();
|
||||||
|
sheet.replaceSync(HeadingAnchors.css);
|
||||||
|
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
|
||||||
|
|
||||||
|
this.headingStyles = {};
|
||||||
|
this.instanceIndex = globalInstanceIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
if (!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.headings.forEach((heading, index) => {
|
||||||
|
if(!heading.hasAttribute(HeadingAnchors.attributes.exclude)) {
|
||||||
|
let anchor = this.getAnchorElement(heading);
|
||||||
|
let placeholder = this.getPlaceholderElement();
|
||||||
|
|
||||||
|
// Prefers anchor position approach for better accessibility
|
||||||
|
// https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
let anchorName = `--ha_${this.instanceIndex}_${index}`;
|
||||||
|
placeholder.style.setProperty("anchor-name", anchorName);
|
||||||
|
anchor.style.positionAnchor = anchorName;
|
||||||
|
}
|
||||||
|
|
||||||
|
heading.appendChild(placeholder);
|
||||||
|
heading.after(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchorFromPlaceholder(placeholder) {
|
||||||
|
if(!placeholder) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let heading = placeholder.closest("h1,h2,h3,h4,h5,h6");
|
||||||
|
if(!heading.nextElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO next element could be more defensive
|
||||||
|
this.positionAnchor(heading.nextElementSibling);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchor(anchor) {
|
||||||
|
if(!anchor || !anchor.previousElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO previous element could be more defensive
|
||||||
|
let heading = anchor.previousElementSibling;
|
||||||
|
this.setFontProp(heading, anchor);
|
||||||
|
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
// quit early
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
anchor.style.setProperty("--ha_offsetx", `${placeholder.offsetLeft}px`);
|
||||||
|
anchor.style.setProperty("--ha_offsety", `${placeholder.offsetTop}px`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setFontProp(heading, anchor) {
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
let style = getComputedStyle(placeholder);
|
||||||
|
let props = ["font-weight", "font-size", "line-height", "font-family"];
|
||||||
|
let [weight, size, lh, family] = props.map(name => style.getPropertyValue(name));
|
||||||
|
anchor.style.setProperty("font", `${weight} ${size}/${lh} ${family}`);
|
||||||
|
let vars = style.getPropertyValue("font-variation-settings");
|
||||||
|
if(vars) {
|
||||||
|
anchor.style.setProperty("font-variation-settings", vars);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getAccessibleTextPrefix() {
|
||||||
|
// Useful for i18n
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.prefix) || "Jump to section titled";
|
||||||
|
}
|
||||||
|
|
||||||
|
getContent() {
|
||||||
|
if(this.hasAttribute(HeadingAnchors.attributes.content)) {
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.content);
|
||||||
|
}
|
||||||
|
return "#";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Placeholder nests inside of heading
|
||||||
|
getPlaceholderElement() {
|
||||||
|
let ph = document.createElement("span");
|
||||||
|
ph.setAttribute("aria-hidden", true);
|
||||||
|
ph.classList.add(HeadingAnchors.classes.placeholder);
|
||||||
|
let content = this.getContent();
|
||||||
|
if(content) {
|
||||||
|
ph.textContent = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
ph.addEventListener("mouseover", (e) => {
|
||||||
|
let placeholder = e.target.closest(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
this.positionAnchorFromPlaceholder(placeholder);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return ph;
|
||||||
|
}
|
||||||
|
|
||||||
|
getAnchorElement(heading) {
|
||||||
|
let anchor = document.createElement("a");
|
||||||
|
anchor.href = `#${heading.id}`;
|
||||||
|
anchor.classList.add(HeadingAnchors.classes.anchor);
|
||||||
|
|
||||||
|
let content = this.getContent();
|
||||||
|
anchor.innerHTML = `<span class="${HeadingAnchors.classes.srOnly}">${this.getAccessibleTextPrefix()}: ${heading.textContent}</span>${content ? `<span aria-hidden="true">${content}</span>` : ""}`;
|
||||||
|
|
||||||
|
anchor.addEventListener("focus", e => {
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
if(anchor) {
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
anchor.addEventListener("mouseover", (e) => {
|
||||||
|
// when CSS anchor positioning is supported, this is only used to set the font
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
});
|
||||||
|
|
||||||
|
return anchor;
|
||||||
|
}
|
||||||
|
|
||||||
|
get headings() {
|
||||||
|
return this.querySelectorAll(this.selector.split(",").map(entry => `${entry.trim()}[id]`));
|
||||||
|
}
|
||||||
|
|
||||||
|
get selector() {
|
||||||
|
return this.getAttribute("selector") || HeadingAnchors.defaultSelector;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HeadingAnchors.register();
|
||||||
|
|
||||||
|
export { HeadingAnchors }
|
||||||
|
// Efficient iterative Fibonacci
|
||||||
|
function fib(n) {
|
||||||
|
if (n < 2) return n;
|
||||||
|
let a = 0, b = 1;
|
||||||
|
for (let i = 2; i <= n; i++) {
|
||||||
|
[a, b] = [b, a + b];
|
||||||
|
}
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
window.onload = function() {
|
||||||
|
const n = 2; // Example: change this number or get it dynamically
|
||||||
|
const result = fib(n);
|
||||||
|
document.getElementById("fib_seq").textContent = n;
|
||||||
|
document.getElementById("fib_num").textContent = result; // Doesn't render properly on mobile; figure out subscript numbers...
|
||||||
|
};
|
||||||
234
dist/bUsipRFsvJ.js
vendored
Normal file
|
|
@ -0,0 +1,234 @@
|
||||||
|
// Thank you to https://github.com/daviddarnes/heading-anchors
|
||||||
|
// Thank you to https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
|
||||||
|
let globalInstanceIndex = 0;
|
||||||
|
|
||||||
|
class HeadingAnchors extends HTMLElement {
|
||||||
|
static register(tagName = "heading-anchors", registry = window.customElements) {
|
||||||
|
if(registry && !registry.get(tagName)) {
|
||||||
|
registry.define(tagName, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static attributes = {
|
||||||
|
exclude: "data-ha-exclude",
|
||||||
|
prefix: "prefix",
|
||||||
|
content: "content",
|
||||||
|
}
|
||||||
|
|
||||||
|
static classes = {
|
||||||
|
anchor: "ha",
|
||||||
|
placeholder: "ha-placeholder",
|
||||||
|
srOnly: "ha-visualhide",
|
||||||
|
}
|
||||||
|
|
||||||
|
static defaultSelector = "h2,h3,h4,h5,h6";
|
||||||
|
|
||||||
|
static css = `
|
||||||
|
.${HeadingAnchors.classes.srOnly} {
|
||||||
|
clip: rect(0 0 0 0);
|
||||||
|
height: 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: absolute;
|
||||||
|
width: 1px;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: var(--ha_offsetx);
|
||||||
|
top: var(--ha_offsety);
|
||||||
|
text-decoration: none;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
opacity: .3;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor}:is(:focus-within, :hover) {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor},
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 .25em;
|
||||||
|
|
||||||
|
/* Disable selection of visually hidden label */
|
||||||
|
-webkit-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@supports (anchor-name: none) {
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: anchor(left);
|
||||||
|
top: anchor(top);
|
||||||
|
}
|
||||||
|
}`;
|
||||||
|
|
||||||
|
get supports() {
|
||||||
|
return "replaceSync" in CSSStyleSheet.prototype;
|
||||||
|
}
|
||||||
|
|
||||||
|
get supportsAnchorPosition() {
|
||||||
|
return CSS.supports("anchor-name: none");
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
if(!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sheet = new CSSStyleSheet();
|
||||||
|
sheet.replaceSync(HeadingAnchors.css);
|
||||||
|
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
|
||||||
|
|
||||||
|
this.headingStyles = {};
|
||||||
|
this.instanceIndex = globalInstanceIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
if (!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.headings.forEach((heading, index) => {
|
||||||
|
if(!heading.hasAttribute(HeadingAnchors.attributes.exclude)) {
|
||||||
|
let anchor = this.getAnchorElement(heading);
|
||||||
|
let placeholder = this.getPlaceholderElement();
|
||||||
|
|
||||||
|
// Prefers anchor position approach for better accessibility
|
||||||
|
// https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
let anchorName = `--ha_${this.instanceIndex}_${index}`;
|
||||||
|
placeholder.style.setProperty("anchor-name", anchorName);
|
||||||
|
anchor.style.positionAnchor = anchorName;
|
||||||
|
}
|
||||||
|
|
||||||
|
heading.appendChild(placeholder);
|
||||||
|
heading.after(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchorFromPlaceholder(placeholder) {
|
||||||
|
if(!placeholder) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let heading = placeholder.closest("h1,h2,h3,h4,h5,h6");
|
||||||
|
if(!heading.nextElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO next element could be more defensive
|
||||||
|
this.positionAnchor(heading.nextElementSibling);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchor(anchor) {
|
||||||
|
if(!anchor || !anchor.previousElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO previous element could be more defensive
|
||||||
|
let heading = anchor.previousElementSibling;
|
||||||
|
this.setFontProp(heading, anchor);
|
||||||
|
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
// quit early
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
anchor.style.setProperty("--ha_offsetx", `${placeholder.offsetLeft}px`);
|
||||||
|
anchor.style.setProperty("--ha_offsety", `${placeholder.offsetTop}px`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setFontProp(heading, anchor) {
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
let style = getComputedStyle(placeholder);
|
||||||
|
let props = ["font-weight", "font-size", "line-height", "font-family"];
|
||||||
|
let [weight, size, lh, family] = props.map(name => style.getPropertyValue(name));
|
||||||
|
anchor.style.setProperty("font", `${weight} ${size}/${lh} ${family}`);
|
||||||
|
let vars = style.getPropertyValue("font-variation-settings");
|
||||||
|
if(vars) {
|
||||||
|
anchor.style.setProperty("font-variation-settings", vars);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getAccessibleTextPrefix() {
|
||||||
|
// Useful for i18n
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.prefix) || "Jump to section titled";
|
||||||
|
}
|
||||||
|
|
||||||
|
getContent() {
|
||||||
|
if(this.hasAttribute(HeadingAnchors.attributes.content)) {
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.content);
|
||||||
|
}
|
||||||
|
return "#";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Placeholder nests inside of heading
|
||||||
|
getPlaceholderElement() {
|
||||||
|
let ph = document.createElement("span");
|
||||||
|
ph.setAttribute("aria-hidden", true);
|
||||||
|
ph.classList.add(HeadingAnchors.classes.placeholder);
|
||||||
|
let content = this.getContent();
|
||||||
|
if(content) {
|
||||||
|
ph.textContent = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
ph.addEventListener("mouseover", (e) => {
|
||||||
|
let placeholder = e.target.closest(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
this.positionAnchorFromPlaceholder(placeholder);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return ph;
|
||||||
|
}
|
||||||
|
|
||||||
|
getAnchorElement(heading) {
|
||||||
|
let anchor = document.createElement("a");
|
||||||
|
anchor.href = `#${heading.id}`;
|
||||||
|
anchor.classList.add(HeadingAnchors.classes.anchor);
|
||||||
|
|
||||||
|
let content = this.getContent();
|
||||||
|
anchor.innerHTML = `<span class="${HeadingAnchors.classes.srOnly}">${this.getAccessibleTextPrefix()}: ${heading.textContent}</span>${content ? `<span aria-hidden="true">${content}</span>` : ""}`;
|
||||||
|
|
||||||
|
anchor.addEventListener("focus", e => {
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
if(anchor) {
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
anchor.addEventListener("mouseover", (e) => {
|
||||||
|
// when CSS anchor positioning is supported, this is only used to set the font
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
});
|
||||||
|
|
||||||
|
return anchor;
|
||||||
|
}
|
||||||
|
|
||||||
|
get headings() {
|
||||||
|
return this.querySelectorAll(this.selector.split(",").map(entry => `${entry.trim()}[id]`));
|
||||||
|
}
|
||||||
|
|
||||||
|
get selector() {
|
||||||
|
return this.getAttribute("selector") || HeadingAnchors.defaultSelector;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HeadingAnchors.register();
|
||||||
|
|
||||||
|
export { HeadingAnchors }
|
||||||
|
window.onload = function() {
|
||||||
|
fib(1);
|
||||||
|
};
|
||||||
236
dist/bXca3JBF0E.js
vendored
Normal file
|
|
@ -0,0 +1,236 @@
|
||||||
|
// Thank you to https://github.com/daviddarnes/heading-anchors
|
||||||
|
// Thank you to https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
|
||||||
|
let globalInstanceIndex = 0;
|
||||||
|
|
||||||
|
class HeadingAnchors extends HTMLElement {
|
||||||
|
static register(tagName = "heading-anchors", registry = window.customElements) {
|
||||||
|
if(registry && !registry.get(tagName)) {
|
||||||
|
registry.define(tagName, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static attributes = {
|
||||||
|
exclude: "data-ha-exclude",
|
||||||
|
prefix: "prefix",
|
||||||
|
content: "content",
|
||||||
|
}
|
||||||
|
|
||||||
|
static classes = {
|
||||||
|
anchor: "ha",
|
||||||
|
placeholder: "ha-placeholder",
|
||||||
|
srOnly: "ha-visualhide",
|
||||||
|
}
|
||||||
|
|
||||||
|
static defaultSelector = "h2,h3,h4,h5,h6";
|
||||||
|
|
||||||
|
static css = `
|
||||||
|
.${HeadingAnchors.classes.srOnly} {
|
||||||
|
clip: rect(0 0 0 0);
|
||||||
|
height: 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: absolute;
|
||||||
|
width: 1px;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: var(--ha_offsetx);
|
||||||
|
top: var(--ha_offsety);
|
||||||
|
text-decoration: none;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
opacity: .3;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor}:is(:focus-within, :hover) {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor},
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 .25em;
|
||||||
|
|
||||||
|
/* Disable selection of visually hidden label */
|
||||||
|
-webkit-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@supports (anchor-name: none) {
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: anchor(left);
|
||||||
|
top: anchor(top);
|
||||||
|
}
|
||||||
|
}`;
|
||||||
|
|
||||||
|
get supports() {
|
||||||
|
return "replaceSync" in CSSStyleSheet.prototype;
|
||||||
|
}
|
||||||
|
|
||||||
|
get supportsAnchorPosition() {
|
||||||
|
return CSS.supports("anchor-name: none");
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
if(!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sheet = new CSSStyleSheet();
|
||||||
|
sheet.replaceSync(HeadingAnchors.css);
|
||||||
|
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
|
||||||
|
|
||||||
|
this.headingStyles = {};
|
||||||
|
this.instanceIndex = globalInstanceIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
if (!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.headings.forEach((heading, index) => {
|
||||||
|
if(!heading.hasAttribute(HeadingAnchors.attributes.exclude)) {
|
||||||
|
let anchor = this.getAnchorElement(heading);
|
||||||
|
let placeholder = this.getPlaceholderElement();
|
||||||
|
|
||||||
|
// Prefers anchor position approach for better accessibility
|
||||||
|
// https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
let anchorName = `--ha_${this.instanceIndex}_${index}`;
|
||||||
|
placeholder.style.setProperty("anchor-name", anchorName);
|
||||||
|
anchor.style.positionAnchor = anchorName;
|
||||||
|
}
|
||||||
|
|
||||||
|
heading.appendChild(placeholder);
|
||||||
|
heading.after(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchorFromPlaceholder(placeholder) {
|
||||||
|
if(!placeholder) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let heading = placeholder.closest("h1,h2,h3,h4,h5,h6");
|
||||||
|
if(!heading.nextElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO next element could be more defensive
|
||||||
|
this.positionAnchor(heading.nextElementSibling);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchor(anchor) {
|
||||||
|
if(!anchor || !anchor.previousElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO previous element could be more defensive
|
||||||
|
let heading = anchor.previousElementSibling;
|
||||||
|
this.setFontProp(heading, anchor);
|
||||||
|
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
// quit early
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
anchor.style.setProperty("--ha_offsetx", `${placeholder.offsetLeft}px`);
|
||||||
|
anchor.style.setProperty("--ha_offsety", `${placeholder.offsetTop}px`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setFontProp(heading, anchor) {
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
let style = getComputedStyle(placeholder);
|
||||||
|
let props = ["font-weight", "font-size", "line-height", "font-family"];
|
||||||
|
let [weight, size, lh, family] = props.map(name => style.getPropertyValue(name));
|
||||||
|
anchor.style.setProperty("font", `${weight} ${size}/${lh} ${family}`);
|
||||||
|
let vars = style.getPropertyValue("font-variation-settings");
|
||||||
|
if(vars) {
|
||||||
|
anchor.style.setProperty("font-variation-settings", vars);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getAccessibleTextPrefix() {
|
||||||
|
// Useful for i18n
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.prefix) || "Jump to section titled";
|
||||||
|
}
|
||||||
|
|
||||||
|
getContent() {
|
||||||
|
if(this.hasAttribute(HeadingAnchors.attributes.content)) {
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.content);
|
||||||
|
}
|
||||||
|
return "#";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Placeholder nests inside of heading
|
||||||
|
getPlaceholderElement() {
|
||||||
|
let ph = document.createElement("span");
|
||||||
|
ph.setAttribute("aria-hidden", true);
|
||||||
|
ph.classList.add(HeadingAnchors.classes.placeholder);
|
||||||
|
let content = this.getContent();
|
||||||
|
if(content) {
|
||||||
|
ph.textContent = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
ph.addEventListener("mouseover", (e) => {
|
||||||
|
let placeholder = e.target.closest(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
this.positionAnchorFromPlaceholder(placeholder);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return ph;
|
||||||
|
}
|
||||||
|
|
||||||
|
getAnchorElement(heading) {
|
||||||
|
let anchor = document.createElement("a");
|
||||||
|
anchor.href = `#${heading.id}`;
|
||||||
|
anchor.classList.add(HeadingAnchors.classes.anchor);
|
||||||
|
|
||||||
|
let content = this.getContent();
|
||||||
|
anchor.innerHTML = `<span class="${HeadingAnchors.classes.srOnly}">${this.getAccessibleTextPrefix()}: ${heading.textContent}</span>${content ? `<span aria-hidden="true">${content}</span>` : ""}`;
|
||||||
|
|
||||||
|
anchor.addEventListener("focus", e => {
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
if(anchor) {
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
anchor.addEventListener("mouseover", (e) => {
|
||||||
|
// when CSS anchor positioning is supported, this is only used to set the font
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
});
|
||||||
|
|
||||||
|
return anchor;
|
||||||
|
}
|
||||||
|
|
||||||
|
get headings() {
|
||||||
|
return this.querySelectorAll(this.selector.split(",").map(entry => `${entry.trim()}[id]`));
|
||||||
|
}
|
||||||
|
|
||||||
|
get selector() {
|
||||||
|
return this.getAttribute("selector") || HeadingAnchors.defaultSelector;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HeadingAnchors.register();
|
||||||
|
|
||||||
|
export { HeadingAnchors }
|
||||||
|
window.onload = function() {
|
||||||
|
const n = 3; // Example: change this number or get it dynamically
|
||||||
|
const result = fib(n);
|
||||||
|
document.getElementById("fib").textContent = `F(${n}) = ${result}`;
|
||||||
|
};
|
||||||
231
dist/cC8wS6ZjFU.js
vendored
Normal file
|
|
@ -0,0 +1,231 @@
|
||||||
|
// Thank you to https://github.com/daviddarnes/heading-anchors
|
||||||
|
// Thank you to https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
|
||||||
|
let globalInstanceIndex = 0;
|
||||||
|
|
||||||
|
class HeadingAnchors extends HTMLElement {
|
||||||
|
static register(tagName = "heading-anchors", registry = window.customElements) {
|
||||||
|
if(registry && !registry.get(tagName)) {
|
||||||
|
registry.define(tagName, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static attributes = {
|
||||||
|
exclude: "data-ha-exclude",
|
||||||
|
prefix: "prefix",
|
||||||
|
content: "content",
|
||||||
|
}
|
||||||
|
|
||||||
|
static classes = {
|
||||||
|
anchor: "ha",
|
||||||
|
placeholder: "ha-placeholder",
|
||||||
|
srOnly: "ha-visualhide",
|
||||||
|
}
|
||||||
|
|
||||||
|
static defaultSelector = "h2,h3,h4,h5,h6";
|
||||||
|
|
||||||
|
static css = `
|
||||||
|
.${HeadingAnchors.classes.srOnly} {
|
||||||
|
clip: rect(0 0 0 0);
|
||||||
|
height: 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: absolute;
|
||||||
|
width: 1px;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: var(--ha_offsetx);
|
||||||
|
top: var(--ha_offsety);
|
||||||
|
text-decoration: none;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
opacity: .3;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor}:is(:focus-within, :hover) {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor},
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 .25em;
|
||||||
|
|
||||||
|
/* Disable selection of visually hidden label */
|
||||||
|
-webkit-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@supports (anchor-name: none) {
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: anchor(left);
|
||||||
|
top: anchor(top);
|
||||||
|
}
|
||||||
|
}`;
|
||||||
|
|
||||||
|
get supports() {
|
||||||
|
return "replaceSync" in CSSStyleSheet.prototype;
|
||||||
|
}
|
||||||
|
|
||||||
|
get supportsAnchorPosition() {
|
||||||
|
return CSS.supports("anchor-name: none");
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
if(!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sheet = new CSSStyleSheet();
|
||||||
|
sheet.replaceSync(HeadingAnchors.css);
|
||||||
|
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
|
||||||
|
|
||||||
|
this.headingStyles = {};
|
||||||
|
this.instanceIndex = globalInstanceIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
if (!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.headings.forEach((heading, index) => {
|
||||||
|
if(!heading.hasAttribute(HeadingAnchors.attributes.exclude)) {
|
||||||
|
let anchor = this.getAnchorElement(heading);
|
||||||
|
let placeholder = this.getPlaceholderElement();
|
||||||
|
|
||||||
|
// Prefers anchor position approach for better accessibility
|
||||||
|
// https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
let anchorName = `--ha_${this.instanceIndex}_${index}`;
|
||||||
|
placeholder.style.setProperty("anchor-name", anchorName);
|
||||||
|
anchor.style.positionAnchor = anchorName;
|
||||||
|
}
|
||||||
|
|
||||||
|
heading.appendChild(placeholder);
|
||||||
|
heading.after(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchorFromPlaceholder(placeholder) {
|
||||||
|
if(!placeholder) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let heading = placeholder.closest("h1,h2,h3,h4,h5,h6");
|
||||||
|
if(!heading.nextElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO next element could be more defensive
|
||||||
|
this.positionAnchor(heading.nextElementSibling);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchor(anchor) {
|
||||||
|
if(!anchor || !anchor.previousElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO previous element could be more defensive
|
||||||
|
let heading = anchor.previousElementSibling;
|
||||||
|
this.setFontProp(heading, anchor);
|
||||||
|
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
// quit early
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
anchor.style.setProperty("--ha_offsetx", `${placeholder.offsetLeft}px`);
|
||||||
|
anchor.style.setProperty("--ha_offsety", `${placeholder.offsetTop}px`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setFontProp(heading, anchor) {
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
let style = getComputedStyle(placeholder);
|
||||||
|
let props = ["font-weight", "font-size", "line-height", "font-family"];
|
||||||
|
let [weight, size, lh, family] = props.map(name => style.getPropertyValue(name));
|
||||||
|
anchor.style.setProperty("font", `${weight} ${size}/${lh} ${family}`);
|
||||||
|
let vars = style.getPropertyValue("font-variation-settings");
|
||||||
|
if(vars) {
|
||||||
|
anchor.style.setProperty("font-variation-settings", vars);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getAccessibleTextPrefix() {
|
||||||
|
// Useful for i18n
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.prefix) || "Jump to section titled";
|
||||||
|
}
|
||||||
|
|
||||||
|
getContent() {
|
||||||
|
if(this.hasAttribute(HeadingAnchors.attributes.content)) {
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.content);
|
||||||
|
}
|
||||||
|
return "#";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Placeholder nests inside of heading
|
||||||
|
getPlaceholderElement() {
|
||||||
|
let ph = document.createElement("span");
|
||||||
|
ph.setAttribute("aria-hidden", true);
|
||||||
|
ph.classList.add(HeadingAnchors.classes.placeholder);
|
||||||
|
let content = this.getContent();
|
||||||
|
if(content) {
|
||||||
|
ph.textContent = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
ph.addEventListener("mouseover", (e) => {
|
||||||
|
let placeholder = e.target.closest(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
this.positionAnchorFromPlaceholder(placeholder);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return ph;
|
||||||
|
}
|
||||||
|
|
||||||
|
getAnchorElement(heading) {
|
||||||
|
let anchor = document.createElement("a");
|
||||||
|
anchor.href = `#${heading.id}`;
|
||||||
|
anchor.classList.add(HeadingAnchors.classes.anchor);
|
||||||
|
|
||||||
|
let content = this.getContent();
|
||||||
|
anchor.innerHTML = `<span class="${HeadingAnchors.classes.srOnly}">${this.getAccessibleTextPrefix()}: ${heading.textContent}</span>${content ? `<span aria-hidden="true">${content}</span>` : ""}`;
|
||||||
|
|
||||||
|
anchor.addEventListener("focus", e => {
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
if(anchor) {
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
anchor.addEventListener("mouseover", (e) => {
|
||||||
|
// when CSS anchor positioning is supported, this is only used to set the font
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
});
|
||||||
|
|
||||||
|
return anchor;
|
||||||
|
}
|
||||||
|
|
||||||
|
get headings() {
|
||||||
|
return this.querySelectorAll(this.selector.split(",").map(entry => `${entry.trim()}[id]`));
|
||||||
|
}
|
||||||
|
|
||||||
|
get selector() {
|
||||||
|
return this.getAttribute("selector") || HeadingAnchors.defaultSelector;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HeadingAnchors.register();
|
||||||
|
|
||||||
|
export { HeadingAnchors }
|
||||||
233
dist/cddc0j585S.js
vendored
Normal file
|
|
@ -0,0 +1,233 @@
|
||||||
|
// Thank you to https://github.com/daviddarnes/heading-anchors
|
||||||
|
// Thank you to https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
|
||||||
|
let globalInstanceIndex = 0;
|
||||||
|
|
||||||
|
class HeadingAnchors extends HTMLElement {
|
||||||
|
static register(tagName = "heading-anchors", registry = window.customElements) {
|
||||||
|
if(registry && !registry.get(tagName)) {
|
||||||
|
registry.define(tagName, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static attributes = {
|
||||||
|
exclude: "data-ha-exclude",
|
||||||
|
prefix: "prefix",
|
||||||
|
content: "content",
|
||||||
|
}
|
||||||
|
|
||||||
|
static classes = {
|
||||||
|
anchor: "ha",
|
||||||
|
placeholder: "ha-placeholder",
|
||||||
|
srOnly: "ha-visualhide",
|
||||||
|
}
|
||||||
|
|
||||||
|
static defaultSelector = "h2,h3,h4,h5,h6";
|
||||||
|
|
||||||
|
static css = `
|
||||||
|
.${HeadingAnchors.classes.srOnly} {
|
||||||
|
clip: rect(0 0 0 0);
|
||||||
|
height: 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: absolute;
|
||||||
|
width: 1px;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: var(--ha_offsetx);
|
||||||
|
top: var(--ha_offsety);
|
||||||
|
text-decoration: none;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
opacity: .3;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor}:is(:focus-within, :hover) {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor},
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 .25em;
|
||||||
|
|
||||||
|
/* Disable selection of visually hidden label */
|
||||||
|
-webkit-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@supports (anchor-name: none) {
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: anchor(left);
|
||||||
|
top: anchor(top);
|
||||||
|
}
|
||||||
|
}`;
|
||||||
|
|
||||||
|
get supports() {
|
||||||
|
return "replaceSync" in CSSStyleSheet.prototype;
|
||||||
|
}
|
||||||
|
|
||||||
|
get supportsAnchorPosition() {
|
||||||
|
return CSS.supports("anchor-name: none");
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
if(!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sheet = new CSSStyleSheet();
|
||||||
|
sheet.replaceSync(HeadingAnchors.css);
|
||||||
|
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
|
||||||
|
|
||||||
|
this.headingStyles = {};
|
||||||
|
this.instanceIndex = globalInstanceIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
if (!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.headings.forEach((heading, index) => {
|
||||||
|
if(!heading.hasAttribute(HeadingAnchors.attributes.exclude)) {
|
||||||
|
let anchor = this.getAnchorElement(heading);
|
||||||
|
let placeholder = this.getPlaceholderElement();
|
||||||
|
|
||||||
|
// Prefers anchor position approach for better accessibility
|
||||||
|
// https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
let anchorName = `--ha_${this.instanceIndex}_${index}`;
|
||||||
|
placeholder.style.setProperty("anchor-name", anchorName);
|
||||||
|
anchor.style.positionAnchor = anchorName;
|
||||||
|
}
|
||||||
|
|
||||||
|
heading.appendChild(placeholder);
|
||||||
|
heading.after(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchorFromPlaceholder(placeholder) {
|
||||||
|
if(!placeholder) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let heading = placeholder.closest("h1,h2,h3,h4,h5,h6");
|
||||||
|
if(!heading.nextElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO next element could be more defensive
|
||||||
|
this.positionAnchor(heading.nextElementSibling);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchor(anchor) {
|
||||||
|
if(!anchor || !anchor.previousElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO previous element could be more defensive
|
||||||
|
let heading = anchor.previousElementSibling;
|
||||||
|
this.setFontProp(heading, anchor);
|
||||||
|
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
// quit early
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
anchor.style.setProperty("--ha_offsetx", `${placeholder.offsetLeft}px`);
|
||||||
|
anchor.style.setProperty("--ha_offsety", `${placeholder.offsetTop}px`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setFontProp(heading, anchor) {
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
let style = getComputedStyle(placeholder);
|
||||||
|
let props = ["font-weight", "font-size", "line-height", "font-family"];
|
||||||
|
let [weight, size, lh, family] = props.map(name => style.getPropertyValue(name));
|
||||||
|
anchor.style.setProperty("font", `${weight} ${size}/${lh} ${family}`);
|
||||||
|
let vars = style.getPropertyValue("font-variation-settings");
|
||||||
|
if(vars) {
|
||||||
|
anchor.style.setProperty("font-variation-settings", vars);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getAccessibleTextPrefix() {
|
||||||
|
// Useful for i18n
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.prefix) || "Jump to section titled";
|
||||||
|
}
|
||||||
|
|
||||||
|
getContent() {
|
||||||
|
if(this.hasAttribute(HeadingAnchors.attributes.content)) {
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.content);
|
||||||
|
}
|
||||||
|
return "#";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Placeholder nests inside of heading
|
||||||
|
getPlaceholderElement() {
|
||||||
|
let ph = document.createElement("span");
|
||||||
|
ph.setAttribute("aria-hidden", true);
|
||||||
|
ph.classList.add(HeadingAnchors.classes.placeholder);
|
||||||
|
let content = this.getContent();
|
||||||
|
if(content) {
|
||||||
|
ph.textContent = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
ph.addEventListener("mouseover", (e) => {
|
||||||
|
let placeholder = e.target.closest(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
this.positionAnchorFromPlaceholder(placeholder);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return ph;
|
||||||
|
}
|
||||||
|
|
||||||
|
getAnchorElement(heading) {
|
||||||
|
let anchor = document.createElement("a");
|
||||||
|
anchor.href = `#${heading.id}`;
|
||||||
|
anchor.classList.add(HeadingAnchors.classes.anchor);
|
||||||
|
|
||||||
|
let content = this.getContent();
|
||||||
|
anchor.innerHTML = `<span class="${HeadingAnchors.classes.srOnly}">${this.getAccessibleTextPrefix()}: ${heading.textContent}</span>${content ? `<span aria-hidden="true">${content}</span>` : ""}`;
|
||||||
|
|
||||||
|
anchor.addEventListener("focus", e => {
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
if(anchor) {
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
anchor.addEventListener("mouseover", (e) => {
|
||||||
|
// when CSS anchor positioning is supported, this is only used to set the font
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
});
|
||||||
|
|
||||||
|
return anchor;
|
||||||
|
}
|
||||||
|
|
||||||
|
get headings() {
|
||||||
|
return this.querySelectorAll(this.selector.split(",").map(entry => `${entry.trim()}[id]`));
|
||||||
|
}
|
||||||
|
|
||||||
|
get selector() {
|
||||||
|
return this.getAttribute("selector") || HeadingAnchors.defaultSelector;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HeadingAnchors.register();
|
||||||
|
|
||||||
|
export { HeadingAnchors }
|
||||||
|
<p>1</p>
|
||||||
|
window.onload = fib(1);
|
||||||
247
dist/eKalzsYoRS.js
vendored
Normal file
|
|
@ -0,0 +1,247 @@
|
||||||
|
// Thank you to https://github.com/daviddarnes/heading-anchors
|
||||||
|
// Thank you to https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
|
||||||
|
let globalInstanceIndex = 0;
|
||||||
|
|
||||||
|
class HeadingAnchors extends HTMLElement {
|
||||||
|
static register(tagName = "heading-anchors", registry = window.customElements) {
|
||||||
|
if(registry && !registry.get(tagName)) {
|
||||||
|
registry.define(tagName, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static attributes = {
|
||||||
|
exclude: "data-ha-exclude",
|
||||||
|
prefix: "prefix",
|
||||||
|
content: "content",
|
||||||
|
}
|
||||||
|
|
||||||
|
static classes = {
|
||||||
|
anchor: "ha",
|
||||||
|
placeholder: "ha-placeholder",
|
||||||
|
srOnly: "ha-visualhide",
|
||||||
|
}
|
||||||
|
|
||||||
|
static defaultSelector = "h2,h3,h4,h5,h6";
|
||||||
|
|
||||||
|
static css = `
|
||||||
|
.${HeadingAnchors.classes.srOnly} {
|
||||||
|
clip: rect(0 0 0 0);
|
||||||
|
height: 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: absolute;
|
||||||
|
width: 1px;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: var(--ha_offsetx);
|
||||||
|
top: var(--ha_offsety);
|
||||||
|
text-decoration: none;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
opacity: .3;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor}:is(:focus-within, :hover) {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor},
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 .25em;
|
||||||
|
|
||||||
|
/* Disable selection of visually hidden label */
|
||||||
|
-webkit-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@supports (anchor-name: none) {
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: anchor(left);
|
||||||
|
top: anchor(top);
|
||||||
|
}
|
||||||
|
}`;
|
||||||
|
|
||||||
|
get supports() {
|
||||||
|
return "replaceSync" in CSSStyleSheet.prototype;
|
||||||
|
}
|
||||||
|
|
||||||
|
get supportsAnchorPosition() {
|
||||||
|
return CSS.supports("anchor-name: none");
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
if(!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sheet = new CSSStyleSheet();
|
||||||
|
sheet.replaceSync(HeadingAnchors.css);
|
||||||
|
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
|
||||||
|
|
||||||
|
this.headingStyles = {};
|
||||||
|
this.instanceIndex = globalInstanceIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
if (!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.headings.forEach((heading, index) => {
|
||||||
|
if(!heading.hasAttribute(HeadingAnchors.attributes.exclude)) {
|
||||||
|
let anchor = this.getAnchorElement(heading);
|
||||||
|
let placeholder = this.getPlaceholderElement();
|
||||||
|
|
||||||
|
// Prefers anchor position approach for better accessibility
|
||||||
|
// https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
let anchorName = `--ha_${this.instanceIndex}_${index}`;
|
||||||
|
placeholder.style.setProperty("anchor-name", anchorName);
|
||||||
|
anchor.style.positionAnchor = anchorName;
|
||||||
|
}
|
||||||
|
|
||||||
|
heading.appendChild(placeholder);
|
||||||
|
heading.after(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchorFromPlaceholder(placeholder) {
|
||||||
|
if(!placeholder) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let heading = placeholder.closest("h1,h2,h3,h4,h5,h6");
|
||||||
|
if(!heading.nextElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO next element could be more defensive
|
||||||
|
this.positionAnchor(heading.nextElementSibling);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchor(anchor) {
|
||||||
|
if(!anchor || !anchor.previousElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO previous element could be more defensive
|
||||||
|
let heading = anchor.previousElementSibling;
|
||||||
|
this.setFontProp(heading, anchor);
|
||||||
|
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
// quit early
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
anchor.style.setProperty("--ha_offsetx", `${placeholder.offsetLeft}px`);
|
||||||
|
anchor.style.setProperty("--ha_offsety", `${placeholder.offsetTop}px`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setFontProp(heading, anchor) {
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
let style = getComputedStyle(placeholder);
|
||||||
|
let props = ["font-weight", "font-size", "line-height", "font-family"];
|
||||||
|
let [weight, size, lh, family] = props.map(name => style.getPropertyValue(name));
|
||||||
|
anchor.style.setProperty("font", `${weight} ${size}/${lh} ${family}`);
|
||||||
|
let vars = style.getPropertyValue("font-variation-settings");
|
||||||
|
if(vars) {
|
||||||
|
anchor.style.setProperty("font-variation-settings", vars);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getAccessibleTextPrefix() {
|
||||||
|
// Useful for i18n
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.prefix) || "Jump to section titled";
|
||||||
|
}
|
||||||
|
|
||||||
|
getContent() {
|
||||||
|
if(this.hasAttribute(HeadingAnchors.attributes.content)) {
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.content);
|
||||||
|
}
|
||||||
|
return "#";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Placeholder nests inside of heading
|
||||||
|
getPlaceholderElement() {
|
||||||
|
let ph = document.createElement("span");
|
||||||
|
ph.setAttribute("aria-hidden", true);
|
||||||
|
ph.classList.add(HeadingAnchors.classes.placeholder);
|
||||||
|
let content = this.getContent();
|
||||||
|
if(content) {
|
||||||
|
ph.textContent = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
ph.addEventListener("mouseover", (e) => {
|
||||||
|
let placeholder = e.target.closest(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
this.positionAnchorFromPlaceholder(placeholder);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return ph;
|
||||||
|
}
|
||||||
|
|
||||||
|
getAnchorElement(heading) {
|
||||||
|
let anchor = document.createElement("a");
|
||||||
|
anchor.href = `#${heading.id}`;
|
||||||
|
anchor.classList.add(HeadingAnchors.classes.anchor);
|
||||||
|
|
||||||
|
let content = this.getContent();
|
||||||
|
anchor.innerHTML = `<span class="${HeadingAnchors.classes.srOnly}">${this.getAccessibleTextPrefix()}: ${heading.textContent}</span>${content ? `<span aria-hidden="true">${content}</span>` : ""}`;
|
||||||
|
|
||||||
|
anchor.addEventListener("focus", e => {
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
if(anchor) {
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
anchor.addEventListener("mouseover", (e) => {
|
||||||
|
// when CSS anchor positioning is supported, this is only used to set the font
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
});
|
||||||
|
|
||||||
|
return anchor;
|
||||||
|
}
|
||||||
|
|
||||||
|
get headings() {
|
||||||
|
return this.querySelectorAll(this.selector.split(",").map(entry => `${entry.trim()}[id]`));
|
||||||
|
}
|
||||||
|
|
||||||
|
get selector() {
|
||||||
|
return this.getAttribute("selector") || HeadingAnchors.defaultSelector;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HeadingAnchors.register();
|
||||||
|
|
||||||
|
export { HeadingAnchors }
|
||||||
|
// Efficient iterative Fibonacci
|
||||||
|
function fib(n) {
|
||||||
|
if (n < 2) return n;
|
||||||
|
let a = 0, b = 1;
|
||||||
|
for (let i = 2; i <= n; i++) {
|
||||||
|
[a, b] = [b, a + b];
|
||||||
|
}
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
window.onload = function() {
|
||||||
|
const n = 8; // Example: change this number or get it dynamically
|
||||||
|
const result = fib(n);
|
||||||
|
document.getElementById("fib").textContent = `F(${n}) = ${result}`;
|
||||||
|
};
|
||||||
237
dist/fqsEuG6ZcE.js
vendored
Normal file
|
|
@ -0,0 +1,237 @@
|
||||||
|
// Thank you to https://github.com/daviddarnes/heading-anchors
|
||||||
|
// Thank you to https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
|
||||||
|
let globalInstanceIndex = 0;
|
||||||
|
|
||||||
|
class HeadingAnchors extends HTMLElement {
|
||||||
|
static register(tagName = "heading-anchors", registry = window.customElements) {
|
||||||
|
if(registry && !registry.get(tagName)) {
|
||||||
|
registry.define(tagName, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static attributes = {
|
||||||
|
exclude: "data-ha-exclude",
|
||||||
|
prefix: "prefix",
|
||||||
|
content: "content",
|
||||||
|
}
|
||||||
|
|
||||||
|
static classes = {
|
||||||
|
anchor: "ha",
|
||||||
|
placeholder: "ha-placeholder",
|
||||||
|
srOnly: "ha-visualhide",
|
||||||
|
}
|
||||||
|
|
||||||
|
static defaultSelector = "h2,h3,h4,h5,h6";
|
||||||
|
|
||||||
|
static css = `
|
||||||
|
.${HeadingAnchors.classes.srOnly} {
|
||||||
|
clip: rect(0 0 0 0);
|
||||||
|
height: 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: absolute;
|
||||||
|
width: 1px;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: var(--ha_offsetx);
|
||||||
|
top: var(--ha_offsety);
|
||||||
|
text-decoration: none;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
opacity: .3;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor}:is(:focus-within, :hover) {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor},
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 .25em;
|
||||||
|
|
||||||
|
/* Disable selection of visually hidden label */
|
||||||
|
-webkit-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@supports (anchor-name: none) {
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: anchor(left);
|
||||||
|
top: anchor(top);
|
||||||
|
}
|
||||||
|
}`;
|
||||||
|
|
||||||
|
get supports() {
|
||||||
|
return "replaceSync" in CSSStyleSheet.prototype;
|
||||||
|
}
|
||||||
|
|
||||||
|
get supportsAnchorPosition() {
|
||||||
|
return CSS.supports("anchor-name: none");
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
if(!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sheet = new CSSStyleSheet();
|
||||||
|
sheet.replaceSync(HeadingAnchors.css);
|
||||||
|
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
|
||||||
|
|
||||||
|
this.headingStyles = {};
|
||||||
|
this.instanceIndex = globalInstanceIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
if (!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.headings.forEach((heading, index) => {
|
||||||
|
if(!heading.hasAttribute(HeadingAnchors.attributes.exclude)) {
|
||||||
|
let anchor = this.getAnchorElement(heading);
|
||||||
|
let placeholder = this.getPlaceholderElement();
|
||||||
|
|
||||||
|
// Prefers anchor position approach for better accessibility
|
||||||
|
// https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
let anchorName = `--ha_${this.instanceIndex}_${index}`;
|
||||||
|
placeholder.style.setProperty("anchor-name", anchorName);
|
||||||
|
anchor.style.positionAnchor = anchorName;
|
||||||
|
}
|
||||||
|
|
||||||
|
heading.appendChild(placeholder);
|
||||||
|
heading.after(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchorFromPlaceholder(placeholder) {
|
||||||
|
if(!placeholder) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let heading = placeholder.closest("h1,h2,h3,h4,h5,h6");
|
||||||
|
if(!heading.nextElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO next element could be more defensive
|
||||||
|
this.positionAnchor(heading.nextElementSibling);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchor(anchor) {
|
||||||
|
if(!anchor || !anchor.previousElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO previous element could be more defensive
|
||||||
|
let heading = anchor.previousElementSibling;
|
||||||
|
this.setFontProp(heading, anchor);
|
||||||
|
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
// quit early
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
anchor.style.setProperty("--ha_offsetx", `${placeholder.offsetLeft}px`);
|
||||||
|
anchor.style.setProperty("--ha_offsety", `${placeholder.offsetTop}px`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setFontProp(heading, anchor) {
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
let style = getComputedStyle(placeholder);
|
||||||
|
let props = ["font-weight", "font-size", "line-height", "font-family"];
|
||||||
|
let [weight, size, lh, family] = props.map(name => style.getPropertyValue(name));
|
||||||
|
anchor.style.setProperty("font", `${weight} ${size}/${lh} ${family}`);
|
||||||
|
let vars = style.getPropertyValue("font-variation-settings");
|
||||||
|
if(vars) {
|
||||||
|
anchor.style.setProperty("font-variation-settings", vars);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getAccessibleTextPrefix() {
|
||||||
|
// Useful for i18n
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.prefix) || "Jump to section titled";
|
||||||
|
}
|
||||||
|
|
||||||
|
getContent() {
|
||||||
|
if(this.hasAttribute(HeadingAnchors.attributes.content)) {
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.content);
|
||||||
|
}
|
||||||
|
return "#";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Placeholder nests inside of heading
|
||||||
|
getPlaceholderElement() {
|
||||||
|
let ph = document.createElement("span");
|
||||||
|
ph.setAttribute("aria-hidden", true);
|
||||||
|
ph.classList.add(HeadingAnchors.classes.placeholder);
|
||||||
|
let content = this.getContent();
|
||||||
|
if(content) {
|
||||||
|
ph.textContent = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
ph.addEventListener("mouseover", (e) => {
|
||||||
|
let placeholder = e.target.closest(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
this.positionAnchorFromPlaceholder(placeholder);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return ph;
|
||||||
|
}
|
||||||
|
|
||||||
|
getAnchorElement(heading) {
|
||||||
|
let anchor = document.createElement("a");
|
||||||
|
anchor.href = `#${heading.id}`;
|
||||||
|
anchor.classList.add(HeadingAnchors.classes.anchor);
|
||||||
|
|
||||||
|
let content = this.getContent();
|
||||||
|
anchor.innerHTML = `<span class="${HeadingAnchors.classes.srOnly}">${this.getAccessibleTextPrefix()}: ${heading.textContent}</span>${content ? `<span aria-hidden="true">${content}</span>` : ""}`;
|
||||||
|
|
||||||
|
anchor.addEventListener("focus", e => {
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
if(anchor) {
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
anchor.addEventListener("mouseover", (e) => {
|
||||||
|
// when CSS anchor positioning is supported, this is only used to set the font
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
});
|
||||||
|
|
||||||
|
return anchor;
|
||||||
|
}
|
||||||
|
|
||||||
|
get headings() {
|
||||||
|
return this.querySelectorAll(this.selector.split(",").map(entry => `${entry.trim()}[id]`));
|
||||||
|
}
|
||||||
|
|
||||||
|
get selector() {
|
||||||
|
return this.getAttribute("selector") || HeadingAnchors.defaultSelector;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HeadingAnchors.register();
|
||||||
|
|
||||||
|
export { HeadingAnchors }
|
||||||
|
window.onload = function() {
|
||||||
|
const n = 1; // Example: change this number or get it dynamically
|
||||||
|
const result = fib(n);
|
||||||
|
document.getElementById("fib_seq").textContent = n;
|
||||||
|
document.getElementById("fib_num").textContent = result; // Doesn't render properly on mobile; figure out subscript numbers...
|
||||||
|
};
|
||||||
236
dist/gi1CLnzG-d.js
vendored
Normal file
|
|
@ -0,0 +1,236 @@
|
||||||
|
// Thank you to https://github.com/daviddarnes/heading-anchors
|
||||||
|
// Thank you to https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
|
||||||
|
let globalInstanceIndex = 0;
|
||||||
|
|
||||||
|
class HeadingAnchors extends HTMLElement {
|
||||||
|
static register(tagName = "heading-anchors", registry = window.customElements) {
|
||||||
|
if(registry && !registry.get(tagName)) {
|
||||||
|
registry.define(tagName, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static attributes = {
|
||||||
|
exclude: "data-ha-exclude",
|
||||||
|
prefix: "prefix",
|
||||||
|
content: "content",
|
||||||
|
}
|
||||||
|
|
||||||
|
static classes = {
|
||||||
|
anchor: "ha",
|
||||||
|
placeholder: "ha-placeholder",
|
||||||
|
srOnly: "ha-visualhide",
|
||||||
|
}
|
||||||
|
|
||||||
|
static defaultSelector = "h2,h3,h4,h5,h6";
|
||||||
|
|
||||||
|
static css = `
|
||||||
|
.${HeadingAnchors.classes.srOnly} {
|
||||||
|
clip: rect(0 0 0 0);
|
||||||
|
height: 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: absolute;
|
||||||
|
width: 1px;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: var(--ha_offsetx);
|
||||||
|
top: var(--ha_offsety);
|
||||||
|
text-decoration: none;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
opacity: .3;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor}:is(:focus-within, :hover) {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor},
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 .25em;
|
||||||
|
|
||||||
|
/* Disable selection of visually hidden label */
|
||||||
|
-webkit-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@supports (anchor-name: none) {
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: anchor(left);
|
||||||
|
top: anchor(top);
|
||||||
|
}
|
||||||
|
}`;
|
||||||
|
|
||||||
|
get supports() {
|
||||||
|
return "replaceSync" in CSSStyleSheet.prototype;
|
||||||
|
}
|
||||||
|
|
||||||
|
get supportsAnchorPosition() {
|
||||||
|
return CSS.supports("anchor-name: none");
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
if(!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sheet = new CSSStyleSheet();
|
||||||
|
sheet.replaceSync(HeadingAnchors.css);
|
||||||
|
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
|
||||||
|
|
||||||
|
this.headingStyles = {};
|
||||||
|
this.instanceIndex = globalInstanceIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
if (!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.headings.forEach((heading, index) => {
|
||||||
|
if(!heading.hasAttribute(HeadingAnchors.attributes.exclude)) {
|
||||||
|
let anchor = this.getAnchorElement(heading);
|
||||||
|
let placeholder = this.getPlaceholderElement();
|
||||||
|
|
||||||
|
// Prefers anchor position approach for better accessibility
|
||||||
|
// https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
let anchorName = `--ha_${this.instanceIndex}_${index}`;
|
||||||
|
placeholder.style.setProperty("anchor-name", anchorName);
|
||||||
|
anchor.style.positionAnchor = anchorName;
|
||||||
|
}
|
||||||
|
|
||||||
|
heading.appendChild(placeholder);
|
||||||
|
heading.after(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchorFromPlaceholder(placeholder) {
|
||||||
|
if(!placeholder) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let heading = placeholder.closest("h1,h2,h3,h4,h5,h6");
|
||||||
|
if(!heading.nextElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO next element could be more defensive
|
||||||
|
this.positionAnchor(heading.nextElementSibling);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchor(anchor) {
|
||||||
|
if(!anchor || !anchor.previousElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO previous element could be more defensive
|
||||||
|
let heading = anchor.previousElementSibling;
|
||||||
|
this.setFontProp(heading, anchor);
|
||||||
|
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
// quit early
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
anchor.style.setProperty("--ha_offsetx", `${placeholder.offsetLeft}px`);
|
||||||
|
anchor.style.setProperty("--ha_offsety", `${placeholder.offsetTop}px`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setFontProp(heading, anchor) {
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
let style = getComputedStyle(placeholder);
|
||||||
|
let props = ["font-weight", "font-size", "line-height", "font-family"];
|
||||||
|
let [weight, size, lh, family] = props.map(name => style.getPropertyValue(name));
|
||||||
|
anchor.style.setProperty("font", `${weight} ${size}/${lh} ${family}`);
|
||||||
|
let vars = style.getPropertyValue("font-variation-settings");
|
||||||
|
if(vars) {
|
||||||
|
anchor.style.setProperty("font-variation-settings", vars);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getAccessibleTextPrefix() {
|
||||||
|
// Useful for i18n
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.prefix) || "Jump to section titled";
|
||||||
|
}
|
||||||
|
|
||||||
|
getContent() {
|
||||||
|
if(this.hasAttribute(HeadingAnchors.attributes.content)) {
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.content);
|
||||||
|
}
|
||||||
|
return "#";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Placeholder nests inside of heading
|
||||||
|
getPlaceholderElement() {
|
||||||
|
let ph = document.createElement("span");
|
||||||
|
ph.setAttribute("aria-hidden", true);
|
||||||
|
ph.classList.add(HeadingAnchors.classes.placeholder);
|
||||||
|
let content = this.getContent();
|
||||||
|
if(content) {
|
||||||
|
ph.textContent = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
ph.addEventListener("mouseover", (e) => {
|
||||||
|
let placeholder = e.target.closest(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
this.positionAnchorFromPlaceholder(placeholder);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return ph;
|
||||||
|
}
|
||||||
|
|
||||||
|
getAnchorElement(heading) {
|
||||||
|
let anchor = document.createElement("a");
|
||||||
|
anchor.href = `#${heading.id}`;
|
||||||
|
anchor.classList.add(HeadingAnchors.classes.anchor);
|
||||||
|
|
||||||
|
let content = this.getContent();
|
||||||
|
anchor.innerHTML = `<span class="${HeadingAnchors.classes.srOnly}">${this.getAccessibleTextPrefix()}: ${heading.textContent}</span>${content ? `<span aria-hidden="true">${content}</span>` : ""}`;
|
||||||
|
|
||||||
|
anchor.addEventListener("focus", e => {
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
if(anchor) {
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
anchor.addEventListener("mouseover", (e) => {
|
||||||
|
// when CSS anchor positioning is supported, this is only used to set the font
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
});
|
||||||
|
|
||||||
|
return anchor;
|
||||||
|
}
|
||||||
|
|
||||||
|
get headings() {
|
||||||
|
return this.querySelectorAll(this.selector.split(",").map(entry => `${entry.trim()}[id]`));
|
||||||
|
}
|
||||||
|
|
||||||
|
get selector() {
|
||||||
|
return this.getAttribute("selector") || HeadingAnchors.defaultSelector;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HeadingAnchors.register();
|
||||||
|
|
||||||
|
export { HeadingAnchors }
|
||||||
|
window.onload = function() {
|
||||||
|
const n = 10; // Example: change this number or get it dynamically
|
||||||
|
const result = fib(n);
|
||||||
|
document.getElementById("fib").textContent = `F(${n}) = ${result}`;
|
||||||
|
};
|
||||||
244
dist/gjkXJxzy_W.js
vendored
Normal file
|
|
@ -0,0 +1,244 @@
|
||||||
|
// Thank you to https://github.com/daviddarnes/heading-anchors
|
||||||
|
// Thank you to https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
|
||||||
|
let globalInstanceIndex = 0;
|
||||||
|
|
||||||
|
class HeadingAnchors extends HTMLElement {
|
||||||
|
static register(tagName = "heading-anchors", registry = window.customElements) {
|
||||||
|
if(registry && !registry.get(tagName)) {
|
||||||
|
registry.define(tagName, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static attributes = {
|
||||||
|
exclude: "data-ha-exclude",
|
||||||
|
prefix: "prefix",
|
||||||
|
content: "content",
|
||||||
|
}
|
||||||
|
|
||||||
|
static classes = {
|
||||||
|
anchor: "ha",
|
||||||
|
placeholder: "ha-placeholder",
|
||||||
|
srOnly: "ha-visualhide",
|
||||||
|
}
|
||||||
|
|
||||||
|
static defaultSelector = "h2,h3,h4,h5,h6";
|
||||||
|
|
||||||
|
static css = `
|
||||||
|
.${HeadingAnchors.classes.srOnly} {
|
||||||
|
clip: rect(0 0 0 0);
|
||||||
|
height: 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: absolute;
|
||||||
|
width: 1px;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: var(--ha_offsetx);
|
||||||
|
top: var(--ha_offsety);
|
||||||
|
text-decoration: none;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
opacity: .3;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor}:is(:focus-within, :hover) {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor},
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 .25em;
|
||||||
|
|
||||||
|
/* Disable selection of visually hidden label */
|
||||||
|
-webkit-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@supports (anchor-name: none) {
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: anchor(left);
|
||||||
|
top: anchor(top);
|
||||||
|
}
|
||||||
|
}`;
|
||||||
|
|
||||||
|
get supports() {
|
||||||
|
return "replaceSync" in CSSStyleSheet.prototype;
|
||||||
|
}
|
||||||
|
|
||||||
|
get supportsAnchorPosition() {
|
||||||
|
return CSS.supports("anchor-name: none");
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
if(!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sheet = new CSSStyleSheet();
|
||||||
|
sheet.replaceSync(HeadingAnchors.css);
|
||||||
|
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
|
||||||
|
|
||||||
|
this.headingStyles = {};
|
||||||
|
this.instanceIndex = globalInstanceIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
if (!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.headings.forEach((heading, index) => {
|
||||||
|
if(!heading.hasAttribute(HeadingAnchors.attributes.exclude)) {
|
||||||
|
let anchor = this.getAnchorElement(heading);
|
||||||
|
let placeholder = this.getPlaceholderElement();
|
||||||
|
|
||||||
|
// Prefers anchor position approach for better accessibility
|
||||||
|
// https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
let anchorName = `--ha_${this.instanceIndex}_${index}`;
|
||||||
|
placeholder.style.setProperty("anchor-name", anchorName);
|
||||||
|
anchor.style.positionAnchor = anchorName;
|
||||||
|
}
|
||||||
|
|
||||||
|
heading.appendChild(placeholder);
|
||||||
|
heading.after(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchorFromPlaceholder(placeholder) {
|
||||||
|
if(!placeholder) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let heading = placeholder.closest("h1,h2,h3,h4,h5,h6");
|
||||||
|
if(!heading.nextElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO next element could be more defensive
|
||||||
|
this.positionAnchor(heading.nextElementSibling);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchor(anchor) {
|
||||||
|
if(!anchor || !anchor.previousElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO previous element could be more defensive
|
||||||
|
let heading = anchor.previousElementSibling;
|
||||||
|
this.setFontProp(heading, anchor);
|
||||||
|
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
// quit early
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
anchor.style.setProperty("--ha_offsetx", `${placeholder.offsetLeft}px`);
|
||||||
|
anchor.style.setProperty("--ha_offsety", `${placeholder.offsetTop}px`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setFontProp(heading, anchor) {
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
let style = getComputedStyle(placeholder);
|
||||||
|
let props = ["font-weight", "font-size", "line-height", "font-family"];
|
||||||
|
let [weight, size, lh, family] = props.map(name => style.getPropertyValue(name));
|
||||||
|
anchor.style.setProperty("font", `${weight} ${size}/${lh} ${family}`);
|
||||||
|
let vars = style.getPropertyValue("font-variation-settings");
|
||||||
|
if(vars) {
|
||||||
|
anchor.style.setProperty("font-variation-settings", vars);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getAccessibleTextPrefix() {
|
||||||
|
// Useful for i18n
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.prefix) || "Jump to section titled";
|
||||||
|
}
|
||||||
|
|
||||||
|
getContent() {
|
||||||
|
if(this.hasAttribute(HeadingAnchors.attributes.content)) {
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.content);
|
||||||
|
}
|
||||||
|
return "#";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Placeholder nests inside of heading
|
||||||
|
getPlaceholderElement() {
|
||||||
|
let ph = document.createElement("span");
|
||||||
|
ph.setAttribute("aria-hidden", true);
|
||||||
|
ph.classList.add(HeadingAnchors.classes.placeholder);
|
||||||
|
let content = this.getContent();
|
||||||
|
if(content) {
|
||||||
|
ph.textContent = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
ph.addEventListener("mouseover", (e) => {
|
||||||
|
let placeholder = e.target.closest(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
this.positionAnchorFromPlaceholder(placeholder);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return ph;
|
||||||
|
}
|
||||||
|
|
||||||
|
getAnchorElement(heading) {
|
||||||
|
let anchor = document.createElement("a");
|
||||||
|
anchor.href = `#${heading.id}`;
|
||||||
|
anchor.classList.add(HeadingAnchors.classes.anchor);
|
||||||
|
|
||||||
|
let content = this.getContent();
|
||||||
|
anchor.innerHTML = `<span class="${HeadingAnchors.classes.srOnly}">${this.getAccessibleTextPrefix()}: ${heading.textContent}</span>${content ? `<span aria-hidden="true">${content}</span>` : ""}`;
|
||||||
|
|
||||||
|
anchor.addEventListener("focus", e => {
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
if(anchor) {
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
anchor.addEventListener("mouseover", (e) => {
|
||||||
|
// when CSS anchor positioning is supported, this is only used to set the font
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
});
|
||||||
|
|
||||||
|
return anchor;
|
||||||
|
}
|
||||||
|
|
||||||
|
get headings() {
|
||||||
|
return this.querySelectorAll(this.selector.split(",").map(entry => `${entry.trim()}[id]`));
|
||||||
|
}
|
||||||
|
|
||||||
|
get selector() {
|
||||||
|
return this.getAttribute("selector") || HeadingAnchors.defaultSelector;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HeadingAnchors.register();
|
||||||
|
|
||||||
|
export { HeadingAnchors }
|
||||||
|
// Efficient iterative Fibonacci
|
||||||
|
function fib(n) {
|
||||||
|
if (n < 2) return n;
|
||||||
|
let a = 0, b = 1;
|
||||||
|
for (let i = 2; i <= n; i++) {
|
||||||
|
[a, b] = [b, a + b];
|
||||||
|
}
|
||||||
|
document.getElementById("fib_seq").textContent = n;
|
||||||
|
document.getElementById("fib_num").textContent = result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
window.onload = fib(1);
|
||||||
237
dist/iKirDyD45_.js
vendored
Normal file
|
|
@ -0,0 +1,237 @@
|
||||||
|
// Thank you to https://github.com/daviddarnes/heading-anchors
|
||||||
|
// Thank you to https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
|
||||||
|
let globalInstanceIndex = 0;
|
||||||
|
|
||||||
|
class HeadingAnchors extends HTMLElement {
|
||||||
|
static register(tagName = "heading-anchors", registry = window.customElements) {
|
||||||
|
if(registry && !registry.get(tagName)) {
|
||||||
|
registry.define(tagName, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static attributes = {
|
||||||
|
exclude: "data-ha-exclude",
|
||||||
|
prefix: "prefix",
|
||||||
|
content: "content",
|
||||||
|
}
|
||||||
|
|
||||||
|
static classes = {
|
||||||
|
anchor: "ha",
|
||||||
|
placeholder: "ha-placeholder",
|
||||||
|
srOnly: "ha-visualhide",
|
||||||
|
}
|
||||||
|
|
||||||
|
static defaultSelector = "h2,h3,h4,h5,h6";
|
||||||
|
|
||||||
|
static css = `
|
||||||
|
.${HeadingAnchors.classes.srOnly} {
|
||||||
|
clip: rect(0 0 0 0);
|
||||||
|
height: 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: absolute;
|
||||||
|
width: 1px;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: var(--ha_offsetx);
|
||||||
|
top: var(--ha_offsety);
|
||||||
|
text-decoration: none;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
opacity: .3;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor}:is(:focus-within, :hover) {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor},
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 .25em;
|
||||||
|
|
||||||
|
/* Disable selection of visually hidden label */
|
||||||
|
-webkit-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@supports (anchor-name: none) {
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: anchor(left);
|
||||||
|
top: anchor(top);
|
||||||
|
}
|
||||||
|
}`;
|
||||||
|
|
||||||
|
get supports() {
|
||||||
|
return "replaceSync" in CSSStyleSheet.prototype;
|
||||||
|
}
|
||||||
|
|
||||||
|
get supportsAnchorPosition() {
|
||||||
|
return CSS.supports("anchor-name: none");
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
if(!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sheet = new CSSStyleSheet();
|
||||||
|
sheet.replaceSync(HeadingAnchors.css);
|
||||||
|
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
|
||||||
|
|
||||||
|
this.headingStyles = {};
|
||||||
|
this.instanceIndex = globalInstanceIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
if (!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.headings.forEach((heading, index) => {
|
||||||
|
if(!heading.hasAttribute(HeadingAnchors.attributes.exclude)) {
|
||||||
|
let anchor = this.getAnchorElement(heading);
|
||||||
|
let placeholder = this.getPlaceholderElement();
|
||||||
|
|
||||||
|
// Prefers anchor position approach for better accessibility
|
||||||
|
// https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
let anchorName = `--ha_${this.instanceIndex}_${index}`;
|
||||||
|
placeholder.style.setProperty("anchor-name", anchorName);
|
||||||
|
anchor.style.positionAnchor = anchorName;
|
||||||
|
}
|
||||||
|
|
||||||
|
heading.appendChild(placeholder);
|
||||||
|
heading.after(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchorFromPlaceholder(placeholder) {
|
||||||
|
if(!placeholder) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let heading = placeholder.closest("h1,h2,h3,h4,h5,h6");
|
||||||
|
if(!heading.nextElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO next element could be more defensive
|
||||||
|
this.positionAnchor(heading.nextElementSibling);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchor(anchor) {
|
||||||
|
if(!anchor || !anchor.previousElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO previous element could be more defensive
|
||||||
|
let heading = anchor.previousElementSibling;
|
||||||
|
this.setFontProp(heading, anchor);
|
||||||
|
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
// quit early
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
anchor.style.setProperty("--ha_offsetx", `${placeholder.offsetLeft}px`);
|
||||||
|
anchor.style.setProperty("--ha_offsety", `${placeholder.offsetTop}px`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setFontProp(heading, anchor) {
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
let style = getComputedStyle(placeholder);
|
||||||
|
let props = ["font-weight", "font-size", "line-height", "font-family"];
|
||||||
|
let [weight, size, lh, family] = props.map(name => style.getPropertyValue(name));
|
||||||
|
anchor.style.setProperty("font", `${weight} ${size}/${lh} ${family}`);
|
||||||
|
let vars = style.getPropertyValue("font-variation-settings");
|
||||||
|
if(vars) {
|
||||||
|
anchor.style.setProperty("font-variation-settings", vars);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getAccessibleTextPrefix() {
|
||||||
|
// Useful for i18n
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.prefix) || "Jump to section titled";
|
||||||
|
}
|
||||||
|
|
||||||
|
getContent() {
|
||||||
|
if(this.hasAttribute(HeadingAnchors.attributes.content)) {
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.content);
|
||||||
|
}
|
||||||
|
return "#";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Placeholder nests inside of heading
|
||||||
|
getPlaceholderElement() {
|
||||||
|
let ph = document.createElement("span");
|
||||||
|
ph.setAttribute("aria-hidden", true);
|
||||||
|
ph.classList.add(HeadingAnchors.classes.placeholder);
|
||||||
|
let content = this.getContent();
|
||||||
|
if(content) {
|
||||||
|
ph.textContent = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
ph.addEventListener("mouseover", (e) => {
|
||||||
|
let placeholder = e.target.closest(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
this.positionAnchorFromPlaceholder(placeholder);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return ph;
|
||||||
|
}
|
||||||
|
|
||||||
|
getAnchorElement(heading) {
|
||||||
|
let anchor = document.createElement("a");
|
||||||
|
anchor.href = `#${heading.id}`;
|
||||||
|
anchor.classList.add(HeadingAnchors.classes.anchor);
|
||||||
|
|
||||||
|
let content = this.getContent();
|
||||||
|
anchor.innerHTML = `<span class="${HeadingAnchors.classes.srOnly}">${this.getAccessibleTextPrefix()}: ${heading.textContent}</span>${content ? `<span aria-hidden="true">${content}</span>` : ""}`;
|
||||||
|
|
||||||
|
anchor.addEventListener("focus", e => {
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
if(anchor) {
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
anchor.addEventListener("mouseover", (e) => {
|
||||||
|
// when CSS anchor positioning is supported, this is only used to set the font
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
});
|
||||||
|
|
||||||
|
return anchor;
|
||||||
|
}
|
||||||
|
|
||||||
|
get headings() {
|
||||||
|
return this.querySelectorAll(this.selector.split(",").map(entry => `${entry.trim()}[id]`));
|
||||||
|
}
|
||||||
|
|
||||||
|
get selector() {
|
||||||
|
return this.getAttribute("selector") || HeadingAnchors.defaultSelector;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HeadingAnchors.register();
|
||||||
|
|
||||||
|
export { HeadingAnchors }
|
||||||
|
window.onload = function() {
|
||||||
|
const n = 8; // Example: change this number or get it dynamically
|
||||||
|
const result = fib(n);
|
||||||
|
document.getElementById("fib_seq").textContent = n;
|
||||||
|
document.getElementById("fib_num").textContent = result; // Doesn't render properly on mobile; figure out subscript numbers...
|
||||||
|
};
|
||||||
247
dist/iuKmc0VMzy.js
vendored
Normal file
|
|
@ -0,0 +1,247 @@
|
||||||
|
// Thank you to https://github.com/daviddarnes/heading-anchors
|
||||||
|
// Thank you to https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
|
||||||
|
let globalInstanceIndex = 0;
|
||||||
|
|
||||||
|
class HeadingAnchors extends HTMLElement {
|
||||||
|
static register(tagName = "heading-anchors", registry = window.customElements) {
|
||||||
|
if(registry && !registry.get(tagName)) {
|
||||||
|
registry.define(tagName, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static attributes = {
|
||||||
|
exclude: "data-ha-exclude",
|
||||||
|
prefix: "prefix",
|
||||||
|
content: "content",
|
||||||
|
}
|
||||||
|
|
||||||
|
static classes = {
|
||||||
|
anchor: "ha",
|
||||||
|
placeholder: "ha-placeholder",
|
||||||
|
srOnly: "ha-visualhide",
|
||||||
|
}
|
||||||
|
|
||||||
|
static defaultSelector = "h2,h3,h4,h5,h6";
|
||||||
|
|
||||||
|
static css = `
|
||||||
|
.${HeadingAnchors.classes.srOnly} {
|
||||||
|
clip: rect(0 0 0 0);
|
||||||
|
height: 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: absolute;
|
||||||
|
width: 1px;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: var(--ha_offsetx);
|
||||||
|
top: var(--ha_offsety);
|
||||||
|
text-decoration: none;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
opacity: .3;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor}:is(:focus-within, :hover) {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor},
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 .25em;
|
||||||
|
|
||||||
|
/* Disable selection of visually hidden label */
|
||||||
|
-webkit-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@supports (anchor-name: none) {
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: anchor(left);
|
||||||
|
top: anchor(top);
|
||||||
|
}
|
||||||
|
}`;
|
||||||
|
|
||||||
|
get supports() {
|
||||||
|
return "replaceSync" in CSSStyleSheet.prototype;
|
||||||
|
}
|
||||||
|
|
||||||
|
get supportsAnchorPosition() {
|
||||||
|
return CSS.supports("anchor-name: none");
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
if(!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sheet = new CSSStyleSheet();
|
||||||
|
sheet.replaceSync(HeadingAnchors.css);
|
||||||
|
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
|
||||||
|
|
||||||
|
this.headingStyles = {};
|
||||||
|
this.instanceIndex = globalInstanceIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
if (!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.headings.forEach((heading, index) => {
|
||||||
|
if(!heading.hasAttribute(HeadingAnchors.attributes.exclude)) {
|
||||||
|
let anchor = this.getAnchorElement(heading);
|
||||||
|
let placeholder = this.getPlaceholderElement();
|
||||||
|
|
||||||
|
// Prefers anchor position approach for better accessibility
|
||||||
|
// https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
let anchorName = `--ha_${this.instanceIndex}_${index}`;
|
||||||
|
placeholder.style.setProperty("anchor-name", anchorName);
|
||||||
|
anchor.style.positionAnchor = anchorName;
|
||||||
|
}
|
||||||
|
|
||||||
|
heading.appendChild(placeholder);
|
||||||
|
heading.after(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchorFromPlaceholder(placeholder) {
|
||||||
|
if(!placeholder) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let heading = placeholder.closest("h1,h2,h3,h4,h5,h6");
|
||||||
|
if(!heading.nextElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO next element could be more defensive
|
||||||
|
this.positionAnchor(heading.nextElementSibling);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchor(anchor) {
|
||||||
|
if(!anchor || !anchor.previousElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO previous element could be more defensive
|
||||||
|
let heading = anchor.previousElementSibling;
|
||||||
|
this.setFontProp(heading, anchor);
|
||||||
|
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
// quit early
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
anchor.style.setProperty("--ha_offsetx", `${placeholder.offsetLeft}px`);
|
||||||
|
anchor.style.setProperty("--ha_offsety", `${placeholder.offsetTop}px`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setFontProp(heading, anchor) {
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
let style = getComputedStyle(placeholder);
|
||||||
|
let props = ["font-weight", "font-size", "line-height", "font-family"];
|
||||||
|
let [weight, size, lh, family] = props.map(name => style.getPropertyValue(name));
|
||||||
|
anchor.style.setProperty("font", `${weight} ${size}/${lh} ${family}`);
|
||||||
|
let vars = style.getPropertyValue("font-variation-settings");
|
||||||
|
if(vars) {
|
||||||
|
anchor.style.setProperty("font-variation-settings", vars);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getAccessibleTextPrefix() {
|
||||||
|
// Useful for i18n
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.prefix) || "Jump to section titled";
|
||||||
|
}
|
||||||
|
|
||||||
|
getContent() {
|
||||||
|
if(this.hasAttribute(HeadingAnchors.attributes.content)) {
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.content);
|
||||||
|
}
|
||||||
|
return "#";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Placeholder nests inside of heading
|
||||||
|
getPlaceholderElement() {
|
||||||
|
let ph = document.createElement("span");
|
||||||
|
ph.setAttribute("aria-hidden", true);
|
||||||
|
ph.classList.add(HeadingAnchors.classes.placeholder);
|
||||||
|
let content = this.getContent();
|
||||||
|
if(content) {
|
||||||
|
ph.textContent = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
ph.addEventListener("mouseover", (e) => {
|
||||||
|
let placeholder = e.target.closest(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
this.positionAnchorFromPlaceholder(placeholder);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return ph;
|
||||||
|
}
|
||||||
|
|
||||||
|
getAnchorElement(heading) {
|
||||||
|
let anchor = document.createElement("a");
|
||||||
|
anchor.href = `#${heading.id}`;
|
||||||
|
anchor.classList.add(HeadingAnchors.classes.anchor);
|
||||||
|
|
||||||
|
let content = this.getContent();
|
||||||
|
anchor.innerHTML = `<span class="${HeadingAnchors.classes.srOnly}">${this.getAccessibleTextPrefix()}: ${heading.textContent}</span>${content ? `<span aria-hidden="true">${content}</span>` : ""}`;
|
||||||
|
|
||||||
|
anchor.addEventListener("focus", e => {
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
if(anchor) {
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
anchor.addEventListener("mouseover", (e) => {
|
||||||
|
// when CSS anchor positioning is supported, this is only used to set the font
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
});
|
||||||
|
|
||||||
|
return anchor;
|
||||||
|
}
|
||||||
|
|
||||||
|
get headings() {
|
||||||
|
return this.querySelectorAll(this.selector.split(",").map(entry => `${entry.trim()}[id]`));
|
||||||
|
}
|
||||||
|
|
||||||
|
get selector() {
|
||||||
|
return this.getAttribute("selector") || HeadingAnchors.defaultSelector;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HeadingAnchors.register();
|
||||||
|
|
||||||
|
export { HeadingAnchors }
|
||||||
|
// Efficient iterative Fibonacci
|
||||||
|
function fib(n) {
|
||||||
|
if (n < 2) return n;
|
||||||
|
let a = 0, b = 1;
|
||||||
|
for (let i = 2; i <= n; i++) {
|
||||||
|
[a, b] = [b, a + b];
|
||||||
|
}
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run on page load
|
||||||
|
window.onload = function() {
|
||||||
|
const n = 1; // Example: change this number or get it dynamically
|
||||||
|
const result = fib(n);
|
||||||
|
document.getElementById("fib").textContent = `F(${n}) = ${result}`;
|
||||||
|
};
|
||||||
237
dist/ix_Ybx5UAh.js
vendored
Normal file
|
|
@ -0,0 +1,237 @@
|
||||||
|
// Thank you to https://github.com/daviddarnes/heading-anchors
|
||||||
|
// Thank you to https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
|
||||||
|
let globalInstanceIndex = 0;
|
||||||
|
|
||||||
|
class HeadingAnchors extends HTMLElement {
|
||||||
|
static register(tagName = "heading-anchors", registry = window.customElements) {
|
||||||
|
if(registry && !registry.get(tagName)) {
|
||||||
|
registry.define(tagName, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static attributes = {
|
||||||
|
exclude: "data-ha-exclude",
|
||||||
|
prefix: "prefix",
|
||||||
|
content: "content",
|
||||||
|
}
|
||||||
|
|
||||||
|
static classes = {
|
||||||
|
anchor: "ha",
|
||||||
|
placeholder: "ha-placeholder",
|
||||||
|
srOnly: "ha-visualhide",
|
||||||
|
}
|
||||||
|
|
||||||
|
static defaultSelector = "h2,h3,h4,h5,h6";
|
||||||
|
|
||||||
|
static css = `
|
||||||
|
.${HeadingAnchors.classes.srOnly} {
|
||||||
|
clip: rect(0 0 0 0);
|
||||||
|
height: 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: absolute;
|
||||||
|
width: 1px;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: var(--ha_offsetx);
|
||||||
|
top: var(--ha_offsety);
|
||||||
|
text-decoration: none;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
opacity: .3;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor}:is(:focus-within, :hover) {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor},
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 .25em;
|
||||||
|
|
||||||
|
/* Disable selection of visually hidden label */
|
||||||
|
-webkit-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@supports (anchor-name: none) {
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: anchor(left);
|
||||||
|
top: anchor(top);
|
||||||
|
}
|
||||||
|
}`;
|
||||||
|
|
||||||
|
get supports() {
|
||||||
|
return "replaceSync" in CSSStyleSheet.prototype;
|
||||||
|
}
|
||||||
|
|
||||||
|
get supportsAnchorPosition() {
|
||||||
|
return CSS.supports("anchor-name: none");
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
if(!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sheet = new CSSStyleSheet();
|
||||||
|
sheet.replaceSync(HeadingAnchors.css);
|
||||||
|
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
|
||||||
|
|
||||||
|
this.headingStyles = {};
|
||||||
|
this.instanceIndex = globalInstanceIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
if (!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.headings.forEach((heading, index) => {
|
||||||
|
if(!heading.hasAttribute(HeadingAnchors.attributes.exclude)) {
|
||||||
|
let anchor = this.getAnchorElement(heading);
|
||||||
|
let placeholder = this.getPlaceholderElement();
|
||||||
|
|
||||||
|
// Prefers anchor position approach for better accessibility
|
||||||
|
// https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
let anchorName = `--ha_${this.instanceIndex}_${index}`;
|
||||||
|
placeholder.style.setProperty("anchor-name", anchorName);
|
||||||
|
anchor.style.positionAnchor = anchorName;
|
||||||
|
}
|
||||||
|
|
||||||
|
heading.appendChild(placeholder);
|
||||||
|
heading.after(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchorFromPlaceholder(placeholder) {
|
||||||
|
if(!placeholder) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let heading = placeholder.closest("h1,h2,h3,h4,h5,h6");
|
||||||
|
if(!heading.nextElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO next element could be more defensive
|
||||||
|
this.positionAnchor(heading.nextElementSibling);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchor(anchor) {
|
||||||
|
if(!anchor || !anchor.previousElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO previous element could be more defensive
|
||||||
|
let heading = anchor.previousElementSibling;
|
||||||
|
this.setFontProp(heading, anchor);
|
||||||
|
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
// quit early
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
anchor.style.setProperty("--ha_offsetx", `${placeholder.offsetLeft}px`);
|
||||||
|
anchor.style.setProperty("--ha_offsety", `${placeholder.offsetTop}px`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setFontProp(heading, anchor) {
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
let style = getComputedStyle(placeholder);
|
||||||
|
let props = ["font-weight", "font-size", "line-height", "font-family"];
|
||||||
|
let [weight, size, lh, family] = props.map(name => style.getPropertyValue(name));
|
||||||
|
anchor.style.setProperty("font", `${weight} ${size}/${lh} ${family}`);
|
||||||
|
let vars = style.getPropertyValue("font-variation-settings");
|
||||||
|
if(vars) {
|
||||||
|
anchor.style.setProperty("font-variation-settings", vars);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getAccessibleTextPrefix() {
|
||||||
|
// Useful for i18n
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.prefix) || "Jump to section titled";
|
||||||
|
}
|
||||||
|
|
||||||
|
getContent() {
|
||||||
|
if(this.hasAttribute(HeadingAnchors.attributes.content)) {
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.content);
|
||||||
|
}
|
||||||
|
return "#";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Placeholder nests inside of heading
|
||||||
|
getPlaceholderElement() {
|
||||||
|
let ph = document.createElement("span");
|
||||||
|
ph.setAttribute("aria-hidden", true);
|
||||||
|
ph.classList.add(HeadingAnchors.classes.placeholder);
|
||||||
|
let content = this.getContent();
|
||||||
|
if(content) {
|
||||||
|
ph.textContent = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
ph.addEventListener("mouseover", (e) => {
|
||||||
|
let placeholder = e.target.closest(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
this.positionAnchorFromPlaceholder(placeholder);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return ph;
|
||||||
|
}
|
||||||
|
|
||||||
|
getAnchorElement(heading) {
|
||||||
|
let anchor = document.createElement("a");
|
||||||
|
anchor.href = `#${heading.id}`;
|
||||||
|
anchor.classList.add(HeadingAnchors.classes.anchor);
|
||||||
|
|
||||||
|
let content = this.getContent();
|
||||||
|
anchor.innerHTML = `<span class="${HeadingAnchors.classes.srOnly}">${this.getAccessibleTextPrefix()}: ${heading.textContent}</span>${content ? `<span aria-hidden="true">${content}</span>` : ""}`;
|
||||||
|
|
||||||
|
anchor.addEventListener("focus", e => {
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
if(anchor) {
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
anchor.addEventListener("mouseover", (e) => {
|
||||||
|
// when CSS anchor positioning is supported, this is only used to set the font
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
});
|
||||||
|
|
||||||
|
return anchor;
|
||||||
|
}
|
||||||
|
|
||||||
|
get headings() {
|
||||||
|
return this.querySelectorAll(this.selector.split(",").map(entry => `${entry.trim()}[id]`));
|
||||||
|
}
|
||||||
|
|
||||||
|
get selector() {
|
||||||
|
return this.getAttribute("selector") || HeadingAnchors.defaultSelector;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HeadingAnchors.register();
|
||||||
|
|
||||||
|
export { HeadingAnchors }
|
||||||
|
window.onload = function() {
|
||||||
|
const n = ; // Example: change this number or get it dynamically
|
||||||
|
const result = fib(n);
|
||||||
|
document.getElementById("fib_seq").textContent = n;
|
||||||
|
document.getElementById("fib_num").textContent = result; // Doesn't render properly on mobile; figure out subscript numbers...
|
||||||
|
};
|
||||||
237
dist/k7YWH9wzXb.js
vendored
Normal file
|
|
@ -0,0 +1,237 @@
|
||||||
|
// Thank you to https://github.com/daviddarnes/heading-anchors
|
||||||
|
// Thank you to https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
|
||||||
|
let globalInstanceIndex = 0;
|
||||||
|
|
||||||
|
class HeadingAnchors extends HTMLElement {
|
||||||
|
static register(tagName = "heading-anchors", registry = window.customElements) {
|
||||||
|
if(registry && !registry.get(tagName)) {
|
||||||
|
registry.define(tagName, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static attributes = {
|
||||||
|
exclude: "data-ha-exclude",
|
||||||
|
prefix: "prefix",
|
||||||
|
content: "content",
|
||||||
|
}
|
||||||
|
|
||||||
|
static classes = {
|
||||||
|
anchor: "ha",
|
||||||
|
placeholder: "ha-placeholder",
|
||||||
|
srOnly: "ha-visualhide",
|
||||||
|
}
|
||||||
|
|
||||||
|
static defaultSelector = "h2,h3,h4,h5,h6";
|
||||||
|
|
||||||
|
static css = `
|
||||||
|
.${HeadingAnchors.classes.srOnly} {
|
||||||
|
clip: rect(0 0 0 0);
|
||||||
|
height: 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: absolute;
|
||||||
|
width: 1px;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: var(--ha_offsetx);
|
||||||
|
top: var(--ha_offsety);
|
||||||
|
text-decoration: none;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
opacity: .3;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor}:is(:focus-within, :hover) {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor},
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 .25em;
|
||||||
|
|
||||||
|
/* Disable selection of visually hidden label */
|
||||||
|
-webkit-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@supports (anchor-name: none) {
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: anchor(left);
|
||||||
|
top: anchor(top);
|
||||||
|
}
|
||||||
|
}`;
|
||||||
|
|
||||||
|
get supports() {
|
||||||
|
return "replaceSync" in CSSStyleSheet.prototype;
|
||||||
|
}
|
||||||
|
|
||||||
|
get supportsAnchorPosition() {
|
||||||
|
return CSS.supports("anchor-name: none");
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
if(!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sheet = new CSSStyleSheet();
|
||||||
|
sheet.replaceSync(HeadingAnchors.css);
|
||||||
|
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
|
||||||
|
|
||||||
|
this.headingStyles = {};
|
||||||
|
this.instanceIndex = globalInstanceIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
if (!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.headings.forEach((heading, index) => {
|
||||||
|
if(!heading.hasAttribute(HeadingAnchors.attributes.exclude)) {
|
||||||
|
let anchor = this.getAnchorElement(heading);
|
||||||
|
let placeholder = this.getPlaceholderElement();
|
||||||
|
|
||||||
|
// Prefers anchor position approach for better accessibility
|
||||||
|
// https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
let anchorName = `--ha_${this.instanceIndex}_${index}`;
|
||||||
|
placeholder.style.setProperty("anchor-name", anchorName);
|
||||||
|
anchor.style.positionAnchor = anchorName;
|
||||||
|
}
|
||||||
|
|
||||||
|
heading.appendChild(placeholder);
|
||||||
|
heading.after(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchorFromPlaceholder(placeholder) {
|
||||||
|
if(!placeholder) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let heading = placeholder.closest("h1,h2,h3,h4,h5,h6");
|
||||||
|
if(!heading.nextElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO next element could be more defensive
|
||||||
|
this.positionAnchor(heading.nextElementSibling);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchor(anchor) {
|
||||||
|
if(!anchor || !anchor.previousElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO previous element could be more defensive
|
||||||
|
let heading = anchor.previousElementSibling;
|
||||||
|
this.setFontProp(heading, anchor);
|
||||||
|
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
// quit early
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
anchor.style.setProperty("--ha_offsetx", `${placeholder.offsetLeft}px`);
|
||||||
|
anchor.style.setProperty("--ha_offsety", `${placeholder.offsetTop}px`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setFontProp(heading, anchor) {
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
let style = getComputedStyle(placeholder);
|
||||||
|
let props = ["font-weight", "font-size", "line-height", "font-family"];
|
||||||
|
let [weight, size, lh, family] = props.map(name => style.getPropertyValue(name));
|
||||||
|
anchor.style.setProperty("font", `${weight} ${size}/${lh} ${family}`);
|
||||||
|
let vars = style.getPropertyValue("font-variation-settings");
|
||||||
|
if(vars) {
|
||||||
|
anchor.style.setProperty("font-variation-settings", vars);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getAccessibleTextPrefix() {
|
||||||
|
// Useful for i18n
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.prefix) || "Jump to section titled";
|
||||||
|
}
|
||||||
|
|
||||||
|
getContent() {
|
||||||
|
if(this.hasAttribute(HeadingAnchors.attributes.content)) {
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.content);
|
||||||
|
}
|
||||||
|
return "#";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Placeholder nests inside of heading
|
||||||
|
getPlaceholderElement() {
|
||||||
|
let ph = document.createElement("span");
|
||||||
|
ph.setAttribute("aria-hidden", true);
|
||||||
|
ph.classList.add(HeadingAnchors.classes.placeholder);
|
||||||
|
let content = this.getContent();
|
||||||
|
if(content) {
|
||||||
|
ph.textContent = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
ph.addEventListener("mouseover", (e) => {
|
||||||
|
let placeholder = e.target.closest(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
this.positionAnchorFromPlaceholder(placeholder);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return ph;
|
||||||
|
}
|
||||||
|
|
||||||
|
getAnchorElement(heading) {
|
||||||
|
let anchor = document.createElement("a");
|
||||||
|
anchor.href = `#${heading.id}`;
|
||||||
|
anchor.classList.add(HeadingAnchors.classes.anchor);
|
||||||
|
|
||||||
|
let content = this.getContent();
|
||||||
|
anchor.innerHTML = `<span class="${HeadingAnchors.classes.srOnly}">${this.getAccessibleTextPrefix()}: ${heading.textContent}</span>${content ? `<span aria-hidden="true">${content}</span>` : ""}`;
|
||||||
|
|
||||||
|
anchor.addEventListener("focus", e => {
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
if(anchor) {
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
anchor.addEventListener("mouseover", (e) => {
|
||||||
|
// when CSS anchor positioning is supported, this is only used to set the font
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
});
|
||||||
|
|
||||||
|
return anchor;
|
||||||
|
}
|
||||||
|
|
||||||
|
get headings() {
|
||||||
|
return this.querySelectorAll(this.selector.split(",").map(entry => `${entry.trim()}[id]`));
|
||||||
|
}
|
||||||
|
|
||||||
|
get selector() {
|
||||||
|
return this.getAttribute("selector") || HeadingAnchors.defaultSelector;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HeadingAnchors.register();
|
||||||
|
|
||||||
|
export { HeadingAnchors }
|
||||||
|
window.onload = function() {
|
||||||
|
const n = 1; // Example: change this number or get it dynamically
|
||||||
|
const result = fib(n);
|
||||||
|
document.getElementById("fib_seq").textContent = n;
|
||||||
|
document.getElementById("fib_num").textContent = result; // Doesn't render properly on mobile;
|
||||||
|
};
|
||||||
248
dist/ldcr_VJC1Q.js
vendored
Normal file
|
|
@ -0,0 +1,248 @@
|
||||||
|
// Thank you to https://github.com/daviddarnes/heading-anchors
|
||||||
|
// Thank you to https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
|
||||||
|
let globalInstanceIndex = 0;
|
||||||
|
|
||||||
|
class HeadingAnchors extends HTMLElement {
|
||||||
|
static register(tagName = "heading-anchors", registry = window.customElements) {
|
||||||
|
if(registry && !registry.get(tagName)) {
|
||||||
|
registry.define(tagName, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static attributes = {
|
||||||
|
exclude: "data-ha-exclude",
|
||||||
|
prefix: "prefix",
|
||||||
|
content: "content",
|
||||||
|
}
|
||||||
|
|
||||||
|
static classes = {
|
||||||
|
anchor: "ha",
|
||||||
|
placeholder: "ha-placeholder",
|
||||||
|
srOnly: "ha-visualhide",
|
||||||
|
}
|
||||||
|
|
||||||
|
static defaultSelector = "h2,h3,h4,h5,h6";
|
||||||
|
|
||||||
|
static css = `
|
||||||
|
.${HeadingAnchors.classes.srOnly} {
|
||||||
|
clip: rect(0 0 0 0);
|
||||||
|
height: 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: absolute;
|
||||||
|
width: 1px;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: var(--ha_offsetx);
|
||||||
|
top: var(--ha_offsety);
|
||||||
|
text-decoration: none;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
opacity: .3;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor}:is(:focus-within, :hover) {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor},
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 .25em;
|
||||||
|
|
||||||
|
/* Disable selection of visually hidden label */
|
||||||
|
-webkit-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@supports (anchor-name: none) {
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: anchor(left);
|
||||||
|
top: anchor(top);
|
||||||
|
}
|
||||||
|
}`;
|
||||||
|
|
||||||
|
get supports() {
|
||||||
|
return "replaceSync" in CSSStyleSheet.prototype;
|
||||||
|
}
|
||||||
|
|
||||||
|
get supportsAnchorPosition() {
|
||||||
|
return CSS.supports("anchor-name: none");
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
if(!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sheet = new CSSStyleSheet();
|
||||||
|
sheet.replaceSync(HeadingAnchors.css);
|
||||||
|
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
|
||||||
|
|
||||||
|
this.headingStyles = {};
|
||||||
|
this.instanceIndex = globalInstanceIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
if (!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.headings.forEach((heading, index) => {
|
||||||
|
if(!heading.hasAttribute(HeadingAnchors.attributes.exclude)) {
|
||||||
|
let anchor = this.getAnchorElement(heading);
|
||||||
|
let placeholder = this.getPlaceholderElement();
|
||||||
|
|
||||||
|
// Prefers anchor position approach for better accessibility
|
||||||
|
// https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
let anchorName = `--ha_${this.instanceIndex}_${index}`;
|
||||||
|
placeholder.style.setProperty("anchor-name", anchorName);
|
||||||
|
anchor.style.positionAnchor = anchorName;
|
||||||
|
}
|
||||||
|
|
||||||
|
heading.appendChild(placeholder);
|
||||||
|
heading.after(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchorFromPlaceholder(placeholder) {
|
||||||
|
if(!placeholder) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let heading = placeholder.closest("h1,h2,h3,h4,h5,h6");
|
||||||
|
if(!heading.nextElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO next element could be more defensive
|
||||||
|
this.positionAnchor(heading.nextElementSibling);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchor(anchor) {
|
||||||
|
if(!anchor || !anchor.previousElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO previous element could be more defensive
|
||||||
|
let heading = anchor.previousElementSibling;
|
||||||
|
this.setFontProp(heading, anchor);
|
||||||
|
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
// quit early
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
anchor.style.setProperty("--ha_offsetx", `${placeholder.offsetLeft}px`);
|
||||||
|
anchor.style.setProperty("--ha_offsety", `${placeholder.offsetTop}px`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setFontProp(heading, anchor) {
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
let style = getComputedStyle(placeholder);
|
||||||
|
let props = ["font-weight", "font-size", "line-height", "font-family"];
|
||||||
|
let [weight, size, lh, family] = props.map(name => style.getPropertyValue(name));
|
||||||
|
anchor.style.setProperty("font", `${weight} ${size}/${lh} ${family}`);
|
||||||
|
let vars = style.getPropertyValue("font-variation-settings");
|
||||||
|
if(vars) {
|
||||||
|
anchor.style.setProperty("font-variation-settings", vars);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getAccessibleTextPrefix() {
|
||||||
|
// Useful for i18n
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.prefix) || "Jump to section titled";
|
||||||
|
}
|
||||||
|
|
||||||
|
getContent() {
|
||||||
|
if(this.hasAttribute(HeadingAnchors.attributes.content)) {
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.content);
|
||||||
|
}
|
||||||
|
return "#";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Placeholder nests inside of heading
|
||||||
|
getPlaceholderElement() {
|
||||||
|
let ph = document.createElement("span");
|
||||||
|
ph.setAttribute("aria-hidden", true);
|
||||||
|
ph.classList.add(HeadingAnchors.classes.placeholder);
|
||||||
|
let content = this.getContent();
|
||||||
|
if(content) {
|
||||||
|
ph.textContent = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
ph.addEventListener("mouseover", (e) => {
|
||||||
|
let placeholder = e.target.closest(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
this.positionAnchorFromPlaceholder(placeholder);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return ph;
|
||||||
|
}
|
||||||
|
|
||||||
|
getAnchorElement(heading) {
|
||||||
|
let anchor = document.createElement("a");
|
||||||
|
anchor.href = `#${heading.id}`;
|
||||||
|
anchor.classList.add(HeadingAnchors.classes.anchor);
|
||||||
|
|
||||||
|
let content = this.getContent();
|
||||||
|
anchor.innerHTML = `<span class="${HeadingAnchors.classes.srOnly}">${this.getAccessibleTextPrefix()}: ${heading.textContent}</span>${content ? `<span aria-hidden="true">${content}</span>` : ""}`;
|
||||||
|
|
||||||
|
anchor.addEventListener("focus", e => {
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
if(anchor) {
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
anchor.addEventListener("mouseover", (e) => {
|
||||||
|
// when CSS anchor positioning is supported, this is only used to set the font
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
});
|
||||||
|
|
||||||
|
return anchor;
|
||||||
|
}
|
||||||
|
|
||||||
|
get headings() {
|
||||||
|
return this.querySelectorAll(this.selector.split(",").map(entry => `${entry.trim()}[id]`));
|
||||||
|
}
|
||||||
|
|
||||||
|
get selector() {
|
||||||
|
return this.getAttribute("selector") || HeadingAnchors.defaultSelector;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HeadingAnchors.register();
|
||||||
|
|
||||||
|
export { HeadingAnchors }
|
||||||
|
// Efficient iterative Fibonacci
|
||||||
|
function fib(n) {
|
||||||
|
if (n < 2) return n;
|
||||||
|
let a = 0, b = 1;
|
||||||
|
for (let i = 2; i <= n; i++) {
|
||||||
|
[a, b] = [b, a + b];
|
||||||
|
}
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
window.onload = function() {
|
||||||
|
const n = 3; // Example: change this number or get it dynamically
|
||||||
|
const result = fib(n);
|
||||||
|
document.getElementById("fib_seq").textContent = n;
|
||||||
|
document.getElementById("fib_num").textContent = result; // Doesn't render properly on mobile; figure out subscript numbers...
|
||||||
|
};
|
||||||
233
dist/mH8Ns4hbWV.js
vendored
Normal file
|
|
@ -0,0 +1,233 @@
|
||||||
|
// Thank you to https://github.com/daviddarnes/heading-anchors
|
||||||
|
// Thank you to https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
|
||||||
|
let globalInstanceIndex = 0;
|
||||||
|
|
||||||
|
class HeadingAnchors extends HTMLElement {
|
||||||
|
static register(tagName = "heading-anchors", registry = window.customElements) {
|
||||||
|
if(registry && !registry.get(tagName)) {
|
||||||
|
registry.define(tagName, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static attributes = {
|
||||||
|
exclude: "data-ha-exclude",
|
||||||
|
prefix: "prefix",
|
||||||
|
content: "content",
|
||||||
|
}
|
||||||
|
|
||||||
|
static classes = {
|
||||||
|
anchor: "ha",
|
||||||
|
placeholder: "ha-placeholder",
|
||||||
|
srOnly: "ha-visualhide",
|
||||||
|
}
|
||||||
|
|
||||||
|
static defaultSelector = "h2,h3,h4,h5,h6";
|
||||||
|
|
||||||
|
static css = `
|
||||||
|
.${HeadingAnchors.classes.srOnly} {
|
||||||
|
clip: rect(0 0 0 0);
|
||||||
|
height: 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: absolute;
|
||||||
|
width: 1px;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: var(--ha_offsetx);
|
||||||
|
top: var(--ha_offsety);
|
||||||
|
text-decoration: none;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
opacity: .3;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor}:is(:focus-within, :hover) {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor},
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 .25em;
|
||||||
|
|
||||||
|
/* Disable selection of visually hidden label */
|
||||||
|
-webkit-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@supports (anchor-name: none) {
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: anchor(left);
|
||||||
|
top: anchor(top);
|
||||||
|
}
|
||||||
|
}`;
|
||||||
|
|
||||||
|
get supports() {
|
||||||
|
return "replaceSync" in CSSStyleSheet.prototype;
|
||||||
|
}
|
||||||
|
|
||||||
|
get supportsAnchorPosition() {
|
||||||
|
return CSS.supports("anchor-name: none");
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
if(!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sheet = new CSSStyleSheet();
|
||||||
|
sheet.replaceSync(HeadingAnchors.css);
|
||||||
|
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
|
||||||
|
|
||||||
|
this.headingStyles = {};
|
||||||
|
this.instanceIndex = globalInstanceIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
if (!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.headings.forEach((heading, index) => {
|
||||||
|
if(!heading.hasAttribute(HeadingAnchors.attributes.exclude)) {
|
||||||
|
let anchor = this.getAnchorElement(heading);
|
||||||
|
let placeholder = this.getPlaceholderElement();
|
||||||
|
|
||||||
|
// Prefers anchor position approach for better accessibility
|
||||||
|
// https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
let anchorName = `--ha_${this.instanceIndex}_${index}`;
|
||||||
|
placeholder.style.setProperty("anchor-name", anchorName);
|
||||||
|
anchor.style.positionAnchor = anchorName;
|
||||||
|
}
|
||||||
|
|
||||||
|
heading.appendChild(placeholder);
|
||||||
|
heading.after(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchorFromPlaceholder(placeholder) {
|
||||||
|
if(!placeholder) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let heading = placeholder.closest("h1,h2,h3,h4,h5,h6");
|
||||||
|
if(!heading.nextElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO next element could be more defensive
|
||||||
|
this.positionAnchor(heading.nextElementSibling);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchor(anchor) {
|
||||||
|
if(!anchor || !anchor.previousElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO previous element could be more defensive
|
||||||
|
let heading = anchor.previousElementSibling;
|
||||||
|
this.setFontProp(heading, anchor);
|
||||||
|
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
// quit early
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
anchor.style.setProperty("--ha_offsetx", `${placeholder.offsetLeft}px`);
|
||||||
|
anchor.style.setProperty("--ha_offsety", `${placeholder.offsetTop}px`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setFontProp(heading, anchor) {
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
let style = getComputedStyle(placeholder);
|
||||||
|
let props = ["font-weight", "font-size", "line-height", "font-family"];
|
||||||
|
let [weight, size, lh, family] = props.map(name => style.getPropertyValue(name));
|
||||||
|
anchor.style.setProperty("font", `${weight} ${size}/${lh} ${family}`);
|
||||||
|
let vars = style.getPropertyValue("font-variation-settings");
|
||||||
|
if(vars) {
|
||||||
|
anchor.style.setProperty("font-variation-settings", vars);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getAccessibleTextPrefix() {
|
||||||
|
// Useful for i18n
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.prefix) || "Jump to section titled";
|
||||||
|
}
|
||||||
|
|
||||||
|
getContent() {
|
||||||
|
if(this.hasAttribute(HeadingAnchors.attributes.content)) {
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.content);
|
||||||
|
}
|
||||||
|
return "#";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Placeholder nests inside of heading
|
||||||
|
getPlaceholderElement() {
|
||||||
|
let ph = document.createElement("span");
|
||||||
|
ph.setAttribute("aria-hidden", true);
|
||||||
|
ph.classList.add(HeadingAnchors.classes.placeholder);
|
||||||
|
let content = this.getContent();
|
||||||
|
if(content) {
|
||||||
|
ph.textContent = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
ph.addEventListener("mouseover", (e) => {
|
||||||
|
let placeholder = e.target.closest(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
this.positionAnchorFromPlaceholder(placeholder);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return ph;
|
||||||
|
}
|
||||||
|
|
||||||
|
getAnchorElement(heading) {
|
||||||
|
let anchor = document.createElement("a");
|
||||||
|
anchor.href = `#${heading.id}`;
|
||||||
|
anchor.classList.add(HeadingAnchors.classes.anchor);
|
||||||
|
|
||||||
|
let content = this.getContent();
|
||||||
|
anchor.innerHTML = `<span class="${HeadingAnchors.classes.srOnly}">${this.getAccessibleTextPrefix()}: ${heading.textContent}</span>${content ? `<span aria-hidden="true">${content}</span>` : ""}`;
|
||||||
|
|
||||||
|
anchor.addEventListener("focus", e => {
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
if(anchor) {
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
anchor.addEventListener("mouseover", (e) => {
|
||||||
|
// when CSS anchor positioning is supported, this is only used to set the font
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
});
|
||||||
|
|
||||||
|
return anchor;
|
||||||
|
}
|
||||||
|
|
||||||
|
get headings() {
|
||||||
|
return this.querySelectorAll(this.selector.split(",").map(entry => `${entry.trim()}[id]`));
|
||||||
|
}
|
||||||
|
|
||||||
|
get selector() {
|
||||||
|
return this.getAttribute("selector") || HeadingAnchors.defaultSelector;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HeadingAnchors.register();
|
||||||
|
|
||||||
|
export { HeadingAnchors }
|
||||||
|
<h1>1</h1>
|
||||||
|
window.onload = fib(1);
|
||||||
235
dist/pK5dGeN6cR.js
vendored
Normal file
|
|
@ -0,0 +1,235 @@
|
||||||
|
// Thank you to https://github.com/daviddarnes/heading-anchors
|
||||||
|
// Thank you to https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
|
||||||
|
let globalInstanceIndex = 0;
|
||||||
|
|
||||||
|
class HeadingAnchors extends HTMLElement {
|
||||||
|
static register(tagName = "heading-anchors", registry = window.customElements) {
|
||||||
|
if(registry && !registry.get(tagName)) {
|
||||||
|
registry.define(tagName, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static attributes = {
|
||||||
|
exclude: "data-ha-exclude",
|
||||||
|
prefix: "prefix",
|
||||||
|
content: "content",
|
||||||
|
}
|
||||||
|
|
||||||
|
static classes = {
|
||||||
|
anchor: "ha",
|
||||||
|
placeholder: "ha-placeholder",
|
||||||
|
srOnly: "ha-visualhide",
|
||||||
|
}
|
||||||
|
|
||||||
|
static defaultSelector = "h2,h3,h4,h5,h6";
|
||||||
|
|
||||||
|
static css = `
|
||||||
|
.${HeadingAnchors.classes.srOnly} {
|
||||||
|
clip: rect(0 0 0 0);
|
||||||
|
height: 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: absolute;
|
||||||
|
width: 1px;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: var(--ha_offsetx);
|
||||||
|
top: var(--ha_offsety);
|
||||||
|
text-decoration: none;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
opacity: .3;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor}:is(:focus-within, :hover) {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor},
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 .25em;
|
||||||
|
|
||||||
|
/* Disable selection of visually hidden label */
|
||||||
|
-webkit-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@supports (anchor-name: none) {
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: anchor(left);
|
||||||
|
top: anchor(top);
|
||||||
|
}
|
||||||
|
}`;
|
||||||
|
|
||||||
|
get supports() {
|
||||||
|
return "replaceSync" in CSSStyleSheet.prototype;
|
||||||
|
}
|
||||||
|
|
||||||
|
get supportsAnchorPosition() {
|
||||||
|
return CSS.supports("anchor-name: none");
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
if(!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sheet = new CSSStyleSheet();
|
||||||
|
sheet.replaceSync(HeadingAnchors.css);
|
||||||
|
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
|
||||||
|
|
||||||
|
this.headingStyles = {};
|
||||||
|
this.instanceIndex = globalInstanceIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
if (!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.headings.forEach((heading, index) => {
|
||||||
|
if(!heading.hasAttribute(HeadingAnchors.attributes.exclude)) {
|
||||||
|
let anchor = this.getAnchorElement(heading);
|
||||||
|
let placeholder = this.getPlaceholderElement();
|
||||||
|
|
||||||
|
// Prefers anchor position approach for better accessibility
|
||||||
|
// https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
let anchorName = `--ha_${this.instanceIndex}_${index}`;
|
||||||
|
placeholder.style.setProperty("anchor-name", anchorName);
|
||||||
|
anchor.style.positionAnchor = anchorName;
|
||||||
|
}
|
||||||
|
|
||||||
|
heading.appendChild(placeholder);
|
||||||
|
heading.after(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchorFromPlaceholder(placeholder) {
|
||||||
|
if(!placeholder) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let heading = placeholder.closest("h1,h2,h3,h4,h5,h6");
|
||||||
|
if(!heading.nextElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO next element could be more defensive
|
||||||
|
this.positionAnchor(heading.nextElementSibling);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchor(anchor) {
|
||||||
|
if(!anchor || !anchor.previousElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO previous element could be more defensive
|
||||||
|
let heading = anchor.previousElementSibling;
|
||||||
|
this.setFontProp(heading, anchor);
|
||||||
|
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
// quit early
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
anchor.style.setProperty("--ha_offsetx", `${placeholder.offsetLeft}px`);
|
||||||
|
anchor.style.setProperty("--ha_offsety", `${placeholder.offsetTop}px`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setFontProp(heading, anchor) {
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
let style = getComputedStyle(placeholder);
|
||||||
|
let props = ["font-weight", "font-size", "line-height", "font-family"];
|
||||||
|
let [weight, size, lh, family] = props.map(name => style.getPropertyValue(name));
|
||||||
|
anchor.style.setProperty("font", `${weight} ${size}/${lh} ${family}`);
|
||||||
|
let vars = style.getPropertyValue("font-variation-settings");
|
||||||
|
if(vars) {
|
||||||
|
anchor.style.setProperty("font-variation-settings", vars);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getAccessibleTextPrefix() {
|
||||||
|
// Useful for i18n
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.prefix) || "Jump to section titled";
|
||||||
|
}
|
||||||
|
|
||||||
|
getContent() {
|
||||||
|
if(this.hasAttribute(HeadingAnchors.attributes.content)) {
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.content);
|
||||||
|
}
|
||||||
|
return "#";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Placeholder nests inside of heading
|
||||||
|
getPlaceholderElement() {
|
||||||
|
let ph = document.createElement("span");
|
||||||
|
ph.setAttribute("aria-hidden", true);
|
||||||
|
ph.classList.add(HeadingAnchors.classes.placeholder);
|
||||||
|
let content = this.getContent();
|
||||||
|
if(content) {
|
||||||
|
ph.textContent = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
ph.addEventListener("mouseover", (e) => {
|
||||||
|
let placeholder = e.target.closest(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
this.positionAnchorFromPlaceholder(placeholder);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return ph;
|
||||||
|
}
|
||||||
|
|
||||||
|
getAnchorElement(heading) {
|
||||||
|
let anchor = document.createElement("a");
|
||||||
|
anchor.href = `#${heading.id}`;
|
||||||
|
anchor.classList.add(HeadingAnchors.classes.anchor);
|
||||||
|
|
||||||
|
let content = this.getContent();
|
||||||
|
anchor.innerHTML = `<span class="${HeadingAnchors.classes.srOnly}">${this.getAccessibleTextPrefix()}: ${heading.textContent}</span>${content ? `<span aria-hidden="true">${content}</span>` : ""}`;
|
||||||
|
|
||||||
|
anchor.addEventListener("focus", e => {
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
if(anchor) {
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
anchor.addEventListener("mouseover", (e) => {
|
||||||
|
// when CSS anchor positioning is supported, this is only used to set the font
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
});
|
||||||
|
|
||||||
|
return anchor;
|
||||||
|
}
|
||||||
|
|
||||||
|
get headings() {
|
||||||
|
return this.querySelectorAll(this.selector.split(",").map(entry => `${entry.trim()}[id]`));
|
||||||
|
}
|
||||||
|
|
||||||
|
get selector() {
|
||||||
|
return this.getAttribute("selector") || HeadingAnchors.defaultSelector;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HeadingAnchors.register();
|
||||||
|
|
||||||
|
export { HeadingAnchors }
|
||||||
|
<h1>1</h1>
|
||||||
|
window.onload = function() {
|
||||||
|
fib(1);
|
||||||
|
};
|
||||||
248
dist/uqzqyPsa5S.js
vendored
Normal file
|
|
@ -0,0 +1,248 @@
|
||||||
|
// Thank you to https://github.com/daviddarnes/heading-anchors
|
||||||
|
// Thank you to https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
|
||||||
|
let globalInstanceIndex = 0;
|
||||||
|
|
||||||
|
class HeadingAnchors extends HTMLElement {
|
||||||
|
static register(tagName = "heading-anchors", registry = window.customElements) {
|
||||||
|
if(registry && !registry.get(tagName)) {
|
||||||
|
registry.define(tagName, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static attributes = {
|
||||||
|
exclude: "data-ha-exclude",
|
||||||
|
prefix: "prefix",
|
||||||
|
content: "content",
|
||||||
|
}
|
||||||
|
|
||||||
|
static classes = {
|
||||||
|
anchor: "ha",
|
||||||
|
placeholder: "ha-placeholder",
|
||||||
|
srOnly: "ha-visualhide",
|
||||||
|
}
|
||||||
|
|
||||||
|
static defaultSelector = "h2,h3,h4,h5,h6";
|
||||||
|
|
||||||
|
static css = `
|
||||||
|
.${HeadingAnchors.classes.srOnly} {
|
||||||
|
clip: rect(0 0 0 0);
|
||||||
|
height: 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: absolute;
|
||||||
|
width: 1px;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: var(--ha_offsetx);
|
||||||
|
top: var(--ha_offsety);
|
||||||
|
text-decoration: none;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
opacity: .3;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor}:is(:focus-within, :hover) {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor},
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 .25em;
|
||||||
|
|
||||||
|
/* Disable selection of visually hidden label */
|
||||||
|
-webkit-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@supports (anchor-name: none) {
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: anchor(left);
|
||||||
|
top: anchor(top);
|
||||||
|
}
|
||||||
|
}`;
|
||||||
|
|
||||||
|
get supports() {
|
||||||
|
return "replaceSync" in CSSStyleSheet.prototype;
|
||||||
|
}
|
||||||
|
|
||||||
|
get supportsAnchorPosition() {
|
||||||
|
return CSS.supports("anchor-name: none");
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
if(!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sheet = new CSSStyleSheet();
|
||||||
|
sheet.replaceSync(HeadingAnchors.css);
|
||||||
|
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
|
||||||
|
|
||||||
|
this.headingStyles = {};
|
||||||
|
this.instanceIndex = globalInstanceIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
if (!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.headings.forEach((heading, index) => {
|
||||||
|
if(!heading.hasAttribute(HeadingAnchors.attributes.exclude)) {
|
||||||
|
let anchor = this.getAnchorElement(heading);
|
||||||
|
let placeholder = this.getPlaceholderElement();
|
||||||
|
|
||||||
|
// Prefers anchor position approach for better accessibility
|
||||||
|
// https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
let anchorName = `--ha_${this.instanceIndex}_${index}`;
|
||||||
|
placeholder.style.setProperty("anchor-name", anchorName);
|
||||||
|
anchor.style.positionAnchor = anchorName;
|
||||||
|
}
|
||||||
|
|
||||||
|
heading.appendChild(placeholder);
|
||||||
|
heading.after(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchorFromPlaceholder(placeholder) {
|
||||||
|
if(!placeholder) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let heading = placeholder.closest("h1,h2,h3,h4,h5,h6");
|
||||||
|
if(!heading.nextElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO next element could be more defensive
|
||||||
|
this.positionAnchor(heading.nextElementSibling);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchor(anchor) {
|
||||||
|
if(!anchor || !anchor.previousElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO previous element could be more defensive
|
||||||
|
let heading = anchor.previousElementSibling;
|
||||||
|
this.setFontProp(heading, anchor);
|
||||||
|
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
// quit early
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
anchor.style.setProperty("--ha_offsetx", `${placeholder.offsetLeft}px`);
|
||||||
|
anchor.style.setProperty("--ha_offsety", `${placeholder.offsetTop}px`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setFontProp(heading, anchor) {
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
let style = getComputedStyle(placeholder);
|
||||||
|
let props = ["font-weight", "font-size", "line-height", "font-family"];
|
||||||
|
let [weight, size, lh, family] = props.map(name => style.getPropertyValue(name));
|
||||||
|
anchor.style.setProperty("font", `${weight} ${size}/${lh} ${family}`);
|
||||||
|
let vars = style.getPropertyValue("font-variation-settings");
|
||||||
|
if(vars) {
|
||||||
|
anchor.style.setProperty("font-variation-settings", vars);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getAccessibleTextPrefix() {
|
||||||
|
// Useful for i18n
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.prefix) || "Jump to section titled";
|
||||||
|
}
|
||||||
|
|
||||||
|
getContent() {
|
||||||
|
if(this.hasAttribute(HeadingAnchors.attributes.content)) {
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.content);
|
||||||
|
}
|
||||||
|
return "#";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Placeholder nests inside of heading
|
||||||
|
getPlaceholderElement() {
|
||||||
|
let ph = document.createElement("span");
|
||||||
|
ph.setAttribute("aria-hidden", true);
|
||||||
|
ph.classList.add(HeadingAnchors.classes.placeholder);
|
||||||
|
let content = this.getContent();
|
||||||
|
if(content) {
|
||||||
|
ph.textContent = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
ph.addEventListener("mouseover", (e) => {
|
||||||
|
let placeholder = e.target.closest(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
this.positionAnchorFromPlaceholder(placeholder);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return ph;
|
||||||
|
}
|
||||||
|
|
||||||
|
getAnchorElement(heading) {
|
||||||
|
let anchor = document.createElement("a");
|
||||||
|
anchor.href = `#${heading.id}`;
|
||||||
|
anchor.classList.add(HeadingAnchors.classes.anchor);
|
||||||
|
|
||||||
|
let content = this.getContent();
|
||||||
|
anchor.innerHTML = `<span class="${HeadingAnchors.classes.srOnly}">${this.getAccessibleTextPrefix()}: ${heading.textContent}</span>${content ? `<span aria-hidden="true">${content}</span>` : ""}`;
|
||||||
|
|
||||||
|
anchor.addEventListener("focus", e => {
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
if(anchor) {
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
anchor.addEventListener("mouseover", (e) => {
|
||||||
|
// when CSS anchor positioning is supported, this is only used to set the font
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
});
|
||||||
|
|
||||||
|
return anchor;
|
||||||
|
}
|
||||||
|
|
||||||
|
get headings() {
|
||||||
|
return this.querySelectorAll(this.selector.split(",").map(entry => `${entry.trim()}[id]`));
|
||||||
|
}
|
||||||
|
|
||||||
|
get selector() {
|
||||||
|
return this.getAttribute("selector") || HeadingAnchors.defaultSelector;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HeadingAnchors.register();
|
||||||
|
|
||||||
|
export { HeadingAnchors }
|
||||||
|
// Efficient iterative Fibonacci
|
||||||
|
function fib(n) {
|
||||||
|
if (n < 2) return n;
|
||||||
|
let a = 0, b = 1;
|
||||||
|
for (let i = 2; i <= n; i++) {
|
||||||
|
[a, b] = [b, a + b];
|
||||||
|
}
|
||||||
|
document.getElementById("fib_seq").textContent = n;
|
||||||
|
document.getElementById("fib_num").textContent = result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
window.onload = function() {
|
||||||
|
const n = 1; // Example: change this number or get it dynamically
|
||||||
|
const result = fib(n);
|
||||||
|
document.getElementById("fib").textContent = `F(${n}) = ${result}`;
|
||||||
|
};
|
||||||
237
dist/uteBBWdAf7.js
vendored
Normal file
|
|
@ -0,0 +1,237 @@
|
||||||
|
// Thank you to https://github.com/daviddarnes/heading-anchors
|
||||||
|
// Thank you to https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
|
||||||
|
let globalInstanceIndex = 0;
|
||||||
|
|
||||||
|
class HeadingAnchors extends HTMLElement {
|
||||||
|
static register(tagName = "heading-anchors", registry = window.customElements) {
|
||||||
|
if(registry && !registry.get(tagName)) {
|
||||||
|
registry.define(tagName, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static attributes = {
|
||||||
|
exclude: "data-ha-exclude",
|
||||||
|
prefix: "prefix",
|
||||||
|
content: "content",
|
||||||
|
}
|
||||||
|
|
||||||
|
static classes = {
|
||||||
|
anchor: "ha",
|
||||||
|
placeholder: "ha-placeholder",
|
||||||
|
srOnly: "ha-visualhide",
|
||||||
|
}
|
||||||
|
|
||||||
|
static defaultSelector = "h2,h3,h4,h5,h6";
|
||||||
|
|
||||||
|
static css = `
|
||||||
|
.${HeadingAnchors.classes.srOnly} {
|
||||||
|
clip: rect(0 0 0 0);
|
||||||
|
height: 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: absolute;
|
||||||
|
width: 1px;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: var(--ha_offsetx);
|
||||||
|
top: var(--ha_offsety);
|
||||||
|
text-decoration: none;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
opacity: .3;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor}:is(:focus-within, :hover) {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor},
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 .25em;
|
||||||
|
|
||||||
|
/* Disable selection of visually hidden label */
|
||||||
|
-webkit-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@supports (anchor-name: none) {
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: anchor(left);
|
||||||
|
top: anchor(top);
|
||||||
|
}
|
||||||
|
}`;
|
||||||
|
|
||||||
|
get supports() {
|
||||||
|
return "replaceSync" in CSSStyleSheet.prototype;
|
||||||
|
}
|
||||||
|
|
||||||
|
get supportsAnchorPosition() {
|
||||||
|
return CSS.supports("anchor-name: none");
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
if(!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sheet = new CSSStyleSheet();
|
||||||
|
sheet.replaceSync(HeadingAnchors.css);
|
||||||
|
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
|
||||||
|
|
||||||
|
this.headingStyles = {};
|
||||||
|
this.instanceIndex = globalInstanceIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
if (!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.headings.forEach((heading, index) => {
|
||||||
|
if(!heading.hasAttribute(HeadingAnchors.attributes.exclude)) {
|
||||||
|
let anchor = this.getAnchorElement(heading);
|
||||||
|
let placeholder = this.getPlaceholderElement();
|
||||||
|
|
||||||
|
// Prefers anchor position approach for better accessibility
|
||||||
|
// https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
let anchorName = `--ha_${this.instanceIndex}_${index}`;
|
||||||
|
placeholder.style.setProperty("anchor-name", anchorName);
|
||||||
|
anchor.style.positionAnchor = anchorName;
|
||||||
|
}
|
||||||
|
|
||||||
|
heading.appendChild(placeholder);
|
||||||
|
heading.after(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchorFromPlaceholder(placeholder) {
|
||||||
|
if(!placeholder) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let heading = placeholder.closest("h1,h2,h3,h4,h5,h6");
|
||||||
|
if(!heading.nextElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO next element could be more defensive
|
||||||
|
this.positionAnchor(heading.nextElementSibling);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchor(anchor) {
|
||||||
|
if(!anchor || !anchor.previousElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO previous element could be more defensive
|
||||||
|
let heading = anchor.previousElementSibling;
|
||||||
|
this.setFontProp(heading, anchor);
|
||||||
|
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
// quit early
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
anchor.style.setProperty("--ha_offsetx", `${placeholder.offsetLeft}px`);
|
||||||
|
anchor.style.setProperty("--ha_offsety", `${placeholder.offsetTop}px`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setFontProp(heading, anchor) {
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
let style = getComputedStyle(placeholder);
|
||||||
|
let props = ["font-weight", "font-size", "line-height", "font-family"];
|
||||||
|
let [weight, size, lh, family] = props.map(name => style.getPropertyValue(name));
|
||||||
|
anchor.style.setProperty("font", `${weight} ${size}/${lh} ${family}`);
|
||||||
|
let vars = style.getPropertyValue("font-variation-settings");
|
||||||
|
if(vars) {
|
||||||
|
anchor.style.setProperty("font-variation-settings", vars);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getAccessibleTextPrefix() {
|
||||||
|
// Useful for i18n
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.prefix) || "Jump to section titled";
|
||||||
|
}
|
||||||
|
|
||||||
|
getContent() {
|
||||||
|
if(this.hasAttribute(HeadingAnchors.attributes.content)) {
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.content);
|
||||||
|
}
|
||||||
|
return "#";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Placeholder nests inside of heading
|
||||||
|
getPlaceholderElement() {
|
||||||
|
let ph = document.createElement("span");
|
||||||
|
ph.setAttribute("aria-hidden", true);
|
||||||
|
ph.classList.add(HeadingAnchors.classes.placeholder);
|
||||||
|
let content = this.getContent();
|
||||||
|
if(content) {
|
||||||
|
ph.textContent = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
ph.addEventListener("mouseover", (e) => {
|
||||||
|
let placeholder = e.target.closest(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
this.positionAnchorFromPlaceholder(placeholder);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return ph;
|
||||||
|
}
|
||||||
|
|
||||||
|
getAnchorElement(heading) {
|
||||||
|
let anchor = document.createElement("a");
|
||||||
|
anchor.href = `#${heading.id}`;
|
||||||
|
anchor.classList.add(HeadingAnchors.classes.anchor);
|
||||||
|
|
||||||
|
let content = this.getContent();
|
||||||
|
anchor.innerHTML = `<span class="${HeadingAnchors.classes.srOnly}">${this.getAccessibleTextPrefix()}: ${heading.textContent}</span>${content ? `<span aria-hidden="true">${content}</span>` : ""}`;
|
||||||
|
|
||||||
|
anchor.addEventListener("focus", e => {
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
if(anchor) {
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
anchor.addEventListener("mouseover", (e) => {
|
||||||
|
// when CSS anchor positioning is supported, this is only used to set the font
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
});
|
||||||
|
|
||||||
|
return anchor;
|
||||||
|
}
|
||||||
|
|
||||||
|
get headings() {
|
||||||
|
return this.querySelectorAll(this.selector.split(",").map(entry => `${entry.trim()}[id]`));
|
||||||
|
}
|
||||||
|
|
||||||
|
get selector() {
|
||||||
|
return this.getAttribute("selector") || HeadingAnchors.defaultSelector;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HeadingAnchors.register();
|
||||||
|
|
||||||
|
export { HeadingAnchors }
|
||||||
|
window.onload = function() {
|
||||||
|
const n = 10; // Example: change this number or get it dynamically
|
||||||
|
const result = fib(n);
|
||||||
|
document.getElementById("fib_seq").textContent = n;
|
||||||
|
document.getElementById("fib_num").textContent = result; // Doesn't render properly on mobile; figure out subscript numbers...
|
||||||
|
};
|
||||||
247
dist/wbPsFzqn9h.js
vendored
Normal file
|
|
@ -0,0 +1,247 @@
|
||||||
|
// Thank you to https://github.com/daviddarnes/heading-anchors
|
||||||
|
// Thank you to https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
|
||||||
|
let globalInstanceIndex = 0;
|
||||||
|
|
||||||
|
class HeadingAnchors extends HTMLElement {
|
||||||
|
static register(tagName = "heading-anchors", registry = window.customElements) {
|
||||||
|
if(registry && !registry.get(tagName)) {
|
||||||
|
registry.define(tagName, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static attributes = {
|
||||||
|
exclude: "data-ha-exclude",
|
||||||
|
prefix: "prefix",
|
||||||
|
content: "content",
|
||||||
|
}
|
||||||
|
|
||||||
|
static classes = {
|
||||||
|
anchor: "ha",
|
||||||
|
placeholder: "ha-placeholder",
|
||||||
|
srOnly: "ha-visualhide",
|
||||||
|
}
|
||||||
|
|
||||||
|
static defaultSelector = "h2,h3,h4,h5,h6";
|
||||||
|
|
||||||
|
static css = `
|
||||||
|
.${HeadingAnchors.classes.srOnly} {
|
||||||
|
clip: rect(0 0 0 0);
|
||||||
|
height: 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: absolute;
|
||||||
|
width: 1px;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: var(--ha_offsetx);
|
||||||
|
top: var(--ha_offsety);
|
||||||
|
text-decoration: none;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
opacity: .3;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor}:is(:focus-within, :hover) {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor},
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 .25em;
|
||||||
|
|
||||||
|
/* Disable selection of visually hidden label */
|
||||||
|
-webkit-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@supports (anchor-name: none) {
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: anchor(left);
|
||||||
|
top: anchor(top);
|
||||||
|
}
|
||||||
|
}`;
|
||||||
|
|
||||||
|
get supports() {
|
||||||
|
return "replaceSync" in CSSStyleSheet.prototype;
|
||||||
|
}
|
||||||
|
|
||||||
|
get supportsAnchorPosition() {
|
||||||
|
return CSS.supports("anchor-name: none");
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
if(!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sheet = new CSSStyleSheet();
|
||||||
|
sheet.replaceSync(HeadingAnchors.css);
|
||||||
|
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
|
||||||
|
|
||||||
|
this.headingStyles = {};
|
||||||
|
this.instanceIndex = globalInstanceIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
if (!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.headings.forEach((heading, index) => {
|
||||||
|
if(!heading.hasAttribute(HeadingAnchors.attributes.exclude)) {
|
||||||
|
let anchor = this.getAnchorElement(heading);
|
||||||
|
let placeholder = this.getPlaceholderElement();
|
||||||
|
|
||||||
|
// Prefers anchor position approach for better accessibility
|
||||||
|
// https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
let anchorName = `--ha_${this.instanceIndex}_${index}`;
|
||||||
|
placeholder.style.setProperty("anchor-name", anchorName);
|
||||||
|
anchor.style.positionAnchor = anchorName;
|
||||||
|
}
|
||||||
|
|
||||||
|
heading.appendChild(placeholder);
|
||||||
|
heading.after(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchorFromPlaceholder(placeholder) {
|
||||||
|
if(!placeholder) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let heading = placeholder.closest("h1,h2,h3,h4,h5,h6");
|
||||||
|
if(!heading.nextElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO next element could be more defensive
|
||||||
|
this.positionAnchor(heading.nextElementSibling);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchor(anchor) {
|
||||||
|
if(!anchor || !anchor.previousElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO previous element could be more defensive
|
||||||
|
let heading = anchor.previousElementSibling;
|
||||||
|
this.setFontProp(heading, anchor);
|
||||||
|
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
// quit early
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
anchor.style.setProperty("--ha_offsetx", `${placeholder.offsetLeft}px`);
|
||||||
|
anchor.style.setProperty("--ha_offsety", `${placeholder.offsetTop}px`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setFontProp(heading, anchor) {
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
let style = getComputedStyle(placeholder);
|
||||||
|
let props = ["font-weight", "font-size", "line-height", "font-family"];
|
||||||
|
let [weight, size, lh, family] = props.map(name => style.getPropertyValue(name));
|
||||||
|
anchor.style.setProperty("font", `${weight} ${size}/${lh} ${family}`);
|
||||||
|
let vars = style.getPropertyValue("font-variation-settings");
|
||||||
|
if(vars) {
|
||||||
|
anchor.style.setProperty("font-variation-settings", vars);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getAccessibleTextPrefix() {
|
||||||
|
// Useful for i18n
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.prefix) || "Jump to section titled";
|
||||||
|
}
|
||||||
|
|
||||||
|
getContent() {
|
||||||
|
if(this.hasAttribute(HeadingAnchors.attributes.content)) {
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.content);
|
||||||
|
}
|
||||||
|
return "#";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Placeholder nests inside of heading
|
||||||
|
getPlaceholderElement() {
|
||||||
|
let ph = document.createElement("span");
|
||||||
|
ph.setAttribute("aria-hidden", true);
|
||||||
|
ph.classList.add(HeadingAnchors.classes.placeholder);
|
||||||
|
let content = this.getContent();
|
||||||
|
if(content) {
|
||||||
|
ph.textContent = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
ph.addEventListener("mouseover", (e) => {
|
||||||
|
let placeholder = e.target.closest(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
this.positionAnchorFromPlaceholder(placeholder);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return ph;
|
||||||
|
}
|
||||||
|
|
||||||
|
getAnchorElement(heading) {
|
||||||
|
let anchor = document.createElement("a");
|
||||||
|
anchor.href = `#${heading.id}`;
|
||||||
|
anchor.classList.add(HeadingAnchors.classes.anchor);
|
||||||
|
|
||||||
|
let content = this.getContent();
|
||||||
|
anchor.innerHTML = `<span class="${HeadingAnchors.classes.srOnly}">${this.getAccessibleTextPrefix()}: ${heading.textContent}</span>${content ? `<span aria-hidden="true">${content}</span>` : ""}`;
|
||||||
|
|
||||||
|
anchor.addEventListener("focus", e => {
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
if(anchor) {
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
anchor.addEventListener("mouseover", (e) => {
|
||||||
|
// when CSS anchor positioning is supported, this is only used to set the font
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
});
|
||||||
|
|
||||||
|
return anchor;
|
||||||
|
}
|
||||||
|
|
||||||
|
get headings() {
|
||||||
|
return this.querySelectorAll(this.selector.split(",").map(entry => `${entry.trim()}[id]`));
|
||||||
|
}
|
||||||
|
|
||||||
|
get selector() {
|
||||||
|
return this.getAttribute("selector") || HeadingAnchors.defaultSelector;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HeadingAnchors.register();
|
||||||
|
|
||||||
|
export { HeadingAnchors }
|
||||||
|
// Efficient iterative Fibonacci
|
||||||
|
function fib(n) {
|
||||||
|
if (n < 2) return n;
|
||||||
|
let a = 0, b = 1;
|
||||||
|
for (let i = 2; i <= n; i++) {
|
||||||
|
[a, b] = [b, a + b];
|
||||||
|
}
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.onload = function() {
|
||||||
|
const n = 1; // Example: change this number or get it dynamically
|
||||||
|
const result = fib(n);
|
||||||
|
document.getElementById("fib_seq").textContent = n;
|
||||||
|
document.getElementById("fib_num").textContent = result; // Doesn't render properly on mobile; figure out subscript numbers...
|
||||||
|
};
|
||||||
247
dist/xtOprDpcek.js
vendored
Normal file
|
|
@ -0,0 +1,247 @@
|
||||||
|
// Thank you to https://github.com/daviddarnes/heading-anchors
|
||||||
|
// Thank you to https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
|
||||||
|
let globalInstanceIndex = 0;
|
||||||
|
|
||||||
|
class HeadingAnchors extends HTMLElement {
|
||||||
|
static register(tagName = "heading-anchors", registry = window.customElements) {
|
||||||
|
if(registry && !registry.get(tagName)) {
|
||||||
|
registry.define(tagName, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static attributes = {
|
||||||
|
exclude: "data-ha-exclude",
|
||||||
|
prefix: "prefix",
|
||||||
|
content: "content",
|
||||||
|
}
|
||||||
|
|
||||||
|
static classes = {
|
||||||
|
anchor: "ha",
|
||||||
|
placeholder: "ha-placeholder",
|
||||||
|
srOnly: "ha-visualhide",
|
||||||
|
}
|
||||||
|
|
||||||
|
static defaultSelector = "h2,h3,h4,h5,h6";
|
||||||
|
|
||||||
|
static css = `
|
||||||
|
.${HeadingAnchors.classes.srOnly} {
|
||||||
|
clip: rect(0 0 0 0);
|
||||||
|
height: 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: absolute;
|
||||||
|
width: 1px;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: var(--ha_offsetx);
|
||||||
|
top: var(--ha_offsety);
|
||||||
|
text-decoration: none;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
opacity: .3;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor}:is(:focus-within, :hover) {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor},
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 .25em;
|
||||||
|
|
||||||
|
/* Disable selection of visually hidden label */
|
||||||
|
-webkit-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@supports (anchor-name: none) {
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: anchor(left);
|
||||||
|
top: anchor(top);
|
||||||
|
}
|
||||||
|
}`;
|
||||||
|
|
||||||
|
get supports() {
|
||||||
|
return "replaceSync" in CSSStyleSheet.prototype;
|
||||||
|
}
|
||||||
|
|
||||||
|
get supportsAnchorPosition() {
|
||||||
|
return CSS.supports("anchor-name: none");
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
if(!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sheet = new CSSStyleSheet();
|
||||||
|
sheet.replaceSync(HeadingAnchors.css);
|
||||||
|
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
|
||||||
|
|
||||||
|
this.headingStyles = {};
|
||||||
|
this.instanceIndex = globalInstanceIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
if (!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.headings.forEach((heading, index) => {
|
||||||
|
if(!heading.hasAttribute(HeadingAnchors.attributes.exclude)) {
|
||||||
|
let anchor = this.getAnchorElement(heading);
|
||||||
|
let placeholder = this.getPlaceholderElement();
|
||||||
|
|
||||||
|
// Prefers anchor position approach for better accessibility
|
||||||
|
// https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
let anchorName = `--ha_${this.instanceIndex}_${index}`;
|
||||||
|
placeholder.style.setProperty("anchor-name", anchorName);
|
||||||
|
anchor.style.positionAnchor = anchorName;
|
||||||
|
}
|
||||||
|
|
||||||
|
heading.appendChild(placeholder);
|
||||||
|
heading.after(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchorFromPlaceholder(placeholder) {
|
||||||
|
if(!placeholder) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let heading = placeholder.closest("h1,h2,h3,h4,h5,h6");
|
||||||
|
if(!heading.nextElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO next element could be more defensive
|
||||||
|
this.positionAnchor(heading.nextElementSibling);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchor(anchor) {
|
||||||
|
if(!anchor || !anchor.previousElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO previous element could be more defensive
|
||||||
|
let heading = anchor.previousElementSibling;
|
||||||
|
this.setFontProp(heading, anchor);
|
||||||
|
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
// quit early
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
anchor.style.setProperty("--ha_offsetx", `${placeholder.offsetLeft}px`);
|
||||||
|
anchor.style.setProperty("--ha_offsety", `${placeholder.offsetTop}px`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setFontProp(heading, anchor) {
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
let style = getComputedStyle(placeholder);
|
||||||
|
let props = ["font-weight", "font-size", "line-height", "font-family"];
|
||||||
|
let [weight, size, lh, family] = props.map(name => style.getPropertyValue(name));
|
||||||
|
anchor.style.setProperty("font", `${weight} ${size}/${lh} ${family}`);
|
||||||
|
let vars = style.getPropertyValue("font-variation-settings");
|
||||||
|
if(vars) {
|
||||||
|
anchor.style.setProperty("font-variation-settings", vars);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getAccessibleTextPrefix() {
|
||||||
|
// Useful for i18n
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.prefix) || "Jump to section titled";
|
||||||
|
}
|
||||||
|
|
||||||
|
getContent() {
|
||||||
|
if(this.hasAttribute(HeadingAnchors.attributes.content)) {
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.content);
|
||||||
|
}
|
||||||
|
return "#";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Placeholder nests inside of heading
|
||||||
|
getPlaceholderElement() {
|
||||||
|
let ph = document.createElement("span");
|
||||||
|
ph.setAttribute("aria-hidden", true);
|
||||||
|
ph.classList.add(HeadingAnchors.classes.placeholder);
|
||||||
|
let content = this.getContent();
|
||||||
|
if(content) {
|
||||||
|
ph.textContent = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
ph.addEventListener("mouseover", (e) => {
|
||||||
|
let placeholder = e.target.closest(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
this.positionAnchorFromPlaceholder(placeholder);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return ph;
|
||||||
|
}
|
||||||
|
|
||||||
|
getAnchorElement(heading) {
|
||||||
|
let anchor = document.createElement("a");
|
||||||
|
anchor.href = `#${heading.id}`;
|
||||||
|
anchor.classList.add(HeadingAnchors.classes.anchor);
|
||||||
|
|
||||||
|
let content = this.getContent();
|
||||||
|
anchor.innerHTML = `<span class="${HeadingAnchors.classes.srOnly}">${this.getAccessibleTextPrefix()}: ${heading.textContent}</span>${content ? `<span aria-hidden="true">${content}</span>` : ""}`;
|
||||||
|
|
||||||
|
anchor.addEventListener("focus", e => {
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
if(anchor) {
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
anchor.addEventListener("mouseover", (e) => {
|
||||||
|
// when CSS anchor positioning is supported, this is only used to set the font
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
});
|
||||||
|
|
||||||
|
return anchor;
|
||||||
|
}
|
||||||
|
|
||||||
|
get headings() {
|
||||||
|
return this.querySelectorAll(this.selector.split(",").map(entry => `${entry.trim()}[id]`));
|
||||||
|
}
|
||||||
|
|
||||||
|
get selector() {
|
||||||
|
return this.getAttribute("selector") || HeadingAnchors.defaultSelector;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HeadingAnchors.register();
|
||||||
|
|
||||||
|
export { HeadingAnchors }
|
||||||
|
// Efficient iterative Fibonacci
|
||||||
|
function fib(n) {
|
||||||
|
if (n < 2) return n;
|
||||||
|
let a = 0, b = 1;
|
||||||
|
for (let i = 2; i <= n; i++) {
|
||||||
|
[a, b] = [b, a + b];
|
||||||
|
}
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
window.onload = function() {
|
||||||
|
const n = 4; // Example: change this number or get it dynamically
|
||||||
|
const result = fib(n);
|
||||||
|
document.getElementById("fib").textContent = `F(${n}) = ${result}`;
|
||||||
|
};
|
||||||
248
dist/ytDKgl3ZR7.js
vendored
Normal file
|
|
@ -0,0 +1,248 @@
|
||||||
|
// Thank you to https://github.com/daviddarnes/heading-anchors
|
||||||
|
// Thank you to https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
|
||||||
|
let globalInstanceIndex = 0;
|
||||||
|
|
||||||
|
class HeadingAnchors extends HTMLElement {
|
||||||
|
static register(tagName = "heading-anchors", registry = window.customElements) {
|
||||||
|
if(registry && !registry.get(tagName)) {
|
||||||
|
registry.define(tagName, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static attributes = {
|
||||||
|
exclude: "data-ha-exclude",
|
||||||
|
prefix: "prefix",
|
||||||
|
content: "content",
|
||||||
|
}
|
||||||
|
|
||||||
|
static classes = {
|
||||||
|
anchor: "ha",
|
||||||
|
placeholder: "ha-placeholder",
|
||||||
|
srOnly: "ha-visualhide",
|
||||||
|
}
|
||||||
|
|
||||||
|
static defaultSelector = "h2,h3,h4,h5,h6";
|
||||||
|
|
||||||
|
static css = `
|
||||||
|
.${HeadingAnchors.classes.srOnly} {
|
||||||
|
clip: rect(0 0 0 0);
|
||||||
|
height: 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: absolute;
|
||||||
|
width: 1px;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: var(--ha_offsetx);
|
||||||
|
top: var(--ha_offsety);
|
||||||
|
text-decoration: none;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
opacity: .3;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor}:is(:focus-within, :hover) {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.${HeadingAnchors.classes.anchor},
|
||||||
|
.${HeadingAnchors.classes.placeholder} {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 .25em;
|
||||||
|
|
||||||
|
/* Disable selection of visually hidden label */
|
||||||
|
-webkit-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@supports (anchor-name: none) {
|
||||||
|
.${HeadingAnchors.classes.anchor} {
|
||||||
|
position: absolute;
|
||||||
|
left: anchor(left);
|
||||||
|
top: anchor(top);
|
||||||
|
}
|
||||||
|
}`;
|
||||||
|
|
||||||
|
get supports() {
|
||||||
|
return "replaceSync" in CSSStyleSheet.prototype;
|
||||||
|
}
|
||||||
|
|
||||||
|
get supportsAnchorPosition() {
|
||||||
|
return CSS.supports("anchor-name: none");
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
if(!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sheet = new CSSStyleSheet();
|
||||||
|
sheet.replaceSync(HeadingAnchors.css);
|
||||||
|
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
|
||||||
|
|
||||||
|
this.headingStyles = {};
|
||||||
|
this.instanceIndex = globalInstanceIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
if (!this.supports) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.headings.forEach((heading, index) => {
|
||||||
|
if(!heading.hasAttribute(HeadingAnchors.attributes.exclude)) {
|
||||||
|
let anchor = this.getAnchorElement(heading);
|
||||||
|
let placeholder = this.getPlaceholderElement();
|
||||||
|
|
||||||
|
// Prefers anchor position approach for better accessibility
|
||||||
|
// https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
let anchorName = `--ha_${this.instanceIndex}_${index}`;
|
||||||
|
placeholder.style.setProperty("anchor-name", anchorName);
|
||||||
|
anchor.style.positionAnchor = anchorName;
|
||||||
|
}
|
||||||
|
|
||||||
|
heading.appendChild(placeholder);
|
||||||
|
heading.after(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchorFromPlaceholder(placeholder) {
|
||||||
|
if(!placeholder) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let heading = placeholder.closest("h1,h2,h3,h4,h5,h6");
|
||||||
|
if(!heading.nextElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO next element could be more defensive
|
||||||
|
this.positionAnchor(heading.nextElementSibling);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill-only
|
||||||
|
positionAnchor(anchor) {
|
||||||
|
if(!anchor || !anchor.previousElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO previous element could be more defensive
|
||||||
|
let heading = anchor.previousElementSibling;
|
||||||
|
this.setFontProp(heading, anchor);
|
||||||
|
|
||||||
|
if(this.supportsAnchorPosition) {
|
||||||
|
// quit early
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
anchor.style.setProperty("--ha_offsetx", `${placeholder.offsetLeft}px`);
|
||||||
|
anchor.style.setProperty("--ha_offsety", `${placeholder.offsetTop}px`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setFontProp(heading, anchor) {
|
||||||
|
let placeholder = heading.querySelector(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
let style = getComputedStyle(placeholder);
|
||||||
|
let props = ["font-weight", "font-size", "line-height", "font-family"];
|
||||||
|
let [weight, size, lh, family] = props.map(name => style.getPropertyValue(name));
|
||||||
|
anchor.style.setProperty("font", `${weight} ${size}/${lh} ${family}`);
|
||||||
|
let vars = style.getPropertyValue("font-variation-settings");
|
||||||
|
if(vars) {
|
||||||
|
anchor.style.setProperty("font-variation-settings", vars);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getAccessibleTextPrefix() {
|
||||||
|
// Useful for i18n
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.prefix) || "Jump to section titled";
|
||||||
|
}
|
||||||
|
|
||||||
|
getContent() {
|
||||||
|
if(this.hasAttribute(HeadingAnchors.attributes.content)) {
|
||||||
|
return this.getAttribute(HeadingAnchors.attributes.content);
|
||||||
|
}
|
||||||
|
return "#";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Placeholder nests inside of heading
|
||||||
|
getPlaceholderElement() {
|
||||||
|
let ph = document.createElement("span");
|
||||||
|
ph.setAttribute("aria-hidden", true);
|
||||||
|
ph.classList.add(HeadingAnchors.classes.placeholder);
|
||||||
|
let content = this.getContent();
|
||||||
|
if(content) {
|
||||||
|
ph.textContent = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
ph.addEventListener("mouseover", (e) => {
|
||||||
|
let placeholder = e.target.closest(`.${HeadingAnchors.classes.placeholder}`);
|
||||||
|
if(placeholder) {
|
||||||
|
this.positionAnchorFromPlaceholder(placeholder);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return ph;
|
||||||
|
}
|
||||||
|
|
||||||
|
getAnchorElement(heading) {
|
||||||
|
let anchor = document.createElement("a");
|
||||||
|
anchor.href = `#${heading.id}`;
|
||||||
|
anchor.classList.add(HeadingAnchors.classes.anchor);
|
||||||
|
|
||||||
|
let content = this.getContent();
|
||||||
|
anchor.innerHTML = `<span class="${HeadingAnchors.classes.srOnly}">${this.getAccessibleTextPrefix()}: ${heading.textContent}</span>${content ? `<span aria-hidden="true">${content}</span>` : ""}`;
|
||||||
|
|
||||||
|
anchor.addEventListener("focus", e => {
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
if(anchor) {
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
anchor.addEventListener("mouseover", (e) => {
|
||||||
|
// when CSS anchor positioning is supported, this is only used to set the font
|
||||||
|
let anchor = e.target.closest(`.${HeadingAnchors.classes.anchor}`);
|
||||||
|
this.positionAnchor(anchor);
|
||||||
|
});
|
||||||
|
|
||||||
|
return anchor;
|
||||||
|
}
|
||||||
|
|
||||||
|
get headings() {
|
||||||
|
return this.querySelectorAll(this.selector.split(",").map(entry => `${entry.trim()}[id]`));
|
||||||
|
}
|
||||||
|
|
||||||
|
get selector() {
|
||||||
|
return this.getAttribute("selector") || HeadingAnchors.defaultSelector;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HeadingAnchors.register();
|
||||||
|
|
||||||
|
export { HeadingAnchors }
|
||||||
|
// Efficient iterative Fibonacci
|
||||||
|
function fib(n) {
|
||||||
|
if (n < 2) return n;
|
||||||
|
let a = 0, b = 1;
|
||||||
|
for (let i = 2; i <= n; i++) {
|
||||||
|
[a, b] = [b, a + b];
|
||||||
|
}
|
||||||
|
document.getElementById("fib_seq").textContent = n;
|
||||||
|
document.getElementById("fib_num").textContent = result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
window.onload = function() {
|
||||||
|
const n = 3; // Example: change this number or get it dynamically
|
||||||
|
const result = fib(n);
|
||||||
|
document.getElementById("fib").textContent = `F(${n}) = ${result}`;
|
||||||
|
};
|
||||||
23
feed/feed.xml
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<?xml-stylesheet href="pretty-atom-feed.xsl" type="text/xsl"?>
|
||||||
|
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
|
||||||
|
<title>Blog Title</title>
|
||||||
|
<subtitle>This is a longer description about your blog.</subtitle>
|
||||||
|
<link href="https://example.com/feed/feed.xml" rel="self" />
|
||||||
|
<link href="https://example.com/" />
|
||||||
|
<updated>2025-10-24T00:00:00Z</updated>
|
||||||
|
<id>https://example.com/</id>
|
||||||
|
<author>
|
||||||
|
<name>Your Name</name>
|
||||||
|
</author>
|
||||||
|
<entry>
|
||||||
|
<title>Notchee #1</title>
|
||||||
|
<link href="https://example.com/blog/notchee001/" />
|
||||||
|
<updated>2025-10-24T00:00:00Z</updated>
|
||||||
|
<id>https://example.com/blog/notchee001/</id>
|
||||||
|
<content type="html"><p><picture><source type="image/avif" srcset="https://example.com/blog/notchee001/uOBi7x7dUh-1871.avif 1871w"><source type="image/webp" srcset="https://example.com/blog/notchee001/uOBi7x7dUh-1871.webp 1871w"><img loading="lazy" decoding="async" src="https://example.com/blog/notchee001/uOBi7x7dUh-1871.jpeg" alt="Four square heads look on suspiciously at another square head with rounded corners." width="1871" height="1171"></picture></p>
|
||||||
|
<p style="text-align:center;"><em>
|
||||||
|
Gary&#39;s new haircut manages to completely derail the meeting of the Society Questioning UnApproved Rounded Entities
|
||||||
|
</em></p></content>
|
||||||
|
</entry>
|
||||||
|
</feed>
|
||||||
89
feed/pretty-atom-feed.xsl
Normal file
0
img/.gitkeep
Normal file
BIN
img/2O9wlKzLwf-1984.avif
Normal file
BIN
img/2O9wlKzLwf-1984.jpeg
Normal file
|
After Width: | Height: | Size: 418 KiB |
BIN
img/2O9wlKzLwf-1984.webp
Normal file
|
After Width: | Height: | Size: 303 KiB |
BIN
img/eCT5mOL4LD-60.avif
Normal file
BIN
img/eCT5mOL4LD-60.jpeg
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
img/eCT5mOL4LD-60.webp
Normal file
|
After Width: | Height: | Size: 510 B |
BIN
img/logo.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
img/notchee-logo.jpg
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
img/uOBi7x7dUh-1871.avif
Normal file
BIN
img/uOBi7x7dUh-1871.jpeg
Normal file
|
After Width: | Height: | Size: 343 KiB |
BIN
img/uOBi7x7dUh-1871.webp
Normal file
|
After Width: | Height: | Size: 218 KiB |
422
index.html
Normal file
|
|
@ -0,0 +1,422 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Notchee</title>
|
||||||
|
<meta name="description" content="I am writing about my experiences as a naval navel-gazer.">
|
||||||
|
<link rel="alternate" href="/feed/feed.xml" type="application/atom+xml" title="Notchee">
|
||||||
|
<link rel="icon" type="image/x-icon" href="/img/notchee-logo.jpg">
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<style>/* This is an arbitrary CSS string added to the bundle */
|
||||||
|
/* Defaults */
|
||||||
|
:root {
|
||||||
|
--font-family: -apple-system, system-ui, sans-serif;
|
||||||
|
--font-family-monospace: Consolas, Menlo, Monaco, Andale Mono WT, Andale Mono, Lucida Console, Lucida Sans Typewriter, DejaVu Sans Mono, Bitstream Vera Sans Mono, Liberation Mono, Nimbus Mono L, Courier New, Courier, monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Theme colors */
|
||||||
|
:root {
|
||||||
|
--color-gray-20: #e0e0e0;
|
||||||
|
--color-gray-50: #C0C0C0;
|
||||||
|
--color-gray-90: #333;
|
||||||
|
|
||||||
|
--background-color: #fff;
|
||||||
|
|
||||||
|
--text-color: var(--color-gray-90);
|
||||||
|
--text-color-link: #082840;
|
||||||
|
--text-color-link-active: #5f2b48;
|
||||||
|
--text-color-link-visited: #17050F;
|
||||||
|
|
||||||
|
--syntax-tab-size: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
:root {
|
||||||
|
--color-gray-20: #e0e0e0;
|
||||||
|
--color-gray-50: #C0C0C0;
|
||||||
|
--color-gray-90: #dad8d8;
|
||||||
|
|
||||||
|
/* --text-color is assigned to --color-gray-_ above */
|
||||||
|
--text-color-link: #1493fb;
|
||||||
|
--text-color-link-active: #6969f7;
|
||||||
|
--text-color-link-visited: #a6a6f8;
|
||||||
|
|
||||||
|
--background-color: #15202b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Global stylesheet */
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
@view-transition {
|
||||||
|
navigation: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0 auto;
|
||||||
|
font-family: var(--font-family);
|
||||||
|
color: var(--text-color);
|
||||||
|
background-color: var(--background-color);
|
||||||
|
}
|
||||||
|
html {
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
max-width: 40em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* https://www.a11yproject.com/posts/how-to-hide-content/ */
|
||||||
|
.visually-hidden:not(:focus):not(:active) {
|
||||||
|
clip: rect(0 0 0 0);
|
||||||
|
clip-path: inset(50%);
|
||||||
|
height: 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: absolute;
|
||||||
|
white-space: nowrap;
|
||||||
|
width: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fluid images via https://www.zachleat.com/web/fluid-images/ */
|
||||||
|
img{
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
img[width][height] {
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
img[src$=".svg"] {
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
max-width: none;
|
||||||
|
}
|
||||||
|
video,
|
||||||
|
iframe {
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
iframe {
|
||||||
|
aspect-ratio: 16/9;
|
||||||
|
}
|
||||||
|
|
||||||
|
p:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
a[href] {
|
||||||
|
color: var(--text-color-link);
|
||||||
|
}
|
||||||
|
a[href]:visited {
|
||||||
|
color: var(--text-color-link-visited);
|
||||||
|
}
|
||||||
|
a[href]:hover,
|
||||||
|
a[href]:active {
|
||||||
|
color: var(--text-color-link-active);
|
||||||
|
}
|
||||||
|
|
||||||
|
main,
|
||||||
|
footer {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
main :first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
border-bottom: 1px dashed var(--color-gray-20);
|
||||||
|
}
|
||||||
|
|
||||||
|
#skip-link {
|
||||||
|
text-decoration: none;
|
||||||
|
background: var(--background-color);
|
||||||
|
color: var(--text-color);
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border: 1px solid var(--color-gray-90);
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Prevent visually-hidden skip link fom pushing content around when focused */
|
||||||
|
#skip-link.visually-hidden:focus {
|
||||||
|
position: absolute;
|
||||||
|
top: 1rem;
|
||||||
|
left: 1rem;
|
||||||
|
/* Ensure it is positioned on top of everything else when it is shown */
|
||||||
|
z-index: 999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.links-nextprev {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: .5em 1em;
|
||||||
|
list-style: "";
|
||||||
|
border-top: 1px dashed var(--color-gray-20);
|
||||||
|
padding: 1em 0;
|
||||||
|
}
|
||||||
|
.links-nextprev > * {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
.links-nextprev-next {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
margin: 1em 0;
|
||||||
|
}
|
||||||
|
table td,
|
||||||
|
table th {
|
||||||
|
padding-right: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre,
|
||||||
|
code {
|
||||||
|
font-family: var(--font-family-monospace);
|
||||||
|
}
|
||||||
|
pre:not([class*="language-"]) {
|
||||||
|
margin: .5em 0;
|
||||||
|
line-height: 1.375; /* 22px /16 */
|
||||||
|
-moz-tab-size: var(--syntax-tab-size);
|
||||||
|
-o-tab-size: var(--syntax-tab-size);
|
||||||
|
tab-size: var(--syntax-tab-size);
|
||||||
|
-webkit-hyphens: none;
|
||||||
|
-ms-hyphens: none;
|
||||||
|
hyphens: none;
|
||||||
|
direction: ltr;
|
||||||
|
text-align: left;
|
||||||
|
white-space: pre;
|
||||||
|
word-spacing: normal;
|
||||||
|
word-break: normal;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
code {
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Header */
|
||||||
|
header {
|
||||||
|
display: flex;
|
||||||
|
gap: 1em;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 1em;
|
||||||
|
}
|
||||||
|
.home-link {
|
||||||
|
flex-grow: 1;
|
||||||
|
font-size: 1em; /* 16px /16 */
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
.home-link:link:not(:hover) {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Nav */
|
||||||
|
.nav {
|
||||||
|
display: flex;
|
||||||
|
gap: .5em 1em;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
.nav-item {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
.nav-item a[href]:not(:hover) {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
.nav a[href][aria-current="page"] {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Posts list */
|
||||||
|
.postlist {
|
||||||
|
counter-reset: start-from var(--postlist-index);
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
padding-left: 1.5rem;
|
||||||
|
}
|
||||||
|
.postlist-item {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: baseline;
|
||||||
|
counter-increment: start-from -1;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
.postlist-item:before {
|
||||||
|
display: inline-block;
|
||||||
|
pointer-events: none;
|
||||||
|
content: "" counter(start-from, decimal-leading-zero) ". ";
|
||||||
|
line-height: 100%;
|
||||||
|
text-align: right;
|
||||||
|
margin-left: -1.5rem;
|
||||||
|
}
|
||||||
|
.postlist-date,
|
||||||
|
.postlist-item:before {
|
||||||
|
font-size: 0.8125em; /* 13px /16 */
|
||||||
|
color: var(--color-gray-90);
|
||||||
|
}
|
||||||
|
.postlist-date {
|
||||||
|
word-spacing: -0.5px;
|
||||||
|
}
|
||||||
|
.postlist-link {
|
||||||
|
font-size: 1.1875em; /* 19px /16 */
|
||||||
|
font-weight: 700;
|
||||||
|
flex-basis: calc(100% - 1.5rem);
|
||||||
|
padding-left: .25em;
|
||||||
|
padding-right: .5em;
|
||||||
|
text-underline-position: from-font;
|
||||||
|
text-underline-offset: 0;
|
||||||
|
text-decoration-thickness: 1px;
|
||||||
|
}
|
||||||
|
.postlist-item-active .postlist-link {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tags */
|
||||||
|
.post-tag {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
text-transform: capitalize;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
.postlist-item > .post-tag {
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tags list */
|
||||||
|
.post-metadata {
|
||||||
|
display: inline-flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: .5em;
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.post-metadata time {
|
||||||
|
margin-right: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fib ::selection {
|
||||||
|
color:#FFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fib ::-webkit-selection {
|
||||||
|
color:#FFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fib {
|
||||||
|
-webkit-tap-highlight-color: white;
|
||||||
|
}
|
||||||
|
/* Message Box */
|
||||||
|
.message-box {
|
||||||
|
--color-message-box: #ffc;
|
||||||
|
|
||||||
|
display: block;
|
||||||
|
background-color: var(--color-message-box);
|
||||||
|
color: var(--color-gray-90);
|
||||||
|
padding: 1em 0.625em; /* 16px 10px /16 */
|
||||||
|
}
|
||||||
|
.message-box ol {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
.message-box {
|
||||||
|
/* Default */
|
||||||
|
/* --color-message-box: #082840; */
|
||||||
|
--color-message-box: none;
|
||||||
|
}
|
||||||
|
}</style>
|
||||||
|
|
||||||
|
<script src="/bundle.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<a href="#main" id="skip-link" class="visually-hidden">Skip to main content</a>
|
||||||
|
|
||||||
|
<header>
|
||||||
|
|
||||||
|
<picture><source type="image/avif" srcset="/img/eCT5mOL4LD-60.avif 60w"><source type="image/webp" srcset="/img/eCT5mOL4LD-60.webp 60w"><img loading="lazy" decoding="async" src="/img/eCT5mOL4LD-60.jpeg" alt="notchee logo" style="width:2rem;" width="60" height="60"></picture> <a href="/" class="home-link">Notchee</a>
|
||||||
|
<nav>
|
||||||
|
<h2 class="visually-hidden" id="top-level-navigation-menu">Top level navigation menu</h2>
|
||||||
|
<ul class="nav">
|
||||||
|
<li class="nav-item"><a href="/" aria-current="page">Home</a></li>
|
||||||
|
<li class="nav-item"><a href="/blog/">Archive</a></li>
|
||||||
|
<li class="nav-item"><a href="/about/">About</a></li>
|
||||||
|
<li class="nav-item"><a href="/feed/feed.xml">Feed</a></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main id="main">
|
||||||
|
<heading-anchors>
|
||||||
|
<!-- Delete this block, which will also remove the component CSS from the bundle -->
|
||||||
|
|
||||||
|
<div class="message-box">
|
||||||
|
|
||||||
|
<section class="latest-post">
|
||||||
|
<article>
|
||||||
|
<p style="font-size:1.75rem; text-align:center; font-weight:bold">Notchee #1: <span style="font-weight:normal">Good Governance</span></p>
|
||||||
|
<p style="text-align:center; font-size:1rem; margin-top:-1.5rem; margin-bottom:2.5rem;"><time datetime="2025-10-24">24 October 2025</time></p>
|
||||||
|
<p><picture><source type="image/avif" srcset="/img/uOBi7x7dUh-1871.avif 1871w"><source type="image/webp" srcset="/img/uOBi7x7dUh-1871.webp 1871w"><img loading="lazy" decoding="async" src="/img/uOBi7x7dUh-1871.jpeg" alt="Four square heads look on suspiciously at another square head with rounded corners." width="1871" height="1171"></picture></p>
|
||||||
|
<p style="text-align:center;"><em>Gary's new haircut manages to completely derail the meeting of the Society Questioning UnApproved Rounded Entities</em></p>
|
||||||
|
</article>
|
||||||
|
<div class="fib">
|
||||||
|
<p style="text-align:center; font-size:0.75rem; color:#15202b;">F<sub id="fib_seq"></sub> = <span id="fib_num"></span></p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
<!-- Stop deleting -->
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<h1 style="text-align:center; margin-top:4rem;" id="archive">Archive</h1>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<ol reversed="" class="postlist" style="--postlist-index: 2;">
|
||||||
|
<li class="postlist-item">
|
||||||
|
<a href="/blog/notchee001/" class="postlist-link">Notchee #1: Good Governance</a>
|
||||||
|
<time class="postlist-date" datetime="2025-10-24">Friday, October 24, 2025</time>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</heading-anchors>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer style="border-top: 1px dashed var(--color-gray-20);">
|
||||||
|
<p style="text-align:center">
|
||||||
|
<em>Built with <a href="https://www.11ty.dev/">Eleventy v3.1.2</a> and <a href="https://github.com/11ty/eleventy-base-blog">Eleventy Base Blog v9 theme</a></em>
|
||||||
|
</p>
|
||||||
|
<p style="text-align:center">
|
||||||
|
<em>© 2025. All content is licensed <a href="https://creativecommons.org/licenses/by-nc-nd/4.0/">CC BY-NC-ND 4.0</a> unless otherwise indicated. You are encouraged to share this work without modification so long as you give credit to <a href="https://notchee.art">notchee.art</a>. To request commercial use of any part of this work please <a href="mailto:hello@notchee.art">contact me</a>.</em>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<!-- This page `/` was built on 2025-10-26T14:26:36.246Z -->
|
||||||
|
<script type="module" src="/dist/k7YWH9wzXb.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
45
sitemap.xml
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml">
|
||||||
|
|
||||||
|
|
||||||
|
<url>
|
||||||
|
<loc>https://notchee.art/blog/notchee001/</loc>
|
||||||
|
<lastmod>2025-10-24</lastmod>
|
||||||
|
</url>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<url>
|
||||||
|
<loc>https://notchee.art/about/</loc>
|
||||||
|
<lastmod>2025-10-25</lastmod>
|
||||||
|
</url>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<url>
|
||||||
|
<loc>https://notchee.art/blog/</loc>
|
||||||
|
<lastmod>2025-10-25</lastmod>
|
||||||
|
</url>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<url>
|
||||||
|
<loc>https://notchee.art/</loc>
|
||||||
|
<lastmod>2025-10-25</lastmod>
|
||||||
|
</url>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<url>
|
||||||
|
<loc>https://notchee.art/tags/</loc>
|
||||||
|
<lastmod>2025-10-25</lastmod>
|
||||||
|
</url>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<url>
|
||||||
|
<loc>https://notchee.art/feed/feed.xml</loc>
|
||||||
|
<lastmod>2025-10-26</lastmod>
|
||||||
|
</url>
|
||||||
|
|
||||||
|
</urlset>
|
||||||
408
tags/index.html
Normal file
|
|
@ -0,0 +1,408 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Notchee</title>
|
||||||
|
<meta name="description" content="I am writing about my experiences as a naval navel-gazer.">
|
||||||
|
<link rel="alternate" href="/feed/feed.xml" type="application/atom+xml" title="Notchee">
|
||||||
|
<link rel="icon" type="image/x-icon" href="/img/notchee-logo.jpg">
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<style>/* This is an arbitrary CSS string added to the bundle */
|
||||||
|
/* Defaults */
|
||||||
|
:root {
|
||||||
|
--font-family: -apple-system, system-ui, sans-serif;
|
||||||
|
--font-family-monospace: Consolas, Menlo, Monaco, Andale Mono WT, Andale Mono, Lucida Console, Lucida Sans Typewriter, DejaVu Sans Mono, Bitstream Vera Sans Mono, Liberation Mono, Nimbus Mono L, Courier New, Courier, monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Theme colors */
|
||||||
|
:root {
|
||||||
|
--color-gray-20: #e0e0e0;
|
||||||
|
--color-gray-50: #C0C0C0;
|
||||||
|
--color-gray-90: #333;
|
||||||
|
|
||||||
|
--background-color: #fff;
|
||||||
|
|
||||||
|
--text-color: var(--color-gray-90);
|
||||||
|
--text-color-link: #082840;
|
||||||
|
--text-color-link-active: #5f2b48;
|
||||||
|
--text-color-link-visited: #17050F;
|
||||||
|
|
||||||
|
--syntax-tab-size: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
:root {
|
||||||
|
--color-gray-20: #e0e0e0;
|
||||||
|
--color-gray-50: #C0C0C0;
|
||||||
|
--color-gray-90: #dad8d8;
|
||||||
|
|
||||||
|
/* --text-color is assigned to --color-gray-_ above */
|
||||||
|
--text-color-link: #1493fb;
|
||||||
|
--text-color-link-active: #6969f7;
|
||||||
|
--text-color-link-visited: #a6a6f8;
|
||||||
|
|
||||||
|
--background-color: #15202b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Global stylesheet */
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
@view-transition {
|
||||||
|
navigation: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0 auto;
|
||||||
|
font-family: var(--font-family);
|
||||||
|
color: var(--text-color);
|
||||||
|
background-color: var(--background-color);
|
||||||
|
}
|
||||||
|
html {
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
max-width: 40em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* https://www.a11yproject.com/posts/how-to-hide-content/ */
|
||||||
|
.visually-hidden:not(:focus):not(:active) {
|
||||||
|
clip: rect(0 0 0 0);
|
||||||
|
clip-path: inset(50%);
|
||||||
|
height: 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: absolute;
|
||||||
|
white-space: nowrap;
|
||||||
|
width: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fluid images via https://www.zachleat.com/web/fluid-images/ */
|
||||||
|
img{
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
img[width][height] {
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
img[src$=".svg"] {
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
max-width: none;
|
||||||
|
}
|
||||||
|
video,
|
||||||
|
iframe {
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
iframe {
|
||||||
|
aspect-ratio: 16/9;
|
||||||
|
}
|
||||||
|
|
||||||
|
p:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
a[href] {
|
||||||
|
color: var(--text-color-link);
|
||||||
|
}
|
||||||
|
a[href]:visited {
|
||||||
|
color: var(--text-color-link-visited);
|
||||||
|
}
|
||||||
|
a[href]:hover,
|
||||||
|
a[href]:active {
|
||||||
|
color: var(--text-color-link-active);
|
||||||
|
}
|
||||||
|
|
||||||
|
main,
|
||||||
|
footer {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
main :first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
border-bottom: 1px dashed var(--color-gray-20);
|
||||||
|
}
|
||||||
|
|
||||||
|
#skip-link {
|
||||||
|
text-decoration: none;
|
||||||
|
background: var(--background-color);
|
||||||
|
color: var(--text-color);
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border: 1px solid var(--color-gray-90);
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Prevent visually-hidden skip link fom pushing content around when focused */
|
||||||
|
#skip-link.visually-hidden:focus {
|
||||||
|
position: absolute;
|
||||||
|
top: 1rem;
|
||||||
|
left: 1rem;
|
||||||
|
/* Ensure it is positioned on top of everything else when it is shown */
|
||||||
|
z-index: 999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.links-nextprev {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: .5em 1em;
|
||||||
|
list-style: "";
|
||||||
|
border-top: 1px dashed var(--color-gray-20);
|
||||||
|
padding: 1em 0;
|
||||||
|
}
|
||||||
|
.links-nextprev > * {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
.links-nextprev-next {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
margin: 1em 0;
|
||||||
|
}
|
||||||
|
table td,
|
||||||
|
table th {
|
||||||
|
padding-right: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre,
|
||||||
|
code {
|
||||||
|
font-family: var(--font-family-monospace);
|
||||||
|
}
|
||||||
|
pre:not([class*="language-"]) {
|
||||||
|
margin: .5em 0;
|
||||||
|
line-height: 1.375; /* 22px /16 */
|
||||||
|
-moz-tab-size: var(--syntax-tab-size);
|
||||||
|
-o-tab-size: var(--syntax-tab-size);
|
||||||
|
tab-size: var(--syntax-tab-size);
|
||||||
|
-webkit-hyphens: none;
|
||||||
|
-ms-hyphens: none;
|
||||||
|
hyphens: none;
|
||||||
|
direction: ltr;
|
||||||
|
text-align: left;
|
||||||
|
white-space: pre;
|
||||||
|
word-spacing: normal;
|
||||||
|
word-break: normal;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
code {
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Header */
|
||||||
|
header {
|
||||||
|
display: flex;
|
||||||
|
gap: 1em;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 1em;
|
||||||
|
}
|
||||||
|
.home-link {
|
||||||
|
flex-grow: 1;
|
||||||
|
font-size: 1em; /* 16px /16 */
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
.home-link:link:not(:hover) {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Nav */
|
||||||
|
.nav {
|
||||||
|
display: flex;
|
||||||
|
gap: .5em 1em;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
.nav-item {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
.nav-item a[href]:not(:hover) {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
.nav a[href][aria-current="page"] {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Posts list */
|
||||||
|
.postlist {
|
||||||
|
counter-reset: start-from var(--postlist-index);
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
padding-left: 1.5rem;
|
||||||
|
}
|
||||||
|
.postlist-item {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: baseline;
|
||||||
|
counter-increment: start-from -1;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
.postlist-item:before {
|
||||||
|
display: inline-block;
|
||||||
|
pointer-events: none;
|
||||||
|
content: "" counter(start-from, decimal-leading-zero) ". ";
|
||||||
|
line-height: 100%;
|
||||||
|
text-align: right;
|
||||||
|
margin-left: -1.5rem;
|
||||||
|
}
|
||||||
|
.postlist-date,
|
||||||
|
.postlist-item:before {
|
||||||
|
font-size: 0.8125em; /* 13px /16 */
|
||||||
|
color: var(--color-gray-90);
|
||||||
|
}
|
||||||
|
.postlist-date {
|
||||||
|
word-spacing: -0.5px;
|
||||||
|
}
|
||||||
|
.postlist-link {
|
||||||
|
font-size: 1.1875em; /* 19px /16 */
|
||||||
|
font-weight: 700;
|
||||||
|
flex-basis: calc(100% - 1.5rem);
|
||||||
|
padding-left: .25em;
|
||||||
|
padding-right: .5em;
|
||||||
|
text-underline-position: from-font;
|
||||||
|
text-underline-offset: 0;
|
||||||
|
text-decoration-thickness: 1px;
|
||||||
|
}
|
||||||
|
.postlist-item-active .postlist-link {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tags */
|
||||||
|
.post-tag {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
text-transform: capitalize;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
.postlist-item > .post-tag {
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tags list */
|
||||||
|
.post-metadata {
|
||||||
|
display: inline-flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: .5em;
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.post-metadata time {
|
||||||
|
margin-right: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fib ::selection {
|
||||||
|
color:#FFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fib ::-webkit-selection {
|
||||||
|
color:#FFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fib {
|
||||||
|
-webkit-tap-highlight-color: white;
|
||||||
|
}
|
||||||
|
/* Message Box */
|
||||||
|
.message-box {
|
||||||
|
--color-message-box: #ffc;
|
||||||
|
|
||||||
|
display: block;
|
||||||
|
background-color: var(--color-message-box);
|
||||||
|
color: var(--color-gray-90);
|
||||||
|
padding: 1em 0.625em; /* 16px 10px /16 */
|
||||||
|
}
|
||||||
|
.message-box ol {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
.message-box {
|
||||||
|
/* Default */
|
||||||
|
/* --color-message-box: #082840; */
|
||||||
|
--color-message-box: none;
|
||||||
|
}
|
||||||
|
}</style>
|
||||||
|
|
||||||
|
<script src="/bundle.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<a href="#main" id="skip-link" class="visually-hidden">Skip to main content</a>
|
||||||
|
|
||||||
|
<header>
|
||||||
|
|
||||||
|
<picture><source type="image/avif" srcset="/img/eCT5mOL4LD-60.avif 60w"><source type="image/webp" srcset="/img/eCT5mOL4LD-60.webp 60w"><img loading="lazy" decoding="async" src="/img/eCT5mOL4LD-60.jpeg" alt="notchee logo" style="width:2rem;" width="60" height="60"></picture> <a href="/" class="home-link">Notchee</a>
|
||||||
|
<nav>
|
||||||
|
<h2 class="visually-hidden" id="top-level-navigation-menu">Top level navigation menu</h2>
|
||||||
|
<ul class="nav">
|
||||||
|
<li class="nav-item"><a href="/">Home</a></li>
|
||||||
|
<li class="nav-item"><a href="/blog/">Archive</a></li>
|
||||||
|
<li class="nav-item"><a href="/about/">About</a></li>
|
||||||
|
<li class="nav-item"><a href="/feed/feed.xml">Feed</a></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main id="main">
|
||||||
|
<heading-anchors>
|
||||||
|
<!-- Delete this block, which will also remove the component CSS from the bundle -->
|
||||||
|
|
||||||
|
<div class="message-box">
|
||||||
|
|
||||||
|
<section class="latest-post">
|
||||||
|
<article>
|
||||||
|
<p style="font-size:1.75rem; text-align:center; font-weight:bold">Notchee #1: <span style="font-weight:normal">Good Governance</span></p>
|
||||||
|
<p style="text-align:center; font-size:1rem; margin-top:-1.5rem; margin-bottom:2.5rem;"><time datetime="2025-10-24">24 October 2025</time></p>
|
||||||
|
<p><picture><source type="image/avif" srcset="/img/uOBi7x7dUh-1871.avif 1871w"><source type="image/webp" srcset="/img/uOBi7x7dUh-1871.webp 1871w"><img loading="lazy" decoding="async" src="/img/uOBi7x7dUh-1871.jpeg" alt="Four square heads look on suspiciously at another square head with rounded corners." width="1871" height="1171"></picture></p>
|
||||||
|
<p style="text-align:center;"><em>Gary's new haircut manages to completely derail the meeting of the Society Questioning UnApproved Rounded Entities</em></p>
|
||||||
|
</article>
|
||||||
|
<div class="fib">
|
||||||
|
<p style="text-align:center; font-size:0.75rem; color:#15202b;">F<sub id="fib_seq"></sub> = <span id="fib_num"></span></p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
<!-- Stop deleting -->
|
||||||
|
|
||||||
|
<h1 id="tags">Tags</h1>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</heading-anchors>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer style="border-top: 1px dashed var(--color-gray-20);">
|
||||||
|
<p style="text-align:center">
|
||||||
|
<em>Built with <a href="https://www.11ty.dev/">Eleventy v3.1.2</a> and <a href="https://github.com/11ty/eleventy-base-blog">Eleventy Base Blog v9 theme</a></em>
|
||||||
|
</p>
|
||||||
|
<p style="text-align:center">
|
||||||
|
<em>© 2025. All content is licensed <a href="https://creativecommons.org/licenses/by-nc-nd/4.0/">CC BY-NC-ND 4.0</a> unless otherwise indicated. You are encouraged to share this work without modification so long as you give credit to <a href="https://notchee.art">notchee.art</a>. To request commercial use of any part of this work please <a href="mailto:hello@notchee.art">contact me</a>.</em>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<!-- This page `/tags/` was built on 2025-10-26T14:26:36.246Z -->
|
||||||
|
<script type="module" src="/dist/k7YWH9wzXb.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||