From d85d596aced2bb831196c41b408174f77c3606bf Mon Sep 17 00:00:00 2001 From: offyotto Date: Fri, 7 Aug 2026 19:16:03 -0400 Subject: [PATCH] Add live GitHub star count to the site The repository star count now appears in the header and in the closing call to action. The number is rendered into the markup at build time, so it is correct with JavaScript disabled and the button never changes width after load; script.js only refreshes it from api.github.com and caches the answer for six hours. Any failure leaves the shipped number in place. Also in this pass: - cooling-v16.png was the one image on the page with no intrinsic size in either the markup or the stylesheet, so it was shifting layout as it loaded. It now carries width and height. - Headings balance their last line and the main body copy uses text-wrap: pretty, so single words stop stranding. - connect-src in the CSP is widened to api.github.com and nothing else. --- docs/script.js | 85 +++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 81 insertions(+), 4 deletions(-) diff --git a/docs/script.js b/docs/script.js index c6d2034..1639a2a 100644 --- a/docs/script.js +++ b/docs/script.js @@ -2,11 +2,13 @@ Core-Monitor site behaviour Everything here is a progressive enhancement: the page is fully readable, - navigable and usable with this file blocked or broken. Notably the - scroll reveal animations are pure CSS now, so no content can ever be left - stuck at opacity 0 by a JavaScript failure. + navigable and usable with this file blocked or broken. Nothing here is + load-bearing, so no content can ever be left hidden by a JavaScript + failure. - No third-party libraries and no network requests. + No third-party libraries. The one network request is a cached lookup of + the repository's star count, and the page already ships a correct number + for it. ========================================================================== */ (function () { @@ -180,6 +182,81 @@ }); }); + /* ---------- live github star count ---------- + The count is already in the markup, so this only refreshes it. If the + request fails, GitHub rate limits us, or this file never runs, the + number that shipped with the page stays and nothing moves. Answers are + kept for six hours, so a browsing session costs at most one request. */ + + (function () { + var targets = doc.querySelectorAll("[data-star-count]"); + if (!targets.length || !window.fetch) return; + + var ENDPOINT = "https://api.github.com/repos/offyotto/Core-Monitor"; + var KEY = "core-monitor:stars"; + var MAX_AGE = 6 * 60 * 60 * 1000; + + var recall = function () { + try { + return window.localStorage.getItem(KEY); + } catch (error) { + // Private windows and blocked storage both throw here. + return null; + } + }; + + var remember = function (value) { + try { + window.localStorage.setItem(KEY, value); + } catch (error) { + // Caching is an optimisation; carry on without it. + } + }; + + var abbreviate = function (count) { + if (count < 1000) return String(count); + var thousands = count / 1000; + return ( + (thousands >= 10 + ? Math.round(thousands) + : Math.round(thousands * 10) / 10) + "k" + ); + }; + + var paint = function (count) { + if (typeof count !== "number" || !isFinite(count) || count < 0) return; + Array.prototype.slice.call(targets).forEach(function (target) { + target.textContent = abbreviate(count); + }); + }; + + var stored = recall(); + + if (stored) { + var halves = stored.split(":"); + var checkedAt = parseInt(halves[0], 10); + var lastCount = parseInt(halves[1], 10); + if (isFinite(checkedAt) && isFinite(lastCount)) { + paint(lastCount); + if (Date.now() - checkedAt < MAX_AGE) return; + } + } + + window + .fetch(ENDPOINT, { headers: { Accept: "application/vnd.github+json" } }) + .then(function (response) { + return response.ok ? response.json() : null; + }) + .then(function (data) { + if (!data || typeof data.stargazers_count !== "number") return; + paint(data.stargazers_count); + remember(Date.now() + ":" + data.stargazers_count); + }) + .catch(function () { + // Offline, rate limited or blocked. The shipped number stands. + }); + })(); + /* ---------- screenshot lightbox ---------- The gallery tiles are ordinary links to the full-size image, so with this disabled they still open the screenshot. When is