@syntrologie/adapt-overlays 2.28.0 → 2.29.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.
@@ -0,0 +1,2978 @@
1
+ import {
2
+ __privateAdd,
3
+ __privateGet,
4
+ __privateSet
5
+ } from "./chunk-VHAA22YE.js";
6
+
7
+ // src/celebrations/effects/confetti.ts
8
+ var INTENSITY_COUNTS = { light: 50, medium: 100, heavy: 200 };
9
+ var DEFAULT_COLORS = [
10
+ "#ff0000",
11
+ "#00ff00",
12
+ "#0000ff",
13
+ "#ffff00",
14
+ "#ff00ff",
15
+ "#00ffff",
16
+ "#ff8800",
17
+ "#8800ff"
18
+ ];
19
+ var confettiEffect = {
20
+ init(width, height, config) {
21
+ const count = INTENSITY_COUNTS[config.intensity];
22
+ const colors = config.colors.length > 0 ? config.colors : DEFAULT_COLORS;
23
+ const particles = [];
24
+ for (let i = 0; i < count; i++) {
25
+ particles.push({
26
+ x: Math.random() * width,
27
+ y: Math.random() * -height * 0.3,
28
+ vx: (Math.random() - 0.5) * 4,
29
+ vy: Math.random() * 2 + 1,
30
+ rotation: Math.random() * Math.PI * 2,
31
+ rotationSpeed: (Math.random() - 0.5) * 0.2,
32
+ size: Math.random() * 6 + 4,
33
+ color: colors[Math.floor(Math.random() * colors.length)],
34
+ opacity: 1,
35
+ shape: Math.random() > 0.5 ? "rect" : "circle"
36
+ });
37
+ }
38
+ return particles;
39
+ },
40
+ update(particles, _dt, _elapsed) {
41
+ let anyVisible = false;
42
+ for (const p of particles) {
43
+ p.vy += 0.15;
44
+ p.vx *= 0.99;
45
+ p.x += p.vx;
46
+ p.y += p.vy;
47
+ p.rotation += p.rotationSpeed;
48
+ if (p.y > 0 && p.opacity > 0) {
49
+ if (p.y > 500) {
50
+ p.opacity -= 0.02;
51
+ if (p.opacity < 0) p.opacity = 0;
52
+ }
53
+ }
54
+ if (p.opacity > 0.01) {
55
+ anyVisible = true;
56
+ }
57
+ }
58
+ return anyVisible;
59
+ },
60
+ render(ctx, particles) {
61
+ for (const p of particles) {
62
+ if (p.opacity < 0.01) continue;
63
+ ctx.save();
64
+ ctx.globalAlpha = p.opacity;
65
+ ctx.fillStyle = p.color;
66
+ ctx.translate(p.x, p.y);
67
+ ctx.rotate(p.rotation);
68
+ if (p.shape === "circle") {
69
+ ctx.beginPath();
70
+ ctx.arc(0, 0, p.size / 2, 0, Math.PI * 2);
71
+ ctx.fill();
72
+ } else {
73
+ ctx.fillRect(-p.size / 2, -p.size / 2, p.size, p.size * 0.6);
74
+ }
75
+ ctx.restore();
76
+ }
77
+ }
78
+ };
79
+
80
+ // src/celebrations/effects/emoji-rain.ts
81
+ var INTENSITY_COUNTS2 = { light: 20, medium: 40, heavy: 80 };
82
+ var DEFAULT_EMOJI = "\u{1F389}";
83
+ var emojiRainEffect = {
84
+ init(width, _height, config) {
85
+ const count = INTENSITY_COUNTS2[config.intensity];
86
+ const emoji = typeof config.props?.emoji === "string" ? config.props.emoji : DEFAULT_EMOJI;
87
+ const particles = [];
88
+ for (let i = 0; i < count; i++) {
89
+ particles.push({
90
+ x: Math.random() * width,
91
+ y: Math.random() * -200,
92
+ vx: 0,
93
+ vy: Math.random() * 1.5 + 1,
94
+ rotation: 0,
95
+ rotationSpeed: 0,
96
+ size: Math.random() * 12 + 16,
97
+ color: "#000000",
98
+ opacity: 1,
99
+ shape: "emoji",
100
+ emoji,
101
+ data: {
102
+ /** Phase offset for horizontal wobble */
103
+ wobblePhase: Math.random() * Math.PI * 2,
104
+ /** Amplitude of horizontal wobble */
105
+ wobbleAmp: Math.random() * 1.5 + 0.5,
106
+ /** Original x for wobble base */
107
+ originX: 0
108
+ }
109
+ });
110
+ }
111
+ for (const p of particles) {
112
+ if (p.data) p.data.originX = p.x;
113
+ }
114
+ return particles;
115
+ },
116
+ update(particles, _dt, elapsed) {
117
+ let anyVisible = false;
118
+ for (const p of particles) {
119
+ p.y += p.vy;
120
+ const phase = p.data?.wobblePhase ?? 0;
121
+ const amp = p.data?.wobbleAmp ?? 1;
122
+ const originX = p.data?.originX ?? p.x;
123
+ p.x = originX + Math.sin(elapsed * 3e-3 + phase) * amp * 20;
124
+ if (p.y > 600) {
125
+ p.opacity -= 0.02;
126
+ if (p.opacity < 0) p.opacity = 0;
127
+ }
128
+ if (p.opacity > 0.01) {
129
+ anyVisible = true;
130
+ }
131
+ }
132
+ return anyVisible;
133
+ },
134
+ render(ctx, particles) {
135
+ for (const p of particles) {
136
+ if (p.opacity < 0.01 || !p.emoji) continue;
137
+ ctx.save();
138
+ ctx.globalAlpha = p.opacity;
139
+ ctx.font = `${p.size}px serif`;
140
+ ctx.textAlign = "center";
141
+ ctx.textBaseline = "middle";
142
+ ctx.fillText(p.emoji, p.x, p.y);
143
+ ctx.restore();
144
+ }
145
+ }
146
+ };
147
+
148
+ // src/celebrations/effects/fireworks.ts
149
+ var PARTICLES_PER_BURST = { light: 20, medium: 40, heavy: 80 };
150
+ var BURST_COUNTS = { light: 3, medium: 4, heavy: 5 };
151
+ var fireworksEffect = {
152
+ init(width, height, config) {
153
+ const perBurst = PARTICLES_PER_BURST[config.intensity];
154
+ const burstCount = BURST_COUNTS[config.intensity];
155
+ const colors = config.colors.length > 0 ? config.colors : ["#ff4444", "#44ff44", "#4444ff"];
156
+ const particles = [];
157
+ for (let b = 0; b < burstCount; b++) {
158
+ const cx = Math.random() * width * 0.8 + width * 0.1;
159
+ const cy = Math.random() * height * 0.6;
160
+ const burstColor = colors[Math.floor(Math.random() * colors.length)];
161
+ for (let i = 0; i < perBurst; i++) {
162
+ const angle = Math.PI * 2 * i / perBurst + (Math.random() - 0.5) * 0.3;
163
+ const speed = Math.random() * 3 + 2;
164
+ particles.push({
165
+ x: cx,
166
+ y: cy,
167
+ vx: Math.cos(angle) * speed,
168
+ vy: Math.sin(angle) * speed,
169
+ rotation: 0,
170
+ rotationSpeed: 0,
171
+ size: Math.random() * 3 + 2,
172
+ color: burstColor,
173
+ opacity: 1,
174
+ shape: "circle",
175
+ data: { centerX: cx, centerY: cy }
176
+ });
177
+ }
178
+ }
179
+ return particles;
180
+ },
181
+ update(particles, _dt, _elapsed) {
182
+ let anyVisible = false;
183
+ for (const p of particles) {
184
+ p.vx *= 0.97;
185
+ p.vy *= 0.97;
186
+ p.vy += 0.03;
187
+ p.x += p.vx;
188
+ p.y += p.vy;
189
+ p.opacity -= 8e-3;
190
+ if (p.opacity < 0) p.opacity = 0;
191
+ if (p.opacity > 0.01) {
192
+ anyVisible = true;
193
+ }
194
+ }
195
+ return anyVisible;
196
+ },
197
+ render(ctx, particles) {
198
+ for (const p of particles) {
199
+ if (p.opacity < 0.01) continue;
200
+ ctx.save();
201
+ ctx.globalAlpha = p.opacity;
202
+ ctx.fillStyle = p.color;
203
+ ctx.shadowBlur = 12;
204
+ ctx.shadowColor = p.color;
205
+ ctx.beginPath();
206
+ ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
207
+ ctx.fill();
208
+ ctx.restore();
209
+ }
210
+ }
211
+ };
212
+
213
+ // src/celebrations/effects/sparkles.ts
214
+ var INTENSITY_COUNTS3 = { light: 30, medium: 60, heavy: 120 };
215
+ var sparklesEffect = {
216
+ init(width, height, config) {
217
+ const count = INTENSITY_COUNTS3[config.intensity];
218
+ const colors = config.colors.length > 0 ? config.colors : ["#ffd700", "#ffffff", "#fffacd"];
219
+ const particles = [];
220
+ for (let i = 0; i < count; i++) {
221
+ particles.push({
222
+ x: Math.random() * width,
223
+ y: Math.random() * height,
224
+ vx: (Math.random() - 0.5) * 0.3,
225
+ vy: -(Math.random() * 0.5 + 0.2),
226
+ rotation: Math.random() * Math.PI * 2,
227
+ rotationSpeed: (Math.random() - 0.5) * 0.1,
228
+ size: Math.random() * 4 + 2,
229
+ color: colors[Math.floor(Math.random() * colors.length)],
230
+ opacity: Math.random() * 0.5 + 0.5,
231
+ shape: "circle",
232
+ data: {
233
+ /** Phase offset for sine-wave twinkle */
234
+ phase: Math.random() * Math.PI * 2,
235
+ /** Base opacity before twinkle modulation */
236
+ baseOpacity: Math.random() * 0.5 + 0.5
237
+ }
238
+ });
239
+ }
240
+ return particles;
241
+ },
242
+ update(particles, _dt, elapsed) {
243
+ let anyVisible = false;
244
+ for (const p of particles) {
245
+ p.x += p.vx;
246
+ p.y += p.vy;
247
+ p.rotation += p.rotationSpeed;
248
+ if (p.data) {
249
+ p.data.baseOpacity = (p.data.baseOpacity ?? 1) - 1e-3;
250
+ if (p.data.baseOpacity < 0) p.data.baseOpacity = 0;
251
+ }
252
+ const phase = p.data?.phase ?? 0;
253
+ const baseOpacity = p.data?.baseOpacity ?? 1;
254
+ if (baseOpacity <= 0.01) {
255
+ p.opacity = 0;
256
+ } else {
257
+ const twinkle = Math.sin(elapsed * 5e-3 + phase) * 0.4 + 0.6;
258
+ p.opacity = baseOpacity * twinkle;
259
+ }
260
+ if (p.opacity > 0.01) {
261
+ anyVisible = true;
262
+ }
263
+ }
264
+ return anyVisible;
265
+ },
266
+ render(ctx, particles) {
267
+ for (const p of particles) {
268
+ if (p.opacity < 0.01) continue;
269
+ ctx.save();
270
+ ctx.globalAlpha = p.opacity;
271
+ ctx.fillStyle = p.color;
272
+ ctx.translate(p.x, p.y);
273
+ ctx.rotate(p.rotation);
274
+ const s = p.size;
275
+ ctx.beginPath();
276
+ ctx.moveTo(0, -s);
277
+ ctx.lineTo(s * 0.3, -s * 0.3);
278
+ ctx.lineTo(s, 0);
279
+ ctx.lineTo(s * 0.3, s * 0.3);
280
+ ctx.lineTo(0, s);
281
+ ctx.lineTo(-s * 0.3, s * 0.3);
282
+ ctx.lineTo(-s, 0);
283
+ ctx.lineTo(-s * 0.3, -s * 0.3);
284
+ ctx.closePath();
285
+ ctx.fill();
286
+ ctx.restore();
287
+ }
288
+ }
289
+ };
290
+
291
+ // src/celebrations/engine.ts
292
+ var CelebrationEngine = class {
293
+ constructor() {
294
+ this.canvas = null;
295
+ this.ctx = null;
296
+ this.rafId = null;
297
+ this.particles = [];
298
+ this.startTime = 0;
299
+ this.lastFrame = 0;
300
+ this.duration = 0;
301
+ this.effect = null;
302
+ this.container = null;
303
+ }
304
+ start(container, effect, config) {
305
+ const prefersReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
306
+ if (prefersReducedMotion) {
307
+ return;
308
+ }
309
+ this.container = container;
310
+ this.effect = effect;
311
+ this.duration = config.duration;
312
+ const canvas = document.createElement("canvas");
313
+ canvas.setAttribute("data-syntro-celebrate", "");
314
+ Object.assign(canvas.style, {
315
+ position: "fixed",
316
+ inset: "0",
317
+ pointerEvents: "none",
318
+ zIndex: "2147483646"
319
+ });
320
+ const dpr = window.devicePixelRatio || 1;
321
+ const width = window.innerWidth;
322
+ const height = window.innerHeight;
323
+ canvas.width = width * dpr;
324
+ canvas.height = height * dpr;
325
+ canvas.style.width = `${width}px`;
326
+ canvas.style.height = `${height}px`;
327
+ const ctx = canvas.getContext("2d");
328
+ if (!ctx) {
329
+ return;
330
+ }
331
+ ctx.scale(dpr, dpr);
332
+ this.canvas = canvas;
333
+ this.ctx = ctx;
334
+ container.appendChild(canvas);
335
+ this.particles = effect.init(width, height, config);
336
+ this.startTime = performance.now();
337
+ this.lastFrame = this.startTime;
338
+ this.tick = this.tick.bind(this);
339
+ this.rafId = requestAnimationFrame(this.tick);
340
+ }
341
+ stop() {
342
+ if (this.rafId !== null) {
343
+ cancelAnimationFrame(this.rafId);
344
+ this.rafId = null;
345
+ }
346
+ if (this.canvas && this.container) {
347
+ this.canvas.remove();
348
+ this.canvas = null;
349
+ }
350
+ this.ctx = null;
351
+ this.effect = null;
352
+ this.container = null;
353
+ this.particles = [];
354
+ }
355
+ tick(now) {
356
+ if (!this.ctx || !this.canvas || !this.effect) return;
357
+ const elapsed = now - this.startTime;
358
+ const dt = now - this.lastFrame;
359
+ this.lastFrame = now;
360
+ if (elapsed >= this.duration) {
361
+ this.stop();
362
+ return;
363
+ }
364
+ const width = this.canvas.width / (window.devicePixelRatio || 1);
365
+ const height = this.canvas.height / (window.devicePixelRatio || 1);
366
+ this.ctx.clearRect(0, 0, width, height);
367
+ const alive = this.effect.update(this.particles, dt, elapsed);
368
+ if (!alive) {
369
+ this.stop();
370
+ return;
371
+ }
372
+ this.effect.render(this.ctx, this.particles);
373
+ this.rafId = requestAnimationFrame(this.tick);
374
+ }
375
+ };
376
+
377
+ // src/celebrations/index.ts
378
+ var FALLBACK_COLORS = [
379
+ "#ff0000",
380
+ "#00ff00",
381
+ "#0000ff",
382
+ "#ffff00",
383
+ "#ff00ff",
384
+ "#00ffff",
385
+ "#ff8800",
386
+ "#8800ff"
387
+ ];
388
+ function buildThemePalette(primary, hover) {
389
+ return [primary, hover, `${primary}cc`, `${hover}cc`, "#ffffff", `${primary}80`];
390
+ }
391
+ function readThemeColors(overlayRoot) {
392
+ try {
393
+ const styles = getComputedStyle(overlayRoot);
394
+ const primary = styles.getPropertyValue("--sc-color-primary")?.trim();
395
+ const hover = styles.getPropertyValue("--sc-color-primary-hover")?.trim();
396
+ if (primary?.startsWith("#") && primary.length >= 7) {
397
+ return buildThemePalette(primary, hover || primary);
398
+ }
399
+ } catch {
400
+ }
401
+ return null;
402
+ }
403
+ var effectRegistry = /* @__PURE__ */ new Map([
404
+ ["confetti", confettiEffect],
405
+ ["fireworks", fireworksEffect],
406
+ ["sparkles", sparklesEffect],
407
+ ["emoji-rain", emojiRainEffect]
408
+ ]);
409
+ var executeCelebrate = async (action, context) => {
410
+ const effect = effectRegistry.get(action.effect);
411
+ if (!effect) {
412
+ console.warn(
413
+ `[adaptive-overlays] Unknown celebration effect: "${action.effect}". Available: ${[...effectRegistry.keys()].join(", ")}`
414
+ );
415
+ return { cleanup: () => {
416
+ } };
417
+ }
418
+ const colors = action.colors ?? readThemeColors(context.overlayRoot) ?? FALLBACK_COLORS;
419
+ const config = {
420
+ duration: action.duration ?? 3e3,
421
+ intensity: action.intensity ?? "medium",
422
+ colors,
423
+ props: action.props
424
+ };
425
+ const engine = new CelebrationEngine();
426
+ engine.start(context.overlayRoot, effect, config);
427
+ context.publishEvent("action.applied", {
428
+ id: context.generateId(),
429
+ kind: "overlays:celebrate",
430
+ effect: action.effect
431
+ });
432
+ return {
433
+ cleanup: () => {
434
+ engine.stop();
435
+ }
436
+ };
437
+ };
438
+
439
+ // src/cta-navigation.ts
440
+ var ALLOWED_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:", "mailto:", "tel:"]);
441
+ function isSafeNavigationHref(href) {
442
+ if (typeof href !== "string" || href.trim().length === 0) return false;
443
+ let parsed;
444
+ try {
445
+ parsed = new URL(href, "https://syntro.local/");
446
+ } catch {
447
+ return false;
448
+ }
449
+ return ALLOWED_PROTOCOLS.has(parsed.protocol);
450
+ }
451
+ function navigateForCta(input) {
452
+ if (input.actionId === "dismiss") return false;
453
+ if (!input.href || !isSafeNavigationHref(input.href)) return false;
454
+ const href = input.href;
455
+ if (input.target === "_blank") {
456
+ window.open(href, "_blank", "noopener,noreferrer");
457
+ } else {
458
+ window.location.assign(href);
459
+ }
460
+ return true;
461
+ }
462
+
463
+ // src/executors/tour.ts
464
+ var ACTIVE_TOUR_KEY = "syntro_active_tour";
465
+ var activeTours = /* @__PURE__ */ new Map();
466
+ function getTourState(tourId) {
467
+ try {
468
+ const data = localStorage.getItem(ACTIVE_TOUR_KEY);
469
+ if (!data) return null;
470
+ const state = JSON.parse(data);
471
+ if (state.tourId !== tourId) return null;
472
+ return state;
473
+ } catch {
474
+ return null;
475
+ }
476
+ }
477
+ function saveTourState(state) {
478
+ try {
479
+ localStorage.setItem(ACTIVE_TOUR_KEY, JSON.stringify(state));
480
+ } catch {
481
+ }
482
+ }
483
+ function clearTourState() {
484
+ try {
485
+ localStorage.removeItem(ACTIVE_TOUR_KEY);
486
+ } catch {
487
+ }
488
+ }
489
+ function getCurrentRoute() {
490
+ return window.location.pathname;
491
+ }
492
+ function stepMatchesRoute(step) {
493
+ if (!step.route) return true;
494
+ const currentRoute = getCurrentRoute();
495
+ if (step.route.includes("*")) {
496
+ const pattern = new RegExp(`^${step.route.replace(/\*/g, ".*")}$`);
497
+ return pattern.test(currentRoute);
498
+ }
499
+ return currentRoute === step.route;
500
+ }
501
+ var executeTour = async (action, context) => {
502
+ const { tourId, steps, startStep, autoStart = true } = action;
503
+ if (steps.length === 0) {
504
+ throw new Error(`Tour "${tourId}" has no steps`);
505
+ }
506
+ if (activeTours.has(tourId)) {
507
+ return {
508
+ cleanup: async () => {
509
+ const existing = activeTours.get(tourId);
510
+ if (existing) {
511
+ await existing.cleanup();
512
+ }
513
+ }
514
+ };
515
+ }
516
+ if (!context.applyAction) {
517
+ throw new Error("Tour executor requires applyAction in context");
518
+ }
519
+ let state = getTourState(tourId);
520
+ const isResumingTour = !!state;
521
+ if (!isResumingTour && !autoStart) {
522
+ return {
523
+ cleanup: () => {
524
+ }
525
+ };
526
+ }
527
+ if (!state) {
528
+ state = {
529
+ tourId,
530
+ currentStepId: startStep || steps[0].id,
531
+ startedAt: Date.now()
532
+ };
533
+ saveTourState(state);
534
+ }
535
+ let currentStepIndex = steps.findIndex((s) => s.id === state.currentStepId);
536
+ if (currentStepIndex === -1) {
537
+ const initialStepId = startStep || steps[0].id;
538
+ currentStepIndex = steps.findIndex((s) => s.id === initialStepId);
539
+ if (currentStepIndex === -1) currentStepIndex = 0;
540
+ state.currentStepId = steps[currentStepIndex].id;
541
+ saveTourState(state);
542
+ }
543
+ const currentStep = steps[currentStepIndex];
544
+ if (!stepMatchesRoute(currentStep)) {
545
+ context.publishEvent("tour.waiting_for_route", {
546
+ tourId,
547
+ stepId: currentStep.id,
548
+ expectedRoute: currentStep.route,
549
+ currentRoute: getCurrentRoute()
550
+ });
551
+ return {
552
+ cleanup: () => {
553
+ }
554
+ };
555
+ }
556
+ let isDestroyed = false;
557
+ let currentActionHandle = null;
558
+ let eventUnsubscribe = null;
559
+ let routeWatcher = null;
560
+ const cleanupCurrentStep = async () => {
561
+ if (eventUnsubscribe) {
562
+ eventUnsubscribe();
563
+ eventUnsubscribe = null;
564
+ }
565
+ if (currentActionHandle?.isApplied()) {
566
+ await currentActionHandle.revert();
567
+ currentActionHandle = null;
568
+ }
569
+ };
570
+ const advanceToStep = async (nextStepId) => {
571
+ if (isDestroyed) return;
572
+ await cleanupCurrentStep();
573
+ if (nextStepId === "end") {
574
+ clearTourState();
575
+ context.publishEvent("tour.completed", {
576
+ tourId,
577
+ totalSteps: steps.length
578
+ });
579
+ isDestroyed = true;
580
+ return;
581
+ }
582
+ const nextStep = steps.find((s) => s.id === nextStepId);
583
+ if (!nextStep) {
584
+ console.error(`[Tour] Step "${nextStepId}" not found`);
585
+ return;
586
+ }
587
+ state.currentStepId = nextStepId;
588
+ saveTourState(state);
589
+ context.publishEvent("tour.step_changed", {
590
+ tourId,
591
+ previousStepId: currentStep.id,
592
+ nextStepId
593
+ });
594
+ if (nextStep.route && nextStep.route !== getCurrentRoute()) {
595
+ context.publishEvent("tour.awaiting_navigation", {
596
+ tourId,
597
+ stepId: nextStepId,
598
+ targetRoute: nextStep.route
599
+ });
600
+ return;
601
+ }
602
+ await executeStep(nextStep);
603
+ };
604
+ const executeStep = async (step) => {
605
+ if (isDestroyed) return;
606
+ context.publishEvent("tour.step_started", {
607
+ tourId,
608
+ stepId: step.id,
609
+ stepIndex: steps.findIndex((s) => s.id === step.id),
610
+ totalSteps: steps.length
611
+ });
612
+ try {
613
+ currentActionHandle = await context.applyAction(step.action);
614
+ } catch (error) {
615
+ console.error(`[Tour] Failed to execute step "${step.id}":`, error);
616
+ context.publishEvent("tour.step_failed", {
617
+ tourId,
618
+ stepId: step.id,
619
+ error: String(error)
620
+ });
621
+ return;
622
+ }
623
+ if (step.onAction && context.subscribeEvent) {
624
+ eventUnsubscribe = context.subscribeEvent("action.modal_cta_clicked", (props) => {
625
+ const actionId = props?.actionId;
626
+ if (actionId && step.onAction) {
627
+ const nextStepId = step.onAction[actionId];
628
+ if (nextStepId) {
629
+ advanceToStep(nextStepId);
630
+ }
631
+ }
632
+ });
633
+ const tooltipUnsubscribe = context.subscribeEvent("action.tooltip_cta_clicked", (props) => {
634
+ const actionId = props?.actionId;
635
+ if (actionId && step.onAction) {
636
+ const nextStepId = step.onAction[actionId];
637
+ if (nextStepId) {
638
+ advanceToStep(nextStepId);
639
+ }
640
+ }
641
+ });
642
+ const originalUnsubscribe = eventUnsubscribe;
643
+ eventUnsubscribe = () => {
644
+ originalUnsubscribe();
645
+ tooltipUnsubscribe();
646
+ };
647
+ }
648
+ };
649
+ const setupRouteWatcher = () => {
650
+ let lastPath = getCurrentRoute();
651
+ const checkRoute = () => {
652
+ const currentPath = getCurrentRoute();
653
+ if (currentPath !== lastPath) {
654
+ lastPath = currentPath;
655
+ context.publishEvent("tour.route_changed", {
656
+ tourId,
657
+ newRoute: currentPath
658
+ });
659
+ }
660
+ };
661
+ if (context.subscribeNavigation) {
662
+ return context.subscribeNavigation(() => checkRoute());
663
+ }
664
+ window.addEventListener("popstate", checkRoute);
665
+ return () => {
666
+ window.removeEventListener("popstate", checkRoute);
667
+ };
668
+ };
669
+ routeWatcher = setupRouteWatcher();
670
+ if (!isResumingTour) {
671
+ context.publishEvent("tour.started", {
672
+ tourId,
673
+ totalSteps: steps.length,
674
+ startStepId: state.currentStepId
675
+ });
676
+ } else {
677
+ context.publishEvent("tour.resumed", {
678
+ tourId,
679
+ stepId: state.currentStepId
680
+ });
681
+ }
682
+ await executeStep(currentStep);
683
+ const cleanup = async () => {
684
+ isDestroyed = true;
685
+ activeTours.delete(tourId);
686
+ await cleanupCurrentStep();
687
+ if (routeWatcher) {
688
+ routeWatcher();
689
+ }
690
+ context.publishEvent("tour.paused", {
691
+ tourId,
692
+ stepId: state.currentStepId
693
+ });
694
+ };
695
+ activeTours.set(tourId, { cleanup });
696
+ return { cleanup };
697
+ };
698
+
699
+ // ../../design-system/dist/tokens/colors.js
700
+ var base = {
701
+ white: "#ffffff",
702
+ black: "#000000"
703
+ };
704
+ var brand = {
705
+ 0: "#2c0b0a",
706
+ 1: "#5b1715",
707
+ 2: "#89221f",
708
+ 3: "#b72e2a",
709
+ 4: "#d44844",
710
+ 5: "#dd6d69",
711
+ 6: "#e5918f",
712
+ 7: "#eeb6b4",
713
+ 8: "#f6dada",
714
+ 9: "#faebea"
715
+ };
716
+ var slateGrey = {
717
+ 0: "#07080a",
718
+ 1: "#0f1318",
719
+ 2: "#0e1114",
720
+ 3: "#1c222a",
721
+ 4: "#2b333f",
722
+ 5: "#394454",
723
+ 6: "#475569",
724
+ 7: "#677384",
725
+ 8: "#87919f",
726
+ 9: "#a8afba",
727
+ 10: "#cbd0d7",
728
+ 11: "#e8eaee",
729
+ 12: "#f6f7f9"
730
+ };
731
+ var green = {
732
+ 0: "#07230a",
733
+ 1: "#0e4514",
734
+ 2: "#16681e",
735
+ 3: "#1d8a28",
736
+ 4: "#24ad32",
737
+ 5: "#4fbd5a",
738
+ 6: "#7acd82",
739
+ 7: "#a5deab",
740
+ 8: "#d0eed3",
741
+ 9: "#e5f6e7"
742
+ };
743
+ var yellow = {
744
+ 0: "#301f09",
745
+ 1: "#5f3e12",
746
+ 2: "#8f5e1b",
747
+ 3: "#be7d24",
748
+ 4: "#ee9c2d",
749
+ 5: "#f1b057",
750
+ 6: "#f5c481",
751
+ 7: "#f8d7ab",
752
+ 8: "#fcebd5",
753
+ 9: "#fdf5ea"
754
+ };
755
+ var red = {
756
+ 0: "#330707",
757
+ 1: "#660f0e",
758
+ 2: "#991616",
759
+ 3: "#cc1e1d",
760
+ 4: "#ff2524",
761
+ 5: "#ff5150",
762
+ 6: "#ff7c7c",
763
+ 7: "#ffa8a7",
764
+ 8: "#ffd3d3",
765
+ 9: "#ffe9e9"
766
+ };
767
+ var blue = {
768
+ 0: "#051533",
769
+ 1: "#0a2a66",
770
+ 2: "#0f3f98",
771
+ 3: "#1454cb",
772
+ 4: "#1969fe",
773
+ 5: "#4787fe",
774
+ 6: "#75a5fe",
775
+ 7: "#a3c3ff",
776
+ 8: "#d1e1ff",
777
+ 9: "#e8f0ff"
778
+ };
779
+ var orange = {
780
+ 0: "#662500",
781
+ 1: "#993d00",
782
+ 2: "#cc5800",
783
+ 3: "#ff7700",
784
+ 4: "#fea85d",
785
+ 5: "#fec58f",
786
+ 6: "#ffd6ae",
787
+ 7: "#fee6cd",
788
+ 8: "#fff1e1",
789
+ 9: "#fff8f0"
790
+ };
791
+ var purple = {
792
+ 0: "#151229",
793
+ 1: "#2a2452",
794
+ 2: "#40357c",
795
+ 3: "#5547a5",
796
+ 4: "#6a59ce",
797
+ 5: "#887ad8",
798
+ 6: "#a69be2",
799
+ 7: "#c3bdeb",
800
+ 8: "#e1def5",
801
+ 9: "#f0eefa"
802
+ };
803
+ var pink = {
804
+ 0: "#37091f",
805
+ 1: "#69123c",
806
+ 2: "#9b1c58",
807
+ 3: "#cd2575",
808
+ 4: "#ff2e92",
809
+ 5: "#ff58a8",
810
+ 6: "#ff82be",
811
+ 7: "#ffabd3",
812
+ 8: "#ffd5e9",
813
+ 9: "#ffeaf4"
814
+ };
815
+ var text = {
816
+ primary: slateGrey[10],
817
+ secondary: slateGrey[9],
818
+ tertiary: slateGrey[8]
819
+ };
820
+ var background = {
821
+ primary: slateGrey[2],
822
+ secondary: slateGrey[0]
823
+ };
824
+ var border = {
825
+ primary: slateGrey[4],
826
+ secondary: slateGrey[3]
827
+ };
828
+ var button = {
829
+ primary: {
830
+ text: base.white,
831
+ icon: base.white,
832
+ border: brand[3],
833
+ backgroundDefault: brand[3],
834
+ backgroundHover: brand[2]
835
+ },
836
+ neutral: {
837
+ text: slateGrey[10],
838
+ textHover: base.white,
839
+ icon: slateGrey[10],
840
+ iconHover: base.white,
841
+ border: slateGrey[4],
842
+ background: slateGrey[2]
843
+ },
844
+ link: {
845
+ text: base.white,
846
+ icon: base.white,
847
+ hover: brand[5]
848
+ },
849
+ error: {
850
+ text: red[5],
851
+ hover: red[6]
852
+ },
853
+ success: {
854
+ text: green[5],
855
+ hover: green[6]
856
+ }
857
+ };
858
+ var badge = {
859
+ slateGrey: {
860
+ content: slateGrey[10],
861
+ pillOutline: slateGrey[10],
862
+ borderPrimary: slateGrey[5],
863
+ borderSecondary: slateGrey[5],
864
+ background: slateGrey[3]
865
+ },
866
+ brand: {
867
+ content: brand[9],
868
+ pillOutline: brand[9],
869
+ borderPrimary: brand[6],
870
+ borderSecondary: brand[6],
871
+ background: brand[0]
872
+ },
873
+ red: {
874
+ content: red[8],
875
+ pillOutline: red[4],
876
+ borderPrimary: red[2],
877
+ borderSecondary: red[2],
878
+ background: red[0]
879
+ },
880
+ yellow: {
881
+ content: yellow[8],
882
+ pillOutline: yellow[4],
883
+ borderPrimary: yellow[2],
884
+ borderSecondary: yellow[2],
885
+ background: yellow[0]
886
+ },
887
+ green: {
888
+ content: green[8],
889
+ pillOutline: green[4],
890
+ borderPrimary: green[2],
891
+ borderSecondary: green[2],
892
+ background: green[0]
893
+ },
894
+ purple: {
895
+ content: purple[8],
896
+ pillOutline: purple[4],
897
+ borderPrimary: purple[2],
898
+ borderSecondary: purple[2],
899
+ background: purple[0]
900
+ },
901
+ blue: {
902
+ content: blue[8],
903
+ pillOutline: blue[4],
904
+ borderPrimary: blue[2],
905
+ borderSecondary: blue[2],
906
+ background: blue[0]
907
+ },
908
+ orange: {
909
+ content: orange[8],
910
+ pillOutline: orange[4],
911
+ borderPrimary: orange[2],
912
+ borderSecondary: orange[2],
913
+ background: orange[0]
914
+ },
915
+ pink: {
916
+ content: pink[8],
917
+ pillOutline: pink[4],
918
+ borderPrimary: pink[2],
919
+ borderSecondary: pink[2],
920
+ background: pink[0]
921
+ }
922
+ };
923
+ var badgeBanner = {
924
+ green: {
925
+ content: green[8],
926
+ border: green[2],
927
+ background: green[0]
928
+ },
929
+ yellow: {
930
+ content: yellow[8],
931
+ border: yellow[2],
932
+ background: yellow[0]
933
+ },
934
+ red: {
935
+ content: red[8],
936
+ border: red[2],
937
+ background: red[0]
938
+ }
939
+ };
940
+ var alert = {
941
+ green: {
942
+ content: green[1],
943
+ background: green[9]
944
+ },
945
+ yellow: {
946
+ content: yellow[1],
947
+ background: yellow[9]
948
+ },
949
+ red: {
950
+ content: red[1],
951
+ background: red[9]
952
+ }
953
+ };
954
+ var tag = {
955
+ content: slateGrey[10],
956
+ border: slateGrey[4],
957
+ background: slateGrey[3]
958
+ };
959
+ var menu = {
960
+ backgroundDefault: slateGrey[2],
961
+ backgroundHover: slateGrey[1],
962
+ selected: slateGrey[3]
963
+ };
964
+ var inputDropdown = {
965
+ background: slateGrey[2],
966
+ icon: slateGrey[10],
967
+ borderDefault: slateGrey[4],
968
+ borderSelected: brand[3],
969
+ textLabel: slateGrey[9],
970
+ textPlaceholder: slateGrey[8],
971
+ textHint: slateGrey[8]
972
+ };
973
+ var inputField = {
974
+ backgroundDefault: slateGrey[2],
975
+ backgroundDisabled: slateGrey[0],
976
+ textLabel: slateGrey[9],
977
+ textPlaceholder: slateGrey[8],
978
+ textHint: slateGrey[8],
979
+ textError: red[5],
980
+ iconDefault: slateGrey[9],
981
+ iconPlaceholder: slateGrey[10],
982
+ iconError: red[5],
983
+ borderDefault: slateGrey[4],
984
+ borderSelected: brand[3],
985
+ borderError: red[5]
986
+ };
987
+ var toggle = {
988
+ handleDefault: base.white,
989
+ handleDisabled: slateGrey[10],
990
+ off: {
991
+ backgroundDefault: slateGrey[4],
992
+ backgroundHover: slateGrey[5],
993
+ backgroundDisabled: slateGrey[4]
994
+ },
995
+ on: {
996
+ backgroundDefault: green[3],
997
+ backgroundHover: green[2],
998
+ backgroundDisabled: slateGrey[4]
999
+ }
1000
+ };
1001
+ var checkbox = {
1002
+ off: {
1003
+ backgroundDefault: "#00000000",
1004
+ backgroundHover: slateGrey[5],
1005
+ backgroundDisabled: slateGrey[2],
1006
+ border: slateGrey[6]
1007
+ },
1008
+ on: {
1009
+ backgroundDefault: green[0],
1010
+ backgroundHover: green[1],
1011
+ backgroundDisabled: slateGrey[2],
1012
+ border: green[3]
1013
+ }
1014
+ };
1015
+ var avatar = {
1016
+ content: slateGrey[10],
1017
+ background: slateGrey[4]
1018
+ };
1019
+ var progressBarSlider = {
1020
+ background: slateGrey[4],
1021
+ active: green[3]
1022
+ };
1023
+ var card = {
1024
+ background: slateGrey[1],
1025
+ content: slateGrey[9],
1026
+ border: slateGrey[4]
1027
+ };
1028
+ var sidebar = {
1029
+ backgroundDefault: slateGrey[1],
1030
+ backgroundHover: slateGrey[3],
1031
+ backgroundActive: slateGrey[4],
1032
+ border: slateGrey[4],
1033
+ contentPrimary: slateGrey[10],
1034
+ contentSecondary: slateGrey[9],
1035
+ contentTertiary: slateGrey[8]
1036
+ };
1037
+ var modal = {
1038
+ background: slateGrey[1],
1039
+ content: slateGrey[9],
1040
+ border: slateGrey[4]
1041
+ };
1042
+ var tab = {
1043
+ activeBackground: slateGrey[3],
1044
+ activeContent: brand[5],
1045
+ inactiveContent: slateGrey[9],
1046
+ border: slateGrey[4]
1047
+ };
1048
+ var table = {
1049
+ header: {
1050
+ textDefault: slateGrey[9],
1051
+ textHover: slateGrey[8],
1052
+ backgroundDefault: slateGrey[1]
1053
+ },
1054
+ border: slateGrey[4],
1055
+ cell: {
1056
+ textPrimary: slateGrey[10],
1057
+ textSecondary: slateGrey[9],
1058
+ backgroundDefault: slateGrey[2],
1059
+ backgroundHover: slateGrey[1]
1060
+ }
1061
+ };
1062
+ var breadcrumbs = {
1063
+ textPrimaryDefault: slateGrey[10],
1064
+ textPrimaryHover: slateGrey[10],
1065
+ textSecondaryDefault: slateGrey[8],
1066
+ textSecondaryHover: slateGrey[9],
1067
+ iconPrimary: slateGrey[10],
1068
+ iconSecondary: slateGrey[8]
1069
+ };
1070
+ var loadingIndicator = {
1071
+ background: green[1],
1072
+ active: green[5]
1073
+ };
1074
+ var datePicker = {
1075
+ textDefault: slateGrey[10],
1076
+ textSelected: base.white,
1077
+ textDisabled: slateGrey[7],
1078
+ backgroundDefault: slateGrey[2],
1079
+ backgroundMiddle: slateGrey[3],
1080
+ backgroundSelected: brand[3],
1081
+ border: slateGrey[4]
1082
+ };
1083
+ var scroll = slateGrey[9];
1084
+
1085
+ // ../../design-system/dist/tokens/panel-shell.js
1086
+ var fab = {
1087
+ /** Diameter in pixels. */
1088
+ size: 56,
1089
+ /** Inset from the panel's top-left corner in pixels. */
1090
+ inset: 12,
1091
+ /** Background color (always the brand black). */
1092
+ background: base.black,
1093
+ /** Icon / logo color. */
1094
+ color: base.white,
1095
+ /** Border — 2px brand red ring. */
1096
+ border: `2px solid ${brand[3]}`,
1097
+ /** Shadow when the panel is open (inner ring for "active" state). */
1098
+ shadowOpen: "0 4px 24px rgba(0,0,0,0.6), 0 0 0 2px rgba(255,255,255,0.08)",
1099
+ /** Shadow when the panel is closed. */
1100
+ shadowClosed: "0 4px 24px rgba(0,0,0,0.6)"
1101
+ };
1102
+
1103
+ // src/overlay-styles.ts
1104
+ var OVERLAY_BASE_CSS = `
1105
+ :host {
1106
+ --syntro-surface: var(--sc-overlay-background, #0f1318);
1107
+ --syntro-fg: var(--sc-overlay-text-color, #cbd0d7);
1108
+ --syntro-accent: var(--sc-color-primary, #b72e2a);
1109
+ --syntro-accent-hover: var(--sc-color-primary-hover, #d44844);
1110
+ --syntro-radius: var(--sc-border-radius, 12px);
1111
+ --syntro-shadow: 0 25px 50px -12px rgba(16,24,40,0.25);
1112
+ --syntro-ring: var(--sc-overlay-highlight-ring, #d44844);
1113
+ --syntro-border: var(--sc-overlay-border, #2b333f);
1114
+ --syntro-tooltip-bg: var(--syntro-surface);
1115
+ --syntro-tooltip-fg: var(--syntro-fg);
1116
+ --syntro-tooltip-title-color: var(--sc-overlay-title-color, var(--syntro-fg));
1117
+ --syntro-tooltip-arrow-bg: var(--sc-overlay-arrow-color, var(--syntro-tooltip-bg));
1118
+ --syntro-tooltip-arrow-size: var(--sc-overlay-arrow-size, 8px);
1119
+ --syntro-tooltip-radius: var(--syntro-radius);
1120
+ --syntro-tooltip-padding: 12px 16px;
1121
+ --syntro-tooltip-shadow: var(--syntro-shadow);
1122
+ --syntro-spotlight-backdrop: rgba(0,0,0, var(--sc-overlay-scrim-opacity, 0.70));
1123
+ }
1124
+
1125
+ /* Tooltip container */
1126
+ .syntro-tooltip {
1127
+ position: fixed;
1128
+ background: var(--syntro-tooltip-bg);
1129
+ color: var(--syntro-tooltip-fg);
1130
+ border-radius: var(--syntro-tooltip-radius);
1131
+ padding: var(--syntro-tooltip-padding);
1132
+ box-shadow: var(--syntro-tooltip-shadow);
1133
+ pointer-events: auto;
1134
+ max-width: min(320px, 90vw);
1135
+ font-family: inherit;
1136
+ font-size: 14px;
1137
+ line-height: 1.5;
1138
+ z-index: 2147483647;
1139
+ opacity: 1;
1140
+ visibility: visible;
1141
+ transition: opacity 200ms cubic-bezier(0.16, 1, 0.3, 1),
1142
+ transform 200ms cubic-bezier(0.16, 1, 0.3, 1);
1143
+ }
1144
+
1145
+ /* Tooltip arrow \u2014 triangle via clip-path (square box so rotation is symmetric) */
1146
+ .syntro-tooltip-arrow {
1147
+ position: absolute;
1148
+ width: var(--syntro-tooltip-arrow-size);
1149
+ height: var(--syntro-tooltip-arrow-size);
1150
+ background: var(--syntro-tooltip-arrow-bg);
1151
+ clip-path: polygon(0 0, 100% 0, 50% 100%);
1152
+ }
1153
+
1154
+ /* Tooltip content */
1155
+ .syntro-tt-title {
1156
+ font-weight: 600;
1157
+ font-size: 15px;
1158
+ margin-bottom: 6px;
1159
+ color: var(--syntro-tooltip-title-color);
1160
+ }
1161
+
1162
+ .syntro-tt-body {
1163
+ font-size: 14px;
1164
+ opacity: 0.9;
1165
+ }
1166
+
1167
+ .syntro-tt-close {
1168
+ position: absolute;
1169
+ top: 8px;
1170
+ right: 8px;
1171
+ background: transparent;
1172
+ border: none;
1173
+ color: inherit;
1174
+ font-size: 20px;
1175
+ line-height: 1;
1176
+ cursor: pointer;
1177
+ opacity: 0.6;
1178
+ transition: opacity 150ms;
1179
+ padding: 4px;
1180
+ }
1181
+
1182
+ .syntro-tt-close:hover {
1183
+ opacity: 1;
1184
+ }
1185
+
1186
+ /* Tooltip action buttons (for tours) */
1187
+ .syntro-tt-actions {
1188
+ display: flex;
1189
+ gap: 8px;
1190
+ margin-top: 12px;
1191
+ justify-content: flex-end;
1192
+ }
1193
+
1194
+ .syntro-tt-btn {
1195
+ padding: 8px 16px;
1196
+ border-radius: 6px;
1197
+ font-size: 13px;
1198
+ font-weight: 500;
1199
+ cursor: pointer;
1200
+ transition: all 150ms;
1201
+ border: 1px solid var(--syntro-border, #2b333f);
1202
+ background: transparent;
1203
+ color: inherit;
1204
+ }
1205
+
1206
+ .syntro-tt-btn:hover {
1207
+ background: rgba(255, 255, 255, 0.06);
1208
+ }
1209
+
1210
+ .syntro-tt-btn-primary {
1211
+ background: var(--syntro-accent, #b72e2a);
1212
+ border-color: transparent;
1213
+ color: #fff;
1214
+ }
1215
+
1216
+ .syntro-tt-btn-primary:hover {
1217
+ background: var(--syntro-accent-hover, #d44844);
1218
+ }
1219
+
1220
+ /* Buttons inside tooltips inherit font */
1221
+ .syntro-tooltip button {
1222
+ font-family: inherit;
1223
+ cursor: pointer;
1224
+ }
1225
+
1226
+ /* Spotlight scrim with fade animation */
1227
+ .syntro-spotlight-scrim {
1228
+ position: fixed;
1229
+ inset: 0;
1230
+ background: var(--syntro-spotlight-backdrop, rgba(2,6,23,.55));
1231
+ backdrop-filter: blur(2px);
1232
+ transition: opacity 220ms cubic-bezier(0.16, 1, 0.3, 1);
1233
+ }
1234
+
1235
+ /* Spotlight ring with pulse animation */
1236
+ .syntro-spotlight-ring {
1237
+ position: fixed;
1238
+ border: 2px solid var(--syntro-ring, #d44844);
1239
+ box-shadow: 0 0 0 4px rgba(212, 72, 68, 0.25),
1240
+ 0 4px 12px rgba(0, 0, 0, 0.3);
1241
+ pointer-events: none;
1242
+ animation: syntro-ring-pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
1243
+ transition: all 220ms cubic-bezier(0.16, 1, 0.3, 1);
1244
+ }
1245
+
1246
+ @keyframes syntro-ring-pulse {
1247
+ 0%, 100% {
1248
+ box-shadow: 0 0 0 4px rgba(212, 72, 68, 0.25),
1249
+ 0 4px 12px rgba(0, 0, 0, 0.3);
1250
+ }
1251
+ 50% {
1252
+ box-shadow: 0 0 0 8px rgba(212, 72, 68, 0.15),
1253
+ 0 4px 16px rgba(0, 0, 0, 0.4);
1254
+ }
1255
+ }
1256
+
1257
+ /* Fade in animation for tooltip */
1258
+ @keyframes syntro-fade-in {
1259
+ from {
1260
+ opacity: 0;
1261
+ transform: scale(0.96) translateY(-4px);
1262
+ }
1263
+ to {
1264
+ opacity: 1;
1265
+ transform: scale(1) translateY(0);
1266
+ }
1267
+ }
1268
+
1269
+ /* Modal scrim */
1270
+ .syntro-modal-scrim {
1271
+ position: fixed;
1272
+ inset: 0;
1273
+ background: rgba(0, 0, 0, 0.6);
1274
+ backdrop-filter: blur(4px);
1275
+ opacity: 0;
1276
+ transition: opacity 200ms ease;
1277
+ pointer-events: auto;
1278
+ }
1279
+
1280
+ /* Modal container */
1281
+ .syntro-modal {
1282
+ position: fixed;
1283
+ top: 50%;
1284
+ left: 50%;
1285
+ transform: translate(-50%, -50%) scale(0.95);
1286
+ background: var(--syntro-surface, #0f172a);
1287
+ color: var(--syntro-fg, #fff);
1288
+ border-radius: 16px;
1289
+ padding: 24px;
1290
+ box-shadow: 0 24px 48px rgba(0, 0, 0, 0.4);
1291
+ pointer-events: auto;
1292
+ opacity: 0;
1293
+ transition: opacity 200ms ease, transform 200ms ease;
1294
+ z-index: 2147483647;
1295
+ }
1296
+
1297
+ .syntro-modal-sm { max-width: 320px; width: 90vw; }
1298
+ .syntro-modal-md { max-width: 480px; width: 90vw; }
1299
+ .syntro-modal-lg { max-width: 640px; width: 90vw; }
1300
+
1301
+ /* Modal content */
1302
+ .syntro-modal-title {
1303
+ font-size: 20px;
1304
+ font-weight: 600;
1305
+ margin-bottom: 12px;
1306
+ }
1307
+
1308
+ .syntro-modal-body {
1309
+ font-size: 15px;
1310
+ line-height: 1.6;
1311
+ opacity: 0.9;
1312
+ margin-bottom: 20px;
1313
+ }
1314
+
1315
+ .syntro-modal-close {
1316
+ position: absolute;
1317
+ top: 12px;
1318
+ right: 12px;
1319
+ background: transparent;
1320
+ border: none;
1321
+ color: inherit;
1322
+ font-size: 24px;
1323
+ line-height: 1;
1324
+ cursor: pointer;
1325
+ opacity: 0.6;
1326
+ transition: opacity 150ms;
1327
+ padding: 4px;
1328
+ }
1329
+
1330
+ .syntro-modal-close:hover {
1331
+ opacity: 1;
1332
+ }
1333
+
1334
+ /* Modal buttons */
1335
+ .syntro-modal-actions {
1336
+ display: flex;
1337
+ gap: 12px;
1338
+ justify-content: flex-end;
1339
+ }
1340
+
1341
+ .syntro-modal-btn {
1342
+ padding: 10px 20px;
1343
+ border-radius: 8px;
1344
+ font-size: 14px;
1345
+ font-weight: 500;
1346
+ cursor: pointer;
1347
+ transition: all 150ms;
1348
+ border: 1px solid var(--syntro-border, #2b333f);
1349
+ background: transparent;
1350
+ color: inherit;
1351
+ }
1352
+
1353
+ .syntro-modal-btn:hover {
1354
+ background: rgba(255, 255, 255, 0.06);
1355
+ }
1356
+
1357
+ .syntro-modal-btn-primary {
1358
+ background: var(--syntro-accent, #b72e2a);
1359
+ border-color: transparent;
1360
+ color: #fff;
1361
+ }
1362
+
1363
+ .syntro-modal-btn-primary:hover {
1364
+ background: var(--syntro-accent-hover, #d44844);
1365
+ }
1366
+ `;
1367
+ function ensureOverlayStyles(overlayRoot) {
1368
+ if (typeof document === "undefined") return;
1369
+ const root = overlayRoot.getRootNode();
1370
+ const target = root instanceof ShadowRoot ? root : root instanceof Document ? root.head : document.head;
1371
+ if (target.querySelector('style[data-syntro="base-overlay"]')) return;
1372
+ const style = document.createElement("style");
1373
+ style.setAttribute("data-syntro", "base-overlay");
1374
+ style.textContent = OVERLAY_BASE_CSS;
1375
+ target.appendChild(style);
1376
+ }
1377
+
1378
+ // src/highlight.ts
1379
+ function showHighlight(anchorEl, overlayRoot, opts) {
1380
+ ensureOverlayStyles(overlayRoot);
1381
+ const padding = opts?.paddingPx ?? 12;
1382
+ const radius = opts?.radiusPx ?? 12;
1383
+ const opacity = Math.min(Math.max(opts?.scrimOpacity ?? 0.55, 0), 1);
1384
+ const ringColor = opts?.ringColor ?? `var(--syntro-ring, ${blue[5]})`;
1385
+ const blocking = opts?.blocking ?? false;
1386
+ const onClickOutside = opts?.onClickOutside ?? true;
1387
+ const onEsc = opts?.onEsc ?? true;
1388
+ const supportsPathClip = typeof CSS !== "undefined" && CSS.supports?.("clip-path", "path('M0 0 H1 V1 Z')");
1389
+ const rootStyles = getComputedStyle(document.documentElement);
1390
+ const tokenScrim = rootStyles.getPropertyValue("--syntro-spotlight-backdrop").trim();
1391
+ const tokenRing = rootStyles.getPropertyValue("--syntro-ring").trim();
1392
+ const scrim = document.createElement("div");
1393
+ scrim.className = "syntro-spotlight-scrim";
1394
+ const needsPointerEvents = blocking || onClickOutside;
1395
+ Object.assign(scrim.style, {
1396
+ position: "fixed",
1397
+ inset: "0",
1398
+ zIndex: "2147483646",
1399
+ pointerEvents: needsPointerEvents ? "auto" : "none",
1400
+ background: tokenScrim || `rgba(2, 6, 23, ${opacity})`,
1401
+ transition: "opacity 220ms ease",
1402
+ opacity: "0"
1403
+ });
1404
+ overlayRoot.appendChild(scrim);
1405
+ requestAnimationFrame(() => scrim.style.opacity = "1");
1406
+ const ring = document.createElement("div");
1407
+ ring.className = "syntro-spotlight-ring";
1408
+ Object.assign(ring.style, {
1409
+ position: "fixed",
1410
+ pointerEvents: "none",
1411
+ borderRadius: `${radius}px`,
1412
+ border: `2px solid ${ringColor || tokenRing || blue[5]}`,
1413
+ boxShadow: `0 0 0 4px rgba(255,255,255,0.35)`,
1414
+ zIndex: "2147483647",
1415
+ transition: "all 220ms cubic-bezier(0.16,1,0.3,1)"
1416
+ });
1417
+ overlayRoot.appendChild(ring);
1418
+ const fallbackSlices = [];
1419
+ if (!supportsPathClip) {
1420
+ for (let i = 0; i < 4; i++) {
1421
+ const slice = document.createElement("div");
1422
+ slice.style.position = "fixed";
1423
+ slice.style.background = "inherit";
1424
+ fallbackSlices.push(slice);
1425
+ scrim.appendChild(slice);
1426
+ }
1427
+ }
1428
+ const setClipPath = (path) => {
1429
+ scrim.style.clipPath = path;
1430
+ scrim.style.webkitClipPath = path;
1431
+ };
1432
+ const update = () => {
1433
+ if (!anchorEl.isConnected) {
1434
+ handle.destroy();
1435
+ return;
1436
+ }
1437
+ const rect = anchorEl.getBoundingClientRect();
1438
+ const x = rect.left - padding;
1439
+ const y = rect.top - padding;
1440
+ const w = rect.width + padding * 2;
1441
+ const h = rect.height + padding * 2;
1442
+ Object.assign(ring.style, {
1443
+ left: `${x}px`,
1444
+ top: `${y}px`,
1445
+ width: `${w}px`,
1446
+ height: `${h}px`
1447
+ });
1448
+ if (supportsPathClip) {
1449
+ const vw = window.innerWidth;
1450
+ const vh = window.innerHeight;
1451
+ const r = Math.min(radius, w / 2, h / 2);
1452
+ const outer = `M 0 0 L ${vw} 0 L ${vw} ${vh} L 0 ${vh} Z`;
1453
+ const inner = `M ${x + r} ${y} A ${r} ${r} 0 0 0 ${x} ${y + r} L ${x} ${y + h - r} A ${r} ${r} 0 0 0 ${x + r} ${y + h} L ${x + w - r} ${y + h} A ${r} ${r} 0 0 0 ${x + w} ${y + h - r} L ${x + w} ${y + r} A ${r} ${r} 0 0 0 ${x + w - r} ${y} L ${x + r} ${y} Z`;
1454
+ setClipPath(`path('${outer} ${inner}')`);
1455
+ } else {
1456
+ const [top, right, bottom, left] = fallbackSlices;
1457
+ Object.assign(top.style, {
1458
+ left: "0px",
1459
+ top: "0px",
1460
+ width: "100vw",
1461
+ height: `${y}px`
1462
+ });
1463
+ Object.assign(bottom.style, {
1464
+ left: "0px",
1465
+ top: `${y + h}px`,
1466
+ width: "100vw",
1467
+ height: `${Math.max(0, window.innerHeight - (y + h))}px`
1468
+ });
1469
+ Object.assign(left.style, {
1470
+ left: "0px",
1471
+ top: `${y}px`,
1472
+ width: `${x}px`,
1473
+ height: `${h}px`
1474
+ });
1475
+ Object.assign(right.style, {
1476
+ left: `${x + w}px`,
1477
+ top: `${y}px`,
1478
+ width: `${Math.max(0, window.innerWidth - (x + w))}px`,
1479
+ height: `${h}px`
1480
+ });
1481
+ }
1482
+ };
1483
+ const ro = new ResizeObserver(() => requestAnimationFrame(update));
1484
+ ro.observe(anchorEl);
1485
+ const onScroll = () => requestAnimationFrame(update);
1486
+ const onResize = () => requestAnimationFrame(update);
1487
+ window.addEventListener("scroll", onScroll, true);
1488
+ window.addEventListener("resize", onResize);
1489
+ const onKey = (e) => {
1490
+ if (e.key === "Escape" && onEsc) {
1491
+ opts?.onDismiss?.();
1492
+ handle.destroy();
1493
+ }
1494
+ };
1495
+ if (onEsc) {
1496
+ window.addEventListener("keydown", onKey);
1497
+ }
1498
+ const onClick = (event) => {
1499
+ if (blocking) {
1500
+ event.preventDefault();
1501
+ event.stopPropagation();
1502
+ } else if (onClickOutside) {
1503
+ opts?.onDismiss?.();
1504
+ handle.destroy();
1505
+ }
1506
+ };
1507
+ scrim.addEventListener("click", onClick);
1508
+ const handle = {
1509
+ destroy() {
1510
+ ro.disconnect();
1511
+ window.removeEventListener("scroll", onScroll, true);
1512
+ window.removeEventListener("resize", onResize);
1513
+ if (onEsc) {
1514
+ window.removeEventListener("keydown", onKey);
1515
+ }
1516
+ scrim.removeEventListener("click", onClick);
1517
+ scrim.style.pointerEvents = "none";
1518
+ scrim.style.opacity = "0";
1519
+ setTimeout(() => {
1520
+ try {
1521
+ scrim.remove();
1522
+ } catch {
1523
+ }
1524
+ try {
1525
+ ring.remove();
1526
+ } catch {
1527
+ }
1528
+ }, 220);
1529
+ }
1530
+ };
1531
+ update();
1532
+ return handle;
1533
+ }
1534
+
1535
+ // src/sanitizer.ts
1536
+ var ALLOWED_TAGS = /* @__PURE__ */ new Set([
1537
+ "b",
1538
+ "strong",
1539
+ "i",
1540
+ "em",
1541
+ "u",
1542
+ "span",
1543
+ "div",
1544
+ "p",
1545
+ "br",
1546
+ "ul",
1547
+ "ol",
1548
+ "li",
1549
+ "code",
1550
+ "pre",
1551
+ "small",
1552
+ "sup",
1553
+ "sub",
1554
+ "a",
1555
+ "button",
1556
+ // SVG elements (for inline Lucide icons in config HTML)
1557
+ "svg",
1558
+ "path",
1559
+ "circle",
1560
+ "line",
1561
+ "polyline",
1562
+ "polygon",
1563
+ "rect",
1564
+ "g"
1565
+ ]);
1566
+ function normalizeUrlAttr(value) {
1567
+ const withoutControlCharacters = Array.from(value, (character) => {
1568
+ const codePoint = character.codePointAt(0) ?? 0;
1569
+ return codePoint <= 31 || codePoint === 127 ? "" : character;
1570
+ }).join("");
1571
+ return withoutControlCharacters.trim().toLowerCase();
1572
+ }
1573
+ function isDangerousUrlAttr(name, value) {
1574
+ if (name !== "href" && name !== "src" && name !== "formaction") return false;
1575
+ const normalized = normalizeUrlAttr(value);
1576
+ return normalized.startsWith("javascript:") || normalized.startsWith("vbscript:") || normalized.startsWith("data:");
1577
+ }
1578
+ function sanitizeHtml(html2) {
1579
+ const hasNative = typeof window.Sanitizer === "function";
1580
+ if (hasNative) {
1581
+ try {
1582
+ const s = new window.Sanitizer({});
1583
+ const frag = s.sanitizeToFragment(html2);
1584
+ const div = document.createElement("div");
1585
+ div.append(frag);
1586
+ return div.innerHTML;
1587
+ } catch {
1588
+ }
1589
+ }
1590
+ const tpl = document.createElement("template");
1591
+ tpl.innerHTML = html2;
1592
+ const root = tpl.content;
1593
+ const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT, null);
1594
+ const toRemove = [];
1595
+ while (walker.nextNode()) {
1596
+ const el = walker.currentNode;
1597
+ const tag2 = el.tagName.toLowerCase();
1598
+ if (!ALLOWED_TAGS.has(tag2)) {
1599
+ toRemove.push(el);
1600
+ continue;
1601
+ }
1602
+ for (const attr of Array.from(el.attributes)) {
1603
+ const name = attr.name.toLowerCase();
1604
+ const isEvent = name.startsWith("on");
1605
+ if (isEvent || isDangerousUrlAttr(name, attr.value)) {
1606
+ el.removeAttribute(attr.name);
1607
+ }
1608
+ }
1609
+ }
1610
+ for (const el of toRemove) {
1611
+ while (el.firstChild) {
1612
+ el.parentNode?.insertBefore(el.firstChild, el);
1613
+ }
1614
+ el.remove();
1615
+ }
1616
+ return tpl.innerHTML;
1617
+ }
1618
+
1619
+ // src/modal.ts
1620
+ var V = {
1621
+ bg: "var(--sc-overlay-background, #ffffff)",
1622
+ title: "var(--sc-overlay-title-color, var(--sc-overlay-text-color, #111827))",
1623
+ text: "var(--sc-overlay-text-color, #4b5563)",
1624
+ accent: "var(--sc-color-primary, #4f46e5)",
1625
+ radius: "var(--sc-border-radius, 12px)"
1626
+ };
1627
+ var executeModal = async (action, context) => {
1628
+ const { content, size = "md", blocking = false, scrim, dismiss, ctaButtons } = action;
1629
+ ensureOverlayStyles(context.overlayRoot);
1630
+ const scrimEl = document.createElement("div");
1631
+ scrimEl.className = "syntro-modal-scrim";
1632
+ scrimEl.style.cssText = `
1633
+ position: fixed;
1634
+ inset: 0;
1635
+ background: rgba(0, 0, 0, ${scrim?.opacity ?? 0.6});
1636
+ z-index: 2147483645;
1637
+ opacity: 0;
1638
+ transition: opacity 200ms ease-out;
1639
+ `;
1640
+ context.overlayRoot.appendChild(scrimEl);
1641
+ const modal2 = document.createElement("div");
1642
+ modal2.className = `syntro-modal syntro-modal-${size}`;
1643
+ modal2.setAttribute("role", "dialog");
1644
+ modal2.setAttribute("aria-modal", "true");
1645
+ const sizeMap = { sm: "360px", md: "480px", lg: "640px" };
1646
+ modal2.style.cssText = `
1647
+ position: fixed;
1648
+ top: 50%;
1649
+ left: 50%;
1650
+ transform: translate(-50%, -50%) scale(0.95);
1651
+ max-width: ${sizeMap[size]};
1652
+ width: 90%;
1653
+ background: ${V.bg};
1654
+ border-radius: ${V.radius};
1655
+ box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
1656
+ z-index: 2147483646;
1657
+ opacity: 0;
1658
+ transition: opacity 200ms ease-out, transform 200ms ease-out;
1659
+ padding: 24px;
1660
+ `;
1661
+ let html2 = "";
1662
+ if (content.title) {
1663
+ html2 += `<h2 class="syntro-modal-title" style="margin: 0 0 12px 0; font-size: 18px; font-weight: 600; color: ${V.title};">${sanitizeHtml(content.title)}</h2>`;
1664
+ }
1665
+ html2 += `<div class="syntro-modal-body" style="color: ${V.text}; line-height: 1.5;">${sanitizeHtml(content.body)}</div>`;
1666
+ if (dismiss?.closeButton !== false) {
1667
+ html2 += `
1668
+ <button class="syntro-modal-close" data-syntro-action="dismiss" style="
1669
+ position: absolute;
1670
+ top: 16px;
1671
+ right: 16px;
1672
+ background: none;
1673
+ border: none;
1674
+ cursor: pointer;
1675
+ padding: 4px;
1676
+ color: ${V.text};
1677
+ opacity: 0.6;
1678
+ " aria-label="Close">
1679
+ <svg width="20" height="20" viewBox="0 0 20 20" fill="currentColor">
1680
+ <path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"/>
1681
+ </svg>
1682
+ </button>
1683
+ `;
1684
+ }
1685
+ if (ctaButtons && ctaButtons.length > 0) {
1686
+ html2 += `<div class="syntro-modal-actions" style="display: flex; gap: 12px; margin-top: 24px; justify-content: flex-end;">`;
1687
+ for (const btn of ctaButtons) {
1688
+ const isPrimary = btn.primary ?? false;
1689
+ html2 += `
1690
+ <button
1691
+ class="syntro-modal-btn ${isPrimary ? "syntro-modal-btn-primary" : ""}"
1692
+ data-syntro-action="${sanitizeHtml(btn.actionId)}"
1693
+ style="
1694
+ padding: 10px 20px;
1695
+ border-radius: 8px;
1696
+ font-size: 14px;
1697
+ font-weight: 500;
1698
+ cursor: pointer;
1699
+ transition: background 150ms ease;
1700
+ ${isPrimary ? `background: ${V.accent}; color: white; border: none;` : `background: transparent; color: ${V.accent}; border: 1px solid currentColor; opacity: 0.7;`}
1701
+ "
1702
+ >
1703
+ ${sanitizeHtml(btn.label)}
1704
+ </button>
1705
+ `;
1706
+ }
1707
+ html2 += `</div>`;
1708
+ }
1709
+ modal2.innerHTML = html2;
1710
+ context.overlayRoot.appendChild(modal2);
1711
+ let actionClicked = null;
1712
+ const actionBtns = modal2.querySelectorAll("[data-syntro-action]");
1713
+ const actionHandler = (e) => {
1714
+ const btn = e.currentTarget;
1715
+ const actionId = btn.getAttribute("data-syntro-action");
1716
+ if (actionId) {
1717
+ actionClicked = actionId;
1718
+ const matchingBtn = ctaButtons?.find((b) => b.actionId === actionId);
1719
+ const href = matchingBtn?.href;
1720
+ context.publishEvent("action.modal_cta_clicked", {
1721
+ actionId,
1722
+ ...href ? { href } : {}
1723
+ });
1724
+ handle.destroy();
1725
+ if (matchingBtn) {
1726
+ navigateForCta(matchingBtn);
1727
+ }
1728
+ }
1729
+ };
1730
+ actionBtns.forEach((btn) => btn.addEventListener("click", actionHandler));
1731
+ const onKey = (e) => {
1732
+ if (e.key === "Escape" && dismiss?.onEsc !== false) {
1733
+ handle.destroy();
1734
+ }
1735
+ };
1736
+ window.addEventListener("keydown", onKey);
1737
+ const onScrimClick = () => {
1738
+ if (!blocking) {
1739
+ handle.destroy();
1740
+ }
1741
+ };
1742
+ scrimEl.addEventListener("click", onScrimClick);
1743
+ const originalInert = [];
1744
+ if (blocking) {
1745
+ Array.from(document.body.children).forEach((el) => {
1746
+ if (el !== context.overlayRoot && el.getAttribute("inert") === null && !el.querySelector("[data-syntro-editor-panel]") && !el.hasAttribute("data-syntro-editor-panel")) {
1747
+ el.setAttribute("inert", "");
1748
+ originalInert.push(el);
1749
+ }
1750
+ });
1751
+ }
1752
+ let timeoutId;
1753
+ if (dismiss?.timeoutMs) {
1754
+ timeoutId = setTimeout(() => {
1755
+ handle.destroy();
1756
+ }, dismiss.timeoutMs);
1757
+ }
1758
+ const focusableEls = modal2.querySelectorAll(
1759
+ 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
1760
+ );
1761
+ if (focusableEls.length > 0) {
1762
+ requestAnimationFrame(() => focusableEls[0].focus());
1763
+ }
1764
+ requestAnimationFrame(() => {
1765
+ scrimEl.style.opacity = "1";
1766
+ modal2.style.opacity = "1";
1767
+ modal2.style.transform = "translate(-50%, -50%) scale(1)";
1768
+ });
1769
+ context.publishEvent("action.applied", {
1770
+ id: context.generateId(),
1771
+ kind: "overlays:modal",
1772
+ size,
1773
+ blocking
1774
+ });
1775
+ const handle = {
1776
+ destroy() {
1777
+ if (timeoutId) {
1778
+ clearTimeout(timeoutId);
1779
+ }
1780
+ window.removeEventListener("keydown", onKey);
1781
+ scrimEl.removeEventListener("click", onScrimClick);
1782
+ actionBtns.forEach((btn) => btn.removeEventListener("click", actionHandler));
1783
+ originalInert.forEach((el) => el.removeAttribute("inert"));
1784
+ modal2.style.pointerEvents = "none";
1785
+ scrimEl.style.pointerEvents = "none";
1786
+ modal2.style.opacity = "0";
1787
+ modal2.style.transform = "translate(-50%, -50%) scale(0.95)";
1788
+ scrimEl.style.opacity = "0";
1789
+ setTimeout(() => {
1790
+ try {
1791
+ modal2.remove();
1792
+ } catch {
1793
+ }
1794
+ try {
1795
+ scrimEl.remove();
1796
+ } catch {
1797
+ }
1798
+ }, 200);
1799
+ context.publishEvent("action.modal_dismissed", {
1800
+ actionClicked
1801
+ });
1802
+ }
1803
+ };
1804
+ return {
1805
+ cleanup: () => {
1806
+ handle.destroy();
1807
+ }
1808
+ };
1809
+ };
1810
+
1811
+ // src/tooltip.ts
1812
+ import {
1813
+ arrow as arrowMiddleware,
1814
+ autoUpdate,
1815
+ computePosition,
1816
+ flip,
1817
+ hide,
1818
+ offset,
1819
+ shift
1820
+ } from "@floating-ui/dom";
1821
+ function getAnchorReference(anchorEl) {
1822
+ const rect = anchorEl.getBoundingClientRect();
1823
+ const viewportWidth = window.innerWidth;
1824
+ const viewportHeight = window.innerHeight;
1825
+ const isLargeElement = rect.width > viewportWidth * 0.8 || rect.height > viewportHeight * 0.8;
1826
+ if (!isLargeElement) {
1827
+ return anchorEl;
1828
+ }
1829
+ const visibleLeft = Math.max(rect.left, 0);
1830
+ const visibleTop = Math.max(rect.top, 0);
1831
+ const visibleRight = Math.min(rect.right, viewportWidth);
1832
+ const visibleBottom = Math.min(rect.bottom, viewportHeight);
1833
+ const centerX = (visibleLeft + visibleRight) / 2;
1834
+ const centerY = (visibleTop + visibleBottom) / 2;
1835
+ return {
1836
+ getBoundingClientRect() {
1837
+ return {
1838
+ width: 0,
1839
+ height: 0,
1840
+ x: centerX,
1841
+ y: centerY,
1842
+ top: centerY,
1843
+ left: centerX,
1844
+ right: centerX,
1845
+ bottom: centerY
1846
+ };
1847
+ }
1848
+ };
1849
+ }
1850
+ function showTooltip(anchorEl, overlayRoot, opts) {
1851
+ ensureOverlayStyles(overlayRoot);
1852
+ if (!opts.trigger || opts.trigger === "immediate") {
1853
+ const rect = anchorEl.getBoundingClientRect();
1854
+ const isLargeElement = rect.width > window.innerWidth * 0.8 || rect.height > window.innerHeight * 0.8;
1855
+ if (!isLargeElement) {
1856
+ anchorEl.scrollIntoView({ behavior: "smooth", block: "center", inline: "center" });
1857
+ }
1858
+ }
1859
+ const div = document.createElement("div");
1860
+ div.className = "syntro-tooltip";
1861
+ div.setAttribute("role", "tooltip");
1862
+ div.innerHTML = sanitizeHtml(opts.html);
1863
+ if (!opts.trigger || opts.trigger === "immediate") {
1864
+ const closeBtn = document.createElement("button");
1865
+ closeBtn.className = "syntro-tooltip-close";
1866
+ closeBtn.setAttribute("aria-label", "Close");
1867
+ closeBtn.textContent = "\xD7";
1868
+ Object.assign(closeBtn.style, {
1869
+ position: "absolute",
1870
+ top: "4px",
1871
+ right: "4px",
1872
+ background: "none",
1873
+ border: "none",
1874
+ color: "inherit",
1875
+ fontSize: "16px",
1876
+ lineHeight: "1",
1877
+ cursor: "pointer",
1878
+ opacity: "0.6",
1879
+ padding: "2px 4px"
1880
+ });
1881
+ closeBtn.addEventListener("mouseenter", () => {
1882
+ closeBtn.style.opacity = "1";
1883
+ });
1884
+ closeBtn.addEventListener("mouseleave", () => {
1885
+ closeBtn.style.opacity = "0.6";
1886
+ });
1887
+ closeBtn.addEventListener("click", () => handle.destroy());
1888
+ div.style.position = "relative";
1889
+ div.appendChild(closeBtn);
1890
+ }
1891
+ const actionBtns = div.querySelectorAll("[data-syntro-action]");
1892
+ const actionHandler = (e) => {
1893
+ const btn = e.currentTarget;
1894
+ const actionId = btn.getAttribute("data-syntro-action");
1895
+ if (actionId && opts.onAction) {
1896
+ opts.onAction(actionId);
1897
+ }
1898
+ };
1899
+ actionBtns.forEach((btn) => btn.addEventListener("click", actionHandler));
1900
+ const arrowEl = document.createElement("div");
1901
+ arrowEl.className = "syntro-tooltip-arrow";
1902
+ div.appendChild(arrowEl);
1903
+ overlayRoot.appendChild(div);
1904
+ const middleware = [
1905
+ offset(opts.offsetPx ?? 8),
1906
+ flip(),
1907
+ shift({ padding: 8 }),
1908
+ hide(),
1909
+ arrowMiddleware({ element: arrowEl })
1910
+ ];
1911
+ const placement = opts.placement && opts.placement !== "auto" ? opts.placement : "top";
1912
+ const cleanup = autoUpdate(anchorEl, div, async () => {
1913
+ if (!anchorEl.isConnected) {
1914
+ handle.destroy();
1915
+ return;
1916
+ }
1917
+ const currentAnchorRef = getAnchorReference(anchorEl);
1918
+ const result = await computePosition(currentAnchorRef, div, {
1919
+ placement,
1920
+ strategy: "fixed",
1921
+ middleware
1922
+ });
1923
+ const { x, y, strategy, middlewareData, placement: finalPlacement } = result;
1924
+ Object.assign(div.style, {
1925
+ left: `${x}px`,
1926
+ top: `${y}px`,
1927
+ position: strategy
1928
+ });
1929
+ if (middlewareData.arrow) {
1930
+ const { x: arrowX, y: arrowY } = middlewareData.arrow;
1931
+ const side = finalPlacement.split("-")[0];
1932
+ const staticSide = {
1933
+ top: "bottom",
1934
+ right: "left",
1935
+ bottom: "top",
1936
+ left: "right"
1937
+ };
1938
+ Object.assign(arrowEl.style, {
1939
+ left: arrowX != null ? `${arrowX}px` : "",
1940
+ top: arrowY != null ? `${arrowY}px` : "",
1941
+ right: "",
1942
+ bottom: "",
1943
+ [staticSide[side]]: "-4px"
1944
+ });
1945
+ const rotation = {
1946
+ top: "0deg",
1947
+ right: "90deg",
1948
+ bottom: "180deg",
1949
+ left: "270deg"
1950
+ };
1951
+ arrowEl.style.transform = `rotate(${rotation[side] || "0deg"})`;
1952
+ }
1953
+ });
1954
+ const onKey = (e) => {
1955
+ if (e.key === "Escape") handle.destroy();
1956
+ };
1957
+ window.addEventListener("keydown", onKey);
1958
+ const originalInert = [];
1959
+ if (opts.blocking) {
1960
+ Array.from(document.body.children).forEach((el) => {
1961
+ if (el !== overlayRoot && el.getAttribute("inert") === null) {
1962
+ el.setAttribute("inert", "");
1963
+ originalInert.push(el.id || el.tagName);
1964
+ }
1965
+ });
1966
+ const focusableEls = Array.from(
1967
+ div.querySelectorAll(
1968
+ 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
1969
+ )
1970
+ );
1971
+ if (focusableEls.length > 0) {
1972
+ const firstFocusable = focusableEls[0];
1973
+ const lastFocusable = focusableEls[focusableEls.length - 1];
1974
+ const trapFocus = (e) => {
1975
+ if (e.key !== "Tab") return;
1976
+ if (e.shiftKey) {
1977
+ if (document.activeElement === firstFocusable) {
1978
+ lastFocusable.focus();
1979
+ e.preventDefault();
1980
+ }
1981
+ } else if (document.activeElement === lastFocusable) {
1982
+ firstFocusable.focus();
1983
+ e.preventDefault();
1984
+ }
1985
+ };
1986
+ div.addEventListener("keydown", trapFocus);
1987
+ requestAnimationFrame(() => firstFocusable.focus());
1988
+ }
1989
+ }
1990
+ const attachTrigger = () => {
1991
+ if (opts.trigger === "hover") {
1992
+ let hideTimeout = null;
1993
+ const show = () => {
1994
+ if (hideTimeout) {
1995
+ clearTimeout(hideTimeout);
1996
+ hideTimeout = null;
1997
+ }
1998
+ div.style.visibility = "visible";
1999
+ div.style.opacity = "1";
2000
+ };
2001
+ const scheduleHide = () => {
2002
+ hideTimeout = setTimeout(() => {
2003
+ div.style.visibility = "hidden";
2004
+ div.style.opacity = "0";
2005
+ hideTimeout = null;
2006
+ }, 100);
2007
+ };
2008
+ div.style.visibility = "hidden";
2009
+ div.style.opacity = "0";
2010
+ div.style.transition = "opacity 200ms ease, visibility 200ms";
2011
+ anchorEl.addEventListener("mouseenter", show);
2012
+ anchorEl.addEventListener("mouseleave", scheduleHide);
2013
+ div.addEventListener("mouseenter", show);
2014
+ div.addEventListener("mouseleave", scheduleHide);
2015
+ anchorEl.addEventListener("focus", show);
2016
+ anchorEl.addEventListener("blur", scheduleHide);
2017
+ return () => {
2018
+ if (hideTimeout) clearTimeout(hideTimeout);
2019
+ anchorEl.removeEventListener("mouseenter", show);
2020
+ anchorEl.removeEventListener("mouseleave", scheduleHide);
2021
+ div.removeEventListener("mouseenter", show);
2022
+ div.removeEventListener("mouseleave", scheduleHide);
2023
+ anchorEl.removeEventListener("focus", show);
2024
+ anchorEl.removeEventListener("blur", scheduleHide);
2025
+ };
2026
+ }
2027
+ if (opts.trigger === "click") {
2028
+ const toggle2 = () => {
2029
+ const isVisible = div.style.visibility === "visible";
2030
+ if (isVisible) {
2031
+ handle.destroy();
2032
+ } else {
2033
+ div.style.visibility = "visible";
2034
+ div.style.opacity = "1";
2035
+ }
2036
+ };
2037
+ div.style.visibility = "hidden";
2038
+ div.style.opacity = "0";
2039
+ div.style.transition = "opacity 200ms ease, visibility 200ms";
2040
+ anchorEl.addEventListener("click", toggle2);
2041
+ return () => anchorEl.removeEventListener("click", toggle2);
2042
+ }
2043
+ div.style.opacity = "0";
2044
+ div.style.transition = "opacity 200ms ease";
2045
+ requestAnimationFrame(() => {
2046
+ div.style.opacity = "1";
2047
+ });
2048
+ return () => {
2049
+ };
2050
+ };
2051
+ const removeTrigger = attachTrigger();
2052
+ const handle = {
2053
+ el: div,
2054
+ destroy() {
2055
+ cleanup();
2056
+ removeTrigger();
2057
+ window.removeEventListener("keydown", onKey);
2058
+ actionBtns.forEach((btn) => btn.removeEventListener("click", actionHandler));
2059
+ if (opts.blocking) {
2060
+ Array.from(document.body.children).forEach((el) => {
2061
+ if (el !== overlayRoot) {
2062
+ el.removeAttribute("inert");
2063
+ }
2064
+ });
2065
+ }
2066
+ div.style.pointerEvents = "none";
2067
+ div.style.opacity = "0";
2068
+ setTimeout(() => {
2069
+ try {
2070
+ div.remove();
2071
+ } catch {
2072
+ }
2073
+ }, 200);
2074
+ }
2075
+ };
2076
+ return handle;
2077
+ }
2078
+
2079
+ // src/WorkflowWidgetLit.ts
2080
+ import { html, LitElement, nothing } from "lit";
2081
+ import { styleMap } from "lit/directives/style-map.js";
2082
+ var TOKEN_BLUE_4 = "#1969fe";
2083
+ var TOKEN_GREEN_4 = "#24ad32";
2084
+ var TOKEN_SLATE_2 = "#0e1114";
2085
+ var TOKEN_SLATE_7 = "#677384";
2086
+ var TOKEN_SLATE_9 = "#a8afba";
2087
+ var TOKEN_SLATE_12 = "#f6f7f9";
2088
+ var TOKEN_WHITE = "#ffffff";
2089
+ function showWorkflowToast(notification) {
2090
+ const toast = document.createElement("div");
2091
+ toast.setAttribute("data-testid", "workflow-toast");
2092
+ Object.assign(toast.style, {
2093
+ position: "fixed",
2094
+ bottom: "16px",
2095
+ right: "16px",
2096
+ zIndex: "2147483646",
2097
+ padding: "12px 16px",
2098
+ borderRadius: "8px",
2099
+ backgroundColor: `var(--se-color-bg-surface, ${TOKEN_WHITE})`,
2100
+ color: `var(--se-color-text-primary, ${TOKEN_SLATE_2})`,
2101
+ boxShadow: "0 4px 12px rgba(0, 0, 0, 0.15)",
2102
+ maxWidth: "320px",
2103
+ fontFamily: "var(--se-font-family, system-ui, sans-serif)",
2104
+ fontSize: "14px",
2105
+ lineHeight: "1.4",
2106
+ transition: "opacity 0.3s ease"
2107
+ });
2108
+ const titleEl = document.createElement("div");
2109
+ titleEl.style.fontWeight = "600";
2110
+ titleEl.textContent = notification.title;
2111
+ toast.appendChild(titleEl);
2112
+ if (notification.body) {
2113
+ const bodyEl = document.createElement("div");
2114
+ bodyEl.style.marginTop = "4px";
2115
+ bodyEl.style.fontSize = "13px";
2116
+ bodyEl.style.color = "var(--se-color-text-secondary, #666)";
2117
+ bodyEl.textContent = notification.body;
2118
+ toast.appendChild(bodyEl);
2119
+ }
2120
+ document.body.appendChild(toast);
2121
+ let removeTimer;
2122
+ const fadeTimer = setTimeout(() => {
2123
+ toast.style.opacity = "0";
2124
+ removeTimer = setTimeout(() => {
2125
+ toast.remove();
2126
+ }, 300);
2127
+ }, 4e3);
2128
+ return () => {
2129
+ clearTimeout(fadeTimer);
2130
+ clearTimeout(removeTimer);
2131
+ toast.remove();
2132
+ };
2133
+ }
2134
+ function extractWorkflowsFromActive(activeActions) {
2135
+ const workflows = /* @__PURE__ */ new Map();
2136
+ for (const entry of activeActions) {
2137
+ const action = entry.action;
2138
+ if (action.kind === "overlays:tour" && action.workflow && action.tourId) {
2139
+ const meta = action.workflow;
2140
+ const rawSteps = action.steps || [];
2141
+ const steps = rawSteps.map((s) => ({
2142
+ id: s.id,
2143
+ title: meta.stepTitles?.[s.id] || s.id
2144
+ }));
2145
+ workflows.set(action.tourId, { meta, steps });
2146
+ }
2147
+ }
2148
+ return workflows;
2149
+ }
2150
+ var TAG_NAME = "syntro-workflow-tracker";
2151
+ var _unsubTourStarted, _unsubTourEvents, _toastCleanups, _scanGeneration, _notified, _completedMap, _persistInitialized, _tourWorkflows;
2152
+ var WorkflowTrackerLit = class extends LitElement {
2153
+ constructor() {
2154
+ super(...arguments);
2155
+ // ── Public properties ─────────────────────────────────────────────────────
2156
+ this.runtimeRef = null;
2157
+ // ── Internal reactive state ───────────────────────────────────────────────
2158
+ /** @internal */
2159
+ this._workflowEntries = [];
2160
+ /**
2161
+ * @internal
2162
+ * Bumped on tour.started / tour.resumed to trigger re-scan of active actions.
2163
+ */
2164
+ /** @internal */
2165
+ this._actionVersion = 0;
2166
+ // ── Private (non-reactive) fields ─────────────────────────────────────────
2167
+ // Subscription cleanup functions
2168
+ __privateAdd(this, _unsubTourStarted, null);
2169
+ __privateAdd(this, _unsubTourEvents, null);
2170
+ // Toast cleanup tracking
2171
+ __privateAdd(this, _toastCleanups, []);
2172
+ __privateAdd(this, _scanGeneration, 0);
2173
+ // Notifications already shown (mirrors notifiedRef)
2174
+ __privateAdd(this, _notified, /* @__PURE__ */ new Set());
2175
+ // Completed timestamps (mirrors completedMapRef)
2176
+ __privateAdd(this, _completedMap, {});
2177
+ // Whether persisted state has been loaded
2178
+ __privateAdd(this, _persistInitialized, false);
2179
+ // Cache of the last scanned tourWorkflows map (used by event handler)
2180
+ __privateAdd(this, _tourWorkflows, /* @__PURE__ */ new Map());
2181
+ }
2182
+ // ── Light DOM ─────────────────────────────────────────────────────────────
2183
+ /**
2184
+ * Render into the element itself (light DOM) so host-page CSS variables
2185
+ * flow through without a nested shadow boundary.
2186
+ */
2187
+ createRenderRoot() {
2188
+ return this;
2189
+ }
2190
+ // ── Helpers ───────────────────────────────────────────────────────────────
2191
+ get _stateNs() {
2192
+ return this.runtimeRef?.state?.user?.ns?.("workflows") ?? null;
2193
+ }
2194
+ /**
2195
+ * Re-scan active actions and update _tourWorkflows + entries.
2196
+ * Called initially and whenever _actionVersion bumps.
2197
+ */
2198
+ _rescanWorkflows() {
2199
+ const active = this.runtimeRef?.actions?.getActive?.() ?? [];
2200
+ const workflows = extractWorkflowsFromActive(active);
2201
+ __privateSet(this, _tourWorkflows, workflows);
2202
+ if (workflows.size === 0) return;
2203
+ const stateNs = this._stateNs;
2204
+ const dismissed = stateNs?.get("dismissed") ?? [];
2205
+ const completed = stateNs?.get("completed") ?? {};
2206
+ this._workflowEntries = (() => {
2207
+ const existingIds = new Set(this._workflowEntries.map((e) => e.tourId));
2208
+ const newEntries = [];
2209
+ for (const [tourId, { meta, steps }] of workflows) {
2210
+ if (existingIds.has(tourId)) continue;
2211
+ let status = "active";
2212
+ if (dismissed.includes(tourId)) {
2213
+ status = "dismissed";
2214
+ } else if (completed[tourId]) {
2215
+ status = "completed";
2216
+ }
2217
+ newEntries.push({
2218
+ tourId,
2219
+ meta,
2220
+ steps,
2221
+ currentStepId: null,
2222
+ completedSteps: [],
2223
+ status,
2224
+ completedAt: completed[tourId] || void 0
2225
+ });
2226
+ }
2227
+ return newEntries.length > 0 ? [...this._workflowEntries, ...newEntries] : this._workflowEntries;
2228
+ })();
2229
+ for (const [tourId, { meta }] of workflows) {
2230
+ const dismissed2 = stateNs?.get("dismissed") ?? [];
2231
+ const completed2 = stateNs?.get("completed") ?? {};
2232
+ if (!__privateGet(this, _notified).has(tourId) && meta.notification && !dismissed2.includes(tourId) && !completed2[tourId]) {
2233
+ __privateGet(this, _notified).add(tourId);
2234
+ stateNs?.set("notified", [...__privateGet(this, _notified)]);
2235
+ const cleanup = showWorkflowToast(meta.notification);
2236
+ __privateGet(this, _toastCleanups).push(cleanup);
2237
+ }
2238
+ }
2239
+ }
2240
+ // ── Lifecycle ─────────────────────────────────────────────────────────────
2241
+ connectedCallback() {
2242
+ super.connectedCallback();
2243
+ this._initSubscriptions();
2244
+ }
2245
+ disconnectedCallback() {
2246
+ super.disconnectedCallback();
2247
+ this._teardownSubscriptions();
2248
+ for (const cleanup of __privateGet(this, _toastCleanups)) {
2249
+ cleanup();
2250
+ }
2251
+ __privateSet(this, _toastCleanups, []);
2252
+ }
2253
+ updated(changed) {
2254
+ if (changed.has("runtimeRef")) {
2255
+ this._teardownSubscriptions();
2256
+ this._initSubscriptions({ deferInitialScan: true });
2257
+ }
2258
+ if (changed.has("_actionVersion")) {
2259
+ this._queueWorkflowRescan();
2260
+ }
2261
+ }
2262
+ // ── Subscription management ───────────────────────────────────────────────
2263
+ _initSubscriptions(options = {}) {
2264
+ if (!this.runtimeRef?.events?.subscribe) return;
2265
+ if (!__privateGet(this, _persistInitialized) && this._stateNs) {
2266
+ const notified = this._stateNs.get("notified") ?? [];
2267
+ for (const id of notified) {
2268
+ __privateGet(this, _notified).add(id);
2269
+ }
2270
+ const completed = this._stateNs.get("completed") ?? {};
2271
+ __privateSet(this, _completedMap, { ...completed });
2272
+ __privateSet(this, _persistInitialized, true);
2273
+ }
2274
+ __privateSet(this, _unsubTourStarted, this.runtimeRef.events.subscribe(
2275
+ { names: ["tour.started", "tour.resumed"] },
2276
+ () => {
2277
+ this._actionVersion += 1;
2278
+ }
2279
+ ));
2280
+ __privateSet(this, _unsubTourEvents, this.runtimeRef.events.subscribe(
2281
+ { patterns: ["^tour\\."] },
2282
+ (event) => {
2283
+ this._handleTourEvent(event);
2284
+ }
2285
+ ));
2286
+ if (options.deferInitialScan) {
2287
+ this._queueWorkflowRescan();
2288
+ } else {
2289
+ this._rescanWorkflows();
2290
+ }
2291
+ }
2292
+ _teardownSubscriptions() {
2293
+ var _a, _b;
2294
+ __privateSet(this, _scanGeneration, __privateGet(this, _scanGeneration) + 1);
2295
+ (_a = __privateGet(this, _unsubTourStarted)) == null ? void 0 : _a.call(this);
2296
+ __privateSet(this, _unsubTourStarted, null);
2297
+ (_b = __privateGet(this, _unsubTourEvents)) == null ? void 0 : _b.call(this);
2298
+ __privateSet(this, _unsubTourEvents, null);
2299
+ }
2300
+ _queueWorkflowRescan() {
2301
+ const generation = __privateGet(this, _scanGeneration);
2302
+ queueMicrotask(() => {
2303
+ if (!this.isConnected || generation !== __privateGet(this, _scanGeneration)) return;
2304
+ this._rescanWorkflows();
2305
+ });
2306
+ }
2307
+ // ── Event handler ─────────────────────────────────────────────────────────
2308
+ _handleTourEvent(event) {
2309
+ const tourId = event.props?.tourId;
2310
+ if (!tourId) return;
2311
+ if (!__privateGet(this, _tourWorkflows).has(tourId) && event.name === "tour.started") {
2312
+ this._actionVersion += 1;
2313
+ return;
2314
+ }
2315
+ if (!__privateGet(this, _tourWorkflows).has(tourId)) return;
2316
+ const stateNs = this._stateNs;
2317
+ this._workflowEntries = this._workflowEntries.map((entry) => {
2318
+ if (entry.tourId !== tourId) return entry;
2319
+ switch (event.name) {
2320
+ case "tour.started": {
2321
+ const startStepId = event.props?.startStepId || entry.steps[0]?.id || null;
2322
+ if (!__privateGet(this, _notified).has(tourId)) {
2323
+ __privateGet(this, _notified).add(tourId);
2324
+ stateNs?.set("notified", [...__privateGet(this, _notified)]);
2325
+ const workflow = __privateGet(this, _tourWorkflows).get(tourId);
2326
+ if (workflow?.meta.notification) {
2327
+ const cleanup = showWorkflowToast(workflow.meta.notification);
2328
+ __privateGet(this, _toastCleanups).push(cleanup);
2329
+ }
2330
+ }
2331
+ const activeIds = this._workflowEntries.filter((e) => e.status === "active" || e.tourId === tourId).map((e) => e.tourId);
2332
+ if (!activeIds.includes(tourId)) {
2333
+ activeIds.push(tourId);
2334
+ }
2335
+ stateNs?.set("active", [...new Set(activeIds)]);
2336
+ return {
2337
+ ...entry,
2338
+ status: "active",
2339
+ currentStepId: startStepId,
2340
+ completedSteps: entry.status === "active" ? entry.completedSteps : []
2341
+ };
2342
+ }
2343
+ case "tour.step_started": {
2344
+ const stepId = event.props?.stepId;
2345
+ return {
2346
+ ...entry,
2347
+ currentStepId: stepId || entry.currentStepId
2348
+ };
2349
+ }
2350
+ case "tour.step_changed": {
2351
+ const previousStepId = event.props?.previousStepId;
2352
+ const nextStepId = event.props?.nextStepId;
2353
+ const completedSteps = previousStepId && !entry.completedSteps.includes(previousStepId) ? [...entry.completedSteps, previousStepId] : entry.completedSteps;
2354
+ return {
2355
+ ...entry,
2356
+ currentStepId: nextStepId || entry.currentStepId,
2357
+ completedSteps
2358
+ };
2359
+ }
2360
+ case "tour.completed": {
2361
+ const completedAt = Date.now();
2362
+ __privateGet(this, _completedMap)[tourId] = completedAt;
2363
+ stateNs?.set("completed", { ...__privateGet(this, _completedMap) });
2364
+ return {
2365
+ ...entry,
2366
+ status: "completed",
2367
+ currentStepId: null,
2368
+ completedSteps: entry.steps.map((s) => s.id),
2369
+ completedAt
2370
+ };
2371
+ }
2372
+ case "tour.paused":
2373
+ return entry;
2374
+ default:
2375
+ return entry;
2376
+ }
2377
+ });
2378
+ }
2379
+ // ── User action handlers ──────────────────────────────────────────────────
2380
+ _handleStepClick(tourId, stepId) {
2381
+ this.runtimeRef?.events?.publish("workflow:jump_to_step", { tourId, stepId });
2382
+ this.dispatchEvent(
2383
+ new CustomEvent("workflow-step-click", {
2384
+ bubbles: true,
2385
+ detail: { tourId, stepId }
2386
+ })
2387
+ );
2388
+ }
2389
+ _handleDismiss(tourId) {
2390
+ this._workflowEntries = this._workflowEntries.map(
2391
+ (entry) => entry.tourId === tourId ? { ...entry, status: "dismissed" } : entry
2392
+ );
2393
+ const dismissedIds = this._workflowEntries.filter((e) => e.status === "dismissed").map((e) => e.tourId);
2394
+ this._stateNs?.set("dismissed", dismissedIds);
2395
+ this.dispatchEvent(
2396
+ new CustomEvent("workflow-dismissed", {
2397
+ bubbles: true,
2398
+ detail: { tourId }
2399
+ })
2400
+ );
2401
+ }
2402
+ // ── Render helpers ────────────────────────────────────────────────────────
2403
+ _renderProgressBar(completed, total) {
2404
+ const percent = total > 0 ? Math.round(completed / total * 100) : 0;
2405
+ const trackStyles = {
2406
+ width: "100%",
2407
+ height: "6px",
2408
+ borderRadius: "9999px",
2409
+ background: "rgba(255,255,255,0.08)",
2410
+ overflow: "hidden"
2411
+ };
2412
+ const fillStyles = {
2413
+ height: "100%",
2414
+ borderRadius: "9999px",
2415
+ background: `var(--se-color-primary, ${TOKEN_BLUE_4})`,
2416
+ transition: "width 0.3s ease",
2417
+ width: `${percent}%`
2418
+ };
2419
+ return html`
2420
+ <div style=${styleMap(trackStyles)}>
2421
+ <div
2422
+ role="progressbar"
2423
+ aria-valuenow=${percent}
2424
+ aria-valuemin="0"
2425
+ aria-valuemax="100"
2426
+ style=${styleMap(fillStyles)}
2427
+ ></div>
2428
+ </div>
2429
+ `;
2430
+ }
2431
+ _renderStepItem(step, isCompleted, isCurrent, tourId) {
2432
+ const btnStyles = {
2433
+ display: "flex",
2434
+ alignItems: "center",
2435
+ gap: "8px",
2436
+ width: "100%",
2437
+ padding: "6px 8px",
2438
+ borderRadius: "4px",
2439
+ border: "none",
2440
+ background: "transparent",
2441
+ cursor: "pointer",
2442
+ textAlign: "left",
2443
+ fontSize: "12px",
2444
+ lineHeight: "1.4",
2445
+ color: isCurrent ? `var(--se-color-text-primary, ${TOKEN_SLATE_12})` : `var(--se-color-text-secondary, ${TOKEN_SLATE_9})`,
2446
+ fontWeight: isCurrent ? "600" : "400"
2447
+ };
2448
+ const indicatorWrapStyles = {
2449
+ flexShrink: "0",
2450
+ width: "16px",
2451
+ textAlign: "center"
2452
+ };
2453
+ const dotStyles = {
2454
+ display: "inline-block",
2455
+ width: "6px",
2456
+ height: "6px",
2457
+ borderRadius: "50%",
2458
+ background: isCurrent ? `var(--se-color-primary, ${TOKEN_BLUE_4})` : "rgba(255,255,255,0.12)"
2459
+ };
2460
+ const checkStyles = {
2461
+ color: `var(--se-color-success, ${TOKEN_GREEN_4})`
2462
+ };
2463
+ const labelStyles = {
2464
+ flex: "1",
2465
+ overflow: "hidden",
2466
+ textOverflow: "ellipsis",
2467
+ whiteSpace: "nowrap"
2468
+ };
2469
+ const indicator = isCompleted ? html`<span role="img" aria-label="completed" style=${styleMap(checkStyles)}>&#10003;</span>` : isCurrent ? html`<span style=${styleMap(dotStyles)}></span>` : html`<span style=${styleMap(dotStyles)}></span>`;
2470
+ return html`
2471
+ <button
2472
+ type="button"
2473
+ data-testid=${`step-${step.id}`}
2474
+ data-current=${isCurrent ? "true" : nothing}
2475
+ data-completed=${isCompleted ? "true" : nothing}
2476
+ aria-current=${isCurrent ? "step" : nothing}
2477
+ style=${styleMap(btnStyles)}
2478
+ @click=${() => this._handleStepClick(tourId, step.id)}
2479
+ >
2480
+ <span style=${styleMap(indicatorWrapStyles)}>${indicator}</span>
2481
+ <span style=${styleMap(labelStyles)}>${step.title}</span>
2482
+ </button>
2483
+ `;
2484
+ }
2485
+ _renderWorkflowCard(workflow) {
2486
+ const completedCount = workflow.completedSteps.length;
2487
+ const totalSteps = workflow.steps.length;
2488
+ const cardStyles = {
2489
+ padding: "12px",
2490
+ borderRadius: "8px",
2491
+ border: "1px solid rgba(255,255,255,0.08)",
2492
+ background: "rgba(255,255,255,0.02)"
2493
+ };
2494
+ const headerStyles = {
2495
+ display: "flex",
2496
+ alignItems: "center",
2497
+ gap: "8px",
2498
+ marginBottom: "8px"
2499
+ };
2500
+ const titleStyles = {
2501
+ flex: "1",
2502
+ fontSize: "13px",
2503
+ fontWeight: "600",
2504
+ color: `var(--se-color-text-primary, ${TOKEN_SLATE_12})`,
2505
+ overflow: "hidden",
2506
+ textOverflow: "ellipsis",
2507
+ whiteSpace: "nowrap"
2508
+ };
2509
+ const dismissBtnStyles = {
2510
+ flexShrink: "0",
2511
+ padding: "2px 6px",
2512
+ border: "none",
2513
+ borderRadius: "4px",
2514
+ background: "transparent",
2515
+ color: `var(--se-color-text-tertiary, ${TOKEN_SLATE_7})`,
2516
+ fontSize: "12px",
2517
+ cursor: "pointer",
2518
+ lineHeight: "1"
2519
+ };
2520
+ const progressWrapStyles = {
2521
+ marginBottom: "8px"
2522
+ };
2523
+ const progressLabelStyles = {
2524
+ fontSize: "10px",
2525
+ color: `var(--se-color-text-tertiary, ${TOKEN_SLATE_7})`,
2526
+ marginTop: "4px"
2527
+ };
2528
+ const stepsColStyles = {
2529
+ display: "flex",
2530
+ flexDirection: "column"
2531
+ };
2532
+ return html`
2533
+ <div style=${styleMap(cardStyles)}>
2534
+ <!-- Header: icon + title + dismiss -->
2535
+ <div style=${styleMap(headerStyles)}>
2536
+ ${workflow.meta.icon ? html`<span data-testid="workflow-icon" style="flex-shrink:0;font-size:14px">${workflow.meta.icon}</span>` : nothing}
2537
+ <span style=${styleMap(titleStyles)}>${workflow.meta.title}</span>
2538
+ <button
2539
+ type="button"
2540
+ data-testid=${`dismiss-${workflow.tourId}`}
2541
+ style=${styleMap(dismissBtnStyles)}
2542
+ aria-label=${`Dismiss ${workflow.meta.title}`}
2543
+ @click=${() => this._handleDismiss(workflow.tourId)}
2544
+ >&#10005;</button>
2545
+ </div>
2546
+
2547
+ <!-- Progress bar + label -->
2548
+ <div style=${styleMap(progressWrapStyles)}>
2549
+ ${this._renderProgressBar(completedCount, totalSteps)}
2550
+ <div style=${styleMap(progressLabelStyles)}>
2551
+ ${completedCount} of ${totalSteps} steps
2552
+ </div>
2553
+ </div>
2554
+
2555
+ <!-- Step list -->
2556
+ <div style=${styleMap(stepsColStyles)}>
2557
+ ${workflow.steps.map(
2558
+ (step) => this._renderStepItem(
2559
+ step,
2560
+ workflow.completedSteps.includes(step.id),
2561
+ workflow.currentStepId === step.id,
2562
+ workflow.tourId
2563
+ )
2564
+ )}
2565
+ </div>
2566
+ </div>
2567
+ `;
2568
+ }
2569
+ // ── Render ────────────────────────────────────────────────────────────────
2570
+ render() {
2571
+ const activeWorkflows = this._workflowEntries.filter((w) => w.status === "active");
2572
+ if (activeWorkflows.length === 0) {
2573
+ const emptyStyles = {
2574
+ display: "flex",
2575
+ alignItems: "center",
2576
+ justifyContent: "center",
2577
+ padding: "24px 0",
2578
+ fontSize: "12px",
2579
+ color: `var(--se-color-text-tertiary, ${TOKEN_SLATE_7})`
2580
+ };
2581
+ return html`<div style=${styleMap(emptyStyles)}>No active workflows</div>`;
2582
+ }
2583
+ const containerStyles = {
2584
+ display: "flex",
2585
+ flexDirection: "column",
2586
+ gap: "8px"
2587
+ };
2588
+ return html`
2589
+ <div style=${styleMap(containerStyles)}>
2590
+ ${activeWorkflows.map((workflow) => this._renderWorkflowCard(workflow))}
2591
+ </div>
2592
+ `;
2593
+ }
2594
+ };
2595
+ _unsubTourStarted = new WeakMap();
2596
+ _unsubTourEvents = new WeakMap();
2597
+ _toastCleanups = new WeakMap();
2598
+ _scanGeneration = new WeakMap();
2599
+ _notified = new WeakMap();
2600
+ _completedMap = new WeakMap();
2601
+ _persistInitialized = new WeakMap();
2602
+ _tourWorkflows = new WeakMap();
2603
+ // ── Static properties (no decorators) ────────────────────────────────────
2604
+ WorkflowTrackerLit.properties = {
2605
+ // Public input: runtime ref injected by MountableWidget
2606
+ runtimeRef: { attribute: false },
2607
+ // Internal reactive state
2608
+ _workflowEntries: { state: true },
2609
+ _actionVersion: { state: true }
2610
+ };
2611
+ if (typeof window !== "undefined" && !customElements.get(TAG_NAME)) {
2612
+ customElements.define(TAG_NAME, WorkflowTrackerLit);
2613
+ }
2614
+ var WorkflowWidgetLitMountable = {
2615
+ mount(container, config) {
2616
+ const runtime2 = config?.runtime ?? null;
2617
+ if (typeof window !== "undefined" && !customElements.get(TAG_NAME)) {
2618
+ customElements.define(TAG_NAME, WorkflowTrackerLit);
2619
+ }
2620
+ const el = document.createElement(TAG_NAME);
2621
+ el.runtimeRef = runtime2;
2622
+ container.appendChild(el);
2623
+ return () => {
2624
+ el.remove();
2625
+ };
2626
+ },
2627
+ update(container, config) {
2628
+ const el = container.querySelector(TAG_NAME);
2629
+ if (!el) return;
2630
+ const runtime2 = config?.runtime ?? null;
2631
+ el.runtimeRef = runtime2;
2632
+ }
2633
+ };
2634
+
2635
+ // src/runtime.ts
2636
+ var executeHighlight = async (action, context) => {
2637
+ let anchorEl = context.resolveAnchor(action.anchorId);
2638
+ if (!anchorEl && context.waitForAnchor) {
2639
+ anchorEl = await context.waitForAnchor(action.anchorId, 3e3);
2640
+ }
2641
+ if (!anchorEl) {
2642
+ console.warn(`[adaptive-overlays] Anchor not found after waiting: ${action.anchorId.selector}`);
2643
+ return { cleanup: () => {
2644
+ } };
2645
+ }
2646
+ if (anchorEl.getAttribute("data-syntro-highlight-dismissed")) {
2647
+ return { cleanup: () => {
2648
+ } };
2649
+ }
2650
+ const existing = anchorEl.getAttribute("data-syntro-highlight");
2651
+ if (existing) {
2652
+ const prev = context.overlayRoot.querySelectorAll(
2653
+ ".syntro-spotlight-scrim, .syntro-spotlight-ring"
2654
+ );
2655
+ prev.forEach((el) => el.remove());
2656
+ }
2657
+ anchorEl.setAttribute("data-syntro-highlight", "true");
2658
+ let ringColor = action.style?.color;
2659
+ if (!ringColor) {
2660
+ try {
2661
+ const primary = getComputedStyle(context.overlayRoot).getPropertyValue("--sc-color-primary")?.trim();
2662
+ if (primary) ringColor = primary;
2663
+ } catch {
2664
+ }
2665
+ }
2666
+ const handle = showHighlight(anchorEl, context.overlayRoot, {
2667
+ paddingPx: action.style?.paddingPx ?? 12,
2668
+ radiusPx: action.style?.radiusPx ?? 12,
2669
+ scrimOpacity: action.style?.scrimOpacity ?? 0.55,
2670
+ ringColor,
2671
+ blocking: action.blocking ?? false,
2672
+ onClickOutside: action.onClickOutside ?? true,
2673
+ onEsc: action.onEsc ?? true,
2674
+ onDismiss: () => {
2675
+ anchorEl.setAttribute("data-syntro-highlight-dismissed", "true");
2676
+ }
2677
+ });
2678
+ context.publishEvent("action.applied", {
2679
+ id: context.generateId(),
2680
+ kind: "overlays:highlight",
2681
+ anchorId: action.anchorId
2682
+ });
2683
+ return {
2684
+ cleanup: () => {
2685
+ handle.destroy();
2686
+ anchorEl.removeAttribute("data-syntro-highlight");
2687
+ anchorEl.removeAttribute("data-syntro-highlight-dismissed");
2688
+ }
2689
+ };
2690
+ };
2691
+ var executePulse = async (action, context) => {
2692
+ let anchorEl = context.resolveAnchor(action.anchorId);
2693
+ if (!anchorEl && context.waitForAnchor) {
2694
+ anchorEl = await context.waitForAnchor(action.anchorId, 3e3);
2695
+ }
2696
+ if (!anchorEl) {
2697
+ console.warn(`[adaptive-overlays] Anchor not found after waiting: ${action.anchorId.selector}`);
2698
+ return { cleanup: () => {
2699
+ } };
2700
+ }
2701
+ const duration = action.duration ?? 4e3;
2702
+ await new Promise((resolve) => requestAnimationFrame(resolve));
2703
+ const parseHex = (hex) => ({
2704
+ r: parseInt(hex.slice(1, 3), 16),
2705
+ g: parseInt(hex.slice(3, 5), 16),
2706
+ b: parseInt(hex.slice(5, 7), 16)
2707
+ });
2708
+ const fallback = { r: 79, g: 70, b: 229 };
2709
+ let primary = fallback;
2710
+ let secondary = null;
2711
+ try {
2712
+ const styles = getComputedStyle(context.overlayRoot);
2713
+ const pHex = styles.getPropertyValue("--sc-color-primary")?.trim();
2714
+ const sHex = styles.getPropertyValue("--sc-color-primary-hover")?.trim();
2715
+ if (pHex?.startsWith("#") && pHex.length >= 7) {
2716
+ primary = parseHex(pHex);
2717
+ }
2718
+ if (sHex?.startsWith("#") && sHex.length >= 7) {
2719
+ secondary = parseHex(sHex);
2720
+ }
2721
+ } catch {
2722
+ }
2723
+ const existing = document.querySelector("[data-syntro-pulse-styles]");
2724
+ if (existing) existing.remove();
2725
+ const style = document.createElement("style");
2726
+ style.setAttribute("data-syntro-pulse-styles", "");
2727
+ const { r: pr, g: pg, b: pb } = primary;
2728
+ if (secondary) {
2729
+ const { r: sr, g: sg, b: sb } = secondary;
2730
+ style.textContent = `
2731
+ @keyframes syntro-pulse-anim {
2732
+ 0%, 100% {
2733
+ box-shadow: 0 0 0 0 rgba(${pr}, ${pg}, ${pb}, 0.35);
2734
+ }
2735
+ 25% {
2736
+ box-shadow: 0 0 0 12px rgba(${pr}, ${pg}, ${pb}, 0);
2737
+ }
2738
+ 50% {
2739
+ box-shadow: 0 0 0 0 rgba(${sr}, ${sg}, ${sb}, 0.35);
2740
+ }
2741
+ 75% {
2742
+ box-shadow: 0 0 0 12px rgba(${sr}, ${sg}, ${sb}, 0);
2743
+ }
2744
+ }
2745
+ `;
2746
+ } else {
2747
+ style.textContent = `
2748
+ @keyframes syntro-pulse-anim {
2749
+ 0%, 100% {
2750
+ box-shadow: 0 0 0 0 rgba(${pr}, ${pg}, ${pb}, 0.35);
2751
+ }
2752
+ 50% {
2753
+ box-shadow: 0 0 0 12px rgba(${pr}, ${pg}, ${pb}, 0);
2754
+ }
2755
+ }
2756
+ `;
2757
+ }
2758
+ document.head.appendChild(style);
2759
+ const originalAnimation = anchorEl.style.animation;
2760
+ anchorEl.style.animation = "syntro-pulse-anim 2.5s cubic-bezier(0.4, 0, 0.6, 1) infinite";
2761
+ anchorEl.setAttribute("data-syntro-pulse", "true");
2762
+ const timeoutId = setTimeout(() => {
2763
+ anchorEl.style.animation = originalAnimation;
2764
+ anchorEl.removeAttribute("data-syntro-pulse");
2765
+ }, duration);
2766
+ context.publishEvent("action.applied", {
2767
+ id: context.generateId(),
2768
+ kind: "overlays:pulse",
2769
+ anchorId: action.anchorId,
2770
+ duration
2771
+ });
2772
+ return {
2773
+ cleanup: () => {
2774
+ clearTimeout(timeoutId);
2775
+ if (!anchorEl.isConnected) return;
2776
+ anchorEl.style.animation = originalAnimation;
2777
+ anchorEl.removeAttribute("data-syntro-pulse");
2778
+ }
2779
+ };
2780
+ };
2781
+ var executeBadge = async (action, context) => {
2782
+ let anchorEl = context.resolveAnchor(action.anchorId);
2783
+ if (!anchorEl && context.waitForAnchor) {
2784
+ anchorEl = await context.waitForAnchor(action.anchorId, 3e3);
2785
+ }
2786
+ if (!anchorEl) {
2787
+ console.warn(`[adaptive-overlays] Anchor not found after waiting: ${action.anchorId.selector}`);
2788
+ return { cleanup: () => {
2789
+ } };
2790
+ }
2791
+ let badgeColor = "#4f46e5";
2792
+ try {
2793
+ const primary = getComputedStyle(context.overlayRoot).getPropertyValue("--sc-color-primary")?.trim();
2794
+ if (primary?.startsWith("#") && primary.length >= 7) {
2795
+ badgeColor = primary;
2796
+ }
2797
+ } catch {
2798
+ }
2799
+ const badge2 = document.createElement("div");
2800
+ badge2.textContent = action.content;
2801
+ badge2.setAttribute("data-syntro-badge", action.anchorId.selector);
2802
+ Object.assign(badge2.style, {
2803
+ position: "absolute",
2804
+ padding: "2px 6px",
2805
+ fontSize: "12px",
2806
+ fontWeight: "600",
2807
+ lineHeight: "1",
2808
+ color: "white",
2809
+ background: badgeColor,
2810
+ borderRadius: "9999px",
2811
+ pointerEvents: "none",
2812
+ zIndex: "2147483646",
2813
+ whiteSpace: "nowrap"
2814
+ });
2815
+ const position = action.position ?? "top-right";
2816
+ const originalPosition = anchorEl.style.position;
2817
+ if (getComputedStyle(anchorEl).position === "static") {
2818
+ anchorEl.style.position = "relative";
2819
+ }
2820
+ anchorEl.appendChild(badge2);
2821
+ switch (position) {
2822
+ case "top-left":
2823
+ Object.assign(badge2.style, { top: "-8px", left: "-8px" });
2824
+ break;
2825
+ case "top-right":
2826
+ Object.assign(badge2.style, { top: "-8px", right: "-8px" });
2827
+ break;
2828
+ case "bottom-left":
2829
+ Object.assign(badge2.style, { bottom: "-8px", left: "-8px" });
2830
+ break;
2831
+ case "bottom-right":
2832
+ Object.assign(badge2.style, { bottom: "-8px", right: "-8px" });
2833
+ break;
2834
+ }
2835
+ context.publishEvent("action.applied", {
2836
+ id: context.generateId(),
2837
+ kind: "overlays:badge",
2838
+ anchorId: action.anchorId,
2839
+ content: action.content,
2840
+ position
2841
+ });
2842
+ return {
2843
+ cleanup: () => {
2844
+ try {
2845
+ badge2.remove();
2846
+ } catch {
2847
+ }
2848
+ if (!anchorEl.isConnected) return;
2849
+ if (originalPosition !== void 0) {
2850
+ anchorEl.style.position = originalPosition;
2851
+ }
2852
+ },
2853
+ updateFn: (changes) => {
2854
+ if ("content" in changes && typeof changes.content === "string") {
2855
+ badge2.textContent = changes.content;
2856
+ }
2857
+ }
2858
+ };
2859
+ };
2860
+ var executeTooltip = async (action, context) => {
2861
+ let anchorEl = context.resolveAnchor(action.anchorId);
2862
+ if (!anchorEl && context.waitForAnchor) {
2863
+ anchorEl = await context.waitForAnchor(action.anchorId, 3e3);
2864
+ }
2865
+ if (!anchorEl) {
2866
+ console.warn(`[adaptive-overlays] Anchor not found after waiting: ${action.anchorId.selector}`);
2867
+ return { cleanup: () => {
2868
+ } };
2869
+ }
2870
+ const { content } = action;
2871
+ let html2 = "";
2872
+ if (content.title) {
2873
+ html2 += `<div class="syntro-tt-title">${sanitizeHtml(content.title)}</div>`;
2874
+ }
2875
+ html2 += `<div class="syntro-tt-body">${sanitizeHtml(content.body)}</div>`;
2876
+ if (content.ctaButtons && content.ctaButtons.length > 0) {
2877
+ html2 += `<div class="syntro-tt-actions">`;
2878
+ for (const btn of content.ctaButtons) {
2879
+ const isPrimary = btn.primary ?? false;
2880
+ html2 += `
2881
+ <button
2882
+ class="syntro-tt-btn ${isPrimary ? "syntro-tt-btn-primary" : ""}"
2883
+ data-syntro-action="${sanitizeHtml(btn.actionId)}"
2884
+ >
2885
+ ${sanitizeHtml(btn.label)}
2886
+ </button>
2887
+ `;
2888
+ }
2889
+ html2 += `</div>`;
2890
+ } else if (content.cta) {
2891
+ html2 += `<div class="syntro-tt-actions">
2892
+ <button class="syntro-tt-btn syntro-tt-btn-primary" data-syntro-action="cta">
2893
+ ${sanitizeHtml(content.cta.label)}
2894
+ </button>
2895
+ </div>`;
2896
+ }
2897
+ const handle = showTooltip(anchorEl, context.overlayRoot, {
2898
+ html: html2,
2899
+ placement: action.placement ?? "top",
2900
+ trigger: action.trigger ?? "immediate",
2901
+ onAction: (actionId) => {
2902
+ if (actionId === "dismiss") {
2903
+ handle.destroy();
2904
+ return;
2905
+ }
2906
+ if (actionId === "cta" && content.cta) {
2907
+ context.publishEvent("action.cta_clicked", {
2908
+ anchorId: action.anchorId,
2909
+ ctaLabel: content.cta.label
2910
+ });
2911
+ } else if (content.ctaButtons) {
2912
+ const clickedBtn = content.ctaButtons.find((b) => b.actionId === actionId);
2913
+ if (clickedBtn) {
2914
+ context.publishEvent("action.tooltip_cta_clicked", {
2915
+ anchorId: action.anchorId,
2916
+ actionId,
2917
+ label: clickedBtn.label,
2918
+ ...clickedBtn.href ? { href: clickedBtn.href } : {}
2919
+ });
2920
+ handle.destroy();
2921
+ navigateForCta(clickedBtn);
2922
+ return;
2923
+ }
2924
+ }
2925
+ handle.destroy();
2926
+ }
2927
+ });
2928
+ context.publishEvent("action.applied", {
2929
+ id: context.generateId(),
2930
+ kind: "overlays:tooltip",
2931
+ anchorId: action.anchorId,
2932
+ trigger: action.trigger ?? "immediate"
2933
+ });
2934
+ return {
2935
+ cleanup: () => {
2936
+ handle.destroy();
2937
+ }
2938
+ };
2939
+ };
2940
+ var executors = [
2941
+ { kind: "overlays:highlight", executor: executeHighlight },
2942
+ { kind: "overlays:pulse", executor: executePulse },
2943
+ { kind: "overlays:badge", executor: executeBadge },
2944
+ { kind: "overlays:tooltip", executor: executeTooltip },
2945
+ { kind: "overlays:modal", executor: executeModal },
2946
+ { kind: "overlays:tour", executor: executeTour },
2947
+ { kind: "overlays:celebrate", executor: executeCelebrate }
2948
+ ];
2949
+ var runtime = {
2950
+ id: "adaptive-overlays",
2951
+ version: "1.0.0",
2952
+ name: "Overlays",
2953
+ description: "Tooltips, highlights, badges, modals, celebrations, visual overlays, and workflow tracking",
2954
+ executors,
2955
+ widgets: [
2956
+ {
2957
+ id: "adaptive-overlays:workflow-tracker",
2958
+ component: WorkflowWidgetLitMountable,
2959
+ metadata: {
2960
+ name: "Workflow Tracker",
2961
+ icon: "\u{1F4CB}",
2962
+ description: "Tracks multi-step workflow progress across tours"
2963
+ }
2964
+ }
2965
+ ]
2966
+ };
2967
+
2968
+ export {
2969
+ executeTour,
2970
+ executeModal,
2971
+ executeHighlight,
2972
+ executePulse,
2973
+ executeBadge,
2974
+ executeTooltip,
2975
+ executors,
2976
+ runtime
2977
+ };
2978
+ //# sourceMappingURL=chunk-4ANURPES.js.map