/* ============================================================================ SillyLaird scroll-to-top (network-wide). Injected by nginx before on HTML responses across *.sillylaird.ca (see /etc/nginx/snippets/sillylaird-visit-counter.conf). Also loaded as a fallback from site.js / nav.js. Runs once (window.__slScrollTopInit). Shows a fixed "▲ TOP" control after the page has been scrolled, and only when the document is actually taller than the viewport. Works without the host page loading site.css (styles come from scroll-to-top.css). ============================================================================ */ (function () { "use strict"; if (window.__slScrollTopInit) return; window.__slScrollTopInit = true; // Embedded forms / comments / changelog iframe: the parent page owns scroll. try { if (window.self !== window.top) return; } catch (_) { return; } var path = location.pathname || "/"; if (/\/(admin|api|login|logout|auth|captcha|relay|meta|submit)(\/|$|\.)/i.test(path)) { return; } if (/\.(xml|json|m3u|pls|css|js|map|ico|png|jpe?g|gif|webp|svg|woff2?|mp3|flac|webm|mp4)(\?|$)/i.test(path)) { return; } var CSS_HREF = "https://www.sillylaird.ca/assets/css/scroll-to-top.css?v=20260821b"; var THRESHOLD = 280; var MIN_OVERFLOW = 240; function labelForLang() { var lang = (document.documentElement.lang || "").toLowerCase(); if (lang.indexOf("ja") === 0) return "先頭へ戻る"; if (lang.indexOf("zh") === 0) return "回到顶部"; return "Scroll to top"; } function prefersReducedMotion() { return window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches; } function scrollY() { return window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0; } function overflowPx() { var el = document.scrollingElement || document.documentElement; return (el ? el.scrollHeight : 0) - (window.innerHeight || 0); } function ensureCss() { var links = document.getElementsByTagName("link"); var i; for (i = 0; i < links.length; i++) { if (links[i].rel === "stylesheet" && (links[i].href || "").indexOf("scroll-to-top.css") !== -1) { return; } } var l = document.createElement("link"); l.rel = "stylesheet"; l.href = CSS_HREF; document.head.appendChild(l); } function ensureTopTarget() { if (document.getElementById("top")) return; document.documentElement.id = "top"; } function dodgeVisitCounter() { var c = document.getElementById("site-visit-counter"); if (!c) return; var pos = (c.style && c.style.position) || ""; var computed = window.getComputedStyle ? window.getComputedStyle(c).position : ""; if (pos === "fixed" || computed === "fixed") { c.setAttribute("data-stt-docked", "1"); // Inline bottom from visit-counter.js wins over the stylesheet, so // move it here or the two chips sit on top of each other. c.style.bottom = "4.65rem"; } } function ensureButton() { var existing = document.getElementById("scroll-to-top"); if (existing) return existing; var a = document.createElement("a"); a.id = "scroll-to-top"; a.className = "scroll-to-top"; a.href = "#top"; var label = labelForLang(); a.setAttribute("aria-label", label); a.title = label; var caret = document.createElement("span"); caret.className = "scroll-to-top-caret"; caret.setAttribute("aria-hidden", "true"); caret.appendChild(document.createTextNode("\u25B2")); var text = document.createElement("span"); text.className = "scroll-to-top-label"; text.setAttribute("aria-hidden", "true"); text.appendChild(document.createTextNode("TOP")); a.appendChild(caret); a.appendChild(text); document.body.appendChild(a); return a; } function goTop(e) { if (e) e.preventDefault(); if (prefersReducedMotion() || typeof window.scrollTo !== "function") { if (typeof window.scrollTo === "function") { window.scrollTo(0, 0); } else { document.documentElement.scrollTop = 0; document.body.scrollTop = 0; } return; } try { window.scrollTo({ top: 0, behavior: "smooth" }); } catch (_) { window.scrollTo(0, 0); } } function init() { ensureCss(); ensureTopTarget(); var btn = ensureButton(); if (!btn) return; document.body.className = (document.body.className ? document.body.className + " " : "") + "has-scroll-to-top"; dodgeVisitCounter(); window.setTimeout(dodgeVisitCounter, 50); window.setTimeout(dodgeVisitCounter, 400); var ticking = false; function update() { ticking = false; var show = overflowPx() > MIN_OVERFLOW && scrollY() > THRESHOLD; if (show) { if (btn.className.indexOf("is-visible") === -1) { btn.className += " is-visible"; } } else { btn.className = btn.className.replace(/\s*is-visible\b/g, ""); } } function onScroll() { if (ticking) return; ticking = true; if (window.requestAnimationFrame) { window.requestAnimationFrame(update); } else { window.setTimeout(update, 16); } } window.addEventListener("scroll", onScroll, false); window.addEventListener("resize", onScroll, false); btn.addEventListener("click", goTop); update(); } if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", init); } else { init(); } })();