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