From f6c599fef2ee25f770aa127dd4da6e756bfb4e37 Mon Sep 17 00:00:00 2001 From: mstrhakr Date: Fri, 16 Oct 2026 13:48:03 -0400 Subject: [PATCH] feat(scans): add GoatCounter website analytics Docs: document website analytics in README; no canonical guide impact because setup, loader, and utility behavior are unchanged. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- README.md | 2 ++ site/index.html | 2 ++ tests/site.test.cjs | 11 ++++++++++- 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e6e9ac8..bb18f56 100644 --- a/README.md +++ b/README.md @@ -117,6 +117,8 @@ Website source is in [site/](site/); the only loader source is [scans.ps1](scans The landing page follows the PrintMaster site's Solarized palette, teal accents, wordmark, and panel styling. Standalone navigation is Scans-focused: its brand links home locally, setup and release links stay on the page, and external Scans resources are explicitly labeled. The footer separates Agent/Server downloads and fleet-monitoring links under "More from PrintMaster." Styles and the logo are local to this repository; navigation works without JavaScript, and the layout supports light/dark preferences, keyboard navigation, and reduced motion. Release rendering and copy buttons use [site/releases.js](site/releases.js). +The landing page loads GoatCounter asynchronously using `https://printmaster-scans.goatcounter.com/count`, matching the analytics integration on the main and documentation sites. This website-only integration does not change the PowerShell loader or utility. + The main website can frame `https://scans.printmaster.work/?embed=1` at `/scans/`. [site/embed.js](site/embed.js) hides this site's header/footer and locks the dark palette only after a handshake from the immediate parent at `https://printmaster.work`. Without that handshake (including direct visits and JavaScript disabled), the full standalone site remains visible. Origin- and source-checked messages report content height and anchor positions to the main site's wrapper. External links open at the top level; setup commands retain their canonical standalone URLs. The main site owns its header/footer and clipboard delegation. Deploy this embed support before the main-site wrapper. The handshake restricts embed-mode activation, not who can frame the public standalone site. GitHub Pages cannot set a custom HTTP `Content-Security-Policy: frame-ancestors` header; a meta tag cannot enforce it. If the domain later uses a configurable edge/server, set `frame-ancestors 'self' https://printmaster.work` there and avoid `X-Frame-Options: DENY`/`SAMEORIGIN`, which would block the cross-origin main-site frame. diff --git a/site/index.html b/site/index.html index b332a66..b67a1e6 100644 --- a/site/index.html +++ b/site/index.html @@ -112,5 +112,7 @@

If something goes wrong

+ diff --git a/tests/site.test.cjs b/tests/site.test.cjs index ea46124..d201df8 100644 --- a/tests/site.test.cjs +++ b/tests/site.test.cjs @@ -21,6 +21,15 @@ test('includes local PrintMaster branding, a home link, and the canonical Scans assert.match(html, /
/); }); +test('loads the Scans GoatCounter tracker once and asynchronously', () => { + const html = fs.readFileSync(path.join(__dirname, '..', 'site', 'index.html'), 'utf8'); + const trackers = [...html.matchAll(/]*\bdata-goatcounter="([^"]+)"[^>]*><\/script>/g)]; + assert.equal(trackers.length, 1); + assert.equal(trackers[0][1], 'https://printmaster-scans.goatcounter.com/count'); + assert.match(trackers[0][0], /\sasync\s/); + assert.match(trackers[0][0], /src="\/\/gc\.zgo\.at\/count\.js"/); +}); + test('provides cross-site navigation and working setup anchors without JavaScript', () => { const html = fs.readFileSync(path.join(__dirname, '..', 'site', 'index.html'), 'utf8'); assert.match(html, /href="https:\/\/printmaster\.work\/downloads"/); @@ -45,7 +54,7 @@ test('provides cross-site navigation and working setup anchors without JavaScrip assert.ok(ids.includes(id), `missing release/copy hook ${id}`); } for (const match of html.matchAll(/(?:src|href)="([^"#]+)"/g)) { - if (/^https?:/.test(match[1])) continue; + if (/^(?:https?:)?\/\//.test(match[1])) continue; if (match[1] === 'releases.json') continue; const relative = match[1] === 'scans.ps1' || match[1].startsWith('img/') ? match[1] : path.join('site', match[1]); assert.ok(fs.existsSync(path.join(__dirname, '..', relative)), `missing site asset ${match[1]}`);