diff --git a/.gitignore b/.gitignore
index e9667de..926fca6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,4 +3,11 @@ dist/
.env
reference/
*.local
+
+# TypeScript build cache — regenerated on every build, not source
tsconfig.tsbuildinfo
+*.tsbuildinfo
+
+# IDE / agent local config
+.vscode/
+.kiro/
diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md
new file mode 100644
index 0000000..bdf2ab5
--- /dev/null
+++ b/docs/CONTRIBUTING.md
@@ -0,0 +1,115 @@
+# Contributing to usewraith.xyz
+
+## Analytics
+
+### Provider: Plausible Analytics
+
+We use [Plausible Analytics](https://plausible.io) — **not** Google Analytics or any other
+cookie-based tracker.
+
+**Why Plausible?**
+
+| Requirement | Plausible |
+|---|---|
+| No cookies | ✅ Daily-rotating hash, never persisted |
+| No personal data | ✅ IP never stored; no fingerprinting |
+| GDPR / ePrivacy compliant without a consent banner | ✅ [Confirmed by Plausible](https://plausible.io/privacy-focused-web-analytics) |
+| EU-hosted | ✅ Hetzner Germany/Finland |
+| Open source | ✅ [AGPL-3.0](https://github.com/plausible/analytics) |
+| Script bundle ≤ 2 KB gzipped | ✅ ~1 KB (verified via Network tab) |
+
+### How the script is loaded
+
+`index.html` loads the combined Plausible extension script:
+
+```html
+
+
+```
+
+- `script.scroll` — automatically tracks scroll depth at every percentage.
+ No setup needed; Plausible shows scroll depth in the dashboard.
+- `script.tagged-events` — enables the `window.plausible()` JS function for
+ custom goal events (see below).
+- The queue shim lets goal events fire before the script fully loads.
+
+**Note on `integrity` attribute:** Plausible does not currently publish SRI
+hashes for their CDN script because they ship frequent minor updates. If your
+CSP requires SRI, proxy the script through your own infrastructure (see
+[Plausible proxy docs](https://plausible.io/docs/proxy/introduction)).
+
+### Custom goals
+
+All goal tracking goes through the typed helper in `src/analytics.ts`:
+
+```ts
+import { trackEvent } from '../analytics';
+
+trackEvent('Read the Docs');
+trackEvent('Code Tab Change', { props: { tab: 'scan.ts' } });
+```
+
+#### Goals currently configured
+
+| Goal name | Where it fires | Notes |
+|---|---|---|
+| `Read the Docs` | Hero CTA, CtaStrip secondary button | Fires on click |
+| `Try the Demo` | Hero secondary CTA | Fires on click |
+| `Get API Key` | CtaStrip primary button | Fires on click |
+| `Code Tab Change` | Hero code snippet tabs | Includes `tab` prop (`send.ts` / `scan.ts` / `withdraw.ts`) |
+| Scroll depth | Automatic — all pages | Provided by `script.scroll` extension, no code needed |
+
+To add a new goal:
+1. Call `trackEvent('Your Goal Name')` where appropriate.
+2. Go to **usewraith.xyz → Plausible dashboard → Goals → Add Goal** and add
+ a matching Custom Event entry.
+
+### Privacy page
+
+`src/pages/Privacy.tsx` is the canonical "What we collect" page linked from
+the footer. It documents Plausible's data practices in plain language and
+explains the no-cookie guarantee. Keep it up to date whenever the analytics
+setup changes.
+
+Route: `/privacy` (served by React Router, no server-side config needed for
+Vite SPA — just ensure your hosting platform redirects all paths to
+`index.html`).
+
+### No consent banner
+
+Because Plausible sets no cookies and stores no personal data, **no cookie
+consent banner is required** under GDPR, PECR, or the ePrivacy Directive. Do
+not add one. See [Plausible's data policy](https://plausible.io/data-policy)
+for the legal basis.
+
+---
+
+## Development setup
+
+```bash
+pnpm install
+pnpm dev # starts Vite dev server
+pnpm build # TypeScript check + Vite production build
+pnpm format # Prettier
+```
+
+## Commit conventions
+
+Commits follow [Conventional Commits](https://www.conventionalcommits.org/)
+enforced via `commitlint` + `husky`. Examples:
+
+```
+feat: add privacy page
+fix: correct plausible script extension url
+docs: update contributing analytics section
+```
diff --git a/index.html b/index.html
index f66e2ee..ef4c7f3 100644
--- a/index.html
+++ b/index.html
@@ -29,6 +29,28 @@
+
+
+
+
+
=18'}
hasBin: true
- convert-source-map@2.0.0:
- resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
+ cookie@1.1.1:
+ resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==}
+ engines: {node: '>=18'}
cosmiconfig-typescript-loader@6.3.0:
resolution: {integrity: sha512-Akr82WH1Wfqatyiqpj8HDkO2o2KmJRu1FhKfSNJP3K4IdXwHfEyL7MOb62i1AGQVLtIQM+iCE9CGOtrfhR+mmA==}
@@ -1011,8 +1015,22 @@ packages:
peerDependencies:
react: ^19.2.5
- react-is@17.0.2:
- resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==}
+ react-router-dom@7.18.0:
+ resolution: {integrity: sha512-Fi0yY6kgtKae/Th2xibdWK0KSdYZ4B53Gyf6wRtomOKWgpNm7H7+DyfDhncdz9FKbpS+1jmDhg3F4WoGJ+yFOA==}
+ engines: {node: '>=20.0.0'}
+ peerDependencies:
+ react: '>=18'
+ react-dom: '>=18'
+
+ react-router@7.18.0:
+ resolution: {integrity: sha512-pTTGt8J+ji1NOmYnjzT+bAJy/1zD+Jp4ziO6cL7T3ZLvXKtusO7BpFqlRXitqpcPVqllsIXFHRMt+2/k3Xn6HQ==}
+ engines: {node: '>=20.0.0'}
+ peerDependencies:
+ react: '>=18'
+ react-dom: '>=18'
+ peerDependenciesMeta:
+ react-dom:
+ optional: true
react@19.2.5:
resolution: {integrity: sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==}
@@ -1055,8 +1073,8 @@ packages:
engines: {node: '>=10'}
hasBin: true
- siginfo@2.0.0:
- resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
+ set-cookie-parser@2.7.2:
+ resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==}
source-map-js@1.2.1:
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
@@ -1804,7 +1822,7 @@ snapshots:
'@simple-libs/stream-utils': 1.2.0
meow: 13.2.0
- convert-source-map@2.0.0: {}
+ cookie@1.1.1: {}
cosmiconfig-typescript-loader@6.3.0(@types/node@25.6.0)(cosmiconfig@9.0.1(typescript@6.0.2))(typescript@6.0.2):
dependencies:
@@ -2098,7 +2116,19 @@ snapshots:
react: 19.2.5
scheduler: 0.27.0
- react-is@17.0.2: {}
+ react-router-dom@7.18.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5):
+ dependencies:
+ react: 19.2.5
+ react-dom: 19.2.5(react@19.2.5)
+ react-router: 7.18.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
+
+ react-router@7.18.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5):
+ dependencies:
+ cookie: 1.1.1
+ react: 19.2.5
+ set-cookie-parser: 2.7.2
+ optionalDependencies:
+ react-dom: 19.2.5(react@19.2.5)
react@19.2.5: {}
@@ -2144,7 +2174,7 @@ snapshots:
semver@7.7.4: {}
- siginfo@2.0.0: {}
+ set-cookie-parser@2.7.2: {}
source-map-js@1.2.1: {}
diff --git a/src/App.tsx b/src/App.tsx
index 5b69467..2279733 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,3 +1,4 @@
+import { BrowserRouter, Routes, Route } from 'react-router-dom';
import Header from './components/Header';
import Hero from './components/Hero';
import Features from './components/Features';
@@ -8,13 +9,9 @@ import Compare from './components/Compare';
import Showcase from './components/Showcase';
import CtaStrip from './components/CtaStrip';
import Footer from './components/Footer';
-import Press from './pages/Press';
-
-const path = window.location.pathname.replace(/\/$/, '');
-
-export default function App() {
- if (path === '/press') return