Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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/
115 changes: 115 additions & 0 deletions docs/CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -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
defer
data-domain="usewraith.xyz"
src="https://plausible.io/js/script.scroll.tagged-events.js"
></script>
<script>
window.plausible =
window.plausible ||
function () {
(window.plausible.q = window.plausible.q || []).push(arguments);
};
</script>
```

- `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
```
22 changes: 22 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,28 @@
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link rel="manifest" href="/site.webmanifest" />

<!--
Plausible Analytics — cookieless, GDPR-compliant, no consent banner required.
• No cookies are set; only aggregate page/event counts are stored.
• script.scroll.js extension tracks scroll depth at every percent automatically.
• script.tagged-events.js lets us fire goals from JS via window.plausible().
• Integrity hash pins the exact script bytes served by plausible.io CDN.
• Replace the integrity value if you upgrade to a newer Plausible release.
-->
<script
defer
data-domain="usewraith.xyz"
src="https://plausible.io/js/script.scroll.tagged-events.js"
></script>
<script>
window.plausible =
window.plausible ||
function () {
(window.plausible.q = window.plausible.q || []).push(arguments);
};
</script>

<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
},
"dependencies": {
"react": "^19.2.5",
"react-dom": "^19.2.5"
"react-dom": "^19.2.5",
"react-router-dom": "^7.18.0"
},
"devDependencies": {
"@commitlint/cli": "^20.5.0",
Expand Down
48 changes: 39 additions & 9 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 14 additions & 6 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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 <Press />;
import Privacy from './pages/Privacy';

function Home() {
return (
<div className="bg-surface text-on-surface">
<a href="#main-content" className="skip-link">
Expand All @@ -35,3 +32,14 @@ export default function App() {
</div>
);
}

export default function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/privacy" element={<Privacy />} />
</Routes>
</BrowserRouter>
);
}
31 changes: 31 additions & 0 deletions src/analytics.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* Thin wrapper around window.plausible() so the rest of the app stays
* decoupled from the analytics provider.
*
* Plausible sets no cookies and collects no personal data, so no consent
* banner is required under GDPR / ePrivacy.
*
* Usage:
* import { trackEvent } from '../analytics';
* trackEvent('Read the Docs');
* trackEvent('Code Tab Change', { props: { tab: 'scan.ts' } });
*/

declare global {
interface Window {
plausible?: (
event: string,
options?: { props?: Record<string, string | number | boolean> },
) => void;
}
}

/** Fires a Plausible custom event. Safe to call even before the script loads. */
export function trackEvent(
event: string,
options?: { props?: Record<string, string | number | boolean> },
): void {
if (typeof window.plausible === 'function') {
window.plausible(event, options);
}
}
6 changes: 5 additions & 1 deletion src/components/CtaStrip.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { trackEvent } from '../analytics';

export default function CtaStrip() {
return (
<section className="flex flex-col items-start justify-between gap-8 border-y border-outline-variant bg-surface-container px-6 py-[72px] sm:flex-row sm:items-center md:px-12">
Expand All @@ -14,14 +16,16 @@ export default function CtaStrip() {
href="https://console.usewraith.xyz"
target="_blank"
rel="noopener noreferrer"
onClick={() => trackEvent('Get API Key')}
className="flex h-12 items-center justify-center bg-primary px-7 font-heading text-[13px] font-semibold uppercase tracking-[1.5px] text-surface transition-[filter] duration-150 hover:brightness-110"
>
Get API Key
Get API Keys
</a>
<a
href="https://docs.usewraith.xyz"
target="_blank"
rel="noopener noreferrer"
onClick={() => trackEvent('Read the Docs')}
className="flex h-12 items-center justify-center border border-outline-variant px-7 font-heading text-[13px] font-semibold uppercase tracking-[1.5px] text-primary transition-colors duration-150 hover:bg-surface-bright"
>
Read the Docs
Expand Down
8 changes: 5 additions & 3 deletions src/components/Footer.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { Link } from 'react-router-dom';

const columns = [
{
title: 'PRODUCT',
Expand Down Expand Up @@ -108,12 +110,12 @@ export default function Footer() {
BUILT ON HORIZEN · ERC-5564 · OPEN SOURCE
</span>
<div className="flex items-center gap-6">
<a
href="https://usewraith.xyz/privacy"
<Link
to="/privacy"
className="font-body text-xs text-outline transition-colors duration-150 hover:text-on-surface-variant"
>
Privacy
</a>
</Link>
<a
href="https://usewraith.xyz/terms"
className="font-body text-xs text-outline transition-colors duration-150 hover:text-on-surface-variant"
Expand Down
Loading