thumbgate 1.27.6 → 1.27.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/commands/thumbgate-blocked.md +27 -0
- package/.claude/commands/thumbgate-doctor.md +30 -0
- package/.claude/commands/thumbgate-guard.md +36 -0
- package/.claude/commands/thumbgate-protect.md +30 -0
- package/.claude/commands/thumbgate-rules.md +30 -0
- package/.claude-plugin/plugin.json +1 -1
- package/.well-known/llms.txt +6 -2
- package/.well-known/mcp/server-card.json +1 -1
- package/README.md +49 -5
- package/adapters/claude/.mcp.json +2 -2
- package/adapters/letta/README.md +41 -0
- package/adapters/letta/thumbgate-letta-adapter.js +133 -0
- package/adapters/mcp/server-stdio.js +16 -1
- package/adapters/opencode/opencode.json +1 -1
- package/adapters/policy-engine/ethicore-guardian-client.js +68 -0
- package/adapters/policy-engine/thumbgate-policy-engine-adapter.js +260 -0
- package/bench/observability-eval-suite.json +26 -0
- package/bin/cli.js +180 -2
- package/bin/postinstall.js +1 -1
- package/config/gate-templates.json +84 -0
- package/config/gates/claim-verification.json +6 -0
- package/config/gates/default.json +20 -0
- package/config/github-about.json +1 -1
- package/config/model-candidates.json +50 -0
- package/package.json +66 -25
- package/public/agent-manager.html +41 -1
- package/public/agents-cost-savings.html +1 -1
- package/public/ai-malpractice-prevention.html +2 -1
- package/public/assets/brand/github-social-preview.png +0 -0
- package/public/assets/brand/thumbgate-icon-512.png +0 -0
- package/public/assets/brand/thumbgate-icon-pro-512.png +0 -0
- package/public/assets/brand/thumbgate-icon-team-512.png +0 -0
- package/public/assets/brand/thumbgate-logo-1200x360.png +0 -0
- package/public/assets/brand/thumbgate-mark-inline.svg +15 -0
- package/public/assets/brand/thumbgate-mark-pro.svg +23 -0
- package/public/assets/brand/thumbgate-mark-team.svg +26 -0
- package/public/assets/brand/thumbgate-mark.svg +15 -0
- package/public/assets/brand/thumbgate-wordmark.svg +20 -0
- package/public/assets/claude-thumbgate-statusbar.svg +8 -0
- package/public/assets/codex-thumbgate-statusbar-test.svg +9 -0
- package/public/assets/legal-intake-control-flow.svg +66 -0
- package/public/blog.html +1 -1
- package/public/brand/thumbgate-mark.svg +15 -0
- package/public/brand/thumbgate-og.svg +16 -0
- package/public/codex-enterprise.html +1 -1
- package/public/codex-plugin.html +1 -1
- package/public/compare.html +23 -3
- package/public/dashboard.html +312 -30
- package/public/federal.html +1 -1
- package/public/guide.html +5 -4
- package/public/index.html +167 -49
- package/public/js/buyer-intent.js +672 -0
- package/public/learn.html +74 -7
- package/public/lessons.html +2 -1
- package/public/numbers.html +3 -3
- package/public/pricing.html +63 -15
- package/public/pro.html +7 -7
- package/scripts/activation-quickstart.js +187 -0
- package/scripts/agent-memory-lifecycle.js +211 -0
- package/scripts/async-eval-observability.js +236 -0
- package/scripts/auto-promote-gates.js +75 -4
- package/scripts/build-metadata.js +24 -3
- package/scripts/cli-schema.js +22 -0
- package/scripts/dashboard-chat.js +2 -1
- package/scripts/dashboard.js +8 -0
- package/scripts/export-databricks-bundle.js +5 -1
- package/scripts/export-dpo-pairs.js +7 -2
- package/scripts/feedback-aggregate.js +281 -0
- package/scripts/feedback-loop.js +34 -0
- package/scripts/filesystem-search.js +35 -10
- package/scripts/gates-engine.js +198 -6
- package/scripts/gemini-embedding-policy.js +2 -1
- package/scripts/hook-stop-anti-claim.js +227 -0
- package/scripts/hook-thumbgate-cache-updater.js +18 -2
- package/scripts/lesson-inference.js +8 -3
- package/scripts/lesson-search.js +17 -1
- package/scripts/operational-integrity.js +39 -5
- package/scripts/plausible-domain-config.js +4 -2
- package/scripts/rate-limiter.js +12 -6
- package/scripts/secret-redaction.js +166 -0
- package/scripts/security-scanner.js +100 -0
- package/scripts/self-distill-agent.js +3 -1
- package/scripts/self-harness-optimizer.js +141 -0
- package/scripts/seo-gsd.js +635 -0
- package/scripts/statusline-cache-path.js +17 -2
- package/scripts/statusline-cache-read.js +57 -0
- package/scripts/statusline-local-stats.js +9 -1
- package/scripts/statusline-meta.js +5 -2
- package/scripts/statusline.sh +13 -1
- package/scripts/sync-telemetry-from-prod.js +374 -0
- package/scripts/telemetry-analytics.js +9 -0
- package/scripts/thumbgate-search.js +85 -19
- package/scripts/tool-contract-validator.js +76 -0
- package/scripts/vector-store.js +44 -0
- package/scripts/workspace-evolver.js +62 -2
- package/src/api/server.js +715 -86
|
@@ -0,0 +1,672 @@
|
|
|
1
|
+
(function(global) {
|
|
2
|
+
var BUYER_EMAIL_STORAGE_KEY = 'thumbgateBuyerEmail';
|
|
3
|
+
var CHECKOUT_LINK_SELECTOR = 'a[href*="/checkout/pro"], a[href*="/go/pro"]';
|
|
4
|
+
var BUYER_EMAIL_SELECTOR = '[data-buyer-email]';
|
|
5
|
+
|
|
6
|
+
function normalizeBuyerEmail(value) {
|
|
7
|
+
return String(value || '').trim().toLowerCase();
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function isValidBuyerEmail(value) {
|
|
11
|
+
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(normalizeBuyerEmail(value));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function getStorage() {
|
|
15
|
+
return global.localStorage && typeof global.localStorage.getItem === 'function'
|
|
16
|
+
? global.localStorage
|
|
17
|
+
: null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function getStoredBuyerEmail() {
|
|
21
|
+
var storage = getStorage();
|
|
22
|
+
if (!storage) {
|
|
23
|
+
return '';
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
return normalizeBuyerEmail(storage.getItem(BUYER_EMAIL_STORAGE_KEY));
|
|
27
|
+
} catch (_error) {
|
|
28
|
+
return '';
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function storeBuyerEmail(email) {
|
|
33
|
+
var storage = getStorage();
|
|
34
|
+
if (!storage) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
try {
|
|
38
|
+
storage.setItem(BUYER_EMAIL_STORAGE_KEY, normalizeBuyerEmail(email));
|
|
39
|
+
return true;
|
|
40
|
+
} catch (_error) {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function resolveCheckoutUrl(urlValue, email) {
|
|
46
|
+
var origin = global.location && global.location.origin
|
|
47
|
+
? global.location.origin
|
|
48
|
+
: 'https://thumbgate.invalid';
|
|
49
|
+
var checkoutUrl = new URL(String(urlValue || '/checkout/pro'), origin);
|
|
50
|
+
var isHostedProRoute = checkoutUrl.origin === origin
|
|
51
|
+
&& (checkoutUrl.pathname === '/checkout/pro' || checkoutUrl.pathname === '/go/pro');
|
|
52
|
+
if (!isHostedProRoute) {
|
|
53
|
+
checkoutUrl = new URL('/checkout/pro', origin);
|
|
54
|
+
}
|
|
55
|
+
checkoutUrl.pathname = '/checkout/pro';
|
|
56
|
+
checkoutUrl.searchParams.set('confirm', '1');
|
|
57
|
+
if (isValidBuyerEmail(email)) {
|
|
58
|
+
checkoutUrl.searchParams.set('customer_email', normalizeBuyerEmail(email));
|
|
59
|
+
} else {
|
|
60
|
+
checkoutUrl.searchParams.delete('customer_email');
|
|
61
|
+
}
|
|
62
|
+
return checkoutUrl;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function getCheckoutLinks(selector) {
|
|
66
|
+
if (!global.document || typeof global.document.querySelectorAll !== 'function') {
|
|
67
|
+
return [];
|
|
68
|
+
}
|
|
69
|
+
return Array.from(global.document.querySelectorAll(selector || CHECKOUT_LINK_SELECTOR));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function getBaseCheckoutHref(link) {
|
|
73
|
+
if (!link.dataset.baseHref) {
|
|
74
|
+
link.dataset.baseHref = link.getAttribute('href') || link.href || '/checkout/pro';
|
|
75
|
+
}
|
|
76
|
+
return link.dataset.baseHref;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function applyBuyerEmailToCheckoutLinks(email, selector) {
|
|
80
|
+
getCheckoutLinks(selector).forEach(function(link) {
|
|
81
|
+
link.href = resolveCheckoutUrl(getBaseCheckoutHref(link), email).toString();
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function hydrateBuyerEmailInputs(email, selector) {
|
|
86
|
+
if (!global.document || typeof global.document.querySelectorAll !== 'function') {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
Array.from(global.document.querySelectorAll(selector || BUYER_EMAIL_SELECTOR)).forEach(function(input) {
|
|
90
|
+
if (!input.value) {
|
|
91
|
+
input.value = normalizeBuyerEmail(email);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function getNewsletterStatusElement(form) {
|
|
97
|
+
if (!form) {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
return form.querySelector('[data-newsletter-status]')
|
|
101
|
+
|| (form.parentElement ? form.parentElement.querySelector('[data-newsletter-status]') : null);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function setNewsletterStatus(form, message, ok) {
|
|
105
|
+
var statusEl = getNewsletterStatusElement(form);
|
|
106
|
+
if (!statusEl) {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
statusEl.textContent = message;
|
|
110
|
+
statusEl.style.color = ok ? 'var(--cyan)' : 'var(--red, #f87171)';
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function submitNewsletterSignup(email, form) {
|
|
114
|
+
var action = form && form.action ? form.action : '/api/newsletter';
|
|
115
|
+
var response = await fetch(action, {
|
|
116
|
+
method: 'POST',
|
|
117
|
+
headers: {
|
|
118
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
119
|
+
Accept: 'application/json',
|
|
120
|
+
'X-Requested-With': 'fetch',
|
|
121
|
+
},
|
|
122
|
+
body: new URLSearchParams({ email: normalizeBuyerEmail(email) }).toString(),
|
|
123
|
+
credentials: 'same-origin',
|
|
124
|
+
});
|
|
125
|
+
if (!response.ok) {
|
|
126
|
+
var errorMessage = 'Unable to save your email right now.';
|
|
127
|
+
try {
|
|
128
|
+
var errorBody = await response.json();
|
|
129
|
+
if (errorBody && errorBody.error) {
|
|
130
|
+
errorMessage = errorBody.error;
|
|
131
|
+
}
|
|
132
|
+
} catch (_error) {
|
|
133
|
+
// Keep the default error message when the response is not JSON.
|
|
134
|
+
}
|
|
135
|
+
throw new Error(errorMessage);
|
|
136
|
+
}
|
|
137
|
+
try {
|
|
138
|
+
return await response.json();
|
|
139
|
+
} catch (_error) {
|
|
140
|
+
return { accepted: true, duplicate: false };
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function trackEvent(eventName, props) {
|
|
145
|
+
if (typeof global.plausible === 'function') {
|
|
146
|
+
global.plausible(eventName, { props: props || {} });
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
global.plausible = global.plausible || function() {
|
|
150
|
+
(global.plausible.q = global.plausible.q || []).push(arguments);
|
|
151
|
+
};
|
|
152
|
+
if (typeof global.plausible === 'function') {
|
|
153
|
+
global.plausible(eventName, { props: props || {} });
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function getCurrentPathname() {
|
|
158
|
+
return global.location && global.location.pathname
|
|
159
|
+
? global.location.pathname.replace(/\/$/, '') || '/'
|
|
160
|
+
: '/';
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function normalizePlacement(pathname) {
|
|
164
|
+
return String(pathname || '/')
|
|
165
|
+
.replace(/\.html$/, '')
|
|
166
|
+
.replace(/^\//, '')
|
|
167
|
+
.replace(/[^a-z0-9]+/gi, '_')
|
|
168
|
+
.replace(/^_+|_+$/g, '')
|
|
169
|
+
.toLowerCase() || 'home';
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function isRevenueAssistEligible(pathname) {
|
|
173
|
+
var path = String(pathname || '/');
|
|
174
|
+
if (path === '/checkout/pro' || path.indexOf('/go/pro') === 0) return false;
|
|
175
|
+
if (path === '/' || path === '/guide' || path === '/guide.html') return true;
|
|
176
|
+
if (path === '/dashboard' || path === '/dashboard.html') return true;
|
|
177
|
+
if (path === '/learn' || path === '/learn.html' || path.indexOf('/learn/') === 0) return true;
|
|
178
|
+
if (path === '/lessons' || path === '/lessons.html') return true;
|
|
179
|
+
if (path === '/ai-malpractice-prevention' || path === '/ai-malpractice-prevention.html') return true;
|
|
180
|
+
if (path.indexOf('/guides/') === 0) return true;
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function appendCampaignParams(href, params) {
|
|
185
|
+
var origin = global.location && global.location.origin
|
|
186
|
+
? global.location.origin
|
|
187
|
+
: 'https://thumbgate.ai';
|
|
188
|
+
var url = new URL(href, origin);
|
|
189
|
+
Object.keys(params || {}).forEach(function(key) {
|
|
190
|
+
if (params[key] !== null && params[key] !== undefined) {
|
|
191
|
+
url.searchParams.set(key, params[key]);
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
return url.toString();
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function injectRevenueAssistStyles() {
|
|
198
|
+
if (!global.document || global.document.getElementById('thumbgate-revenue-assist-style')) {
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
var style = global.document.createElement('style');
|
|
202
|
+
style.id = 'thumbgate-revenue-assist-style';
|
|
203
|
+
style.textContent = [
|
|
204
|
+
'[data-thumbgate-revenue-assist]{position:fixed;right:18px;bottom:18px;z-index:2147483000;width:min(360px,calc(100vw - 32px));background:#0f172a;color:#f8fafc;border:1px solid rgba(148,163,184,.35);border-radius:8px;box-shadow:0 20px 60px rgba(2,6,23,.38);font:14px/1.45 system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;padding:14px}',
|
|
205
|
+
'[data-thumbgate-revenue-assist] strong{display:block;font-size:15px;margin:0 0 5px}',
|
|
206
|
+
'[data-thumbgate-revenue-assist] p{margin:0 0 10px;color:#cbd5e1}',
|
|
207
|
+
'[data-thumbgate-revenue-assist] nav{display:flex;gap:8px;flex-wrap:wrap}',
|
|
208
|
+
'[data-thumbgate-revenue-assist] a,[data-thumbgate-revenue-assist] button{border-radius:6px;border:1px solid rgba(148,163,184,.45);padding:8px 10px;font-weight:700;text-decoration:none;cursor:pointer}',
|
|
209
|
+
'[data-thumbgate-revenue-assist] a:first-child{background:#22d3ee;color:#082f49;border-color:#22d3ee}',
|
|
210
|
+
'[data-thumbgate-revenue-assist] a:nth-child(2){background:#f8fafc;color:#0f172a;border-color:#f8fafc}',
|
|
211
|
+
'[data-thumbgate-revenue-assist] button{background:transparent;color:#cbd5e1}',
|
|
212
|
+
'[data-thumbgate-abandon-survey]{position:fixed;right:18px;bottom:18px;z-index:2147483001;width:min(380px,calc(100vw - 32px));background:#111827;color:#f9fafb;border:1px solid rgba(148,163,184,.4);border-radius:8px;box-shadow:0 20px 60px rgba(2,6,23,.42);font:14px/1.45 system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;padding:14px}',
|
|
213
|
+
'[data-thumbgate-abandon-survey] strong{display:block;font-size:15px;margin-bottom:8px}',
|
|
214
|
+
'[data-thumbgate-abandon-survey] div{display:grid;gap:7px}',
|
|
215
|
+
'[data-thumbgate-abandon-survey] button{border-radius:6px;border:1px solid rgba(148,163,184,.45);background:#1f2937;color:#f9fafb;padding:8px 10px;text-align:left;cursor:pointer}',
|
|
216
|
+
'@media (max-width:640px){[data-thumbgate-revenue-assist],[data-thumbgate-abandon-survey]{right:12px;bottom:12px;width:calc(100vw - 24px)}}'
|
|
217
|
+
].join('');
|
|
218
|
+
global.document.head.appendChild(style);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function initializeRevenueAssist(options) {
|
|
222
|
+
var settings = options || {};
|
|
223
|
+
if (!global.document || typeof global.document.querySelector !== 'function') return null;
|
|
224
|
+
if (global.document.querySelector('[data-revenue-assist="off"]')) return null;
|
|
225
|
+
if (global.document.querySelector('[data-thumbgate-revenue-assist]')) return null;
|
|
226
|
+
|
|
227
|
+
var pathname = settings.pathname || getCurrentPathname();
|
|
228
|
+
if (!isRevenueAssistEligible(pathname)) return null;
|
|
229
|
+
|
|
230
|
+
injectRevenueAssistStyles();
|
|
231
|
+
|
|
232
|
+
var placement = settings.placement || normalizePlacement(pathname);
|
|
233
|
+
var campaign = settings.campaign || 'high_traffic_pages';
|
|
234
|
+
var proHref = appendCampaignParams(settings.proHref || '/checkout/pro', {
|
|
235
|
+
confirm: '1',
|
|
236
|
+
utm_source: 'owned_site',
|
|
237
|
+
utm_medium: 'sticky_cta',
|
|
238
|
+
utm_campaign: campaign,
|
|
239
|
+
cta_id: 'assist_pro_checkout',
|
|
240
|
+
cta_placement: placement,
|
|
241
|
+
plan_id: 'pro',
|
|
242
|
+
});
|
|
243
|
+
var intakeHref = appendCampaignParams(settings.intakeHref || '/#workflow-sprint-intake', {
|
|
244
|
+
utm_source: 'owned_site',
|
|
245
|
+
utm_medium: 'sticky_cta',
|
|
246
|
+
utm_campaign: campaign,
|
|
247
|
+
cta_id: 'assist_workflow_intake',
|
|
248
|
+
cta_placement: placement,
|
|
249
|
+
client_reference_id: 'thumbgate_assist_' + placement,
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
var panel = global.document.createElement('aside');
|
|
253
|
+
panel.setAttribute('data-thumbgate-revenue-assist', placement);
|
|
254
|
+
panel.setAttribute('aria-label', 'ThumbGate paid help');
|
|
255
|
+
panel.innerHTML = [
|
|
256
|
+
'<strong>Stop the repeated agent failure?</strong>',
|
|
257
|
+
'<p>Use Pro for self-serve proof, or send the workflow first when one failure is already costing time.</p>',
|
|
258
|
+
'<nav>',
|
|
259
|
+
'<a data-assist-cta="assist_pro_checkout" href="' + proHref + '">Get Pro</a>',
|
|
260
|
+
'<a data-assist-cta="assist_workflow_intake" href="' + intakeHref + '">Send workflow first</a>',
|
|
261
|
+
'<button type="button" data-assist-dismiss>Not now</button>',
|
|
262
|
+
'</nav>'
|
|
263
|
+
].join('');
|
|
264
|
+
|
|
265
|
+
panel.querySelectorAll('[data-assist-cta]').forEach(function(link) {
|
|
266
|
+
link.addEventListener('click', function() {
|
|
267
|
+
try {
|
|
268
|
+
global.sessionStorage.setItem('thumbgateRevenueAssistCheckoutSeen', '1');
|
|
269
|
+
} catch (_error) {}
|
|
270
|
+
trackEvent('assist_cta_click', {
|
|
271
|
+
ctaId: link.getAttribute('data-assist-cta'),
|
|
272
|
+
ctaPlacement: placement,
|
|
273
|
+
page: pathname,
|
|
274
|
+
});
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
var dismissButton = panel.querySelector('[data-assist-dismiss]');
|
|
279
|
+
if (dismissButton) {
|
|
280
|
+
dismissButton.addEventListener('click', function() {
|
|
281
|
+
trackEvent('assist_cta_dismiss', {
|
|
282
|
+
ctaPlacement: placement,
|
|
283
|
+
page: pathname,
|
|
284
|
+
});
|
|
285
|
+
panel.remove();
|
|
286
|
+
showAbandonSurvey('cta_dismiss');
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
global.document.body.appendChild(panel);
|
|
291
|
+
trackEvent('assist_cta_impression', {
|
|
292
|
+
ctaPlacement: placement,
|
|
293
|
+
page: pathname,
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
function wasSurveyShown() {
|
|
297
|
+
try {
|
|
298
|
+
return global.sessionStorage.getItem('thumbgateRevenueAssistSurveyShown') === '1';
|
|
299
|
+
} catch (_error) {
|
|
300
|
+
return false;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function markSurveyShown() {
|
|
305
|
+
try {
|
|
306
|
+
global.sessionStorage.setItem('thumbgateRevenueAssistSurveyShown', '1');
|
|
307
|
+
} catch (_error) {}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function checkoutWasSeen() {
|
|
311
|
+
try {
|
|
312
|
+
return global.sessionStorage.getItem('thumbgateRevenueAssistCheckoutSeen') === '1';
|
|
313
|
+
} catch (_error) {
|
|
314
|
+
return false;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function showAbandonSurvey(trigger) {
|
|
319
|
+
if (wasSurveyShown() || checkoutWasSeen() || global.document.querySelector('[data-thumbgate-abandon-survey]')) {
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
markSurveyShown();
|
|
323
|
+
var survey = global.document.createElement('aside');
|
|
324
|
+
survey.setAttribute('data-thumbgate-abandon-survey', trigger || 'unknown');
|
|
325
|
+
survey.setAttribute('aria-label', 'ThumbGate checkout feedback');
|
|
326
|
+
survey.innerHTML = [
|
|
327
|
+
'<strong>What stopped you from buying today?</strong>',
|
|
328
|
+
'<div>',
|
|
329
|
+
'<button type="button" data-abandon-reason="fit_unclear">Not sure it fits my agent stack</button>',
|
|
330
|
+
'<button type="button" data-abandon-reason="need_proof">Need proof before paying</button>',
|
|
331
|
+
'<button type="button" data-abandon-reason="price_scope_unclear">Price or scope is unclear</button>',
|
|
332
|
+
'<button type="button" data-abandon-reason="researching">Just researching</button>',
|
|
333
|
+
'</div>'
|
|
334
|
+
].join('');
|
|
335
|
+
survey.querySelectorAll('[data-abandon-reason]').forEach(function(button) {
|
|
336
|
+
button.addEventListener('click', function() {
|
|
337
|
+
trackEvent('checkout_abandon_reason', {
|
|
338
|
+
reason: button.getAttribute('data-abandon-reason'),
|
|
339
|
+
trigger: trigger || 'unknown',
|
|
340
|
+
ctaPlacement: placement,
|
|
341
|
+
page: pathname,
|
|
342
|
+
});
|
|
343
|
+
survey.remove();
|
|
344
|
+
});
|
|
345
|
+
});
|
|
346
|
+
global.document.body.appendChild(survey);
|
|
347
|
+
trackEvent('checkout_abandon_prompt', {
|
|
348
|
+
trigger: trigger || 'unknown',
|
|
349
|
+
ctaPlacement: placement,
|
|
350
|
+
page: pathname,
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
if (global.setTimeout) {
|
|
355
|
+
global.setTimeout(function() {
|
|
356
|
+
showAbandonSurvey('dwell_45s');
|
|
357
|
+
}, settings.surveyDelayMs || 45000);
|
|
358
|
+
}
|
|
359
|
+
if (global.document && global.document.addEventListener) {
|
|
360
|
+
global.document.addEventListener('mouseleave', function(event) {
|
|
361
|
+
if (event && event.clientY <= 0) {
|
|
362
|
+
showAbandonSurvey('exit_intent');
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
return {
|
|
368
|
+
panel: panel,
|
|
369
|
+
showAbandonSurvey: showAbandonSurvey,
|
|
370
|
+
placement: placement,
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function normalizeInteger(value) {
|
|
375
|
+
var parsed = Number(value);
|
|
376
|
+
return Number.isFinite(parsed) ? Math.round(parsed) : null;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
function bucketDwellMs(value) {
|
|
380
|
+
var ms = normalizeInteger(value) || 0;
|
|
381
|
+
if (ms < 10000) return 'under_10s';
|
|
382
|
+
if (ms < 30000) return '10s_to_30s';
|
|
383
|
+
if (ms < 60000) return '30s_to_60s';
|
|
384
|
+
if (ms < 180000) return '1m_to_3m';
|
|
385
|
+
return 'over_3m';
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
function bucketScrollPercent(value) {
|
|
389
|
+
var pct = normalizeInteger(value);
|
|
390
|
+
if (pct === null) return 'unknown';
|
|
391
|
+
if (pct < 25) return 'under_25';
|
|
392
|
+
if (pct < 50) return '25_to_49';
|
|
393
|
+
if (pct < 75) return '50_to_74';
|
|
394
|
+
if (pct < 100) return '75_to_99';
|
|
395
|
+
return '100';
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function initializeBehaviorAnalytics(options) {
|
|
399
|
+
var settings = options || {};
|
|
400
|
+
var sendTelemetry = typeof settings.sendTelemetry === 'function'
|
|
401
|
+
? settings.sendTelemetry
|
|
402
|
+
: function() {};
|
|
403
|
+
var state = {
|
|
404
|
+
startedAt: Date.now(),
|
|
405
|
+
maxScrollPercent: 0,
|
|
406
|
+
lastVisibleSection: settings.initialSectionId || null,
|
|
407
|
+
emailFocused: false,
|
|
408
|
+
emailCaptured: false,
|
|
409
|
+
sectionSeen: Object.create(null),
|
|
410
|
+
ctaSeen: Object.create(null),
|
|
411
|
+
exitSent: false,
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
function emit(eventType, extra) {
|
|
415
|
+
sendTelemetry(eventType, Object.assign({
|
|
416
|
+
pageType: settings.pageType || 'marketing',
|
|
417
|
+
page: settings.pagePath || (global.location ? global.location.pathname : null),
|
|
418
|
+
landingPath: settings.landingPath || (global.location ? global.location.pathname : null),
|
|
419
|
+
}, extra || {}));
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
function observeTargets(targets, callback, threshold) {
|
|
423
|
+
if (!global.IntersectionObserver || !Array.isArray(targets) || !targets.length || !global.document) {
|
|
424
|
+
return null;
|
|
425
|
+
}
|
|
426
|
+
var observer = new global.IntersectionObserver(function(entries) {
|
|
427
|
+
entries.forEach(function(entry) {
|
|
428
|
+
if (!entry.isIntersecting) return;
|
|
429
|
+
callback(entry.target);
|
|
430
|
+
});
|
|
431
|
+
}, { threshold: threshold || 0.45 });
|
|
432
|
+
|
|
433
|
+
targets.forEach(function(target) {
|
|
434
|
+
if (target && target.element) {
|
|
435
|
+
observer.observe(target.element);
|
|
436
|
+
}
|
|
437
|
+
});
|
|
438
|
+
return observer;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
function resolveTargets(items) {
|
|
442
|
+
if (!global.document || typeof global.document.querySelector !== 'function') {
|
|
443
|
+
return [];
|
|
444
|
+
}
|
|
445
|
+
return (items || []).map(function(item) {
|
|
446
|
+
var element = global.document.querySelector(item.selector);
|
|
447
|
+
if (!element) return null;
|
|
448
|
+
return Object.assign({ element: element }, item);
|
|
449
|
+
}).filter(Boolean);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
function markEmailCaptured() {
|
|
453
|
+
state.emailCaptured = true;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
var sectionTargets = resolveTargets(settings.sections);
|
|
457
|
+
observeTargets(sectionTargets, function(target) {
|
|
458
|
+
var sectionId = target.sectionId || target.id || target.selector || 'unknown';
|
|
459
|
+
state.lastVisibleSection = sectionId;
|
|
460
|
+
if (state.sectionSeen[sectionId]) return;
|
|
461
|
+
state.sectionSeen[sectionId] = true;
|
|
462
|
+
emit('section_view', {
|
|
463
|
+
sectionId: sectionId,
|
|
464
|
+
sectionLabel: target.sectionLabel || sectionId,
|
|
465
|
+
});
|
|
466
|
+
}, 0.35);
|
|
467
|
+
|
|
468
|
+
var ctaTargets = resolveTargets(settings.ctaImpressions);
|
|
469
|
+
observeTargets(ctaTargets, function(target) {
|
|
470
|
+
var ctaId = target.ctaId || target.selector || 'unknown_cta';
|
|
471
|
+
if (state.ctaSeen[ctaId]) return;
|
|
472
|
+
state.ctaSeen[ctaId] = true;
|
|
473
|
+
emit('cta_impression', {
|
|
474
|
+
ctaId: ctaId,
|
|
475
|
+
ctaPlacement: target.ctaPlacement || null,
|
|
476
|
+
planId: target.planId || null,
|
|
477
|
+
});
|
|
478
|
+
}, 0.6);
|
|
479
|
+
|
|
480
|
+
if (global.addEventListener) {
|
|
481
|
+
global.addEventListener('scroll', function() {
|
|
482
|
+
if (!global.document || !global.document.documentElement) return;
|
|
483
|
+
var docHeight = global.document.documentElement.scrollHeight - (global.innerHeight || 0);
|
|
484
|
+
if (docHeight <= 0) {
|
|
485
|
+
state.maxScrollPercent = 100;
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
var nextPercent = Math.max(0, Math.min(100, Math.round(((global.scrollY || 0) / docHeight) * 100)));
|
|
489
|
+
if (nextPercent > state.maxScrollPercent) {
|
|
490
|
+
state.maxScrollPercent = nextPercent;
|
|
491
|
+
}
|
|
492
|
+
}, { passive: true });
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
if (global.document && typeof global.document.querySelectorAll === 'function') {
|
|
496
|
+
var emailSelector = settings.emailSelector || '[data-buyer-email]';
|
|
497
|
+
Array.from(global.document.querySelectorAll(emailSelector)).forEach(function(input) {
|
|
498
|
+
input.addEventListener('focus', function() {
|
|
499
|
+
if (state.emailFocused) return;
|
|
500
|
+
state.emailFocused = true;
|
|
501
|
+
emit('buyer_email_focus', {
|
|
502
|
+
ctaId: settings.emailCtaId || 'buyer_email',
|
|
503
|
+
ctaPlacement: settings.emailCtaPlacement || null,
|
|
504
|
+
});
|
|
505
|
+
});
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
Array.from(global.document.querySelectorAll(settings.newsletterFormSelector || '[data-newsletter-form]')).forEach(function(form) {
|
|
509
|
+
form.addEventListener('submit', function() {
|
|
510
|
+
var input = form.querySelector(settings.formEmailSelector || 'input[name="email"]');
|
|
511
|
+
if (isValidBuyerEmail(getEmailFromInput(input))) {
|
|
512
|
+
markEmailCaptured();
|
|
513
|
+
}
|
|
514
|
+
});
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
function sendExitSignals() {
|
|
519
|
+
if (state.exitSent) return;
|
|
520
|
+
state.exitSent = true;
|
|
521
|
+
var engagementMs = Math.max(0, Date.now() - state.startedAt);
|
|
522
|
+
emit('page_exit', {
|
|
523
|
+
lastVisibleSection: state.lastVisibleSection || 'unknown',
|
|
524
|
+
engagementMs: engagementMs,
|
|
525
|
+
dwellBucket: bucketDwellMs(engagementMs),
|
|
526
|
+
maxScrollPercent: state.maxScrollPercent,
|
|
527
|
+
scrollBucket: bucketScrollPercent(state.maxScrollPercent),
|
|
528
|
+
buyerEmailFocused: state.emailFocused,
|
|
529
|
+
buyerEmailCaptured: state.emailCaptured,
|
|
530
|
+
});
|
|
531
|
+
if (state.emailFocused && !state.emailCaptured) {
|
|
532
|
+
emit('buyer_email_abandon', {
|
|
533
|
+
lastVisibleSection: state.lastVisibleSection || 'unknown',
|
|
534
|
+
engagementMs: engagementMs,
|
|
535
|
+
dwellBucket: bucketDwellMs(engagementMs),
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
if (global.addEventListener) {
|
|
541
|
+
global.addEventListener('pagehide', sendExitSignals);
|
|
542
|
+
global.addEventListener('beforeunload', sendExitSignals);
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
return {
|
|
546
|
+
markEmailCaptured: markEmailCaptured,
|
|
547
|
+
sendExitSignals: sendExitSignals,
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
function getEmailFromInput(input) {
|
|
552
|
+
return normalizeBuyerEmail(input && input.value);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
function initializeBuyerIntent(options) {
|
|
556
|
+
var settings = options || {};
|
|
557
|
+
var storedEmail = getStoredBuyerEmail();
|
|
558
|
+
if (storedEmail) {
|
|
559
|
+
hydrateBuyerEmailInputs(storedEmail, settings.emailSelector);
|
|
560
|
+
applyBuyerEmailToCheckoutLinks(storedEmail, settings.checkoutSelector);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
if (!global.document || typeof global.document.querySelectorAll !== 'function') {
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
Array.from(global.document.querySelectorAll(settings.formSelector || '[data-newsletter-form]')).forEach(function(form) {
|
|
568
|
+
form.addEventListener('submit', async function(event) {
|
|
569
|
+
event.preventDefault();
|
|
570
|
+
var input = form.querySelector(settings.formEmailSelector || 'input[name="email"]');
|
|
571
|
+
var email = getEmailFromInput(input);
|
|
572
|
+
if (!isValidBuyerEmail(email)) {
|
|
573
|
+
setNewsletterStatus(form, settings.invalidEmailMessage || 'Enter a valid work email.', false);
|
|
574
|
+
if (input) {
|
|
575
|
+
input.focus();
|
|
576
|
+
}
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
storeBuyerEmail(email);
|
|
581
|
+
hydrateBuyerEmailInputs(email, settings.emailSelector);
|
|
582
|
+
applyBuyerEmailToCheckoutLinks(email, settings.checkoutSelector);
|
|
583
|
+
|
|
584
|
+
try {
|
|
585
|
+
var result = await submitNewsletterSignup(email, form);
|
|
586
|
+
var successMessage = result && result.duplicate
|
|
587
|
+
? (settings.duplicateMessage || 'You are already on the list. Checkout on this device is now prefilled.')
|
|
588
|
+
: (settings.successMessage || 'Saved. We will keep checkout prefilled on this device.');
|
|
589
|
+
setNewsletterStatus(form, successMessage, true);
|
|
590
|
+
trackEvent('newsletter_signup', {
|
|
591
|
+
page: form.dataset.page || settings.page || 'homepage',
|
|
592
|
+
intent: form.dataset.intent || settings.intent || 'buyer_follow_up',
|
|
593
|
+
});
|
|
594
|
+
} catch (error) {
|
|
595
|
+
setNewsletterStatus(
|
|
596
|
+
form,
|
|
597
|
+
error && error.message ? error.message : 'Unable to save your email right now.',
|
|
598
|
+
false
|
|
599
|
+
);
|
|
600
|
+
}
|
|
601
|
+
});
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
function initializeEmailCheckoutButtons(options) {
|
|
606
|
+
var settings = options || {};
|
|
607
|
+
if (!global.document || typeof global.document.querySelectorAll !== 'function') {
|
|
608
|
+
return;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
Array.from(global.document.querySelectorAll(settings.buttonSelector || '.btn-email-checkout')).forEach(function(button) {
|
|
612
|
+
button.addEventListener('click', async function() {
|
|
613
|
+
var form = button.closest('form');
|
|
614
|
+
var input = form ? form.querySelector(settings.formEmailSelector || 'input[name="email"]') : null;
|
|
615
|
+
var email = getEmailFromInput(input) || getStoredBuyerEmail();
|
|
616
|
+
if (!isValidBuyerEmail(email)) {
|
|
617
|
+
setNewsletterStatus(form, settings.invalidCheckoutMessage || 'Enter a valid work email before checkout.', false);
|
|
618
|
+
if (input) {
|
|
619
|
+
input.focus();
|
|
620
|
+
}
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
storeBuyerEmail(email);
|
|
625
|
+
hydrateBuyerEmailInputs(email, settings.emailSelector);
|
|
626
|
+
applyBuyerEmailToCheckoutLinks(email, settings.checkoutSelector);
|
|
627
|
+
trackEvent(settings.eventName || 'pro_checkout_email_start', settings.eventProps || { page: 'pro', intent: 'checkout' });
|
|
628
|
+
|
|
629
|
+
try {
|
|
630
|
+
await submitNewsletterSignup(email, form);
|
|
631
|
+
} catch (_error) {
|
|
632
|
+
// Continue to checkout even if signup persistence fails.
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
var checkoutLink = global.document.querySelector(settings.checkoutLinkSelector || '.btn-pro-checkout');
|
|
636
|
+
if (checkoutLink) {
|
|
637
|
+
global.location.assign(checkoutLink.href);
|
|
638
|
+
}
|
|
639
|
+
});
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
global.ThumbGateBuyerIntent = {
|
|
644
|
+
normalizeBuyerEmail: normalizeBuyerEmail,
|
|
645
|
+
isValidBuyerEmail: isValidBuyerEmail,
|
|
646
|
+
getStoredBuyerEmail: getStoredBuyerEmail,
|
|
647
|
+
storeBuyerEmail: storeBuyerEmail,
|
|
648
|
+
resolveCheckoutUrl: resolveCheckoutUrl,
|
|
649
|
+
applyBuyerEmailToCheckoutLinks: applyBuyerEmailToCheckoutLinks,
|
|
650
|
+
hydrateBuyerEmailInputs: hydrateBuyerEmailInputs,
|
|
651
|
+
setNewsletterStatus: setNewsletterStatus,
|
|
652
|
+
submitNewsletterSignup: submitNewsletterSignup,
|
|
653
|
+
initializeBuyerIntent: initializeBuyerIntent,
|
|
654
|
+
initializeEmailCheckoutButtons: initializeEmailCheckoutButtons,
|
|
655
|
+
initializeRevenueAssist: initializeRevenueAssist,
|
|
656
|
+
isRevenueAssistEligible: isRevenueAssistEligible,
|
|
657
|
+
trackEvent: trackEvent,
|
|
658
|
+
initializeBehaviorAnalytics: initializeBehaviorAnalytics,
|
|
659
|
+
bucketDwellMs: bucketDwellMs,
|
|
660
|
+
bucketScrollPercent: bucketScrollPercent,
|
|
661
|
+
};
|
|
662
|
+
|
|
663
|
+
if (global.document) {
|
|
664
|
+
if (global.document.readyState === 'loading') {
|
|
665
|
+
global.document.addEventListener('DOMContentLoaded', function() {
|
|
666
|
+
initializeRevenueAssist();
|
|
667
|
+
});
|
|
668
|
+
} else {
|
|
669
|
+
initializeRevenueAssist();
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
})(globalThis);
|