analytics: consolidate everything onto one shared Plausible pa- script
All pcbjam properties now load the same new-format Plausible script (https://plausible.io/js/pa-KjNS9YmidydULZTstsjRg.js — one dashboard, segment by hostname) instead of per-domain data-domain sites: - site: replace Vercel Analytics with the pa- snippet in BaseLayout (is:inline, prod-only, crossorigin="anonymous" for the COEP require-corp routes in vercel.json); drop @vercel/analytics dep. Legal pages (cookies/privacy) rewritten Vercel -> Plausible. - standalone: initAnalytics() rewritten for the new script format (queue shim + plausible.init(), async, no data-domain). The gate is now VITE_PLAUSIBLE_SRC (script URL, deploy-time opt-in) — unset means no tracking, so dev checkouts and third-party GPL builds stay clean. VITE_PLAUSIBLE_DOMAIN is gone. - deploy: build-demo/build-editor --plausible now takes the script URL; release.yml/deploy-demo.yml hardcode the (public) pa- URL, so the repo Actions variable PLAUSIBLE_DOMAIN is no longer used. Verified: standalone tsc + vite build (pa- URL present with env, absent without, crossOrigin kept); astro build has the snippet on all layout pages and no vercel/insights references; astro dev stays untracked. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
ac64799187
commit
40b53ad530
14 changed files with 116 additions and 95 deletions
6
.github/workflows/deploy-demo.yml
vendored
6
.github/workflows/deploy-demo.yml
vendored
|
|
@ -93,13 +93,13 @@ jobs:
|
|||
# full KiCad library set is served read-only from libs/kicad/$LIB_TAG
|
||||
# (published once by publish-libs.yml — run it first for a new lib tag).
|
||||
- name: Build demo
|
||||
# Plausible: set the repo Actions VARIABLE `PLAUSIBLE_DOMAIN` (e.g.
|
||||
# demo.pcbjam.com) to enable analytics; unset ⇒ "" ⇒ build-demo leaves it off.
|
||||
# Plausible: the shared pa- script covering all pcbjam properties
|
||||
# (one dashboard, segment by hostname).
|
||||
run: >
|
||||
node scripts/deploy/build-demo.mjs --tag "${{ steps.tag.outputs.tag }}"
|
||||
--cdn "$CDN" --lib-tag "$LIB_TAG"
|
||||
${MODELS_TAG:+--models-tag "$MODELS_TAG"}
|
||||
--plausible "${{ vars.PLAUSIBLE_DOMAIN }}"
|
||||
--plausible "https://plausible.io/js/pa-KjNS9YmidydULZTstsjRg.js"
|
||||
|
||||
# 4) Ensure the Pages project exists (first deploy creates it; no-op after).
|
||||
# Its production branch must equal PAGES_PROD_BRANCH or deploys land as
|
||||
|
|
|
|||
12
.github/workflows/release.yml
vendored
12
.github/workflows/release.yml
vendored
|
|
@ -124,13 +124,13 @@ jobs:
|
|||
# Standalone pinned to the CDN + this tag's manifest-<tag>.json (written by
|
||||
# publish-wasm above) + the full library set at libs/kicad/$LIB_TAG.
|
||||
- name: Build demo
|
||||
# Plausible: set the repo Actions VARIABLE `PLAUSIBLE_DOMAIN` (e.g.
|
||||
# demo.pcbjam.com) to enable analytics; unset ⇒ "" ⇒ build-demo leaves it off.
|
||||
# Plausible: the shared pa- script covering all pcbjam properties
|
||||
# (one dashboard, segment by hostname).
|
||||
run: >
|
||||
node scripts/deploy/build-demo.mjs --tag "${{ needs.meta.outputs.tag }}"
|
||||
--cdn "$CDN" --lib-tag "$LIB_TAG"
|
||||
${MODELS_TAG:+--models-tag "$MODELS_TAG"}
|
||||
--plausible "${{ vars.PLAUSIBLE_DOMAIN }}"
|
||||
--plausible "https://plausible.io/js/pa-KjNS9YmidydULZTstsjRg.js"
|
||||
|
||||
- name: Ensure Pages project exists
|
||||
run: >
|
||||
|
|
@ -171,13 +171,13 @@ jobs:
|
|||
- name: Install standalone workspace
|
||||
run: pnpm --dir web install --frozen-lockfile
|
||||
|
||||
# --plausible: the editor tracks as its OWN Plausible site (separate
|
||||
# dashboard from the demo's vars.PLAUSIBLE_DOMAIN and the mgmt app's).
|
||||
# --plausible: the shared pa- script covering all pcbjam properties
|
||||
# (one dashboard, segment by hostname).
|
||||
- name: Build editor (remote mode)
|
||||
run: >
|
||||
node scripts/deploy/build-editor.mjs --tag "$RELEASE_TAG"
|
||||
--cdn "$CDN" --api-base "$EDITOR_API_BASE"
|
||||
--plausible "editor.pcbjam.com"
|
||||
--plausible "https://plausible.io/js/pa-KjNS9YmidydULZTstsjRg.js"
|
||||
${MODELS_TAG:+--models-tag "$MODELS_TAG"}
|
||||
|
||||
- name: Ensure Pages project exists
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ function parseArgs(argv) {
|
|||
// cross-posts to <landing>/api/waitlist unless --waitlist overrides it.
|
||||
landing: "https://pcbjam.com",
|
||||
waitlist: null,
|
||||
// Plausible analytics data-domain. Off unless given (or VITE_PLAUSIBLE_DOMAIN
|
||||
// Plausible pa-*.js script URL. Off unless given (or VITE_PLAUSIBLE_SRC
|
||||
// is already in the environment, which passes straight through).
|
||||
plausible: null,
|
||||
};
|
||||
|
|
@ -126,7 +126,7 @@ function main() {
|
|||
VITE_LANDING_URL: a.landing,
|
||||
VITE_WAITLIST_URL: a.waitlist,
|
||||
// Plausible analytics: explicit --plausible wins, else any env-provided value.
|
||||
...(a.plausible ? { VITE_PLAUSIBLE_DOMAIN: a.plausible } : {}),
|
||||
...(a.plausible ? { VITE_PLAUSIBLE_SRC: a.plausible } : {}),
|
||||
};
|
||||
|
||||
console.log(`build-demo: tag=${a.tag} cdn=${a.cdn}`);
|
||||
|
|
@ -137,7 +137,7 @@ function main() {
|
|||
console.log(` VITE_LIBS_SOURCE=${env.VITE_LIBS_SOURCE}${env.VITE_LIBS_MANIFEST_URL ? ` (${env.VITE_LIBS_MANIFEST_URL})` : ""}`);
|
||||
console.log(` VITE_MODELS_MANIFEST_URL=${env.VITE_MODELS_MANIFEST_URL ?? "(unset — 3D models off)"}`);
|
||||
console.log(` VITE_LANDING_URL=${env.VITE_LANDING_URL} VITE_WAITLIST_URL=${env.VITE_WAITLIST_URL}`);
|
||||
console.log(` VITE_PLAUSIBLE_DOMAIN=${env.VITE_PLAUSIBLE_DOMAIN || "(off)"}`);
|
||||
console.log(` VITE_PLAUSIBLE_SRC=${env.VITE_PLAUSIBLE_SRC || "(off)"}`);
|
||||
|
||||
// Keep the dev-only WASM symlink out of the bundle (it'd copy 100s of MB into
|
||||
// dist/; the CDN serves it). In CI it isn't present, so this is a no-op there.
|
||||
|
|
|
|||
|
|
@ -103,7 +103,7 @@ function main() {
|
|||
VITE_APP_TAG: a.tag,
|
||||
VITE_GIT_SHA: gitSha(repoRoot),
|
||||
VITE_REPO_URL: a.repo,
|
||||
...(a.plausible ? { VITE_PLAUSIBLE_DOMAIN: a.plausible } : {}),
|
||||
...(a.plausible ? { VITE_PLAUSIBLE_SRC: a.plausible } : {}),
|
||||
};
|
||||
|
||||
console.log(`build-editor: tag=${a.tag} api=${a.apiBase} cdn=${a.cdn}`);
|
||||
|
|
@ -114,7 +114,7 @@ function main() {
|
|||
console.log(` VITE_LIBS_SOURCE=${env.VITE_LIBS_SOURCE}`);
|
||||
console.log(` VITE_MODELS_MANIFEST_URL=${env.VITE_MODELS_MANIFEST_URL ?? "(unset — 3D models off)"}`);
|
||||
console.log(` VITE_APP_TAG=${env.VITE_APP_TAG} VITE_GIT_SHA=${env.VITE_GIT_SHA || "(none)"}`);
|
||||
console.log(` VITE_PLAUSIBLE_DOMAIN=${env.VITE_PLAUSIBLE_DOMAIN || "(off)"}`);
|
||||
console.log(` VITE_PLAUSIBLE_SRC=${env.VITE_PLAUSIBLE_SRC || "(off)"}`);
|
||||
|
||||
// Keep the dev-only WASM symlink out of the bundle (CDN serves it).
|
||||
const hadWasm = existsSync(publicWasm) || isSymlink(publicWasm);
|
||||
|
|
|
|||
54
site/package-lock.json
generated
54
site/package-lock.json
generated
|
|
@ -11,7 +11,6 @@
|
|||
"@astrojs/mdx": "^6.0.3",
|
||||
"@astrojs/vercel": "^10.0.8",
|
||||
"@fontsource/oswald": "^5.2.8",
|
||||
"@vercel/analytics": "^2.0.1",
|
||||
"astro": "^6.4.4",
|
||||
"resend": "^6.12.4"
|
||||
},
|
||||
|
|
@ -2304,48 +2303,6 @@
|
|||
"integrity": "sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/@vercel/analytics": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@vercel/analytics/-/analytics-2.0.1.tgz",
|
||||
"integrity": "sha512-MTQG6V9qQrt1tsDeF+2Uoo5aPjqbVPys1xvnIftXSJYG2SrwXRHnqEvVoYID7BTruDz4lCd2Z7rM1BdkUehk2g==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@remix-run/react": "^2",
|
||||
"@sveltejs/kit": "^1 || ^2",
|
||||
"next": ">= 13",
|
||||
"nuxt": ">= 3",
|
||||
"react": "^18 || ^19 || ^19.0.0-rc",
|
||||
"svelte": ">= 4",
|
||||
"vue": "^3",
|
||||
"vue-router": "^4"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@remix-run/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@sveltejs/kit": {
|
||||
"optional": true
|
||||
},
|
||||
"next": {
|
||||
"optional": true
|
||||
},
|
||||
"nuxt": {
|
||||
"optional": true
|
||||
},
|
||||
"react": {
|
||||
"optional": true
|
||||
},
|
||||
"svelte": {
|
||||
"optional": true
|
||||
},
|
||||
"vue": {
|
||||
"optional": true
|
||||
},
|
||||
"vue-router": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@vercel/cli-config": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@vercel/cli-config/-/cli-config-0.2.0.tgz",
|
||||
|
|
@ -4327,6 +4284,7 @@
|
|||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -4347,6 +4305,7 @@
|
|||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -4367,6 +4326,7 @@
|
|||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -4387,6 +4347,7 @@
|
|||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -4407,6 +4368,7 @@
|
|||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -4427,6 +4389,7 @@
|
|||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -4447,6 +4410,7 @@
|
|||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -4467,6 +4431,7 @@
|
|||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -4487,6 +4452,7 @@
|
|||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -4507,6 +4473,7 @@
|
|||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -4527,6 +4494,7 @@
|
|||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@
|
|||
"@astrojs/mdx": "^6.0.3",
|
||||
"@astrojs/vercel": "^10.0.8",
|
||||
"@fontsource/oswald": "^5.2.8",
|
||||
"@vercel/analytics": "^2.0.1",
|
||||
"astro": "^6.4.4",
|
||||
"resend": "^6.12.4"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
title: Cookie Policy
|
||||
description: How PCBJam uses cookies and similar technologies, and the choices you have.
|
||||
updated: 2026-06-10
|
||||
updated: 2026-07-15
|
||||
---
|
||||
|
||||
This Cookie Policy explains how **PCBJam** ("PCBJam", "we", "us", "our") uses cookies and similar technologies — such as browser local storage, IndexedDB and cache storage — when you visit **pcbjam.com** or use the PCBJam application (the "Service"), and the choices you have.
|
||||
|
|
@ -84,11 +84,11 @@ If you buy a paid plan, checkout is handled by **Paddle** as our **Merchant of R
|
|||
|
||||
## 5. Analytics: how we measure usage without cookies
|
||||
|
||||
We want to understand how PCBJam is used so we can improve it, **without tracking you.** We use **Vercel Web Analytics**. According to Vercel's documentation (as revised 18 March 2026), and confirmed by our own inspection of the analytics script as currently shipped on this site, it:
|
||||
We want to understand how PCBJam is used so we can improve it, **without tracking you.** We use **Plausible Analytics**, an EU-based, open-source, privacy-first analytics service operated by Plausible Insights OÜ. According to Plausible's [data policy](https://plausible.io/data-policy), and confirmed by our own inspection of the analytics script as currently shipped on this site, it:
|
||||
|
||||
- sets **no cookie** and stores **no identifier** on your device — visitors are counted using a **hash created from the incoming request**, and the script uses no cookies, local storage, or other device storage;
|
||||
- does **not** keep that visitor hash — it is **automatically discarded after 24 hours**;
|
||||
- records **anonymous, aggregated** data points (such as page URL, referrer, approximate location, browser and device type) that are **not** used to build a profile of you or to follow you across other sites.
|
||||
- does **not** keep a persistent visitor identifier — the hash is generated with a salt that is **rotated and deleted every 24 hours**, so you cannot be recognised from one day to the next, and your raw IP address is not stored;
|
||||
- records **anonymous, aggregated** data points (such as page URL, referrer, approximate location, browser and device type) that are **not** used to build a profile of you or to follow you across other sites, and the data is **stored on servers in the EU**.
|
||||
|
||||
Because it stores nothing on your device and the data it records cannot identify you, **we consider that this measurement does not require your prior consent**, and it is not part of any consent banner.
|
||||
|
||||
|
|
@ -99,7 +99,8 @@ We do **not** use Google Analytics, advertising pixels, or any cross-site tracki
|
|||
| Third party | Role | What they may set | Their notice |
|
||||
|---|---|---|---|
|
||||
| **Paddle** (Paddle.com Market Limited and affiliates) | Merchant of Record / payments | Checkout, security and fraud-prevention cookies; their own non-essential cookies (with consent where required, via Paddle's own controls). Independent controller. | [Paddle Privacy Policy](https://www.paddle.com/legal/privacy); Paddle's cookie controls appear within its checkout. |
|
||||
| **Vercel** (Vercel Inc.) | Hosting and cookieless analytics | No analytics cookies (cookieless, see Section 5); at most strictly-necessary hosting cookies. | [Vercel Analytics privacy](https://vercel.com/docs/analytics/privacy-policy) · [Vercel Privacy](https://vercel.com/legal/privacy-policy) |
|
||||
| **Plausible** (Plausible Insights OÜ) | Cookieless analytics | Nothing — no cookies and no device storage (see Section 5). | [Plausible Data Policy](https://plausible.io/data-policy) · [Plausible Privacy](https://plausible.io/privacy) |
|
||||
| **Vercel** (Vercel Inc.) | Hosting | At most strictly-necessary hosting cookies. | [Vercel Privacy](https://vercel.com/legal/privacy-policy) |
|
||||
|
||||
We are not responsible for the privacy practices of these third parties; please review their notices. We update this list as our integrations change.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
title: Privacy Policy
|
||||
description: How PCBJam handles your personal data.
|
||||
updated: 2026-06-10
|
||||
updated: 2026-07-15
|
||||
---
|
||||
|
||||
## 1. A quick summary
|
||||
|
|
@ -11,7 +11,7 @@ This summary is here to help you understand our practices at a glance. It is **n
|
|||
- **Who we are.** PCBJam is a browser-based PCB-design application (built on the open-source KiCad EDA toolchain) operated by Emergence-Engineering Kft. ("PCBJam", "we", "us"). We are the **data controller** for the personal data described here.
|
||||
- **What we collect.** The email address you give us when you join the waitlist, account details, the projects and design files you create or upload, limited technical and usage data, your communications with us, and — if you buy a paid plan — limited transaction data from our payment provider.
|
||||
- **Your design files.** Your projects are **private by default.** We process them on our servers only to run the service you ask for (storage, rendering, design-rule checks, compiling, exports, and the collaboration/sharing you choose to enable). We do not sell them and we do not use their contents to advertise to you.
|
||||
- **Analytics.** We use **Vercel Web Analytics, which is cookieless and does not track you across other websites.** It does not build an advertising profile of you.
|
||||
- **Analytics.** We use **Plausible Analytics, which is cookieless and does not track you across other websites.** It does not build an advertising profile of you.
|
||||
- **Payments** are handled by **Paddle**, which acts as the "Merchant of Record" and an **independent data controller** for your payment information (see §8).
|
||||
- **Your rights.** You can access, correct, export, or delete your data, object to certain processing, and withdraw consent at any time. EU/UK users can complain to a data-protection regulator; California users have specific rights under the CCPA (see §13).
|
||||
- **Contact.** contact@emergence-engineering.com (or hello@pcbjam.com).
|
||||
|
|
@ -102,13 +102,13 @@ Where we rely on **legitimate interests**, we have weighed our interests against
|
|||
|
||||
## 6. Analytics — and why we don't track you
|
||||
|
||||
We use **Vercel Web Analytics** to understand how PCBJam is used so we can improve it. According to Vercel, this product is **cookieless** and privacy-focused:
|
||||
We use **Plausible Analytics** (Plausible Insights OÜ, an EU company) to understand how PCBJam is used so we can improve it. According to Plausible, this product is **cookieless** and privacy-focused:
|
||||
|
||||
- it does **not** use third-party cookies; instead, visitors are counted using a **hash generated from the incoming request**, which is **automatically discarded after 24 hours**;
|
||||
- it does **not** collect identifiers that **track or cross-check you across different applications or websites**, and it is **not tied to your IP address**;
|
||||
- the data is **anonymous and aggregated** — page views, approximate location, device/OS/browser type, and referrer.
|
||||
- it does **not** use cookies or any other device storage; instead, visitors are counted using a **hash generated from the incoming request**, with a salt that is **rotated and deleted every 24 hours**, so no persistent identifier exists;
|
||||
- it does **not** collect identifiers that **track or cross-check you across different applications or websites**, and your **raw IP address is not stored**;
|
||||
- the data is **anonymous and aggregated** — page views, approximate location, device/OS/browser type, and referrer — and is **stored on servers in the EU**.
|
||||
|
||||
Because this analytics does **not** involve cross-context behavioural advertising or cross-site tracking, **we do not consider it a "sale" or "sharing" of personal information** under California law (see §13). Vercel acts as our **processor** for this analytics. See Vercel's [analytics privacy documentation](https://vercel.com/docs/analytics/privacy-policy) and [Data Processing Addendum](https://vercel.com/legal/dpa).
|
||||
Because this analytics does **not** involve cross-context behavioural advertising or cross-site tracking, **we do not consider it a "sale" or "sharing" of personal information** under California law (see §13). Plausible acts as our **processor** for this analytics. See Plausible's [data policy](https://plausible.io/data-policy) and [Data Processing Agreement](https://plausible.io/dpa).
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -142,7 +142,8 @@ We do **not** sell your personal data. We share it only with the following categ
|
|||
|
||||
| Recipient | What they do | Their role | More info |
|
||||
|---|---|---|---|
|
||||
| **Vercel** | Hosting/infrastructure and cookieless web analytics | **Processor** | [Vercel DPA](https://vercel.com/legal/dpa) · [Privacy](https://vercel.com/legal/privacy-policy) |
|
||||
| **Vercel** | Hosting/infrastructure | **Processor** | [Vercel DPA](https://vercel.com/legal/dpa) · [Privacy](https://vercel.com/legal/privacy-policy) |
|
||||
| **Plausible** | Cookieless web analytics | **Processor** | [Plausible DPA](https://plausible.io/dpa) · [Data policy](https://plausible.io/data-policy) |
|
||||
| **Paddle** | Payments / Merchant of Record | **Independent controller** (for payment data) | [Paddle Privacy](https://www.paddle.com/legal/privacy) |
|
||||
| **Resend** | Sending transactional and marketing email | **Processor** | [Resend DPA](https://resend.com/legal/dpa) · [Subprocessors](https://resend.com/legal/subprocessors) |
|
||||
| **Cloudflare** | Storing your project files (R2 object storage), hosting application/account data, and network delivery & security (CDN, DNS, WAF) | **Processor** | [Cloudflare DPA](https://www.cloudflare.com/cloudflare-customer-dpa/) · [GDPR hub](https://www.cloudflare.com/trust-hub/gdpr/) |
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
import { ClientRouter } from 'astro:transitions';
|
||||
import Header from '../components/Header.astro';
|
||||
import Footer from '../components/Footer.astro';
|
||||
import Analytics from '@vercel/analytics/astro';
|
||||
import '../styles/global.css';
|
||||
|
||||
interface Props {
|
||||
|
|
@ -63,6 +62,38 @@ const ogImageAbs = new URL(ogImage, Astro.site ?? Astro.url).href;
|
|||
|
||||
{/* SPA-style navigation: swaps the page without a full reload. */}
|
||||
<ClientRouter />
|
||||
|
||||
{/* Privacy-friendly analytics by Plausible (cookieless, no consent banner
|
||||
needed). The pa- script id keys the shared dashboard covering all
|
||||
pcbjam properties. Prod builds only — `astro dev` stays untracked, and
|
||||
Plausible ignores localhost regardless. Auto-tracks pageviews across
|
||||
ClientRouter navigations (History API). crossorigin="anonymous" so the
|
||||
script still loads on the COEP require-corp routes (see vercel.json):
|
||||
cross-origin subresources there must be fetched with CORS. */}
|
||||
{
|
||||
import.meta.env.PROD && (
|
||||
<>
|
||||
<script
|
||||
is:inline
|
||||
async
|
||||
crossorigin="anonymous"
|
||||
src="https://plausible.io/js/pa-KjNS9YmidydULZTstsjRg.js"></script>
|
||||
<script is:inline>
|
||||
window.plausible =
|
||||
window.plausible ||
|
||||
function () {
|
||||
(plausible.q = plausible.q || []).push(arguments);
|
||||
};
|
||||
plausible.init =
|
||||
plausible.init ||
|
||||
function (i) {
|
||||
plausible.o = i || {};
|
||||
};
|
||||
plausible.init();
|
||||
</script>
|
||||
</>
|
||||
)
|
||||
}
|
||||
</head>
|
||||
<body>
|
||||
<Header />
|
||||
|
|
@ -80,6 +111,5 @@ const ogImageAbs = new URL(ogImage, Astro.site ?? Astro.url).href;
|
|||
)
|
||||
}
|
||||
<Footer />
|
||||
<Analytics />
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -42,11 +42,11 @@ VITE_WASM_ROOT=/wasm
|
|||
# can't follow redirects.
|
||||
# VITE_WAITLIST_URL=https://www.pcbjam.com/api/waitlist
|
||||
|
||||
# Plausible analytics. Unset ⇒ no tracking (dev/checkout default). Set the
|
||||
# data-domain to enable; override the script URL only if the default plausible.io
|
||||
# script won't load under COEP require-corp (then self-host/proxy on cdn.pcbjam.com).
|
||||
# VITE_PLAUSIBLE_DOMAIN=demo.pcbjam.com
|
||||
# VITE_PLAUSIBLE_SRC=https://plausible.io/js/script.js
|
||||
# Plausible analytics. Unset ⇒ no tracking (dev/checkout default). Set the shared
|
||||
# site's pa-*.js script URL to enable (the pa- id keys the dashboard, no
|
||||
# data-domain). Point it at a self-hosted/proxied copy on cdn.pcbjam.com if the
|
||||
# plausible.io script won't load under COEP require-corp.
|
||||
# VITE_PLAUSIBLE_SRC=https://plausible.io/js/pa-KjNS9YmidydULZTstsjRg.js
|
||||
|
||||
# Where a backend project's DOCUMENT content lives ("api" default | "ydoc").
|
||||
# Same /p/<project> URLs either way. "api": file bytes come from the REST
|
||||
|
|
|
|||
|
|
@ -1,10 +1,18 @@
|
|||
import { PLAUSIBLE_DOMAIN, PLAUSIBLE_SRC } from "./config";
|
||||
import { PLAUSIBLE_SRC } from "./config";
|
||||
|
||||
type PlausibleShim = {
|
||||
(...args: unknown[]): void;
|
||||
q?: unknown[][];
|
||||
init?: (options?: object) => void;
|
||||
o?: object;
|
||||
};
|
||||
|
||||
/**
|
||||
* Inject the Plausible analytics script when a domain is configured
|
||||
* (VITE_PLAUSIBLE_DOMAIN). No-op otherwise, so a plain dev checkout — and any
|
||||
* deploy that doesn't set the var — stays untracked. Cookieless + privacy-
|
||||
* friendly, so no consent banner is required.
|
||||
* Inject the Plausible analytics script when a script URL is configured
|
||||
* (VITE_PLAUSIBLE_SRC, the site's pa-*.js URL — the pa- id keys the shared
|
||||
* dashboard, so no data-domain). No-op otherwise, so a plain dev checkout —
|
||||
* and any deploy that doesn't set the var — stays untracked. Cookieless +
|
||||
* privacy-friendly, so no consent banner is required.
|
||||
*
|
||||
* `crossorigin="anonymous"` is required because the demo is served cross-origin
|
||||
* isolated (COEP `require-corp`): a third-party script must be fetched with CORS.
|
||||
|
|
@ -12,11 +20,24 @@ import { PLAUSIBLE_DOMAIN, PLAUSIBLE_SRC } from "./config";
|
|||
* at a self-hosted/proxied copy on cdn.pcbjam.com (which sets CORP).
|
||||
*/
|
||||
export function initAnalytics(): void {
|
||||
if (!PLAUSIBLE_DOMAIN || typeof document === "undefined") return;
|
||||
if (!PLAUSIBLE_SRC || typeof document === "undefined") return;
|
||||
if (document.querySelector("script[data-pcbjam-analytics]")) return;
|
||||
// Official queue shim: buffers plausible() calls until the script loads.
|
||||
const w = window as Window & { plausible?: PlausibleShim };
|
||||
const p: PlausibleShim =
|
||||
w.plausible ||
|
||||
((...args: unknown[]) => {
|
||||
(p.q = p.q || []).push(args);
|
||||
});
|
||||
p.init =
|
||||
p.init ||
|
||||
((options?: object) => {
|
||||
p.o = options || {};
|
||||
});
|
||||
w.plausible = p;
|
||||
p.init();
|
||||
const s = document.createElement("script");
|
||||
s.defer = true;
|
||||
s.setAttribute("data-domain", PLAUSIBLE_DOMAIN);
|
||||
s.async = true;
|
||||
s.setAttribute("data-pcbjam-analytics", "");
|
||||
s.crossOrigin = "anonymous";
|
||||
s.src = PLAUSIBLE_SRC;
|
||||
|
|
|
|||
|
|
@ -61,15 +61,14 @@ export const WAITLIST_URL =
|
|||
import.meta.env.VITE_WAITLIST_URL || "https://www.pcbjam.com/api/waitlist";
|
||||
|
||||
/**
|
||||
* Plausible analytics (privacy-friendly, cookieless). Off unless a domain is set
|
||||
* (plain dev/checkout stays untracked). `PLAUSIBLE_DOMAIN` is the `data-domain`
|
||||
* the dashboard is keyed by; `PLAUSIBLE_SRC` is the script URL (override to a
|
||||
* self-hosted/proxied script — e.g. on cdn.pcbjam.com — if plausible.io won't
|
||||
* load under the demo's COEP `require-corp`). See main.tsx for the injection.
|
||||
* Plausible analytics (privacy-friendly, cookieless). Off unless a script URL is
|
||||
* set (plain dev/checkout stays untracked). `PLAUSIBLE_SRC` is the shared site's
|
||||
* pa-*.js script URL — the pa- id keys the dashboard, no data-domain needed.
|
||||
* Point it at a self-hosted/proxied copy — e.g. on cdn.pcbjam.com — if
|
||||
* plausible.io won't load under the demo's COEP `require-corp`. See main.tsx
|
||||
* for the injection.
|
||||
*/
|
||||
export const PLAUSIBLE_DOMAIN = import.meta.env.VITE_PLAUSIBLE_DOMAIN || null;
|
||||
export const PLAUSIBLE_SRC =
|
||||
import.meta.env.VITE_PLAUSIBLE_SRC || "https://plausible.io/js/script.js";
|
||||
export const PLAUSIBLE_SRC = import.meta.env.VITE_PLAUSIBLE_SRC || null;
|
||||
|
||||
/**
|
||||
* Where the standalone reads PROJECTS from (env VITE_PROJECT_SOURCE):
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import App from "./App";
|
|||
import { initAnalytics } from "./lib/analytics";
|
||||
import "./index.css";
|
||||
|
||||
// Privacy-friendly analytics (Plausible), only when VITE_PLAUSIBLE_DOMAIN is set.
|
||||
// Privacy-friendly analytics (Plausible), only when VITE_PLAUSIBLE_SRC is set.
|
||||
initAnalytics();
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
|
|
|
|||
2
web/standalone/src/vite-env.d.ts
vendored
2
web/standalone/src/vite-env.d.ts
vendored
|
|
@ -30,6 +30,8 @@ interface ImportMetaEnv {
|
|||
readonly VITE_YJS_ENDPOINT?: string;
|
||||
/** Optional connection token for the collab provider. */
|
||||
readonly VITE_YJS_TOKEN?: string;
|
||||
/** Plausible pa-*.js script URL; unset ⇒ no tracking (dev default). */
|
||||
readonly VITE_PLAUSIBLE_SRC?: string;
|
||||
}
|
||||
|
||||
interface ImportMeta {
|
||||
|
|
|
|||
Loading…
Reference in a new issue