thumbgate 1.27.6 → 1.27.7

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.
Files changed (96) hide show
  1. package/.claude/commands/thumbgate-blocked.md +27 -0
  2. package/.claude/commands/thumbgate-doctor.md +30 -0
  3. package/.claude/commands/thumbgate-guard.md +36 -0
  4. package/.claude/commands/thumbgate-protect.md +30 -0
  5. package/.claude/commands/thumbgate-rules.md +30 -0
  6. package/.claude-plugin/plugin.json +1 -1
  7. package/.well-known/llms.txt +6 -2
  8. package/.well-known/mcp/server-card.json +1 -1
  9. package/README.md +49 -5
  10. package/adapters/claude/.mcp.json +2 -2
  11. package/adapters/letta/README.md +41 -0
  12. package/adapters/letta/thumbgate-letta-adapter.js +133 -0
  13. package/adapters/mcp/server-stdio.js +16 -1
  14. package/adapters/opencode/opencode.json +1 -1
  15. package/adapters/policy-engine/ethicore-guardian-client.js +68 -0
  16. package/adapters/policy-engine/thumbgate-policy-engine-adapter.js +260 -0
  17. package/bench/observability-eval-suite.json +26 -0
  18. package/bin/cli.js +180 -2
  19. package/bin/postinstall.js +1 -1
  20. package/config/gate-templates.json +84 -0
  21. package/config/gates/claim-verification.json +6 -0
  22. package/config/gates/default.json +20 -0
  23. package/config/github-about.json +1 -1
  24. package/config/model-candidates.json +50 -0
  25. package/package.json +65 -25
  26. package/public/agent-manager.html +41 -1
  27. package/public/agents-cost-savings.html +1 -1
  28. package/public/ai-malpractice-prevention.html +2 -1
  29. package/public/assets/brand/github-social-preview.png +0 -0
  30. package/public/assets/brand/thumbgate-icon-512.png +0 -0
  31. package/public/assets/brand/thumbgate-icon-pro-512.png +0 -0
  32. package/public/assets/brand/thumbgate-icon-team-512.png +0 -0
  33. package/public/assets/brand/thumbgate-logo-1200x360.png +0 -0
  34. package/public/assets/brand/thumbgate-mark-inline.svg +15 -0
  35. package/public/assets/brand/thumbgate-mark-pro.svg +23 -0
  36. package/public/assets/brand/thumbgate-mark-team.svg +26 -0
  37. package/public/assets/brand/thumbgate-mark.svg +15 -0
  38. package/public/assets/brand/thumbgate-wordmark.svg +20 -0
  39. package/public/assets/claude-thumbgate-statusbar.svg +8 -0
  40. package/public/assets/codex-thumbgate-statusbar-test.svg +9 -0
  41. package/public/assets/legal-intake-control-flow.svg +66 -0
  42. package/public/blog.html +1 -1
  43. package/public/brand/thumbgate-mark.svg +15 -0
  44. package/public/brand/thumbgate-og.svg +16 -0
  45. package/public/codex-enterprise.html +1 -1
  46. package/public/codex-plugin.html +1 -1
  47. package/public/compare.html +23 -3
  48. package/public/dashboard.html +312 -30
  49. package/public/federal.html +1 -1
  50. package/public/guide.html +5 -4
  51. package/public/index.html +167 -49
  52. package/public/js/buyer-intent.js +672 -0
  53. package/public/learn.html +74 -7
  54. package/public/lessons.html +2 -1
  55. package/public/numbers.html +3 -3
  56. package/public/pricing.html +63 -15
  57. package/public/pro.html +7 -7
  58. package/scripts/activation-quickstart.js +187 -0
  59. package/scripts/agent-memory-lifecycle.js +211 -0
  60. package/scripts/async-eval-observability.js +236 -0
  61. package/scripts/auto-promote-gates.js +75 -4
  62. package/scripts/build-metadata.js +24 -3
  63. package/scripts/cli-schema.js +22 -0
  64. package/scripts/dashboard-chat.js +2 -1
  65. package/scripts/dashboard.js +8 -0
  66. package/scripts/export-databricks-bundle.js +5 -1
  67. package/scripts/export-dpo-pairs.js +7 -2
  68. package/scripts/feedback-aggregate.js +281 -0
  69. package/scripts/feedback-loop.js +34 -0
  70. package/scripts/filesystem-search.js +35 -10
  71. package/scripts/gates-engine.js +198 -6
  72. package/scripts/gemini-embedding-policy.js +2 -1
  73. package/scripts/hook-stop-anti-claim.js +227 -0
  74. package/scripts/hook-thumbgate-cache-updater.js +18 -2
  75. package/scripts/lesson-inference.js +8 -3
  76. package/scripts/lesson-search.js +17 -1
  77. package/scripts/operational-integrity.js +39 -5
  78. package/scripts/plausible-domain-config.js +4 -2
  79. package/scripts/rate-limiter.js +12 -6
  80. package/scripts/secret-redaction.js +166 -0
  81. package/scripts/security-scanner.js +100 -0
  82. package/scripts/self-distill-agent.js +3 -1
  83. package/scripts/self-harness-optimizer.js +141 -0
  84. package/scripts/seo-gsd.js +635 -0
  85. package/scripts/statusline-cache-path.js +17 -2
  86. package/scripts/statusline-cache-read.js +57 -0
  87. package/scripts/statusline-local-stats.js +9 -1
  88. package/scripts/statusline-meta.js +5 -2
  89. package/scripts/statusline.sh +13 -1
  90. package/scripts/sync-telemetry-from-prod.js +374 -0
  91. package/scripts/telemetry-analytics.js +9 -0
  92. package/scripts/thumbgate-search.js +85 -19
  93. package/scripts/tool-contract-validator.js +76 -0
  94. package/scripts/vector-store.js +44 -0
  95. package/scripts/workspace-evolver.js +62 -2
  96. 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);