@sitepulse/web 1.0.0

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/dist/index.mjs ADDED
@@ -0,0 +1,1722 @@
1
+ // src/utils.ts
2
+ function getDeviceType() {
3
+ if (typeof window === "undefined") return "desktop";
4
+ const ua = navigator.userAgent.toLowerCase();
5
+ if (/(tablet|ipad|playbook|silk)|(android(?!.*mobi))/i.test(ua)) {
6
+ return "tablet";
7
+ }
8
+ if (/Mobile|iP(hone|od)|Android|BlackBerry|IEMobile|Kindle|Silk-Accelerated|(hpw|web)OS|Opera M(obi|ini)/.test(
9
+ navigator.userAgent
10
+ )) {
11
+ return "mobile";
12
+ }
13
+ return "desktop";
14
+ }
15
+ function getBrowser() {
16
+ if (typeof window === "undefined") return "Unknown";
17
+ const ua = navigator.userAgent;
18
+ if (ua.indexOf("Firefox") > -1) return "Firefox";
19
+ if (ua.indexOf("SamsungBrowser") > -1) return "Samsung Browser";
20
+ if (ua.indexOf("Opera") > -1 || ua.indexOf("OPR") > -1) return "Opera";
21
+ if (ua.indexOf("Trident") > -1) return "Internet Explorer";
22
+ if (ua.indexOf("Edge") > -1 || ua.indexOf("Edg") > -1) return "Edge";
23
+ if (ua.indexOf("Chrome") > -1) return "Chrome";
24
+ if (ua.indexOf("Safari") > -1) return "Safari";
25
+ return "Unknown";
26
+ }
27
+ function getOS() {
28
+ if (typeof window === "undefined") return "Unknown";
29
+ const ua = navigator.userAgent;
30
+ if (ua.indexOf("Win") > -1) return "Windows";
31
+ if (ua.indexOf("Mac") > -1) return "macOS";
32
+ if (ua.indexOf("Linux") > -1) return "Linux";
33
+ if (ua.indexOf("Android") > -1) return "Android";
34
+ if (ua.indexOf("like Mac") > -1 || /iPhone|iPad|iPod/.test(ua)) return "iOS";
35
+ return "Unknown";
36
+ }
37
+ function generateUUID() {
38
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
39
+ return crypto.randomUUID();
40
+ }
41
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
42
+ const r = Math.random() * 16 | 0;
43
+ const v = c === "x" ? r : r & 3 | 8;
44
+ return v.toString(16);
45
+ });
46
+ }
47
+
48
+ // src/storage.ts
49
+ var ANON_KEY = "_sitepulse_anon_id";
50
+ var SESSION_KEY = "_sitepulse_sess_id";
51
+ var SESSION_EXPIRY_KEY = "_sitepulse_sess_exp";
52
+ var SESSION_TIMEOUT_MS = 30 * 60 * 1e3;
53
+ function getOrCreateAnonymousId() {
54
+ if (typeof window === "undefined") return generateUUID();
55
+ try {
56
+ let anonId = localStorage.getItem(ANON_KEY);
57
+ if (!anonId) {
58
+ anonId = `anon_${generateUUID().replace(/-/g, "")}`;
59
+ localStorage.setItem(ANON_KEY, anonId);
60
+ }
61
+ return anonId;
62
+ } catch {
63
+ return `anon_${generateUUID().replace(/-/g, "")}`;
64
+ }
65
+ }
66
+ function getOrCreateSessionId() {
67
+ if (typeof window === "undefined") return generateUUID();
68
+ const now = Date.now();
69
+ try {
70
+ const expStr = sessionStorage.getItem(SESSION_EXPIRY_KEY);
71
+ const existingSession = sessionStorage.getItem(SESSION_KEY);
72
+ if (existingSession && expStr) {
73
+ const exp = parseInt(expStr, 10);
74
+ if (now < exp) {
75
+ sessionStorage.setItem(SESSION_EXPIRY_KEY, (now + SESSION_TIMEOUT_MS).toString());
76
+ return existingSession;
77
+ }
78
+ }
79
+ const newSession = `sess_${generateUUID().replace(/-/g, "")}`;
80
+ sessionStorage.setItem(SESSION_KEY, newSession);
81
+ sessionStorage.setItem(SESSION_EXPIRY_KEY, (now + SESSION_TIMEOUT_MS).toString());
82
+ return newSession;
83
+ } catch {
84
+ return `sess_${generateUUID().replace(/-/g, "")}`;
85
+ }
86
+ }
87
+
88
+ // src/renderer.ts
89
+ function renderHeadingBlock(block, design, isBanner = false) {
90
+ const tag = `h${block.level || 2}`;
91
+ const el = document.createElement(tag);
92
+ el.textContent = block.text;
93
+ el.style.margin = isBanner ? "0" : "0 0 10px 0";
94
+ el.style.fontSize = isBanner ? "13px" : block.level === 1 ? "22px" : block.level === 2 ? "18px" : "15px";
95
+ el.style.fontWeight = "700";
96
+ el.style.lineHeight = isBanner ? "1.4" : "1.25";
97
+ el.style.display = isBanner ? "inline-block" : "block";
98
+ el.style.color = design.textColor || (design.theme === "light" ? "#14151A" : "#FFFFFF");
99
+ el.style.fontFamily = "system-ui, -apple-system, sans-serif";
100
+ return el;
101
+ }
102
+ function renderParagraphBlock(block, design, isBanner = false) {
103
+ const el = document.createElement("p");
104
+ el.textContent = block.text;
105
+ el.style.margin = isBanner ? "0" : "0 0 12px 0";
106
+ el.style.fontSize = isBanner ? "13px" : "13px";
107
+ el.style.lineHeight = isBanner ? "1.4" : "1.5";
108
+ el.style.fontWeight = isBanner ? "500" : "normal";
109
+ el.style.display = isBanner ? "inline-block" : "block";
110
+ el.style.color = design.textColor ? `${design.textColor}CC` : design.theme === "light" ? "#4A4D57" : "#A0A5B5";
111
+ el.style.fontFamily = "system-ui, -apple-system, sans-serif";
112
+ return el;
113
+ }
114
+ function renderImageBlock(block, _design) {
115
+ const el = document.createElement("img");
116
+ el.setAttribute("src", block.src);
117
+ if (block.alt) {
118
+ el.setAttribute("alt", block.alt);
119
+ }
120
+ el.style.maxWidth = "100%";
121
+ el.style.height = "auto";
122
+ el.style.borderRadius = "8px";
123
+ el.style.margin = "0 0 12px 0";
124
+ el.style.display = "block";
125
+ el.style.objectFit = "cover";
126
+ if (block.width) el.style.width = `${block.width}px`;
127
+ if (block.height) el.style.height = `${block.height}px`;
128
+ return el;
129
+ }
130
+ function renderButtonBlock(block, design, handlers, isBanner = false) {
131
+ const btn = document.createElement("button");
132
+ btn.setAttribute("type", block.action === "submit" ? "submit" : "button");
133
+ btn.textContent = block.text;
134
+ btn.style.width = isBanner ? "auto" : "100%";
135
+ btn.style.padding = isBanner ? "5px 14px" : "10px 16px";
136
+ btn.style.fontSize = isBanner ? "12px" : "13px";
137
+ btn.style.fontWeight = "600";
138
+ btn.style.borderRadius = isBanner ? "6px" : "8px";
139
+ btn.style.cursor = "pointer";
140
+ btn.style.border = "none";
141
+ btn.style.margin = isBanner ? "0" : "4px 0 8px 0";
142
+ btn.style.display = isBanner ? "inline-flex" : "block";
143
+ btn.style.alignItems = "center";
144
+ btn.style.justifyContent = "center";
145
+ btn.style.whiteSpace = "nowrap";
146
+ btn.style.fontFamily = "system-ui, -apple-system, sans-serif";
147
+ btn.style.transition = "all 0.15s ease-in-out";
148
+ const accent = design.accentColor || "#1DBF73";
149
+ const btnTextColor = design.buttonTextColor || "#FFFFFF";
150
+ if (block.styleVariant === "outline") {
151
+ btn.style.backgroundColor = "transparent";
152
+ btn.style.border = `1px solid ${accent}`;
153
+ btn.style.color = accent;
154
+ } else if (block.styleVariant === "secondary" || block.styleVariant === "ghost") {
155
+ btn.style.backgroundColor = design.theme === "light" ? "#F0F2F5" : "#1F2430";
156
+ btn.style.color = design.theme === "light" ? "#14151A" : "#FFFFFF";
157
+ } else {
158
+ btn.style.backgroundColor = accent;
159
+ btn.style.color = btnTextColor;
160
+ }
161
+ btn.addEventListener("click", (e) => {
162
+ if (block.action === "link" && block.url) {
163
+ handlers.onLinkClick?.(block.url, block.eventName);
164
+ window.open(block.url, "_blank", "noopener,noreferrer");
165
+ } else if (block.action === "close") {
166
+ handlers.onClose?.();
167
+ } else if (block.action === "event" && block.eventName) {
168
+ handlers.onEventClick?.(block.eventName);
169
+ }
170
+ });
171
+ return btn;
172
+ }
173
+ function renderInputBlock(block, design, isOnlyInput = false) {
174
+ const container = document.createElement("div");
175
+ container.style.margin = "0 0 10px 0";
176
+ container.style.width = "100%";
177
+ const isRequired = isOnlyInput || block.required === true;
178
+ if (block.label) {
179
+ const label = document.createElement("label");
180
+ label.innerHTML = `${block.label}${!isRequired ? ' <span style="opacity:0.55;font-size:10px;font-weight:normal;">(optional)</span>' : ""}`;
181
+ label.style.display = "block";
182
+ label.style.fontSize = "11px";
183
+ label.style.fontWeight = "600";
184
+ label.style.marginBottom = "4px";
185
+ label.style.color = design.textColor || (design.theme === "light" ? "#14151A" : "#E2E8F0");
186
+ container.appendChild(label);
187
+ }
188
+ const input = document.createElement("input");
189
+ input.setAttribute("type", block.inputType || "text");
190
+ input.setAttribute("name", block.name);
191
+ if (block.placeholder) {
192
+ input.setAttribute("placeholder", block.placeholder);
193
+ }
194
+ if (isRequired) {
195
+ input.setAttribute("required", "true");
196
+ }
197
+ input.style.width = "100%";
198
+ input.style.boxSizing = "border-box";
199
+ input.style.padding = "9px 12px";
200
+ input.style.fontSize = "13px";
201
+ input.style.borderRadius = "8px";
202
+ input.style.border = design.theme === "light" ? "1px solid #D1D5DB" : "1px solid #2D3748";
203
+ input.style.backgroundColor = design.theme === "light" ? "#FFFFFF" : "#0D1117";
204
+ input.style.color = design.theme === "light" ? "#14151A" : "#FFFFFF";
205
+ input.style.outline = "none";
206
+ input.style.fontFamily = "system-ui, -apple-system, sans-serif";
207
+ container.appendChild(input);
208
+ return container;
209
+ }
210
+ function isLightColor(hex) {
211
+ if (!hex || !hex.startsWith("#")) return true;
212
+ const cleanHex = hex.replace("#", "");
213
+ if (cleanHex.length < 6) return true;
214
+ const r = parseInt(cleanHex.substring(0, 2), 16) || 0;
215
+ const g = parseInt(cleanHex.substring(2, 4), 16) || 0;
216
+ const b = parseInt(cleanHex.substring(4, 6), 16) || 0;
217
+ const yiq = (r * 299 + g * 587 + b * 114) / 1e3;
218
+ return yiq >= 128;
219
+ }
220
+ function renderCountdownBlock(block, design) {
221
+ const text = design.textColor || (design.theme === "light" ? "#14151A" : "#FFFFFF");
222
+ const isLight = design.backgroundColor ? isLightColor(design.backgroundColor) : design.theme === "light";
223
+ const container = document.createElement("div");
224
+ container.style.display = "flex";
225
+ container.style.gap = "8px";
226
+ container.style.justifyContent = "center";
227
+ container.style.margin = "8px 0 14px 0";
228
+ const target = new Date(block.targetDate).getTime();
229
+ function createTimeBox(label) {
230
+ const box = document.createElement("div");
231
+ box.style.display = "flex";
232
+ box.style.flexDirection = "column";
233
+ box.style.alignItems = "center";
234
+ box.style.minWidth = "40px";
235
+ box.style.padding = "6px 8px";
236
+ box.style.borderRadius = "6px";
237
+ box.style.backgroundColor = isLight ? "rgba(0,0,0,0.04)" : "rgba(255,255,255,0.08)";
238
+ box.style.border = isLight ? "1px solid rgba(0,0,0,0.08)" : "1px solid rgba(255,255,255,0.15)";
239
+ const numSpan = document.createElement("span");
240
+ numSpan.style.fontSize = "16px";
241
+ numSpan.style.fontWeight = "800";
242
+ numSpan.style.fontFamily = "monospace, system-ui";
243
+ numSpan.style.color = design.accentColor || "#1DBF73";
244
+ numSpan.textContent = "00";
245
+ const labelSpan = document.createElement("span");
246
+ labelSpan.style.fontSize = "9px";
247
+ labelSpan.style.textTransform = "uppercase";
248
+ labelSpan.style.color = text;
249
+ labelSpan.style.opacity = "0.6";
250
+ labelSpan.textContent = label;
251
+ box.appendChild(numSpan);
252
+ box.appendChild(labelSpan);
253
+ return { box, numSpan };
254
+ }
255
+ const days = createTimeBox("Days");
256
+ const hours = createTimeBox("Hrs");
257
+ const mins = createTimeBox("Min");
258
+ const secs = createTimeBox("Sec");
259
+ container.appendChild(days.box);
260
+ container.appendChild(hours.box);
261
+ container.appendChild(mins.box);
262
+ container.appendChild(secs.box);
263
+ function update() {
264
+ const now = Date.now();
265
+ const diff = Math.max(0, target - now);
266
+ const d = Math.floor(diff / (1e3 * 60 * 60 * 24));
267
+ const h = Math.floor(diff % (1e3 * 60 * 60 * 24) / (1e3 * 60 * 60));
268
+ const m = Math.floor(diff % (1e3 * 60 * 60) / (1e3 * 60));
269
+ const s = Math.floor(diff % (1e3 * 60) / 1e3);
270
+ days.numSpan.textContent = String(d).padStart(2, "0");
271
+ hours.numSpan.textContent = String(h).padStart(2, "0");
272
+ mins.numSpan.textContent = String(m).padStart(2, "0");
273
+ secs.numSpan.textContent = String(s).padStart(2, "0");
274
+ if (diff === 0 && block.endMessage) {
275
+ container.textContent = block.endMessage;
276
+ }
277
+ }
278
+ update();
279
+ const timer = setInterval(update, 1e3);
280
+ if (block.label) {
281
+ const root = document.createElement("div");
282
+ root.style.width = "100%";
283
+ root.style.margin = "8px 0 14px 0";
284
+ const labelEl = document.createElement("div");
285
+ labelEl.style.fontSize = "12px";
286
+ labelEl.style.fontWeight = "600";
287
+ labelEl.style.color = text;
288
+ labelEl.style.opacity = "0.85";
289
+ labelEl.style.marginBottom = "6px";
290
+ labelEl.textContent = block.label;
291
+ container.style.margin = "0";
292
+ root.appendChild(labelEl);
293
+ root.appendChild(container);
294
+ root.dataset.timerId = String(timer);
295
+ return root;
296
+ }
297
+ container.dataset.timerId = String(timer);
298
+ return container;
299
+ }
300
+ function renderRatingBlock(block, design, isOnlyInput = false) {
301
+ const container = document.createElement("div");
302
+ container.style.margin = "8px 0 14px 0";
303
+ container.style.width = "100%";
304
+ const isRequired = isOnlyInput || block.required !== false;
305
+ if (block.label) {
306
+ const label = document.createElement("label");
307
+ label.innerHTML = `${block.label}${!isRequired ? ' <span style="opacity:0.55;font-size:10px;font-weight:normal;">(optional)</span>' : ""}`;
308
+ label.style.display = "block";
309
+ label.style.fontSize = "12px";
310
+ label.style.fontWeight = "600";
311
+ label.style.marginBottom = "6px";
312
+ label.style.color = design.textColor || (design.theme === "light" ? "#14151A" : "#E2E8F0");
313
+ container.appendChild(label);
314
+ }
315
+ const hiddenInput = document.createElement("input");
316
+ hiddenInput.setAttribute("type", "hidden");
317
+ hiddenInput.setAttribute("name", block.name || "rating");
318
+ if (isRequired) {
319
+ hiddenInput.setAttribute("required", "true");
320
+ }
321
+ container.appendChild(hiddenInput);
322
+ const variant = block.styleVariant || "stars";
323
+ const accent = design.accentColor || "#1DBF73";
324
+ const isLight = design.theme === "light";
325
+ if (variant === "stars") {
326
+ const starRow = document.createElement("div");
327
+ starRow.style.display = "flex";
328
+ starRow.style.gap = "6px";
329
+ starRow.style.justifyContent = "center";
330
+ starRow.style.padding = "4px 0";
331
+ const stars = [];
332
+ let selectedRating = 0;
333
+ for (let i = 1; i <= 5; i++) {
334
+ const starBtn = document.createElement("button");
335
+ starBtn.setAttribute("type", "button");
336
+ starBtn.setAttribute("aria-label", `${i} Star${i > 1 ? "s" : ""}`);
337
+ starBtn.textContent = "\u2605";
338
+ starBtn.style.fontSize = "28px";
339
+ starBtn.style.lineHeight = "1";
340
+ starBtn.style.border = "none";
341
+ starBtn.style.backgroundColor = "transparent";
342
+ starBtn.style.color = isLight ? "#CBD5E1" : "#374151";
343
+ starBtn.style.cursor = "pointer";
344
+ starBtn.style.padding = "2px";
345
+ starBtn.style.transition = "transform 0.15s ease, color 0.15s ease";
346
+ const updateStarColors = (val) => {
347
+ stars.forEach((btn, idx) => {
348
+ if (idx < val) {
349
+ btn.style.color = "#F59E0B";
350
+ btn.style.transform = "scale(1.15)";
351
+ } else {
352
+ btn.style.color = isLight ? "#CBD5E1" : "#374151";
353
+ btn.style.transform = "scale(1.0)";
354
+ }
355
+ });
356
+ };
357
+ starBtn.addEventListener("mouseenter", () => updateStarColors(i));
358
+ starBtn.addEventListener("mouseleave", () => updateStarColors(selectedRating));
359
+ starBtn.addEventListener("click", () => {
360
+ selectedRating = i;
361
+ hiddenInput.value = String(i);
362
+ updateStarColors(i);
363
+ });
364
+ stars.push(starBtn);
365
+ starRow.appendChild(starBtn);
366
+ }
367
+ container.appendChild(starRow);
368
+ } else if (variant === "moods") {
369
+ const moodRow = document.createElement("div");
370
+ moodRow.style.display = "flex";
371
+ moodRow.style.gap = "8px";
372
+ moodRow.style.justifyContent = "space-between";
373
+ moodRow.style.padding = "4px 0";
374
+ const moods = [
375
+ { score: 1, emoji: "\u{1F621}", label: "Terrible" },
376
+ { score: 2, emoji: "\u{1F641}", label: "Bad" },
377
+ { score: 3, emoji: "\u{1F610}", label: "Okay" },
378
+ { score: 4, emoji: "\u{1F60A}", label: "Good" },
379
+ { score: 5, emoji: "\u{1F60D}", label: "Loved it" }
380
+ ];
381
+ const buttons = [];
382
+ moods.forEach((m) => {
383
+ const moodBtn = document.createElement("button");
384
+ moodBtn.setAttribute("type", "button");
385
+ moodBtn.setAttribute("title", m.label);
386
+ moodBtn.textContent = m.emoji;
387
+ moodBtn.style.fontSize = "24px";
388
+ moodBtn.style.padding = "8px";
389
+ moodBtn.style.borderRadius = "12px";
390
+ moodBtn.style.border = isLight ? "1px solid #E2E8F0" : "1px solid #2D3748";
391
+ moodBtn.style.backgroundColor = isLight ? "#F8FAFC" : "#1A202C";
392
+ moodBtn.style.cursor = "pointer";
393
+ moodBtn.style.flex = "1";
394
+ moodBtn.style.display = "flex";
395
+ moodBtn.style.alignItems = "center";
396
+ moodBtn.style.justifyContent = "center";
397
+ moodBtn.style.transition = "all 0.15s ease";
398
+ moodBtn.addEventListener("click", () => {
399
+ hiddenInput.value = String(m.score);
400
+ buttons.forEach((b) => {
401
+ b.style.backgroundColor = isLight ? "#F8FAFC" : "#1A202C";
402
+ b.style.borderColor = isLight ? "#E2E8F0" : "#2D3748";
403
+ b.style.transform = "scale(1.0)";
404
+ });
405
+ moodBtn.style.backgroundColor = `${accent}20`;
406
+ moodBtn.style.borderColor = accent;
407
+ moodBtn.style.transform = "scale(1.15)";
408
+ });
409
+ buttons.push(moodBtn);
410
+ moodRow.appendChild(moodBtn);
411
+ });
412
+ container.appendChild(moodRow);
413
+ } else {
414
+ const npsRow = document.createElement("div");
415
+ npsRow.style.display = "grid";
416
+ npsRow.style.gridTemplateColumns = "repeat(10, minmax(0, 1fr))";
417
+ npsRow.style.gap = "4px";
418
+ npsRow.style.padding = "4px 0";
419
+ const buttons = [];
420
+ for (let i = 1; i <= 10; i++) {
421
+ const npsBtn = document.createElement("button");
422
+ npsBtn.setAttribute("type", "button");
423
+ npsBtn.textContent = String(i);
424
+ npsBtn.style.padding = "6px 0";
425
+ npsBtn.style.fontSize = "11px";
426
+ npsBtn.style.fontWeight = "700";
427
+ npsBtn.style.borderRadius = "6px";
428
+ npsBtn.style.border = isLight ? "1px solid #E2E8F0" : "1px solid #2D3748";
429
+ npsBtn.style.backgroundColor = isLight ? "#F8FAFC" : "#1A202C";
430
+ npsBtn.style.color = isLight ? "#14151A" : "#FFFFFF";
431
+ npsBtn.style.cursor = "pointer";
432
+ npsBtn.style.transition = "all 0.15s ease";
433
+ npsBtn.addEventListener("click", () => {
434
+ hiddenInput.value = String(i);
435
+ buttons.forEach((b) => {
436
+ b.style.backgroundColor = isLight ? "#F8FAFC" : "#1A202C";
437
+ b.style.borderColor = isLight ? "#E2E8F0" : "#2D3748";
438
+ b.style.color = isLight ? "#14151A" : "#FFFFFF";
439
+ });
440
+ npsBtn.style.backgroundColor = accent;
441
+ npsBtn.style.borderColor = accent;
442
+ npsBtn.style.color = "#FFFFFF";
443
+ });
444
+ buttons.push(npsBtn);
445
+ npsRow.appendChild(npsBtn);
446
+ }
447
+ container.appendChild(npsRow);
448
+ }
449
+ return container;
450
+ }
451
+ function renderSocialProofBlock(block, design, siteId, hasCloseButton) {
452
+ const mode = block.mode || (block.metric && !block.events ? "live_visitors" : "recent_activity");
453
+ const accent = design.accentColor || "#1DBF73";
454
+ const text = design.textColor || (design.theme === "light" ? "#14151A" : "#FFFFFF");
455
+ const isLight = design.backgroundColor ? isLightColor(design.backgroundColor) : design.theme === "light";
456
+ function getSPGlass() {
457
+ const alpha = (design.socialProofOpacity !== void 0 ? design.socialProofOpacity : 85) / 100;
458
+ if (design.backgroundColor) {
459
+ const hex = design.backgroundColor.replace("#", "");
460
+ const r = parseInt(hex.substring(0, 2), 16) || 0;
461
+ const g = parseInt(hex.substring(2, 4), 16) || 0;
462
+ const b = parseInt(hex.substring(4, 6), 16) || 0;
463
+ const lightBg = isLightColor(design.backgroundColor);
464
+ return {
465
+ bg: `rgba(${r},${g},${b},${alpha})`,
466
+ border: lightBg ? "1px solid rgba(0,0,0,0.1)" : "1px solid rgba(255,255,255,0.15)",
467
+ shadow: lightBg ? "0 8px 32px rgba(0,0,0,0.12), 0 2px 8px rgba(0,0,0,0.06)" : "0 8px 32px rgba(0,0,0,0.5), 0 2px 8px rgba(0,0,0,0.3)",
468
+ backdrop: null
469
+ };
470
+ }
471
+ if (design.theme === "light") {
472
+ return {
473
+ bg: `rgba(255,255,255,${alpha})`,
474
+ border: "1px solid rgba(255,255,255,0.75)",
475
+ shadow: "0 8px 32px rgba(0,0,0,0.1), 0 2px 8px rgba(0,0,0,0.04)",
476
+ backdrop: "blur(24px) saturate(200%)"
477
+ };
478
+ }
479
+ return {
480
+ bg: `rgba(12,14,22,${alpha})`,
481
+ border: "1px solid rgba(255,255,255,0.13)",
482
+ shadow: "0 8px 32px rgba(0,0,0,0.6), 0 2px 8px rgba(0,0,0,0.35)",
483
+ backdrop: "blur(24px) saturate(180%)"
484
+ };
485
+ }
486
+ const glass = getSPGlass();
487
+ if (mode === "live_visitors") {
488
+ const container2 = document.createElement("div");
489
+ container2.style.display = "flex";
490
+ container2.style.alignItems = "center";
491
+ container2.style.gap = "10px";
492
+ container2.style.padding = "10px 14px";
493
+ container2.style.borderRadius = "14px";
494
+ container2.style.fontSize = "12px";
495
+ container2.style.fontWeight = "600";
496
+ container2.style.backgroundColor = glass.bg;
497
+ container2.style.border = glass.border;
498
+ container2.style.boxShadow = glass.shadow;
499
+ if (glass.backdrop) {
500
+ container2.style.backdropFilter = glass.backdrop;
501
+ container2.style.webkitBackdropFilter = glass.backdrop;
502
+ }
503
+ container2.style.color = text;
504
+ const flameBox = document.createElement("div");
505
+ flameBox.style.fontSize = "18px";
506
+ flameBox.style.lineHeight = "1";
507
+ flameBox.textContent = "\u{1F525}";
508
+ const textWrap = document.createElement("div");
509
+ textWrap.style.flex = "1";
510
+ textWrap.style.minWidth = "0";
511
+ const titleSpan = document.createElement("div");
512
+ titleSpan.style.fontWeight = "700";
513
+ titleSpan.style.color = text;
514
+ const template = block.formatString || "\u{1F525} {count} people are viewing this right now";
515
+ titleSpan.textContent = template.replace("{count}", "...");
516
+ const subSpan = document.createElement("div");
517
+ subSpan.style.fontSize = "10px";
518
+ subSpan.style.color = text;
519
+ subSpan.style.opacity = "0.75";
520
+ subSpan.style.marginTop = "2px";
521
+ subSpan.textContent = block.liveSubtitle || "High demand on this page";
522
+ if (block.showVerifiedBadge !== false) {
523
+ subSpan.textContent += " \u2022 \u26A1 Verified Telemetry";
524
+ }
525
+ textWrap.appendChild(titleSpan);
526
+ textWrap.appendChild(subSpan);
527
+ container2.appendChild(flameBox);
528
+ container2.appendChild(textWrap);
529
+ if (siteId && typeof window !== "undefined" && typeof fetch !== "undefined") {
530
+ const windowMinutes = block.windowMinutes || 60;
531
+ const pageUrl = window.location.href;
532
+ const queryUrl = `/api/campaigns/social-proof?siteId=${encodeURIComponent(siteId)}&metric=${encodeURIComponent(block.metric || "live_visitors")}&windowMinutes=${windowMinutes}&pageUrl=${encodeURIComponent(pageUrl)}`;
533
+ fetch(queryUrl).then((res) => res.ok ? res.json() : null).then((data) => {
534
+ if (data && typeof data.count === "number") {
535
+ titleSpan.textContent = template.replace("{count}", String(data.count));
536
+ }
537
+ }).catch(() => {
538
+ });
539
+ }
540
+ return container2;
541
+ }
542
+ if (mode === "review") {
543
+ const container2 = document.createElement("div");
544
+ container2.style.padding = "12px 14px";
545
+ container2.style.borderRadius = "14px";
546
+ container2.style.fontSize = "12px";
547
+ container2.style.backgroundColor = glass.bg;
548
+ container2.style.border = glass.border;
549
+ container2.style.boxShadow = glass.shadow;
550
+ if (glass.backdrop) {
551
+ container2.style.backdropFilter = glass.backdrop;
552
+ container2.style.webkitBackdropFilter = glass.backdrop;
553
+ }
554
+ container2.style.color = text;
555
+ const starRow = document.createElement("div");
556
+ starRow.style.display = "flex";
557
+ starRow.style.alignItems = "center";
558
+ starRow.style.justifyContent = "space-between";
559
+ starRow.style.marginBottom = "6px";
560
+ const stars = document.createElement("div");
561
+ stars.style.color = "#F59E0B";
562
+ stars.style.fontSize = "13px";
563
+ stars.style.letterSpacing = "2px";
564
+ stars.textContent = "\u2605".repeat(block.rating || 5);
565
+ if (block.showVerifiedBadge !== false) {
566
+ const badge = document.createElement("span");
567
+ badge.style.fontSize = "10px";
568
+ badge.style.fontWeight = "600";
569
+ badge.style.color = accent;
570
+ badge.textContent = "\u2713 Verified Customer";
571
+ starRow.appendChild(badge);
572
+ }
573
+ starRow.prepend(stars);
574
+ const quote = document.createElement("p");
575
+ quote.style.margin = "0 0 6px 0";
576
+ quote.style.fontStyle = "italic";
577
+ quote.style.fontSize = "12px";
578
+ quote.style.lineHeight = "1.4";
579
+ quote.style.color = text;
580
+ quote.textContent = `"${block.reviewText || "SitePulse cut our incident response time down to under 1 minute."}"`;
581
+ const author = document.createElement("div");
582
+ author.style.fontSize = "10px";
583
+ author.style.color = text;
584
+ author.style.opacity = "0.8";
585
+ author.textContent = `${block.reviewerName || "Sarah Jenkins"} \u2022 ${block.reviewerTitle || "Verified User"}`;
586
+ container2.appendChild(starRow);
587
+ container2.appendChild(quote);
588
+ container2.appendChild(author);
589
+ return container2;
590
+ }
591
+ if (mode === "aggregate") {
592
+ const container2 = document.createElement("div");
593
+ container2.style.display = "flex";
594
+ container2.style.alignItems = "center";
595
+ container2.style.gap = "10px";
596
+ container2.style.padding = "10px 14px";
597
+ container2.style.borderRadius = "14px";
598
+ container2.style.fontSize = "12px";
599
+ container2.style.backgroundColor = glass.bg;
600
+ container2.style.border = glass.border;
601
+ container2.style.boxShadow = glass.shadow;
602
+ if (glass.backdrop) {
603
+ container2.style.backdropFilter = glass.backdrop;
604
+ container2.style.webkitBackdropFilter = glass.backdrop;
605
+ }
606
+ container2.style.color = text;
607
+ const icon = document.createElement("div");
608
+ icon.style.fontSize = "18px";
609
+ icon.textContent = "\u{1F4CA}";
610
+ const textWrap = document.createElement("div");
611
+ const title = document.createElement("div");
612
+ title.style.fontWeight = "700";
613
+ title.style.color = text;
614
+ title.textContent = block.aggregateLabel || "Over 1,200 organizations trust SitePulse";
615
+ const sub = document.createElement("div");
616
+ sub.style.fontSize = "10px";
617
+ sub.style.color = accent;
618
+ sub.style.fontWeight = "600";
619
+ sub.textContent = "\u26A1 Live Platform Data";
620
+ textWrap.appendChild(title);
621
+ textWrap.appendChild(sub);
622
+ container2.appendChild(icon);
623
+ container2.appendChild(textWrap);
624
+ return container2;
625
+ }
626
+ const container = document.createElement("div");
627
+ container.style.display = "flex";
628
+ container.style.alignItems = "flex-start";
629
+ container.style.gap = "10px";
630
+ container.style.padding = "10px 12px";
631
+ container.style.borderRadius = "14px";
632
+ container.style.fontSize = "12px";
633
+ container.style.backgroundColor = glass.bg;
634
+ container.style.border = glass.border;
635
+ container.style.boxShadow = glass.shadow;
636
+ if (glass.backdrop) {
637
+ container.style.backdropFilter = glass.backdrop;
638
+ container.style.webkitBackdropFilter = glass.backdrop;
639
+ }
640
+ container.style.color = text;
641
+ container.style.overflow = "hidden";
642
+ container.style.willChange = "transform, opacity";
643
+ container.style.cursor = "default";
644
+ container.style.position = "relative";
645
+ if (hasCloseButton) {
646
+ container.style.paddingRight = "30px";
647
+ }
648
+ const iconBox = document.createElement("div");
649
+ iconBox.style.width = "36px";
650
+ iconBox.style.height = "36px";
651
+ iconBox.style.borderRadius = "10px";
652
+ iconBox.style.display = "flex";
653
+ iconBox.style.alignItems = "center";
654
+ iconBox.style.justifyContent = "center";
655
+ iconBox.style.fontSize = "16px";
656
+ iconBox.style.backgroundColor = `${accent}18`;
657
+ iconBox.style.border = `1px solid ${accent}35`;
658
+ iconBox.style.color = accent;
659
+ iconBox.style.flexShrink = "0";
660
+ iconBox.style.marginTop = "2px";
661
+ const contentBox = document.createElement("div");
662
+ contentBox.style.flex = "1";
663
+ contentBox.style.minWidth = "0";
664
+ const titleEl = document.createElement("div");
665
+ titleEl.style.fontWeight = "700";
666
+ titleEl.style.fontSize = "12px";
667
+ titleEl.style.lineHeight = "1.3";
668
+ titleEl.style.whiteSpace = "nowrap";
669
+ titleEl.style.overflow = "hidden";
670
+ titleEl.style.textOverflow = "ellipsis";
671
+ titleEl.style.color = text;
672
+ const messageEl = document.createElement("div");
673
+ messageEl.style.fontSize = "11px";
674
+ messageEl.style.color = text;
675
+ messageEl.style.opacity = "0.85";
676
+ messageEl.style.marginTop = "1px";
677
+ messageEl.style.lineHeight = "1.3";
678
+ const footerEl = document.createElement("div");
679
+ footerEl.style.fontSize = "9.5px";
680
+ footerEl.style.color = text;
681
+ footerEl.style.opacity = "0.6";
682
+ footerEl.style.marginTop = "3px";
683
+ footerEl.style.display = "flex";
684
+ footerEl.style.alignItems = "center";
685
+ footerEl.style.gap = "4px";
686
+ contentBox.appendChild(titleEl);
687
+ contentBox.appendChild(messageEl);
688
+ contentBox.appendChild(footerEl);
689
+ container.appendChild(iconBox);
690
+ container.appendChild(contentBox);
691
+ const events = block.events && block.events.length > 0 ? block.events : [
692
+ {
693
+ title: "Someone from Lagos, Nigeria",
694
+ message: "Upgraded to Growth Plan (Annual)",
695
+ timeAgo: "2 minutes ago",
696
+ avatarType: "avatar"
697
+ }
698
+ ];
699
+ let currentIdx = 0;
700
+ function renderEvent(idx) {
701
+ const ev = events[idx % events.length];
702
+ titleEl.textContent = ev.title;
703
+ messageEl.textContent = ev.message;
704
+ let iconText = "\u{1F464}";
705
+ if (ev.avatarType === "map_pin") iconText = "\u{1F4CD}";
706
+ else if (ev.avatarType === "verified_check") iconText = "\u2713";
707
+ else if (ev.avatarType === "sparkles") iconText = "\u2728";
708
+ else if (ev.avatarType === "flame") iconText = "\u{1F525}";
709
+ else if (ev.avatarType === "cart") iconText = "\u{1F6D2}";
710
+ iconBox.textContent = iconText;
711
+ footerEl.textContent = ev.timeAgo;
712
+ if (block.showVerifiedBadge !== false) {
713
+ footerEl.textContent += " \u2022 \u26A1 Verified by SitePulse";
714
+ }
715
+ if (ev.linkUrl) {
716
+ container.style.cursor = "pointer";
717
+ container.onclick = () => window.open(ev.linkUrl, "_blank", "noopener,noreferrer");
718
+ } else {
719
+ container.style.cursor = "default";
720
+ container.onclick = null;
721
+ }
722
+ }
723
+ renderEvent(0);
724
+ const isRightSide = (design.position || "bottom-left").includes("right");
725
+ const exitTranslate = isRightSide ? "translateX(110%)" : "translateX(-110%)";
726
+ const enterTranslate = exitTranslate;
727
+ const SLIDE_OUT_MS = 270;
728
+ const SLIDE_IN_MS = 420;
729
+ if (hasCloseButton && events.length > 0) {
730
+ const snoozeBtn = document.createElement("button");
731
+ snoozeBtn.setAttribute("type", "button");
732
+ snoozeBtn.setAttribute("aria-label", "Snooze notification");
733
+ snoozeBtn.textContent = "\u2715";
734
+ snoozeBtn.style.position = "absolute";
735
+ snoozeBtn.style.top = "7px";
736
+ snoozeBtn.style.right = "7px";
737
+ snoozeBtn.style.width = "18px";
738
+ snoozeBtn.style.height = "18px";
739
+ snoozeBtn.style.borderRadius = "50%";
740
+ snoozeBtn.style.border = "none";
741
+ snoozeBtn.style.backgroundColor = isLight ? "rgba(0,0,0,0.07)" : "rgba(255,255,255,0.15)";
742
+ snoozeBtn.style.color = isLight ? "#4A4D57" : "#9CA3AF";
743
+ snoozeBtn.style.cursor = "pointer";
744
+ snoozeBtn.style.fontSize = "9px";
745
+ snoozeBtn.style.display = "flex";
746
+ snoozeBtn.style.alignItems = "center";
747
+ snoozeBtn.style.justifyContent = "center";
748
+ snoozeBtn.style.zIndex = "10";
749
+ snoozeBtn.style.flexShrink = "0";
750
+ container.appendChild(snoozeBtn);
751
+ snoozeBtn.addEventListener("click", (e) => {
752
+ e.stopPropagation();
753
+ clearTimeout(rotationTimer);
754
+ container.style.transition = `transform ${SLIDE_OUT_MS}ms ease-in, opacity ${SLIDE_OUT_MS - 60}ms ease-in`;
755
+ container.style.transform = exitTranslate;
756
+ container.style.opacity = "0";
757
+ setTimeout(() => {
758
+ currentIdx = (currentIdx + 1) % events.length;
759
+ renderEvent(currentIdx);
760
+ container.style.transition = "none";
761
+ container.style.transform = enterTranslate;
762
+ container.style.opacity = "0";
763
+ void container.offsetWidth;
764
+ setTimeout(() => {
765
+ container.style.transition = `transform ${SLIDE_IN_MS}ms cubic-bezier(0.34,1.56,0.64,1), opacity 250ms ease-out`;
766
+ container.style.transform = "translateX(0)";
767
+ container.style.opacity = "1";
768
+ scheduleNext();
769
+ }, (block.cycleIntervalSeconds || 4) * 1e3);
770
+ }, SLIDE_OUT_MS + 20);
771
+ });
772
+ }
773
+ let rotationTimer;
774
+ function scheduleNext() {
775
+ rotationTimer = setTimeout(doSlide, (block.displayDurationSeconds || 6) * 1e3);
776
+ }
777
+ function doSlide() {
778
+ container.style.transition = `transform ${SLIDE_OUT_MS}ms ease-in, opacity ${SLIDE_OUT_MS - 60}ms ease-in`;
779
+ container.style.transform = exitTranslate;
780
+ container.style.opacity = "0";
781
+ setTimeout(() => {
782
+ container.style.transition = "none";
783
+ container.style.transform = enterTranslate;
784
+ container.style.opacity = "0";
785
+ currentIdx = (currentIdx + 1) % events.length;
786
+ renderEvent(currentIdx);
787
+ void container.offsetWidth;
788
+ setTimeout(() => {
789
+ container.style.transition = `transform ${SLIDE_IN_MS}ms cubic-bezier(0.34,1.56,0.64,1), opacity 250ms ease-out`;
790
+ container.style.transform = "translateX(0)";
791
+ container.style.opacity = "1";
792
+ scheduleNext();
793
+ }, (block.cycleIntervalSeconds || 4) * 1e3);
794
+ }, SLIDE_OUT_MS + 20);
795
+ }
796
+ if (events.length > 1 && typeof window !== "undefined") {
797
+ scheduleNext();
798
+ if (typeof MutationObserver !== "undefined" && document.body) {
799
+ const observer = new MutationObserver(() => {
800
+ if (!document.body.contains(container)) {
801
+ clearTimeout(rotationTimer);
802
+ observer.disconnect();
803
+ }
804
+ });
805
+ observer.observe(document.body, { childList: true, subtree: true });
806
+ }
807
+ }
808
+ return container;
809
+ }
810
+ function renderDividerBlock(_block, design) {
811
+ const hr = document.createElement("hr");
812
+ hr.style.border = "none";
813
+ hr.style.borderTop = design.theme === "light" ? "1px solid #E5E7EB" : "1px solid #2D3748";
814
+ hr.style.margin = "12px 0";
815
+ return hr;
816
+ }
817
+ function ensureSDKStyles() {
818
+ try {
819
+ if (typeof document === "undefined" || typeof document.getElementById !== "function" || !document.head) return;
820
+ if (!document.getElementById("sitepulse-sdk-global-styles")) {
821
+ const style = document.createElement("style");
822
+ style.id = "sitepulse-sdk-global-styles";
823
+ style.textContent = `
824
+ @keyframes sitepulse-fade-in {
825
+ from { opacity: 0; }
826
+ to { opacity: 1; }
827
+ }
828
+ @keyframes sitepulse-slide-up {
829
+ from { opacity: 0; transform: translateY(24px) scale(0.97); }
830
+ to { opacity: 1; transform: translateY(0) scale(1); }
831
+ }
832
+ @keyframes sitepulse-slide-down {
833
+ from { opacity: 0; transform: translateY(-24px) scale(0.97); }
834
+ to { opacity: 1; transform: translateY(0) scale(1); }
835
+ }
836
+ @keyframes sitepulse-zoom-in {
837
+ from { opacity: 0; transform: scale(0.85); }
838
+ to { opacity: 1; transform: scale(1); }
839
+ }
840
+ @keyframes sitepulse-ticker-scroll {
841
+ 0% { transform: translateX(0); }
842
+ 100% { transform: translateX(-50%); }
843
+ }
844
+ `;
845
+ document.head.appendChild(style);
846
+ }
847
+ } catch {
848
+ }
849
+ }
850
+ function renderCampaignDOM(campaign, handlers = {}) {
851
+ ensureSDKStyles();
852
+ const design = campaign.design || {
853
+ theme: "dark",
854
+ position: "center",
855
+ borderRadius: 16,
856
+ padding: 24,
857
+ overlay: true,
858
+ closeButton: true
859
+ };
860
+ const isBanner = campaign.type === "banner" || campaign.type === "announcement" || campaign.type === "scrolling_banner";
861
+ const isScrollingTicker = campaign.type === "scrolling_banner";
862
+ const wrapper = document.createElement("div");
863
+ wrapper.id = `sitepulse-campaign-${campaign.id}`;
864
+ wrapper.style.position = "fixed";
865
+ wrapper.style.zIndex = "2147483647";
866
+ wrapper.style.fontFamily = "system-ui, -apple-system, sans-serif";
867
+ let overlayEl = null;
868
+ if (design.overlay && !isBanner && (campaign.type === "modal" || campaign.type === "popup" || design.position === "center")) {
869
+ overlayEl = document.createElement("div");
870
+ overlayEl.style.position = "fixed";
871
+ overlayEl.style.inset = "0";
872
+ overlayEl.style.backgroundColor = "rgba(0, 0, 0, 0.6)";
873
+ overlayEl.style.backdropFilter = "blur(2px)";
874
+ overlayEl.style.zIndex = "2147483646";
875
+ overlayEl.style.animation = "sitepulse-fade-in 0.2s ease-out";
876
+ document.body.appendChild(overlayEl);
877
+ }
878
+ if (isBanner) {
879
+ wrapper.style.left = "0";
880
+ wrapper.style.right = "0";
881
+ wrapper.style.width = "100%";
882
+ wrapper.style.maxWidth = "100vw";
883
+ wrapper.style.transform = "none";
884
+ if (design.position === "bottom") {
885
+ wrapper.style.bottom = "0";
886
+ wrapper.style.top = "auto";
887
+ } else {
888
+ wrapper.style.top = "0";
889
+ wrapper.style.bottom = "auto";
890
+ }
891
+ } else {
892
+ switch (design.position) {
893
+ case "top":
894
+ wrapper.style.top = "16px";
895
+ wrapper.style.left = "50%";
896
+ wrapper.style.transform = "translateX(-50%)";
897
+ wrapper.style.width = "min(92vw, 560px)";
898
+ break;
899
+ case "bottom":
900
+ wrapper.style.bottom = "16px";
901
+ wrapper.style.left = "50%";
902
+ wrapper.style.transform = "translateX(-50%)";
903
+ wrapper.style.width = "min(92vw, 560px)";
904
+ break;
905
+ case "top-left":
906
+ wrapper.style.top = "20px";
907
+ wrapper.style.left = "20px";
908
+ wrapper.style.width = "min(90vw, 360px)";
909
+ break;
910
+ case "top-right":
911
+ wrapper.style.top = "20px";
912
+ wrapper.style.right = "20px";
913
+ wrapper.style.width = "min(90vw, 360px)";
914
+ break;
915
+ case "bottom-left":
916
+ wrapper.style.bottom = "20px";
917
+ wrapper.style.left = "20px";
918
+ wrapper.style.width = "min(90vw, 360px)";
919
+ break;
920
+ case "bottom-right":
921
+ wrapper.style.bottom = "20px";
922
+ wrapper.style.right = "20px";
923
+ wrapper.style.width = "min(90vw, 360px)";
924
+ break;
925
+ case "center":
926
+ default:
927
+ wrapper.style.top = "50%";
928
+ wrapper.style.left = "50%";
929
+ wrapper.style.transform = "translate(-50%, -50%)";
930
+ wrapper.style.width = "min(90vw, 440px)";
931
+ break;
932
+ }
933
+ }
934
+ const card = document.createElement("div");
935
+ card.style.boxSizing = "border-box";
936
+ card.style.backgroundColor = design.backgroundColor || (design.theme === "light" ? "#FFFFFF" : "#0E1117");
937
+ card.style.color = design.textColor || (design.theme === "light" ? "#14151A" : "#FFFFFF");
938
+ card.style.borderRadius = isBanner ? "0px" : `${design.borderRadius ?? 16}px`;
939
+ card.style.padding = isBanner ? isScrollingTicker ? "8px 48px 8px 16px" : "10px 48px 10px 20px" : `${design.padding ?? 24}px`;
940
+ card.style.boxShadow = isBanner ? "0 2px 10px rgba(0, 0, 0, 0.15)" : "0 20px 25px -5px rgba(0, 0, 0, 0.3), 0 10px 10px -5px rgba(0, 0, 0, 0.2)";
941
+ card.style.border = isBanner ? design.theme === "light" ? "none" : "none" : design.theme === "light" ? "1px solid rgba(0,0,0,0.08)" : "1px solid rgba(255,255,255,0.1)";
942
+ card.style.position = "relative";
943
+ card.style.overflow = "hidden";
944
+ card.style.width = "100%";
945
+ if (isBanner) {
946
+ card.style.display = "flex";
947
+ card.style.alignItems = "center";
948
+ card.style.justifyContent = "center";
949
+ }
950
+ if (campaign.type === "social_proof") {
951
+ card.style.backgroundColor = "transparent";
952
+ card.style.boxShadow = "none";
953
+ card.style.border = "none";
954
+ card.style.padding = "0";
955
+ }
956
+ if (design.closeButton !== false && campaign.type !== "social_proof") {
957
+ const closeBtn = document.createElement("button");
958
+ closeBtn.setAttribute("type", "button");
959
+ closeBtn.setAttribute("aria-label", "Close Campaign");
960
+ closeBtn.textContent = "\u2715";
961
+ closeBtn.style.position = "absolute";
962
+ if (isBanner) {
963
+ closeBtn.style.top = "50%";
964
+ closeBtn.style.right = "14px";
965
+ closeBtn.style.transform = "translateY(-50%)";
966
+ } else {
967
+ closeBtn.style.top = "12px";
968
+ closeBtn.style.right = "12px";
969
+ }
970
+ closeBtn.style.width = "26px";
971
+ closeBtn.style.height = "26px";
972
+ closeBtn.style.borderRadius = "50%";
973
+ closeBtn.style.border = "none";
974
+ closeBtn.style.backgroundColor = design.theme === "light" ? "rgba(0,0,0,0.06)" : "rgba(255,255,255,0.12)";
975
+ closeBtn.style.color = design.theme === "light" ? "#4A4D57" : "#9CA3AF";
976
+ closeBtn.style.cursor = "pointer";
977
+ closeBtn.style.fontSize = "12px";
978
+ closeBtn.style.display = "flex";
979
+ closeBtn.style.alignItems = "center";
980
+ closeBtn.style.justifyContent = "center";
981
+ closeBtn.style.zIndex = "10";
982
+ closeBtn.addEventListener("click", () => {
983
+ destroy();
984
+ handlers.onClose?.();
985
+ });
986
+ card.appendChild(closeBtn);
987
+ }
988
+ const form = document.createElement("form");
989
+ form.style.margin = "0";
990
+ form.style.padding = "0";
991
+ if (isBanner) {
992
+ form.style.display = "flex";
993
+ form.style.alignItems = "center";
994
+ form.style.justifyContent = "center";
995
+ form.style.gap = "14px";
996
+ form.style.flexWrap = "wrap";
997
+ form.style.width = "100%";
998
+ }
999
+ form.addEventListener("submit", (e) => {
1000
+ e.preventDefault();
1001
+ const formData = new FormData(form);
1002
+ const entries = {};
1003
+ formData.forEach((val, key) => {
1004
+ entries[key] = String(val);
1005
+ });
1006
+ const inputElements = form.querySelectorAll("input, select, textarea");
1007
+ inputElements.forEach((inputEl) => {
1008
+ const name = inputEl.getAttribute("name") || inputEl.getAttribute("type") || "input";
1009
+ if (inputEl.value && !entries[name]) {
1010
+ entries[name] = inputEl.value;
1011
+ }
1012
+ });
1013
+ const emailVal = entries.email || entries.email_address || entries.newsletter_email || null;
1014
+ handlers.onSubmit?.(entries);
1015
+ if (typeof fetch !== "undefined") {
1016
+ fetch("/api/campaigns/submit", {
1017
+ method: "POST",
1018
+ headers: { "Content-Type": "application/json" },
1019
+ body: JSON.stringify({
1020
+ siteId: campaign.site_id,
1021
+ campaignId: campaign.id,
1022
+ email: emailVal,
1023
+ data: entries,
1024
+ page: {
1025
+ url: typeof window !== "undefined" ? window.location.href : void 0,
1026
+ title: typeof document !== "undefined" ? document.title : void 0
1027
+ }
1028
+ })
1029
+ }).catch(() => {
1030
+ });
1031
+ }
1032
+ form.style.display = "none";
1033
+ const accent = design.accentColor || "#1DBF73";
1034
+ const successBox = document.createElement("div");
1035
+ successBox.style.padding = isBanner ? "6px 12px" : "20px 10px";
1036
+ successBox.style.textAlign = "center";
1037
+ successBox.style.display = "flex";
1038
+ successBox.style.flexDirection = isBanner ? "row" : "column";
1039
+ successBox.style.alignItems = "center";
1040
+ successBox.style.justifyContent = "center";
1041
+ successBox.style.gap = isBanner ? "8px" : "0";
1042
+ const checkIcon = document.createElement("div");
1043
+ checkIcon.style.width = isBanner ? "24px" : "44px";
1044
+ checkIcon.style.height = isBanner ? "24px" : "44px";
1045
+ checkIcon.style.borderRadius = "50%";
1046
+ checkIcon.style.backgroundColor = `${accent}20`;
1047
+ checkIcon.style.color = accent;
1048
+ checkIcon.style.display = "flex";
1049
+ checkIcon.style.alignItems = "center";
1050
+ checkIcon.style.justifyContent = "center";
1051
+ checkIcon.style.fontSize = isBanner ? "13px" : "22px";
1052
+ checkIcon.style.fontWeight = "bold";
1053
+ checkIcon.style.marginBottom = isBanner ? "0" : "10px";
1054
+ checkIcon.textContent = "\u2713";
1055
+ const successTitle = document.createElement("span");
1056
+ successTitle.style.margin = "0";
1057
+ successTitle.style.fontSize = isBanner ? "13px" : "17px";
1058
+ successTitle.style.fontWeight = "700";
1059
+ successTitle.style.color = design.textColor || (design.theme === "light" ? "#14151A" : "#FFFFFF");
1060
+ successTitle.textContent = isBanner ? "Thank you! Submission received." : "Thank you!";
1061
+ successBox.appendChild(checkIcon);
1062
+ successBox.appendChild(successTitle);
1063
+ if (!isBanner) {
1064
+ const successMsg = document.createElement("p");
1065
+ successMsg.style.margin = "0";
1066
+ successMsg.style.fontSize = "13px";
1067
+ successMsg.style.lineHeight = "1.4";
1068
+ successMsg.style.color = design.textColor ? `${design.textColor}CC` : design.theme === "light" ? "#64748B" : "#94A3B8";
1069
+ successMsg.textContent = "Your submission has been received successfully.";
1070
+ successBox.appendChild(successMsg);
1071
+ }
1072
+ card.appendChild(successBox);
1073
+ setTimeout(() => {
1074
+ destroy();
1075
+ }, 2500);
1076
+ });
1077
+ const blocks = campaign.content?.blocks || [];
1078
+ const inputBlocksCount = blocks.filter((b) => b.type === "input" || b.type === "rating").length;
1079
+ const isOnlyInput = inputBlocksCount <= 1;
1080
+ if (isScrollingTicker) {
1081
+ let buildSegment2 = function() {
1082
+ const seg = document.createElement("div");
1083
+ seg.style.display = "inline-flex";
1084
+ seg.style.alignItems = "center";
1085
+ seg.style.gap = "14px";
1086
+ for (const block of blocks) {
1087
+ let node = null;
1088
+ switch (block.type) {
1089
+ case "heading":
1090
+ node = renderHeadingBlock(block, design, true);
1091
+ break;
1092
+ case "paragraph":
1093
+ node = renderParagraphBlock(block, design, true);
1094
+ break;
1095
+ case "button":
1096
+ node = renderButtonBlock(block, design, {
1097
+ ...handlers,
1098
+ onClose: () => {
1099
+ destroy();
1100
+ handlers.onClose?.();
1101
+ }
1102
+ }, true);
1103
+ break;
1104
+ case "image":
1105
+ node = renderImageBlock(block, design);
1106
+ break;
1107
+ case "divider":
1108
+ node = renderDividerBlock(block, design);
1109
+ break;
1110
+ }
1111
+ if (node) seg.appendChild(node);
1112
+ }
1113
+ return seg;
1114
+ };
1115
+ var buildSegment = buildSegment2;
1116
+ const tickerWrapper = document.createElement("div");
1117
+ tickerWrapper.style.overflow = "hidden";
1118
+ tickerWrapper.style.whiteSpace = "nowrap";
1119
+ tickerWrapper.style.width = "100%";
1120
+ tickerWrapper.style.display = "flex";
1121
+ tickerWrapper.style.alignItems = "center";
1122
+ tickerWrapper.style.position = "relative";
1123
+ tickerWrapper.style.maskImage = "linear-gradient(to right, transparent, black 20px, black calc(100% - 20px), transparent)";
1124
+ tickerWrapper.style.webkitMaskImage = "linear-gradient(to right, transparent, black 20px, black calc(100% - 20px), transparent)";
1125
+ const tickerTrack = document.createElement("div");
1126
+ tickerTrack.style.display = "inline-flex";
1127
+ tickerTrack.style.alignItems = "center";
1128
+ tickerTrack.style.gap = "48px";
1129
+ tickerTrack.style.animation = "sitepulse-ticker-scroll 24s linear infinite";
1130
+ tickerTrack.style.willChange = "transform";
1131
+ tickerWrapper.addEventListener("mouseenter", () => {
1132
+ tickerTrack.style.animationPlayState = "paused";
1133
+ });
1134
+ tickerWrapper.addEventListener("mouseleave", () => {
1135
+ tickerTrack.style.animationPlayState = "running";
1136
+ });
1137
+ for (let i = 0; i < 4; i++) {
1138
+ tickerTrack.appendChild(buildSegment2());
1139
+ }
1140
+ tickerWrapper.appendChild(tickerTrack);
1141
+ card.appendChild(tickerWrapper);
1142
+ } else {
1143
+ for (const block of blocks) {
1144
+ try {
1145
+ let blockNode = null;
1146
+ switch (block.type) {
1147
+ case "heading":
1148
+ blockNode = renderHeadingBlock(block, design, isBanner);
1149
+ break;
1150
+ case "paragraph":
1151
+ blockNode = renderParagraphBlock(block, design, isBanner);
1152
+ break;
1153
+ case "image":
1154
+ blockNode = renderImageBlock(block, design);
1155
+ break;
1156
+ case "button":
1157
+ blockNode = renderButtonBlock(block, design, {
1158
+ ...handlers,
1159
+ onClose: () => {
1160
+ destroy();
1161
+ handlers.onClose?.();
1162
+ }
1163
+ }, isBanner);
1164
+ break;
1165
+ case "input":
1166
+ blockNode = renderInputBlock(block, design, isOnlyInput);
1167
+ break;
1168
+ case "countdown":
1169
+ blockNode = renderCountdownBlock(block, design);
1170
+ break;
1171
+ case "social_proof":
1172
+ blockNode = renderSocialProofBlock(block, design, campaign.site_id, design.closeButton !== false);
1173
+ break;
1174
+ case "rating":
1175
+ blockNode = renderRatingBlock(block, design, isOnlyInput);
1176
+ break;
1177
+ case "divider":
1178
+ blockNode = renderDividerBlock(block, design);
1179
+ break;
1180
+ }
1181
+ if (blockNode) {
1182
+ form.appendChild(blockNode);
1183
+ }
1184
+ } catch {
1185
+ }
1186
+ }
1187
+ card.appendChild(form);
1188
+ }
1189
+ wrapper.appendChild(card);
1190
+ const animStyle = design.animationStyle || "fade";
1191
+ if (animStyle !== "none") {
1192
+ const animMap = {
1193
+ "fade": "sitepulse-fade-in 0.35s ease-out both",
1194
+ "slide-up": "sitepulse-slide-up 0.4s cubic-bezier(0.34,1.56,0.64,1) both",
1195
+ "slide-down": "sitepulse-slide-down 0.4s cubic-bezier(0.34,1.56,0.64,1) both",
1196
+ "zoom": "sitepulse-zoom-in 0.35s cubic-bezier(0.34,1.56,0.64,1) both"
1197
+ };
1198
+ card.style.animation = animMap[animStyle] ?? animMap["fade"];
1199
+ }
1200
+ document.body.appendChild(wrapper);
1201
+ let originalPaddingTop = null;
1202
+ let originalPaddingBottom = null;
1203
+ let resizeObserver = null;
1204
+ if (isBanner && typeof document !== "undefined" && document.body) {
1205
+ try {
1206
+ const isBottom = design.position === "bottom";
1207
+ if (isBottom) {
1208
+ originalPaddingBottom = document.body.style.paddingBottom;
1209
+ } else {
1210
+ originalPaddingTop = document.body.style.paddingTop;
1211
+ }
1212
+ let basePaddingTop = 0;
1213
+ let basePaddingBottom = 0;
1214
+ try {
1215
+ if (typeof window !== "undefined" && window.getComputedStyle) {
1216
+ const computed = window.getComputedStyle(document.body);
1217
+ if (isBottom) {
1218
+ basePaddingBottom = parseFloat(computed.paddingBottom) || 0;
1219
+ } else {
1220
+ basePaddingTop = parseFloat(computed.paddingTop) || 0;
1221
+ }
1222
+ }
1223
+ } catch {
1224
+ }
1225
+ const updateOffset = () => {
1226
+ if (!wrapper || !document.body) return;
1227
+ const bannerHeight = wrapper.offsetHeight;
1228
+ if (bannerHeight > 0) {
1229
+ if (isBottom) {
1230
+ document.body.style.paddingBottom = `${basePaddingBottom + bannerHeight}px`;
1231
+ } else {
1232
+ document.body.style.paddingTop = `${basePaddingTop + bannerHeight}px`;
1233
+ }
1234
+ }
1235
+ };
1236
+ if (typeof requestAnimationFrame !== "undefined") {
1237
+ requestAnimationFrame(updateOffset);
1238
+ } else {
1239
+ setTimeout(updateOffset, 10);
1240
+ }
1241
+ if (typeof ResizeObserver !== "undefined") {
1242
+ resizeObserver = new ResizeObserver(() => {
1243
+ updateOffset();
1244
+ });
1245
+ resizeObserver.observe(wrapper);
1246
+ }
1247
+ } catch {
1248
+ }
1249
+ }
1250
+ function destroy() {
1251
+ if (resizeObserver) {
1252
+ try {
1253
+ resizeObserver.disconnect();
1254
+ } catch {
1255
+ }
1256
+ }
1257
+ if (typeof document !== "undefined" && document.body) {
1258
+ try {
1259
+ if (originalPaddingTop !== null) {
1260
+ document.body.style.paddingTop = originalPaddingTop;
1261
+ }
1262
+ if (originalPaddingBottom !== null) {
1263
+ document.body.style.paddingBottom = originalPaddingBottom;
1264
+ }
1265
+ } catch {
1266
+ }
1267
+ }
1268
+ if (overlayEl && overlayEl.parentNode) {
1269
+ overlayEl.parentNode.removeChild(overlayEl);
1270
+ }
1271
+ if (wrapper && wrapper.parentNode) {
1272
+ wrapper.parentNode.removeChild(wrapper);
1273
+ }
1274
+ }
1275
+ return { element: wrapper, destroy };
1276
+ }
1277
+
1278
+ // src/evaluator.ts
1279
+ function evaluateAudience(audience, state) {
1280
+ if (!audience || audience.type === "all") {
1281
+ return true;
1282
+ }
1283
+ try {
1284
+ switch (audience.type) {
1285
+ case "new_visitors":
1286
+ return state.isNewVisitor;
1287
+ case "returning_visitors":
1288
+ return !state.isNewVisitor;
1289
+ case "device":
1290
+ if (!audience.deviceTypes || audience.deviceTypes.length === 0) return true;
1291
+ return audience.deviceTypes.includes(state.deviceType);
1292
+ case "specific_pages": {
1293
+ if (!audience.pagePaths || audience.pagePaths.length === 0) return true;
1294
+ const currentPath = state.pathname.toLowerCase();
1295
+ return audience.pagePaths.some((pattern) => {
1296
+ const cleanPattern = pattern.toLowerCase().trim();
1297
+ if (cleanPattern.endsWith("*")) {
1298
+ return currentPath.startsWith(cleanPattern.slice(0, -1));
1299
+ }
1300
+ return currentPath === cleanPattern;
1301
+ });
1302
+ }
1303
+ default:
1304
+ return true;
1305
+ }
1306
+ } catch {
1307
+ return false;
1308
+ }
1309
+ }
1310
+ function attachTrigger(trigger, onTriggered) {
1311
+ if (!trigger) {
1312
+ onTriggered();
1313
+ return () => {
1314
+ };
1315
+ }
1316
+ try {
1317
+ let fired = false;
1318
+ const fireOnce = () => {
1319
+ if (fired) return;
1320
+ fired = true;
1321
+ try {
1322
+ onTriggered();
1323
+ } catch {
1324
+ }
1325
+ };
1326
+ switch (trigger.type) {
1327
+ case "delay": {
1328
+ const ms = Math.max(0, (trigger.seconds || 0) * 1e3);
1329
+ const timer = setTimeout(fireOnce, ms);
1330
+ return () => clearTimeout(timer);
1331
+ }
1332
+ case "scroll_percent": {
1333
+ if (typeof window === "undefined") return () => {
1334
+ };
1335
+ const targetPercent = Math.min(100, Math.max(1, trigger.scrollPercent || 50));
1336
+ const checkScroll = () => {
1337
+ try {
1338
+ const scrollTop = window.scrollY || document.documentElement.scrollTop;
1339
+ const docHeight = document.documentElement.scrollHeight - document.documentElement.clientHeight;
1340
+ if (docHeight <= 0) return;
1341
+ const percent = scrollTop / docHeight * 100;
1342
+ if (percent >= targetPercent) {
1343
+ window.removeEventListener("scroll", checkScroll);
1344
+ fireOnce();
1345
+ }
1346
+ } catch {
1347
+ }
1348
+ };
1349
+ window.addEventListener("scroll", checkScroll, { passive: true });
1350
+ return () => window.removeEventListener("scroll", checkScroll);
1351
+ }
1352
+ case "exit_intent": {
1353
+ if (typeof document === "undefined") return () => {
1354
+ };
1355
+ const handleMouseLeave = (e) => {
1356
+ try {
1357
+ if (e.clientY <= 0) {
1358
+ document.removeEventListener("mouseleave", handleMouseLeave);
1359
+ fireOnce();
1360
+ }
1361
+ } catch {
1362
+ }
1363
+ };
1364
+ document.addEventListener("mouseleave", handleMouseLeave);
1365
+ return () => document.removeEventListener("mouseleave", handleMouseLeave);
1366
+ }
1367
+ case "click": {
1368
+ if (!trigger.elementSelector || typeof document === "undefined") return () => {
1369
+ };
1370
+ const handleClick = (e) => {
1371
+ try {
1372
+ const target = e.target;
1373
+ if (target && target.closest(trigger.elementSelector)) {
1374
+ document.removeEventListener("click", handleClick);
1375
+ fireOnce();
1376
+ }
1377
+ } catch {
1378
+ }
1379
+ };
1380
+ document.addEventListener("click", handleClick, true);
1381
+ return () => document.removeEventListener("click", handleClick, true);
1382
+ }
1383
+ case "event": {
1384
+ if (typeof window === "undefined") return () => {
1385
+ };
1386
+ const handleCustomEvent = (e) => {
1387
+ try {
1388
+ const customEvent = e;
1389
+ if (!trigger.eventName || customEvent.detail?.eventName === trigger.eventName) {
1390
+ window.removeEventListener("sitepulse:event", handleCustomEvent);
1391
+ fireOnce();
1392
+ }
1393
+ } catch {
1394
+ }
1395
+ };
1396
+ window.addEventListener("sitepulse:event", handleCustomEvent);
1397
+ return () => window.removeEventListener("sitepulse:event", handleCustomEvent);
1398
+ }
1399
+ default:
1400
+ fireOnce();
1401
+ return () => {
1402
+ };
1403
+ }
1404
+ } catch {
1405
+ return () => {
1406
+ };
1407
+ }
1408
+ }
1409
+
1410
+ // src/index.ts
1411
+ var SitePulseClient = class {
1412
+ config = null;
1413
+ queue = [];
1414
+ flushTimer = null;
1415
+ initialized = false;
1416
+ lastUrl = "";
1417
+ registeredCampaignTeardowns = [];
1418
+ init(config) {
1419
+ const isReinit = this.initialized;
1420
+ this.config = {
1421
+ endpointUrl: "/api/collect",
1422
+ campaignsEndpointUrl: "/api/campaigns/active",
1423
+ flushIntervalMs: 5e3,
1424
+ autoTrack: true,
1425
+ autoFetchCampaigns: true,
1426
+ ...config
1427
+ };
1428
+ this.initialized = true;
1429
+ if (typeof window !== "undefined") {
1430
+ if (isReinit && this.registeredCampaignTeardowns.length > 0) {
1431
+ this.registeredCampaignTeardowns.forEach((fn) => {
1432
+ try {
1433
+ fn();
1434
+ } catch {
1435
+ }
1436
+ });
1437
+ this.registeredCampaignTeardowns = [];
1438
+ }
1439
+ if (!isReinit) {
1440
+ this.flushTimer = setInterval(() => {
1441
+ this.flush();
1442
+ }, this.config.flushIntervalMs);
1443
+ const handleFlushOnUnload = () => {
1444
+ this.flush(true);
1445
+ };
1446
+ if (document.addEventListener) {
1447
+ document.addEventListener("visibilitychange", () => {
1448
+ if (document.visibilityState === "hidden") {
1449
+ handleFlushOnUnload();
1450
+ }
1451
+ });
1452
+ window.addEventListener("pagehide", handleFlushOnUnload);
1453
+ }
1454
+ if (this.config.autoTrack) {
1455
+ this.autoCapturePageViews();
1456
+ }
1457
+ }
1458
+ if (this.config.autoFetchCampaigns) {
1459
+ this.fetchAndRegisterActiveCampaigns();
1460
+ }
1461
+ }
1462
+ }
1463
+ track(eventName, properties = {}) {
1464
+ if (!this.config || typeof window === "undefined") return;
1465
+ try {
1466
+ const event = {
1467
+ eventName,
1468
+ anonymousId: getOrCreateAnonymousId(),
1469
+ sessionId: getOrCreateSessionId(),
1470
+ properties,
1471
+ page: {
1472
+ url: window.location.href,
1473
+ referrer: document.referrer || void 0,
1474
+ title: document.title || void 0
1475
+ },
1476
+ deviceType: getDeviceType(),
1477
+ browser: getBrowser(),
1478
+ os: getOS(),
1479
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1480
+ };
1481
+ this.queue.push(event);
1482
+ try {
1483
+ const customEvent = new CustomEvent("sitepulse:event", {
1484
+ detail: { eventName, properties }
1485
+ });
1486
+ window.dispatchEvent(customEvent);
1487
+ } catch {
1488
+ }
1489
+ if (this.queue.length >= 25) {
1490
+ this.flush();
1491
+ }
1492
+ } catch {
1493
+ }
1494
+ }
1495
+ page(properties = {}) {
1496
+ this.track("page_view", properties);
1497
+ }
1498
+ flush(isUnloading = false) {
1499
+ if (!this.config || this.queue.length === 0 || typeof window === "undefined") return;
1500
+ const batch = this.queue.splice(0, 50);
1501
+ const payload = JSON.stringify({
1502
+ siteId: this.config.siteId,
1503
+ events: batch
1504
+ });
1505
+ const endpoint = this.config.endpointUrl || "/api/collect";
1506
+ try {
1507
+ if (isUnloading && typeof navigator !== "undefined" && navigator.sendBeacon) {
1508
+ const blob = new Blob([payload], { type: "application/json" });
1509
+ const queued = navigator.sendBeacon(endpoint, blob);
1510
+ if (queued) return;
1511
+ }
1512
+ fetch(endpoint, {
1513
+ method: "POST",
1514
+ headers: {
1515
+ "Content-Type": "application/json"
1516
+ },
1517
+ body: payload,
1518
+ keepalive: true
1519
+ }).catch(() => {
1520
+ });
1521
+ } catch {
1522
+ }
1523
+ }
1524
+ /**
1525
+ * Fetches active campaigns for this site from the endpoint, caches in sessionStorage,
1526
+ * and automatically registers each active campaign. (§8.3)
1527
+ */
1528
+ async fetchAndRegisterActiveCampaigns() {
1529
+ if (!this.config || typeof window === "undefined") return;
1530
+ const siteId = this.config.siteId;
1531
+ const cacheKey = `_sitepulse_campaigns_${siteId}`;
1532
+ const cacheTtlMs = 60 * 1e3;
1533
+ try {
1534
+ let campaigns = null;
1535
+ try {
1536
+ const cachedRaw = sessionStorage.getItem(cacheKey);
1537
+ if (cachedRaw) {
1538
+ const parsed = JSON.parse(cachedRaw);
1539
+ if (parsed.timestamp && Date.now() - parsed.timestamp < cacheTtlMs) {
1540
+ campaigns = parsed.campaigns;
1541
+ }
1542
+ }
1543
+ } catch {
1544
+ }
1545
+ if (!campaigns) {
1546
+ const endpoint = `${this.config.campaignsEndpointUrl || "/api/campaigns/active"}?siteId=${encodeURIComponent(siteId)}`;
1547
+ const res = await fetch(endpoint, { method: "GET" });
1548
+ if (res.ok) {
1549
+ const data = await res.json();
1550
+ campaigns = data.campaigns || [];
1551
+ try {
1552
+ sessionStorage.setItem(
1553
+ cacheKey,
1554
+ JSON.stringify({ timestamp: Date.now(), campaigns })
1555
+ );
1556
+ } catch {
1557
+ }
1558
+ }
1559
+ }
1560
+ if (Array.isArray(campaigns)) {
1561
+ for (const c of campaigns) {
1562
+ this.registerCampaign(c);
1563
+ }
1564
+ }
1565
+ } catch {
1566
+ }
1567
+ }
1568
+ /**
1569
+ * Directly renders a Campaign into the DOM using the fixed block renderer.
1570
+ * (§8.3 & §3.2: Zero-innerHTML guaranteed)
1571
+ */
1572
+ renderCampaign(campaign, handlers = {}) {
1573
+ if (typeof window === "undefined" || typeof document === "undefined") return null;
1574
+ try {
1575
+ this.track("campaign_impression", {
1576
+ campaignId: campaign.id,
1577
+ campaignType: campaign.type,
1578
+ campaignName: campaign.name
1579
+ });
1580
+ const dom = renderCampaignDOM(campaign, {
1581
+ onLinkClick: (url, eventName) => {
1582
+ this.track("campaign_click", {
1583
+ campaignId: campaign.id,
1584
+ url,
1585
+ eventName
1586
+ });
1587
+ handlers.onLinkClick?.(url, eventName);
1588
+ },
1589
+ onEventClick: (eventName) => {
1590
+ this.track("campaign_click", {
1591
+ campaignId: campaign.id,
1592
+ eventName
1593
+ });
1594
+ handlers.onEventClick?.(eventName);
1595
+ },
1596
+ onSubmit: (formData) => {
1597
+ this.track("campaign_submission", {
1598
+ campaignId: campaign.id,
1599
+ formData
1600
+ });
1601
+ handlers.onSubmit?.(formData);
1602
+ },
1603
+ onClose: () => {
1604
+ this.track("campaign_dismiss", {
1605
+ campaignId: campaign.id
1606
+ });
1607
+ handlers.onClose?.();
1608
+ }
1609
+ });
1610
+ return { destroy: dom.destroy };
1611
+ } catch {
1612
+ return null;
1613
+ }
1614
+ }
1615
+ /**
1616
+ * Registers a Campaign: Evaluates audience rules and trigger signals,
1617
+ * then mounts DOM when matched. Returns a teardown function.
1618
+ */
1619
+ registerCampaign(campaign) {
1620
+ if (typeof window === "undefined" || typeof document === "undefined") {
1621
+ return () => {
1622
+ };
1623
+ }
1624
+ try {
1625
+ const visitorState = {
1626
+ isNewVisitor: true,
1627
+ deviceType: getDeviceType() || "desktop",
1628
+ pathname: window.location.pathname,
1629
+ sessionCount: 1
1630
+ };
1631
+ const audienceMatches = evaluateAudience(campaign.audience, visitorState);
1632
+ if (!audienceMatches) {
1633
+ return () => {
1634
+ };
1635
+ }
1636
+ let activeCampaignInstance = null;
1637
+ const detachTrigger = attachTrigger(campaign.trigger, () => {
1638
+ activeCampaignInstance = this.renderCampaign(campaign);
1639
+ });
1640
+ const teardown = () => {
1641
+ detachTrigger();
1642
+ if (activeCampaignInstance) {
1643
+ activeCampaignInstance.destroy();
1644
+ }
1645
+ };
1646
+ this.registeredCampaignTeardowns.push(teardown);
1647
+ return teardown;
1648
+ } catch {
1649
+ return () => {
1650
+ };
1651
+ }
1652
+ }
1653
+ autoCapturePageViews() {
1654
+ if (typeof window === "undefined") return;
1655
+ this.lastUrl = window.location.href;
1656
+ this.page();
1657
+ if (typeof history !== "undefined") {
1658
+ const originalPushState = history.pushState;
1659
+ if (originalPushState) {
1660
+ history.pushState = (...args) => {
1661
+ originalPushState.apply(history, args);
1662
+ this.checkRouteChange();
1663
+ };
1664
+ }
1665
+ const originalReplaceState = history.replaceState;
1666
+ if (originalReplaceState) {
1667
+ history.replaceState = (...args) => {
1668
+ originalReplaceState.apply(history, args);
1669
+ this.checkRouteChange();
1670
+ };
1671
+ }
1672
+ }
1673
+ window.addEventListener("popstate", () => this.checkRouteChange());
1674
+ window.addEventListener("hashchange", () => this.checkRouteChange());
1675
+ }
1676
+ checkRouteChange() {
1677
+ setTimeout(() => {
1678
+ const currentUrl = window.location.href;
1679
+ if (currentUrl !== this.lastUrl) {
1680
+ this.lastUrl = currentUrl;
1681
+ this.page();
1682
+ }
1683
+ }, 50);
1684
+ }
1685
+ };
1686
+ var SitePulse = new SitePulseClient();
1687
+ if (typeof window !== "undefined") {
1688
+ window.SitePulse = SitePulse;
1689
+ try {
1690
+ const script = document.currentScript || document.querySelector("script[data-site-id]");
1691
+ if (script) {
1692
+ const siteId = script.getAttribute("data-site-id");
1693
+ const endpoint = script.getAttribute("data-endpoint");
1694
+ const campaignsEndpoint = script.getAttribute("data-campaigns-endpoint");
1695
+ if (siteId) {
1696
+ SitePulse.init({
1697
+ siteId,
1698
+ endpointUrl: endpoint || void 0,
1699
+ campaignsEndpointUrl: campaignsEndpoint || void 0
1700
+ });
1701
+ }
1702
+ }
1703
+ } catch {
1704
+ }
1705
+ }
1706
+ var index_default = SitePulse;
1707
+ export {
1708
+ SitePulse,
1709
+ attachTrigger,
1710
+ index_default as default,
1711
+ evaluateAudience,
1712
+ renderButtonBlock,
1713
+ renderCampaignDOM,
1714
+ renderCountdownBlock,
1715
+ renderDividerBlock,
1716
+ renderHeadingBlock,
1717
+ renderImageBlock,
1718
+ renderInputBlock,
1719
+ renderParagraphBlock,
1720
+ renderRatingBlock,
1721
+ renderSocialProofBlock
1722
+ };