1import { bindThemeControls, initTheme } from "./theme";
2import { decodeCloudflareEmails } from "./email";
3import { initPhotoPreview } from "./photo-preview";
4import { initCards } from "./cards";
5import { initToc } from "./toc";
6import { initCopy, initFeedCopy } from "./copy";
7import { initSrcView } from "./src-view";
8import { initClouds } from "./clouds";
9import { navigate, startRouter } from "./router";
10import { asElement } from "./dom";
11
12// Per-page wiring, re-runnable after every body swap. A fresh AbortController
13// each call tears down the previous page's listeners in one shot, so nothing
14// stacks across navigations.
15let pageController: AbortController | undefined;
16
17const initPage = (): void => {
18 pageController?.abort();
19 pageController = new AbortController();
20 const { signal } = pageController;
21
22 initTheme();
23 decodeCloudflareEmails();
24 initPhotoPreview(signal);
25 initCards(signal, navigate);
26 initToc(signal);
27 initCopy(signal);
28 initFeedCopy(signal);
29 initSrcView(signal);
30 initClouds(signal);
31};
32
33// .menu-panel lives outside .site-header-outer (see menu-panel.html) and
34// isn't touched by the router's swap, so a session-level delegated listener
35// is enough - same reasoning as bindThemeControls().
36const bindMenuClose = (): void => {
37 document.addEventListener("click", (e) => {
38 if (!asElement(e.target)?.closest("[data-menu-close]")) return;
39 const details = document.querySelector<HTMLDetailsElement>(".menu-mobile");
40 if (details) details.open = false;
41 });
42};
43
44document.addEventListener("DOMContentLoaded", () => {
45 bindThemeControls();
46 bindMenuClose();
47 startRouter(initPage); // the router calls initPage() after every swap
48 initPage();
49});