website
git clone https://git.pyrossh.dev/website
木 Personal website of pyrossh. Built with astrojs, shiki, vite.
assets/client.js
// All client-side JS the site ships, in one plain vanilla file — no
// bundler needed for two small, independent event listeners.
// Wires up the theme button rendered as static markup in site-header.njk
// (no web component, no framework — a stateless click-to-toggle button
// doesn't need one). Loaded with `defer` so this runs after the DOM is
// parsed and #theme-change already exists.
document.getElementById("theme-change").addEventListener("click", () => {
const root = document.documentElement;
// data-theme is tri-state: absent means "follow the OS preference" (see
// `color-scheme: light dark` in shared.css). Resolve against that OS
// preference when no explicit value is set yet, so the first click
// always flips to the opposite of what's *currently shown* rather than
// assuming light.
const currentTheme = root.getAttribute("data-theme");
const isDark = currentTheme ? currentTheme === "dark" : matchMedia("(prefers-color-scheme: dark)").matches;
const next = isDark ? "light" : "dark";
root.setAttribute("data-theme", next);
localStorage.setItem("theme", next);
});
// .file-explorer-tree lives outside #file-pane (see the comment on
// .file-explorer in repos.css) so htmx swaps of #file-pane never re-render
// it — clicking a file never rebuilds the tree. That's good for keeping
// scroll position/expanded folders, but it also means the .active-file
// class painted at page-load time never moves on its own. Move it here,
// synchronously on click, instead of waiting on htmx's response.
document.addEventListener("click", (e) => {
const link = e.target.closest(".file-explorer-tree a");
if (!link) return;
const tree = link.closest(".file-explorer-tree");
const current = tree.querySelector("a.active-file");
if (current) current.classList.remove("active-file");
link.classList.add("active-file");
});
// Copies the git clone command shown in the repo header (see .clone-cmd in
// repos.css) and flips the button to a checkmark for a bit as feedback,
// instead of a tooltip/toast that would need its own positioning logic.
document.addEventListener("click", (e) => {
const btn = e.target.closest(".copy-btn");
if (!btn) return;
navigator.clipboard.writeText(btn.dataset.clone).then(() => {
clearTimeout(btn._copyResetTimer);
btn.classList.add("copied");
btn._copyResetTimer = setTimeout(() => btn.classList.remove("copied"), 1500);
});
});