12345function timelineTips() {6 const dots = document.querySelectorAll(".timeline .dot");7 if (dots.length === 0) return;8 const tip = document.createElement("div");9 tip.className = "timeline-tip";10 tip.hidden = true;11 document.body.append(tip);12 for (const dot of dots) {13 14 const title = dot.querySelector("title");15 const [id, message, meta] = (title?.textContent ?? "").split("\n");16 title?.remove();17 dot.setAttribute("aria-label", `${id} ${message}`);18 dot.addEventListener("pointerenter", (event) => {19 tip.replaceChildren(line("tip-id", id), line("tip-msg", message), line("tip-meta", meta));20 tip.hidden = false;21 const left = event.clientX + 14;22 tip.style.left = `${left + 340 > window.innerWidth ? Math.max(8, event.clientX - 354) : left}px`;23 tip.style.top = `${event.clientY - 10}px`;24 });25 dot.addEventListener("pointerleave", () => {26 tip.hidden = true;27 });28 }29}3031function line(className, text) {32 const div = document.createElement("div");33 div.className = className;34 div.textContent = text ?? "";35 return div;36}3738function copyButtons() {39 for (const button of document.querySelectorAll(".copy-btn[data-copy]")) {40 button.hidden = false;41 button.addEventListener("click", async () => {42 await navigator.clipboard.writeText(button.dataset.copy);43 button.textContent = "copied";44 button.classList.add("copied");45 setTimeout(() => {46 button.textContent = "copy";47 button.classList.remove("copied");48 }, 1400);49 });50 }51}525354function lineRanges() {55 const code = document.querySelector(".code");56 if (!code) return;57 let anchor = null;58 const mark = () => {59 for (const marked of code.querySelectorAll(".line.hl")) marked.classList.remove("hl");60 const match = /^#L(\d+)(?:-L(\d+))?$/.exec(location.hash);61 if (!match) return;62 const from = Number(match[1]);63 const to = Number(match[2] ?? match[1]);64 anchor = from;65 for (let n = Math.min(from, to); n <= Math.max(from, to); n++) {66 document.getElementById(`L${n}`)?.classList.add("hl");67 }68 document.getElementById(`L${Math.min(from, to)}`)?.scrollIntoView({ block: "center" });69 };70 code.addEventListener("click", (event) => {71 const link = event.target.closest(".ln");72 if (!link || !event.shiftKey || anchor === null) return;73 event.preventDefault();74 const n = Number(link.textContent);75 history.replaceState(null, "", `#L${Math.min(anchor, n)}-L${Math.max(anchor, n)}`);76 mark();77 });78 window.addEventListener("hashchange", mark);79 mark();80}81828384function atlas() {85 const root = document.querySelector(".atlas");86 const filter = document.querySelector(".atlas-filter");87 if (!root || !filter) return;88 const panel = root.querySelector(".atlas-panel");89 const folders = [...root.querySelectorAll(".atlas-folder")];90 const files = [...root.querySelectorAll(".atlas-file")];91 const key = `kepr:atlas:${root.dataset.repo}`;92 const opened = new Set(remembered(key));93 for (const folder of folders) {94 folder.open = opened.has(folder.dataset.path);95 folder.addEventListener("toggle", () => {96 if (filter.value.trim()) return;97 if (folder.open) opened.add(folder.dataset.path);98 else opened.delete(folder.dataset.path);99 remember(key, [...opened]);100 });101 }102 let selected = null;103 const show = (row) => {104 selected?.classList.remove("selected");105 selected = row;106 row.classList.add("selected");107 panel.replaceChildren(row.nextElementSibling.content.cloneNode(true));108 };109 for (const row of root.querySelectorAll(".atlas-row")) {110 row.addEventListener("pointerenter", (event) => {111 if (event.pointerType === "mouse") show(row);112 });113 row.addEventListener("focus", () => show(row));114 }115 filter.addEventListener("input", () => {116 const query = filter.value.trim().toLowerCase();117 for (const file of files) file.hidden = query !== "" && !file.dataset.name.includes(query);118 for (const folder of folders) {119 folder.hidden = query !== "" && !folder.querySelector(".atlas-file:not([hidden])");120 folder.open = query !== "" || opened.has(folder.dataset.path);121 }122 });123 filter.hidden = false;124 panel.hidden = false;125}126127128129function remembered(key) {130 try {131 const value = JSON.parse(localStorage.getItem(key) ?? "[]");132 return Array.isArray(value) && value.every((path) => typeof path === "string") ? value : [];133 } catch {134 return [];135 }136}137138function remember(key, value) {139 try {140 localStorage.setItem(key, JSON.stringify(value));141 } catch {}142}143144145146function hexGlow() {147 const columns = document.querySelectorAll(".hex-col");148 if (columns.length === 0) return;149 const reduce = matchMedia("(prefers-reduced-motion: reduce)");150 let timer = null;151 const update = () => {152 clearInterval(timer);153 for (const column of columns) column.classList.remove("glow");154 if (reduce.matches) return;155 timer = setInterval(() => {156 const column = columns[Math.floor(Math.random() * columns.length)];157 if (document.querySelectorAll(".hex-col.glow").length >= 3) return;158 column.classList.add("glow");159 setTimeout(() => column.classList.remove("glow"), 3200);160 }, 1500);161 };162 reduce.addEventListener("change", update);163 update();164}165166timelineTips();167copyButtons();168lineRanges();169atlas();170hexGlow();