## What
Adds GA4 to `artcraft-website-next`, covering the whole marketing site.
- `src/components/analytics.tsx` loads gtag.js (`afterInteractive`) and
installs one delegated click listener. It's mounted once in the root
layout.
- `src/lib/analytics.ts` holds the typed event catalogue,
`trackEvent()`, `trackAttrs()` (data attributes that work in server
components) and the link classifier.
- Analytics is **off unless `NEXT_PUBLIC_GA_MEASUREMENT_ID` is set**, so
local dev and deploy previews stay out of the reports.
## Why
The new site had no analytics. This gives us download, signup and
checkout funnels, plus CTA performance by page section.
## Events
GA4 Enhanced Measurement handles page views (App Router history
changes), scrolls, outbound clicks and common file downloads. On top of
that:
| Event | Trigger |
|---|---|
| `app_download` | GitHub release asset clicks (ArtCraft + Crafting
Apps): app, version, platform, file |
| `webapp_click` | Links to app.getartcraft.com: destination path |
| `cta_click` | Other `Button` links (tagged `data-cta`) |
| `app_select` | App cards and icons on `/apps` and sibling cards on app
pages: app name |
| `social_click`, `contact_click` | `SOCIAL_LINKS` hosts, mailto / copy
email |
| `begin_checkout` | Plan selection (`signup`/`subscribe`/`switch`) and
credit packs, with GA4 ecommerce `items` |
| `billing_cadence_change`, `view_credit_packs`, `manage_subscription` |
Pricing actions |
| `prompt_submit`, `video_generation` | H3 promptbox (`logged_in`;
complete/failed/timeout) |
| `sign_up`, `login` | Auth gate success |
| `generate_lead` | Beta form submission (honeypot hits excluded) |
| `video_start`, `share`, `copy_command`, `copy_prompt`,
`media_download`, `press_kit_download` | Video facades, share bars, copy
buttons, media/press downloads |
Every delegated click event also carries `link_location`: nav, footer,
modal, or the enclosing `section[id]`.
## Reviewer notes
- `Button` now renders `data-cta=""` on its anchor variants. There is no
visual change.
- `PricingTable.choosePlan` now branches on the computed `checkoutType`
rather than repeating the `user`/`hasActivePlan` conditions. The
behaviour is unchanged.
- `AuthGateModal` forms now return early on error instead of using
if/else, so the success event goes on the success path.
## Deploy
Set `NEXT_PUBLIC_GA_MEASUREMENT_ID` in Netlify, **Production context
only**, then redeploy. The ID is inlined at build time. This is
documented in `netlify.toml`.
After deploying:
- Mark key events in GA: `app_download`, `begin_checkout`, `sign_up`,
`generate_lead`, `webapp_click`.
- Register custom dimensions (e.g. `link_location`, `app_name`,
`platform`, `destination`, `checkout_type`) so the params show in
reports.
## Testing
Ran the dev server with a dummy measurement ID and checked `dataLayer`
on these pages:
- `/`: nav, footer, CTA, social, mailto and YouTube events.
- `/download`: Windows and macOS installers.
- `/apps/photocraft`: Linux downloads, share, copy command, GitHub link,
and `app_select` on the sibling cards (also checked on the `/apps` icon
row and lineup cards).
- `/pricing`: cadence toggle.
- `/minimax-h3`: logged-out `prompt_submit` and example `video_start`.
Not exercised in the browser: the beta form (submitting would post a
real Google Form entry), signup/login, checkout, the credits modal, the
media page, the press kit, email copy and manage plan.
`tsc` shows no new errors. Existing errors in `media-viewer-3d.tsx` and
the Playwright test files are unchanged. The production build was not
run.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>