Skip to content

Commit fc6381c

Browse files
authored
feat(landing): add GA4 + GTM analytics with custom event tracking (#35)
- Add GTM container (GTM-WVW97Q3V) and GA4 gtag.js (G-4HVP1PLDB3) - Create scripts/analytics.js with trackEvent helper, scroll depth tracking, and debounced playground edit tracking - Track copy commands (hero, blocks, CTA), dialog opens, drift watch/fix, playground tabs, terminal tabs, nav clicks, outbound clicks, and section views
1 parent 18e54d5 commit fc6381c

7 files changed

Lines changed: 66 additions & 7 deletions

File tree

apps/landing/index.html

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,21 @@
11
<!DOCTYPE html>
22
<html lang="en">
33
<head>
4+
<!-- Google Tag Manager -->
5+
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
6+
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
7+
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
8+
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
9+
})(window,document,'script','dataLayer','GTM-WVW97Q3V');</script>
10+
<!-- End Google Tag Manager -->
11+
<!-- Google tag (gtag.js) -->
12+
<script async src="https://www.googletagmanager.com/gtag/js?id=G-4HVP1PLDB3"></script>
13+
<script>
14+
window.dataLayer = window.dataLayer || [];
15+
function gtag(){dataLayer.push(arguments);}
16+
gtag('js', new Date());
17+
gtag('config', 'G-4HVP1PLDB3');
18+
</script>
419
<meta charset="UTF-8">
520
<meta name="viewport" content="width=device-width, initial-scale=1.0">
621
<title>dev-workflows — Write rules once, compile everywhere</title>
@@ -1382,6 +1397,10 @@
13821397
</style>
13831398
</head>
13841399
<body>
1400+
<!-- Google Tag Manager (noscript) -->
1401+
<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-WVW97Q3V"
1402+
height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
1403+
<!-- End Google Tag Manager (noscript) -->
13851404

13861405
<a href="#problem" class="skip-link">Skip to content</a>
13871406

@@ -1392,10 +1411,10 @@
13921411
<img src="dark-logo-sm.png" alt="dev-workflows" class="logo-img">
13931412
</a>
13941413
<div class="nav-links">
1395-
<a href="#problem">Problem</a>
1396-
<a href="#demo">Demo</a>
1397-
<a href="#blocks">Blocks</a>
1398-
<a href="https://docs.dev-workflows.com" target="_blank">Docs</a>
1414+
<a href="#problem" onclick="trackEvent('nav_click', { target: 'problem' })">Problem</a>
1415+
<a href="#demo" onclick="trackEvent('nav_click', { target: 'demo' })">Demo</a>
1416+
<a href="#blocks" onclick="trackEvent('nav_click', { target: 'blocks' })">Blocks</a>
1417+
<a href="https://docs.dev-workflows.com" target="_blank" onclick="trackEvent('nav_click', { target: 'docs' })">Docs</a>
13991418
</div>
14001419
</div>
14011420
<div class="nav-right">
@@ -1424,7 +1443,7 @@ <h1>
14241443
No accounts. No cloud. Your rules live with your code.
14251444
</p>
14261445
<div class="hero-actions">
1427-
<a href="#problem" class="btn btn-primary">See how it works →</a>
1446+
<a href="#problem" class="btn btn-primary" onclick="trackEvent('cta_click', { cta_location: 'hero', cta_text: 'see_how_it_works' })">See how it works →</a>
14281447
<button class="btn btn-ghost" onclick="copyInstall(this)">
14291448
<svg aria-hidden="true" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg>
14301449
npx dev-workflows init
@@ -1853,12 +1872,13 @@ <h2>Stop duplicating rules across editors.</h2>
18531872
<footer>
18541873
<div>dev-workflows — Define rules once. Compile them everywhere.</div>
18551874
<div class="footer-links">
1856-
<a href="https://github.com/gpolanco/dev-workflows" target="_blank">GitHub</a>
1857-
<a href="https://www.npmjs.com/package/dev-workflows" target="_blank">npm</a>
1875+
<a href="https://github.com/gpolanco/dev-workflows" target="_blank" onclick="trackEvent('outbound_click', { destination: 'github' })">GitHub</a>
1876+
<a href="https://www.npmjs.com/package/dev-workflows" target="_blank" onclick="trackEvent('outbound_click', { destination: 'npm' })">npm</a>
18581877
</div>
18591878
</footer>
18601879

18611880
<!-- ═══════ SCRIPTS ═══════ -->
1881+
<script src="scripts/analytics.js"></script>
18621882
<script src="scripts/copy.js"></script>
18631883
<script src="scripts/dialog.js"></script>
18641884
<script src="scripts/drift.js"></script>

apps/landing/scripts/analytics.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// ── Analytics: GA4 event helper ──
2+
function trackEvent(event, params) {
3+
if (typeof gtag === 'function') {
4+
gtag('event', event, params);
5+
}
6+
}
7+
8+
// ── Scroll depth tracking ──
9+
const scrollTracked = {};
10+
const scrollObs = new IntersectionObserver((entries) => {
11+
entries.forEach(e => {
12+
if (e.isIntersecting && !scrollTracked[e.target.id]) {
13+
scrollTracked[e.target.id] = true;
14+
trackEvent('section_view', { section: e.target.id });
15+
}
16+
});
17+
}, { threshold: 0.3 });
18+
19+
document.querySelectorAll('section[id]').forEach(s => scrollObs.observe(s));
20+
21+
// ── Playground edit tracking (debounced) ──
22+
let editTimer;
23+
const sourceEditor = document.getElementById('sourceEditor');
24+
if (sourceEditor) {
25+
sourceEditor.addEventListener('input', function() {
26+
clearTimeout(editTimer);
27+
editTimer = setTimeout(() => {
28+
trackEvent('playground_edit', { action: 'yaml_edited' });
29+
}, 2000);
30+
});
31+
}

apps/landing/scripts/copy.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// ── Copy install command ──
22
function copyInstall(el) {
33
navigator.clipboard.writeText('npx dev-workflows init');
4+
trackEvent('copy_command', { command: 'npx dev-workflows init', location: 'hero' });
45
const orig = el.textContent || el.innerText;
56
if (el.classList.contains('btn-ghost')) {
67
const origHTML = el.innerHTML;
@@ -11,12 +12,14 @@ function copyInstall(el) {
1112

1213
function copyBlock(btn, text) {
1314
navigator.clipboard.writeText(text);
15+
trackEvent('copy_command', { command: text, location: 'blocks' });
1416
btn.classList.add('copied');
1517
setTimeout(() => btn.classList.remove('copied'), 1500);
1618
}
1719

1820
function copyCta(el) {
1921
navigator.clipboard.writeText('npx dev-workflows init');
22+
trackEvent('copy_command', { command: 'npx dev-workflows init', location: 'cta_bottom' });
2023
el.classList.add('copied');
2124
setTimeout(() => el.classList.remove('copied'), 1500);
2225
}

apps/landing/scripts/dialog.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ const DIALOG_CONTENT = {
101101
function openDialog(key) {
102102
const d = DIALOG_CONTENT[key];
103103
if (!d) return;
104+
trackEvent('dialog_open', { tool: key });
104105
const overlay = document.getElementById('cfDialogOverlay');
105106
const icon = document.getElementById('cfDialogIcon');
106107
const filename = document.getElementById('cfDialogFilename');

apps/landing/scripts/drift.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ function renderDriftStage(stage) {
168168
}
169169

170170
function startDrift() {
171+
trackEvent('drift_watch', { action: 'start' });
171172
const watchBtn = document.getElementById('driftWatchBtn');
172173
const hint = document.getElementById('driftHint');
173174
const resetBtn = document.getElementById('driftResetBtn');
@@ -215,6 +216,7 @@ function resetDrift() {
215216
}
216217

217218
function fixDrift() {
219+
trackEvent('drift_fix', { action: 'compile_fix' });
218220
clearInterval(driftTimer);
219221
document.getElementById('driftHint').classList.remove('show');
220222

apps/landing/scripts/playground.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ function switchTab(btn) {
44
document.querySelectorAll('.pg-tab').forEach(t => t.classList.remove('active'));
55
btn.classList.add('active');
66
currentTab = btn.dataset.target;
7+
trackEvent('playground_tab', { tab: currentTab });
78
renderOutput();
89
}
910

apps/landing/scripts/terminal.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ function clearTermTimers() {
103103
}
104104

105105
function switchTermTab(tab, btnEl) {
106+
trackEvent('terminal_tab', { command: tab });
106107
// Update active tab
107108
document.querySelectorAll('.term-tab').forEach(t => t.classList.remove('active'));
108109
if (btnEl) btnEl.classList.add('active');

0 commit comments

Comments
 (0)