website

#astro#js#html#css

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);
  });
});