@cognior/iap-sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of @cognior/iap-sdk might be problematic. Click here for more details.

Files changed (60) hide show
  1. package/.github/copilot-instructions.md +95 -0
  2. package/README.md +79 -0
  3. package/TRACKING.md +105 -0
  4. package/USER_CONTEXT_README.md +284 -0
  5. package/package.json +154 -0
  6. package/src/config.ts +25 -0
  7. package/src/core/flowEngine.ts +1833 -0
  8. package/src/core/triggerManager.ts +1011 -0
  9. package/src/experiences/banner.ts +366 -0
  10. package/src/experiences/beacon.ts +668 -0
  11. package/src/experiences/hotspotTour.ts +654 -0
  12. package/src/experiences/hotspots.ts +566 -0
  13. package/src/experiences/modal.ts +1337 -0
  14. package/src/experiences/modalSequence.ts +1247 -0
  15. package/src/experiences/popover.ts +652 -0
  16. package/src/experiences/registry.ts +21 -0
  17. package/src/experiences/survey.ts +1639 -0
  18. package/src/experiences/taskList.ts +625 -0
  19. package/src/experiences/tooltip.ts +740 -0
  20. package/src/experiences/types.ts +395 -0
  21. package/src/experiences/walkthrough.ts +670 -0
  22. package/src/flow-sequence.ts +177 -0
  23. package/src/flows.ts +512 -0
  24. package/src/http.ts +61 -0
  25. package/src/index.ts +355 -0
  26. package/src/services/flowManager.ts +905 -0
  27. package/src/services/flowNormalizer.ts +74 -0
  28. package/src/services/locationContextService.ts +189 -0
  29. package/src/services/pageContextService.ts +221 -0
  30. package/src/services/userContextService.ts +286 -0
  31. package/src/state/appState.ts +0 -0
  32. package/src/state/hooks.ts +0 -0
  33. package/src/state/index.ts +0 -0
  34. package/src/state/migration.ts +0 -0
  35. package/src/state/store.ts +0 -0
  36. package/src/styles/banner.css.ts +0 -0
  37. package/src/styles/hotspot.css.ts +0 -0
  38. package/src/styles/hotspotTour.css.ts +0 -0
  39. package/src/styles/modal.css.ts +564 -0
  40. package/src/styles/survey.css.ts +1013 -0
  41. package/src/styles/taskList.css.ts +0 -0
  42. package/src/styles/tooltip.css.ts +149 -0
  43. package/src/styles/walkthrough.css.ts +0 -0
  44. package/src/tourUtils.ts +0 -0
  45. package/src/tracking.ts +223 -0
  46. package/src/utils/debounce.ts +66 -0
  47. package/src/utils/eventSequenceValidator.ts +124 -0
  48. package/src/utils/flowTrackingSystem.ts +524 -0
  49. package/src/utils/idGenerator.ts +155 -0
  50. package/src/utils/immediateValidationPrevention.ts +184 -0
  51. package/src/utils/normalize.ts +50 -0
  52. package/src/utils/privacyManager.ts +166 -0
  53. package/src/utils/ruleEvaluator.ts +199 -0
  54. package/src/utils/sanitize.ts +79 -0
  55. package/src/utils/selectors.ts +107 -0
  56. package/src/utils/stepExecutor.ts +345 -0
  57. package/src/utils/triggerNormalizer.ts +149 -0
  58. package/src/utils/validationInterceptor.ts +650 -0
  59. package/tsconfig.json +13 -0
  60. package/tsup.config.ts +13 -0
@@ -0,0 +1,654 @@
1
+ // src/experiences/hotspotTour.ts
2
+ // Sequential guided tour experience with hotspots
3
+
4
+ import { sanitizeHtml } from "../utils/sanitize";
5
+ import { register } from "./registry";
6
+ import { waitForElement } from "../utils/triggerNormalizer";
7
+ import type { HotspotTourPayload, HotspotTourStep, CornerPlacement } from "./types";
8
+
9
+ type HotspotTourFlow = { id: string; type: "hotspotTour"; payload: HotspotTourPayload };
10
+
11
+ const hotspotTourCssText = `
12
+ :root {
13
+ --dap-z-tour: 2147483635;
14
+ --dap-tour-primary: #3b82f6;
15
+ --dap-tour-bg: #ffffff;
16
+ --dap-tour-border: #e2e8f0;
17
+ --dap-tour-shadow: 0 10px 30px rgba(0, 0, 0, 0.15);
18
+ --dap-tour-text: #1e293b;
19
+ --dap-tour-text-muted: #64748b;
20
+ --dap-tour-overlay: rgba(15, 23, 42, 0.5);
21
+ }
22
+
23
+ .dap-tour-overlay {
24
+ position: fixed;
25
+ inset: 0;
26
+ background: var(--dap-tour-overlay);
27
+ z-index: var(--dap-z-tour);
28
+ pointer-events: none;
29
+ opacity: 0;
30
+ animation: tourOverlayFadeIn 0.3s ease-out forwards;
31
+ }
32
+
33
+ @keyframes tourOverlayFadeIn {
34
+ to { opacity: 1; }
35
+ }
36
+
37
+ .dap-tour-spotlight {
38
+ position: absolute;
39
+ border: 3px solid var(--dap-tour-primary);
40
+ border-radius: 8px;
41
+ pointer-events: none;
42
+ z-index: calc(var(--dap-z-tour) + 1);
43
+ box-shadow: 0 0 0 9999px rgba(15, 23, 42, 0.5);
44
+ transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
45
+ animation: tourSpotlightPulse 2s ease-in-out infinite;
46
+ }
47
+
48
+ @keyframes tourSpotlightPulse {
49
+ 0%, 100% {
50
+ border-color: var(--dap-tour-primary);
51
+ box-shadow: 0 0 0 9999px rgba(15, 23, 42, 0.5), 0 0 20px rgba(59, 130, 246, 0.3);
52
+ }
53
+ 50% {
54
+ border-color: #60a5fa;
55
+ box-shadow: 0 0 0 9999px rgba(15, 23, 42, 0.4), 0 0 30px rgba(59, 130, 246, 0.5);
56
+ }
57
+ }
58
+
59
+ .dap-tour-tooltip {
60
+ position: absolute;
61
+ background: var(--dap-tour-bg);
62
+ border: 1px solid var(--dap-tour-border);
63
+ border-radius: 12px;
64
+ box-shadow: var(--dap-tour-shadow);
65
+ padding: 20px;
66
+ max-width: 360px;
67
+ z-index: calc(var(--dap-z-tour) + 2);
68
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
69
+ opacity: 0;
70
+ transform: scale(0.9);
71
+ animation: tourTooltipIn 0.3s ease-out 0.2s forwards;
72
+ pointer-events: auto;
73
+ }
74
+
75
+ @keyframes tourTooltipIn {
76
+ to {
77
+ opacity: 1;
78
+ transform: scale(1);
79
+ }
80
+ }
81
+
82
+ .dap-tour-tooltip::before {
83
+ content: '';
84
+ position: absolute;
85
+ width: 0;
86
+ height: 0;
87
+ border: 12px solid transparent;
88
+ }
89
+
90
+ .dap-tour-tooltip.top::before {
91
+ bottom: -24px;
92
+ left: 50%;
93
+ transform: translateX(-50%);
94
+ border-top-color: var(--dap-tour-bg);
95
+ }
96
+
97
+ .dap-tour-tooltip.bottom::before {
98
+ top: -24px;
99
+ left: 50%;
100
+ transform: translateX(-50%);
101
+ border-bottom-color: var(--dap-tour-bg);
102
+ }
103
+
104
+ .dap-tour-tooltip.left::before {
105
+ right: -24px;
106
+ top: 50%;
107
+ transform: translateY(-50%);
108
+ border-left-color: var(--dap-tour-bg);
109
+ }
110
+
111
+ .dap-tour-tooltip.right::before {
112
+ left: -24px;
113
+ top: 50%;
114
+ transform: translateY(-50%);
115
+ border-right-color: var(--dap-tour-bg);
116
+ }
117
+
118
+ .dap-tour-header {
119
+ display: flex;
120
+ align-items: center;
121
+ justify-content: space-between;
122
+ margin-bottom: 12px;
123
+ }
124
+
125
+ .dap-tour-title {
126
+ font-size: 18px;
127
+ font-weight: 600;
128
+ color: var(--dap-tour-text);
129
+ margin: 0;
130
+ }
131
+
132
+ .dap-tour-step-indicator {
133
+ font-size: 12px;
134
+ color: var(--dap-tour-text-muted);
135
+ background: var(--dap-tour-border);
136
+ padding: 2px 8px;
137
+ border-radius: 12px;
138
+ font-weight: 500;
139
+ }
140
+
141
+ .dap-tour-description {
142
+ font-size: 14px;
143
+ color: var(--dap-tour-text-muted);
144
+ line-height: 1.5;
145
+ margin: 0 0 16px 0;
146
+ }
147
+
148
+ .dap-tour-actions {
149
+ display: flex;
150
+ gap: 8px;
151
+ justify-content: space-between;
152
+ align-items: center;
153
+ }
154
+
155
+ .dap-tour-nav {
156
+ display: flex;
157
+ gap: 8px;
158
+ }
159
+
160
+ .dap-tour-btn {
161
+ padding: 8px 16px;
162
+ border: 1px solid var(--dap-tour-border);
163
+ border-radius: 6px;
164
+ background: transparent;
165
+ color: var(--dap-tour-text);
166
+ font-size: 14px;
167
+ font-weight: 500;
168
+ cursor: pointer;
169
+ transition: all 0.15s ease;
170
+ }
171
+
172
+ .dap-tour-btn:hover {
173
+ background: var(--dap-tour-border);
174
+ }
175
+
176
+ .dap-tour-btn.primary {
177
+ background: var(--dap-tour-primary);
178
+ border-color: var(--dap-tour-primary);
179
+ color: white;
180
+ }
181
+
182
+ .dap-tour-btn.primary:hover {
183
+ background: #2563eb;
184
+ border-color: #2563eb;
185
+ }
186
+
187
+ .dap-tour-skip {
188
+ font-size: 12px;
189
+ color: var(--dap-tour-text-muted);
190
+ text-decoration: underline;
191
+ cursor: pointer;
192
+ padding: 4px;
193
+ border: none;
194
+ background: none;
195
+ }
196
+
197
+ .dap-tour-skip:hover {
198
+ color: var(--dap-tour-text);
199
+ }
200
+
201
+ .dap-tour-progress {
202
+ position: fixed;
203
+ top: 20px;
204
+ left: 50%;
205
+ transform: translateX(-50%);
206
+ background: var(--dap-tour-bg);
207
+ border: 1px solid var(--dap-tour-border);
208
+ border-radius: 20px;
209
+ padding: 8px 16px;
210
+ z-index: calc(var(--dap-z-tour) + 1);
211
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
212
+ box-shadow: var(--dap-tour-shadow);
213
+ animation: tourProgressIn 0.3s ease-out forwards;
214
+ opacity: 0;
215
+ }
216
+
217
+ @keyframes tourProgressIn {
218
+ to { opacity: 1; }
219
+ }
220
+
221
+ .dap-tour-progress-text {
222
+ font-size: 12px;
223
+ color: var(--dap-tour-text-muted);
224
+ margin-bottom: 4px;
225
+ text-align: center;
226
+ }
227
+
228
+ .dap-tour-progress-bar {
229
+ width: 200px;
230
+ height: 3px;
231
+ background: var(--dap-tour-border);
232
+ border-radius: 2px;
233
+ overflow: hidden;
234
+ }
235
+
236
+ .dap-tour-progress-fill {
237
+ height: 100%;
238
+ background: var(--dap-tour-primary);
239
+ border-radius: 2px;
240
+ transition: width 0.4s ease;
241
+ width: 0%;
242
+ }
243
+
244
+ .dap-tour-close {
245
+ position: fixed;
246
+ top: 20px;
247
+ right: 20px;
248
+ background: var(--dap-tour-bg);
249
+ border: 1px solid var(--dap-tour-border);
250
+ border-radius: 50%;
251
+ width: 40px;
252
+ height: 40px;
253
+ display: flex;
254
+ align-items: center;
255
+ justify-content: center;
256
+ cursor: pointer;
257
+ z-index: calc(var(--dap-z-tour) + 1);
258
+ box-shadow: var(--dap-tour-shadow);
259
+ color: var(--dap-tour-text-muted);
260
+ font-size: 18px;
261
+ transition: all 0.15s ease;
262
+ }
263
+
264
+ .dap-tour-close:hover {
265
+ background: var(--dap-tour-border);
266
+ color: var(--dap-tour-text);
267
+ }
268
+
269
+ @media (max-width: 640px) {
270
+ .dap-tour-tooltip {
271
+ max-width: 320px;
272
+ padding: 16px;
273
+ }
274
+
275
+ .dap-tour-progress {
276
+ top: 10px;
277
+ padding: 6px 12px;
278
+ }
279
+
280
+ .dap-tour-progress-bar {
281
+ width: 160px;
282
+ }
283
+
284
+ .dap-tour-close {
285
+ top: 10px;
286
+ right: 10px;
287
+ width: 36px;
288
+ height: 36px;
289
+ }
290
+
291
+ .dap-tour-actions {
292
+ flex-direction: column;
293
+ gap: 8px;
294
+ }
295
+
296
+ .dap-tour-nav {
297
+ width: 100%;
298
+ justify-content: space-between;
299
+ }
300
+ }
301
+ `;
302
+
303
+ export function registerHotspotTour() {
304
+ register("hotspotTour", renderHotspotTour);
305
+ }
306
+
307
+ export async function renderHotspotTour(flow: HotspotTourFlow): Promise<void> {
308
+ const { payload, id } = flow;
309
+
310
+ // Extract completion tracker
311
+ const completionTracker = payload._completionTracker;
312
+
313
+ // Ensure CSS is injected
314
+ ensureStyles();
315
+
316
+ // Tour state
317
+ let currentStepIndex = 0;
318
+ let currentSpotlight: HTMLElement | null = null;
319
+ let currentTooltip: HTMLElement | null = null;
320
+ let autoAdvanceTimer: number | undefined;
321
+
322
+ // Create overlay and UI elements
323
+ const overlay = createTourOverlay();
324
+ const progressEl = payload.showProgress ? createProgressIndicator(payload) : null;
325
+ const closeEl = createCloseButton();
326
+
327
+ document.documentElement.appendChild(overlay);
328
+ if (progressEl) document.documentElement.appendChild(progressEl);
329
+ document.documentElement.appendChild(closeEl);
330
+
331
+ // Event listeners
332
+ closeEl.addEventListener("click", completeTour);
333
+
334
+ // Keyboard navigation
335
+ document.addEventListener("keydown", handleKeyboard);
336
+
337
+ // Start the tour
338
+ await showStep(currentStepIndex);
339
+
340
+ async function showStep(stepIndex: number) {
341
+ if (stepIndex >= payload.steps.length) {
342
+ completeTour();
343
+ return;
344
+ }
345
+
346
+ const step = payload.steps[stepIndex];
347
+ currentStepIndex = stepIndex;
348
+
349
+ // Clear auto-advance timer
350
+ if (autoAdvanceTimer) {
351
+ clearTimeout(autoAdvanceTimer);
352
+ autoAdvanceTimer = undefined;
353
+ }
354
+
355
+ try {
356
+ // Wait for target element
357
+ const element = await waitForElement(step.selector, { timeout: 3000 });
358
+ if (!(element instanceof HTMLElement)) {
359
+ console.warn(`[DAP] Element not found for step: ${step.selector}`);
360
+ nextStep();
361
+ return;
362
+ }
363
+
364
+ // Create spotlight
365
+ createSpotlight(element);
366
+
367
+ // Create tooltip
368
+ createTooltip(step, element);
369
+
370
+ // Update progress
371
+ if (progressEl) {
372
+ updateProgress();
373
+ }
374
+
375
+ // Auto-advance if configured
376
+ if (payload.autoAdvance && payload.autoAdvance > 0) {
377
+ autoAdvanceTimer = window.setTimeout(() => {
378
+ nextStep();
379
+ }, payload.autoAdvance * 1000);
380
+ }
381
+
382
+ } catch (error) {
383
+ console.warn(`[DAP] Failed to show step ${stepIndex}:`, error);
384
+ nextStep();
385
+ }
386
+ }
387
+
388
+ function createSpotlight(element: HTMLElement) {
389
+ // Remove previous spotlight
390
+ if (currentSpotlight) {
391
+ currentSpotlight.remove();
392
+ }
393
+
394
+ const rect = element.getBoundingClientRect();
395
+ const spotlight = document.createElement("div");
396
+ spotlight.className = "dap-tour-spotlight";
397
+
398
+ // Position spotlight around element with padding
399
+ const padding = 8;
400
+ spotlight.style.left = `${rect.left + window.scrollX - padding}px`;
401
+ spotlight.style.top = `${rect.top + window.scrollY - padding}px`;
402
+ spotlight.style.width = `${rect.width + padding * 2}px`;
403
+ spotlight.style.height = `${rect.height + padding * 2}px`;
404
+
405
+ document.documentElement.appendChild(spotlight);
406
+ currentSpotlight = spotlight;
407
+
408
+ // Scroll element into view if needed
409
+ element.scrollIntoView({ behavior: "smooth", block: "center" });
410
+ }
411
+
412
+ function createTooltip(step: HotspotTourStep, element: HTMLElement) {
413
+ // Remove previous tooltip
414
+ if (currentTooltip) {
415
+ currentTooltip.remove();
416
+ }
417
+
418
+ const tooltip = document.createElement("div");
419
+ tooltip.className = "dap-tour-tooltip";
420
+
421
+ // Header
422
+ const header = document.createElement("div");
423
+ header.className = "dap-tour-header";
424
+
425
+ const title = document.createElement("h3");
426
+ title.className = "dap-tour-title";
427
+ title.textContent = step.title;
428
+
429
+ const indicator = document.createElement("span");
430
+ indicator.className = "dap-tour-step-indicator";
431
+ indicator.textContent = `${currentStepIndex + 1} / ${payload.steps.length}`;
432
+
433
+ header.appendChild(title);
434
+ header.appendChild(indicator);
435
+
436
+ // Description
437
+ const description = document.createElement("div");
438
+ description.className = "dap-tour-description";
439
+ description.innerHTML = sanitizeHtml(step.description);
440
+
441
+ // Actions
442
+ const actions = document.createElement("div");
443
+ actions.className = "dap-tour-actions";
444
+
445
+ // Skip button (if allowed)
446
+ if (payload.allowSkip) {
447
+ const skipBtn = document.createElement("button");
448
+ skipBtn.className = "dap-tour-skip";
449
+ skipBtn.textContent = "Skip tour";
450
+ skipBtn.addEventListener("click", completeTour);
451
+ actions.appendChild(skipBtn);
452
+ } else {
453
+ actions.appendChild(document.createElement("div")); // spacer
454
+ }
455
+
456
+ // Navigation
457
+ const nav = document.createElement("div");
458
+ nav.className = "dap-tour-nav";
459
+
460
+ // Previous button
461
+ if (currentStepIndex > 0) {
462
+ const prevBtn = document.createElement("button");
463
+ prevBtn.className = "dap-tour-btn";
464
+ prevBtn.textContent = "Previous";
465
+ prevBtn.addEventListener("click", previousStep);
466
+ nav.appendChild(prevBtn);
467
+ }
468
+
469
+ // Next/Close button
470
+ const nextBtn = document.createElement("button");
471
+ nextBtn.className = "dap-tour-btn primary";
472
+
473
+ if (currentStepIndex === payload.steps.length - 1) {
474
+ nextBtn.textContent = step.action === "close" ? "Close" : "Finish";
475
+ nextBtn.addEventListener("click", () => {
476
+ if (step.action === "custom" && step.customAction) {
477
+ window.dispatchEvent(new CustomEvent("dap-tour-action", {
478
+ detail: { action: step.customAction, tourId: id, stepId: step.id }
479
+ }));
480
+ }
481
+ completeTour();
482
+ });
483
+ } else {
484
+ nextBtn.textContent = "Next";
485
+ nextBtn.addEventListener("click", nextStep);
486
+ }
487
+
488
+ nav.appendChild(nextBtn);
489
+ actions.appendChild(nav);
490
+
491
+ // Assemble tooltip
492
+ tooltip.appendChild(header);
493
+ tooltip.appendChild(description);
494
+ tooltip.appendChild(actions);
495
+
496
+ // Position tooltip
497
+ positionTooltip(tooltip, element, step.placement || "bottom");
498
+
499
+ document.documentElement.appendChild(tooltip);
500
+ currentTooltip = tooltip;
501
+ }
502
+
503
+ function positionTooltip(tooltip: HTMLElement, element: HTMLElement, placement: CornerPlacement) {
504
+ const rect = element.getBoundingClientRect();
505
+
506
+ // Apply placement class for arrow
507
+ tooltip.classList.add(placement);
508
+
509
+ let left = 0;
510
+ let top = 0;
511
+
512
+ switch (placement) {
513
+ case "top":
514
+ left = rect.left + window.scrollX + rect.width / 2 - 180; // tooltip width / 2
515
+ top = rect.top + window.scrollY - 16 - 140; // tooltip height estimate
516
+ break;
517
+ case "bottom":
518
+ left = rect.left + window.scrollX + rect.width / 2 - 180;
519
+ top = rect.bottom + window.scrollY + 16;
520
+ break;
521
+ case "left":
522
+ left = rect.left + window.scrollX - 360 - 16;
523
+ top = rect.top + window.scrollY + rect.height / 2 - 70;
524
+ break;
525
+ case "right":
526
+ left = rect.right + window.scrollX + 16;
527
+ top = rect.top + window.scrollY + rect.height / 2 - 70;
528
+ break;
529
+ default:
530
+ left = rect.left + window.scrollX + rect.width / 2 - 180;
531
+ top = rect.bottom + window.scrollY + 16;
532
+ }
533
+
534
+ // Keep within viewport bounds
535
+ const viewportWidth = window.innerWidth;
536
+ const viewportHeight = window.innerHeight;
537
+
538
+ left = Math.max(10, Math.min(left, viewportWidth - 370));
539
+ top = Math.max(10, Math.min(top, viewportHeight - 160));
540
+
541
+ tooltip.style.left = `${left}px`;
542
+ tooltip.style.top = `${top}px`;
543
+ }
544
+
545
+ function nextStep() {
546
+ showStep(currentStepIndex + 1);
547
+ }
548
+
549
+ function previousStep() {
550
+ if (currentStepIndex > 0) {
551
+ showStep(currentStepIndex - 1);
552
+ }
553
+ }
554
+
555
+ function updateProgress() {
556
+ if (!progressEl) return;
557
+
558
+ const progress = ((currentStepIndex + 1) / payload.steps.length) * 100;
559
+ const progressText = progressEl.querySelector(".dap-tour-progress-text") as HTMLElement;
560
+ const progressFill = progressEl.querySelector(".dap-tour-progress-fill") as HTMLElement;
561
+
562
+ if (progressText) {
563
+ progressText.textContent = `Step ${currentStepIndex + 1} of ${payload.steps.length}`;
564
+ }
565
+ if (progressFill) {
566
+ progressFill.style.width = `${progress}%`;
567
+ }
568
+ }
569
+
570
+ function handleKeyboard(e: KeyboardEvent) {
571
+ switch (e.key) {
572
+ case "Escape":
573
+ if (payload.allowSkip) {
574
+ completeTour();
575
+ }
576
+ break;
577
+ case "ArrowRight":
578
+ e.preventDefault();
579
+ nextStep();
580
+ break;
581
+ case "ArrowLeft":
582
+ e.preventDefault();
583
+ previousStep();
584
+ break;
585
+ }
586
+ }
587
+
588
+ function completeTour() {
589
+ // Clear timer
590
+ if (autoAdvanceTimer) {
591
+ clearTimeout(autoAdvanceTimer);
592
+ }
593
+
594
+ // Remove event listener
595
+ document.removeEventListener("keydown", handleKeyboard);
596
+
597
+ // Clean up elements
598
+ overlay.remove();
599
+ if (currentSpotlight) currentSpotlight.remove();
600
+ if (currentTooltip) currentTooltip.remove();
601
+ if (progressEl) progressEl.remove();
602
+ closeEl.remove();
603
+
604
+ // Signal completion
605
+ if (completionTracker?.onComplete) {
606
+ console.debug(`[DAP] Completing hotspot tour flow: ${id}`);
607
+ completionTracker.onComplete();
608
+ }
609
+ }
610
+ }
611
+
612
+ function createTourOverlay(): HTMLElement {
613
+ const overlay = document.createElement("div");
614
+ overlay.className = "dap-tour-overlay";
615
+ return overlay;
616
+ }
617
+
618
+ function createProgressIndicator(payload: HotspotTourPayload): HTMLElement {
619
+ const progress = document.createElement("div");
620
+ progress.className = "dap-tour-progress";
621
+
622
+ const text = document.createElement("div");
623
+ text.className = "dap-tour-progress-text";
624
+ text.textContent = `Step 1 of ${payload.steps.length}`;
625
+
626
+ const bar = document.createElement("div");
627
+ bar.className = "dap-tour-progress-bar";
628
+
629
+ const fill = document.createElement("div");
630
+ fill.className = "dap-tour-progress-fill";
631
+
632
+ bar.appendChild(fill);
633
+ progress.appendChild(text);
634
+ progress.appendChild(bar);
635
+
636
+ return progress;
637
+ }
638
+
639
+ function createCloseButton(): HTMLElement {
640
+ const closeBtn = document.createElement("button");
641
+ closeBtn.className = "dap-tour-close";
642
+ closeBtn.innerHTML = "×";
643
+ closeBtn.setAttribute("aria-label", "Close tour");
644
+ return closeBtn;
645
+ }
646
+
647
+ function ensureStyles() {
648
+ if (!document.getElementById("dap-tour-style")) {
649
+ const style = document.createElement("style");
650
+ style.id = "dap-tour-style";
651
+ style.textContent = hotspotTourCssText;
652
+ document.head.appendChild(style);
653
+ }
654
+ }