@perspective-ai/sdk 1.0.0-alpha.2

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 (45) hide show
  1. package/README.md +333 -0
  2. package/dist/browser.cjs +1939 -0
  3. package/dist/browser.cjs.map +1 -0
  4. package/dist/browser.d.cts +213 -0
  5. package/dist/browser.d.ts +213 -0
  6. package/dist/browser.js +1900 -0
  7. package/dist/browser.js.map +1 -0
  8. package/dist/cdn/perspective.global.js +406 -0
  9. package/dist/cdn/perspective.global.js.map +1 -0
  10. package/dist/constants.cjs +142 -0
  11. package/dist/constants.cjs.map +1 -0
  12. package/dist/constants.d.cts +104 -0
  13. package/dist/constants.d.ts +104 -0
  14. package/dist/constants.js +127 -0
  15. package/dist/constants.js.map +1 -0
  16. package/dist/index.cjs +1596 -0
  17. package/dist/index.cjs.map +1 -0
  18. package/dist/index.d.cts +155 -0
  19. package/dist/index.d.ts +155 -0
  20. package/dist/index.js +1579 -0
  21. package/dist/index.js.map +1 -0
  22. package/package.json +83 -0
  23. package/src/browser.test.ts +388 -0
  24. package/src/browser.ts +509 -0
  25. package/src/config.test.ts +81 -0
  26. package/src/config.ts +95 -0
  27. package/src/constants.ts +214 -0
  28. package/src/float.test.ts +332 -0
  29. package/src/float.ts +231 -0
  30. package/src/fullpage.test.ts +224 -0
  31. package/src/fullpage.ts +126 -0
  32. package/src/iframe.test.ts +1037 -0
  33. package/src/iframe.ts +421 -0
  34. package/src/index.ts +61 -0
  35. package/src/loading.ts +90 -0
  36. package/src/popup.test.ts +344 -0
  37. package/src/popup.ts +157 -0
  38. package/src/slider.test.ts +277 -0
  39. package/src/slider.ts +158 -0
  40. package/src/styles.ts +395 -0
  41. package/src/types.ts +148 -0
  42. package/src/utils.test.ts +162 -0
  43. package/src/utils.ts +86 -0
  44. package/src/widget.test.ts +375 -0
  45. package/src/widget.ts +195 -0
@@ -0,0 +1,1939 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/browser.ts
21
+ var browser_exports = {};
22
+ __export(browser_exports, {
23
+ autoInit: () => autoInit,
24
+ configure: () => configure,
25
+ createChatBubble: () => createChatBubble,
26
+ createFloatBubble: () => createFloatBubble,
27
+ createFullpage: () => createFullpage,
28
+ createWidget: () => createWidget,
29
+ default: () => browser_default,
30
+ destroy: () => destroy,
31
+ destroyAll: () => destroyAll,
32
+ getConfig: () => getConfig,
33
+ init: () => init,
34
+ mount: () => mount,
35
+ openPopup: () => openPopup,
36
+ openSlider: () => openSlider
37
+ });
38
+ module.exports = __toCommonJS(browser_exports);
39
+
40
+ // src/constants.ts
41
+ var SDK_VERSION = "1.0.0";
42
+ var FEATURES = {
43
+ RESIZE: 1 << 0,
44
+ // 0b0001
45
+ THEME_SYNC: 1 << 1,
46
+ // 0b0010
47
+ ANON_ID: 1 << 2,
48
+ // 0b0100
49
+ SCROLLBAR_STYLES: 1 << 3
50
+ // 0b1000
51
+ };
52
+ var CURRENT_FEATURES = FEATURES.RESIZE | FEATURES.THEME_SYNC | FEATURES.ANON_ID | FEATURES.SCROLLBAR_STYLES;
53
+ var PARAM_KEYS = {
54
+ // User identification
55
+ email: "email",
56
+ name: "name",
57
+ // Navigation
58
+ returnUrl: "returnUrl",
59
+ // Interview behavior
60
+ voice: "voice",
61
+ scroll: "scroll",
62
+ hideProgress: "hideProgress",
63
+ hideGreeting: "hideGreeting",
64
+ hideBranding: "hideBranding",
65
+ // Interview mode & auth
66
+ mode: "mode",
67
+ invite: "invite",
68
+ // System (internal)
69
+ embed: "embed",
70
+ embedType: "embed_type",
71
+ theme: "theme"
72
+ };
73
+ var BRAND_KEYS = {
74
+ // Light mode
75
+ primary: "brand.primary",
76
+ secondary: "brand.secondary",
77
+ bg: "brand.bg",
78
+ text: "brand.text",
79
+ // Dark mode
80
+ darkPrimary: "brand.dark.primary",
81
+ darkSecondary: "brand.dark.secondary",
82
+ darkBg: "brand.dark.bg",
83
+ darkText: "brand.dark.text"
84
+ };
85
+ var UTM_PARAMS = [
86
+ "utm_source",
87
+ "utm_medium",
88
+ "utm_campaign",
89
+ "utm_term",
90
+ "utm_content"
91
+ ];
92
+ var RESERVED_PARAMS = /* @__PURE__ */ new Set([
93
+ PARAM_KEYS.embed,
94
+ PARAM_KEYS.embedType,
95
+ PARAM_KEYS.theme,
96
+ BRAND_KEYS.primary,
97
+ BRAND_KEYS.secondary,
98
+ BRAND_KEYS.bg,
99
+ BRAND_KEYS.text,
100
+ BRAND_KEYS.darkPrimary,
101
+ BRAND_KEYS.darkSecondary,
102
+ BRAND_KEYS.darkBg,
103
+ BRAND_KEYS.darkText,
104
+ ...UTM_PARAMS
105
+ ]);
106
+ var DATA_ATTRS = {
107
+ widget: "data-perspective-widget",
108
+ popup: "data-perspective-popup",
109
+ slider: "data-perspective-slider",
110
+ float: "data-perspective-float",
111
+ // Primary name
112
+ chat: "data-perspective-chat",
113
+ // Legacy alias
114
+ fullpage: "data-perspective-fullpage",
115
+ params: "data-perspective-params",
116
+ brand: "data-perspective-brand",
117
+ brandDark: "data-perspective-brand-dark",
118
+ theme: "data-perspective-theme",
119
+ noStyle: "data-perspective-no-style"
120
+ };
121
+ var MESSAGE_TYPES = {
122
+ // SDK -> Iframe (initialization)
123
+ init: "perspective:init",
124
+ // Iframe -> SDK
125
+ ready: "perspective:ready",
126
+ resize: "perspective:resize",
127
+ submit: "perspective:submit",
128
+ close: "perspective:close",
129
+ error: "perspective:error",
130
+ redirect: "perspective:redirect",
131
+ // SDK -> Iframe (internal)
132
+ anonId: "perspective:anon-id",
133
+ injectStyles: "perspective:inject-styles",
134
+ themeChange: "perspective:theme-change",
135
+ // Iframe -> SDK (internal)
136
+ requestScrollbarStyles: "perspective:request-scrollbar-styles"
137
+ };
138
+ var PARAM_VALUES = {
139
+ disabled: "0",
140
+ enabled: "1",
141
+ true: "true",
142
+ false: "false"
143
+ };
144
+ var THEME_VALUES = {
145
+ dark: "dark",
146
+ light: "light",
147
+ system: "system"
148
+ };
149
+ var STORAGE_KEYS = {
150
+ anonId: "perspective-anon-id"
151
+ };
152
+
153
+ // src/config.ts
154
+ var DEFAULT_HOST = "https://getperspective.ai";
155
+ var globalConfig = {};
156
+ function configure(config) {
157
+ globalConfig = { ...globalConfig, ...config };
158
+ }
159
+ function getConfig() {
160
+ return { ...globalConfig };
161
+ }
162
+ function hasDom() {
163
+ return typeof window !== "undefined" && typeof document !== "undefined";
164
+ }
165
+ function normalizeToOrigin(host) {
166
+ try {
167
+ return new URL(host).origin;
168
+ } catch {
169
+ return host;
170
+ }
171
+ }
172
+ function getHost(instanceHost) {
173
+ if (instanceHost) {
174
+ return normalizeToOrigin(instanceHost);
175
+ }
176
+ if (globalConfig.host) {
177
+ return normalizeToOrigin(globalConfig.host);
178
+ }
179
+ if (hasDom()) {
180
+ const scriptHost = getScriptHost();
181
+ if (scriptHost) {
182
+ return scriptHost;
183
+ }
184
+ }
185
+ return DEFAULT_HOST;
186
+ }
187
+ var capturedScriptHost = null;
188
+ function getScriptHost() {
189
+ if (capturedScriptHost !== null) {
190
+ return capturedScriptHost;
191
+ }
192
+ if (!hasDom()) {
193
+ return null;
194
+ }
195
+ const currentScript = document.currentScript;
196
+ if (currentScript?.src) {
197
+ try {
198
+ capturedScriptHost = new URL(currentScript.src).origin;
199
+ return capturedScriptHost;
200
+ } catch {
201
+ }
202
+ }
203
+ capturedScriptHost = "";
204
+ return null;
205
+ }
206
+ if (hasDom()) {
207
+ getScriptHost();
208
+ }
209
+
210
+ // src/utils.ts
211
+ function cn(...classes) {
212
+ return classes.map((c) => (c || "").split(" ")).flat().filter(Boolean).join(" ");
213
+ }
214
+ function getThemeClass(theme) {
215
+ return theme && theme !== THEME_VALUES.system ? `perspective-${theme}-theme` : void 0;
216
+ }
217
+ function resolveIsDark(theme) {
218
+ if (theme === THEME_VALUES.dark) return true;
219
+ if (theme === THEME_VALUES.light) return false;
220
+ if (!hasDom()) return false;
221
+ return window.matchMedia("(prefers-color-scheme: dark)").matches;
222
+ }
223
+ function resolveTheme(themeOverride) {
224
+ return resolveIsDark(themeOverride) ? "dark" : "light";
225
+ }
226
+ function normalizeHex(color) {
227
+ const trimmed = color.trim();
228
+ if (!trimmed) return void 0;
229
+ const normalized = trimmed.startsWith("#") ? trimmed : `#${trimmed}`;
230
+ if (/^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/.test(normalized)) {
231
+ return normalized;
232
+ }
233
+ const hexChars = normalized.slice(1).replace(/[^0-9a-fA-F]/g, "");
234
+ if (hexChars.length >= 6) return `#${hexChars.slice(0, 6)}`;
235
+ if (hexChars.length >= 3) return `#${hexChars.slice(0, 3)}`;
236
+ return void 0;
237
+ }
238
+ function hexToRgba(hex, alpha) {
239
+ const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
240
+ if (!result || !result[1] || !result[2] || !result[3]) {
241
+ return `rgba(118, 41, 200, ${alpha})`;
242
+ }
243
+ const r = parseInt(result[1], 16);
244
+ const g = parseInt(result[2], 16);
245
+ const b = parseInt(result[3], 16);
246
+ return `rgba(${r}, ${g}, ${b}, ${alpha})`;
247
+ }
248
+
249
+ // src/iframe.ts
250
+ function isAllowedRedirectUrl(url) {
251
+ if (!url || typeof url !== "string") return false;
252
+ try {
253
+ const parsed = new URL(url, window.location.origin);
254
+ const protocol = parsed.protocol.toLowerCase();
255
+ const hostname = parsed.hostname.toLowerCase();
256
+ if (protocol === "https:") return true;
257
+ if (protocol === "http:" && (hostname === "localhost" || hostname === "127.0.0.1"))
258
+ return true;
259
+ return false;
260
+ } catch {
261
+ return false;
262
+ }
263
+ }
264
+ function getOrCreateAnonId() {
265
+ if (!hasDom()) return "";
266
+ try {
267
+ let id = localStorage.getItem(STORAGE_KEYS.anonId);
268
+ if (!id) {
269
+ id = crypto.randomUUID();
270
+ localStorage.setItem(STORAGE_KEYS.anonId, id);
271
+ }
272
+ return id;
273
+ } catch {
274
+ return crypto.randomUUID();
275
+ }
276
+ }
277
+ function getUtmParams() {
278
+ if (!hasDom()) return {};
279
+ const params = {};
280
+ const searchParams = new URLSearchParams(window.location.search);
281
+ for (const key of UTM_PARAMS) {
282
+ const value = searchParams.get(key);
283
+ if (value) {
284
+ params[key] = value;
285
+ }
286
+ }
287
+ return params;
288
+ }
289
+ function buildIframeUrl(researchId, type, host, customParams, brand, themeOverride) {
290
+ const url = new URL(`${host}/interview/${researchId}`);
291
+ url.searchParams.set(PARAM_KEYS.embed, PARAM_VALUES.true);
292
+ url.searchParams.set(PARAM_KEYS.embedType, type === "float" ? "chat" : type);
293
+ if (hasDom()) {
294
+ const isDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
295
+ if (themeOverride && themeOverride !== THEME_VALUES.system) {
296
+ url.searchParams.set(PARAM_KEYS.theme, themeOverride);
297
+ } else {
298
+ url.searchParams.set(
299
+ PARAM_KEYS.theme,
300
+ isDark ? THEME_VALUES.dark : THEME_VALUES.light
301
+ );
302
+ }
303
+ } else {
304
+ url.searchParams.set(PARAM_KEYS.theme, themeOverride || THEME_VALUES.light);
305
+ }
306
+ const utmParams = getUtmParams();
307
+ for (const [key, value] of Object.entries(utmParams)) {
308
+ url.searchParams.set(key, value);
309
+ }
310
+ const setColor = (key, color) => {
311
+ if (!color) return;
312
+ const normalized = normalizeHex(color);
313
+ if (normalized) url.searchParams.set(key, normalized);
314
+ };
315
+ if (brand?.light) {
316
+ setColor(BRAND_KEYS.primary, brand.light.primary);
317
+ setColor(BRAND_KEYS.secondary, brand.light.secondary);
318
+ setColor(BRAND_KEYS.bg, brand.light.bg);
319
+ setColor(BRAND_KEYS.text, brand.light.text);
320
+ }
321
+ if (brand?.dark) {
322
+ setColor(BRAND_KEYS.darkPrimary, brand.dark.primary);
323
+ setColor(BRAND_KEYS.darkSecondary, brand.dark.secondary);
324
+ setColor(BRAND_KEYS.darkBg, brand.dark.bg);
325
+ setColor(BRAND_KEYS.darkText, brand.dark.text);
326
+ }
327
+ if (customParams) {
328
+ for (const [key, value] of Object.entries(customParams)) {
329
+ if (!RESERVED_PARAMS.has(key)) {
330
+ url.searchParams.set(key, value);
331
+ }
332
+ }
333
+ }
334
+ return url.toString();
335
+ }
336
+ function createIframe(researchId, type, host, params, brand, themeOverride) {
337
+ if (!hasDom()) {
338
+ return {};
339
+ }
340
+ const iframe = document.createElement("iframe");
341
+ iframe.src = buildIframeUrl(
342
+ researchId,
343
+ type,
344
+ host,
345
+ params,
346
+ brand,
347
+ themeOverride
348
+ );
349
+ iframe.setAttribute("allow", "microphone; camera");
350
+ iframe.setAttribute("allowfullscreen", "true");
351
+ iframe.setAttribute(
352
+ "sandbox",
353
+ "allow-scripts allow-same-origin allow-forms allow-popups allow-modals allow-top-navigation"
354
+ );
355
+ iframe.setAttribute("data-perspective", "true");
356
+ iframe.style.cssText = "border:none;";
357
+ return iframe;
358
+ }
359
+ function setupMessageListener(researchId, config, iframe, host, options) {
360
+ if (!hasDom()) {
361
+ return () => {
362
+ };
363
+ }
364
+ const handler = (event) => {
365
+ if (event.origin !== host) return;
366
+ if (event.source !== iframe.contentWindow) return;
367
+ if (typeof event.data?.type !== "string") return;
368
+ if (!event.data.type.startsWith("perspective:")) return;
369
+ if (event.data.researchId !== researchId) return;
370
+ switch (event.data.type) {
371
+ case MESSAGE_TYPES.ready:
372
+ sendScrollbarStyles(iframe, host);
373
+ sendMessage(iframe, host, {
374
+ type: MESSAGE_TYPES.anonId,
375
+ anonId: getOrCreateAnonId()
376
+ });
377
+ sendMessage(iframe, host, {
378
+ type: MESSAGE_TYPES.init,
379
+ version: SDK_VERSION,
380
+ features: CURRENT_FEATURES,
381
+ researchId
382
+ });
383
+ config.onReady?.();
384
+ break;
385
+ case MESSAGE_TYPES.resize:
386
+ if (!options?.skipResize) {
387
+ iframe.style.height = `${event.data.height}px`;
388
+ }
389
+ break;
390
+ case MESSAGE_TYPES.submit:
391
+ config.onSubmit?.({ researchId });
392
+ break;
393
+ case MESSAGE_TYPES.close:
394
+ config.onClose?.();
395
+ break;
396
+ case MESSAGE_TYPES.error:
397
+ const error = new Error(
398
+ event.data.error
399
+ );
400
+ error.code = event.data.code || "UNKNOWN";
401
+ config.onError?.(error);
402
+ break;
403
+ case MESSAGE_TYPES.redirect:
404
+ const redirectUrl = event.data.url;
405
+ if (!isAllowedRedirectUrl(redirectUrl)) {
406
+ console.warn(
407
+ "[Perspective] Blocked unsafe redirect URL:",
408
+ redirectUrl
409
+ );
410
+ return;
411
+ }
412
+ if (config.onNavigate) {
413
+ config.onNavigate(redirectUrl);
414
+ } else {
415
+ window.location.href = redirectUrl;
416
+ }
417
+ break;
418
+ }
419
+ };
420
+ window.addEventListener("message", handler);
421
+ return () => window.removeEventListener("message", handler);
422
+ }
423
+ function sendMessage(iframe, host, message) {
424
+ if (!hasDom()) return;
425
+ iframe.contentWindow?.postMessage(message, host);
426
+ }
427
+ var activeIframes = /* @__PURE__ */ new Map();
428
+ function registerIframe(iframe, host) {
429
+ activeIframes.set(iframe, host);
430
+ return () => {
431
+ activeIframes.delete(iframe);
432
+ if (activeIframes.size === 0) {
433
+ teardownGlobalListeners();
434
+ }
435
+ };
436
+ }
437
+ function getScrollbarStyles() {
438
+ if (!hasDom()) return "";
439
+ const isDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
440
+ const borderColor = isDark ? "hsl(217 33% 17%)" : "hsl(240 6% 90%)";
441
+ return `
442
+ * {
443
+ scrollbar-width: thin;
444
+ scrollbar-color: ${borderColor} transparent;
445
+ }
446
+ *::-webkit-scrollbar {
447
+ width: 10px;
448
+ height: 10px;
449
+ }
450
+ *::-webkit-scrollbar-track {
451
+ background: transparent;
452
+ }
453
+ *::-webkit-scrollbar-thumb {
454
+ background-color: ${borderColor};
455
+ border-radius: 9999px;
456
+ border: 2px solid transparent;
457
+ background-clip: padding-box;
458
+ }
459
+ *::-webkit-scrollbar-thumb:hover {
460
+ background-color: color-mix(in srgb, ${borderColor} 80%, currentColor);
461
+ }
462
+ `;
463
+ }
464
+ function sendScrollbarStyles(iframe, host) {
465
+ const styles = getScrollbarStyles();
466
+ sendMessage(iframe, host, {
467
+ type: MESSAGE_TYPES.injectStyles,
468
+ styles
469
+ });
470
+ }
471
+ function notifyThemeChange() {
472
+ if (!hasDom()) return;
473
+ const isDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
474
+ activeIframes.forEach((host, iframe) => {
475
+ const message = {
476
+ type: MESSAGE_TYPES.themeChange,
477
+ theme: isDark ? THEME_VALUES.dark : THEME_VALUES.light
478
+ };
479
+ sendMessage(iframe, host, message);
480
+ sendScrollbarStyles(iframe, host);
481
+ });
482
+ }
483
+ var themeListener = null;
484
+ var themeMediaQuery = null;
485
+ var globalMessageHandler = null;
486
+ var globalListenersInitialized = false;
487
+ function setupThemeListener() {
488
+ if (themeListener || !hasDom()) return;
489
+ themeMediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
490
+ themeListener = () => notifyThemeChange();
491
+ themeMediaQuery.addEventListener("change", themeListener);
492
+ }
493
+ function teardownThemeListener() {
494
+ if (themeListener && themeMediaQuery) {
495
+ themeMediaQuery.removeEventListener("change", themeListener);
496
+ themeListener = null;
497
+ themeMediaQuery = null;
498
+ }
499
+ }
500
+ function setupGlobalListeners() {
501
+ if (!hasDom() || globalMessageHandler) return;
502
+ setupThemeListener();
503
+ globalMessageHandler = (event) => {
504
+ if (!event.data?.type?.startsWith("perspective:")) return;
505
+ if (event.data.type === MESSAGE_TYPES.requestScrollbarStyles) {
506
+ const iframes = Array.from(
507
+ document.querySelectorAll("iframe[data-perspective]")
508
+ );
509
+ const sourceIframe = iframes.find(
510
+ (iframe) => iframe.contentWindow === event.source
511
+ );
512
+ if (sourceIframe) {
513
+ const host = activeIframes.get(sourceIframe);
514
+ if (host && event.origin === host) {
515
+ sendScrollbarStyles(sourceIframe, host);
516
+ }
517
+ }
518
+ }
519
+ };
520
+ window.addEventListener("message", globalMessageHandler);
521
+ }
522
+ function teardownGlobalListeners() {
523
+ if (globalMessageHandler) {
524
+ window.removeEventListener("message", globalMessageHandler);
525
+ globalMessageHandler = null;
526
+ }
527
+ teardownThemeListener();
528
+ globalListenersInitialized = false;
529
+ }
530
+ function ensureGlobalListeners() {
531
+ if (globalListenersInitialized) return;
532
+ globalListenersInitialized = true;
533
+ setupGlobalListeners();
534
+ }
535
+
536
+ // src/loading.ts
537
+ var DEFAULT_COLORS = {
538
+ light: {
539
+ bg: "#ffffff",
540
+ primary: "#7629C8"
541
+ },
542
+ dark: {
543
+ bg: "#02040a",
544
+ primary: "#B170FF"
545
+ }
546
+ };
547
+ function getLoadingColors(options) {
548
+ const theme = resolveTheme(options?.theme);
549
+ const isDark = theme === "dark";
550
+ const brandColors = isDark ? options?.brand?.dark : options?.brand?.light;
551
+ return {
552
+ bg: brandColors?.bg || (isDark ? DEFAULT_COLORS.dark.bg : DEFAULT_COLORS.light.bg),
553
+ primary: brandColors?.primary || (isDark ? DEFAULT_COLORS.dark.primary : DEFAULT_COLORS.light.primary)
554
+ };
555
+ }
556
+ function createLoadingIndicator(options) {
557
+ if (!hasDom()) {
558
+ return { remove: () => {
559
+ }, style: {} };
560
+ }
561
+ const colors = getLoadingColors(options);
562
+ const container = document.createElement("div");
563
+ container.className = "perspective-loading";
564
+ container.style.cssText = `
565
+ position: absolute;
566
+ top: 0;
567
+ left: 0;
568
+ right: 0;
569
+ bottom: 0;
570
+ display: flex;
571
+ align-items: center;
572
+ justify-content: center;
573
+ background: ${colors.bg};
574
+ transition: opacity 0.3s ease;
575
+ z-index: 1;
576
+ `;
577
+ const spinner = document.createElement("div");
578
+ spinner.style.cssText = `
579
+ width: 2.5rem;
580
+ height: 2.5rem;
581
+ border: 3px solid ${hexToRgba(colors.primary, 0.15)};
582
+ border-top-color: ${colors.primary};
583
+ border-radius: 50%;
584
+ animation: perspective-spin 0.8s linear infinite;
585
+ `;
586
+ container.appendChild(spinner);
587
+ return container;
588
+ }
589
+
590
+ // src/styles.ts
591
+ var stylesInjected = false;
592
+ var LIGHT_THEME = `
593
+ --perspective-overlay-bg: rgba(0, 0, 0, 0.5);
594
+ --perspective-modal-bg: #ffffff;
595
+ --perspective-modal-text: #151B23;
596
+ --perspective-close-bg: rgba(0, 0, 0, 0.1);
597
+ --perspective-close-text: #666666;
598
+ --perspective-close-hover-bg: rgba(0, 0, 0, 0.2);
599
+ --perspective-close-hover-text: #333333;
600
+ --perspective-border: hsl(240 6% 90%);
601
+ `;
602
+ var DARK_THEME = `
603
+ --perspective-overlay-bg: rgba(0, 0, 0, 0.7);
604
+ --perspective-modal-bg: #02040a;
605
+ --perspective-modal-text: #ffffff;
606
+ --perspective-close-bg: rgba(255, 255, 255, 0.1);
607
+ --perspective-close-text: #a0a0a0;
608
+ --perspective-close-hover-bg: rgba(255, 255, 255, 0.2);
609
+ --perspective-close-hover-text: #ffffff;
610
+ --perspective-border: hsl(217 33% 17%);
611
+ `;
612
+ function injectStyles() {
613
+ if (!hasDom()) return;
614
+ if (stylesInjected) return;
615
+ stylesInjected = true;
616
+ const style = document.createElement("style");
617
+ style.id = "perspective-embed-styles";
618
+ style.textContent = `
619
+ /* Theme-aware color variables */
620
+ .perspective-embed-root, .perspective-light-theme {
621
+ ${LIGHT_THEME}
622
+ --perspective-shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
623
+ --perspective-shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
624
+ --perspective-shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
625
+ --perspective-shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
626
+ --perspective-radius: 1.2rem;
627
+ --perspective-radius-sm: calc(var(--perspective-radius) - 4px);
628
+ }
629
+
630
+ /* Dark theme */
631
+ .perspective-dark-theme {
632
+ ${DARK_THEME}
633
+ }
634
+
635
+ /* System dark mode support */
636
+ @media (prefers-color-scheme: dark) {
637
+ .perspective-embed-root:not(.perspective-light-theme) {
638
+ ${DARK_THEME}
639
+ }
640
+ }
641
+
642
+ /* Scrollbar styling */
643
+ .perspective-modal,
644
+ .perspective-slider,
645
+ .perspective-float-window,
646
+ .perspective-chat-window {
647
+ scrollbar-width: thin;
648
+ scrollbar-color: var(--perspective-border) transparent;
649
+ }
650
+
651
+ .perspective-modal::-webkit-scrollbar,
652
+ .perspective-slider::-webkit-scrollbar,
653
+ .perspective-float-window::-webkit-scrollbar,
654
+ .perspective-chat-window::-webkit-scrollbar {
655
+ width: 10px;
656
+ height: 10px;
657
+ }
658
+
659
+ .perspective-modal::-webkit-scrollbar-track,
660
+ .perspective-slider::-webkit-scrollbar-track,
661
+ .perspective-float-window::-webkit-scrollbar-track,
662
+ .perspective-chat-window::-webkit-scrollbar-track {
663
+ background: transparent;
664
+ }
665
+
666
+ .perspective-modal::-webkit-scrollbar-thumb,
667
+ .perspective-slider::-webkit-scrollbar-thumb,
668
+ .perspective-float-window::-webkit-scrollbar-thumb,
669
+ .perspective-chat-window::-webkit-scrollbar-thumb {
670
+ background-color: var(--perspective-border);
671
+ border-radius: 9999px;
672
+ border: 2px solid transparent;
673
+ background-clip: padding-box;
674
+ }
675
+
676
+ .perspective-modal::-webkit-scrollbar-thumb:hover,
677
+ .perspective-slider::-webkit-scrollbar-thumb:hover,
678
+ .perspective-float-window::-webkit-scrollbar-thumb:hover,
679
+ .perspective-chat-window::-webkit-scrollbar-thumb:hover {
680
+ background-color: color-mix(in srgb, var(--perspective-border) 80%, currentColor);
681
+ }
682
+
683
+ /* Overlay for popup/modal */
684
+ .perspective-overlay {
685
+ position: fixed;
686
+ inset: 0;
687
+ background: var(--perspective-overlay-bg);
688
+ display: flex;
689
+ align-items: center;
690
+ justify-content: center;
691
+ z-index: 9999;
692
+ animation: perspective-fade-in 0.2s ease-out;
693
+ }
694
+
695
+ @keyframes perspective-fade-in {
696
+ from { opacity: 0; }
697
+ to { opacity: 1; }
698
+ }
699
+
700
+ @keyframes perspective-spin {
701
+ to { transform: rotate(360deg); }
702
+ }
703
+
704
+ /* Modal container */
705
+ .perspective-modal {
706
+ position: relative;
707
+ width: 90%;
708
+ max-width: 600px;
709
+ height: 80vh;
710
+ max-height: 700px;
711
+ background: var(--perspective-modal-bg);
712
+ color: var(--perspective-modal-text);
713
+ border-radius: var(--perspective-radius);
714
+ overflow: hidden;
715
+ box-shadow: var(--perspective-shadow-xl);
716
+ animation: perspective-slide-up 0.3s ease-out;
717
+ }
718
+
719
+ @keyframes perspective-slide-up {
720
+ from {
721
+ opacity: 0;
722
+ transform: translateY(20px) scale(0.95);
723
+ }
724
+ to {
725
+ opacity: 1;
726
+ transform: translateY(0) scale(1);
727
+ }
728
+ }
729
+
730
+ .perspective-modal iframe {
731
+ width: 100%;
732
+ height: 100%;
733
+ border: none;
734
+ }
735
+
736
+ /* Close button */
737
+ .perspective-close {
738
+ position: absolute;
739
+ top: 1rem;
740
+ right: 1.5rem;
741
+ width: 2rem;
742
+ height: 2rem;
743
+ border: none;
744
+ background: var(--perspective-close-bg);
745
+ color: var(--perspective-close-text);
746
+ border-radius: 50%;
747
+ cursor: pointer;
748
+ display: flex;
749
+ align-items: center;
750
+ justify-content: center;
751
+ font-size: 1rem;
752
+ z-index: 10;
753
+ transition: background-color 0.2s ease, color 0.2s ease;
754
+ }
755
+
756
+ .perspective-close:hover {
757
+ background: var(--perspective-close-hover-bg);
758
+ color: var(--perspective-close-hover-text);
759
+ }
760
+
761
+ .perspective-close:focus-visible {
762
+ outline: 2px solid currentColor;
763
+ outline-offset: 2px;
764
+ }
765
+
766
+ .perspective-close svg {
767
+ width: 1rem;
768
+ height: 1rem;
769
+ stroke-width: 2;
770
+ }
771
+
772
+ /* Slider drawer */
773
+ .perspective-slider {
774
+ position: fixed;
775
+ top: 0;
776
+ right: 0;
777
+ width: 100%;
778
+ max-width: 450px;
779
+ height: 100%;
780
+ background: var(--perspective-modal-bg);
781
+ color: var(--perspective-modal-text);
782
+ box-shadow: var(--perspective-shadow-xl);
783
+ z-index: 9999;
784
+ animation: perspective-slide-in 0.3s ease-out;
785
+ }
786
+
787
+ @keyframes perspective-slide-in {
788
+ from { transform: translateX(100%); }
789
+ to { transform: translateX(0); }
790
+ }
791
+
792
+ .perspective-slider iframe {
793
+ width: 100%;
794
+ height: 100%;
795
+ border: none;
796
+ }
797
+
798
+ .perspective-slider .perspective-close {
799
+ top: 1rem;
800
+ right: 2rem;
801
+ }
802
+
803
+ /* Slider backdrop */
804
+ .perspective-slider-backdrop {
805
+ position: fixed;
806
+ inset: 0;
807
+ background: var(--perspective-overlay-bg);
808
+ z-index: 9998;
809
+ animation: perspective-fade-in 0.2s ease-out;
810
+ }
811
+
812
+ /* Float bubble (and legacy chat-bubble alias) */
813
+ .perspective-float-bubble,
814
+ .perspective-chat-bubble {
815
+ position: fixed;
816
+ bottom: 1.5rem;
817
+ right: 1.5rem;
818
+ width: 3.75rem;
819
+ height: 3.75rem;
820
+ border-radius: 50%;
821
+ background: var(--perspective-float-bg, var(--perspective-chat-bg, #7629C8));
822
+ color: white;
823
+ border: none;
824
+ cursor: pointer;
825
+ box-shadow: var(--perspective-float-shadow, var(--perspective-chat-shadow, 0 4px 12px rgba(118, 41, 200, 0.4)));
826
+ z-index: 9996;
827
+ display: flex;
828
+ align-items: center;
829
+ justify-content: center;
830
+ transition: transform 0.2s ease, box-shadow 0.2s ease;
831
+ }
832
+
833
+ .perspective-float-bubble:hover,
834
+ .perspective-chat-bubble:hover {
835
+ transform: scale(1.05);
836
+ box-shadow: var(--perspective-float-shadow-hover, var(--perspective-chat-shadow-hover, 0 6px 16px rgba(118, 41, 200, 0.5)));
837
+ }
838
+
839
+ .perspective-float-bubble:focus-visible,
840
+ .perspective-chat-bubble:focus-visible {
841
+ outline: 2px solid currentColor;
842
+ outline-offset: 2px;
843
+ }
844
+
845
+ .perspective-float-bubble svg,
846
+ .perspective-chat-bubble svg {
847
+ width: 1.75rem;
848
+ height: 1.75rem;
849
+ stroke-width: 2;
850
+ }
851
+
852
+ /* Float window (and legacy chat-window alias) */
853
+ .perspective-float-window,
854
+ .perspective-chat-window {
855
+ position: fixed;
856
+ bottom: 6.25rem;
857
+ right: 1.5rem;
858
+ width: 380px;
859
+ height: calc(100vh - 8.75rem);
860
+ max-height: 600px;
861
+ background: var(--perspective-modal-bg);
862
+ color: var(--perspective-modal-text);
863
+ border-radius: var(--perspective-radius);
864
+ overflow: hidden;
865
+ box-shadow: var(--perspective-shadow-xl);
866
+ z-index: 9997;
867
+ animation: perspective-float-open 0.3s ease-out;
868
+ }
869
+
870
+ @keyframes perspective-float-open {
871
+ from {
872
+ opacity: 0;
873
+ transform: translateY(20px) scale(0.9);
874
+ }
875
+ to {
876
+ opacity: 1;
877
+ transform: translateY(0) scale(1);
878
+ }
879
+ }
880
+
881
+ .perspective-float-window iframe,
882
+ .perspective-chat-window iframe {
883
+ width: 100%;
884
+ height: 100%;
885
+ border: none;
886
+ }
887
+
888
+ .perspective-float-window .perspective-close,
889
+ .perspective-chat-window .perspective-close {
890
+ top: 1rem;
891
+ right: 1.5rem;
892
+ }
893
+
894
+ /* Fullpage */
895
+ .perspective-fullpage {
896
+ position: fixed;
897
+ inset: 0;
898
+ z-index: 9999;
899
+ background: var(--perspective-modal-bg);
900
+ }
901
+
902
+ .perspective-fullpage iframe {
903
+ width: 100%;
904
+ height: 100%;
905
+ border: none;
906
+ }
907
+
908
+ /* Responsive */
909
+ @media (max-width: 640px) {
910
+ .perspective-modal {
911
+ width: 100%;
912
+ height: 100%;
913
+ max-width: none;
914
+ max-height: none;
915
+ border-radius: 0;
916
+ }
917
+
918
+ .perspective-slider {
919
+ max-width: 100%;
920
+ }
921
+
922
+ .perspective-float-window,
923
+ .perspective-chat-window {
924
+ width: calc(100% - 2rem);
925
+ right: 1rem;
926
+ bottom: 5.625rem;
927
+ height: calc(100vh - 7.5rem);
928
+ }
929
+
930
+ .perspective-float-bubble,
931
+ .perspective-chat-bubble {
932
+ bottom: 1rem;
933
+ right: 1rem;
934
+ }
935
+ }
936
+
937
+ @media (max-width: 450px) {
938
+ .perspective-float-window,
939
+ .perspective-chat-window {
940
+ width: calc(100% - 1rem);
941
+ right: 0.5rem;
942
+ bottom: 5rem;
943
+ height: calc(100vh - 6.5rem);
944
+ }
945
+
946
+ .perspective-float-bubble,
947
+ .perspective-chat-bubble {
948
+ bottom: 0.75rem;
949
+ right: 0.75rem;
950
+ width: 3.5rem;
951
+ height: 3.5rem;
952
+ }
953
+
954
+ .perspective-float-bubble svg,
955
+ .perspective-chat-bubble svg {
956
+ width: 1.5rem;
957
+ height: 1.5rem;
958
+ }
959
+ }
960
+ `;
961
+ document.head.appendChild(style);
962
+ }
963
+ var MIC_ICON = `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
964
+ <path stroke-linecap="round" stroke-linejoin="round" d="M12 18.75a6 6 0 006-6v-1.5m-6 7.5a6 6 0 01-6-6v-1.5m6 7.5v3.75m-3.75 0h7.5M12 15.75a3 3 0 01-3-3V4.5a3 3 0 116 0v8.25a3 3 0 01-3 3z" />
965
+ </svg>`;
966
+ var CLOSE_ICON = `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
967
+ <path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
968
+ </svg>`;
969
+
970
+ // src/widget.ts
971
+ var widgetResources = /* @__PURE__ */ new WeakMap();
972
+ function createNoOpHandle(researchId, type) {
973
+ return {
974
+ unmount: () => {
975
+ },
976
+ update: () => {
977
+ },
978
+ destroy: () => {
979
+ },
980
+ researchId,
981
+ type,
982
+ iframe: null,
983
+ container: null
984
+ };
985
+ }
986
+ function createExistingWidgetHandle(container, researchId) {
987
+ const existingWrapper = container.querySelector(
988
+ ".perspective-embed-root"
989
+ );
990
+ const existingIframe = container.querySelector(
991
+ "iframe[data-perspective]"
992
+ );
993
+ let destroyed = false;
994
+ const unmount = () => {
995
+ if (destroyed) return;
996
+ destroyed = true;
997
+ if (existingIframe) {
998
+ const resources = widgetResources.get(existingIframe);
999
+ if (resources) {
1000
+ resources.cleanup();
1001
+ resources.unregister();
1002
+ widgetResources.delete(existingIframe);
1003
+ }
1004
+ }
1005
+ existingWrapper?.remove();
1006
+ };
1007
+ return {
1008
+ unmount,
1009
+ update: () => {
1010
+ },
1011
+ destroy: unmount,
1012
+ researchId,
1013
+ type: "widget",
1014
+ iframe: existingIframe,
1015
+ container
1016
+ };
1017
+ }
1018
+ function createWidget(container, config) {
1019
+ const { researchId } = config;
1020
+ if (!hasDom() || !container) {
1021
+ return createNoOpHandle(researchId, "widget");
1022
+ }
1023
+ if (container.querySelector("iframe[data-perspective]")) {
1024
+ return createExistingWidgetHandle(container, researchId);
1025
+ }
1026
+ const host = getHost(config.host);
1027
+ injectStyles();
1028
+ ensureGlobalListeners();
1029
+ const wrapper = document.createElement("div");
1030
+ wrapper.className = cn("perspective-embed-root", getThemeClass(config.theme));
1031
+ wrapper.style.cssText = "position:relative;width:100%;height:100%;min-height:500px;";
1032
+ const loading = createLoadingIndicator({
1033
+ theme: config.theme,
1034
+ brand: config.brand
1035
+ });
1036
+ wrapper.appendChild(loading);
1037
+ const iframe = createIframe(
1038
+ researchId,
1039
+ "widget",
1040
+ host,
1041
+ config.params,
1042
+ config.brand,
1043
+ config.theme
1044
+ );
1045
+ iframe.style.width = "100%";
1046
+ iframe.style.height = "100%";
1047
+ iframe.style.minHeight = "500px";
1048
+ iframe.style.opacity = "0";
1049
+ iframe.style.transition = "opacity 0.3s ease";
1050
+ wrapper.appendChild(iframe);
1051
+ container.appendChild(wrapper);
1052
+ let currentConfig = { ...config };
1053
+ const cleanup = setupMessageListener(
1054
+ researchId,
1055
+ {
1056
+ get onReady() {
1057
+ return () => {
1058
+ loading.style.opacity = "0";
1059
+ iframe.style.opacity = "1";
1060
+ setTimeout(() => loading.remove(), 300);
1061
+ currentConfig.onReady?.();
1062
+ };
1063
+ },
1064
+ get onSubmit() {
1065
+ return currentConfig.onSubmit;
1066
+ },
1067
+ get onNavigate() {
1068
+ return currentConfig.onNavigate;
1069
+ },
1070
+ get onClose() {
1071
+ return currentConfig.onClose;
1072
+ },
1073
+ get onError() {
1074
+ return currentConfig.onError;
1075
+ }
1076
+ },
1077
+ iframe,
1078
+ host,
1079
+ { skipResize: true }
1080
+ );
1081
+ const unregisterIframe = registerIframe(iframe, host);
1082
+ widgetResources.set(iframe, {
1083
+ cleanup,
1084
+ unregister: unregisterIframe,
1085
+ wrapper
1086
+ });
1087
+ let destroyed = false;
1088
+ const unmount = () => {
1089
+ if (destroyed) return;
1090
+ destroyed = true;
1091
+ cleanup();
1092
+ unregisterIframe();
1093
+ widgetResources.delete(iframe);
1094
+ wrapper.remove();
1095
+ };
1096
+ return {
1097
+ unmount,
1098
+ update: (options) => {
1099
+ currentConfig = { ...currentConfig, ...options };
1100
+ },
1101
+ destroy: unmount,
1102
+ researchId,
1103
+ type: "widget",
1104
+ iframe,
1105
+ container
1106
+ };
1107
+ }
1108
+
1109
+ // src/popup.ts
1110
+ function createNoOpHandle2(researchId) {
1111
+ return {
1112
+ unmount: () => {
1113
+ },
1114
+ update: () => {
1115
+ },
1116
+ destroy: () => {
1117
+ },
1118
+ researchId,
1119
+ type: "popup",
1120
+ iframe: null,
1121
+ container: null
1122
+ };
1123
+ }
1124
+ function openPopup(config) {
1125
+ const { researchId } = config;
1126
+ if (!hasDom()) {
1127
+ return createNoOpHandle2(researchId);
1128
+ }
1129
+ const host = getHost(config.host);
1130
+ injectStyles();
1131
+ ensureGlobalListeners();
1132
+ const overlay = document.createElement("div");
1133
+ overlay.className = cn(
1134
+ "perspective-overlay perspective-embed-root",
1135
+ getThemeClass(config.theme)
1136
+ );
1137
+ const modal = document.createElement("div");
1138
+ modal.className = "perspective-modal";
1139
+ const closeBtn = document.createElement("button");
1140
+ closeBtn.className = "perspective-close";
1141
+ closeBtn.innerHTML = CLOSE_ICON;
1142
+ closeBtn.setAttribute("aria-label", "Close");
1143
+ const loading = createLoadingIndicator({
1144
+ theme: config.theme,
1145
+ brand: config.brand
1146
+ });
1147
+ loading.style.borderRadius = "16px";
1148
+ const iframe = createIframe(
1149
+ researchId,
1150
+ "popup",
1151
+ host,
1152
+ config.params,
1153
+ config.brand,
1154
+ config.theme
1155
+ );
1156
+ iframe.style.opacity = "0";
1157
+ iframe.style.transition = "opacity 0.3s ease";
1158
+ modal.appendChild(closeBtn);
1159
+ modal.appendChild(loading);
1160
+ modal.appendChild(iframe);
1161
+ overlay.appendChild(modal);
1162
+ document.body.appendChild(overlay);
1163
+ let currentConfig = { ...config };
1164
+ let isOpen = true;
1165
+ let messageCleanup = null;
1166
+ const unregisterIframe = registerIframe(iframe, host);
1167
+ const destroy2 = () => {
1168
+ if (!isOpen) return;
1169
+ isOpen = false;
1170
+ messageCleanup?.();
1171
+ unregisterIframe();
1172
+ overlay.remove();
1173
+ document.removeEventListener("keydown", escHandler);
1174
+ currentConfig.onClose?.();
1175
+ };
1176
+ messageCleanup = setupMessageListener(
1177
+ researchId,
1178
+ {
1179
+ get onReady() {
1180
+ return () => {
1181
+ loading.style.opacity = "0";
1182
+ iframe.style.opacity = "1";
1183
+ setTimeout(() => loading.remove(), 300);
1184
+ currentConfig.onReady?.();
1185
+ };
1186
+ },
1187
+ get onSubmit() {
1188
+ return currentConfig.onSubmit;
1189
+ },
1190
+ get onNavigate() {
1191
+ return currentConfig.onNavigate;
1192
+ },
1193
+ get onClose() {
1194
+ return destroy2;
1195
+ },
1196
+ get onError() {
1197
+ return currentConfig.onError;
1198
+ }
1199
+ },
1200
+ iframe,
1201
+ host,
1202
+ { skipResize: true }
1203
+ );
1204
+ closeBtn.addEventListener("click", destroy2);
1205
+ overlay.addEventListener("click", (e) => {
1206
+ if (e.target === overlay) destroy2();
1207
+ });
1208
+ const escHandler = (e) => {
1209
+ if (e.key === "Escape") {
1210
+ destroy2();
1211
+ }
1212
+ };
1213
+ document.addEventListener("keydown", escHandler);
1214
+ return {
1215
+ unmount: destroy2,
1216
+ update: (options) => {
1217
+ currentConfig = { ...currentConfig, ...options };
1218
+ },
1219
+ destroy: destroy2,
1220
+ researchId,
1221
+ type: "popup",
1222
+ iframe,
1223
+ container: overlay
1224
+ };
1225
+ }
1226
+
1227
+ // src/slider.ts
1228
+ function createNoOpHandle3(researchId) {
1229
+ return {
1230
+ unmount: () => {
1231
+ },
1232
+ update: () => {
1233
+ },
1234
+ destroy: () => {
1235
+ },
1236
+ researchId,
1237
+ type: "slider",
1238
+ iframe: null,
1239
+ container: null
1240
+ };
1241
+ }
1242
+ function openSlider(config) {
1243
+ const { researchId } = config;
1244
+ if (!hasDom()) {
1245
+ return createNoOpHandle3(researchId);
1246
+ }
1247
+ const host = getHost(config.host);
1248
+ injectStyles();
1249
+ ensureGlobalListeners();
1250
+ const backdrop = document.createElement("div");
1251
+ backdrop.className = cn(
1252
+ "perspective-slider-backdrop perspective-embed-root",
1253
+ getThemeClass(config.theme)
1254
+ );
1255
+ const slider = document.createElement("div");
1256
+ slider.className = cn(
1257
+ "perspective-slider perspective-embed-root",
1258
+ getThemeClass(config.theme)
1259
+ );
1260
+ const closeBtn = document.createElement("button");
1261
+ closeBtn.className = "perspective-close";
1262
+ closeBtn.innerHTML = CLOSE_ICON;
1263
+ closeBtn.setAttribute("aria-label", "Close");
1264
+ const loading = createLoadingIndicator({
1265
+ theme: config.theme,
1266
+ brand: config.brand
1267
+ });
1268
+ const iframe = createIframe(
1269
+ researchId,
1270
+ "slider",
1271
+ host,
1272
+ config.params,
1273
+ config.brand,
1274
+ config.theme
1275
+ );
1276
+ iframe.style.opacity = "0";
1277
+ iframe.style.transition = "opacity 0.3s ease";
1278
+ slider.appendChild(closeBtn);
1279
+ slider.appendChild(loading);
1280
+ slider.appendChild(iframe);
1281
+ document.body.appendChild(backdrop);
1282
+ document.body.appendChild(slider);
1283
+ let currentConfig = { ...config };
1284
+ let isOpen = true;
1285
+ let messageCleanup = null;
1286
+ const unregisterIframe = registerIframe(iframe, host);
1287
+ const destroy2 = () => {
1288
+ if (!isOpen) return;
1289
+ isOpen = false;
1290
+ messageCleanup?.();
1291
+ unregisterIframe();
1292
+ slider.remove();
1293
+ backdrop.remove();
1294
+ document.removeEventListener("keydown", escHandler);
1295
+ currentConfig.onClose?.();
1296
+ };
1297
+ messageCleanup = setupMessageListener(
1298
+ researchId,
1299
+ {
1300
+ get onReady() {
1301
+ return () => {
1302
+ loading.style.opacity = "0";
1303
+ iframe.style.opacity = "1";
1304
+ setTimeout(() => loading.remove(), 300);
1305
+ currentConfig.onReady?.();
1306
+ };
1307
+ },
1308
+ get onSubmit() {
1309
+ return currentConfig.onSubmit;
1310
+ },
1311
+ get onNavigate() {
1312
+ return currentConfig.onNavigate;
1313
+ },
1314
+ get onClose() {
1315
+ return destroy2;
1316
+ },
1317
+ get onError() {
1318
+ return currentConfig.onError;
1319
+ }
1320
+ },
1321
+ iframe,
1322
+ host,
1323
+ { skipResize: true }
1324
+ );
1325
+ closeBtn.addEventListener("click", destroy2);
1326
+ backdrop.addEventListener("click", destroy2);
1327
+ const escHandler = (e) => {
1328
+ if (e.key === "Escape") {
1329
+ destroy2();
1330
+ }
1331
+ };
1332
+ document.addEventListener("keydown", escHandler);
1333
+ return {
1334
+ unmount: destroy2,
1335
+ update: (options) => {
1336
+ currentConfig = { ...currentConfig, ...options };
1337
+ },
1338
+ destroy: destroy2,
1339
+ researchId,
1340
+ type: "slider",
1341
+ iframe,
1342
+ container: slider
1343
+ };
1344
+ }
1345
+
1346
+ // src/float.ts
1347
+ function createNoOpHandle4(researchId) {
1348
+ return {
1349
+ unmount: () => {
1350
+ },
1351
+ update: () => {
1352
+ },
1353
+ destroy: () => {
1354
+ },
1355
+ open: () => {
1356
+ },
1357
+ close: () => {
1358
+ },
1359
+ toggle: () => {
1360
+ },
1361
+ isOpen: false,
1362
+ researchId,
1363
+ type: "float",
1364
+ iframe: null,
1365
+ container: null
1366
+ };
1367
+ }
1368
+ function createFloatBubble(config) {
1369
+ const { researchId, _themeConfig, theme, brand } = config;
1370
+ if (!hasDom()) {
1371
+ return createNoOpHandle4(researchId);
1372
+ }
1373
+ const host = getHost(config.host);
1374
+ injectStyles();
1375
+ ensureGlobalListeners();
1376
+ const bubble = document.createElement("button");
1377
+ bubble.className = cn(
1378
+ "perspective-float-bubble perspective-embed-root",
1379
+ getThemeClass(config.theme)
1380
+ );
1381
+ bubble.innerHTML = MIC_ICON;
1382
+ bubble.setAttribute("aria-label", "Open chat");
1383
+ bubble.setAttribute("data-perspective", "float-bubble");
1384
+ if (_themeConfig || brand) {
1385
+ const isDark = resolveIsDark(theme);
1386
+ const bg = isDark ? brand?.dark?.primary ?? _themeConfig?.darkPrimaryColor ?? "#a78bfa" : brand?.light?.primary ?? _themeConfig?.primaryColor ?? "#7c3aed";
1387
+ bubble.style.setProperty("--perspective-float-bg", bg);
1388
+ bubble.style.setProperty(
1389
+ "--perspective-float-shadow",
1390
+ `0 4px 12px ${bg}66`
1391
+ );
1392
+ bubble.style.setProperty(
1393
+ "--perspective-float-shadow-hover",
1394
+ `0 6px 16px ${bg}80`
1395
+ );
1396
+ bubble.style.backgroundColor = bg;
1397
+ bubble.style.boxShadow = `0 4px 12px ${bg}66`;
1398
+ }
1399
+ document.body.appendChild(bubble);
1400
+ let floatWindow = null;
1401
+ let iframe = null;
1402
+ let cleanup = null;
1403
+ let unregisterIframe = null;
1404
+ let isOpen = false;
1405
+ let currentConfig = { ...config };
1406
+ const openFloat = () => {
1407
+ if (isOpen) return;
1408
+ isOpen = true;
1409
+ floatWindow = document.createElement("div");
1410
+ floatWindow.className = cn(
1411
+ "perspective-float-window perspective-embed-root",
1412
+ getThemeClass(currentConfig.theme)
1413
+ );
1414
+ const closeBtn = document.createElement("button");
1415
+ closeBtn.className = "perspective-close";
1416
+ closeBtn.innerHTML = CLOSE_ICON;
1417
+ closeBtn.setAttribute("aria-label", "Close chat");
1418
+ closeBtn.addEventListener("click", closeFloat);
1419
+ const loading = createLoadingIndicator({
1420
+ theme: currentConfig.theme,
1421
+ brand: currentConfig.brand
1422
+ });
1423
+ loading.style.borderRadius = "16px";
1424
+ iframe = createIframe(
1425
+ researchId,
1426
+ "float",
1427
+ host,
1428
+ currentConfig.params,
1429
+ currentConfig.brand,
1430
+ currentConfig.theme
1431
+ );
1432
+ iframe.style.opacity = "0";
1433
+ iframe.style.transition = "opacity 0.3s ease";
1434
+ floatWindow.appendChild(closeBtn);
1435
+ floatWindow.appendChild(loading);
1436
+ floatWindow.appendChild(iframe);
1437
+ document.body.appendChild(floatWindow);
1438
+ cleanup = setupMessageListener(
1439
+ researchId,
1440
+ {
1441
+ get onReady() {
1442
+ return () => {
1443
+ loading.style.opacity = "0";
1444
+ iframe.style.opacity = "1";
1445
+ setTimeout(() => loading.remove(), 300);
1446
+ currentConfig.onReady?.();
1447
+ };
1448
+ },
1449
+ get onSubmit() {
1450
+ return currentConfig.onSubmit;
1451
+ },
1452
+ get onNavigate() {
1453
+ return currentConfig.onNavigate;
1454
+ },
1455
+ get onClose() {
1456
+ return closeFloat;
1457
+ },
1458
+ get onError() {
1459
+ return currentConfig.onError;
1460
+ }
1461
+ },
1462
+ iframe,
1463
+ host,
1464
+ { skipResize: true }
1465
+ );
1466
+ if (iframe) {
1467
+ unregisterIframe = registerIframe(iframe, host);
1468
+ }
1469
+ bubble.innerHTML = CLOSE_ICON;
1470
+ bubble.setAttribute("aria-label", "Close chat");
1471
+ };
1472
+ const closeFloat = () => {
1473
+ if (!isOpen) return;
1474
+ isOpen = false;
1475
+ cleanup?.();
1476
+ unregisterIframe?.();
1477
+ floatWindow?.remove();
1478
+ floatWindow = null;
1479
+ iframe = null;
1480
+ cleanup = null;
1481
+ unregisterIframe = null;
1482
+ bubble.innerHTML = MIC_ICON;
1483
+ bubble.setAttribute("aria-label", "Open chat");
1484
+ currentConfig.onClose?.();
1485
+ };
1486
+ bubble.addEventListener("click", () => {
1487
+ if (isOpen) {
1488
+ closeFloat();
1489
+ } else {
1490
+ openFloat();
1491
+ }
1492
+ });
1493
+ const unmount = () => {
1494
+ closeFloat();
1495
+ bubble.remove();
1496
+ };
1497
+ return {
1498
+ unmount,
1499
+ update: (options) => {
1500
+ currentConfig = { ...currentConfig, ...options };
1501
+ },
1502
+ destroy: unmount,
1503
+ open: openFloat,
1504
+ close: closeFloat,
1505
+ toggle: () => {
1506
+ if (isOpen) {
1507
+ closeFloat();
1508
+ } else {
1509
+ openFloat();
1510
+ }
1511
+ },
1512
+ get isOpen() {
1513
+ return isOpen;
1514
+ },
1515
+ researchId,
1516
+ type: "float",
1517
+ get iframe() {
1518
+ return iframe;
1519
+ },
1520
+ container: bubble
1521
+ };
1522
+ }
1523
+ var createChatBubble = createFloatBubble;
1524
+
1525
+ // src/fullpage.ts
1526
+ function createNoOpHandle5(researchId) {
1527
+ return {
1528
+ unmount: () => {
1529
+ },
1530
+ update: () => {
1531
+ },
1532
+ destroy: () => {
1533
+ },
1534
+ researchId,
1535
+ type: "fullpage",
1536
+ iframe: null,
1537
+ container: null
1538
+ };
1539
+ }
1540
+ function createFullpage(config) {
1541
+ const { researchId } = config;
1542
+ if (!hasDom()) {
1543
+ return createNoOpHandle5(researchId);
1544
+ }
1545
+ const host = getHost(config.host);
1546
+ injectStyles();
1547
+ ensureGlobalListeners();
1548
+ const container = document.createElement("div");
1549
+ container.className = cn(
1550
+ "perspective-embed-root perspective-fullpage",
1551
+ getThemeClass(config.theme)
1552
+ );
1553
+ const loading = createLoadingIndicator({
1554
+ theme: config.theme,
1555
+ brand: config.brand
1556
+ });
1557
+ container.appendChild(loading);
1558
+ const iframe = createIframe(
1559
+ researchId,
1560
+ "fullpage",
1561
+ host,
1562
+ config.params,
1563
+ config.brand,
1564
+ config.theme
1565
+ );
1566
+ iframe.style.opacity = "0";
1567
+ iframe.style.transition = "opacity 0.3s ease";
1568
+ container.appendChild(iframe);
1569
+ document.body.appendChild(container);
1570
+ let currentConfig = { ...config };
1571
+ let messageCleanup = null;
1572
+ const unregisterIframe = registerIframe(iframe, host);
1573
+ const unmount = () => {
1574
+ messageCleanup?.();
1575
+ unregisterIframe();
1576
+ container.remove();
1577
+ currentConfig.onClose?.();
1578
+ };
1579
+ messageCleanup = setupMessageListener(
1580
+ researchId,
1581
+ {
1582
+ get onReady() {
1583
+ return () => {
1584
+ loading.style.opacity = "0";
1585
+ iframe.style.opacity = "1";
1586
+ setTimeout(() => loading.remove(), 300);
1587
+ currentConfig.onReady?.();
1588
+ };
1589
+ },
1590
+ get onSubmit() {
1591
+ return currentConfig.onSubmit;
1592
+ },
1593
+ get onNavigate() {
1594
+ return currentConfig.onNavigate;
1595
+ },
1596
+ get onClose() {
1597
+ return unmount;
1598
+ },
1599
+ get onError() {
1600
+ return currentConfig.onError;
1601
+ }
1602
+ },
1603
+ iframe,
1604
+ host,
1605
+ { skipResize: true }
1606
+ );
1607
+ return {
1608
+ unmount,
1609
+ update: (options) => {
1610
+ currentConfig = { ...currentConfig, ...options };
1611
+ },
1612
+ destroy: unmount,
1613
+ researchId,
1614
+ type: "fullpage",
1615
+ iframe,
1616
+ container
1617
+ };
1618
+ }
1619
+
1620
+ // src/browser.ts
1621
+ var instances = /* @__PURE__ */ new Map();
1622
+ var configCache = /* @__PURE__ */ new Map();
1623
+ var styledButtons = /* @__PURE__ */ new Map();
1624
+ var buttonThemeMediaQuery = null;
1625
+ var DEFAULT_THEME = {
1626
+ primaryColor: "#7c3aed",
1627
+ textColor: "#ffffff",
1628
+ darkPrimaryColor: "#a78bfa",
1629
+ darkTextColor: "#ffffff"
1630
+ };
1631
+ async function fetchConfig(researchId) {
1632
+ if (configCache.has(researchId)) {
1633
+ return configCache.get(researchId);
1634
+ }
1635
+ try {
1636
+ const host = getHost();
1637
+ const res = await fetch(`${host}/api/v1/embed/config/${researchId}`);
1638
+ if (!res.ok) return DEFAULT_THEME;
1639
+ const config = await res.json();
1640
+ configCache.set(researchId, config);
1641
+ return config;
1642
+ } catch {
1643
+ return DEFAULT_THEME;
1644
+ }
1645
+ }
1646
+ function styleButton(el, themeConfig, options) {
1647
+ if (el.hasAttribute(DATA_ATTRS.noStyle)) return;
1648
+ styledButtons.set(el, {
1649
+ themeConfig,
1650
+ theme: options?.theme,
1651
+ brand: options?.brand
1652
+ });
1653
+ updateButtonTheme(el, {
1654
+ themeConfig,
1655
+ theme: options?.theme,
1656
+ brand: options?.brand
1657
+ });
1658
+ }
1659
+ function updateButtonTheme(el, config) {
1660
+ const { themeConfig, theme, brand } = config;
1661
+ const isDark = resolveIsDark(theme);
1662
+ const bg = isDark ? brand?.dark?.primary ?? themeConfig.darkPrimaryColor : brand?.light?.primary ?? themeConfig.primaryColor;
1663
+ const text = isDark ? brand?.dark?.text ?? themeConfig.darkTextColor : brand?.light?.text ?? themeConfig.textColor;
1664
+ el.style.backgroundColor = bg;
1665
+ el.style.color = text;
1666
+ el.style.padding = "10px 20px";
1667
+ el.style.border = "none";
1668
+ el.style.borderRadius = "8px";
1669
+ el.style.fontWeight = "500";
1670
+ el.style.cursor = "pointer";
1671
+ }
1672
+ function updateAllButtonThemes() {
1673
+ styledButtons.forEach((config, el) => {
1674
+ if (document.contains(el)) {
1675
+ updateButtonTheme(el, config);
1676
+ } else {
1677
+ styledButtons.delete(el);
1678
+ }
1679
+ });
1680
+ }
1681
+ var buttonThemeListener = null;
1682
+ function setupButtonThemeListener() {
1683
+ if (buttonThemeListener || !hasDom()) return;
1684
+ buttonThemeMediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
1685
+ buttonThemeListener = () => updateAllButtonThemes();
1686
+ buttonThemeMediaQuery.addEventListener("change", buttonThemeListener);
1687
+ }
1688
+ function teardownButtonThemeListener() {
1689
+ if (buttonThemeListener && buttonThemeMediaQuery) {
1690
+ buttonThemeMediaQuery.removeEventListener("change", buttonThemeListener);
1691
+ buttonThemeListener = null;
1692
+ buttonThemeMediaQuery = null;
1693
+ }
1694
+ }
1695
+ function parseParamsAttr(el) {
1696
+ const paramsStr = el.getAttribute(DATA_ATTRS.params);
1697
+ if (!paramsStr) return void 0;
1698
+ const params = {};
1699
+ for (const pair of paramsStr.split(",")) {
1700
+ const [key, ...valueParts] = pair.trim().split("=");
1701
+ if (key) {
1702
+ params[key.trim()] = valueParts.join("=").trim();
1703
+ }
1704
+ }
1705
+ return Object.keys(params).length > 0 ? params : void 0;
1706
+ }
1707
+ function parseBrandAttr(attrValue) {
1708
+ if (!attrValue) return void 0;
1709
+ const colors = {};
1710
+ for (const pair of attrValue.split(",")) {
1711
+ const [key, ...valueParts] = pair.trim().split("=");
1712
+ if (key && valueParts.length > 0) {
1713
+ const value = valueParts.join("=").trim();
1714
+ if (value) {
1715
+ const k = key.trim();
1716
+ if (k === "primary" || k === "secondary" || k === "bg" || k === "text") {
1717
+ colors[k] = value;
1718
+ }
1719
+ }
1720
+ }
1721
+ }
1722
+ return Object.keys(colors).length > 0 ? colors : void 0;
1723
+ }
1724
+ function extractBrandConfig(el) {
1725
+ const light = parseBrandAttr(el.getAttribute(DATA_ATTRS.brand));
1726
+ const dark = parseBrandAttr(el.getAttribute(DATA_ATTRS.brandDark));
1727
+ const themeAttr = el.getAttribute(DATA_ATTRS.theme);
1728
+ const config = {};
1729
+ if (light || dark) {
1730
+ config.brand = {};
1731
+ if (light) config.brand.light = light;
1732
+ if (dark) config.brand.dark = dark;
1733
+ }
1734
+ if (themeAttr === THEME_VALUES.dark || themeAttr === THEME_VALUES.light || themeAttr === THEME_VALUES.system) {
1735
+ config.theme = themeAttr;
1736
+ }
1737
+ return config;
1738
+ }
1739
+ function init(config) {
1740
+ const { researchId } = config;
1741
+ const type = config.type === "chat" ? "float" : config.type ?? "widget";
1742
+ if (instances.has(researchId)) {
1743
+ instances.get(researchId).unmount();
1744
+ instances.delete(researchId);
1745
+ }
1746
+ let instance;
1747
+ switch (type) {
1748
+ case "popup":
1749
+ instance = openPopup(config);
1750
+ break;
1751
+ case "slider":
1752
+ instance = openSlider(config);
1753
+ break;
1754
+ case "float":
1755
+ instance = createFloatBubble(config);
1756
+ break;
1757
+ case "fullpage":
1758
+ instance = createFullpage(config);
1759
+ break;
1760
+ default:
1761
+ throw new Error(
1762
+ `Unknown embed type "${type}". Valid types: popup, slider, float, fullpage (use init()), or widget (use mount()).`
1763
+ );
1764
+ }
1765
+ instances.set(researchId, instance);
1766
+ return instance;
1767
+ }
1768
+ function mount(container, config) {
1769
+ const { researchId } = config;
1770
+ const type = config.type === "chat" ? "float" : config.type ?? "widget";
1771
+ const el = typeof container === "string" ? document.querySelector(container) : container;
1772
+ if (!el) {
1773
+ throw new Error(`Container not found: ${container}`);
1774
+ }
1775
+ if (instances.has(researchId)) {
1776
+ instances.get(researchId).unmount();
1777
+ instances.delete(researchId);
1778
+ }
1779
+ let instance;
1780
+ switch (type) {
1781
+ case "widget":
1782
+ instance = createWidget(el, config);
1783
+ break;
1784
+ default:
1785
+ instance = init({ ...config, type });
1786
+ return instance;
1787
+ }
1788
+ instances.set(researchId, instance);
1789
+ return instance;
1790
+ }
1791
+ function destroy(researchId) {
1792
+ const instance = instances.get(researchId);
1793
+ if (instance) {
1794
+ instance.unmount();
1795
+ instances.delete(researchId);
1796
+ }
1797
+ }
1798
+ function destroyAll() {
1799
+ instances.forEach((instance) => instance.unmount());
1800
+ instances.clear();
1801
+ styledButtons.clear();
1802
+ teardownButtonThemeListener();
1803
+ }
1804
+ function autoInit() {
1805
+ if (!hasDom()) return;
1806
+ setupButtonThemeListener();
1807
+ document.querySelectorAll(`[${DATA_ATTRS.widget}]`).forEach((el) => {
1808
+ const researchId = el.getAttribute(DATA_ATTRS.widget);
1809
+ if (researchId && !instances.has(researchId)) {
1810
+ const params = parseParamsAttr(el);
1811
+ const brandConfig = extractBrandConfig(el);
1812
+ mount(el, { researchId, type: "widget", params, ...brandConfig });
1813
+ }
1814
+ });
1815
+ document.querySelectorAll(`[${DATA_ATTRS.fullpage}]`).forEach((el) => {
1816
+ const researchId = el.getAttribute(DATA_ATTRS.fullpage);
1817
+ if (researchId && !instances.has(researchId)) {
1818
+ const params = parseParamsAttr(el);
1819
+ const brandConfig = extractBrandConfig(el);
1820
+ init({ researchId, type: "fullpage", params, ...brandConfig });
1821
+ }
1822
+ });
1823
+ document.querySelectorAll(`[${DATA_ATTRS.popup}]`).forEach((el) => {
1824
+ if (el.hasAttribute("data-perspective-initialized")) return;
1825
+ el.setAttribute("data-perspective-initialized", "true");
1826
+ const researchId = el.getAttribute(DATA_ATTRS.popup);
1827
+ if (researchId) {
1828
+ const params = parseParamsAttr(el);
1829
+ const brandConfig = extractBrandConfig(el);
1830
+ styleButton(el, DEFAULT_THEME, brandConfig);
1831
+ el.addEventListener("click", (e) => {
1832
+ e.preventDefault();
1833
+ init({ researchId, type: "popup", params, ...brandConfig });
1834
+ });
1835
+ fetchConfig(researchId).then((config) => {
1836
+ styleButton(el, config, brandConfig);
1837
+ });
1838
+ }
1839
+ });
1840
+ document.querySelectorAll(`[${DATA_ATTRS.slider}]`).forEach((el) => {
1841
+ if (el.hasAttribute("data-perspective-initialized")) return;
1842
+ el.setAttribute("data-perspective-initialized", "true");
1843
+ const researchId = el.getAttribute(DATA_ATTRS.slider);
1844
+ if (researchId) {
1845
+ const params = parseParamsAttr(el);
1846
+ const brandConfig = extractBrandConfig(el);
1847
+ styleButton(el, DEFAULT_THEME, brandConfig);
1848
+ el.addEventListener("click", (e) => {
1849
+ e.preventDefault();
1850
+ init({ researchId, type: "slider", params, ...brandConfig });
1851
+ });
1852
+ fetchConfig(researchId).then((config) => {
1853
+ styleButton(el, config, brandConfig);
1854
+ });
1855
+ }
1856
+ });
1857
+ const floatSelector = `[${DATA_ATTRS.float}], [${DATA_ATTRS.chat}]`;
1858
+ const floatEl = document.querySelector(floatSelector);
1859
+ if (floatEl) {
1860
+ const researchId = floatEl.getAttribute(DATA_ATTRS.float) || floatEl.getAttribute(DATA_ATTRS.chat);
1861
+ if (researchId && !instances.has(researchId)) {
1862
+ const params = parseParamsAttr(floatEl);
1863
+ const brandConfig = extractBrandConfig(floatEl);
1864
+ init({
1865
+ researchId,
1866
+ type: "float",
1867
+ params,
1868
+ ...brandConfig,
1869
+ _themeConfig: DEFAULT_THEME
1870
+ });
1871
+ fetchConfig(researchId).then((config) => {
1872
+ const bubble = document.querySelector(
1873
+ '[data-perspective="float-bubble"]'
1874
+ );
1875
+ if (bubble && !floatEl.hasAttribute(DATA_ATTRS.noStyle)) {
1876
+ const isDark = resolveIsDark(brandConfig.theme);
1877
+ const bg = isDark ? brandConfig.brand?.dark?.primary ?? config.darkPrimaryColor : brandConfig.brand?.light?.primary ?? config.primaryColor;
1878
+ bubble.style.setProperty("--perspective-float-bg", bg);
1879
+ bubble.style.setProperty(
1880
+ "--perspective-float-shadow",
1881
+ `0 4px 12px ${bg}66`
1882
+ );
1883
+ bubble.style.setProperty(
1884
+ "--perspective-float-shadow-hover",
1885
+ `0 6px 16px ${bg}80`
1886
+ );
1887
+ bubble.style.backgroundColor = bg;
1888
+ bubble.style.boxShadow = `0 4px 12px ${bg}66`;
1889
+ }
1890
+ });
1891
+ }
1892
+ }
1893
+ }
1894
+ var Perspective = {
1895
+ // Configuration
1896
+ configure,
1897
+ getConfig,
1898
+ // Instance management
1899
+ init,
1900
+ mount,
1901
+ destroy,
1902
+ destroyAll,
1903
+ autoInit,
1904
+ // Direct creation functions (primary API)
1905
+ createWidget,
1906
+ openPopup,
1907
+ openSlider,
1908
+ createFloatBubble,
1909
+ createFullpage,
1910
+ // Legacy alias
1911
+ createChatBubble
1912
+ };
1913
+ if (hasDom() && !window.__PERSPECTIVE_SDK_INITIALIZED__) {
1914
+ window.__PERSPECTIVE_SDK_INITIALIZED__ = true;
1915
+ if (document.readyState === "loading") {
1916
+ document.addEventListener("DOMContentLoaded", autoInit, { once: true });
1917
+ } else {
1918
+ autoInit();
1919
+ }
1920
+ window.Perspective = Perspective;
1921
+ }
1922
+ var browser_default = Perspective;
1923
+ // Annotate the CommonJS export names for ESM import in node:
1924
+ 0 && (module.exports = {
1925
+ autoInit,
1926
+ configure,
1927
+ createChatBubble,
1928
+ createFloatBubble,
1929
+ createFullpage,
1930
+ createWidget,
1931
+ destroy,
1932
+ destroyAll,
1933
+ getConfig,
1934
+ init,
1935
+ mount,
1936
+ openPopup,
1937
+ openSlider
1938
+ });
1939
+ //# sourceMappingURL=browser.cjs.map