document.documentElement.classList.add("js"); const SIDENOTES = { inside: true, // true: notes inside the frame, right of the post. false: outside, in the margin insideShare: 0.21, // share of the post area given to the notes when inside (0.3 = 70/30) border: false, // border around each note // readingWidth: "70ch", // max width of the post text, null = no cap readingWidth: null, // max width of the post text, null = no cap }; document.addEventListener("htmx:load", () => { const area = document.querySelector("[data-scroll-area]"); const article = area?.querySelector("article"); if (!area || !article) return; article.style.maxWidth = SIDENOTES.readingWidth ?? "none"; if (SIDENOTES.inside) article.style.marginRight = `${SIDENOTES.insideShare * 100}%`; const refs = [...document.querySelectorAll("a.footnote-ref")]; if (refs.length === 0) return; const panel = document.createElement("div"); panel.className = "sidenotes" + (SIDENOTES.border ? " bordered" : ""); document.body.append(panel); const items = refs.map((ref) => { ref.addEventListener("click", (e) => e.preventDefault()); const note = document.getElementById(ref.getAttribute("href").slice(1)); const body = document.createElement("div"); body.append(...note.cloneNode(true).childNodes); body.querySelector(".footnote-backref")?.remove(); const num = document.createElement("sup"); num.className = "sidenote-num"; num.textContent = ref.textContent; (body.querySelector("p") ?? body).prepend(num); const el = document.createElement("div"); el.className = "sidenote"; el.append(body); panel.append(el); return { ref, el }; }); const gap = 6; let queued = false; const layout = () => { queued = false; const rem = parseFloat(getComputedStyle(document.documentElement).fontSize); const top0 = area.getBoundingClientRect().top + area.clientTop; let left, right; if (SIDENOTES.inside) { const r = area.getBoundingClientRect(); left = article.getBoundingClientRect().right + rem; right = r.left + area.clientLeft + area.clientWidth - parseFloat(getComputedStyle(area).paddingRight); } else { left = area.closest("main").getBoundingClientRect().right + rem; right = window.innerWidth - rem; } Object.assign(panel.style, { left: `${left}px`, top: `${top0}px`, width: `${Math.max(0, right - left)}px`, height: `${area.clientHeight}px`, }); let bottom = -Infinity; for (const { ref, el } of items) { const top = Math.max( ref.getBoundingClientRect().top - top0, bottom + gap, ); el.style.top = `${top}px`; bottom = top + el.offsetHeight; } }; const schedule = () => { if (!queued) { queued = true; requestAnimationFrame(layout); } }; area.addEventListener("scroll", schedule, { passive: true }); window.addEventListener("resize", schedule); new ResizeObserver(schedule).observe(area); document.fonts?.ready.then(schedule); layout(); });