About This Website

Contents
This site is hosted via GitHub Pages, and the full source code is available on Microsoft GitHub.

My static site generator of choice is hakyll, which leverages pandoc for the actual rendering. While the bulk of the work is done by these two programs, there are a few thin layers on top for syntax highlighting and prerendering LaTeX. With the bundled nix flake, building should hopefully be deterministic enough that a simple make watch creates a preview server.

Most of what makes the site “unique” is already stated in the readme; however, this page gives me the chance to expand on some of the ideas, should they not have gotten their own blog post already.

Sidenotes§

Sidenotes—as opposed to footnotes—are implemented using a somewhat customised Tufte css, called sidenotes.css in my case, as well as SideNotesHTML.hs from the pandoc-sidenote library.
Here is an example! By the way, you might notice some links having a little ° after them—these are links to this domain. I’m honestly still not sure this is useful in any way, but I quite like the eye candy.
While this uses absolutely no JavaScript, unlike most pure css implementations it allows arbitrary blocks to be placed inside of sidenotes. I have a post about the implementation details on the Haskell-side of things.

One tricky thing to remember is that the landing page for the blog has to recount the sidenotes, as otherwise they’d show the wrong numbers.

Syntax highlighting§

Syntax highlighting is not provided by Skylighting, as pandoc would normally do, but rather by pygments. It provides better highlighting and supports a lot more languages, so I really see no reason not to use it. It’s still regexp-based instead of being more akin to a “real” parser, but at least I don’t have to trust random C code to not pwn me when I add a new language.
#!/usr/bin/env python3

import sys
from pygments import highlight
from pygments.lexers import get_lexer_by_name
from pygments.formatters import HtmlFormatter

while True:
    lang = sys.stdin.readline().rstrip("\n")
    html = highlight(
        sys.stdin.read(int(sys.stdin.readline().rstrip("\n"))),
        get_lexer_by_name(lang),
        HtmlFormatter(cssclass=f"highlight-{lang}", cssstyles="padding-left: 1em;"),
    )
    print(html, flush=True)

The pygments.css file is an absolute mess of different special cases, and I feel like I need to start over quite soon, but so far it works! I’ve also written a few more words about my transition process from skylighting to pygments, if you’re interested.

KaTeX rendering§

All LaTeX output is pregenerated with KaTeX and directly embedded into the html. This speeds up page loading, and eliminates the need for client-side JavaScript completely. Since this is a static site without comments or any other kind of user-generated content, it seems almost comical to still require every visitor to render the same thing, rather than doing it once server-side.

Interfacing with KaTeX is done with the tiny maths.js script. I originally got the idea from pandoc issue #6651, and the JS side of things hasn’t changed much, except for removing the dependency on deno:
// https://github.com/jgm/pandoc/issues/6651#issuecomment-1099727774

import { createInterface } from "node:readline"

// I don't want to create a package.json…
import { createRequire } from "module";
const katex = createRequire(import.meta.url)('katex');

for await (const line of createInterface({ input: process.stdin })) {
  try {
    let DISPLAY    = ":DISPLAY ";
    let useDisplay = line.startsWith(DISPLAY);
    let cleanLine  = useDisplay ? line.substring(DISPLAY.length) : line;
    console.log(katex.renderToString(cleanLine, {
      output: "html",
      displayMode: useDisplay,
      strict: "error",
      throwOnError: true,
    }));
  } catch (error) {
    throw new Error(`Input: ${line}\n\nError: ${error}`);
  }
}

On the hakyll site, all that’s needed to interface with this is hlKaTeX. Some more implementation considerations are mentioned in a dedicated post.

Section marks§

This is a very small thing, but I think many sites get section marks “wrong” by placing them at the end of the headline. This makes them appear somewhat “ragged” to me, even if they are hidden most of the time (and only shown on hover) anyways. On this site, they are instead moved to the left side of the heading, in the space between the table of contents (or sidebar) and the main content.

BibTeX§

Citations are handled by BibTeX; I’m used to it from writing LaTeX, and it sports some neat pandoc integration. There is a “References” section—not mentioned in the tocat the end of the document, where all citations the page may have mentioned reside and are properly aligned in a table. Clicking on any of [Rie17], [Kel82], or [EGNO15] should jump you to it.

Under the hood, pandoc—by means of citeprocuses some convoluted xml format to describe how citations ought to look. Not finding anything that I’m completely happy with, the site is currently using a hacked-on version of an alphanumeric DIN 1505-2 style, which most reminded me of BibLaTeX’s alphabetic style. I’ve written many more words about this in a dedicated post.

No JavaScript§

As many of the above points already hint at, not using any JavaScript is an explicit goal of mine, which also means aiming for a usable site in text-based browsers such as eww. Some eye candy like small-caps and sidenotes are obviously lost, but the main content should still be comfortably viewable.

Fonts§

If you let it, this website uses lots of custom fonts. The default serif font is Alegreya, my own build of Iosevka stands in for anything monospaced, and for titles I use Vollkorn. Since I’m using KaTeX for rendering maths, there are also quite a few LaTeX fonts loaded whenever a page needs them. While the site of course works with system fonts as well, there has been a somewhat conscious choice to deviate from them. Especially code should look exactly as it appears in my editor, which is particularly relevant when it comes to alignmentUnicode characters may indeed have different apparent widths! Further, lots of system fonts do not support some typographical features, such as small caps
Instead of proper small caps, many system fonts scale down their capital letters. This makes them entirely too thin, and really rather ugly looking. If you’ve not blocked hosted fonts, compare “this this THIS” with “this this THIS”, and notice how small-caps (the middle “this”) in the former example are well-formed, while in the latter they are way too thin.

I’m sorry if this is a sort of “remember that you breathe” situation, and you now notice whenever people use small-caps “incorrectly” in this way.
, that I think give this site a certain “flair”.

I do, however, want to make sure that I’m not sending 2mb per font to every visitor. The Web Open Font Format exists specifically for this purpose, but even that is not enough in a lot of cases. Fonts are quite fully featured nowadays, containing almost every glyph under the sun, which just blows up their size a lot. There thankfully exist some neat tools for aggressively massaging ripping out unwanted code points, and only retaining a subset of them. I’m using the fontTools Python library to only keep the glyphs that are actually used in the generated html pages.
#!/usr/bin/env python
# fmt: off

import os
import re
from pathlib import Path

from bs4 import BeautifulSoup
from fontTools.subset import Options, Subsetter
from fontTools.ttLib import TTFont


code_font = "hopf"
text_font = "Alegreya"
title_font = "Vollkorn"
latex_font = "KaTeX"


def used_glyphs(path: str) -> tuple[str, str, str, str]:
    html = [  # Get HTML for all pages
        BeautifulSoup(Path(f"{p}/{f}").read_text(), "html.parser")
        for (p, _, fs) in os.walk(path)
        for f in fs
        if f.endswith(".html")
    ]

    latex_html = [p.find_all("span", class_=re.compile("katex*")) for p in html]
    latex = set()  # Glyphs used in LaTeX
    [latex.update(tag.get_text()) for page in latex_html for tag in page]

    code_html = [page.find_all("code") for page in html] + [
        page.find_all("div", class_=re.compile("highlight-*")) for page in html
    ]
    code = set()  # Glyphs used in code
    [code.update(tag.get_text()) for page in code_html for tag in page]

    # Fonts used only for titles and headings.
    title_html = [page.find_all(h) for page in html for h in ["h" + str(x) for x in range(1, 7)]]
    title = set()
    [title.update(tag.get_text()) for page in title_html for tag in page]

    # For the regular text, only keep what's strictly needed.
    normal = set()
    [tag.extract() for page in latex_html for tag in page]  # Mutates `hmtl`!
    [tag.extract() for page in code_html for tag in page]   # Mutates `html`!
    [normal.update(page.get_text()) for page in html]

    # Return only the relevant glyphs for each of the fonts.
    return "".join(code), "".join(title), "°▸▾".join(normal), "".join(latex)


def optimise_font(in_file: str, out_file: str, text: str) -> None:
    before_size = os.path.getsize(in_file)  # might be that in_file = out_file

    options = Options(hinting=False, desubroutinize=True)
    if text_font in in_file or latex_font in in_file:
        options.layout_features = ["*"]  # small-caps et al
    elif title_font in in_file:
        options.layout_features += ["smcp", "c2sc"]
    font = TTFont(in_file, lazy=True)
    font.flavor = "woff2"
    subs = Subsetter(options)
    subs.populate(text=text)
    subs.subset(font)
    font.save(out_file)
    font.close()

    print(
        f"Size for {Path(in_file).stem} changed from "
        f"{before_size / 1024:.1f}KB "
        f"to {os.path.getsize(out_file) / 1024:.1f}KB"
    )


PR = os.environ["PROJECT_ROOT"]
in_path = f"{PR}/uncompressed_fonts/"
code, title, normal, latex = used_glyphs(f"{PR}/docs")

for font in os.listdir(in_path):
    in_file = in_path + font
    optimise_font(
        in_file,
        f"{PR}/css/fonts/{font.replace('.ttf', '.woff2')}",
        code if code_font in in_file
        else title if title_font in in_file
        else latex if latex_font in in_file
        else normal,
    )

References§

[EGNO15]
Pavel Etingof and Shlomo Gelaki and Dmitri Nikshych and Victor Ostrik: Tensor categories, Mathematical surveys and monographs. vol. 205: American Mathematical Society, Providence, RI, 2015;  ISBN 978-1-4704-2024-6
[Kel82]
Gregory Maxwell Kelly: Basic concepts of enriched category theory, Lond. Math. Soc. Lect. Note ser. vol. 64: Cambridge University Press, Cambridge. London Mathematical Society, London, 1982
[Rie17]
E. Riehl: Category theory in context, Aurora: Dover modern math originals: Dover Publications, 2017;  ISBN 9780486820804