commit 0b1263ebb41a84c2896e34caefcd0fa3e7a78253
parent 6d10ee2f0971b99f3a4e173abf68f2b22e3e6f06
Author: maydayv7 <maydayv7@gmail.com>
Date: Mon, 7 Sep 2026 01:12:55 +0530
feat(site): Performance + Accessibility
Diffstat:
47 files changed, 795 insertions(+), 589 deletions(-)
diff --git a/site/.gitignore b/site/.gitignore
@@ -3,3 +3,4 @@ public
node_modules
static/processed_images
static/giallo.css
+__pycache__
diff --git a/site/README.md b/site/README.md
@@ -20,6 +20,15 @@ zola -r site serve
Then click [here](http://localhost:1111)
To build the site, run `nix build .#website`.
+The build also subsets webfonts, retaining text glyphs, ligatures, and Nerd icons referenced by generated HTML/CSS/JS.
+
+To produce the same optimized assets from the `site` development shell:
+
+```sh
+zola build
+python3 scripts/optimize_fonts.py public
+```
+
To override the URL, run: <pre><code>nix build --impure --expr 'with (builtins.getFlake (toString ../.)).legacyPackages.x86_64-linux; callPackage ./site { site = "<b><i>URL</i></b>"; }'</code></pre>
#### Continuous Integration
diff --git a/site/content/blog/nixos/prelude.md b/site/content/blog/nixos/prelude.md
@@ -66,7 +66,7 @@ But why stop there? I grabbed [**Spicetify**](https://github.com/Gerg-L/spicetif
You have no idea [^4].
-I don't "install" development tools. I have a [directory](https://github.com/maydayv7/dotfiles/tree/main/shells) that defines isolated environments for C++, Python, JavaScript and so on.
+I don't "install" development tools. I have a [directory](https://github.com/maydayv7/dotfiles/tree/stable/shells) that defines isolated environments for C++, Python, JavaScript and so on.
When I `cd` into a project, [Direnv](https://direnv.net/) and [**Lorri**](https://github.com/nix-community/lorri) kick in, and the compiler, linter, LSP and whatnot along with all of their configuration just _appear_ in my shell. When I leave, they vanish.
Every little thing is specified in code, from backups to network settings and even complex editor workflows (thanks for your concern, but I very likey my trusty [VS Code](https://code.visualstudio.com/)).
@@ -128,7 +128,7 @@ You can check out the list of links below to resourceful Nix documentation/tutor
- NixOS [Package Search](https://search.nixos.org/)
- [`nixpkgs`](https://github.com/NixOS/nixpkgs) Package Repository
- [NUR](https://github.com/nix-community/NUR) Nix User Repository
-- NixOS [Hardware Modules](https://github.com/nixos/hardware)
+- NixOS [Hardware Modules](https://github.com/nixos/nixos-hardware)
- Home Manager [Options](https://nix-community.github.io/home-manager/options.html)
### Other Sources
diff --git a/site/content/blog/nixos/win11vm.md b/site/content/blog/nixos/win11vm.md
@@ -55,7 +55,7 @@ boot.kernelParams = [
];
```
-**Case B - the host renders on the _same_ GPU you want to pass** (Eg. NVIDIA PRIME Sync, where the desktop holds `nvidia_drm`). A NixOS [specialisation](https://nixos.org/manual/nixos/stable/#sec-specialisation) can be used: your default boot keeps the GPU for the desktop, and a different boot entry binds it to `vfio-pci` instead:
+**Case B - the host renders on the _same_ GPU you want to pass** (Eg. NVIDIA PRIME Sync, where the desktop holds `nvidia_drm`). A NixOS [specialisation](https://wiki.nixos.org/wiki/Specialisation) can be used: your default boot keeps the GPU for the desktop, and a different boot entry binds it to `vfio-pci` instead:
```
specialisation.vfio.configuration = {
diff --git a/site/content/test.md b/site/content/test.md
@@ -84,7 +84,7 @@ fn foo(arg: String) -> Result<u32, Io::Error> {
## Math
-Supports Math Type-Setting using [$\KaTeX$](http://khan.github.io/KaTeX/)
+Supports Math Type-Setting using [KaTeX](https://katex.org/)
```
$$
@@ -107,11 +107,11 @@ I'm not a fool to be fooled by a fool, you fool ;)
# Media
-Supports wide range of Multi-Media using [_shortcodes_](https://www.getzola.org/documentation/content/shortcodes/)
+Supports wide range of Multi-Media using [_shortcodes_](https://www.getzola.org/documentation/)
## Image
-{{ figure(src="https://picsum.photos/700", alt="Random", style="border-style: inset; border-radius: 5px;", caption="Random Image", caption_style="font-weight: bold;") }}
+{{ figure(src="https://picsum.photos/700", alt="Random", width=700, height=700, style="border-style: inset; border-radius: 5px;", caption="Random Image", caption_style="font-weight: bold;") }}
## Video
diff --git a/site/default.nix b/site/default.nix
@@ -8,13 +8,19 @@ pkgs.stdenvNoCC.mkDerivation {
version = "stable";
src = ./.;
- buildInputs = [pkgs.zola];
+ nativeBuildInputs = [
+ pkgs.zola
+ (pkgs.python3.withPackages (python: [python.fonttools python.brotli]))
+ ];
installPhase = "cp -r public $out";
- buildPhase = "zola build ${
- if (site != null)
- then "--base-url " + site
- else ""
- }";
+ buildPhase = ''
+ zola build ${
+ if (site != null)
+ then "--base-url " + site
+ else ""
+ }
+ python3 scripts/optimize_fonts.py public
+ '';
meta = {
description = "My Personal Website";
diff --git a/site/git/default.nix b/site/git/default.nix
@@ -32,6 +32,7 @@ in
coreutils
git
stagit-fork
+ (python3.withPackages (python: [python.fonttools python.brotli]))
];
text = ''
@@ -104,6 +105,8 @@ in
echo "Generating Index..."
stagit-index -n ${sitename} "$BUILD_ROOT"/*.git > "$OUTPUT_DIR/index.html"
+ python3 ${../scripts/optimize_fonts.py} "$OUTPUT_DIR" --source "$FONTS_SRC"
+
echo "Successfully built to $OUTPUT_DIR!"
'';
}
diff --git a/site/git/index.js b/site/git/index.js
@@ -14,9 +14,9 @@ export default {
const url = new URL(request.url);
// Theme Switching
- if (url.pathname.endsWith("style.css")) {
+ if (url.pathname === "/style.css") {
const theme = getCookie(request.headers.get("Cookie"), "theme_color");
- if (theme && theme !== "red") {
+ if (["black", "blue"].includes(theme)) {
const themeUrl = new URL(url);
themeUrl.pathname = themeUrl.pathname.replace(
"style.css",
@@ -28,7 +28,7 @@ export default {
if (themed.ok) {
const headers = new Headers(themed.headers);
headers.set("Vary", "Cookie");
- headers.set("Cache-Control", "no-store");
+ headers.set("Cache-Control", "private, no-cache");
return new Response(themed.body, {
status: themed.status,
headers,
@@ -39,10 +39,11 @@ export default {
// Serve assets
const response = await env.ASSETS.fetch(request);
- if (response.status === 404) {
- const fallbackUrl = new URL(request.url);
- fallbackUrl.pathname = "/index.html";
- return env.ASSETS.fetch(new Request(fallbackUrl, request));
+ if (url.pathname === "/style.css") {
+ const headers = new Headers(response.headers);
+ headers.set("Vary", "Cookie");
+ headers.set("Cache-Control", "private, no-cache");
+ return new Response(response.body, { status: response.status, headers });
}
return response;
},
diff --git a/site/git/style.css b/site/git/style.css
@@ -1,18 +1,21 @@
/* Fonts */
@font-face {
font-family: "JetBrainsMono Nerd Font";
+ font-display: swap;
src: url("/fonts/JetBrainsMono-Nerd-Regular.woff2") format("woff2");
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: "JetBrainsMono Nerd Font";
+ font-display: swap;
src: url("/fonts/JetBrainsMono-Nerd-Bold.woff2") format("woff2");
font-weight: bold;
font-style: normal;
}
@font-face {
font-family: "JetBrainsMono Nerd Font";
+ font-display: swap;
src: url("/fonts/JetBrainsMono-Nerd-Italic.woff2") format("woff2");
font-weight: normal;
font-style: italic;
diff --git a/site/git/wrangler.toml b/site/git/wrangler.toml
@@ -5,7 +5,7 @@ compatibility_date = "2025-12-15"
[assets]
binding = "ASSETS"
directory = "./public"
-run_worker_first = true
+run_worker_first = ["/style.css"]
[env.production]
routes = [
diff --git a/site/index.js b/site/index.js
@@ -1,16 +1,39 @@
export default {
async fetch(request, env) {
+ const url = new URL(request.url);
+ if (url.hostname === "www.maydayv7.cc") {
+ url.hostname = "maydayv7.cc";
+ url.protocol = "https:";
+ return Response.redirect(url, 308);
+ }
// Curl
const userAgent = request.headers.get("User-Agent") || "";
if (userAgent.toLowerCase().startsWith("curl")) {
- const url = new URL(request.url);
if (url.pathname === "/") {
url.pathname = "/curl.txt";
- return env.ASSETS.fetch(new Request(url.toString()));
+ const response = await env.ASSETS.fetch(new Request(url, request));
+ const headers = new Headers(response.headers);
+ headers.append("Vary", "User-Agent");
+ return new Response(response.body, {
+ status: response.status,
+ headers,
+ });
}
}
// Serve Website
- return env.ASSETS.fetch(request);
+ const response = await env.ASSETS.fetch(request);
+ if (
+ response.ok &&
+ /^\/processed_images\/[^/]+\.[a-f0-9]{16}\.[a-z0-9]+$/.test(url.pathname)
+ ) {
+ const headers = new Headers(response.headers);
+ headers.set("Cache-Control", "public, max-age=31536000, immutable");
+ return new Response(response.body, { status: response.status, headers });
+ }
+ if (url.pathname !== "/") return response;
+ const headers = new Headers(response.headers);
+ headers.append("Vary", "User-Agent");
+ return new Response(response.body, { status: response.status, headers });
},
};
diff --git a/site/sass/_comments.scss b/site/sass/_comments.scss
@@ -7,6 +7,10 @@
padding: 0 18px;
transition: max-height 0.2s ease-out;
+ &-content {
+ display: flow-root;
+ }
+
&-collapsible {
background-color: var(--background);
border: 2px solid var(--accent-alpha-70);
diff --git a/site/sass/_footer.scss b/site/sass/_footer.scss
@@ -112,6 +112,10 @@ a.backlink {
p {
display: inline;
color: rgba(255, 255, 255, 0.7);
+
+ &:first-of-type {
+ margin-inline-start: 1ch;
+ }
}
&-label {
diff --git a/site/sass/_header.scss b/site/sass/_header.scss
@@ -72,7 +72,7 @@
}
&-mobile {
- @media (min-width: $phone-max-width) {
+ @media (min-width: ($phone-max-width + 1px)) {
display: none;
}
@@ -144,6 +144,9 @@
top: 35px;
&-trigger {
+ background: none;
+ border: none;
+ font: inherit;
color: var(--accent-secondary);
cursor: pointer;
margin: 0;
@@ -160,6 +163,9 @@
}
&-trigger {
+ background: none;
+ font: inherit;
+ cursor: pointer;
display: none;
@media (max-width: $phone-max-width) {
diff --git a/site/sass/_image.scss b/site/sass/_image.scss
@@ -3,6 +3,7 @@
img {
display: block;
max-width: 100%;
+ height: auto;
&.center {
margin-left: auto;
@@ -19,15 +20,17 @@ img {
}
figure {
- display: table;
- min-width: 300px;
+ display: block;
+ width: fit-content;
+ max-width: 100%;
+ min-width: min(300px, 100%);
text-align: center;
figcaption {
background: var(--background);
caption-side: bottom;
color: var(--accent);
- display: table-caption;
+ display: block;
font-size: 14px;
margin-top: 5px;
padding: 5px 10px;
diff --git a/site/sass/_main.scss b/site/sass/_main.scss
@@ -67,6 +67,38 @@ body {
.content {
display: flex;
+ min-width: 0;
+}
+
+.skip-link {
+ position: absolute;
+ left: 20px;
+ top: -100px;
+ padding: 10px;
+ background: var(--background);
+ z-index: 200;
+
+ &:focus {
+ top: 10px;
+ }
+}
+
+:focus-visible {
+ outline: 2px dotted var(--accent-secondary);
+ outline-offset: 3px;
+}
+
+@media (prefers-reduced-motion: reduce) {
+ html {
+ scroll-behavior: auto;
+ }
+ *,
+ *::before,
+ *::after {
+ scroll-behavior: auto !important;
+ transition: none !important;
+ animation: none !important;
+ }
}
h1 {
@@ -100,7 +132,7 @@ h6 {
}
.zola-anchor {
- visibility: hidden;
+ opacity: 0;
}
}
diff --git a/site/sass/_post.scss b/site/sass/_post.scss
@@ -136,6 +136,8 @@ blockquote {
.post {
text-align: left;
+ min-width: 0;
+ overflow-wrap: anywhere;
width: 100%;
&:not(:last-of-type) {
@@ -184,6 +186,7 @@ blockquote {
}
&-sub {
+ font-size: 1.2rem;
--border: 2.5px dotted var(--accent-secondary) !important;
color: var(--accent-secondary) !important;
padding-top: 15px !important;
@@ -293,18 +296,15 @@ blockquote {
margin-left: 0.5rem;
text-decoration: none;
vertical-align: 1%;
- visibility: hidden;
+ opacity: 0;
}
- &:hover {
+ &:hover,
+ &:focus-within {
.zola-anchor {
- visibility: visible;
+ opacity: 1;
}
}
-
- @media (max-width: $phone-max-width) {
- visibility: visible;
- }
}
}
}
diff --git a/site/sass/_search.scss b/site/sass/_search.scss
@@ -3,7 +3,7 @@
.search {
&-container {
display: inline-block;
- left: 666px;
+ align-self: flex-end;
position: relative;
top: -32px;
transition: all 0.5s ease-out;
@@ -20,7 +20,7 @@
}
@media (max-width: $tablet-max-width) {
- left: 300px;
+ align-self: flex-end;
width: 300px;
}
@@ -58,7 +58,7 @@
&__number {
font-size: small;
text-align: end;
- margin: -15px 5px -25px;
+ margin: 0 0 1rem;
}
&__item {
@@ -82,6 +82,8 @@
&__items {
list-style: none;
+ margin: 0;
+ padding: 0;
}
li {
diff --git a/site/sass/fonts.scss b/site/sass/fonts.scss
@@ -2,6 +2,7 @@
@font-face {
font-family: "JetBrainsMono";
+ font-display: swap;
src: url("fonts/JetBrainsMono-Nerd-Regular.woff2") format("woff2");
font-weight: 400;
font-style: normal;
@@ -9,6 +10,7 @@
@font-face {
font-family: "JetBrainsMono";
+ font-display: swap;
src: url("fonts/JetBrainsMono-Nerd-Bold.woff2") format("woff2");
font-weight: 700;
font-style: normal;
@@ -16,6 +18,7 @@
@font-face {
font-family: "JetBrainsMono";
+ font-display: swap;
src: url("fonts/JetBrainsMono-Nerd-Italic.woff2") format("woff2");
font-weight: 400;
font-style: italic;
@@ -23,6 +26,7 @@
@font-face {
font-family: "JetBrainsMono";
+ font-display: swap;
src: url("fonts/JetBrainsMono-Nerd-BoldItalic.woff2") format("woff2");
font-weight: 700;
font-style: italic;
diff --git a/site/scripts/optimize_fonts.py b/site/scripts/optimize_fonts.py
@@ -0,0 +1,68 @@
+"""Subset generated webfonts, retaining all text glyphs and referenced Nerd icons"""
+
+import argparse
+import html
+import re
+from pathlib import Path
+
+from fontTools import subset
+from fontTools.ttLib import TTFont
+
+
+def private_use(codepoint):
+ return (
+ 0xE000 <= codepoint <= 0xF8FF
+ or 0xF0000 <= codepoint <= 0xFFFFD
+ or 0x100000 <= codepoint <= 0x10FFFD
+ )
+
+
+def optimize(output, source):
+ used = set()
+ for path in sorted(output.rglob("*")):
+ if path.suffix not in {".html", ".css", ".js", ".json", ".txt"}:
+ continue
+ text = html.unescape(path.read_text(encoding="utf-8"))
+ used.update(map(ord, text))
+ # CSS content escapes and JavaScript/JSON Unicode escapes
+ used.update(
+ int(value, 16) for value in re.findall(r"\\([0-9a-fA-F]{1,6})", text)
+ )
+ used.update(
+ int(value, 16) for value in re.findall(r"\\u\{?([0-9a-fA-F]{4,6})", text)
+ )
+
+ fonts = sorted(source.glob("JetBrainsMono-Nerd-*.woff2"))
+ if not fonts:
+ raise FileNotFoundError(f"No source webfonts in {source}")
+ for path in fonts:
+ font = TTFont(path, recalcTimestamp=False)
+ required = {
+ codepoint for codepoint in font.getBestCmap() if not private_use(codepoint)
+ } | used
+ options = subset.Options()
+ options.layout_features = ["*"]
+ options.name_IDs = ["*"]
+ options.name_legacy = True
+ options.name_languages = ["*"]
+ options.drop_tables += ["FFTM", "PfEd"] # FontForge editor metadata
+ subsetter = subset.Subsetter(options=options)
+ subsetter.populate(unicodes=required)
+ subsetter.subset(font)
+ target = output / "fonts" / path.name
+ target.parent.mkdir(parents=True, exist_ok=True)
+ font.save(target)
+ font.close()
+ print(f"{path.name}: {path.stat().st_size:,} → {target.stat().st_size:,} bytes")
+
+
+if __name__ == "__main__":
+ parser = argparse.ArgumentParser(description=__doc__)
+ parser.add_argument("output", type=Path)
+ parser.add_argument("--source", type=Path, default=Path("static/fonts"))
+ args = parser.parse_args()
+ if not args.output.is_dir():
+ parser.error("output must be an existing generated site directory")
+ if (args.output / "fonts").resolve() == args.source.resolve():
+ parser.error("output fonts must be separate from the source fonts")
+ optimize(args.output, args.source)
diff --git a/site/shell.nix b/site/shell.nix
@@ -5,6 +5,7 @@ pkgs.mkShell {
git
zola
wrangler
+ (python3.withPackages (python: [python.fonttools python.brotli]))
];
shellHook = ''echo "## Website Builder Shell ##"'';
}
diff --git a/site/static/js/clipboard.js b/site/static/js/clipboard.js
@@ -4,6 +4,10 @@ document.querySelectorAll("pre > code").forEach(function (codeBlock) {
button.type = "button";
button.innerText = " Copy";
button.addEventListener("click", function () {
+ if (!navigator.clipboard) {
+ button.innerText = "Select code to copy";
+ return;
+ }
navigator.clipboard.writeText(codeBlock.innerText).then(
function () {
button.blur();
@@ -19,6 +23,7 @@ document.querySelectorAll("pre > code").forEach(function (codeBlock) {
});
var pre = codeBlock.parentNode;
+ pre.tabIndex = 0;
var block = pre.parentNode.classList.contains("highlight")
? pre.parentNode
: pre;
diff --git a/site/static/js/comments.js b/site/static/js/comments.js
@@ -1,21 +1,63 @@
-var buttons = document.getElementsByClassName("comments-collapsible");
-for (var i = 0; i < buttons.length; i++) {
- buttons[i].addEventListener("click", function () {
- this.classList.toggle("active");
- var content = this.nextElementSibling;
- if (content.style.maxHeight) {
- // Closing
- content.style.maxHeight = content.scrollHeight + "px";
+document.querySelectorAll(".comments-collapsible").forEach((button) => {
+ const content = document.getElementById(button.getAttribute("aria-controls"));
+ const thread = content.querySelector(".comments-content");
+ const loader = thread.querySelector("script[data-src]");
+ const reducedMotion = matchMedia("(prefers-reduced-motion: reduce)");
+ let loaded = false;
+ let open = false;
+ let hideTimer;
+
+ const resize = new ResizeObserver(() => {
+ if (open) content.style.maxHeight = `${thread.scrollHeight}px`;
+ });
+ resize.observe(thread);
+
+ button.addEventListener("click", () => {
+ open = !open;
+ clearTimeout(hideTimer);
+ button.setAttribute("aria-expanded", String(open));
+ button.classList.toggle("active", open);
+ button.textContent = open ? "Hide Comments" : "Show Comments";
+ content.inert = !open;
+
+ if (open) {
+ content.hidden = false;
void content.offsetHeight;
- content.style.maxHeight = null;
- this.innerHTML = "Show Comments";
+ content.style.maxHeight = `${thread.scrollHeight}px`;
} else {
- // Opening
- content.style.maxHeight = content.scrollHeight + "px";
- this.innerHTML = "Hide Comments";
- setTimeout(() => {
- if (content.style.maxHeight) content.style.maxHeight = "none";
- }, 200);
+ content.style.maxHeight = "0px";
+ if (reducedMotion.matches) content.hidden = true;
+ else
+ hideTimer = setTimeout(() => {
+ content.hidden = true;
+ }, 220);
+ }
+
+ if (!open || loaded) return;
+ loaded = true;
+ thread.querySelector('[role="status"]')?.remove();
+ if (thread.id === "disqus_thread") {
+ window.disqus_config = function () {
+ this.page.url = content.dataset.url;
+ this.page.identifier = content.dataset.url;
+ };
+ }
+ const script = document.createElement("script");
+ for (const attribute of loader.attributes) {
+ if (!["type", "data-src"].includes(attribute.name))
+ script.setAttribute(attribute.name, attribute.value);
}
+ script.src = loader.dataset.src;
+ script.async = true;
+ script.onerror = () => {
+ loaded = false;
+ script.remove();
+ const status = document.createElement("p");
+ status.setAttribute("role", "status");
+ status.textContent =
+ "Comments could not load. Close and reopen to retry.";
+ thread.append(status);
+ };
+ thread.append(script);
});
-}
+});
diff --git a/site/static/js/footnote.js b/site/static/js/footnote.js
@@ -14,6 +14,7 @@ window.addEventListener("load", function () {
backlink.href = "#" + refhash;
backlink.className = "backlink";
backlink.innerText = "↩";
+ backlink.setAttribute("aria-label", "Back to footnote reference " + hash);
const paras = footnote.children;
const lastPara = paras[paras.length - 1];
lastPara.appendChild(backlink);
diff --git a/site/static/js/load.js b/site/static/js/load.js
@@ -1,5 +1,2 @@
-var loading = document.querySelector(".loading");
-
-window.addEventListener("load", function () {
- loading.style.display = "none";
-});
+// The HTML is ready when this deferred script runs
+document.querySelector(".loading")?.remove();
diff --git a/site/static/js/mobile.js b/site/static/js/mobile.js
@@ -1,85 +1,53 @@
-!(function () {
- var container = document.querySelector(".container"),
- menu_mobile_trigger = document.querySelector(".menu-trigger"),
- menu_mobile = document.querySelector(".menu-mobile"),
- menu_desktop =
- (document.querySelector(".menu-inner-desktop"),
- document.querySelector(".menu-inner-list-more-trigger")),
- menu_more = document.querySelector(".menu-inner-list-more"),
- page_form = document.querySelector(".pagination__form"),
- phone_width = getComputedStyle(document.body).getPropertyValue(
- "--phoneWidth",
- ),
- is_phone = function () {
- return window.matchMedia(phone_width).matches;
- },
- was_phone = is_phone(),
- toggle_mobile_menu = function () {
- menu_mobile && menu_mobile.classList.toggle("hidden");
- },
- hide_mobile_menu = function () {
- menu_mobile && menu_mobile.classList.add("hidden");
- },
- toggle_menu_more = function () {
- menu_more && menu_more.classList.toggle("hidden");
- },
- hide_menu_more = function () {
- menu_more && menu_more.classList.add("hidden");
- },
- toggle_vis = function () {
- (menu_mobile && is_phone() && menu_mobile.classList.add("hidden"),
- menu_more && !is_phone() && menu_more.classList.add("hidden"));
- };
+(() => {
+ const phone = matchMedia(
+ getComputedStyle(document.documentElement)
+ .getPropertyValue("--phoneWidth")
+ .trim(),
+ );
+ const menus = [
+ [
+ document.querySelector(".menu-trigger"),
+ document.getElementById("mobile-menu"),
+ ],
+ [
+ document.querySelector(".menu-inner-list-more-trigger"),
+ document.getElementById("more-menu"),
+ ],
+ ].filter(([button, panel]) => button && panel);
- (toggle_vis(),
- page_form &&
- (page_form.onsubmit = function (event) {
- var loc;
- if (this.page.value == 1) {
- loc = this.action.slice(0, -5);
- } else {
- loc = this.action += this.page.value + "/";
- }
- event.preventDefault();
- window.location.href = loc;
- }),
- menu_mobile &&
- menu_mobile.addEventListener("click", function (event) {
- return event.stopPropagation();
- }),
- menu_more &&
- menu_more.addEventListener("click", function (event) {
- return event.stopPropagation();
- }),
- document.body.addEventListener("click", function () {
- if (is_phone() || !menu_more || menu_more.classList.contains("hidden")) {
- is_phone() && hide_mobile_menu();
- } else {
- hide_menu_more();
+ function setOpen(button, panel, open) {
+ panel.classList.toggle("hidden", !open);
+ button.setAttribute("aria-expanded", String(open));
+ }
+ for (const [button, panel] of menus) {
+ setOpen(button, panel, false);
+ button.addEventListener("click", () =>
+ setOpen(button, panel, button.getAttribute("aria-expanded") !== "true"),
+ );
+ document.addEventListener("click", (event) => {
+ if (!button.contains(event.target) && !panel.contains(event.target))
+ setOpen(button, panel, false);
+ });
+ document.addEventListener("keydown", (event) => {
+ if (
+ event.key === "Escape" &&
+ button.getAttribute("aria-expanded") === "true"
+ ) {
+ setOpen(button, panel, false);
+ button.focus();
}
- }),
- window.addEventListener("resize", toggle_vis),
- menu_mobile_trigger &&
- (menu_mobile_trigger.addEventListener("click", function (event) {
- (event.stopPropagation(), toggle_mobile_menu());
- }),
- menu_mobile_trigger.addEventListener("keyup", function (event) {
- (event.stopPropagation(),
- event.code === "Enter" && toggle_mobile_menu());
- })),
- menu_desktop &&
- (menu_desktop.addEventListener("click", function (event) {
- (event.stopPropagation(),
- toggle_menu_more(),
- menu_more.getBoundingClientRect().right >
- container.getBoundingClientRect().right &&
- ((menu_more.style.left = "auto"), (menu_more.style.right = 0)));
- }),
- menu_desktop.addEventListener("keyup", function (event) {
- (event.stopPropagation(),
- event.code === "Enter" && toggle_menu_more(),
- menu_more.getBoundingClientRect().right >
- container.getBoundingClientRect().right &&
- ((menu_more.style.left = "auto"), (menu_more.style.right = 0)));
- })));
+ });
+ phone.addEventListener("change", () => setOpen(button, panel, false));
+ }
+ const form = document.querySelector(".pagination__form");
+ if (form)
+ form.addEventListener("submit", (event) => {
+ event.preventDefault();
+ if (!form.reportValidity()) return;
+ const page = Number(form.elements.page.value);
+ location.href =
+ page === 1
+ ? form.action.replace(/page\/$/, "")
+ : `${form.action}${page}/`;
+ });
})();
diff --git a/site/static/js/scroll.js b/site/static/js/scroll.js
@@ -16,10 +16,12 @@ if (scrollButton) {
function scrollBack() {
rootElement.scrollTo({
top: 0,
- behavior: "smooth",
+ behavior: matchMedia("(prefers-reduced-motion: reduce)").matches
+ ? "instant"
+ : "smooth",
});
}
scrollButton.addEventListener("click", scrollBack);
- document.addEventListener("scroll", handleScroll);
+ document.addEventListener("scroll", handleScroll, { passive: true });
}
diff --git a/site/static/js/search.js b/site/static/js/search.js
@@ -1,144 +1,165 @@
-function debounce(func, wait) {
- var timeout;
-
- return function () {
- var context = this;
- var args = arguments;
- clearTimeout(timeout);
+(() => {
+ const input = document.getElementById("search");
+ if (!input) return;
+ const container = input.closest(".search-container");
+ const panel = document.getElementById("search-results");
+ const items = panel.querySelector(".search-results__items");
+ const status = panel.querySelector('[role="status"]');
+ let engine;
+ let library;
+ let timer;
+ let revision = 0;
+
+ function loadEngine() {
+ if (engine) return engine;
+ if (!library) {
+ library = new Promise((resolve, reject) => {
+ if (window.Fuse) return resolve();
+ const script = document.createElement("script");
+ script.src = input.dataset.library;
+ script.onload = resolve;
+ script.onerror = () => {
+ script.remove();
+ library = null;
+ reject(new Error("Search library unavailable"));
+ };
+ document.head.appendChild(script);
+ });
+ }
+ engine = Promise.all([
+ library,
+ fetch(input.dataset.index).then((response) => {
+ if (!response.ok) throw new Error("Search index unavailable");
+ return response.json();
+ }),
+ ])
+ .then(
+ ([, index]) =>
+ new Fuse(index, {
+ keys: [
+ { name: "title", weight: 2 },
+ { name: "body", weight: 1 },
+ ],
+ minMatchCharLength: 2,
+ threshold: 0.3,
+ ignoreLocation: true,
+ }),
+ )
+ .catch((error) => {
+ engine = null;
+ throw error;
+ });
+ return engine;
+ }
- timeout = setTimeout(function () {
- timeout = null;
- func.apply(context, args);
- }, wait);
- };
-}
+ function show(visible) {
+ panel.style.display = visible ? "block" : "none";
+ }
-function makeTeaser(body, terms) {
- var TEASER_MAX_CHARS = 200;
- var lowerBody = body.toLowerCase();
- var lowerTerms = terms.map(function (t) {
- return t.toLowerCase();
- });
+ function close() {
+ revision++;
+ clearTimeout(timer);
+ panel.removeAttribute("aria-busy");
+ show(false);
+ }
- // Find the first occurrence of any term
- var firstIndex = -1;
- for (var i = 0; i < lowerTerms.length; i++) {
- var idx = lowerBody.indexOf(lowerTerms[i]);
- if (idx !== -1 && (firstIndex === -1 || idx < firstIndex)) {
- firstIndex = idx;
+ function teaser(body, terms) {
+ const lower = body.toLowerCase();
+ const matches = terms
+ .map((term) => lower.indexOf(term.toLowerCase()))
+ .filter((index) => index >= 0);
+ const start = Math.max(0, (matches.length ? Math.min(...matches) : 0) - 40);
+ const text =
+ (start ? "…" : "") +
+ body.slice(start, start + 200) +
+ (body.length > start + 200 ? "…" : "");
+ const paragraph = document.createElement("div");
+ const pattern = new RegExp(
+ terms
+ .map((term) => term.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"))
+ .join("|"),
+ "gi",
+ );
+ let position = 0;
+ for (const match of text.matchAll(pattern)) {
+ paragraph.append(text.slice(position, match.index));
+ const bold = document.createElement("b");
+ bold.textContent = match[0];
+ paragraph.append(bold);
+ position = match.index + match[0].length;
}
+ paragraph.append(text.slice(position));
+ return paragraph;
}
- // Start a bit before the first match
- var start = Math.max(0, firstIndex - 40);
- var end = Math.min(body.length, start + TEASER_MAX_CHARS);
- var teaser =
- (start > 0 ? "…" : "") +
- body.substring(start, end) +
- (end < body.length ? "…" : "");
-
- // Bold the matching terms
- for (var i = 0; i < lowerTerms.length; i++) {
- if (lowerTerms[i].length > 0) {
- var regex = new RegExp(
- "(" + lowerTerms[i].replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + ")",
- "gi",
- );
- teaser = teaser.replace(regex, "<b>$1</b>");
+ async function search() {
+ const term = input.value.trim();
+ const request = ++revision;
+ items.replaceChildren();
+ if (!term) return show(false);
+ show(true);
+ status.textContent = "Searching…";
+ panel.setAttribute("aria-busy", "true");
+ try {
+ const fuse = await loadEngine();
+ if (request !== revision) return;
+ const results = fuse.search(term);
+ status.textContent = results.length
+ ? `${results.length} ${results.length === 1 ? "result" : "results"}${results.length > 10 ? " (showing 10)" : ""}`
+ : "No results found.";
+ const fragment = document.createDocumentFragment();
+ for (const { item } of results.slice(0, 10)) {
+ const url = new URL(item.url, location.href);
+ if (url.origin !== location.origin) continue;
+ const row = document.createElement("li");
+ row.className = "search-results__item";
+ const title = document.createElement("h2");
+ title.className = "title";
+ const link = document.createElement("a");
+ link.href = url.href;
+ link.textContent = item.title;
+ title.append(link);
+ row.append(
+ title,
+ teaser((item.body || "").replace(/§/g, ""), term.split(/\s+/)),
+ );
+ const more = document.createElement("a");
+ more.href = url.href;
+ const label = document.createElement("i");
+ label.textContent = "Read More →";
+ more.append(label);
+ row.append(more);
+ fragment.append(row);
+ }
+ items.append(fragment);
+ } catch {
+ if (request === revision)
+ status.textContent = "Search could not load. Try typing again.";
+ } finally {
+ if (request === revision) panel.removeAttribute("aria-busy");
}
}
- return teaser;
-}
-
-function formatSearchResultItem(item, terms) {
- var body = (item.item.body || "").replace(/§/g, "");
- return (
- '<div class="search-results__item">' +
- `<h1 class='title'>` +
- `<a href="${item.item.url}">${item.item.title}</a>` +
- `</h1>` +
- `<div>${makeTeaser(body, terms)}</div>` +
- `<a href='${item.item.url}'>` +
- `<i>Read More <span class="icon is-small">→</span></i>` +
- `</a>` +
- "</div>"
- );
-}
-
-function initSearch() {
- var $searchInput = document.getElementById("search");
- var $searchResults = document.querySelector(".search-results");
- var $searchResultsItems = document.querySelector(".search-results__items");
- var MAX_ITEMS = 10;
- var currentTerm = "";
-
- var fuse = new Fuse(window.searchIndex, {
- keys: [
- { name: "title", weight: 2 },
- { name: "body", weight: 1 },
- ],
- includeMatches: true,
- minMatchCharLength: 2,
- threshold: 0.3,
- ignoreLocation: true,
+ input.addEventListener("focus", () => {
+ loadEngine().catch(() => {});
+ if (input.value.trim()) search();
});
-
- $searchInput.addEventListener(
- "keyup",
- debounce(function () {
- var term = $searchInput.value.trim();
- if (term === currentTerm) {
- return;
- }
-
- $searchResults.style.display = term === "" ? "none" : "block";
- $searchResultsItems.innerHTML = "";
- if (term === "") {
- currentTerm = "";
- return;
- }
-
- var results = fuse.search(term);
- if (results.length === 0) {
- $searchResults.style.display = "none";
- currentTerm = term;
- return;
- }
-
- var number = document.createElement("p");
- number.innerHTML =
- '<div class="search-results__number">' +
- `<b>${results.length}</b> ${
- results.length === 1 ? "result" : "results"
- }` +
- "</div>";
- $searchResultsItems.appendChild(number);
-
- currentTerm = term;
- for (var i = 0; i < Math.min(results.length, MAX_ITEMS); i++) {
- var item = document.createElement("li");
- item.innerHTML = formatSearchResultItem(results[i], term.split(" "));
- $searchResultsItems.appendChild(item);
- }
- }, 150),
- );
-
- window.addEventListener("click", function (e) {
- if (
- $searchResults.style.display == "block" &&
- !$searchResults.contains(e.target)
- ) {
- $searchResults.style.display = "none";
+ input.addEventListener("input", () => {
+ revision++;
+ clearTimeout(timer);
+ if (!input.value.trim()) return close();
+ timer = setTimeout(search, 150);
+ });
+ container.addEventListener("keydown", (event) => {
+ if (event.key === "Escape") {
+ input.focus();
+ close();
}
});
-}
-
-if (
- document.readyState === "complete" ||
- (document.readyState !== "loading" && !document.documentElement.doScroll)
-) {
- initSearch();
-} else {
- document.addEventListener("DOMContentLoaded", initSearch);
-}
+ document.addEventListener("click", (event) => {
+ if (!container.contains(event.target)) close();
+ });
+ container.addEventListener("focusout", (event) => {
+ if (!container.contains(event.relatedTarget)) close();
+ });
+})();
diff --git a/site/static/js/theme.js b/site/static/js/theme.js
@@ -1,138 +1,44 @@
-(function (switch_theme) {
- const COOKIE_NAME = "theme_color";
- function getCookie(name) {
- const v = document.cookie.match("(^|;) ?" + name + "=([^;]*)(;|$)");
- return v ? v[2] : null;
- }
-
- function setCookie(name, value) {
- document.cookie = `${name}=${value}; path=/; max-age=31536000; SameSite=Lax`;
- }
-
- function saveTheme(themeName) {
- setCookie(COOKIE_NAME, themeName);
- }
-
- function clearTheme() {
- document.cookie = `${COOKIE_NAME}=; path=/; max-age=0; SameSite=Lax`;
- }
-
- const THEME = getCookie(COOKIE_NAME);
- const STOP_BLINK_CSS_ID = "stop-blink";
- const STYLESHEET_CLASSNAME = "stylesheet";
- let previousLink = null;
- let baseUrl = "";
-
- const onLinkLoad = (event) => {
- let link = event.currentTarget;
- link.removeEventListener("load", onLinkLoad);
- link.removeEventListener("error", onLinkError);
- removeStylesheets();
- link.className += STYLESHEET_CLASSNAME;
- previousLink = null;
- showBody();
- };
-
- const onLinkError = (event) => {
- let link = event.currentTarget;
- link.removeEventListener("load", onLinkLoad);
- link.removeEventListener("error", onLinkError);
- clearTheme();
- updateThemeSelect(link.id, false);
- link.remove();
- if (previousLink) {
- document.getElementsByTagName("head")[0].appendChild(previousLink);
- }
- let sheets = document.querySelectorAll(`.${STYLESHEET_CLASSNAME}`);
- if (sheets.length > 0) updateThemeSelect(sheets[0].id, true);
- showBody();
- };
-
- function changeTheme(themeName, firstLoad) {
- var fileref = document.createElement("link");
- fileref.rel = "stylesheet";
- fileref.type = "text/css";
- fileref.href = `${baseUrl}${
- baseUrl.slice(-1) !== "/" ? "/" : ""
- }color/${themeName}.css`;
- fileref.id = themeName;
-
- let link = document.getElementsByTagName("head")[0].appendChild(fileref);
- link.addEventListener("load", onLinkLoad);
- link.addEventListener("error", onLinkError);
-
- if (firstLoad) {
- let sheets = document.querySelectorAll(`.${STYLESHEET_CLASSNAME}`);
- if (sheets.length > 0) previousLink = sheets[0];
- }
-
- saveTheme(themeName);
- }
-
- function removeStylesheets() {
- document.querySelectorAll(`.${STYLESHEET_CLASSNAME}`).forEach((el) => {
- if (el !== previousLink) el.remove();
- });
- }
-
- function hideBody() {
- let head = document.getElementsByTagName("head")[0];
- let style = document.createElement("style");
- let css = "body{visibility:hidden;}";
-
- style.id = STOP_BLINK_CSS_ID;
- style.setAttribute("type", "text/css");
-
- if (style.styleSheet) {
- style.styleSheet.cssText = css;
- } else {
- style.appendChild(document.createTextNode(css));
+(() => {
+ const selector = document.getElementById("theme-select");
+ let current = document.getElementById("site-theme");
+ if (!selector || !current) return;
+ const allowed = new Set([...selector.options].map((option) => option.value));
+ let pending;
+
+ function changeTheme(theme) {
+ if (!allowed.has(theme)) return;
+ if (pending) {
+ pending.remove();
+ pending = null;
}
- head.appendChild(style);
- }
-
- function showBody() {
- let css = document.getElementById(STOP_BLINK_CSS_ID);
- if (css) css.remove();
- }
-
- function updateThemeSelect(theme, setSelected) {
- let elements = document.querySelectorAll("#theme-select>option");
- if (elements.length) {
- elements.forEach((element) => {
- if (element.value === theme) {
- if (setSelected) {
- element.selected = "selected";
- } else {
- element.remove();
- }
- }
- });
- } else {
- window.addEventListener("load", () => {
- updateThemeSelect(theme, setSelected);
- });
+ if (theme === current.dataset.theme) {
+ selector.value = theme;
+ return;
}
- }
-
- switch_theme.init = function (url) {
- baseUrl = url;
- if (THEME) {
- setCookie(COOKIE_NAME, THEME);
- if (!document.getElementById(THEME)) {
- hideBody();
- changeTheme(THEME, true);
- updateThemeSelect(THEME, true);
- }
- }
-
- window.addEventListener("load", () => {
- let selector = document.getElementById("theme-select");
- if (selector) {
- selector.onchange = function () {
- changeTheme(this.value);
- };
- }
- });
- };
-})((window.switch_theme = window.switch_theme || {}));
+ const link = document.createElement("link");
+ link.rel = "stylesheet";
+ link.href = new URL(`${theme}.css`, current.href).href;
+ link.dataset.theme = theme;
+ pending = link;
+ link.onload = () => {
+ if (pending !== link) return;
+ current.replaceWith(link);
+ link.id = "site-theme";
+ current = link;
+ pending = null;
+ selector.value = theme;
+ document.cookie = `theme_color=${theme}; path=/; max-age=31536000; SameSite=Lax`;
+ };
+ link.onerror = () => {
+ if (pending !== link) return;
+ link.remove();
+ pending = null;
+ selector.value = current.dataset.theme;
+ };
+ document.head.appendChild(link);
+ }
+
+ selector.addEventListener("change", () => changeTheme(selector.value));
+ const saved = document.cookie.match(/(?:^|;\s*)theme_color=([^;]*)/);
+ if (saved && allowed.has(saved[1])) changeTheme(saved[1]);
+})();
diff --git a/site/templates/base.html b/site/templates/base.html
@@ -20,13 +20,26 @@ squiggle -%} {% import "macros/edit.html" as edit -%} {% import
head::styling(config=config) }} {%- endblock css -%} {%- block font -%} {{
head::font(config=config) }} {%- endblock font -%} {%- block rss -%} {%-
endblock rss -%} {%- block favicon -%} {{ head::favicon(config=config) }}
- {%- endblock favicon -%} {%- block math -%} {%- endblock math -%}
+ {%- endblock favicon -%} {%- block math -%} {%- endblock math -%} {% block
+ analytics -%} {{ analytics::google_analytics(config=config) }} {%- endblock
+ analytics %}
+ <noscript
+ ><style>
+ .js-only,
+ .header .menu-trigger,
+ .header .menu-mobile {
+ display: none !important;
+ }
+ .header .menu-desktop,
+ .header .menu-inner-desktop {
+ display: flex;
+ }
+ </style></noscript
+ >
</head>
- {% block analytics -%} {{ analytics::google_analytics(config=config) }} {%-
- endblock analytics %}
-
<body class="">
+ <a class="skip-link" href="#main-content">Skip to content</a>
{%- if config.extra.full_width -%} {%- set container = "container full" -%}
{%- elif config.extra.center -%} {%- set container = "container center" -%}
{%- else -%} {%- set container = "container" -%} {%- endif -%} {%- if not
@@ -36,9 +49,9 @@ squiggle -%} {% import "macros/edit.html" as edit -%} {% import
{%- block header -%} {{ header::header(config=config,
current_url=current_url) }} {%- endblock header -%}
- <div class="content">
+ <main class="content" id="main-content" tabindex="-1">
{%- block main -%} {{ lists::list_pages() }} {%- endblock main -%}
- </div>
+ </main>
{%- block pagination -%} {{ pagination::paginate(pagelink = current_url)
}} {%- endblock pagination -%} {%- block footer -%}
diff --git a/site/templates/index.html b/site/templates/index.html
@@ -7,7 +7,7 @@
<div class="post-content">{{ section.content | safe }}</div>
</article>
- <h3 class="post-title post-title-sub">Recent Posts</h3>
+ <h2 class="post-title post-title-sub">Recent Posts</h2>
{% set section = get_section(path="blog/_index.md") %}
<ul class="post-list">
{% for page in section.pages -%} {%- if loop.index0 < 5 -%}
diff --git a/site/templates/macros/comments.html b/site/templates/macros/comments.html
@@ -1,44 +1,44 @@
-{% macro comments() %} {% if config.extra.disqus.enabled is defined and
-config.extra.disqus.enabled == true %} {{ comments::disqus() }} {{
-js::comments() }} {% endif %} {% if config.extra.giscus.enabled is defined and
-config.extra.giscus.enabled == true %} {{ comments::giscus() }} {{
-js::comments() }} {% endif %} {% endmacro comments %} {% macro disqus() %}
-<div class="comments" id="disqus_thread"></div>
-<script>
- var disqus_config = function () {
- this.page.url = "{{ page.permalink | safe }}";
- this.page.identifier = "{{ page.permalink | safe }}";
- };
-
- (function () {
- var d = document,
- s = d.createElement("script");
- s.src = "https://{{ config.extra.disqus.short_name }}.disqus.com/embed.js";
- s.setAttribute("data-timestamp", +new Date());
- (d.head || d.body).appendChild(s);
- })();
-</script>
-<noscript
- >Please enable JavaScript to view the
- <a href="https://disqus.com/?ref_noscript"
- >Comments powered by Disqus</a
- ></noscript
+{% macro comments() %} {% if config.extra.disqus.enabled or
+config.extra.giscus.enabled %} {% if config.extra.disqus.enabled %}{% set thread
+= "disqus_thread" %}{% else %}{% set thread = "giscus_thread" %}{% endif %}
+<button
+ type="button"
+ class="comments-collapsible js-only"
+ aria-expanded="false"
+ aria-controls="comments-{{ thread }}"
>
-{% endmacro disqus %} {% macro giscus() %}
-<div class="comments" id="giscus_thread">
- <script
- src="https://giscus.app/client.js"
- data-repo="{{ config.extra.giscus.repo }}"
- data-repo-id="{{ config.extra.giscus.repo_id }}"
- data-category="{{ config.extra.giscus.category }}"
- data-category-id="{{ config.extra.giscus.category_id }}"
- data-mapping="pathname"
- data-reactions-enabled="1"
- data-emit-metadata="0"
- data-input-position="top"
- data-theme="transparent_dark"
- data-lang="en"
- crossorigin="anonymous"
- async></script>
+ Show Comments
+</button>
+<div
+ class="comments"
+ id="comments-{{ thread }}"
+ data-url="{{ page.permalink }}"
+ hidden
+ inert
+>
+ <div class="comments-content" id="{{ thread }}">
+ {% if config.extra.disqus.enabled %}
+ <script
+ type="text/plain"
+ data-src="https://{{ config.extra.disqus.short_name }}.disqus.com/embed.js"
+ ></script>
+ {% else %}
+ <script
+ type="text/plain"
+ data-src="https://giscus.app/client.js"
+ data-repo="{{ config.extra.giscus.repo }}"
+ data-repo-id="{{ config.extra.giscus.repo_id }}"
+ data-category="{{ config.extra.giscus.category }}"
+ data-category-id="{{ config.extra.giscus.category_id }}"
+ data-mapping="pathname"
+ data-reactions-enabled="1"
+ data-emit-metadata="0"
+ data-input-position="top"
+ data-theme="transparent_dark"
+ data-lang="en"
+ crossorigin="anonymous"
+ ></script>
+ {% endif %}
+ </div>
</div>
-{% endmacro giscus %}
+{{ js::comments() }} {% endif %} {% endmacro comments %}
diff --git a/site/templates/macros/footer.html b/site/templates/macros/footer.html
@@ -10,14 +10,14 @@
</span>
</div>
{% endmacro copyright %} {% macro theme(config) %}
-<div class="theme-container">
+<div class="theme-container js-only">
<div class="theme_before"></div>
<div class="theme-content">
- Theme:
+ <label for="theme-select">Theme:</label>
<select id="theme-select">
{% for theme in config.extra.color_schemes %} {% if
config.extra.theme_color %} {% set default_theme =
- config.extra.theme_color %} {% else %} {% set default_theme = "blue" %} {%
+ config.extra.theme_color %} {% else %} {% set default_theme = "red" %} {%
endif %} {% if default_theme == theme %}
<option selected="selected" value="{{ theme }}">{{ theme }}</option>
{% else %}
@@ -30,11 +30,11 @@
{% endmacro theme %} {% macro webring(config) %} {% if config.extra.webring %}
<div class="webring">
<a href="{{ config.extra.webring_url }}/prev/{{ config.extra.author }}"
- ><- prev</a
- >
- <a href="{{ config.extra.webring_url }}">{{ config.extra.webring_name }}</a>
- <a href="{{ config.extra.webring_url }}/next/{{ config.extra.author }}"
- >next -></a
+ ><- prev</a
+ > <a href="{{ config.extra.webring_url }}"
+ >{{ config.extra.webring_name }}</a
+ > <a href="{{ config.extra.webring_url }}/next/{{ config.extra.author }}"
+ >next -></a
>
</div>
{% endif %} {% endmacro webring %}
diff --git a/site/templates/macros/head.html b/site/templates/macros/head.html
@@ -1,12 +1,14 @@
-{%- macro font(config) %} <link rel="stylesheet" href="{{
+{%- macro font(config) %}
+<link rel="preload" href="{{ get_url(path="fonts/JetBrainsMono-Nerd-Regular.woff2") }}" as="font" type="font/woff2" crossorigin />
+<link rel="stylesheet" href="{{
get_url(path="fonts.css", trailing_slash=false) }}"> {% endmacro font -%} {%
macro styling(config) %}
<link rel="stylesheet" href="{{
get_url(path="style.css", trailing_slash=false) }}"> {% if
config.extra.theme_color -%} {% set color = "color/" ~config.extra.theme_color ~
".css" -%}
-<link rel="stylesheet" href="{{ get_url(path=color, trailing_slash=false) }}" />
-{%- else -%} <link rel="stylesheet" href="{{ get_url(path="color/red.css",
+<link id="site-theme" data-theme="{{ config.extra.theme_color }}" rel="stylesheet" href="{{ get_url(path=color, trailing_slash=false) }}" />
+{%- else -%} <link id="site-theme" data-theme="red" rel="stylesheet" href="{{ get_url(path="color/red.css",
trailing_slash=false) }}"> {% endif %} {% endmacro styling %} {% macro
feed_discovery(path) %}
<link
@@ -18,12 +20,29 @@ feed_discovery(path) %}
config.extra.favicon %}
<link
rel="shortcut icon"
- href="{{ get_url(path=config.extra.favicon, trailing_slash=false) }}?"
+ href="{{ get_url(path=config.extra.favicon, trailing_slash=false) }}"
type="image/x-icon" />
{% endif %} {% endmacro favicon %} {% macro general_meta() %}
-<meta http-equiv="content-type" content="text/html; charset=utf-8" />
-<meta
- name="viewport"
- content="width=device-width, initial-scale=1.0, maximum-scale=1" />
-<meta name="description" content="{{ config.description }}" />
+{% set description = config.description %}
+{% set title = config.title %}
+{% if page %}
+ {% set title = page.title %}
+ {% if page.description %}{% set description = page.description %}
+ {% else %}{% set description = page.content | striptags | truncate(length=160) %}{% endif %}
+{% elif section %}
+ {% set title = section.title %}
+ {% if section.description %}{% set description = section.description %}{% endif %}
+{% elif term %}{% set title = term.name %}
+{% elif taxonomy %}{% set title = taxonomy.name %}{% endif %}
+<meta name="description" content="{{ description }}" />
+<meta property="og:title" content="{{ title }}" />
+<meta property="og:description" content="{{ description }}" />
+<meta property="og:site_name" content="{{ config.title }}" />
+<meta property="og:type" content="{% if page and page.date %}article{% else %}website{% endif %}" />
+<meta name="twitter:card" content="summary" />
+{% if current_url %}
+<link rel="canonical" href="{{ current_url }}" />
+<meta property="og:url" content="{{ current_url }}" />
+{% else %}<meta name="robots" content="noindex" />{% endif %}
+{{ head::feed_discovery(path=config.extra.feed_path) }}
{% endmacro general_meta %}
diff --git a/site/templates/macros/header.html b/site/templates/macros/header.html
@@ -3,16 +3,31 @@
<header class="header">
<div class="header-inner">
<div class="header-logo">{{ logo::logo(config=config) }}</div>
- <div class="menu-trigger" tabindex="0">
- <div class="menu-trigger-button">menu</div>
- </div>
+ <button
+ type="button"
+ class="menu-trigger menu-trigger-button"
+ aria-controls="mobile-menu"
+ aria-expanded="false"
+ >
+ menu
+ </button>
</div>
{{ menu::menu(config=config, current_url=current_url) }} {% if
config.build_search_index %}
- <div class="search-container js-only">
- <input class="input" id="search" type="search" placeholder=" Search" />
- <div class="search-results box">
- <div class="search-results__items"></div>
+ <div class="search-container js-only" role="search">
+ <input
+ class="input"
+ id="search"
+ type="search"
+ placeholder=" Search"
+ aria-label="Search this site"
+ aria-controls="search-results"
+ data-library="{{ get_url(path='fuse.min.js') }}"
+ data-index="{{ get_url(path='search_index.en.json') }}"
+ />
+ <div class="search-results box" id="search-results">
+ <p class="search-results__number" role="status"></p>
+ <ul class="search-results__items"></ul>
</div>
</div>
{% endif %}
diff --git a/site/templates/macros/javascript.html b/site/templates/macros/javascript.html
@@ -1,18 +1,15 @@
{% macro script() %}
-<script src="{{ get_url(path="js/load.js") }}"></script>
-<script src="{{ get_url(path="js/theme.js") }}"></script>
-<script src="{{ get_url(path="js/scroll.js") }}"></script>
-<script> switch_theme.init("{{config.base_url | safe}}"); </script>
-<script src="{{ get_url(path="js/clipboard.js") }}"></script>
-<script src="{{ get_url(path="js/footnote.js") }}"></script>
+<script defer src="{{ get_url(path="js/load.js") }}"></script>
+<script defer src="{{ get_url(path="js/theme.js") }}"></script>
+{% if page %}<script defer src="{{ get_url(path="js/scroll.js") }}"></script>{% endif %}
+{% if page and page.content is containing("<pre") %}<script defer src="{{ get_url(path="js/clipboard.js") }}"></script>{% endif %}
+{% if page and page.content is containing("footnote-reference") %}<script defer src="{{ get_url(path="js/footnote.js") }}"></script>{% endif %}
{% if config.build_search_index %}
- <script src="{{ get_url(path="fuse.min.js") }}"></script>
- <script src="{{ get_url(path="search_index.en.js") }}"></script>
- <script src="{{ get_url(path="js/search.js") }}"></script>
+ <script defer src="{{ get_url(path="js/search.js") }}"></script>
{% endif %}
{% endmacro script %}
-{% macro mobile() %} <script src="{{ get_url(path="js/mobile.js") }}"></script> {% endmacro mobile %}
-{% macro comments() %} <script src="{{ get_url(path="js/comments.js") }}"></script> {% endmacro comments %}
+{% macro mobile() %} <script defer src="{{ get_url(path="js/mobile.js") }}"></script> {% endmacro mobile %}
+{% macro comments() %} <script defer src="{{ get_url(path="js/comments.js") }}"></script> {% endmacro comments %}
{% macro katex() %} {% if config.extra.math_katex %}
<link rel="stylesheet" href="{{ get_url(path="math/katex.css") }}">
<script defer type="text/javascript" src="{{ get_url(path="js/katex.js") | safe }}"></script>
@@ -25,5 +22,5 @@
{left: '\\(', right: '\\)', display: false},
{left: '\\[', right: '\\]', display: true}
]
- });"></script>
+ }); document.querySelectorAll('.katex-display').forEach(element => element.tabIndex = 0);"></script>
{% endif %} {% endmacro katex %}
diff --git a/site/templates/macros/logo.html b/site/templates/macros/logo.html
@@ -3,7 +3,7 @@
href="{%- if config.extra.logo_home_link -%}{{ config.extra.logo_home_link }}{%- else -%}{{ config.base_url }}{%- endif -%}">
<div class="logo">
{{ config.extra.logo_text | default(value="logo") | safe }}
- <div class="loading"></div>
+ <div class="loading js-only" aria-hidden="true"></div>
</div>
</a>
{%- endmacro logo -%}
diff --git a/site/templates/macros/menu.html b/site/templates/macros/menu.html
@@ -5,24 +5,24 @@ if config.extra.main_menu_show -%} {%- set show_len =
config.extra.main_menu_show -%} {%- set menu_more = config.extra.main_menu_more
-%} {%- else -%} {%- set show_len = main_len -%} {%- set menu_more = "" -%} {%-
endif -%}
-<nav class="menu-desktop">
+<nav class="menu-desktop" aria-label="Main navigation">
<ul class="menu-inner menu-inner-desktop">
{{ menu::items(menu=menu_items | slice(end=show_len),
current_url=current_url)}} {%- if main_len > show_len -%}
- <ul class="menu-inner-list">
- <li class="menu-inner-list-more-trigger" tabindex="0">
+ <li class="menu-inner-list">
+ <button type="button" class="menu-inner-list-more-trigger" aria-controls="more-menu" aria-expanded="false">
{{ menu_more }} ▾
- </li>
- <ul class="menu-inner-list-more">
+ </button>
+ <ul class="menu-inner-list-more" id="more-menu">
{{ menu::items(menu=menu_items | slice(start=show_len),
current_url=current_url) }}
</ul>
- </ul>
+ </li>
{%- endif -%}
</ul>
</nav>
-<nav class="menu-mobile">
- <ul class="menu-inner menu-inner-mobile hidden">
+<nav class="menu-mobile hidden" id="mobile-menu" aria-label="Main navigation">
+ <ul class="menu-inner menu-inner-mobile">
{{ menu::items(menu=menu_items, current_url=current_url) }}
</ul>
</nav>
diff --git a/site/templates/macros/pagination.html b/site/templates/macros/pagination.html
@@ -5,7 +5,7 @@
{% if paginator.previous -%}
<span class="pagination__buttons-previous">
<a href="{{ paginator.previous }}">
- <span class="button__text"><- newer</span>
+ <span class="button__text"><- newer</span>
</a>
</span>
{% endif -%}
@@ -13,7 +13,8 @@
<form
class="pagination__form"
enctype="text/plain"
- action="{{ paginator.base_url }}">
+ action="{{ paginator.base_url }}"
+ >
<label for="page">page</label>
<input
id="page"
@@ -23,7 +24,8 @@
min="1"
max="{{ paginator.number_pagers }}"
oninput="this.style.width = this.value.length + 3 + 'ch'"
- required />/ {{ paginator.number_pagers }}
+ required
+ />/ {{ paginator.number_pagers }}
<button>Go</button>
</form>
</div>
diff --git a/site/templates/macros/social.html b/site/templates/macros/social.html
@@ -3,53 +3,116 @@
{%- if config.generate_feeds -%}
<a href="{{ config.extra.feed_path }}" style="text-decoration: none">
{%- set rss_image = resize_image(path="../static/social/rss.png", width=33,
- height=33, op="scale", format="png") -%}
- <img src="{{ rss_image.url }}" style="display: inline" alt="RSS Badge" />
+ height=33, op="scale", format="png") -%} {%- set rss_image_2x =
+ resize_image(path="../static/social/rss.png", width=66, height=66,
+ op="scale", format="png") -%} {%- set rss_image_3x =
+ resize_image(path="../static/social/rss.png", width=99, height=99,
+ op="scale", format="png") -%}
+ <img
+ src="{{ rss_image.url }}"
+ srcset="{{ rss_image_2x.url }} 2x, {{ rss_image_3x.url }} 3x"
+ width="33"
+ height="33"
+ loading="lazy"
+ decoding="async"
+ style="display: inline"
+ alt="RSS Badge"
+ />
</a>
{%- endif -%} {%- if config.extra.github_profile -%}
<a href="{{ config.extra.github_profile }}" style="text-decoration: none">
{%- set gh_image = resize_image(path="../static/social/github.png",
- width=33, height=33, op="scale", format="png") -%}
+ width=33, height=33, op="scale", format="png") -%} {%- set gh_image_2x =
+ resize_image(path="../static/social/github.png", width=66, height=66,
+ op="scale", format="png") -%} {%- set gh_image_3x =
+ resize_image(path="../static/social/github.png", width=99, height=99,
+ op="scale", format="png") -%}
<img
src="{{ gh_image.url }}"
+ srcset="{{ gh_image_2x.url }} 2x, {{ gh_image_3x.url }} 3x"
+ width="33"
+ height="33"
+ loading="lazy"
+ decoding="async"
style="display: inline"
- alt="GitHub Profile Badge" />
+ alt="GitHub Profile Badge"
+ />
</a>
{%- endif -%} {%- if config.extra.email -%}
<a href="{{ config.extra.email }}" style="text-decoration: none">
{%- set email_image = resize_image(path="../static/social/mail.png",
- width=36, height=36, op="scale", format="png") -%}
+ width=36, height=36, op="scale", format="png") -%} {%- set email_image_2x =
+ resize_image(path="../static/social/mail.png", width=72, height=72,
+ op="scale", format="png") -%} {%- set email_image_3x =
+ resize_image(path="../static/social/mail.png", width=108, height=108,
+ op="scale", format="png") -%}
<img
src="{{ email_image.url }}"
+ srcset="{{ email_image_2x.url }} 2x, {{ email_image_3x.url }} 3x"
+ width="36"
+ height="36"
+ loading="lazy"
+ decoding="async"
style="display: inline"
- alt="Email Badge" />
+ alt="Email Badge"
+ />
</a>
{%- endif -%} {%- if config.extra.twitter -%}
<a href="{{ config.extra.twitter }}" style="text-decoration: none">
{%- set twitter_image = resize_image(path="../static/social/twitter.png",
- width=33, height=33, op="scale", format="png") -%}
+ width=33, height=33, op="scale", format="png") -%} {%- set twitter_image_2x
+ = resize_image(path="../static/social/twitter.png", width=66, height=66,
+ op="scale", format="png") -%} {%- set twitter_image_3x =
+ resize_image(path="../static/social/twitter.png", width=99, height=99,
+ op="scale", format="png") -%}
<img
src="{{ twitter_image.url }}"
+ srcset="{{ twitter_image_2x.url }} 2x, {{ twitter_image_3x.url }} 3x"
+ width="33"
+ height="33"
+ loading="lazy"
+ decoding="async"
style="display: inline"
- alt="Twitter Badge" />
+ alt="Twitter Badge"
+ />
</a>
{%- endif -%} {%- if config.extra.reddit_profile -%}
<a href="{{ config.extra.reddit_profile }}" style="text-decoration: none">
{%- set reddit_image = resize_image(path="../static/social/reddit.png",
- width=33, height=33, op="scale", format="png") -%}
+ width=33, height=33, op="scale", format="png") -%} {%- set reddit_image_2x =
+ resize_image(path="../static/social/reddit.png", width=66, height=66,
+ op="scale", format="png") -%} {%- set reddit_image_3x =
+ resize_image(path="../static/social/reddit.png", width=99, height=99,
+ op="scale", format="png") -%}
<img
src="{{ reddit_image.url }}"
+ srcset="{{ reddit_image_2x.url }} 2x, {{ reddit_image_3x.url }} 3x"
+ width="33"
+ height="33"
+ loading="lazy"
+ decoding="async"
style="display: inline"
- alt="Reddit Badge" />
+ alt="Reddit Badge"
+ />
</a>
{%- endif -%} {%- if config.extra.discord_profile -%}
<a href="{{ config.extra.discord_profile }}" style="text-decoration: none">
{%- set discord_image = resize_image(path="../static/social/discord.png",
- width=39, height=39, op="scale", format="png") -%}
+ width=39, height=39, op="scale", format="png") -%} {%- set discord_image_2x
+ = resize_image(path="../static/social/discord.png", width=78, height=78,
+ op="scale", format="png") -%} {%- set discord_image_3x =
+ resize_image(path="../static/social/discord.png", width=117, height=117,
+ op="scale", format="png") -%}
<img
src="{{ discord_image.url }}"
+ srcset="{{ discord_image_2x.url }} 2x, {{ discord_image_3x.url }} 3x"
+ width="39"
+ height="39"
+ loading="lazy"
+ decoding="async"
style="display: inline"
- alt="Discord Badge" />
+ alt="Discord Badge"
+ />
</a>
{%- endif -%}
</div>
diff --git a/site/templates/macros/toc.html b/site/templates/macros/toc.html
@@ -1,57 +1,16 @@
-{% macro toc (t) %} {% if t %}
-<div class="toc" id="nav-container">
+{% macro entries(headings) %}
+<ul>
+ {% for heading in headings %}
+ <li>
+ <a href="{{ heading.permalink | safe }}">{{ heading.title }}</a>
+ {% if heading.children %}{{ toc::entries(headings=heading.children) }}{%
+ endif %}
+ </li>
+ {% endfor %}
+</ul>
+{% endmacro entries %} {% macro toc(t) %} {% if t %}
+<nav class="toc" id="nav-container" aria-label="Table of contents">
<p class="toc-head">Table of Contents</p>
- <div id="nav-content">
- <ul>
- {% for h1 in page.toc %}
- <li>
- <a href="{{ h1.permalink | safe }}">{{ h1.title }}</a>
- {% if h1.children %}
- <ul>
- {% for h2 in h1.children %}
- <li>
- <a href="{{ h2.permalink | safe }}">{{ h2.title }}</a>
- </li>
- {% if h2.children %}
- <ul>
- {% for h3 in h2.children %}
- <li>
- <a href="{{ h3.permalink | safe }}">{{ h3.title }}</a>
- {% if h3.children %}
- <ul>
- {% for h4 in h3.children %}
- <li>
- <a href="{{ h4.permalink | safe }}">{{ h4.title }}</a>
- </li>
- {% if h4.children %}
- <ul>
- {% for h5 in h4.children %}
- <li>
- <a href="{{ h5.permalink | safe }}">{{ h5.title }}</a>
- {% if h5.children %}
- <ul>
- {% for h6 in h5.children %}
- <li>
- <a href="{{ h6.permalink | safe }}">{{ h6.title }}</a>
- </li>
- {% endfor %}
- </ul>
- {% endif %}
- </li>
- {% endfor %}
- </ul>
- {% endif %} {% endfor %}
- </ul>
- {% endif %}
- </li>
- {% endfor %}
- </ul>
- {% endif %} {% endfor %}
- </ul>
- {% endif %}
- </li>
- {% endfor %}
- </ul>
- </div>
-</div>
-{% endif %} {% endmacro %}
+ <div id="nav-content">{{ toc::entries(headings=t) }}</div>
+</nav>
+{% endif %} {% endmacro toc %}
diff --git a/site/templates/page.html b/site/templates/page.html
@@ -36,7 +36,6 @@ endblock math -%} {%- block title %}
{% endif %} {% if config.extra.edit_page and page.extra.edit %} {{
edit::edit_page(current_path=current_path) }} {% endif %} {% if
page.extra.comments %}
- <button type="button" class="comments-collapsible">Show Comments</button>
{{ comments::comments() }} {% endif %} {% if page.extra.github %}
<script async defer src="https://buttons.github.io/buttons.js"></script>
{% endif %} {{ pages::related() }}
diff --git a/site/templates/shortcodes/figure.html b/site/templates/shortcodes/figure.html
@@ -1,6 +1,22 @@
{% if src %}
<figure class="{% if position %}{{ position }}{% else -%} center {%- endif %}">
<img
+ loading="lazy"
+ decoding="async"
+ {%
+ if
+ width
+ %}width="{{ width }}"
+ {%
+ endif
+ %}
+ {%
+ if
+ height
+ %}height="{{ height }}"
+ {%
+ endif
+ %}
src="{{ src | safe }}"
{%
if
@@ -16,7 +32,8 @@
style="{{ style }}"
{%
endif
- %} />
+ %}
+ />
{% if caption %}
<figcaption
class="{% if caption_position %}{{ caption_position }}{% else -%} center {%- endif %}"
@@ -27,7 +44,8 @@
style="{{ caption_style | safe }}"
{%
endif
- %}>
+ %}
+ >
{{ caption }}
</figcaption>
{% endif %}
diff --git a/site/templates/shortcodes/image.html b/site/templates/shortcodes/image.html
@@ -1,7 +1,10 @@
<figure
- class="{% if class is defined %}{{ class | safe }}{% else -%} center {%- endif %}">
+ class="{% if class is defined %}{{ class | safe }}{% else -%} center {%- endif %}"
+>
{%- if static is defined -%}
<img
+ loading="lazy"
+ decoding="async"
src="{{ get_url(path=src) | safe }}"
{%
if
@@ -10,9 +13,12 @@
alt="{{ alt }}"
{%
endif
- %} />
+ %}
+ />
{%- else -%} {%- set src = page.path ~ src -%}
<img
+ loading="lazy"
+ decoding="async"
src="{{ get_url(path=src) | safe }}"
{%
if
@@ -21,7 +27,8 @@
alt="{{ alt }}"
{%
endif
- %} />
+ %}
+ />
{%- endif -%} {% if caption %}
<figcaption
{%
@@ -31,7 +38,8 @@
style="{{ caption_style | safe }}"
{%
endif
- %}>
+ %}
+ >
{{ caption }}
</figcaption>
{% endif %}
diff --git a/site/templates/shortcodes/url.html b/site/templates/shortcodes/url.html
@@ -1,4 +1,4 @@
-{% if trail_slash -%} {% set url = get_url(path=path,
+{% if trail_slash is defined -%} {% set url = get_url(path=path,
trailing_slash=trail_slash) %} {%- else -%} {% set url = get_url(path=path) %}
{%- endif -%}
<a href="{{ url }}">{{ text }}</a>
diff --git a/site/templates/shortcodes/youtube.html b/site/templates/shortcodes/youtube.html
@@ -1,5 +1,6 @@
<div class="youtube is-flex is-justify-content-center is-align-items-center">
<iframe
+ title="{{ title | default(value="YouTube video") }}"
width="100%"
src="https://www.youtube.com/embed/{{ id }}"
frameborder="0"
diff --git a/site/zola.toml b/site/zola.toml
@@ -1,13 +1,13 @@
# Site URL
-base_url = "/"
+base_url = "https://maydayv7.cc"
title = "maydayv7"
description = "My Personal Website"
# Default Language used in Feeds
default_language = "en"
-# Compile all '.scss' files in './sass'
-compile_sass = true
+compile_sass = true # Compile all '.scss' files in './sass'
+minify_html = true
# Build search index to be used later by JS Library
build_search_index = true
@@ -47,7 +47,7 @@ include_title = true
include_description = false
include_path = false
include_content = true
-index_format = "fuse_javascript"
+index_format = "fuse_json"
[extra]
# Site Theme