| (function () { | |
| "use strict"; | |
| // ---------- Theme toggle ---------- | |
| const root = document.documentElement; | |
| const storedTheme = localStorage.getItem("ff-theme"); | |
| if (storedTheme === "light" || storedTheme === "dark") { | |
| root.setAttribute("data-theme", storedTheme); | |
| } | |
| const toggle = document.getElementById("theme-toggle"); | |
| if (toggle) { | |
| toggle.addEventListener("click", function () { | |
| const current = | |
| root.getAttribute("data-theme") || | |
| (window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"); | |
| const next = current === "dark" ? "light" : "dark"; | |
| root.setAttribute("data-theme", next); | |
| localStorage.setItem("ff-theme", next); | |
| }); | |
| } | |
| // ---------- Copy-to-clipboard on code blocks ---------- | |
| document.querySelectorAll("pre > code").forEach(function (code) { | |
| const pre = code.parentElement; | |
| if (!pre || pre.querySelector(".copy-btn")) return; | |
| const btn = document.createElement("button"); | |
| btn.className = "copy-btn"; | |
| btn.type = "button"; | |
| btn.textContent = "Copy"; | |
| btn.setAttribute("aria-label", "Copy code to clipboard"); | |
| btn.addEventListener("click", function () { | |
| const text = code.innerText; | |
| const done = function () { | |
| btn.textContent = "Copied"; | |
| btn.classList.add("copied"); | |
| setTimeout(function () { | |
| btn.textContent = "Copy"; | |
| btn.classList.remove("copied"); | |
| }, 1400); | |
| }; | |
| if (navigator.clipboard && navigator.clipboard.writeText) { | |
| navigator.clipboard.writeText(text).then(done, function () { | |
| fallbackCopy(text); | |
| done(); | |
| }); | |
| } else { | |
| fallbackCopy(text); | |
| done(); | |
| } | |
| }); | |
| pre.appendChild(btn); | |
| }); | |
| function fallbackCopy(text) { | |
| const ta = document.createElement("textarea"); | |
| ta.value = text; | |
| ta.style.position = "fixed"; | |
| ta.style.opacity = "0"; | |
| document.body.appendChild(ta); | |
| ta.select(); | |
| try { document.execCommand("copy"); } catch (e) { /* ignore */ } | |
| document.body.removeChild(ta); | |
| } | |
| // ---------- Live version refresh from GitHub Releases ---------- | |
| // The build-time workflow substitutes {{VERSION}} in the HTML; this | |
| // additionally refreshes the displayed version in the browser so the | |
| // site always reflects the very latest release without redeploying. | |
| const versionNodes = document.querySelectorAll("[data-version]"); | |
| const downloadLink = document.getElementById("download-latest"); | |
| if (versionNodes.length === 0) return; | |
| // Don't refetch more than once per hour per visitor. | |
| const CACHE_KEY = "ff-latest-release"; | |
| const CACHE_TTL = 60 * 60 * 1000; | |
| let cached = null; | |
| try { | |
| const raw = localStorage.getItem(CACHE_KEY); | |
| if (raw) { | |
| const parsed = JSON.parse(raw); | |
| if (parsed && parsed.ts && Date.now() - parsed.ts < CACHE_TTL) { | |
| cached = parsed; | |
| } | |
| } | |
| } catch (e) { /* ignore */ } | |
| if (cached && cached.tag) { | |
| applyVersion(cached.tag, cached.url); | |
| } else { | |
| fetch("https://api.github.com/repos/fastfloat/fast_float/releases/latest", { | |
| headers: { Accept: "application/vnd.github+json" }, | |
| }) | |
| .then(function (r) { return r.ok ? r.json() : null; }) | |
| .then(function (data) { | |
| if (!data || !data.tag_name) return; | |
| try { | |
| localStorage.setItem( | |
| CACHE_KEY, | |
| JSON.stringify({ ts: Date.now(), tag: data.tag_name, url: data.html_url }) | |
| ); | |
| } catch (e) { /* ignore */ } | |
| applyVersion(data.tag_name, data.html_url); | |
| }) | |
| .catch(function () { /* offline / rate limited — keep build-time value */ }); | |
| } | |
| function applyVersion(tag, url) { | |
| const clean = tag.replace(/^v/, ""); | |
| versionNodes.forEach(function (el) { | |
| // Preserve a leading "v" if the original text used one. | |
| const wasV = (el.textContent || "").trim().startsWith("v"); | |
| el.textContent = (wasV ? "v" : "") + clean; | |
| }); | |
| if (downloadLink && url) downloadLink.href = url; | |
| } | |
| })(); |