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