diff --git a/scripts/build-site.mjs b/scripts/build-site.mjs
index 3b9d0cc..fbbc084 100644
--- a/scripts/build-site.mjs
+++ b/scripts/build-site.mjs
@@ -18,6 +18,10 @@ const SITE_NAME = "CoreVideo";
// Cloudflare hosts. The token is public by design (it ships in every page).
const ANALYTICS_BEACON =
` \n`;
+// Initialize the data layer before loading the container. The site's CSP
+// requires the initializer to be served as a separate script.
+const GOOGLE_TAG_MANAGER = `
+ `;
const OG_IMAGE = `${PRIMARY_ORIGIN}/assets/corevideo-share.jpg`;
const OG_IMAGE_ALT =
"CoreVideo - Zoom participant video and audio as native OBS Studio sources";
@@ -923,6 +927,7 @@ function layout(page, content, options = {}) {
+${GOOGLE_TAG_MANAGER}
${escapeHtml(fullTitle)}
@@ -1310,6 +1315,7 @@ const DOCS_DESCRIPTION =
const DOCS_CANONICAL = canonicalUrl("documentation/index.html");
const docsHtml = fs.readFileSync(path.join(docsDir, "index.html"), "utf8")
+ .replace(/()/i, `$1\n${GOOGLE_TAG_MANAGER}`)
.replaceAll("iamfatness.github.io/CoreVideo", publicDocumentationUrl
? new URL("/documentation", publicDocumentationUrl).host + "/documentation"
: "CoreVideo documentation")
@@ -1355,6 +1361,11 @@ writeText(
fs.readFileSync(path.join(siteAssetsDir, "docs-init.js"), "utf8"),
);
+writeText(
+ "assets/google-tag-manager.js",
+ fs.readFileSync(path.join(siteAssetsDir, "google-tag-manager.js"), "utf8"),
+);
+
// Bundled application fonts (Space Grotesk + IBM Plex Mono), self-hosted so
// the CSP font-src 'self' policy allows them.
const fontsSource = path.join(siteAssetsDir, "fonts");
diff --git a/site-assets/google-tag-manager.js b/site-assets/google-tag-manager.js
new file mode 100644
index 0000000..cb30f8c
--- /dev/null
+++ b/site-assets/google-tag-manager.js
@@ -0,0 +1,5 @@
+window.dataLayer = window.dataLayer || [];
+function gtag() {
+ window.dataLayer.push(arguments);
+}
+gtag('js', new Date());
diff --git a/site-worker.js b/site-worker.js
index 9d662a2..3916e17 100644
--- a/site-worker.js
+++ b/site-worker.js
@@ -2,7 +2,10 @@ import { handleReviewFeedback } from './review-feedback.js';
const SECURITY_HEADERS = {
"content-security-policy":
- "default-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; script-src 'self' https://cdn.jsdelivr.net https://static.cloudflareinsights.com; connect-src 'self' https://cloudflareinsights.com; font-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'self'",
+ "default-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; " +
+ "script-src 'self' https://cdn.jsdelivr.net https://static.cloudflareinsights.com https://www.googletagmanager.com; " +
+ "connect-src 'self' https://cloudflareinsights.com https://www.googletagmanager.com https://*.google-analytics.com https://*.google.com; " +
+ "font-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'self'",
"referrer-policy": "strict-origin-when-cross-origin",
"x-content-type-options": "nosniff",
};
diff --git a/wiki/Privacy-Policy.md b/wiki/Privacy-Policy.md
index 5da5f92..ea2f0d3 100644
--- a/wiki/Privacy-Policy.md
+++ b/wiki/Privacy-Policy.md
@@ -1,6 +1,6 @@
# Privacy Policy
-_Last updated: September 2026_
+_Last updated: November 2026_
## Overview
@@ -109,6 +109,14 @@ The documentation site at `corevideo.io` is served through Cloudflare and source
- [Cloudflare Privacy Policy](https://www.cloudflare.com/privacypolicy/)
- [GitHub Privacy Statement](https://docs.github.com/en/site-policy/privacy-policies/github-general-privacy-statement)
+### Google Analytics (Website)
+
+The public pages on `corevideo.io` use Google Analytics through Google Tag
+Manager to measure site usage, including page views. Google Analytics may use
+cookies to distinguish visits. See [how Google uses information from sites that
+use its services](https://policies.google.com/technologies/partner-sites) and the
+[Google Analytics opt-out browser add-on](https://tools.google.com/dlpage/gaoptout).
+
---
## Data Retention