/* ============================================================================ SillyLaird per-page visit counter (network-wide). Injected by nginx before on HTML responses across *.sillylaird.ca (see /etc/nginx/snippets/sillylaird-visit-counter.conf). Also safe to load from site.js / nav.js. Runs once per page load (window.__slVisitCounterInit). Storage is localStorage + sessionStorage, keyed by host + path so each page (and each subdomain) keeps its own count. Language variants share a key. Also beacons a privacy-friendly hit to /api/hit.php (no IP stored server-side; only host/path, coarse browser·OS label, language, referrer host, local count). ============================================================================ */ (function () { "use strict"; if (window.__slVisitCounterInit) return; window.__slVisitCounterInit = true; var path = location.pathname || "/"; // Skip admin / API / auth / binary endpoints — no counter chrome there. 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 HIT_URL = "https://www.sillylaird.ca/api/hit.php"; function pageKey() { var p = (location.pathname || "/").replace(/\/+$/, "") || "/"; // /foo/index.php and /foo/index_jp.php → /foo p = p.replace(/\/index(?:_[a-z]{2})?\.php$/i, "") || "/"; // /links_jp.php / links_zh.php → /links.php p = p.replace(/_(jp|zh)\.php$/i, ".php"); return location.hostname + p; } function ensureStyles() { if (document.getElementById("sl-visit-counter-style")) return; var s = document.createElement("style"); s.id = "sl-visit-counter-style"; s.textContent = ".site-visit-counter{margin:.5rem 0 0;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;" + "font-size:0.8rem;letter-spacing:0.08em;color:rgba(160,160,170,.9);}" + ".site-visit-counter-digits{color:#39ff14;text-shadow:0 0 6px rgba(57,255,20,.35);}" + "body.light-mode .site-visit-counter{color:rgba(40,40,50,.75);}" + "body.light-mode .site-visit-counter-digits{color:#1a7a12;text-shadow:none;}"; (document.head || document.documentElement).appendChild(s); } function ensureMarkup() { var digits = document.getElementById("site-visit-digits"); if (digits) return digits; ensureStyles(); var wrap = document.createElement("p"); wrap.className = "site-visit-counter"; wrap.id = "site-visit-counter"; wrap.setAttribute("aria-live", "polite"); wrap.innerHTML = 'You are visitor ' + '------'; var footerInner = document.querySelector(".site-footer .footer-inner, footer .footer-inner"); var footer = document.querySelector("footer.site-footer, footer"); if (footerInner) { footerInner.appendChild(wrap); } else if (footer) { footer.appendChild(wrap); } else { wrap.style.cssText = "position:fixed;right:10px;bottom:8px;z-index:40;margin:0;padding:4px 8px;" + "background:rgba(0,0,0,.55);border-radius:4px;backdrop-filter:blur(4px);"; document.body.appendChild(wrap); } return document.getElementById("site-visit-digits"); } function reportHit(localCount, isNew) { var payload = { host: location.hostname, path: location.pathname || "/", local_count: localCount, unique: !!isNew, lang: (navigator.language || "").slice(0, 16), ref: document.referrer || "", }; var body = JSON.stringify(payload); try { if (navigator.sendBeacon) { var blob = new Blob([body], { type: "application/json" }); if (navigator.sendBeacon(HIT_URL, blob)) return; } } catch (_) {} try { fetch(HIT_URL, { method: "POST", headers: { "Content-Type": "application/json" }, body: body, mode: "cors", credentials: "omit", keepalive: true, }).catch(function () {}); } catch (_) {} } function count() { var el = ensureMarkup(); if (!el) return; var page = pageKey(); var KEY = "sillylaird_visit_count_v2:" + page; var SESS = "sillylaird_visit_sess_v2:" + page; var n = 0; var isNew = false; try { n = parseInt(localStorage.getItem(KEY) || "0", 10) || 0; } catch (_) {} try { if (!sessionStorage.getItem(SESS)) { n += 1; isNew = true; localStorage.setItem(KEY, String(n)); sessionStorage.setItem(SESS, "1"); } } catch (_) {} el.textContent = String(n).padStart(6, "0"); // Log every page view server-side (unique flag only when new session for page). reportHit(n, isNew); } if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", count); } else { count(); } })();