SPB Git

spb/khaelor Public

KHAELOR — a terminal-native autonomous engineering agent powered by Anthropic.

TypeScript 82.9% HTML 14.9% CSS 1.1% JavaScript 0.7%
4.0 KB · 121 lines javascript
Raw Blame History
1/**2 * KHAELOR3 * File: website/public/site.js4 * Description: Minimal site behavior — theme toggle, active-nav highlighting, copy-to-clipboard on code blocks.5 *6 * Author: Simon-Pierre Boucher7 * Contact: contact@spboucher.ai8 */910(function () {11  "use strict";1213  // ── theme ──────────────────────────────────────────────────────────14  // The initial theme is applied by the inline <head> snippet (no FOUC);15  // this wires the toggle button.16  var root = document.documentElement;1718  function currentTheme() {19    return root.getAttribute("data-theme") === "light" ? "light" : "dark";20  }2122  function applyTheme(theme) {23    if (theme === "light") {24      root.setAttribute("data-theme", "light");25    } else {26      root.removeAttribute("data-theme");27    }28    var btn = document.getElementById("theme-toggle");29    if (btn) btn.textContent = theme === "light" ? "dark" : "light";30  }3132  document.addEventListener("DOMContentLoaded", function () {33    applyTheme(currentTheme());34    var btn = document.getElementById("theme-toggle");35    if (btn) {36      btn.addEventListener("click", function () {37        var next = currentTheme() === "light" ? "dark" : "light";38        try {39          localStorage.setItem("khaelor-theme", next);40        } catch (e) {41          /* private mode — theme just won't persist */42        }43        applyTheme(next);44      });45    }4647    highlightNav();48    addCopyButtons();49  });5051  // ── active nav highlighting ────────────────────────────────────────52  function highlightNav() {53    var path = location.pathname.replace(/\/+$/, "");54    if (path === "") path = "/index.html";55    var links = document.querySelectorAll(".sidebar a, .site-header nav a");56    links.forEach(function (a) {57      var href = a.getAttribute("href");58      if (!href || href.charAt(0) === "#") return;59      var target = new URL(href, location.href).pathname.replace(/\/+$/, "");60      if (target === "") target = "/index.html";61      if (target === path) {62        a.classList.add("active");63      } else if (64        a.closest(".site-header") &&65        target.indexOf("/docs/") !== -1 &&66        path.indexOf("/docs/") === 067      ) {68        // header "Docs" link stays lit anywhere under /docs/69        a.classList.add("active");70      }71    });72  }7374  // ── copy-to-clipboard on code blocks ───────────────────────────────75  function copyText(text, btn) {76    function done(ok) {77      btn.textContent = ok ? "copied" : "failed";78      btn.classList.toggle("copied", ok);79      setTimeout(function () {80        btn.textContent = "copy";81        btn.classList.remove("copied");82      }, 1600);83    }84    if (navigator.clipboard && navigator.clipboard.writeText) {85      navigator.clipboard.writeText(text).then(86        function () { done(true); },87        function () { done(false); }88      );89    } else {90      done(false);91    }92  }9394  function addCopyButtons() {95    // <pre data-copy> blocks get a copy button; the hero install line has its own.96    document.querySelectorAll("pre").forEach(function (pre) {97      if (pre.hasAttribute("data-no-copy")) return;98      var code = pre.querySelector("code");99      if (!code) return;100      var btn = document.createElement("button");101      btn.type = "button";102      btn.className = "copy-btn";103      btn.textContent = "copy";104      btn.setAttribute("aria-label", "Copy code to clipboard");105      btn.addEventListener("click", function () {106        copyText(code.innerText.replace(/\n$/, ""), btn);107      });108      pre.appendChild(btn);109    });110111    document.querySelectorAll(".install-line").forEach(function (line) {112      var code = line.querySelector("code");113      var btn = line.querySelector("button");114      if (!code || !btn) return;115      btn.addEventListener("click", function () {116        copyText(code.innerText.trim(), btn);117      });118    });119  }120})();121